openink 0.2.0 → 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/CHANGELOG.md +22 -0
- package/README.md +4 -1
- package/docs/architecture.md +1 -1
- package/docs/blocks.md +5 -3
- package/docs/getting-started.md +1 -1
- package/docs/spec.md +2 -0
- package/package.json +1 -1
- package/schema/spec.schema.json +296 -4
- package/src/build.js +43 -12
- package/src/cli.js +22 -3
- package/src/dev.js +16 -4
- package/src/icons.js +110 -0
- package/src/render/page.js +17 -1
- package/src/spec/docs.js +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,28 @@ All notable changes are documented here. The format follows [Keep a Changelog](h
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.4.0] - 2026-10-06
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `openink dev --open` opens the prototype in the default browser once the server is running (#25).
|
|
12
|
+
- Spec errors and warnings start with the file, line and column (`spec.yaml:42:7`), in `validate`, `build`, `dev` and
|
|
13
|
+
the browser overlay. Most terminals and editors open that spot with a click (#24).
|
|
14
|
+
- `openink dev` shows spec errors over the prototype in the browser, and hides them as soon as the spec builds again.
|
|
15
|
+
Before, a failed rebuild only showed in the terminal, and the browser kept the last good build (#23).
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- `openink dev` prints each warning with its path and message, not only how many there are (#22).
|
|
20
|
+
|
|
21
|
+
## [0.3.0] - 2026-10-05
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- 73 more icons, 110 in all: arrows, status, files, shopping, weather and 20 brand logos (Google, Apple, GitHub,
|
|
26
|
+
Facebook, X, Instagram, LinkedIn, YouTube, TikTok, WhatsApp and more) for sign-in buttons, share bars and footers.
|
|
27
|
+
The gallery has a new Icons screen that shows every icon with its name (#20).
|
|
28
|
+
|
|
7
29
|
## [0.2.0] - 2026-10-02
|
|
8
30
|
|
|
9
31
|
### Added
|
package/README.md
CHANGED
|
@@ -71,6 +71,7 @@ npx openink pdf # dist/<name>.pdf, one screen per page
|
|
|
71
71
|
npx openink png # dist/png/<screen>.png
|
|
72
72
|
npx openink blocks # list every block and its props
|
|
73
73
|
npx openink dev --theme dark # try a colour theme without editing the spec
|
|
74
|
+
npx openink dev --open # also open the preview in your browser
|
|
74
75
|
```
|
|
75
76
|
|
|
76
77
|
Or install it once and drop the `npx`:
|
|
@@ -111,7 +112,7 @@ The agent writes and validates the spec, checks screenshots of every screen and
|
|
|
111
112
|
|---|---|
|
|
112
113
|
| **Layout** | `stack` `row` `grid` `card` `accordion` `device` (phone / tablet / browser frame) `divider` `spacer` |
|
|
113
114
|
| **Text** | `h1` `h2` `h3` `text` `list` `note` `badge` |
|
|
114
|
-
| **Content** | `icon` (
|
|
115
|
+
| **Content** | `icon` (110 hand-drawn icons, brand logos included) `avatar` `hero` `stat` `rating` `link` |
|
|
115
116
|
| **Media** | `image` `map` `video` `carousel` `dropzone` `box` `chart` (line, area, bar, pie, donut) |
|
|
116
117
|
| **Forms** | `input` `search` `textarea` `select` `checkbox` `toggle` `radio` `slider` |
|
|
117
118
|
| **Actions** | `button` (with icon) `chips` `tabs` `nextbar` |
|
|
@@ -190,3 +191,5 @@ Issues and pull requests are welcome; see [CONTRIBUTING.md](CONTRIBUTING.md). Ad
|
|
|
190
191
|
## License
|
|
191
192
|
|
|
192
193
|
[MIT](LICENSE)
|
|
194
|
+
|
|
195
|
+
Brand names and logos are trademarks of their owners. Open Ink's brand icons are simplified sketches for wireframes only and do not imply endorsement. Use each brand's official assets in production.
|
package/docs/architecture.md
CHANGED
|
@@ -17,7 +17,7 @@ dist/ ──► http server + fs.watch + reload poller (dev.js)
|
|
|
17
17
|
|
|
18
18
|
**One definition per block.** `src/render/blocks/` is the single source of truth. The validator, the JSON Schema, `docs/blocks.md` and `openink blocks` are all generated from it, and tests fail when the generated files are stale. Adding a block is one object.
|
|
19
19
|
|
|
20
|
-
**Validate everything, early.** Assistants and humans both make typos. The validator never throws; it returns `{ errors, warnings }` with a path into the spec and a suggestion
|
|
20
|
+
**Validate everything, early.** Assistants and humans both make typos. The validator never throws; it returns `{ errors, warnings }` with a path into the spec and a suggestion; the CLI adds the line and column from the YAML source. `build` refuses to write output if there are errors.
|
|
21
21
|
|
|
22
22
|
**Bundled runtime.** wired-elements and RoughJS are bundled into `openink.js` at build time, so a prototype has no CDN dependency (except the Google Fonts stylesheet, which falls back to a system cursive font offline).
|
|
23
23
|
|
package/docs/blocks.md
CHANGED
|
@@ -27,7 +27,9 @@ Blocks that list `go`, `toast`, `open` or `close` in their props are clickable (
|
|
|
27
27
|
|
|
28
28
|
Used by `icon`, `button.icon`, nav items and `tabbar`:
|
|
29
29
|
|
|
30
|
-
`home` · `search` · `heart` · `comment` · `share` · `bookmark` · `plus` · `bell` · `user` · `users` · `mail` · `settings` · `camera` · `image` · `video` · `star` · `menu` · `more` · `close` · `check` · `arrow-right` · `arrow-left` · `chevron-down` · `chevron-right` · `play` · `pin` · `trash` · `edit` · `upload` · `download` · `lock` · `cart` · `chart` · `calendar` · `filter` · `info` · `send`
|
|
30
|
+
`home` · `search` · `heart` · `comment` · `share` · `bookmark` · `plus` · `bell` · `user` · `users` · `mail` · `settings` · `camera` · `image` · `video` · `star` · `menu` · `more` · `close` · `check` · `arrow-right` · `arrow-left` · `chevron-down` · `chevron-right` · `play` · `pin` · `trash` · `edit` · `upload` · `download` · `lock` · `cart` · `chart` · `calendar` · `filter` · `info` · `send` · `minus` · `arrow-up` · `arrow-down` · `chevron-up` · `chevron-left` · `external` · `link` · `refresh` · `login` · `logout` · `grid` · `list` · `pause` · `check-circle` · `warning` · `help` · `eye` · `eye-off` · `unlock` · `key` · `thumbs-up` · `smile` · `flag` · `zap` · `phone` · `inbox` · `paperclip` · `mic` · `volume` · `music` · `wifi` · `hash` · `at` · `rss` · `file` · `folder` · `copy` · `book` · `briefcase` · `printer` · `code` · `clock` · `bag` · `tag` · `gift` · `credit-card` · `dollar` · `truck` · `globe` · `map` · `sun` · `moon` · `cloud` · `facebook` · `x` · `instagram` · `linkedin` · `youtube` · `github` · `tiktok` · `whatsapp` · `telegram` · `discord` · `pinterest` · `reddit` · `snapchat` · `slack` · `spotify` · `apple` · `google` · `microsoft` · `dribbble` · `twitch`
|
|
31
|
+
|
|
32
|
+
Brand names and logos are trademarks of their owners. Open Ink's brand icons are simplified sketches for wireframes only and do not imply endorsement. Use each brand's official assets in production.
|
|
31
33
|
|
|
32
34
|
## Layout
|
|
33
35
|
|
|
@@ -198,7 +200,7 @@ Hand-drawn icon.
|
|
|
198
200
|
|
|
199
201
|
| Prop | Type | Description |
|
|
200
202
|
|---|---|---|
|
|
201
|
-
| `name` * | `home` \| `search` \| `heart` \| `comment` \| `share` \| `bookmark` \| `plus` \| `bell` \| `user` \| `users` \| `mail` \| `settings` \| `camera` \| `image` \| `video` \| `star` \| `menu` \| `more` \| `close` \| `check` \| `arrow-right` \| `arrow-left` \| `chevron-down` \| `chevron-right` \| `play` \| `pin` \| `trash` \| `edit` \| `upload` \| `download` \| `lock` \| `cart` \| `chart` \| `calendar` \| `filter` \| `info` \| `send` | Icon name. |
|
|
203
|
+
| `name` * | `home` \| `search` \| `heart` \| `comment` \| `share` \| `bookmark` \| `plus` \| `bell` \| `user` \| `users` \| `mail` \| `settings` \| `camera` \| `image` \| `video` \| `star` \| `menu` \| `more` \| `close` \| `check` \| `arrow-right` \| `arrow-left` \| `chevron-down` \| `chevron-right` \| `play` \| `pin` \| `trash` \| `edit` \| `upload` \| `download` \| `lock` \| `cart` \| `chart` \| `calendar` \| `filter` \| `info` \| `send` \| `minus` \| `arrow-up` \| `arrow-down` \| `chevron-up` \| `chevron-left` \| `external` \| `link` \| `refresh` \| `login` \| `logout` \| `grid` \| `list` \| `pause` \| `check-circle` \| `warning` \| `help` \| `eye` \| `eye-off` \| `unlock` \| `key` \| `thumbs-up` \| `smile` \| `flag` \| `zap` \| `phone` \| `inbox` \| `paperclip` \| `mic` \| `volume` \| `music` \| `wifi` \| `hash` \| `at` \| `rss` \| `file` \| `folder` \| `copy` \| `book` \| `briefcase` \| `printer` \| `code` \| `clock` \| `bag` \| `tag` \| `gift` \| `credit-card` \| `dollar` \| `truck` \| `globe` \| `map` \| `sun` \| `moon` \| `cloud` \| `facebook` \| `x` \| `instagram` \| `linkedin` \| `youtube` \| `github` \| `tiktok` \| `whatsapp` \| `telegram` \| `discord` \| `pinterest` \| `reddit` \| `snapchat` \| `slack` \| `spotify` \| `apple` \| `google` \| `microsoft` \| `dribbble` \| `twitch` | Icon name. |
|
|
202
204
|
| `size` | number | Size in px (default 22). |
|
|
203
205
|
| `filled` | boolean | Solid instead of outline (heart, star, bookmark…). |
|
|
204
206
|
|
|
@@ -458,7 +460,7 @@ Button, optionally with an icon. Navigates with `go`, opens a `modal` with `open
|
|
|
458
460
|
| Prop | Type | Description |
|
|
459
461
|
|---|---|---|
|
|
460
462
|
| `label` | text | Button text. Leave out for an icon-only button. |
|
|
461
|
-
| `icon` | `home` \| `search` \| `heart` \| `comment` \| `share` \| `bookmark` \| `plus` \| `bell` \| `user` \| `users` \| `mail` \| `settings` \| `camera` \| `image` \| `video` \| `star` \| `menu` \| `more` \| `close` \| `check` \| `arrow-right` \| `arrow-left` \| `chevron-down` \| `chevron-right` \| `play` \| `pin` \| `trash` \| `edit` \| `upload` \| `download` \| `lock` \| `cart` \| `chart` \| `calendar` \| `filter` \| `info` \| `send` | Icon shown before the label. |
|
|
463
|
+
| `icon` | `home` \| `search` \| `heart` \| `comment` \| `share` \| `bookmark` \| `plus` \| `bell` \| `user` \| `users` \| `mail` \| `settings` \| `camera` \| `image` \| `video` \| `star` \| `menu` \| `more` \| `close` \| `check` \| `arrow-right` \| `arrow-left` \| `chevron-down` \| `chevron-right` \| `play` \| `pin` \| `trash` \| `edit` \| `upload` \| `download` \| `lock` \| `cart` \| `chart` \| `calendar` \| `filter` \| `info` \| `send` \| `minus` \| `arrow-up` \| `arrow-down` \| `chevron-up` \| `chevron-left` \| `external` \| `link` \| `refresh` \| `login` \| `logout` \| `grid` \| `list` \| `pause` \| `check-circle` \| `warning` \| `help` \| `eye` \| `eye-off` \| `unlock` \| `key` \| `thumbs-up` \| `smile` \| `flag` \| `zap` \| `phone` \| `inbox` \| `paperclip` \| `mic` \| `volume` \| `music` \| `wifi` \| `hash` \| `at` \| `rss` \| `file` \| `folder` \| `copy` \| `book` \| `briefcase` \| `printer` \| `code` \| `clock` \| `bag` \| `tag` \| `gift` \| `credit-card` \| `dollar` \| `truck` \| `globe` \| `map` \| `sun` \| `moon` \| `cloud` \| `facebook` \| `x` \| `instagram` \| `linkedin` \| `youtube` \| `github` \| `tiktok` \| `whatsapp` \| `telegram` \| `discord` \| `pinterest` \| `reddit` \| `snapchat` \| `slack` \| `spotify` \| `apple` \| `google` \| `microsoft` \| `dribbble` \| `twitch` | Icon shown before the label. |
|
|
462
464
|
| `primary` | boolean | Heavier border: the main action on the screen. |
|
|
463
465
|
| `disabled` | boolean | Grey out the button. |
|
|
464
466
|
| `go` | string | Id of the screen to open when clicked. |
|
package/docs/getting-started.md
CHANGED
|
@@ -8,7 +8,7 @@ cd my-wireframes
|
|
|
8
8
|
npx openink dev
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Open <http://localhost:3000
|
|
11
|
+
Open <http://localhost:3000> (or start with `npx openink dev --open` to open it for you). Edit `spec.yaml` and the page reloads on save.
|
|
12
12
|
|
|
13
13
|
**Skipping `npx`.** `npx openink` downloads the tool on first use. To pin a version per project, or to type just `openink`, install it:
|
|
14
14
|
|
package/docs/spec.md
CHANGED
|
@@ -190,6 +190,8 @@ screens:
|
|
|
190
190
|
|
|
191
191
|
`icon` blocks, `button.icon`, nav items and tab-bar items use a built-in set of hand-drawn icons (`home`, `search`, `heart`, `comment`, `send`, `bell`, `user`, `camera`, …). The full list is at the end of the [block reference](blocks.md#icons). An unknown name is an error with a "did you mean" hint.
|
|
192
192
|
|
|
193
|
+
The set includes brand logos (`google`, `apple`, `github`, `facebook`, `x`, `linkedin`, …) for sign-in buttons, share bars and footers. Brand names and logos are trademarks of their owners. Open Ink's brand icons are simplified sketches for wireframes only and do not imply endorsement. Use each brand's official assets in production.
|
|
194
|
+
|
|
193
195
|
## Quoting text with commas
|
|
194
196
|
|
|
195
197
|
Inside `{ ... }` a comma ends the value, so YAML reads `{ type: text, text: Hello, world }` as text `Hello` plus a stray key `world`. Wrap such text in quotes: `text: "Hello, world"`. `validate` warns when it sees this.
|
package/package.json
CHANGED
package/schema/spec.schema.json
CHANGED
|
@@ -275,7 +275,80 @@
|
|
|
275
275
|
"calendar",
|
|
276
276
|
"filter",
|
|
277
277
|
"info",
|
|
278
|
-
"send"
|
|
278
|
+
"send",
|
|
279
|
+
"minus",
|
|
280
|
+
"arrow-up",
|
|
281
|
+
"arrow-down",
|
|
282
|
+
"chevron-up",
|
|
283
|
+
"chevron-left",
|
|
284
|
+
"external",
|
|
285
|
+
"link",
|
|
286
|
+
"refresh",
|
|
287
|
+
"login",
|
|
288
|
+
"logout",
|
|
289
|
+
"grid",
|
|
290
|
+
"list",
|
|
291
|
+
"pause",
|
|
292
|
+
"check-circle",
|
|
293
|
+
"warning",
|
|
294
|
+
"help",
|
|
295
|
+
"eye",
|
|
296
|
+
"eye-off",
|
|
297
|
+
"unlock",
|
|
298
|
+
"key",
|
|
299
|
+
"thumbs-up",
|
|
300
|
+
"smile",
|
|
301
|
+
"flag",
|
|
302
|
+
"zap",
|
|
303
|
+
"phone",
|
|
304
|
+
"inbox",
|
|
305
|
+
"paperclip",
|
|
306
|
+
"mic",
|
|
307
|
+
"volume",
|
|
308
|
+
"music",
|
|
309
|
+
"wifi",
|
|
310
|
+
"hash",
|
|
311
|
+
"at",
|
|
312
|
+
"rss",
|
|
313
|
+
"file",
|
|
314
|
+
"folder",
|
|
315
|
+
"copy",
|
|
316
|
+
"book",
|
|
317
|
+
"briefcase",
|
|
318
|
+
"printer",
|
|
319
|
+
"code",
|
|
320
|
+
"clock",
|
|
321
|
+
"bag",
|
|
322
|
+
"tag",
|
|
323
|
+
"gift",
|
|
324
|
+
"credit-card",
|
|
325
|
+
"dollar",
|
|
326
|
+
"truck",
|
|
327
|
+
"globe",
|
|
328
|
+
"map",
|
|
329
|
+
"sun",
|
|
330
|
+
"moon",
|
|
331
|
+
"cloud",
|
|
332
|
+
"facebook",
|
|
333
|
+
"x",
|
|
334
|
+
"instagram",
|
|
335
|
+
"linkedin",
|
|
336
|
+
"youtube",
|
|
337
|
+
"github",
|
|
338
|
+
"tiktok",
|
|
339
|
+
"whatsapp",
|
|
340
|
+
"telegram",
|
|
341
|
+
"discord",
|
|
342
|
+
"pinterest",
|
|
343
|
+
"reddit",
|
|
344
|
+
"snapchat",
|
|
345
|
+
"slack",
|
|
346
|
+
"spotify",
|
|
347
|
+
"apple",
|
|
348
|
+
"google",
|
|
349
|
+
"microsoft",
|
|
350
|
+
"dribbble",
|
|
351
|
+
"twitch"
|
|
279
352
|
]
|
|
280
353
|
},
|
|
281
354
|
"label": {
|
|
@@ -1011,7 +1084,80 @@
|
|
|
1011
1084
|
"calendar",
|
|
1012
1085
|
"filter",
|
|
1013
1086
|
"info",
|
|
1014
|
-
"send"
|
|
1087
|
+
"send",
|
|
1088
|
+
"minus",
|
|
1089
|
+
"arrow-up",
|
|
1090
|
+
"arrow-down",
|
|
1091
|
+
"chevron-up",
|
|
1092
|
+
"chevron-left",
|
|
1093
|
+
"external",
|
|
1094
|
+
"link",
|
|
1095
|
+
"refresh",
|
|
1096
|
+
"login",
|
|
1097
|
+
"logout",
|
|
1098
|
+
"grid",
|
|
1099
|
+
"list",
|
|
1100
|
+
"pause",
|
|
1101
|
+
"check-circle",
|
|
1102
|
+
"warning",
|
|
1103
|
+
"help",
|
|
1104
|
+
"eye",
|
|
1105
|
+
"eye-off",
|
|
1106
|
+
"unlock",
|
|
1107
|
+
"key",
|
|
1108
|
+
"thumbs-up",
|
|
1109
|
+
"smile",
|
|
1110
|
+
"flag",
|
|
1111
|
+
"zap",
|
|
1112
|
+
"phone",
|
|
1113
|
+
"inbox",
|
|
1114
|
+
"paperclip",
|
|
1115
|
+
"mic",
|
|
1116
|
+
"volume",
|
|
1117
|
+
"music",
|
|
1118
|
+
"wifi",
|
|
1119
|
+
"hash",
|
|
1120
|
+
"at",
|
|
1121
|
+
"rss",
|
|
1122
|
+
"file",
|
|
1123
|
+
"folder",
|
|
1124
|
+
"copy",
|
|
1125
|
+
"book",
|
|
1126
|
+
"briefcase",
|
|
1127
|
+
"printer",
|
|
1128
|
+
"code",
|
|
1129
|
+
"clock",
|
|
1130
|
+
"bag",
|
|
1131
|
+
"tag",
|
|
1132
|
+
"gift",
|
|
1133
|
+
"credit-card",
|
|
1134
|
+
"dollar",
|
|
1135
|
+
"truck",
|
|
1136
|
+
"globe",
|
|
1137
|
+
"map",
|
|
1138
|
+
"sun",
|
|
1139
|
+
"moon",
|
|
1140
|
+
"cloud",
|
|
1141
|
+
"facebook",
|
|
1142
|
+
"x",
|
|
1143
|
+
"instagram",
|
|
1144
|
+
"linkedin",
|
|
1145
|
+
"youtube",
|
|
1146
|
+
"github",
|
|
1147
|
+
"tiktok",
|
|
1148
|
+
"whatsapp",
|
|
1149
|
+
"telegram",
|
|
1150
|
+
"discord",
|
|
1151
|
+
"pinterest",
|
|
1152
|
+
"reddit",
|
|
1153
|
+
"snapchat",
|
|
1154
|
+
"slack",
|
|
1155
|
+
"spotify",
|
|
1156
|
+
"apple",
|
|
1157
|
+
"google",
|
|
1158
|
+
"microsoft",
|
|
1159
|
+
"dribbble",
|
|
1160
|
+
"twitch"
|
|
1015
1161
|
],
|
|
1016
1162
|
"description": "Icon name."
|
|
1017
1163
|
},
|
|
@@ -1996,7 +2142,80 @@
|
|
|
1996
2142
|
"calendar",
|
|
1997
2143
|
"filter",
|
|
1998
2144
|
"info",
|
|
1999
|
-
"send"
|
|
2145
|
+
"send",
|
|
2146
|
+
"minus",
|
|
2147
|
+
"arrow-up",
|
|
2148
|
+
"arrow-down",
|
|
2149
|
+
"chevron-up",
|
|
2150
|
+
"chevron-left",
|
|
2151
|
+
"external",
|
|
2152
|
+
"link",
|
|
2153
|
+
"refresh",
|
|
2154
|
+
"login",
|
|
2155
|
+
"logout",
|
|
2156
|
+
"grid",
|
|
2157
|
+
"list",
|
|
2158
|
+
"pause",
|
|
2159
|
+
"check-circle",
|
|
2160
|
+
"warning",
|
|
2161
|
+
"help",
|
|
2162
|
+
"eye",
|
|
2163
|
+
"eye-off",
|
|
2164
|
+
"unlock",
|
|
2165
|
+
"key",
|
|
2166
|
+
"thumbs-up",
|
|
2167
|
+
"smile",
|
|
2168
|
+
"flag",
|
|
2169
|
+
"zap",
|
|
2170
|
+
"phone",
|
|
2171
|
+
"inbox",
|
|
2172
|
+
"paperclip",
|
|
2173
|
+
"mic",
|
|
2174
|
+
"volume",
|
|
2175
|
+
"music",
|
|
2176
|
+
"wifi",
|
|
2177
|
+
"hash",
|
|
2178
|
+
"at",
|
|
2179
|
+
"rss",
|
|
2180
|
+
"file",
|
|
2181
|
+
"folder",
|
|
2182
|
+
"copy",
|
|
2183
|
+
"book",
|
|
2184
|
+
"briefcase",
|
|
2185
|
+
"printer",
|
|
2186
|
+
"code",
|
|
2187
|
+
"clock",
|
|
2188
|
+
"bag",
|
|
2189
|
+
"tag",
|
|
2190
|
+
"gift",
|
|
2191
|
+
"credit-card",
|
|
2192
|
+
"dollar",
|
|
2193
|
+
"truck",
|
|
2194
|
+
"globe",
|
|
2195
|
+
"map",
|
|
2196
|
+
"sun",
|
|
2197
|
+
"moon",
|
|
2198
|
+
"cloud",
|
|
2199
|
+
"facebook",
|
|
2200
|
+
"x",
|
|
2201
|
+
"instagram",
|
|
2202
|
+
"linkedin",
|
|
2203
|
+
"youtube",
|
|
2204
|
+
"github",
|
|
2205
|
+
"tiktok",
|
|
2206
|
+
"whatsapp",
|
|
2207
|
+
"telegram",
|
|
2208
|
+
"discord",
|
|
2209
|
+
"pinterest",
|
|
2210
|
+
"reddit",
|
|
2211
|
+
"snapchat",
|
|
2212
|
+
"slack",
|
|
2213
|
+
"spotify",
|
|
2214
|
+
"apple",
|
|
2215
|
+
"google",
|
|
2216
|
+
"microsoft",
|
|
2217
|
+
"dribbble",
|
|
2218
|
+
"twitch"
|
|
2000
2219
|
],
|
|
2001
2220
|
"description": "Icon shown before the label."
|
|
2002
2221
|
},
|
|
@@ -2376,7 +2595,80 @@
|
|
|
2376
2595
|
"calendar",
|
|
2377
2596
|
"filter",
|
|
2378
2597
|
"info",
|
|
2379
|
-
"send"
|
|
2598
|
+
"send",
|
|
2599
|
+
"minus",
|
|
2600
|
+
"arrow-up",
|
|
2601
|
+
"arrow-down",
|
|
2602
|
+
"chevron-up",
|
|
2603
|
+
"chevron-left",
|
|
2604
|
+
"external",
|
|
2605
|
+
"link",
|
|
2606
|
+
"refresh",
|
|
2607
|
+
"login",
|
|
2608
|
+
"logout",
|
|
2609
|
+
"grid",
|
|
2610
|
+
"list",
|
|
2611
|
+
"pause",
|
|
2612
|
+
"check-circle",
|
|
2613
|
+
"warning",
|
|
2614
|
+
"help",
|
|
2615
|
+
"eye",
|
|
2616
|
+
"eye-off",
|
|
2617
|
+
"unlock",
|
|
2618
|
+
"key",
|
|
2619
|
+
"thumbs-up",
|
|
2620
|
+
"smile",
|
|
2621
|
+
"flag",
|
|
2622
|
+
"zap",
|
|
2623
|
+
"phone",
|
|
2624
|
+
"inbox",
|
|
2625
|
+
"paperclip",
|
|
2626
|
+
"mic",
|
|
2627
|
+
"volume",
|
|
2628
|
+
"music",
|
|
2629
|
+
"wifi",
|
|
2630
|
+
"hash",
|
|
2631
|
+
"at",
|
|
2632
|
+
"rss",
|
|
2633
|
+
"file",
|
|
2634
|
+
"folder",
|
|
2635
|
+
"copy",
|
|
2636
|
+
"book",
|
|
2637
|
+
"briefcase",
|
|
2638
|
+
"printer",
|
|
2639
|
+
"code",
|
|
2640
|
+
"clock",
|
|
2641
|
+
"bag",
|
|
2642
|
+
"tag",
|
|
2643
|
+
"gift",
|
|
2644
|
+
"credit-card",
|
|
2645
|
+
"dollar",
|
|
2646
|
+
"truck",
|
|
2647
|
+
"globe",
|
|
2648
|
+
"map",
|
|
2649
|
+
"sun",
|
|
2650
|
+
"moon",
|
|
2651
|
+
"cloud",
|
|
2652
|
+
"facebook",
|
|
2653
|
+
"x",
|
|
2654
|
+
"instagram",
|
|
2655
|
+
"linkedin",
|
|
2656
|
+
"youtube",
|
|
2657
|
+
"github",
|
|
2658
|
+
"tiktok",
|
|
2659
|
+
"whatsapp",
|
|
2660
|
+
"telegram",
|
|
2661
|
+
"discord",
|
|
2662
|
+
"pinterest",
|
|
2663
|
+
"reddit",
|
|
2664
|
+
"snapchat",
|
|
2665
|
+
"slack",
|
|
2666
|
+
"spotify",
|
|
2667
|
+
"apple",
|
|
2668
|
+
"google",
|
|
2669
|
+
"microsoft",
|
|
2670
|
+
"dribbble",
|
|
2671
|
+
"twitch"
|
|
2380
2672
|
]
|
|
2381
2673
|
},
|
|
2382
2674
|
"label": {
|
package/src/build.js
CHANGED
|
@@ -19,17 +19,46 @@ export class SpecError extends Error {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
/**
|
|
22
|
+
/** The source range of a validator path such as `screens[3].blocks[2].type`, or of its nearest existing parent. */
|
|
23
|
+
function rangeAt(doc, issuePath) {
|
|
24
|
+
const keys = (issuePath.match(/[^.[\]]+/g) || []).map((k) => (/^\d+$/.test(k) ? Number(k) : k));
|
|
25
|
+
let node = doc.contents;
|
|
26
|
+
let range = node?.range;
|
|
27
|
+
for (const key of keys) {
|
|
28
|
+
if (YAML.isAlias(node)) node = node.resolve(doc);
|
|
29
|
+
if (YAML.isMap(node)) {
|
|
30
|
+
// point at the key (`type:`), which is where an editor should put the cursor
|
|
31
|
+
const pair = node.items.find((p) => String(YAML.isScalar(p.key) ? p.key.value : p.key) === String(key));
|
|
32
|
+
if (!pair) break;
|
|
33
|
+
range = pair.key?.range ?? range;
|
|
34
|
+
node = pair.value;
|
|
35
|
+
} else if (YAML.isSeq(node) && typeof key === "number" && node.items[key]) {
|
|
36
|
+
node = node.items[key];
|
|
37
|
+
range = node.range ?? range;
|
|
38
|
+
} else break;
|
|
39
|
+
}
|
|
40
|
+
return range;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Find and parse the spec in a project directory.
|
|
45
|
+
* `locate(issue)` returns the issue with `loc` set to `file:line:col` (relative to the working directory).
|
|
46
|
+
*/
|
|
23
47
|
export function loadSpec(dir = ".") {
|
|
24
48
|
const file = SPEC_FILES.map((f) => path.join(dir, f)).find(fs.existsSync);
|
|
25
49
|
if (!file) throw new SpecError(`No spec.yaml found in ${path.resolve(dir)}. Run \`openink init\` to create one.`);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
50
|
+
const lineCounter = new YAML.LineCounter();
|
|
51
|
+
const doc = YAML.parseDocument(fs.readFileSync(file, "utf8"), { lineCounter });
|
|
52
|
+
if (doc.errors.length) throw new SpecError(`${path.basename(file)} is not valid YAML: ${doc.errors[0].message}`);
|
|
53
|
+
const spec = doc.toJS();
|
|
54
|
+
const name = path.relative(process.cwd(), file) || path.basename(file);
|
|
55
|
+
const locate = (issue) => {
|
|
56
|
+
const range = rangeAt(doc, issue.path || "");
|
|
57
|
+
if (!range) return issue;
|
|
58
|
+
const { line, col } = lineCounter.linePos(range[0]);
|
|
59
|
+
return { ...issue, loc: `${name}:${line}:${col}` };
|
|
60
|
+
};
|
|
61
|
+
return { spec, file, locate };
|
|
33
62
|
}
|
|
34
63
|
|
|
35
64
|
let runtimeCache;
|
|
@@ -44,7 +73,7 @@ async function bundleRuntime() {
|
|
|
44
73
|
/**
|
|
45
74
|
* Build a project into static files.
|
|
46
75
|
* @param {{ dir?: string, out?: string, dev?: boolean, theme?: string }} [opts] `theme` overrides the spec's theme
|
|
47
|
-
* @returns {Promise<{ outDir: string, spec: object, warnings: {path:string,message:string}[] }>}
|
|
76
|
+
* @returns {Promise<{ outDir: string, spec: object, warnings: {path:string,message:string,loc?:string}[] }>}
|
|
48
77
|
*/
|
|
49
78
|
export async function build({ dir = ".", out = "dist", dev = false, theme } = {}) {
|
|
50
79
|
const projectDir = path.resolve(dir);
|
|
@@ -53,14 +82,16 @@ export async function build({ dir = ".", out = "dist", dev = false, theme } = {}
|
|
|
53
82
|
throw new Error(`Output directory ${outDir} would contain the project itself. Choose a different --out.`);
|
|
54
83
|
}
|
|
55
84
|
|
|
56
|
-
const { spec } = loadSpec(projectDir);
|
|
85
|
+
const { spec, locate } = loadSpec(projectDir);
|
|
57
86
|
if (theme) spec.theme = theme;
|
|
58
|
-
const
|
|
87
|
+
const result = validate(spec);
|
|
88
|
+
const errors = result.errors;
|
|
59
89
|
const customTheme = typeof spec?.theme === "string" && !isPreset(spec.theme) && spec.theme.endsWith(".css");
|
|
60
90
|
if (customTheme && !fs.existsSync(path.join(projectDir, spec.theme))) {
|
|
61
91
|
errors.push({ path: "theme", message: `Theme file "${spec.theme}" not found next to the spec.` });
|
|
62
92
|
}
|
|
63
|
-
if (errors.length) throw new SpecError(`${errors.length} problem${errors.length > 1 ? "s" : ""} in the spec`, errors);
|
|
93
|
+
if (errors.length) throw new SpecError(`${errors.length} problem${errors.length > 1 ? "s" : ""} in the spec`, errors.map(locate));
|
|
94
|
+
const warnings = result.warnings.map(locate);
|
|
64
95
|
|
|
65
96
|
fs.rmSync(outDir, { recursive: true, force: true });
|
|
66
97
|
fs.mkdirSync(outDir, { recursive: true });
|
package/src/cli.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
1
2
|
import fs from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
@@ -24,6 +25,7 @@ Commands:
|
|
|
24
25
|
Options:
|
|
25
26
|
--out <dir> Output directory, relative to the project (default: dist)
|
|
26
27
|
--port <n> Dev server port (default: 3000; the next free port if taken)
|
|
28
|
+
--open Open the prototype in the default browser when the dev server starts
|
|
27
29
|
--theme <name> Try a colour theme without editing the spec: sketch, color, pastel, blueprint, dark
|
|
28
30
|
-v, --version Print the version
|
|
29
31
|
-h, --help Show this help
|
|
@@ -40,6 +42,7 @@ function parse(argv) {
|
|
|
40
42
|
const a = argv[i];
|
|
41
43
|
if (a === "-h" || a === "--help") opts.help = true;
|
|
42
44
|
else if (a === "-v" || a === "--version") opts.version = true;
|
|
45
|
+
else if (a === "--open") opts.open = true;
|
|
43
46
|
else if (a === "--out" || a === "--port" || a === "--theme") {
|
|
44
47
|
if (argv[i + 1] === undefined) throw new Error(`${a} needs a value`);
|
|
45
48
|
opts[a.slice(2)] = argv[++i];
|
|
@@ -49,8 +52,9 @@ function parse(argv) {
|
|
|
49
52
|
return opts;
|
|
50
53
|
}
|
|
51
54
|
|
|
55
|
+
// `loc` (file:line:col) comes first, so terminals and editors can open the spot with a click
|
|
52
56
|
const printIssues = (list, color, label) =>
|
|
53
|
-
list.forEach((i) => console.error(` ${color(label)} ${dim(i.path || "spec")}: ${i.message}`));
|
|
57
|
+
list.forEach((i) => console.error(` ${color(label)} ${i.loc ? `${i.loc} ` : ""}${dim(i.path || "spec")}: ${i.message}`));
|
|
54
58
|
|
|
55
59
|
function init(dir = ".") {
|
|
56
60
|
const target = path.resolve(dir);
|
|
@@ -65,6 +69,18 @@ function init(dir = ".") {
|
|
|
65
69
|
console.log(green("✓") + ` Created ${rel || "."}/spec.yaml\n\nNext:\n ${rel ? `cd ${rel} && ` : ""}npx openink dev`);
|
|
66
70
|
}
|
|
67
71
|
|
|
72
|
+
/** The command that opens a URL in the default browser on this platform. */
|
|
73
|
+
export const openCommand = (url, platform = process.platform) =>
|
|
74
|
+
platform === "win32" ? ["cmd", ["/c", "start", '""', url]] : [platform === "darwin" ? "open" : "xdg-open", [url]];
|
|
75
|
+
|
|
76
|
+
// a browser that fails to open is only worth a hint: the server keeps running
|
|
77
|
+
function openBrowser(url) {
|
|
78
|
+
const [cmd, args] = openCommand(url);
|
|
79
|
+
const child = spawn(cmd, args, { stdio: "ignore", detached: true, windowsVerbatimArguments: process.platform === "win32" });
|
|
80
|
+
child.on("error", () => console.error(yellow(`Could not open a browser. Open ${url} yourself.`)));
|
|
81
|
+
child.unref();
|
|
82
|
+
}
|
|
83
|
+
|
|
68
84
|
/** @param {string[]} argv */
|
|
69
85
|
export async function run(argv) {
|
|
70
86
|
const opts = parse(argv);
|
|
@@ -81,8 +97,9 @@ export async function run(argv) {
|
|
|
81
97
|
return console.log(blocksMarkdown());
|
|
82
98
|
|
|
83
99
|
case "validate": {
|
|
84
|
-
const { spec } = loadSpec(dir);
|
|
85
|
-
const
|
|
100
|
+
const { spec, locate } = loadSpec(dir);
|
|
101
|
+
const result = validate(spec);
|
|
102
|
+
const errors = result.errors.map(locate), warnings = result.warnings.map(locate);
|
|
86
103
|
printIssues(warnings, yellow, "warn ");
|
|
87
104
|
printIssues(errors, red, "error");
|
|
88
105
|
if (errors.length) throw new SpecError(`${errors.length} error${errors.length > 1 ? "s" : ""}`);
|
|
@@ -109,8 +126,10 @@ export async function run(argv) {
|
|
|
109
126
|
port: opts.port ? +opts.port : 3000,
|
|
110
127
|
theme: opts.theme,
|
|
111
128
|
onIssues: (e) => { console.error(red("✗ " + e.message)); printIssues(e.issues, red, "error"); },
|
|
129
|
+
onWarnings: (warnings) => printIssues(warnings, yellow, "warn "),
|
|
112
130
|
});
|
|
113
131
|
console.log(green("✓") + ` Serving ${server.url} ${dim("(Ctrl+C to stop)")}`);
|
|
132
|
+
if (opts.open) openBrowser(server.url);
|
|
114
133
|
process.on("SIGINT", () => { server.close(); process.exit(0); });
|
|
115
134
|
return new Promise(() => {}); // keep running
|
|
116
135
|
}
|
package/src/dev.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import http from "node:http";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { build, SpecError } from "./build.js";
|
|
5
|
+
import { devErrorPage } from "./render/page.js";
|
|
5
6
|
|
|
6
7
|
const MIME = {
|
|
7
8
|
".html": "text/html; charset=utf-8", ".js": "text/javascript", ".css": "text/css", ".json": "application/json",
|
|
@@ -11,21 +12,28 @@ const MIME = {
|
|
|
11
12
|
|
|
12
13
|
const MAX_PORT_TRIES = 10;
|
|
13
14
|
|
|
15
|
+
/** A failed build as plain text, for the overlay in the browser. */
|
|
16
|
+
const describe = (e) => [e.message, ...(e.issues || []).map((i) => ` ${i.loc ? `${i.loc} ` : ""}${i.path || "spec"}: ${i.message}`)].join("\n");
|
|
17
|
+
|
|
14
18
|
/**
|
|
15
19
|
* Serve the project with live reload: rebuilds when files change and the browser refreshes itself.
|
|
16
|
-
* @param {{ dir?: string, out?: string, port?: number, theme?: string, log?: (msg: string) => void, onIssues?: (e: SpecError) => void }} [opts]
|
|
20
|
+
* @param {{ dir?: string, out?: string, port?: number, theme?: string, log?: (msg: string) => void, onIssues?: (e: SpecError) => void, onWarnings?: (warnings: {path:string,message:string}[]) => void }} [opts]
|
|
17
21
|
*/
|
|
18
|
-
export async function dev({ dir = ".", out = ".openink-dev", port = 3000, theme, log = console.log, onIssues = () => {} } = {}) {
|
|
22
|
+
export async function dev({ dir = ".", out = ".openink-dev", port = 3000, theme, log = console.log, onIssues = () => {}, onWarnings = () => {} } = {}) {
|
|
19
23
|
const projectDir = path.resolve(dir);
|
|
20
24
|
const outDir = path.resolve(projectDir, out);
|
|
21
25
|
let version = String(Date.now());
|
|
26
|
+
let error = null; // the last build's failure, shown in the browser until a build succeeds
|
|
22
27
|
|
|
23
28
|
const rebuild = async () => {
|
|
24
29
|
try {
|
|
25
30
|
const { warnings } = await build({ dir, out, dev: true, theme });
|
|
26
31
|
version = String(Date.now());
|
|
32
|
+
error = null;
|
|
27
33
|
log(`✓ built${warnings.length ? ` (${warnings.length} warning${warnings.length > 1 ? "s" : ""})` : ""}`);
|
|
34
|
+
if (warnings.length) onWarnings(warnings);
|
|
28
35
|
} catch (e) {
|
|
36
|
+
error = describe(e);
|
|
29
37
|
if (e instanceof SpecError) onIssues(e);
|
|
30
38
|
else log(`✗ ${e.message}`);
|
|
31
39
|
}
|
|
@@ -34,8 +42,12 @@ export async function dev({ dir = ".", out = ".openink-dev", port = 3000, theme,
|
|
|
34
42
|
|
|
35
43
|
const server = http.createServer((req, res) => {
|
|
36
44
|
const url = decodeURIComponent(new URL(req.url, "http://x").pathname);
|
|
37
|
-
if (url === "/__version")
|
|
45
|
+
if (url === "/__version") {
|
|
46
|
+
return void res.writeHead(200, { "Content-Type": MIME[".json"], "Cache-Control": "no-store" }).end(JSON.stringify({ version, error }));
|
|
47
|
+
}
|
|
38
48
|
const file = path.join(outDir, url === "/" ? "index.html" : url);
|
|
49
|
+
// no good build yet: a page that shows the errors and reloads once the spec builds
|
|
50
|
+
if (url === "/" && !fs.existsSync(file)) return void res.writeHead(200, { "Content-Type": MIME[".html"], "Cache-Control": "no-store" }).end(devErrorPage());
|
|
39
51
|
if (!file.startsWith(outDir) || !fs.existsSync(file) || fs.statSync(file).isDirectory()) return void res.writeHead(404).end("Not found");
|
|
40
52
|
res.writeHead(200, { "Content-Type": MIME[path.extname(file)] || "application/octet-stream", "Cache-Control": "no-store" });
|
|
41
53
|
fs.createReadStream(file).pipe(res);
|
|
@@ -64,5 +76,5 @@ export async function dev({ dir = ".", out = ".openink-dev", port = 3000, theme,
|
|
|
64
76
|
});
|
|
65
77
|
|
|
66
78
|
const close = () => { watcher.close(); server.close(); fs.rmSync(outDir, { recursive: true, force: true }); };
|
|
67
|
-
return { url: `http://localhost:${port}`, close };
|
|
79
|
+
return { url: `http://localhost:${server.address().port}`, close };
|
|
68
80
|
}
|
package/src/icons.js
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
// Each icon is a list of SVG path strings. `filled` icons can be solid when the `filled` attribute is set.
|
|
4
4
|
|
|
5
5
|
const dot = (x, y, r = 1) => `M${x - r} ${y}a${r} ${r} 0 1 0 ${2 * r} 0a${r} ${r} 0 1 0 ${-2 * r} 0`;
|
|
6
|
+
// A rectangle with rounded corners.
|
|
7
|
+
const box = (x, y, w, h, r) =>
|
|
8
|
+
`M${x + r} ${y}h${w - 2 * r}a${r} ${r} 0 0 1 ${r} ${r}v${h - 2 * r}a${r} ${r} 0 0 1 ${-r} ${r}h${2 * r - w}a${r} ${r} 0 0 1 ${-r} ${-r}v${2 * r - h}a${r} ${r} 0 0 1 ${r} ${-r}z`;
|
|
6
9
|
|
|
7
10
|
export const ICONS = {
|
|
8
11
|
home: ["M3 11.5L12 3l9 8.5", "M5.5 10v10.5h13V10", "M10 20.5v-6h4v6"],
|
|
@@ -42,6 +45,113 @@ export const ICONS = {
|
|
|
42
45
|
filter: ["M3 5h18l-7 8v6l-4-2v-4z"],
|
|
43
46
|
info: ["M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18z", "M12 11v6", dot(12, 7.5, 0.6)],
|
|
44
47
|
send: ["M3 11l18-8-8 18-2-8z"],
|
|
48
|
+
|
|
49
|
+
// arrows and controls
|
|
50
|
+
minus: ["M4 12h16"],
|
|
51
|
+
"arrow-up": ["M12 20V4M6 10l6-6 6 6"],
|
|
52
|
+
"arrow-down": ["M12 4v16M6 14l6 6 6-6"],
|
|
53
|
+
"chevron-up": ["M6 15l6-6 6 6"],
|
|
54
|
+
"chevron-left": ["M15 6l-6 6 6 6"],
|
|
55
|
+
external: ["M14 4h6v6", "M20 4l-9 9", "M18 14v6H4V6h6"],
|
|
56
|
+
link: ["M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1", "M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1"],
|
|
57
|
+
refresh: ["M20 12a8 8 0 1 1-2.3-5.7", "M20 3.5V8h-4.5"],
|
|
58
|
+
login: ["M15 4h5v16h-5", "M10 8l4 4-4 4", "M14 12H3"],
|
|
59
|
+
logout: ["M9 4H4v16h5", "M16 8l4 4-4 4", "M20 12H9"],
|
|
60
|
+
grid: ["M4 4h7v7H4z", "M13 4h7v7h-7z", "M4 13h7v7H4z", "M13 13h7v7h-7z"],
|
|
61
|
+
list: ["M9 6h12M9 12h12M9 18h12", dot(4.5, 6), dot(4.5, 12), dot(4.5, 18)],
|
|
62
|
+
pause: ["M7 4h3v16H7z", "M14 4h3v16h-3z"],
|
|
63
|
+
|
|
64
|
+
// status
|
|
65
|
+
"check-circle": [dot(12, 12, 9), "M8 12.5l3 3 5-6"],
|
|
66
|
+
warning: ["M12 3L2 20h20z", "M12 10v4", dot(12, 17, 0.6)],
|
|
67
|
+
help: [dot(12, 12, 9), "M9.5 9a2.5 2.5 0 1 1 3.5 2.3c-.7.3-1 .9-1 1.7v.5", dot(12, 17, 0.6)],
|
|
68
|
+
eye: ["M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z", dot(12, 12, 3)],
|
|
69
|
+
"eye-off": ["M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z", dot(12, 12, 3), "M4 4l16 16"],
|
|
70
|
+
unlock: ["M6 11h12v10H6z", "M8.5 11V8a3.5 3.5 0 0 1 6.8-1.2"],
|
|
71
|
+
key: [dot(7.5, 16.5, 4), "M10.5 13.5L20 4", "M16.5 7.5l2.5 2.5", "M18.5 5.5l2 2"],
|
|
72
|
+
"thumbs-up": ["M7 10v11H3V10z", "M7 10l4-8a2.5 2.5 0 0 1 3 2.5V9h5.5a2 2 0 0 1 2 2.3l-1.4 8A2 2 0 0 1 18 21H7"],
|
|
73
|
+
smile: [dot(12, 12, 9), "M8 14.5a5 5 0 0 0 8 0", dot(9, 9.5, 0.6), dot(15, 9.5, 0.6)],
|
|
74
|
+
flag: ["M5 21V4", "M5 4h12l-2.5 4.5L17 13H5"],
|
|
75
|
+
zap: ["M13 2L4 14h7l-1 8 9-12h-7z"],
|
|
76
|
+
|
|
77
|
+
// communication and media
|
|
78
|
+
phone: ["M5 4h4l2 5-2.5 1.5a11 11 0 0 0 5 5L15 13l5 2v4a2 2 0 0 1-2 2A16 16 0 0 1 3 6a2 2 0 0 1 2-2z"],
|
|
79
|
+
inbox: ["M3 13l3-8h12l3 8v6H3z", "M3 13h5l1 3h6l1-3h5"],
|
|
80
|
+
paperclip: ["M20 11l-8.5 8.5a5 5 0 0 1-7-7L13 4a3.5 3.5 0 0 1 5 5l-8.5 8.5a2 2 0 0 1-3-3L14 7"],
|
|
81
|
+
mic: [box(9, 2, 6, 12, 3), "M5 11a7 7 0 0 0 14 0", "M12 18v4M8 22h8"],
|
|
82
|
+
volume: ["M4 9h4l5-4v14l-5-4H4z", "M16 9a4 4 0 0 1 0 6", "M18.5 6.5a7.5 7.5 0 0 1 0 11"],
|
|
83
|
+
music: ["M9 18V5l12-2v13", dot(6, 18, 3), dot(18, 16, 3)],
|
|
84
|
+
wifi: ["M2 8.5a15 15 0 0 1 20 0", "M5 12a10 10 0 0 1 14 0", "M8.5 15.5a5 5 0 0 1 7 0", dot(12, 19)],
|
|
85
|
+
hash: ["M4 9h16M4 15h16", "M10 3L8 21M16 3l-2 18"],
|
|
86
|
+
at: [dot(12, 12, 4), "M16 8v5a2.5 2.5 0 0 0 5 0v-1a9 9 0 1 0-3.5 7.1"],
|
|
87
|
+
rss: ["M5 11a8 8 0 0 1 8 8", "M5 5a14 14 0 0 1 14 14", dot(6, 18, 1.2)],
|
|
88
|
+
|
|
89
|
+
// files and work
|
|
90
|
+
file: ["M5 2h9l5 5v15H5z", "M14 2v5h5"],
|
|
91
|
+
folder: ["M3 5h6l2 3h10v12H3z"],
|
|
92
|
+
copy: ["M9 9h11v11H9z", "M5 15H4V4h11v1"],
|
|
93
|
+
book: ["M4 4h6a2 2 0 0 1 2 2v14a2 2 0 0 0-2-2H4z", "M20 4h-6a2 2 0 0 0-2 2v14a2 2 0 0 1 2-2h6z"],
|
|
94
|
+
briefcase: ["M3 7h18v13H3z", "M9 7V4h6v3", "M3 13h18"],
|
|
95
|
+
printer: ["M6 9V3h12v6", "M6 17H3V9h18v8h-3", "M6 14h12v7H6z"],
|
|
96
|
+
code: ["M8 7l-5 5 5 5", "M16 7l5 5-5 5", "M14 4l-4 16"],
|
|
97
|
+
clock: [dot(12, 12, 9), "M12 7v5l3.5 2"],
|
|
98
|
+
|
|
99
|
+
// shopping and places
|
|
100
|
+
bag: ["M4 7h16l-1 14H5z", "M8.5 10V6a3.5 3.5 0 0 1 7 0v4"],
|
|
101
|
+
tag: ["M3 3h8l10 10-8 8L3 11z", dot(7.5, 7.5, 1.5)],
|
|
102
|
+
gift: ["M3 8h18v4H3z", "M5 12v9h14v-9", "M12 8v13", "M12 8C10 4 6.5 4.5 7.5 7c.5 1 2.5 1 4.5 1z", "M12 8c2-4 5.5-3.5 4.5-1-.5 1-2.5 1-4.5 1z"],
|
|
103
|
+
"credit-card": ["M2 6h20v13H2z", "M2 10h20", "M5 15h4"],
|
|
104
|
+
dollar: ["M12 2v20", "M17 6H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"],
|
|
105
|
+
truck: ["M2 5h12v11H2z", "M14 9h4l4 4v3h-8", dot(6, 18, 2), dot(18, 18, 2)],
|
|
106
|
+
globe: [dot(12, 12, 9), "M3 12h18", "M12 3a4.5 9 0 0 1 0 18a4.5 9 0 0 1 0-18"],
|
|
107
|
+
map: ["M3 6l6-2 6 2 6-2v14l-6 2-6-2-6 2z", "M9 4v14M15 6v14"],
|
|
108
|
+
|
|
109
|
+
// weather
|
|
110
|
+
sun: [dot(12, 12, 4), "M12 2v2M12 20v2M2 12h2M20 12h2", "M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"],
|
|
111
|
+
moon: ["M20 14.5A8.5 8.5 0 1 1 9.5 4a7 7 0 0 0 10.5 10.5z"],
|
|
112
|
+
cloud: ["M7 19a5 5 0 0 1-.5-10A6 6 0 0 1 18 9a5 5 0 0 1 0 10z"],
|
|
113
|
+
|
|
114
|
+
// brands: simplified outlines for "sign in with" buttons, share bars and footers. The names and logos are
|
|
115
|
+
// trademarks of their owners; these are wireframe sketches drawn for Open Ink, not the official artwork.
|
|
116
|
+
facebook: [dot(12, 12, 9), "M15.5 8H14a2 2 0 0 0-2 2v11", "M9.5 13h5"],
|
|
117
|
+
x: ["M4 4h4.5L20 20h-4.5z", "M20 4l-6.5 7.5M10.5 13.5L4 20"],
|
|
118
|
+
instagram: [box(3, 3, 18, 18, 5), dot(12, 12, 4), dot(17, 7, 0.7)],
|
|
119
|
+
linkedin: [box(3, 3, 18, 18, 2), "M7.5 10.5v6", dot(7.5, 7.5, 0.8), "M11 16.5v-6M11 13a2.5 2.5 0 0 1 5 0v3.5"],
|
|
120
|
+
youtube: [box(2, 5, 20, 14, 4), "M10 9l5 3-5 3z"],
|
|
121
|
+
github: [
|
|
122
|
+
"M8 21v-3.5c-3.5-.5-5-2.5-5-6 0-1.3.4-2.4 1.2-3.3-.3-1.2-.2-2.4.3-3.7 1.4 0 2.6.7 3.7 1.5a10 10 0 0 1 7.6 0c1.1-.8 2.3-1.5 3.7-1.5.5 1.3.6 2.5.3 3.7.8.9 1.2 2 1.2 3.3 0 3.5-1.5 5.5-5 6V21",
|
|
123
|
+
"M8 18.5c-2.5.5-3.5-1-4.5-2",
|
|
124
|
+
],
|
|
125
|
+
tiktok: ["M14 3v12.5a3.5 3.5 0 1 1-3.5-3.5", "M14 3c.5 2.5 2.5 4.5 5 4.5"],
|
|
126
|
+
whatsapp: ["M4.6 16.3A8.5 8.5 0 1 1 7.8 19.4L3.5 20.5z", "M9 8.5c0 3.5 3 6.5 6.5 6.5l1-1.5-2-1-1 .8c-1-.5-2-1.5-2.5-2.5l.8-1-1-2z"],
|
|
127
|
+
telegram: [dot(12, 12, 9), "M6.5 12L17 7.5 15 17l-4.5-3.5z", "M10.5 13.5L14 10.5"],
|
|
128
|
+
discord: [
|
|
129
|
+
"M5 17c0-4 1-7.5 3-10.5 1.2-.5 2.5-.8 3-.8l.5 1h1l.5-1c.5 0 1.8.3 3 .8 2 3 3 6.5 3 10.5-1.3 1.2-2.8 1.8-4.2 2.2l-1-2c-1.3.3-3.3.3-4.6 0l-1 2C7.8 18.8 6.3 18.2 5 17z",
|
|
130
|
+
dot(9.5, 13, 1.2),
|
|
131
|
+
dot(14.5, 13, 1.2),
|
|
132
|
+
],
|
|
133
|
+
pinterest: [dot(12, 12, 9), "M9 13c-.7-.8-1-1.7-1-2.8a4 4 0 0 1 8 .3c0 2.5-1.5 4.5-3.5 4.5-1 0-1.8-.6-1.6-1.6l.8-3.4", "M11.5 12l-2 9"],
|
|
134
|
+
reddit: [
|
|
135
|
+
"M3.5 14a8.5 5.5 0 1 0 17 0a8.5 5.5 0 1 0-17 0",
|
|
136
|
+
"M12 8.5l1.5-5 4 1",
|
|
137
|
+
dot(18.5, 4.5, 1.3),
|
|
138
|
+
dot(9, 13.5, 0.9),
|
|
139
|
+
dot(15, 13.5, 0.9),
|
|
140
|
+
"M9 16.5c1.8 1.2 4.2 1.2 6 0",
|
|
141
|
+
],
|
|
142
|
+
snapchat: [
|
|
143
|
+
"M12 3c3 0 5 2.2 5 5v2.5l2-.5c.5 0 .8.6.3 1l-2.3 1.3c.6 2 2 3.3 4 3.8-.3.8-1.5 1-2.5 1.2l-.5 1.4-2-.2c-1 .3-2 1.5-4 1.5s-3-1.2-4-1.5l-2 .2-.5-1.4c-1-.2-2.2-.4-2.5-1.2 2-.5 3.4-1.8 4-3.8L4.7 11c-.5-.4-.2-1 .3-1l2 .5V8c0-2.8 2-5 5-5z",
|
|
144
|
+
],
|
|
145
|
+
slack: [box(8.5, 2, 3, 9, 1.5), box(13, 8.5, 9, 3, 1.5), box(12.5, 13, 3, 9, 1.5), box(2, 12.5, 9, 3, 1.5)],
|
|
146
|
+
spotify: [dot(12, 12, 9), "M7 9.5c3.5-1 7.5-.7 10.5 1", "M7.5 12.8c3-.8 6-.5 8.5 1", "M8 15.8c2.5-.6 4.5-.4 6.5.7"],
|
|
147
|
+
apple: [
|
|
148
|
+
"M12 7.5c1-.6 2-.9 3-.9 2 0 3.5 1 4.3 2.4-1.7 1-2.6 2.6-2.3 4.6.2 1.5 1 2.6 2.3 3.2-.8 2.3-2.4 4.7-4.3 4.7-1 0-1.8-.6-3-.6s-2 .6-3 .6c-2.3 0-5-4.5-5-8.5 0-3.2 2-5.4 4.5-5.4 1.3 0 2.5.6 3.5.9z",
|
|
149
|
+
"M12 6.5c0-2 1.3-3.5 3.3-3.8 0 2-1.3 3.6-3.3 3.8z",
|
|
150
|
+
],
|
|
151
|
+
google: ["M20 12h-8", "M20 12a8 8 0 1 1-2.3-5.7"],
|
|
152
|
+
microsoft: ["M3 3h8.5v8.5H3z", "M12.5 3H21v8.5h-8.5z", "M3 12.5h8.5V21H3z", "M12.5 12.5H21V21h-8.5z"],
|
|
153
|
+
dribbble: [dot(12, 12, 9), "M3.2 10.5c6 .5 11.5-1.5 14-5.5", "M8.5 3.7c3.5 4 6 10 7 16.5", "M5.5 18.5c2.5-4 7-6 15.3-4.2"],
|
|
154
|
+
twitch: ["M4 6l1.5-3H20v11l-4 4h-4l-3 3v-3H4z", "M11 7v4M15 7v4"],
|
|
45
155
|
};
|
|
46
156
|
|
|
47
157
|
export const ICON_NAMES = Object.keys(ICONS);
|
package/src/render/page.js
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
import { createContext, esc } from "./context.js";
|
|
2
2
|
import { colorVar, isPreset } from "../themes.js";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// Dev mode: polls the dev server, reloads after a good build and shows the errors of a failed one.
|
|
5
|
+
const DEV_RELOAD = `<script>(function(){var v,box;
|
|
6
|
+
function show(e){if(!box){box=document.createElement("div");box.id="oi-dev-error";box.setAttribute("role","alert");box.style.cssText="position:fixed;inset:0;z-index:2147483647;overflow:auto;margin:0;padding:32px;background:rgba(40,0,0,.92);color:#fff;font:14px/1.6 ui-monospace,Menlo,Consolas,monospace;white-space:pre-wrap";document.body.appendChild(box)}box.textContent="\\u2717 "+e+"\\n\\nFix the spec and save: this page updates by itself."}
|
|
7
|
+
function hide(){if(box){box.remove();box=null}}
|
|
8
|
+
setInterval(function(){fetch("/__version").then(function(r){return r.json()}).then(function(s){if(v&&s.version!==v)return location.reload();v=s.version;s.error?show(s.error):hide()}).catch(function(){})},600)})()</script>`;
|
|
9
|
+
|
|
10
|
+
/** The page the dev server shows when there is no good build yet: only the poller, which shows the errors. */
|
|
11
|
+
export const devErrorPage = () => `<!DOCTYPE html>
|
|
12
|
+
<html lang="en">
|
|
13
|
+
<head>
|
|
14
|
+
<meta charset="UTF-8" />
|
|
15
|
+
<title>openink</title>
|
|
16
|
+
${DEV_RELOAD}
|
|
17
|
+
</head>
|
|
18
|
+
<body></body>
|
|
19
|
+
</html>
|
|
20
|
+
`;
|
|
5
21
|
|
|
6
22
|
/**
|
|
7
23
|
* Render a validated spec to a complete HTML document.
|
package/src/spec/docs.js
CHANGED
|
@@ -45,6 +45,8 @@ export function blocksMarkdown() {
|
|
|
45
45
|
"",
|
|
46
46
|
ICON_NAMES.map((n) => `\`${n}\``).join(" · "),
|
|
47
47
|
"",
|
|
48
|
+
"Brand names and logos are trademarks of their owners. Open Ink's brand icons are simplified sketches for wireframes only and do not imply endorsement. Use each brand's official assets in production.",
|
|
49
|
+
"",
|
|
48
50
|
];
|
|
49
51
|
for (const g of groups) {
|
|
50
52
|
out.push(`## ${g}`, "");
|