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.
Files changed (82) hide show
  1. package/.prettierrc +8 -0
  2. package/.vscode/settings.json +3 -0
  3. package/README.md +123 -27
  4. package/bin.js +30 -0
  5. package/content/.cache.json +627 -0
  6. package/content/.obsidian/app.json +3 -0
  7. package/content/.obsidian/appearance.json +3 -0
  8. package/content/.obsidian/core-plugins-migration.json +30 -0
  9. package/content/.obsidian/core-plugins.json +20 -0
  10. package/content/.obsidian/workspace.json +168 -0
  11. package/content/about.md +3 -0
  12. package/content/assets/open-props.css +1630 -0
  13. package/content/assets/styles-2.css +128 -0
  14. package/content/assets/styles-3.css +275 -0
  15. package/content/docs/file-structure.md +31 -0
  16. package/content/docs/folder-settings.md +22 -0
  17. package/content/docs/home.md +8 -0
  18. package/content/docs/images.md +10 -0
  19. package/content/docs/pages.md +64 -0
  20. package/content/docs/quickstart.md +52 -0
  21. package/content/docs/run-build-deploy.md +20 -0
  22. package/content/docs/settings.md +4 -0
  23. package/content/docs/styling.md +10 -0
  24. package/content/docs/taxonomies.md +37 -0
  25. package/content/home.md +73 -0
  26. package/content/roadmap.md +81 -0
  27. package/content/settings.md +12 -0
  28. package/content/vercel.json +5 -0
  29. package/jsconfig.json +18 -0
  30. package/package.json +37 -19
  31. package/server.js +80 -0
  32. package/src/app.d.ts +12 -0
  33. package/src/app.html +12 -0
  34. package/src/lib/components/Breadcrumbs.svelte +19 -0
  35. package/src/lib/components/DefaultStyles.svelte +126 -0
  36. package/src/lib/components/FrontMatterTaxonomy.svelte +48 -0
  37. package/src/lib/components/Frontmatter.svelte +54 -0
  38. package/src/lib/components/FrontmatterProperty.svelte +72 -0
  39. package/src/lib/components/Markdown/Image.svelte +48 -0
  40. package/src/lib/components/Markdown/Link.svelte +10 -0
  41. package/src/lib/components/Markdown/LinkPreview.svelte +39 -0
  42. package/src/lib/components/Markdown/Text.svelte +6 -0
  43. package/src/lib/components/Markdown/index.svelte +84 -0
  44. package/src/lib/components/Markdown/validators.js +29 -0
  45. package/src/lib/components/Nav.svelte +39 -0
  46. package/src/lib/components/Page.svelte +59 -0
  47. package/src/lib/components/Sitemap.svelte +38 -0
  48. package/src/lib/components/index.js +9 -0
  49. package/src/lib/index.js +1 -0
  50. package/src/lib/utilities/buildURL.js +18 -0
  51. package/src/lib/utilities/checkFileExists.js +16 -0
  52. package/src/lib/utilities/createFolderClass.js +4 -0
  53. package/src/lib/utilities/createPageClass.js +4 -0
  54. package/src/lib/utilities/getFileLabel.js +18 -0
  55. package/src/lib/utilities/getFolder.js +16 -0
  56. package/src/lib/utilities/getFolderLabel.js +8 -0
  57. package/src/lib/utilities/getPage.js +24 -0
  58. package/src/lib/utilities/getPagesByFolder.js +93 -0
  59. package/src/lib/utilities/index.js +20 -0
  60. package/src/lib/utilities/isActiveLink.js +12 -0
  61. package/src/lib/utilities/isObject.js +8 -0
  62. package/src/lib/utilities/loadCache.js +28 -0
  63. package/src/lib/utilities/mutateMarkdownAST.js +59 -0
  64. package/src/lib/utilities/mutateMarkdownFrontmatter.js +115 -0
  65. package/src/lib/utilities/parseDate.js +43 -0
  66. package/src/lib/utilities/processMarkdownFiles.js +212 -0
  67. package/src/lib/utilities/readMarkdownFile.js +134 -0
  68. package/src/lib/utilities/regexPatterns.js +12 -0
  69. package/src/lib/utilities/resolveHomeDirPath.js +5 -0
  70. package/src/lib/utilities/writeCache.js +14 -0
  71. package/src/routes/[...path]/+layout.server.js +71 -0
  72. package/src/routes/[...path]/+page.server.js +22 -0
  73. package/src/routes/[...path]/+page.svelte +125 -0
  74. package/src/routes/feed.xml/+server.js +120 -0
  75. package/src/routes/robots.txt/+server.js +54 -0
  76. package/src/routes/sitemap.xml/+server.js +68 -0
  77. package/static/favicon.png +0 -0
  78. package/static/styles.css +0 -0
  79. package/svelte.config.js +32 -0
  80. package/vercel.json +5 -0
  81. package/vite.config.js +72 -0
  82. package/index.js +0 -28
