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,48 @@
1
+ <script>
2
+ let { node } = $props();
3
+ let { url, alt } = node;
4
+
5
+ let defaultWidth = 800;
6
+ const srcsetWidths = [640, 828, 1080, 1200, 1920, 2048, 3840];
7
+
8
+ let urlObject = createURLObject();
9
+
10
+ function createURLObject() {
11
+ try {
12
+ return new URL(url);
13
+ } catch (e) {
14
+ console.error(`Error on image ${url}:`);
15
+ console.error(e);
16
+ }
17
+ }
18
+
19
+ let srcset = createSrcset();
20
+
21
+ function createSrcset() {
22
+ if (urlObject?.host.includes('imgix.net')) {
23
+ urlObject?.searchParams.set('width', String(defaultWidth));
24
+ urlObject?.searchParams.append('auto', 'format,compress');
25
+
26
+ return srcsetWidths.reduce((srcset, width) => {
27
+ urlObject?.searchParams.set('width', width);
28
+ return `${srcset}, ${urlObject?.href} ${width}w`;
29
+ }, '');
30
+
31
+ urlObject?.searchParams.set('width', String(defaultWidth));
32
+ }
33
+ }
34
+ </script>
35
+
36
+ {#if urlObject}
37
+ <img src={urlObject.href} {alt} {srcset} />
38
+ {/if}
39
+
40
+ <style>
41
+ img {
42
+ transition: max-width 1s linear;
43
+ max-width: 100%;
44
+ height: auto;
45
+ width: auto;
46
+ margin-inline: auto;
47
+ }
48
+ </style>
@@ -0,0 +1,10 @@
1
+ <script>
2
+ import Markdown from './index.svelte';
3
+
4
+ let { node, level, website } = $props();
5
+ let { url, children } = $derived(node);
6
+ </script>
7
+
8
+ <a href={url}>
9
+ <Markdown props={{ ast: children, level, website }} />
10
+ </a>
@@ -0,0 +1,39 @@
1
+ <script>
2
+ let { metadata, url } = $props();
3
+ let { image, description, author } = $derived(metadata || {});
4
+
5
+ const href = $derived(metadata?.ogURL || metadata?.canonicalURL || url || '');
6
+
7
+ const title = $derived(metadata?.ogTitle || metadata?.title || '');
8
+
9
+ const urlObject = new URL(url);
10
+ </script>
11
+
12
+ {#if metadata}
13
+ <article class="link-preview">
14
+ <a {href}>
15
+ {#if image}
16
+ <img src={image} alt="" />
17
+ {/if}
18
+ <h2>{title ? title + ' - ' : ''}<span class="host">{urlObject.host}</span></h2>
19
+ {#if author}
20
+ <p>
21
+ By {author}
22
+ </p>
23
+ {/if}
24
+ {#if description}
25
+ <p>
26
+ {description}
27
+ </p>
28
+ {/if}
29
+ </a>
30
+ </article>
31
+ {:else}
32
+ <article class="link-preview">
33
+ <a {href}>
34
+ <h2>
35
+ <span class="host">{urlObject.host}</span>
36
+ </h2>
37
+ </a>
38
+ </article>
39
+ {/if}
@@ -0,0 +1,6 @@
1
+ <script>
2
+ let { node } = $props();
3
+ let { value } = $derived(node);
4
+ </script>
5
+
6
+ {value}
@@ -0,0 +1,84 @@
1
+ <script>
2
+ import { Page } from '$lib/components';
3
+ import { getPage, getPagesByFolder, createPageClass, createFolderClass } from '$lib/utilities';
4
+ import { getFolderAndCount, isFileLink } from './validators';
5
+ import Image from './Image.svelte';
6
+ import Link from './Link.svelte';
7
+ import Text from './Text.svelte';
8
+ import LinkPreview from './LinkPreview.svelte';
9
+ import { toString } from 'mdast-util-to-string';
10
+ import { kebabCase } from 'change-case';
11
+
12
+ let { props } = $props();
13
+
14
+ let { ast, level, website } = $derived(props);
15
+
16
+ function getElement(node) {
17
+ if (node.type === 'heading') {
18
+ return ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'div'][node.depth - 1];
19
+ }
20
+ const elements = {
21
+ emphasis: 'em',
22
+ text: 'span',
23
+ paragraph: 'p',
24
+ strong: 'strong',
25
+ list: node.ordered ? 'ol' : 'ul',
26
+ listItem: 'li',
27
+ blockquote: 'blockquote',
28
+ inlineCode: 'code',
29
+ code: 'pre',
30
+ figure: 'figure',
31
+ figcaption: 'figcaption'
32
+ };
33
+
34
+ return elements[node.type];
35
+ }
36
+ </script>
37
+
38
+ {#if ast}
39
+ {#each ast as node}
40
+ {@const { count, path, property, content } = getFolderAndCount(node)}
41
+ {#if count}
42
+ {@const pages = getPagesByFolder(website, path, true, count, property)}
43
+ <section class={createFolderClass(path)}>
44
+ {#each pages as page}
45
+ <article class={createPageClass(page.url, 'thumbnail')}>
46
+ <Page link={true} {content} {page} level={level + 1} {website} />
47
+ </article>
48
+ {/each}
49
+ </section>
50
+ {:else if isFileLink(node)}
51
+ <article class={createPageClass(node.children[0].value, 'thumbnail')}>
52
+ <Page
53
+ link={true}
54
+ page={getPage(website, node.children[0].value)}
55
+ level={level + 1}
56
+ {website}
57
+ content={false}
58
+ />
59
+ </article>
60
+ {:else if node.type === 'heading'}
61
+ <svelte:element this={getElement(node)} id={kebabCase(toString(node))}>
62
+ <svelte:self props={{ ast: node.children, level }} />
63
+ </svelte:element>
64
+ {:else if node.type === 'link' && node.children}
65
+ <Link {node} {level} {website} />
66
+ {:else if node.type === 'thematicBreak'}
67
+ <hr />
68
+ {:else if node.type === 'image'}
69
+ <Image {node} />
70
+ {:else if node.type === 'url'}
71
+ <LinkPreview url={node.value} metadata={node.metadata} />
72
+ {:else if node.children}
73
+ <svelte:element this={getElement(node)}>
74
+ <svelte:self props={{ ast: node.children, level }} />
75
+ </svelte:element>
76
+ {:else if node.type === 'text'}
77
+ <Text {node} />
78
+ {:else}
79
+ <svelte:element this={getElement(node)}>
80
+ <Text {node} />
81
+ </svelte:element>
82
+ {/if}
83
+ {/each}
84
+ {/if}
@@ -0,0 +1,29 @@
1
+ const pathRegex = /^\/[\/\w\d=-_\.\?&-]*$/;
2
+
3
+ export function getFolderAndCount(node) {
4
+ if (
5
+ node.type === 'paragraph' &&
6
+ node.children.length === 1 &&
7
+ node.children[0].type === 'text' &&
8
+ (node.children[0].value.match(pathRegex) || node.children[0].value.match(/^\/$/))
9
+ ) {
10
+ const urlObject = new URL(node.children[0].value, 'https://placeholder.getvowel.com');
11
+ const isFolder = urlObject.searchParams.get('count');
12
+ const property = urlObject.searchParams.get('property');
13
+ const content = urlObject.searchParams.get('content') === 'true';
14
+ if (isFolder) return { count: isFolder, path: urlObject.pathname, property, content };
15
+ }
16
+ return false;
17
+ }
18
+
19
+ export function isFileLink(node) {
20
+ if (
21
+ node.type === 'paragraph' &&
22
+ node.children.length === 1 &&
23
+ node.children[0].type === 'text' &&
24
+ node.children[0].value.match(pathRegex)
25
+ ) {
26
+ return true;
27
+ }
28
+ return false;
29
+ }
@@ -0,0 +1,39 @@
1
+ <script>
2
+ import { getFileLabel, getFolderLabel, isActiveLink } from '$lib/utilities';
3
+
4
+ let { folder, segments, child } = $props();
5
+
6
+ // There's definitely a better way to filter this
7
+ function filterFolder(folder) {
8
+ const filteredFolder = {};
9
+ for (let key in folder) {
10
+ if (!folder[key]['$']?.date) {
11
+ filteredFolder[key] = folder[key];
12
+ }
13
+ }
14
+ return filteredFolder;
15
+ }
16
+
17
+ const evergreen = $derived(filterFolder(folder));
18
+ </script>
19
+
20
+ {#if Object.keys(evergreen).some((key) => key !== '$' && key !== '_')}
21
+ <nav>
22
+ {#if !child && evergreen['$']?.url !== '/'}
23
+ <a aria-current={isActiveLink(segments)} href={evergreen['$']?.url}
24
+ >{getFolderLabel(evergreen)}</a
25
+ >
26
+ {/if}
27
+ {#each Object.keys(evergreen) as key}
28
+ {#if !key.match(/^[$_]$/) && key !== '.obsidian'}
29
+ <a
30
+ href={evergreen[key]['_']?.url || evergreen[key]['$']?.url}
31
+ aria-current={isActiveLink(segments, key)}>{getFolderLabel(evergreen[key]) || key}</a
32
+ >
33
+ {/if}
34
+ {/each}
35
+ </nav>
36
+ {#if segments[0]}
37
+ <svelte:self folder={evergreen[segments[0]]} segments={segments.slice(1)} child={true} />
38
+ {/if}
39
+ {/if}
@@ -0,0 +1,59 @@
1
+ <script>
2
+ import { Markdown, Frontmatter } from '$lib/components';
3
+ import Image from './Markdown/Image.svelte';
4
+
5
+ let { page, content = true, link, level = 0, website = {}, format = 'html' } = $props();
6
+ let { ast, title, date, image, imputedProperties } = $derived(page || {});
7
+
8
+ const dateObject = date?.type === 'date' && new Date(date.output);
9
+ // TODO: Add conditional logic for format = 'xml'
10
+ </script>
11
+
12
+ <!-- Image -->
13
+ {#if image}
14
+ {#if link}
15
+ <a href={page.url}>
16
+ <Image node={{ url: image.output, alt: '' }} />
17
+ </a>
18
+ {:else}
19
+ <Image node={{ url: image.output, alt: '' }} />
20
+ {/if}
21
+ {/if}
22
+
23
+ <!-- Title -->
24
+ {#if title}
25
+ {#if link}
26
+ <h1>
27
+ <a href={page.url}>
28
+ {page.imputedProperties.title || title || page.imputedProperties?.fileName}
29
+ </a>
30
+ </h1>
31
+ {:else}
32
+ <h1>{page.imputedProperties.title || title || page.imputedProperties?.fileName}</h1>
33
+ {/if}
34
+ {/if}
35
+
36
+ <!-- Date -->
37
+ {#if link}
38
+ {#if dateObject}
39
+ <time datetime={dateObject.toISOString()}>
40
+ <a href={page.url}>
41
+ {dateObject.toLocaleDateString()}
42
+ </a>
43
+ </time>
44
+ {/if}
45
+ {:else if dateObject}
46
+ <time datetime={dateObject.toISOString()}>{dateObject.toLocaleDateString()}</time>
47
+ {/if}
48
+
49
+ <Frontmatter props={{ properties: page, website }} />
50
+
51
+ {#if page?.description || (!content && page?.imputedProperties?.description)}
52
+ <p class="description">{page?.description || page?.imputedProperties.description}</p>
53
+ {/if}
54
+
55
+ {#if level < 2 && content}
56
+ <div class="content">
57
+ <Markdown props={{ ast, level, website }} />
58
+ </div>
59
+ {/if}
@@ -0,0 +1,38 @@
1
+ <script>
2
+ import { getFileLabel } from '$lib/utilities';
3
+ import { getFolderLabel, isActiveLink } from '../utilities';
4
+
5
+ let { section, key, root, segments } = $props();
6
+
7
+ const element = section['_'] ? 'ul' : 'div';
8
+ </script>
9
+
10
+ <!-- TODO: Add a sort -->
11
+
12
+ {#if typeof section === 'object'}
13
+ <ul>
14
+ {#if root}
15
+ <li class="home">
16
+ <a aria-current={isActiveLink(segments)} href={section['$'].url}>
17
+ {getFileLabel(section['$']) || getFolderLabel(section)}
18
+ </a>
19
+ </li>
20
+ {/if}
21
+ {#each Object.keys(section) as key}
22
+ {#if Object.keys(section[key]).length === 1 && section[key].$}
23
+ <li class={key}>
24
+ <a aria-current={isActiveLink(segments, key)} href={section[key].$.url}>
25
+ {getFileLabel(section[key]['$']) || getFolderLabel(section[key])}
26
+ </a>
27
+ </li>
28
+ {:else if section[key].$}
29
+ <li class={key}>
30
+ <a aria-current={isActiveLink(segments, key)} href={section[key].$.url}>
31
+ {getFileLabel(section[key]['$']) || getFolderLabel(section[key])}
32
+ </a>
33
+ <svelte:self section={section[key]} segments={segments.slice(1)} {key} />
34
+ </li>
35
+ {/if}
36
+ {/each}
37
+ </ul>
38
+ {/if}
@@ -0,0 +1,9 @@
1
+ export { default as Breadcrumbs } from './Breadcrumbs.svelte';
2
+ export { default as Markdown } from './Markdown/index.svelte';
3
+ export { default as Nav } from './Nav.svelte';
4
+ export { default as Frontmatter } from './Frontmatter.svelte';
5
+ export { default as Page } from './Page.svelte';
6
+ export { default as Sitemap } from './Sitemap.svelte';
7
+ export { default as FrontmatterProperty } from './FrontmatterProperty.svelte';
8
+ export { default as FrontmatterTaxonomy } from './FrontMatterTaxonomy.svelte';
9
+ export { default as DefaultStyles } from './DefaultStyles.svelte';
@@ -0,0 +1 @@
1
+ // place files you want to import through the `$lib` alias in this folder.
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Build a URL for a file.
3
+ * @param {string} parents - Parent URL.
4
+ * @param {string} route - Filename.
5
+ * @returns {string} - URL for the file.
6
+ */
7
+ export default function buildURL(parents, route) {
8
+ if (!parents) {
9
+ if (route === 'home' || route === 'settings') {
10
+ return '/';
11
+ }
12
+ return '/' + route;
13
+ }
14
+ if (route === 'home' || route === 'settings') {
15
+ return parents;
16
+ }
17
+ return parents + '/' + route;
18
+ }
@@ -0,0 +1,16 @@
1
+ import fs from 'fs/promises';
2
+ // import { constants as fsConstants } from 'fs';
3
+
4
+ /**
5
+ * Description
6
+ * @param {string} folderPath - File page for a document.
7
+ * @returns {Promise<boolean>}
8
+ */
9
+ export default async function checkFileExists(folderPath) {
10
+ try {
11
+ await fs.access(folderPath);
12
+ return true; // Folder exists
13
+ } catch (error) {
14
+ return false; // Folder does not exist
15
+ }
16
+ }
@@ -0,0 +1,4 @@
1
+ export default function createFolderClass(url) {
2
+ if (url === '/') return 'folder-root';
3
+ return 'folder' + url.replaceAll('/', '-');
4
+ }
@@ -0,0 +1,4 @@
1
+ export default function createPageClass(url, type = 'page') {
2
+ if (url === '/') return 'page home';
3
+ return type + url.split('/').join(' ');
4
+ }
@@ -0,0 +1,18 @@
1
+ function createTitleFromDescription(description) {
2
+ if (description?.length > 30) {
3
+ return description.slice(0, 30) + '...';
4
+ }
5
+ return description;
6
+ }
7
+
8
+ export default function getFileLabel(page) {
9
+ return (
10
+ page?.breadcrumb ||
11
+ page?.title ||
12
+ page?.imputedProperties?.title ||
13
+ createTitleFromDescription(page?.description) ||
14
+ createTitleFromDescription(page?.imputedProperties?.description) ||
15
+ page?.imputedProperties?.fileName ||
16
+ null
17
+ );
18
+ }
@@ -0,0 +1,16 @@
1
+ export default function getFolder(website, path) {
2
+ const dotPath = path
3
+ .replace(/^\//, '') // Remove preceding slash
4
+ .replace(/\/$/, '') // Remove trailing slash
5
+ .replaceAll('/', '.'); // Convert to dots
6
+
7
+ let drill = website;
8
+
9
+ dotPath.split('.').forEach((segment) => {
10
+ if (segment) {
11
+ drill = drill[segment];
12
+ }
13
+ });
14
+
15
+ return drill;
16
+ }
@@ -0,0 +1,8 @@
1
+ import { getFileLabel } from '.';
2
+
3
+ const fallBackLabel = 'Untitled';
4
+
5
+ export default function getFolderLabel(folder) {
6
+ if (typeof folder !== 'object') return 'Untitled';
7
+ return getFileLabel(folder['_']) || getFileLabel(folder['$']) || fallBackLabel;
8
+ }
@@ -0,0 +1,24 @@
1
+ // import objectPath from 'object-path';
2
+
3
+ /**
4
+ * Takes a file URL and a website object and returns the file object.
5
+ * @param {import('./processMarkdownFiles').Directory} website - A website object.
6
+ * @param {string} path - A `/`-delimited filepath.
7
+ * @returns {import('./processMarkdownFiles').MarkdownFile}
8
+ */
9
+ export default function getPage(website, path) {
10
+ let page = website;
11
+
12
+ // const dotPath = path.replace(/^\//, '').replaceAll('/', '.') + (path ? '.' : '') + '$';
13
+
14
+ const segments = path
15
+ .replace(/^\//, '')
16
+ .concat(path ? '/$' : '$')
17
+ .split('/');
18
+
19
+ segments.forEach((segment) => {
20
+ if (typeof page === 'object' && page !== null) page = page[segment];
21
+ });
22
+
23
+ return page;
24
+ }
@@ -0,0 +1,93 @@
1
+ // @ts-ignore
2
+ import { getFolder, getFileLabel } from '.';
3
+
4
+ // @ts-ignore
5
+ const excludedFileNames = ['_'];
6
+
7
+ /**
8
+ * Traverse a folder to get all of the pages.
9
+ * @param {import('./processMarkdownFiles').Directory} folder - The path to the folder to retrieve.
10
+ * @param {boolean} root - Whether this is the root of the recursion.
11
+ * @returns {Array<Page>}
12
+ */
13
+ function traverseFolder(folder, root = false, property = '.') {
14
+ let propertyKey = (property || '.').split('.')[0];
15
+ let propertyValue = (property || '.').split('.')[1];
16
+
17
+ let pages = [];
18
+
19
+ for (let key in folder) {
20
+ if (key === '$') {
21
+ if (!root) {
22
+ if (propertyKey) {
23
+ if (
24
+ folder[key].hasOwnProperty(propertyKey) &&
25
+ folder[key][propertyKey] === propertyValue
26
+ ) {
27
+ pages.push(folder[key]);
28
+ }
29
+ } else {
30
+ pages.push(folder[key]);
31
+ }
32
+ }
33
+ // if (
34
+ // !root &&
35
+ // folder[key].$?.hasOwnProperty(propertyKey) &&
36
+ // folder[key][propertyKey] === propertyValue
37
+ // )
38
+ // pages.push(folder[key]);
39
+ } else if (key !== '_') {
40
+ const child = folder[key];
41
+
42
+ // @ts-ignore
43
+ pages.push(...traverseFolder(child, false, property));
44
+ }
45
+ }
46
+
47
+ // @ts-ignore
48
+ return pages;
49
+ }
50
+
51
+ /**
52
+ * Description
53
+ * @param {Page} a
54
+ * @param {Page} b
55
+ * @returns {number}
56
+ */
57
+ function sortPage(a, b) {
58
+ if (a.hasOwnProperty('date') && !b.hasOwnProperty('date')) return -1;
59
+ else if (!a.hasOwnProperty('date') && b.hasOwnProperty('date')) return 1;
60
+ else if (a.date && b.date) {
61
+ const aDate = Number(new Date(a.date.output));
62
+ const bDate = Number(new Date(b.date.output));
63
+ return bDate - aDate;
64
+ } else if (getFileLabel(a) > getFileLabel(b)) return 1;
65
+ return -1;
66
+ }
67
+
68
+ /**
69
+ * @typedef {import('./processMarkdownFiles').MarkdownFile} Page
70
+ */
71
+
72
+ /**
73
+ * Recursively get all of the pages in a folder.
74
+ * @param {import('./processMarkdownFiles').Directory} website - The entire website.
75
+ * @param {string} path - The path to the folder to retrieve.
76
+ * @param {number | undefined} count - Number of pages to return.
77
+ * @returns {Array<Page>}
78
+ */
79
+ export default function getPagesByFolder(
80
+ website,
81
+ path,
82
+ excludeRoot = true,
83
+ count = undefined,
84
+ property
85
+ ) {
86
+ const folder = getFolder(website, path);
87
+
88
+ const pages = traverseFolder(folder, excludeRoot, property);
89
+
90
+ pages.sort(sortPage);
91
+
92
+ return pages.slice(0, count);
93
+ }
@@ -0,0 +1,20 @@
1
+ export { default as getPage } from './getPage';
2
+ export { default as getFileLabel } from './getFileLabel';
3
+ export { default as getFolderLabel } from './getFolderLabel';
4
+ export { default as getFolder } from './getFolder';
5
+ export { default as getPagesByFolder } from './getPagesByFolder';
6
+ export { default as buildURL } from './buildURL';
7
+ export { default as checkFileExists } from './checkFileExists';
8
+ export { default as writeCache } from './writeCache';
9
+ export { default as readMarkdownFile } from './readMarkdownFile';
10
+ export { default as processMarkdownFiles } from './processMarkdownFiles';
11
+ export { default as loadCache } from './loadCache';
12
+ export { default as mutateMarkdownFrontmatter } from './mutateMarkdownFrontmatter';
13
+ export { default as isObject } from './isObject';
14
+ export { default as regexPatterns } from './regexPatterns';
15
+ export { default as mutateMarkdownAST } from './mutateMarkdownAST';
16
+ export { default as parseDate } from './parseDate';
17
+ export { default as resolveHomeDirPath } from './resolveHomeDirPath';
18
+ export { default as createPageClass } from './createPageClass';
19
+ export { default as createFolderClass } from './createFolderClass';
20
+ export { default as isActiveLink } from './isActiveLink';
@@ -0,0 +1,12 @@
1
+ export default function isActiveLink(segments, key) {
2
+ if (!segments?.[0] && !key) {
3
+ return 'page';
4
+ }
5
+ if (segments.length === 1 && segments[0] === key) {
6
+ return 'page';
7
+ }
8
+ if (segments.length > 1 && segments[0] === key) {
9
+ return 'true';
10
+ }
11
+ return undefined;
12
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Check if a value is an object
3
+ * @param {any} value - Value to check.
4
+ * @returns {boolean}
5
+ */
6
+ export default function isObject(value) {
7
+ return typeof value === 'object' && !Array.isArray(value) && value !== null;
8
+ }
@@ -0,0 +1,28 @@
1
+ import fs from 'fs/promises';
2
+ import resolveHomeDirPath from './resolveHomeDirPath';
3
+
4
+ /**
5
+ * @typedef {Object<string, object>} Cache
6
+ * @description A map of URLs to their metadata.
7
+ */
8
+
9
+ /**
10
+ * Load a cache of URLs
11
+ * @returns {Promise<Cache>}
12
+ */
13
+ export default async function loadCache(homeDir) {
14
+ const cachePath = resolveHomeDirPath('.cache.json', homeDir);
15
+
16
+ try {
17
+ let data = await fs.readFile(cachePath);
18
+ return JSON.parse(data);
19
+ } catch (error) {
20
+ if (error.code === 'ENOENT') {
21
+ await fs.writeFile(cachePath, '{}', 'utf8');
22
+ console.log('.cache.json created');
23
+ } else {
24
+ throw error;
25
+ }
26
+ }
27
+ return {};
28
+ }