@nuasite/cms 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.
- package/README.md +237 -0
- package/dist/src/build-processor.d.ts +20 -0
- package/dist/src/build-processor.d.ts.map +1 -0
- package/dist/src/collection-scanner.d.ts +6 -0
- package/dist/src/collection-scanner.d.ts.map +1 -0
- package/dist/src/component-registry.d.ts +63 -0
- package/dist/src/component-registry.d.ts.map +1 -0
- package/dist/src/config.d.ts +24 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/dev-middleware.d.ts +20 -0
- package/dist/src/dev-middleware.d.ts.map +1 -0
- package/dist/src/editor/ai.d.ts +60 -0
- package/dist/src/editor/ai.d.ts.map +1 -0
- package/dist/src/editor/api.d.ts +140 -0
- package/dist/src/editor/api.d.ts.map +1 -0
- package/dist/src/editor/color-utils.d.ts +106 -0
- package/dist/src/editor/color-utils.d.ts.map +1 -0
- package/dist/src/editor/components/ai-chat.d.ts +11 -0
- package/dist/src/editor/components/ai-chat.d.ts.map +1 -0
- package/dist/src/editor/components/ai-tooltip.d.ts +12 -0
- package/dist/src/editor/components/ai-tooltip.d.ts.map +1 -0
- package/dist/src/editor/components/attribute-editor.d.ts +5 -0
- package/dist/src/editor/components/attribute-editor.d.ts.map +1 -0
- package/dist/src/editor/components/block-editor.d.ts +12 -0
- package/dist/src/editor/components/block-editor.d.ts.map +1 -0
- package/dist/src/editor/components/collections-browser.d.ts +2 -0
- package/dist/src/editor/components/collections-browser.d.ts.map +1 -0
- package/dist/src/editor/components/color-toolbar.d.ts +12 -0
- package/dist/src/editor/components/color-toolbar.d.ts.map +1 -0
- package/dist/src/editor/components/confirm-dialog.d.ts +2 -0
- package/dist/src/editor/components/confirm-dialog.d.ts.map +1 -0
- package/dist/src/editor/components/create-page-modal.d.ts +2 -0
- package/dist/src/editor/components/create-page-modal.d.ts.map +1 -0
- package/dist/src/editor/components/editable-highlights.d.ts +9 -0
- package/dist/src/editor/components/editable-highlights.d.ts.map +1 -0
- package/dist/src/editor/components/error-boundary.d.ts +32 -0
- package/dist/src/editor/components/error-boundary.d.ts.map +1 -0
- package/dist/src/editor/components/fields.d.ts +75 -0
- package/dist/src/editor/components/fields.d.ts.map +1 -0
- package/dist/src/editor/components/frontmatter-fields.d.ts +29 -0
- package/dist/src/editor/components/frontmatter-fields.d.ts.map +1 -0
- package/dist/src/editor/components/highlight-overlay.d.ts +64 -0
- package/dist/src/editor/components/highlight-overlay.d.ts.map +1 -0
- package/dist/src/editor/components/image-overlay.d.ts +12 -0
- package/dist/src/editor/components/image-overlay.d.ts.map +1 -0
- package/dist/src/editor/components/markdown-editor-overlay.d.ts +6 -0
- package/dist/src/editor/components/markdown-editor-overlay.d.ts.map +1 -0
- package/dist/src/editor/components/markdown-inline-editor.d.ts +10 -0
- package/dist/src/editor/components/markdown-inline-editor.d.ts.map +1 -0
- package/dist/src/editor/components/media-library.d.ts +2 -0
- package/dist/src/editor/components/media-library.d.ts.map +1 -0
- package/dist/src/editor/components/outline.d.ts +21 -0
- package/dist/src/editor/components/outline.d.ts.map +1 -0
- package/dist/src/editor/components/redirect-countdown.d.ts +2 -0
- package/dist/src/editor/components/redirect-countdown.d.ts.map +1 -0
- package/dist/src/editor/components/seo-editor.d.ts +2 -0
- package/dist/src/editor/components/seo-editor.d.ts.map +1 -0
- package/dist/src/editor/components/text-style-toolbar.d.ts +8 -0
- package/dist/src/editor/components/text-style-toolbar.d.ts.map +1 -0
- package/dist/src/editor/components/toast/toast-container.d.ts +7 -0
- package/dist/src/editor/components/toast/toast-container.d.ts.map +1 -0
- package/dist/src/editor/components/toast/toast.d.ts +7 -0
- package/dist/src/editor/components/toast/toast.d.ts.map +1 -0
- package/dist/src/editor/components/toast/types.d.ts +7 -0
- package/dist/src/editor/components/toast/types.d.ts.map +1 -0
- package/dist/src/editor/components/toolbar.d.ts +21 -0
- package/dist/src/editor/components/toolbar.d.ts.map +1 -0
- package/dist/src/editor/config.d.ts +4 -0
- package/dist/src/editor/config.d.ts.map +1 -0
- package/dist/src/editor/constants.d.ts +101 -0
- package/dist/src/editor/constants.d.ts.map +1 -0
- package/dist/src/editor/context.d.ts +14 -0
- package/dist/src/editor/context.d.ts.map +1 -0
- package/dist/src/editor/dom.d.ts +77 -0
- package/dist/src/editor/dom.d.ts.map +1 -0
- package/dist/src/editor/editor.d.ts +64 -0
- package/dist/src/editor/editor.d.ts.map +1 -0
- package/dist/src/editor/history.d.ts +20 -0
- package/dist/src/editor/history.d.ts.map +1 -0
- package/dist/src/editor/hooks/index.d.ts +14 -0
- package/dist/src/editor/hooks/index.d.ts.map +1 -0
- package/dist/src/editor/hooks/useAIHandlers.d.ts +22 -0
- package/dist/src/editor/hooks/useAIHandlers.d.ts.map +1 -0
- package/dist/src/editor/hooks/useBlockEditorHandlers.d.ts +18 -0
- package/dist/src/editor/hooks/useBlockEditorHandlers.d.ts.map +1 -0
- package/dist/src/editor/hooks/useElementDetection.d.ts +26 -0
- package/dist/src/editor/hooks/useElementDetection.d.ts.map +1 -0
- package/dist/src/editor/hooks/useImageHoverDetection.d.ts +12 -0
- package/dist/src/editor/hooks/useImageHoverDetection.d.ts.map +1 -0
- package/dist/src/editor/hooks/useTextSelection.d.ts +23 -0
- package/dist/src/editor/hooks/useTextSelection.d.ts.map +1 -0
- package/dist/src/editor/hooks/useTooltipState.d.ts +19 -0
- package/dist/src/editor/hooks/useTooltipState.d.ts.map +1 -0
- package/dist/src/editor/hooks/utils.d.ts +32 -0
- package/dist/src/editor/hooks/utils.d.ts.map +1 -0
- package/dist/src/editor/index.d.ts +12 -0
- package/dist/src/editor/index.d.ts.map +1 -0
- package/dist/src/editor/lib/cn.d.ts +3 -0
- package/dist/src/editor/lib/cn.d.ts.map +1 -0
- package/dist/src/editor/manifest.d.ts +19 -0
- package/dist/src/editor/manifest.d.ts.map +1 -0
- package/dist/src/editor/markdown-api.d.ts +36 -0
- package/dist/src/editor/markdown-api.d.ts.map +1 -0
- package/dist/src/editor/signals.d.ts +242 -0
- package/dist/src/editor/signals.d.ts.map +1 -0
- package/dist/src/editor/storage.d.ts +27 -0
- package/dist/src/editor/storage.d.ts.map +1 -0
- package/dist/src/editor/text-styling.d.ts +350 -0
- package/dist/src/editor/text-styling.d.ts.map +1 -0
- package/dist/src/editor/themes.d.ts +38 -0
- package/dist/src/editor/themes.d.ts.map +1 -0
- package/dist/src/editor/types.d.ts +454 -0
- package/dist/src/editor/types.d.ts.map +1 -0
- package/dist/src/error-collector.d.ts +56 -0
- package/dist/src/error-collector.d.ts.map +1 -0
- package/dist/src/handlers/component-ops.d.ts +34 -0
- package/dist/src/handlers/component-ops.d.ts.map +1 -0
- package/dist/src/handlers/markdown-ops.d.ts +41 -0
- package/dist/src/handlers/markdown-ops.d.ts.map +1 -0
- package/dist/src/handlers/request-utils.d.ts +20 -0
- package/dist/src/handlers/request-utils.d.ts.map +1 -0
- package/dist/src/handlers/source-writer.d.ts +51 -0
- package/dist/src/handlers/source-writer.d.ts.map +1 -0
- package/dist/src/html-processor.d.ts +63 -0
- package/dist/src/html-processor.d.ts.map +1 -0
- package/dist/src/index.d.ts +41 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/manifest-writer.d.ts +111 -0
- package/dist/src/manifest-writer.d.ts.map +1 -0
- package/dist/src/media/contember.d.ts +15 -0
- package/dist/src/media/contember.d.ts.map +1 -0
- package/dist/src/media/local.d.ts +9 -0
- package/dist/src/media/local.d.ts.map +1 -0
- package/dist/src/media/s3.d.ts +12 -0
- package/dist/src/media/s3.d.ts.map +1 -0
- package/dist/src/media/types.d.ts +40 -0
- package/dist/src/media/types.d.ts.map +1 -0
- package/dist/src/preview-generator.d.ts +19 -0
- package/dist/src/preview-generator.d.ts.map +1 -0
- package/dist/src/seo-processor.d.ts +23 -0
- package/dist/src/seo-processor.d.ts.map +1 -0
- package/dist/src/source-finder/ast-extractors.d.ts +35 -0
- package/dist/src/source-finder/ast-extractors.d.ts.map +1 -0
- package/dist/src/source-finder/ast-parser.d.ts +16 -0
- package/dist/src/source-finder/ast-parser.d.ts.map +1 -0
- package/dist/src/source-finder/cache.d.ts +18 -0
- package/dist/src/source-finder/cache.d.ts.map +1 -0
- package/dist/src/source-finder/collection-finder.d.ts +29 -0
- package/dist/src/source-finder/collection-finder.d.ts.map +1 -0
- package/dist/src/source-finder/cross-file-tracker.d.ts +39 -0
- package/dist/src/source-finder/cross-file-tracker.d.ts.map +1 -0
- package/dist/src/source-finder/element-finder.d.ts +42 -0
- package/dist/src/source-finder/element-finder.d.ts.map +1 -0
- package/dist/src/source-finder/image-finder.d.ts +24 -0
- package/dist/src/source-finder/image-finder.d.ts.map +1 -0
- package/dist/src/source-finder/index.d.ts +9 -0
- package/dist/src/source-finder/index.d.ts.map +1 -0
- package/dist/src/source-finder/search-index.d.ts +27 -0
- package/dist/src/source-finder/search-index.d.ts.map +1 -0
- package/dist/src/source-finder/snippet-utils.d.ts +90 -0
- package/dist/src/source-finder/snippet-utils.d.ts.map +1 -0
- package/dist/src/source-finder/source-lookup.d.ts +16 -0
- package/dist/src/source-finder/source-lookup.d.ts.map +1 -0
- package/dist/src/source-finder/types.d.ts +167 -0
- package/dist/src/source-finder/types.d.ts.map +1 -0
- package/dist/src/source-finder/variable-extraction.d.ts +37 -0
- package/dist/src/source-finder/variable-extraction.d.ts.map +1 -0
- package/dist/src/tailwind-colors.d.ts +54 -0
- package/dist/src/tailwind-colors.d.ts.map +1 -0
- package/dist/src/tsconfig.tsbuildinfo +1 -0
- package/dist/src/types.d.ts +367 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/utils.d.ts +61 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/vite-plugin.d.ts +14 -0
- package/dist/src/vite-plugin.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +80 -0
- package/src/build-processor.ts +784 -0
- package/src/collection-scanner.ts +304 -0
- package/src/component-registry.ts +393 -0
- package/src/config.ts +74 -0
- package/src/dev-middleware.ts +525 -0
- package/src/dist/src/tsconfig.tsbuildinfo +1 -0
- package/src/editor/ai.ts +185 -0
- package/src/editor/api.ts +513 -0
- package/src/editor/color-utils.ts +556 -0
- package/src/editor/components/ai-chat.tsx +632 -0
- package/src/editor/components/ai-tooltip.tsx +179 -0
- package/src/editor/components/attribute-editor.tsx +596 -0
- package/src/editor/components/block-editor.tsx +546 -0
- package/src/editor/components/collections-browser.tsx +248 -0
- package/src/editor/components/color-toolbar.tsx +314 -0
- package/src/editor/components/confirm-dialog.tsx +69 -0
- package/src/editor/components/create-page-modal.tsx +163 -0
- package/src/editor/components/editable-highlights.tsx +260 -0
- package/src/editor/components/error-boundary.tsx +87 -0
- package/src/editor/components/fields.tsx +387 -0
- package/src/editor/components/frontmatter-fields.tsx +469 -0
- package/src/editor/components/highlight-overlay.ts +229 -0
- package/src/editor/components/image-overlay.tsx +230 -0
- package/src/editor/components/markdown-editor-overlay.tsx +505 -0
- package/src/editor/components/markdown-inline-editor.tsx +780 -0
- package/src/editor/components/media-library.tsx +297 -0
- package/src/editor/components/outline.tsx +402 -0
- package/src/editor/components/redirect-countdown.tsx +45 -0
- package/src/editor/components/seo-editor.tsx +498 -0
- package/src/editor/components/text-style-toolbar.tsx +362 -0
- package/src/editor/components/toast/toast-container.tsx +15 -0
- package/src/editor/components/toast/toast.tsx +49 -0
- package/src/editor/components/toast/types.ts +7 -0
- package/src/editor/components/toolbar.tsx +366 -0
- package/src/editor/config.ts +12 -0
- package/src/editor/constants.ts +106 -0
- package/src/editor/context.tsx +38 -0
- package/src/editor/dom.ts +357 -0
- package/src/editor/editor.ts +1510 -0
- package/src/editor/env.d.ts +4 -0
- package/src/editor/history.ts +355 -0
- package/src/editor/hooks/index.ts +19 -0
- package/src/editor/hooks/useAIHandlers.ts +345 -0
- package/src/editor/hooks/useBlockEditorHandlers.ts +206 -0
- package/src/editor/hooks/useElementDetection.ts +284 -0
- package/src/editor/hooks/useImageHoverDetection.ts +102 -0
- package/src/editor/hooks/useTextSelection.ts +187 -0
- package/src/editor/hooks/useTooltipState.ts +126 -0
- package/src/editor/hooks/utils.ts +101 -0
- package/src/editor/index.tsx +481 -0
- package/src/editor/lib/cn.ts +4 -0
- package/src/editor/manifest.ts +25 -0
- package/src/editor/markdown-api.ts +209 -0
- package/src/editor/signals.ts +1351 -0
- package/src/editor/storage.ts +266 -0
- package/src/editor/styles.css +465 -0
- package/src/editor/text-styling.ts +773 -0
- package/src/editor/themes.ts +210 -0
- package/src/editor/types.ts +591 -0
- package/src/error-collector.ts +106 -0
- package/src/handlers/component-ops.ts +463 -0
- package/src/handlers/markdown-ops.ts +202 -0
- package/src/handlers/request-utils.ts +151 -0
- package/src/handlers/source-writer.ts +649 -0
- package/src/html-processor.ts +1108 -0
- package/src/index.ts +284 -0
- package/src/manifest-writer.ts +371 -0
- package/src/media/contember.ts +84 -0
- package/src/media/local.ts +114 -0
- package/src/media/s3.ts +133 -0
- package/src/media/types.ts +33 -0
- package/src/preview-generator.ts +293 -0
- package/src/seo-processor.ts +567 -0
- package/src/source-finder/ast-extractors.ts +185 -0
- package/src/source-finder/ast-parser.ts +150 -0
- package/src/source-finder/cache.ts +76 -0
- package/src/source-finder/collection-finder.ts +335 -0
- package/src/source-finder/cross-file-tracker.ts +741 -0
- package/src/source-finder/element-finder.ts +387 -0
- package/src/source-finder/image-finder.ts +283 -0
- package/src/source-finder/index.ts +37 -0
- package/src/source-finder/search-index.ts +525 -0
- package/src/source-finder/snippet-utils.ts +668 -0
- package/src/source-finder/source-lookup.ts +200 -0
- package/src/source-finder/types.ts +210 -0
- package/src/source-finder/variable-extraction.ts +406 -0
- package/src/tailwind-colors.ts +874 -0
- package/src/tsconfig.json +25 -0
- package/src/types.ts +406 -0
- package/src/utils.ts +186 -0
- package/src/vite-plugin.ts +42 -0
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
import type { ComponentNode, ElementNode, Node as AstroNode, TextNode } from '@astrojs/compiler/types'
|
|
2
|
+
import fs from 'node:fs/promises'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { getProjectRoot } from '../config'
|
|
6
|
+
import { buildDefinitionPath, parseExpressionPath } from './ast-extractors'
|
|
7
|
+
import { getCachedParsedFile } from './ast-parser'
|
|
8
|
+
import {
|
|
9
|
+
addToImageSearchIndex,
|
|
10
|
+
addToTextSearchIndex,
|
|
11
|
+
getDirectoryCache,
|
|
12
|
+
getImageSearchIndex,
|
|
13
|
+
getTextSearchIndex,
|
|
14
|
+
isSearchIndexInitialized,
|
|
15
|
+
setSearchIndexInitialized,
|
|
16
|
+
} from './cache'
|
|
17
|
+
import { escapeRegex } from '../utils'
|
|
18
|
+
import { extractImageSnippet, extractInnerHtmlFromSnippet, normalizeText } from './snippet-utils'
|
|
19
|
+
import type { CachedParsedFile, SourceLocation } from './types'
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// File Collection
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Collect all .astro files in a directory recursively
|
|
27
|
+
*/
|
|
28
|
+
export async function collectAstroFiles(dir: string): Promise<string[]> {
|
|
29
|
+
const cache = getDirectoryCache()
|
|
30
|
+
const cached = cache.get(dir)
|
|
31
|
+
if (cached) return cached
|
|
32
|
+
|
|
33
|
+
const results: string[] = []
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const entries = await fs.readdir(dir, { withFileTypes: true })
|
|
37
|
+
|
|
38
|
+
await Promise.all(entries.map(async (entry) => {
|
|
39
|
+
const fullPath = path.join(dir, entry.name)
|
|
40
|
+
if (entry.isDirectory()) {
|
|
41
|
+
const subFiles = await collectAstroFiles(fullPath)
|
|
42
|
+
results.push(...subFiles)
|
|
43
|
+
} else if (entry.isFile() && (entry.name.endsWith('.astro') || entry.name.endsWith('.tsx') || entry.name.endsWith('.jsx'))) {
|
|
44
|
+
results.push(fullPath)
|
|
45
|
+
}
|
|
46
|
+
}))
|
|
47
|
+
} catch {
|
|
48
|
+
// Directory doesn't exist
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
cache.set(dir, results)
|
|
52
|
+
return results
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ============================================================================
|
|
56
|
+
// Index Initialization
|
|
57
|
+
// ============================================================================
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Initialize search index by pre-scanning all source files.
|
|
61
|
+
* This is much faster than searching per-entry.
|
|
62
|
+
*/
|
|
63
|
+
export async function initializeSearchIndex(): Promise<void> {
|
|
64
|
+
if (isSearchIndexInitialized()) return
|
|
65
|
+
|
|
66
|
+
const srcDir = path.join(getProjectRoot(), 'src')
|
|
67
|
+
const searchDirs = [
|
|
68
|
+
path.join(srcDir, 'components'),
|
|
69
|
+
path.join(srcDir, 'pages'),
|
|
70
|
+
path.join(srcDir, 'layouts'),
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
// Collect all Astro files first
|
|
74
|
+
const allFiles: string[] = []
|
|
75
|
+
for (const dir of searchDirs) {
|
|
76
|
+
try {
|
|
77
|
+
const files = await collectAstroFiles(dir)
|
|
78
|
+
allFiles.push(...files)
|
|
79
|
+
} catch {
|
|
80
|
+
// Directory doesn't exist
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Parse all files in parallel and build indexes
|
|
85
|
+
await Promise.all(allFiles.map(async (filePath) => {
|
|
86
|
+
try {
|
|
87
|
+
const cached = await getCachedParsedFile(filePath)
|
|
88
|
+
if (!cached) return
|
|
89
|
+
|
|
90
|
+
const relFile = path.relative(getProjectRoot(), filePath)
|
|
91
|
+
|
|
92
|
+
// Index all text content from this file
|
|
93
|
+
indexFileContent(cached, relFile)
|
|
94
|
+
|
|
95
|
+
// Index all images from this file
|
|
96
|
+
indexFileImages(cached, relFile)
|
|
97
|
+
} catch {
|
|
98
|
+
// Skip files that fail to parse
|
|
99
|
+
}
|
|
100
|
+
}))
|
|
101
|
+
|
|
102
|
+
setSearchIndexInitialized(true)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ============================================================================
|
|
106
|
+
// Content Indexing
|
|
107
|
+
// ============================================================================
|
|
108
|
+
|
|
109
|
+
// Helper for indexing - get text content from node
|
|
110
|
+
// Treats <br> elements as whitespace to match rendered HTML behavior
|
|
111
|
+
function getTextContent(node: AstroNode): string {
|
|
112
|
+
if (node.type === 'text') {
|
|
113
|
+
return (node as TextNode).value
|
|
114
|
+
}
|
|
115
|
+
// Treat <br> elements as whitespace (they create line breaks in rendered HTML)
|
|
116
|
+
if (node.type === 'element' && (node as ElementNode).name.toLowerCase() === 'br') {
|
|
117
|
+
return ' '
|
|
118
|
+
}
|
|
119
|
+
// Treat <wbr> elements as empty (word break opportunity, no visible content)
|
|
120
|
+
if (node.type === 'element' && (node as ElementNode).name.toLowerCase() === 'wbr') {
|
|
121
|
+
return ''
|
|
122
|
+
}
|
|
123
|
+
if ('children' in node && Array.isArray(node.children)) {
|
|
124
|
+
return node.children.map(getTextContent).join('')
|
|
125
|
+
}
|
|
126
|
+
return ''
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Helper for indexing - check for expression children
|
|
130
|
+
function hasExpressionChild(node: AstroNode): { found: boolean; varNames: string[] } {
|
|
131
|
+
const varNames: string[] = []
|
|
132
|
+
if (node.type === 'expression') {
|
|
133
|
+
const exprText = getTextContent(node)
|
|
134
|
+
const fullPath = parseExpressionPath(exprText)
|
|
135
|
+
if (fullPath) {
|
|
136
|
+
varNames.push(fullPath)
|
|
137
|
+
}
|
|
138
|
+
return { found: true, varNames }
|
|
139
|
+
}
|
|
140
|
+
if ('children' in node && Array.isArray(node.children)) {
|
|
141
|
+
for (const child of node.children) {
|
|
142
|
+
const result = hasExpressionChild(child)
|
|
143
|
+
if (result.found) {
|
|
144
|
+
varNames.push(...result.varNames)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return { found: varNames.length > 0, varNames }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Extract complete tag snippet including content and indentation.
|
|
153
|
+
* Local version for indexing (to avoid circular dependency)
|
|
154
|
+
*/
|
|
155
|
+
function extractCompleteTagSnippet(lines: string[], startLine: number, tag: string): string {
|
|
156
|
+
const escapedTag = escapeRegex(tag)
|
|
157
|
+
const openTagPattern = new RegExp(`<${escapedTag}(?:[\\s>]|$)`, 'gi')
|
|
158
|
+
|
|
159
|
+
let actualStartLine = startLine
|
|
160
|
+
const startLineContent = lines[startLine] || ''
|
|
161
|
+
if (!openTagPattern.test(startLineContent)) {
|
|
162
|
+
for (let i = startLine - 1; i >= Math.max(0, startLine - 20); i--) {
|
|
163
|
+
const line = lines[i]
|
|
164
|
+
if (!line) continue
|
|
165
|
+
openTagPattern.lastIndex = 0
|
|
166
|
+
if (openTagPattern.test(line)) {
|
|
167
|
+
actualStartLine = i
|
|
168
|
+
break
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const snippetLines: string[] = []
|
|
174
|
+
let depth = 0
|
|
175
|
+
let foundClosing = false
|
|
176
|
+
|
|
177
|
+
for (let i = actualStartLine; i < Math.min(actualStartLine + 30, lines.length); i++) {
|
|
178
|
+
const line = lines[i]
|
|
179
|
+
if (!line) continue
|
|
180
|
+
|
|
181
|
+
snippetLines.push(line)
|
|
182
|
+
|
|
183
|
+
const openTags = (line.match(new RegExp(`<${escapedTag}(?:[\\s>]|$)`, 'gi')) || []).length
|
|
184
|
+
const selfClosing = (line.match(new RegExp(`<${escapedTag}[^>]*/>`, 'gi')) || []).length
|
|
185
|
+
const closeTags = (line.match(new RegExp(`</${escapedTag}>`, 'gi')) || []).length
|
|
186
|
+
|
|
187
|
+
depth += openTags - selfClosing - closeTags
|
|
188
|
+
|
|
189
|
+
if (selfClosing > 0 || (depth <= 0 && (closeTags > 0 || openTags > 0))) {
|
|
190
|
+
foundClosing = true
|
|
191
|
+
break
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!foundClosing && snippetLines.length > 1) {
|
|
196
|
+
return snippetLines[0]!
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return snippetLines.join('\n')
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Extract the opening tag from source lines with its start line number.
|
|
204
|
+
* Local version for indexing (to avoid circular dependency)
|
|
205
|
+
*/
|
|
206
|
+
function extractOpeningTagWithLine(
|
|
207
|
+
lines: string[],
|
|
208
|
+
startLine: number,
|
|
209
|
+
tag: string,
|
|
210
|
+
): { snippet: string; startLine: number } | undefined {
|
|
211
|
+
const escapedTag = escapeRegex(tag)
|
|
212
|
+
const openTagPattern = new RegExp(`<${escapedTag}(?:[\\s>]|$)`, 'gi')
|
|
213
|
+
|
|
214
|
+
let actualStartLine = startLine
|
|
215
|
+
const startLineContent = lines[startLine] || ''
|
|
216
|
+
if (!openTagPattern.test(startLineContent)) {
|
|
217
|
+
for (let i = startLine - 1; i >= Math.max(0, startLine - 20); i--) {
|
|
218
|
+
const line = lines[i]
|
|
219
|
+
if (!line) continue
|
|
220
|
+
openTagPattern.lastIndex = 0
|
|
221
|
+
if (openTagPattern.test(line)) {
|
|
222
|
+
actualStartLine = i
|
|
223
|
+
break
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const snippetLines: string[] = []
|
|
229
|
+
for (let i = actualStartLine; i < Math.min(actualStartLine + 10, lines.length); i++) {
|
|
230
|
+
const line = lines[i]
|
|
231
|
+
if (!line) continue
|
|
232
|
+
|
|
233
|
+
snippetLines.push(line)
|
|
234
|
+
const combined = snippetLines.join('\n')
|
|
235
|
+
|
|
236
|
+
const openTagMatch = combined.match(new RegExp(`<${escapedTag}[^>]*>`, 'i'))
|
|
237
|
+
if (openTagMatch) {
|
|
238
|
+
return { snippet: openTagMatch[0], startLine: actualStartLine }
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const selfClosingMatch = combined.match(new RegExp(`<${escapedTag}[^>]*/\\s*>`, 'i'))
|
|
242
|
+
if (selfClosingMatch) {
|
|
243
|
+
return { snippet: selfClosingMatch[0], startLine: actualStartLine }
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return undefined
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Index all searchable text content from a parsed file
|
|
252
|
+
*/
|
|
253
|
+
export function indexFileContent(cached: CachedParsedFile, relFile: string): void {
|
|
254
|
+
// Walk AST and collect all text elements
|
|
255
|
+
function visit(node: AstroNode) {
|
|
256
|
+
if ((node.type === 'element' || node.type === 'component')) {
|
|
257
|
+
const elemNode = node as ElementNode | ComponentNode
|
|
258
|
+
const tag = elemNode.name.toLowerCase()
|
|
259
|
+
const textContent = getTextContent(elemNode)
|
|
260
|
+
const normalizedText = normalizeText(textContent)
|
|
261
|
+
const line = elemNode.position?.start.line ?? 0
|
|
262
|
+
|
|
263
|
+
if (normalizedText && normalizedText.length >= 2) {
|
|
264
|
+
// Check for variable references
|
|
265
|
+
const exprInfo = hasExpressionChild(elemNode)
|
|
266
|
+
if (exprInfo.found && exprInfo.varNames.length > 0) {
|
|
267
|
+
for (const exprPath of exprInfo.varNames) {
|
|
268
|
+
for (const def of cached.variableDefinitions) {
|
|
269
|
+
// Build the full definition path for comparison
|
|
270
|
+
// For array indices (numeric names), use bracket notation
|
|
271
|
+
const defPath = buildDefinitionPath(def)
|
|
272
|
+
// Check if the expression path matches the definition path
|
|
273
|
+
// e.g., 'config.nav.title' matches def with parentName='config.nav', name='title'
|
|
274
|
+
// or 'items[0]' matches def with parentName='items', name='0'
|
|
275
|
+
if (defPath === exprPath) {
|
|
276
|
+
const normalizedDef = normalizeText(def.value)
|
|
277
|
+
const completeSnippet = extractCompleteTagSnippet(cached.lines, line - 1, tag)
|
|
278
|
+
const snippet = extractInnerHtmlFromSnippet(completeSnippet, tag) ?? completeSnippet
|
|
279
|
+
|
|
280
|
+
addToTextSearchIndex({
|
|
281
|
+
file: relFile,
|
|
282
|
+
line: def.line,
|
|
283
|
+
snippet: cached.lines[def.line - 1] || '',
|
|
284
|
+
type: 'variable',
|
|
285
|
+
variableName: defPath,
|
|
286
|
+
definitionLine: def.line,
|
|
287
|
+
normalizedText: normalizedDef,
|
|
288
|
+
tag,
|
|
289
|
+
})
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Index static text content
|
|
296
|
+
const completeSnippet = extractCompleteTagSnippet(cached.lines, line - 1, tag)
|
|
297
|
+
const snippet = extractInnerHtmlFromSnippet(completeSnippet, tag) ?? completeSnippet
|
|
298
|
+
const openingTagInfo = extractOpeningTagWithLine(cached.lines, line - 1, tag)
|
|
299
|
+
|
|
300
|
+
addToTextSearchIndex({
|
|
301
|
+
file: relFile,
|
|
302
|
+
line,
|
|
303
|
+
snippet,
|
|
304
|
+
openingTagSnippet: openingTagInfo?.snippet,
|
|
305
|
+
type: 'static',
|
|
306
|
+
normalizedText,
|
|
307
|
+
tag,
|
|
308
|
+
})
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Also index component props
|
|
312
|
+
if (node.type === 'component') {
|
|
313
|
+
for (const attr of elemNode.attributes) {
|
|
314
|
+
if (attr.type === 'attribute' && attr.kind === 'quoted' && attr.value) {
|
|
315
|
+
const normalizedValue = normalizeText(attr.value)
|
|
316
|
+
if (normalizedValue && normalizedValue.length >= 2) {
|
|
317
|
+
addToTextSearchIndex({
|
|
318
|
+
file: relFile,
|
|
319
|
+
line: attr.position?.start.line ?? line,
|
|
320
|
+
snippet: cached.lines[(attr.position?.start.line ?? line) - 1] || '',
|
|
321
|
+
type: 'prop',
|
|
322
|
+
variableName: attr.name,
|
|
323
|
+
normalizedText: normalizedValue,
|
|
324
|
+
tag,
|
|
325
|
+
})
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if ('children' in node && Array.isArray(node.children)) {
|
|
333
|
+
for (const child of node.children) {
|
|
334
|
+
visit(child)
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
visit(cached.ast)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Index all images from a parsed file
|
|
344
|
+
*/
|
|
345
|
+
export function indexFileImages(cached: CachedParsedFile, relFile: string): void {
|
|
346
|
+
// For Astro files, use AST
|
|
347
|
+
if (relFile.endsWith('.astro')) {
|
|
348
|
+
function visit(node: AstroNode) {
|
|
349
|
+
if (node.type === 'element') {
|
|
350
|
+
const elemNode = node as ElementNode
|
|
351
|
+
if (elemNode.name.toLowerCase() === 'img') {
|
|
352
|
+
for (const attr of elemNode.attributes) {
|
|
353
|
+
if (attr.type === 'attribute' && attr.name === 'src' && attr.value) {
|
|
354
|
+
const srcLine = attr.position?.start.line ?? elemNode.position?.start.line ?? 0
|
|
355
|
+
const snippet = extractImageSnippet(cached.lines, srcLine - 1)
|
|
356
|
+
addToImageSearchIndex({
|
|
357
|
+
file: relFile,
|
|
358
|
+
line: srcLine,
|
|
359
|
+
snippet,
|
|
360
|
+
src: attr.value,
|
|
361
|
+
})
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Also index component nodes with src attributes (e.g., <Image src="..." />)
|
|
368
|
+
// This captures image component usages where the actual src is defined
|
|
369
|
+
if (node.type === 'component') {
|
|
370
|
+
const compNode = node as ComponentNode
|
|
371
|
+
for (const attr of compNode.attributes) {
|
|
372
|
+
if (attr.type === 'attribute' && attr.name === 'src' && attr.value) {
|
|
373
|
+
const srcLine = attr.position?.start.line ?? compNode.position?.start.line ?? 0
|
|
374
|
+
const snippet = extractImageSnippet(cached.lines, srcLine - 1)
|
|
375
|
+
addToImageSearchIndex({
|
|
376
|
+
file: relFile,
|
|
377
|
+
line: srcLine,
|
|
378
|
+
snippet,
|
|
379
|
+
src: attr.value,
|
|
380
|
+
})
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if ('children' in node && Array.isArray(node.children)) {
|
|
386
|
+
for (const child of node.children) {
|
|
387
|
+
visit(child)
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
visit(cached.ast)
|
|
392
|
+
} else {
|
|
393
|
+
// For tsx/jsx, use regex
|
|
394
|
+
const srcPatterns = [/src="([^"]+)"/g, /src='([^']+)'/g]
|
|
395
|
+
for (let i = 0; i < cached.lines.length; i++) {
|
|
396
|
+
const line = cached.lines[i]
|
|
397
|
+
if (!line) continue
|
|
398
|
+
|
|
399
|
+
for (const pattern of srcPatterns) {
|
|
400
|
+
pattern.lastIndex = 0
|
|
401
|
+
let match: RegExpExecArray | null
|
|
402
|
+
while ((match = pattern.exec(line)) !== null) {
|
|
403
|
+
const snippet = extractImageSnippet(cached.lines, i)
|
|
404
|
+
addToImageSearchIndex({
|
|
405
|
+
file: relFile,
|
|
406
|
+
line: i + 1,
|
|
407
|
+
snippet,
|
|
408
|
+
src: match[1]!,
|
|
409
|
+
})
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// ============================================================================
|
|
417
|
+
// Index Lookup
|
|
418
|
+
// ============================================================================
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Fast text lookup using pre-built index
|
|
422
|
+
*/
|
|
423
|
+
export function findInTextIndex(textContent: string, tag: string): SourceLocation | undefined {
|
|
424
|
+
const normalizedSearch = normalizeText(textContent)
|
|
425
|
+
const tagLower = tag.toLowerCase()
|
|
426
|
+
const index = getTextSearchIndex()
|
|
427
|
+
|
|
428
|
+
// First try exact match with same tag
|
|
429
|
+
for (const entry of index) {
|
|
430
|
+
if (entry.tag === tagLower && entry.normalizedText === normalizedSearch) {
|
|
431
|
+
return {
|
|
432
|
+
file: entry.file,
|
|
433
|
+
line: entry.line,
|
|
434
|
+
snippet: entry.snippet,
|
|
435
|
+
openingTagSnippet: entry.openingTagSnippet,
|
|
436
|
+
type: entry.type,
|
|
437
|
+
variableName: entry.variableName,
|
|
438
|
+
definitionLine: entry.definitionLine,
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Then try partial match for longer text
|
|
444
|
+
if (normalizedSearch.length > 10) {
|
|
445
|
+
const textPreview = normalizedSearch.slice(0, Math.min(30, normalizedSearch.length))
|
|
446
|
+
for (const entry of index) {
|
|
447
|
+
if (entry.tag === tagLower && entry.normalizedText.includes(textPreview)) {
|
|
448
|
+
return {
|
|
449
|
+
file: entry.file,
|
|
450
|
+
line: entry.line,
|
|
451
|
+
snippet: entry.snippet,
|
|
452
|
+
openingTagSnippet: entry.openingTagSnippet,
|
|
453
|
+
type: entry.type,
|
|
454
|
+
variableName: entry.variableName,
|
|
455
|
+
definitionLine: entry.definitionLine,
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Try any tag match
|
|
462
|
+
for (const entry of index) {
|
|
463
|
+
if (entry.normalizedText === normalizedSearch) {
|
|
464
|
+
return {
|
|
465
|
+
file: entry.file,
|
|
466
|
+
line: entry.line,
|
|
467
|
+
snippet: entry.snippet,
|
|
468
|
+
openingTagSnippet: entry.openingTagSnippet,
|
|
469
|
+
type: entry.type,
|
|
470
|
+
variableName: entry.variableName,
|
|
471
|
+
definitionLine: entry.definitionLine,
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return undefined
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Extract the pathname from a src value (handles both absolute URLs and relative paths)
|
|
481
|
+
*/
|
|
482
|
+
function extractPathname(src: string): string {
|
|
483
|
+
try {
|
|
484
|
+
return new URL(src).pathname
|
|
485
|
+
} catch {
|
|
486
|
+
return (src.split('?')[0] ?? src)
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Fast image lookup using pre-built index
|
|
492
|
+
*/
|
|
493
|
+
export function findInImageIndex(imageSrc: string): SourceLocation | undefined {
|
|
494
|
+
const index = getImageSearchIndex()
|
|
495
|
+
|
|
496
|
+
// Exact match first
|
|
497
|
+
for (const entry of index) {
|
|
498
|
+
if (entry.src === imageSrc) {
|
|
499
|
+
return {
|
|
500
|
+
file: entry.file,
|
|
501
|
+
line: entry.line,
|
|
502
|
+
snippet: entry.snippet,
|
|
503
|
+
type: 'static',
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Fallback: path suffix matching for CDN-transformed URLs
|
|
509
|
+
// e.g., rendered src "/cdn-cgi/image/.../assets/photo.webp" should match
|
|
510
|
+
// authored src "https://cdn.nuasite.com/assets/photo.webp"
|
|
511
|
+
const targetPath = extractPathname(imageSrc)
|
|
512
|
+
for (const entry of index) {
|
|
513
|
+
const entryPath = extractPathname(entry.src)
|
|
514
|
+
if (entryPath.length > 5 && (targetPath.endsWith(entryPath) || entryPath.endsWith(targetPath))) {
|
|
515
|
+
return {
|
|
516
|
+
file: entry.file,
|
|
517
|
+
line: entry.line,
|
|
518
|
+
snippet: entry.snippet,
|
|
519
|
+
type: 'static',
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return undefined
|
|
525
|
+
}
|