reallysimpledocs 1.0.0-beta.4 → 1.0.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 (53) hide show
  1. package/README.md +59 -39
  2. package/package.json +11 -11
  3. package/src/astro/index.js +64 -98
  4. package/src/astro/preview-scripts.js +140 -0
  5. package/src/astro/preview-source.js +150 -0
  6. package/src/astro/rehype-basecoat-tables.js +62 -0
  7. package/src/components/index.js +1 -0
  8. package/src/css/custom.css +197 -58
  9. package/src/css/styles/luma.css +9 -0
  10. package/src/css/styles/lyra.css +9 -0
  11. package/src/css/styles/maia.css +9 -0
  12. package/src/css/styles/mira.css +9 -0
  13. package/src/css/styles/nova.css +9 -0
  14. package/src/css/styles/rhea.css +9 -0
  15. package/src/css/styles/sera.css +9 -0
  16. package/src/css/styles/vega.css +9 -0
  17. package/src/runtime/components/BaseLayout.astro +22 -28
  18. package/src/runtime/components/CommandDialog.astro +52 -35
  19. package/src/runtime/components/DefaultContentHeader.astro +0 -17
  20. package/src/runtime/components/DefaultHead.astro +110 -0
  21. package/src/runtime/components/DefaultHeadContent.astro +2 -0
  22. package/src/runtime/components/DefaultSidebarHeader.astro +1 -1
  23. package/src/runtime/components/DocsLayout.astro +99 -166
  24. package/src/runtime/components/DropdownMenu.astro +1 -1
  25. package/src/runtime/components/ThemeToggle.astro +21 -0
  26. package/src/runtime/components/mdx/Callout.astro +2 -32
  27. package/src/runtime/components/mdx/Card.astro +3 -8
  28. package/src/runtime/components/mdx/CardGrid.astro +1 -1
  29. package/src/runtime/components/mdx/Code.astro +21 -27
  30. package/src/runtime/components/mdx/CodeGroup.astro +114 -24
  31. package/src/runtime/components/mdx/Preview.astro +7 -13
  32. package/src/runtime/components/mdx/Steps.astro +1 -5
  33. package/src/runtime/components/mdx/Tab.astro +6 -0
  34. package/src/runtime/components/mdx/Tabs.astro +7 -7
  35. package/src/runtime/lib/code-meta.js +8 -0
  36. package/src/runtime/lib/docs.js +141 -119
  37. package/src/runtime/lib/highlight.js +1 -14
  38. package/src/runtime/lib/html.js +9 -0
  39. package/src/runtime/lib/macros.js +10 -5
  40. package/src/runtime/lib/markdown-export.js +240 -169
  41. package/src/runtime/pages/doc.astro +1 -3
  42. package/src/runtime/pages/llms-full.txt.ts +2 -2
  43. package/src/runtime/pages/llms.txt.ts +2 -2
  44. package/src/runtime/pages/markdown.ts +2 -2
  45. package/src/runtime/pages/search-index.ts +12 -3
  46. package/CHANGELOG.md +0 -35
  47. package/src/css/overrides.css +0 -1
  48. package/src/css/sources.css +0 -1
  49. package/src/runtime/lib/markdown-exporters/index.js +0 -144
  50. package/src/runtime/lib/mdx-source.js +0 -19
  51. package/src/runtime/lib/preview-source-plugin.js +0 -33
  52. package/src/runtime/scripts/copy-code.js +0 -130
  53. package/src/site/ContentHeader.astro +0 -33
@@ -1,7 +1,11 @@
1
1
  ---
2
+ import config from "virtual:reallysimpledocs/config";
2
3
  import { renderCommandDialog } from "../lib/macros.js";
4
+ import { routeFilePath } from "../lib/paths.js";
5
+ import LoaderCircle from "lucide-static/icons/loader-circle.svg?raw";
6
+ import lunrUrl from "lunr/lunr.min.js?url";
3
7
 
4
- const { indexUrl } = Astro.props;
8
+ const { indexUrl = routeFilePath(config.routeBase, "search-index.json") } = Astro.props;
5
9
  ---
6
10
 
7
11
  <div data-search data-index-url={indexUrl}>
