@tinyvolt/pie 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.nvmrc +1 -0
- package/LICENSE +21 -0
- package/README.md +68 -2
- package/functions/midpoint.ts +29 -0
- package/functions/square.ts +20 -0
- package/init/index.js +87 -0
- package/init/site-agents.md +257 -0
- package/init/site-gitignore +28 -0
- package/init/site-tsconfig.json +20 -0
- package/init/site-vite.config.ts +6 -0
- package/layouts/header.tsx +16 -0
- package/layouts/index.tsx +10 -0
- package/nodes/square.tsx +36 -0
- package/package.json +63 -4
- package/pages/about.md +5 -0
- package/pages/example.md +7 -0
- package/pages/python-editor.md +29 -0
- package/public/favicon.ico +0 -0
- package/public/logo.svg +5 -0
- package/site.yml +1 -0
- package/src/app.css +387 -0
- package/src/app.tsx +30 -0
- package/src/authorStyles.ts +3 -0
- package/src/components/Header.tsx +16 -0
- package/src/components/PageMeta.tsx +40 -0
- package/src/components/PageTitle.tsx +10 -0
- package/src/components/SplitPane.tsx +170 -0
- package/src/components/icons.tsx +94 -0
- package/src/entry-client.tsx +4 -0
- package/src/entry-server.tsx +21 -0
- package/src/global.d.ts +7 -0
- package/src/lib/appDir.ts +4 -0
- package/src/lib/autodiff/index.ts +49 -0
- package/src/lib/layouts/layout.ts +20 -0
- package/src/lib/layouts/vitePlugin.ts +39 -0
- package/src/lib/markdown/components/CanvasPage.tsx +25 -0
- package/src/lib/markdown/components/MarkdownBody.tsx +42 -0
- package/src/lib/markdown/components/PythonEditor.tsx +254 -0
- package/src/lib/markdown/frontmatter.ts +83 -0
- package/src/lib/markdown/pages.ts +23 -0
- package/src/lib/markdown/render.ts +62 -0
- package/src/lib/markdown/vitePlugin.ts +94 -0
- package/src/lib/nodes/components/CenteredGrid.tsx +59 -0
- package/src/lib/nodes/components/NodeCanvas.tsx +58 -0
- package/src/lib/nodes/execute.ts +166 -0
- package/src/lib/nodes/functions/Node2NodeFunc.ts +307 -0
- package/src/lib/nodes/functions/array.ts +36 -0
- package/src/lib/nodes/functions/autodiff.ts +64 -0
- package/src/lib/nodes/functions/basic.ts +298 -0
- package/src/lib/nodes/functions/registry.ts +46 -0
- package/src/lib/nodes/functions/sideEffects.ts +193 -0
- package/src/lib/nodes/functions/vitePlugin.ts +40 -0
- package/src/lib/nodes/hooks/useCanvasResize.ts +38 -0
- package/src/lib/nodes/jax.ts +191 -0
- package/src/lib/nodes/render/renderLogic.tsx +306 -0
- package/src/lib/nodes/state/NameGenerator.ts +31 -0
- package/src/lib/nodes/state/Node.ts +211 -0
- package/src/lib/nodes/state/NodeStore.ts +110 -0
- package/src/lib/nodes/tensors/broadcast.ts +101 -0
- package/src/lib/nodes/tensors/tensor.ts +42 -0
- package/src/lib/python/lift.ts +105 -0
- package/src/lib/python/py/__pycache__/pie.cpython-314.pyc +0 -0
- package/src/lib/python/py/pie.py +231 -0
- package/src/lib/python/py/pie_mpl.py +15 -0
- package/src/lib/python/py/show_figures.py +5 -0
- package/src/lib/python/runner.ts +202 -0
- package/src/lib/python/worker.ts +109 -0
- package/src/lib/site/vitePlugin.ts +58 -0
- package/src/routes/[...slug].tsx +39 -0
- package/src/routes/index.tsx +24 -0
- package/src/routes/pages/index.tsx +20 -0
- package/styles/theme.css +27 -0
- package/vite.ts +96 -0
package/package.json
CHANGED
|
@@ -1,6 +1,65 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tinyvolt/pie",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "A Markdown-based SSG for interactive science explainers. Create a site with: npx @tinyvolt/pie init my-site",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/TinyVolt/pie-ssg.git"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
"./vite": "./vite.ts"
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"pie": "init/index.js"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"src",
|
|
18
|
+
"vite.ts",
|
|
19
|
+
"init",
|
|
20
|
+
"pages",
|
|
21
|
+
"functions",
|
|
22
|
+
"nodes",
|
|
23
|
+
"styles",
|
|
24
|
+
"layouts",
|
|
25
|
+
"public",
|
|
26
|
+
"site.yml",
|
|
27
|
+
".nvmrc"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"dev": "vite dev",
|
|
31
|
+
"build": "vite build",
|
|
32
|
+
"start": "node .output/server/index.mjs",
|
|
33
|
+
"preview": "vite preview"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@codemirror/lang-python": "6.2.1",
|
|
37
|
+
"@codemirror/view": "6.43.14",
|
|
38
|
+
"@jax-js/jax": "0.1.25",
|
|
39
|
+
"@solidjs/meta": "0.29.4",
|
|
40
|
+
"@solidjs/router": "1.0.0",
|
|
41
|
+
"@solidjs/start": "2.0.6",
|
|
42
|
+
"codemirror": "6.0.2",
|
|
43
|
+
"katex": "0.19.0",
|
|
44
|
+
"marked": "18.1.1",
|
|
45
|
+
"nitro": "3.0.260610-beta"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"solid-js": "1.9.14",
|
|
49
|
+
"vite": "8.1.4"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
},
|
|
54
|
+
"packageManager": "pnpm@12.9.1",
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": "^24"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@types/node": "24.19.2",
|
|
60
|
+
"solid-js": "1.9.14",
|
|
61
|
+
"typescript": "7.0.2",
|
|
62
|
+
"vite": "8.1.4"
|
|
63
|
+
},
|
|
64
|
+
"license": "MIT"
|
|
65
|
+
}
|
package/pages/example.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Python editor
|
|
3
|
+
canvas: left
|
|
4
|
+
---
|
|
5
|
+
# Python editor
|
|
6
|
+
|
|
7
|
+
This is plain python code created using \`\`\`python ... \`\`\` code fence:
|
|
8
|
+
```python
|
|
9
|
+
A = pie.point(1, 2)
|
|
10
|
+
print(A)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
Below are python editors created using \`\`\`python-editor ... \`\`\` code fence:
|
|
16
|
+
|
|
17
|
+
```python-editor collapsed
|
|
18
|
+
a = pie.point(1, 2, out='a')
|
|
19
|
+
r = pie.scalar(1, out='r')
|
|
20
|
+
c = pie.circle(a, r)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```python-editor
|
|
24
|
+
a = pie.point(0, 0, out='a')
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```python-editor
|
|
28
|
+
pie.scalar(5, out='r')
|
|
29
|
+
```
|
|
Binary file
|
package/public/logo.svg
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 10.5 64 64" width="256" height="256">
|
|
2
|
+
<path d="M32 29 L8 29 A24 24 0 0 0 20 49.78 Z" fill="#41DBC9"/>
|
|
3
|
+
<path d="M32 29 L44 49.78 A24 24 0 0 0 56 29 Z" fill="#41DBC9"/>
|
|
4
|
+
<path d="M32 34 L20 54.78 A24 24 0 0 0 44 54.78 Z" fill="#0B6B73"/>
|
|
5
|
+
</svg>
|
package/site.yml
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
title: pie-ssg
|
package/src/app.css
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
body {
|
|
2
|
+
margin: 0;
|
|
3
|
+
color: var(--color-text, #1a1a1a);
|
|
4
|
+
background: var(--color-bg, #fff);
|
|
5
|
+
font-family: var(--font-ui, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif);
|
|
6
|
+
line-height: var(--line-height, 1.6);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
a {
|
|
10
|
+
color: inherit;
|
|
11
|
+
text-underline-offset: 0.15em;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
#app {
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
min-height: 100dvh;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.site-header {
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: baseline;
|
|
23
|
+
gap: 1.5rem;
|
|
24
|
+
padding: 0.75rem 1.5rem;
|
|
25
|
+
border-bottom: 1px solid var(--color-border, #ddd);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.site-header-title {
|
|
29
|
+
font-weight: 600;
|
|
30
|
+
text-decoration: none;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.site-header-nav {
|
|
34
|
+
display: flex;
|
|
35
|
+
gap: 1rem;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.site-header-nav a {
|
|
39
|
+
color: var(--color-muted, #555);
|
|
40
|
+
text-decoration: none;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.site-header-nav a:hover {
|
|
44
|
+
color: var(--color-hover, #000);
|
|
45
|
+
text-decoration: underline;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Page text: a centered column about 70 characters wide. */
|
|
49
|
+
main {
|
|
50
|
+
box-sizing: border-box;
|
|
51
|
+
width: 100%;
|
|
52
|
+
max-width: var(--content-width, 40rem);
|
|
53
|
+
margin: 0 auto;
|
|
54
|
+
padding: 2rem 1.5rem 4rem;
|
|
55
|
+
font-family: var(--font-body, Georgia, "Times New Roman", serif);
|
|
56
|
+
font-size: var(--font-size-body, 1.125rem);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
main h1,
|
|
60
|
+
main h2,
|
|
61
|
+
main h3,
|
|
62
|
+
main h4 {
|
|
63
|
+
line-height: 1.25;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
main h1 {
|
|
67
|
+
font-size: 2rem;
|
|
68
|
+
margin: 0 0 1rem;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
main h2 {
|
|
72
|
+
font-size: 1.4rem;
|
|
73
|
+
margin: 2rem 0 0.75rem;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
main h3 {
|
|
77
|
+
font-size: 1.15rem;
|
|
78
|
+
margin: 1.5rem 0 0.5rem;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
main p,
|
|
82
|
+
main ul,
|
|
83
|
+
main ol,
|
|
84
|
+
main blockquote,
|
|
85
|
+
main table {
|
|
86
|
+
margin: 0 0 1rem;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
main li + li {
|
|
90
|
+
margin-top: 0.25rem;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
main hr {
|
|
94
|
+
margin: 2rem 0;
|
|
95
|
+
border: 0;
|
|
96
|
+
border-top: 1px solid var(--color-border, #ddd);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
main blockquote {
|
|
100
|
+
padding-left: 1rem;
|
|
101
|
+
border-left: 3px solid var(--color-border, #ddd);
|
|
102
|
+
color: var(--color-muted, #555);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
main img {
|
|
106
|
+
max-width: 100%;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
main code {
|
|
110
|
+
font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
|
|
111
|
+
font-size: 0.85em;
|
|
112
|
+
padding: 0.1em 0.3em;
|
|
113
|
+
background: var(--color-code-bg, #f4f4f4);
|
|
114
|
+
border-radius: 3px;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
main > pre {
|
|
118
|
+
margin: 0 0 1rem;
|
|
119
|
+
padding: 0.75rem 1rem;
|
|
120
|
+
overflow-x: auto;
|
|
121
|
+
background: var(--color-code-bg, #f4f4f4);
|
|
122
|
+
border-radius: 4px;
|
|
123
|
+
line-height: 1.5;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
main pre code {
|
|
127
|
+
padding: 0;
|
|
128
|
+
background: none;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
main table {
|
|
132
|
+
border-collapse: collapse;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
main th,
|
|
136
|
+
main td {
|
|
137
|
+
padding: 0.3rem 0.75rem;
|
|
138
|
+
border-bottom: 1px solid var(--color-border, #ddd);
|
|
139
|
+
text-align: left;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
main .katex-display {
|
|
143
|
+
overflow-x: auto;
|
|
144
|
+
overflow-y: hidden;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.split-pane {
|
|
148
|
+
display: flex;
|
|
149
|
+
min-width: 0;
|
|
150
|
+
min-height: 0;
|
|
151
|
+
overflow: hidden;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.split-pane--left {
|
|
155
|
+
flex-direction: row;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.split-pane--right {
|
|
159
|
+
flex-direction: row-reverse;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.split-pane--top {
|
|
163
|
+
flex-direction: column;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.split-pane--bottom {
|
|
167
|
+
flex-direction: column-reverse;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* Each pane scrolls on its own. */
|
|
171
|
+
.split-pane-pane {
|
|
172
|
+
position: relative;
|
|
173
|
+
min-width: 0;
|
|
174
|
+
min-height: 0;
|
|
175
|
+
overflow: auto;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* Draggable divider: a thin bar along whichever axis the split uses. */
|
|
179
|
+
.split-pane-divider {
|
|
180
|
+
position: relative;
|
|
181
|
+
flex: 0 0 6px;
|
|
182
|
+
align-self: stretch;
|
|
183
|
+
background: var(--color-border, #ddd);
|
|
184
|
+
cursor: col-resize;
|
|
185
|
+
z-index: 5;
|
|
186
|
+
transition: background 0.15s ease;
|
|
187
|
+
touch-action: none;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.split-pane-divider:hover,
|
|
191
|
+
.split-pane-divider:active {
|
|
192
|
+
background: var(--color-border-hover, #999);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.split-pane-divider--horizontal {
|
|
196
|
+
cursor: row-resize;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.split-pane-buttons {
|
|
200
|
+
position: absolute;
|
|
201
|
+
top: 50%;
|
|
202
|
+
left: 50%;
|
|
203
|
+
display: flex;
|
|
204
|
+
flex-direction: column;
|
|
205
|
+
gap: 0.3rem;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.split-pane-divider--horizontal .split-pane-buttons {
|
|
209
|
+
flex-direction: row;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.split-pane-button {
|
|
213
|
+
display: inline-flex;
|
|
214
|
+
align-items: center;
|
|
215
|
+
justify-content: center;
|
|
216
|
+
width: 1.25rem;
|
|
217
|
+
height: 1.25rem;
|
|
218
|
+
padding: 0;
|
|
219
|
+
background: var(--color-bg, #fff);
|
|
220
|
+
border: 1px solid var(--color-border, #ddd);
|
|
221
|
+
border-radius: 50%;
|
|
222
|
+
color: var(--color-subtle, #666);
|
|
223
|
+
cursor: pointer;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.split-pane-button:hover {
|
|
227
|
+
color: var(--color-hover, #000);
|
|
228
|
+
border-color: var(--color-border-hover, #999);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* A canvas page fills the screen below the header. */
|
|
232
|
+
#app:has(.canvas-page) {
|
|
233
|
+
height: 100dvh;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.canvas-page {
|
|
237
|
+
flex: 1 1 0;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.canvas-page-canvas {
|
|
241
|
+
height: 100%;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* NodeCanvas measures this box to size its grid. */
|
|
245
|
+
.node-canvas {
|
|
246
|
+
width: 100%;
|
|
247
|
+
height: 100%;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.canvas-page-canvas svg {
|
|
251
|
+
display: block;
|
|
252
|
+
width: 100%;
|
|
253
|
+
height: 100%;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/* Hidden until MarkdownBody swaps it for the editor; keeps its space so the text below doesn't jump. */
|
|
257
|
+
pre:has(> code.language-python-editor) {
|
|
258
|
+
visibility: hidden;
|
|
259
|
+
max-height: 20rem;
|
|
260
|
+
overflow: hidden;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.python-editor {
|
|
264
|
+
margin: 1rem 0;
|
|
265
|
+
border: 1px solid var(--color-border, #ddd);
|
|
266
|
+
border-radius: 4px;
|
|
267
|
+
font-family: var(--font-ui, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/* Default cap on the code's height. Dragging the resize bar replaces it with a fixed height. */
|
|
271
|
+
.python-editor .cm-editor {
|
|
272
|
+
font-size: 0.875rem;
|
|
273
|
+
max-height: 20rem;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.python-editor .cm-scroller {
|
|
277
|
+
overflow-y: auto;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.python-editor-resize {
|
|
281
|
+
height: 6px;
|
|
282
|
+
cursor: row-resize;
|
|
283
|
+
touch-action: none;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.python-editor-resize:hover {
|
|
287
|
+
background: var(--color-border, #ddd);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.python-editor-toolbar {
|
|
291
|
+
display: flex;
|
|
292
|
+
align-items: center;
|
|
293
|
+
gap: 0.5rem;
|
|
294
|
+
padding: 0.25rem 0.5rem;
|
|
295
|
+
border-top: 1px solid var(--color-border, #ddd);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.python-editor-button {
|
|
299
|
+
display: inline-flex;
|
|
300
|
+
align-items: center;
|
|
301
|
+
justify-content: center;
|
|
302
|
+
width: 1.75rem;
|
|
303
|
+
height: 1.75rem;
|
|
304
|
+
padding: 0;
|
|
305
|
+
background: var(--color-bg, #fff);
|
|
306
|
+
border: 1px solid var(--color-border, #ddd);
|
|
307
|
+
border-radius: 4px;
|
|
308
|
+
cursor: pointer;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.python-editor-button:hover {
|
|
312
|
+
border-color: var(--color-border-hover, #999);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.python-editor-status {
|
|
316
|
+
color: var(--color-subtle, #666);
|
|
317
|
+
font-size: 0.875rem;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.python-editor-result {
|
|
321
|
+
display: inline-flex;
|
|
322
|
+
align-items: center;
|
|
323
|
+
gap: 0.25rem;
|
|
324
|
+
font-size: 0.875rem;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.python-editor-result-success {
|
|
328
|
+
color: var(--color-success, #1a7f37);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.python-editor-result-error {
|
|
332
|
+
color: var(--color-error, #c00);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.python-editor-result-stopped {
|
|
336
|
+
color: var(--color-warning, #9a6700);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.python-editor-collapse {
|
|
340
|
+
margin-left: auto;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/* Collapsed: only the toolbar shows. Hidden with CSS so the editor keeps its code. */
|
|
344
|
+
.python-editor-collapsed .python-editor-code,
|
|
345
|
+
.python-editor-collapsed .python-editor-resize,
|
|
346
|
+
.python-editor-collapsed .python-editor-output {
|
|
347
|
+
display: none;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.python-editor-collapsed .python-editor-toolbar {
|
|
351
|
+
border-top: none;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/* Right padding leaves room for the copy button so it doesn't cover the text. */
|
|
355
|
+
.python-editor-output {
|
|
356
|
+
position: relative;
|
|
357
|
+
padding: 0.5rem 2.75rem 0.5rem 0.5rem;
|
|
358
|
+
border-top: 1px solid var(--color-border, #ddd);
|
|
359
|
+
font-size: 0.875rem;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.python-editor-copy {
|
|
363
|
+
position: absolute;
|
|
364
|
+
top: 0.5rem;
|
|
365
|
+
right: 0.5rem;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.python-editor-text {
|
|
369
|
+
margin: 0;
|
|
370
|
+
max-height: 20rem;
|
|
371
|
+
overflow: auto;
|
|
372
|
+
white-space: pre-wrap;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.python-editor-plot {
|
|
376
|
+
display: block;
|
|
377
|
+
max-width: 100%;
|
|
378
|
+
height: auto;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.python-editor-error {
|
|
382
|
+
color: var(--color-error, #c00);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.python-editor-note {
|
|
386
|
+
color: var(--color-subtle, #666);
|
|
387
|
+
}
|
package/src/app.tsx
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { MetaProvider } from "@solidjs/meta";
|
|
2
|
+
import { Router } from "@solidjs/router";
|
|
3
|
+
import { FileRoutes } from "@solidjs/start/router";
|
|
4
|
+
import { Suspense } from "solid-js";
|
|
5
|
+
import { Header as BuiltInHeader } from "./components/Header";
|
|
6
|
+
import { PageTitle } from "./components/PageTitle";
|
|
7
|
+
import { layout, type LayoutModules } from "./lib/layouts/layout";
|
|
8
|
+
import "./app.css";
|
|
9
|
+
// Author styles load after `app.css`, so they override it.
|
|
10
|
+
import "./authorStyles";
|
|
11
|
+
|
|
12
|
+
// The site's own `layouts/header.tsx`, or the built-in header.
|
|
13
|
+
const Header = layout(import.meta.glob<LayoutModules[string]>("/layouts/header.tsx", { eager: true }), BuiltInHeader);
|
|
14
|
+
|
|
15
|
+
export default function App() {
|
|
16
|
+
return (
|
|
17
|
+
<Router
|
|
18
|
+
root={props => (
|
|
19
|
+
<MetaProvider>
|
|
20
|
+
{/* Default; a page's own PageTitle overrides it. */}
|
|
21
|
+
<PageTitle />
|
|
22
|
+
<Header />
|
|
23
|
+
<Suspense>{props.children}</Suspense>
|
|
24
|
+
</MetaProvider>
|
|
25
|
+
)}
|
|
26
|
+
>
|
|
27
|
+
<FileRoutes />
|
|
28
|
+
</Router>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import site from "/site.yml";
|
|
2
|
+
|
|
3
|
+
/** The site name and nav links, shown at the top of every page when the site has no `layouts/header.tsx`. */
|
|
4
|
+
export function Header() {
|
|
5
|
+
return (
|
|
6
|
+
<header class="site-header">
|
|
7
|
+
<a class="site-header-title" href="/">
|
|
8
|
+
{site.title}
|
|
9
|
+
</a>
|
|
10
|
+
<nav class="site-header-nav">
|
|
11
|
+
<a href="/about">About</a>
|
|
12
|
+
<a href="/pages">Pages</a>
|
|
13
|
+
</nav>
|
|
14
|
+
</header>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Link, Meta } from "@solidjs/meta";
|
|
2
|
+
import { useLocation } from "@solidjs/router";
|
|
3
|
+
import { Show } from "solid-js";
|
|
4
|
+
import site from "/site.yml";
|
|
5
|
+
import { PageTitle } from "./PageTitle";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The tab title plus the tags for search results and link previews. Each route renders it once:
|
|
9
|
+
* `@solidjs/meta` replaces a `<meta>` only when its content matches too, so a page's tag can't override a default.
|
|
10
|
+
* Empty `title` or `description` falls back to `site.yml`. The canonical link and `og:url` need `site.yml`'s `url`.
|
|
11
|
+
*/
|
|
12
|
+
export function PageMeta(props: { title?: string; description?: string }) {
|
|
13
|
+
const location = useLocation();
|
|
14
|
+
const ogTitle = () => props.title || site.title;
|
|
15
|
+
const description = () => props.description || site.description;
|
|
16
|
+
return (
|
|
17
|
+
<>
|
|
18
|
+
<PageTitle>{props.title}</PageTitle>
|
|
19
|
+
<Meta property="og:type" content="website" />
|
|
20
|
+
<Meta property="og:site_name" content={site.title} />
|
|
21
|
+
<Meta property="og:title" content={ogTitle()} />
|
|
22
|
+
<Show when={description()}>
|
|
23
|
+
{description => (
|
|
24
|
+
<>
|
|
25
|
+
<Meta name="description" content={description()} />
|
|
26
|
+
<Meta property="og:description" content={description()} />
|
|
27
|
+
</>
|
|
28
|
+
)}
|
|
29
|
+
</Show>
|
|
30
|
+
<Show when={site.url}>
|
|
31
|
+
{url => (
|
|
32
|
+
<>
|
|
33
|
+
<Link rel="canonical" href={url() + location.pathname} />
|
|
34
|
+
<Meta property="og:url" content={url() + location.pathname} />
|
|
35
|
+
</>
|
|
36
|
+
)}
|
|
37
|
+
</Show>
|
|
38
|
+
</>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Title } from "@solidjs/meta";
|
|
2
|
+
import site from "/site.yml";
|
|
3
|
+
|
|
4
|
+
/** Sets the browser tab title to "<page> – <site title>", or the site title alone when no page title is given. */
|
|
5
|
+
export function PageTitle(props: { children?: string }) {
|
|
6
|
+
// A ternary written inside the JSX gets wrapped in a memo, which `@solidjs/meta` reads
|
|
7
|
+
// outside any owner when it removes the tag, causing a "never be disposed" warning.
|
|
8
|
+
const title = () => (props.children ? `${props.children} – ${site.title}` : site.title);
|
|
9
|
+
return <Title>{title()}</Title>;
|
|
10
|
+
}
|