getfilepress 0.1.7 → 0.1.9
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/README.md +8 -0
- package/package.json +2 -1
- package/packages/app/src/routes/+layout.svelte +11 -2
- package/packages/app/src/routes/posts/+page.server.ts +12 -0
- package/packages/app/src/routes/posts/+page.svelte +38 -0
- package/packages/app/src/routes/writing/+page.server.ts +2 -6
- package/packages/app/src/routes/writing/+page.svelte +1 -38
- package/packages/core/src/lib/components/PostIndex.svelte +1 -1
- package/packages/core/src/lib/components/SiteFooter.svelte +4 -1
- package/packages/core/src/lib/components/SiteHeader.svelte +4 -1
- package/packages/core/src/lib/config.ts +4 -4
- package/packages/core/src/lib/content/feeds.ts +1 -1
- package/packages/core/src/lib/index.ts +2 -0
- package/packages/core/src/lib/paths-shared.ts +8 -0
- package/packages/core/src/lib/paths.ts +1 -1
package/README.md
CHANGED
|
@@ -200,6 +200,8 @@ Any other static host works the same way: publish `build/` as the web root. The
|
|
|
200
200
|
|
|
201
201
|
Full notes (including an agent checklist): [`docs/DEPLOY.md`](docs/DEPLOY.md) · product page: [getfilepress.com/deploy](https://getfilepress.com/deploy).
|
|
202
202
|
|
|
203
|
+
If the site ships an agent skill, write the install page from [`docs/SKILL_PAGE.md`](docs/SKILL_PAGE.md) · [getfilepress.com/skill-page](https://getfilepress.com/skill-page).
|
|
204
|
+
|
|
203
205
|
## Repository layout
|
|
204
206
|
|
|
205
207
|
```
|
|
@@ -215,6 +217,12 @@ scripts/ filepress CLI and create-site scaffold
|
|
|
215
217
|
pnpm test # engine unit tests
|
|
216
218
|
```
|
|
217
219
|
|
|
220
|
+
<!-- xfacts-nutrition-label -->
|
|
221
|
+
|
|
222
|
+
## Nutrition label
|
|
223
|
+
|
|
224
|
+
- **AppFacts:** [viewer](https://appfacts.dev/v#af1.eNptkkGLGzEMhf-K0dnZoVefygZKt01KYbK9lKUotjLxxmMZWzNhCPnvxZNk9tAebX9Pen7SBUYwnzRE7AkMHHyglKkU0CBTqlc9R86UGDQUQRkKGEArfiTQELylWCq2fdndCHsCc4GAsRuwqy-7KVFrs0-iVTtSENLqG474uFu3rVZfd9sNaMhDFD87-cGOnt6rj0PGns6cT2Dgpv_uBTTsh-gCZTDwywvNzafgYwcG1m0LGo5c5H4OPLhDwEzqJ3ZU4KrBUSpgfl8ggoHPZS78XprTXDtVEWdSZ9qrDwNXfcNv9J17fVGW-8SRovyHHf1CPg8-OCXMQWF0ytGoCuWR8gLLkKPjc1wsxJGyzOkoYbXFfJqfH_wQ_cGTezi5nZTlKNVMymypFB87lXyi4CMtysAWw56yHO_iLUbsSCXOogJhoaIOnNXMVasUOPUUBa5vGspol-z-CVdDHUorKN6q-xCql9G7-tW3OjofXF2ThPaEHf3p5-ZVlmLq6_JRkSXolT2SrYlCXcTihfMEBo4iqZim6bwch_2T5b5Zo2CYiqy-cO5otdmsm4-Vvv4FzW_8rQ) · [raw](https://github.com/Catalyst-Forge-LLC/filepress/blob/main/APP_FACTS.md)
|
|
225
|
+
|
|
218
226
|
## License
|
|
219
227
|
|
|
220
228
|
MIT — see [LICENSE](LICENSE).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "getfilepress",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "FilePress — file-based Markdown blog engine. Link this package from a content-only site (config + posts), then run `filepress build`.",
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"dev:www": "node scripts/ensure-lease.mjs getfilepress 5180 && pnpm filepress dev --site getfilepress --host 0.0.0.0",
|
|
62
62
|
"create-site": "node scripts/create-site.mjs",
|
|
63
63
|
"sync-siblings": "tsx scripts/sync-sibling-sites.ts",
|
|
64
|
+
"siblings": "tsx scripts/siblings/server.ts",
|
|
64
65
|
"import": "node scripts/filepress.mjs import",
|
|
65
66
|
"prepublishOnly": "node scripts/publish-gate.mjs",
|
|
66
67
|
"prepack": "node scripts/prepack.mjs",
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import '$lib/theme-entry';
|
|
3
3
|
import criticalTheme from '$critical-theme';
|
|
4
|
-
import { SiteHeader, SiteFooter } from '@filepress/core';
|
|
4
|
+
import { SiteHeader, SiteFooter, isPathMountHref } from '@filepress/core';
|
|
5
5
|
import config from '$site-config';
|
|
6
6
|
import GenieHost from '$lib/genie/GenieHost.svelte';
|
|
7
7
|
|
|
8
|
-
|
|
9
8
|
let { children } = $props();
|
|
9
|
+
|
|
10
|
+
function markPathMountLink(event: PointerEvent) {
|
|
11
|
+
const a = (event.target as HTMLElement | null)?.closest('a[href]');
|
|
12
|
+
if (!a) return;
|
|
13
|
+
const href = a.getAttribute('href');
|
|
14
|
+
if (!href || !isPathMountHref(href, config.paths)) return;
|
|
15
|
+
a.setAttribute('data-sveltekit-reload', '');
|
|
16
|
+
}
|
|
10
17
|
</script>
|
|
11
18
|
|
|
19
|
+
<svelte:window onpointerdown={markPathMountLink} />
|
|
20
|
+
|
|
12
21
|
<svelte:head>
|
|
13
22
|
{#if criticalTheme}
|
|
14
23
|
{@html `<style data-filepress-critical>${criticalTheme}</style>`}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { redirect } from '@sveltejs/kit';
|
|
2
|
+
import type { PageServerLoad } from './$types';
|
|
3
|
+
import { content } from '$lib/content.server';
|
|
4
|
+
import config from '$site-config';
|
|
5
|
+
|
|
6
|
+
export const load: PageServerLoad = () => {
|
|
7
|
+
// Without homePage, the post index stays at `/`.
|
|
8
|
+
if (!config.homePage) {
|
|
9
|
+
redirect(302, '/');
|
|
10
|
+
}
|
|
11
|
+
return content.getIndexPage(1, config.postsPerPage);
|
|
12
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { PageData } from './$types';
|
|
3
|
+
import { PostIndex, absoluteUrl, ogImageUrl, postsIndexPath } from '@filepress/core';
|
|
4
|
+
import config from '$site-config';
|
|
5
|
+
|
|
6
|
+
let { data }: { data: PageData } = $props();
|
|
7
|
+
|
|
8
|
+
const pageTitle = $derived(`Posts — ${config.title}`);
|
|
9
|
+
const canonical = $derived(absoluteUrl(config, postsIndexPath(config)));
|
|
10
|
+
const ogImage = $derived(ogImageUrl(config));
|
|
11
|
+
const description = $derived(
|
|
12
|
+
`Posts from ${config.title}. ${config.description}`.trim()
|
|
13
|
+
);
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<svelte:head>
|
|
17
|
+
<title>{pageTitle}</title>
|
|
18
|
+
<meta name="description" content={description} />
|
|
19
|
+
<link rel="canonical" href={canonical} />
|
|
20
|
+
<meta property="og:type" content="website" />
|
|
21
|
+
<meta property="og:title" content="Posts — {config.title}" />
|
|
22
|
+
<meta property="og:description" content={description} />
|
|
23
|
+
<meta property="og:url" content={canonical} />
|
|
24
|
+
{#if ogImage}
|
|
25
|
+
<meta property="og:image" content={ogImage} />
|
|
26
|
+
<meta name="twitter:card" content="summary" />
|
|
27
|
+
<meta name="twitter:image" content={ogImage} />
|
|
28
|
+
{/if}
|
|
29
|
+
</svelte:head>
|
|
30
|
+
|
|
31
|
+
<PostIndex
|
|
32
|
+
site={config}
|
|
33
|
+
featured={data.featured}
|
|
34
|
+
posts={data.posts}
|
|
35
|
+
page={data.page}
|
|
36
|
+
totalPages={data.totalPages}
|
|
37
|
+
indexHref={postsIndexPath(config)}
|
|
38
|
+
/>
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import { redirect } from '@sveltejs/kit';
|
|
2
2
|
import type { PageServerLoad } from './$types';
|
|
3
|
-
import {
|
|
3
|
+
import { postsIndexPath } from '@filepress/core';
|
|
4
4
|
import config from '$site-config';
|
|
5
5
|
|
|
6
6
|
export const load: PageServerLoad = () => {
|
|
7
|
-
|
|
8
|
-
if (!config.homePage) {
|
|
9
|
-
redirect(302, '/');
|
|
10
|
-
}
|
|
11
|
-
return content.getIndexPage(1, config.postsPerPage);
|
|
7
|
+
redirect(308, postsIndexPath(config));
|
|
12
8
|
};
|
|
@@ -1,38 +1 @@
|
|
|
1
|
-
<
|
|
2
|
-
import type { PageData } from './$types';
|
|
3
|
-
import { PostIndex, absoluteUrl, ogImageUrl, postsIndexPath } from '@filepress/core';
|
|
4
|
-
import config from '$site-config';
|
|
5
|
-
|
|
6
|
-
let { data }: { data: PageData } = $props();
|
|
7
|
-
|
|
8
|
-
const pageTitle = $derived(`Posts — ${config.title}`);
|
|
9
|
-
const canonical = $derived(absoluteUrl(config, postsIndexPath(config)));
|
|
10
|
-
const ogImage = $derived(ogImageUrl(config));
|
|
11
|
-
const description = $derived(
|
|
12
|
-
`Posts from ${config.title}. ${config.description}`.trim()
|
|
13
|
-
);
|
|
14
|
-
</script>
|
|
15
|
-
|
|
16
|
-
<svelte:head>
|
|
17
|
-
<title>{pageTitle}</title>
|
|
18
|
-
<meta name="description" content={description} />
|
|
19
|
-
<link rel="canonical" href={canonical} />
|
|
20
|
-
<meta property="og:type" content="website" />
|
|
21
|
-
<meta property="og:title" content="Posts — {config.title}" />
|
|
22
|
-
<meta property="og:description" content={description} />
|
|
23
|
-
<meta property="og:url" content={canonical} />
|
|
24
|
-
{#if ogImage}
|
|
25
|
-
<meta property="og:image" content={ogImage} />
|
|
26
|
-
<meta name="twitter:card" content="summary" />
|
|
27
|
-
<meta name="twitter:image" content={ogImage} />
|
|
28
|
-
{/if}
|
|
29
|
-
</svelte:head>
|
|
30
|
-
|
|
31
|
-
<PostIndex
|
|
32
|
-
site={config}
|
|
33
|
-
featured={data.featured}
|
|
34
|
-
posts={data.posts}
|
|
35
|
-
page={data.page}
|
|
36
|
-
totalPages={data.totalPages}
|
|
37
|
-
indexHref={postsIndexPath(config)}
|
|
38
|
-
/>
|
|
1
|
+
<p>Moved to <a href="/posts">/posts</a>.</p>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { SiteConfig } from '../config';
|
|
3
|
+
import { isPathMountHref } from '../paths-shared';
|
|
3
4
|
import NavIcon from './NavIcon.svelte';
|
|
4
5
|
|
|
5
6
|
let { site, year = new Date().getFullYear() }: { site: SiteConfig; year?: number } = $props();
|
|
@@ -16,7 +17,9 @@
|
|
|
16
17
|
class:nav-github={item.icon === 'github'}
|
|
17
18
|
{...(item.icon === 'github'
|
|
18
19
|
? { target: '_blank', rel: 'noopener noreferrer' }
|
|
19
|
-
:
|
|
20
|
+
: isPathMountHref(item.href, site.paths)
|
|
21
|
+
? { 'data-sveltekit-reload': true }
|
|
22
|
+
: {})}
|
|
20
23
|
>
|
|
21
24
|
{#if item.icon}
|
|
22
25
|
<NavIcon name={item.icon} />
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { SiteConfig } from '../config';
|
|
3
|
+
import { isPathMountHref } from '../paths-shared';
|
|
3
4
|
import NavIcon from './NavIcon.svelte';
|
|
4
5
|
|
|
5
6
|
let { site }: { site: SiteConfig } = $props();
|
|
@@ -28,7 +29,9 @@
|
|
|
28
29
|
class:nav-github={item.icon === 'github'}
|
|
29
30
|
{...(item.icon === 'github'
|
|
30
31
|
? { target: '_blank', rel: 'noopener noreferrer' }
|
|
31
|
-
:
|
|
32
|
+
: isPathMountHref(item.href, site.paths)
|
|
33
|
+
? { 'data-sveltekit-reload': true }
|
|
34
|
+
: {})}
|
|
32
35
|
>
|
|
33
36
|
{#if item.icon}
|
|
34
37
|
<NavIcon name={item.icon} />
|
|
@@ -55,7 +55,7 @@ export interface SiteConfig {
|
|
|
55
55
|
postsPerPage: number;
|
|
56
56
|
/**
|
|
57
57
|
* When set, `/` renders this static page slug (`pages/<slug>.md`) and the
|
|
58
|
-
* chronological post index moves to `/
|
|
58
|
+
* chronological post index moves to `/posts`.
|
|
59
59
|
*/
|
|
60
60
|
homePage: string | null;
|
|
61
61
|
nav: NavItem[];
|
|
@@ -86,7 +86,7 @@ export interface SiteConfigInput {
|
|
|
86
86
|
ogImage?: string;
|
|
87
87
|
author?: string;
|
|
88
88
|
postsPerPage?: number;
|
|
89
|
-
/** Static page slug to show at `/` (post index then lives at `/
|
|
89
|
+
/** Static page slug to show at `/` (post index then lives at `/posts`). */
|
|
90
90
|
homePage?: string;
|
|
91
91
|
nav?: NavItem[];
|
|
92
92
|
/** Custom footer links; replaces the default RSS + Topics row when set. */
|
|
@@ -120,7 +120,7 @@ function normalizeNavItems(items: NavItem[] | undefined): NavItem[] | null {
|
|
|
120
120
|
|
|
121
121
|
/** Path to page 1 of the chronological post index. */
|
|
122
122
|
export function postsIndexPath(site: Pick<SiteConfig, 'homePage'>): string {
|
|
123
|
-
return site.homePage ? '/
|
|
123
|
+
return site.homePage ? '/posts' : '/';
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
/**
|
|
@@ -151,7 +151,7 @@ export function defineFilepressConfig(input: SiteConfigInput): SiteConfig {
|
|
|
151
151
|
const defaultNav: NavItem[] = homePage
|
|
152
152
|
? [
|
|
153
153
|
{ label: 'Home', href: '/' },
|
|
154
|
-
{ label: 'Posts', href: '/
|
|
154
|
+
{ label: 'Posts', href: '/posts' },
|
|
155
155
|
{ label: 'Topics', href: '/topics' }
|
|
156
156
|
]
|
|
157
157
|
: [
|
|
@@ -69,7 +69,7 @@ export function buildSitemapXml(
|
|
|
69
69
|
|
|
70
70
|
const urls: { loc: string; lastmod?: string }[] = [
|
|
71
71
|
{ loc: absoluteUrl(site, '/') },
|
|
72
|
-
...(site.homePage ? [{ loc: absoluteUrl(site, '/
|
|
72
|
+
...(site.homePage ? [{ loc: absoluteUrl(site, '/posts') }] : []),
|
|
73
73
|
{ loc: absoluteUrl(site, '/topics') },
|
|
74
74
|
{ loc: absoluteUrl(site, '/tags') },
|
|
75
75
|
...extraPages,
|
|
@@ -87,3 +87,11 @@ export function normalizePathMounts(input: PathMount[] | undefined): PathMount[]
|
|
|
87
87
|
export function pathMountReservedSlugs(mounts: PathMount[]): string[] {
|
|
88
88
|
return mounts.map((m) => m.url.slice(1).split('/')[0]!).filter(Boolean);
|
|
89
89
|
}
|
|
90
|
+
|
|
91
|
+
/** Same-origin href that belongs to a `paths` mount (e.g. `/docs`, `/docs/install`). */
|
|
92
|
+
export function isPathMountHref(href: string, mounts: PathMount[]): boolean {
|
|
93
|
+
const raw = href.trim();
|
|
94
|
+
if (!raw.startsWith('/')) return false;
|
|
95
|
+
const path = raw.split(/[?#]/)[0] ?? '';
|
|
96
|
+
return mounts.some((m) => path === m.url || path.startsWith(`${m.url}/`));
|
|
97
|
+
}
|
|
@@ -12,7 +12,7 @@ import { join, relative, resolve, sep } from 'node:path';
|
|
|
12
12
|
import type { PathMount } from './paths-shared';
|
|
13
13
|
|
|
14
14
|
export type { PathMount } from './paths-shared';
|
|
15
|
-
export { normalizePathMounts, pathMountReservedSlugs } from './paths-shared';
|
|
15
|
+
export { normalizePathMounts, pathMountReservedSlugs, isPathMountHref } from './paths-shared';
|
|
16
16
|
|
|
17
17
|
/** Absolute filesystem path for a mount's source directory. */
|
|
18
18
|
export function resolvePathMountDir(siteRoot: string, mount: PathMount): string {
|