@refrakt-md/content 0.11.3 → 0.14.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/dist/frontmatter.d.ts +17 -0
- package/dist/frontmatter.d.ts.map +1 -1
- package/dist/frontmatter.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +30 -3
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +32 -2
- package/dist/loader.js.map +1 -1
- package/dist/pipeline.d.ts +3 -3
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +11 -11
- package/dist/pipeline.js.map +1 -1
- package/dist/refract-loader.d.ts +53 -1
- package/dist/refract-loader.d.ts.map +1 -1
- package/dist/refract-loader.js +131 -42
- package/dist/refract-loader.js.map +1 -1
- package/dist/site.d.ts +58 -2
- package/dist/site.d.ts.map +1 -1
- package/dist/site.js +137 -32
- package/dist/site.js.map +1 -1
- package/dist/tint-cascade.d.ts +45 -0
- package/dist/tint-cascade.d.ts.map +1 -0
- package/dist/tint-cascade.js +71 -0
- package/dist/tint-cascade.js.map +1 -0
- package/dist/tint-ssr.d.ts +46 -0
- package/dist/tint-ssr.d.ts.map +1 -0
- package/dist/tint-ssr.js +67 -0
- package/dist/tint-ssr.js.map +1 -0
- package/package.json +5 -5
package/dist/site.d.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import type { RenderableTreeNodes, Schema } from '@markdoc/markdoc';
|
|
2
2
|
import type { PageSeo, HeadingInfo } from '@refrakt-md/runes';
|
|
3
|
-
import type {
|
|
3
|
+
import type { Plugin, PipelineWarning, AggregatedData, SecurityPolicy } from '@refrakt-md/types';
|
|
4
4
|
import type { PipelineStats } from './pipeline.js';
|
|
5
5
|
import { ContentTree, type PartialFile } from './content-tree.js';
|
|
6
6
|
import { Frontmatter } from './frontmatter.js';
|
|
7
7
|
import { Route } from './router.js';
|
|
8
8
|
import { ResolvedLayout } from './layout.js';
|
|
9
|
+
import { type ResolvedTintCascade } from './tint-cascade.js';
|
|
9
10
|
import { NavTree } from './navigation.js';
|
|
11
|
+
/** Async reader for ad-hoc lookups in virtual (non-FS) hosting environments.
|
|
12
|
+
* Returns the file content or `null` when the path is unknown. */
|
|
13
|
+
export type VirtualReader = (path: string) => Promise<string | null>;
|
|
10
14
|
export interface Site {
|
|
11
15
|
/** The content tree */
|
|
12
16
|
tree: ContentTree;
|
|
@@ -31,6 +35,10 @@ export interface SitePage {
|
|
|
31
35
|
headings: HeadingInfo[];
|
|
32
36
|
layout: ResolvedLayout;
|
|
33
37
|
seo: PageSeo;
|
|
38
|
+
/** Per-page tint cascade resolved from the layout chain + page frontmatter
|
|
39
|
+
* (SPEC-052). Adapters emit this as `data-theme` / `data-tint` /
|
|
40
|
+
* `data-tint-lock` attributes on `<html>` at SSR time. */
|
|
41
|
+
tintCascade: ResolvedTintCascade;
|
|
34
42
|
}
|
|
35
43
|
/**
|
|
36
44
|
* Load a content directory and resolve all pages, routes, layouts, and navigation.
|
|
@@ -41,6 +49,54 @@ export interface SitePage {
|
|
|
41
49
|
*
|
|
42
50
|
* When `sandboxExamplesDir` is provided, sandbox runes with `src` attributes
|
|
43
51
|
* can load code from external files in that directory.
|
|
52
|
+
*
|
|
53
|
+
* `securityPolicy` controls how runes treat untrusted author content. Defaults
|
|
54
|
+
* to `'trusted'` (current behaviour). Set `'strict'` for hosted-product use
|
|
55
|
+
* to strip scripts and harden the sandbox iframe.
|
|
56
|
+
*/
|
|
57
|
+
export declare function loadContent(dirPath: string, basePath?: string, icons?: Record<string, Record<string, string>>, additionalTags?: Record<string, Schema>, packages?: Plugin[], sandboxExamplesDir?: string, variables?: Record<string, unknown>, securityPolicy?: SecurityPolicy): Promise<Site>;
|
|
58
|
+
/** Options accepted by {@link loadContentFromTree}. */
|
|
59
|
+
export interface LoadContentFromTreeOptions {
|
|
60
|
+
/** URL base path for the Router. Default: `'/'`. */
|
|
61
|
+
basePath?: string;
|
|
62
|
+
/** Icon registry to inject into the Markdoc transform context. */
|
|
63
|
+
icons?: Record<string, Record<string, string>>;
|
|
64
|
+
/** Markdoc tag schemas to merge on top of the core runes. */
|
|
65
|
+
additionalTags?: Record<string, Schema>;
|
|
66
|
+
/** Plugins whose pipeline hooks should run in addition to core hooks. */
|
|
67
|
+
plugins?: Plugin[];
|
|
68
|
+
/** Site-wide Markdoc variables available in content via `{% $name %}`. */
|
|
69
|
+
variables?: Record<string, unknown>;
|
|
70
|
+
/** Security policy for sandbox runes. Default: `'trusted'`. */
|
|
71
|
+
securityPolicy?: SecurityPolicy;
|
|
72
|
+
/** Site-wide `theme.colorScheme` default — seeds the per-page tint cascade.
|
|
73
|
+
* Defaults to `'auto'`. Adapters typically read this from
|
|
74
|
+
* `refrakt.config.json` site.theme.colorScheme. See SPEC-052. */
|
|
75
|
+
colorScheme?: 'auto' | 'light' | 'dark';
|
|
76
|
+
/** Optional reader for ad-hoc lookups in virtual environments.
|
|
77
|
+
*
|
|
78
|
+
* Reserved for future asynchronous resolution paths (e.g., on-demand sandbox
|
|
79
|
+
* source resolution). Not currently consumed by any built-in code path — the
|
|
80
|
+
* page corpus, partials, and layouts all come from `tree`. Accepted here so
|
|
81
|
+
* hosts can wire it once and not need to thread it again when new internal
|
|
82
|
+
* consumers land. */
|
|
83
|
+
reader?: VirtualReader;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Like {@link loadContent}, but driven by a pre-built {@link ContentTree}
|
|
87
|
+
* instead of a filesystem directory. The full transform + cross-page pipeline
|
|
88
|
+
* still runs.
|
|
89
|
+
*
|
|
90
|
+
* Use this from hosted environments where content originates somewhere other
|
|
91
|
+
* than the local filesystem (e.g., a GitHub fetch, a database, an in-memory
|
|
92
|
+
* authoring sandbox). The caller is responsible for assembling the tree.
|
|
93
|
+
*
|
|
94
|
+
* Differences from `loadContent`:
|
|
95
|
+
* - No directory read — accepts the tree directly.
|
|
96
|
+
* - No git history — timestamps come from frontmatter only.
|
|
97
|
+
* - No sandbox filesystem access — sandbox runes resolve to null/empty.
|
|
98
|
+
* (Provide `__sandboxReadFile` etc. via `variables` if your host has its own
|
|
99
|
+
* synchronous access strategy.)
|
|
44
100
|
*/
|
|
45
|
-
export declare function
|
|
101
|
+
export declare function loadContentFromTree(tree: ContentTree, options?: LoadContentFromTreeOptions): Promise<Site>;
|
|
46
102
|
//# sourceMappingURL=site.d.ts.map
|
package/dist/site.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"site.d.ts","sourceRoot":"","sources":["../src/site.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAQ,mBAAmB,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1E,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"site.d.ts","sourceRoot":"","sources":["../src/site.ts"],"names":[],"mappings":"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"}
|
package/dist/site.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
3
|
import Markdoc from '@markdoc/markdoc';
|
|
4
|
-
import { tags, nodes, extractHeadings, extractSeo, corePipelineHooks, escapeFenceTags } from '@refrakt-md/runes';
|
|
4
|
+
import { tags, nodes, extractHeadings, extractSeo, corePipelineHooks, escapeFenceTags, resolveCoreSentinels } from '@refrakt-md/runes';
|
|
5
|
+
import { resolveSecurityPolicy } from '@refrakt-md/types';
|
|
5
6
|
import { ContentTree } from './content-tree.js';
|
|
6
7
|
import { parseFrontmatter } from './frontmatter.js';
|
|
7
8
|
import { Router } from './router.js';
|
|
8
9
|
import { resolveLayouts } from './layout.js';
|
|
10
|
+
import { resolveTintCascade } from './tint-cascade.js';
|
|
9
11
|
import { runPipeline } from './pipeline.js';
|
|
10
12
|
import { getGitTimestamps, resolveTimestamps } from './timestamps.js';
|
|
11
13
|
/** Synchronous file reader that returns null on failure. */
|
|
@@ -49,24 +51,17 @@ function transformContent(content, path, icons, additionalTags, contentVariables
|
|
|
49
51
|
}
|
|
50
52
|
return { renderable: Markdoc.transform(ast, config), headings };
|
|
51
53
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
* can load code from external files in that directory.
|
|
61
|
-
*/
|
|
62
|
-
export async function loadContent(dirPath, basePath = '/', icons, additionalTags, packages, sandboxExamplesDir, variables) {
|
|
63
|
-
const tree = await ContentTree.fromDirectory(dirPath);
|
|
64
|
-
const router = new Router(basePath);
|
|
54
|
+
const nullSandboxHooks = {
|
|
55
|
+
read: () => null,
|
|
56
|
+
list: () => [],
|
|
57
|
+
exists: () => false,
|
|
58
|
+
};
|
|
59
|
+
async function processContentTree(tree, opts) {
|
|
60
|
+
const resolvedSecurity = resolveSecurityPolicy(opts.securityPolicy);
|
|
61
|
+
const router = new Router(opts.basePath ?? '/');
|
|
65
62
|
const pages = [];
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
? resolve(sandboxExamplesDir)
|
|
69
|
-
: resolve(dirPath, '..', 'examples');
|
|
63
|
+
const sandbox = opts.sandbox ?? nullSandboxHooks;
|
|
64
|
+
const gitTimestamps = opts.gitTimestamps ?? new Map();
|
|
70
65
|
// Pre-parse partials into Markdoc ASTs for the transform config
|
|
71
66
|
const partialFiles = tree.partials();
|
|
72
67
|
let parsedPartials;
|
|
@@ -76,38 +71,76 @@ export async function loadContent(dirPath, basePath = '/', icons, additionalTags
|
|
|
76
71
|
parsedPartials[name] = Markdoc.parse(escapeFenceTags(partial.raw));
|
|
77
72
|
}
|
|
78
73
|
}
|
|
79
|
-
// Batch-collect git timestamps once before the page loop
|
|
80
|
-
const gitTimestamps = getGitTimestamps(dirPath);
|
|
81
74
|
for (const page of tree.pages()) {
|
|
82
75
|
const { frontmatter, content } = parseFrontmatter(page.raw);
|
|
83
76
|
const route = router.resolve(page.relativePath, frontmatter);
|
|
84
|
-
const layout = resolveLayouts(page, tree.root, icons);
|
|
77
|
+
const layout = resolveLayouts(page, tree.root, opts.icons);
|
|
85
78
|
const fileTimestamps = resolveTimestamps(page.relativePath, page.filePath, gitTimestamps, frontmatter);
|
|
86
79
|
const contentVariables = {
|
|
87
|
-
...variables,
|
|
80
|
+
...opts.variables,
|
|
88
81
|
frontmatter,
|
|
89
82
|
page: { url: route.url, filePath: route.filePath, draft: route.draft },
|
|
90
83
|
file: {
|
|
91
84
|
created: fileTimestamps.created,
|
|
92
85
|
modified: fileTimestamps.modified,
|
|
93
86
|
},
|
|
94
|
-
__sandboxReadFile:
|
|
95
|
-
__sandboxListDir:
|
|
96
|
-
__sandboxDirExists:
|
|
97
|
-
__sandboxExamplesDir:
|
|
87
|
+
__sandboxReadFile: sandbox.read,
|
|
88
|
+
__sandboxListDir: sandbox.list,
|
|
89
|
+
__sandboxDirExists: sandbox.exists,
|
|
90
|
+
__sandboxExamplesDir: opts.sandboxExamplesDir,
|
|
91
|
+
__securityPolicy: resolvedSecurity,
|
|
98
92
|
};
|
|
99
|
-
const { renderable, headings } = transformContent(content, route.url, icons, additionalTags, contentVariables, parsedPartials);
|
|
93
|
+
const { renderable, headings } = transformContent(content, route.url, opts.icons, opts.additionalTags, contentVariables, parsedPartials);
|
|
100
94
|
const seo = extractSeo(renderable, frontmatter, route.url);
|
|
101
|
-
|
|
95
|
+
const tintCascade = resolveTintCascade(page, tree.root, {
|
|
96
|
+
colorScheme: opts.colorScheme,
|
|
97
|
+
});
|
|
98
|
+
pages.push({ route, frontmatter, content, renderable, headings, layout, seo, tintCascade });
|
|
102
99
|
}
|
|
103
|
-
// Build hook sets: core always runs first, then
|
|
104
|
-
const hookSets = [{
|
|
105
|
-
for (const pkg of
|
|
100
|
+
// Build hook sets: core always runs first, then plugins in config order
|
|
101
|
+
const hookSets = [{ pluginName: '__core__', hooks: corePipelineHooks }];
|
|
102
|
+
for (const pkg of opts.plugins ?? []) {
|
|
106
103
|
if (pkg.pipeline) {
|
|
107
|
-
hookSets.push({
|
|
104
|
+
hookSets.push({ pluginName: pkg.name, hooks: pkg.pipeline });
|
|
108
105
|
}
|
|
109
106
|
}
|
|
110
107
|
const { pages: enrichedPages, warnings, stats, aggregated } = await runPipeline(pages, hookSets);
|
|
108
|
+
// Apply auto-resolutions to layout regions per page. Layouts are parsed once
|
|
109
|
+
// and shared across pages, but the auto-open / auto-pagination sentinels need
|
|
110
|
+
// per-page context (current URL, sibling order). The pipeline already wired
|
|
111
|
+
// core hooks against each page's renderable; here we apply the same resolvers
|
|
112
|
+
// to every region's content with the same aggregated data.
|
|
113
|
+
const coreData = aggregated['__core__'];
|
|
114
|
+
if (coreData) {
|
|
115
|
+
for (const page of enrichedPages) {
|
|
116
|
+
if (page.layout.regions.size === 0)
|
|
117
|
+
continue;
|
|
118
|
+
const ctx = makeContextForRegions(warnings, page.route.url);
|
|
119
|
+
// Build the global search scope: page.renderable + every region's content.
|
|
120
|
+
// This gives auto-pagination visibility into navs that live in other
|
|
121
|
+
// regions (e.g. the sidebar nav in the `nav` region) so prev/next can
|
|
122
|
+
// follow the declared reading order.
|
|
123
|
+
const navSearchScope = [page.renderable];
|
|
124
|
+
for (const region of page.layout.regions.values()) {
|
|
125
|
+
navSearchScope.push(region.content);
|
|
126
|
+
}
|
|
127
|
+
const resolvedRegions = new Map();
|
|
128
|
+
let mutated = false;
|
|
129
|
+
for (const [name, region] of page.layout.regions) {
|
|
130
|
+
const resolved = resolveCoreSentinels(region.content, page.route.url, coreData, ctx, navSearchScope);
|
|
131
|
+
if (resolved !== region.content) {
|
|
132
|
+
mutated = true;
|
|
133
|
+
resolvedRegions.set(name, { ...region, content: resolved });
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
resolvedRegions.set(name, region);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (mutated) {
|
|
140
|
+
page.layout.regions = resolvedRegions;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
111
144
|
return {
|
|
112
145
|
tree,
|
|
113
146
|
pages: enrichedPages,
|
|
@@ -118,4 +151,76 @@ export async function loadContent(dirPath, basePath = '/', icons, additionalTags
|
|
|
118
151
|
partials: partialFiles,
|
|
119
152
|
};
|
|
120
153
|
}
|
|
154
|
+
function makeContextForRegions(warnings, url) {
|
|
155
|
+
return {
|
|
156
|
+
info(message, infoUrl) { warnings.push({ severity: 'info', phase: 'postProcess', pluginName: '__core__/regions', url: infoUrl ?? url, message }); },
|
|
157
|
+
warn(message, warnUrl) { warnings.push({ severity: 'warning', phase: 'postProcess', pluginName: '__core__/regions', url: warnUrl ?? url, message }); },
|
|
158
|
+
error(message, errUrl) { warnings.push({ severity: 'error', phase: 'postProcess', pluginName: '__core__/regions', url: errUrl ?? url, message }); },
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Load a content directory and resolve all pages, routes, layouts, and navigation.
|
|
163
|
+
*
|
|
164
|
+
* When `packages` are provided, the cross-page pipeline runs after loading:
|
|
165
|
+
* core hooks + package hooks register entities, aggregate cross-page data,
|
|
166
|
+
* and post-process pages before returning.
|
|
167
|
+
*
|
|
168
|
+
* When `sandboxExamplesDir` is provided, sandbox runes with `src` attributes
|
|
169
|
+
* can load code from external files in that directory.
|
|
170
|
+
*
|
|
171
|
+
* `securityPolicy` controls how runes treat untrusted author content. Defaults
|
|
172
|
+
* to `'trusted'` (current behaviour). Set `'strict'` for hosted-product use
|
|
173
|
+
* to strip scripts and harden the sandbox iframe.
|
|
174
|
+
*/
|
|
175
|
+
export async function loadContent(dirPath, basePath = '/', icons, additionalTags, packages, sandboxExamplesDir, variables, securityPolicy) {
|
|
176
|
+
const tree = await ContentTree.fromDirectory(dirPath);
|
|
177
|
+
const resolvedExamplesDir = sandboxExamplesDir
|
|
178
|
+
? resolve(sandboxExamplesDir)
|
|
179
|
+
: resolve(dirPath, '..', 'examples');
|
|
180
|
+
return processContentTree(tree, {
|
|
181
|
+
basePath,
|
|
182
|
+
icons,
|
|
183
|
+
additionalTags,
|
|
184
|
+
plugins: packages,
|
|
185
|
+
variables,
|
|
186
|
+
securityPolicy,
|
|
187
|
+
gitTimestamps: getGitTimestamps(dirPath),
|
|
188
|
+
sandbox: {
|
|
189
|
+
read: sandboxReadFile,
|
|
190
|
+
list: sandboxListDir,
|
|
191
|
+
exists: sandboxDirExists,
|
|
192
|
+
},
|
|
193
|
+
sandboxExamplesDir: resolvedExamplesDir,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Like {@link loadContent}, but driven by a pre-built {@link ContentTree}
|
|
198
|
+
* instead of a filesystem directory. The full transform + cross-page pipeline
|
|
199
|
+
* still runs.
|
|
200
|
+
*
|
|
201
|
+
* Use this from hosted environments where content originates somewhere other
|
|
202
|
+
* than the local filesystem (e.g., a GitHub fetch, a database, an in-memory
|
|
203
|
+
* authoring sandbox). The caller is responsible for assembling the tree.
|
|
204
|
+
*
|
|
205
|
+
* Differences from `loadContent`:
|
|
206
|
+
* - No directory read — accepts the tree directly.
|
|
207
|
+
* - No git history — timestamps come from frontmatter only.
|
|
208
|
+
* - No sandbox filesystem access — sandbox runes resolve to null/empty.
|
|
209
|
+
* (Provide `__sandboxReadFile` etc. via `variables` if your host has its own
|
|
210
|
+
* synchronous access strategy.)
|
|
211
|
+
*/
|
|
212
|
+
export async function loadContentFromTree(tree, options = {}) {
|
|
213
|
+
// `reader` is accepted on the options bag for forward compatibility but not
|
|
214
|
+
// currently plumbed — sandbox runes call file ops synchronously, so an async
|
|
215
|
+
// reader can't back them today.
|
|
216
|
+
return processContentTree(tree, {
|
|
217
|
+
basePath: options.basePath,
|
|
218
|
+
icons: options.icons,
|
|
219
|
+
additionalTags: options.additionalTags,
|
|
220
|
+
plugins: options.plugins,
|
|
221
|
+
variables: options.variables,
|
|
222
|
+
securityPolicy: options.securityPolicy,
|
|
223
|
+
colorScheme: options.colorScheme,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
121
226
|
//# sourceMappingURL=site.js.map
|
package/dist/site.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"site.js","sourceRoot":"","sources":["../src/site.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"site.js","sourceRoot":"","sources":["../src/site.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ContentDirectory, ContentPage } from './content-tree.js';
|
|
2
|
+
/**
|
|
3
|
+
* The resolved tint cascade for a page — a deterministic three-field tuple
|
|
4
|
+
* the renderer emits onto `<html>` as `data-*` attributes per SPEC-052.
|
|
5
|
+
*
|
|
6
|
+
* - `tint` — named tint preset, or `null` (no named tint active)
|
|
7
|
+
* - `tintMode` — `'auto'` / `'light'` / `'dark'`. `'auto'` is the default;
|
|
8
|
+
* `'light'` and `'dark'` indicate an explicit mode preference.
|
|
9
|
+
* - `locked` — when `true`, the SSR-emitted mode is final: user
|
|
10
|
+
* preference is preserved in localStorage but not applied, and the
|
|
11
|
+
* theme-toggle UI hides.
|
|
12
|
+
*/
|
|
13
|
+
export interface ResolvedTintCascade {
|
|
14
|
+
tint: string | null;
|
|
15
|
+
tintMode: 'auto' | 'light' | 'dark';
|
|
16
|
+
locked: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Root defaults applied before any layout / page frontmatter contributes.
|
|
20
|
+
* Adapters can pass `theme.colorScheme` from `refrakt.config.json` to seed
|
|
21
|
+
* `tintMode`; everything else starts unset.
|
|
22
|
+
*/
|
|
23
|
+
export interface CascadeRootDefaults {
|
|
24
|
+
/** Maps to {@link ResolvedTintCascade.tintMode}. */
|
|
25
|
+
colorScheme?: 'auto' | 'light' | 'dark';
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Resolve the tint cascade for a page by walking its layout chain
|
|
29
|
+
* outermost-to-innermost and overlaying its own frontmatter last.
|
|
30
|
+
*
|
|
31
|
+
* Cascade order (last writer wins per field):
|
|
32
|
+
*
|
|
33
|
+
* 1. Root defaults (typically from `theme.colorScheme` in site config)
|
|
34
|
+
* 2. Each `_layout.md` from the root toward the page, in order
|
|
35
|
+
* 3. The page's own frontmatter
|
|
36
|
+
*
|
|
37
|
+
* `undefined` at any level means "inherit from the next outer layer".
|
|
38
|
+
* Explicit `null` for `tint` is the canonical "reset to no named tint"
|
|
39
|
+
* idiom — preserved through the cascade so authors can break out of a
|
|
40
|
+
* parent layout's tint without applying a new one.
|
|
41
|
+
*
|
|
42
|
+
* Pure function: given the same inputs, returns the same tuple. No I/O.
|
|
43
|
+
*/
|
|
44
|
+
export declare function resolveTintCascade(page: ContentPage, rootDir: ContentDirectory, defaults?: CascadeRootDefaults): ResolvedTintCascade;
|
|
45
|
+
//# sourceMappingURL=tint-cascade.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tint-cascade.d.ts","sourceRoot":"","sources":["../src/tint-cascade.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGvE;;;;;;;;;;GAUG;AACH,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IACpC,MAAM,EAAE,OAAO,CAAC;CAChB;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IACnC,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;CACxC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CACjC,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,gBAAgB,EACzB,QAAQ,GAAE,mBAAwB,GAChC,mBAAmB,CAiBrB"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { parseFrontmatter } from './frontmatter.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve the tint cascade for a page by walking its layout chain
|
|
4
|
+
* outermost-to-innermost and overlaying its own frontmatter last.
|
|
5
|
+
*
|
|
6
|
+
* Cascade order (last writer wins per field):
|
|
7
|
+
*
|
|
8
|
+
* 1. Root defaults (typically from `theme.colorScheme` in site config)
|
|
9
|
+
* 2. Each `_layout.md` from the root toward the page, in order
|
|
10
|
+
* 3. The page's own frontmatter
|
|
11
|
+
*
|
|
12
|
+
* `undefined` at any level means "inherit from the next outer layer".
|
|
13
|
+
* Explicit `null` for `tint` is the canonical "reset to no named tint"
|
|
14
|
+
* idiom — preserved through the cascade so authors can break out of a
|
|
15
|
+
* parent layout's tint without applying a new one.
|
|
16
|
+
*
|
|
17
|
+
* Pure function: given the same inputs, returns the same tuple. No I/O.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveTintCascade(page, rootDir, defaults = {}) {
|
|
20
|
+
const resolved = {
|
|
21
|
+
tint: null,
|
|
22
|
+
tintMode: defaults.colorScheme ?? 'auto',
|
|
23
|
+
locked: false,
|
|
24
|
+
};
|
|
25
|
+
const layoutChain = findLayoutChain(page, rootDir);
|
|
26
|
+
for (const layoutPage of layoutChain) {
|
|
27
|
+
const layoutFrontmatter = parseFrontmatter(layoutPage.raw).frontmatter;
|
|
28
|
+
applyLayer(resolved, layoutFrontmatter);
|
|
29
|
+
}
|
|
30
|
+
const pageFrontmatter = parseFrontmatter(page.raw).frontmatter;
|
|
31
|
+
applyLayer(resolved, pageFrontmatter);
|
|
32
|
+
return resolved;
|
|
33
|
+
}
|
|
34
|
+
function applyLayer(resolved, layer) {
|
|
35
|
+
if ('tint' in layer && layer.tint !== undefined) {
|
|
36
|
+
// `null` is a real value here — author's "reset to no named tint" idiom.
|
|
37
|
+
resolved.tint = layer.tint;
|
|
38
|
+
}
|
|
39
|
+
if ('tint-mode' in layer && layer['tint-mode'] !== undefined) {
|
|
40
|
+
const mode = layer['tint-mode'];
|
|
41
|
+
if (mode === 'auto' || mode === 'light' || mode === 'dark') {
|
|
42
|
+
resolved.tintMode = mode;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if ('tint-lock' in layer && layer['tint-lock'] !== undefined) {
|
|
46
|
+
resolved.locked = Boolean(layer['tint-lock']);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Walk from the root down to the page's directory, collecting every
|
|
51
|
+
* `_layout.md` along the way. Returns the chain in outermost-to-innermost
|
|
52
|
+
* order so the cascade can apply each in turn.
|
|
53
|
+
*
|
|
54
|
+
* Mirrors `layout.ts`'s `findLayoutChain` so the two stay in lockstep.
|
|
55
|
+
*/
|
|
56
|
+
function findLayoutChain(page, rootDir) {
|
|
57
|
+
const parts = page.relativePath.split('/').slice(0, -1);
|
|
58
|
+
const chain = [];
|
|
59
|
+
let current = rootDir;
|
|
60
|
+
if (current.layout)
|
|
61
|
+
chain.push(current.layout);
|
|
62
|
+
for (const part of parts) {
|
|
63
|
+
current = current.children.find(c => c.name === part);
|
|
64
|
+
if (!current)
|
|
65
|
+
break;
|
|
66
|
+
if (current.layout)
|
|
67
|
+
chain.push(current.layout);
|
|
68
|
+
}
|
|
69
|
+
return chain;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=tint-cascade.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tint-cascade.js","sourceRoot":"","sources":["../src/tint-cascade.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAoB,MAAM,kBAAkB,CAAC;AA6BtE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,kBAAkB,CACjC,IAAiB,EACjB,OAAyB,EACzB,WAAgC,EAAE;IAElC,MAAM,QAAQ,GAAwB;QACrC,IAAI,EAAE,IAAI;QACV,QAAQ,EAAE,QAAQ,CAAC,WAAW,IAAI,MAAM;QACxC,MAAM,EAAE,KAAK;KACb,CAAC;IAEF,MAAM,WAAW,GAAG,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACnD,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACtC,MAAM,iBAAiB,GAAG,gBAAgB,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC;QACvE,UAAU,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC;IACzC,CAAC;IAED,MAAM,eAAe,GAAG,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC;IAC/D,UAAU,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;IAEtC,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED,SAAS,UAAU,CAAC,QAA6B,EAAE,KAAkB;IACpE,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QACjD,yEAAyE;QACzE,QAAQ,CAAC,IAAI,GAAG,KAAK,CAAC,IAAqB,CAAC;IAC7C,CAAC;IACD,IAAI,WAAW,IAAI,KAAK,IAAI,KAAK,CAAC,WAAW,CAAC,KAAK,SAAS,EAAE,CAAC;QAC9D,MAAM,IAAI,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC;QAChC,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;YAC5D,QAAQ,CAAC,QAAQ,GAAG,IAAI,CAAC;QAC1B,CAAC;IACF,CAAC;IACD,IAAI,WAAW,IAAI,KAAK,IAAI,KAAK,CAAC,WAAW,CAAC,KAAK,SAAS,EAAE,CAAC;QAC9D,QAAQ,CAAC,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC;IAC/C,CAAC;AACF,CAAC;AAED;;;;;;GAMG;AACH,SAAS,eAAe,CAAC,IAAiB,EAAE,OAAyB;IACpE,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACxD,MAAM,KAAK,GAAkB,EAAE,CAAC;IAEhC,IAAI,OAAO,GAAiC,OAAO,CAAC;IACpD,IAAI,OAAO,CAAC,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAE/C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QACtD,IAAI,CAAC,OAAO;YAAE,MAAM;QACpB,IAAI,OAAO,CAAC,MAAM;YAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD,CAAC;IAED,OAAO,KAAK,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ResolvedTintCascade } from './tint-cascade.js';
|
|
2
|
+
/**
|
|
3
|
+
* Build the attribute string adapters should put on the `<html>` element at
|
|
4
|
+
* SSR time, per SPEC-052. Reads the resolved cascade tuple from a SitePage
|
|
5
|
+
* and emits `data-theme`, `data-tint`, `data-tint-lock`, plus the matching
|
|
6
|
+
* `color-scheme` declaration when locked.
|
|
7
|
+
*
|
|
8
|
+
* The returned string is just the attributes (no leading/trailing space) —
|
|
9
|
+
* adapters interpolate it into their HTML shell however suits them best.
|
|
10
|
+
* For example, in a SvelteKit `hooks.server.ts` `transformPageChunk`:
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* html.replace('<html', `<html ${htmlTintAttributes(cascade)}`)
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* If the cascade has no opinions (default `'auto'`, no locked mode, no
|
|
17
|
+
* named tint), returns the empty string — `<html>` stays clean and the
|
|
18
|
+
* pre-paint script handles user / system preference at runtime.
|
|
19
|
+
*/
|
|
20
|
+
export declare function htmlTintAttributes(cascade: ResolvedTintCascade): string;
|
|
21
|
+
/**
|
|
22
|
+
* Build the `<meta name="color-scheme">` content value adapters should
|
|
23
|
+
* emit. `'light dark'` when unlocked (browser may pick); explicit when
|
|
24
|
+
* locked.
|
|
25
|
+
*/
|
|
26
|
+
export declare function colorSchemeMetaContent(cascade: ResolvedTintCascade): string;
|
|
27
|
+
/**
|
|
28
|
+
* The canonical anti-FOIT pre-paint script. Drop this inline into `<head>`
|
|
29
|
+
* before any stylesheets so it runs before first paint and there's no flash
|
|
30
|
+
* of incorrect theme.
|
|
31
|
+
*
|
|
32
|
+
* Behaviour per SPEC-052:
|
|
33
|
+
* - On a locked page (`data-tint-lock="true"`), do nothing — the SSR
|
|
34
|
+
* output is final.
|
|
35
|
+
* - On an unlocked page, apply the user's saved preference from
|
|
36
|
+
* localStorage (key `rf-theme`); fall back to system
|
|
37
|
+
* `prefers-color-scheme` if no preference is saved.
|
|
38
|
+
* - The toggle component (separate concern) writes the same localStorage
|
|
39
|
+
* key on click. Saved preference is preserved across visits to locked
|
|
40
|
+
* pages.
|
|
41
|
+
*
|
|
42
|
+
* Returns the raw JavaScript string (no `<script>` wrapper) so adapters
|
|
43
|
+
* can inline it however their templating system prefers.
|
|
44
|
+
*/
|
|
45
|
+
export declare function prePaintScript(): string;
|
|
46
|
+
//# sourceMappingURL=tint-ssr.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tint-ssr.d.ts","sourceRoot":"","sources":["../src/tint-ssr.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAE7D;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAYvE;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAK3E;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAEvC"}
|
package/dist/tint-ssr.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the attribute string adapters should put on the `<html>` element at
|
|
3
|
+
* SSR time, per SPEC-052. Reads the resolved cascade tuple from a SitePage
|
|
4
|
+
* and emits `data-theme`, `data-tint`, `data-tint-lock`, plus the matching
|
|
5
|
+
* `color-scheme` declaration when locked.
|
|
6
|
+
*
|
|
7
|
+
* The returned string is just the attributes (no leading/trailing space) —
|
|
8
|
+
* adapters interpolate it into their HTML shell however suits them best.
|
|
9
|
+
* For example, in a SvelteKit `hooks.server.ts` `transformPageChunk`:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* html.replace('<html', `<html ${htmlTintAttributes(cascade)}`)
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* If the cascade has no opinions (default `'auto'`, no locked mode, no
|
|
16
|
+
* named tint), returns the empty string — `<html>` stays clean and the
|
|
17
|
+
* pre-paint script handles user / system preference at runtime.
|
|
18
|
+
*/
|
|
19
|
+
export function htmlTintAttributes(cascade) {
|
|
20
|
+
const parts = [];
|
|
21
|
+
if (cascade.tintMode === 'light' || cascade.tintMode === 'dark') {
|
|
22
|
+
parts.push(`data-theme="${cascade.tintMode}"`);
|
|
23
|
+
}
|
|
24
|
+
if (cascade.tint !== null) {
|
|
25
|
+
parts.push(`data-tint="${escapeAttr(cascade.tint)}"`);
|
|
26
|
+
}
|
|
27
|
+
if (cascade.locked) {
|
|
28
|
+
parts.push('data-tint-lock="true"');
|
|
29
|
+
}
|
|
30
|
+
return parts.join(' ');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Build the `<meta name="color-scheme">` content value adapters should
|
|
34
|
+
* emit. `'light dark'` when unlocked (browser may pick); explicit when
|
|
35
|
+
* locked.
|
|
36
|
+
*/
|
|
37
|
+
export function colorSchemeMetaContent(cascade) {
|
|
38
|
+
if (cascade.locked && (cascade.tintMode === 'light' || cascade.tintMode === 'dark')) {
|
|
39
|
+
return cascade.tintMode;
|
|
40
|
+
}
|
|
41
|
+
return 'light dark';
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The canonical anti-FOIT pre-paint script. Drop this inline into `<head>`
|
|
45
|
+
* before any stylesheets so it runs before first paint and there's no flash
|
|
46
|
+
* of incorrect theme.
|
|
47
|
+
*
|
|
48
|
+
* Behaviour per SPEC-052:
|
|
49
|
+
* - On a locked page (`data-tint-lock="true"`), do nothing — the SSR
|
|
50
|
+
* output is final.
|
|
51
|
+
* - On an unlocked page, apply the user's saved preference from
|
|
52
|
+
* localStorage (key `rf-theme`); fall back to system
|
|
53
|
+
* `prefers-color-scheme` if no preference is saved.
|
|
54
|
+
* - The toggle component (separate concern) writes the same localStorage
|
|
55
|
+
* key on click. Saved preference is preserved across visits to locked
|
|
56
|
+
* pages.
|
|
57
|
+
*
|
|
58
|
+
* Returns the raw JavaScript string (no `<script>` wrapper) so adapters
|
|
59
|
+
* can inline it however their templating system prefers.
|
|
60
|
+
*/
|
|
61
|
+
export function prePaintScript() {
|
|
62
|
+
return `(function(){var d=document.documentElement;if(d.dataset.tintLock==='true')return;var s=null;try{s=localStorage.getItem('rf-theme')}catch(e){}var m=s&&s!=='auto'?s:(matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light');d.setAttribute('data-theme',m)})();`;
|
|
63
|
+
}
|
|
64
|
+
function escapeAttr(value) {
|
|
65
|
+
return value.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<');
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=tint-ssr.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tint-ssr.js","sourceRoot":"","sources":["../src/tint-ssr.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAA4B;IAC9D,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,OAAO,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;QACjE,KAAK,CAAC,IAAI,CAAC,eAAe,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IAChD,CAAC;IACD,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QAC3B,KAAK,CAAC,IAAI,CAAC,cAAc,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACvD,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACpB,KAAK,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;IACrC,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACxB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAA4B;IAClE,IAAI,OAAO,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,OAAO,CAAC,QAAQ,KAAK,MAAM,CAAC,EAAE,CAAC;QACrF,OAAO,OAAO,CAAC,QAAQ,CAAC;IACzB,CAAC;IACD,OAAO,YAAY,CAAC;AACrB,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,cAAc;IAC7B,OAAO,4QAA4Q,CAAC;AACrR,CAAC;AAED,SAAS,UAAU,CAAC,KAAa;IAChC,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AACnF,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@refrakt-md/content",
|
|
3
3
|
"description": "Content loading and transformation pipeline",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.14.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"build": "tsc"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@refrakt-md/highlight": "0.
|
|
33
|
-
"@refrakt-md/runes": "0.
|
|
34
|
-
"@refrakt-md/transform": "0.
|
|
35
|
-
"@refrakt-md/types": "0.
|
|
32
|
+
"@refrakt-md/highlight": "0.14.0",
|
|
33
|
+
"@refrakt-md/runes": "0.14.0",
|
|
34
|
+
"@refrakt-md/transform": "0.14.0",
|
|
35
|
+
"@refrakt-md/types": "0.14.0",
|
|
36
36
|
"@markdoc/markdoc": "0.4.0",
|
|
37
37
|
"yaml": "^2.4.0"
|
|
38
38
|
}
|