@zvndev/darkmown 0.9.0 → 0.11.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/CHANGELOG.md +20 -0
- package/README.md +225 -17
- package/docs/cli.md +24 -2
- package/docs/spec-alignment.md +13 -5
- package/package.json +6 -3
- package/site/_/nav.wd +2 -0
- package/site/_/roadmap.json +2 -2
- package/site/pages/docs/index.wd +100 -4
- package/site/pages/index.wd +0 -1
- package/site/pages/loops.skin +80 -0
- package/site/pages/loops.wd +86 -0
- package/site/pages/store.skin +70 -0
- package/site/pages/store.wd +53 -0
- package/src/builder.js +14 -9
- package/src/cli.js +14 -2
- package/src/compiler.js +494 -72
- package/src/runtime.js +294 -62
- package/src/scaffold.js +20 -1
- package/types/compiler.d.ts +49 -10
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
tokens
|
|
2
|
+
ink #171717
|
|
3
|
+
paper #faf8f3
|
|
4
|
+
panel #ffffff
|
|
5
|
+
line #dedbd3
|
|
6
|
+
accent #16645a
|
|
7
|
+
warm #a83f2a
|
|
8
|
+
radius 8px
|
|
9
|
+
|
|
10
|
+
page
|
|
11
|
+
margin 0
|
|
12
|
+
font ui-serif, Georgia, Cambria, serif
|
|
13
|
+
color $ink
|
|
14
|
+
bg $paper
|
|
15
|
+
line-height 1.7
|
|
16
|
+
|
|
17
|
+
main
|
|
18
|
+
max-width 820px
|
|
19
|
+
margin 0 auto
|
|
20
|
+
padding 3rem 2rem 5rem
|
|
21
|
+
|
|
22
|
+
h1
|
|
23
|
+
font-size 3rem
|
|
24
|
+
line-height 1
|
|
25
|
+
|
|
26
|
+
h2
|
|
27
|
+
margin-top 2.4rem
|
|
28
|
+
border-top 1px solid $line
|
|
29
|
+
padding-top 1.2rem
|
|
30
|
+
|
|
31
|
+
a
|
|
32
|
+
color $accent
|
|
33
|
+
|
|
34
|
+
button
|
|
35
|
+
border 1px solid $ink
|
|
36
|
+
bg $ink
|
|
37
|
+
color white
|
|
38
|
+
padding .72rem 1rem
|
|
39
|
+
margin .3rem .5rem .3rem 0
|
|
40
|
+
radius $radius
|
|
41
|
+
font inherit
|
|
42
|
+
cursor pointer
|
|
43
|
+
|
|
44
|
+
button:hover
|
|
45
|
+
bg $accent
|
|
46
|
+
border-color $accent
|
|
47
|
+
|
|
48
|
+
input
|
|
49
|
+
padding .7rem .9rem
|
|
50
|
+
border 1px solid $line
|
|
51
|
+
radius $radius
|
|
52
|
+
font inherit
|
|
53
|
+
bg $panel
|
|
54
|
+
width 100%
|
|
55
|
+
max-width 360px
|
|
56
|
+
margin 1rem 0
|
|
57
|
+
|
|
58
|
+
ul
|
|
59
|
+
border 1px solid $line
|
|
60
|
+
bg $panel
|
|
61
|
+
padding 1rem 1rem 1rem 2rem
|
|
62
|
+
radius $radius
|
|
63
|
+
|
|
64
|
+
.person-row
|
|
65
|
+
border 1px solid $line
|
|
66
|
+
bg $panel
|
|
67
|
+
padding .65rem 1.1rem
|
|
68
|
+
margin .5rem 0
|
|
69
|
+
radius $radius
|
|
70
|
+
display flex
|
|
71
|
+
gap .6rem
|
|
72
|
+
align-items baseline
|
|
73
|
+
flex-wrap wrap
|
|
74
|
+
|
|
75
|
+
#paged
|
|
76
|
+
border 1px dashed $line
|
|
77
|
+
bg $panel
|
|
78
|
+
padding .4rem 1.2rem 1.2rem
|
|
79
|
+
margin 1rem 0
|
|
80
|
+
radius $radius
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Loop ergonomics
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
@include /nav.wd
|
|
6
|
+
|
|
7
|
+
<main>
|
|
8
|
+
|
|
9
|
+
# Loop ergonomics
|
|
10
|
+
|
|
11
|
+
One loop, fully equipped. `@loop` gains `sort by`, `offset`, `limit`, `reverse`,
|
|
12
|
+
an `@empty` branch, and per-row meta variables (`{ $number }`, `{ $first }`,
|
|
13
|
+
`{ $last }`, `{ $count }`) — all in plain Markdown, all reactive when the source
|
|
14
|
+
or a clause argument is `:state`.
|
|
15
|
+
|
|
16
|
+
:state people = [{"id": 1, "name": "Ada", "age": 36}, {"id": 2, "name": "Cyrus", "age": 71}, {"id": 3, "name": "Bao", "age": 29}, {"id": 4, "name": "Dax", "age": 52}, {"id": 5, "name": "Esme", "age": 44}]
|
|
17
|
+
:state sortOffset = 0
|
|
18
|
+
:state pageSize = 2
|
|
19
|
+
:state query = ""
|
|
20
|
+
:state recent = [{"id": 1, "label": "Item 1"}, {"id": 2, "label": "Item 2"}, {"id": 3, "label": "Item 3"}, {"id": 4, "label": "Item 4"}, {"id": 5, "label": "Item 5"}]
|
|
21
|
+
|
|
22
|
+
## Sorted, numbered, with first / last markers
|
|
23
|
+
|
|
24
|
+
`sort by p.age desc` orders the eldest first. Each row reads its position from
|
|
25
|
+
the loop meta vars: `{ $number }` is the 1-based index, and `:if $first` /
|
|
26
|
+
`:if $last` light up only on the boundary rows.
|
|
27
|
+
|
|
28
|
+
@loop people into p sort by p.age desc offset sortOffset
|
|
29
|
+
::: card .person-row
|
|
30
|
+
**{ $number }. { p.name }** — age { p.age }
|
|
31
|
+
:if $first
|
|
32
|
+
(first)
|
|
33
|
+
:endif
|
|
34
|
+
:if $last
|
|
35
|
+
(last)
|
|
36
|
+
:endif
|
|
37
|
+
:::
|
|
38
|
+
@endloop
|
|
39
|
+
|
|
40
|
+
The list above is sorted oldest-first. `offset` is wired to a `:state`, so a
|
|
41
|
+
button can slide the window without a reload:
|
|
42
|
+
|
|
43
|
+
:button "Sort: skip the eldest" -> sortOffset = 1
|
|
44
|
+
:button "Reset order" -> sortOffset = 0
|
|
45
|
+
|
|
46
|
+
## Reactive paging with `limit`
|
|
47
|
+
|
|
48
|
+
`limit pageSize` reads a `:state` value, so paging is just a button that bumps
|
|
49
|
+
the number. The runtime re-slices the same loop in place — no extra request, no
|
|
50
|
+
reload.
|
|
51
|
+
|
|
52
|
+
::: section #paged
|
|
53
|
+
@loop people into p limit pageSize
|
|
54
|
+
::: card .person-row
|
|
55
|
+
{ p.name } — age { p.age }
|
|
56
|
+
:::
|
|
57
|
+
@endloop
|
|
58
|
+
|
|
59
|
+
:button "Show more" -> pageSize += 2
|
|
60
|
+
:button "Collapse" -> pageSize = 2
|
|
61
|
+
:::
|
|
62
|
+
|
|
63
|
+
## `where` + `@empty`
|
|
64
|
+
|
|
65
|
+
`where` filters live as you type, and `@empty` renders the moment the filtered
|
|
66
|
+
set hits zero rows — the one place a loop gets to say "nothing here":
|
|
67
|
+
|
|
68
|
+
:bind query placeholder="Search people…"
|
|
69
|
+
|
|
70
|
+
@loop people into p where p.name contains query
|
|
71
|
+
::: card .person-row
|
|
72
|
+
{ p.name }
|
|
73
|
+
:::
|
|
74
|
+
@empty
|
|
75
|
+
No matches.
|
|
76
|
+
@endloop
|
|
77
|
+
|
|
78
|
+
## `reverse` flips the source order
|
|
79
|
+
|
|
80
|
+
No sort key needed — `reverse` walks the list back to front:
|
|
81
|
+
|
|
82
|
+
@loop recent into r reverse
|
|
83
|
+
- { r.label }
|
|
84
|
+
@endloop
|
|
85
|
+
|
|
86
|
+
</main>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
tokens
|
|
2
|
+
ink #171717
|
|
3
|
+
paper #faf8f3
|
|
4
|
+
panel #ffffff
|
|
5
|
+
line #dedbd3
|
|
6
|
+
accent #16645a
|
|
7
|
+
warm #a83f2a
|
|
8
|
+
radius 8px
|
|
9
|
+
|
|
10
|
+
page
|
|
11
|
+
margin 0
|
|
12
|
+
font ui-serif, Georgia, Cambria, serif
|
|
13
|
+
color $ink
|
|
14
|
+
bg $paper
|
|
15
|
+
line-height 1.7
|
|
16
|
+
|
|
17
|
+
main
|
|
18
|
+
max-width 820px
|
|
19
|
+
margin 0 auto
|
|
20
|
+
padding 3rem 2rem 5rem
|
|
21
|
+
|
|
22
|
+
h1
|
|
23
|
+
font-size 3rem
|
|
24
|
+
line-height 1
|
|
25
|
+
|
|
26
|
+
h2
|
|
27
|
+
margin-top 2.4rem
|
|
28
|
+
border-top 1px solid $line
|
|
29
|
+
padding-top 1.2rem
|
|
30
|
+
|
|
31
|
+
a
|
|
32
|
+
color $accent
|
|
33
|
+
|
|
34
|
+
button
|
|
35
|
+
border 1px solid $ink
|
|
36
|
+
bg $ink
|
|
37
|
+
color white
|
|
38
|
+
padding .72rem 1rem
|
|
39
|
+
margin .3rem .5rem .3rem 0
|
|
40
|
+
radius $radius
|
|
41
|
+
font inherit
|
|
42
|
+
cursor pointer
|
|
43
|
+
|
|
44
|
+
button:hover
|
|
45
|
+
bg $accent
|
|
46
|
+
border-color $accent
|
|
47
|
+
|
|
48
|
+
input
|
|
49
|
+
padding .7rem .9rem
|
|
50
|
+
border 1px solid $line
|
|
51
|
+
radius $radius
|
|
52
|
+
font inherit
|
|
53
|
+
bg $panel
|
|
54
|
+
width 100%
|
|
55
|
+
max-width 360px
|
|
56
|
+
margin 1rem 0
|
|
57
|
+
|
|
58
|
+
#shop
|
|
59
|
+
border 1px solid $line
|
|
60
|
+
bg $panel
|
|
61
|
+
padding .4rem 1.4rem 1.4rem
|
|
62
|
+
margin 1.5rem 0
|
|
63
|
+
radius $radius
|
|
64
|
+
|
|
65
|
+
.line
|
|
66
|
+
border 1px solid $line
|
|
67
|
+
bg $paper
|
|
68
|
+
padding .55rem 1.1rem
|
|
69
|
+
margin .45rem 0
|
|
70
|
+
radius $radius
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Global store
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
@include /nav.wd
|
|
6
|
+
|
|
7
|
+
<main>
|
|
8
|
+
|
|
9
|
+
# Global store
|
|
10
|
+
|
|
11
|
+
`:store` is durable, page-global state. It persists to `localStorage` under the
|
|
12
|
+
`wd:store:` prefix, rehydrates on load, and syncs across tabs through the
|
|
13
|
+
`storage` event — no backend, no wiring. Add `ephemeral` to opt a store out of
|
|
14
|
+
persistence entirely.
|
|
15
|
+
|
|
16
|
+
::: section #shop
|
|
17
|
+
:store cart = []
|
|
18
|
+
|
|
19
|
+
## A durable cart
|
|
20
|
+
|
|
21
|
+
This cart is a `:store`, so it survives a full reload and mirrors into every open
|
|
22
|
+
tab. Add a couple of items, refresh the page, and the count holds.
|
|
23
|
+
|
|
24
|
+
Cart: { cart.length }
|
|
25
|
+
|
|
26
|
+
:button "Add to cart" -> cart append {"id": "sticker", "name": "Sticker", "price": 4}
|
|
27
|
+
:button "Reset cart" -> cart reset
|
|
28
|
+
|
|
29
|
+
@loop cart into line
|
|
30
|
+
::: card .line
|
|
31
|
+
{ line.name } — ${ line.price }
|
|
32
|
+
:::
|
|
33
|
+
@empty
|
|
34
|
+
The cart is empty. Add something, then reload — it stays.
|
|
35
|
+
@endloop
|
|
36
|
+
|
|
37
|
+
`Reset cart` runs `-> cart reset`, which restores the **declared** seed (`[]`),
|
|
38
|
+
not the last persisted value — and writes that seed back to storage.
|
|
39
|
+
:::
|
|
40
|
+
|
|
41
|
+
## An ephemeral draft
|
|
42
|
+
|
|
43
|
+
`:store draft = "" ephemeral` is in-memory only. It is bound two-way to the
|
|
44
|
+
input and echoed below, but nothing reaches `localStorage`, so a reload wipes it
|
|
45
|
+
clean.
|
|
46
|
+
|
|
47
|
+
:store draft = "" ephemeral
|
|
48
|
+
|
|
49
|
+
:bind draft placeholder="Type a draft note…"
|
|
50
|
+
|
|
51
|
+
Draft says: **{ draft }**
|
|
52
|
+
|
|
53
|
+
</main>
|
package/src/builder.js
CHANGED
|
@@ -66,21 +66,26 @@ export function buildSite(cwd = process.cwd()) {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
|
-
* Strip
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* and the gzip budget honest (measured against what users
|
|
73
|
-
*
|
|
69
|
+
* Strip developer comments from the runtime before it ships. The source keeps
|
|
70
|
+
* full JSDoc type annotations (for `checkJs` + `.d.ts`) and explanatory comments
|
|
71
|
+
* so it stays readable; the browser downloads neither. This keeps the shipped
|
|
72
|
+
* runtime lean and the gzip budget honest (measured against what users receive),
|
|
73
|
+
* WITHOUT minifying — identifiers, structure, and whitespace are all preserved.
|
|
74
|
+
*
|
|
75
|
+
* Two passes, each conservative: `/** ... *\/` JSDoc blocks, then whole-line
|
|
76
|
+
* `//` comments (a line that is nothing but a comment). Trailing `//` after code
|
|
77
|
+
* and `//` inside strings/URLs are deliberately left untouched, so no string
|
|
78
|
+
* literal is ever harmed.
|
|
74
79
|
* @param {string} source
|
|
75
80
|
* @returns {string}
|
|
76
81
|
*/
|
|
77
82
|
export function stripRuntimeComments(source) {
|
|
78
83
|
return source
|
|
79
|
-
//
|
|
80
|
-
// inline `/** @type {x} */ code` keeps its line and never glues onto a
|
|
81
|
-
// preceding `//` comment.
|
|
84
|
+
// JSDoc blocks — type annotations the browser never needs.
|
|
82
85
|
.replace(/\/\*\*[\s\S]*?\*\//g, "")
|
|
83
|
-
//
|
|
86
|
+
// Whole-line `//` comments — developer notes that don't belong in the download.
|
|
87
|
+
.replace(/^[ \t]*\/\/.*$/gm, "")
|
|
88
|
+
// Drop the now blank/whitespace-only lines the removals leave behind.
|
|
84
89
|
.replace(/^[ \t]*\n/gm, "");
|
|
85
90
|
}
|
|
86
91
|
|
package/src/cli.js
CHANGED
|
@@ -20,8 +20,9 @@ if (command === "help" || command === "--help" || command === "-h") {
|
|
|
20
20
|
} else if (command === "init") {
|
|
21
21
|
const target = process.argv[3] || ".";
|
|
22
22
|
const result = initProject(path.resolve(process.cwd(), target));
|
|
23
|
-
|
|
24
|
-
console.log(`
|
|
23
|
+
const relativeRoot = path.relative(process.cwd(), result.root) || ".";
|
|
24
|
+
console.log(`Created Darkmown project at ${relativeRoot}`);
|
|
25
|
+
console.log(`Next: ${nextStep(relativeRoot)}`);
|
|
25
26
|
} else if (command === "build") {
|
|
26
27
|
const result = buildSite();
|
|
27
28
|
console.log(`Built ${result.routes.length} routes into ${path.relative(process.cwd(), result.distRoot)}`);
|
|
@@ -157,6 +158,7 @@ Usage:
|
|
|
157
158
|
darkmown dev Start the live compiler dev server
|
|
158
159
|
darkmown build Compile site/pages into dist
|
|
159
160
|
darkmown serve Preview the built dist locally
|
|
161
|
+
darkmown version Print the installed Darkmown version
|
|
160
162
|
darkmown help Show this help
|
|
161
163
|
|
|
162
164
|
Authoring:
|
|
@@ -167,3 +169,13 @@ Authoring:
|
|
|
167
169
|
*.js Colocated page behavior
|
|
168
170
|
`);
|
|
169
171
|
}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* @param {string} relativeRoot
|
|
176
|
+
* @returns {string}
|
|
177
|
+
*/
|
|
178
|
+
function nextStep(relativeRoot) {
|
|
179
|
+
if (relativeRoot === ".") return "npm install && npm run dev";
|
|
180
|
+
return `cd ${relativeRoot} && npm install && npm run dev`;
|
|
181
|
+
}
|