vowel 0.1.3 → 0.1.5

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/bin.js CHANGED
@@ -21,7 +21,7 @@ const binDirName = path.dirname(binFilePath);
21
21
  const homeDir = process.cwd();
22
22
 
23
23
  const spawnArgs = ['./server.js', '--directory', homeDir];
24
- if (args.build) spawnArgs.push('--build');
24
+ if (args._.includes('build')) spawnArgs.push('--build');
25
25
 
26
26
  if (!args.verbose) {
27
27
  filterConsole(['jsconfig', 'vite', 'Vite', 'tsconfig', `--host`]);
package/content/home.md CHANGED
@@ -14,6 +14,8 @@ npx -p svelte@next -p vowel@latest npx vowel
14
14
 
15
15
  Vowel is a minimal website framework for developers who love to write CSS and read RSS.
16
16
 
17
+ > Here's a lovely block quote.
18
+
17
19
  Vowel will generate a feature-rich HTML website from a directory of markdown files. The output is plain HTML. You can style it however you want with your own CSS.
18
20
 
19
21
  See a [demo on StackBlitz](https://stackblitz.com/edit/stackblitz-starters-nh7m7v?file=home.md). (**Error:** This demo might not work due conflicts in Svelte 5's peer dependencies.)
@@ -24,47 +26,47 @@ Vowel is created by Sam Littlefair. If you have questions or comments, [reach ou
24
26
 
25
27
  # Features
26
28
 
27
- ## 🗺️ Sitemap
29
+ ## Sitemap 🗺️
28
30
 
29
31
  Submit your website to search engines with a sitemap that lists all of your pages.
30
32
 
31
- ## 📡 RSS
33
+ ## RSS 📡
32
34
 
33
35
  Deliver your content to RSS readers with an Atom feed that lists your full posts in reverse-chronology.
34
36
 
35
- ## ⚡️ Speed
37
+ ## Speed ⚡️
36
38
 
37
39
  Get a blazing-fast, Svelte-rendered website.
38
40
 
39
- ## 🗿 Static generation
41
+ ## Static generation 🗿
40
42
 
41
43
  Load pages quickly with plain HTML.
42
44
 
43
- ## 🔒 Copyright
45
+ ## Copyright 🔒
44
46
 
45
47
  Display an up-to-date copyright in the footer of your website.
46
48
 
47
- ## 🔗 Rich link previews
49
+ ## Rich link previews 🔗
48
50
 
49
51
  Create rich links with titles and images, both internally and externally.
50
52
 
51
- ## 🚏 Navigation
53
+ ## Navigation 🚏
52
54
 
53
55
  Structure your content with a header, nav, sitemap, and breadcrumbs.
54
56
 
55
- ## 🙃 Emoji favicon
57
+ ## Emoji favicon 🙃
56
58
 
57
59
  Use any emoji you want for your website's icon.
58
60
 
59
- ## 🖇️ Custom taxonomies
61
+ ## Custom taxonomies 🖇️
60
62
 
61
63
  Go beyond tags to create authors and categories.
62
64
 
63
- ## 🧮 Advanced frontmatter
65
+ ## Advanced frontmatter 🧮
64
66
 
65
67
  Display dates, lists, nested properties, links, and images in your frontmatter.
66
68
 
67
- ## 👩‍🎤 Live editing
69
+ ## Live editing 👩‍🎤
68
70
 
69
71
  See updates live as you save.
70
72
 
@@ -24,10 +24,13 @@ description: What's planned.
24
24
 
25
25
  ## V2
26
26
 
27
- - [x] Fallback homepages
28
- - [ ] Create post list element (`#tags/blue`, `@tags/blue`, `~tags/blue`)
27
+ - [ ] Tables of contents
29
28
  - [/] CSS defaults (work in progress)
29
+ - [ ] Fallback homepages
30
+ - [ ] Init CLI
31
+ - [ ] Better default styles
30
32
  - [ ] CSS layout [presets](https://github.com/swyxio/spark-joy?tab=readme-ov-file#drop-in-css-frameworks)
33
+ - [ ] Post list element (`#tags/blue`, `@tags/blue`, `~tags/blue`)
31
34
  - [x] Images as <figure>s
32
35
  - [x] Frontmatter interpolation (title and description)
33
36
  - [ ] Frontmatter interpolation (image)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vowel",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "bin": "./bin.js",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
package/server.js CHANGED
@@ -57,6 +57,7 @@ async function buildProject() {
57
57
  }
58
58
 
59
59
  const jsonData = JSON.stringify(vercelDefaults, null, 2);
60
+
60
61
  await writeFile(vercelConfigPath, jsonData, 'utf-8');
61
62
  await writeFile(join($home[0], '.output', 'vercel.json'), jsonData, 'utf-8');
62
63
  console.log('Build completed successfully.');
@@ -0,0 +1,10 @@
1
+ <script>
2
+ let { children, component, ...rest } = $props();
3
+ </script>
4
+
5
+ {#if component}
6
+ <svelte:component this={component} {...rest}>
7
+ {@render children()}
8
+ </svelte:component>
9
+ {/if}
10
+ {@render children()}
@@ -1,138 +1,459 @@
1
1
  <style>
2
- :global(:root) {
3
- --font-sans: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue,
4
- helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;
5
- --font-serif: Iowan Old Style, Apple Garamond, Baskerville, Times New Roman, Droid Serif, Times,
6
- Source Serif Pro, serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
7
- --font-mono: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace;
8
- }
2
+ /*
3
+ Inspiration for this stylesheet comes from Normalize.css, Josh Comeau,
4
+ Barebones, Basic.css, Pico, Typeplate, Simple.css, and more
5
+ */
9
6
 
10
- :global(:where(html, body, .page)) {
11
- margin: 0;
12
- padding: 0;
13
- box-sizing: border-box;
14
- }
7
+ :global {
8
+ /* Variables */
15
9
 
16
- :global(:where(.container)) {
17
- height: 100vh;
18
- overflow-y: scroll;
19
- font-family: var(--font-sans);
20
- }
10
+ :root {
11
+ --font-sans: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue,
12
+ helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;
13
+ --font-serif: Iowan Old Style, Apple Garamond, Baskerville, Times New Roman, Droid Serif,
14
+ Times, Source Serif Pro, serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
15
+ --font-mono: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace;
16
+ --text-color: hsl(0, 0%, 13%);
17
+ --accent-color: hsl(215, 100%, 35%);
18
+ --accent-color-hover: hsl(215, 76%, 49%);
19
+ --border-color: hsl(0, 0%, 73%);
20
+ --main-background: hsl(0, 0%, 100%);
21
+ --code-background: hsl(0, 0%, 95%);
22
+ --soft-background: hsl(0, 0%, 95%);
23
+ }
21
24
 
22
- :global(:where(.page)) {
23
- max-width: 60ch;
24
- margin: auto;
25
- min-height: 100vh;
26
- display: flex;
27
- flex-direction: column;
28
- padding: 1rem;
29
- overflow-x: hidden;
30
- }
25
+ @media (prefers-color-scheme: dark) {
26
+ :root {
27
+ --text-color: hsl(0, 0%, 80%);
28
+ --accent-color: hsl(194, 76%, 49%);
29
+ --accent-color-hover: hsl(194, 86%, 57%);
30
+ --border-color: hsl(0, 0%, 27%);
31
+ --main-background: hsl(0, 0%, 12%);
32
+ --code-background: hsl(0, 0%, 5%);
33
+ --soft-background: hsl(0, 0%, 14%);
34
+ }
35
+ }
31
36
 
32
- /* Global */
37
+ /*
38
+ Reset
39
+ */
33
40
 
34
- :global(:where(img)) {
35
- display: block;
36
- width: 100%;
37
- height: auto;
38
- }
41
+ *,
42
+ *::before,
43
+ *::after {
44
+ box-sizing: border-box;
45
+ }
39
46
 
40
- /* Header */
47
+ * {
48
+ margin: 0;
49
+ }
41
50
 
42
- :global(:where(nav)) {
43
- display: flex;
44
- column-gap: 1rem;
45
- row-gap: 0.5rem;
46
- flex-wrap: wrap;
47
- justify-content: flex-start;
48
- }
51
+ html {
52
+ line-height: 1.15;
53
+ -webkit-text-size-adjust: 100%;
54
+ font-size: 62.5%;
55
+ }
49
56
 
50
- :global(:where(nav + nav)) {
51
- margin-top: 1rem;
52
- }
57
+ body {
58
+ font-size: 1.6rem;
59
+ line-height: 1.5;
60
+ -webkit-font-smoothing: antialiased;
61
+ font-weight: 400;
62
+ }
53
63
 
54
- :global(:where(nav > a)) {
55
- text-wrap: nowrap;
56
- }
64
+ main,
65
+ img,
66
+ details,
67
+ picture,
68
+ video,
69
+ canvas,
70
+ svg {
71
+ display: block;
72
+ max-width: 100%;
73
+ }
57
74
 
58
- :global(:where(header a.site-title)) {
59
- color: unset;
60
- text-decoration: none;
61
- font-weight: bold;
62
- font-size: 2em;
63
- }
75
+ summary {
76
+ display: list-item;
77
+ }
64
78
 
65
- :global(:where(.sitemap)) {
66
- margin: auto;
67
- }
79
+ hr {
80
+ box-sizing: content-box;
81
+ height: 0;
82
+ overflow: visible;
83
+ }
68
84
 
69
- :global(:where(nav, aside.sitemap) :where([aria-current='page'], [aria-current='true'])) {
70
- text-decoration: none;
71
- color: unset;
72
- cursor: default;
73
- }
85
+ pre {
86
+ font-family: monospace, monospace;
87
+ font-size: 1em;
88
+ }
74
89
 
75
- :global(:where(header .breadcrumbs)) {
76
- margin-top: 2rem;
77
- }
90
+ a {
91
+ background-color: transparent;
92
+ }
78
93
 
79
- :global(:where(header .breadcrumbs a)) {
80
- font-size: 0.9em;
81
- text-decoration: none;
82
- color: unset;
83
- }
94
+ abbr[title] {
95
+ border-bottom: none;
96
+ text-decoration: underline;
97
+ text-decoration: underline dotted;
98
+ }
84
99
 
85
- :global(:where(header .breadcrumbs .separator):after) {
86
- content: '/';
87
- }
100
+ b,
101
+ strong {
102
+ font-weight: bolder;
103
+ }
88
104
 
89
- /* Content block elements */
105
+ code,
106
+ kbd,
107
+ samp {
108
+ font-family: monospace, monospace;
109
+ font-size: 1em;
110
+ }
90
111
 
91
- :global(:where(pre)) {
92
- padding: 1rem;
93
- background: #eee;
94
- overflow-x: auto;
95
- }
112
+ small {
113
+ font-size: 80%;
114
+ }
96
115
 
97
- :global(:where(article)) {
98
- border: 8px ridge #eee;
99
- padding-inline: 1rem;
100
- }
116
+ sub,
117
+ sup {
118
+ font-size: 75%;
119
+ line-height: 0;
120
+ position: relative;
121
+ vertical-align: baseline;
122
+ }
101
123
 
102
- :global(:where(article + article)) {
103
- margin-top: 1rem;
104
- }
124
+ sub {
125
+ bottom: -0.25em;
126
+ }
105
127
 
106
- /* Content inline elements */
128
+ sup {
129
+ top: -0.5em;
130
+ }
107
131
 
108
- :global(:where(code)) {
109
- background: #eee;
110
- margin: -3px -1px;
111
- padding: 3px 4px;
112
- border-radius: 5px;
113
- }
132
+ img {
133
+ border-style: none;
134
+ height: auto;
135
+ }
114
136
 
115
- /* Aside */
137
+ /* Layout */
116
138
 
117
- :global(:where(aside.sitemap)) {
118
- margin-top: auto;
119
- }
139
+ .container {
140
+ height: 100vh;
141
+ overflow-y: scroll;
142
+ font-family: var(--font-sans);
143
+ }
120
144
 
121
- :global([aria-current='page']) {
122
- color: unset;
123
- text-decoration: none;
124
- cursor: unset;
125
- }
145
+ .page {
146
+ max-width: 60ch;
147
+ margin: auto;
148
+ min-height: 100vh;
149
+ display: flex;
150
+ flex-direction: column;
151
+ padding: 1rem;
152
+ overflow-x: hidden;
153
+ }
126
154
 
127
- /* Footer */
155
+ main {
156
+ margin-bottom: auto;
157
+ padding-bottom: 4rem;
158
+ }
128
159
 
129
- :global(:where(footer)) {
130
- text-align: center;
131
- }
160
+ /* Colors */
161
+
162
+ body {
163
+ color: var(--text-color);
164
+ background: var(--main-background);
165
+ }
166
+
167
+ a {
168
+ color: var(--accent-color);
169
+ }
170
+
171
+ a:hover {
172
+ color: var(--accent-color-hover);
173
+ }
174
+
175
+ /* Typography */
176
+
177
+ p,
178
+ h1,
179
+ h2,
180
+ h3,
181
+ h4,
182
+ h5,
183
+ h6 {
184
+ overflow-wrap: break-word;
185
+ }
186
+
187
+ h1,
188
+ h2,
189
+ h3,
190
+ h4,
191
+ h5,
192
+ h6 {
193
+ margin-top: 0.5rem;
194
+ margin-bottom: 1rem;
195
+ text-rendering: optimizeLegibility;
196
+ font-weight: 600;
197
+ line-height: 1.1;
198
+ }
199
+ h1 {
200
+ font-size: 3rem;
201
+ line-height: 1.2;
202
+ font-weight: 800;
203
+ }
204
+
205
+ h2 {
206
+ font-size: 2.6rem;
207
+ margin-top: 3rem;
208
+ }
209
+
210
+ h3 {
211
+ font-size: 2rem;
212
+ margin-top: 3rem;
213
+ }
214
+
215
+ h4 {
216
+ font-size: 1.44rem;
217
+ }
218
+
219
+ h5 {
220
+ font-size: 1.15rem;
221
+ }
222
+
223
+ h6 {
224
+ font-size: 0.96rem;
225
+ }
226
+
227
+ article h1 {
228
+ font-weight: 600;
229
+ font-size: 2rem;
230
+ }
231
+
232
+ @media only screen and (max-width: 720px) {
233
+ h1 {
234
+ font-size: 2.5rem;
235
+ }
236
+
237
+ h2 {
238
+ font-size: 2.1rem;
239
+ }
240
+
241
+ h3 {
242
+ font-size: 1.75rem;
243
+ }
244
+
245
+ h4 {
246
+ font-size: 1.25rem;
247
+ }
248
+ }
249
+
250
+ p {
251
+ margin-top: 0;
252
+ }
253
+
254
+ ol,
255
+ ul {
256
+ /* padding-left: 0; */
257
+ margin-top: 0;
258
+ }
259
+
260
+ ul ul,
261
+ ul ol,
262
+ ol ol,
263
+ ol ul {
264
+ font-size: 100%;
265
+ /* margin: 1rem 0 1rem 3rem; */
266
+ }
267
+
268
+ li {
269
+ margin-bottom: 0.5rem;
270
+ }
271
+
272
+ /* Extra selector for specificity, see below */
273
+ li > p:not(:last-child) {
274
+ margin: 0;
275
+ padding: 0;
276
+ }
277
+
278
+ code {
279
+ background: var(--code-background);
280
+ font-size: 0.9em;
281
+ white-space: nowrap;
282
+ margin-inline: 0px;
283
+ padding: 3px 4px;
284
+ border-radius: 5px;
285
+ }
286
+
287
+ code,
288
+ mark {
289
+ -webkit-box-decoration-break: clone;
290
+ box-decoration-break: clone;
291
+ }
292
+
293
+ pre {
294
+ background: var(--code-background);
295
+ padding: 1rem 1.5rem;
296
+ border-radius: 5px;
297
+ overflow-x: auto;
298
+ }
299
+
300
+ dt {
301
+ float: left;
302
+ margin: 0 20px 0 0;
303
+ width: 120px;
304
+ color: #daf2fd;
305
+ font-weight: 500;
306
+ text-align: right;
307
+ }
308
+
309
+ dd {
310
+ margin: 0 0 5px 0;
311
+ text-align: left;
312
+ }
313
+
314
+ blockquote {
315
+ margin: 1.5em 0;
316
+ padding: 0 1.6em 0 1.6em;
317
+ border-left: 5px solid var(--border-color);
318
+ }
319
+
320
+ a {
321
+ text-decoration: none;
322
+ }
323
+
324
+ figcaption {
325
+ margin-top: 0.5rem;
326
+ font-style: italic;
327
+ }
328
+
329
+ article {
330
+ border: 1px solid var(--border-color);
331
+ background: var(--soft-background);
332
+ padding: 1rem 2rem 1.5rem;
333
+ border-radius: 0.5rem;
334
+ }
335
+
336
+ article + article {
337
+ margin-top: 1rem;
338
+ }
339
+
340
+ th,
341
+ td {
342
+ padding: 12px 15px;
343
+ text-align: left;
344
+ border-bottom: 1px solid var(--border-color-softer);
345
+ }
346
+ th:first-child,
347
+ td:first-child {
348
+ padding-left: 0;
349
+ }
350
+ th:last-child,
351
+ td:last-child {
352
+ padding-right: 0;
353
+ }
354
+
355
+ :where(pre, blockquote, dl, figure, table, p, ul, ol, form, article):not(:last-child) {
356
+ margin-bottom: 2.5rem;
357
+ }
358
+
359
+ hr {
360
+ margin-top: 3rem;
361
+ margin-bottom: 3.5rem;
362
+ border-width: 0px;
363
+ border-top: 1px solid var(--text-color);
364
+ }
365
+
366
+ /* Header */
367
+
368
+ nav.top-bar {
369
+ border: 1px solid var(--border-color);
370
+ padding: 6px 10px;
371
+ margin-inline: -10px;
372
+ border-radius: 5px;
373
+ display: flex;
374
+ column-gap: 2rem;
375
+ row-gap: 0.5rem;
376
+ flex-wrap: wrap;
377
+ justify-content: flex-start;
378
+ background: var(--soft-background);
379
+ }
380
+
381
+ nav.top-bar:has(+ nav.top-bar) {
382
+ border-bottom: none;
383
+ border-bottom-left-radius: 0;
384
+ border-bottom-right-radius: 0;
385
+ }
386
+
387
+ nav.top-bar:has(+ nav.top-bar):after {
388
+ display: block;
389
+ content: '';
390
+ border-bottom: 1px solid var(--border-color);
391
+ flex-basis: 100%;
392
+ margin-top: 0.7rem;
393
+ }
394
+
395
+ nav.top-bar + nav.top-bar {
396
+ border-top-left-radius: 0;
397
+ border-top-right-radius: 0;
398
+ border-top: none;
399
+ margin-top: 0;
400
+ }
401
+
402
+ nav > a {
403
+ text-wrap: nowrap;
404
+ }
405
+
406
+ header a.site-title {
407
+ color: unset;
408
+ text-decoration: none;
409
+ font-weight: 900;
410
+ font-size: 2.8em;
411
+ }
412
+
413
+ header .slogan {
414
+ /* font-family: var(--font-serif); */
415
+ font-weight: 500;
416
+ }
417
+
418
+ nav :where([aria-current='page'], [aria-current='true']),
419
+ nav :where([aria-current='page'], [aria-current='true']):hover {
420
+ text-decoration: none;
421
+ color: unset;
422
+ cursor: default;
423
+ }
424
+
425
+ header .breadcrumbs {
426
+ margin-block: 4rem 1rem;
427
+ }
428
+
429
+ header .breadcrumbs a {
430
+ font-size: 0.9em;
431
+ text-decoration: none;
432
+ color: unset;
433
+ }
434
+
435
+ header .breadcrumbs .separator:after {
436
+ margin-inline: 0.5rem;
437
+ content: '|';
438
+ }
439
+
440
+ /* Sidebar */
441
+
442
+ .sidebar {
443
+ margin: auto;
444
+ }
445
+
446
+ /* Footer */
447
+
448
+ footer {
449
+ text-align: center;
450
+ margin-block: 6rem 8rem;
451
+ }
132
452
 
133
- /* Pages */
453
+ /* Pages */
134
454
 
135
- :global(:where(.page.home > main > h1)) {
136
- display: none;
455
+ .page.home > main > h1 {
456
+ display: none;
457
+ }
137
458
  }
138
459
  </style>
@@ -3,8 +3,17 @@
3
3
 
4
4
  let { node, level, website } = $props();
5
5
  let { url, children } = $derived(node);
6
+
7
+ function isAbsoluteURL(url) {
8
+ try {
9
+ new URL(url);
10
+ return true;
11
+ } catch (e) {
12
+ return false;
13
+ }
14
+ }
6
15
  </script>
7
16
 
8
- <a href={url}>
17
+ <a href={url} target={isAbsoluteURL(url) ? '_blank' : null}>
9
18
  <Markdown props={{ ast: children, level, website }} />
10
19
  </a>
@@ -18,7 +18,7 @@
18
18
  </script>
19
19
 
20
20
  {#if Object.keys(evergreen).some((key) => key !== '$' && key !== '_')}
21
- <nav>
21
+ <nav class="top-bar">
22
22
  {#if !child && evergreen['$']?.url !== '/'}
23
23
  <a aria-current={isActiveLink(segments)} href={evergreen['$']?.url}
24
24
  >{getFolderLabel(evergreen)}</a
@@ -6,6 +6,7 @@
6
6
  import { error } from '@sveltejs/kit';
7
7
  import { getFolder, getFolderLabel } from '../../lib/utilities';
8
8
  import { page as pageStore } from '$app/stores';
9
+ import ConditionalWrapper from '../../lib/components/ConditionalWrapper.svelte';
9
10
 
10
11
  let { data } = $props();
11
12
 
@@ -42,7 +43,7 @@
42
43
  }
43
44
 
44
45
  const breadcrumbs = getBreadcrumbs().slice(1, -1).reverse();
45
- const breadcrumbSegment = breadcrumbs.length ? ` - ${breadcrumbs.join(' - ')} - ` : '';
46
+ const breadcrumbSegment = breadcrumbs.length ? ` - ${breadcrumbs.join(' - ')} - ` : ' - ';
46
47
  return `${pageMetaTitle}${breadcrumbSegment}${siteTitle}`;
47
48
  }
48
49
 
@@ -113,9 +114,9 @@
113
114
  <main>
114
115
  <Page level={0} {page} {website} path={data.path} />
115
116
  </main>
116
- <aside class="sitemap">
117
+ <nav class="sidebar">
117
118
  <Sitemap section={data.website} segments={data.path.split('/')} root />
118
- </aside>
119
+ </nav>
119
120
  <footer>
120
121
  © {website._.author ? website._.author + ' ' : ''}
121
122
  {new Date().getFullYear()}