imprensa 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 (44) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +51 -0
  3. package/default.css +239 -0
  4. package/dist/client-runtime-D7MhMWCo.d.mts +45 -0
  5. package/dist/components/doc.d.mts +2 -0
  6. package/dist/components/doc.mjs +2 -0
  7. package/dist/components/icons.d.mts +23 -0
  8. package/dist/components/icons.mjs +40 -0
  9. package/dist/components/index.d.mts +33 -0
  10. package/dist/components/index.mjs +253 -0
  11. package/dist/core/client-runtime.d.mts +2 -0
  12. package/dist/core/client-runtime.mjs +121 -0
  13. package/dist/core/prerender-core.d.mts +2 -0
  14. package/dist/core/prerender-core.mjs +2 -0
  15. package/dist/core/runtime.d.mts +3 -0
  16. package/dist/core/runtime.mjs +3 -0
  17. package/dist/core/shiki-build.d.mts +9 -0
  18. package/dist/core/shiki-build.mjs +34 -0
  19. package/dist/doc-pager-D-YhwEQN.d.mts +27 -0
  20. package/dist/doc-toolbar-DUQS2gnK.mjs +460 -0
  21. package/dist/docs/config.d.mts +13 -0
  22. package/dist/docs/config.mjs +10 -0
  23. package/dist/docs/landing-shiki.d.mts +7 -0
  24. package/dist/docs/landing-shiki.mjs +7 -0
  25. package/dist/docs/mdx.d.mts +79 -0
  26. package/dist/docs/mdx.mjs +293 -0
  27. package/dist/docs/rehype.d.mts +25 -0
  28. package/dist/docs/rehype.mjs +2 -0
  29. package/dist/frontmatter-DVneGjCO.mjs +16 -0
  30. package/dist/global-search-Dfv8DYN3.mjs +310 -0
  31. package/dist/index.d.mts +41 -0
  32. package/dist/index.mjs +668 -0
  33. package/dist/prerender-core-D4Li--RS.mjs +172 -0
  34. package/dist/prerender-core-DBi9ntWW.d.mts +48 -0
  35. package/dist/rehype-BWpGaBql.mjs +182 -0
  36. package/dist/search-store-DDGHRAKl.mjs +64 -0
  37. package/dist/shiki-gFey7C-z.d.mts +3289 -0
  38. package/dist/sidebar-layout-DsEhSkJS.mjs +43 -0
  39. package/dist/socials-BIszPk-A.d.mts +8 -0
  40. package/docs/architecture.md +26 -0
  41. package/docs/integration-notes.md +6 -0
  42. package/index.d.ts +49 -0
  43. package/package.json +128 -0
  44. package/tsconfig.json +28 -0
