mdorigin 0.1.2 → 0.1.3
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 +69 -24
- package/dist/adapters/cloudflare.d.ts +5 -1
- package/dist/adapters/cloudflare.js +2 -1
- package/dist/adapters/node.d.ts +2 -0
- package/dist/adapters/node.js +1 -0
- package/dist/cli/build-cloudflare.js +5 -4
- package/dist/cli/build-index.js +17 -3
- package/dist/cli/build-search.js +5 -4
- package/dist/cli/dev.js +5 -4
- package/dist/cli/main.js +4 -4
- package/dist/cloudflare.d.ts +1 -0
- package/dist/cloudflare.js +26 -1
- package/dist/core/extensions.d.ts +66 -0
- package/dist/core/extensions.js +86 -0
- package/dist/core/request-handler.d.ts +2 -0
- package/dist/core/request-handler.js +141 -74
- package/dist/core/site-config.d.ts +14 -0
- package/dist/core/site-config.js +74 -16
- package/dist/html/template.d.ts +3 -0
- package/dist/html/template.js +26 -2
- package/dist/index-builder.d.ts +3 -1
- package/dist/index-builder.js +28 -16
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/package.json +11 -5
package/README.md
CHANGED
|
@@ -2,7 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
`mdorigin` is a markdown-first publishing engine.
|
|
4
4
|
|
|
5
|
-
It treats markdown as the only source of truth, serves raw `.md` directly for agents, renders
|
|
5
|
+
It treats markdown as the only source of truth, serves raw `.md` directly for agents, renders HTML for humans from the same directory tree, and can expose the same content to both browsers and tools through stable routes.
|
|
6
|
+
|
|
7
|
+
## Core Principle
|
|
8
|
+
|
|
9
|
+
`mdorigin` is not meant to become a template system.
|
|
10
|
+
|
|
11
|
+
Its core is:
|
|
12
|
+
|
|
13
|
+
- routing rules
|
|
14
|
+
- markdown tree normalization
|
|
15
|
+
- document, index, asset, search, and site semantics
|
|
16
|
+
- stable page models derived from the same content tree
|
|
17
|
+
|
|
18
|
+
That means `mdorigin` should own content semantics, while page rendering remains extensible. In practice, this is the direction for advanced customization: users should be able to replace page-level rendering with code, without replacing the routing and content kernel itself.
|
|
19
|
+
|
|
20
|
+
## Why mdorigin
|
|
21
|
+
|
|
22
|
+
- markdown stays directly accessible at `.md` routes
|
|
23
|
+
- extensionless routes render human-friendly HTML from the same files
|
|
24
|
+
- `README.md`, `index.md`, and `SKILL.md` all fit into one routing model
|
|
25
|
+
- the same core works in local preview and Cloudflare Workers
|
|
26
|
+
- optional search is powered by [`indexbind`](https://github.com/jolestar/indexbind)
|
|
6
27
|
|
|
7
28
|
## Install
|
|
8
29
|
|
|
@@ -18,9 +39,53 @@ mdorigin dev --root docs/site
|
|
|
18
39
|
|
|
19
40
|
If you prefer a project-local install instead, use `npm install --save-dev mdorigin` and run it with `npx --no-install mdorigin ...`.
|
|
20
41
|
|
|
42
|
+
## Quick Start
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
mdorigin dev --root docs/site
|
|
46
|
+
mdorigin build index --root docs/site
|
|
47
|
+
mdorigin build cloudflare --root docs/site
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
That is enough to preview a site locally, keep directory indexes up to date, and generate a Cloudflare Worker bundle.
|
|
51
|
+
|
|
52
|
+
## Code-Based Extensions
|
|
53
|
+
|
|
54
|
+
`mdorigin` now supports a code config alongside `mdorigin.config.json`.
|
|
55
|
+
|
|
56
|
+
Use `mdorigin.config.ts` when you want to customize rendering or indexing with code instead of asking for a template system:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
export default {
|
|
60
|
+
siteTitle: "My Site",
|
|
61
|
+
plugins: [
|
|
62
|
+
{
|
|
63
|
+
name: "custom-footer",
|
|
64
|
+
renderFooter() {
|
|
65
|
+
return '<footer class="custom-footer">Built with mdorigin</footer>';
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Current stable hooks are:
|
|
73
|
+
|
|
74
|
+
- `transformIndex(entries, context)`
|
|
75
|
+
- `renderHeader(context)`
|
|
76
|
+
- `renderFooter(context)`
|
|
77
|
+
- `renderPage(page, context, next)`
|
|
78
|
+
- `transformHtml(html, context)`
|
|
79
|
+
|
|
80
|
+
The intended boundary is:
|
|
81
|
+
|
|
82
|
+
- `mdorigin` owns routing and normalized content semantics
|
|
83
|
+
- plugins may replace page rendering
|
|
84
|
+
- plugins do not replace the request kernel itself
|
|
85
|
+
|
|
21
86
|
## Optional Search
|
|
22
87
|
|
|
23
|
-
`mdorigin` can build a local retrieval bundle through the optional [`indexbind`](https://github.com/jolestar/indexbind) package:
|
|
88
|
+
`mdorigin` can build a local retrieval bundle through the optional [`indexbind`](https://github.com/jolestar/indexbind) package. For the retrieval engine itself, see the `indexbind` docs: <https://indexbind.jolestar.workers.dev>.
|
|
24
89
|
|
|
25
90
|
```bash
|
|
26
91
|
npm install indexbind
|
|
@@ -46,33 +111,13 @@ Runtime endpoints:
|
|
|
46
111
|
- `/api/search?q=cloudflare+deploy`
|
|
47
112
|
- `/api/openapi.json`
|
|
48
113
|
|
|
49
|
-
## Repo Development
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
npm install
|
|
53
|
-
npm run check
|
|
54
|
-
npm run dev -- --root docs/site
|
|
55
|
-
npm run build:index -- --root docs/site
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
## Release
|
|
59
|
-
|
|
60
|
-
Publishing is handled by GitHub Actions through npm trusted publishing.
|
|
61
|
-
|
|
62
|
-
- workflow: `.github/workflows/release.yml`
|
|
63
|
-
- trigger: push a tag like `v0.1.2`, or run the workflow manually
|
|
64
|
-
|
|
65
|
-
The npm package settings still need a one-time trusted publisher entry for:
|
|
66
|
-
|
|
67
|
-
- owner: `jolestar`
|
|
68
|
-
- repository: `mdorigin`
|
|
69
|
-
- workflow file: `release.yml`
|
|
70
|
-
|
|
71
114
|
## Docs
|
|
72
115
|
|
|
116
|
+
- Docs site: <https://mdorigin.jolestar.workers.dev>
|
|
73
117
|
- Getting started: [`docs/site/guides/getting-started.md`](docs/site/guides/getting-started.md)
|
|
74
118
|
- Routing model: [`docs/site/concepts/routing.md`](docs/site/concepts/routing.md)
|
|
75
119
|
- Directory indexes: [`docs/site/concepts/directory-indexes.md`](docs/site/concepts/directory-indexes.md)
|
|
76
120
|
- Configuration: [`docs/site/reference/configuration.md`](docs/site/reference/configuration.md)
|
|
77
121
|
- CLI: [`docs/site/reference/cli.md`](docs/site/reference/cli.md)
|
|
122
|
+
- Search setup: [`docs/site/guides/getting-started.md`](docs/site/guides/getting-started.md#quick-start)
|
|
78
123
|
- Cloudflare deployment: [`docs/site/guides/cloudflare.md`](docs/site/guides/cloudflare.md)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ContentEntryKind } from '../core/content-store.js';
|
|
2
|
+
import type { MdoPlugin } from '../core/extensions.js';
|
|
2
3
|
import type { ResolvedSiteConfig } from '../core/site-config.js';
|
|
3
4
|
import { type SearchBundleEntry } from '../search.js';
|
|
4
5
|
export interface CloudflareManifestEntry {
|
|
@@ -16,4 +17,7 @@ export interface CloudflareManifest {
|
|
|
16
17
|
export interface ExportedHandlerLike {
|
|
17
18
|
fetch(request: Request): Promise<Response>;
|
|
18
19
|
}
|
|
19
|
-
export
|
|
20
|
+
export interface CreateCloudflareWorkerOptions {
|
|
21
|
+
plugins?: MdoPlugin[];
|
|
22
|
+
}
|
|
23
|
+
export declare function createCloudflareWorker(manifest: CloudflareManifest, options?: CreateCloudflareWorkerOptions): ExportedHandlerLike;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MemoryContentStore, } from '../core/content-store.js';
|
|
2
2
|
import { handleSiteRequest } from '../core/request-handler.js';
|
|
3
3
|
import { createSearchApiFromBundle } from '../search.js';
|
|
4
|
-
export function createCloudflareWorker(manifest) {
|
|
4
|
+
export function createCloudflareWorker(manifest, options = {}) {
|
|
5
5
|
const store = new MemoryContentStore(manifest.entries.map((entry) => {
|
|
6
6
|
if (entry.kind === 'text') {
|
|
7
7
|
return {
|
|
@@ -50,6 +50,7 @@ export function createCloudflareWorker(manifest) {
|
|
|
50
50
|
searchParams: url.searchParams,
|
|
51
51
|
requestUrl: request.url,
|
|
52
52
|
searchApi,
|
|
53
|
+
plugins: options.plugins,
|
|
53
54
|
});
|
|
54
55
|
const headers = new Headers(siteResponse.headers);
|
|
55
56
|
const body = siteResponse.body instanceof Uint8Array
|
package/dist/adapters/node.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type IncomingMessage, type ServerResponse } from 'node:http';
|
|
2
2
|
import type { ContentStore } from '../core/content-store.js';
|
|
3
|
+
import type { MdoPlugin } from '../core/extensions.js';
|
|
3
4
|
import type { ResolvedSiteConfig } from '../core/site-config.js';
|
|
4
5
|
import type { SearchApi } from '../search.js';
|
|
5
6
|
export interface NodeAdapterOptions {
|
|
@@ -7,6 +8,7 @@ export interface NodeAdapterOptions {
|
|
|
7
8
|
draftMode: 'include' | 'exclude';
|
|
8
9
|
siteConfig: ResolvedSiteConfig;
|
|
9
10
|
searchApi?: SearchApi;
|
|
11
|
+
plugins?: MdoPlugin[];
|
|
10
12
|
}
|
|
11
13
|
export declare function createFileSystemContentStore(rootDir: string): ContentStore;
|
|
12
14
|
export declare function createNodeRequestListener(options: NodeAdapterOptions): (request: IncomingMessage, response: ServerResponse) => Promise<void>;
|
package/dist/adapters/node.js
CHANGED
|
@@ -126,6 +126,7 @@ export function createNodeRequestListener(options) {
|
|
|
126
126
|
searchParams: url.searchParams,
|
|
127
127
|
requestUrl: url.toString(),
|
|
128
128
|
searchApi: options.searchApi,
|
|
129
|
+
plugins: options.plugins,
|
|
129
130
|
});
|
|
130
131
|
response.statusCode = siteResponse.status;
|
|
131
132
|
for (const [headerName, headerValue] of Object.entries(siteResponse.headers)) {
|
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { createFileSystemContentStore } from '../adapters/node.js';
|
|
3
3
|
import { writeCloudflareBundle } from '../cloudflare.js';
|
|
4
|
-
import { applySiteConfigFrontmatterDefaults,
|
|
4
|
+
import { applySiteConfigFrontmatterDefaults, loadUserSiteConfig, } from '../core/site-config.js';
|
|
5
5
|
export async function runBuildCloudflareCommand(argv) {
|
|
6
6
|
const args = parseArgs(argv);
|
|
7
7
|
if (!args.root) {
|
|
8
|
-
console.error('Usage: mdorigin build cloudflare --root <content-dir> [--out ./dist/cloudflare] [--config
|
|
8
|
+
console.error('Usage: mdorigin build cloudflare --root <content-dir> [--out ./dist/cloudflare] [--config <config-file>] [--search ./dist/search]');
|
|
9
9
|
process.exitCode = 1;
|
|
10
10
|
return;
|
|
11
11
|
}
|
|
12
12
|
const rootDir = path.resolve(args.root);
|
|
13
|
-
const
|
|
13
|
+
const loadedConfig = await loadUserSiteConfig({
|
|
14
14
|
cwd: process.cwd(),
|
|
15
15
|
rootDir,
|
|
16
16
|
configPath: args.config,
|
|
17
17
|
});
|
|
18
18
|
const store = createFileSystemContentStore(rootDir);
|
|
19
|
-
const siteConfig = await applySiteConfigFrontmatterDefaults(store,
|
|
19
|
+
const siteConfig = await applySiteConfigFrontmatterDefaults(store, loadedConfig.siteConfig);
|
|
20
20
|
const result = await writeCloudflareBundle({
|
|
21
21
|
rootDir,
|
|
22
22
|
outDir: path.resolve(args.out ?? 'dist/cloudflare'),
|
|
23
23
|
siteConfig,
|
|
24
24
|
searchDir: args.search ? path.resolve(args.search) : undefined,
|
|
25
|
+
configModulePath: loadedConfig.configModulePath,
|
|
25
26
|
});
|
|
26
27
|
console.log(`cloudflare worker written to ${result.workerFile}`);
|
|
27
28
|
}
|
package/dist/cli/build-index.js
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { buildDirectoryIndexes } from '../index-builder.js';
|
|
3
|
+
import { loadUserSiteConfig } from '../core/site-config.js';
|
|
3
4
|
export async function runBuildIndexCommand(argv) {
|
|
4
5
|
const args = parseArgs(argv);
|
|
5
6
|
if (!args.root && !args.dir) {
|
|
6
|
-
console.error('Usage: mdorigin build index (--root <content-dir> | --dir <content-dir>)');
|
|
7
|
+
console.error('Usage: mdorigin build index (--root <content-dir> | --dir <content-dir>) [--config <config-file>]');
|
|
7
8
|
process.exitCode = 1;
|
|
8
9
|
return;
|
|
9
10
|
}
|
|
11
|
+
const rootDir = args.root ? path.resolve(args.root) : undefined;
|
|
12
|
+
const dir = args.dir ? path.resolve(args.dir) : undefined;
|
|
13
|
+
const loadedConfig = await loadUserSiteConfig({
|
|
14
|
+
cwd: process.cwd(),
|
|
15
|
+
rootDir: rootDir ?? dir,
|
|
16
|
+
configPath: args.config,
|
|
17
|
+
});
|
|
10
18
|
const result = await buildDirectoryIndexes({
|
|
11
|
-
rootDir
|
|
12
|
-
dir
|
|
19
|
+
rootDir,
|
|
20
|
+
dir,
|
|
21
|
+
plugins: loadedConfig.plugins,
|
|
13
22
|
});
|
|
14
23
|
console.log(`updated ${result.updatedFiles.length} index file(s)`);
|
|
15
24
|
if (result.skippedDirectories.length > 0) {
|
|
@@ -29,6 +38,11 @@ function parseArgs(argv) {
|
|
|
29
38
|
if (argument === '--dir' && nextValue) {
|
|
30
39
|
result.dir = nextValue;
|
|
31
40
|
index += 1;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (argument === '--config' && nextValue) {
|
|
44
|
+
result.config = nextValue;
|
|
45
|
+
index += 1;
|
|
32
46
|
}
|
|
33
47
|
}
|
|
34
48
|
return result;
|
package/dist/cli/build-search.js
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { buildSearchBundle } from '../search.js';
|
|
3
|
-
import { applySiteConfigFrontmatterDefaults,
|
|
3
|
+
import { applySiteConfigFrontmatterDefaults, loadUserSiteConfig, } from '../core/site-config.js';
|
|
4
4
|
import { createFileSystemContentStore } from '../adapters/node.js';
|
|
5
5
|
export async function runBuildSearchCommand(rawArgs) {
|
|
6
6
|
const args = parseArgs(rawArgs);
|
|
7
7
|
if (!args.root) {
|
|
8
|
-
throw new Error('Usage: mdorigin build search --root <content-dir> [--out ./dist/search] [--embedding-backend model2vec|hashing] [--model sentence-transformers/all-MiniLM-L6-v2] [--config
|
|
8
|
+
throw new Error('Usage: mdorigin build search --root <content-dir> [--out ./dist/search] [--embedding-backend model2vec|hashing] [--model sentence-transformers/all-MiniLM-L6-v2] [--config <config-file>]');
|
|
9
9
|
}
|
|
10
10
|
const rootDir = path.resolve(args.root);
|
|
11
11
|
const store = createFileSystemContentStore(rootDir);
|
|
12
|
-
const
|
|
12
|
+
const loadedConfig = await loadUserSiteConfig({
|
|
13
13
|
rootDir,
|
|
14
14
|
configPath: args.config,
|
|
15
|
-
})
|
|
15
|
+
});
|
|
16
|
+
const siteConfig = await applySiteConfigFrontmatterDefaults(store, loadedConfig.siteConfig);
|
|
16
17
|
const result = await buildSearchBundle({
|
|
17
18
|
rootDir,
|
|
18
19
|
outDir: path.resolve(args.out ?? 'dist/search'),
|
package/dist/cli/dev.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { createFileSystemContentStore, createNodeServer } from '../adapters/node.js';
|
|
3
|
-
import { applySiteConfigFrontmatterDefaults,
|
|
3
|
+
import { applySiteConfigFrontmatterDefaults, loadUserSiteConfig, } from '../core/site-config.js';
|
|
4
4
|
import { createSearchApiFromDirectory } from '../search.js';
|
|
5
5
|
export async function runDevCommand(argv) {
|
|
6
6
|
const args = parseArgs(argv);
|
|
7
7
|
if (!args.root) {
|
|
8
|
-
console.error('Usage: mdorigin dev --root <content-dir> [--port 3000] [--config
|
|
8
|
+
console.error('Usage: mdorigin dev --root <content-dir> [--port 3000] [--config <config-file>] [--search ./dist/search]');
|
|
9
9
|
process.exitCode = 1;
|
|
10
10
|
return;
|
|
11
11
|
}
|
|
12
12
|
const rootDir = path.resolve(args.root);
|
|
13
13
|
const port = args.port ?? 3000;
|
|
14
|
-
const
|
|
14
|
+
const loadedConfig = await loadUserSiteConfig({
|
|
15
15
|
cwd: process.cwd(),
|
|
16
16
|
rootDir,
|
|
17
17
|
configPath: args.config,
|
|
18
18
|
});
|
|
19
19
|
const store = createFileSystemContentStore(rootDir);
|
|
20
|
-
const siteConfig = await applySiteConfigFrontmatterDefaults(store,
|
|
20
|
+
const siteConfig = await applySiteConfigFrontmatterDefaults(store, loadedConfig.siteConfig);
|
|
21
21
|
const server = createNodeServer({
|
|
22
22
|
rootDir,
|
|
23
23
|
draftMode: 'include',
|
|
@@ -25,6 +25,7 @@ export async function runDevCommand(argv) {
|
|
|
25
25
|
searchApi: args.search
|
|
26
26
|
? await createSearchApiFromDirectory(path.resolve(args.search))
|
|
27
27
|
: undefined,
|
|
28
|
+
plugins: loadedConfig.plugins,
|
|
28
29
|
});
|
|
29
30
|
await new Promise((resolve, reject) => {
|
|
30
31
|
server.once('error', reject);
|
package/dist/cli/main.js
CHANGED
|
@@ -33,10 +33,10 @@ async function main() {
|
|
|
33
33
|
}
|
|
34
34
|
console.error([
|
|
35
35
|
'Usage:',
|
|
36
|
-
' mdorigin dev --root <content-dir> [--port 3000] [--config
|
|
37
|
-
' mdorigin build index (--root <content-dir> | --dir <content-dir>)',
|
|
38
|
-
' mdorigin build search --root <content-dir> [--out ./dist/search] [--embedding-backend model2vec|hashing] [--model sentence-transformers/all-MiniLM-L6-v2] [--config
|
|
39
|
-
' mdorigin build cloudflare --root <content-dir> [--out ./dist/cloudflare] [--config
|
|
36
|
+
' mdorigin dev --root <content-dir> [--port 3000] [--config <config-file>] [--search ./dist/search]',
|
|
37
|
+
' mdorigin build index (--root <content-dir> | --dir <content-dir>) [--config <config-file>]',
|
|
38
|
+
' mdorigin build search --root <content-dir> [--out ./dist/search] [--embedding-backend model2vec|hashing] [--model sentence-transformers/all-MiniLM-L6-v2] [--config <config-file>]',
|
|
39
|
+
' mdorigin build cloudflare --root <content-dir> [--out ./dist/cloudflare] [--config <config-file>] [--search ./dist/search]',
|
|
40
40
|
' mdorigin init cloudflare [--dir .] [--entry ./dist/cloudflare/worker.mjs] [--name <worker-name>] [--compatibility-date 2026-03-20] [--force]',
|
|
41
41
|
' mdorigin search --index <search-dir> [--top-k 10] <query>',
|
|
42
42
|
].join('\n'));
|
package/dist/cloudflare.d.ts
CHANGED
package/dist/cloudflare.js
CHANGED
|
@@ -49,12 +49,37 @@ export async function writeCloudflareBundle(options) {
|
|
|
49
49
|
});
|
|
50
50
|
const packageImport = options.packageImport ?? 'mdorigin/cloudflare-runtime';
|
|
51
51
|
const workerFile = path.join(outDir, 'worker.mjs');
|
|
52
|
+
const configImportPath = options.configModulePath
|
|
53
|
+
? toPosixPath(path.relative(outDir, options.configModulePath))
|
|
54
|
+
: null;
|
|
52
55
|
const workerSource = [
|
|
53
56
|
`import { createCloudflareWorker } from '${packageImport}';`,
|
|
57
|
+
configImportPath
|
|
58
|
+
? `import * as userConfigModule from '${configImportPath.startsWith('.') ? configImportPath : `./${configImportPath}`}';`
|
|
59
|
+
: '',
|
|
54
60
|
'',
|
|
55
61
|
`const manifest = ${JSON.stringify(manifest, null, 2)};`,
|
|
56
62
|
'',
|
|
57
|
-
|
|
63
|
+
configImportPath
|
|
64
|
+
? [
|
|
65
|
+
'function unwrapUserConfigModule(moduleValue) {',
|
|
66
|
+
' let current = moduleValue;',
|
|
67
|
+
" while (current && typeof current === 'object' && 'default' in current && current.default !== undefined) {",
|
|
68
|
+
' current = current.default;',
|
|
69
|
+
' }',
|
|
70
|
+
" if (current && typeof current === 'object' && 'config' in current && current.config !== undefined) {",
|
|
71
|
+
' return current.config;',
|
|
72
|
+
' }',
|
|
73
|
+
' return current;',
|
|
74
|
+
'}',
|
|
75
|
+
'',
|
|
76
|
+
'const userConfig = unwrapUserConfigModule(userConfigModule);',
|
|
77
|
+
'',
|
|
78
|
+
].join('\n')
|
|
79
|
+
: '',
|
|
80
|
+
configImportPath
|
|
81
|
+
? 'export default createCloudflareWorker(manifest, { plugins: Array.isArray(userConfig?.plugins) ? userConfig.plugins : [] });'
|
|
82
|
+
: 'export default createCloudflareWorker(manifest);',
|
|
58
83
|
'',
|
|
59
84
|
].join('\n');
|
|
60
85
|
await mkdir(outDir, { recursive: true });
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { ManagedIndexEntry } from './markdown.js';
|
|
2
|
+
import type { EditLinkConfig, ResolvedSiteConfig, SiteLogo, SiteNavItem, SiteSocialLink } from './site-config.js';
|
|
3
|
+
import type { TemplateName } from '../html/template-kind.js';
|
|
4
|
+
import type { BuiltInThemeName } from '../html/theme.js';
|
|
5
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
6
|
+
export interface IndexTransformContext {
|
|
7
|
+
mode: 'build' | 'render';
|
|
8
|
+
directoryPath?: string;
|
|
9
|
+
requestPath?: string;
|
|
10
|
+
sourcePath?: string;
|
|
11
|
+
siteConfig?: ResolvedSiteConfig;
|
|
12
|
+
}
|
|
13
|
+
export interface PageRenderModel {
|
|
14
|
+
kind: 'document' | 'catalog';
|
|
15
|
+
requestPath: string;
|
|
16
|
+
sourcePath: string;
|
|
17
|
+
siteTitle: string;
|
|
18
|
+
siteDescription?: string;
|
|
19
|
+
siteUrl?: string;
|
|
20
|
+
favicon?: string;
|
|
21
|
+
socialImage?: string;
|
|
22
|
+
logo?: SiteLogo;
|
|
23
|
+
title: string;
|
|
24
|
+
bodyHtml: string;
|
|
25
|
+
summary?: string;
|
|
26
|
+
date?: string;
|
|
27
|
+
showSummary: boolean;
|
|
28
|
+
showDate: boolean;
|
|
29
|
+
theme: BuiltInThemeName;
|
|
30
|
+
template: TemplateName;
|
|
31
|
+
topNav: SiteNavItem[];
|
|
32
|
+
footerNav: SiteNavItem[];
|
|
33
|
+
footerText?: string;
|
|
34
|
+
socialLinks: SiteSocialLink[];
|
|
35
|
+
editLink?: EditLinkConfig;
|
|
36
|
+
editLinkHref?: string;
|
|
37
|
+
stylesheetContent?: string;
|
|
38
|
+
canonicalPath?: string;
|
|
39
|
+
alternateMarkdownPath?: string;
|
|
40
|
+
catalogEntries: ManagedIndexEntry[];
|
|
41
|
+
catalogRequestPath: string;
|
|
42
|
+
catalogInitialPostCount: number;
|
|
43
|
+
catalogLoadMoreStep: number;
|
|
44
|
+
searchEnabled: boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface RenderHookContext {
|
|
47
|
+
page: PageRenderModel;
|
|
48
|
+
siteConfig: ResolvedSiteConfig;
|
|
49
|
+
}
|
|
50
|
+
export interface MdoPlugin {
|
|
51
|
+
name?: string;
|
|
52
|
+
transformIndex?(entries: ManagedIndexEntry[], context: IndexTransformContext): MaybePromise<ManagedIndexEntry[]>;
|
|
53
|
+
renderHeader?(context: RenderHookContext): MaybePromise<string | undefined | null>;
|
|
54
|
+
renderFooter?(context: RenderHookContext): MaybePromise<string | undefined | null>;
|
|
55
|
+
renderPage?(page: PageRenderModel, context: RenderHookContext, next: (page: PageRenderModel) => MaybePromise<string>): MaybePromise<string | undefined | null>;
|
|
56
|
+
transformHtml?(html: string, context: RenderHookContext): MaybePromise<string>;
|
|
57
|
+
}
|
|
58
|
+
export declare function applyIndexTransforms(entries: ManagedIndexEntry[], plugins: MdoPlugin[], context: IndexTransformContext): Promise<ManagedIndexEntry[]>;
|
|
59
|
+
export declare function renderHeaderOverride(plugins: MdoPlugin[], context: RenderHookContext): Promise<string | undefined>;
|
|
60
|
+
export declare function renderFooterOverride(plugins: MdoPlugin[], context: RenderHookContext): Promise<string | undefined>;
|
|
61
|
+
export declare function renderPageWithPlugins(page: PageRenderModel, plugins: MdoPlugin[], context: RenderHookContext, renderDefault: (page: PageRenderModel) => MaybePromise<string>): Promise<{
|
|
62
|
+
html: string;
|
|
63
|
+
page: PageRenderModel;
|
|
64
|
+
}>;
|
|
65
|
+
export declare function transformHtmlWithPlugins(html: string, plugins: MdoPlugin[], context: RenderHookContext): Promise<string>;
|
|
66
|
+
export {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export async function applyIndexTransforms(entries, plugins, context) {
|
|
2
|
+
let current = [...entries];
|
|
3
|
+
for (const plugin of plugins) {
|
|
4
|
+
if (!plugin.transformIndex) {
|
|
5
|
+
continue;
|
|
6
|
+
}
|
|
7
|
+
current = [...(await plugin.transformIndex(current, context))];
|
|
8
|
+
}
|
|
9
|
+
return current;
|
|
10
|
+
}
|
|
11
|
+
export async function renderHeaderOverride(plugins, context) {
|
|
12
|
+
let rendered;
|
|
13
|
+
for (const plugin of plugins) {
|
|
14
|
+
if (!plugin.renderHeader) {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const value = await plugin.renderHeader(context);
|
|
18
|
+
if (typeof value === 'string') {
|
|
19
|
+
rendered = value;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return rendered;
|
|
23
|
+
}
|
|
24
|
+
export async function renderFooterOverride(plugins, context) {
|
|
25
|
+
let rendered;
|
|
26
|
+
for (const plugin of plugins) {
|
|
27
|
+
if (!plugin.renderFooter) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const value = await plugin.renderFooter(context);
|
|
31
|
+
if (typeof value === 'string') {
|
|
32
|
+
rendered = value;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return rendered;
|
|
36
|
+
}
|
|
37
|
+
export async function renderPageWithPlugins(page, plugins, context, renderDefault) {
|
|
38
|
+
const renderers = plugins
|
|
39
|
+
.map((plugin) => plugin.renderPage)
|
|
40
|
+
.filter((renderPage) => typeof renderPage === 'function');
|
|
41
|
+
let finalPage = page;
|
|
42
|
+
const dispatch = async (index, currentPage) => {
|
|
43
|
+
const renderPage = renderers[index];
|
|
44
|
+
if (!renderPage) {
|
|
45
|
+
finalPage = currentPage;
|
|
46
|
+
return renderDefault(currentPage);
|
|
47
|
+
}
|
|
48
|
+
const currentContext = {
|
|
49
|
+
...context,
|
|
50
|
+
page: currentPage,
|
|
51
|
+
};
|
|
52
|
+
let nextInvoked = false;
|
|
53
|
+
const next = async (nextPage) => {
|
|
54
|
+
nextInvoked = true;
|
|
55
|
+
finalPage = nextPage;
|
|
56
|
+
return dispatch(index + 1, nextPage);
|
|
57
|
+
};
|
|
58
|
+
const rendered = await renderPage(currentPage, currentContext, next);
|
|
59
|
+
if (typeof rendered === 'string') {
|
|
60
|
+
if (!nextInvoked) {
|
|
61
|
+
finalPage = currentPage;
|
|
62
|
+
}
|
|
63
|
+
return rendered;
|
|
64
|
+
}
|
|
65
|
+
return next(currentPage);
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
html: await dispatch(0, page),
|
|
69
|
+
page: finalPage,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export async function transformHtmlWithPlugins(html, plugins, context) {
|
|
73
|
+
let current = html;
|
|
74
|
+
for (const plugin of plugins) {
|
|
75
|
+
if (!plugin.transformHtml) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const result = await plugin.transformHtml(current, context);
|
|
79
|
+
if (typeof result !== 'string') {
|
|
80
|
+
const pluginName = plugin.name ?? 'unknown plugin';
|
|
81
|
+
throw new Error(`transformHtmlWithPlugins expected plugin "${pluginName}" to return a string, but got ${typeof result}`);
|
|
82
|
+
}
|
|
83
|
+
current = result;
|
|
84
|
+
}
|
|
85
|
+
return current;
|
|
86
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ContentStore } from './content-store.js';
|
|
2
|
+
import type { MdoPlugin } from './extensions.js';
|
|
2
3
|
import type { ResolvedSiteConfig } from './site-config.js';
|
|
3
4
|
import type { SearchApi } from '../search.js';
|
|
4
5
|
export interface HandleSiteRequestOptions {
|
|
@@ -8,6 +9,7 @@ export interface HandleSiteRequestOptions {
|
|
|
8
9
|
searchParams?: URLSearchParams;
|
|
9
10
|
requestUrl?: string;
|
|
10
11
|
searchApi?: SearchApi;
|
|
12
|
+
plugins?: MdoPlugin[];
|
|
11
13
|
}
|
|
12
14
|
export interface SiteResponse {
|
|
13
15
|
status: number;
|
|
@@ -2,10 +2,12 @@ import path from 'node:path';
|
|
|
2
2
|
import { inferDirectoryContentType } from './content-type.js';
|
|
3
3
|
import { getDirectoryIndexCandidates } from './directory-index.js';
|
|
4
4
|
import { extractManagedIndexEntries, getDocumentSummary, getDocumentTitle as getParsedDocumentTitle, parseMarkdownDocument, stripManagedIndexBlock, stripManagedIndexLinks, } from './markdown.js';
|
|
5
|
+
import { applyIndexTransforms, renderFooterOverride, renderHeaderOverride, renderPageWithPlugins, transformHtmlWithPlugins, } from './extensions.js';
|
|
5
6
|
import { handleApiRoute } from './api.js';
|
|
6
7
|
import { normalizeRequestPath, resolveRequest } from './router.js';
|
|
7
8
|
import { escapeHtml, renderCatalogArticleItems, renderDocument, } from '../html/template.js';
|
|
8
9
|
export async function handleSiteRequest(store, pathname, options) {
|
|
10
|
+
const plugins = options.plugins ?? [];
|
|
9
11
|
const searchEnabled = options.searchApi !== undefined;
|
|
10
12
|
const apiRoute = await handleApiRoute(pathname, options.searchParams, {
|
|
11
13
|
searchApi: options.searchApi,
|
|
@@ -81,7 +83,12 @@ export async function handleSiteRequest(store, pathname, options) {
|
|
|
81
83
|
? stripManagedIndexLinks(entry.text, new Set(navigation.items.map((item) => item.href)))
|
|
82
84
|
: entry.text;
|
|
83
85
|
const catalogEntries = options.siteConfig.template === 'catalog'
|
|
84
|
-
? extractManagedIndexEntries(renderedBody)
|
|
86
|
+
? await applyIndexTransforms(extractManagedIndexEntries(renderedBody), plugins, {
|
|
87
|
+
mode: 'render',
|
|
88
|
+
requestPath: resolved.requestPath,
|
|
89
|
+
sourcePath: resolved.sourcePath,
|
|
90
|
+
siteConfig: options.siteConfig,
|
|
91
|
+
})
|
|
85
92
|
: [];
|
|
86
93
|
if (catalogFragmentRequest !== null &&
|
|
87
94
|
options.siteConfig.template === 'catalog') {
|
|
@@ -93,42 +100,18 @@ export async function handleSiteRequest(store, pathname, options) {
|
|
|
93
100
|
const renderedParsed = documentBody === entry.text
|
|
94
101
|
? parsed
|
|
95
102
|
: await parseMarkdownDocument(resolved.sourcePath, documentBody);
|
|
96
|
-
return {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
body: renderedParsed.html,
|
|
109
|
-
summary: options.siteConfig.showSummary === false
|
|
110
|
-
? undefined
|
|
111
|
-
: getDocumentSummary(parsed.meta, parsed.body),
|
|
112
|
-
date: options.siteConfig.showDate === false ? undefined : parsed.meta.date,
|
|
113
|
-
showSummary: options.siteConfig.showSummary,
|
|
114
|
-
showDate: options.siteConfig.showDate,
|
|
115
|
-
theme: options.siteConfig.theme,
|
|
116
|
-
template: options.siteConfig.template,
|
|
117
|
-
topNav: navigation.items,
|
|
118
|
-
footerNav: options.siteConfig.footerNav,
|
|
119
|
-
footerText: options.siteConfig.footerText,
|
|
120
|
-
socialLinks: options.siteConfig.socialLinks,
|
|
121
|
-
editLinkHref: getEditLinkHref(options.siteConfig, resolved.sourcePath),
|
|
122
|
-
stylesheetContent: options.siteConfig.stylesheetContent,
|
|
123
|
-
canonicalPath: getCanonicalHtmlPathForContentPath(resolved.sourcePath),
|
|
124
|
-
alternateMarkdownPath: getMarkdownRequestPathForContentPath(resolved.sourcePath),
|
|
125
|
-
catalogEntries,
|
|
126
|
-
catalogRequestPath: resolved.requestPath,
|
|
127
|
-
catalogInitialPostCount: options.siteConfig.catalogInitialPostCount,
|
|
128
|
-
catalogLoadMoreStep: options.siteConfig.catalogLoadMoreStep,
|
|
129
|
-
searchEnabled,
|
|
130
|
-
}),
|
|
131
|
-
};
|
|
103
|
+
return renderStructuredPage({
|
|
104
|
+
requestPath: resolved.requestPath,
|
|
105
|
+
sourcePath: resolved.sourcePath,
|
|
106
|
+
parsed,
|
|
107
|
+
renderedParsed,
|
|
108
|
+
siteConfig: options.siteConfig,
|
|
109
|
+
topNav: navigation.items,
|
|
110
|
+
catalogEntries,
|
|
111
|
+
searchEnabled,
|
|
112
|
+
plugins,
|
|
113
|
+
varyOnAccept: shouldVaryOnAccept(resolved),
|
|
114
|
+
});
|
|
132
115
|
}
|
|
133
116
|
function getCatalogFragmentRequest(searchParams) {
|
|
134
117
|
if (searchParams?.get('catalog-format') !== 'posts') {
|
|
@@ -155,6 +138,109 @@ function normalizePositiveInteger(value) {
|
|
|
155
138
|
const parsed = Number.parseInt(value, 10);
|
|
156
139
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
|
|
157
140
|
}
|
|
141
|
+
function buildPageRenderModel(options) {
|
|
142
|
+
return {
|
|
143
|
+
kind: options.siteConfig.template === 'catalog' ? 'catalog' : 'document',
|
|
144
|
+
requestPath: options.resolvedRequestPath,
|
|
145
|
+
sourcePath: options.sourcePath,
|
|
146
|
+
siteTitle: options.siteConfig.siteTitle,
|
|
147
|
+
siteDescription: options.siteConfig.siteDescription,
|
|
148
|
+
siteUrl: options.siteConfig.siteUrl,
|
|
149
|
+
favicon: options.siteConfig.favicon,
|
|
150
|
+
socialImage: options.siteConfig.socialImage,
|
|
151
|
+
logo: options.siteConfig.logo,
|
|
152
|
+
title: getDocumentTitle(options.parsed),
|
|
153
|
+
bodyHtml: options.renderedBodyHtml,
|
|
154
|
+
summary: options.siteConfig.showSummary === false
|
|
155
|
+
? undefined
|
|
156
|
+
: getDocumentSummary(options.parsed.meta, options.parsed.body),
|
|
157
|
+
date: options.siteConfig.showDate === false ? undefined : options.parsed.meta.date,
|
|
158
|
+
showSummary: options.siteConfig.showSummary,
|
|
159
|
+
showDate: options.siteConfig.showDate,
|
|
160
|
+
theme: options.siteConfig.theme,
|
|
161
|
+
template: options.siteConfig.template,
|
|
162
|
+
topNav: options.topNav,
|
|
163
|
+
footerNav: options.siteConfig.footerNav,
|
|
164
|
+
footerText: options.siteConfig.footerText,
|
|
165
|
+
socialLinks: options.siteConfig.socialLinks,
|
|
166
|
+
editLink: options.siteConfig.editLink,
|
|
167
|
+
editLinkHref: getEditLinkHref(options.siteConfig, options.sourcePath),
|
|
168
|
+
stylesheetContent: options.siteConfig.stylesheetContent,
|
|
169
|
+
canonicalPath: getCanonicalHtmlPathForContentPath(options.sourcePath),
|
|
170
|
+
alternateMarkdownPath: getMarkdownRequestPathForContentPath(options.sourcePath),
|
|
171
|
+
catalogEntries: options.catalogEntries,
|
|
172
|
+
catalogRequestPath: options.resolvedRequestPath,
|
|
173
|
+
catalogInitialPostCount: options.siteConfig.catalogInitialPostCount,
|
|
174
|
+
catalogLoadMoreStep: options.siteConfig.catalogLoadMoreStep,
|
|
175
|
+
searchEnabled: options.searchEnabled,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
async function renderStructuredPage(options) {
|
|
179
|
+
const page = buildPageRenderModel({
|
|
180
|
+
resolvedRequestPath: options.requestPath,
|
|
181
|
+
sourcePath: options.sourcePath,
|
|
182
|
+
renderedBodyHtml: options.renderedParsed.html,
|
|
183
|
+
parsed: options.parsed,
|
|
184
|
+
siteConfig: options.siteConfig,
|
|
185
|
+
topNav: options.topNav,
|
|
186
|
+
catalogEntries: options.catalogEntries,
|
|
187
|
+
searchEnabled: options.searchEnabled,
|
|
188
|
+
});
|
|
189
|
+
const renderContext = {
|
|
190
|
+
page,
|
|
191
|
+
siteConfig: options.siteConfig,
|
|
192
|
+
};
|
|
193
|
+
const renderedPage = await renderPageWithPlugins(page, options.plugins, renderContext, async (currentPage) => {
|
|
194
|
+
const currentContext = {
|
|
195
|
+
page: currentPage,
|
|
196
|
+
siteConfig: options.siteConfig,
|
|
197
|
+
};
|
|
198
|
+
const headerHtml = await renderHeaderOverride(options.plugins, currentContext);
|
|
199
|
+
const footerHtml = await renderFooterOverride(options.plugins, currentContext);
|
|
200
|
+
return (renderDocument({
|
|
201
|
+
siteTitle: currentPage.siteTitle,
|
|
202
|
+
siteDescription: currentPage.siteDescription,
|
|
203
|
+
siteUrl: currentPage.siteUrl,
|
|
204
|
+
favicon: currentPage.favicon,
|
|
205
|
+
socialImage: currentPage.socialImage,
|
|
206
|
+
logo: currentPage.logo,
|
|
207
|
+
title: currentPage.title,
|
|
208
|
+
body: currentPage.bodyHtml,
|
|
209
|
+
summary: currentPage.summary,
|
|
210
|
+
date: currentPage.date,
|
|
211
|
+
showSummary: currentPage.showSummary,
|
|
212
|
+
showDate: currentPage.showDate,
|
|
213
|
+
theme: currentPage.theme,
|
|
214
|
+
template: currentPage.template,
|
|
215
|
+
topNav: currentPage.topNav,
|
|
216
|
+
footerNav: currentPage.footerNav,
|
|
217
|
+
footerText: currentPage.footerText,
|
|
218
|
+
socialLinks: currentPage.socialLinks,
|
|
219
|
+
editLinkHref: currentPage.editLinkHref,
|
|
220
|
+
stylesheetContent: currentPage.stylesheetContent,
|
|
221
|
+
canonicalPath: currentPage.canonicalPath,
|
|
222
|
+
alternateMarkdownPath: currentPage.alternateMarkdownPath,
|
|
223
|
+
catalogEntries: currentPage.catalogEntries,
|
|
224
|
+
catalogRequestPath: currentPage.catalogRequestPath,
|
|
225
|
+
catalogInitialPostCount: currentPage.catalogInitialPostCount,
|
|
226
|
+
catalogLoadMoreStep: currentPage.catalogLoadMoreStep,
|
|
227
|
+
searchEnabled: currentPage.searchEnabled,
|
|
228
|
+
headerHtml,
|
|
229
|
+
footerHtml,
|
|
230
|
+
}));
|
|
231
|
+
});
|
|
232
|
+
const finalHtml = await transformHtmlWithPlugins(renderedPage.html, options.plugins, {
|
|
233
|
+
page: renderedPage.page,
|
|
234
|
+
siteConfig: options.siteConfig,
|
|
235
|
+
});
|
|
236
|
+
return {
|
|
237
|
+
status: 200,
|
|
238
|
+
headers: withVaryAcceptIfNeeded({
|
|
239
|
+
'content-type': 'text/html; charset=utf-8',
|
|
240
|
+
}, options.varyOnAccept ?? false),
|
|
241
|
+
body: finalHtml,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
158
244
|
function renderCatalogPostsFragment(entries, request) {
|
|
159
245
|
const articles = entries.filter((entry) => entry.kind === 'article');
|
|
160
246
|
const visibleArticles = articles.slice(request.offset, request.offset + request.limit);
|
|
@@ -377,6 +463,7 @@ function getDirectoryIndexContentPathForRequestPath(requestPath) {
|
|
|
377
463
|
: `${requestPath.slice(1).replace(/\/$/, '')}/index.md`;
|
|
378
464
|
}
|
|
379
465
|
async function tryRenderAlternateDirectoryIndex(store, requestPath, options) {
|
|
466
|
+
const plugins = options.plugins ?? [];
|
|
380
467
|
const directoryPath = requestPath === '/' ? '' : requestPath.slice(1).replace(/\/$/, '');
|
|
381
468
|
for (const candidatePath of getDirectoryIndexCandidates(directoryPath)) {
|
|
382
469
|
if (candidatePath === (directoryPath === '' ? 'index.md' : `${directoryPath}/index.md`)) {
|
|
@@ -397,7 +484,12 @@ async function tryRenderAlternateDirectoryIndex(store, requestPath, options) {
|
|
|
397
484
|
? stripManagedIndexLinks(entry.text, new Set(navigation.items.map((item) => item.href)))
|
|
398
485
|
: entry.text;
|
|
399
486
|
const catalogEntries = options.siteConfig.template === 'catalog'
|
|
400
|
-
? extractManagedIndexEntries(renderedBody)
|
|
487
|
+
? await applyIndexTransforms(extractManagedIndexEntries(renderedBody), plugins, {
|
|
488
|
+
mode: 'render',
|
|
489
|
+
requestPath,
|
|
490
|
+
sourcePath: candidatePath,
|
|
491
|
+
siteConfig: options.siteConfig,
|
|
492
|
+
})
|
|
401
493
|
: [];
|
|
402
494
|
const catalogFragmentRequest = getCatalogFragmentRequest(options.searchParams);
|
|
403
495
|
if (catalogFragmentRequest !== null &&
|
|
@@ -410,42 +502,17 @@ async function tryRenderAlternateDirectoryIndex(store, requestPath, options) {
|
|
|
410
502
|
const renderedParsed = documentBody === entry.text
|
|
411
503
|
? parsed
|
|
412
504
|
: await parseMarkdownDocument(candidatePath, documentBody);
|
|
413
|
-
return {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
title: getDocumentTitle(parsed),
|
|
425
|
-
body: renderedParsed.html,
|
|
426
|
-
summary: options.siteConfig.showSummary === false
|
|
427
|
-
? undefined
|
|
428
|
-
: getDocumentSummary(parsed.meta, parsed.body),
|
|
429
|
-
date: options.siteConfig.showDate === false ? undefined : parsed.meta.date,
|
|
430
|
-
showSummary: options.siteConfig.showSummary,
|
|
431
|
-
showDate: options.siteConfig.showDate,
|
|
432
|
-
theme: options.siteConfig.theme,
|
|
433
|
-
template: options.siteConfig.template,
|
|
434
|
-
topNav: navigation.items,
|
|
435
|
-
footerNav: options.siteConfig.footerNav,
|
|
436
|
-
footerText: options.siteConfig.footerText,
|
|
437
|
-
socialLinks: options.siteConfig.socialLinks,
|
|
438
|
-
editLinkHref: getEditLinkHref(options.siteConfig, candidatePath),
|
|
439
|
-
stylesheetContent: options.siteConfig.stylesheetContent,
|
|
440
|
-
canonicalPath: requestPath,
|
|
441
|
-
alternateMarkdownPath: getMarkdownRequestPathForContentPath(candidatePath),
|
|
442
|
-
catalogEntries,
|
|
443
|
-
catalogRequestPath: requestPath,
|
|
444
|
-
catalogInitialPostCount: options.siteConfig.catalogInitialPostCount,
|
|
445
|
-
catalogLoadMoreStep: options.siteConfig.catalogLoadMoreStep,
|
|
446
|
-
searchEnabled: options.searchApi !== undefined,
|
|
447
|
-
}),
|
|
448
|
-
};
|
|
505
|
+
return renderStructuredPage({
|
|
506
|
+
requestPath,
|
|
507
|
+
sourcePath: candidatePath,
|
|
508
|
+
parsed,
|
|
509
|
+
renderedParsed,
|
|
510
|
+
siteConfig: options.siteConfig,
|
|
511
|
+
topNav: navigation.items,
|
|
512
|
+
catalogEntries,
|
|
513
|
+
searchEnabled: options.searchApi !== undefined,
|
|
514
|
+
plugins,
|
|
515
|
+
});
|
|
449
516
|
}
|
|
450
517
|
return null;
|
|
451
518
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ContentStore } from './content-store.js';
|
|
2
|
+
import type { MdoPlugin } from './extensions.js';
|
|
2
3
|
import type { TemplateName } from '../html/template-kind.js';
|
|
3
4
|
import type { BuiltInThemeName } from '../html/theme.js';
|
|
4
5
|
export interface SiteNavItem {
|
|
@@ -23,6 +24,7 @@ export interface SiteConfig {
|
|
|
23
24
|
siteDescription?: string;
|
|
24
25
|
siteUrl?: string;
|
|
25
26
|
favicon?: string;
|
|
27
|
+
socialImage?: string;
|
|
26
28
|
logo?: SiteLogo;
|
|
27
29
|
showDate?: boolean;
|
|
28
30
|
showSummary?: boolean;
|
|
@@ -38,11 +40,15 @@ export interface SiteConfig {
|
|
|
38
40
|
catalogInitialPostCount?: number;
|
|
39
41
|
catalogLoadMoreStep?: number;
|
|
40
42
|
}
|
|
43
|
+
export interface UserSiteConfig extends SiteConfig {
|
|
44
|
+
plugins?: MdoPlugin[];
|
|
45
|
+
}
|
|
41
46
|
export interface ResolvedSiteConfig {
|
|
42
47
|
siteTitle: string;
|
|
43
48
|
siteDescription?: string;
|
|
44
49
|
siteUrl?: string;
|
|
45
50
|
favicon?: string;
|
|
51
|
+
socialImage?: string;
|
|
46
52
|
logo?: SiteLogo;
|
|
47
53
|
showDate: boolean;
|
|
48
54
|
showSummary: boolean;
|
|
@@ -65,5 +71,13 @@ export interface LoadSiteConfigOptions {
|
|
|
65
71
|
rootDir?: string;
|
|
66
72
|
configPath?: string;
|
|
67
73
|
}
|
|
74
|
+
export interface LoadedSiteConfig {
|
|
75
|
+
siteConfig: ResolvedSiteConfig;
|
|
76
|
+
plugins: MdoPlugin[];
|
|
77
|
+
configFilePath: string;
|
|
78
|
+
configModulePath?: string;
|
|
79
|
+
}
|
|
68
80
|
export declare function loadSiteConfig(options?: LoadSiteConfigOptions): Promise<ResolvedSiteConfig>;
|
|
81
|
+
export declare function loadUserSiteConfig(options?: LoadSiteConfigOptions): Promise<LoadedSiteConfig>;
|
|
69
82
|
export declare function applySiteConfigFrontmatterDefaults(store: ContentStore, siteConfig: ResolvedSiteConfig): Promise<ResolvedSiteConfig>;
|
|
83
|
+
export declare function defineConfig(config: UserSiteConfig): UserSiteConfig;
|
package/dist/core/site-config.js
CHANGED
|
@@ -1,30 +1,26 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises';
|
|
1
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { tsImport } from 'tsx/esm/api';
|
|
3
5
|
import { getDirectoryIndexCandidates } from './directory-index.js';
|
|
4
6
|
import { parseMarkdownDocument } from './markdown.js';
|
|
5
7
|
export async function loadSiteConfig(options = {}) {
|
|
8
|
+
return (await loadUserSiteConfig(options)).siteConfig;
|
|
9
|
+
}
|
|
10
|
+
export async function loadUserSiteConfig(options = {}) {
|
|
6
11
|
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
7
12
|
const rootDir = options.rootDir ? path.resolve(options.rootDir) : null;
|
|
8
13
|
const configFilePath = options.configPath
|
|
9
14
|
? path.resolve(cwd, options.configPath)
|
|
10
15
|
: await resolveDefaultConfigPath(cwd, rootDir);
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
const configSource = await readFile(configFilePath, 'utf8');
|
|
14
|
-
parsedConfig = JSON.parse(configSource);
|
|
15
|
-
}
|
|
16
|
-
catch (error) {
|
|
17
|
-
if (!isNodeNotFound(error)) {
|
|
18
|
-
throw error;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
16
|
+
const parsedConfig = await loadConfigSource(configFilePath);
|
|
21
17
|
const stylesheetPath = parsedConfig.stylesheet
|
|
22
18
|
? path.resolve(path.dirname(configFilePath), parsedConfig.stylesheet)
|
|
23
19
|
: null;
|
|
24
20
|
const stylesheetContent = stylesheetPath
|
|
25
21
|
? await readFile(stylesheetPath, 'utf8')
|
|
26
22
|
: undefined;
|
|
27
|
-
|
|
23
|
+
const siteConfig = {
|
|
28
24
|
siteTitle: typeof parsedConfig.siteTitle === 'string' && parsedConfig.siteTitle !== ''
|
|
29
25
|
? parsedConfig.siteTitle
|
|
30
26
|
: 'mdorigin',
|
|
@@ -34,6 +30,7 @@ export async function loadSiteConfig(options = {}) {
|
|
|
34
30
|
: undefined,
|
|
35
31
|
siteUrl: normalizeSiteUrl(parsedConfig.siteUrl),
|
|
36
32
|
favicon: normalizeSiteHref(parsedConfig.favicon),
|
|
33
|
+
socialImage: normalizeSiteHref(parsedConfig.socialImage),
|
|
37
34
|
logo: normalizeLogo(parsedConfig.logo),
|
|
38
35
|
showDate: parsedConfig.showDate ?? true,
|
|
39
36
|
showSummary: parsedConfig.showSummary ?? true,
|
|
@@ -56,6 +53,12 @@ export async function loadSiteConfig(options = {}) {
|
|
|
56
53
|
siteDescriptionConfigured: typeof parsedConfig.siteDescription === 'string' &&
|
|
57
54
|
parsedConfig.siteDescription !== '',
|
|
58
55
|
};
|
|
56
|
+
return {
|
|
57
|
+
siteConfig,
|
|
58
|
+
plugins: Array.isArray(parsedConfig.plugins) ? parsedConfig.plugins : [],
|
|
59
|
+
configFilePath,
|
|
60
|
+
configModulePath: isCodeConfigPath(configFilePath) ? configFilePath : undefined,
|
|
61
|
+
};
|
|
59
62
|
}
|
|
60
63
|
export async function applySiteConfigFrontmatterDefaults(store, siteConfig) {
|
|
61
64
|
if (siteConfig.siteTitleConfigured && siteConfig.siteDescriptionConfigured) {
|
|
@@ -84,11 +87,11 @@ export async function applySiteConfigFrontmatterDefaults(store, siteConfig) {
|
|
|
84
87
|
return siteConfig;
|
|
85
88
|
}
|
|
86
89
|
async function resolveDefaultConfigPath(cwd, rootDir) {
|
|
87
|
-
const rootConfigPath = rootDir ?
|
|
88
|
-
if (rootConfigPath
|
|
90
|
+
const rootConfigPath = rootDir ? await findConfigPath(rootDir) : null;
|
|
91
|
+
if (rootConfigPath) {
|
|
89
92
|
return rootConfigPath;
|
|
90
93
|
}
|
|
91
|
-
return path.join(cwd, 'mdorigin.config.json');
|
|
94
|
+
return (await findConfigPath(cwd)) ?? path.join(cwd, 'mdorigin.config.json');
|
|
92
95
|
}
|
|
93
96
|
function isBuiltInThemeName(value) {
|
|
94
97
|
return value === 'paper' || value === 'atlas' || value === 'gazette';
|
|
@@ -104,7 +107,7 @@ function isNodeNotFound(error) {
|
|
|
104
107
|
}
|
|
105
108
|
async function pathExists(filePath) {
|
|
106
109
|
try {
|
|
107
|
-
await
|
|
110
|
+
await stat(filePath);
|
|
108
111
|
return true;
|
|
109
112
|
}
|
|
110
113
|
catch (error) {
|
|
@@ -114,6 +117,61 @@ async function pathExists(filePath) {
|
|
|
114
117
|
throw error;
|
|
115
118
|
}
|
|
116
119
|
}
|
|
120
|
+
async function findConfigPath(directory) {
|
|
121
|
+
for (const candidate of getConfigCandidates(directory)) {
|
|
122
|
+
if (await pathExists(candidate)) {
|
|
123
|
+
return candidate;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
function getConfigCandidates(directory) {
|
|
129
|
+
return [
|
|
130
|
+
path.join(directory, 'mdorigin.config.ts'),
|
|
131
|
+
path.join(directory, 'mdorigin.config.mjs'),
|
|
132
|
+
path.join(directory, 'mdorigin.config.js'),
|
|
133
|
+
path.join(directory, 'mdorigin.config.json'),
|
|
134
|
+
];
|
|
135
|
+
}
|
|
136
|
+
async function loadConfigSource(configFilePath) {
|
|
137
|
+
if (!(await pathExists(configFilePath))) {
|
|
138
|
+
return {};
|
|
139
|
+
}
|
|
140
|
+
if (configFilePath.endsWith('.json')) {
|
|
141
|
+
const configSource = await readFile(configFilePath, 'utf8');
|
|
142
|
+
return JSON.parse(configSource);
|
|
143
|
+
}
|
|
144
|
+
const imported = configFilePath.endsWith('.ts')
|
|
145
|
+
? await tsImport(configFilePath, import.meta.url)
|
|
146
|
+
: await import(pathToFileURL(configFilePath).href);
|
|
147
|
+
const config = unwrapConfigModule(imported);
|
|
148
|
+
if (typeof config !== 'object' || config === null) {
|
|
149
|
+
throw new Error(`${configFilePath} must export a config object`);
|
|
150
|
+
}
|
|
151
|
+
return config;
|
|
152
|
+
}
|
|
153
|
+
function isCodeConfigPath(filePath) {
|
|
154
|
+
return /\.(mjs|js|ts)$/.test(filePath);
|
|
155
|
+
}
|
|
156
|
+
export function defineConfig(config) {
|
|
157
|
+
return config;
|
|
158
|
+
}
|
|
159
|
+
function unwrapConfigModule(moduleValue) {
|
|
160
|
+
let current = moduleValue;
|
|
161
|
+
while (typeof current === 'object' &&
|
|
162
|
+
current !== null &&
|
|
163
|
+
'default' in current &&
|
|
164
|
+
current.default !== undefined) {
|
|
165
|
+
current = current.default;
|
|
166
|
+
}
|
|
167
|
+
if (typeof current === 'object' &&
|
|
168
|
+
current !== null &&
|
|
169
|
+
'config' in current &&
|
|
170
|
+
current.config !== undefined) {
|
|
171
|
+
return current.config;
|
|
172
|
+
}
|
|
173
|
+
return current;
|
|
174
|
+
}
|
|
117
175
|
function normalizeTopNav(value) {
|
|
118
176
|
if (!Array.isArray(value)) {
|
|
119
177
|
return [];
|
package/dist/html/template.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export interface RenderDocumentOptions {
|
|
|
7
7
|
siteDescription?: string;
|
|
8
8
|
siteUrl?: string;
|
|
9
9
|
favicon?: string;
|
|
10
|
+
socialImage?: string;
|
|
10
11
|
logo?: SiteLogo;
|
|
11
12
|
title: string;
|
|
12
13
|
body: string;
|
|
@@ -29,6 +30,8 @@ export interface RenderDocumentOptions {
|
|
|
29
30
|
catalogInitialPostCount?: number;
|
|
30
31
|
catalogLoadMoreStep?: number;
|
|
31
32
|
searchEnabled?: boolean;
|
|
33
|
+
headerHtml?: string;
|
|
34
|
+
footerHtml?: string;
|
|
32
35
|
}
|
|
33
36
|
export declare function renderDocument(options: RenderDocumentOptions): string;
|
|
34
37
|
export declare function escapeHtml(value: string): string;
|
package/dist/html/template.js
CHANGED
|
@@ -14,6 +14,14 @@ export function renderDocument(options) {
|
|
|
14
14
|
const faviconMeta = options.favicon
|
|
15
15
|
? `<link rel="icon" href="${escapeHtml(options.favicon)}">`
|
|
16
16
|
: '';
|
|
17
|
+
const absoluteSocialImageUrl = getAbsoluteSiteAssetUrl(options.siteUrl, options.socialImage);
|
|
18
|
+
const socialImageMeta = absoluteSocialImageUrl
|
|
19
|
+
? [
|
|
20
|
+
`<meta property="og:image" content="${escapeHtml(absoluteSocialImageUrl)}">`,
|
|
21
|
+
'<meta name="twitter:card" content="summary_large_image">',
|
|
22
|
+
`<meta name="twitter:image" content="${escapeHtml(absoluteSocialImageUrl)}">`,
|
|
23
|
+
].join('')
|
|
24
|
+
: '';
|
|
17
25
|
const alternateMarkdownMeta = options.alternateMarkdownPath
|
|
18
26
|
? `<link rel="alternate" type="text/markdown" href="${escapeHtml(options.alternateMarkdownPath)}">`
|
|
19
27
|
: '';
|
|
@@ -78,6 +86,9 @@ export function renderDocument(options) {
|
|
|
78
86
|
})
|
|
79
87
|
: options.body;
|
|
80
88
|
const searchScript = options.searchEnabled ? renderSearchScript() : '';
|
|
89
|
+
const headerBlock = options.headerHtml ??
|
|
90
|
+
`<header class="site-header"><div class="site-header__inner"><div class="site-header__brand"><p class="site-header__title"><a href="${brandHref}">${logoBlock}<span>${siteTitle}</span></a></p>${siteDescriptionBlock}</div><div class="site-header__actions">${navBlock}${searchToggleBlock}</div></div></header>`;
|
|
91
|
+
const renderedFooterBlock = options.footerHtml ?? footerBlock;
|
|
81
92
|
return [
|
|
82
93
|
'<!doctype html>',
|
|
83
94
|
'<html lang="en">',
|
|
@@ -88,20 +99,33 @@ export function renderDocument(options) {
|
|
|
88
99
|
summaryMeta,
|
|
89
100
|
canonicalMeta,
|
|
90
101
|
faviconMeta,
|
|
102
|
+
socialImageMeta,
|
|
91
103
|
alternateMarkdownMeta,
|
|
92
104
|
stylesheetBlock,
|
|
93
105
|
'</head>',
|
|
94
106
|
`<body data-theme="${options.theme}" data-template="${options.template}">`,
|
|
95
|
-
|
|
107
|
+
headerBlock,
|
|
96
108
|
'<main>',
|
|
97
109
|
`<article>${articleBody}</article>`,
|
|
98
110
|
'</main>',
|
|
99
|
-
|
|
111
|
+
renderedFooterBlock,
|
|
100
112
|
searchScript,
|
|
101
113
|
'</body>',
|
|
102
114
|
'</html>',
|
|
103
115
|
].join('');
|
|
104
116
|
}
|
|
117
|
+
function getAbsoluteSiteAssetUrl(siteUrl, href) {
|
|
118
|
+
if (!href) {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
if (/^[a-zA-Z][a-zA-Z\d+.-]*:/.test(href) || href.startsWith('//')) {
|
|
122
|
+
return href;
|
|
123
|
+
}
|
|
124
|
+
if (!siteUrl) {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
return new URL(href.replace(/^\//, ''), `${siteUrl}/`).toString();
|
|
128
|
+
}
|
|
105
129
|
export function escapeHtml(value) {
|
|
106
130
|
return value
|
|
107
131
|
.replaceAll('&', '&')
|
package/dist/index-builder.d.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
import { type MdoPlugin } from './core/extensions.js';
|
|
1
2
|
export interface BuildIndexOptions {
|
|
2
3
|
rootDir?: string;
|
|
3
4
|
dir?: string;
|
|
5
|
+
plugins?: MdoPlugin[];
|
|
4
6
|
}
|
|
5
7
|
export interface BuildIndexResult {
|
|
6
8
|
updatedFiles: string[];
|
|
7
9
|
skippedDirectories: string[];
|
|
8
10
|
}
|
|
9
11
|
export declare function buildDirectoryIndexes(options: BuildIndexOptions): Promise<BuildIndexResult>;
|
|
10
|
-
export declare function buildManagedIndexBlock(directoryPath: string): Promise<string>;
|
|
12
|
+
export declare function buildManagedIndexBlock(directoryPath: string, plugins?: MdoPlugin[]): Promise<string>;
|
|
11
13
|
export declare function upsertManagedIndexBlock(source: string, block: string, options?: {
|
|
12
14
|
directoryPath?: string;
|
|
13
15
|
}): string;
|
package/dist/index-builder.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readdir, readFile, realpath, stat, writeFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { applyIndexTransforms } from './core/extensions.js';
|
|
3
4
|
import { inferDirectoryContentType } from './core/content-type.js';
|
|
4
5
|
import { getDirectoryIndexCandidates } from './core/directory-index.js';
|
|
5
6
|
import { getDocumentSummary, getDocumentTitle, parseMarkdownDocument, } from './core/markdown.js';
|
|
@@ -16,6 +17,7 @@ export async function buildDirectoryIndexes(options) {
|
|
|
16
17
|
const directoryPath = path.resolve(options.dir);
|
|
17
18
|
const updatedFile = await updateSingleDirectoryIndex(directoryPath, {
|
|
18
19
|
createIfMissing: false,
|
|
20
|
+
plugins: options.plugins ?? [],
|
|
19
21
|
});
|
|
20
22
|
return {
|
|
21
23
|
updatedFiles: updatedFile ? [updatedFile] : [],
|
|
@@ -29,6 +31,7 @@ export async function buildDirectoryIndexes(options) {
|
|
|
29
31
|
for (const directoryPath of directories) {
|
|
30
32
|
const updatedFile = await updateSingleDirectoryIndex(directoryPath, {
|
|
31
33
|
createIfMissing: false,
|
|
34
|
+
plugins: options.plugins ?? [],
|
|
32
35
|
});
|
|
33
36
|
if (updatedFile) {
|
|
34
37
|
updatedFiles.push(updatedFile);
|
|
@@ -55,7 +58,7 @@ async function updateSingleDirectoryIndex(directoryPath, options) {
|
|
|
55
58
|
const existingContent = indexFilePath
|
|
56
59
|
? await readFile(indexFilePath, 'utf8')
|
|
57
60
|
: '';
|
|
58
|
-
const block = await buildManagedIndexBlock(directoryPath);
|
|
61
|
+
const block = await buildManagedIndexBlock(directoryPath, options.plugins);
|
|
59
62
|
const nextContent = upsertManagedIndexBlock(existingContent, block, {
|
|
60
63
|
directoryPath,
|
|
61
64
|
});
|
|
@@ -64,7 +67,7 @@ async function updateSingleDirectoryIndex(directoryPath, options) {
|
|
|
64
67
|
}
|
|
65
68
|
return targetFilePath;
|
|
66
69
|
}
|
|
67
|
-
export async function buildManagedIndexBlock(directoryPath) {
|
|
70
|
+
export async function buildManagedIndexBlock(directoryPath, plugins = []) {
|
|
68
71
|
const entries = await readdir(directoryPath, { withFileTypes: true });
|
|
69
72
|
const directories = [];
|
|
70
73
|
const articles = [];
|
|
@@ -118,7 +121,23 @@ export async function buildManagedIndexBlock(directoryPath) {
|
|
|
118
121
|
}
|
|
119
122
|
directories.sort(compareDirectories);
|
|
120
123
|
articles.sort(compareArticles);
|
|
121
|
-
|
|
124
|
+
const transformedEntries = await applyIndexTransforms([
|
|
125
|
+
...directories.map((entry) => ({
|
|
126
|
+
kind: 'directory',
|
|
127
|
+
title: entry.title,
|
|
128
|
+
href: entry.link,
|
|
129
|
+
})),
|
|
130
|
+
...articles.map((entry) => ({
|
|
131
|
+
kind: 'article',
|
|
132
|
+
title: entry.title,
|
|
133
|
+
href: entry.link,
|
|
134
|
+
detail: [entry.date, entry.summary].filter(Boolean).join(' · ') || undefined,
|
|
135
|
+
})),
|
|
136
|
+
], plugins, {
|
|
137
|
+
mode: 'build',
|
|
138
|
+
directoryPath,
|
|
139
|
+
});
|
|
140
|
+
return renderManagedIndexBlock(transformedEntries);
|
|
122
141
|
}
|
|
123
142
|
export function upsertManagedIndexBlock(source, block, options = {}) {
|
|
124
143
|
const hasStart = source.includes(INDEX_START_MARKER);
|
|
@@ -137,20 +156,13 @@ export function upsertManagedIndexBlock(source, block, options = {}) {
|
|
|
137
156
|
}
|
|
138
157
|
return `${trimmed}\n\n${block}\n`;
|
|
139
158
|
}
|
|
140
|
-
function renderManagedIndexBlock(
|
|
159
|
+
function renderManagedIndexBlock(entries) {
|
|
141
160
|
const lines = [INDEX_START_MARKER, ''];
|
|
142
|
-
if (
|
|
143
|
-
for (const entry of
|
|
144
|
-
lines.push(`- [${entry.title}](${entry.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
if (articles.length > 0) {
|
|
149
|
-
for (const article of articles) {
|
|
150
|
-
lines.push(`- [${article.title}](${article.link})`);
|
|
151
|
-
const detail = [article.date, article.summary].filter(Boolean).join(' · ');
|
|
152
|
-
if (detail !== '') {
|
|
153
|
-
lines.push(` ${detail}`);
|
|
161
|
+
if (entries.length > 0) {
|
|
162
|
+
for (const entry of entries) {
|
|
163
|
+
lines.push(`- [${entry.title}](${entry.href})`);
|
|
164
|
+
if (entry.detail) {
|
|
165
|
+
lines.push(` ${entry.detail}`);
|
|
154
166
|
}
|
|
155
167
|
lines.push('');
|
|
156
168
|
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { defineConfig, type SiteConfig, type UserSiteConfig, type SiteNavItem, type SiteLogo, type SiteSocialLink, type EditLinkConfig, } from './core/site-config.js';
|
|
2
|
+
export type { MdoPlugin, PageRenderModel, RenderHookContext, IndexTransformContext, } from './core/extensions.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { defineConfig, } from './core/site-config.js';
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mdorigin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Markdown-first publishing
|
|
5
|
+
"description": "Markdown-first publishing for humans and agents.",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/jolestar/mdorigin.git"
|
|
9
9
|
},
|
|
10
|
-
"homepage": "https://
|
|
10
|
+
"homepage": "https://mdorigin.jolestar.workers.dev",
|
|
11
11
|
"bugs": {
|
|
12
12
|
"url": "https://github.com/jolestar/mdorigin/issues"
|
|
13
13
|
},
|
|
@@ -16,12 +16,18 @@
|
|
|
16
16
|
"publishing",
|
|
17
17
|
"static-site",
|
|
18
18
|
"cloudflare-workers",
|
|
19
|
+
"agents",
|
|
20
|
+
"skills",
|
|
19
21
|
"cli"
|
|
20
22
|
],
|
|
21
23
|
"bin": {
|
|
22
24
|
"mdorigin": "dist/cli/main.js"
|
|
23
25
|
},
|
|
24
26
|
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"default": "./dist/index.js"
|
|
30
|
+
},
|
|
25
31
|
"./search": {
|
|
26
32
|
"types": "./dist/search.d.ts",
|
|
27
33
|
"default": "./dist/search.js"
|
|
@@ -62,11 +68,11 @@
|
|
|
62
68
|
"gray-matter": "^4.0.3",
|
|
63
69
|
"remark": "^15.0.1",
|
|
64
70
|
"remark-gfm": "^4.0.1",
|
|
65
|
-
"remark-html": "^16.0.1"
|
|
71
|
+
"remark-html": "^16.0.1",
|
|
72
|
+
"tsx": "^4.20.5"
|
|
66
73
|
},
|
|
67
74
|
"devDependencies": {
|
|
68
75
|
"@types/node": "^24.5.2",
|
|
69
|
-
"tsx": "^4.20.5",
|
|
70
76
|
"typescript": "^5.9.2"
|
|
71
77
|
},
|
|
72
78
|
"optionalDependencies": {
|