@geekity/cms 0.3.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/LICENSE +21 -0
- package/README.md +870 -0
- package/admin/layouts/_flash.njk +5 -0
- package/admin/layouts/base.njk +27 -0
- package/admin/layouts/dashboard.njk +78 -0
- package/admin/layouts/document-conflict.njk +49 -0
- package/admin/layouts/document-editor.njk +167 -0
- package/admin/layouts/document-list.njk +92 -0
- package/admin/layouts/federation.njk +145 -0
- package/admin/layouts/login.njk +23 -0
- package/admin/layouts/placeholder.njk +10 -0
- package/admin/layouts/settings.njk +116 -0
- package/admin/layouts/setup.njk +27 -0
- package/admin/layouts/shell.njk +37 -0
- package/admin/layouts/users.njk +91 -0
- package/admin/static/admin.css +726 -0
- package/admin/static/editor.js +30 -0
- package/dist/admin/assets.d.ts +26 -0
- package/dist/admin/assets.d.ts.map +1 -0
- package/dist/admin/assets.js +38 -0
- package/dist/admin/assets.js.map +1 -0
- package/dist/admin/credentials.d.ts +28 -0
- package/dist/admin/credentials.d.ts.map +1 -0
- package/dist/admin/credentials.js +39 -0
- package/dist/admin/credentials.js.map +1 -0
- package/dist/admin/documents.d.ts +134 -0
- package/dist/admin/documents.d.ts.map +1 -0
- package/dist/admin/documents.js +613 -0
- package/dist/admin/documents.js.map +1 -0
- package/dist/admin/federation.d.ts +194 -0
- package/dist/admin/federation.d.ts.map +1 -0
- package/dist/admin/federation.js +299 -0
- package/dist/admin/federation.js.map +1 -0
- package/dist/admin/flash.d.ts +24 -0
- package/dist/admin/flash.d.ts.map +1 -0
- package/dist/admin/flash.js +31 -0
- package/dist/admin/flash.js.map +1 -0
- package/dist/admin/index.d.ts +23 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +15 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/passwords.d.ts +49 -0
- package/dist/admin/passwords.d.ts.map +1 -0
- package/dist/admin/passwords.js +122 -0
- package/dist/admin/passwords.js.map +1 -0
- package/dist/admin/preview.d.ts +18 -0
- package/dist/admin/preview.d.ts.map +1 -0
- package/dist/admin/preview.js +83 -0
- package/dist/admin/preview.js.map +1 -0
- package/dist/admin/routes.d.ts +54 -0
- package/dist/admin/routes.d.ts.map +1 -0
- package/dist/admin/routes.js +383 -0
- package/dist/admin/routes.js.map +1 -0
- package/dist/admin/session.d.ts +44 -0
- package/dist/admin/session.d.ts.map +1 -0
- package/dist/admin/session.js +69 -0
- package/dist/admin/session.js.map +1 -0
- package/dist/admin/settings.d.ts +194 -0
- package/dist/admin/settings.d.ts.map +1 -0
- package/dist/admin/settings.js +470 -0
- package/dist/admin/settings.js.map +1 -0
- package/dist/admin/store.d.ts +407 -0
- package/dist/admin/store.d.ts.map +1 -0
- package/dist/admin/store.js +704 -0
- package/dist/admin/store.js.map +1 -0
- package/dist/admin/templates.d.ts +36 -0
- package/dist/admin/templates.d.ts.map +1 -0
- package/dist/admin/templates.js +43 -0
- package/dist/admin/templates.js.map +1 -0
- package/dist/admin/uploads.d.ts +96 -0
- package/dist/admin/uploads.d.ts.map +1 -0
- package/dist/admin/uploads.js +179 -0
- package/dist/admin/uploads.js.map +1 -0
- package/dist/admin/users.d.ts +95 -0
- package/dist/admin/users.d.ts.map +1 -0
- package/dist/admin/users.js +233 -0
- package/dist/admin/users.js.map +1 -0
- package/dist/cli.d.ts +85 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +424 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +172 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +184 -0
- package/dist/config.js.map +1 -0
- package/dist/content/document.d.ts +66 -0
- package/dist/content/document.d.ts.map +1 -0
- package/dist/content/document.js +13 -0
- package/dist/content/document.js.map +1 -0
- package/dist/content/index.d.ts +17 -0
- package/dist/content/index.d.ts.map +1 -0
- package/dist/content/index.js +10 -0
- package/dist/content/index.js.map +1 -0
- package/dist/content/markdown.d.ts +3 -0
- package/dist/content/markdown.d.ts.map +1 -0
- package/dist/content/markdown.js +39 -0
- package/dist/content/markdown.js.map +1 -0
- package/dist/content/media.d.ts +65 -0
- package/dist/content/media.d.ts.map +1 -0
- package/dist/content/media.js +143 -0
- package/dist/content/media.js.map +1 -0
- package/dist/content/parser.d.ts +24 -0
- package/dist/content/parser.d.ts.map +1 -0
- package/dist/content/parser.js +165 -0
- package/dist/content/parser.js.map +1 -0
- package/dist/content/save.d.ts +60 -0
- package/dist/content/save.d.ts.map +1 -0
- package/dist/content/save.js +103 -0
- package/dist/content/save.js.map +1 -0
- package/dist/content/slug.d.ts +25 -0
- package/dist/content/slug.d.ts.map +1 -0
- package/dist/content/slug.js +75 -0
- package/dist/content/slug.js.map +1 -0
- package/dist/content/store.d.ts +133 -0
- package/dist/content/store.d.ts.map +1 -0
- package/dist/content/store.js +411 -0
- package/dist/content/store.js.map +1 -0
- package/dist/content/sync.d.ts +146 -0
- package/dist/content/sync.d.ts.map +1 -0
- package/dist/content/sync.js +421 -0
- package/dist/content/sync.js.map +1 -0
- package/dist/content/writer.d.ts +37 -0
- package/dist/content/writer.d.ts.map +1 -0
- package/dist/content/writer.js +97 -0
- package/dist/content/writer.js.map +1 -0
- package/dist/env.d.ts +48 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +2 -0
- package/dist/env.js.map +1 -0
- package/dist/federation/actor.d.ts +55 -0
- package/dist/federation/actor.d.ts.map +1 -0
- package/dist/federation/actor.js +101 -0
- package/dist/federation/actor.js.map +1 -0
- package/dist/federation/article.d.ts +83 -0
- package/dist/federation/article.d.ts.map +1 -0
- package/dist/federation/article.js +169 -0
- package/dist/federation/article.js.map +1 -0
- package/dist/federation/delivery.d.ts +90 -0
- package/dist/federation/delivery.d.ts.map +1 -0
- package/dist/federation/delivery.js +289 -0
- package/dist/federation/delivery.js.map +1 -0
- package/dist/federation/federation.d.ts +75 -0
- package/dist/federation/federation.d.ts.map +1 -0
- package/dist/federation/federation.js +190 -0
- package/dist/federation/federation.js.map +1 -0
- package/dist/federation/followers.d.ts +43 -0
- package/dist/federation/followers.d.ts.map +1 -0
- package/dist/federation/followers.js +69 -0
- package/dist/federation/followers.js.map +1 -0
- package/dist/federation/inbox.d.ts +68 -0
- package/dist/federation/inbox.d.ts.map +1 -0
- package/dist/federation/inbox.js +171 -0
- package/dist/federation/inbox.js.map +1 -0
- package/dist/federation/index.d.ts +14 -0
- package/dist/federation/index.d.ts.map +1 -0
- package/dist/federation/index.js +10 -0
- package/dist/federation/index.js.map +1 -0
- package/dist/federation/keys.d.ts +27 -0
- package/dist/federation/keys.d.ts.map +1 -0
- package/dist/federation/keys.js +60 -0
- package/dist/federation/keys.js.map +1 -0
- package/dist/federation/mount.d.ts +23 -0
- package/dist/federation/mount.d.ts.map +1 -0
- package/dist/federation/mount.js +71 -0
- package/dist/federation/mount.js.map +1 -0
- package/dist/federation/paths.d.ts +85 -0
- package/dist/federation/paths.d.ts.map +1 -0
- package/dist/federation/paths.js +97 -0
- package/dist/federation/paths.js.map +1 -0
- package/dist/index.d.ts +106 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +148 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +71 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +125 -0
- package/dist/init.js.map +1 -0
- package/dist/web/assets.d.ts +65 -0
- package/dist/web/assets.d.ts.map +1 -0
- package/dist/web/assets.js +152 -0
- package/dist/web/assets.js.map +1 -0
- package/dist/web/context.d.ts +121 -0
- package/dist/web/context.d.ts.map +1 -0
- package/dist/web/context.js +112 -0
- package/dist/web/context.js.map +1 -0
- package/dist/web/documents.d.ts +28 -0
- package/dist/web/documents.d.ts.map +1 -0
- package/dist/web/documents.js +39 -0
- package/dist/web/documents.js.map +1 -0
- package/dist/web/feeds.d.ts +122 -0
- package/dist/web/feeds.d.ts.map +1 -0
- package/dist/web/feeds.js +229 -0
- package/dist/web/feeds.js.map +1 -0
- package/dist/web/index.d.ts +17 -0
- package/dist/web/index.d.ts.map +1 -0
- package/dist/web/index.js +10 -0
- package/dist/web/index.js.map +1 -0
- package/dist/web/negotiate.d.ts +219 -0
- package/dist/web/negotiate.d.ts.map +1 -0
- package/dist/web/negotiate.js +409 -0
- package/dist/web/negotiate.js.map +1 -0
- package/dist/web/pagination.d.ts +48 -0
- package/dist/web/pagination.d.ts.map +1 -0
- package/dist/web/pagination.js +30 -0
- package/dist/web/pagination.js.map +1 -0
- package/dist/web/render.d.ts +72 -0
- package/dist/web/render.d.ts.map +1 -0
- package/dist/web/render.js +73 -0
- package/dist/web/render.js.map +1 -0
- package/dist/web/routes.d.ts +31 -0
- package/dist/web/routes.d.ts.map +1 -0
- package/dist/web/routes.js +366 -0
- package/dist/web/routes.js.map +1 -0
- package/dist/web/templates.d.ts +34 -0
- package/dist/web/templates.d.ts.map +1 -0
- package/dist/web/templates.js +106 -0
- package/dist/web/templates.js.map +1 -0
- package/package.json +68 -0
- package/templates/site/content/_data/site.json +8 -0
- package/templates/site/content/pages/about.md +11 -0
- package/templates/site/content/pages/pages.json +3 -0
- package/templates/site/content/posts/2026-01-01-hello-world.md +34 -0
- package/templates/site/content/posts/posts.json +4 -0
- package/templates/site/geekity.config.ts +11 -0
- package/templates/site/gitignore +3 -0
- package/templates/site/server.ts +20 -0
- package/templates/site/tsconfig.json +18 -0
- package/themes/default/README.md +148 -0
- package/themes/default/layouts/404.njk +9 -0
- package/themes/default/layouts/base.njk +59 -0
- package/themes/default/layouts/home.njk +9 -0
- package/themes/default/layouts/page.njk +13 -0
- package/themes/default/layouts/post.njk +30 -0
- package/themes/default/layouts/tag.njk +19 -0
- package/themes/default/partials/pagination.njk +14 -0
- package/themes/default/partials/post-list.njk +24 -0
- package/themes/default/partials/tags.njk +12 -0
- package/themes/default/static/style.css +327 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import matter from 'gray-matter';
|
|
3
|
+
import { KNOWN_FRONT_MATTER_KEYS } from './document.js';
|
|
4
|
+
import { renderMarkdown } from './markdown.js';
|
|
5
|
+
import { defaultPermalink, slugify } from './slug.js';
|
|
6
|
+
import { normalizeBody, serializeDocument } from './writer.js';
|
|
7
|
+
const KNOWN_KEYS = new Set(KNOWN_FRONT_MATTER_KEYS);
|
|
8
|
+
/**
|
|
9
|
+
* Parse the text of a Markdown file into a {@link Document}.
|
|
10
|
+
*
|
|
11
|
+
* Front matter is read with gray-matter and the body rendered with
|
|
12
|
+
* markdown-it. Dates become ISO 8601 strings whether the file quoted them or
|
|
13
|
+
* left YAML to parse them, and every key the CMS does not model is kept.
|
|
14
|
+
*/
|
|
15
|
+
export function parseDocument(source, options) {
|
|
16
|
+
const path = normalizePath(options.path);
|
|
17
|
+
const text = source.replace(/\r\n?/g, '\n');
|
|
18
|
+
if (!/^---\n/.test(text)) {
|
|
19
|
+
throw new TypeError(`${path} has no YAML front matter.`);
|
|
20
|
+
}
|
|
21
|
+
// The empty options object opts out of gray-matter's module-level cache, so
|
|
22
|
+
// two documents parsed from identical text never share nested data objects.
|
|
23
|
+
const parsed = matter(text, {});
|
|
24
|
+
const data = parsed.data;
|
|
25
|
+
const title = requiredString(data['title'], 'title', path);
|
|
26
|
+
const type = options.type ?? typeForPath(path);
|
|
27
|
+
const date = asDate(data['date'], 'date', path);
|
|
28
|
+
const body = normalizeBody(parsed.content);
|
|
29
|
+
const permalink = asString(data['permalink']) ??
|
|
30
|
+
defaultPermalink({
|
|
31
|
+
type,
|
|
32
|
+
slug: fallbackSlug(title, path),
|
|
33
|
+
date,
|
|
34
|
+
});
|
|
35
|
+
const content = {
|
|
36
|
+
title,
|
|
37
|
+
permalink,
|
|
38
|
+
tags: asTags(data['tags']),
|
|
39
|
+
draft: data['draft'] === true,
|
|
40
|
+
extra: extraOf(data),
|
|
41
|
+
body,
|
|
42
|
+
...(date === undefined ? {} : { date }),
|
|
43
|
+
...optional('updated', asDate(data['updated'], 'updated', path)),
|
|
44
|
+
...optional('description', asString(data['description'])),
|
|
45
|
+
...optional('author', asString(data['author'])),
|
|
46
|
+
...optional('activitypub', asActivityPub(data['activitypub'], path)),
|
|
47
|
+
};
|
|
48
|
+
return {
|
|
49
|
+
...content,
|
|
50
|
+
type,
|
|
51
|
+
path,
|
|
52
|
+
slug: slugForPermalink(permalink) ?? fallbackSlug(title, path),
|
|
53
|
+
html: renderMarkdown(body),
|
|
54
|
+
hash: hashDocument(content),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** The SHA-256 a {@link Document} carries: a hash of its canonical file text. */
|
|
58
|
+
export function hashDocument(content) {
|
|
59
|
+
return createHash('sha256').update(serializeDocument(content), 'utf8').digest('hex');
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Decide whether a path holds a post or a page. Only the directory matters, so
|
|
63
|
+
* a file that moves between `posts/` and `pages/` changes type with it.
|
|
64
|
+
*/
|
|
65
|
+
export function typeForPath(path) {
|
|
66
|
+
for (const segment of path.split('/')) {
|
|
67
|
+
if (segment === 'posts')
|
|
68
|
+
return 'post';
|
|
69
|
+
if (segment === 'pages')
|
|
70
|
+
return 'page';
|
|
71
|
+
}
|
|
72
|
+
throw new TypeError(`Cannot tell the type of ${path}: it is under neither posts/ nor pages/. Pass an explicit type.`);
|
|
73
|
+
}
|
|
74
|
+
/** Content paths are POSIX and relative, whatever the host filesystem says. */
|
|
75
|
+
function normalizePath(path) {
|
|
76
|
+
return path.replace(/\\/g, '/').replace(/^\.?\//, '');
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The slug a permalink implies: its last segment, without any file extension.
|
|
80
|
+
* `/2026/09/hello-world/` is `hello-world`; `/feed.xml` is `feed`.
|
|
81
|
+
*/
|
|
82
|
+
function slugForPermalink(permalink) {
|
|
83
|
+
const segments = permalink.split('/').filter((segment) => segment !== '');
|
|
84
|
+
const last = segments.at(-1);
|
|
85
|
+
if (last === undefined)
|
|
86
|
+
return undefined;
|
|
87
|
+
const slug = last.replace(/\.[^.]+$/, '');
|
|
88
|
+
return slug === '' ? undefined : slug;
|
|
89
|
+
}
|
|
90
|
+
/** The slug for a document whose permalink has not been decided yet. */
|
|
91
|
+
function fallbackSlug(title, path) {
|
|
92
|
+
const fromTitle = slugify(title);
|
|
93
|
+
if (fromTitle !== '')
|
|
94
|
+
return fromTitle;
|
|
95
|
+
const basename = path.split('/').at(-1) ?? '';
|
|
96
|
+
return slugify(basename.replace(/\.md$/, '').replace(/^\d{4}-\d{2}-\d{2}-/, ''));
|
|
97
|
+
}
|
|
98
|
+
function extraOf(data) {
|
|
99
|
+
const extra = {};
|
|
100
|
+
for (const [key, value] of Object.entries(data)) {
|
|
101
|
+
if (KNOWN_KEYS.has(key))
|
|
102
|
+
continue;
|
|
103
|
+
extra[key] = value;
|
|
104
|
+
}
|
|
105
|
+
return extra;
|
|
106
|
+
}
|
|
107
|
+
function optional(key, value) {
|
|
108
|
+
return value === undefined ? {} : { [key]: value };
|
|
109
|
+
}
|
|
110
|
+
function requiredString(value, key, path) {
|
|
111
|
+
const text = asString(value);
|
|
112
|
+
if (text === undefined) {
|
|
113
|
+
throw new TypeError(`${path} needs a non-empty ${key} in its front matter.`);
|
|
114
|
+
}
|
|
115
|
+
return text;
|
|
116
|
+
}
|
|
117
|
+
function asString(value) {
|
|
118
|
+
if (typeof value !== 'string')
|
|
119
|
+
return undefined;
|
|
120
|
+
return value === '' ? undefined : value;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* YAML parses an unquoted timestamp into a Date and leaves a quoted one a
|
|
124
|
+
* string. Either way a Document holds an ISO 8601 string, so the offset the
|
|
125
|
+
* author wrote survives when there was one.
|
|
126
|
+
*/
|
|
127
|
+
function asDate(value, key, path) {
|
|
128
|
+
if (value === undefined || value === null)
|
|
129
|
+
return undefined;
|
|
130
|
+
if (value instanceof Date) {
|
|
131
|
+
if (Number.isNaN(value.getTime())) {
|
|
132
|
+
throw new TypeError(`${path} has an unreadable ${key}.`);
|
|
133
|
+
}
|
|
134
|
+
return value.toISOString();
|
|
135
|
+
}
|
|
136
|
+
const text = asString(value);
|
|
137
|
+
if (text === undefined) {
|
|
138
|
+
throw new TypeError(`${path} has a ${key} that is neither a date nor a string.`);
|
|
139
|
+
}
|
|
140
|
+
return text;
|
|
141
|
+
}
|
|
142
|
+
/** Eleventy accepts one tag as a bare string; the CMS always keeps a list. */
|
|
143
|
+
function asTags(value) {
|
|
144
|
+
if (value === undefined || value === null)
|
|
145
|
+
return [];
|
|
146
|
+
if (typeof value === 'string')
|
|
147
|
+
return value === '' ? [] : [value];
|
|
148
|
+
if (!Array.isArray(value))
|
|
149
|
+
return [];
|
|
150
|
+
return value.filter((tag) => typeof tag === 'string' && tag !== '');
|
|
151
|
+
}
|
|
152
|
+
function asActivityPub(value, path) {
|
|
153
|
+
if (value === undefined || value === null)
|
|
154
|
+
return undefined;
|
|
155
|
+
if (typeof value !== 'object' || Array.isArray(value)) {
|
|
156
|
+
throw new TypeError(`${path} has an activitypub key that is not a mapping.`);
|
|
157
|
+
}
|
|
158
|
+
const block = value;
|
|
159
|
+
const metadata = {
|
|
160
|
+
...optional('id', asString(block['id'])),
|
|
161
|
+
...optional('published', asDate(block['published'], 'activitypub.published', path)),
|
|
162
|
+
};
|
|
163
|
+
return Object.keys(metadata).length === 0 ? undefined : metadata;
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/content/parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Document, DocumentContent, DocumentType } from './document.ts';
|
|
2
|
+
import type { ContentStore } from './store.ts';
|
|
3
|
+
/** What {@link contentFilePath} needs to name a file. */
|
|
4
|
+
export interface ContentFilePathInput {
|
|
5
|
+
/** Posts get a dated filename; pages do not. */
|
|
6
|
+
type: DocumentType;
|
|
7
|
+
/** The slug, already URL-safe. It becomes the tail of the filename. */
|
|
8
|
+
slug: string;
|
|
9
|
+
/** Publish date, ISO 8601. Required for a post, ignored for a page. */
|
|
10
|
+
date?: string | Date | undefined;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Where a document's file goes, relative to the content directory.
|
|
14
|
+
*
|
|
15
|
+
* doc-2: a post filename carries its date so the directory sorts on disk, a
|
|
16
|
+
* page filename does not. Neither is ever parsed for a URL — the front matter's
|
|
17
|
+
* `permalink` is what decides that — so renaming a file changes nothing but
|
|
18
|
+
* the sort order.
|
|
19
|
+
*/
|
|
20
|
+
export declare function contentFilePath(input: ContentFilePathInput): string;
|
|
21
|
+
/** What {@link freeSlug} looks for a gap in. */
|
|
22
|
+
export interface FreeSlugOptions extends ContentFilePathInput {
|
|
23
|
+
/** The content directory the file would go in. */
|
|
24
|
+
contentDir: string;
|
|
25
|
+
/** The index, consulted so a permalink another file claims is skipped too. */
|
|
26
|
+
store: ContentStore;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The given slug, or the first numbered variant of it that neither the content
|
|
30
|
+
* directory nor the index has already claimed.
|
|
31
|
+
*
|
|
32
|
+
* Both are checked because they can disagree for a moment: a file the watcher
|
|
33
|
+
* has not picked up yet is on disk and not in the index, and a file that was
|
|
34
|
+
* deleted outside the CMS is the other way round. Overwriting either would
|
|
35
|
+
* lose someone's writing.
|
|
36
|
+
*/
|
|
37
|
+
export declare function freeSlug(options: FreeSlugOptions): Promise<string>;
|
|
38
|
+
/** What {@link saveDocument} writes, and where. */
|
|
39
|
+
export interface SaveDocumentOptions {
|
|
40
|
+
/** The content directory. Missing parent directories are created. */
|
|
41
|
+
contentDir: string;
|
|
42
|
+
/** The index to bring up to date once the bytes have landed. */
|
|
43
|
+
store: ContentStore;
|
|
44
|
+
/** The file's path relative to {@link SaveDocumentOptions.contentDir}. */
|
|
45
|
+
path: string;
|
|
46
|
+
/** The document to serialize. */
|
|
47
|
+
content: DocumentContent;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The admin's write path, per doc-1: serialize, write the file atomically, then
|
|
51
|
+
* update the index directly rather than waiting for the watcher.
|
|
52
|
+
*
|
|
53
|
+
* The temporary file is written in the same directory as its destination so the
|
|
54
|
+
* rename is a rename and not a copy across devices, and a reader either sees
|
|
55
|
+
* the old file or the whole new one, never a half-written one. The watcher
|
|
56
|
+
* event that follows the rename is a no-op, because the hash already matches
|
|
57
|
+
* what this put in the index.
|
|
58
|
+
*/
|
|
59
|
+
export declare function saveDocument(options: SaveDocumentOptions): Promise<Document>;
|
|
60
|
+
//# sourceMappingURL=save.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"save.d.ts","sourceRoot":"","sources":["../../src/content/save.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAG7E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C,yDAAyD;AACzD,MAAM,WAAW,oBAAoB;IACnC,gDAAgD;IAChD,IAAI,EAAE,YAAY,CAAC;IACnB,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CAClC;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAYnE;AAED,gDAAgD;AAChD,MAAM,WAAW,eAAgB,SAAQ,oBAAoB;IAC3D,kDAAkD;IAClD,UAAU,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,KAAK,EAAE,YAAY,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAcxE;AAED,mDAAmD;AACnD,MAAM,WAAW,mBAAmB;IAClC,qEAAqE;IACrE,UAAU,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,KAAK,EAAE,YAAY,CAAC;IACpB,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,OAAO,EAAE,eAAe,CAAC;CAC1B;AAED;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAsBlF"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { access, mkdir, rename, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { parseDocument } from './parser.js';
|
|
5
|
+
import { defaultPermalink } from './slug.js';
|
|
6
|
+
import { serializeDocument } from './writer.js';
|
|
7
|
+
/**
|
|
8
|
+
* Where a document's file goes, relative to the content directory.
|
|
9
|
+
*
|
|
10
|
+
* doc-2: a post filename carries its date so the directory sorts on disk, a
|
|
11
|
+
* page filename does not. Neither is ever parsed for a URL — the front matter's
|
|
12
|
+
* `permalink` is what decides that — so renaming a file changes nothing but
|
|
13
|
+
* the sort order.
|
|
14
|
+
*/
|
|
15
|
+
export function contentFilePath(input) {
|
|
16
|
+
if (input.slug === '') {
|
|
17
|
+
throw new TypeError('A content file needs a non-empty slug.');
|
|
18
|
+
}
|
|
19
|
+
if (input.type === 'page')
|
|
20
|
+
return `pages/${input.slug}.md`;
|
|
21
|
+
if (input.date === undefined) {
|
|
22
|
+
throw new TypeError(`A post filename needs a date; "${input.slug}" has none.`);
|
|
23
|
+
}
|
|
24
|
+
return `posts/${calendarDay(input.date)}-${input.slug}.md`;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The given slug, or the first numbered variant of it that neither the content
|
|
28
|
+
* directory nor the index has already claimed.
|
|
29
|
+
*
|
|
30
|
+
* Both are checked because they can disagree for a moment: a file the watcher
|
|
31
|
+
* has not picked up yet is on disk and not in the index, and a file that was
|
|
32
|
+
* deleted outside the CMS is the other way round. Overwriting either would
|
|
33
|
+
* lose someone's writing.
|
|
34
|
+
*/
|
|
35
|
+
export async function freeSlug(options) {
|
|
36
|
+
const { contentDir, store, ...input } = options;
|
|
37
|
+
for (let suffix = 1;; suffix += 1) {
|
|
38
|
+
const slug = suffix === 1 ? input.slug : `${input.slug}-${String(suffix)}`;
|
|
39
|
+
const relative = contentFilePath({ ...input, slug });
|
|
40
|
+
const permalink = defaultPermalink({ type: input.type, slug, date: input.date });
|
|
41
|
+
if (store.getByPath(relative) !== undefined)
|
|
42
|
+
continue;
|
|
43
|
+
if (store.getByPermalink(permalink) !== undefined)
|
|
44
|
+
continue;
|
|
45
|
+
if (await exists(path.join(contentDir, ...relative.split('/'))))
|
|
46
|
+
continue;
|
|
47
|
+
return slug;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The admin's write path, per doc-1: serialize, write the file atomically, then
|
|
52
|
+
* update the index directly rather than waiting for the watcher.
|
|
53
|
+
*
|
|
54
|
+
* The temporary file is written in the same directory as its destination so the
|
|
55
|
+
* rename is a rename and not a copy across devices, and a reader either sees
|
|
56
|
+
* the old file or the whole new one, never a half-written one. The watcher
|
|
57
|
+
* event that follows the rename is a no-op, because the hash already matches
|
|
58
|
+
* what this put in the index.
|
|
59
|
+
*/
|
|
60
|
+
export async function saveDocument(options) {
|
|
61
|
+
const relative = options.path.replace(/\\/g, '/');
|
|
62
|
+
const file = path.join(options.contentDir, ...relative.split('/'));
|
|
63
|
+
const source = serializeDocument(options.content);
|
|
64
|
+
// Parsed before it is written, so a document that cannot be read back is
|
|
65
|
+
// refused rather than left on disk for the watcher to complain about.
|
|
66
|
+
const document = parseDocument(source, { path: relative });
|
|
67
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
68
|
+
const temporary = `${file}.${randomBytes(6).toString('hex')}.tmp`;
|
|
69
|
+
try {
|
|
70
|
+
await writeFile(temporary, source, 'utf8');
|
|
71
|
+
await rename(temporary, file);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
await rm(temporary, { force: true });
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
options.store.upsert(document);
|
|
78
|
+
return document;
|
|
79
|
+
}
|
|
80
|
+
/** The calendar day of a date as written, without shifting it into another one. */
|
|
81
|
+
function calendarDay(date) {
|
|
82
|
+
if (date instanceof Date) {
|
|
83
|
+
if (Number.isNaN(date.getTime())) {
|
|
84
|
+
throw new TypeError('A post filename needs a valid date.');
|
|
85
|
+
}
|
|
86
|
+
return date.toISOString().slice(0, 10);
|
|
87
|
+
}
|
|
88
|
+
const match = /^(\d{4}-\d{2}-\d{2})/.exec(date);
|
|
89
|
+
if (match?.[1] === undefined) {
|
|
90
|
+
throw new TypeError(`A post filename needs an ISO 8601 date, received ${JSON.stringify(date)}`);
|
|
91
|
+
}
|
|
92
|
+
return match[1];
|
|
93
|
+
}
|
|
94
|
+
async function exists(file) {
|
|
95
|
+
try {
|
|
96
|
+
await access(file);
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=save.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"save.js","sourceRoot":"","sources":["../../src/content/save.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { DocumentType } from './document.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Turn a title into a URL slug: lowercase ASCII letters, digits and hyphens.
|
|
4
|
+
*
|
|
5
|
+
* Accented letters lose their accents, a few letters that do not decompose are
|
|
6
|
+
* transliterated, and everything else becomes a separator. Returns an empty
|
|
7
|
+
* string when nothing survives, so callers can fall back to something else.
|
|
8
|
+
*/
|
|
9
|
+
export declare function slugify(title: string): string;
|
|
10
|
+
/** What {@link defaultPermalink} needs to know about a document. */
|
|
11
|
+
export interface DefaultPermalinkInput {
|
|
12
|
+
type: DocumentType;
|
|
13
|
+
slug: string;
|
|
14
|
+
/** Publish date. Required for posts, ignored for pages. */
|
|
15
|
+
date?: string | Date | undefined;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The permalink the admin form proposes for a new document.
|
|
19
|
+
*
|
|
20
|
+
* Posts live under their year and month, pages at the top level. Both end in a
|
|
21
|
+
* slash, which is the canonical form. The stored `permalink` is what actually
|
|
22
|
+
* counts: this only fills the field in when the form supplies none.
|
|
23
|
+
*/
|
|
24
|
+
export declare function defaultPermalink(input: DefaultPermalinkInput): string;
|
|
25
|
+
//# sourceMappingURL=slug.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slug.d.ts","sourceRoot":"","sources":["../../src/content/slug.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAoBlD;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAW7C;AAED,oEAAoE;AACpE,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,YAAY,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CAClC;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,MAAM,CAarE"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Letters that Unicode decomposition leaves alone. NFKD splits `é` into `e` and
|
|
3
|
+
* a combining accent, but `æ` and `ß` are letters in their own right, so they
|
|
4
|
+
* need spelling out before the ASCII filter throws them away.
|
|
5
|
+
*/
|
|
6
|
+
const TRANSLITERATIONS = [
|
|
7
|
+
[/ß/g, 'ss'],
|
|
8
|
+
[/æ/g, 'ae'],
|
|
9
|
+
[/œ/g, 'oe'],
|
|
10
|
+
[/ø/g, 'o'],
|
|
11
|
+
[/đ|ð/g, 'd'],
|
|
12
|
+
[/ł/g, 'l'],
|
|
13
|
+
[/þ/g, 'th'],
|
|
14
|
+
[/ħ/g, 'h'],
|
|
15
|
+
[/ı/g, 'i'],
|
|
16
|
+
[/ŋ/g, 'n'],
|
|
17
|
+
];
|
|
18
|
+
/**
|
|
19
|
+
* Turn a title into a URL slug: lowercase ASCII letters, digits and hyphens.
|
|
20
|
+
*
|
|
21
|
+
* Accented letters lose their accents, a few letters that do not decompose are
|
|
22
|
+
* transliterated, and everything else becomes a separator. Returns an empty
|
|
23
|
+
* string when nothing survives, so callers can fall back to something else.
|
|
24
|
+
*/
|
|
25
|
+
export function slugify(title) {
|
|
26
|
+
let value = title.normalize('NFKD').toLowerCase();
|
|
27
|
+
for (const [pattern, replacement] of TRANSLITERATIONS) {
|
|
28
|
+
value = value.replace(pattern, replacement);
|
|
29
|
+
}
|
|
30
|
+
return value
|
|
31
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
32
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
33
|
+
.replace(/^-+|-+$/g, '');
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The permalink the admin form proposes for a new document.
|
|
37
|
+
*
|
|
38
|
+
* Posts live under their year and month, pages at the top level. Both end in a
|
|
39
|
+
* slash, which is the canonical form. The stored `permalink` is what actually
|
|
40
|
+
* counts: this only fills the field in when the form supplies none.
|
|
41
|
+
*/
|
|
42
|
+
export function defaultPermalink(input) {
|
|
43
|
+
if (input.slug === '') {
|
|
44
|
+
throw new TypeError('A permalink needs a non-empty slug.');
|
|
45
|
+
}
|
|
46
|
+
if (input.type === 'page')
|
|
47
|
+
return `/${input.slug}/`;
|
|
48
|
+
if (input.date === undefined) {
|
|
49
|
+
throw new TypeError(`A post permalink needs a date; "${input.slug}" has none.`);
|
|
50
|
+
}
|
|
51
|
+
const { year, month } = yearAndMonth(input.date);
|
|
52
|
+
return `/${year}/${month}/${input.slug}/`;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Read the calendar year and month of a date as written, without shifting it
|
|
56
|
+
* into UTC: a post published at 00:30 on 1 October in Berlin belongs to
|
|
57
|
+
* October, not to September.
|
|
58
|
+
*/
|
|
59
|
+
function yearAndMonth(date) {
|
|
60
|
+
if (date instanceof Date) {
|
|
61
|
+
if (Number.isNaN(date.getTime())) {
|
|
62
|
+
throw new TypeError('A post permalink needs a valid date.');
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
year: String(date.getUTCFullYear()).padStart(4, '0'),
|
|
66
|
+
month: String(date.getUTCMonth() + 1).padStart(2, '0'),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const match = /^(\d{4})-(\d{2})/.exec(date);
|
|
70
|
+
if (match === null || match[1] === undefined || match[2] === undefined) {
|
|
71
|
+
throw new TypeError(`A post permalink needs an ISO 8601 date, received ${JSON.stringify(date)}`);
|
|
72
|
+
}
|
|
73
|
+
return { year: match[1], month: match[2] };
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=slug.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slug.js","sourceRoot":"","sources":["../../src/content/slug.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,gBAAgB,GAA6C;IACjE,CAAC,IAAI,EAAE,IAAI,CAAC;IACZ,CAAC,IAAI,EAAE,IAAI,CAAC;IACZ,CAAC,IAAI,EAAE,IAAI,CAAC;IACZ,CAAC,IAAI,EAAE,GAAG,CAAC;IACX,CAAC,MAAM,EAAE,GAAG,CAAC;IACb,CAAC,IAAI,EAAE,GAAG,CAAC;IACX,CAAC,IAAI,EAAE,IAAI,CAAC;IACZ,CAAC,IAAI,EAAE,GAAG,CAAC;IACX,CAAC,IAAI,EAAE,GAAG,CAAC;IACX,CAAC,IAAI,EAAE,GAAG,CAAC;CACZ,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,IAAI,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;IAElD,KAAK,MAAM,CAAC,OAAO,EAAE,WAAW,CAAC,IAAI,gBAAgB,EAAE,CAAC;QACtD,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAC9C,CAAC;IAED,OAAO,KAAK;SACT,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC;SAC/B,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;AAC7B,CAAC;AAUD;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAA4B;IAC3D,IAAI,KAAK,CAAC,IAAI,KAAK,EAAE,EAAE,CAAC;QACtB,MAAM,IAAI,SAAS,CAAC,qCAAqC,CAAC,CAAC;IAC7D,CAAC;IAED,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,IAAI,KAAK,CAAC,IAAI,GAAG,CAAC;IAEpD,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,IAAI,SAAS,CAAC,mCAAmC,KAAK,CAAC,IAAI,aAAa,CAAC,CAAC;IAClF,CAAC;IAED,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACjD,OAAO,IAAI,IAAI,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,GAAG,CAAC;AAC5C,CAAC;AAED;;;;GAIG;AACH,SAAS,YAAY,CAAC,IAAmB;IACvC,IAAI,IAAI,YAAY,IAAI,EAAE,CAAC;QACzB,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;YACjC,MAAM,IAAI,SAAS,CAAC,sCAAsC,CAAC,CAAC;QAC9D,CAAC;QACD,OAAO;YACL,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC;YACpD,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC;SACvD,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5C,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;QACvE,MAAM,IAAI,SAAS,CACjB,qDAAqD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAC5E,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AAC7C,CAAC"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { Document, DocumentType } from './document.ts';
|
|
2
|
+
/** File name of the derived index inside a site's data directory. */
|
|
3
|
+
export declare const DATABASE_FILE = "geekity.db";
|
|
4
|
+
/** Directory name that marks a document as thrown away but not yet deleted. */
|
|
5
|
+
export declare const TRASH_DIRECTORY = "_trash";
|
|
6
|
+
/** Where {@link openContentStore} puts the database. */
|
|
7
|
+
export interface OpenContentStoreOptions {
|
|
8
|
+
/** Directory the database lives in. Created if it is missing. */
|
|
9
|
+
dataDir: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The derived index over the Markdown files.
|
|
13
|
+
*
|
|
14
|
+
* Files are the source of truth (decision-1); this is a queryable copy that
|
|
15
|
+
* answers the questions a directory walk cannot answer cheaply. Deleting the
|
|
16
|
+
* database is safe because the next boot rebuilds it.
|
|
17
|
+
*/
|
|
18
|
+
export interface ContentStore {
|
|
19
|
+
/** Absolute path of the SQLite file. */
|
|
20
|
+
readonly file: string;
|
|
21
|
+
/**
|
|
22
|
+
* Insert or replace the row for `document.path`, tags included.
|
|
23
|
+
*
|
|
24
|
+
* Throws {@link DuplicatePermalinkError} when another path already claims
|
|
25
|
+
* the same permalink.
|
|
26
|
+
*/
|
|
27
|
+
upsert(document: Document): void;
|
|
28
|
+
/** {@link ContentStore.upsert} for many documents, in one transaction. */
|
|
29
|
+
upsertAll(documents: Iterable<Document>): void;
|
|
30
|
+
/** Drop a document by path. Returns `false` when there was nothing to drop. */
|
|
31
|
+
remove(path: string): boolean;
|
|
32
|
+
/** The document at a content-relative path, or `undefined`. */
|
|
33
|
+
getByPath(path: string): Document | undefined;
|
|
34
|
+
/** The document a URL resolves to, or `undefined`. Permalinks are unique. */
|
|
35
|
+
getByPermalink(permalink: string): Document | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* The document with this slug, or `undefined`. Slugs are not unique across
|
|
38
|
+
* years, so the newest match wins.
|
|
39
|
+
*/
|
|
40
|
+
getBySlug(slug: string): Document | undefined;
|
|
41
|
+
/** Published, untrashed posts, newest first. */
|
|
42
|
+
listPosts(options?: ListOptions): Document[];
|
|
43
|
+
/** Published, untrashed documents carrying a tag, newest first. */
|
|
44
|
+
listByTag(tag: string, options?: ListByTagOptions): Document[];
|
|
45
|
+
/** Everything the admin may see, trash and drafts included unless filtered. */
|
|
46
|
+
listAll(options?: ListAllOptions): Document[];
|
|
47
|
+
/** Every indexed path, sorted. What a sync compares the content tree against. */
|
|
48
|
+
listPaths(): string[];
|
|
49
|
+
/** How many documents there are of each kind. */
|
|
50
|
+
counts(): ContentCounts;
|
|
51
|
+
/** How many published, untrashed documents carry a tag. */
|
|
52
|
+
countByTag(tag: string, options?: ListByTagOptions): number;
|
|
53
|
+
/** Every tag in use on published, untrashed documents, with its count. */
|
|
54
|
+
listTags(): TagCount[];
|
|
55
|
+
/** Close the database. Safe to call twice. */
|
|
56
|
+
close(): void;
|
|
57
|
+
}
|
|
58
|
+
/** Paging for the public listings. */
|
|
59
|
+
export interface ListOptions {
|
|
60
|
+
/** How many documents to return. Defaults to everything. */
|
|
61
|
+
limit?: number | undefined;
|
|
62
|
+
/** How many to skip first. Defaults to 0. */
|
|
63
|
+
offset?: number | undefined;
|
|
64
|
+
}
|
|
65
|
+
/** Paging plus the type filter the tag archives need. */
|
|
66
|
+
export interface ListByTagOptions extends ListOptions {
|
|
67
|
+
/** Restrict to posts or pages. Defaults to both. */
|
|
68
|
+
type?: DocumentType | undefined;
|
|
69
|
+
}
|
|
70
|
+
/** Filters for the admin listing, which is the only view of the trash. */
|
|
71
|
+
export interface ListAllOptions extends ListOptions {
|
|
72
|
+
/** Restrict to posts or pages. Defaults to both. */
|
|
73
|
+
type?: DocumentType | undefined;
|
|
74
|
+
/** `true` for drafts only, `false` for published only. Defaults to both. */
|
|
75
|
+
draft?: boolean | undefined;
|
|
76
|
+
/** `true` for trashed only, `false` for live only. Defaults to live only. */
|
|
77
|
+
trashed?: boolean | undefined;
|
|
78
|
+
/** Restrict to documents carrying this tag. */
|
|
79
|
+
tag?: string | undefined;
|
|
80
|
+
}
|
|
81
|
+
/** What {@link ContentStore.counts} reports. */
|
|
82
|
+
export interface ContentCounts {
|
|
83
|
+
/** Every indexed document, trash included. */
|
|
84
|
+
total: number;
|
|
85
|
+
/** Published, untrashed posts: the size of the public archive. */
|
|
86
|
+
posts: number;
|
|
87
|
+
/** Published, untrashed pages. */
|
|
88
|
+
pages: number;
|
|
89
|
+
/** Untrashed drafts of either type. */
|
|
90
|
+
drafts: number;
|
|
91
|
+
/** Documents under `_trash/`. */
|
|
92
|
+
trashed: number;
|
|
93
|
+
}
|
|
94
|
+
/** One tag and how many published documents carry it. */
|
|
95
|
+
export interface TagCount {
|
|
96
|
+
tag: string;
|
|
97
|
+
count: number;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Thrown when two files claim the same permalink. Two documents at one URL is
|
|
101
|
+
* a content mistake the site owner has to fix, so the index refuses it rather
|
|
102
|
+
* than picking a winner.
|
|
103
|
+
*/
|
|
104
|
+
export declare class DuplicatePermalinkError extends Error {
|
|
105
|
+
readonly name = "DuplicatePermalinkError";
|
|
106
|
+
/** The permalink both documents claim. */
|
|
107
|
+
readonly permalink: string;
|
|
108
|
+
/** The path that was being indexed. */
|
|
109
|
+
readonly path: string;
|
|
110
|
+
/** The path that already held the permalink, when it could be looked up. */
|
|
111
|
+
readonly conflictingPath: string | undefined;
|
|
112
|
+
constructor(permalink: string, path: string, conflictingPath: string | undefined);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Whether a content path is in the trash. Trashed documents stay indexed so
|
|
116
|
+
* the admin can restore them, and stay out of every public listing.
|
|
117
|
+
*/
|
|
118
|
+
export declare function isTrashedPath(contentPath: string): boolean;
|
|
119
|
+
/**
|
|
120
|
+
* The value a document sorts by: its date as a UTC instant, so
|
|
121
|
+
* `2026-09-02T09:00:00-05:00` and `2026-09-02T14:00:00Z` compare equal.
|
|
122
|
+
*
|
|
123
|
+
* A document whose date is missing or unreadable sorts last, because SQLite
|
|
124
|
+
* orders NULL below every string.
|
|
125
|
+
*/
|
|
126
|
+
export declare function dateSortKey(date: string | undefined): string | null;
|
|
127
|
+
/**
|
|
128
|
+
* Open (and if needed create) the index in `dataDir`, applying every migration
|
|
129
|
+
* the package ships. Applying them is idempotent, so reopening an up-to-date
|
|
130
|
+
* database does nothing.
|
|
131
|
+
*/
|
|
132
|
+
export declare function openContentStore(options: OpenContentStoreOptions): ContentStore;
|
|
133
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/content/store.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAuB,QAAQ,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAEjF,qEAAqE;AACrE,eAAO,MAAM,aAAa,eAAe,CAAC;AAE1C,+EAA+E;AAC/E,eAAO,MAAM,eAAe,WAAW,CAAC;AAExC,wDAAwD;AACxD,MAAM,WAAW,uBAAuB;IACtC,iEAAiE;IACjE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,wCAAwC;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,MAAM,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IACjC,0EAA0E;IAC1E,SAAS,CAAC,SAAS,EAAE,QAAQ,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC;IAC/C,+EAA+E;IAC/E,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9B,+DAA+D;IAC/D,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC9C,6EAA6E;IAC7E,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;IACxD;;;OAGG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC9C,gDAAgD;IAChD,SAAS,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,QAAQ,EAAE,CAAC;IAC7C,mEAAmE;IACnE,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,QAAQ,EAAE,CAAC;IAC/D,+EAA+E;IAC/E,OAAO,CAAC,OAAO,CAAC,EAAE,cAAc,GAAG,QAAQ,EAAE,CAAC;IAC9C,iFAAiF;IACjF,SAAS,IAAI,MAAM,EAAE,CAAC;IACtB,iDAAiD;IACjD,MAAM,IAAI,aAAa,CAAC;IACxB,2DAA2D;IAC3D,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAAC;IAC5D,0EAA0E;IAC1E,QAAQ,IAAI,QAAQ,EAAE,CAAC;IACvB,8CAA8C;IAC9C,KAAK,IAAI,IAAI,CAAC;CACf;AAED,sCAAsC;AACtC,MAAM,WAAW,WAAW;IAC1B,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED,yDAAyD;AACzD,MAAM,WAAW,gBAAiB,SAAQ,WAAW;IACnD,oDAAoD;IACpD,IAAI,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;CACjC;AAED,0EAA0E;AAC1E,MAAM,WAAW,cAAe,SAAQ,WAAW;IACjD,oDAAoD;IACpD,IAAI,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;IAChC,4EAA4E;IAC5E,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,6EAA6E;IAC7E,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,+CAA+C;IAC/C,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B;AAED,gDAAgD;AAChD,MAAM,WAAW,aAAa;IAC5B,8CAA8C;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,yDAAyD;AACzD,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,SAAkB,IAAI,6BAA6B;IACnD,0CAA0C;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4EAA4E;IAC5E,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IAE7C,YAAY,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,SAAS,EAS/E;CACF;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAInE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,YAAY,CAgO/E"}
|