@wizzlethorpe/vaults 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 (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +125 -0
  3. package/dist/api.js +42 -0
  4. package/dist/api.js.map +1 -0
  5. package/dist/auth.js +62 -0
  6. package/dist/auth.js.map +1 -0
  7. package/dist/build.js +758 -0
  8. package/dist/build.js.map +1 -0
  9. package/dist/commands/build.js +23 -0
  10. package/dist/commands/build.js.map +1 -0
  11. package/dist/commands/init.js +67 -0
  12. package/dist/commands/init.js.map +1 -0
  13. package/dist/commands/password.js +74 -0
  14. package/dist/commands/password.js.map +1 -0
  15. package/dist/commands/preview.js +60 -0
  16. package/dist/commands/preview.js.map +1 -0
  17. package/dist/commands/push.js +191 -0
  18. package/dist/commands/push.js.map +1 -0
  19. package/dist/commands/role.js +122 -0
  20. package/dist/commands/role.js.map +1 -0
  21. package/dist/config.js +79 -0
  22. package/dist/config.js.map +1 -0
  23. package/dist/favicon.js +91 -0
  24. package/dist/favicon.js.map +1 -0
  25. package/dist/images.js +47 -0
  26. package/dist/images.js.map +1 -0
  27. package/dist/index.js +154 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/obsidian.js +47 -0
  30. package/dist/obsidian.js.map +1 -0
  31. package/dist/render/auth-template.js +677 -0
  32. package/dist/render/auth-template.js.map +1 -0
  33. package/dist/render/callouts.js +65 -0
  34. package/dist/render/callouts.js.map +1 -0
  35. package/dist/render/embed.js +190 -0
  36. package/dist/render/embed.js.map +1 -0
  37. package/dist/render/layout.js +414 -0
  38. package/dist/render/layout.js.map +1 -0
  39. package/dist/render/mcp-template.js +239 -0
  40. package/dist/render/mcp-template.js.map +1 -0
  41. package/dist/render/pipeline.js +59 -0
  42. package/dist/render/pipeline.js.map +1 -0
  43. package/dist/render/preview.js +81 -0
  44. package/dist/render/preview.js.map +1 -0
  45. package/dist/render/slug.js +12 -0
  46. package/dist/render/slug.js.map +1 -0
  47. package/dist/render/styles.js +383 -0
  48. package/dist/render/styles.js.map +1 -0
  49. package/dist/render/types.js +2 -0
  50. package/dist/render/types.js.map +1 -0
  51. package/dist/render/wikilink.js +55 -0
  52. package/dist/render/wikilink.js.map +1 -0
  53. package/dist/scan.js +45 -0
  54. package/dist/scan.js.map +1 -0
  55. package/dist/settings.js +157 -0
  56. package/dist/settings.js.map +1 -0
  57. package/dist/util.js +60 -0
  58. package/dist/util.js.map +1 -0
  59. package/package.json +64 -0
@@ -0,0 +1,59 @@
1
+ import { unified } from "unified";
2
+ import remarkParse from "remark-parse";
3
+ import remarkGfm from "remark-gfm";
4
+ import remarkRehype from "remark-rehype";
5
+ import rehypeRaw from "rehype-raw";
6
+ import rehypeSanitize, { defaultSchema } from "rehype-sanitize";
7
+ import rehypeSlug from "rehype-slug";
8
+ import rehypeStringify from "rehype-stringify";
9
+ import matter from "gray-matter";
10
+ import { wikiLinkPlugin } from "./wikilink.js";
11
+ import { embedPlugin } from "./embed.js";
12
+ import { calloutPlugin } from "./callouts.js";
13
+ const sanitizeSchema = {
14
+ ...defaultSchema,
15
+ clobberPrefix: "",
16
+ attributes: {
17
+ ...defaultSchema.attributes,
18
+ "*": [...(defaultSchema.attributes?.["*"] ?? []), "className"],
19
+ img: ["src", "alt", "width", "height", "loading"],
20
+ a: ["href", "title", "className", "id"],
21
+ div: ["className", "data*"],
22
+ },
23
+ };
24
+ export async function renderMarkdown(source, context, fallbackTitle) {
25
+ const parsed = matter(source);
26
+ const fm = parsed.data;
27
+ const outlinks = [];
28
+ const warnings = [];
29
+ // Pre-process the markdown source before parsing.
30
+ // 1. Strip Obsidian-style comments (%% ... %% — single- or multi-line).
31
+ // 2. Escape pipes inside wikilinks/embeds so they don't break GFM tables.
32
+ // CommonMark unescapes `\|` back to `|` in the resulting text node, so
33
+ // the wikilink regex still matches downstream. Negative lookbehind
34
+ // avoids double-escaping pipes the user already escaped Obsidian-style.
35
+ const content = parsed.content
36
+ .replace(/%%[\s\S]*?%%/g, "")
37
+ .replace(/!?\[\[([^\[\]\n]+?)\]\]/g, (m) => m.replace(/(?<!\\)\|/g, "\\|"));
38
+ const file = await unified()
39
+ .use(remarkParse)
40
+ .use(remarkGfm)
41
+ .use(calloutPlugin({ redactRoles: context.redactRoles }))
42
+ .use(embedPlugin({ context, warnings }))
43
+ .use(wikiLinkPlugin({ context, outlinks, warnings }))
44
+ .use(remarkRehype, { allowDangerousHtml: true })
45
+ .use(rehypeRaw)
46
+ .use(rehypeSlug)
47
+ .use(rehypeSanitize, sanitizeSchema)
48
+ .use(rehypeStringify)
49
+ .process(content);
50
+ const title = (typeof fm.title === "string" && fm.title)
51
+ || extractH1(parsed.content)
52
+ || fallbackTitle;
53
+ return { html: String(file), title, frontmatter: fm, outlinks, warnings };
54
+ }
55
+ function extractH1(markdown) {
56
+ const m = /^#\s+(.+)$/m.exec(markdown);
57
+ return m ? m[1].trim() : null;
58
+ }
59
+ //# sourceMappingURL=pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/render/pipeline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ import { unified } from "unified";
2
+ import remarkParse from "remark-parse";
3
+ import remarkGfm from "remark-gfm";
4
+ import remarkRehype from "remark-rehype";
5
+ import rehypeSanitize, { defaultSchema } from "rehype-sanitize";
6
+ import rehypeStringify from "rehype-stringify";
7
+ import { slugify } from "./slug.js";
8
+ const SUMMARY_CHARS = 320;
9
+ const previewPipeline = unified()
10
+ .use(remarkParse)
11
+ .use(remarkGfm)
12
+ .use(remarkRehype)
13
+ .use(rehypeSanitize, {
14
+ ...defaultSchema,
15
+ // Strip away anything heavy or risky for a tiny popover.
16
+ tagNames: defaultSchema.tagNames?.filter((t) => !["img", "iframe", "video"].includes(t)),
17
+ })
18
+ .use(rehypeStringify);
19
+ export async function buildPreview(rawMarkdown, title) {
20
+ // Strip frontmatter and Obsidian %% comments %% before walking the body.
21
+ const body = stripFrontmatter(rawMarkdown).replace(/%%[\s\S]*?%%/g, "").trim();
22
+ const summary = await renderSnippet(body);
23
+ const headings = {};
24
+ // Match headings even when nested inside a callout/blockquote.
25
+ const sectionRe = /^(?:>\s*)?(#{1,6})\s+(.+)$/gm;
26
+ const matches = [...body.matchAll(sectionRe)];
27
+ for (let i = 0; i < matches.length; i++) {
28
+ const m = matches[i];
29
+ const headingTitle = m[2].trim();
30
+ const start = (m.index ?? 0) + m[0].length;
31
+ const next = matches[i + 1];
32
+ const end = next ? next.index ?? body.length : body.length;
33
+ const sectionBody = body.slice(start, end);
34
+ const anchor = slugify(headingTitle);
35
+ headings[anchor] = {
36
+ title: headingTitle,
37
+ summary: await renderSnippet(sectionBody),
38
+ };
39
+ }
40
+ return { title, summary, headings };
41
+ }
42
+ /**
43
+ * Truncate the markdown to the first ~SUMMARY_CHARS of body content (skipping
44
+ * headings, image embeds, tables) and render it to sanitised HTML.
45
+ */
46
+ async function renderSnippet(source) {
47
+ const truncated = truncateMarkdown(source.trim(), SUMMARY_CHARS);
48
+ if (!truncated)
49
+ return "";
50
+ const file = await previewPipeline.process(truncated);
51
+ return String(file).trim();
52
+ }
53
+ function truncateMarkdown(source, maxChars) {
54
+ const paragraphs = source.split(/\n\s*\n/);
55
+ const out = [];
56
+ let total = 0;
57
+ for (const raw of paragraphs) {
58
+ let p = raw.trim()
59
+ .replace(/^>\s?/gm, "") // strip blockquote markers
60
+ .replace(/^\[!\w+\][+-]?[^\n]*\n?/, "") // strip leading [!type] callout marker
61
+ .split("\n").filter((line) => !/^#{1,6}\s/.test(line)).join("\n") // drop heading lines
62
+ .trim();
63
+ if (!p)
64
+ continue;
65
+ if (/^!\[\[/.test(p))
66
+ continue; // skip image embeds
67
+ if (/^\|/.test(p))
68
+ continue; // skip tables
69
+ // Wikilinks aren't in the preview pipeline — render their display text inline.
70
+ p = p.replace(/!?\[\[([^\[\]|#\n]+?)(?:#[^\[\]|#\n]+?)?(?:\|([^\[\]#\n]+?))?\]\]/g, (_, name, alias) => alias ?? name);
71
+ out.push(p);
72
+ total += p.length;
73
+ if (total >= maxChars)
74
+ break;
75
+ }
76
+ return out.join("\n\n");
77
+ }
78
+ function stripFrontmatter(s) {
79
+ return s.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");
80
+ }
81
+ //# sourceMappingURL=preview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview.js","sourceRoot":"","sources":["../../src/render/preview.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ // Stable slug used for wikilink resolution. Mirrors GitHub-style heading slugs
2
+ // so [[Page Title]] always finds the page regardless of how it was capitalised.
3
+ export function slugify(name) {
4
+ return name
5
+ .normalize("NFKD")
6
+ .replace(/[̀-ͯ]/g, "")
7
+ .toLowerCase()
8
+ .replace(/\.md$/i, "")
9
+ .replace(/[^a-z0-9]+/g, "-")
10
+ .replace(/^-+|-+$/g, "");
11
+ }
12
+ //# sourceMappingURL=slug.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slug.js","sourceRoot":"","sources":["../../src/render/slug.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,gFAAgF;AAChF,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,OAAO,IAAI;SACR,SAAS,CAAC,MAAM,CAAC;SACjB,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;SACrB,WAAW,EAAE;SACb,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;SACrB,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,383 @@
1
+ // Theme + layout styles for the rendered wiki. Self-contained, no build step.
2
+ // Light: parchment + scarlet, Dark: charcoal + emerald (auto by prefers-color-scheme).
3
+ /**
4
+ * Per-vault accent overrides. When `accent_color` (light) or `accent_color_dark`
5
+ * is set in settings.md, append this block after DEFAULT_CSS so it wins. The
6
+ * derived shades (--accent-soft, --wikilink-bg) are recomputed via color-mix
7
+ * so they stay coherent with whatever color the user picked.
8
+ */
9
+ export function renderThemeOverride(opts) {
10
+ const blocks = [];
11
+ if (opts.lightAccent)
12
+ blocks.push(accentBlock(":root", opts.lightAccent));
13
+ if (opts.darkAccent) {
14
+ blocks.push(`@media (prefers-color-scheme: dark) {\n${accentBlock(":root", opts.darkAccent)}\n}`);
15
+ }
16
+ return blocks.length === 0 ? "" : "\n\n/* User accent overrides (settings.md) */\n" + blocks.join("\n");
17
+ }
18
+ function accentBlock(selector, color) {
19
+ return `${selector} {
20
+ --accent: ${color};
21
+ --accent-soft: color-mix(in srgb, ${color} 70%, white);
22
+ --wikilink-bg: color-mix(in srgb, ${color} 10%, transparent);
23
+ --wikilink-bg-hover: color-mix(in srgb, ${color} 20%, transparent);
24
+ }`;
25
+ }
26
+ export const DEFAULT_CSS = `:root {
27
+ --bg: #f4ecd8; --fg: #1d1a17; --muted: #6b665e;
28
+ --accent: #a8201a; --accent-soft: #c8423d; --accent-fg: #fbf6e8;
29
+ --rule: #d8cfb8;
30
+ --wikilink-bg: rgba(168,32,26,0.10); --wikilink-bg-hover: rgba(168,32,26,0.20);
31
+ --max-width: 56rem;
32
+ font-family: 'Iowan Old Style', 'Palatino Linotype', Georgia, serif;
33
+ }
34
+ @media (prefers-color-scheme: dark) {
35
+ :root {
36
+ --bg: #181a1a; --fg: #e6e3dc; --muted: #8a908c;
37
+ --accent: #2ecc71; --accent-soft: #58e08c; --accent-fg: #0d1411;
38
+ --rule: #2a2e2c;
39
+ --wikilink-bg: rgba(46,204,113,0.12); --wikilink-bg-hover: rgba(46,204,113,0.22);
40
+ }
41
+ }
42
+ * { box-sizing: border-box; }
43
+ body { margin: 0; background: var(--bg); color: var(--fg); line-height: 1.6; }
44
+ a { color: var(--accent); text-decoration: none; }
45
+ a:hover { text-decoration: underline; }
46
+
47
+ /* Pill styling only for wikilinks inside article body. */
48
+ article a.internal {
49
+ background: var(--wikilink-bg); padding: 0.05em 0.35em; border-radius: 3px;
50
+ text-decoration: none; transition: background 0.12s ease; color: var(--accent);
51
+ }
52
+ article a.internal:hover { background: var(--wikilink-bg-hover); text-decoration: none; }
53
+ article a.internal.new, article a.internal.is-unresolved { opacity: 0.7; font-style: italic; }
54
+
55
+ /* Brand sits at the top of the left sidebar in place of the old top nav.
56
+ The sidebar's flex gap handles spacing — no extra margin or rule needed. */
57
+ .sidebar > .brand {
58
+ display: block; padding: 0 0.5rem;
59
+ font-weight: 700; font-size: 1.05rem; letter-spacing: 0.04em;
60
+ color: var(--fg); text-decoration: none;
61
+ }
62
+ .sidebar > .brand:hover { color: var(--accent); text-decoration: none; }
63
+
64
+ .app-grid {
65
+ display: grid; grid-template-columns: 15rem minmax(0, 56rem) 17rem;
66
+ gap: 2.5rem; max-width: 96rem; margin: 0 auto; padding: 1.5rem;
67
+ }
68
+ main { padding: 2rem 0 4rem; min-width: 0; }
69
+ .sidebar { padding: 1.5rem 1.5rem 1.5rem 0; border-right: 1px solid var(--rule); font-size: 0.9rem; display: flex; flex-direction: column; gap: 0.6rem; }
70
+ /* Visual break between the header group (brand/search/auth) and the sitemap. */
71
+ .sidebar > nav:last-child { margin-top: 0.9rem; padding-top: 0.9rem; border-top: 1px solid var(--rule); }
72
+ .rightbar { padding: 1.5rem 0 1.5rem 1.5rem; border-left: 1px solid var(--rule); font-size: 0.9rem; }
73
+ .sidebar h4, .rightbar h4 {
74
+ margin: 0 0 0.5rem; font-size: 0.75rem; text-transform: uppercase;
75
+ letter-spacing: 0.08em; color: var(--muted); font-weight: 600;
76
+ }
77
+
78
+ @media (max-width: 1100px) {
79
+ .app-grid { grid-template-columns: 1fr; gap: 0; }
80
+ .sidebar { border: none; padding: 0.75rem 0 0; }
81
+ .rightbar { border: none; padding: 1rem 0; border-top: 1px solid var(--rule); margin-top: 2rem; }
82
+ main { padding: 0.75rem 0 3rem; }
83
+ }
84
+
85
+ .search-box { position: relative; }
86
+ #vault-search {
87
+ width: 100%; padding: 0.5rem 0.75rem; font: inherit; font-size: 0.9rem;
88
+ background: var(--bg); color: var(--fg); border: 1px solid var(--rule); border-radius: 4px; outline: none;
89
+ }
90
+ #vault-search:focus { border-color: var(--accent); box-shadow: 0 0 0 2px var(--wikilink-bg); }
91
+ .search-results {
92
+ display: none; position: absolute; top: calc(100% + 4px); left: 0; right: 0;
93
+ background: var(--bg); border: 1px solid var(--rule); border-radius: 4px;
94
+ box-shadow: 0 6px 20px rgba(0,0,0,0.18); max-height: 22rem; overflow-y: auto; z-index: 100;
95
+ }
96
+ .search-result { display: block; padding: 0.6rem 0.75rem; border-bottom: 1px solid var(--rule); color: var(--fg); }
97
+ .search-result:last-child { border-bottom: none; }
98
+ .search-result:hover { background: var(--wikilink-bg); text-decoration: none; }
99
+ .search-result-title { font-weight: 600; color: var(--accent); }
100
+ .search-result-folder { font-size: 0.72rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; margin-top: 0.15rem; }
101
+ .search-empty { padding: 0.75rem; color: var(--muted); font-style: italic; font-size: 0.85rem; }
102
+
103
+ /* Auth box — sits under the search box; populated by JS from a non-HttpOnly
104
+ display cookie set by the Function on login. */
105
+ .auth-box {
106
+ font-size: 0.78rem; color: var(--muted);
107
+ padding: 0.5rem 0.65rem; border: 1px solid var(--rule); border-radius: 4px;
108
+ display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
109
+ flex-wrap: wrap;
110
+ }
111
+ .auth-box:empty { display: none; }
112
+ .auth-box .auth-status strong { color: var(--accent); font-weight: 600; }
113
+ .auth-box .auth-action { color: var(--accent); text-decoration: none; font-weight: 500; }
114
+ .auth-box .auth-action:hover { text-decoration: underline; }
115
+ .search-result-summary {
116
+ font-size: 0.78rem; color: var(--muted); margin-top: 0.25rem; line-height: 1.4;
117
+ display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
118
+ }
119
+ .search-result-summary mark {
120
+ background: color-mix(in srgb, var(--accent) 25%, transparent);
121
+ color: inherit; padding: 0 0.1em; border-radius: 2px;
122
+ }
123
+
124
+ .toc { display: block; }
125
+ .toc-summary {
126
+ list-style: none; cursor: pointer; background: transparent; border: none; padding: 0;
127
+ margin-bottom: 0.5rem; font-size: 0.75rem; text-transform: uppercase;
128
+ letter-spacing: 0.08em; color: var(--muted); font-weight: 600;
129
+ display: inline-flex; align-items: center; gap: 0.4rem;
130
+ transition: color 0.12s ease;
131
+ }
132
+ .toc-summary:hover { color: var(--accent); }
133
+ .toc-summary::-webkit-details-marker { display: none; }
134
+ /* CSS-drawn chevron — same shape as the sitemap-folder toggles. Rotates
135
+ when [open] so the affordance reads as "click to collapse / expand". */
136
+ .toc-summary::before {
137
+ content: ''; display: inline-block;
138
+ width: 5px; height: 5px;
139
+ border-right: 1.5px solid currentColor;
140
+ border-bottom: 1.5px solid currentColor;
141
+ transform: rotate(-45deg);
142
+ transition: transform 0.15s ease;
143
+ opacity: 0.7;
144
+ }
145
+ .toc[open] > .toc-summary::before { transform: rotate(45deg); }
146
+ .toc ul { list-style: none; padding: 0; margin: 0; }
147
+ .toc li { margin: 0.25rem 0; }
148
+ .toc a { color: var(--muted); display: block; padding: 0.1rem 0 0.1rem 0.5rem; border-left: 2px solid transparent; transition: color 0.12s, border-color 0.12s; }
149
+ .toc a:hover { color: var(--accent); border-left-color: var(--accent); text-decoration: none; }
150
+ .toc-d3 a { padding-left: 1.25rem; font-size: 0.85rem; }
151
+ .toc-d4 a { padding-left: 2rem; font-size: 0.8rem; }
152
+
153
+ /* Backlinks panel — same visual rhythm as the TOC. */
154
+ .rightbar .backlinks { margin-top: 1.75rem; }
155
+ .rightbar .backlinks h4 {
156
+ margin: 0 0 0.5rem; font-size: 0.75rem;
157
+ text-transform: uppercase; letter-spacing: 0.08em;
158
+ color: var(--muted); font-weight: 600;
159
+ }
160
+ .rightbar .backlinks ul { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 0.15rem; }
161
+ .rightbar .backlinks a {
162
+ display: block; padding: 0.15rem 0 0.15rem 0.5rem;
163
+ color: var(--muted); text-decoration: none;
164
+ border-left: 2px solid transparent;
165
+ background: transparent;
166
+ transition: color 0.12s, border-color 0.12s;
167
+ }
168
+ .rightbar .backlinks a:hover { color: var(--accent); border-left-color: var(--accent); }
169
+
170
+ .page-meta {
171
+ color: var(--muted); font-size: 0.78rem;
172
+ margin: -0.75rem 0 1.75rem;
173
+ display: flex; gap: 0.4rem; align-items: center; flex-wrap: wrap;
174
+ }
175
+ .page-meta time { font-variant-numeric: tabular-nums; }
176
+ .page-meta .meta-sep { opacity: 0.5; }
177
+
178
+ .crumbs { color: var(--muted); font-size: 0.875rem; margin-bottom: 1rem; }
179
+ .crumbs a { color: var(--muted); text-decoration: none; }
180
+ .crumbs a:hover { color: var(--accent); text-decoration: underline; }
181
+ .crumb-sep { color: var(--muted); font-weight: 600; opacity: 0.7; padding: 0 0.05rem; }
182
+
183
+ article h1 { margin-top: 0; font-size: 2.75rem; line-height: 1.15; }
184
+ article h2 { margin-top: 2rem; border-bottom: 1px solid var(--rule); padding-bottom: 0.25rem; }
185
+ article hr { border: 0; border-top: 1px solid var(--rule); margin: 2rem 0; }
186
+ article img { max-width: 100%; border-radius: 4px; }
187
+
188
+ /* Tables — readable defaults so cell content doesn't run together. */
189
+ article table {
190
+ border-collapse: collapse;
191
+ margin: 1.25rem 0;
192
+ width: 100%;
193
+ }
194
+ article th, article td {
195
+ padding: 0.6rem 0.85rem;
196
+ vertical-align: top;
197
+ border-bottom: 1px solid var(--rule);
198
+ text-align: left;
199
+ }
200
+ article thead th {
201
+ border-bottom: 2px solid var(--rule);
202
+ font-weight: 600;
203
+ }
204
+ article tbody tr:last-child > td { border-bottom: none; }
205
+ article td > img:first-child:last-child {
206
+ /* Solo image in a cell (e.g. portrait + bio layouts) — keep it from sprawling. */
207
+ max-width: 12rem;
208
+ }
209
+ /* Default size for ![[image]] embeds without an explicit |N hint. The
210
+ width itself is set by --default-img-width on <body>, configurable via
211
+ the default_image_width setting. */
212
+ article img.default-width { width: var(--default-img-width, 50vw); max-width: 100%; }
213
+
214
+ /* Centre images when settings.center_images is on. Scoped to standalone
215
+ <p><img></p> wrappers (Markdown emits these for image-only paragraphs)
216
+ so inline images mid-sentence aren't displaced. */
217
+ body.center-images article p > img:only-child { display: block; margin-left: auto; margin-right: auto; }
218
+ body.center-images article p:has(> img:only-child) { text-align: center; }
219
+
220
+ /* Lightbox — click an image in the article body to view it full-size. */
221
+ article img { cursor: zoom-in; }
222
+ .lightbox-overlay {
223
+ position: fixed; inset: 0; z-index: 1000;
224
+ display: flex; align-items: center; justify-content: center;
225
+ background: rgba(0, 0, 0, 0.85);
226
+ cursor: zoom-out;
227
+ animation: lightbox-fade 0.12s ease-out;
228
+ }
229
+ .lightbox-overlay img {
230
+ max-width: 95vw; max-height: 95vh;
231
+ object-fit: contain; border-radius: 4px;
232
+ box-shadow: 0 8px 40px rgba(0, 0, 0, 0.5);
233
+ }
234
+ @keyframes lightbox-fade { from { opacity: 0; } to { opacity: 1; } }
235
+ article code { background: color-mix(in srgb, var(--muted) 12%, transparent); padding: 0.1em 0.35em; border-radius: 3px; font-size: 0.9em; }
236
+ article pre { background: color-mix(in srgb, var(--muted) 12%, transparent); padding: 1rem; border-radius: 6px; overflow-x: auto; }
237
+ article pre code { background: none; padding: 0; }
238
+ article blockquote { margin: 1rem 0; padding: 0.5rem 1rem; border-left: 3px solid var(--rule); color: var(--muted); }
239
+
240
+ /* Sitemap nav (in left sidebar). Tree-view layout: folder rows split into a
241
+ chevron toggle column and a folder-link column with a divider between;
242
+ page rows align with the folder-link column so all names start at the same x. */
243
+ .sidebar .sitemap-list, .sidebar .sitemap-list ul { list-style: none; padding: 0; margin: 0; }
244
+ .sidebar .sitemap-list li { margin: 0; }
245
+
246
+ .sidebar .sitemap-list a,
247
+ .sidebar .sitemap-list a.internal,
248
+ .sidebar .sitemap-list a.internal-link {
249
+ display: block;
250
+ padding: 0.2rem 0.4rem 0.2rem 1.5rem; /* aligns with folder-link text */
251
+ background: transparent;
252
+ color: var(--muted);
253
+ text-decoration: none;
254
+ border-radius: 3px;
255
+ line-height: 1.45;
256
+ }
257
+ .sidebar .sitemap-list a:hover {
258
+ background: var(--wikilink-bg); color: var(--accent);
259
+ }
260
+ .sidebar .sitemap-list a[aria-current="page"] {
261
+ color: var(--accent); font-weight: 600; background: var(--wikilink-bg);
262
+ }
263
+
264
+ .sidebar .sitemap-folder > details > summary {
265
+ display: flex;
266
+ align-items: stretch;
267
+ cursor: pointer;
268
+ user-select: none;
269
+ font-weight: 500;
270
+ color: var(--muted);
271
+ border-radius: 3px;
272
+ line-height: 1.45;
273
+ list-style: none; /* hide native disclosure marker (Firefox) */
274
+ }
275
+ .sidebar .sitemap-folder > details > summary::-webkit-details-marker { display: none; }
276
+
277
+ .sidebar .sitemap-folder > details > summary > .folder-toggle {
278
+ position: relative;
279
+ flex: 0 0 1.25rem;
280
+ border-right: 1px solid var(--rule);
281
+ border-radius: 3px 0 0 3px;
282
+ }
283
+ .sidebar .sitemap-folder > details > summary:hover > .folder-toggle {
284
+ background: var(--wikilink-bg);
285
+ }
286
+ /* CSS-drawn chevron — crisper and more compact than any Unicode glyph. */
287
+ .sidebar .sitemap-folder > details > summary > .folder-toggle::before {
288
+ content: '';
289
+ position: absolute;
290
+ left: 50%;
291
+ top: 50%;
292
+ width: 5px;
293
+ height: 5px;
294
+ border-right: 1.5px solid currentColor;
295
+ border-bottom: 1.5px solid currentColor;
296
+ transform: translate(-65%, -65%) rotate(-45deg);
297
+ transform-origin: center;
298
+ opacity: 0.65;
299
+ transition: transform 0.15s ease, opacity 0.15s ease;
300
+ }
301
+ .sidebar .sitemap-folder > details[open] > summary > .folder-toggle::before {
302
+ transform: translate(-50%, -50%) rotate(45deg);
303
+ }
304
+ .sidebar .sitemap-folder > details > summary:hover > .folder-toggle::before {
305
+ opacity: 1;
306
+ }
307
+
308
+ .sidebar .sitemap-folder > details > summary > .folder-link {
309
+ flex: 1;
310
+ display: block;
311
+ padding: 0.2rem 0.4rem 0.2rem 0.25rem;
312
+ color: inherit;
313
+ text-decoration: none;
314
+ border-radius: 0 3px 3px 0;
315
+ }
316
+ .sidebar .sitemap-folder > details > summary > .folder-link:hover {
317
+ background: var(--wikilink-bg);
318
+ color: var(--accent);
319
+ text-decoration: none;
320
+ }
321
+
322
+ /* Children of a folder indent uniformly — chevron position is preserved
323
+ relative to the nested ul so descendants form a clean tree. */
324
+ .sidebar .sitemap-folder > details > .sitemap-list {
325
+ padding-left: 0.75rem;
326
+ }
327
+
328
+ /* 404 page — leans on the standard article layout but bumps the lead text. */
329
+ .lead-404 { font-size: 1.05rem; color: var(--muted); margin-top: 0.5rem; }
330
+
331
+ /* Auto-generated folder index pages */
332
+ .folder-count { color: var(--muted); margin-bottom: 1.5rem; font-size: 0.9rem; }
333
+ .folder-listing { list-style: none; padding: 0; margin: 0; }
334
+ .folder-listing > li { padding: 0.6rem 0; border-bottom: 1px solid var(--rule); }
335
+ .folder-listing > li:last-child { border-bottom: none; }
336
+
337
+ /* Callouts */
338
+ .callout {
339
+ margin: 1rem 0; padding: 0.75rem 1rem; border-left: 4px solid var(--muted);
340
+ border-radius: 0 4px 4px 0; background: color-mix(in srgb, var(--muted) 8%, transparent);
341
+ }
342
+ .callout > .callout-title { font-weight: 700; margin-bottom: 0.35rem; color: var(--muted); text-transform: uppercase; font-size: 0.8rem; letter-spacing: 0.05em; }
343
+ .callout > *:last-child { margin-bottom: 0; }
344
+ .callout-note, .callout-info { border-left-color: #3b7bbf; background: color-mix(in srgb, #3b7bbf 10%, transparent); }
345
+ .callout-note > .callout-title, .callout-info > .callout-title { color: #3b7bbf; }
346
+ .callout-tip, .callout-hint { border-left-color: #2a8b58; background: color-mix(in srgb, #2a8b58 10%, transparent); }
347
+ .callout-tip > .callout-title, .callout-hint > .callout-title { color: #2a8b58; }
348
+ .callout-warning, .callout-caution { border-left-color: #c89a4d; background: color-mix(in srgb, #c89a4d 12%, transparent); }
349
+ .callout-warning > .callout-title, .callout-caution > .callout-title { color: #a87a2d; }
350
+ .callout-danger, .callout-error { border-left-color: #b94a3a; background: color-mix(in srgb, #b94a3a 10%, transparent); }
351
+ .callout-danger > .callout-title, .callout-error > .callout-title { color: #b94a3a; }
352
+ .callout-dm { border-left-color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); }
353
+ .callout-dm > .callout-title { color: var(--accent); }
354
+
355
+ /* Embed (transcluded ![[Page]]) */
356
+ .embed {
357
+ position: relative; border-left: 3px solid var(--accent-soft);
358
+ padding: 0.75rem 1rem 1.1rem; margin: 1rem 0; background: var(--wikilink-bg); border-radius: 0 4px 4px 0;
359
+ }
360
+ .embed > *:first-child { margin-top: 0; }
361
+ .embed > .embed-source { margin-bottom: 0 !important; }
362
+ .embed-source {
363
+ position: absolute; bottom: 0.3rem; right: 0.75rem; margin: 0 !important;
364
+ font-size: 0.72rem; line-height: 1;
365
+ }
366
+ .embed-source a.internal { background: transparent; padding: 0; color: var(--muted); border-radius: 0; }
367
+ .embed-source a.internal:hover { background: transparent; color: var(--accent); }
368
+ .embed-broken { border-left-color: #b94a3a; color: var(--muted); font-style: italic; }
369
+ .embed-cycle, .embed-truncated { border-left-color: var(--muted); color: var(--muted); font-style: italic; }
370
+
371
+ /* Hover preview popover */
372
+ .wiki-preview {
373
+ position: absolute; display: none; max-width: 22rem;
374
+ padding: 0.75rem 1rem; background: var(--bg); color: var(--fg);
375
+ border: 1px solid var(--rule); border-left: 3px solid var(--accent);
376
+ border-radius: 4px; box-shadow: 0 6px 20px rgba(0,0,0,0.18);
377
+ font-size: 0.9rem; line-height: 1.45; z-index: 1000;
378
+ }
379
+ .wiki-preview-title { font-weight: 700; margin-bottom: 0.2rem; }
380
+ .wiki-preview-subheading { color: var(--accent); font-size: 0.8rem; margin-bottom: 0.4rem; font-style: italic; }
381
+ .wiki-preview-body { color: var(--muted); }
382
+ `;
383
+ //# sourceMappingURL=styles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.js","sourceRoot":"","sources":["../../src/render/styles.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,uFAAuF;AAEvF;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAiD;IACnF,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,IAAI,CAAC,WAAW;QAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;IAC1E,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;QACpB,MAAM,CAAC,IAAI,CAAC,0CAA0C,WAAW,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IACpG,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,iDAAiD,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1G,CAAC;AAED,SAAS,WAAW,CAAC,QAAgB,EAAE,KAAa;IAClD,OAAO,GAAG,QAAQ;cACN,KAAK;sCACmB,KAAK;sCACL,KAAK;4CACC,KAAK;EAC/C,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoW1B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/render/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,55 @@
1
+ import { findAndReplace } from "mdast-util-find-and-replace";
2
+ import { slugify } from "./slug.js";
3
+ // Matches [[Page]], [[Page|alias]], [[Page#anchor]], [[Page#anchor|alias]].
4
+ // Negative lookbehind blocks ![[embed]] from being consumed here.
5
+ const WIKILINK_RE = /(?<!!)(?<!\[)\[\[([^\[\]|#\n]+?)(?:#([^\[\]|#\n]+?))?(?:\|([^\[\]#\n]+?))?\]\]/g;
6
+ export function wikiLinkPlugin(opts) {
7
+ return () => (tree) => {
8
+ findAndReplace(tree, [
9
+ [
10
+ WIKILINK_RE,
11
+ (_match, rawName, rawAnchor, rawAlias) => {
12
+ const name = rawName.trim();
13
+ const anchor = rawAnchor?.trim();
14
+ const display = rawAlias?.trim() ?? name;
15
+ const slug = slugify(name);
16
+ // Resolution order:
17
+ // 1. Slug of the full input (matches basename slugs and aliases)
18
+ // 2. Full-path slug (e.g. [[NPCs/Aldric]])
19
+ // 3. `<slug>/index` (so a bare [[NPCs]] picks up the auto-
20
+ // generated folder index)
21
+ // 4. Last path segment slug (so [[Scenarios/The Open Door]]
22
+ // still resolves when the file actually lives elsewhere
23
+ // under that basename — Obsidian treats the slash form as
24
+ // a path; we're more lenient).
25
+ const lastSegment = name.includes("/") ? name.split("/").pop() : "";
26
+ const page = opts.context.pages.get(slug)
27
+ ?? opts.context.pages.get(slugify(name.replace(/\.md$/i, "").replace(/\//g, "/")))
28
+ ?? opts.context.pages.get(slugify(name + "/index"))
29
+ ?? (lastSegment ? opts.context.pages.get(slugify(lastSegment)) : undefined);
30
+ const href = page != null
31
+ ? "/" + page.path.replace(/\.md$/i, "").split("/").map(encodeURIComponent).join("/") + (anchor ? `#${anchor}` : "")
32
+ : "#";
33
+ if (page && opts.outlinks)
34
+ opts.outlinks.push(page.path);
35
+ if (!page && opts.warnings)
36
+ opts.warnings.push({ kind: "broken-link", target: name });
37
+ // Mirror Obsidian's DOM: `internal-link` is the canonical class community
38
+ // snippets target. We also keep `internal` (and `new` for unresolved) for
39
+ // our default CSS.
40
+ const className = page != null
41
+ ? ["internal", "internal-link"]
42
+ : ["internal", "internal-link", "is-unresolved", "new"];
43
+ const node = {
44
+ type: "link",
45
+ url: href,
46
+ children: [{ type: "text", value: display }],
47
+ data: { hProperties: { className } },
48
+ };
49
+ return node;
50
+ },
51
+ ],
52
+ ]);
53
+ };
54
+ }
55
+ //# sourceMappingURL=wikilink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wikilink.js","sourceRoot":"","sources":["../../src/render/wikilink.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE7D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,4EAA4E;AAC5E,kEAAkE;AAClE,MAAM,WAAW,GAAG,iFAAiF,CAAC;AAEtG,MAAM,UAAU,cAAc,CAAC,IAM9B;IACC,OAAO,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE;QACpB,cAAc,CAAC,IAAI,EAAE;YACnB;gBACE,WAAW;gBACX,CAAC,MAAc,EAAE,OAAe,EAAE,SAAkB,EAAE,QAAiB,EAAE,EAAE;oBACzE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;oBAC5B,MAAM,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,CAAC;oBACjC,MAAM,OAAO,GAAG,QAAQ,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC;oBACzC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;oBAE3B,oBAAoB;oBACpB,mEAAmE;oBACnE,6CAA6C;oBAC7C,6DAA6D;oBAC7D,+BAA+B;oBAC/B,8DAA8D;oBAC9D,6DAA6D;oBAC7D,+DAA+D;oBAC/D,oCAAoC;oBACpC,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAG,CAAC,CAAC,CAAC,EAAE,CAAC;oBACrE,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;2BACpC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;2BAC/E,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,GAAG,QAAQ,CAAC,CAAC;2BAChD,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;oBAC9E,MAAM,IAAI,GAAG,IAAI,IAAI,IAAI;wBACvB,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;wBACnH,CAAC,CAAC,GAAG,CAAC;oBAER,IAAI,IAAI,IAAI,IAAI,CAAC,QAAQ;wBAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBACzD,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ;wBAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;oBAEtF,0EAA0E;oBAC1E,0EAA0E;oBAC1E,mBAAmB;oBACnB,MAAM,SAAS,GAAG,IAAI,IAAI,IAAI;wBAC5B,CAAC,CAAC,CAAC,UAAU,EAAE,eAAe,CAAC;wBAC/B,CAAC,CAAC,CAAC,UAAU,EAAE,eAAe,EAAE,eAAe,EAAE,KAAK,CAAC,CAAC;oBAE1D,MAAM,IAAI,GAAS;wBACjB,IAAI,EAAE,MAAM;wBACZ,GAAG,EAAE,IAAI;wBACT,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAiB,CAAC;wBAC3D,IAAI,EAAE,EAAE,WAAW,EAAE,EAAE,SAAS,EAAE,EAAE;qBACrC,CAAC;oBACF,OAAO,IAAI,CAAC;gBACd,CAAC;aACF;SACF,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC"}