vowel 0.0.2 → 0.1.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/.prettierrc +8 -0
- package/.vscode/settings.json +3 -0
- package/README.md +123 -27
- package/bin.js +30 -0
- package/content/.cache.json +627 -0
- package/content/.obsidian/app.json +3 -0
- package/content/.obsidian/appearance.json +3 -0
- package/content/.obsidian/core-plugins-migration.json +30 -0
- package/content/.obsidian/core-plugins.json +20 -0
- package/content/.obsidian/workspace.json +168 -0
- package/content/about.md +3 -0
- package/content/assets/open-props.css +1630 -0
- package/content/assets/styles-2.css +128 -0
- package/content/assets/styles-3.css +275 -0
- package/content/docs/file-structure.md +31 -0
- package/content/docs/folder-settings.md +22 -0
- package/content/docs/home.md +8 -0
- package/content/docs/images.md +10 -0
- package/content/docs/pages.md +64 -0
- package/content/docs/quickstart.md +52 -0
- package/content/docs/run-build-deploy.md +20 -0
- package/content/docs/settings.md +4 -0
- package/content/docs/styling.md +10 -0
- package/content/docs/taxonomies.md +37 -0
- package/content/home.md +73 -0
- package/content/roadmap.md +81 -0
- package/content/settings.md +12 -0
- package/content/vercel.json +5 -0
- package/jsconfig.json +18 -0
- package/package.json +37 -19
- package/server.js +80 -0
- package/src/app.d.ts +12 -0
- package/src/app.html +12 -0
- package/src/lib/components/Breadcrumbs.svelte +19 -0
- package/src/lib/components/DefaultStyles.svelte +126 -0
- package/src/lib/components/FrontMatterTaxonomy.svelte +48 -0
- package/src/lib/components/Frontmatter.svelte +54 -0
- package/src/lib/components/FrontmatterProperty.svelte +72 -0
- package/src/lib/components/Markdown/Image.svelte +48 -0
- package/src/lib/components/Markdown/Link.svelte +10 -0
- package/src/lib/components/Markdown/LinkPreview.svelte +39 -0
- package/src/lib/components/Markdown/Text.svelte +6 -0
- package/src/lib/components/Markdown/index.svelte +84 -0
- package/src/lib/components/Markdown/validators.js +29 -0
- package/src/lib/components/Nav.svelte +39 -0
- package/src/lib/components/Page.svelte +59 -0
- package/src/lib/components/Sitemap.svelte +38 -0
- package/src/lib/components/index.js +9 -0
- package/src/lib/index.js +1 -0
- package/src/lib/utilities/buildURL.js +18 -0
- package/src/lib/utilities/checkFileExists.js +16 -0
- package/src/lib/utilities/createFolderClass.js +4 -0
- package/src/lib/utilities/createPageClass.js +4 -0
- package/src/lib/utilities/getFileLabel.js +18 -0
- package/src/lib/utilities/getFolder.js +16 -0
- package/src/lib/utilities/getFolderLabel.js +8 -0
- package/src/lib/utilities/getPage.js +24 -0
- package/src/lib/utilities/getPagesByFolder.js +93 -0
- package/src/lib/utilities/index.js +20 -0
- package/src/lib/utilities/isActiveLink.js +12 -0
- package/src/lib/utilities/isObject.js +8 -0
- package/src/lib/utilities/loadCache.js +28 -0
- package/src/lib/utilities/mutateMarkdownAST.js +59 -0
- package/src/lib/utilities/mutateMarkdownFrontmatter.js +115 -0
- package/src/lib/utilities/parseDate.js +43 -0
- package/src/lib/utilities/processMarkdownFiles.js +212 -0
- package/src/lib/utilities/readMarkdownFile.js +134 -0
- package/src/lib/utilities/regexPatterns.js +12 -0
- package/src/lib/utilities/resolveHomeDirPath.js +5 -0
- package/src/lib/utilities/writeCache.js +14 -0
- package/src/routes/[...path]/+layout.server.js +71 -0
- package/src/routes/[...path]/+page.server.js +22 -0
- package/src/routes/[...path]/+page.svelte +125 -0
- package/src/routes/feed.xml/+server.js +120 -0
- package/src/routes/robots.txt/+server.js +54 -0
- package/src/routes/sitemap.xml/+server.js +68 -0
- package/static/favicon.png +0 -0
- package/static/styles.css +0 -0
- package/svelte.config.js +32 -0
- package/vercel.json +5 -0
- package/vite.config.js +72 -0
- package/index.js +0 -28
package/content/home.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Home
|
|
2
|
+
|
|
3
|
+
Vowel is a very simple Markdown- and CSS-based website generator.
|
|
4
|
+
|
|
5
|
+
To try it out, `cd` into a directory of markdown files and run:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
npx -p svelte@next -p vowel@latest npx vowel
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
(This `npx` command forces the Svelte 5 peer depedency, without which the app will throw an error.)
|
|
12
|
+
|
|
13
|
+
 Here's a photo
