@dogsbay/format-astro 0.2.0-beta.92 → 0.2.0-beta.98
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/base-path.d.ts +10 -0
- package/dist/base-path.d.ts.map +1 -1
- package/dist/base-path.js +11 -1
- package/dist/base-path.js.map +1 -1
- package/dist/blog.d.ts +134 -0
- package/dist/blog.d.ts.map +1 -0
- package/dist/blog.js +319 -0
- package/dist/blog.js.map +1 -0
- package/dist/diff-decoration.d.ts +79 -0
- package/dist/diff-decoration.d.ts.map +1 -0
- package/dist/diff-decoration.js +541 -0
- package/dist/diff-decoration.js.map +1 -0
- package/dist/granularity.d.ts +83 -0
- package/dist/granularity.d.ts.map +1 -0
- package/dist/granularity.js +247 -0
- package/dist/granularity.js.map +1 -0
- package/dist/index.d.ts +17 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -1
- package/dist/index.js.map +1 -1
- package/dist/lead.d.ts.map +1 -1
- package/dist/lead.js +30 -6
- package/dist/lead.js.map +1 -1
- package/dist/llms-txt.d.ts +19 -0
- package/dist/llms-txt.d.ts.map +1 -1
- package/dist/llms-txt.js +40 -2
- package/dist/llms-txt.js.map +1 -1
- package/dist/project.d.ts +134 -2
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +1149 -137
- package/dist/project.js.map +1 -1
- package/dist/serialize.d.ts +1 -0
- package/dist/serialize.d.ts.map +1 -1
- package/dist/serialize.js +302 -138
- package/dist/serialize.js.map +1 -1
- package/dist/sitemap.d.ts +24 -0
- package/dist/sitemap.d.ts.map +1 -1
- package/dist/sitemap.js +32 -3
- package/dist/sitemap.js.map +1 -1
- package/dist/taxonomy.d.ts +24 -1
- package/dist/taxonomy.d.ts.map +1 -1
- package/dist/taxonomy.js +23 -2
- package/dist/taxonomy.js.map +1 -1
- package/package.json +8 -7
package/dist/base-path.d.ts
CHANGED
|
@@ -176,6 +176,16 @@ export declare function resolvePrefixes(siteUrl: string | undefined, basePath: s
|
|
|
176
176
|
* - default basePath, empty slug → `"/docs"`
|
|
177
177
|
* - empty basePath, slug "auth" → `"/auth"`
|
|
178
178
|
* - empty basePath, empty slug → `"/"`
|
|
179
|
+
* - empty basePath, slug "index" → `"/"` (root home)
|
|
180
|
+
*
|
|
181
|
+
* A bare `index` slug means the directory root: `content/index.md` is
|
|
182
|
+
* deliberately loaded with slug `"index"` (so the root-home and `.md`-mirror
|
|
183
|
+
* special cases fire — see plans/dir-index-slug-nav-drop.md), but the page is
|
|
184
|
+
* served at the directory URL and its nav href drops `index` (via
|
|
185
|
+
* `fileToHref`'s `/index` strip). `currentPath` must match that nav href for
|
|
186
|
+
* `getPagination` to find the home page, so we drop a whole-slug `index` here
|
|
187
|
+
* too. Without this the home page's `currentPath` (`/base/index`) never matches
|
|
188
|
+
* its nav entry (`/base`), so prev/next silently falls back to the first item.
|
|
179
189
|
*/
|
|
180
190
|
export declare function buildCurrentPath(basePath: string, section: string | undefined, slug: string): string;
|
|
181
191
|
//# sourceMappingURL=base-path.d.ts.map
|
package/dist/base-path.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-path.d.ts","sourceRoot":"","sources":["../src/base-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAKnE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAE3D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,IAAI,EAAE,MAAM,GACX,MAAM,CAMR;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAGxE;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG;IACzD,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB,CAiBA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMvE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B;IACD,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,CASA;AAED
|
|
1
|
+
{"version":3,"file":"base-path.d.ts","sourceRoot":"","sources":["../src/base-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAKnE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAE3D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,IAAI,EAAE,MAAM,GACX,MAAM,CAMR;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAGxE;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG;IACzD,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB,CAiBA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMvE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B;IACD,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,CASA;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,IAAI,EAAE,MAAM,GACX,MAAM,CAMR"}
|
package/dist/base-path.js
CHANGED
|
@@ -225,6 +225,16 @@ export function resolvePrefixes(siteUrl, basePath) {
|
|
|
225
225
|
* - default basePath, empty slug → `"/docs"`
|
|
226
226
|
* - empty basePath, slug "auth" → `"/auth"`
|
|
227
227
|
* - empty basePath, empty slug → `"/"`
|
|
228
|
+
* - empty basePath, slug "index" → `"/"` (root home)
|
|
229
|
+
*
|
|
230
|
+
* A bare `index` slug means the directory root: `content/index.md` is
|
|
231
|
+
* deliberately loaded with slug `"index"` (so the root-home and `.md`-mirror
|
|
232
|
+
* special cases fire — see plans/dir-index-slug-nav-drop.md), but the page is
|
|
233
|
+
* served at the directory URL and its nav href drops `index` (via
|
|
234
|
+
* `fileToHref`'s `/index` strip). `currentPath` must match that nav href for
|
|
235
|
+
* `getPagination` to find the home page, so we drop a whole-slug `index` here
|
|
236
|
+
* too. Without this the home page's `currentPath` (`/base/index`) never matches
|
|
237
|
+
* its nav entry (`/base`), so prev/next silently falls back to the first item.
|
|
228
238
|
*/
|
|
229
239
|
export function buildCurrentPath(basePath, section, slug) {
|
|
230
240
|
const parts = [];
|
|
@@ -232,7 +242,7 @@ export function buildCurrentPath(basePath, section, slug) {
|
|
|
232
242
|
parts.push(seg);
|
|
233
243
|
if (section)
|
|
234
244
|
parts.push(section);
|
|
235
|
-
if (slug)
|
|
245
|
+
if (slug && slug !== "index")
|
|
236
246
|
parts.push(slug);
|
|
237
247
|
return parts.length === 0 ? "/" : `/${parts.join("/")}`;
|
|
238
248
|
}
|
package/dist/base-path.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-path.js","sourceRoot":"","sources":["../src/base-path.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"base-path.js","sourceRoot":"","sources":["../src/base-path.ts"],"names":[],"mappings":"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"}
|
package/dist/blog.d.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { ExportPage } from "@dogsbay/types";
|
|
2
|
+
/** Resolved `blog:` config (defaults filled). */
|
|
3
|
+
export interface BlogEmitConfig {
|
|
4
|
+
/**
|
|
5
|
+
* Where the reverse-chronological index lives, relative to the site.
|
|
6
|
+
* `"/"` (the default) means the site root IS the blog index — the
|
|
7
|
+
* shape a blog-only site wants.
|
|
8
|
+
*/
|
|
9
|
+
indexPath: string;
|
|
10
|
+
/** Posts per index page. */
|
|
11
|
+
postsPerPage: number;
|
|
12
|
+
/** Emit an RSS feed route. Phase 4; accepted and ignored for now. */
|
|
13
|
+
rss: boolean;
|
|
14
|
+
/** Which `PageMeta` date orders the list. */
|
|
15
|
+
dateField: "created" | "updated";
|
|
16
|
+
}
|
|
17
|
+
/** Defaults for a `blog:` block that omits fields. */
|
|
18
|
+
export declare const BLOG_DEFAULTS: BlogEmitConfig;
|
|
19
|
+
/** One post in the emitted data file. */
|
|
20
|
+
export interface BlogPostRef {
|
|
21
|
+
slug: string;
|
|
22
|
+
title: string;
|
|
23
|
+
url: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
/** ISO date from `meta[dateField]`. Absent when the post carries none. */
|
|
26
|
+
date?: string;
|
|
27
|
+
/** Only when it differs from `date` — the layout shows it as "updated". */
|
|
28
|
+
updated?: string;
|
|
29
|
+
author?: string[];
|
|
30
|
+
tags?: string[];
|
|
31
|
+
heroImage?: string;
|
|
32
|
+
/** Whole minutes, minimum 1. */
|
|
33
|
+
readingMinutes: number;
|
|
34
|
+
}
|
|
35
|
+
/** `src/data/blog.json`. */
|
|
36
|
+
export interface BlogData {
|
|
37
|
+
/** URL-form index path, prefixed with the COMBINED serving prefix. */
|
|
38
|
+
indexPath: string;
|
|
39
|
+
/**
|
|
40
|
+
* Filesystem-layout prefix (`site.basePath`), used to place route
|
|
41
|
+
* files under `src/pages/`. Deliberately NOT the same field as the URL
|
|
42
|
+
* prefix: Astro's `base` adds `urlBase` at request time, so baking it
|
|
43
|
+
* into file paths too would double it. Same split `taxonomy.ts` takes
|
|
44
|
+
* as `basePath` + `urlPrefix`.
|
|
45
|
+
*/
|
|
46
|
+
basePath: string;
|
|
47
|
+
/**
|
|
48
|
+
* The COMBINED serving prefix (urlBase + basePath). What DocsLayout's
|
|
49
|
+
* `basePath` prop wants — it composes the llms.txt link, switcher
|
|
50
|
+
* hrefs and the alternate link from it, all of which are URLs. Kept
|
|
51
|
+
* separate from `basePath` above, which is a filesystem path.
|
|
52
|
+
*/
|
|
53
|
+
urlPrefix: string;
|
|
54
|
+
/** `content.section`, when the build has one. Part of route placement. */
|
|
55
|
+
section?: string;
|
|
56
|
+
postsPerPage: number;
|
|
57
|
+
/** Newest first. */
|
|
58
|
+
posts: BlogPostRef[];
|
|
59
|
+
postCount: number;
|
|
60
|
+
}
|
|
61
|
+
/** Prev/next for one post, in date order. */
|
|
62
|
+
export interface BlogAdjacency {
|
|
63
|
+
/** The OLDER neighbour — back along the timeline, rendered on the left. */
|
|
64
|
+
prev?: {
|
|
65
|
+
title: string;
|
|
66
|
+
href: string;
|
|
67
|
+
};
|
|
68
|
+
/** The NEWER neighbour — forward, rendered on the right. */
|
|
69
|
+
next?: {
|
|
70
|
+
title: string;
|
|
71
|
+
href: string;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Count words in a page's body.
|
|
76
|
+
*
|
|
77
|
+
* Deliberately crude: whitespace-split on the extracted plain text.
|
|
78
|
+
* Reading time is a reading CUE, not a measurement, and a more precise
|
|
79
|
+
* count would not change the rendered minutes.
|
|
80
|
+
*/
|
|
81
|
+
export declare function countWords(page: ExportPage): number;
|
|
82
|
+
/** Whole minutes at {@link WORDS_PER_MINUTE}, never less than 1. */
|
|
83
|
+
export declare function readingMinutes(page: ExportPage): number;
|
|
84
|
+
/**
|
|
85
|
+
* Build the post list from the page set.
|
|
86
|
+
*
|
|
87
|
+
* EVERY page is a post except the one that would occupy the index
|
|
88
|
+
* route itself. A site that declares `blog:` IS a blog; there is no
|
|
89
|
+
* second "is this a post?" axis to configure, and inventing one would
|
|
90
|
+
* mean a post silently vanishing because a field was missing.
|
|
91
|
+
*
|
|
92
|
+
* Undated posts sort last, in title order, rather than being dropped —
|
|
93
|
+
* a post with no date is an authoring mistake worth SEEING, not
|
|
94
|
+
* hiding.
|
|
95
|
+
*/
|
|
96
|
+
export declare function buildBlogData(pages: ExportPage[], opts: {
|
|
97
|
+
basePath: string;
|
|
98
|
+
urlPrefix?: string;
|
|
99
|
+
/** `content.section`, threaded exactly as taxonomy.ts threads it. */
|
|
100
|
+
section?: string;
|
|
101
|
+
/** `excludeFromRoutes` predicate — see makeSlugExcluder. */
|
|
102
|
+
excludeSlug?: (slug: string) => boolean;
|
|
103
|
+
config: BlogEmitConfig;
|
|
104
|
+
}): BlogData;
|
|
105
|
+
/**
|
|
106
|
+
* Prev/next per post slug, in TIME order — `prev` older, `next` newer.
|
|
107
|
+
*
|
|
108
|
+
* The footer renders prev on the left and next on the right, so this is
|
|
109
|
+
* the direction a timeline runs: older behind you, newer ahead. It also
|
|
110
|
+
* matches how a series is read. `data.posts` is newest-first for the
|
|
111
|
+
* index listing, so the neighbours are deliberately taken the other way
|
|
112
|
+
* round from the array order.
|
|
113
|
+
*
|
|
114
|
+
* The first version had `prev` as the NEWER neighbour, reasoning that a
|
|
115
|
+
* blog archive is browsed backwards. That made the right-hand link walk
|
|
116
|
+
* a reader BACKWARDS through a series — part 1's "next" was nothing, and
|
|
117
|
+
* its left-hand link was part 2.
|
|
118
|
+
*/
|
|
119
|
+
export declare function blogAdjacency(data: BlogData): Map<string, BlogAdjacency>;
|
|
120
|
+
/** Number of index pages at the configured page size. */
|
|
121
|
+
export declare function indexPageCount(data: BlogData): number;
|
|
122
|
+
/**
|
|
123
|
+
* Emit `src/data/blog.json` plus the index route(s).
|
|
124
|
+
*
|
|
125
|
+
* Page 1 lives at the index path; subsequent pages at
|
|
126
|
+
* `<indexPath>/page/<n>`. Data JSON is overwritten unconditionally —
|
|
127
|
+
* it is pure derived data — while route files respect the marker.
|
|
128
|
+
*/
|
|
129
|
+
export declare function emitBlogFiles(outputDir: string, data: BlogData, config: BlogEmitConfig): {
|
|
130
|
+
routes: string[];
|
|
131
|
+
skipped: string[];
|
|
132
|
+
pruned: string[];
|
|
133
|
+
};
|
|
134
|
+
//# sourceMappingURL=blog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blog.d.ts","sourceRoot":"","sources":["../src/blog.ts"],"names":[],"mappings":"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"}
|
package/dist/blog.js
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blog emission for `dogsbay site build`.
|
|
3
|
+
*
|
|
4
|
+
* Dogsbay has no blog primitive; this adds one. See
|
|
5
|
+
* plans/blog-capability.md.
|
|
6
|
+
*
|
|
7
|
+
* The shape deliberately mirrors `taxonomy.ts`, which already solves
|
|
8
|
+
* the same problem — turn a page set into a generated list page:
|
|
9
|
+
*
|
|
10
|
+
* - `src/data/blog.json` — the post list, newest first
|
|
11
|
+
* - `src/pages<indexPath>/…` — the reverse-chron index route(s)
|
|
12
|
+
*
|
|
13
|
+
* A blog index is that emitter sorted by DATE rather than grouped by
|
|
14
|
+
* term. Route files carry the auto-generated marker and are left alone
|
|
15
|
+
* once a writer edits them, exactly as taxonomy routes are.
|
|
16
|
+
*
|
|
17
|
+
* Two things here are not obvious and are load-bearing:
|
|
18
|
+
*
|
|
19
|
+
* - **Adjacency is chronological, not navigational.** `getPagination`
|
|
20
|
+
* in `@dogsbay/docs-layout` walks `NavItem[]`, i.e. sidebar order. A
|
|
21
|
+
* blog has no sidebar and its prev/next is "the post before this
|
|
22
|
+
* one in time". Post pages get their links from {@link blogAdjacency}
|
|
23
|
+
* instead.
|
|
24
|
+
* - **Reading time is computed HERE, at emit time.** The word count
|
|
25
|
+
* never changes after the build, so doing it in the browser would
|
|
26
|
+
* re-walk the DOM on every page load for a constant.
|
|
27
|
+
*/
|
|
28
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
29
|
+
import { dirname, join } from "node:path";
|
|
30
|
+
import { basePathSegments, joinBaseUrl, withBasePath } from "./base-path.js";
|
|
31
|
+
import { derivePageDescription, textOfNode } from "./taxonomy.js";
|
|
32
|
+
/** Words per minute used for reading-time estimates. */
|
|
33
|
+
const WORDS_PER_MINUTE = 200;
|
|
34
|
+
/** Marker that makes an emitted route file safe to regenerate. */
|
|
35
|
+
const AUTO_GENERATED_MARKER = "// AUTO-GENERATED by dogsbay";
|
|
36
|
+
/**
|
|
37
|
+
* The marker `emitAstroPages` puts on its generated ROUTE STUBS (the root
|
|
38
|
+
* redirect). Matched exactly, because content pages carry a different
|
|
39
|
+
* banner — `AUTO-GENERATED by \`dogsbay convert\`` — and treating that as
|
|
40
|
+
* regenerable meant a real `content/index.md` was silently overwritten by
|
|
41
|
+
* the blog index AND excluded from the post list. The page just vanished.
|
|
42
|
+
*/
|
|
43
|
+
const STUB_MARKER = "// AUTO-GENERATED by dogsbay site build";
|
|
44
|
+
/** Defaults for a `blog:` block that omits fields. */
|
|
45
|
+
export const BLOG_DEFAULTS = {
|
|
46
|
+
indexPath: "/",
|
|
47
|
+
postsPerPage: 20,
|
|
48
|
+
rss: true,
|
|
49
|
+
dateField: "created",
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Count words in a page's body.
|
|
53
|
+
*
|
|
54
|
+
* Deliberately crude: whitespace-split on the extracted plain text.
|
|
55
|
+
* Reading time is a reading CUE, not a measurement, and a more precise
|
|
56
|
+
* count would not change the rendered minutes.
|
|
57
|
+
*/
|
|
58
|
+
export function countWords(page) {
|
|
59
|
+
const text = (page.tree ?? [])
|
|
60
|
+
.map((node) => textOfNode(node))
|
|
61
|
+
.join(" ")
|
|
62
|
+
.replace(/\s+/g, " ")
|
|
63
|
+
.trim();
|
|
64
|
+
if (text.length === 0)
|
|
65
|
+
return 0;
|
|
66
|
+
return text.split(" ").length;
|
|
67
|
+
}
|
|
68
|
+
/** Whole minutes at {@link WORDS_PER_MINUTE}, never less than 1. */
|
|
69
|
+
export function readingMinutes(page) {
|
|
70
|
+
const words = countWords(page);
|
|
71
|
+
if (words === 0)
|
|
72
|
+
return 1;
|
|
73
|
+
return Math.max(1, Math.round(words / WORDS_PER_MINUTE));
|
|
74
|
+
}
|
|
75
|
+
function firstString(value) {
|
|
76
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Build the post list from the page set.
|
|
80
|
+
*
|
|
81
|
+
* EVERY page is a post except the one that would occupy the index
|
|
82
|
+
* route itself. A site that declares `blog:` IS a blog; there is no
|
|
83
|
+
* second "is this a post?" axis to configure, and inventing one would
|
|
84
|
+
* mean a post silently vanishing because a field was missing.
|
|
85
|
+
*
|
|
86
|
+
* Undated posts sort last, in title order, rather than being dropped —
|
|
87
|
+
* a post with no date is an authoring mistake worth SEEING, not
|
|
88
|
+
* hiding.
|
|
89
|
+
*/
|
|
90
|
+
export function buildBlogData(pages, opts) {
|
|
91
|
+
const { basePath, section, config } = opts;
|
|
92
|
+
// URLs use the COMBINED prefix (urlBase + basePath) — the path the host
|
|
93
|
+
// actually serves under. Falling back to basePath keeps callers that
|
|
94
|
+
// only have the one value working, but a mounted site MUST pass
|
|
95
|
+
// urlPrefix or every post link drops the mount and lands on whatever
|
|
96
|
+
// owns the host root.
|
|
97
|
+
const urlPrefix = opts.urlPrefix ?? basePath;
|
|
98
|
+
const posts = [];
|
|
99
|
+
for (const page of pages) {
|
|
100
|
+
// The index route is generated from the post list; a page at that
|
|
101
|
+
// slug would be listing itself.
|
|
102
|
+
if (page.slug === "index" && config.indexPath === "/")
|
|
103
|
+
continue;
|
|
104
|
+
// Only pages that BECOME routes can be posts. emitAstroPages skips
|
|
105
|
+
// fragments and excludeFromRoutes matches, so listing them produced
|
|
106
|
+
// index cards and Newer/Older links to pages that never existed.
|
|
107
|
+
const fm = (page.frontmatter ?? {});
|
|
108
|
+
if (page.fragment === true || fm._fragment === true)
|
|
109
|
+
continue;
|
|
110
|
+
if (opts.excludeSlug?.(page.slug))
|
|
111
|
+
continue;
|
|
112
|
+
const meta = page.meta ?? {};
|
|
113
|
+
const date = meta[config.dateField];
|
|
114
|
+
const updated = meta.updated;
|
|
115
|
+
posts.push({
|
|
116
|
+
slug: page.slug,
|
|
117
|
+
title: page.title ?? page.slug,
|
|
118
|
+
// section threaded like taxonomy.ts's pageUrl — without it, a
|
|
119
|
+
// sectioned build linked /slug/ while the page lives at
|
|
120
|
+
// /section/slug/, so every blog link 404'd.
|
|
121
|
+
url: joinBaseUrl(urlPrefix, section, page.slug),
|
|
122
|
+
description: derivePageDescription(page),
|
|
123
|
+
...(typeof date === "string" ? { date } : {}),
|
|
124
|
+
...(typeof updated === "string" && updated !== date ? { updated } : {}),
|
|
125
|
+
...(meta.author ? { author: meta.author } : {}),
|
|
126
|
+
...(meta.tags ? { tags: meta.tags } : {}),
|
|
127
|
+
...(firstString(fm.heroImage) ? { heroImage: firstString(fm.heroImage) } : {}),
|
|
128
|
+
readingMinutes: readingMinutes(page),
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
posts.sort((a, b) => {
|
|
132
|
+
if (a.date && b.date)
|
|
133
|
+
return a.date < b.date ? 1 : a.date > b.date ? -1 : 0;
|
|
134
|
+
if (a.date)
|
|
135
|
+
return -1;
|
|
136
|
+
if (b.date)
|
|
137
|
+
return 1;
|
|
138
|
+
return a.title.localeCompare(b.title);
|
|
139
|
+
});
|
|
140
|
+
return {
|
|
141
|
+
indexPath: config.indexPath === "/"
|
|
142
|
+
? urlPrefix || "/"
|
|
143
|
+
: withBasePath(urlPrefix, config.indexPath),
|
|
144
|
+
basePath,
|
|
145
|
+
...(section ? { section } : {}),
|
|
146
|
+
urlPrefix,
|
|
147
|
+
postsPerPage: config.postsPerPage,
|
|
148
|
+
posts,
|
|
149
|
+
postCount: posts.length,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Prev/next per post slug, in TIME order — `prev` older, `next` newer.
|
|
154
|
+
*
|
|
155
|
+
* The footer renders prev on the left and next on the right, so this is
|
|
156
|
+
* the direction a timeline runs: older behind you, newer ahead. It also
|
|
157
|
+
* matches how a series is read. `data.posts` is newest-first for the
|
|
158
|
+
* index listing, so the neighbours are deliberately taken the other way
|
|
159
|
+
* round from the array order.
|
|
160
|
+
*
|
|
161
|
+
* The first version had `prev` as the NEWER neighbour, reasoning that a
|
|
162
|
+
* blog archive is browsed backwards. That made the right-hand link walk
|
|
163
|
+
* a reader BACKWARDS through a series — part 1's "next" was nothing, and
|
|
164
|
+
* its left-hand link was part 2.
|
|
165
|
+
*/
|
|
166
|
+
export function blogAdjacency(data) {
|
|
167
|
+
const out = new Map();
|
|
168
|
+
data.posts.forEach((post, i) => {
|
|
169
|
+
// posts[] is newest-first, so the OLDER neighbour is the next index.
|
|
170
|
+
const prev = data.posts[i + 1];
|
|
171
|
+
const next = data.posts[i - 1];
|
|
172
|
+
out.set(post.slug, {
|
|
173
|
+
...(prev ? { prev: { title: prev.title, href: prev.url } } : {}),
|
|
174
|
+
...(next ? { next: { title: next.title, href: next.url } } : {}),
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Regenerate an emitted route unless a writer has taken it over.
|
|
181
|
+
*
|
|
182
|
+
* Same contract as `taxonomy.ts`: the marker means "ours, safe to
|
|
183
|
+
* overwrite"; its absence means someone edited the file and their work
|
|
184
|
+
* wins. A content-derived page also counts as ours — it carries a
|
|
185
|
+
* `dogsbay convert` banner — so a scaffold's placeholder index does not
|
|
186
|
+
* block the blog index from claiming the route.
|
|
187
|
+
*/
|
|
188
|
+
function shouldRegenerate(path) {
|
|
189
|
+
if (!existsSync(path))
|
|
190
|
+
return true;
|
|
191
|
+
const contents = readFileSync(path, "utf-8");
|
|
192
|
+
// Ours (a previous blog index) or a generated route stub — replaceable.
|
|
193
|
+
// Anything else, INCLUDING a content page, belongs to the author.
|
|
194
|
+
return contents.includes(AUTO_GENERATED_MARKER) || contents.includes(STUB_MARKER);
|
|
195
|
+
}
|
|
196
|
+
/** Number of index pages at the configured page size. */
|
|
197
|
+
export function indexPageCount(data) {
|
|
198
|
+
return Math.max(1, Math.ceil(data.posts.length / data.postsPerPage));
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Emit `src/data/blog.json` plus the index route(s).
|
|
202
|
+
*
|
|
203
|
+
* Page 1 lives at the index path; subsequent pages at
|
|
204
|
+
* `<indexPath>/page/<n>`. Data JSON is overwritten unconditionally —
|
|
205
|
+
* it is pure derived data — while route files respect the marker.
|
|
206
|
+
*/
|
|
207
|
+
export function emitBlogFiles(outputDir, data, config) {
|
|
208
|
+
mkdirSync(join(outputDir, "src", "data"), { recursive: true });
|
|
209
|
+
writeFileSync(join(outputDir, "src", "data", "blog.json"), JSON.stringify(data, null, 2));
|
|
210
|
+
const routes = [];
|
|
211
|
+
const skipped = [];
|
|
212
|
+
// Routes live where the PAGES live: <basePath>/<section>/… . Missing
|
|
213
|
+
// the section put the index at the site root while every post sat one
|
|
214
|
+
// level down.
|
|
215
|
+
const segments = [
|
|
216
|
+
...basePathSegments(data.basePath),
|
|
217
|
+
...(data.section ? basePathSegments(`/${data.section}`) : []),
|
|
218
|
+
];
|
|
219
|
+
const indexSegments = config.indexPath === "/" ? [] : basePathSegments(config.indexPath);
|
|
220
|
+
const total = indexPageCount(data);
|
|
221
|
+
for (let pageNo = 1; pageNo <= total; pageNo++) {
|
|
222
|
+
const parts = pageNo === 1
|
|
223
|
+
? [...segments, ...indexSegments, "index.astro"]
|
|
224
|
+
: [...segments, ...indexSegments, "page", `${pageNo}.astro`];
|
|
225
|
+
const routePath = join(outputDir, "src", "pages", ...parts);
|
|
226
|
+
if (!shouldRegenerate(routePath)) {
|
|
227
|
+
skipped.push(routePath);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
mkdirSync(dirname(routePath), { recursive: true });
|
|
231
|
+
writeFileSync(routePath, buildIndexRoute(pageNo, total));
|
|
232
|
+
routes.push(routePath);
|
|
233
|
+
}
|
|
234
|
+
// Prune pagination routes the current post count no longer needs.
|
|
235
|
+
// Without this, deleting posts (or raising postsPerPage) left the
|
|
236
|
+
// previous build's page/3.astro in place: it still compiles, slices
|
|
237
|
+
// past the end of the list, and serves a live "No posts yet. Page 3
|
|
238
|
+
// of 2".
|
|
239
|
+
const pruned = [];
|
|
240
|
+
for (let stale = total + 1;; stale++) {
|
|
241
|
+
const stalePath = join(outputDir, "src", "pages", ...segments, ...indexSegments, "page", `${stale}.astro`);
|
|
242
|
+
if (!existsSync(stalePath))
|
|
243
|
+
break;
|
|
244
|
+
if (!shouldRegenerate(stalePath))
|
|
245
|
+
break; // writer took it over
|
|
246
|
+
rmSync(stalePath, { force: true });
|
|
247
|
+
pruned.push(stalePath);
|
|
248
|
+
}
|
|
249
|
+
return { routes, skipped, pruned };
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* One index route file.
|
|
253
|
+
*
|
|
254
|
+
* Reads the emitted JSON at build time (not runtime) and hands a slice
|
|
255
|
+
* to `<BlogIndex>`. Kept deliberately thin: all layout lives in the
|
|
256
|
+
* component, so a template fix propagates on rebuild rather than being
|
|
257
|
+
* baked into every generated route.
|
|
258
|
+
*/
|
|
259
|
+
function buildIndexRoute(pageNo, totalPages) {
|
|
260
|
+
// wideLayout: blog chrome centres prose at max-w-3xl, which is right for
|
|
261
|
+
// a post and far too narrow for a three-across card grid. The index is
|
|
262
|
+
// the one blog route that wants the full measure.
|
|
263
|
+
//
|
|
264
|
+
// `@/` alias throughout, not relative paths — page 1 and page N sit at
|
|
265
|
+
// different depths under src/pages, and a hardcoded `../../` is correct
|
|
266
|
+
// for exactly one of them. Matches the taxonomy route template.
|
|
267
|
+
return `---
|
|
268
|
+
${AUTO_GENERATED_MARKER} site build — safe to delete; will regenerate next build.
|
|
269
|
+
// To customize this page, remove the marker line above (or modify the file
|
|
270
|
+
// in any way that drops it) and dogsbay will leave your edits alone.
|
|
271
|
+
import "@/styles/global.css";
|
|
272
|
+
import DocsLayout from "@dogsbay/docs-layout/DocsLayout.astro";
|
|
273
|
+
import BlogIndex from "@dogsbay/docs-layout/BlogIndex.astro";
|
|
274
|
+
import type { SiteConfig } from "@dogsbay/types";
|
|
275
|
+
import blog from "@/data/blog.json";
|
|
276
|
+
import navData from "@/data/nav.json";
|
|
277
|
+
import siteConfigData from "@/data/site.json";
|
|
278
|
+
const siteConfig = siteConfigData as SiteConfig;
|
|
279
|
+
// Read untyped: favicon is site.json data, not part of the published
|
|
280
|
+
// SiteConfig type, so this needs no @dogsbay/types release.
|
|
281
|
+
const favicon = (siteConfigData as { favicon?: string }).favicon;
|
|
282
|
+
const pageNo = ${pageNo};
|
|
283
|
+
const totalPages = ${totalPages};
|
|
284
|
+
const start = (pageNo - 1) * blog.postsPerPage;
|
|
285
|
+
const posts = blog.posts.slice(start, start + blog.postsPerPage);
|
|
286
|
+
const title = pageNo === 1 ? siteConfig.siteName : \`\${siteConfig.siteName} — page \${pageNo}\`;
|
|
287
|
+
---
|
|
288
|
+
<DocsLayout
|
|
289
|
+
siteName={siteConfig.siteName}
|
|
290
|
+
title={title}
|
|
291
|
+
nav={navData as any[]}
|
|
292
|
+
headings={[]}
|
|
293
|
+
siteUrl={siteConfig.siteUrl || "/"}
|
|
294
|
+
description={siteConfig.description || undefined}
|
|
295
|
+
ogImage={siteConfig.ogImage || undefined}
|
|
296
|
+
twitterHandle={siteConfig.twitterHandle || undefined}
|
|
297
|
+
themeColor={siteConfig.themeColor || undefined}
|
|
298
|
+
repoUrl={siteConfig.repoUrl || undefined}
|
|
299
|
+
plausibleDomain={siteConfig.plausible?.domain}
|
|
300
|
+
plausibleScriptUrl={siteConfig.plausible?.scriptUrl}
|
|
301
|
+
tagPrefixes={siteConfig.tagPrefixes}
|
|
302
|
+
tagLabels={siteConfig.tagLabels}
|
|
303
|
+
taxonomyIndexPaths={siteConfig.taxonomyIndexPaths}
|
|
304
|
+
chrome="blog"
|
|
305
|
+
favicon={favicon}
|
|
306
|
+
basePath={blog.urlPrefix}
|
|
307
|
+
autoH1={false}
|
|
308
|
+
wideLayout={true}
|
|
309
|
+
>
|
|
310
|
+
<BlogIndex
|
|
311
|
+
posts={posts}
|
|
312
|
+
indexPath={blog.indexPath}
|
|
313
|
+
pageNo={pageNo}
|
|
314
|
+
totalPages={totalPages}
|
|
315
|
+
/>
|
|
316
|
+
</DocsLayout>
|
|
317
|
+
`;
|
|
318
|
+
}
|
|
319
|
+
//# sourceMappingURL=blog.js.map
|
package/dist/blog.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blog.js","sourceRoot":"","sources":["../src/blog.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diff decorations — DiffTree marks rendered through the NORMAL
|
|
3
|
+
* serializer (plans/tree-diff-rendering.md §2).
|
|
4
|
+
*
|
|
5
|
+
* `@dogsbay/tree-diff` annotates nodes with structural fields
|
|
6
|
+
* (`diff`, `diffInline`, `diffPeer`, `diffMovedFrom`); this module
|
|
7
|
+
* reads them STRUCTURALLY (no dependency on tree-diff) and decorates
|
|
8
|
+
* the serializer's output:
|
|
9
|
+
*
|
|
10
|
+
* - block marks → a `data-diff` attribute injected into the
|
|
11
|
+
* rendered snippet's first opening tag (attribute injection works
|
|
12
|
+
* uniformly for components, table rows, and plain tags — no
|
|
13
|
+
* invalid wrapper elements inside tables/tabs), styled by the
|
|
14
|
+
* generated theme CSS;
|
|
15
|
+
* - changed prose → the word-level segments render as
|
|
16
|
+
* `<ins>/<del>` runs (the merged reading view);
|
|
17
|
+
* - changed code → a before/after pair: the old side renders
|
|
18
|
+
* through the same code renderer, labeled, above the new side.
|
|
19
|
+
*
|
|
20
|
+
* A tree with no marks renders byte-identically to before — the
|
|
21
|
+
* decoration layer is invisible until a DiffTree arrives.
|
|
22
|
+
*/
|
|
23
|
+
import type { TreeNode } from "@dogsbay/types";
|
|
24
|
+
/** Structural mirror of @dogsbay/tree-diff's DiffNode fields. */
|
|
25
|
+
export interface DiffMarks {
|
|
26
|
+
diff?: "added" | "removed" | "changed" | "moved";
|
|
27
|
+
diffInline?: {
|
|
28
|
+
op: "eq" | "ins" | "del";
|
|
29
|
+
text: string;
|
|
30
|
+
}[];
|
|
31
|
+
diffPeer?: TreeNode;
|
|
32
|
+
diffMovedFrom?: string[];
|
|
33
|
+
}
|
|
34
|
+
export declare const diffMarksOf: (node: TreeNode) => DiffMarks;
|
|
35
|
+
export declare function hasDiffMarks(node: TreeNode): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Inject attributes into the FIRST opening tag of a rendered
|
|
38
|
+
* snippet. Handles components (`<TableRow`), plain tags (`<p`), and
|
|
39
|
+
* skips leading whitespace/JSX expressions.
|
|
40
|
+
*/
|
|
41
|
+
export declare function injectAttrs(rendered: string, attrs: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* Render the merged word-level segments of a changed prose node.
|
|
44
|
+
* Formatting inside changed prose flattens to text in this view —
|
|
45
|
+
* the marks are the content (the unchanged siblings keep their full
|
|
46
|
+
* inline rendering).
|
|
47
|
+
*/
|
|
48
|
+
export declare function inlineSegmentsToHtml(segments: NonNullable<DiffMarks["diffInline"]>): string;
|
|
49
|
+
export interface MergedCodeDiff {
|
|
50
|
+
/** Unified code: unchanged lines, removed lines, added lines. */
|
|
51
|
+
code: string;
|
|
52
|
+
/** 1-based line numbers, for CodeRich's diffAdd/diffRemove props. */
|
|
53
|
+
addLines: number[];
|
|
54
|
+
removeLines: number[];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Merge two code texts into ONE unified block, reporting the changed
|
|
58
|
+
* lines BY NUMBER rather than by `[!code]` comment markers.
|
|
59
|
+
*
|
|
60
|
+
* Markers were the first design and they leak: Shiki can only strip a
|
|
61
|
+
* marker it recognizes as a comment in the grammar, so in `plaintext`
|
|
62
|
+
* — which every unknown language (`package-install`, doc-tool tokens)
|
|
63
|
+
* falls back to — the marker renders as literal text in the reader's
|
|
64
|
+
* code. Line numbers are language-independent and can't leak.
|
|
65
|
+
*
|
|
66
|
+
* Returns null over the line cap; callers fall back to before/after.
|
|
67
|
+
*/
|
|
68
|
+
export declare function buildMergedCodeDiff(oldCode: string, newCode: string): MergedCodeDiff | null;
|
|
69
|
+
/**
|
|
70
|
+
* Decorate one rendered node. `renderPeer` renders the other side of
|
|
71
|
+
* a changed pair through the same serializer (code before/after).
|
|
72
|
+
*/
|
|
73
|
+
export declare function decorateDiff(node: TreeNode, rendered: string, renderPeer: (peer: TreeNode) => string): string;
|
|
74
|
+
/**
|
|
75
|
+
* The diff stylesheet the generated site carries — theme-token based,
|
|
76
|
+
* both color modes, no hardcoded palette (key-decisions doctrine).
|
|
77
|
+
*/
|
|
78
|
+
export declare const DIFF_CSS = "/* tree-diff decorations \u2014 one DiffTree, rendered by the normal components.\n Mark styling is GUARDED by html:not([data-diff-hidden]) rather than\n force-reset under hidden mode: a reset like \"border-left: none\n !important\" also clobbers a component's OWN border (an added warning\n callout lost its left edge \u2014 user find, adapters/mysql). Guarded\n rules simply stop applying, and component styling survives. */\n[data-diff] { position: relative; }\nhtml:not([data-diff-hidden]) [data-diff=\"added\"] {\n background: color-mix(in oklch, var(--color-success, oklch(0.72 0.19 150)) 12%, transparent);\n border-left: 3px solid var(--color-success, oklch(0.72 0.19 150));\n padding-left: 0.75rem;\n}\nhtml:not([data-diff-hidden]) [data-diff=\"removed\"] {\n background: color-mix(in oklch, var(--color-destructive, oklch(0.64 0.21 25)) 10%, transparent);\n border-left: 3px solid var(--color-destructive, oklch(0.64 0.21 25));\n padding-left: 0.75rem;\n opacity: 0.75;\n}\nhtml:not([data-diff-hidden]) [data-diff=\"removed\"] :is(p, li, code, pre) { text-decoration: line-through; text-decoration-thickness: 1px; }\nhtml:not([data-diff-hidden]) [data-diff=\"changed\"] {\n border-left: 3px solid var(--color-warning, oklch(0.8 0.16 85));\n padding-left: 0.75rem;\n}\nhtml:not([data-diff-hidden]) [data-diff=\"moved\"] {\n border-left: 3px dashed var(--color-info, oklch(0.7 0.14 240));\n padding-left: 0.75rem;\n}\nhtml:not([data-diff-hidden]) [data-diff=\"moved\"][data-diff-from]::after {\n content: \"moved from \" attr(data-diff-from);\n display: block;\n font-size: 0.75rem;\n color: var(--color-muted-foreground, inherit);\n opacity: 0.8;\n}\n\n/* Explicit \"added\"/\"removed\" label pills straddling the block's top\n edge. Components with their own chrome (callouts, code blocks)\n camouflage a tint-only mark \u2014 a green-tinted warning callout reads\n as a callout variant, not as new content (user feedback,\n better-auth adapters/mysql). Table rows are excluded (a pill can't\n straddle a tr); nested marks defer to the outermost label. */\nhtml:not([data-diff-hidden]) [data-diff=\"added\"]:not(tr, th, td)::before,\nhtml:not([data-diff-hidden]) [data-diff=\"removed\"]:not(tr, th, td)::before {\n position: absolute;\n top: 0;\n right: 0.6rem;\n transform: translateY(-55%);\n font-size: 0.65rem;\n font-weight: 600;\n letter-spacing: 0.04em;\n line-height: 1.4;\n padding: 0 0.5rem;\n border-radius: 99px;\n z-index: 1;\n text-decoration: none;\n}\nhtml:not([data-diff-hidden]) [data-diff=\"added\"]:not(tr, th, td)::before {\n content: \"added\";\n background: color-mix(in oklch, var(--color-success, oklch(0.72 0.19 150)) 25%, var(--color-background, Canvas));\n border: 1px solid color-mix(in oklch, var(--color-success, oklch(0.72 0.19 150)) 45%, transparent);\n}\nhtml:not([data-diff-hidden]) [data-diff=\"removed\"]:not(tr, th, td)::before {\n content: \"removed\";\n background: color-mix(in oklch, var(--color-destructive, oklch(0.64 0.21 25)) 20%, var(--color-background, Canvas));\n border: 1px solid color-mix(in oklch, var(--color-destructive, oklch(0.64 0.21 25)) 45%, transparent);\n}\n[data-diff] [data-diff]::before { content: none !important; }\n\n/* Nav change markers (the sidebar is client-rendered from nav.json, so\n the styles ship here rather than with the component). Same contract as\n the tab markers: a GLYPH carries the meaning, colour reinforces it,\n visually-hidden text announces it. */\n.db-nav-mark {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: 1rem;\n height: 1rem;\n margin-left: auto;\n border-radius: 99px;\n font-size: 0.75rem;\n font-weight: 700;\n line-height: 1;\n color: var(--color-muted-foreground, currentColor);\n}\n.db-nav-mark[data-nav-mark=\"added\"] { color: var(--color-success, oklch(0.72 0.19 150)); }\n.db-nav-mark[data-nav-mark=\"changed\"] { color: var(--color-warning, oklch(0.8 0.16 85)); }\n.db-nav-mark[data-nav-mark=\"removed\"] { color: var(--color-destructive, oklch(0.64 0.21 25)); }\n.db-nav-mark[data-nav-mark=\"moved\"] { color: var(--color-info, oklch(0.7 0.14 240)); }\n/* a subtree marker is a hint about descendants, not a claim about this row */\n.db-nav-mark[data-nav-mark-subtree] { opacity: 0.55; }\n/* removed pages get a SECOND non-colour signal */\n/* Belt-and-braces: a subtree mark must never strike through its own row. */\n[data-nav-mark-row=\"removed\"]:not([data-nav-mark-subtree]) { text-decoration: line-through; opacity: 0.7; }\n@media (forced-colors: active) {\n .db-nav-mark { color: CanvasText; }\n}\n\n/* Which tab changed? A marked panel is invisible until opened, so its\n trigger carries a marker (the group's bar says \"in here\", the marker\n says \"this one\"). The marker is a GLYPH, not a colored dot: colour\n alone fails WCAG 1.4.1 and background-tinted dots vanish entirely in\n forced-colors mode. Colour reinforces; shape carries the meaning.\n Visually-hidden text rides along (emitted by the serializer). */\n.db-tab-mark {\n display: inline-block;\n margin-left: 0.35rem;\n font-size: 0.75rem;\n font-weight: 700;\n line-height: 1;\n vertical-align: middle;\n color: var(--color-muted-foreground, currentColor);\n}\n[data-diff-hidden] .db-tab-mark { display: none; }\n.db-tab-mark[data-diff-tab=\"changed\"] { color: var(--color-warning, oklch(0.8 0.16 85)); }\n.db-tab-mark[data-diff-tab=\"added\"] { color: var(--color-success, oklch(0.72 0.19 150)); }\n.db-tab-mark[data-diff-tab=\"removed\"] { color: var(--color-destructive, oklch(0.64 0.21 25)); }\n.db-tab-mark[data-diff-tab=\"moved\"] { color: var(--color-info, oklch(0.7 0.14 240)); }\n@media (forced-colors: active) {\n .db-tab-mark { color: CanvasText; }\n}\nins.db-diff-ins {\n text-decoration: none;\n background: color-mix(in oklch, var(--color-success, oklch(0.72 0.19 150)) 22%, transparent);\n border-radius: 2px;\n padding: 0 1px;\n}\ndel.db-diff-del {\n background: color-mix(in oklch, var(--color-destructive, oklch(0.64 0.21 25)) 18%, transparent);\n border-radius: 2px;\n padding: 0 1px;\n}\n.db-diff-pair { display: grid; gap: 0.25rem; margin-block: 1rem; }\n.db-diff-pair-label {\n font-size: 0.7rem;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--color-muted-foreground, inherit);\n opacity: 0.8;\n}\ntr[data-diff] { border-left-width: 3px; }\n\n/* Folded unchanged sections are CHROME, not content callouts:\n dashed, dimmed, compact \u2014 visually \"collapsed context\". */\n.db-diff-fold {\n border: 1px dashed color-mix(in oklch, currentColor 25%, transparent) !important;\n border-left-width: 1px !important;\n background: transparent !important;\n opacity: 0.7;\n margin-block: 0.5rem !important;\n}\n.db-diff-fold:hover, .db-diff-fold[open] { opacity: 1; }\n.db-diff-fold summary, .db-diff-fold [data-slot=\"trigger\"] {\n font-size: 0.85rem;\n}\n\n/* The comparison banner at the top of each reader page. */\n.db-diff-banner-row { display: flex; flex-wrap: wrap; gap: 0.4rem 0.8rem; align-items: center; width: 100%; }\n.db-diff-banner {\n border: 1px solid color-mix(in oklch, currentColor 15%, transparent);\n border-radius: 8px;\n padding: 0.75rem 1rem;\n margin-block-end: 1.5rem;\n font-size: 0.85rem;\n display: flex;\n flex-wrap: wrap;\n gap: 0.4rem 1rem;\n align-items: center;\n}\n.db-diff-banner .db-chip {\n display: inline-block;\n border-radius: 99px;\n padding: 0.05rem 0.6rem;\n font-size: 0.75rem;\n border: 1px solid transparent;\n}\n.db-chip-added { background: color-mix(in oklch, var(--color-success, oklch(0.72 0.19 150)) 15%, transparent); }\n.db-chip-removed { background: color-mix(in oklch, var(--color-destructive, oklch(0.64 0.21 25)) 12%, transparent); }\n.db-chip-changed { background: color-mix(in oklch, var(--color-warning, oklch(0.8 0.16 85)) 15%, transparent); }\n.db-chip-moved { background: color-mix(in oklch, var(--color-info, oklch(0.7 0.14 240)) 12%, transparent); }\n.db-chip-muted { border-color: color-mix(in oklch, currentColor 25%, transparent); opacity: 0.8; }\n\n/* The persistent view-switcher bar (Reader / Docs-ops / Agent):\n perspectives on ONE comparison \u2014 tabs are links, verification\n lives in the chrome, the Monitor CTA sits next to the value. */\n.db-cmp-bar {\n position: sticky;\n /* BELOW the docs header, not under it: both were sticky at top:0 and\n the header's higher z-index simply swallowed this bar on scroll \u2014\n the chrome carrying the trust contract was the chrome that vanished.\n --db-header-height is declared by DocsLayout (the header sizes itself\n from the same variable, so they cannot drift). */\n top: var(--db-header-height, 3.25rem);\n z-index: 30;\n display: flex;\n flex-wrap: wrap;\n gap: 0.4rem 0.9rem;\n align-items: center;\n padding: 0.5rem 0.9rem;\n margin-block-end: 1rem;\n border: 1px solid color-mix(in oklch, currentColor 15%, transparent);\n border-radius: 8px;\n background: var(--color-background, Canvas);\n font-size: 0.8rem;\n}\n.db-cmp-bar .db-chip {\n display: inline-block;\n border-radius: 99px;\n padding: 0.05rem 0.6rem;\n font-size: 0.75rem;\n border: 1px solid transparent;\n}\n.db-cmp-bar button.db-chip { cursor: pointer; background: transparent; font: inherit; font-size: 0.75rem; color: inherit; }\n.db-cmp-tabs {\n display: inline-flex;\n gap: 0.15rem;\n border: 1px solid color-mix(in oklch, currentColor 20%, transparent);\n border-radius: 99px;\n padding: 0.1rem;\n}\n.db-cmp-tab {\n padding: 0.1rem 0.7rem;\n border-radius: 99px;\n text-decoration: none;\n color: inherit;\n}\n.db-cmp-tab[aria-current=\"page\"] {\n background: color-mix(in oklch, currentColor 12%, transparent);\n font-weight: 600;\n}\n.db-cmp-pairs { position: relative; display: inline-block; }\n.db-cmp-pairs summary { cursor: pointer; }\n.db-cmp-pairs ul {\n position: absolute;\n top: 100%;\n left: 0;\n margin: 0.25rem 0 0;\n padding: 0.35rem 0.75rem;\n list-style: none;\n background: var(--color-background, Canvas);\n border: 1px solid color-mix(in oklch, currentColor 20%, transparent);\n border-radius: 8px;\n white-space: nowrap;\n z-index: 40;\n}\n.db-cmp-monitor { text-decoration: none; border-color: color-mix(in oklch, currentColor 30%, transparent) !important; }\n.db-cmp-monitor:hover { background: color-mix(in oklch, currentColor 10%, transparent); }\n\n/* A wholly added/removed PAGE carries one mark around all its content.\n The per-block tint would be overwhelming at page scale, so page level\n gets a stronger edge and a much lighter wash \u2014 legible at a glance,\n still readable as prose. The \"added\"/\"removed\" pill (the ::before on\n [data-diff]) rides on this wrapper, which is exactly right: it labels\n the page, once. */\nhtml:not([data-diff-hidden]) [data-diff].db-diff-page {\n border-left-width: 5px;\n padding-left: 1rem;\n padding-block: 0.5rem;\n border-radius: 0 6px 6px 0;\n}\nhtml:not([data-diff-hidden]) [data-diff=\"added\"].db-diff-page {\n background: color-mix(in oklch, var(--color-success, oklch(0.72 0.19 150)) 5%, transparent);\n}\nhtml:not([data-diff-hidden]) [data-diff=\"removed\"].db-diff-page {\n background: color-mix(in oklch, var(--color-destructive, oklch(0.64 0.21 25)) 5%, transparent);\n opacity: 1; /* the page is READABLE \u2014 the mark says removed, not the dimming */\n}\n/* \u2026and a removed PAGE is not struck through \u2014 that is for a deleted line,\n not for a document the reader still needs to read. */\n[data-diff=\"removed\"].db-diff-page :is(p, li, code, pre) { text-decoration: none; }\n\n/* Changed raw HTML: the faithful new render, plus the previous version\n one click away. Without it a deletion inside opaque markup (a dropped\n table column) is simply invisible \u2014 the reader is told \"something\n changed\" and shown no evidence. */\n.db-diff-html-before {\n margin-block-start: 0.5rem;\n font-size: 0.8rem;\n border: 1px dashed color-mix(in oklch, currentColor 25%, transparent);\n border-radius: 6px;\n padding: 0.25rem 0.6rem;\n}\n/* Full-strength text: the summary is a CONTROL \u2014 dimming it (opacity or\n muted-foreground) drops it below 4.5:1 and axe rightly fails it. */\n.db-diff-html-before summary { cursor: pointer; color: inherit; }\n.db-diff-html-before[open] summary { margin-block-end: 0.5rem; }\n[data-diff-hidden] .db-diff-html-before { display: none; }\n\n/* Merged code diffs carry a hidden clean twin (the post-change\n render); Hide highlights swaps them. */\n.db-diff-code { position: relative; }\n.db-diff-code-clean { display: none; }\n[data-diff-hidden] .db-diff-code-clean { display: block; }\n[data-diff-hidden] .db-diff-code-merged { display: none; }\n\n/* Whitespace-only code edits are still marked (indentation is\n semantic in YAML/Python/markdown) \u2014 but NAMED, so the reader\n is not hunting an invisible 3-space shift. */\nhtml:not([data-diff-hidden]) .db-diff-code-ws .db-diff-code-merged::before {\n content: \"whitespace only\";\n position: absolute;\n top: 0;\n right: 0.6rem;\n transform: translateY(-55%);\n z-index: 1;\n font-size: 0.65rem;\n font-weight: 600;\n letter-spacing: 0.04em;\n line-height: 1.4;\n padding: 0 0.5rem;\n border-radius: 99px;\n background: color-mix(in oklch, var(--color-warning, oklch(0.8 0.16 85)) 22%, var(--color-background, Canvas));\n border: 1px solid color-mix(in oklch, var(--color-warning, oklch(0.8 0.16 85)) 45%, transparent);\n}\n\n/* Hide highlights \u2014 read the target version clean. One attribute on\n <html>: the guarded mark rules above stop applying by themselves;\n only content that does not exist in the target version (removed\n blocks, del runs, the \"before\" code side) needs hiding, plus the\n ins tint. */\n[data-diff-hidden] :is([data-diff=\"removed\"], del.db-diff-del, .db-diff-pair-label) { display: none !important; }\n[data-diff-hidden] ins.db-diff-ins { background: transparent; padding: 0; }\n";
|
|
79
|
+
//# sourceMappingURL=diff-decoration.d.ts.map
|