@mvarble/mesearch 0.1.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.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +123 -0
  3. package/app/ambient.d.ts +24 -0
  4. package/app/app.html +28 -0
  5. package/app/components/Proof.svelte +19 -0
  6. package/app/components/Statement.svelte +19 -0
  7. package/app/error.html +23 -0
  8. package/app/hooks.server.ts +8 -0
  9. package/app/lib/format.ts +6 -0
  10. package/app/lib/server/refs.ts +32 -0
  11. package/app/lib/types.ts +16 -0
  12. package/app/params/kind.ts +5 -0
  13. package/app/routes/+error.svelte +37 -0
  14. package/app/routes/+layout.svelte +41 -0
  15. package/app/routes/+layout.ts +2 -0
  16. package/app/routes/+page.server.ts +37 -0
  17. package/app/routes/+page.svelte +212 -0
  18. package/app/routes/+page.ts +18 -0
  19. package/app/routes/[kind=kind]/[slug]/+page.server.ts +62 -0
  20. package/app/routes/[kind=kind]/[slug]/+page.svelte +116 -0
  21. package/app/routes/[kind=kind]/[slug]/+page.ts +8 -0
  22. package/app/routes/search.json/+server.ts +30 -0
  23. package/app/static/favicon.svg +1 -0
  24. package/dist/cli.d.ts +2 -0
  25. package/dist/cli.js +146 -0
  26. package/dist/cli.js.map +1 -0
  27. package/dist/config.d.ts +26 -0
  28. package/dist/config.js +82 -0
  29. package/dist/config.js.map +1 -0
  30. package/dist/headings.d.ts +2 -0
  31. package/dist/headings.js +17 -0
  32. package/dist/headings.js.map +1 -0
  33. package/dist/index.d.ts +1 -0
  34. package/dist/index.js +2 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/init.d.ts +6 -0
  37. package/dist/init.js +214 -0
  38. package/dist/init.js.map +1 -0
  39. package/dist/kit.d.ts +4 -0
  40. package/dist/kit.js +180 -0
  41. package/dist/kit.js.map +1 -0
  42. package/dist/paths.d.ts +8 -0
  43. package/dist/paths.js +16 -0
  44. package/dist/paths.js.map +1 -0
  45. package/dist/project.d.ts +26 -0
  46. package/dist/project.js +92 -0
  47. package/dist/project.js.map +1 -0
  48. package/dist/resolve.d.ts +2 -0
  49. package/dist/resolve.js +51 -0
  50. package/dist/resolve.js.map +1 -0
  51. package/package.json +83 -0
  52. package/templates/AGENTS.md +64 -0
  53. package/templates/content/concepts/example-concept/description.md +1 -0
  54. package/templates/content/concepts/example-concept/index.md +16 -0
  55. package/templates/content/description.md +1 -0
  56. package/templates/content/writeups/example-writeup/index.md +7 -0
  57. package/templates/mesearch.config.ts +22 -0
  58. package/tsconfig.project.json +16 -0