|
|
14
|
+
|
|
15
|
+
Vowel is a minimal website framework for developers who love to write CSS and read RSS.
|
|
16
|
+
|
|
17
|
+
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
|
+
|
|
19
|
+
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.)
|
|
20
|
+
|
|
21
|
+
Vowel is created by Sam Littlefair. If you have questions or comments, [reach out to me on Twitter](https://twitter.com/samlfair).
|
|
22
|
+
|
|
23
|
+
**Warning!** This project is still in early development. There are many bugs and the API is changing quickly.
|
|
24
|
+
|
|
25
|
+
# Features
|
|
26
|
+
|
|
27
|
+
## 🗺️ Sitemap
|
|
28
|
+
|
|
29
|
+
Submit your website to search engines with a sitemap that lists all of your pages.
|
|
30
|
+
|
|
31
|
+
## 📡 RSS
|
|
32
|
+
|
|
33
|
+
Deliver your content to RSS readers with an Atom feed that lists your full posts in reverse-chronology.
|
|
34
|
+
|
|
35
|
+
## ⚡️ Speed
|
|
36
|
+
|
|
37
|
+
Get a blazing-fast, Svelte-rendered website.
|
|
38
|
+
|
|
39
|
+
## 🗿 Static generation
|
|
40
|
+
|
|
41
|
+
Load pages quickly with plain HTML.
|
|
42
|
+
|
|
43
|
+
## 🔒 Copyright
|
|
44
|
+
|
|
45
|
+
Display an up-to-date copyright in the footer of your website.
|
|
46
|
+
|
|
47
|
+
## 🔗 Rich link previews
|
|
48
|
+
|
|
49
|
+
Create rich links with titles and images, both internally and externally.
|
|
50
|
+
|
|
51
|
+
## 🚏 Navigation
|
|
52
|
+
|
|
53
|
+
Structure your content with a header, nav, sitemap, and breadcrumbs.
|
|
54
|
+
|
|
55
|
+
## 🙃 Emoji favicon
|
|
56
|
+
|
|
57
|
+
Use any emoji you want for your website's icon.
|
|
58
|
+
|
|
59
|
+
## 🖇️ Custom taxonomies
|
|
60
|
+
|
|
61
|
+
Go beyond tags to create authors and categories.
|
|
62
|
+
|
|
63
|
+
## 🧮 Advanced frontmatter
|
|
64
|
+
|
|
65
|
+
Display dates, lists, nested properties, links, and images in your frontmatter.
|
|
66
|
+
|
|
67
|
+
## 👩🎤 Live editing
|
|
68
|
+
|
|
69
|
+
See updates live as you save.
|
|
70
|
+
|
|
71
|
+
# Try it
|
|
72
|
+
|
|
73
|
+
/docs/quickstart
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: What's planned.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Roadmap
|
|
6
|
+
|
|
7
|
+
## MVP
|
|
8
|
+
|
|
9
|
+
- [x] Add all element types supported in Obsidian to markdown
|
|
10
|
+
- [x] Add rich link previews
|
|
11
|
+
- [x] Add RSS
|
|
12
|
+
- [x] Add robots.txt
|
|
13
|
+
- [x] AI
|
|
14
|
+
- [x] Search
|
|
15
|
+
- [x] Image
|
|
16
|
+
- [x] Typing
|
|
17
|
+
- [x] Add sitemap
|
|
18
|
+
- [x] Make taxonomies linkable in frontmatter (if a corresponding folder exists)
|
|
19
|
+
- [x] Add header metadata (meta tags)
|
|
20
|
+
- [x] CSS handling
|
|
21
|
+
- [x] Favicon handling
|
|
22
|
+
- [x] Docs
|
|
23
|
+
- [x] Build action
|
|
24
|
+
|
|
25
|
+
## V2
|
|
26
|
+
|
|
27
|
+
- [x] Fallback homepages
|
|
28
|
+
- [ ] Create post list element (`#tags/blue`, `@tags/blue`, `~tags/blue`)
|
|
29
|
+
- [/] CSS defaults (work in progress)
|
|
30
|
+
- [ ] CSS layout [presets](https://github.com/swyxio/spark-joy?tab=readme-ov-file#drop-in-css-frameworks)
|
|
31
|
+
- [x] Images as <figure>s
|
|
32
|
+
- [x] Frontmatter interpolation (title and description)
|
|
33
|
+
- [ ] Frontmatter interpolation (image)
|
|
34
|
+
- [x] Better favicon and styles handling
|
|
35
|
+
- [x] Ignore README files
|
|
36
|
+
- [ ] Add more header metadata
|
|
37
|
+
- [ ] Define folder settings
|
|
38
|
+
- [ ] Title
|
|
39
|
+
- [ ] Breadrumb
|
|
40
|
+
- [x] Define special page properties
|
|
41
|
+
- [x] Title
|
|
42
|
+
- [x] Description
|
|
43
|
+
- [x] Meta image
|
|
44
|
+
- [x] Date
|
|
45
|
+
- [ ] Modified date
|
|
46
|
+
- [ ] Tags
|
|
47
|
+
- [ ] Author/Authors
|
|
48
|
+
- [ ] Make file names kebab-cased for URLs and links
|
|
49
|
+
- [x] Include breadcrumbs in page titles ~~(optionally)~~
|
|
50
|
+
- [ ] Strikethrough
|
|
51
|
+
- [ ] Add hidden routes (`$`)
|
|
52
|
+
- [x] Image alt text
|
|
53
|
+
- [ ] Code highlighter
|
|
54
|
+
- [x] Deploy action (Vercel)
|
|
55
|
+
- [x] Add anchor links for headings
|
|
56
|
+
- [ ] Post TOC
|
|
57
|
+
- [ ] GitHub-style notes
|
|
58
|
+
- [ ] Add in-browser search
|
|
59
|
+
- [x] Figure out how to stop components from remounting on every change
|
|
60
|
+
- [ ] Make RSS optional
|
|
61
|
+
- [ ] Pagination
|
|
62
|
+
- [x] Demote headings global option (headings are now demoted intelligently)
|
|
63
|
+
|
|
64
|
+
## Wish list
|
|
65
|
+
|
|
66
|
+
- [ ] Automatic file creation
|
|
67
|
+
- [ ] Image serving and optimization
|
|
68
|
+
- [ ] GUI
|
|
69
|
+
- [ ] More deploy actions
|
|
70
|
+
- [ ] Optional JSON interactivity (ratings, comments, SubPubHub)
|
|
71
|
+
- [ ] Redirects
|
|
72
|
+
- [ ] Advanced markdown
|
|
73
|
+
- [ ] Highlight
|
|
74
|
+
- [ ] Sanitize
|
|
75
|
+
- [ ] Tables
|
|
76
|
+
- [ ] Strike
|
|
77
|
+
- [ ] Task lists
|
|
78
|
+
- [ ] Footnotes
|
|
79
|
+
- [ ] Mermaid
|
|
80
|
+
- [ ] Math
|
|
81
|
+
- [ ] Wikilinks
|
package/jsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
// "extends": "./.svelte-kit/tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"allowJs": true,
|
|
5
|
+
"checkJs": true,
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"forceConsistentCasingInFileNames": true,
|
|
8
|
+
"resolveJsonModule": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"sourceMap": true,
|
|
11
|
+
"strict": true,
|
|
12
|
+
"moduleResolution": "bundler"
|
|
13
|
+
}
|
|
14
|
+
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias and https://kit.svelte.dev/docs/configuration#files
|
|
15
|
+
//
|
|
16
|
+
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
|
17
|
+
// from the referenced tsconfig.json - TypeScript does not merge them in
|
|
18
|
+
}
|
package/package.json
CHANGED
|
@@ -1,21 +1,39 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
2
|
+
"name": "vowel",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"bin": "./bin.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "vite dev",
|
|
7
|
+
"build": "vite build",
|
|
8
|
+
"preview": "vite preview",
|
|
9
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
|
|
10
|
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
|
|
11
|
+
"lint": "prettier --check .",
|
|
12
|
+
"format": "prettier --write ."
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@picocss/pico": "^1.5.10",
|
|
17
|
+
"@sveltejs/adapter-auto": "^3.0.0",
|
|
18
|
+
"@sveltejs/adapter-static": "^3.0.1",
|
|
19
|
+
"@sveltejs/kit": "^2.5.6",
|
|
20
|
+
"@sveltejs/vite-plugin-svelte": "^4.0.0-next.3",
|
|
21
|
+
"any-date-parser": "^1.5.4",
|
|
22
|
+
"change-case": "^5.4.1",
|
|
23
|
+
"js-yaml": "^4.1.0",
|
|
24
|
+
"milligram": "^1.4.1",
|
|
25
|
+
"object-path": "^0.11.8",
|
|
26
|
+
"open-props": "^1.6.13",
|
|
27
|
+
"prettier": "^3.0.0",
|
|
28
|
+
"prettier-plugin-svelte": "^3.0.0",
|
|
29
|
+
"remark": "^15.0.1",
|
|
30
|
+
"remark-frontmatter": "^5.0.0",
|
|
31
|
+
"remark-html": "^16.0.1",
|
|
32
|
+
"spectre.css": "^0.5.9",
|
|
33
|
+
"svelte": "^5.0.0-next.121",
|
|
34
|
+
"typescript": "^5.0.0",
|
|
35
|
+
"url-metadata": "^3.4.9",
|
|
36
|
+
"vite": "^5.0.12",
|
|
37
|
+
"xml": "^1.0.1"
|
|
38
|
+
}
|
|
21
39
|
}
|
package/server.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { fileURLToPath } from 'url';
|
|
2
|
+
import { createServer, build } from 'vite';
|
|
3
|
+
import { writeFile, mkdir, readFile } from 'fs/promises';
|
|
4
|
+
import { existsSync } from 'fs';
|
|
5
|
+
import { dirname, join } from 'path';
|
|
6
|
+
|
|
7
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
8
|
+
|
|
9
|
+
// Check if this is a package build
|
|
10
|
+
const isBuild = process.argv[2] === 'build';
|
|
11
|
+
|
|
12
|
+
// If this is not a build, read the process argument
|
|
13
|
+
const arg = !isBuild ? process.argv[2] : false;
|
|
14
|
+
|
|
15
|
+
// Check if this is a user (process argument) or package dev (cwd)
|
|
16
|
+
const $home = [arg || join(process.cwd(), 'content')];
|
|
17
|
+
|
|
18
|
+
const define = {
|
|
19
|
+
$home
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const config = {
|
|
23
|
+
// any valid user config options, plus `mode` and `configFile`
|
|
24
|
+
configFile: join(__dirname, 'vite.config.js'),
|
|
25
|
+
root: __dirname,
|
|
26
|
+
server: {
|
|
27
|
+
port: 1337
|
|
28
|
+
},
|
|
29
|
+
define
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const vercelDefaults = {
|
|
33
|
+
cleanUrls: true,
|
|
34
|
+
outputDirectory: '.output',
|
|
35
|
+
buildCommand: null
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
async function buildProject() {
|
|
39
|
+
const outputDir = join($home[0], '.output');
|
|
40
|
+
const vercelConfigPath = join($home[0], 'vercel.json');
|
|
41
|
+
const vercelDirPath = join($home[0], '.vercel');
|
|
42
|
+
const projectJsonPath = join(vercelDirPath, 'project.json');
|
|
43
|
+
let projectData = null;
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
if (existsSync(projectJsonPath)) {
|
|
47
|
+
const data = await readFile(projectJsonPath, 'utf-8');
|
|
48
|
+
projectData = JSON.parse(data);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
await build(config);
|
|
52
|
+
|
|
53
|
+
if (projectData !== null) {
|
|
54
|
+
await mkdir(vercelDirPath, { recursive: true });
|
|
55
|
+
await writeFile(projectJsonPath, JSON.stringify(projectData, null, 2), 'utf-8');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const jsonData = JSON.stringify(vercelDefaults, null, 2);
|
|
59
|
+
await writeFile(vercelConfigPath, jsonData, 'utf-8');
|
|
60
|
+
await writeFile(join($home[0], '.output', 'vercel.json'), jsonData, 'utf-8');
|
|
61
|
+
console.log('Build completed successfully.');
|
|
62
|
+
} catch (err) {
|
|
63
|
+
console.error('Build failed:', err);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function runServer() {
|
|
68
|
+
const server = await createServer(config);
|
|
69
|
+
await server.listen();
|
|
70
|
+
|
|
71
|
+
server.printUrls();
|
|
72
|
+
server.bindCLIShortcuts({ print: true });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// If build mode
|
|
76
|
+
if (isBuild || process.argv[3] === 'true') {
|
|
77
|
+
buildProject();
|
|
78
|
+
} else {
|
|
79
|
+
runServer();
|
|
80
|
+
}
|
package/src/app.d.ts
ADDED
package/src/app.html
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { page } from '$app/stores';
|
|
3
|
+
import { getFolderLabel, getFolder } from '$lib/utilities';
|
|
4
|
+
|
|
5
|
+
let { level } = $props();
|
|
6
|
+
|
|
7
|
+
const path = $derived($page.data.segments.slice(0, level).join('/'));
|
|
8
|
+
// The page for the current crumb
|
|
9
|
+
const crumbyPage = $derived(getFolder($page.data.website, path));
|
|
10
|
+
const folderLabel = $derived(getFolderLabel(crumbyPage));
|
|
11
|
+
|
|
12
|
+
const active = $derived(level === $page.data.segments.length);
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<a aria-current={active ? 'page' : undefined} href={crumbyPage?.['$'].url}>{folderLabel}</a>
|
|
16
|
+
{#if !active}
|
|
17
|
+
<span aria-hidden="true" class="separator"></span>
|
|
18
|
+
<svelte:self level={level + 1} />
|
|
19
|
+
{/if}
|
|
@@ -0,0 +1,126 @@
|
|
|
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
|
+
}
|
|
9
|
+
|
|
10
|
+
:global(:where(html, body, .page)) {
|
|
11
|
+
margin: 0;
|
|
12
|
+
padding: 0;
|
|
13
|
+
box-sizing: border-box;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
:global(:where(.container)) {
|
|
17
|
+
height: 100vh;
|
|
18
|
+
overflow-y: scroll;
|
|
19
|
+
font-family: var(--font-sans);
|
|
20
|
+
}
|
|
21
|
+
|
|
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
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Header */
|
|
33
|
+
|
|
34
|
+
:global(:where(nav)) {
|
|
35
|
+
display: flex;
|
|
36
|
+
column-gap: 1rem;
|
|
37
|
+
row-gap: 0.5rem;
|
|
38
|
+
flex-wrap: wrap;
|
|
39
|
+
justify-content: flex-start;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
:global(:where(nav + nav)) {
|
|
43
|
+
margin-top: 1rem;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
:global(:where(nav > a)) {
|
|
47
|
+
text-wrap: nowrap;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
:global(:where(header a.site-title)) {
|
|
51
|
+
color: unset;
|
|
52
|
+
text-decoration: none;
|
|
53
|
+
font-weight: bold;
|
|
54
|
+
font-size: 2em;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
:global(:where(nav, aside.sitemap) :where([aria-current='page'], [aria-current='true'])) {
|
|
58
|
+
text-decoration: none;
|
|
59
|
+
color: unset;
|
|
60
|
+
cursor: default;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
:global(:where(header .breadcrumbs)) {
|
|
64
|
+
margin-top: 2rem;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
:global(:where(header .breadcrumbs a)) {
|
|
68
|
+
font-size: 0.9em;
|
|
69
|
+
text-decoration: none;
|
|
70
|
+
color: unset;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
:global(:where(header .breadcrumbs .separator):after) {
|
|
74
|
+
content: '/';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* Content block elements */
|
|
78
|
+
|
|
79
|
+
:global(:where(pre)) {
|
|
80
|
+
padding: 1rem;
|
|
81
|
+
background: #eee;
|
|
82
|
+
overflow-x: auto;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
:global(:where(article)) {
|
|
86
|
+
border: 8px ridge #eee;
|
|
87
|
+
padding-inline: 1rem;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
:global(:where(article + article)) {
|
|
91
|
+
margin-top: 1rem;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* Content inline elements */
|
|
95
|
+
|
|
96
|
+
:global(:where(code)) {
|
|
97
|
+
background: #eee;
|
|
98
|
+
margin: -3px -1px;
|
|
99
|
+
padding: 3px 4px;
|
|
100
|
+
border-radius: 5px;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* Aside */
|
|
104
|
+
|
|
105
|
+
:global(:where(aside.sitemap)) {
|
|
106
|
+
margin-top: auto;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
:global([aria-current='page']) {
|
|
110
|
+
color: unset;
|
|
111
|
+
text-decoration: none;
|
|
112
|
+
cursor: unset;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* Footer */
|
|
116
|
+
|
|
117
|
+
:global(:where(footer)) {
|
|
118
|
+
text-align: center;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Pages */
|
|
122
|
+
|
|
123
|
+
:global(:where(.page.home > main > h1)) {
|
|
124
|
+
display: none;
|
|
125
|
+
}
|
|
126
|
+
</style>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { sentenceCase } from 'change-case';
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
import { getFileLabel, getPage } from '$lib/utilities';
|
|
5
|
+
|
|
6
|
+
/** @type {{ property: any, key: string }}*/
|
|
7
|
+
let { property, key, website } = $props();
|
|
8
|
+
|
|
9
|
+
const path = key + '/' + property;
|
|
10
|
+
const page = getPage(website, path);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Check if a value is an object
|
|
14
|
+
* @param {any} value - Value to check.
|
|
15
|
+
* @returns {boolean}
|
|
16
|
+
*/
|
|
17
|
+
function isObject(value) {
|
|
18
|
+
return typeof value === 'object' && !Array.isArray(value) && value !== null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Check if a value is an object
|
|
23
|
+
* @param {any} value - Value to check.
|
|
24
|
+
* @returns {boolean}
|
|
25
|
+
*/
|
|
26
|
+
function isArray(value) {
|
|
27
|
+
return typeof value === 'object' && Array.isArray(value) && value !== null;
|
|
28
|
+
}
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<dl>
|
|
32
|
+
<dt class={key}>{sentenceCase(key)}</dt>
|
|
33
|
+
<dd class={key}>
|
|
34
|
+
{#if isArray(property)}
|
|
35
|
+
<ul>
|
|
36
|
+
{#each property as item}
|
|
37
|
+
{@const path = key + '/' + item}
|
|
38
|
+
{@const page = getPage(website, path)}
|
|
39
|
+
<li>
|
|
40
|
+
<a href={page.url}>{getFileLabel(page)}</a>
|
|
41
|
+
</li>
|
|
42
|
+
{/each}
|
|
43
|
+
</ul>
|
|
44
|
+
{:else}
|
|
45
|
+
<a href={page.url}>{getFileLabel(page)}</a>
|
|
46
|
+
{/if}
|
|
47
|
+
</dd>
|
|
48
|
+
</dl>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { FrontmatterProperty, FrontmatterTaxonomy } from './';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {import("../utilities/processMarkdownFiles").MarkdownFile} MarkdownFile
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @type {{props: {properties: MarkdownFile}}}
|
|
10
|
+
*/
|
|
11
|
+
let { props } = $props();
|
|
12
|
+
// TODO: Normalize frontmatter props
|
|
13
|
+
let { properties, website } = $derived(props);
|
|
14
|
+
|
|
15
|
+
const excludedProperties = [
|
|
16
|
+
'title',
|
|
17
|
+
'description',
|
|
18
|
+
'type',
|
|
19
|
+
'ast',
|
|
20
|
+
'url',
|
|
21
|
+
'frontmatter',
|
|
22
|
+
'published',
|
|
23
|
+
'date',
|
|
24
|
+
'image',
|
|
25
|
+
'breadcrumb',
|
|
26
|
+
'imputedProperties'
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
// const { website } = $page.data;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Fetch a given property from a page.
|
|
33
|
+
* @param {MarkdownFile} properties - A markdown file.
|
|
34
|
+
* @param {string} key - The name of a property
|
|
35
|
+
* @returns {any}
|
|
36
|
+
*/
|
|
37
|
+
function getProperty(properties, key) {
|
|
38
|
+
// @ts-ignore
|
|
39
|
+
return properties[key];
|
|
40
|
+
}
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
{#if properties}
|
|
44
|
+
{#each Object.keys(properties) as key}
|
|
45
|
+
{#if !excludedProperties.includes(key)}
|
|
46
|
+
{@const property = getProperty(properties, key)}
|
|
47
|
+
{#if website.hasOwnProperty(key)}
|
|
48
|
+
<FrontmatterTaxonomy {property} {key} {website} />
|
|
49
|
+
{:else}
|
|
50
|
+
<FrontmatterProperty {property} {key} />
|
|
51
|
+
{/if}
|
|
52
|
+
{/if}
|
|
53
|
+
{/each}
|
|
54
|
+
{/if}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { sentenceCase } from 'change-case';
|
|
3
|
+
import Frontmatter from './Frontmatter.svelte';
|
|
4
|
+
import Image from './Markdown/Image.svelte';
|
|
5
|
+
|
|
6
|
+
/** @type {{ property: any, key: string }}*/
|
|
7
|
+
let { property, key } = $props();
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
{#if property}
|
|
11
|
+
<dl class={key}>
|
|
12
|
+
<dt>{sentenceCase(key)}</dt>
|
|
13
|
+
<dd>
|
|
14
|
+
{@render propertySwitch(property)}
|
|
15
|
+
</dd>
|
|
16
|
+
</dl>
|
|
17
|
+
{/if}
|
|
18
|
+
|
|
19
|
+
{#snippet array(items)}
|
|
20
|
+
<ul>
|
|
21
|
+
{#each items as item}
|
|
22
|
+
<li>
|
|
23
|
+
{@render propertySwitch(item)}
|
|
24
|
+
</li>
|
|
25
|
+
{/each}
|
|
26
|
+
</ul>
|
|
27
|
+
{/snippet}
|
|
28
|
+
|
|
29
|
+
{#snippet propertySwitch(value)}
|
|
30
|
+
{#if value.type === 'array'}
|
|
31
|
+
{@render array(value.output)}
|
|
32
|
+
{:else if value.type === 'object'}
|
|
33
|
+
<Frontmatter props={{ properties: value.output }} />
|
|
34
|
+
{:else if value.type === 'image'}
|
|
35
|
+
{@render image(value.output)}
|
|
36
|
+
{:else if value.type === 'date'}
|
|
37
|
+
{@render date(value.output)}
|
|
38
|
+
{:else if value.type === 'url'}
|
|
39
|
+
{@render url(value.output)}
|
|
40
|
+
{:else}
|
|
41
|
+
{value.output || value}
|
|
42
|
+
{/if}
|
|
43
|
+
{/snippet}
|
|
44
|
+
|
|
45
|
+
{#snippet image(value)}
|
|
46
|
+
<Image node={{ url: value, alt: '' }} />
|
|
47
|
+
{/snippet}
|
|
48
|
+
|
|
49
|
+
{#snippet date(value)}
|
|
50
|
+
{@const date = new Date(value)}
|
|
51
|
+
<time datetime={date.toISOString()}>{date.toLocaleDateString('en-US')}</time>
|
|
52
|
+
{/snippet}
|
|
53
|
+
|
|
54
|
+
{#snippet url(value)}
|
|
55
|
+
<article>
|
|
56
|
+
<a href={value.url}>
|
|
57
|
+
{#if value['og:image']}
|
|
58
|
+
<img src={value['og:image']} alt="" />
|
|
59
|
+
{/if}
|
|
60
|
+
<h2>
|
|
61
|
+
{value['og:title'] || value.title}
|
|
62
|
+
</h2>
|
|
63
|
+
<p>
|
|
64
|
+
{value['og:description']}
|
|
65
|
+
</p>
|
|
66
|
+
</a>
|
|
67
|
+
</article>
|
|
68
|
+
{/snippet}
|
|
69
|
+
|
|
70
|
+
{#snippet pdf(value)}
|
|
71
|
+
<a href={value.url}>{value.url}</a>
|
|
72
|
+
{/snippet}
|