vowel 0.1.19 → 0.1.20
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/package.json +1 -2
- package/src/lib/components/DefaultStyles.svelte +18 -0
- package/src/lib/components/Frontmatter.svelte +2 -1
- package/src/lib/components/Markdown/Image.svelte +3 -1
- package/src/lib/components/Markdown/index.svelte +1 -1
- package/src/lib/components/Nav.svelte +1 -1
- package/src/lib/components/Page.svelte +20 -0
- package/src/lib/components/ResetStyles.svelte +3 -2
- package/src/lib/utilities/getFileLabel.js +2 -1
- package/src/routes/+error.svelte +110 -0
- package/src/routes/[...path]/+layout.server.js +2 -0
- package/src/routes/[...path]/+page.server.js +3 -10
- package/src/routes/[...path]/+page.svelte +10 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vowel",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
4
4
|
"bin": "./bin.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "vite dev",
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
"@sveltejs/adapter-static": "^3.0.1",
|
|
19
19
|
"@sveltejs/kit": "^2.5.6",
|
|
20
20
|
"@sveltejs/vite-plugin-svelte": "^4.0.0-next.3",
|
|
21
|
-
"@vowel/styles": "^0.0.5",
|
|
22
21
|
"any-date-parser": "^1.5.4",
|
|
23
22
|
"change-case": "^5.4.1",
|
|
24
23
|
"filter-console": "^1.0.0",
|
|
@@ -102,6 +102,10 @@
|
|
|
102
102
|
margin-bottom: 2.5rem;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
dt {
|
|
106
|
+
font-size: 1.1em;
|
|
107
|
+
}
|
|
108
|
+
|
|
105
109
|
dl.link dt {
|
|
106
110
|
display: none;
|
|
107
111
|
}
|
|
@@ -134,6 +138,20 @@
|
|
|
134
138
|
content: '#';
|
|
135
139
|
}
|
|
136
140
|
|
|
141
|
+
dl.contents ul {
|
|
142
|
+
list-style: none;
|
|
143
|
+
padding: 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
dl.contents .depth-2 {
|
|
147
|
+
font-weight: 500;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
dl.contents .depth-3 {
|
|
151
|
+
font-size: 0.95em;
|
|
152
|
+
padding-left: 0.5em;
|
|
153
|
+
}
|
|
154
|
+
|
|
137
155
|
/* Header */
|
|
138
156
|
|
|
139
157
|
header {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
let { props } = $props();
|
|
12
12
|
// TODO: Normalize frontmatter props
|
|
13
13
|
let { properties, website, format } = $derived(props);
|
|
14
|
+
const keys = $derived(Object.keys(properties || {}));
|
|
14
15
|
|
|
15
16
|
const excludedProperties = [
|
|
16
17
|
'title',
|
|
@@ -41,7 +42,7 @@
|
|
|
41
42
|
</script>
|
|
42
43
|
|
|
43
44
|
{#if properties}
|
|
44
|
-
{#each
|
|
45
|
+
{#each keys as key}
|
|
45
46
|
{#if !excludedProperties.includes(key)}
|
|
46
47
|
{@const property = getProperty(properties, key)}
|
|
47
48
|
{#if website.hasOwnProperty(key)}
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
{:else if node.type === 'url'}
|
|
88
88
|
<LinkPreview url={node.value} metadata={node.metadata} {format} />
|
|
89
89
|
{:else if node.type === 'listItem'}
|
|
90
|
-
{@const isChecklistItem = node.
|
|
90
|
+
{@const isChecklistItem = node.checked !== null}
|
|
91
91
|
{#if isChecklistItem}
|
|
92
92
|
<li
|
|
93
93
|
class={`checklist-item ${node.checked ? 'checked' : 'unchecked'}`}
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
<nav class="top-bar">
|
|
22
22
|
{#if !child}
|
|
23
23
|
<a aria-current={isActiveLink(segments)} href={evergreen?.url || evergreen['$']?.url}
|
|
24
|
-
>{getFolderLabel(evergreen)}</a
|
|
24
|
+
>{getFolderLabel(evergreen, true)}</a
|
|
25
25
|
>
|
|
26
26
|
{/if}
|
|
27
27
|
{#each Object.keys(evergreen) as key}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { Markdown, Frontmatter } from '$lib/components';
|
|
3
|
+
import { toString } from 'mdast-util-to-string';
|
|
4
|
+
import { kebabCase } from 'change-case';
|
|
3
5
|
import Image from './Markdown/Image.svelte';
|
|
4
6
|
|
|
5
7
|
let { page, content = true, link, level = 0, website = {}, format = 'html' } = $props();
|
|
@@ -7,6 +9,8 @@
|
|
|
7
9
|
|
|
8
10
|
const dateObject = date?.type === 'date' && new Date(date.output);
|
|
9
11
|
// TODO: Add conditional logic for format = 'xml'
|
|
12
|
+
|
|
13
|
+
const headings = ast.filter((child) => child.type === 'heading');
|
|
10
14
|
</script>
|
|
11
15
|
|
|
12
16
|
<!-- Image -->
|
|
@@ -52,6 +56,22 @@
|
|
|
52
56
|
<p class="description">{page?.description || page?.imputedProperties.description}</p>
|
|
53
57
|
{/if}
|
|
54
58
|
|
|
59
|
+
{#if format === 'html' && page.toc && headings.length > 1}
|
|
60
|
+
<dl class="contents">
|
|
61
|
+
<dt>Contents</dt>
|
|
62
|
+
<dd>
|
|
63
|
+
<ul>
|
|
64
|
+
{#each headings as heading, index}
|
|
65
|
+
{@const headingString = toString(heading.children)}
|
|
66
|
+
<li class={`heading depth-${heading.depth}`}>
|
|
67
|
+
<a href={`#${kebabCase(headingString)}`}>{headingString}</a>
|
|
68
|
+
</li>
|
|
69
|
+
{/each}
|
|
70
|
+
</ul>
|
|
71
|
+
</dd>
|
|
72
|
+
</dl>
|
|
73
|
+
{/if}
|
|
74
|
+
|
|
55
75
|
{#if level < 2 && content}
|
|
56
76
|
<div class="content">
|
|
57
77
|
<Markdown props={{ ast, level, website, format }} />
|
|
@@ -22,9 +22,10 @@ export default function getFileLabel(page, shorter = false, date = true) {
|
|
|
22
22
|
: false;
|
|
23
23
|
|
|
24
24
|
return (
|
|
25
|
-
page?.breadcrumb ||
|
|
25
|
+
(shorter && page?.breadcrumb) ||
|
|
26
26
|
page?.title ||
|
|
27
27
|
page?.imputedProperties?.title ||
|
|
28
|
+
page?.breadcrumb ||
|
|
28
29
|
formattedDate ||
|
|
29
30
|
(shorter && fileName) ||
|
|
30
31
|
description ||
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { page } from '$app/stores';
|
|
3
|
+
import {
|
|
4
|
+
Nav,
|
|
5
|
+
Page,
|
|
6
|
+
Breadcrumbs,
|
|
7
|
+
Sitemap,
|
|
8
|
+
ResetStyles,
|
|
9
|
+
DefaultStyles,
|
|
10
|
+
TypographyStyles,
|
|
11
|
+
NoStyles
|
|
12
|
+
} from '$lib/components/index.js';
|
|
13
|
+
import { invalidateAll } from '$app/navigation';
|
|
14
|
+
|
|
15
|
+
const themes = {
|
|
16
|
+
none: NoStyles,
|
|
17
|
+
reset: ResetStyles,
|
|
18
|
+
typography: TypographyStyles,
|
|
19
|
+
default: DefaultStyles
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
let { error: data } = $page;
|
|
23
|
+
|
|
24
|
+
const { website } = $derived(data);
|
|
25
|
+
|
|
26
|
+
const { slogan, theme } = $derived(website?._);
|
|
27
|
+
|
|
28
|
+
// Import CSS for HMR in dev mode
|
|
29
|
+
if (data.dev && data.files.css.exists) {
|
|
30
|
+
import(/* @vite-ignore */ data.files.css.path);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const siteTitle = website._.title;
|
|
34
|
+
|
|
35
|
+
function makePageMetaTitle() {
|
|
36
|
+
if (siteTitle) return `Page not found = ${siteTitle}`;
|
|
37
|
+
return `Page not found`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getFavicon() {
|
|
41
|
+
if (website._?.icon)
|
|
42
|
+
return `data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 20 90 60%22><text y=%22.9em%22 font-size=%2290%22>${website._.icon}</text></svg>`;
|
|
43
|
+
if (data.files.favicon.exists) return '/favicon.png';
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const favicon = getFavicon();
|
|
48
|
+
|
|
49
|
+
$effect(() => {
|
|
50
|
+
// Update website on file change
|
|
51
|
+
if (import.meta.hot) {
|
|
52
|
+
import.meta.hot.on('vowel:update', ({ path, file }) => {
|
|
53
|
+
invalidateAll().then(() => {
|
|
54
|
+
window.location.reload();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
import.meta.hot.on('vowel:refresh', () => {
|
|
58
|
+
console.log('Refresh');
|
|
59
|
+
invalidateAll().then(() => {
|
|
60
|
+
window.location.reload();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<svelte:component this={themes[theme || 'default']} />
|
|
68
|
+
|
|
69
|
+
<svelte:head>
|
|
70
|
+
<title>{makePageMetaTitle()}</title>
|
|
71
|
+
<meta property="og:site_name" content={website._.title} />
|
|
72
|
+
{#if favicon}
|
|
73
|
+
<link rel="icon" href={favicon} />
|
|
74
|
+
{/if}
|
|
75
|
+
{#if !data.dev && data.files.css.exists}
|
|
76
|
+
<link rel="stylesheet" href="/styles.css" />
|
|
77
|
+
{/if}
|
|
78
|
+
</svelte:head>
|
|
79
|
+
|
|
80
|
+
<div data-sveltekit-preload-data="hover" class="page 404">
|
|
81
|
+
<header>
|
|
82
|
+
{#if website._.logo}
|
|
83
|
+
<a href="/" class="site-logo">
|
|
84
|
+
{#if website._.logo.endsWith('.svg')}
|
|
85
|
+
<object type="image/svg+xml" data={website._.logo} title={website._.title || undefined}
|
|
86
|
+
></object>
|
|
87
|
+
{:else}
|
|
88
|
+
<img alt="website logo" src={website._.logo} />
|
|
89
|
+
{/if}
|
|
90
|
+
</a>
|
|
91
|
+
{/if}
|
|
92
|
+
{#if siteTitle}
|
|
93
|
+
<a href="/" class="site-title">{siteTitle}</a>
|
|
94
|
+
{/if}
|
|
95
|
+
{#if website._.slogan}
|
|
96
|
+
<p class="slogan">{website._.slogan}</p>
|
|
97
|
+
{/if}
|
|
98
|
+
<Nav folder={website} segments={['']} />
|
|
99
|
+
</header>
|
|
100
|
+
<main>
|
|
101
|
+
<h1>Page Not Found</h1>
|
|
102
|
+
</main>
|
|
103
|
+
<nav class="sidebar">
|
|
104
|
+
<Sitemap section={website} segments={['']} root />
|
|
105
|
+
</nav>
|
|
106
|
+
<footer>
|
|
107
|
+
© {website._.author ? website._.author + ' ' : ''}
|
|
108
|
+
{new Date().getFullYear()}
|
|
109
|
+
</footer>
|
|
110
|
+
</div>
|
|
@@ -4,6 +4,8 @@ import { constants } from 'fs';
|
|
|
4
4
|
import { normalize, join, basename } from 'path';
|
|
5
5
|
import { dev } from '$app/environment';
|
|
6
6
|
import mri from 'mri';
|
|
7
|
+
import { getPage } from '../../lib/utilities';
|
|
8
|
+
import { error } from '@sveltejs/kit';
|
|
7
9
|
|
|
8
10
|
const args = mri(process.argv);
|
|
9
11
|
const isBuild = args._.includes('build');
|
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
loadCache,
|
|
5
|
-
checkFileExists,
|
|
6
|
-
readMarkdownFile
|
|
7
|
-
} from '../../lib/utilities';
|
|
8
|
-
import { join } from 'path';
|
|
1
|
+
import { getPagesByFolder, processMarkdownFiles, loadCache } from '../../lib/utilities';
|
|
2
|
+
|
|
3
|
+
export const prerender = true;
|
|
9
4
|
|
|
10
5
|
/** @type {import('./$types').PageLoad} */
|
|
11
6
|
export async function load({ params }) {
|
|
@@ -15,8 +10,6 @@ export async function load({ params }) {
|
|
|
15
10
|
return { path, segments };
|
|
16
11
|
}
|
|
17
12
|
|
|
18
|
-
export const prerender = true;
|
|
19
|
-
|
|
20
13
|
export async function entries() {
|
|
21
14
|
const initialCache = await loadCache($home[0]);
|
|
22
15
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import { error } from '@sveltejs/kit';
|
|
15
15
|
import { getFolder, getFolderLabel } from '../../lib/utilities';
|
|
16
16
|
import { page as pageStore } from '$app/stores';
|
|
17
|
-
import {
|
|
17
|
+
import { invalidateAll } from '$app/navigation';
|
|
18
18
|
|
|
19
19
|
const themes = {
|
|
20
20
|
none: NoStyles,
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
|
|
28
28
|
const { website, folderName } = $derived(data);
|
|
29
29
|
|
|
30
|
+
throw error(404, { message: 'Page not found', ...data });
|
|
31
|
+
|
|
30
32
|
const { slogan, theme } = $derived(website._);
|
|
31
33
|
|
|
32
34
|
// Import CSS for HMR in dev mode
|
|
@@ -42,7 +44,7 @@
|
|
|
42
44
|
|
|
43
45
|
const websiteTitle = data.website._.title || data.folderName;
|
|
44
46
|
const pageMetaTitle = $derived(getFileLabel(page));
|
|
45
|
-
const siteTitle = website._.title
|
|
47
|
+
const siteTitle = website._.title;
|
|
46
48
|
|
|
47
49
|
function getBreadcrumbs(level = 0) {
|
|
48
50
|
const path = $pageStore.data.segments.slice(0, level).join('/');
|
|
@@ -73,8 +75,6 @@
|
|
|
73
75
|
|
|
74
76
|
const favicon = getFavicon();
|
|
75
77
|
|
|
76
|
-
let key = $state(Date.now());
|
|
77
|
-
|
|
78
78
|
$effect(() => {
|
|
79
79
|
// Update website on file change
|
|
80
80
|
if (import.meta.hot) {
|
|
@@ -131,7 +131,12 @@
|
|
|
131
131
|
<header>
|
|
132
132
|
{#if website._.logo}
|
|
133
133
|
<a href="/" class="site-logo">
|
|
134
|
-
|
|
134
|
+
{#if website._.logo.endsWith('.svg')}
|
|
135
|
+
<object type="image/svg+xml" data={website._.logo} title={website._.title || undefined}
|
|
136
|
+
></object>
|
|
137
|
+
{:else}
|
|
138
|
+
<img alt="website logo" src={website._.logo} />
|
|
139
|
+
{/if}
|
|
135
140
|
</a>
|
|
136
141
|
{/if}
|
|
137
142
|
{#if siteTitle}
|