@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,387 @@
|
|
|
1
|
+
import type { ComponentNode, ElementNode, Node as AstroNode, TextNode } from '@astrojs/compiler/types'
|
|
2
|
+
|
|
3
|
+
import { buildDefinitionPath, parseExpressionPath } from './ast-extractors'
|
|
4
|
+
import { normalizeText } from './snippet-utils'
|
|
5
|
+
import type {
|
|
6
|
+
ComponentPropMatch,
|
|
7
|
+
ExpressionPropMatch,
|
|
8
|
+
FindElementResult,
|
|
9
|
+
ImportInfo,
|
|
10
|
+
SpreadPropMatch,
|
|
11
|
+
TemplateMatch,
|
|
12
|
+
VariableDefinition,
|
|
13
|
+
} from './types'
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Text Content Extraction
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get text content from an AST node recursively.
|
|
21
|
+
* Treats <br> elements as whitespace to match rendered HTML behavior.
|
|
22
|
+
*/
|
|
23
|
+
export function getTextContent(node: AstroNode): string {
|
|
24
|
+
if (node.type === 'text') {
|
|
25
|
+
return (node as TextNode).value
|
|
26
|
+
}
|
|
27
|
+
// Treat <br> elements as whitespace (they create line breaks in rendered HTML)
|
|
28
|
+
if (node.type === 'element' && (node as ElementNode).name.toLowerCase() === 'br') {
|
|
29
|
+
return ' '
|
|
30
|
+
}
|
|
31
|
+
// Treat <wbr> elements as empty (word break opportunity, no visible content)
|
|
32
|
+
if (node.type === 'element' && (node as ElementNode).name.toLowerCase() === 'wbr') {
|
|
33
|
+
return ''
|
|
34
|
+
}
|
|
35
|
+
if ('children' in node && Array.isArray(node.children)) {
|
|
36
|
+
return node.children.map(getTextContent).join('')
|
|
37
|
+
}
|
|
38
|
+
return ''
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Check for expression children and extract variable names
|
|
43
|
+
*/
|
|
44
|
+
export function hasExpressionChild(node: AstroNode): { found: boolean; varNames: string[] } {
|
|
45
|
+
const varNames: string[] = []
|
|
46
|
+
if (node.type === 'expression') {
|
|
47
|
+
// Try to extract variable name from expression
|
|
48
|
+
// The expression node children contain the text representation
|
|
49
|
+
const exprText = getTextContent(node)
|
|
50
|
+
// Extract variable paths like {foo}, {foo.bar}, {items[0]}, {config.nav.title}, {links[0].text}
|
|
51
|
+
const fullPath = parseExpressionPath(exprText)
|
|
52
|
+
if (fullPath) {
|
|
53
|
+
varNames.push(fullPath)
|
|
54
|
+
}
|
|
55
|
+
return { found: true, varNames }
|
|
56
|
+
}
|
|
57
|
+
if ('children' in node && Array.isArray(node.children)) {
|
|
58
|
+
for (const child of node.children) {
|
|
59
|
+
const result = hasExpressionChild(child)
|
|
60
|
+
if (result.found) {
|
|
61
|
+
varNames.push(...result.varNames)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { found: varNames.length > 0, varNames }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ============================================================================
|
|
69
|
+
// Element Finding
|
|
70
|
+
// ============================================================================
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Walk the Astro AST to find elements matching a tag with specific text content.
|
|
74
|
+
* Returns the best match (local variables or static content) AND all prop/import candidates
|
|
75
|
+
* that need cross-file verification for multiple same-tag elements.
|
|
76
|
+
* @param propAliases - Map of local variable names to prop names from Astro.props (for cross-file tracking)
|
|
77
|
+
* @param imports - Import information from frontmatter (for cross-file tracking)
|
|
78
|
+
*/
|
|
79
|
+
export function findElementWithText(
|
|
80
|
+
ast: AstroNode,
|
|
81
|
+
tag: string,
|
|
82
|
+
searchText: string,
|
|
83
|
+
variableDefinitions: VariableDefinition[],
|
|
84
|
+
propAliases: Map<string, string> = new Map(),
|
|
85
|
+
imports: ImportInfo[] = [],
|
|
86
|
+
): FindElementResult {
|
|
87
|
+
const normalizedSearch = normalizeText(searchText)
|
|
88
|
+
const tagLower = tag.toLowerCase()
|
|
89
|
+
let bestMatch: TemplateMatch | null = null
|
|
90
|
+
let bestScore = 0
|
|
91
|
+
const propCandidates: TemplateMatch[] = []
|
|
92
|
+
const importCandidates: TemplateMatch[] = []
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Extract the base variable name from an expression path.
|
|
96
|
+
* e.g., 'items[0]' -> 'items', 'config.nav.title' -> 'config'
|
|
97
|
+
*/
|
|
98
|
+
function getBaseVarName(exprPath: string): string {
|
|
99
|
+
const match = exprPath.match(/^(\w+)/)
|
|
100
|
+
return match?.[1] ?? exprPath
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function visit(node: AstroNode) {
|
|
104
|
+
// Check if this is an element or component matching our tag
|
|
105
|
+
if ((node.type === 'element' || node.type === 'component') && node.name.toLowerCase() === tagLower) {
|
|
106
|
+
const elemNode = node as ElementNode | ComponentNode
|
|
107
|
+
const textContent = getTextContent(elemNode)
|
|
108
|
+
const normalizedContent = normalizeText(textContent)
|
|
109
|
+
const line = elemNode.position?.start.line ?? 0
|
|
110
|
+
|
|
111
|
+
// Check for expression (variable reference)
|
|
112
|
+
const exprInfo = hasExpressionChild(elemNode)
|
|
113
|
+
if (exprInfo.found && exprInfo.varNames.length > 0) {
|
|
114
|
+
// Look for matching variable definition
|
|
115
|
+
for (const exprPath of exprInfo.varNames) {
|
|
116
|
+
let foundInLocal = false
|
|
117
|
+
|
|
118
|
+
for (const def of variableDefinitions) {
|
|
119
|
+
// Build the full definition path for comparison
|
|
120
|
+
const defPath = buildDefinitionPath(def)
|
|
121
|
+
// Check if the expression path matches the definition path
|
|
122
|
+
if (defPath === exprPath) {
|
|
123
|
+
foundInLocal = true
|
|
124
|
+
const normalizedDef = normalizeText(def.value)
|
|
125
|
+
if (normalizedDef === normalizedSearch) {
|
|
126
|
+
// Found a variable match - this is highest priority
|
|
127
|
+
if (bestScore < 100) {
|
|
128
|
+
bestScore = 100
|
|
129
|
+
bestMatch = {
|
|
130
|
+
line,
|
|
131
|
+
type: 'variable',
|
|
132
|
+
variableName: defPath,
|
|
133
|
+
definitionLine: def.line,
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// If not found in local definitions, check if it's from props or imports
|
|
142
|
+
if (!foundInLocal) {
|
|
143
|
+
const baseVar = getBaseVarName(exprPath)
|
|
144
|
+
|
|
145
|
+
// Check props first
|
|
146
|
+
const actualPropName = propAliases.get(baseVar)
|
|
147
|
+
if (actualPropName) {
|
|
148
|
+
// This expression uses a prop - collect as candidate for cross-file verification
|
|
149
|
+
// (don't set bestMatch yet - we need to verify each candidate)
|
|
150
|
+
propCandidates.push({
|
|
151
|
+
line,
|
|
152
|
+
type: 'variable',
|
|
153
|
+
usesProp: true,
|
|
154
|
+
propName: actualPropName, // Use the actual prop name, not the local alias
|
|
155
|
+
expressionPath: exprPath,
|
|
156
|
+
})
|
|
157
|
+
} else {
|
|
158
|
+
// Check if it's from an import
|
|
159
|
+
const importInfo = imports.find((imp) => imp.localName === baseVar)
|
|
160
|
+
if (importInfo) {
|
|
161
|
+
// This expression uses an import - collect as candidate for cross-file verification
|
|
162
|
+
importCandidates.push({
|
|
163
|
+
line,
|
|
164
|
+
type: 'variable',
|
|
165
|
+
usesImport: true,
|
|
166
|
+
importInfo,
|
|
167
|
+
expressionPath: exprPath,
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Check for direct text match (static content)
|
|
176
|
+
// Only match if there's meaningful text content (not just variable names/expressions)
|
|
177
|
+
if (normalizedContent && normalizedContent.length >= 2 && normalizedSearch.length > 0) {
|
|
178
|
+
// For short search text (<= 10 chars), require exact match
|
|
179
|
+
if (normalizedSearch.length <= 10) {
|
|
180
|
+
if (normalizedContent.includes(normalizedSearch)) {
|
|
181
|
+
const score = 80
|
|
182
|
+
if (score > bestScore) {
|
|
183
|
+
bestScore = score
|
|
184
|
+
const actualLine = findTextLine(elemNode, normalizedSearch)
|
|
185
|
+
bestMatch = {
|
|
186
|
+
line: actualLine ?? line,
|
|
187
|
+
type: 'static',
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
} // For longer search text, check if content contains a significant portion
|
|
192
|
+
else if (normalizedSearch.length > 10) {
|
|
193
|
+
const textPreview = normalizedSearch.slice(0, Math.min(30, normalizedSearch.length))
|
|
194
|
+
if (normalizedContent.includes(textPreview)) {
|
|
195
|
+
const matchLength = Math.min(normalizedSearch.length, normalizedContent.length)
|
|
196
|
+
const score = 50 + (matchLength / normalizedSearch.length) * 40
|
|
197
|
+
if (score > bestScore) {
|
|
198
|
+
bestScore = score
|
|
199
|
+
const actualLine = findTextLine(elemNode, textPreview)
|
|
200
|
+
bestMatch = {
|
|
201
|
+
line: actualLine ?? line,
|
|
202
|
+
type: 'static',
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
} // Try matching first few words for very long text
|
|
206
|
+
else if (normalizedSearch.length > 20) {
|
|
207
|
+
const firstWords = normalizedSearch.split(' ').slice(0, 3).join(' ')
|
|
208
|
+
if (firstWords && normalizedContent.includes(firstWords)) {
|
|
209
|
+
const score = 40
|
|
210
|
+
if (score > bestScore) {
|
|
211
|
+
bestScore = score
|
|
212
|
+
const actualLine = findTextLine(elemNode, firstWords)
|
|
213
|
+
bestMatch = {
|
|
214
|
+
line: actualLine ?? line,
|
|
215
|
+
type: 'static',
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Recursively visit children
|
|
225
|
+
if ('children' in node && Array.isArray(node.children)) {
|
|
226
|
+
for (const child of node.children) {
|
|
227
|
+
visit(child)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function findTextLine(node: AstroNode, searchText: string): number | null {
|
|
233
|
+
if (node.type === 'text') {
|
|
234
|
+
const textNode = node as TextNode
|
|
235
|
+
if (normalizeText(textNode.value).includes(searchText)) {
|
|
236
|
+
return textNode.position?.start.line ?? null
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if ('children' in node && Array.isArray(node.children)) {
|
|
240
|
+
for (const child of node.children) {
|
|
241
|
+
const line = findTextLine(child, searchText)
|
|
242
|
+
if (line !== null) return line
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return null
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
visit(ast)
|
|
249
|
+
return { bestMatch, propCandidates, importCandidates }
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ============================================================================
|
|
253
|
+
// Component Prop Finding
|
|
254
|
+
// ============================================================================
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Walk the Astro AST to find component props with specific text value
|
|
258
|
+
*/
|
|
259
|
+
export function findComponentProp(
|
|
260
|
+
ast: AstroNode,
|
|
261
|
+
searchText: string,
|
|
262
|
+
): ComponentPropMatch | null {
|
|
263
|
+
const normalizedSearch = normalizeText(searchText)
|
|
264
|
+
|
|
265
|
+
function visit(node: AstroNode): ComponentPropMatch | null {
|
|
266
|
+
// Check component nodes (PascalCase names)
|
|
267
|
+
if (node.type === 'component') {
|
|
268
|
+
const compNode = node as ComponentNode
|
|
269
|
+
for (const attr of compNode.attributes) {
|
|
270
|
+
if (attr.type === 'attribute' && attr.kind === 'quoted') {
|
|
271
|
+
const normalizedValue = normalizeText(attr.value)
|
|
272
|
+
if (normalizedValue === normalizedSearch) {
|
|
273
|
+
return {
|
|
274
|
+
line: attr.position?.start.line ?? compNode.position?.start.line ?? 0,
|
|
275
|
+
propName: attr.name,
|
|
276
|
+
propValue: attr.value,
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Recursively visit children
|
|
284
|
+
if ('children' in node && Array.isArray(node.children)) {
|
|
285
|
+
for (const child of node.children) {
|
|
286
|
+
const result = visit(child)
|
|
287
|
+
if (result) return result
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return null
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return visit(ast)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Walk the Astro AST to find component usages with expression props.
|
|
299
|
+
* Looks for patterns like: <Nav items={navItems} />
|
|
300
|
+
* @param ast - The Astro AST
|
|
301
|
+
* @param componentName - The component name to search for (e.g., 'Nav')
|
|
302
|
+
* @param propName - The prop name to find (e.g., 'items')
|
|
303
|
+
*/
|
|
304
|
+
export function findExpressionProp(
|
|
305
|
+
ast: AstroNode,
|
|
306
|
+
componentName: string,
|
|
307
|
+
propName: string,
|
|
308
|
+
): ExpressionPropMatch | null {
|
|
309
|
+
function visit(node: AstroNode): ExpressionPropMatch | null {
|
|
310
|
+
// Check component nodes matching the name
|
|
311
|
+
if (node.type === 'component') {
|
|
312
|
+
const compNode = node as ComponentNode
|
|
313
|
+
if (compNode.name === componentName) {
|
|
314
|
+
for (const attr of compNode.attributes) {
|
|
315
|
+
// Check for expression attributes: items={navItems}
|
|
316
|
+
if (attr.type === 'attribute' && attr.name === propName && attr.kind === 'expression') {
|
|
317
|
+
// The value contains the expression text
|
|
318
|
+
const exprText = attr.value?.trim() || ''
|
|
319
|
+
if (exprText) {
|
|
320
|
+
return {
|
|
321
|
+
componentName,
|
|
322
|
+
propName,
|
|
323
|
+
expressionText: exprText,
|
|
324
|
+
line: attr.position?.start.line ?? compNode.position?.start.line ?? 0,
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Recursively visit children
|
|
333
|
+
if ('children' in node && Array.isArray(node.children)) {
|
|
334
|
+
for (const child of node.children) {
|
|
335
|
+
const result = visit(child)
|
|
336
|
+
if (result) return result
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return null
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return visit(ast)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Walk the Astro AST to find component usages with spread props.
|
|
348
|
+
* Looks for patterns like: <Card {...cardProps} />
|
|
349
|
+
* @param ast - The Astro AST
|
|
350
|
+
* @param componentName - The component name to search for (e.g., 'Card')
|
|
351
|
+
*/
|
|
352
|
+
export function findSpreadProp(
|
|
353
|
+
ast: AstroNode,
|
|
354
|
+
componentName: string,
|
|
355
|
+
): SpreadPropMatch | null {
|
|
356
|
+
function visit(node: AstroNode): SpreadPropMatch | null {
|
|
357
|
+
// Check component nodes matching the name
|
|
358
|
+
if (node.type === 'component') {
|
|
359
|
+
const compNode = node as ComponentNode
|
|
360
|
+
if (compNode.name === componentName) {
|
|
361
|
+
for (const attr of compNode.attributes) {
|
|
362
|
+
// Check for spread attributes: {...cardProps}
|
|
363
|
+
// In Astro AST: type='attribute', kind='spread', name=variable name
|
|
364
|
+
if (attr.type === 'attribute' && attr.kind === 'spread' && attr.name) {
|
|
365
|
+
return {
|
|
366
|
+
componentName,
|
|
367
|
+
spreadVarName: attr.name,
|
|
368
|
+
line: attr.position?.start.line ?? compNode.position?.start.line ?? 0,
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Recursively visit children
|
|
376
|
+
if ('children' in node && Array.isArray(node.children)) {
|
|
377
|
+
for (const child of node.children) {
|
|
378
|
+
const result = visit(child)
|
|
379
|
+
if (result) return result
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return null
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return visit(ast)
|
|
387
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import type { ComponentNode, ElementNode, Node as AstroNode } 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 { getCachedParsedFile } from './ast-parser'
|
|
7
|
+
import { isSearchIndexInitialized } from './cache'
|
|
8
|
+
import { findInImageIndex } from './search-index'
|
|
9
|
+
import { extractImageSnippet } from './snippet-utils'
|
|
10
|
+
import type { ImageMatch, SourceLocation } from './types'
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Image Element Finding
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Walk the Astro AST to find img elements or Image component usages with specific src
|
|
18
|
+
*/
|
|
19
|
+
export function findImageElement(
|
|
20
|
+
ast: AstroNode,
|
|
21
|
+
imageSrc: string,
|
|
22
|
+
lines: string[],
|
|
23
|
+
): ImageMatch | null {
|
|
24
|
+
function visit(node: AstroNode): ImageMatch | null {
|
|
25
|
+
// Check <img> elements
|
|
26
|
+
if (node.type === 'element') {
|
|
27
|
+
const elemNode = node as ElementNode
|
|
28
|
+
if (elemNode.name.toLowerCase() === 'img') {
|
|
29
|
+
for (const attr of elemNode.attributes) {
|
|
30
|
+
if (attr.type === 'attribute' && attr.name === 'src' && attr.value === imageSrc) {
|
|
31
|
+
const srcLine = attr.position?.start.line ?? elemNode.position?.start.line ?? 0
|
|
32
|
+
const snippet = extractImageSnippet(lines, srcLine - 1)
|
|
33
|
+
return {
|
|
34
|
+
line: srcLine,
|
|
35
|
+
src: imageSrc,
|
|
36
|
+
snippet,
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Check component nodes with src attributes (e.g., <Image src="..." />)
|
|
44
|
+
if (node.type === 'component') {
|
|
45
|
+
const compNode = node as ComponentNode
|
|
46
|
+
for (const attr of compNode.attributes) {
|
|
47
|
+
if (attr.type === 'attribute' && attr.name === 'src' && attr.value === imageSrc) {
|
|
48
|
+
const srcLine = attr.position?.start.line ?? compNode.position?.start.line ?? 0
|
|
49
|
+
const snippet = extractImageSnippet(lines, srcLine - 1)
|
|
50
|
+
return {
|
|
51
|
+
line: srcLine,
|
|
52
|
+
src: imageSrc,
|
|
53
|
+
snippet,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Recursively visit children
|
|
60
|
+
if ('children' in node && Array.isArray(node.children)) {
|
|
61
|
+
for (const child of node.children) {
|
|
62
|
+
const result = visit(child)
|
|
63
|
+
if (result) return result
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return visit(ast)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Walk the Astro AST to find img elements near a given source line.
|
|
75
|
+
* Used as a fallback when the src value can't be matched (expression attributes).
|
|
76
|
+
* Returns the img element closest to the expected line.
|
|
77
|
+
*/
|
|
78
|
+
export function findImageElementNearLine(
|
|
79
|
+
ast: AstroNode,
|
|
80
|
+
expectedLine: number,
|
|
81
|
+
lines: string[],
|
|
82
|
+
): ImageMatch | null {
|
|
83
|
+
let bestMatch: ImageMatch | null = null
|
|
84
|
+
let bestDistance = Infinity
|
|
85
|
+
|
|
86
|
+
function visit(node: AstroNode): void {
|
|
87
|
+
if (node.type === 'element') {
|
|
88
|
+
const elemNode = node as ElementNode
|
|
89
|
+
if (elemNode.name.toLowerCase() === 'img') {
|
|
90
|
+
// Check if this img has a src attribute (any kind)
|
|
91
|
+
const srcAttr = elemNode.attributes.find(
|
|
92
|
+
attr => attr.type === 'attribute' && attr.name === 'src',
|
|
93
|
+
)
|
|
94
|
+
if (srcAttr) {
|
|
95
|
+
const imgLine = srcAttr.position?.start.line ?? elemNode.position?.start.line ?? 0
|
|
96
|
+
const distance = Math.abs(imgLine - expectedLine)
|
|
97
|
+
|
|
98
|
+
if (distance < bestDistance) {
|
|
99
|
+
bestDistance = distance
|
|
100
|
+
const snippet = extractImageSnippet(lines, imgLine - 1)
|
|
101
|
+
bestMatch = {
|
|
102
|
+
line: imgLine,
|
|
103
|
+
src: srcAttr.value,
|
|
104
|
+
snippet,
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if ('children' in node && Array.isArray(node.children)) {
|
|
112
|
+
for (const child of node.children) {
|
|
113
|
+
visit(child)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
visit(ast)
|
|
119
|
+
|
|
120
|
+
// Only return match if within a reasonable distance (15 lines)
|
|
121
|
+
return bestMatch && bestDistance <= 15 ? bestMatch : null
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ============================================================================
|
|
125
|
+
// Image Source Location Finding
|
|
126
|
+
// ============================================================================
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Parse URLs from a srcset attribute string.
|
|
130
|
+
* srcset format: "url1 480w, url2 768w, ..."
|
|
131
|
+
*/
|
|
132
|
+
function parseSrcsetUrls(srcSet: string): string[] {
|
|
133
|
+
return srcSet
|
|
134
|
+
.split(',')
|
|
135
|
+
.map(entry => entry.trim().split(/\s+/)[0])
|
|
136
|
+
.filter((url): url is string => !!url && url.length > 0)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Find source file and line number for an image by its src attribute.
|
|
141
|
+
* Also checks srcset URLs as fallback when src doesn't match (e.g., when src
|
|
142
|
+
* is a local upload path but srcset contains CDN-transformed original URLs).
|
|
143
|
+
* Uses pre-built search index for fast lookups.
|
|
144
|
+
*/
|
|
145
|
+
export async function findImageSourceLocation(
|
|
146
|
+
imageSrc: string,
|
|
147
|
+
imageSrcSet?: string,
|
|
148
|
+
): Promise<SourceLocation | undefined> {
|
|
149
|
+
// Use index if available (much faster)
|
|
150
|
+
if (isSearchIndexInitialized()) {
|
|
151
|
+
const result = findInImageIndex(imageSrc)
|
|
152
|
+
if (result) return result
|
|
153
|
+
|
|
154
|
+
// Fallback: try URLs extracted from srcset
|
|
155
|
+
if (imageSrcSet) {
|
|
156
|
+
const srcsetUrls = parseSrcsetUrls(imageSrcSet)
|
|
157
|
+
for (const url of srcsetUrls) {
|
|
158
|
+
const srcsetResult = findInImageIndex(url)
|
|
159
|
+
if (srcsetResult) return srcsetResult
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return undefined
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Fallback to slow search if index not initialized
|
|
167
|
+
const srcDir = path.join(getProjectRoot(), 'src')
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
const searchDirs = [
|
|
171
|
+
path.join(srcDir, 'pages'),
|
|
172
|
+
path.join(srcDir, 'components'),
|
|
173
|
+
path.join(srcDir, 'layouts'),
|
|
174
|
+
]
|
|
175
|
+
|
|
176
|
+
for (const dir of searchDirs) {
|
|
177
|
+
try {
|
|
178
|
+
const result = await searchDirectoryForImage(dir, imageSrc)
|
|
179
|
+
if (result) {
|
|
180
|
+
return result
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
// Directory doesn't exist, continue
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
} catch {
|
|
187
|
+
// Search failed
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return undefined
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ============================================================================
|
|
194
|
+
// Directory Search for Images
|
|
195
|
+
// ============================================================================
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Recursively search directory for image with matching src
|
|
199
|
+
*/
|
|
200
|
+
export async function searchDirectoryForImage(
|
|
201
|
+
dir: string,
|
|
202
|
+
imageSrc: string,
|
|
203
|
+
): Promise<SourceLocation | undefined> {
|
|
204
|
+
try {
|
|
205
|
+
const entries = await fs.readdir(dir, { withFileTypes: true })
|
|
206
|
+
|
|
207
|
+
for (const entry of entries) {
|
|
208
|
+
const fullPath = path.join(dir, entry.name)
|
|
209
|
+
|
|
210
|
+
if (entry.isDirectory()) {
|
|
211
|
+
const result = await searchDirectoryForImage(fullPath, imageSrc)
|
|
212
|
+
if (result) return result
|
|
213
|
+
} else if (entry.isFile() && (entry.name.endsWith('.astro') || entry.name.endsWith('.tsx') || entry.name.endsWith('.jsx'))) {
|
|
214
|
+
const result = await searchFileForImage(fullPath, imageSrc)
|
|
215
|
+
if (result) return result
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} catch {
|
|
219
|
+
// Error reading directory
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return undefined
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Search a single file for an image with matching src.
|
|
227
|
+
* Uses caching for better performance.
|
|
228
|
+
*/
|
|
229
|
+
async function searchFileForImage(
|
|
230
|
+
filePath: string,
|
|
231
|
+
imageSrc: string,
|
|
232
|
+
): Promise<SourceLocation | undefined> {
|
|
233
|
+
try {
|
|
234
|
+
// Use cached parsed file
|
|
235
|
+
const cached = await getCachedParsedFile(filePath)
|
|
236
|
+
if (!cached) return undefined
|
|
237
|
+
|
|
238
|
+
const { lines, ast } = cached
|
|
239
|
+
|
|
240
|
+
// Use AST parsing for Astro files
|
|
241
|
+
if (filePath.endsWith('.astro')) {
|
|
242
|
+
const imageMatch = findImageElement(ast, imageSrc, lines)
|
|
243
|
+
|
|
244
|
+
if (imageMatch) {
|
|
245
|
+
return {
|
|
246
|
+
file: path.relative(getProjectRoot(), filePath),
|
|
247
|
+
line: imageMatch.line,
|
|
248
|
+
snippet: imageMatch.snippet,
|
|
249
|
+
type: 'static',
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Regex fallback for TSX/JSX files or if AST parsing failed
|
|
255
|
+
const srcPatterns = [
|
|
256
|
+
`src="${imageSrc}"`,
|
|
257
|
+
`src='${imageSrc}'`,
|
|
258
|
+
]
|
|
259
|
+
|
|
260
|
+
for (let i = 0; i < lines.length; i++) {
|
|
261
|
+
const line = lines[i]
|
|
262
|
+
if (!line) continue
|
|
263
|
+
|
|
264
|
+
for (const pattern of srcPatterns) {
|
|
265
|
+
if (line.includes(pattern)) {
|
|
266
|
+
// Found the image, extract the full <img> tag as snippet
|
|
267
|
+
const snippet = extractImageSnippet(lines, i)
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
file: path.relative(getProjectRoot(), filePath),
|
|
271
|
+
line: i + 1,
|
|
272
|
+
snippet,
|
|
273
|
+
type: 'static',
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
} catch {
|
|
279
|
+
// Error reading file
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return undefined
|
|
283
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Public API - Barrel File
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// This file re-exports the public API for backward compatibility.
|
|
5
|
+
// All imports from './source-finder' will continue to work unchanged.
|
|
6
|
+
|
|
7
|
+
// Types (public)
|
|
8
|
+
export type { CollectionInfo, MarkdownContent, SourceLocation, VariableReference } from './types'
|
|
9
|
+
|
|
10
|
+
// Cache management
|
|
11
|
+
export { clearSourceFinderCache } from './cache'
|
|
12
|
+
|
|
13
|
+
// Search index
|
|
14
|
+
export { initializeSearchIndex } from './search-index'
|
|
15
|
+
|
|
16
|
+
// Source location finding
|
|
17
|
+
export { findSourceLocation } from './source-lookup'
|
|
18
|
+
|
|
19
|
+
// Attribute source finding
|
|
20
|
+
export { findAttributeSourceLocation } from './cross-file-tracker'
|
|
21
|
+
|
|
22
|
+
// Image finding
|
|
23
|
+
export { findImageSourceLocation } from './image-finder'
|
|
24
|
+
|
|
25
|
+
// Collection/markdown finding
|
|
26
|
+
export { findCollectionSource, findMarkdownSourceLocation, parseMarkdownContent } from './collection-finder'
|
|
27
|
+
|
|
28
|
+
// Snippet utilities (used by html-processor)
|
|
29
|
+
export {
|
|
30
|
+
enhanceManifestWithSourceSnippets,
|
|
31
|
+
extractCompleteTagSnippet,
|
|
32
|
+
extractInnerHtmlFromSnippet,
|
|
33
|
+
extractOpeningTagWithLine,
|
|
34
|
+
extractSourceSnippet,
|
|
35
|
+
updateAttributeSources,
|
|
36
|
+
updateColorClassSources,
|
|
37
|
+
} from './snippet-utils'
|