@@ -0,0 +1,125 @@
1
+ <script>
2
+ import { Nav, Page, Breadcrumbs, Sitemap, DefaultStyles } from '$lib/components/index.js';
3
+ import { getPage, createPageClass } from '$lib/utilities/index.js';
4
+ import LinkPreview from '$lib/components/Markdown/LinkPreview.svelte';
5
+ import getFileLabel from '../../lib/utilities/getFileLabel';
6
+ import { error } from '@sveltejs/kit';
7
+ import { getFolder, getFolderLabel } from '../../lib/utilities';
8
+ import { page as pageStore } from '$app/stores';
9
+ import { dev, building } from '$app/environment';
10
+
11
+ let { data } = $props();
12
+
13
+ const { website, folderName } = $derived(data);
14
+ const { slogan } = website;
15
+
16
+ if (data.files.css.exists && dev) {
17
+ // Import styles in dev mode
18
+ import(/* @vite-ignore */ data.files.css.path);
19
+ }
20
+
21
+ const page = $derived(getPage(website, data.path));
22
+
23
+ if (!page) {
24
+ error(404, 'Page not found');
25
+ }
26
+
27
+ const websiteTitle = data.website._.title || data.folderName;
28
+ const pageMetaTitle = $derived(getFileLabel(page));
29
+ const siteTitle = website._.title || folderName;
30
+
31
+ function getBreadcrumbs(level = 0) {
32
+ const path = $pageStore.data.segments.slice(0, level).join('/');
33
+ const crumbyPage = getFolder($pageStore.data.website, path);
34
+ const folderLabel = getFolderLabel(crumbyPage);
35
+ const active = level > $pageStore.data.segments.length;
36
+ if (!active) return [folderLabel, ...getBreadcrumbs(level + 1)];
37
+ return [];
38
+ }
39
+
40
+ function makePageMetaTitle() {
41
+ if (page.url === '/') {
42
+ if (slogan) return `${siteTitle || pageMetaTitle} - ${slogan}`;
43
+ return siteTitle || pageMetaTitle;
44
+ }
45
+
46
+ const breadcrumbs = getBreadcrumbs().slice(1, -1).reverse();
47
+ const breadcrumbSegment = breadcrumbs.length ? `${breadcrumbs.join(' - ')}` : '';
48
+ return `${pageMetaTitle} - ${breadcrumbSegment} - ${siteTitle}`;
49
+ }
50
+
51
+ function getFavicon() {
52
+ if (website._?.icon)
53
+ 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>`;
54
+ if (data.files.favicon.exists) return '/favicon.png';
55
+ return false;
56
+ }
57
+
58
+ const favicon = getFavicon();
59
+
60
+ $effect(() => {
61
+ // Update website on file change
62
+ if (import.meta.hot) {
63
+ import.meta.hot.on('vowel:update', ({ path, file }) => {
64
+ let page = getPage(website, path);
65
+
66
+ for (const key in file) {
67
+ page[key] = file[key];
68
+ }
69
+ });
70
+ }
71
+ });
72
+ </script>
73
+
74
+ <!-- svelte-ignore state_referenced_locally -->
75
+ <!-- svelte-ignore state_referenced_locally -->
76
+ <svelte:head>
77
+ <title>{makePageMetaTitle()}</title>
78
+ <meta property="”og:url”" content={page.url} />
79
+ <meta property="og:site_name" content={website._.title || folderName} />
80
+ {#if page.hasOwnProperty('description') || page.imputedProperties?.hasOwnProperty('description')}
81
+ <meta
82
+ property="og:description"
83
+ content={page.description || page.imputedProperties.description}
84
+ />
85
+ {/if}
86
+ {#if favicon}
87
+ <link rel="icon" href={favicon} />
88
+ {/if}
89
+ {#if data.files.css.exists && !dev}
90
+ <!-- Import styles in build mode -->
91
+ <link rel="stylesheet" href="styles.css" />
92
+ {/if}
93
+ </svelte:head>
94
+
95
+ <DefaultStyles />
96
+
97
+ <div data-sveltekit-preload-data="hover" class={createPageClass(page.url)}>
98
+ <header>
99
+ {#if website._.logo}
100
+ <a href="/" class="site-logo">
101
+ <img alt="website logo" src={website._.logo} />
102
+ </a>
103
+ {/if}
104
+ {#if siteTitle}
105
+ <a href="/" class="site-title">{siteTitle}</a>
106
+ {/if}
107
+ {#if website._.slogan}
108
+ <p class="slogan">{website._.slogan}</p>
109
+ {/if}
110
+ <Nav folder={data.website} segments={data.path.split('/')} />
111
+ <nav class="breadcrumbs" aria-label="Breadcrumb">
112
+ <Breadcrumbs level={0} />
113
+ </nav>
114
+ </header>
115
+ <main>
116
+ <Page level={0} {page} {website} path={data.path} />
117
+ </main>
118
+ <aside class="sitemap">
119
+ <Sitemap section={data.website} segments={data.path.split('/')} root />
120
+ </aside>
121
+ <footer>
122
+ © {website._.author ? website._.author + ' ' : ''}
123
+ {new Date().getFullYear()}
124
+ </footer>
125
+ </div>
@@ -0,0 +1,120 @@
1
+ import xml from 'xml';
2
+ import { render } from 'svelte/server';
3
+ import Page from './../../lib/components/Page.svelte';
4
+ import {
5
+ getPagesByFolder,
6
+ getFileLabel,
7
+ processMarkdownFiles,
8
+ loadCache
9
+ } from '../../lib/utilities';
10
+
11
+ export const prerender = true;
12
+
13
+ export async function GET({}) {
14
+ /*
15
+ It's wasteful to load everything here, but it should only run
16
+ once at buildtime, so it's not a significant issue.
17
+ */
18
+ const initialCache = await loadCache($home[0]);
19
+
20
+ const { folder: website } = await processMarkdownFiles(initialCache);
21
+
22
+ const allPages = getPagesByFolder(website, '/');
23
+
24
+ const sortedPages = allPages.filter((page) => page.hasOwnProperty('date'));
25
+
26
+ const site_title = {
27
+ title: website._.title || 'Untitled'
28
+ };
29
+
30
+ const feed = [];
31
+
32
+ feed.push({
33
+ _attr: {
34
+ xmlns: 'http://www.w3.org/2005/Atom'
35
+ }
36
+ });
37
+
38
+ feed.push(site_title);
39
+
40
+ if (website._.domain)
41
+ feed.push(
42
+ {
43
+ link: {
44
+ _attr: {
45
+ rel: 'self',
46
+ href: `${website._.domain}/feed`
47
+ }
48
+ }
49
+ },
50
+ {
51
+ id: 'https://littlefair.ca/feed'
52
+ }
53
+ );
54
+
55
+ if (website._.author)
56
+ feed.push(
57
+ {
58
+ author: {
59
+ name: 'Sam Littlefair'
60
+ }
61
+ },
62
+ { rights: `Copyright (c) ${new Date().getFullYear()} Sam Littlefair` }
63
+ );
64
+
65
+ feed.push(
66
+ ...sortedPages.map((page) => {
67
+ const url = website._.domain ? website._.domain + page.url : 'undefined';
68
+ const entry = [
69
+ {
70
+ title: getFileLabel(page)
71
+ },
72
+ {
73
+ link: {
74
+ _attr: {
75
+ rel: 'alternate',
76
+ href: url
77
+ }
78
+ }
79
+ },
80
+ { id: url },
81
+ { updated: page.date }
82
+ ];
83
+
84
+ if (page.description) {
85
+ entry.push({
86
+ summary: page.description
87
+ });
88
+ }
89
+
90
+ if (page.author) {
91
+ entry.push({
92
+ author: {
93
+ name: page.author
94
+ }
95
+ });
96
+ }
97
+
98
+ entry.push({
99
+ content: [
100
+ { _attr: { type: 'html' } },
101
+ render(Page, { props: { page, level: 0, format: 'xml' } }).html
102
+ ]
103
+ });
104
+
105
+ return {
106
+ entry
107
+ };
108
+ })
109
+ );
110
+
111
+ const headers = {
112
+ 'Cache-Control': 'max-age=0, s-maxage=3600',
113
+ 'Content-Type': 'text/xml'
114
+ };
115
+
116
+ const response = new Response(`<?xml version="1.0" encoding="utf-8"?>` + xml({ feed }, true), {
117
+ headers
118
+ });
119
+ return response;
120
+ }
@@ -0,0 +1,54 @@
1
+ import { processMarkdownFiles, loadCache } from '../../lib/utilities';
2
+
3
+ /*
4
+ TODO: Will this work at this location? Otherwise, figure out how to move it.
5
+ */
6
+
7
+ const allow = `Allow: /`;
8
+ const disallow = `Disallow: /`;
9
+
10
+ const google_images = `User-agent: Googlebot-Image`;
11
+
12
+ const google_general = `User-agent: Google-Extended`;
13
+
14
+ const gpt_bot = `User-agent: GPTBot`;
15
+
16
+ const gpt_user = `User-agent: ChatGPT-User`;
17
+
18
+ const common_crawl = `User-agent: CCBot`;
19
+
20
+ export const prerender = true;
21
+
22
+ export async function GET({}) {
23
+ /*
24
+ It's wasteful to load everything here, but it should only run
25
+ once at buildtime, so it's not a significant issue.
26
+ */
27
+ const initialCache = await loadCache($home[0]);
28
+
29
+ const { folder: website } = await processMarkdownFiles(initialCache);
30
+
31
+ const declarations = [];
32
+
33
+ declarations.push(
34
+ google_general + '\n' + (website._.robots?.google === false ? disallow : allow)
35
+ );
36
+ declarations.push(
37
+ google_images + '\n' + (website._.robots?.google_images === false ? disallow : allow)
38
+ );
39
+ declarations.push(gpt_bot + '\n' + (website._.robots?.ai === false ? disallow : allow));
40
+ declarations.push(gpt_user + '\n' + (website._.robots?.ai === false ? disallow : allow));
41
+ declarations.push(common_crawl + '\n' + (website._.robots?.ai === false ? disallow : allow));
42
+
43
+ const text = declarations.join('\n\n') || '';
44
+
45
+ const headers = {
46
+ 'Cache-Control': 'max-age=0, s-maxage=3600',
47
+ 'Content-Type': 'text/plain'
48
+ };
49
+
50
+ const response = new Response(text, {
51
+ headers
52
+ });
53
+ return response;
54
+ }
@@ -0,0 +1,68 @@
1
+ import { getPagesByFolder, processMarkdownFiles, loadCache } from '../../lib/utilities';
2
+
3
+ export const prerender = true;
4
+
5
+ export async function GET({}) {
6
+ const fallback = {
7
+ folder: {}
8
+ };
9
+
10
+ const initialCache = await loadCache($home[0]);
11
+
12
+ const { folder: website } = await processMarkdownFiles(initialCache);
13
+
14
+ const pages = getPagesByFolder(website, '/');
15
+
16
+ const site_title = {
17
+ title: website._.title || 'Untitled'
18
+ };
19
+
20
+ const domain = website._.domain || false;
21
+
22
+ function createEntry(page) {
23
+ return `
24
+ <url>
25
+ <loc>${new URL(page.url || '/', domain)}</loc>
26
+ <changefreq>daily</changefreq>
27
+ <priority>0.7</priority>
28
+ </url>
29
+ `;
30
+ }
31
+
32
+ function createSitemap(pages, domain) {
33
+ return `<?xml version="1.0" encoding="UTF-8" ?>
34
+ <urlset
35
+ xmlns="https://www.sitemaps.org/schemas/sitemap/0.9"
36
+ xmlns:news="https://www.google.com/schemas/sitemap-news/0.9"
37
+ xmlns:xhtml="https://www.w3.org/1999/xhtml"
38
+ xmlns:mobile="https://www.google.com/schemas/sitemap-mobile/1.0"
39
+ xmlns:image="https://www.google.com/schemas/sitemap-image/1.1"
40
+ xmlns:video="https://www.google.com/schemas/sitemap-video/1.1"
41
+ >
42
+ <url>
43
+ <loc>${domain}</loc>
44
+ <changefreq>daily</changefreq>
45
+ <priority>0.7</priority>
46
+ </url>
47
+ ${pages.map(createEntry).join('')}
48
+ </urlset>
49
+ `;
50
+ }
51
+
52
+ function createFallBackSitemap() {
53
+ return `<?xml version="1.0" encoding="UTF-8" ?><message>Specify a 'domain' in /settings.md</message>`;
54
+ }
55
+
56
+ const sitemap = domain ? createSitemap(pages, domain) : createFallBackSitemap();
57
+
58
+ const headers = {
59
+ 'Cache-Control': 'max-age=0, s-maxage=3600',
60
+ 'Content-Type': 'text/xml'
61
+ };
62
+
63
+ const response = new Response(sitemap, {
64
+ headers
65
+ });
66
+
67
+ return response;
68
+ }
Binary file
File without changes
@@ -0,0 +1,32 @@
1
+ import adapter from '@sveltejs/adapter-static';
2
+ import path, { join } from 'path';
3
+
4
+ const demoDir = process.cwd() + '/content';
5
+ const NPMRunDev = process.argv[2] === 'dev';
6
+ const isBuild = process.argv[2] === 'build';
7
+ const receivedHomePath = NPMRunDev || isBuild ? false : process.argv[2];
8
+ const homeDir = receivedHomePath || demoDir;
9
+ const relativePathToHome = path.relative(process.cwd(), homeDir);
10
+
11
+ /** @type {import('@sveltejs/kit').Config} */
12
+ const config = {
13
+ kit: {
14
+ // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
15
+ // If your environment is not supported or you settled on a specific environment, switch out the adapter.
16
+ // See https://kit.svelte.dev/docs/adapters for more information about adapters.
17
+ adapter: adapter({
18
+ pages: join(relativePathToHome, '.output'),
19
+ assets: join(relativePathToHome, '.output'),
20
+ strict: false
21
+ }),
22
+ files: {
23
+ assets: join(relativePathToHome, 'assets')
24
+ },
25
+ prerender: {
26
+ handleHttpError: 'warn',
27
+ entries: ['*', '/', '/robots.txt', '/sitemap.xml', '/feed.xml']
28
+ }
29
+ }
30
+ };
31
+
32
+ export default config;
package/vercel.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "cleanUrls": true,
3
+ "outputDirectory": "./.output",
4
+ "buildCommand": null
5
+ }
package/vite.config.js ADDED
@@ -0,0 +1,72 @@
1
+ import { sveltekit } from '@sveltejs/kit/vite';
2
+ import { defineConfig } from 'vite';
3
+ import path from 'path';
4
+ import { readMarkdownFile, loadCache } from './src/lib/utilities/index';
5
+
6
+ const demoDir = process.cwd() + '/content';
7
+ const NPMRunDev = process.argv[2] === 'dev';
8
+ const isBuild = process.argv[2] === 'build';
9
+ const receivedHomePath = NPMRunDev || isBuild ? false : process.argv[2];
10
+
11
+ const homeDir = receivedHomePath || demoDir;
12
+
13
+ const $home = [homeDir];
14
+
15
+ export default defineConfig({
16
+ plugins: [
17
+ sveltekit(),
18
+ {
19
+ // Hot reload markdown
20
+ name: 'markdown:watch',
21
+ configureServer(server) {
22
+ console.log(`Root directory: ${homeDir}`);
23
+ server.watcher.add(homeDir);
24
+ server.watcher.on('change', async (homePath, stats) => {
25
+ if (homePath.endsWith('.md')) {
26
+ // Remove `.md`
27
+ const cache = await loadCache(homeDir);
28
+ const file = await readMarkdownFile(homePath, cache);
29
+ Object.assign(file, file.frontmatter);
30
+ const relativePath = path
31
+ .relative(homeDir, homePath)
32
+ .slice(0, -3)
33
+ .replace(/\/?(home|settings)$/, '');
34
+ server.ws.send('vowel:update', {
35
+ path: relativePath,
36
+ stats,
37
+ file
38
+ });
39
+ }
40
+ });
41
+ }
42
+ }
43
+ ],
44
+ define: { $home },
45
+ optimizeDeps: {
46
+ exclude: ['svelte', 'remark', 'remark-frontmatter', 'remark-html'],
47
+ include: [
48
+ 'micromark',
49
+ 'unified',
50
+ 'fault',
51
+ 'url-metadata',
52
+ 'any-date-parser',
53
+ 'any-date-parser/src/formats/ago/ago.js',
54
+ 'any-date-parser/src/formats/ago/ago.js',
55
+ 'any-date-parser/src/formats/chinese/chinese.js',
56
+ 'any-date-parser/src/formats/dayMonth/dayMonth.js',
57
+ 'any-date-parser/src/formats/dayMonthname/dayMonthname.js',
58
+ 'any-date-parser/src/formats/monthDay/monthDay.js',
59
+ 'any-date-parser/src/formats/monthnameDay/monthnameDay.js',
60
+ 'any-date-parser/src/formats/today/today.js'
61
+ ]
62
+ },
63
+ server: {
64
+ fs: {
65
+ strict: false,
66
+ allow: homeDir
67
+ }
68
+ },
69
+ resolve: {
70
+ // extensions: ['.css']
71
+ }
72
+ });
package/index.js DELETED
@@ -1,28 +0,0 @@
1
- const vowels = ['a', 'e', 'i', 'o', 'u'];
2
-
3
- function isVowel(c) {
4
- return vowels.indexOf(c.toLowerCase()) !== -1;
5
- }
6
-
7
- function includesVowel(s) {
8
- for (let i = 0; i < s.length; i += 1) {
9
- if (isVowel(s.charAt(i))) return true;
10
- }
11
- return false;
12
- }
13
-
14
- function startsWithVowel(s) {
15
- return vowels.some(v => s.toLowerCase().startsWith(v));
16
- }
17
-
18
- function endsWithVowel(s) {
19
- return vowels.some(v => s.toLowerCase().endsWith(v));
20
- }
21
-
22
- module.exports = {
23
- vowels,
24
- isVowel,
25
- includesVowel,
26
- startsWithVowel,
27
- endsWithVowel,
28
- };