@@ -0,0 +1,18 @@
1
+ import type { Component } from 'svelte';
2
+ import loaders from '$cms/loaders';
3
+
4
+ const component = async (filename: string | undefined) =>
5
+ filename && loaders[filename] ? (await loaders[filename]()).default : undefined;
6
+
7
+ // The site's description, and every document's, as components: the home page
8
+ // shows the first and the graph's panel the others.
9
+ export const load = async ({ data }) => {
10
+ const descriptions: Record<string, Component> = {};
11
+ await Promise.all(
12
+ data.documents.map(async (doc) => {
13
+ const loaded = await component(doc.descriptionFilename);
14
+ if (loaded) descriptions[doc.key] = loaded;
15
+ }),
16
+ );
17
+ return { ...data, Description: await component(data.description), descriptions };
18
+ };
@@ -0,0 +1,62 @@
1
+ import { error } from '@sveltejs/kit';
2
+ import type { OutlineEntry } from '@mvarble/mesearch-cms/presets/mesearch';
3
+ import { citationLabel } from '@mvarble/mesearch-cms/presets/mesearch/runtime';
4
+ import { referenceHtml } from '@mvarble/mesearch-ui/server';
5
+ import site from '$site';
6
+ import { cms } from '$cms';
7
+
8
+ import { inlineHtml, toIndexEntry, toRef } from '$lib/server/refs';
9
+ import type { TocEntry } from '$lib/types';
10
+
11
+ export const entries = () =>
12
+ [...cms.documents.list(), ...cms.sequences.list()].map((doc) => {
13
+ const [kind, slug] = doc.key.split('/') as [string, string];
14
+ return { kind, slug };
15
+ });
16
+
17
+ export const load = ({ params }) => {
18
+ const key = `${params.kind}/${params.slug}`;
19
+ const doc = cms.documents.get(key);
20
+ if (!doc) error(404, `There is no document at ${key}.`);
21
+
22
+ const toc = (entry: OutlineEntry): TocEntry => ({
23
+ slug: entry.slug,
24
+ depth: entry.depth,
25
+ titleHtml: inlineHtml(doc, entry.title),
26
+ children: entry.children.map(toc),
27
+ });
28
+
29
+ // Where the document sits in each sequence that includes it.
30
+ const memberships = cms.sequences.containing(key).map(({ sequence, index, documents }) => ({
31
+ sequence: toRef(sequence),
32
+ index,
33
+ documents: documents.map(toRef),
34
+ }));
35
+
36
+ // A sequence's own page lists what it orders.
37
+ const contents =
38
+ doc.kind == 'sequence'
39
+ ? (cms.sequences.get(key)?.documents ?? [])
40
+ .map((member) => cms.documents.get(member))
41
+ .filter((member) => !!member)
42
+ .map(toIndexEntry)
43
+ : [];
44
+
45
+ return {
46
+ doc: toIndexEntry(doc),
47
+ filename: doc.filename,
48
+ toc: cms.outline(doc.filename).map(toc),
49
+ prerequisites: cms.prerequisites(key).map(toRef),
50
+ dependents: cms.dependents(key).map(toRef),
51
+ related: cms.related(key).map(toRef),
52
+ memberships,
53
+ contents,
54
+ // What the page cites, for the list at its end; `cite:key` links on the
55
+ // page jump to `#cite:key`.
56
+ references: cms.bibliography(key).map((citation) => ({
57
+ id: `cite:${citation.key}`,
58
+ label: citationLabel(citation),
59
+ html: referenceHtml(citation, { ...site.katexMacros, ...doc.katexMacros }),
60
+ })),
61
+ };
62
+ };
@@ -0,0 +1,116 @@
1
+ <script lang="ts">
2
+ import {
3
+ Article,
4
+ ArticleHeader,
5
+ Chips,
6
+ Icon,
7
+ Pager,
8
+ Parts,
9
+ References,
10
+ SequenceTrack,
11
+ Toc,
12
+ kindLabel,
13
+ } from '@mvarble/mesearch-ui';
14
+ import site from '$site';
15
+ import { href } from '$lib/format';
16
+
17
+ let { data } = $props();
18
+ let doc = $derived(data.doc);
19
+
20
+ // Where the document sits: the site, its kind, and each sequence it is in.
21
+ let crumbs = $derived([
22
+ { label: site.title, url: href('') },
23
+ { label: kindLabel(doc.kind) },
24
+ ...data.memberships.map((m) => ({
25
+ label: m.sequence.title,
26
+ url: m.sequence.url,
27
+ note: `${m.index + 1} of ${m.documents.length}`,
28
+ })),
29
+ ]);
30
+ </script>
31
+
32
+ <svelte:head>
33
+ <title>{doc.title} · {site.title}</title>
34
+ {#if doc.summary}<meta name="description" content={doc.summary} />{/if}
35
+ </svelte:head>
36
+
37
+ {#snippet header()}
38
+ <ArticleHeader
39
+ {crumbs}
40
+ titleHtml={doc.titleHtml}
41
+ created={doc.created}
42
+ updated={doc.updated}
43
+ readingMinutes={doc.readingMinutes}
44
+ buildsOn={data.prerequisites}
45
+ />
46
+ {/snippet}
47
+
48
+ {#snippet sequences()}
49
+ {#each data.memberships as membership (membership.sequence.key)}
50
+ <SequenceTrack
51
+ title={membership.sequence.titleHtml}
52
+ url={membership.sequence.url}
53
+ items={membership.documents}
54
+ current={doc.key}
55
+ />
56
+ {/each}
57
+ {/snippet}
58
+
59
+ {#snippet contents(folded: boolean)}
60
+ <Toc entries={data.toc} label={folded ? 'Contents' : 'On this page'} />
61
+ {/snippet}
62
+
63
+ {#snippet footer()}
64
+ {#each data.memberships as membership (membership.sequence.key)}
65
+ <Pager
66
+ label="Through {membership.sequence.title}"
67
+ previous={membership.documents[membership.index - 1]}
68
+ next={membership.documents[membership.index + 1]}
69
+ />
70
+ {/each}
71
+ <section><Chips heading="Leads to" refs={data.dependents} /></section>
72
+ <section><Chips heading="Related" refs={data.related} /></section>
73
+ <a class="back" href="{href('')}#atlas"><Icon name="graph" size={16} /> Back to the map</a>
74
+ {/snippet}
75
+
76
+ <Article
77
+ kind={doc.kind}
78
+ {header}
79
+ {footer}
80
+ left={data.memberships.length ? sequences : undefined}
81
+ right={data.toc.length ? contents : undefined}
82
+ leftLabel={data.memberships.length == 1
83
+ ? `Sequence: ${data.memberships[0]!.sequence.title}`
84
+ : 'Sequences'}
85
+ leftNote={data.memberships.length == 1
86
+ ? `${data.memberships[0]!.index + 1} of ${data.memberships[0]!.documents.length}`
87
+ : undefined}
88
+ after={data.contents.length || data.references.length ? after : undefined}
89
+ >
90
+ <data.Content />
91
+ </Article>
92
+
93
+ {#snippet after()}
94
+ <References entries={data.references} />
95
+ {#if data.contents.length}<Parts parts={data.contents} />{/if}
96
+ {/snippet}
97
+
98
+ <style>
99
+ section:empty {
100
+ display: none;
101
+ }
102
+
103
+ .back {
104
+ display: inline-flex;
105
+ align-items: center;
106
+ gap: 0.45rem;
107
+ align-self: flex-start;
108
+ font: 500 0.85rem / 1 var(--font-ui);
109
+ color: var(--muted);
110
+ text-decoration: none;
111
+ }
112
+
113
+ .back:hover {
114
+ color: var(--accent);
115
+ }
116
+ </style>
@@ -0,0 +1,8 @@
1
+ import { error } from '@sveltejs/kit';
2
+ import loaders from '$cms/loaders';
3
+
4
+ export const load = async ({ data }) => {
5
+ const loader = loaders[data.filename];
6
+ if (!loader) error(404, `${data.filename} is not a document.`);
7
+ return { ...data, Content: (await loader()).default };
8
+ };
@@ -0,0 +1,30 @@
1
+ import { json } from '@sveltejs/kit';
2
+ import { cms } from '$cms';
3
+
4
+ import { href, plain } from '$lib/format';
5
+ import { inlineHtml } from '$lib/server/refs';
6
+ import type { SearchEntry } from '$lib/types';
7
+
8
+ export const prerender = true;
9
+ export const trailingSlash = 'never';
10
+
11
+ // What the search palette searches: titles, summaries and the headings within
12
+ // each document. One file, fetched the first time the palette opens, rather
13
+ // than a copy inside every page.
14
+ export const GET = () => {
15
+ const entries: SearchEntry[] = [...cms.documents.list(), ...cms.sequences.list()].map(
16
+ (doc) => ({
17
+ key: doc.key,
18
+ kind: doc.kind,
19
+ url: href(doc.key),
20
+ title: plain(doc.title),
21
+ summary: plain(doc.summary),
22
+ summaryHtml: inlineHtml(doc, doc.summary),
23
+ headings: cms.headings(doc.filename).map((heading) => ({
24
+ title: plain(heading.title),
25
+ slug: heading.slug,
26
+ })),
27
+ }),
28
+ );
29
+ return json(entries);
30
+ };
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="14" fill="#34317f"/><circle cx="22" cy="24" r="6" fill="#f9f8f3"/><circle cx="42" cy="24" r="6" fill="#f9f8f3"/><circle cx="32" cy="44" r="6" fill="#f9f8f3"/><path d="M22 24 32 44M42 24 32 44" stroke="#f9f8f3" stroke-width="3"/></svg>
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { pathToFileURL } from 'node:url';
5
+ import { parseArgs } from 'node:util';
6
+ import { initProject } from "./init.js";
7
+ import { packageDir } from "./paths.js";
8
+ import { exportOptions, findRoot, openProject } from "./project.js";
9
+ const USAGE = `
10
+ mesearch --- an opinionated site for a library of notes.
11
+
12
+ mesearch init [dir] scaffold a project: content/, config, stylesheet, AGENTS.md
13
+ mesearch dev serve the site, updating as documents change
14
+ mesearch build write the static site
15
+
16
+ Init options
17
+ --force overwrite files that already exist
18
+
19
+ Dev options
20
+ -p, --port <n> port to listen on
21
+ --host [addr] expose the server on the network
22
+ --open open a browser once the server is up
23
+
24
+ Build options
25
+ -o, --out <dir> where to write the site (default: build)
26
+ --base <path> URL prefix the site is served under, overriding the config
27
+ --force overwrite an output directory mesearch did not write
28
+
29
+ -h, --help show this message
30
+ -v, --version show the version
31
+
32
+ Run inside a project: the nearest directory up from here whose package.json
33
+ depends on @mvarble/mesearch, or that has a content/ folder.
34
+ `.trim();
35
+ const OPTIONS = {
36
+ out: { type: 'string', short: 'o' },
37
+ base: { type: 'string' },
38
+ force: { type: 'boolean', default: false },
39
+ port: { type: 'string', short: 'p' },
40
+ host: { type: 'string' },
41
+ open: { type: 'boolean', default: false },
42
+ help: { type: 'boolean', short: 'h', default: false },
43
+ version: { type: 'boolean', short: 'v', default: false },
44
+ };
45
+ async function main() {
46
+ const { values, positionals } = parseArgs({ options: OPTIONS, allowPositionals: true });
47
+ if (values.help)
48
+ return console.log(USAGE);
49
+ if (values.version)
50
+ return console.log(version());
51
+ const [command, ...rest] = positionals;
52
+ if (command == 'init') {
53
+ const dir = path.resolve(rest[0] ?? '.');
54
+ initProject(dir, { force: values.force });
55
+ return;
56
+ }
57
+ if (command != 'dev' && command != 'build') {
58
+ console.error(USAGE);
59
+ throw new Error(command ? `mesearch: no command \`${command}\`.` : 'mesearch: name a command.');
60
+ }
61
+ const root = findRoot(process.cwd());
62
+ if (!fs.existsSync(path.join(root, 'content'))) {
63
+ throw new Error(`mesearch: \`${root}\` has no content/ folder. Run \`mesearch init\` to make one.`);
64
+ }
65
+ const options = {
66
+ watch: command == 'dev',
67
+ outDir: values.out ? path.resolve(values.out) : undefined,
68
+ base: values.base,
69
+ };
70
+ exportOptions(options);
71
+ const project = await openProject(root, options);
72
+ const configFile = scaffold(project);
73
+ // SvelteKit reads `svelte.config.js` from the working directory, so that
74
+ // is where the scaffolding has to be.
75
+ process.chdir(project.workDir);
76
+ if (command == 'dev')
77
+ await dev(project, configFile, values);
78
+ else
79
+ await build(project, configFile, values.force);
80
+ }
81
+ // `.mesearch/`: what makes a directory of documents look like a SvelteKit
82
+ // project, rewritten on every run so that it always matches this version.
83
+ function scaffold(project) {
84
+ fs.mkdirSync(project.workDir, { recursive: true });
85
+ const kit = JSON.stringify(pathToFileURL(path.join(packageDir, 'dist', 'kit.js')).href);
86
+ const root = JSON.stringify(project.root);
87
+ const header = '// Written by mesearch on every run; edits here are lost.\n';
88
+ const write = (name, contents) => fs.writeFileSync(path.join(project.workDir, name), contents);
89
+ write('package.json', JSON.stringify({ private: true, type: 'module' }, null, 4) + '\n');
90
+ write('svelte.config.js', `${header}import { svelteConfig } from ${kit};\n\nexport default await svelteConfig(${root});\n`);
91
+ write('vite.config.js', `${header}import { viteConfig } from ${kit};\n\nexport default await viteConfig(${root});\n`);
92
+ write('.gitignore', '*\n');
93
+ return path.join(project.workDir, 'vite.config.js');
94
+ }
95
+ async function dev(project, configFile, values) {
96
+ const { createServer } = await import('vite');
97
+ const server = await createServer({
98
+ configFile,
99
+ server: {
100
+ port: values.port ? Number(values.port) : undefined,
101
+ host: values.host,
102
+ open: values.open,
103
+ // Only when a port was asked for. Otherwise Vite moves to the next
104
+ // free one, and a tab still open on the old port looks frozen.
105
+ strictPort: values.port !== undefined,
106
+ },
107
+ });
108
+ try {
109
+ await server.listen();
110
+ }
111
+ catch (error) {
112
+ await server.close();
113
+ throw error;
114
+ }
115
+ console.log(`mesearch: serving ${project.config.title} from ${project.root}`);
116
+ server.printUrls();
117
+ }
118
+ // Marks a directory as ours, so that a mistyped `--out` cannot quietly delete
119
+ // somebody's work: the build empties its output directory, and it will only do
120
+ // that to a directory that is empty or that a previous build made.
121
+ const MARKER = '.mesearch-build';
122
+ async function build(project, configFile, force) {
123
+ const outDir = project.outDir;
124
+ const entries = fs.existsSync(outDir) ? fs.readdirSync(outDir) : [];
125
+ if (entries.length > 0 && !entries.includes(MARKER) && !force) {
126
+ throw new Error(`mesearch: \`${outDir}\` is not empty and was not written by mesearch.\n` +
127
+ 'Building would erase it. Pass --force if that is what you want.');
128
+ }
129
+ const { createBuilder } = await import('vite');
130
+ // `null` lets the config decide between Vite's builders, as `vite build`
131
+ // does. SvelteKit relies on the legacy one, driving its own server and
132
+ // client passes; forcing the new one builds the server for the browser.
133
+ const builder = await createBuilder({ configFile, logLevel: 'warn' }, null);
134
+ await builder.buildApp();
135
+ fs.writeFileSync(path.join(outDir, MARKER), '');
136
+ console.log(`mesearch: wrote ${path.relative(project.root, outDir) || '.'}/`);
137
+ }
138
+ function version() {
139
+ const manifest = fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8');
140
+ return JSON.parse(manifest).version;
141
+ }
142
+ main().catch((error) => {
143
+ console.error(error instanceof Error ? error.message : error);
144
+ process.exitCode = 1;
145
+ });
146
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ export interface GraphConfig {
2
+ charge?: number;
3
+ linkDistance?: number;
4
+ gravity?: number;
5
+ }
6
+ export interface MesearchConfig {
7
+ title?: string;
8
+ author?: string;
9
+ lang?: string;
10
+ base?: string;
11
+ katexMacros?: Record<string, string>;
12
+ graph?: GraphConfig;
13
+ }
14
+ export declare const defineConfig: (config: MesearchConfig) => MesearchConfig;
15
+ export interface ResolvedConfig {
16
+ title: string;
17
+ author?: string;
18
+ lang: string;
19
+ base: string;
20
+ katexMacros: Record<string, string>;
21
+ graph: Required<GraphConfig>;
22
+ file?: string;
23
+ }
24
+ export declare function findConfigFile(root: string): string | undefined;
25
+ export declare function loadConfig(root: string): Promise<ResolvedConfig>;
26
+ export declare function validate(raw: unknown, file: string | undefined, fallbackTitle: string): ResolvedConfig;
package/dist/config.js ADDED
@@ -0,0 +1,82 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { CONFIG_FILES } from "./paths.js";
4
+ // Typed identity, so an editor can check `mesearch.config.ts`.
5
+ export const defineConfig = (config) => config;
6
+ export function findConfigFile(root) {
7
+ return CONFIG_FILES.map((name) => path.join(root, name)).find((file) => fs.existsSync(file));
8
+ }
9
+ // Loads `mesearch.config.ts` through Vite's module runner, so the config can be
10
+ // TypeScript and can import whatever the project has installed.
11
+ export async function loadConfig(root) {
12
+ const file = findConfigFile(root);
13
+ let raw = {};
14
+ if (file) {
15
+ const { runnerImport } = await import('vite');
16
+ const { module } = await runnerImport(file, {
17
+ configFile: false,
18
+ root,
19
+ logLevel: 'error',
20
+ });
21
+ raw = module.default ?? {};
22
+ }
23
+ return validate(raw, file, path.basename(root));
24
+ }
25
+ // Every problem with the config at once, rather than the first.
26
+ export function validate(raw, file, fallbackTitle) {
27
+ const where = file ? path.basename(file) : 'the config';
28
+ const problems = [];
29
+ const config = (raw && typeof raw == 'object' ? raw : {});
30
+ if (raw && typeof raw != 'object')
31
+ problems.push(`${where} must export an object.`);
32
+ const string = (field) => {
33
+ const value = config[field];
34
+ if (value === undefined)
35
+ return undefined;
36
+ if (typeof value != 'string')
37
+ problems.push(`${where}: \`${field}\` must be a string.`);
38
+ return typeof value == 'string' ? value : undefined;
39
+ };
40
+ const number = (object, field, fallback) => {
41
+ const value = object[field];
42
+ if (value === undefined)
43
+ return fallback;
44
+ if (typeof value != 'number' || !Number.isFinite(value)) {
45
+ problems.push(`${where}: \`graph.${field}\` must be a number.`);
46
+ return fallback;
47
+ }
48
+ return value;
49
+ };
50
+ let base = string('base') ?? '';
51
+ if (base == '/')
52
+ base = '';
53
+ if (base && (!base.startsWith('/') || base.endsWith('/'))) {
54
+ problems.push(`${where}: \`base\` must start with a slash and not end with one, as in '/notes'.`);
55
+ }
56
+ const macros = config.katexMacros ?? {};
57
+ if (typeof macros != 'object' ||
58
+ Array.isArray(macros) ||
59
+ Object.values(macros).some((value) => typeof value != 'string')) {
60
+ problems.push(`${where}: \`katexMacros\` must map macro names to strings.`);
61
+ }
62
+ const graph = (config.graph ?? {});
63
+ if (typeof graph != 'object')
64
+ problems.push(`${where}: \`graph\` must be an object.`);
65
+ const resolved = {
66
+ title: string('title') ?? fallbackTitle,
67
+ author: string('author'),
68
+ lang: string('lang') ?? 'en',
69
+ base,
70
+ katexMacros: typeof macros == 'object' ? macros : {},
71
+ graph: {
72
+ charge: number(graph, 'charge', -900),
73
+ linkDistance: number(graph, 'linkDistance', 80),
74
+ gravity: number(graph, 'gravity', 0.02),
75
+ },
76
+ file,
77
+ };
78
+ if (problems.length)
79
+ throw new Error(problems.map((p) => `mesearch: ${p}`).join('\n'));
80
+ return resolved;
81
+ }
82
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { Root } from 'hast';
2
+ export declare const rehypeDemoteHeadings: () => (tree: Root) => void;
@@ -0,0 +1,17 @@
1
+ // A document's `#` is a section of the page, whose only `<h1>` is the title
2
+ // in its header --- so every heading in a document is rendered one level
3
+ // down: `#` as `<h2>`, `##` as `<h3>`, and so on, `######` staying `<h6>`.
4
+ export const rehypeDemoteHeadings = () => (tree) => {
5
+ const stack = [tree];
6
+ while (stack.length) {
7
+ for (const child of stack.pop().children) {
8
+ if (child.type != 'element')
9
+ continue;
10
+ const level = /^h([1-6])$/.exec(child.tagName)?.[1];
11
+ if (level)
12
+ child.tagName = `h${Math.min(6, Number(level) + 1)}`;
13
+ stack.push(child);
14
+ }
15
+ }
16
+ };
17
+ //# sourceMappingURL=headings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headings.js","sourceRoot":"","sources":["../src/headings.ts"],"names":[],"mappings":"AAEA,4EAA4E;AAC5E,yEAAyE;AACzE,2EAA2E;AAC3E,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,EAAE,CAAC,CAAC,IAAU,EAAE,EAAE;IACrD,MAAM,KAAK,GAA0B,CAAC,IAAI,CAAC,CAAC;IAC5C,OAAO,KAAK,CAAC,MAAM,EAAE,CAAC;QAClB,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,GAAG,EAAG,CAAC,QAAQ,EAAE,CAAC;YACxC,IAAI,KAAK,CAAC,IAAI,IAAI,SAAS;gBAAE,SAAS;YACtC,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YACpD,IAAI,KAAK;gBAAE,KAAK,CAAC,OAAO,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YAChE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;IACL,CAAC;AACL,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export { defineConfig, type GraphConfig, type MesearchConfig } from './config.ts';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { defineConfig } from "./config.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAyC,MAAM,aAAa,CAAC"}
package/dist/init.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ export interface InitOptions {
2
+ force?: boolean;
3
+ }
4
+ export declare function initProject(dir: string, options?: InitOptions): void;
5
+ export declare function tokensFile(): string;
6
+ export declare function userStylesheet(): string;