create-obsidian-arrow 0.2.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -7
- package/index.mjs +29 -10
- package/package.json +1 -1
- package/template/AGENTS.md +31 -5
- package/template/README.md +47 -5
- package/template/_gitignore +3 -0
- package/template/docs/prompts/agent-setup.md +26 -13
- package/template/docs/prompts/update-existing.md +13 -9
- package/template/docs/workflow.md +21 -8
- package/template/package.json +6 -2
- package/template/pnpm-lock.yaml +197 -0
- package/template/src/components/DiffViewer.ts +42 -0
- package/template/src/components/SettingsPanel.ts +1 -1
- package/template/src/main.ts +4 -3
- package/template/src/utilities.css +205 -0
- package/template/stories/DiffViewer.stories.ts +75 -0
- package/template/stories/SettingsPanel.stories.ts +11 -0
- package/template/stories/Toggle.stories.ts +28 -0
- package/template/test/token-utils.test.mjs +65 -0
- package/template/test/viewer-derive.test.mjs +65 -0
- package/template/test/viewer-stories.test.mjs +44 -0
- package/template/{src → tools}/router/client.ts +15 -2
- package/template/tools/router/routeToPage.ts +104 -0
- package/template/{src → tools}/sandbox/home.ts +55 -26
- package/template/tools/sandbox/sandbox.css +474 -0
- package/template/tools/viewer/ClassesPage.ts +37 -0
- package/template/tools/viewer/ComponentsIndex.ts +56 -0
- package/template/tools/viewer/StoryPage.ts +73 -0
- package/template/tools/viewer/TokensPage.ts +82 -0
- package/template/tools/viewer/derive.ts +91 -0
- package/template/tools/viewer/discovery.ts +64 -0
- package/template/tools/viewer/obsidian-classes.ts +269 -0
- package/template/tools/viewer/sidebar.ts +55 -0
- package/template/tools/viewer/stories.ts +83 -0
- package/template/tools/viewer/token-utils.ts +84 -0
- package/template/tools/viewer/tokens.ts +30 -0
- package/template/src/examples/ExamplesIndex.ts +0 -36
- package/template/src/examples/registry.ts +0 -26
- package/template/src/router/routeToPage.ts +0 -57
- package/template/src/sandbox/sandbox.css +0 -130
- /package/template/{src → tools}/sandbox/frame.ts +0 -0
- /package/template/{src → tools}/sandbox/layout.ts +0 -0
- /package/template/{src → tools}/sandbox/shell.ts +0 -0
- /package/template/{src → tools}/sandbox/theme.ts +0 -0
- /package/template/{src → tools}/sandbox/toolbar.ts +0 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure token parsing/grouping for the reference index. The browser side feeds
|
|
3
|
+
* this CSSOM rule text (rule.cssText); keeping the parser string-in/data-out
|
|
4
|
+
* makes it node:test-able without a DOM.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface TokenDecl {
|
|
8
|
+
name: string;
|
|
9
|
+
value: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function parseCustomProps(cssText: string): TokenDecl[] {
|
|
13
|
+
const out: TokenDecl[] = [];
|
|
14
|
+
const re = /(--[A-Za-z0-9_-]+)\s*:\s*([^;}]+)/g;
|
|
15
|
+
let match = re.exec(cssText);
|
|
16
|
+
while (match !== null) {
|
|
17
|
+
out.push({ name: match[1], value: match[2].trim() });
|
|
18
|
+
match = re.exec(cssText);
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const GROUP_PREFIXES: [string, string][] = [
|
|
24
|
+
["--size-", "Size & spacing"],
|
|
25
|
+
["--radius-", "Radius"],
|
|
26
|
+
["--color-", "Colors"],
|
|
27
|
+
["--background-", "Backgrounds"],
|
|
28
|
+
["--text-", "Text"],
|
|
29
|
+
["--font-", "Fonts & type"],
|
|
30
|
+
["--shadow-", "Shadows"],
|
|
31
|
+
["--interactive-", "Interactive"],
|
|
32
|
+
["--icon-", "Icons"],
|
|
33
|
+
];
|
|
34
|
+
const OTHER_LABEL = "Other";
|
|
35
|
+
|
|
36
|
+
export interface TokenGroup {
|
|
37
|
+
label: string;
|
|
38
|
+
tokens: TokenDecl[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function groupTokens(decls: TokenDecl[]): TokenGroup[] {
|
|
42
|
+
const latest = new Map<string, string>();
|
|
43
|
+
for (const decl of decls) {
|
|
44
|
+
latest.set(decl.name, decl.value);
|
|
45
|
+
}
|
|
46
|
+
const buckets = new Map<string, TokenDecl[]>();
|
|
47
|
+
for (const [name, value] of latest) {
|
|
48
|
+
const label = GROUP_PREFIXES.find(([prefix]) => name.startsWith(prefix))?.[1] ?? OTHER_LABEL;
|
|
49
|
+
const bucket = buckets.get(label) ?? [];
|
|
50
|
+
bucket.push({ name, value });
|
|
51
|
+
buckets.set(label, bucket);
|
|
52
|
+
}
|
|
53
|
+
const order = [...GROUP_PREFIXES.map(([, label]) => label), OTHER_LABEL];
|
|
54
|
+
return order
|
|
55
|
+
.filter((label) => buckets.has(label))
|
|
56
|
+
.map((label) => ({
|
|
57
|
+
label,
|
|
58
|
+
tokens: (buckets.get(label) ?? []).sort((a, b) => a.name.localeCompare(b.name)),
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type ValueKind = "color" | "length" | "other";
|
|
63
|
+
|
|
64
|
+
export function classifyValue(resolved: string): ValueKind {
|
|
65
|
+
const value = resolved.trim();
|
|
66
|
+
if (
|
|
67
|
+
/^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(value) ||
|
|
68
|
+
/^(rgb|rgba|hsl|hsla)\(/i.test(value)
|
|
69
|
+
) {
|
|
70
|
+
return "color";
|
|
71
|
+
}
|
|
72
|
+
if (value === "0" || /^-?\d+(\.\d+)?(px|em|rem|%|ch|vw|vh|vmin|vmax|pt)$/.test(value)) {
|
|
73
|
+
return "length";
|
|
74
|
+
}
|
|
75
|
+
return "other";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function filterTokens(decls: TokenDecl[], query: string): TokenDecl[] {
|
|
79
|
+
const q = query.trim().toLowerCase();
|
|
80
|
+
if (q === "") {
|
|
81
|
+
return decls;
|
|
82
|
+
}
|
|
83
|
+
return decls.filter((decl) => decl.name.toLowerCase().includes(q));
|
|
84
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { TokenDecl } from "./token-utils";
|
|
2
|
+
import { parseCustomProps } from "./token-utils";
|
|
3
|
+
|
|
4
|
+
/** Walk every same-origin stylesheet (recursing into @media etc.) and parse
|
|
5
|
+
* custom-property declarations out of each style rule's cssText. */
|
|
6
|
+
export function collectTokenDecls(): TokenDecl[] {
|
|
7
|
+
const out: TokenDecl[] = [];
|
|
8
|
+
const walk = (rules: CSSRuleList): void => {
|
|
9
|
+
for (const rule of Array.from(rules)) {
|
|
10
|
+
if (rule instanceof CSSStyleRule) {
|
|
11
|
+
out.push(...parseCustomProps(rule.cssText));
|
|
12
|
+
} else if (rule instanceof CSSGroupingRule) {
|
|
13
|
+
walk(rule.cssRules);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
for (const sheet of Array.from(document.styleSheets)) {
|
|
18
|
+
try {
|
|
19
|
+
walk(sheet.cssRules);
|
|
20
|
+
} catch {
|
|
21
|
+
// cross-origin sheet — skip
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Resolved value of a token in the CURRENT theme. */
|
|
28
|
+
export function resolveToken(name: string): string {
|
|
29
|
+
return getComputedStyle(document.body).getPropertyValue(name).trim();
|
|
30
|
+
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { html } from "@arrow-js/core";
|
|
2
|
-
import type { ArrowTemplate } from "@arrow-js/core";
|
|
3
|
-
import type { Example } from "./registry";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Landing page at "/" — an Obsidian-styled list of the available example
|
|
7
|
-
* components, each linking to its own route. Plain anchors (full reloads) keep
|
|
8
|
-
* the router trivial; the sandbox doesn't need SPA navigation.
|
|
9
|
-
*/
|
|
10
|
-
export const ExamplesIndex = (items: Example[]): ArrowTemplate => html`
|
|
11
|
-
<div class="oas-settings">
|
|
12
|
-
<div class="setting-item setting-item-heading">
|
|
13
|
-
<div class="setting-item-info">
|
|
14
|
-
<div class="setting-item-name">Examples</div>
|
|
15
|
-
<div class="setting-item-description">
|
|
16
|
-
Component demos rendered with real Obsidian styling.
|
|
17
|
-
</div>
|
|
18
|
-
</div>
|
|
19
|
-
</div>
|
|
20
|
-
${items.map((example) =>
|
|
21
|
-
html`
|
|
22
|
-
<div class="setting-item">
|
|
23
|
-
<div class="setting-item-info">
|
|
24
|
-
<div class="setting-item-name">
|
|
25
|
-
<a href="${example.path}">${example.label}</a>
|
|
26
|
-
</div>
|
|
27
|
-
<div class="setting-item-description">${example.description}</div>
|
|
28
|
-
</div>
|
|
29
|
-
<div class="setting-item-control">
|
|
30
|
-
<a class="mod-cta oas-open-link" href="${example.path}">Open</a>
|
|
31
|
-
</div>
|
|
32
|
-
</div>
|
|
33
|
-
`.key(example.path)
|
|
34
|
-
)}
|
|
35
|
-
</div>
|
|
36
|
-
`;
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { ArrowExpression } from "@arrow-js/core";
|
|
2
|
-
import { SettingsPanel } from "../components/SettingsPanel";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Registry of example components, keyed by route path. Add a new demo here and
|
|
6
|
-
* it shows up on the index page and at its own path automatically.
|
|
7
|
-
*/
|
|
8
|
-
export interface Example {
|
|
9
|
-
path: string;
|
|
10
|
-
label: string;
|
|
11
|
-
description: string;
|
|
12
|
-
view: () => ArrowExpression;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const examples: Example[] = [
|
|
16
|
-
{
|
|
17
|
-
path: "/example",
|
|
18
|
-
label: "Settings panel",
|
|
19
|
-
description: "Vertical tabs, toggles, a keyed list, and an async boundary() section.",
|
|
20
|
-
view: () => SettingsPanel(),
|
|
21
|
-
},
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
export function findExample(path: string): Example | undefined {
|
|
25
|
-
return examples.find((example) => example.path === path);
|
|
26
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { html } from "@arrow-js/core";
|
|
2
|
-
import type { ArrowExpression } from "@arrow-js/core";
|
|
3
|
-
import { findExample } from "../examples/registry";
|
|
4
|
-
import { Home } from "../sandbox/home";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Single route resolver, shared by every entry point. Returns the page status,
|
|
8
|
-
* title (metadata), and Arrow view together — the same shape the Arrow Vite
|
|
9
|
-
* scaffold uses, so a future SSR/hydration lane can call this identically on
|
|
10
|
-
* both server and client. The client router (./client.ts) wraps the view in the
|
|
11
|
-
* sandbox Frame and sets document.title from this.
|
|
12
|
-
*/
|
|
13
|
-
export interface Page {
|
|
14
|
-
status: number;
|
|
15
|
-
title: string;
|
|
16
|
-
view: ArrowExpression;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const APP_NAME = "Arrow Sandbox";
|
|
20
|
-
|
|
21
|
-
export function routeToPage(url: string): Page {
|
|
22
|
-
const { pathname } = new URL(url, window.location.origin);
|
|
23
|
-
|
|
24
|
-
if (pathname === "/" || pathname === "") {
|
|
25
|
-
return {
|
|
26
|
-
status: 200,
|
|
27
|
-
title: APP_NAME,
|
|
28
|
-
view: Home(),
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const match = findExample(pathname);
|
|
33
|
-
if (match) {
|
|
34
|
-
return {
|
|
35
|
-
status: 200,
|
|
36
|
-
title: `${match.label} · ${APP_NAME}`,
|
|
37
|
-
view: match.view(),
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
status: 404,
|
|
43
|
-
title: `Not found · ${APP_NAME}`,
|
|
44
|
-
view: html`
|
|
45
|
-
<div class="oas-settings">
|
|
46
|
-
<div class="setting-item setting-item-heading">
|
|
47
|
-
<div class="setting-item-info">
|
|
48
|
-
<div class="setting-item-name">Not found</div>
|
|
49
|
-
<div class="setting-item-description">
|
|
50
|
-
No route for <code>${pathname}</code>. <a href="/">Back to examples</a>.
|
|
51
|
-
</div>
|
|
52
|
-
</div>
|
|
53
|
-
</div>
|
|
54
|
-
</div>
|
|
55
|
-
`,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Sandbox-only chrome. NOT ported into the plugin.
|
|
3
|
-
*
|
|
4
|
-
* Per the arrow-js-obsidian CSS-specificity lesson, every custom rule is scoped
|
|
5
|
-
* under a container class + element type so it can't lose to Obsidian's global
|
|
6
|
-
* rules (e.g. `button:not(.clickable-icon)`) and can't leak. All values use
|
|
7
|
-
* Obsidian tokens so the sandbox tracks the active theme.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
html,
|
|
11
|
-
body {
|
|
12
|
-
margin: 0;
|
|
13
|
-
height: 100%;
|
|
14
|
-
background: var(--background-primary);
|
|
15
|
-
color: var(--text-normal);
|
|
16
|
-
font-family: var(--font-interface);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
#app {
|
|
20
|
-
height: 100vh;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/* Shell: width-control toolbar on top, pane stage below. */
|
|
24
|
-
.oas-shell {
|
|
25
|
-
display: flex;
|
|
26
|
-
flex-direction: column;
|
|
27
|
-
height: 100vh;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
.oas-toolbar {
|
|
31
|
-
display: flex;
|
|
32
|
-
align-items: center;
|
|
33
|
-
gap: var(--size-4-2);
|
|
34
|
-
padding: var(--size-4-1) var(--size-4-3);
|
|
35
|
-
background: var(--background-secondary);
|
|
36
|
-
border-bottom: 1px solid var(--background-modifier-border);
|
|
37
|
-
color: var(--text-muted);
|
|
38
|
-
font-size: var(--font-ui-small);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
.oas-toolbar .oas-toolbar-label {
|
|
42
|
-
font-weight: var(--font-medium);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
.oas-toolbar .oas-width-range {
|
|
46
|
-
flex: 0 1 220px;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.oas-toolbar .oas-width-readout {
|
|
50
|
-
min-width: 52px;
|
|
51
|
-
font-family: var(--font-monospace);
|
|
52
|
-
color: var(--text-normal);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.oas-toolbar button.oas-preset {
|
|
56
|
-
height: var(--size-4-5);
|
|
57
|
-
padding: 0 var(--size-4-2);
|
|
58
|
-
font-size: var(--font-ui-smaller);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/* Stage: left-aligned so the edge drag handle resizes width 1:1. */
|
|
62
|
-
.oas-stage {
|
|
63
|
-
flex: 1;
|
|
64
|
-
display: flex;
|
|
65
|
-
justify-content: flex-start;
|
|
66
|
-
overflow: hidden;
|
|
67
|
-
background: var(--background-secondary);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
.oas-frame.workspace-leaf {
|
|
71
|
-
position: relative;
|
|
72
|
-
flex: 0 0 auto;
|
|
73
|
-
height: 100%;
|
|
74
|
-
min-width: 240px;
|
|
75
|
-
display: flex;
|
|
76
|
-
flex-direction: column;
|
|
77
|
-
background: var(--background-primary);
|
|
78
|
-
border-right: 1px solid var(--background-modifier-border);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
.oas-frame .oas-view-content {
|
|
82
|
-
flex: 1;
|
|
83
|
-
overflow-y: auto;
|
|
84
|
-
padding: var(--size-4-3);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
.oas-frame .view-header {
|
|
88
|
-
display: flex;
|
|
89
|
-
align-items: center;
|
|
90
|
-
justify-content: space-between;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
.oas-frame .oas-view-header-left {
|
|
94
|
-
display: flex;
|
|
95
|
-
align-items: center;
|
|
96
|
-
gap: var(--size-4-2);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.oas-frame .view-header .oas-theme-toggle,
|
|
100
|
-
.oas-frame .view-header .oas-home {
|
|
101
|
-
font-size: var(--font-ui-medium);
|
|
102
|
-
line-height: 1;
|
|
103
|
-
text-decoration: none;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
.oas-frame a.oas-open-link {
|
|
107
|
-
text-decoration: none;
|
|
108
|
-
display: inline-flex;
|
|
109
|
-
align-items: center;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
.oas-frame .setting-item-control button.oas-recheck {
|
|
113
|
-
margin-left: var(--size-4-2);
|
|
114
|
-
font-size: var(--font-ui-smaller);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/* Drag-to-resize handle on the pane's right edge (Obsidian-like). */
|
|
118
|
-
.oas-frame .oas-resize-handle {
|
|
119
|
-
position: absolute;
|
|
120
|
-
top: 0;
|
|
121
|
-
right: 0;
|
|
122
|
-
width: 6px;
|
|
123
|
-
height: 100%;
|
|
124
|
-
cursor: ew-resize;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
.oas-frame .oas-resize-handle:hover {
|
|
128
|
-
background: var(--interactive-accent);
|
|
129
|
-
opacity: 0.5;
|
|
130
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|