learn-anything-cli 1.2.1 → 1.3.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.
- package/package.json +1 -1
- package/site-dist/assets/{Dashboard-Za6uixpr.js → Dashboard-C70WqXrP.js} +1 -1
- package/site-dist/assets/TopicPage-BePCuA7I.js +1 -0
- package/site-dist/assets/index-DBMXdjNt.css +1 -0
- package/site-dist/assets/index-VhcS_S3t.js +53 -0
- package/site-dist/index.html +2 -2
- package/site-dist/serve.mjs +145 -0
- package/site-dist/assets/TopicPage-QHC9zH4V.js +0 -1
- package/site-dist/assets/index-D_8pRYp_.js +0 -49
- package/site-dist/assets/index-DrpV-hxk.css +0 -1
package/site-dist/index.html
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,400..700;1,14..32,400..700&display=swap"
|
|
11
11
|
rel="stylesheet"
|
|
12
12
|
/>
|
|
13
|
-
<script type="module" crossorigin src="/assets/index-
|
|
14
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
13
|
+
<script type="module" crossorigin src="/assets/index-VhcS_S3t.js"></script>
|
|
14
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DBMXdjNt.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
17
17
|
<div id="app"></div>
|
package/site-dist/serve.mjs
CHANGED
|
@@ -88,6 +88,7 @@ function safeReadText(filePath) {
|
|
|
88
88
|
const sseClients = new Set();
|
|
89
89
|
|
|
90
90
|
function broadcastReload() {
|
|
91
|
+
searchIndexCache = null;
|
|
91
92
|
for (const res of sseClients) {
|
|
92
93
|
try {
|
|
93
94
|
res.write('data: reload\n\n');
|
|
@@ -237,6 +238,146 @@ function buildTopicData(slug) {
|
|
|
237
238
|
return { state, knowledgeMap, sessions, rootSessions, exercises, rootExercises };
|
|
238
239
|
}
|
|
239
240
|
|
|
241
|
+
/* ------------------------------------------------------------------ */
|
|
242
|
+
/* API: search index */
|
|
243
|
+
/* ------------------------------------------------------------------ */
|
|
244
|
+
|
|
245
|
+
let searchIndexCache = null;
|
|
246
|
+
|
|
247
|
+
/** Map both domain and concept slugs → display names from a topic's state. */
|
|
248
|
+
function buildSlugNameMap(state) {
|
|
249
|
+
const map = new Map();
|
|
250
|
+
for (const domain of (state && state.domains) || []) {
|
|
251
|
+
map.set(domain.slug, domain.name);
|
|
252
|
+
for (const concept of domain.concepts || []) {
|
|
253
|
+
map.set(concept.slug, concept.name);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return map;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Collect `.md` files at root or exactly one subdirectory deep — matching the
|
|
260
|
+
sidebar's two-level navigation tree (deeper nesting is not displayed, so not indexed). */
|
|
261
|
+
function collectMarkdownFiles(dir) {
|
|
262
|
+
const out = [];
|
|
263
|
+
if (!existsSync(dir)) return out;
|
|
264
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
265
|
+
if (entry.isDirectory()) {
|
|
266
|
+
const subDir = join(dir, entry.name);
|
|
267
|
+
for (const f of readdirSync(subDir, { withFileTypes: true })) {
|
|
268
|
+
if (f.isFile() && f.name.endsWith('.md')) out.push(`${entry.name}/${f.name}`);
|
|
269
|
+
}
|
|
270
|
+
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
271
|
+
out.push(entry.name);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return out;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const HEADING_RE = /^(#{1,6})\s+(.+)$/;
|
|
278
|
+
|
|
279
|
+
/** Extract ATX headings (level 1–6), skipping fenced code blocks. */
|
|
280
|
+
function extractHeadings(content) {
|
|
281
|
+
const out = [];
|
|
282
|
+
let inFence = false;
|
|
283
|
+
for (const line of content.split('\n')) {
|
|
284
|
+
if (/^\s*(`{3,}|~{3,})/.test(line)) {
|
|
285
|
+
inFence = !inFence;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (inFence) continue;
|
|
289
|
+
const m = line.match(HEADING_RE);
|
|
290
|
+
if (!m) continue;
|
|
291
|
+
const level = m[1].length;
|
|
292
|
+
const title = m[2].replace(/\s+#+$/, '').trim();
|
|
293
|
+
if (title) out.push({ title, level });
|
|
294
|
+
}
|
|
295
|
+
return out;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** Build entries for one in-scope file: a filename pseudo-entry plus its headings. */
|
|
299
|
+
function buildFileEntries({ filePath, apiPath, topicSlug, topicName, section, kind }) {
|
|
300
|
+
const entries = [];
|
|
301
|
+
const baseName = filePath.split('/').pop().replace(/\.md$/, '');
|
|
302
|
+
entries.push({ title: baseName, level: 0, path: apiPath, topicSlug, topicName, section, kind });
|
|
303
|
+
const content = safeReadText(filePath);
|
|
304
|
+
if (content) {
|
|
305
|
+
for (const { title, level } of extractHeadings(content)) {
|
|
306
|
+
entries.push({ title, level, path: apiPath, topicSlug, topicName, section, kind });
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return entries;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** Build the full flat search index across all topics. */
|
|
313
|
+
function buildSearchIndex() {
|
|
314
|
+
const index = [];
|
|
315
|
+
if (!existsSync(TOPICS_DIR)) return index;
|
|
316
|
+
for (const entry of readdirSync(TOPICS_DIR, { withFileTypes: true })) {
|
|
317
|
+
if (!entry.isDirectory()) continue;
|
|
318
|
+
const slug = entry.name;
|
|
319
|
+
const topicDir = join(TOPICS_DIR, slug);
|
|
320
|
+
const state = safeReadJson(join(topicDir, 'state.json'));
|
|
321
|
+
const topicName = (state && state.topic) || slug;
|
|
322
|
+
const slugName = buildSlugNameMap(state);
|
|
323
|
+
|
|
324
|
+
// Session notes: sessions/**/*.md
|
|
325
|
+
const sessionsDir = join(topicDir, 'sessions');
|
|
326
|
+
for (const rel of collectMarkdownFiles(sessionsDir)) {
|
|
327
|
+
const dirName = rel.includes('/') ? rel.slice(0, rel.indexOf('/')) : '';
|
|
328
|
+
const section = dirName ? slugName.get(dirName) || dirName : topicName;
|
|
329
|
+
index.push(
|
|
330
|
+
...buildFileEntries({
|
|
331
|
+
filePath: join(sessionsDir, rel),
|
|
332
|
+
apiPath: `/topics/${slug}/sessions/${rel}`,
|
|
333
|
+
topicSlug: slug,
|
|
334
|
+
topicName,
|
|
335
|
+
section,
|
|
336
|
+
kind: 'note',
|
|
337
|
+
}),
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Knowledge map: knowledge-map.md
|
|
342
|
+
const kmPath = join(topicDir, 'knowledge-map.md');
|
|
343
|
+
if (existsSync(kmPath)) {
|
|
344
|
+
index.push(
|
|
345
|
+
...buildFileEntries({
|
|
346
|
+
filePath: kmPath,
|
|
347
|
+
apiPath: `/topics/${slug}/knowledge-map.md`,
|
|
348
|
+
topicSlug: slug,
|
|
349
|
+
topicName,
|
|
350
|
+
section: 'Knowledge Map',
|
|
351
|
+
kind: 'knowledge-map',
|
|
352
|
+
}),
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Exercise docs: exercises/**/*.md
|
|
357
|
+
const exercisesDir = join(topicDir, 'exercises');
|
|
358
|
+
for (const rel of collectMarkdownFiles(exercisesDir)) {
|
|
359
|
+
const dirName = rel.includes('/') ? rel.slice(0, rel.indexOf('/')) : '';
|
|
360
|
+
const section = dirName ? slugName.get(dirName) || dirName : topicName;
|
|
361
|
+
index.push(
|
|
362
|
+
...buildFileEntries({
|
|
363
|
+
filePath: join(exercisesDir, rel),
|
|
364
|
+
apiPath: `/topics/${slug}/exercises/${rel}`,
|
|
365
|
+
topicSlug: slug,
|
|
366
|
+
topicName,
|
|
367
|
+
section,
|
|
368
|
+
kind: 'exercise',
|
|
369
|
+
}),
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return index;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function getSearchIndex() {
|
|
377
|
+
if (!searchIndexCache) searchIndexCache = buildSearchIndex();
|
|
378
|
+
return searchIndexCache;
|
|
379
|
+
}
|
|
380
|
+
|
|
240
381
|
/* ------------------------------------------------------------------ */
|
|
241
382
|
/* API: file content */
|
|
242
383
|
/* ------------------------------------------------------------------ */
|
|
@@ -315,6 +456,10 @@ function handler(req, res) {
|
|
|
315
456
|
return serveFileContent(res, req.url);
|
|
316
457
|
}
|
|
317
458
|
|
|
459
|
+
if (pathname === '/api/search-index') {
|
|
460
|
+
return json(res, getSearchIndex());
|
|
461
|
+
}
|
|
462
|
+
|
|
318
463
|
// SSE
|
|
319
464
|
if (pathname === '/api/events') {
|
|
320
465
|
res.writeHead(200, {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{d as x,c as l,a as n,t as f,f as s,o,h as m,i as g,j as y,u as w,b as _,k,m as b,p as h,q as M,s as H,v as T}from"./index-D_8pRYp_.js";const L={class:"h-full"},j={key:0,class:"flex items-center justify-center h-full min-h-75 text-sm text-text-3"},$={key:1,class:"prose-content space-y-3","aria-hidden":"true"},B=["innerHTML"],C={key:3,class:"prose-content rounded-lg overflow-hidden bg-(--color-bg-alt)"},F={class:"flex items-center justify-between px-5 py-2 bg-transparent"},D={class:"text-xs text-text-3 font-mono"},E={class:"m-0! p-0! bg-transparent! text-left overflow-x-auto"},S=["innerHTML"],V=x({__name:"ContentViewer",props:{file:{}},setup(a){const e=a,c=s(()=>e.file&&e.file.path.split("/").pop()||""),u=s(()=>y(c.value)),p=s(()=>{var t;return((t=e.file)==null?void 0:t.content)!==void 0}),r=s(()=>{var t;return((t=e.file)==null?void 0:t.content)===void 0?"":e.file.type==="markdown"?m(e.file.content):g(e.file.content,u.value)}),d=s(()=>{var t;return((t=e.file)==null?void 0:t.type)==="markdown"});return(t,i)=>(o(),l("div",L,[a.file?p.value?d.value?(o(),l("div",{key:2,class:"prose-content",innerHTML:r.value},null,8,B)):(o(),l("div",C,[n("div",F,[n("span",D,f(c.value),1)]),n("pre",E,[n("code",{class:"block px-6! py-5! leading-[1.7] font-mono text-[0.875em] text-text-2",innerHTML:r.value},null,8,S)])])):(o(),l("div",$,[...i[0]||(i[0]=[n("div",{class:"h-5 w-2/3 rounded bg-(--color-divider)"},null,-1),n("div",{class:"h-3 w-full rounded bg-(--color-divider)"},null,-1),n("div",{class:"h-3 w-full rounded bg-(--color-divider)"},null,-1),n("div",{class:"h-3 w-4/5 rounded bg-(--color-divider)"},null,-1)])])):(o(),l("div",j," Select a file from the sidebar to view its content "))]))}}),K={key:0,class:"flex flex-col items-center justify-center py-24 text-center"},N={class:"text-base text-(--color-pencil)"},q={key:1},I=["innerHTML"],R=x({__name:"TopicPage",props:{slug:{}},setup(a){const e=a,{t:c}=w(),u=s(()=>(h(),M(e.slug))),p=s(()=>(h(),T(e.slug))),r=s(()=>{const i=p.value;return i?m(i):""}),d=b("topicSelectedFile",H(null)),t=s(()=>!d.value);return(i,v)=>u.value?(o(),l("div",q,[t.value?(o(),l("div",{key:0,class:"prose-content",innerHTML:r.value},null,8,I)):(o(),k(V,{key:1,file:_(d)},null,8,["file"]))])):(o(),l("div",K,[v[0]||(v[0]=n("div",{class:"text-4xl mb-4 opacity-60 select-none"},"🔍",-1)),n("p",N,f(_(c)("topic.notFound"))+": "+f(a.slug),1)]))}});export{R as default};
|