@nuasite/cms 0.19.1 → 0.20.2
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/dist/editor.js +12615 -12689
- package/package.json +3 -3
- package/src/build-processor.ts +4 -4
- package/src/dev-middleware.ts +185 -189
- package/src/editor/api.ts +0 -251
- package/src/editor/components/fields.tsx +6 -6
- package/src/editor/components/markdown-editor-overlay.tsx +46 -70
- package/src/editor/components/markdown-inline-editor.tsx +34 -165
- package/src/editor/components/mdx-block-view.tsx +351 -47
- package/src/editor/components/mdx-component-picker.tsx +35 -11
- package/src/editor/components/media-library.tsx +1 -15
- package/src/editor/components/modal-shell.tsx +1 -1
- package/src/editor/components/toolbar.tsx +0 -75
- package/src/editor/constants.ts +0 -4
- package/src/editor/editor.ts +2 -192
- package/src/editor/hooks/index.ts +0 -3
- package/src/editor/hooks/useBlockEditorHandlers.ts +1 -8
- package/src/editor/hooks/useTooltipState.ts +1 -2
- package/src/editor/index.tsx +2 -18
- package/src/editor/milkdown-mdx-plugin.tsx +116 -19
- package/src/editor/milkdown-utils.ts +174 -0
- package/src/editor/post-message.ts +0 -6
- package/src/editor/signals.ts +0 -183
- package/src/editor/styles.css +0 -108
- package/src/editor/types.ts +0 -76
- package/src/html-processor.ts +9 -7
- package/src/source-finder/cache.ts +47 -0
- package/src/source-finder/collection-finder.ts +181 -0
- package/src/source-finder/index.ts +5 -2
- package/src/source-finder/search-index.ts +79 -0
- package/src/source-finder/snippet-utils.ts +36 -61
- package/src/types.ts +0 -4
- package/src/utils.ts +10 -0
- package/src/vite-plugin.ts +24 -4
- package/src/editor/ai.ts +0 -185
- package/src/editor/components/ai-chat.tsx +0 -631
- package/src/editor/components/ai-tooltip.tsx +0 -180
- package/src/editor/components/mdx-props-editor.tsx +0 -94
- package/src/editor/hooks/useAIHandlers.ts +0 -345
package/package.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"directory": "packages/astro-cms"
|
|
15
15
|
},
|
|
16
16
|
"license": "Apache-2.0",
|
|
17
|
-
"version": "0.
|
|
17
|
+
"version": "0.20.2",
|
|
18
18
|
"module": "src/index.ts",
|
|
19
19
|
"types": "src/index.ts",
|
|
20
20
|
"type": "module",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"@tailwindcss/vite": "^4.2.2",
|
|
46
46
|
"@types/bun": "1.3.11",
|
|
47
47
|
"clsx": "^2.1.1",
|
|
48
|
-
"marked": "^
|
|
48
|
+
"marked": "^18.0.0",
|
|
49
49
|
"preact": "^10.29.1",
|
|
50
50
|
"prosemirror-commands": "^1.7.1",
|
|
51
51
|
"prosemirror-inputrules": "^1.5.1",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"tailwind-merge": "^3.5.0"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
|
-
"astro": "6.1.
|
|
62
|
+
"astro": "6.1.4",
|
|
63
63
|
"typescript": "^6.0.2",
|
|
64
64
|
"vite": "^7.0.0",
|
|
65
65
|
"@aws-sdk/client-s3": "^3.0.0"
|
package/src/build-processor.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { extractComponentName, processHtml } from './html-processor'
|
|
|
10
10
|
import type { ManifestWriter } from './manifest-writer'
|
|
11
11
|
import { generateComponentPreviews } from './preview-generator'
|
|
12
12
|
import {
|
|
13
|
+
clearCollectionTextIndex,
|
|
13
14
|
clearSourceFinderCache,
|
|
14
15
|
extractOpeningTagWithLine,
|
|
15
16
|
findCollectionSource,
|
|
@@ -23,6 +24,7 @@ import {
|
|
|
23
24
|
} from './source-finder'
|
|
24
25
|
import type { ComponentInstance } from './types'
|
|
25
26
|
import type { CmsMarkerOptions, CollectionEntry } from './types'
|
|
27
|
+
import { firstNonEmptyLine } from './utils'
|
|
26
28
|
|
|
27
29
|
// Concurrency limit for parallel processing
|
|
28
30
|
const MAX_CONCURRENT = 10
|
|
@@ -323,10 +325,7 @@ async function processFile(
|
|
|
323
325
|
}
|
|
324
326
|
|
|
325
327
|
// Get the first non-empty line of the markdown body for wrapper detection
|
|
326
|
-
const bodyFirstLine = mdContent?.body
|
|
327
|
-
?.split('\n')
|
|
328
|
-
.find((line) => line.trim().length > 0)
|
|
329
|
-
?.trim()
|
|
328
|
+
const bodyFirstLine = firstNonEmptyLine(mdContent?.body)
|
|
330
329
|
|
|
331
330
|
// Create ID generator - use atomic increment
|
|
332
331
|
const pageIdStart = idCounter.value
|
|
@@ -780,6 +779,7 @@ export async function processBuildOutput(
|
|
|
780
779
|
|
|
781
780
|
// Clear caches from previous builds and initialize search index
|
|
782
781
|
clearSourceFinderCache()
|
|
782
|
+
clearCollectionTextIndex()
|
|
783
783
|
|
|
784
784
|
const htmlFiles = await findHtmlFiles(outDir)
|
|
785
785
|
|
package/src/dev-middleware.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { parse } from 'node-html-parser'
|
|
2
1
|
import fs from 'node:fs/promises'
|
|
3
2
|
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
4
3
|
import path from 'node:path'
|
|
@@ -17,15 +16,23 @@ import { processHtml } from './html-processor'
|
|
|
17
16
|
import type { ManifestWriter } from './manifest-writer'
|
|
18
17
|
import type { MediaStorageAdapter } from './media/types'
|
|
19
18
|
import {
|
|
20
|
-
clearSourceFinderCache,
|
|
21
19
|
findCollectionSource,
|
|
22
20
|
findImageSourceLocation,
|
|
23
21
|
findSourceLocation,
|
|
24
22
|
initializeSearchIndex,
|
|
25
23
|
parseMarkdownContent,
|
|
24
|
+
reindexDirtyFiles,
|
|
26
25
|
} from './source-finder'
|
|
27
|
-
import type {
|
|
28
|
-
|
|
26
|
+
import type {
|
|
27
|
+
CmsMarkerOptions,
|
|
28
|
+
CollectionDefinition,
|
|
29
|
+
CollectionEntry,
|
|
30
|
+
ComponentDefinition,
|
|
31
|
+
ComponentInstance,
|
|
32
|
+
ManifestEntry,
|
|
33
|
+
PageSeoData,
|
|
34
|
+
} from './types'
|
|
35
|
+
import { firstNonEmptyLine, normalizePagePath } from './utils'
|
|
29
36
|
|
|
30
37
|
/** Minimal ViteDevServer interface to avoid version conflicts between Astro's bundled Vite and root Vite */
|
|
31
38
|
interface ViteDevServerLike {
|
|
@@ -37,6 +44,9 @@ interface ViteDevServerLike {
|
|
|
37
44
|
on: (event: string, listener: (...args: any[]) => void) => any
|
|
38
45
|
removeListener: (event: string, listener: (...args: any[]) => void) => any
|
|
39
46
|
}
|
|
47
|
+
ws?: {
|
|
48
|
+
send: (payload: { type: string; path?: string }) => void
|
|
49
|
+
}
|
|
40
50
|
}
|
|
41
51
|
|
|
42
52
|
/**
|
|
@@ -104,12 +114,19 @@ export function createDevMiddleware(
|
|
|
104
114
|
|
|
105
115
|
const route = url.replace('/_nua/cms/', '').split('?')[0]!
|
|
106
116
|
|
|
107
|
-
handleCmsApiRoute(route, req, res, manifestWriter, config.contentDir, options.mediaAdapter)
|
|
108
|
-
(
|
|
117
|
+
handleCmsApiRoute(route, req, res, manifestWriter, config.contentDir, options.mediaAdapter)
|
|
118
|
+
.then(() => {
|
|
119
|
+
// Explicitly trigger full-reload after content-modifying routes.
|
|
120
|
+
// In sandboxed environments (e.g. E2B), chokidar file watcher events
|
|
121
|
+
// may not fire reliably, so we send the HMR event directly.
|
|
122
|
+
if (req.method === 'POST' && server.ws) {
|
|
123
|
+
server.ws.send({ type: 'full-reload' })
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
.catch((error) => {
|
|
109
127
|
console.error('[astro-cms] API error:', error)
|
|
110
128
|
sendError(res, 'Internal server error', 500)
|
|
111
|
-
}
|
|
112
|
-
)
|
|
129
|
+
})
|
|
113
130
|
})
|
|
114
131
|
}
|
|
115
132
|
|
|
@@ -258,18 +275,23 @@ export function createDevMiddleware(
|
|
|
258
275
|
const html = Buffer.concat(chunks!).toString('utf8')
|
|
259
276
|
const pagePath = normalizePagePath(requestUrl)
|
|
260
277
|
|
|
261
|
-
//
|
|
262
|
-
|
|
263
|
-
.then(({ html: transformed, entries, components, collection, seo }) => {
|
|
278
|
+
// Phase 1 (fast): mark HTML with CMS IDs and build basic entries
|
|
279
|
+
markHtmlForDev(html, pagePath, config, idCounter, manifestWriter)
|
|
280
|
+
.then(({ html: transformed, entries, components, collection, seo, collectionDefinitions: colDefs }) => {
|
|
281
|
+
// Store basic manifest immediately so editor toolbar has data
|
|
264
282
|
manifestWriter.addPage(pagePath, entries, components, collection, seo)
|
|
265
283
|
|
|
284
|
+
// Send the marked HTML to the browser without waiting for source resolution
|
|
266
285
|
res.write = originalWrite
|
|
267
286
|
res.end = originalEnd
|
|
268
287
|
if (!res.headersSent) {
|
|
269
288
|
res.removeHeader('content-length')
|
|
270
289
|
}
|
|
290
|
+
res.end(transformed, ...args)
|
|
271
291
|
|
|
272
|
-
|
|
292
|
+
// Phase 2 (background): resolve source locations and enhance manifest
|
|
293
|
+
// This runs after the page is already visible to the user
|
|
294
|
+
enhanceManifestInBackground(pagePath, entries, components, collection, seo, colDefs, config, manifestWriter)
|
|
273
295
|
})
|
|
274
296
|
.catch((error) => {
|
|
275
297
|
console.error('[cms] Error transforming HTML:', error)
|
|
@@ -289,35 +311,35 @@ export function createDevMiddleware(
|
|
|
289
311
|
})
|
|
290
312
|
}
|
|
291
313
|
|
|
292
|
-
|
|
314
|
+
/**
|
|
315
|
+
* Phase 1 (fast): Mark HTML with CMS IDs and build basic manifest entries.
|
|
316
|
+
* Returns quickly so the page can be sent to the browser without delay.
|
|
317
|
+
* Source resolution and snippet enhancement are deferred to Phase 2.
|
|
318
|
+
*/
|
|
319
|
+
async function markHtmlForDev(
|
|
293
320
|
html: string,
|
|
294
321
|
pagePath: string,
|
|
295
322
|
config: Required<CmsMarkerOptions>,
|
|
296
323
|
idCounter: { value: number },
|
|
297
324
|
manifestWriter: ManifestWriter,
|
|
298
325
|
) {
|
|
299
|
-
//
|
|
300
|
-
|
|
326
|
+
// Re-index only files that changed since last page load (tracked by Vite watcher).
|
|
327
|
+
await reindexDirtyFiles()
|
|
301
328
|
|
|
302
329
|
// In dev mode, reset counter per page for consistent IDs during HMR
|
|
303
330
|
let pageCounter = 0
|
|
304
331
|
const idGenerator = () => `cms-${pageCounter++}`
|
|
305
332
|
|
|
306
|
-
// Check if this is a collection page
|
|
333
|
+
// Check if this is a collection page
|
|
307
334
|
const collectionInfo = await findCollectionSource(pagePath, config.contentDir)
|
|
308
335
|
const isCollectionPage = !!collectionInfo
|
|
309
336
|
|
|
310
|
-
// Parse markdown content if this is a collection page
|
|
311
337
|
let mdContent: Awaited<ReturnType<typeof parseMarkdownContent>> | undefined
|
|
312
338
|
if (collectionInfo) {
|
|
313
339
|
mdContent = await parseMarkdownContent(collectionInfo)
|
|
314
340
|
}
|
|
315
341
|
|
|
316
|
-
|
|
317
|
-
const bodyFirstLine = mdContent?.body
|
|
318
|
-
?.split('\n')
|
|
319
|
-
.find((line) => line.trim().length > 0)
|
|
320
|
-
?.trim()
|
|
342
|
+
const bodyFirstLine = firstNonEmptyLine(mdContent?.body)
|
|
321
343
|
|
|
322
344
|
const result = await processHtml(
|
|
323
345
|
html,
|
|
@@ -330,214 +352,188 @@ async function processHtmlForDev(
|
|
|
330
352
|
generateManifest: config.generateManifest,
|
|
331
353
|
markComponents: config.markComponents,
|
|
332
354
|
componentDirs: config.componentDirs,
|
|
333
|
-
// Skip marking markdown-rendered content on collection pages
|
|
334
|
-
// The markdown body is treated as a single editable unit
|
|
335
355
|
skipMarkdownContent: isCollectionPage,
|
|
336
|
-
// Pass collection info for wrapper element marking
|
|
337
356
|
collectionInfo: collectionInfo
|
|
338
357
|
? { name: collectionInfo.name, slug: collectionInfo.slug, bodyFirstLine, bodyText: mdContent?.body, contentPath: collectionInfo.file }
|
|
339
358
|
: undefined,
|
|
340
|
-
// Pass SEO options
|
|
341
359
|
seo: config.seo,
|
|
342
|
-
// Pass collection definitions for resolving frontmatter text on listing pages
|
|
343
360
|
collectionDefinitions: manifestWriter.getCollectionDefinitions(),
|
|
344
361
|
},
|
|
345
362
|
idGenerator,
|
|
346
363
|
)
|
|
347
364
|
|
|
348
|
-
//
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
fileCache.set(filePath, null)
|
|
360
|
-
return null
|
|
365
|
+
// Build collection entry if this is a collection page
|
|
366
|
+
let collectionEntry: CollectionEntry | undefined
|
|
367
|
+
if (collectionInfo && mdContent) {
|
|
368
|
+
collectionEntry = {
|
|
369
|
+
collectionName: mdContent.collectionName,
|
|
370
|
+
collectionSlug: mdContent.collectionSlug,
|
|
371
|
+
sourcePath: mdContent.file,
|
|
372
|
+
frontmatter: mdContent.frontmatter,
|
|
373
|
+
body: mdContent.body,
|
|
374
|
+
bodyStartLine: mdContent.bodyStartLine,
|
|
375
|
+
wrapperId: result.collectionWrapperId,
|
|
361
376
|
}
|
|
362
377
|
}
|
|
363
378
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
379
|
+
return {
|
|
380
|
+
html: result.html,
|
|
381
|
+
entries: result.entries,
|
|
382
|
+
components: result.components,
|
|
383
|
+
collection: collectionEntry,
|
|
384
|
+
seo: result.seo,
|
|
385
|
+
collectionDefinitions: result.collectionDefinitions,
|
|
386
|
+
}
|
|
387
|
+
}
|
|
370
388
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
389
|
+
/**
|
|
390
|
+
* Phase 2 (background): Resolve source locations, enhance snippets, populate
|
|
391
|
+
* component props, and update the manifest. Runs after the HTML response is sent.
|
|
392
|
+
*/
|
|
393
|
+
async function enhanceManifestInBackground(
|
|
394
|
+
pagePath: string,
|
|
395
|
+
entries: Record<string, ManifestEntry>,
|
|
396
|
+
components: Record<string, ComponentInstance>,
|
|
397
|
+
collection: CollectionEntry | undefined,
|
|
398
|
+
seo: PageSeoData | undefined,
|
|
399
|
+
collectionDefinitions: Record<string, CollectionDefinition> | undefined,
|
|
400
|
+
config: Required<CmsMarkerOptions>,
|
|
401
|
+
manifestWriter: ManifestWriter,
|
|
402
|
+
): Promise<void> {
|
|
403
|
+
try {
|
|
404
|
+
// Populate component props from source invocations
|
|
405
|
+
const projectRoot = getProjectRoot()
|
|
406
|
+
const fileCache = new Map<string, string[] | null>()
|
|
407
|
+
const readLines = async (filePath: string): Promise<string[] | null> => {
|
|
408
|
+
if (fileCache.has(filePath)) return fileCache.get(filePath)!
|
|
409
|
+
try {
|
|
410
|
+
const content = await fs.readFile(filePath, 'utf-8')
|
|
411
|
+
const lines = content.split('\n')
|
|
412
|
+
fileCache.set(filePath, lines)
|
|
413
|
+
return lines
|
|
414
|
+
} catch {
|
|
415
|
+
fileCache.set(filePath, null)
|
|
416
|
+
return null
|
|
381
417
|
}
|
|
382
418
|
}
|
|
383
419
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
420
|
+
for (const comp of Object.values(components)) {
|
|
421
|
+
if (comp.componentName.startsWith('__array:')) continue
|
|
422
|
+
|
|
423
|
+
let found = false
|
|
424
|
+
|
|
425
|
+
if (comp.invocationSourcePath) {
|
|
426
|
+
const filePath = normalizeFilePath(comp.invocationSourcePath)
|
|
427
|
+
const lines = await readLines(path.resolve(projectRoot, filePath))
|
|
388
428
|
if (lines) {
|
|
389
429
|
const invLine = findComponentInvocationLine(lines, comp.componentName, comp.invocationIndex ?? 0)
|
|
390
430
|
if (invLine >= 0) {
|
|
391
431
|
comp.props = extractPropsFromSource(lines, invLine, comp.componentName)
|
|
392
|
-
|
|
432
|
+
found = true
|
|
393
433
|
}
|
|
394
434
|
}
|
|
395
435
|
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// Resolve spread props for array-rendered components.
|
|
400
|
-
// Group components by (name, invocationSourcePath) to detect array patterns.
|
|
401
|
-
const componentGroups = new Map<string, typeof result.components[string][]>()
|
|
402
|
-
for (const comp of Object.values(result.components)) {
|
|
403
|
-
const key = `${comp.componentName}::${comp.invocationSourcePath ?? ''}`
|
|
404
|
-
if (!componentGroups.has(key)) componentGroups.set(key, [])
|
|
405
|
-
componentGroups.get(key)!.push(comp)
|
|
406
|
-
}
|
|
407
436
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
// For inline array components (__array:varName or __array:varName#N), find the .map() line
|
|
419
|
-
// directly instead of searching for a component tag that won't exist
|
|
420
|
-
let pattern: ReturnType<typeof detectArrayPattern>
|
|
421
|
-
const parsed = parseInlineArrayName(firstComp.componentName)
|
|
422
|
-
if (parsed) {
|
|
423
|
-
const { arrayVarName, mapOccurrence } = parsed
|
|
424
|
-
const fmEndCheck = findFrontmatterEnd(lines)
|
|
425
|
-
const mapRegex = new RegExp(buildMapPattern(arrayVarName))
|
|
426
|
-
let mapLine = -1
|
|
427
|
-
let seen = 0
|
|
428
|
-
for (let i = fmEndCheck; i < lines.length; i++) {
|
|
429
|
-
if (mapRegex.test(lines[i]!)) {
|
|
430
|
-
if (seen === mapOccurrence) {
|
|
431
|
-
mapLine = i
|
|
432
|
-
break
|
|
437
|
+
if (!found) {
|
|
438
|
+
for (const candidate of getPageFileCandidates(pagePath)) {
|
|
439
|
+
const lines = await readLines(path.resolve(projectRoot, candidate))
|
|
440
|
+
if (lines) {
|
|
441
|
+
const invLine = findComponentInvocationLine(lines, comp.componentName, comp.invocationIndex ?? 0)
|
|
442
|
+
if (invLine >= 0) {
|
|
443
|
+
comp.props = extractPropsFromSource(lines, invLine, comp.componentName)
|
|
444
|
+
break
|
|
445
|
+
}
|
|
433
446
|
}
|
|
434
|
-
seen++
|
|
435
447
|
}
|
|
436
448
|
}
|
|
437
|
-
if (mapLine < 0) continue
|
|
438
|
-
pattern = { arrayVarName, mapLineIndex: mapLine }
|
|
439
|
-
} else {
|
|
440
|
-
// Find the invocation line (occurrence 0, since .map() has a single <Component> tag)
|
|
441
|
-
const invLine = findComponentInvocationLine(lines, firstComp.componentName, 0)
|
|
442
|
-
if (invLine < 0) continue
|
|
443
|
-
pattern = detectArrayPattern(lines, invLine)
|
|
444
449
|
}
|
|
445
|
-
if (!pattern) continue
|
|
446
|
-
|
|
447
|
-
const fmEnd = findFrontmatterEnd(lines)
|
|
448
|
-
if (fmEnd === 0) continue
|
|
449
450
|
|
|
450
|
-
|
|
451
|
+
// Resolve spread props for array-rendered components
|
|
452
|
+
const componentGroups = new Map<string, typeof components[string][]>()
|
|
453
|
+
for (const comp of Object.values(components)) {
|
|
454
|
+
const key = `${comp.componentName}::${comp.invocationSourcePath ?? ''}`
|
|
455
|
+
if (!componentGroups.has(key)) componentGroups.set(key, [])
|
|
456
|
+
componentGroups.get(key)!.push(comp)
|
|
457
|
+
}
|
|
451
458
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
const comp = sorted[i]!
|
|
456
|
-
if (Object.keys(comp.props).length > 0) continue
|
|
459
|
+
for (const group of componentGroups.values()) {
|
|
460
|
+
if (group.length < 1) continue
|
|
461
|
+
if (!group.some(c => Object.keys(c.props).length === 0)) continue
|
|
457
462
|
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
463
|
+
const firstComp = group[0]!
|
|
464
|
+
const filePath = normalizeFilePath(firstComp.invocationSourcePath ?? firstComp.sourcePath)
|
|
465
|
+
const lines = await readLines(path.resolve(projectRoot, filePath))
|
|
466
|
+
if (!lines) continue
|
|
467
|
+
|
|
468
|
+
const fmEnd = findFrontmatterEnd(lines)
|
|
469
|
+
|
|
470
|
+
let pattern: ReturnType<typeof detectArrayPattern>
|
|
471
|
+
const parsed = parseInlineArrayName(firstComp.componentName)
|
|
472
|
+
if (parsed) {
|
|
473
|
+
const { arrayVarName, mapOccurrence } = parsed
|
|
474
|
+
const mapRegex = new RegExp(buildMapPattern(arrayVarName))
|
|
475
|
+
let mapLine = -1
|
|
476
|
+
let seen = 0
|
|
477
|
+
for (let i = fmEnd; i < lines.length; i++) {
|
|
478
|
+
if (mapRegex.test(lines[i]!)) {
|
|
479
|
+
if (seen === mapOccurrence) {
|
|
480
|
+
mapLine = i
|
|
481
|
+
break
|
|
482
|
+
}
|
|
483
|
+
seen++
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (mapLine < 0) continue
|
|
487
|
+
pattern = { arrayVarName, mapLineIndex: mapLine }
|
|
488
|
+
} else {
|
|
489
|
+
const invLine = findComponentInvocationLine(lines, firstComp.componentName, 0)
|
|
490
|
+
if (invLine < 0) continue
|
|
491
|
+
pattern = detectArrayPattern(lines, invLine)
|
|
461
492
|
}
|
|
462
|
-
|
|
463
|
-
|
|
493
|
+
if (!pattern) continue
|
|
494
|
+
if (fmEnd === 0) continue
|
|
464
495
|
|
|
465
|
-
|
|
466
|
-
let collectionEntry: CollectionEntry | undefined
|
|
467
|
-
if (collectionInfo && mdContent) {
|
|
468
|
-
collectionEntry = {
|
|
469
|
-
collectionName: mdContent.collectionName,
|
|
470
|
-
collectionSlug: mdContent.collectionSlug,
|
|
471
|
-
sourcePath: mdContent.file,
|
|
472
|
-
frontmatter: mdContent.frontmatter,
|
|
473
|
-
body: mdContent.body,
|
|
474
|
-
bodyStartLine: mdContent.bodyStartLine,
|
|
475
|
-
wrapperId: result.collectionWrapperId,
|
|
476
|
-
}
|
|
477
|
-
}
|
|
496
|
+
const frontmatterContent = lines.slice(1, fmEnd - 1).join('\n')
|
|
478
497
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
// Re-resolve sources with the fully-built search index (the earlier enhancement
|
|
484
|
-
// step runs before the index is ready, so its results may be stale).
|
|
485
|
-
for (const entry of Object.values(result.entries)) {
|
|
486
|
-
if (entry.imageMetadata?.src) {
|
|
487
|
-
const imageSource = await findImageSourceLocation(entry.imageMetadata.src, entry.imageMetadata.srcSet)
|
|
488
|
-
if (imageSource) {
|
|
489
|
-
entry.sourcePath = imageSource.file
|
|
490
|
-
entry.sourceLine = imageSource.line
|
|
491
|
-
entry.sourceSnippet = imageSource.snippet
|
|
492
|
-
}
|
|
493
|
-
} else if (entry.text && entry.tag) {
|
|
494
|
-
const textSource = await findSourceLocation(entry.text, entry.tag)
|
|
495
|
-
if (textSource) {
|
|
496
|
-
entry.sourcePath = textSource.file
|
|
497
|
-
entry.sourceLine = textSource.line
|
|
498
|
-
entry.sourceSnippet = textSource.snippet
|
|
499
|
-
if (textSource.variableName) entry.variableName = textSource.variableName
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
}
|
|
498
|
+
const sorted = [...group].sort((a, b) => (a.invocationIndex ?? 0) - (b.invocationIndex ?? 0))
|
|
499
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
500
|
+
const comp = sorted[i]!
|
|
501
|
+
if (Object.keys(comp.props).length > 0) continue
|
|
503
502
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
// Remove entries that don't have a resolved sourcePath
|
|
510
|
-
if (!entry.sourcePath) {
|
|
511
|
-
idsToRemove.push(id)
|
|
512
|
-
delete result.entries[id]
|
|
503
|
+
const arrayProps = extractArrayElementProps(frontmatterContent, pattern.arrayVarName, i)
|
|
504
|
+
if (arrayProps) {
|
|
505
|
+
comp.props = arrayProps
|
|
506
|
+
}
|
|
507
|
+
}
|
|
513
508
|
}
|
|
514
|
-
}
|
|
515
509
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
510
|
+
// Ensure the search index is initialized
|
|
511
|
+
await initializeSearchIndex()
|
|
512
|
+
|
|
513
|
+
// Re-resolve sources with the search index
|
|
514
|
+
for (const entry of Object.values(entries)) {
|
|
515
|
+
if (entry.imageMetadata?.src) {
|
|
516
|
+
const imageSource = await findImageSourceLocation(entry.imageMetadata.src, entry.imageMetadata.srcSet)
|
|
517
|
+
if (imageSource) {
|
|
518
|
+
entry.sourcePath = imageSource.file
|
|
519
|
+
entry.sourceLine = imageSource.line
|
|
520
|
+
entry.sourceSnippet = imageSource.snippet
|
|
521
|
+
}
|
|
522
|
+
} else if (entry.text && entry.tag) {
|
|
523
|
+
const textSource = await findSourceLocation(entry.text, entry.tag)
|
|
524
|
+
if (textSource) {
|
|
525
|
+
entry.sourcePath = textSource.file
|
|
526
|
+
entry.sourceLine = textSource.line
|
|
527
|
+
entry.sourceSnippet = textSource.snippet
|
|
528
|
+
if (textSource.variableName) entry.variableName = textSource.variableName
|
|
529
|
+
}
|
|
530
530
|
}
|
|
531
531
|
}
|
|
532
|
-
finalHtml = root.toString()
|
|
533
|
-
}
|
|
534
532
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
collection: collectionEntry,
|
|
540
|
-
seo: result.seo,
|
|
533
|
+
// Update the manifest with fully-resolved entries and component props
|
|
534
|
+
manifestWriter.addPage(pagePath, entries, components, collection, seo)
|
|
535
|
+
} catch (error) {
|
|
536
|
+
console.error('[cms] Background enhancement failed:', error)
|
|
541
537
|
}
|
|
542
538
|
}
|
|
543
539
|
|