svelte-docsmith 0.12.0 → 0.12.2
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/buildtime/cli/archive-version.js +16 -5
- package/dist/buildtime/git.d.ts +30 -2
- package/dist/buildtime/git.js +58 -8
- package/dist/buildtime/vite/index.js +13 -1
- package/dist/buildtime/vite/pages.d.ts +10 -10
- package/dist/buildtime/vite/pages.js +11 -12
- package/dist/components/markdown/pre.svelte +50 -41
- package/package.json +1 -1
|
@@ -13,7 +13,7 @@ import path from 'node:path';
|
|
|
13
13
|
import { assertValidVersionId } from '../../core/version.js';
|
|
14
14
|
import { freezeLastUpdated, isInheritedRouteFile, rewriteDocsLinks } from '../archive.js';
|
|
15
15
|
import { ARCHIVE_MARKER, discoverArchives, markerContents } from '../archives.js';
|
|
16
|
-
import {
|
|
16
|
+
import { commitDates } from '../git.js';
|
|
17
17
|
import { docsBaseFrom } from '../paths.js';
|
|
18
18
|
import { isPageFile } from '../vite/pages.js';
|
|
19
19
|
import { CliError } from './error.js';
|
|
@@ -79,6 +79,14 @@ export function archiveVersion(options, log) {
|
|
|
79
79
|
yield { file, source: path.join(contentDir, path.relative(toDir, file)) };
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
+
// Read the dates before copying, so the walk sees the docs root as it was
|
|
83
|
+
// rather than a tree half full of untracked copies. Say so if they could not
|
|
84
|
+
// be read: an archive is written once and never edited again, so a page that
|
|
85
|
+
// misses its date here keeps the wrong one for good.
|
|
86
|
+
const dates = commitDates(contentDir, (reason) => {
|
|
87
|
+
log(`\n! Could not read commit dates: ${reason}`);
|
|
88
|
+
log(' Archived pages will keep no last-updated date.\n');
|
|
89
|
+
});
|
|
82
90
|
copyCurrent(contentDir, toDir);
|
|
83
91
|
fs.writeFileSync(path.join(toDir, ARCHIVE_MARKER), markerContents(id));
|
|
84
92
|
let pages = 0;
|
|
@@ -87,12 +95,15 @@ export function archiveVersion(options, log) {
|
|
|
87
95
|
continue;
|
|
88
96
|
pages++;
|
|
89
97
|
const text = fs.readFileSync(file, 'utf-8');
|
|
90
|
-
fs.writeFileSync(file, freezeLastUpdated(rewriteDocsLinks(text, { docsBase, versionId: id, archivedIds }),
|
|
98
|
+
fs.writeFileSync(file, freezeLastUpdated(rewriteDocsLinks(text, { docsBase, versionId: id, archivedIds }), dates.get(source)));
|
|
91
99
|
}
|
|
92
100
|
log(`\n✓ Archived the current docs into ${rel(toDir)} (${pages} pages)\n`);
|
|
93
|
-
log('Links were rewritten to stay inside the archive
|
|
94
|
-
|
|
95
|
-
|
|
101
|
+
log('Links were rewritten to stay inside the archive.');
|
|
102
|
+
// Only claim the dates were kept when the walk actually produced some.
|
|
103
|
+
if (dates.size)
|
|
104
|
+
log('Each page kept its real last-updated date.');
|
|
105
|
+
log('Review the diff, then update docsmith({ versions }) so the archive');
|
|
106
|
+
log('is served:\n');
|
|
96
107
|
log(' versions: {');
|
|
97
108
|
log(" current: { id: '<new release>', label: '<new release>' },");
|
|
98
109
|
log(` archived: [{ id: '${id}', label: '${label}' }${archivedIds.size ? ', …' : ''}]`);
|
package/dist/buildtime/git.d.ts
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Commit day (`YYYY-MM-DD`) every tracked file under `dir` last changed, keyed by
|
|
3
|
+
* absolute path. One history walk rather than a `git log` per file: the per-file
|
|
4
|
+
* form costs a process each, which is the build's largest single expense once a
|
|
5
|
+
* site has more than a handful of pages.
|
|
6
|
+
*
|
|
7
|
+
* `--relative` makes git print paths relative to `dir`, so they join straight
|
|
8
|
+
* back onto it with no second call to find the repository root. `--no-renames`
|
|
9
|
+
* makes a rename list both of its paths, which keeps the answer identical to
|
|
10
|
+
* asking about one file at a time.
|
|
11
|
+
*
|
|
12
|
+
* An empty map for a directory outside a repository, or one git could not read.
|
|
13
|
+
* That is the same "no date" every caller already handles, and it is deliberately
|
|
14
|
+
* all-or-nothing: a truncated read would date some pages and not others, which
|
|
15
|
+
* looks like real data and is not.
|
|
16
|
+
*
|
|
17
|
+
* Only a truncated read calls `onFail`, because it is the one failure that would
|
|
18
|
+
* otherwise date some pages and not others. A directory outside a repository, a
|
|
19
|
+
* missing directory, or a machine without git all yield no dates, which every
|
|
20
|
+
* caller already handles and which the plugin already reports where it matters.
|
|
21
|
+
* Reporting is the caller's to do, which is why this takes a callback rather
|
|
22
|
+
* than writing to the console itself.
|
|
23
|
+
*/
|
|
24
|
+
export declare function commitDates(dir: string, onFail?: (reason: string) => void,
|
|
25
|
+
/**
|
|
26
|
+
* Cap on the walk's output. The default is far above any real docs root; it is
|
|
27
|
+
* a parameter so the truncation path can be exercised without a repository
|
|
28
|
+
* large enough to produce 256 MB of history.
|
|
29
|
+
*/
|
|
30
|
+
maxBuffer?: number): Map<string, string>;
|
|
3
31
|
/**
|
|
4
32
|
* Release dates keyed by version, read from the commit that introduced each
|
|
5
33
|
* `## <version>` heading in a changelog. Changesets does not write dates, and a
|
package/dist/buildtime/git.js
CHANGED
|
@@ -12,14 +12,64 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { spawnSync } from 'node:child_process';
|
|
14
14
|
import path from 'node:path';
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Commit day (`YYYY-MM-DD`) every tracked file under `dir` last changed, keyed by
|
|
17
|
+
* absolute path. One history walk rather than a `git log` per file: the per-file
|
|
18
|
+
* form costs a process each, which is the build's largest single expense once a
|
|
19
|
+
* site has more than a handful of pages.
|
|
20
|
+
*
|
|
21
|
+
* `--relative` makes git print paths relative to `dir`, so they join straight
|
|
22
|
+
* back onto it with no second call to find the repository root. `--no-renames`
|
|
23
|
+
* makes a rename list both of its paths, which keeps the answer identical to
|
|
24
|
+
* asking about one file at a time.
|
|
25
|
+
*
|
|
26
|
+
* An empty map for a directory outside a repository, or one git could not read.
|
|
27
|
+
* That is the same "no date" every caller already handles, and it is deliberately
|
|
28
|
+
* all-or-nothing: a truncated read would date some pages and not others, which
|
|
29
|
+
* looks like real data and is not.
|
|
30
|
+
*
|
|
31
|
+
* Only a truncated read calls `onFail`, because it is the one failure that would
|
|
32
|
+
* otherwise date some pages and not others. A directory outside a repository, a
|
|
33
|
+
* missing directory, or a machine without git all yield no dates, which every
|
|
34
|
+
* caller already handles and which the plugin already reports where it matters.
|
|
35
|
+
* Reporting is the caller's to do, which is why this takes a callback rather
|
|
36
|
+
* than writing to the console itself.
|
|
37
|
+
*/
|
|
38
|
+
export function commitDates(dir, onFail,
|
|
39
|
+
/**
|
|
40
|
+
* Cap on the walk's output. The default is far above any real docs root; it is
|
|
41
|
+
* a parameter so the truncation path can be exercised without a repository
|
|
42
|
+
* large enough to produce 256 MB of history.
|
|
43
|
+
*/
|
|
44
|
+
maxBuffer = 256 * 1024 * 1024) {
|
|
45
|
+
const dates = new Map();
|
|
46
|
+
const res = spawnSync('git', ['log', '--format=%cs', '--name-only', '--relative', '--no-renames', '--', '.'],
|
|
47
|
+
// The 1 MB default would truncate rather than throw on a large history,
|
|
48
|
+
// which is why the cap is raised and the result checked below.
|
|
49
|
+
{ cwd: dir, encoding: 'utf-8', maxBuffer });
|
|
50
|
+
// `ENOBUFS` is the output outgrowing maxBuffer, which truncates rather than
|
|
51
|
+
// throws and is the only failure that could leave a half-filled map. `ENOENT`
|
|
52
|
+
// (no such directory, or no git installed) and a non-zero status ("not a
|
|
53
|
+
// repository") both mean no dates at all, which is a state callers expect.
|
|
54
|
+
if (res.error?.code === 'ENOBUFS') {
|
|
55
|
+
onFail?.(res.error.message);
|
|
56
|
+
}
|
|
57
|
+
if (res.error || res.status !== 0)
|
|
58
|
+
return dates;
|
|
59
|
+
let date;
|
|
60
|
+
for (const line of res.stdout.split('\n')) {
|
|
61
|
+
if (!line)
|
|
62
|
+
continue;
|
|
63
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(line)) {
|
|
64
|
+
date = line;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
// Newest commit first, so the first date a file appears under is its latest.
|
|
68
|
+
const file = path.resolve(dir, line);
|
|
69
|
+
if (date && !dates.has(file))
|
|
70
|
+
dates.set(file, date);
|
|
71
|
+
}
|
|
72
|
+
return dates;
|
|
23
73
|
}
|
|
24
74
|
/**
|
|
25
75
|
* Release dates keyed by version, read from the commit that introduced each
|
|
@@ -26,6 +26,7 @@ import { docsBaseFrom } from '../paths.js';
|
|
|
26
26
|
import { isPageFile, readSourcePages, withCommitDates } from './pages.js';
|
|
27
27
|
import { contentIndex, llmsIndex, searchIndex } from './collect.js';
|
|
28
28
|
import { collectReleases } from './releases.js';
|
|
29
|
+
import { commitDates } from '../git.js';
|
|
29
30
|
/**
|
|
30
31
|
* The svelte-docsmith Vite plugin. Add it to `plugins` in `vite.config.ts`:
|
|
31
32
|
*
|
|
@@ -70,6 +71,17 @@ function contentIndexPlugin(options) {
|
|
|
70
71
|
const changelogRoute = options.changelogPath ?? '/changelog';
|
|
71
72
|
const changelogOverrides = path.join(routesDir, changelogRoute);
|
|
72
73
|
const indexOptions = { contentDir, routesDir, versions };
|
|
74
|
+
// One history walk, reused for the life of the plugin. A build loads this
|
|
75
|
+
// module once, so it is always fresh there; `dev` reloads it on every page
|
|
76
|
+
// save, and re-walking each time would cost git a process for dates that
|
|
77
|
+
// cannot have changed without a commit. The trade is that committing during
|
|
78
|
+
// `dev` leaves the dates as they were until the server restarts.
|
|
79
|
+
let dates;
|
|
80
|
+
const trackedDates = () => (dates ??= commitDates(contentDir, (reason) => {
|
|
81
|
+
console.warn(`[svelte-docsmith] could not read commit dates under ${contentDir}\n` +
|
|
82
|
+
` ${reason}\n` +
|
|
83
|
+
` Pages will render without a last-updated date, and sitemap entries without \`lastmod\`.`);
|
|
84
|
+
}));
|
|
73
85
|
return {
|
|
74
86
|
name: 'docsmith-content',
|
|
75
87
|
enforce: 'pre',
|
|
@@ -101,7 +113,7 @@ function contentIndexPlugin(options) {
|
|
|
101
113
|
const { pages, exists } = readSourcePages(contentDir);
|
|
102
114
|
watch(pages);
|
|
103
115
|
// Only this index has a date field, so only this one pays for git.
|
|
104
|
-
const docs = contentIndex(withCommitDates(pages), indexOptions);
|
|
116
|
+
const docs = contentIndex(withCommitDates(pages, trackedDates()), indexOptions);
|
|
105
117
|
warnAboutDocsRoot(contentDir, exists, docs.length);
|
|
106
118
|
const resolved = resolveVersions(versions, docsBase, docs);
|
|
107
119
|
return (`export const docs = ${JSON.stringify(docs, null, 2)};\n` +
|
|
@@ -39,18 +39,18 @@ export declare function readSourcePages(contentDir: string): {
|
|
|
39
39
|
*/
|
|
40
40
|
export declare function titleOf(page: SourcePage): string | undefined;
|
|
41
41
|
/**
|
|
42
|
-
* Date each page from
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
42
|
+
* Date each page from `dates`, keyed by absolute path, unless it carries its own
|
|
43
|
+
* frontmatter `lastUpdated`. Archiving copies every page in one commit, so an
|
|
44
|
+
* archive's pages are written with the date they last really changed and must
|
|
45
|
+
* keep it.
|
|
46
46
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* untitled pages, which no index carries.
|
|
47
|
+
* Pure: the caller walks git once with {@link commitDates} and hands the result
|
|
48
|
+
* in. Asking per page cost a process each and was the build's largest single
|
|
49
|
+
* expense, and doing the walk here would put it behind a function the search and
|
|
50
|
+
* llms indexes also call, making them pay for a date neither has anywhere to put.
|
|
51
|
+
* For the same reason this skips untitled pages, which no index carries.
|
|
52
52
|
*/
|
|
53
|
-
export declare function withCommitDates(pages: SourcePage[]): SourcePage[];
|
|
53
|
+
export declare function withCommitDates(pages: SourcePage[], dates: Map<string, string>): SourcePage[];
|
|
54
54
|
/**
|
|
55
55
|
* List the absolute paths of every `+page.md`/`+page.svx` under `contentDir`.
|
|
56
56
|
*/
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
import fs from 'node:fs';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { parseFrontmatter } from './frontmatter.js';
|
|
10
|
-
import { lastCommitDate } from '../git.js';
|
|
11
10
|
const PAGE_NAMES = ['+page.md', '+page.svx'];
|
|
12
11
|
/** Whether a filename is a doc page the plugin should index. */
|
|
13
12
|
export function isPageFile(file) {
|
|
@@ -40,18 +39,18 @@ export function titleOf(page) {
|
|
|
40
39
|
return typeof page.frontmatter.title === 'string' ? page.frontmatter.title : undefined;
|
|
41
40
|
}
|
|
42
41
|
/**
|
|
43
|
-
* Date each page from
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
42
|
+
* Date each page from `dates`, keyed by absolute path, unless it carries its own
|
|
43
|
+
* frontmatter `lastUpdated`. Archiving copies every page in one commit, so an
|
|
44
|
+
* archive's pages are written with the date they last really changed and must
|
|
45
|
+
* keep it.
|
|
47
46
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* untitled pages, which no index carries.
|
|
47
|
+
* Pure: the caller walks git once with {@link commitDates} and hands the result
|
|
48
|
+
* in. Asking per page cost a process each and was the build's largest single
|
|
49
|
+
* expense, and doing the walk here would put it behind a function the search and
|
|
50
|
+
* llms indexes also call, making them pay for a date neither has anywhere to put.
|
|
51
|
+
* For the same reason this skips untitled pages, which no index carries.
|
|
53
52
|
*/
|
|
54
|
-
export function withCommitDates(pages) {
|
|
53
|
+
export function withCommitDates(pages, dates) {
|
|
55
54
|
return pages.map((page) => {
|
|
56
55
|
if (titleOf(page) === undefined)
|
|
57
56
|
return page;
|
|
@@ -59,7 +58,7 @@ export function withCommitDates(pages) {
|
|
|
59
58
|
...page,
|
|
60
59
|
lastUpdated: typeof page.frontmatter.lastUpdated === 'string'
|
|
61
60
|
? page.frontmatter.lastUpdated
|
|
62
|
-
:
|
|
61
|
+
: dates.get(page.file)
|
|
63
62
|
};
|
|
64
63
|
});
|
|
65
64
|
}
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
{/if}
|
|
99
99
|
|
|
100
100
|
<pre
|
|
101
|
-
class="not-prose shiki max-h-[32rem] flex shrink"
|
|
101
|
+
class="docsmith-code not-prose shiki max-h-[32rem] flex shrink"
|
|
102
102
|
class:docsmith-line-numbers={lineNumbers}
|
|
103
103
|
style={lineNumbers && startLine !== 1 ? `--docsmith-line-start: ${startLine - 1}` : undefined}
|
|
104
104
|
use:clipboard.readText>
|
|
@@ -113,8 +113,17 @@
|
|
|
113
113
|
* published package, where the consumer's Tailwind theme context isn't
|
|
114
114
|
* available to resolve @apply (Tailwind v4 compiles scoped styles in
|
|
115
115
|
* isolation).
|
|
116
|
+
*
|
|
117
|
+
* Shiki's markup arrives through `{@html}`, so Svelte cannot scope these for
|
|
118
|
+
* us and they have to be global. Every one is therefore anchored on
|
|
119
|
+
* `pre.docsmith-code`, the class this component puts on its own element.
|
|
120
|
+
* Anchoring on the bare element instead would restyle every code block on
|
|
121
|
+
* the page, including ones this component never rendered — and since a
|
|
122
|
+
* route's CSS only arrives when that route loads (SvelteKit preloads on
|
|
123
|
+
* link hover), the restyle would land mid-session and shift the layout
|
|
124
|
+
* under the pointer.
|
|
116
125
|
*/
|
|
117
|
-
:global(pre code) {
|
|
126
|
+
:global(pre.docsmith-code code) {
|
|
118
127
|
display: block;
|
|
119
128
|
flex-grow: 1;
|
|
120
129
|
height: 100%;
|
|
@@ -122,7 +131,7 @@
|
|
|
122
131
|
padding-block: 1rem;
|
|
123
132
|
}
|
|
124
133
|
|
|
125
|
-
:global(pre span.line) {
|
|
134
|
+
:global(pre.docsmith-code span.line) {
|
|
126
135
|
display: inline-block;
|
|
127
136
|
width: 100%;
|
|
128
137
|
padding-inline: 1rem;
|
|
@@ -149,15 +158,15 @@
|
|
|
149
158
|
A CSS counter on Shiki's existing per-line spans, so numbering adds no DOM
|
|
150
159
|
and stays out of the text the copy button reads. `--docsmith-line-start`
|
|
151
160
|
offsets the counter for a snippet lifted out of a larger file. */
|
|
152
|
-
:global(pre.docsmith-line-numbers code) {
|
|
161
|
+
:global(pre.docsmith-code.docsmith-line-numbers code) {
|
|
153
162
|
counter-reset: docsmith-line var(--docsmith-line-start, 0);
|
|
154
163
|
}
|
|
155
|
-
:global(pre.docsmith-line-numbers span.line) {
|
|
164
|
+
:global(pre.docsmith-code.docsmith-line-numbers span.line) {
|
|
156
165
|
counter-increment: docsmith-line;
|
|
157
166
|
position: relative;
|
|
158
167
|
padding-inline-start: 3.2rem;
|
|
159
168
|
}
|
|
160
|
-
:global(pre.docsmith-line-numbers span.line::before) {
|
|
169
|
+
:global(pre.docsmith-code.docsmith-line-numbers span.line::before) {
|
|
161
170
|
content: counter(docsmith-line);
|
|
162
171
|
position: absolute;
|
|
163
172
|
left: 0;
|
|
@@ -169,19 +178,19 @@
|
|
|
169
178
|
user-select: none;
|
|
170
179
|
}
|
|
171
180
|
/* The diff gutter glyph would collide with the number, so shift it over. */
|
|
172
|
-
:global(pre.docsmith-line-numbers span.line.diff::before) {
|
|
181
|
+
:global(pre.docsmith-code.docsmith-line-numbers span.line.diff::before) {
|
|
173
182
|
content: counter(docsmith-line);
|
|
174
183
|
}
|
|
175
|
-
:global(pre.docsmith-line-numbers span.line.diff::after) {
|
|
184
|
+
:global(pre.docsmith-code.docsmith-line-numbers span.line.diff::after) {
|
|
176
185
|
position: absolute;
|
|
177
186
|
left: 2.7rem;
|
|
178
187
|
font-weight: 600;
|
|
179
188
|
}
|
|
180
|
-
:global(pre.docsmith-line-numbers span.line.diff.add::after) {
|
|
189
|
+
:global(pre.docsmith-code.docsmith-line-numbers span.line.diff.add::after) {
|
|
181
190
|
content: '+';
|
|
182
191
|
color: oklch(0.6 0.15 150);
|
|
183
192
|
}
|
|
184
|
-
:global(pre.docsmith-line-numbers span.line.diff.remove::after) {
|
|
193
|
+
:global(pre.docsmith-code.docsmith-line-numbers span.line.diff.remove::after) {
|
|
185
194
|
content: '−';
|
|
186
195
|
color: oklch(0.58 0.19 25);
|
|
187
196
|
}
|
|
@@ -193,76 +202,76 @@
|
|
|
193
202
|
the code background via color-mix. */
|
|
194
203
|
|
|
195
204
|
/* --- Line highlight (// [!code highlight] and {1,3} meta) --- */
|
|
196
|
-
:global(pre span.line.highlighted) {
|
|
205
|
+
:global(pre.docsmith-code span.line.highlighted) {
|
|
197
206
|
background: color-mix(in oklch, var(--primary) 12%, transparent);
|
|
198
207
|
}
|
|
199
|
-
:global(.dark pre span.line.highlighted) {
|
|
208
|
+
:global(.dark pre.docsmith-code span.line.highlighted) {
|
|
200
209
|
background: color-mix(in oklch, var(--primary) 20%, transparent);
|
|
201
210
|
}
|
|
202
211
|
|
|
203
212
|
/* --- Diff (// [!code ++] / [!code --]) with +/- gutter glyphs --- */
|
|
204
|
-
:global(pre span.line.diff) {
|
|
213
|
+
:global(pre.docsmith-code span.line.diff) {
|
|
205
214
|
position: relative;
|
|
206
215
|
}
|
|
207
|
-
:global(pre span.line.diff.add) {
|
|
216
|
+
:global(pre.docsmith-code span.line.diff.add) {
|
|
208
217
|
background: color-mix(in oklch, oklch(0.7 0.16 150) 15%, transparent);
|
|
209
218
|
}
|
|
210
|
-
:global(pre span.line.diff.remove) {
|
|
219
|
+
:global(pre.docsmith-code span.line.diff.remove) {
|
|
211
220
|
background: color-mix(in oklch, oklch(0.62 0.2 25) 14%, transparent);
|
|
212
221
|
}
|
|
213
|
-
:global(.dark pre span.line.diff.add) {
|
|
222
|
+
:global(.dark pre.docsmith-code span.line.diff.add) {
|
|
214
223
|
background: color-mix(in oklch, oklch(0.7 0.16 150) 22%, transparent);
|
|
215
224
|
}
|
|
216
|
-
:global(.dark pre span.line.diff.remove) {
|
|
225
|
+
:global(.dark pre.docsmith-code span.line.diff.remove) {
|
|
217
226
|
background: color-mix(in oklch, oklch(0.62 0.2 25) 22%, transparent);
|
|
218
227
|
}
|
|
219
|
-
:global(pre span.line.diff::before) {
|
|
228
|
+
:global(pre.docsmith-code span.line.diff::before) {
|
|
220
229
|
position: absolute;
|
|
221
230
|
left: 0.45rem;
|
|
222
231
|
font-weight: 600;
|
|
223
232
|
}
|
|
224
|
-
:global(pre span.line.diff.add::before) {
|
|
233
|
+
:global(pre.docsmith-code span.line.diff.add::before) {
|
|
225
234
|
content: '+';
|
|
226
235
|
color: oklch(0.6 0.15 150);
|
|
227
236
|
}
|
|
228
|
-
:global(pre span.line.diff.remove::before) {
|
|
237
|
+
:global(pre.docsmith-code span.line.diff.remove::before) {
|
|
229
238
|
content: '−';
|
|
230
239
|
color: oklch(0.58 0.19 25);
|
|
231
240
|
}
|
|
232
|
-
:global(.dark pre span.line.diff.add::before) {
|
|
241
|
+
:global(.dark pre.docsmith-code span.line.diff.add::before) {
|
|
233
242
|
color: oklch(0.78 0.17 150);
|
|
234
243
|
}
|
|
235
|
-
:global(.dark pre span.line.diff.remove::before) {
|
|
244
|
+
:global(.dark pre.docsmith-code span.line.diff.remove::before) {
|
|
236
245
|
color: oklch(0.72 0.18 25);
|
|
237
246
|
}
|
|
238
247
|
|
|
239
248
|
/* --- Error / warning severity (// [!code error] / [!code warning]).
|
|
240
249
|
No gutter glyph, so they stay distinct from the +/- diff lines. --- */
|
|
241
|
-
:global(pre span.line.highlighted.error) {
|
|
250
|
+
:global(pre.docsmith-code span.line.highlighted.error) {
|
|
242
251
|
background: color-mix(in oklch, oklch(0.62 0.2 25) 18%, transparent);
|
|
243
252
|
}
|
|
244
|
-
:global(pre span.line.highlighted.warning) {
|
|
253
|
+
:global(pre.docsmith-code span.line.highlighted.warning) {
|
|
245
254
|
background: color-mix(in oklch, oklch(0.8 0.13 85) 22%, transparent);
|
|
246
255
|
}
|
|
247
|
-
:global(.dark pre span.line.highlighted.error) {
|
|
256
|
+
:global(.dark pre.docsmith-code span.line.highlighted.error) {
|
|
248
257
|
background: color-mix(in oklch, oklch(0.62 0.2 25) 26%, transparent);
|
|
249
258
|
}
|
|
250
|
-
:global(.dark pre span.line.highlighted.warning) {
|
|
259
|
+
:global(.dark pre.docsmith-code span.line.highlighted.warning) {
|
|
251
260
|
background: color-mix(in oklch, oklch(0.8 0.13 85) 20%, transparent);
|
|
252
261
|
}
|
|
253
262
|
|
|
254
263
|
/* --- Focus (// [!code focus]): dim the other lines and restore on hover.
|
|
255
264
|
A clean opacity fade reads calmer than a muddy blur. `:has` recreates
|
|
256
265
|
Shiki's stripped container flag from the line classes alone. --- */
|
|
257
|
-
:global(pre code:has(span.line.focused) span.line:not(.focused)) {
|
|
266
|
+
:global(pre.docsmith-code code:has(span.line.focused) span.line:not(.focused)) {
|
|
258
267
|
opacity: 0.42;
|
|
259
268
|
transition: opacity 0.25s ease;
|
|
260
269
|
}
|
|
261
|
-
:global(pre:hover code:has(span.line.focused) span.line:not(.focused)) {
|
|
270
|
+
:global(pre.docsmith-code:hover code:has(span.line.focused) span.line:not(.focused)) {
|
|
262
271
|
opacity: 1;
|
|
263
272
|
}
|
|
264
273
|
@media (prefers-reduced-motion: reduce) {
|
|
265
|
-
:global(pre code:has(span.line.focused) span.line:not(.focused)) {
|
|
274
|
+
:global(pre.docsmith-code code:has(span.line.focused) span.line:not(.focused)) {
|
|
266
275
|
transition: none;
|
|
267
276
|
}
|
|
268
277
|
}
|
|
@@ -273,11 +282,11 @@
|
|
|
273
282
|
inspectable; the popup is a hidden sibling revealed on hover. `unclipPopups`
|
|
274
283
|
then promotes it to fixed positioning so the code block's scrolling does not
|
|
275
284
|
clip it — see the comment there. */
|
|
276
|
-
:global(pre .twoslash-hover) {
|
|
285
|
+
:global(pre.docsmith-code .twoslash-hover) {
|
|
277
286
|
position: relative;
|
|
278
287
|
border-bottom: 1px dotted color-mix(in oklch, var(--muted-foreground) 55%, transparent);
|
|
279
288
|
}
|
|
280
|
-
:global(pre .twoslash-popup-container) {
|
|
289
|
+
:global(pre.docsmith-code .twoslash-popup-container) {
|
|
281
290
|
position: absolute;
|
|
282
291
|
z-index: 40;
|
|
283
292
|
display: none;
|
|
@@ -298,11 +307,11 @@
|
|
|
298
307
|
font-size: 0.85em;
|
|
299
308
|
line-height: 1.5;
|
|
300
309
|
}
|
|
301
|
-
:global(pre .twoslash-hover:hover .twoslash-popup-container) {
|
|
310
|
+
:global(pre.docsmith-code .twoslash-hover:hover .twoslash-popup-container) {
|
|
302
311
|
display: block;
|
|
303
312
|
}
|
|
304
313
|
/* The signature keeps Shiki's own colours and monospace. */
|
|
305
|
-
:global(pre .twoslash-popup-code) {
|
|
314
|
+
:global(pre.docsmith-code .twoslash-popup-code) {
|
|
306
315
|
display: block;
|
|
307
316
|
font-family: var(--font-mono, ui-monospace, monospace);
|
|
308
317
|
white-space: pre-wrap;
|
|
@@ -311,7 +320,7 @@
|
|
|
311
320
|
/* Prose, by contrast, is prose. Inside a <pre> it would otherwise inherit
|
|
312
321
|
monospace and whatever token colour the hovered span happened to carry,
|
|
313
322
|
which reads as more code rather than an explanation of it. */
|
|
314
|
-
:global(pre .twoslash-popup-docs) {
|
|
323
|
+
:global(pre.docsmith-code .twoslash-popup-docs) {
|
|
315
324
|
/* The dark-theme swap repaints every span inside `.shiki` with
|
|
316
325
|
`--shiki-dark`, which these spans inherit from whichever token was
|
|
317
326
|
hovered — so docs took on the colour of the word above them. Redefining
|
|
@@ -330,20 +339,20 @@
|
|
|
330
339
|
|
|
331
340
|
/* Each @param is its own line: the tags are inline spans, so without this
|
|
332
341
|
they run together into one paragraph. */
|
|
333
|
-
:global(pre .twoslash-popup-docs-tags) {
|
|
342
|
+
:global(pre.docsmith-code .twoslash-popup-docs-tags) {
|
|
334
343
|
display: flex;
|
|
335
344
|
flex-direction: column;
|
|
336
345
|
gap: 0.35rem;
|
|
337
346
|
}
|
|
338
|
-
:global(pre .twoslash-popup-docs-tag) {
|
|
347
|
+
:global(pre.docsmith-code .twoslash-popup-docs-tag) {
|
|
339
348
|
display: block;
|
|
340
349
|
}
|
|
341
|
-
:global(pre .twoslash-popup-docs-tag-value) {
|
|
350
|
+
:global(pre.docsmith-code .twoslash-popup-docs-tag-value) {
|
|
342
351
|
--shiki-dark: var(--muted-foreground);
|
|
343
352
|
color: var(--muted-foreground);
|
|
344
353
|
}
|
|
345
354
|
/* And the tag name needs a gap, or it reads as "@paramcallbackfn". */
|
|
346
|
-
:global(pre .twoslash-popup-docs-tag-name) {
|
|
355
|
+
:global(pre.docsmith-code .twoslash-popup-docs-tag-name) {
|
|
347
356
|
--shiki-dark: var(--primary);
|
|
348
357
|
margin-right: 0.4em;
|
|
349
358
|
font-family: var(--font-mono, ui-monospace, monospace);
|
|
@@ -351,18 +360,18 @@
|
|
|
351
360
|
color: var(--primary);
|
|
352
361
|
}
|
|
353
362
|
/* A snippet that deliberately shows an error (// @errors:) marks the line. */
|
|
354
|
-
:global(pre .twoslash-error) {
|
|
363
|
+
:global(pre.docsmith-code .twoslash-error) {
|
|
355
364
|
background: color-mix(in oklch, oklch(0.62 0.2 25) 14%, transparent);
|
|
356
365
|
}
|
|
357
366
|
|
|
358
367
|
/* --- Word highlight (// [!code word:name]): a primary-tinted inline pill --- */
|
|
359
|
-
:global(pre .highlighted-word) {
|
|
368
|
+
:global(pre.docsmith-code .highlighted-word) {
|
|
360
369
|
border-radius: 0.3rem;
|
|
361
370
|
padding: 0.1rem 0.3rem;
|
|
362
371
|
background: color-mix(in oklch, var(--primary) 16%, transparent);
|
|
363
372
|
box-shadow: 0 0 0 1px color-mix(in oklch, var(--primary) 35%, transparent);
|
|
364
373
|
}
|
|
365
|
-
:global(.dark pre .highlighted-word) {
|
|
374
|
+
:global(.dark pre.docsmith-code .highlighted-word) {
|
|
366
375
|
background: color-mix(in oklch, var(--primary) 24%, transparent);
|
|
367
376
|
}
|
|
368
377
|
</style>
|