@@ -15,9 +19,8 @@ const { indexUrl } = Astro.props;
15
19
  />
16
20
  </div>
17
21
 
18
- <script>
19
- import lunrUrl from "lunr/lunr.js?url";
20
-
22
+ <script src={lunrUrl}></script>
23
+ <script is:inline define:vars={{ loadingIcon: LoaderCircle }}>
21
24
  const root = document.querySelector("[data-search]");
22
25
  const indexUrl = root?.getAttribute("data-index-url");
23
26
  const dialog = document.getElementById("command-search");
@@ -26,7 +29,6 @@ const { indexUrl } = Astro.props;
26
29
  const menu = dialog?.querySelector('[role="menu"]');
27
30
  const state = {
28
31
  loadPromise: null,
29
- lunrPromise: null,
30
32
  index: null,
31
33
  documents: [],
32
34
  bySlug: new Map(),
@@ -131,8 +133,8 @@ const { indexUrl } = Astro.props;
131
133
  return space > -1 ? space : end;
132
134
  }
133
135
 
134
- function snippetFromBody(body, ranges) {
135
- const text = String(body || "");
136
+ function snippetFromText(value, ranges) {
137
+ const text = String(value || "");
136
138
  if (!text || !ranges.length) return "";
137
139
 
138
140
  const first = ranges[0];
@@ -149,25 +151,36 @@ const { indexUrl } = Astro.props;
149
151
 
150
152
  function resultView(result, query) {
151
153
  const doc = result.doc || result;
154
+ const displayTitle = doc.breadcrumb || doc.title;
152
155
  if (!query.trim()) {
153
156
  return {
154
157
  path: doc.path,
155
- title: escapeHtml(doc.title),
158
+ title: escapeHtml(displayTitle),
156
159
  snippet: "",
157
160
  };
158
161
  }
159
162
 
160
- const titleRanges = refineRanges(doc.title, rangesFromMatch(result.match, "title"), query);
163
+ let titleRanges = rangesFromMatch(result.match, "breadcrumb");
164
+ if (!titleRanges.length) {
165
+ const titleOffset = String(displayTitle).lastIndexOf(doc.title);
166
+ titleRanges = rangesFromMatch(result.match, "title").map(([start, end]) => [
167
+ titleOffset > -1 ? titleOffset + start : start,
168
+ titleOffset > -1 ? titleOffset + end : end,
169
+ ]);
170
+ }
171
+ titleRanges = refineRanges(displayTitle, titleRanges, query);
161
172
  const bodyRanges = refineRanges(doc.body, rangesFromMatch(result.match, "body"), query);
173
+ const codeRanges = refineRanges(doc.code, rangesFromMatch(result.match, "code"), query);
162
174
  return {
163
175
  path: doc.path,
164
- title: highlightRanges(doc.title, titleRanges),
165
- snippet: snippetFromBody(doc.body, bodyRanges),
176
+ title: highlightRanges(displayTitle, titleRanges),
177
+ snippet: snippetFromText(doc.body, bodyRanges) || snippetFromText(doc.code, codeRanges),
166
178
  };
167
179
  }
168
180
 
169
181
  function renderItems(items, label, query = "") {
170
182
  if (!menu) return;
183
+ delete menu.dataset.loading;
171
184
  if (!items.length) {
172
185
  menu.innerHTML = "";
173
186
  refreshCommand();
@@ -191,41 +204,41 @@ const { indexUrl } = Astro.props;
191
204
 
192
205
  function renderLoading() {
193
206
  if (!menu) return;
194
- menu.innerHTML = `<div role="group" aria-labelledby="command-search-loading-heading">
195
- <span role="heading" id="command-search-loading-heading">Search</span>
196
- <div role="menuitem" aria-disabled="true" data-force><span>Loading...</span></div>
207
+ menu.dataset.loading = "true";
208
+ menu.innerHTML = `<div role="status" aria-live="polite" data-rsd-command-loading>
209
+ <span aria-hidden="true">${loadingIcon}</span>
210
+ <span>Loading...</span>
197
211
  </div>`;
198
212
  refreshCommand();
199
213
  }
200
214
 
201
- function loadLunr() {
202
- if (window.lunr) return Promise.resolve(window.lunr);
203
- if (!state.lunrPromise) {
204
- state.lunrPromise = new Promise((resolve, reject) => {
205
- const script = document.createElement("script");
206
- script.src = lunrUrl;
207
- script.async = true;
208
- script.onload = () => (window.lunr ? resolve(window.lunr) : reject(new Error("Failed to load lunr.")));
209
- script.onerror = () => reject(new Error("Failed to load lunr."));
210
- document.head.appendChild(script);
211
- });
212
- }
213
- return state.lunrPromise;
215
+ function renderEmpty() {
216
+ if (!menu) return;
217
+ delete menu.dataset.loading;
218
+ menu.innerHTML = "";
219
+ refreshCommand();
214
220
  }
215
221
 
216
222
  async function loadIndex() {
217
223
  if (state.index) return state;
218
- if (!indexUrl) throw new Error("Missing reallysimpledocs search index URL.");
224
+ if (!indexUrl) throw new Error("Missing ReallySimpleDocs search index URL.");
219
225
  if (!state.loadPromise) {
220
226
  renderLoading();
221
227
  state.loadPromise = fetch(indexUrl)
222
- .then((response) => response.json())
223
- .then(async (data) => {
224
- const lunr = await loadLunr();
228
+ .then((response) => {
229
+ if (!response.ok) throw new Error(`Failed to load search index: ${response.status}`);
230
+ return response.json();
231
+ })
232
+ .then((data) => {
225
233
  state.documents = data.documents || [];
226
234
  state.bySlug = new Map(state.documents.map((doc) => [doc.slug, doc]));
227
235
  state.index = lunr.Index.load(data.index);
228
236
  return state;
237
+ })
238
+ .catch((error) => {
239
+ state.loadPromise = null;
240
+ renderEmpty();
241
+ throw error;
229
242
  });
230
243
  }
231
244
  return state.loadPromise;
@@ -255,10 +268,14 @@ const { indexUrl } = Astro.props;
255
268
  }
256
269
 
257
270
  async function refreshResults() {
258
- await loadIndex();
259
- const query = input?.value || "";
260
- const results = search(query).slice(0, MAX_RESULTS);
261
- renderItems(results, query.trim() ? "Results" : "Docs", query);
271
+ try {
272
+ await loadIndex();
273
+ const query = input?.value || "";
274
+ const results = search(query).slice(0, MAX_RESULTS);
275
+ renderItems(results, query.trim() ? "Results" : "Docs", query);
276
+ } catch (error) {
277
+ console.error("[search] Failed to load index:", error);
278
+ }
262
279
  }
263
280
 
264
281
  window.rsdOpenCommandSearch = async () => {
@@ -1,19 +1,2 @@
1
1
  ---
2
- import Moon from "lucide-static/icons/moon.svg?raw";
3
- import Sun from "lucide-static/icons/sun.svg?raw";
4
2
  ---
5
-
6
- <nav class="hidden items-center gap-2 sm:flex">
7
- <button
8
- type="button"
9
- aria-label="Toggle dark mode"
10
- data-tooltip="Toggle dark mode"
11
- data-side="bottom"
12
- data-align="end"
13
- onclick="document.dispatchEvent(new CustomEvent('basecoat:theme'))"
14
- class="btn-sm-icon-ghost"
15
- >
16
- <span class="hidden dark:block"><Fragment set:html={Sun} /></span>
17
- <span class="block dark:hidden"><Fragment set:html={Moon} /></span>
18
- </button>
19
- </nav>
@@ -0,0 +1,110 @@
1
+ ---
2
+ import Copy from "lucide-static/icons/copy.svg?raw";
3
+ import Check from "lucide-static/icons/check.svg?raw";
4
+ import basecoatJsUrl from "basecoat-css/all.min?url";
5
+ ---
6
+
7
+ <script is:inline>
8
+ (() => {
9
+ try {
10
+ const stored = localStorage.getItem("themeMode");
11
+ if (
12
+ stored
13
+ ? stored === "dark"
14
+ : matchMedia("(prefers-color-scheme: dark)").matches
15
+ ) {
16
+ document.documentElement.classList.add("dark");
17
+ }
18
+ } catch (_) {}
19
+ })();
20
+ </script>
21
+ <script src={basecoatJsUrl}></script>
22
+ <script is:inline define:vars={{ copyIcon: Copy, checkIcon: Check }}>
23
+ const copyText = async (text) => {
24
+ if (window.isSecureContext && navigator.clipboard?.writeText) {
25
+ return navigator.clipboard.writeText(text);
26
+ }
27
+
28
+ const textarea = document.createElement("textarea");
29
+ textarea.value = text;
30
+ textarea.setAttribute("readonly", "");
31
+ textarea.style.position = "fixed";
32
+ textarea.style.top = "-9999px";
33
+ textarea.style.left = "-9999px";
34
+ document.body.appendChild(textarea);
35
+ textarea.select();
36
+ const ok = document.execCommand("copy");
37
+ textarea.remove();
38
+ if (!ok) throw new Error("Copy failed");
39
+ };
40
+
41
+ const initCopyCode = (codeBlock) => {
42
+ if (codeBlock.dataset.copyCodeInitialized) return;
43
+ const codeEl = codeBlock.querySelector(":scope > code");
44
+ if (!codeEl) return;
45
+
46
+ let frame = codeBlock.parentElement?.classList.contains("code-block")
47
+ ? codeBlock.parentElement
48
+ : null;
49
+ if (!frame) {
50
+ frame = document.createElement("div");
51
+ frame.className = "code-block";
52
+ codeBlock.parentNode.insertBefore(frame, codeBlock);
53
+ frame.appendChild(codeBlock);
54
+ }
55
+
56
+ const target = frame;
57
+ if (target.querySelector(":scope > button[aria-label='Copy code']")) {
58
+ codeBlock.dataset.copyCodeInitialized = "true";
59
+ return;
60
+ }
61
+
62
+ const button = document.createElement("button");
63
+ button.type = "button";
64
+ button.setAttribute("aria-label", "Copy code");
65
+ button.innerHTML = copyIcon;
66
+
67
+ button.addEventListener("click", async () => {
68
+ const text = (codeEl.textContent || "").replace(/\n$/, "");
69
+ if (!text) return;
70
+
71
+ try {
72
+ await copyText(text);
73
+ button.innerHTML = checkIcon;
74
+ } catch (error) {
75
+ console.error("[copy] Failed to copy:", error);
76
+ button.textContent = "Failed";
77
+ }
78
+ window.setTimeout(() => {
79
+ button.innerHTML = copyIcon;
80
+ }, 1200);
81
+ });
82
+
83
+ target.appendChild(button);
84
+ codeBlock.dataset.copyCodeInitialized = "true";
85
+ };
86
+
87
+ const initCopyButtons = (root = document) => {
88
+ root
89
+ .querySelectorAll("pre:has(> code):not([data-copy-code-initialized])")
90
+ .forEach(initCopyCode);
91
+ };
92
+
93
+ if (document.readyState === "loading") {
94
+ document.addEventListener("DOMContentLoaded", () => initCopyButtons(), {
95
+ once: true,
96
+ });
97
+ } else {
98
+ initCopyButtons();
99
+ }
100
+
101
+ new MutationObserver((mutations) => {
102
+ for (const mutation of mutations) {
103
+ mutation.addedNodes.forEach((node) => {
104
+ if (node.nodeType !== Node.ELEMENT_NODE) return;
105
+ if (node.matches?.("pre:has(> code)")) initCopyCode(node);
106
+ initCopyButtons(node);
107
+ });
108
+ }
109
+ }).observe(document.documentElement, { childList: true, subtree: true });
110
+ </script>
@@ -0,0 +1,2 @@
1
+ ---
2
+ ---
@@ -9,7 +9,7 @@ const logo = site.logo?.svg
9
9
  : "";
10
10
  ---
11
11
 
12
- <a href="/" class="btn-ghost h-12 w-full justify-start p-2">
12
+ <a href="/" class="btn h-12 w-full justify-start p-2" data-variant="ghost" data-size="lg">
13
13
  {
14
14
  logo && (
15
15
  <div class="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">