@@ -0,0 +1,172 @@
1
+ //#region src/core/prerender-head.ts
2
+ function flattenTagProps(entry) {
3
+ const props = {};
4
+ for (const [key, val] of Object.entries(entry)) if (typeof val === "string") props[key] = val;
5
+ else if (typeof val === "number" || typeof val === "boolean") props[key] = String(val);
6
+ return props;
7
+ }
8
+ function isPlainTag(entry) {
9
+ return !("call" in entry);
10
+ }
11
+ function headMetaEntries(head) {
12
+ if (!Array.isArray(head.meta)) return [];
13
+ return head.meta.flatMap((entry) => typeof entry === "object" && entry !== null && isPlainTag(entry) ? [flattenTagProps(entry)] : []);
14
+ }
15
+ function headLinkEntries(head) {
16
+ if (!Array.isArray(head.link)) return [];
17
+ return head.link.flatMap((entry) => typeof entry === "object" && entry !== null && isPlainTag(entry) ? [flattenTagProps(entry)] : []);
18
+ }
19
+ function appendCanonicalTags(links, meta, canonicalUrl) {
20
+ return {
21
+ links: [...links.filter((link) => link.rel !== "canonical"), {
22
+ rel: "canonical",
23
+ href: canonicalUrl
24
+ }],
25
+ meta: [
26
+ ...meta.filter((item) => item.property !== "og:url" && item.name !== "twitter:url"),
27
+ {
28
+ property: "og:url",
29
+ content: canonicalUrl
30
+ },
31
+ {
32
+ name: "twitter:url",
33
+ content: canonicalUrl
34
+ }
35
+ ]
36
+ };
37
+ }
38
+ //#endregion
39
+ //#region src/core/shiki-client-langs.ts
40
+ function normalizeShikiLangId(lang) {
41
+ return lang === "ts" ? "typescript" : lang;
42
+ }
43
+ /** Grammars from `shiki.langs` (normalized), used for MDX build and browser alike. */
44
+ function resolveShikiLangs(options) {
45
+ if (options === false) return ["typescript"];
46
+ return [...new Set(options?.langs ?? ["typescript"])].map(normalizeShikiLangId);
47
+ }
48
+ /** Theme ids from `shiki.themes` (values), used for MDX build and browser alike. */
49
+ function resolveShikiThemeIds(options) {
50
+ if (options === false) return ["night-owl-light", "houston"];
51
+ const shiki = options ?? {};
52
+ if (!shiki.themes) return ["night-owl-light", "houston"];
53
+ return [...new Set(Object.values(shiki.themes))];
54
+ }
55
+ //#endregion
56
+ //#region src/core/snippet-shiki.ts
57
+ const WRAPPER_CLASS = "max-w-full overflow-x-auto rounded-xl border border-areia-border text-xs leading-relaxed [&_pre]:min-w-max [&_pre]:p-4 [&_pre]:text-xs [&_pre]:leading-relaxed [&_pre]:!m-0";
58
+ function themePair(shiki) {
59
+ if (shiki === false || !shiki?.themes) return {
60
+ light: "night-owl-light",
61
+ dark: "houston"
62
+ };
63
+ return shiki.themes;
64
+ }
65
+ async function getHighlighter(shiki) {
66
+ const { createConfiguredHighlighterCore } = await import("./core/shiki-build.mjs");
67
+ return createConfiguredHighlighterCore(resolveShikiThemeIds(shiki), resolveShikiLangs(shiki));
68
+ }
69
+ async function codeToSnippetHtml(code, lang, shiki) {
70
+ const h = await getHighlighter(shiki);
71
+ const themes = themePair(shiki);
72
+ return `<div class="${WRAPPER_CLASS}" data-imprensa-snippet>${h.codeToHtml(code, {
73
+ lang,
74
+ themes
75
+ })}</div>`;
76
+ }
77
+ const SNIPPET_SLOT_RE = /(<div data-ilha-slot="[^"]*" data-ilha-props='[^']*'>)(<div class="[^"]*" data-imprensa-snippet>[\s\S]*?<\/div>)(<\/div>)/g;
78
+ /** Paint Snippet islands in prerendered HTML (same Shiki themes as MDX). */
79
+ async function paintSnippetSlotsInHtml(html, shiki) {
80
+ if (shiki === false) return html;
81
+ let out = html;
82
+ const matches = [...html.matchAll(SNIPPET_SLOT_RE)];
83
+ for (const match of matches) {
84
+ const propsMatch = match[1].match(/data-ilha-props='([^']*)'/);
85
+ if (!propsMatch) continue;
86
+ try {
87
+ const props = JSON.parse(propsMatch[1]);
88
+ if (typeof props.code !== "string" || typeof props.lang !== "string") continue;
89
+ const painted = await codeToSnippetHtml(props.code, props.lang, shiki);
90
+ out = out.replace(match[0], `${match[1]}${painted}${match[3]}`);
91
+ } catch {}
92
+ }
93
+ return out;
94
+ }
95
+ //#endregion
96
+ //#region src/core/prerender-core.ts
97
+ function encodeBase64(value) {
98
+ const bufferCtor = globalThis.Buffer;
99
+ if (bufferCtor) return bufferCtor.from(value, "utf8").toString("base64");
100
+ return btoa(unescape(encodeURIComponent(value)));
101
+ }
102
+ function createPrerender(options) {
103
+ return async function prerender(data) {
104
+ const url = data?.url ?? "/";
105
+ const mdxPage = await options.renderMdx?.(url);
106
+ options.setPrerenderedMdxHtml?.(mdxPage?.html);
107
+ let renderedHtml = await options.pageRouter.renderHydratable(url, options.registry, { snapshot: true });
108
+ if (options.shiki !== false) renderedHtml = await paintSnippetSlotsInHtml(renderedHtml, options.shiki);
109
+ const html = renderedHtml.replace(/<script/gi, "&lt;script").replace(/<\/script>/gi, "&lt;/script&gt;");
110
+ const canonicalUrl = options.hostname ? new URL(url, options.hostname).href : void 0;
111
+ const mergedHead = {
112
+ ...options.headDefaults,
113
+ ...await options.getMdxHead?.(url)
114
+ };
115
+ let linkTags = headLinkEntries(mergedHead);
116
+ let metaTags = headMetaEntries(mergedHead);
117
+ if (canonicalUrl) {
118
+ const canonical = appendCanonicalTags(linkTags, metaTags, canonicalUrl);
119
+ linkTags = canonical.links;
120
+ metaTags = canonical.meta;
121
+ }
122
+ const head = {};
123
+ if (mergedHead.title) head.title = String(mergedHead.title);
124
+ const elements = /* @__PURE__ */ new Set();
125
+ for (const m of metaTags) elements.add({
126
+ type: "meta",
127
+ props: m
128
+ });
129
+ for (const l of linkTags) elements.add({
130
+ type: "link",
131
+ props: l
132
+ });
133
+ if (mdxPage && options.hostname) {
134
+ const pageUrl = new URL(url, options.hostname).href;
135
+ const metaList = metaTags;
136
+ const title = typeof mergedHead.title === "string" && mergedHead.title || metaList.find((m) => m.property === "og:title")?.content || "Documentation";
137
+ const description = metaList.find((m) => m.name === "description")?.content;
138
+ const jsonLd = {
139
+ "@context": "https://schema.org",
140
+ "@type": "TechArticle",
141
+ headline: title,
142
+ url: pageUrl,
143
+ mainEntityOfPage: pageUrl,
144
+ ...description ? { description } : {},
145
+ publisher: {
146
+ "@type": "Organization",
147
+ name: "Imprensa",
148
+ url: options.hostname
149
+ }
150
+ };
151
+ elements.add({
152
+ type: "script",
153
+ props: { type: "application/ld+json" },
154
+ children: JSON.stringify(jsonLd)
155
+ });
156
+ }
157
+ if (elements.size > 0) head.elements = elements;
158
+ const links = options.pageRouter.routes().map((route) => route.pattern).filter((link) => !link.includes("*"));
159
+ for (const routePath of options.mdxRoutes ?? []) links.push(routePath);
160
+ return {
161
+ html,
162
+ head: Object.keys(head).length > 0 ? head : void 0,
163
+ links: new Set(links),
164
+ data: mdxPage ? {
165
+ mdxHtmlBase64: encodeBase64(mdxPage.html),
166
+ mdxPath: mdxPage.path
167
+ } : void 0
168
+ };
169
+ };
170
+ }
171
+ //#endregion
172
+ export { resolveShikiThemeIds as i, codeToSnippetHtml as n, resolveShikiLangs as r, createPrerender as t };
@@ -0,0 +1,48 @@
1
+ import { t as ImprensaShikiOptions } from "./shiki-gFey7C-z.mjs";
2
+ import { HydrateOptions, RouterBuilder } from "@ilha/router";
3
+ import { PrerenderArguments } from "vite-prerender-plugin";
4
+ import { Island } from "ilha";
5
+ import { ResolvableHead } from "unhead/types";
6
+
7
+ //#region src/core/ilha-types.d.ts
8
+ /** Island registry produced by `@ilha/router` codegen (`ilha:pages/*`). */
9
+ type ImprensaIslandRegistry = Record<string, Island<Record<string, unknown>, Record<string, unknown>>>;
10
+ /** Re-export router surface used by imprensa runtime and prerender (matches `RouterBuilder`). */
11
+ type ImprensaPageRouter = RouterBuilder;
12
+ //#endregion
13
+ //#region src/core/prerender-core.d.ts
14
+ /** Minimal router surface for apps that re-export codegen `pageRouter` into prerender. */
15
+ type RouterLike = Pick<ImprensaPageRouter, "mount" | "hydrate" | "renderHydratable" | "routes">;
16
+ type RenderedMdx = {
17
+ html: string;
18
+ path: string;
19
+ } | undefined;
20
+ type ImprensaPrerenderOptions = {
21
+ pageRouter: RouterLike;
22
+ registry: ImprensaIslandRegistry;
23
+ mdxRoutes?: Iterable<string>;
24
+ renderMdx?: (url: string) => RenderedMdx | Promise<RenderedMdx>;
25
+ setPrerenderedMdxHtml?: (html: string | undefined) => void;
26
+ getMdxHead?: (url: string) => ResolvableHead | undefined | Promise<ResolvableHead | undefined>;
27
+ headDefaults?: ResolvableHead | null;
28
+ hostname?: string;
29
+ shiki?: ImprensaShikiOptions;
30
+ };
31
+ declare function createPrerender(options: ImprensaPrerenderOptions): (data?: PrerenderArguments) => Promise<{
32
+ html: string;
33
+ head: {
34
+ title?: string;
35
+ elements?: Set<{
36
+ type: string;
37
+ props: Record<string, string>;
38
+ children?: string;
39
+ }>;
40
+ } | undefined;
41
+ links: Set<string>;
42
+ data: {
43
+ mdxHtmlBase64: string;
44
+ mdxPath: string;
45
+ } | undefined;
46
+ }>;
47
+ //#endregion
48
+ export { ImprensaIslandRegistry as a, HydrateOptions as i, RouterLike as n, createPrerender as r, ImprensaPrerenderOptions as t };
@@ -0,0 +1,182 @@
1
+ import path from "node:path";
2
+ import fs from "node:fs";
3
+ //#region src/docs/rehype/types.ts
4
+ function isLocalLink(value) {
5
+ return typeof value === "string" && value.startsWith("/") && !value.startsWith("//") && !value.startsWith("/__");
6
+ }
7
+ //#endregion
8
+ //#region src/docs/rehype/route-graph.ts
9
+ const routesFile = path.join(process.cwd(), ".ilha", "routes.ts");
10
+ const pagesDir = path.join(process.cwd(), "src", "pages");
11
+ let routeCache;
12
+ function normalizeRoute(value) {
13
+ try {
14
+ return new URL(value, "http://localhost").pathname.replace(/\/+$/, "") || "/";
15
+ } catch {
16
+ return value.replace(/\/+$/, "") || "/";
17
+ }
18
+ }
19
+ function routePatternToRegex(pattern) {
20
+ if (pattern === "/") return /^\/$/;
21
+ const source = pattern.replace(/\/+$/, "").split("/").filter(Boolean).map((segment) => {
22
+ if (segment === "*") return "[^/]+";
23
+ if (segment.startsWith(":")) return "[^/]+";
24
+ return segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
25
+ }).join("/");
26
+ return new RegExp(`^/${source}$`);
27
+ }
28
+ function pageFileToRoute(filePath) {
29
+ const routePath = path.relative(pagesDir, filePath).replace(/\\/g, "/").replace(/\.(?:[cm]?[jt]sx?|mdx?)$/, "").replace(/\/index$/, "").split("/").filter((segment) => !segment.startsWith("+") && !/^\(.+\)$/.test(segment)).join("/");
30
+ if (!routePath || routePath === "index") return "/";
31
+ if (routePath.includes("[")) return void 0;
32
+ return normalizeRoute(`/${routePath}`);
33
+ }
34
+ function walk(dir, callback) {
35
+ if (!fs.existsSync(dir)) return;
36
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
37
+ const entryPath = path.join(dir, entry.name);
38
+ if (entry.isDirectory()) walk(entryPath, callback);
39
+ else callback(entryPath);
40
+ }
41
+ }
42
+ function getRoutes() {
43
+ if (routeCache) return routeCache;
44
+ const concrete = /* @__PURE__ */ new Set();
45
+ const patterns = [];
46
+ if (fs.existsSync(routesFile)) {
47
+ const source = fs.readFileSync(routesFile, "utf8");
48
+ for (const match of source.matchAll(/\.route\(\s*["'`]([^"'`]+)["'`]/g)) {
49
+ const routePath = normalizeRoute(match[1]);
50
+ if (routePath.includes("**")) continue;
51
+ if (/[*:]/.test(routePath)) patterns.push(routePatternToRegex(routePath));
52
+ else concrete.add(routePath);
53
+ }
54
+ }
55
+ walk(pagesDir, (filePath) => {
56
+ if (!/\.(?:[cm]?[jt]sx?|mdx?)$/.test(filePath)) return;
57
+ const routePath = pageFileToRoute(filePath);
58
+ if (routePath) concrete.add(routePath);
59
+ });
60
+ routeCache = {
61
+ concrete,
62
+ patterns
63
+ };
64
+ return routeCache;
65
+ }
66
+ function hasRoute(href) {
67
+ const routePath = normalizeRoute(href);
68
+ const { concrete, patterns } = getRoutes();
69
+ return concrete.has(routePath) || patterns.some((pattern) => pattern.test(routePath));
70
+ }
71
+ function walkPages(callback) {
72
+ walk(pagesDir, callback);
73
+ }
74
+ //#endregion
75
+ //#region src/docs/rehype/heading-utils.ts
76
+ let anchorCache;
77
+ function textContent(node) {
78
+ if (!node) return "";
79
+ if (typeof node.value === "string") return node.value;
80
+ return (node.children ?? []).map(textContent).join("");
81
+ }
82
+ function slugifyHeading(value) {
83
+ return value.trim().toLowerCase().replace(/<[^>]+>/g, "").replace(/[^\w\s-]/g, "").replace(/\s+/g, "-");
84
+ }
85
+ function collectHeadings(node, headings = []) {
86
+ if (!node) return headings;
87
+ if (node.type === "element" && /^h[1-6]$/.test(node.tagName ?? "")) headings.push(node);
88
+ for (const child of node.children ?? []) if (child) collectHeadings(child, headings);
89
+ return headings;
90
+ }
91
+ function headingId(node) {
92
+ const id = node.properties?.id;
93
+ return typeof id === "string" ? id : slugifyHeading(textContent(node));
94
+ }
95
+ function markdownHeadingIds(source) {
96
+ const ids = /* @__PURE__ */ new Set();
97
+ for (const match of source.matchAll(/^#{1,6}\s+(.+)$/gm)) ids.add(slugifyHeading(match[1].replace(/\s+#+\s*$/, "")));
98
+ return ids;
99
+ }
100
+ function getAnchorMap() {
101
+ if (anchorCache) return anchorCache;
102
+ const anchors = /* @__PURE__ */ new Map();
103
+ walkPages((filePath) => {
104
+ if (!/\.mdx?$/.test(filePath)) return;
105
+ const routePath = pageFileToRoute(filePath);
106
+ if (!routePath) return;
107
+ anchors.set(routePath, markdownHeadingIds(fs.readFileSync(filePath, "utf8")));
108
+ });
109
+ anchorCache = anchors;
110
+ return anchorCache;
111
+ }
112
+ function fail(file, node, rule, reason) {
113
+ const place = node?.position?.start ? {
114
+ line: node.position.start.line,
115
+ column: node.position.start.column
116
+ } : void 0;
117
+ const filePath = file.path ? path.relative(process.cwd(), file.path) : "unknown file";
118
+ const suffix = place?.line ? `:${place.line}:${place.column ?? 1}` : "";
119
+ throw new Error(`${filePath}${suffix} error ${rule} ${reason}`);
120
+ }
121
+ function visitLinks(node, callback) {
122
+ if (!node) return;
123
+ if (node.type === "element" && node.tagName === "a" && isLocalLink(node.properties?.href)) callback(node, node.properties.href);
124
+ for (const child of node.children ?? []) if (child) visitLinks(child, callback);
125
+ }
126
+ //#endregion
127
+ //#region src/docs/rehype/preview.ts
128
+ function rehypePreview() {
129
+ function visit(node) {
130
+ if (!node.children) return;
131
+ for (let i = 0; i < node.children.length; i++) {
132
+ const child = node.children[i];
133
+ if (child.type === "element" && child.tagName === "pre" && child.children?.[0]?.tagName === "code") {
134
+ const code = child.children[0];
135
+ if (!(code.properties?.metastring ?? code.properties?.meta ?? "").includes("preview")) {
136
+ visit(child);
137
+ continue;
138
+ }
139
+ const text = textContent(code);
140
+ node.children[i] = {
141
+ type: "element",
142
+ tagName: "Preview",
143
+ properties: { code: text },
144
+ children: []
145
+ };
146
+ } else visit(child);
147
+ }
148
+ }
149
+ return (tree) => visit(tree);
150
+ }
151
+ //#endregion
152
+ //#region src/docs/rehype/dead-links.ts
153
+ function rehypeDeadLinks() {
154
+ return (tree, file) => {
155
+ const headings = collectHeadings(tree);
156
+ const h1s = headings.filter((heading) => heading.tagName === "h1");
157
+ if (h1s.length !== 1) fail(file, h1s[1] ?? headings[0], "rehype-heading-structure", `Expected exactly one h1, found ${h1s.length}.`);
158
+ for (let index = 1; index < headings.length; index++) {
159
+ const previousLevel = Number(headings[index - 1].tagName?.slice(1));
160
+ const currentLevel = Number(headings[index].tagName?.slice(1));
161
+ if (currentLevel > previousLevel + 1) fail(file, headings[index], "rehype-heading-structure", `Skipped heading level from h${previousLevel} to h${currentLevel}.`);
162
+ }
163
+ const ids = /* @__PURE__ */ new Set();
164
+ for (const heading of headings) {
165
+ const id = headingId(heading);
166
+ if (ids.has(id)) fail(file, heading, "rehype-duplicate-heading-id", `Duplicate heading id "${id}".`);
167
+ ids.add(id);
168
+ }
169
+ const currentRoute = file.path ? pageFileToRoute(file.path) : void 0;
170
+ visitLinks(tree, (node, href) => {
171
+ const [rawPath, rawHash] = href.split("#");
172
+ const routePath = normalizeRoute(rawPath || currentRoute || "/");
173
+ if (!hasRoute(routePath)) fail(file, node, "rehype-dead-links", `Dead link "${href}". No matching route was found in .ilha/routes.ts or src/pages.`);
174
+ if (rawHash) {
175
+ const anchor = decodeURIComponent(rawHash);
176
+ if (!(routePath === currentRoute ? ids : getAnchorMap().get(routePath))?.has(anchor)) fail(file, node, "rehype-dead-anchor-links", `Dead anchor link "${href}". No heading id "${anchor}" exists on "${routePath}".`);
177
+ }
178
+ });
179
+ };
180
+ }
181
+ //#endregion
182
+ export { rehypePreview as n, rehypeDeadLinks as t };
@@ -0,0 +1,64 @@
1
+ import MiniSearch from "minisearch";
2
+ import { searchDocuments } from "imprensa/mdx";
3
+ import { createStore } from "@ilha/store";
4
+ //#region src/components/search-core.tsx
5
+ const searchIndex = new MiniSearch({
6
+ fields: ["title", "text"],
7
+ storeFields: [
8
+ "title",
9
+ "path",
10
+ "text"
11
+ ],
12
+ searchOptions: {
13
+ boost: { title: 3 },
14
+ fuzzy: .2,
15
+ prefix: true
16
+ }
17
+ });
18
+ searchIndex.addAll(searchDocuments);
19
+ function getQueryTerms(query) {
20
+ return query.trim().toLowerCase().split(/\s+/).filter(Boolean);
21
+ }
22
+ function getTextMatchIndex(text, query) {
23
+ const normalizedText = text.toLowerCase();
24
+ return getQueryTerms(query).map((term) => normalizedText.indexOf(term)).filter((index) => index >= 0).sort((a, b) => a - b)[0];
25
+ }
26
+ function getMatchedExcerpt(text, query) {
27
+ const firstMatch = getTextMatchIndex(text, query);
28
+ if (firstMatch === void 0) return void 0;
29
+ const start = Math.max(0, firstMatch - 60);
30
+ const end = Math.min(text.length, firstMatch + 140);
31
+ const excerpt = text.slice(start, end).trim();
32
+ return `${start > 0 ? "…" : ""}${excerpt}${end < text.length ? "…" : ""}`;
33
+ }
34
+ function getSearchResults(query) {
35
+ const trimmedQuery = query.trim();
36
+ if (!trimmedQuery) return [];
37
+ return searchIndex.search(trimmedQuery).slice(0, 8).map((result) => ({
38
+ id: result.id,
39
+ title: result.title,
40
+ path: result.path,
41
+ text: result.text
42
+ }));
43
+ }
44
+ //#endregion
45
+ //#region src/components/search-store.ts
46
+ const searchStore = createStore({
47
+ open: false,
48
+ query: ""
49
+ });
50
+ /** Hoisted for `bind:open` / portaled bridge — same alien-signals graph as islands. */
51
+ const searchOpen = searchStore.bind((s) => s.open);
52
+ const searchQuery = searchStore.bind((s) => s.query);
53
+ function closeSearch() {
54
+ searchOpen(false);
55
+ }
56
+ function openSearch() {
57
+ searchOpen(true);
58
+ }
59
+ function toggleSearch() {
60
+ if (searchOpen()) closeSearch();
61
+ else openSearch();
62
+ }
63
+ //#endregion
64
+ export { toggleSearch as a, searchQuery as i, openSearch as n, getMatchedExcerpt as o, searchOpen as r, getSearchResults as s, closeSearch as t };