@wtdlee/repomap 0.3.0 → 0.3.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/analyzers/index.d.ts +69 -5
- package/dist/analyzers/index.js +1 -5
- package/dist/{server/doc-server.js → chunk-4K4MGTPV.js} +41 -329
- package/dist/chunk-6F4PWJZI.js +0 -0
- package/dist/chunk-J2CM7T7U.js +1 -0
- package/dist/{generators/page-map-generator.js → chunk-MOEA75XK.js} +278 -503
- package/dist/{generators/rails-map-generator.js → chunk-SL2GMDBN.js} +48 -129
- package/dist/chunk-UJT5KTVK.js +36 -0
- package/dist/chunk-VV3A3UE3.js +1 -0
- package/dist/chunk-XWZH2RDG.js +19 -0
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +29 -499
- package/dist/dataflow-analyzer-BfAiqVUp.d.ts +180 -0
- package/dist/env-detector-BIWJ7OYF.js +1 -0
- package/dist/generators/assets/common.css +564 -23
- package/dist/generators/index.d.ts +431 -3
- package/dist/generators/index.js +1 -3
- package/dist/index.d.ts +53 -10
- package/dist/index.js +1 -11
- package/dist/page-map-generator-XNZ4TDJT.js +1 -0
- package/dist/rails-TJCDGBBF.js +1 -0
- package/dist/rails-map-generator-JL5PKHYP.js +1 -0
- package/dist/server/index.d.ts +33 -1
- package/dist/server/index.js +1 -1
- package/dist/types.d.ts +39 -37
- package/dist/types.js +1 -5
- package/package.json +4 -2
- package/dist/analyzers/base-analyzer.d.ts +0 -45
- package/dist/analyzers/base-analyzer.js +0 -47
- package/dist/analyzers/dataflow-analyzer.d.ts +0 -29
- package/dist/analyzers/dataflow-analyzer.js +0 -425
- package/dist/analyzers/graphql-analyzer.d.ts +0 -22
- package/dist/analyzers/graphql-analyzer.js +0 -386
- package/dist/analyzers/pages-analyzer.d.ts +0 -84
- package/dist/analyzers/pages-analyzer.js +0 -1695
- package/dist/analyzers/rails/index.d.ts +0 -46
- package/dist/analyzers/rails/index.js +0 -145
- package/dist/analyzers/rails/rails-controller-analyzer.d.ts +0 -82
- package/dist/analyzers/rails/rails-controller-analyzer.js +0 -478
- package/dist/analyzers/rails/rails-grpc-analyzer.d.ts +0 -44
- package/dist/analyzers/rails/rails-grpc-analyzer.js +0 -262
- package/dist/analyzers/rails/rails-model-analyzer.d.ts +0 -88
- package/dist/analyzers/rails/rails-model-analyzer.js +0 -493
- package/dist/analyzers/rails/rails-react-analyzer.d.ts +0 -41
- package/dist/analyzers/rails/rails-react-analyzer.js +0 -529
- package/dist/analyzers/rails/rails-routes-analyzer.d.ts +0 -62
- package/dist/analyzers/rails/rails-routes-analyzer.js +0 -540
- package/dist/analyzers/rails/rails-view-analyzer.d.ts +0 -49
- package/dist/analyzers/rails/rails-view-analyzer.js +0 -386
- package/dist/analyzers/rails/ruby-parser.d.ts +0 -63
- package/dist/analyzers/rails/ruby-parser.js +0 -212
- package/dist/analyzers/rest-api-analyzer.d.ts +0 -65
- package/dist/analyzers/rest-api-analyzer.js +0 -479
- package/dist/core/cache.d.ts +0 -47
- package/dist/core/cache.js +0 -151
- package/dist/core/engine.d.ts +0 -46
- package/dist/core/engine.js +0 -319
- package/dist/core/index.d.ts +0 -2
- package/dist/core/index.js +0 -2
- package/dist/generators/markdown-generator.d.ts +0 -25
- package/dist/generators/markdown-generator.js +0 -782
- package/dist/generators/mermaid-generator.d.ts +0 -35
- package/dist/generators/mermaid-generator.js +0 -364
- package/dist/generators/page-map-generator.d.ts +0 -22
- package/dist/generators/rails-map-generator.d.ts +0 -21
- package/dist/server/doc-server.d.ts +0 -30
- package/dist/utils/env-detector.d.ts +0 -31
- package/dist/utils/env-detector.js +0 -188
- package/dist/utils/parallel.d.ts +0 -23
- package/dist/utils/parallel.js +0 -70
- package/dist/utils/port.d.ts +0 -15
- package/dist/utils/port.js +0 -41
|
@@ -1,142 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Interactive page map generator
|
|
3
|
-
*/
|
|
4
|
-
export class PageMapGenerator {
|
|
5
|
-
graphqlOps = [];
|
|
6
|
-
apiCalls = [];
|
|
7
|
-
components = [];
|
|
8
|
-
generatePageMapHtml(report, options) {
|
|
9
|
-
const allPages = [];
|
|
10
|
-
const envResult = options?.envResult;
|
|
11
|
-
const railsAnalysis = options?.railsAnalysis;
|
|
12
|
-
const activeTab = options?.activeTab || 'pages';
|
|
13
|
-
// Get repository name for display
|
|
14
|
-
const repoName = report.repositories[0]?.displayName || report.repositories[0]?.name || 'Repository';
|
|
15
|
-
for (const repoResult of report.repositories) {
|
|
16
|
-
this.graphqlOps.push(...(repoResult.analysis?.graphqlOperations || []));
|
|
17
|
-
this.apiCalls.push(...(repoResult.analysis?.apiCalls || []));
|
|
18
|
-
// Collect component information
|
|
19
|
-
const comps = repoResult.analysis?.components || [];
|
|
20
|
-
for (const comp of comps) {
|
|
21
|
-
this.components.push({
|
|
22
|
-
name: comp.name,
|
|
23
|
-
filePath: comp.filePath,
|
|
24
|
-
type: comp.type,
|
|
25
|
-
dependencies: comp.dependencies || [],
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
for (const repoResult of report.repositories) {
|
|
30
|
-
const pages = repoResult.analysis?.pages || [];
|
|
31
|
-
for (const page of pages) {
|
|
32
|
-
allPages.push({
|
|
33
|
-
...page,
|
|
34
|
-
repo: repoResult.name,
|
|
35
|
-
children: [],
|
|
36
|
-
parent: null,
|
|
37
|
-
depth: 0,
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
const { rootPages, relations } = this.buildHierarchy(allPages);
|
|
42
|
-
return this.renderPageMapHtml(allPages, rootPages, relations, repoName, {
|
|
43
|
-
envResult,
|
|
44
|
-
railsAnalysis,
|
|
45
|
-
activeTab,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
buildHierarchy(pages) {
|
|
49
|
-
const pathMap = new Map();
|
|
50
|
-
const relations = [];
|
|
51
|
-
for (const page of pages) {
|
|
52
|
-
pathMap.set(page.path, page);
|
|
53
|
-
}
|
|
54
|
-
for (const page of pages) {
|
|
55
|
-
const segments = page.path.split('/').filter(Boolean);
|
|
56
|
-
for (let i = segments.length - 1; i >= 1; i--) {
|
|
57
|
-
const parentPath = '/' + segments.slice(0, i).join('/');
|
|
58
|
-
const parent = pathMap.get(parentPath);
|
|
59
|
-
if (parent) {
|
|
60
|
-
page.parent = parentPath;
|
|
61
|
-
page.depth = parent.depth + 1;
|
|
62
|
-
if (!parent.children.includes(page.path)) {
|
|
63
|
-
parent.children.push(page.path);
|
|
64
|
-
}
|
|
65
|
-
relations.push({
|
|
66
|
-
from: parentPath,
|
|
67
|
-
to: page.path,
|
|
68
|
-
type: 'parent-child',
|
|
69
|
-
description: `Sub-page of ${parentPath}`,
|
|
70
|
-
});
|
|
71
|
-
break;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
if (!page.parent) {
|
|
75
|
-
// Use segment count for depth when no parent page exists
|
|
76
|
-
// This ensures proper indentation based on URL structure
|
|
77
|
-
page.depth = Math.max(0, segments.length - 1);
|
|
78
|
-
}
|
|
79
|
-
if (page.layout) {
|
|
80
|
-
for (const other of pages) {
|
|
81
|
-
if (other.path !== page.path && other.layout === page.layout) {
|
|
82
|
-
const existing = relations.find((r) => r.type === 'same-layout' &&
|
|
83
|
-
((r.from === page.path && r.to === other.path) ||
|
|
84
|
-
(r.from === other.path && r.to === page.path)));
|
|
85
|
-
if (!existing) {
|
|
86
|
-
relations.push({
|
|
87
|
-
from: page.path,
|
|
88
|
-
to: other.path,
|
|
89
|
-
type: 'same-layout',
|
|
90
|
-
description: `Both use ${page.layout}`,
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
const rootPages = pages.filter((p) => !p.parent).sort((a, b) => a.path.localeCompare(b.path));
|
|
98
|
-
return { rootPages, relations };
|
|
99
|
-
}
|
|
100
|
-
renderPageMapHtml(allPages, rootPages, relations, repoName, options) {
|
|
101
|
-
const envResult = options?.envResult;
|
|
102
|
-
const railsAnalysis = options?.railsAnalysis;
|
|
103
|
-
const activeTab = options?.activeTab || 'pages';
|
|
104
|
-
const graphqlOpsJson = JSON.stringify(this.graphqlOps.map((op) => ({
|
|
105
|
-
name: op.name,
|
|
106
|
-
type: op.type,
|
|
107
|
-
variables: op.variables,
|
|
108
|
-
fields: op.fields,
|
|
109
|
-
returnType: op.returnType,
|
|
110
|
-
usedIn: op.usedIn,
|
|
111
|
-
})));
|
|
112
|
-
const componentsJson = JSON.stringify(this.components);
|
|
113
|
-
// Rails data for integrated view
|
|
114
|
-
const railsRoutesJson = railsAnalysis ? JSON.stringify(railsAnalysis.routes.routes) : '[]';
|
|
115
|
-
const railsControllersJson = railsAnalysis
|
|
116
|
-
? JSON.stringify(railsAnalysis.controllers.controllers)
|
|
117
|
-
: '[]';
|
|
118
|
-
const railsModelsJson = railsAnalysis ? JSON.stringify(railsAnalysis.models.models) : '[]';
|
|
119
|
-
const railsViewsJson = railsAnalysis
|
|
120
|
-
? JSON.stringify(railsAnalysis.views)
|
|
121
|
-
: '{ "views": [], "pages": [], "summary": {} }';
|
|
122
|
-
const railsReactJson = railsAnalysis
|
|
123
|
-
? JSON.stringify(railsAnalysis.react)
|
|
124
|
-
: '{ "components": [], "entryPoints": [], "summary": {} }';
|
|
125
|
-
const railsGrpcJson = railsAnalysis ? JSON.stringify(railsAnalysis.grpc) : '{ "services": [] }';
|
|
126
|
-
const railsSummaryJson = railsAnalysis ? JSON.stringify(railsAnalysis.summary) : 'null';
|
|
127
|
-
// Environment info
|
|
128
|
-
const hasRails = envResult?.hasRails || false;
|
|
129
|
-
const hasNextjs = envResult?.hasNextjs || false;
|
|
130
|
-
const hasReact = envResult?.hasReact || false;
|
|
131
|
-
// Group by first path segment
|
|
132
|
-
const groups = new Map();
|
|
133
|
-
for (const page of allPages) {
|
|
134
|
-
const seg = page.path.split('/').filter(Boolean)[0] || 'root';
|
|
135
|
-
if (!groups.has(seg))
|
|
136
|
-
groups.set(seg, []);
|
|
137
|
-
groups.get(seg)?.push(page);
|
|
138
|
-
}
|
|
139
|
-
return `<!DOCTYPE html>
|
|
1
|
+
var S=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,r){let c=[],v=r?.envResult,t=r?.railsAnalysis,l=r?.activeTab||"pages",e=s.repositories[0]?.displayName||s.repositories[0]?.name||"Repository";for(let p of s.repositories){this.graphqlOps.push(...p.analysis?.graphqlOperations||[]),this.apiCalls.push(...p.analysis?.apiCalls||[]);let f=p.analysis?.components||[];for(let a of f)this.components.push({name:a.name,filePath:a.filePath,type:a.type,dependencies:a.dependencies||[]});}for(let p of s.repositories){let f=p.analysis?.pages||[];for(let a of f)c.push({...a,repo:p.name,children:[],parent:null,depth:0});}let{rootPages:i,relations:o}=this.buildHierarchy(c);return this.renderPageMapHtml(c,i,o,e,{envResult:v,railsAnalysis:t,activeTab:l})}buildHierarchy(s){let r=new Map,c=[];for(let t of s)r.set(t.path,t);for(let t of s){let l=t.path.split("/").filter(Boolean);for(let e=l.length-1;e>=1;e--){let i="/"+l.slice(0,e).join("/"),o=r.get(i);if(o){t.parent=i,t.depth=o.depth+1,o.children.includes(t.path)||o.children.push(t.path),c.push({from:i,to:t.path,type:"parent-child",description:`Sub-page of ${i}`});break}}if(t.parent||(t.depth=Math.max(0,l.length-1)),t.layout)for(let e of s)e.path!==t.path&&e.layout===t.layout&&(c.find(o=>o.type==="same-layout"&&(o.from===t.path&&o.to===e.path||o.from===e.path&&o.to===t.path))||c.push({from:t.path,to:e.path,type:"same-layout",description:`Both use ${t.layout}`}));}return {rootPages:s.filter(t=>!t.parent).sort((t,l)=>t.path.localeCompare(l.path)),relations:c}}renderPageMapHtml(s,r,c,v,t){let l=t?.envResult,e=t?.railsAnalysis,i=t?.activeTab||"pages",o=JSON.stringify(this.graphqlOps.map(d=>({name:d.name,type:d.type,variables:d.variables,fields:d.fields,returnType:d.returnType,usedIn:d.usedIn}))),p=JSON.stringify(this.components),f=e?JSON.stringify(e.routes.routes):"[]",a=e?JSON.stringify(e.controllers.controllers):"[]",m=e?JSON.stringify(e.models.models):"[]",u=e?JSON.stringify(e.views):'{ "views": [], "pages": [], "summary": {} }',h=e?JSON.stringify(e.react):'{ "components": [], "entryPoints": [], "summary": {} }',y=e?JSON.stringify(e.grpc):'{ "services": [] }',P=e?JSON.stringify(e.summary):"null",n=l?.hasRails||false,w=l?.hasNextjs||false,C=l?.hasReact||false,x=new Map;for(let d of s){let b=d.path.split("/").filter(Boolean)[0]||"root";x.has(b)||x.set(b,[]),x.get(b)?.push(d);}return `<!DOCTYPE html>
|
|
140
2
|
<html lang="en">
|
|
141
3
|
<head>
|
|
142
4
|
<meta charset="UTF-8">
|
|
@@ -147,23 +9,21 @@ export class PageMapGenerator {
|
|
|
147
9
|
<body>
|
|
148
10
|
<header class="header">
|
|
149
11
|
<div style="display:flex;align-items:center;gap:24px">
|
|
150
|
-
<h1 style="cursor:pointer" onclick="location.href='/'"
|
|
12
|
+
<h1 style="cursor:pointer" onclick="location.href='/'">\u{1F4CA} ${v}</h1>
|
|
151
13
|
<nav style="display:flex;gap:4px">
|
|
152
|
-
<a href="/page-map" class="nav-link ${
|
|
153
|
-
${
|
|
14
|
+
<a href="/page-map" class="nav-link ${i==="pages"?"active":""}">Page Map</a>
|
|
15
|
+
${n?`<a href="/rails-map" class="nav-link ${i==="rails"?"active":""}">Rails Map</a>`:""}
|
|
154
16
|
<a href="/docs" class="nav-link">Docs</a>
|
|
155
17
|
<a href="/api/report" class="nav-link" target="_blank">API</a>
|
|
156
18
|
</nav>
|
|
157
19
|
</div>
|
|
158
20
|
<div style="display:flex;gap:12px;align-items:center">
|
|
159
21
|
<!-- Environment filter badges -->
|
|
160
|
-
${
|
|
161
|
-
? `<div class="env-filters" style="display:flex;gap:4px;margin-right:8px">
|
|
22
|
+
${n&&w?`<div class="env-filters" style="display:flex;gap:4px;margin-right:8px">
|
|
162
23
|
<button class="env-badge env-badge-active" data-env="all" onclick="filterByEnv('all')">All</button>
|
|
163
|
-
<button class="env-badge" data-env="nextjs" onclick="filterByEnv('nextjs')"
|
|
164
|
-
<button class="env-badge" data-env="rails" onclick="filterByEnv('rails')"
|
|
165
|
-
</div
|
|
166
|
-
: ''}
|
|
24
|
+
<button class="env-badge" data-env="nextjs" onclick="filterByEnv('nextjs')">\u269B\uFE0F Next.js</button>
|
|
25
|
+
<button class="env-badge" data-env="rails" onclick="filterByEnv('rails')">\u{1F6E4}\uFE0F Rails</button>
|
|
26
|
+
</div>`:""}
|
|
167
27
|
<input class="search" type="text" placeholder="Search pages, queries..." oninput="filter(this.value)">
|
|
168
28
|
<div class="tabs">
|
|
169
29
|
<button class="tab active" onclick="setView('tree')">List</button>
|
|
@@ -198,42 +58,40 @@ export class PageMapGenerator {
|
|
|
198
58
|
<!-- Frontend Stats -->
|
|
199
59
|
<h3 style="margin-top:16px;font-size:10px;text-transform:uppercase;color:var(--text2);letter-spacing:1px">Frontend</h3>
|
|
200
60
|
<div class="stats" id="stats-container">
|
|
201
|
-
<div class="stat" data-filter="pages"><div class="stat-val">${
|
|
202
|
-
<div class="stat" data-filter="hierarchies"><div class="stat-val">${
|
|
61
|
+
<div class="stat" data-filter="pages"><div class="stat-val">${s.length}</div><div class="stat-label">Pages</div></div>
|
|
62
|
+
<div class="stat" data-filter="hierarchies"><div class="stat-val">${c.filter(d=>d.type==="parent-child").length}</div><div class="stat-label">Hierarchies</div></div>
|
|
203
63
|
<div class="stat" data-filter="graphql"><div class="stat-val">${this.graphqlOps.length}</div><div class="stat-label">GraphQL</div></div>
|
|
204
64
|
<div class="stat" data-filter="restapi"><div class="stat-val">${this.apiCalls.length}</div><div class="stat-label">REST API</div></div>
|
|
205
65
|
</div>
|
|
206
66
|
|
|
207
|
-
${
|
|
208
|
-
? `
|
|
67
|
+
${n&&e?`
|
|
209
68
|
<!-- Rails Stats -->
|
|
210
69
|
<h3 style="margin-top:16px;font-size:10px;text-transform:uppercase;color:var(--text2);letter-spacing:1px;cursor:pointer" onclick="switchToRailsTab()">Rails Backend</h3>
|
|
211
70
|
<div class="stats" id="rails-stats">
|
|
212
|
-
<div class="stat" data-filter="rails-routes" onclick="switchToRailsTab()"><div class="stat-val">${
|
|
213
|
-
<div class="stat" data-filter="rails-controllers" onclick="showRailsControllers(); this.blur();"><div class="stat-val">${
|
|
214
|
-
<div class="stat" data-filter="rails-models" onclick="showRailsModels(); this.blur();"><div class="stat-val">${
|
|
215
|
-
<div class="stat" data-filter="rails-grpc" onclick="showRailsGrpc(); this.blur();"><div class="stat-val">${
|
|
216
|
-
<div class="stat" data-filter="rails-react" onclick="showReactComponents(); this.blur();"><div class="stat-val">${
|
|
71
|
+
<div class="stat" data-filter="rails-routes" onclick="switchToRailsTab()"><div class="stat-val">${e.summary.totalRoutes}</div><div class="stat-label">Routes</div></div>
|
|
72
|
+
<div class="stat" data-filter="rails-controllers" onclick="showRailsControllers(); this.blur();"><div class="stat-val">${e.summary.totalControllers}</div><div class="stat-label">Controllers</div></div>
|
|
73
|
+
<div class="stat" data-filter="rails-models" onclick="showRailsModels(); this.blur();"><div class="stat-val">${e.summary.totalModels}</div><div class="stat-label">Models</div></div>
|
|
74
|
+
<div class="stat" data-filter="rails-grpc" onclick="showRailsGrpc(); this.blur();"><div class="stat-val">${e.summary.totalGrpcServices}</div><div class="stat-label">gRPC</div></div>
|
|
75
|
+
<div class="stat" data-filter="rails-react" onclick="showReactComponents(); this.blur();"><div class="stat-val">${e.summary.totalReactComponents}</div><div class="stat-label">\u269B React</div></div>
|
|
217
76
|
</div>
|
|
218
|
-
|
|
219
|
-
: ''}
|
|
77
|
+
`:""}
|
|
220
78
|
</aside>
|
|
221
79
|
|
|
222
80
|
<div class="content">
|
|
223
81
|
<!-- Pages Tree View (for all screens - Next.js/React/Rails) -->
|
|
224
|
-
<div class="tree-view ${
|
|
225
|
-
${
|
|
226
|
-
<div id="page-map-react-components-section" style="${
|
|
82
|
+
<div class="tree-view ${i==="pages"?"active":""}" id="tree-view" data-tab="pages">
|
|
83
|
+
${s.length>0?this.buildTreeHtml(x,s):""}
|
|
84
|
+
<div id="page-map-react-components-section" style="${n?"margin-top:20px;border-top:1px solid var(--bg3);padding-top:20px":""}">
|
|
227
85
|
</div>
|
|
228
|
-
<div id="page-map-rails-section" style="${
|
|
229
|
-
${
|
|
86
|
+
<div id="page-map-rails-section" style="${s.length>0&&n?"margin-top:20px;border-top:1px solid var(--bg3);padding-top:20px":""}">
|
|
87
|
+
${n&&s.length===0?'<div class="empty-state-sm">Loading screens...</div>':""}
|
|
230
88
|
</div>
|
|
231
89
|
</div>
|
|
232
90
|
|
|
233
91
|
<!-- Rails Routes View (dedicated) -->
|
|
234
|
-
<div class="tree-view ${
|
|
92
|
+
<div class="tree-view ${i==="rails"?"active":""}" id="rails-tree-view" data-tab="rails">
|
|
235
93
|
<div id="rails-routes-container">
|
|
236
|
-
${
|
|
94
|
+
${n?'<div class="empty-state-sm">Loading Rails routes...</div>':'<div class="empty-state">No Rails environment detected</div>'}
|
|
237
95
|
</div>
|
|
238
96
|
</div>
|
|
239
97
|
|
|
@@ -254,7 +112,7 @@ export class PageMapGenerator {
|
|
|
254
112
|
<div class="detail" id="detail">
|
|
255
113
|
<div class="detail-header">
|
|
256
114
|
<div class="detail-title" id="detail-title"></div>
|
|
257
|
-
<button class="detail-close" onclick="closeDetail()"
|
|
115
|
+
<button class="detail-close" onclick="closeDetail()">\xD7</button>
|
|
258
116
|
</div>
|
|
259
117
|
<div class="detail-body" id="detail-body"></div>
|
|
260
118
|
</div>
|
|
@@ -263,10 +121,10 @@ export class PageMapGenerator {
|
|
|
263
121
|
<div class="modal-box">
|
|
264
122
|
<div class="modal-head">
|
|
265
123
|
<div style="display:flex;align-items:center;gap:8px">
|
|
266
|
-
<button id="modal-back" class="modal-back" onclick="modalBack()" style="display:none"
|
|
124
|
+
<button id="modal-back" class="modal-back" onclick="modalBack()" style="display:none">\u2190</button>
|
|
267
125
|
<h3 id="modal-title"></h3>
|
|
268
126
|
</div>
|
|
269
|
-
<button class="modal-close" onclick="closeModal()"
|
|
127
|
+
<button class="modal-close" onclick="closeModal()">\xD7</button>
|
|
270
128
|
</div>
|
|
271
129
|
<div class="modal-body" id="modal-body"></div>
|
|
272
130
|
</div>
|
|
@@ -275,16 +133,16 @@ export class PageMapGenerator {
|
|
|
275
133
|
<script>
|
|
276
134
|
// Environment detection results
|
|
277
135
|
const envInfo = {
|
|
278
|
-
hasRails: ${
|
|
279
|
-
hasNextjs: ${
|
|
280
|
-
hasReact: ${
|
|
136
|
+
hasRails: ${n},
|
|
137
|
+
hasNextjs: ${w},
|
|
138
|
+
hasReact: ${C}
|
|
281
139
|
};
|
|
282
140
|
|
|
283
141
|
// Frontend data
|
|
284
|
-
const pages = ${JSON.stringify(
|
|
285
|
-
const relations = ${JSON.stringify(
|
|
286
|
-
const graphqlOps = ${
|
|
287
|
-
const components = ${
|
|
142
|
+
const pages = ${JSON.stringify(s)};
|
|
143
|
+
const relations = ${JSON.stringify(c)};
|
|
144
|
+
const graphqlOps = ${o};
|
|
145
|
+
const components = ${p};
|
|
288
146
|
const apiCallsData = ${JSON.stringify(this.apiCalls)};
|
|
289
147
|
window.apiCalls = apiCallsData;
|
|
290
148
|
const pageMap = new Map(pages.map(p => [p.path, p]));
|
|
@@ -292,16 +150,16 @@ export class PageMapGenerator {
|
|
|
292
150
|
const compMap = new Map(components.map(c => [c.name, c]));
|
|
293
151
|
|
|
294
152
|
// Rails data (if available)
|
|
295
|
-
const railsRoutes = ${
|
|
296
|
-
const railsControllers = ${
|
|
297
|
-
const railsModels = ${
|
|
298
|
-
const railsViews = ${
|
|
299
|
-
const railsReact = ${
|
|
300
|
-
const railsGrpc = ${
|
|
301
|
-
const railsSummary = ${
|
|
153
|
+
const railsRoutes = ${f};
|
|
154
|
+
const railsControllers = ${a};
|
|
155
|
+
const railsModels = ${m};
|
|
156
|
+
const railsViews = ${u};
|
|
157
|
+
const railsReact = ${h};
|
|
158
|
+
const railsGrpc = ${y};
|
|
159
|
+
const railsSummary = ${P};
|
|
302
160
|
|
|
303
161
|
// Current active tab state
|
|
304
|
-
let currentMainTab = '${
|
|
162
|
+
let currentMainTab = '${i}';
|
|
305
163
|
|
|
306
164
|
// Modal history stack for back navigation
|
|
307
165
|
const modalHistory = [];
|
|
@@ -395,17 +253,17 @@ export class PageMapGenerator {
|
|
|
395
253
|
routesByNamespace.get(ns).push(r);
|
|
396
254
|
});
|
|
397
255
|
|
|
398
|
-
let html = '<div
|
|
256
|
+
let html = '<div class="max-h-60vh overflow-y-auto">';
|
|
399
257
|
for (const [ns, routes] of routesByNamespace) {
|
|
400
258
|
html += '<div style="margin-bottom:16px">';
|
|
401
|
-
html += '<div style="font-weight:600;margin-bottom:8px;color:var(--accent)"
|
|
259
|
+
html += '<div style="font-weight:600;margin-bottom:8px;color:var(--accent)">\u{1F4C2} ' + ns + ' (' + routes.length + ')</div>';
|
|
402
260
|
html += '<table style="width:100%;border-collapse:collapse;font-size:12px">';
|
|
403
|
-
html += '<tr
|
|
261
|
+
html += '<tr class="bg-surface"><th class="cell">Method</th><th class="cell">Path</th><th class="cell">Controller#Action</th></tr>';
|
|
404
262
|
routes.slice(0, 20).forEach(r => {
|
|
405
263
|
const methodColor = {GET:'#22c55e',POST:'#3b82f6',PUT:'#f59e0b',PATCH:'#f59e0b',DELETE:'#ef4444'}[r.method] || '#888';
|
|
406
264
|
html += '<tr style="border-bottom:1px solid var(--border)">';
|
|
407
265
|
html += '<td style="padding:6px"><span style="background:' + methodColor + ';color:white;padding:2px 6px;border-radius:3px;font-size:10px">' + r.method + '</span></td>';
|
|
408
|
-
html += '<td style="padding:6px;font-family:monospace">' + r.path.replace(/:([a-z_]+)/g, '<span
|
|
266
|
+
html += '<td style="padding:6px;font-family:monospace">' + r.path.replace(/:([a-z_]+)/g, '<span class="text-warning">:$1</span>') + '</td>';
|
|
409
267
|
html += '<td style="padding:6px;color:var(--text2)">' + r.controller + '#' + r.action + '</td>';
|
|
410
268
|
html += '</tr>';
|
|
411
269
|
});
|
|
@@ -416,7 +274,7 @@ export class PageMapGenerator {
|
|
|
416
274
|
}
|
|
417
275
|
html += '</div>';
|
|
418
276
|
|
|
419
|
-
showModal('
|
|
277
|
+
showModal('\u{1F6E4}\uFE0F Rails Routes (' + railsRoutes.length + ')', html);
|
|
420
278
|
}
|
|
421
279
|
|
|
422
280
|
function showRailsControllers() {
|
|
@@ -425,13 +283,13 @@ export class PageMapGenerator {
|
|
|
425
283
|
return;
|
|
426
284
|
}
|
|
427
285
|
|
|
428
|
-
let html = '<div
|
|
286
|
+
let html = '<div class="max-h-60vh overflow-y-auto">';
|
|
429
287
|
railsControllers.forEach(ctrl => {
|
|
430
|
-
html += '<div
|
|
431
|
-
html += '<div
|
|
432
|
-
html += '<div
|
|
288
|
+
html += '<div class="info-box">';
|
|
289
|
+
html += '<div class="section-title">' + ctrl.className + '</div>';
|
|
290
|
+
html += '<div class="hint mb-3">extends ' + ctrl.parentClass + '</div>';
|
|
433
291
|
if (ctrl.actions && ctrl.actions.length > 0) {
|
|
434
|
-
html += '<div
|
|
292
|
+
html += '<div class="flex flex-wrap gap-1">';
|
|
435
293
|
ctrl.actions.slice(0, 10).forEach(action => {
|
|
436
294
|
const color = action.visibility === 'public' ? '#22c55e' : action.visibility === 'private' ? '#ef4444' : '#f59e0b';
|
|
437
295
|
html += '<span style="background:rgba(255,255,255,0.1);padding:2px 8px;border-radius:4px;font-size:11px;border-left:2px solid ' + color + '">' + action.name + '</span>';
|
|
@@ -443,7 +301,7 @@ export class PageMapGenerator {
|
|
|
443
301
|
});
|
|
444
302
|
html += '</div>';
|
|
445
303
|
|
|
446
|
-
showModal('
|
|
304
|
+
showModal('\u{1F3AE} Rails Controllers (' + railsControllers.length + ')', html);
|
|
447
305
|
}
|
|
448
306
|
|
|
449
307
|
function showRailsModels() {
|
|
@@ -452,16 +310,16 @@ export class PageMapGenerator {
|
|
|
452
310
|
return;
|
|
453
311
|
}
|
|
454
312
|
|
|
455
|
-
let html = '<div
|
|
313
|
+
let html = '<div class="max-h-60vh overflow-y-auto">';
|
|
456
314
|
railsModels.forEach(model => {
|
|
457
|
-
html += '<div
|
|
458
|
-
html += '<div
|
|
459
|
-
html += '<div
|
|
460
|
-
html += '<span
|
|
461
|
-
html += '<span
|
|
315
|
+
html += '<div class="info-box">';
|
|
316
|
+
html += '<div class="section-title">\u{1F4E6} ' + model.className + '</div>';
|
|
317
|
+
html += '<div class="flex gap-3 hint mb-3">';
|
|
318
|
+
html += '<span>\u{1F4CE} ' + (model.associations?.length || 0) + ' associations</span>';
|
|
319
|
+
html += '<span>\u2713 ' + (model.validations?.length || 0) + ' validations</span>';
|
|
462
320
|
html += '</div>';
|
|
463
321
|
if (model.associations && model.associations.length > 0) {
|
|
464
|
-
html += '<div
|
|
322
|
+
html += '<div class="flex flex-wrap gap-1">';
|
|
465
323
|
model.associations.slice(0, 8).forEach(assoc => {
|
|
466
324
|
const typeColor = {belongs_to:'#3b82f6',has_many:'#22c55e',has_one:'#f59e0b'}[assoc.type] || '#888';
|
|
467
325
|
html += '<span style="background:rgba(255,255,255,0.1);padding:2px 8px;border-radius:4px;font-size:10px"><span style="color:' + typeColor + '">' + assoc.type + '</span> :' + assoc.name + '</span>';
|
|
@@ -473,7 +331,7 @@ export class PageMapGenerator {
|
|
|
473
331
|
});
|
|
474
332
|
html += '</div>';
|
|
475
333
|
|
|
476
|
-
showModal('
|
|
334
|
+
showModal('\u{1F4E6} Rails Models (' + railsModels.length + ')', html);
|
|
477
335
|
}
|
|
478
336
|
|
|
479
337
|
function showReactComponents() {
|
|
@@ -487,34 +345,34 @@ export class PageMapGenerator {
|
|
|
487
345
|
(b.usedIn?.length || 0) - (a.usedIn?.length || 0)
|
|
488
346
|
);
|
|
489
347
|
|
|
490
|
-
let html = '<div
|
|
348
|
+
let html = '<div class="max-h-60vh overflow-y-auto">';
|
|
491
349
|
|
|
492
350
|
// Stats
|
|
493
351
|
html += '<div style="display:flex;gap:16px;margin-bottom:16px;padding:12px;background:var(--bg3);border-radius:8px">';
|
|
494
|
-
html += '<div
|
|
495
|
-
html += '<div
|
|
496
|
-
html += '<div
|
|
497
|
-
html += '<div
|
|
352
|
+
html += '<div class="text-center"><div style="font-size:20px;font-weight:bold;color:var(--accent)">' + railsReact.summary.totalComponents + '</div><div style="font-size:10px;color:var(--text2)">Components</div></div>';
|
|
353
|
+
html += '<div class="text-center"><div style="font-size:20px;font-weight:bold;color:#22c55e">' + railsReact.summary.ssrComponents + '</div><div style="font-size:10px;color:var(--text2)">SSR</div></div>';
|
|
354
|
+
html += '<div class="text-center"><div style="font-size:20px;font-weight:bold;color:#3b82f6">' + railsReact.summary.clientComponents + '</div><div style="font-size:10px;color:var(--text2)">Client</div></div>';
|
|
355
|
+
html += '<div class="text-center"><div style="font-size:20px;font-weight:bold;color:#f59e0b">' + railsReact.summary.totalEntryPoints + '</div><div style="font-size:10px;color:var(--text2)">Entry Points</div></div>';
|
|
498
356
|
html += '</div>';
|
|
499
357
|
|
|
500
358
|
sortedComponents.forEach(comp => {
|
|
501
359
|
const usageCount = comp.usedIn?.length || 0;
|
|
502
|
-
const ssrBadge = comp.ssr ? '<span
|
|
360
|
+
const ssrBadge = comp.ssr ? '<span class="badge-success">SSR</span>' : '';
|
|
503
361
|
|
|
504
362
|
html += '<div style="background:var(--bg3);padding:12px;border-radius:6px;margin-bottom:8px;cursor:pointer" onclick="showReactComponentDetail(\\'' + encodeURIComponent(JSON.stringify(comp)) + '\\')">';
|
|
505
363
|
html += '<div style="display:flex;align-items:center;justify-content:space-between">';
|
|
506
|
-
html += '<div style="font-weight:600;display:flex;align-items:center"><span
|
|
507
|
-
html += '<span
|
|
364
|
+
html += '<div style="font-weight:600;display:flex;align-items:center"><span class="text-react mr-2">\u269B</span>' + comp.name + ssrBadge + '</div>';
|
|
365
|
+
html += '<span class="hint">' + usageCount + ' usage' + (usageCount !== 1 ? 's' : '') + '</span>';
|
|
508
366
|
html += '</div>';
|
|
509
367
|
|
|
510
368
|
// Entry point info
|
|
511
369
|
if (comp.entryFile) {
|
|
512
|
-
html += '<div style="font-size:10px;color:var(--text2);margin-top:4px;font-family:monospace"
|
|
370
|
+
html += '<div style="font-size:10px;color:var(--text2);margin-top:4px;font-family:monospace">\u{1F4E5} entries/' + comp.entryFile + '</div>';
|
|
513
371
|
}
|
|
514
372
|
|
|
515
373
|
// Source file info
|
|
516
374
|
if (comp.sourceFile) {
|
|
517
|
-
html += '<div style="font-size:10px;color:var(--accent);margin-top:2px;font-family:monospace"
|
|
375
|
+
html += '<div style="font-size:10px;color:var(--accent);margin-top:2px;font-family:monospace">\u{1F4C4} ' + comp.sourceFile + '</div>';
|
|
518
376
|
}
|
|
519
377
|
|
|
520
378
|
// Usage preview
|
|
@@ -525,7 +383,7 @@ export class PageMapGenerator {
|
|
|
525
383
|
html += '<span style="background:rgba(255,255,255,0.1);padding:2px 6px;border-radius:3px;font-size:10px;border-left:2px solid ' + patternColor + '">' + usage.controller + '/' + usage.action + '</span>';
|
|
526
384
|
});
|
|
527
385
|
if (comp.usedIn.length > 3) {
|
|
528
|
-
html += '<span
|
|
386
|
+
html += '<span class="hint-sm">+' + (comp.usedIn.length - 3) + ' more</span>';
|
|
529
387
|
}
|
|
530
388
|
html += '</div>';
|
|
531
389
|
}
|
|
@@ -534,7 +392,7 @@ export class PageMapGenerator {
|
|
|
534
392
|
});
|
|
535
393
|
|
|
536
394
|
html += '</div>';
|
|
537
|
-
showModal('
|
|
395
|
+
showModal('\u269B React Components (' + railsReact.components.length + ')', html);
|
|
538
396
|
}
|
|
539
397
|
|
|
540
398
|
function showReactComponentDetail(encodedData) {
|
|
@@ -544,7 +402,7 @@ export class PageMapGenerator {
|
|
|
544
402
|
|
|
545
403
|
// Component Info
|
|
546
404
|
html += '<div class="detail-section">';
|
|
547
|
-
html += '<div class="detail-label"
|
|
405
|
+
html += '<div class="detail-label">\u269B Component Name</div>';
|
|
548
406
|
html += '<div style="display:flex;align-items:center;gap:8px">';
|
|
549
407
|
html += '<span style="font-family:monospace;font-size:16px;font-weight:600">' + comp.name + '</span>';
|
|
550
408
|
if (comp.ssr) {
|
|
@@ -557,7 +415,7 @@ export class PageMapGenerator {
|
|
|
557
415
|
// Entry Point
|
|
558
416
|
if (comp.entryFile) {
|
|
559
417
|
html += '<div class="detail-section">';
|
|
560
|
-
html += '<div class="detail-label"
|
|
418
|
+
html += '<div class="detail-label">\u{1F4E5} Entry Point</div>';
|
|
561
419
|
html += '<div class="code-path">';
|
|
562
420
|
html += comp.entryFile;
|
|
563
421
|
html += '</div></div>';
|
|
@@ -566,7 +424,7 @@ export class PageMapGenerator {
|
|
|
566
424
|
// Source File
|
|
567
425
|
if (comp.sourceFile || comp.importPath) {
|
|
568
426
|
html += '<div class="detail-section">';
|
|
569
|
-
html += '<div class="detail-label"
|
|
427
|
+
html += '<div class="detail-label">\u{1F4C4} Source File</div>';
|
|
570
428
|
html += '<div class="code-path" style="color:var(--accent)">';
|
|
571
429
|
html += comp.sourceFile || comp.importPath;
|
|
572
430
|
html += '</div></div>';
|
|
@@ -575,7 +433,7 @@ export class PageMapGenerator {
|
|
|
575
433
|
// Usage in Views
|
|
576
434
|
if (comp.usedIn && comp.usedIn.length > 0) {
|
|
577
435
|
html += '<div class="detail-section">';
|
|
578
|
-
html += '<div class="detail-label"
|
|
436
|
+
html += '<div class="detail-label">\u{1F4CD} Used in Views (' + comp.usedIn.length + ')</div>';
|
|
579
437
|
html += '<div class="detail-items">';
|
|
580
438
|
|
|
581
439
|
comp.usedIn.forEach(usage => {
|
|
@@ -600,7 +458,7 @@ export class PageMapGenerator {
|
|
|
600
458
|
html += '</div></div>';
|
|
601
459
|
}
|
|
602
460
|
|
|
603
|
-
showModal('
|
|
461
|
+
showModal('\u269B ' + comp.name, html, true);
|
|
604
462
|
}
|
|
605
463
|
|
|
606
464
|
function showRailsGrpc() {
|
|
@@ -609,17 +467,17 @@ export class PageMapGenerator {
|
|
|
609
467
|
return;
|
|
610
468
|
}
|
|
611
469
|
|
|
612
|
-
let html = '<div
|
|
470
|
+
let html = '<div class="max-h-60vh overflow-y-auto">';
|
|
613
471
|
railsGrpc.services.forEach(svc => {
|
|
614
|
-
html += '<div
|
|
615
|
-
html += '<div
|
|
472
|
+
html += '<div class="info-box">';
|
|
473
|
+
html += '<div class="section-title">\u{1F50C} ' + svc.className + '</div>';
|
|
616
474
|
if (svc.namespace) {
|
|
617
|
-
html += '<div
|
|
475
|
+
html += '<div class="hint mb-3">namespace: ' + svc.namespace + '</div>';
|
|
618
476
|
}
|
|
619
477
|
if (svc.rpcs && svc.rpcs.length > 0) {
|
|
620
|
-
html += '<div
|
|
478
|
+
html += '<div class="flex flex-wrap gap-1">';
|
|
621
479
|
svc.rpcs.slice(0, 15).forEach(rpc => {
|
|
622
|
-
html += '<span class="tag
|
|
480
|
+
html += '<span class="tag tag-rpc tag-sm">' + rpc.name + '</span>';
|
|
623
481
|
});
|
|
624
482
|
if (svc.rpcs.length > 15) html += '<span style="color:var(--text2);font-size:11px">+' + (svc.rpcs.length - 15) + ' more</span>';
|
|
625
483
|
html += '</div>';
|
|
@@ -628,7 +486,7 @@ export class PageMapGenerator {
|
|
|
628
486
|
});
|
|
629
487
|
html += '</div>';
|
|
630
488
|
|
|
631
|
-
showModal('
|
|
489
|
+
showModal('\u{1F50C} gRPC Services (' + railsGrpc.services.length + ')', html);
|
|
632
490
|
}
|
|
633
491
|
|
|
634
492
|
// Render Rails routes in tree view
|
|
@@ -641,7 +499,7 @@ export class PageMapGenerator {
|
|
|
641
499
|
const routes = railsRoutes || [];
|
|
642
500
|
|
|
643
501
|
if (pages.length === 0 && routes.length === 0) {
|
|
644
|
-
container.innerHTML = '<div
|
|
502
|
+
container.innerHTML = '<div class="empty-state">No Rails pages or routes found</div>';
|
|
645
503
|
return;
|
|
646
504
|
}
|
|
647
505
|
|
|
@@ -674,7 +532,7 @@ export class PageMapGenerator {
|
|
|
674
532
|
controllerInfo = railsControllers.find(c => {
|
|
675
533
|
// Strategy 1: Match by filePath (most accurate)
|
|
676
534
|
// filePath: "api/v1/users_controller.rb" or "users_controller.rb"
|
|
677
|
-
const filePathNormalized = c.filePath.replace(/_controller
|
|
535
|
+
const filePathNormalized = c.filePath.replace(/_controller.rb$/, '').replace(/_/g, '');
|
|
678
536
|
if (filePathNormalized === routeCtrl.replace(/_/g, '')) return true;
|
|
679
537
|
|
|
680
538
|
// Strategy 2: Match by controller name (without namespace)
|
|
@@ -771,7 +629,7 @@ export class PageMapGenerator {
|
|
|
771
629
|
const coverageTooltip = 'Percentage of routes successfully matched with controller actions to extract details (JSON/HTML rendering, redirects, etc). This is a tool analysis metric, not a code quality indicator.';
|
|
772
630
|
const coverageClass = coverage > 70 ? 'coverage-high' : coverage > 40 ? 'coverage-mid' : 'coverage-low';
|
|
773
631
|
html += '<div class="coverage-info">';
|
|
774
|
-
html += '<div class="coverage-text" title="' + coverageTooltip + '">Action Details Coverage: <span class="' + coverageClass + '">' + coverage + '%</span> (' + totalWithActionInfo + '/' + combinedData.length + ' routes analyzed)
|
|
632
|
+
html += '<div class="coverage-text" title="' + coverageTooltip + '">Action Details Coverage: <span class="' + coverageClass + '">' + coverage + '%</span> (' + totalWithActionInfo + '/' + combinedData.length + ' routes analyzed) \u2139\uFE0F</div>';
|
|
775
633
|
html += '</div>';
|
|
776
634
|
}
|
|
777
635
|
html += '</div>';
|
|
@@ -782,8 +640,8 @@ export class PageMapGenerator {
|
|
|
782
640
|
|
|
783
641
|
html += '<div class="group">';
|
|
784
642
|
html += '<div class="group-header" onclick="toggleGroup(this)" style="border-left-color:' + color + '">';
|
|
785
|
-
html += '<span class="group-toggle"
|
|
786
|
-
html += '<span class="group-name"
|
|
643
|
+
html += '<span class="group-toggle">\u25BC</span>';
|
|
644
|
+
html += '<span class="group-name">\u{1F4C2} ' + ns + '</span>';
|
|
787
645
|
html += '<span class="group-count">' + routes.length + '</span>';
|
|
788
646
|
html += '</div>';
|
|
789
647
|
const routeListId = 'routes-' + ns.replace(/[^a-zA-Z0-9]/g, '-');
|
|
@@ -803,12 +661,12 @@ export class PageMapGenerator {
|
|
|
803
661
|
if (displayPath.includes('=>') || displayPath.includes('redirect')) {
|
|
804
662
|
// Extract just the route pattern before any redirect logic
|
|
805
663
|
const match = displayPath.match(/^([^"]+)"/);
|
|
806
|
-
displayPath = match ? match[1].trim() + '
|
|
664
|
+
displayPath = match ? match[1].trim() + ' \u2192 redirect' : displayPath.slice(0, 60) + '...';
|
|
807
665
|
}
|
|
808
666
|
if (displayPath.length > 80) {
|
|
809
667
|
displayPath = displayPath.slice(0, 77) + '...';
|
|
810
668
|
}
|
|
811
|
-
const pathHighlighted = displayPath.replace(/:([a-z_]+)/g, '<span
|
|
669
|
+
const pathHighlighted = displayPath.replace(/:([a-z_]+)/g, '<span class="text-warning">:$1</span>');
|
|
812
670
|
|
|
813
671
|
// Indicators for view, API, and response types
|
|
814
672
|
let indicators = '';
|
|
@@ -817,7 +675,7 @@ export class PageMapGenerator {
|
|
|
817
675
|
if (action) {
|
|
818
676
|
if (action.rendersJson) indicators += '<span class="route-tag route-tag-json" title="Returns JSON">JSON</span>';
|
|
819
677
|
if (action.rendersHtml && !action.rendersJson) indicators += '<span class="route-tag route-tag-html" title="Returns HTML">HTML</span>';
|
|
820
|
-
if (action.redirectsTo) indicators += '<span class="route-tag route-tag-redirect" title="Redirects"
|
|
678
|
+
if (action.redirectsTo) indicators += '<span class="route-tag route-tag-redirect" title="Redirects">\u2192</span>';
|
|
821
679
|
}
|
|
822
680
|
if (route.hasView) indicators += '<span class="route-tag route-tag-view" title="Has View Template">View</span>';
|
|
823
681
|
if (route.services.length > 0) indicators += '<span class="route-tag route-tag-svc" title="Uses Services: ' + route.services.join(', ') + '">Svc</span>';
|
|
@@ -838,7 +696,7 @@ export class PageMapGenerator {
|
|
|
838
696
|
|
|
839
697
|
html += '<div class="page-item rails-route-item" data-path="' + searchPath + '"' + hiddenAttr + ' ' + filterAttrs.join(' ') + ' onclick="showRailsRouteDetail(\\''+encodeURIComponent(JSON.stringify(route))+'\\', true)" style="cursor:pointer;' + hiddenStyle + '">';
|
|
840
698
|
html += '<span class="page-type" style="background:' + methodColor + ';min-width:50px;text-align:center">' + route.method + '</span>';
|
|
841
|
-
html += '<span class="page-path"
|
|
699
|
+
html += '<span class="page-path">' + pathHighlighted + '</span>';
|
|
842
700
|
html += indicators;
|
|
843
701
|
html += '</div>';
|
|
844
702
|
});
|
|
@@ -846,7 +704,7 @@ export class PageMapGenerator {
|
|
|
846
704
|
html += '</div>';
|
|
847
705
|
|
|
848
706
|
if (hasMoreRoutes) {
|
|
849
|
-
html += '<div id="' + routeListId + '-more" style="padding:8px 12px;cursor:pointer;color:var(--accent);font-size:11px" onclick="toggleMoreItems(\\'' + routeListId + '\\', ' + routes.length + ')"
|
|
707
|
+
html += '<div id="' + routeListId + '-more" style="padding:8px 12px;cursor:pointer;color:var(--accent);font-size:11px" onclick="toggleMoreItems(\\'' + routeListId + '\\', ' + routes.length + ')">\u25BC Show ' + (routes.length - routeLimit) + ' more routes</div>';
|
|
850
708
|
}
|
|
851
709
|
|
|
852
710
|
html += '</div>';
|
|
@@ -924,7 +782,7 @@ export class PageMapGenerator {
|
|
|
924
782
|
|
|
925
783
|
html += '<div class="detail-section">';
|
|
926
784
|
html += '<div class="detail-label">Path</div>';
|
|
927
|
-
html += '<div class="detail-value" style="font-family:monospace">' + route.path.replace(/:([a-z_]+)/g, '<span
|
|
785
|
+
html += '<div class="detail-value" style="font-family:monospace">' + route.path.replace(/:([a-z_]+)/g, '<span class="text-warning">:$1</span>') + '</div>';
|
|
928
786
|
html += '</div>';
|
|
929
787
|
|
|
930
788
|
html += '<div class="detail-section">';
|
|
@@ -935,7 +793,7 @@ export class PageMapGenerator {
|
|
|
935
793
|
// Response Type - NEW
|
|
936
794
|
if (action) {
|
|
937
795
|
html += '<div class="detail-section">';
|
|
938
|
-
html += '<div class="detail-label"
|
|
796
|
+
html += '<div class="detail-label">\u{1F4E1} Response Type</div>';
|
|
939
797
|
html += '<div class="detail-value">';
|
|
940
798
|
const responseTypes = [];
|
|
941
799
|
if (action.rendersJson) responseTypes.push('<span style="background:#3b82f6;color:white;padding:2px 8px;border-radius:4px;font-size:11px;margin-right:4px">JSON</span>');
|
|
@@ -946,14 +804,14 @@ export class PageMapGenerator {
|
|
|
946
804
|
responseTypes.push('<span style="background:#8b5cf6;color:white;padding:2px 8px;border-radius:4px;font-size:11px;margin-right:4px">' + f.toUpperCase() + '</span>');
|
|
947
805
|
});
|
|
948
806
|
}
|
|
949
|
-
html += responseTypes.length > 0 ? responseTypes.join('') : '<span
|
|
807
|
+
html += responseTypes.length > 0 ? responseTypes.join('') : '<span class="text-muted">Unknown</span>';
|
|
950
808
|
html += '</div></div>';
|
|
951
809
|
|
|
952
810
|
// Redirect destination if exists
|
|
953
811
|
if (action.redirectsTo) {
|
|
954
812
|
html += '<div class="detail-section">';
|
|
955
|
-
html += '<div class="detail-label"
|
|
956
|
-
html += '<div class="detail-value
|
|
813
|
+
html += '<div class="detail-label">\u21AA\uFE0F Redirects To</div>';
|
|
814
|
+
html += '<div class="detail-value-block">' + action.redirectsTo + '</div>';
|
|
957
815
|
html += '</div>';
|
|
958
816
|
}
|
|
959
817
|
}
|
|
@@ -961,10 +819,10 @@ export class PageMapGenerator {
|
|
|
961
819
|
// View info
|
|
962
820
|
if (route.hasView && route.view) {
|
|
963
821
|
html += '<div class="detail-section">';
|
|
964
|
-
html += '<div class="detail-label"
|
|
965
|
-
html += '<div class="detail-value"
|
|
822
|
+
html += '<div class="detail-label">\u{1F4C4} View Template</div>';
|
|
823
|
+
html += '<div class="detail-value">app/views/' + route.view.path + '</div>';
|
|
966
824
|
if (route.view.partials && route.view.partials.length > 0) {
|
|
967
|
-
html += '<div
|
|
825
|
+
html += '<div class="subtext">Partials: ' + route.view.partials.slice(0, 5).join(', ') + (route.view.partials.length > 5 ? '...' : '') + '</div>';
|
|
968
826
|
}
|
|
969
827
|
if (route.view.instanceVars && route.view.instanceVars.length > 0) {
|
|
970
828
|
html += '<div style="margin-top:4px;font-size:11px;color:var(--text2)">Instance vars: @' + route.view.instanceVars.slice(0, 5).join(', @') + (route.view.instanceVars.length > 5 ? '...' : '') + '</div>';
|
|
@@ -975,8 +833,8 @@ export class PageMapGenerator {
|
|
|
975
833
|
// Before/After Filters - NEW
|
|
976
834
|
if (ctrl && (ctrl.beforeActions.length > 0 || ctrl.afterActions.length > 0)) {
|
|
977
835
|
html += '<div class="detail-section">';
|
|
978
|
-
html += '<div class="detail-label"
|
|
979
|
-
html += '<div
|
|
836
|
+
html += '<div class="detail-label">\u{1F512} Filters Applied to This Action</div>';
|
|
837
|
+
html += '<div class="code-block">';
|
|
980
838
|
|
|
981
839
|
// Filter before_actions that apply to this action
|
|
982
840
|
const applicableBeforeFilters = ctrl.beforeActions.filter(f => {
|
|
@@ -989,7 +847,7 @@ export class PageMapGenerator {
|
|
|
989
847
|
html += '<div style="font-size:11px;margin-bottom:6px"><span style="color:#22c55e;font-weight:600">Before:</span></div>';
|
|
990
848
|
html += '<div class="detail-items" style="margin-left:8px">';
|
|
991
849
|
applicableBeforeFilters.forEach(f => {
|
|
992
|
-
let filterInfo = '<span class="tag
|
|
850
|
+
let filterInfo = '<span class="tag tag-before tag-sm">before</span><span class="name">' + f.name + '</span>';
|
|
993
851
|
if (f.if) filterInfo += '<span style="font-size:10px;color:var(--text2);margin-left:4px">if: ' + f.if + '</span>';
|
|
994
852
|
if (f.unless) filterInfo += '<span style="font-size:10px;color:var(--text2);margin-left:4px">unless: ' + f.unless + '</span>';
|
|
995
853
|
html += '<div class="detail-item">' + filterInfo + '</div>';
|
|
@@ -1007,7 +865,7 @@ export class PageMapGenerator {
|
|
|
1007
865
|
html += '<div style="font-size:11px;margin-top:8px;margin-bottom:6px"><span style="color:#f59e0b;font-weight:600">After:</span></div>';
|
|
1008
866
|
html += '<div class="detail-items" style="margin-left:8px">';
|
|
1009
867
|
applicableAfterFilters.forEach(f => {
|
|
1010
|
-
let filterInfo = '<span class="tag
|
|
868
|
+
let filterInfo = '<span class="tag tag-after tag-sm">after</span><span class="name">' + f.name + '</span>';
|
|
1011
869
|
html += '<div class="detail-item">' + filterInfo + '</div>';
|
|
1012
870
|
});
|
|
1013
871
|
html += '</div>';
|
|
@@ -1023,10 +881,10 @@ export class PageMapGenerator {
|
|
|
1023
881
|
const services = route.services && route.services.length > 0 ? route.services : (action?.servicesCalled || []);
|
|
1024
882
|
if (services.length > 0) {
|
|
1025
883
|
html += '<div class="detail-section">';
|
|
1026
|
-
html += '<div class="detail-label"
|
|
884
|
+
html += '<div class="detail-label">\u2699\uFE0F Services Called</div>';
|
|
1027
885
|
html += '<div class="detail-items">';
|
|
1028
886
|
services.forEach(s => {
|
|
1029
|
-
html += '<div class="detail-item"><span class="tag
|
|
887
|
+
html += '<div class="detail-item"><span class="tag tag-service">Service</span><span class="name">' + s + '</span></div>';
|
|
1030
888
|
});
|
|
1031
889
|
html += '</div></div>';
|
|
1032
890
|
}
|
|
@@ -1034,10 +892,10 @@ export class PageMapGenerator {
|
|
|
1034
892
|
// gRPC Calls
|
|
1035
893
|
if (route.grpcCalls && route.grpcCalls.length > 0) {
|
|
1036
894
|
html += '<div class="detail-section">';
|
|
1037
|
-
html += '<div class="detail-label"
|
|
895
|
+
html += '<div class="detail-label">\u{1F50C} gRPC Calls</div>';
|
|
1038
896
|
html += '<div class="detail-items">';
|
|
1039
897
|
route.grpcCalls.forEach(g => {
|
|
1040
|
-
html += '<div class="detail-item"><span class="tag
|
|
898
|
+
html += '<div class="detail-item"><span class="tag tag-grpc">gRPC</span><span class="name">' + g + '</span></div>';
|
|
1041
899
|
});
|
|
1042
900
|
html += '</div></div>';
|
|
1043
901
|
}
|
|
@@ -1046,10 +904,10 @@ export class PageMapGenerator {
|
|
|
1046
904
|
const models = route.modelAccess && route.modelAccess.length > 0 ? route.modelAccess : (action?.modelsCalled || []);
|
|
1047
905
|
if (models.length > 0) {
|
|
1048
906
|
html += '<div class="detail-section">';
|
|
1049
|
-
html += '<div class="detail-label"
|
|
907
|
+
html += '<div class="detail-label">\u{1F4BE} Models Accessed</div>';
|
|
1050
908
|
html += '<div class="detail-items">';
|
|
1051
909
|
models.forEach(m => {
|
|
1052
|
-
html += '<div class="detail-item"><span class="tag
|
|
910
|
+
html += '<div class="detail-item"><span class="tag tag-model">Model</span><span class="name">' + m + '</span></div>';
|
|
1053
911
|
});
|
|
1054
912
|
html += '</div></div>';
|
|
1055
913
|
}
|
|
@@ -1064,17 +922,17 @@ export class PageMapGenerator {
|
|
|
1064
922
|
|
|
1065
923
|
if (meaningfulCalls.length > 0) {
|
|
1066
924
|
html += '<div class="detail-section">';
|
|
1067
|
-
html += '<div class="detail-label"
|
|
925
|
+
html += '<div class="detail-label">\u{1F517} Method Calls in Action</div>';
|
|
1068
926
|
html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px;max-height:150px;overflow-y:auto">';
|
|
1069
927
|
html += '<div style="font-family:monospace;font-size:11px;line-height:1.6">';
|
|
1070
928
|
meaningfulCalls.forEach((call, i) => {
|
|
1071
|
-
html += '<div
|
|
929
|
+
html += '<div class="accordion-item">';
|
|
1072
930
|
html += '<span style="color:var(--text2);margin-right:8px">' + (i+1) + '.</span>';
|
|
1073
|
-
html += '<span
|
|
931
|
+
html += '<span class="text-accent">' + call + '</span>';
|
|
1074
932
|
html += '</div>';
|
|
1075
933
|
});
|
|
1076
934
|
if (action.methodCalls.length > 15) {
|
|
1077
|
-
html += '<div
|
|
935
|
+
html += '<div class="note">...and ' + (action.methodCalls.length - 15) + ' more calls</div>';
|
|
1078
936
|
}
|
|
1079
937
|
html += '</div></div></div>';
|
|
1080
938
|
}
|
|
@@ -1082,27 +940,27 @@ export class PageMapGenerator {
|
|
|
1082
940
|
|
|
1083
941
|
// Source Files - NEW
|
|
1084
942
|
html += '<div class="detail-section">';
|
|
1085
|
-
html += '<div class="detail-label"
|
|
943
|
+
html += '<div class="detail-label">\u{1F4C1} Source Files</div>';
|
|
1086
944
|
html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px;font-family:monospace;font-size:11px">';
|
|
1087
945
|
|
|
1088
946
|
if (route.line > 0) {
|
|
1089
|
-
html += '<div
|
|
1090
|
-
html += '<span
|
|
1091
|
-
html += '<span>config/routes.rb:<span
|
|
947
|
+
html += '<div class="detail-item flex items-center py-1">';
|
|
948
|
+
html += '<span class="text-muted w-20">Route:</span>';
|
|
949
|
+
html += '<span>config/routes.rb:<span class="text-success">' + route.line + '</span></span>';
|
|
1092
950
|
html += '</div>';
|
|
1093
951
|
}
|
|
1094
952
|
|
|
1095
953
|
if (ctrl) {
|
|
1096
|
-
html += '<div
|
|
1097
|
-
html += '<span
|
|
954
|
+
html += '<div class="detail-item flex items-center py-1">';
|
|
955
|
+
html += '<span class="text-muted w-20">Controller:</span>';
|
|
1098
956
|
html += '<span>app/controllers/' + ctrl.filePath;
|
|
1099
|
-
if (action && action.line) html += ':<span
|
|
957
|
+
if (action && action.line) html += ':<span class="text-success">' + action.line + '</span>';
|
|
1100
958
|
html += '</span></div>';
|
|
1101
959
|
}
|
|
1102
960
|
|
|
1103
961
|
if (route.hasView && route.view) {
|
|
1104
|
-
html += '<div
|
|
1105
|
-
html += '<span
|
|
962
|
+
html += '<div class="detail-item flex items-center py-1">';
|
|
963
|
+
html += '<span class="text-muted w-20">View:</span>';
|
|
1106
964
|
html += '<span>app/views/' + route.view.path + '</span>';
|
|
1107
965
|
html += '</div>';
|
|
1108
966
|
}
|
|
@@ -1111,13 +969,13 @@ export class PageMapGenerator {
|
|
|
1111
969
|
// Controller Info Summary
|
|
1112
970
|
if (ctrl) {
|
|
1113
971
|
html += '<div class="detail-section">';
|
|
1114
|
-
html += '<div class="detail-label"
|
|
1115
|
-
html += '<div
|
|
1116
|
-
html += '<div
|
|
972
|
+
html += '<div class="detail-label">\u{1F4CB} Controller Info</div>';
|
|
973
|
+
html += '<div class="code-block">';
|
|
974
|
+
html += '<div class="section-title">' + ctrl.className + '</div>';
|
|
1117
975
|
html += '<div style="font-size:11px;color:var(--text2)">extends ' + ctrl.parentClass + '</div>';
|
|
1118
976
|
if (ctrl.concerns && ctrl.concerns.length > 0) {
|
|
1119
977
|
html += '<div style="margin-top:6px;font-size:11px">';
|
|
1120
|
-
html += '<span
|
|
978
|
+
html += '<span class="text-muted">Concerns:</span> ' + ctrl.concerns.join(', ');
|
|
1121
979
|
html += '</div>';
|
|
1122
980
|
}
|
|
1123
981
|
html += '</div></div>';
|
|
@@ -1157,15 +1015,15 @@ export class PageMapGenerator {
|
|
|
1157
1015
|
const withUsageCount = components.filter(c => c.usedIn && c.usedIn.length > 0).length;
|
|
1158
1016
|
|
|
1159
1017
|
let html = '';
|
|
1160
|
-
html += '<div
|
|
1161
|
-
html += '<div style="font-weight:600;margin-bottom:8px;display:flex;align-items:center;gap:8px"><span style="color:#61dafb"
|
|
1018
|
+
html += '<div class="info-box mb-3">';
|
|
1019
|
+
html += '<div style="font-weight:600;margin-bottom:8px;display:flex;align-items:center;gap:8px"><span style="color:#61dafb">\u269B</span> React Components (from Rails)</div>';
|
|
1162
1020
|
html += '<div style="display:flex;gap:16px;flex-wrap:wrap;font-size:12px;color:var(--text2)">';
|
|
1163
1021
|
html += '<span>' + components.length + ' components</span>';
|
|
1164
|
-
html += '<span
|
|
1165
|
-
html += '<span
|
|
1166
|
-
html += '<span
|
|
1022
|
+
html += '<span>\u2022</span>';
|
|
1023
|
+
html += '<span class="text-success">' + ssrCount + ' SSR</span>';
|
|
1024
|
+
html += '<span>\u2022</span>';
|
|
1167
1025
|
html += '<span style="color:#3b82f6">' + (components.length - ssrCount) + ' client</span>';
|
|
1168
|
-
html += '<span
|
|
1026
|
+
html += '<span>\u2022</span>';
|
|
1169
1027
|
html += '<span style="color:#8b5cf6">' + withUsageCount + ' with usage</span>';
|
|
1170
1028
|
html += '</div></div>';
|
|
1171
1029
|
|
|
@@ -1178,8 +1036,8 @@ export class PageMapGenerator {
|
|
|
1178
1036
|
if (withEntry.length > 0) {
|
|
1179
1037
|
html += '<div class="group" data-group="react-with-entry">';
|
|
1180
1038
|
html += '<div class="group-header" onclick="toggleGroup(this)">';
|
|
1181
|
-
html += '<span class="group-toggle"
|
|
1182
|
-
html += '<span style="color:#61dafb;margin-right:4px"
|
|
1039
|
+
html += '<span class="group-toggle">\u25BC</span>';
|
|
1040
|
+
html += '<span style="color:#61dafb;margin-right:4px">\u{1F4E5}</span>';
|
|
1183
1041
|
html += '<span class="group-name">With Entry Points (' + withEntry.length + ')</span>';
|
|
1184
1042
|
html += '</div>';
|
|
1185
1043
|
html += '<div class="group-items">';
|
|
@@ -1187,9 +1045,9 @@ export class PageMapGenerator {
|
|
|
1187
1045
|
withEntry.forEach(comp => {
|
|
1188
1046
|
const usageCount = comp.usedIn?.length || 0;
|
|
1189
1047
|
const tags = [];
|
|
1190
|
-
if (comp.ssr) tags.push('<span class="tag
|
|
1191
|
-
if (usageCount > 0) tags.push('<span class="tag
|
|
1192
|
-
if (comp.sourceFile) tags.push('<span class="tag
|
|
1048
|
+
if (comp.ssr) tags.push('<span class="tag tag-ssr" title="Server-Side Rendering">SSR</span>');
|
|
1049
|
+
if (usageCount > 0) tags.push('<span class="tag tag-view" title="Used in ' + usageCount + ' view(s)">View:' + usageCount + '</span>');
|
|
1050
|
+
if (comp.sourceFile) tags.push('<span class="tag tag-src" title="Has source file">SRC</span>');
|
|
1193
1051
|
|
|
1194
1052
|
// Find URL from routes based on controller/action OR infer from entry file
|
|
1195
1053
|
let urlInfo = '';
|
|
@@ -1204,14 +1062,14 @@ export class PageMapGenerator {
|
|
|
1204
1062
|
urlInfo = '/' + usage.controller.replace(/_/g, '/') + '/' + usage.action;
|
|
1205
1063
|
}
|
|
1206
1064
|
} else if (comp.entryFile) {
|
|
1207
|
-
// Infer URL from entry file name (e.g., tickets.tsx
|
|
1065
|
+
// Infer URL from entry file name (e.g., tickets.tsx \u2192 /tickets)
|
|
1208
1066
|
const fileName = comp.entryFile.split('/').pop().replace(/\\.(tsx?|jsx?)$/, '');
|
|
1209
1067
|
urlInfo = '/' + fileName.replace(/_/g, '-');
|
|
1210
1068
|
}
|
|
1211
1069
|
|
|
1212
1070
|
html += '<div class="page-item" data-path="' + comp.name.toLowerCase() + '" onclick="showReactComponentDetail(\\'' + encodeURIComponent(JSON.stringify(comp)) + '\\')">';
|
|
1213
1071
|
html += '<div class="page-info">';
|
|
1214
|
-
html += '<span class="page-name" style="display:flex;align-items:center"><span
|
|
1072
|
+
html += '<span class="page-name" style="display:flex;align-items:center"><span class="text-react mr-2">\u269B</span>' + comp.name + '</span>';
|
|
1215
1073
|
html += '<span class="page-path" style="font-size:10px;color:var(--accent)">' + urlInfo + '</span>';
|
|
1216
1074
|
html += '</div>';
|
|
1217
1075
|
html += '<div class="page-tags">' + tags.join('') + '</div>';
|
|
@@ -1225,8 +1083,8 @@ export class PageMapGenerator {
|
|
|
1225
1083
|
if (withoutEntry.length > 0) {
|
|
1226
1084
|
html += '<div class="group" data-group="react-view-only">';
|
|
1227
1085
|
html += '<div class="group-header" onclick="toggleGroup(this)">';
|
|
1228
|
-
html += '<span class="group-toggle"
|
|
1229
|
-
html += '<span style="color:#f59e0b;margin-right:4px"
|
|
1086
|
+
html += '<span class="group-toggle">\u25BC</span>';
|
|
1087
|
+
html += '<span style="color:#f59e0b;margin-right:4px">\u{1F441}\uFE0F</span>';
|
|
1230
1088
|
html += '<span class="group-name">View-only Components (' + withoutEntry.length + ')</span>';
|
|
1231
1089
|
html += '</div>';
|
|
1232
1090
|
html += '<div class="group-items">';
|
|
@@ -1234,8 +1092,8 @@ export class PageMapGenerator {
|
|
|
1234
1092
|
withoutEntry.forEach(comp => {
|
|
1235
1093
|
const usageCount = comp.usedIn?.length || 0;
|
|
1236
1094
|
const tags = [];
|
|
1237
|
-
if (comp.ssr) tags.push('<span class="tag
|
|
1238
|
-
if (usageCount > 0) tags.push('<span class="tag
|
|
1095
|
+
if (comp.ssr) tags.push('<span class="tag tag-ssr" title="Server-Side Rendering">SSR</span>');
|
|
1096
|
+
if (usageCount > 0) tags.push('<span class="tag tag-view" title="Used in ' + usageCount + ' view(s)">View:' + usageCount + '</span>');
|
|
1239
1097
|
|
|
1240
1098
|
// Find URL from routes based on controller/action
|
|
1241
1099
|
let urlInfo = '';
|
|
@@ -1253,7 +1111,7 @@ export class PageMapGenerator {
|
|
|
1253
1111
|
|
|
1254
1112
|
html += '<div class="page-item" data-path="' + comp.name.toLowerCase() + '" onclick="showReactComponentDetail(\\'' + encodeURIComponent(JSON.stringify(comp)) + '\\')">';
|
|
1255
1113
|
html += '<div class="page-info">';
|
|
1256
|
-
html += '<span class="page-name" style="display:flex;align-items:center"><span
|
|
1114
|
+
html += '<span class="page-name" style="display:flex;align-items:center"><span class="text-react mr-2">\u269B</span>' + comp.name + '</span>';
|
|
1257
1115
|
html += '<span class="page-path" style="font-size:10px;color:var(--accent)">' + (urlInfo || 'View-only') + '</span>';
|
|
1258
1116
|
html += '</div>';
|
|
1259
1117
|
html += '<div class="page-tags">' + tags.join('') + '</div>';
|
|
@@ -1302,7 +1160,7 @@ export class PageMapGenerator {
|
|
|
1302
1160
|
const ctrlName = view.controller.split('/').pop().replace(/_/g, '');
|
|
1303
1161
|
const ctrl = railsControllers?.find(c => {
|
|
1304
1162
|
if (c.name === view.controller || c.name === ctrlName) return true;
|
|
1305
|
-
const filePathNormalized = c.filePath.replace(/_controller
|
|
1163
|
+
const filePathNormalized = c.filePath.replace(/_controller.rb$/, '').replace(/_/g, '');
|
|
1306
1164
|
return filePathNormalized === view.controller.replace(/_/g, '');
|
|
1307
1165
|
});
|
|
1308
1166
|
const action = ctrl?.actions?.find(a => a.name === view.action);
|
|
@@ -1363,15 +1221,15 @@ export class PageMapGenerator {
|
|
|
1363
1221
|
const totalWithPartials = enrichedViews.filter(v => v.partials.length > 0).length;
|
|
1364
1222
|
|
|
1365
1223
|
let html = '';
|
|
1366
|
-
html += '<div
|
|
1367
|
-
html += '<div style="font-weight:600;margin-bottom:8px"
|
|
1224
|
+
html += '<div class="info-box mb-3">';
|
|
1225
|
+
html += '<div style="font-weight:600;margin-bottom:8px">\u{1F5BC}\uFE0F Rails Screens (View Templates)</div>';
|
|
1368
1226
|
html += '<div style="display:flex;gap:16px;flex-wrap:wrap;font-size:12px;color:var(--text2)">';
|
|
1369
1227
|
html += '<span>' + enrichedViews.length + ' screens</span>';
|
|
1370
|
-
html += '<span
|
|
1228
|
+
html += '<span>\u2022</span>';
|
|
1371
1229
|
html += '<span>' + sortedControllers.length + ' sections</span>';
|
|
1372
|
-
html += '<span
|
|
1230
|
+
html += '<span>\u2022</span>';
|
|
1373
1231
|
html += '<span style="color:#8b5cf6">' + totalWithServices + ' with services</span>';
|
|
1374
|
-
html += '<span
|
|
1232
|
+
html += '<span>\u2022</span>';
|
|
1375
1233
|
html += '<span style="color:#06b6d4">' + totalWithPartials + ' with partials</span>';
|
|
1376
1234
|
html += '</div></div>';
|
|
1377
1235
|
|
|
@@ -1385,8 +1243,8 @@ export class PageMapGenerator {
|
|
|
1385
1243
|
|
|
1386
1244
|
html += '<div class="group">';
|
|
1387
1245
|
html += '<div class="group-header" onclick="toggleGroup(this)" style="border-left-color:' + color + '">';
|
|
1388
|
-
html += '<span class="group-toggle"
|
|
1389
|
-
html += '<span class="group-name"
|
|
1246
|
+
html += '<span class="group-toggle">\u25BC</span>';
|
|
1247
|
+
html += '<span class="group-name">\u{1F4C1} ' + ctrl + '</span>';
|
|
1390
1248
|
html += '<span class="group-count">' + controllerViews.length + ' screens</span>';
|
|
1391
1249
|
html += '</div>';
|
|
1392
1250
|
html += '<div class="group-items" id="' + screenListId + '">';
|
|
@@ -1400,17 +1258,17 @@ export class PageMapGenerator {
|
|
|
1400
1258
|
indicators += '<span class="route-tag route-tag-template" title="' + view.template.toUpperCase() + ' template">' + view.template.toUpperCase() + '</span>';
|
|
1401
1259
|
if (view.reactComponents && view.reactComponents.length > 0) {
|
|
1402
1260
|
const rcNames = view.reactComponents.map(rc => rc.name).slice(0, 2).join(', ') + (view.reactComponents.length > 2 ? '...' : '');
|
|
1403
|
-
indicators += '<span class="route-tag route-tag-react" title="React: ' + rcNames + '"
|
|
1261
|
+
indicators += '<span class="route-tag route-tag-react" title="React: ' + rcNames + '">\u269B ' + view.reactComponents.length + '</span>';
|
|
1404
1262
|
}
|
|
1405
|
-
if (view.partials.length > 0) indicators += '<span class="route-tag route-tag-partials" title="Uses partials: ' + view.partials.slice(0,3).join(', ') + (view.partials.length > 3 ? '...' : '') + '"
|
|
1406
|
-
if (view.instanceVars.length > 0) indicators += '<span class="route-tag route-tag-vars" title="Instance vars: @' + view.instanceVars.slice(0,5).join(', @') + '"
|
|
1263
|
+
if (view.partials.length > 0) indicators += '<span class="route-tag route-tag-partials" title="Uses partials: ' + view.partials.slice(0,3).join(', ') + (view.partials.length > 3 ? '...' : '') + '">\u{1F9E9} ' + view.partials.length + '</span>';
|
|
1264
|
+
if (view.instanceVars.length > 0) indicators += '<span class="route-tag route-tag-vars" title="Instance vars: @' + view.instanceVars.slice(0,5).join(', @') + '">\u{1F4E6} ' + view.instanceVars.length + '</span>';
|
|
1407
1265
|
if (view.services.length > 0) indicators += '<span class="route-tag route-tag-svc" title="Services: ' + view.services.join(', ') + '">Svc</span>';
|
|
1408
1266
|
if (view.grpcCalls.length > 0) indicators += '<span class="route-tag route-tag-grpc" title="gRPC: ' + view.grpcCalls.join(', ') + '">gRPC</span>';
|
|
1409
1267
|
if (view.modelAccess.length > 0) indicators += '<span class="route-tag route-tag-db" title="Models: ' + view.modelAccess.join(', ') + '">DB</span>';
|
|
1410
|
-
if (!view.hasRoute) indicators += '<span class="route-tag route-tag-warn" title="No matching route found"
|
|
1268
|
+
if (!view.hasRoute) indicators += '<span class="route-tag route-tag-warn" title="No matching route found">\u26A0\uFE0F</span>';
|
|
1411
1269
|
|
|
1412
1270
|
// Display: URL path (if route exists) or controller/action
|
|
1413
|
-
const displayName = view.hasRoute ? view.path.replace(/:([a-z_]+)/g, '<span
|
|
1271
|
+
const displayName = view.hasRoute ? view.path.replace(/:([a-z_]+)/g, '<span class="text-warning">:$1</span>') : view.controller + '#' + view.action;
|
|
1414
1272
|
|
|
1415
1273
|
// Search-friendly data-path includes path, controller, action
|
|
1416
1274
|
const searchPath = [view.path || '', view.controller || '', view.action || '', view.viewPath || ''].join(' ').toLowerCase();
|
|
@@ -1428,7 +1286,7 @@ export class PageMapGenerator {
|
|
|
1428
1286
|
|
|
1429
1287
|
if (hasMoreScreens) {
|
|
1430
1288
|
html += '<div id="' + screenListId + '-more" style="padding:8px 12px;cursor:pointer;color:var(--accent);font-size:11px" onclick="toggleMoreItems(\\'' + screenListId + '\\', ' + controllerViews.length + ')">';
|
|
1431
|
-
html += '
|
|
1289
|
+
html += '\u25BC Show ' + (controllerViews.length - screenLimit) + ' more screens';
|
|
1432
1290
|
html += '</div>';
|
|
1433
1291
|
}
|
|
1434
1292
|
|
|
@@ -1446,9 +1304,9 @@ export class PageMapGenerator {
|
|
|
1446
1304
|
|
|
1447
1305
|
// URL/Route info
|
|
1448
1306
|
html += '<div class="detail-section">';
|
|
1449
|
-
html += '<div class="detail-label"
|
|
1307
|
+
html += '<div class="detail-label">\u{1F310} URL Path</div>';
|
|
1450
1308
|
if (screen.hasRoute) {
|
|
1451
|
-
html += '<div class="detail-value" style="font-family:monospace">' + screen.path.replace(/:([a-z_]+)/g, '<span
|
|
1309
|
+
html += '<div class="detail-value" style="font-family:monospace">' + screen.path.replace(/:([a-z_]+)/g, '<span class="text-warning">:$1</span>') + '</div>';
|
|
1452
1310
|
} else {
|
|
1453
1311
|
html += '<div class="detail-value" style="color:var(--text2)">No route defined (orphan view)</div>';
|
|
1454
1312
|
}
|
|
@@ -1456,7 +1314,7 @@ export class PageMapGenerator {
|
|
|
1456
1314
|
|
|
1457
1315
|
// View Template info
|
|
1458
1316
|
html += '<div class="detail-section">';
|
|
1459
|
-
html += '<div class="detail-label"
|
|
1317
|
+
html += '<div class="detail-label">\u{1F4C4} View Template</div>';
|
|
1460
1318
|
html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px;font-family:monospace;font-size:12px">';
|
|
1461
1319
|
html += '<div style="color:var(--accent)">app/views/' + screen.viewPath + '</div>';
|
|
1462
1320
|
html += '<div style="margin-top:6px;display:flex;gap:8px">';
|
|
@@ -1466,7 +1324,7 @@ export class PageMapGenerator {
|
|
|
1466
1324
|
// Instance Variables (data passed to view) with type info
|
|
1467
1325
|
if (screen.instanceVars && screen.instanceVars.length > 0) {
|
|
1468
1326
|
html += '<div class="detail-section">';
|
|
1469
|
-
html += '<div class="detail-label"
|
|
1327
|
+
html += '<div class="detail-label">\u{1F4E6} Data Available in View (@variables)</div>';
|
|
1470
1328
|
|
|
1471
1329
|
// Build assignment map from controller analysis
|
|
1472
1330
|
const assignmentMap = {};
|
|
@@ -1487,16 +1345,16 @@ export class PageMapGenerator {
|
|
|
1487
1345
|
|
|
1488
1346
|
// Function to find matching model for a variable name
|
|
1489
1347
|
function findModelForVar(varName) {
|
|
1490
|
-
// Direct match: @company
|
|
1348
|
+
// Direct match: @company \u2192 Company
|
|
1491
1349
|
const pascalCase = varName.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('');
|
|
1492
1350
|
if (modelNames.has(pascalCase)) return { model: pascalCase, confidence: 'exact' };
|
|
1493
1351
|
|
|
1494
|
-
// Singular form: @companies
|
|
1352
|
+
// Singular form: @companies \u2192 Company
|
|
1495
1353
|
const singular = varName.replace(/ies$/, 'y').replace(/s$/, '');
|
|
1496
1354
|
const singularPascal = singular.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('');
|
|
1497
1355
|
if (modelNames.has(singularPascal)) return { model: singularPascal, confidence: 'plural' };
|
|
1498
1356
|
|
|
1499
|
-
// current_X pattern: @current_user
|
|
1357
|
+
// current_X pattern: @current_user \u2192 User
|
|
1500
1358
|
if (varName.startsWith('current_')) {
|
|
1501
1359
|
const rest = varName.replace('current_', '');
|
|
1502
1360
|
const restPascal = rest.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('');
|
|
@@ -1541,7 +1399,7 @@ export class PageMapGenerator {
|
|
|
1541
1399
|
|
|
1542
1400
|
const tooltip = assignment && assignment.assignedValue ? assignment.assignedValue.replace(/"/g, '"') : '';
|
|
1543
1401
|
html += '<div class="detail-item"' + hiddenClass + ' title="' + tooltip + '">';
|
|
1544
|
-
html += '<span class="tag
|
|
1402
|
+
html += '<span class="tag tag-var tag-sm">@</span>';
|
|
1545
1403
|
html += '<span class="name" style="font-family:monospace;font-weight:500">' + v + '</span>';
|
|
1546
1404
|
|
|
1547
1405
|
if (linkedModel) {
|
|
@@ -1571,7 +1429,7 @@ export class PageMapGenerator {
|
|
|
1571
1429
|
// "Show more" button
|
|
1572
1430
|
if (hasMore) {
|
|
1573
1431
|
html += '<div id="' + listId + '-more" style="margin-top:8px;cursor:pointer;color:var(--accent);font-size:11px" onclick="toggleMoreItems(\\'' + listId + '\\', ' + screen.instanceVars.length + ')">';
|
|
1574
|
-
html += '
|
|
1432
|
+
html += '\u25BC Show ' + (screen.instanceVars.length - initialLimit) + ' more variables';
|
|
1575
1433
|
html += '</div>';
|
|
1576
1434
|
}
|
|
1577
1435
|
|
|
@@ -1581,14 +1439,14 @@ export class PageMapGenerator {
|
|
|
1581
1439
|
// React Components loaded in this view
|
|
1582
1440
|
if (screen.reactComponents && screen.reactComponents.length > 0) {
|
|
1583
1441
|
html += '<div class="detail-section">';
|
|
1584
|
-
html += '<div class="detail-label"
|
|
1442
|
+
html += '<div class="detail-label">\u269B\uFE0F React Components</div>';
|
|
1585
1443
|
html += '<div class="detail-items">';
|
|
1586
1444
|
screen.reactComponents.forEach(rc => {
|
|
1587
1445
|
html += '<div class="detail-item">';
|
|
1588
|
-
html += '<span class="tag" style="
|
|
1446
|
+
html += '<span class="tag tag-react tag-sm" style="font-weight:600">React</span>';
|
|
1589
1447
|
html += '<span class="name" style="font-family:monospace;font-weight:500">' + rc.name + '</span>';
|
|
1590
1448
|
if (rc.ssr) {
|
|
1591
|
-
html += '<span
|
|
1449
|
+
html += '<span class="badge-success">SSR</span>';
|
|
1592
1450
|
}
|
|
1593
1451
|
if (rc.propsVar) {
|
|
1594
1452
|
html += '<span style="margin-left:auto;font-size:10px;color:var(--text2);font-family:monospace">props: ' + rc.propsVar + '</span>';
|
|
@@ -1605,15 +1463,15 @@ export class PageMapGenerator {
|
|
|
1605
1463
|
const partialListId = 'partials-' + Math.random().toString(36).substr(2, 9);
|
|
1606
1464
|
|
|
1607
1465
|
html += '<div class="detail-section">';
|
|
1608
|
-
html += '<div class="detail-label"
|
|
1466
|
+
html += '<div class="detail-label">\u{1F9E9} Partials Used (' + screen.partials.length + ')</div>';
|
|
1609
1467
|
html += '<div class="detail-items" id="' + partialListId + '">';
|
|
1610
1468
|
screen.partials.forEach((p, idx) => {
|
|
1611
1469
|
const hiddenClass = idx >= partialLimit ? ' style="display:none" data-hidden="true"' : '';
|
|
1612
|
-
html += '<div class="detail-item"' + hiddenClass + '><span class="tag
|
|
1470
|
+
html += '<div class="detail-item"' + hiddenClass + '><span class="tag tag-partial tag-sm">PARTIAL</span><span class="name" style="font-family:monospace;font-size:11px">' + p + '</span></div>';
|
|
1613
1471
|
});
|
|
1614
1472
|
html += '</div>';
|
|
1615
1473
|
if (hasMorePartials) {
|
|
1616
|
-
html += '<div id="' + partialListId + '-more" style="margin-top:6px;cursor:pointer;color:var(--accent);font-size:11px" onclick="toggleMoreItems(\\'' + partialListId + '\\', ' + screen.partials.length + ')"
|
|
1474
|
+
html += '<div id="' + partialListId + '-more" style="margin-top:6px;cursor:pointer;color:var(--accent);font-size:11px" onclick="toggleMoreItems(\\'' + partialListId + '\\', ' + screen.partials.length + ')">\u25BC Show ' + (screen.partials.length - partialLimit) + ' more</div>';
|
|
1617
1475
|
}
|
|
1618
1476
|
html += '</div>';
|
|
1619
1477
|
}
|
|
@@ -1621,10 +1479,10 @@ export class PageMapGenerator {
|
|
|
1621
1479
|
// Services Called
|
|
1622
1480
|
if (screen.services && screen.services.length > 0) {
|
|
1623
1481
|
html += '<div class="detail-section">';
|
|
1624
|
-
html += '<div class="detail-label"
|
|
1482
|
+
html += '<div class="detail-label">\u2699\uFE0F Services Called</div>';
|
|
1625
1483
|
html += '<div class="detail-items">';
|
|
1626
1484
|
screen.services.forEach(s => {
|
|
1627
|
-
html += '<div class="detail-item"><span class="tag
|
|
1485
|
+
html += '<div class="detail-item"><span class="tag tag-service">Service</span><span class="name">' + s + '</span></div>';
|
|
1628
1486
|
});
|
|
1629
1487
|
html += '</div></div>';
|
|
1630
1488
|
}
|
|
@@ -1632,10 +1490,10 @@ export class PageMapGenerator {
|
|
|
1632
1490
|
// gRPC Calls
|
|
1633
1491
|
if (screen.grpcCalls && screen.grpcCalls.length > 0) {
|
|
1634
1492
|
html += '<div class="detail-section">';
|
|
1635
|
-
html += '<div class="detail-label"
|
|
1493
|
+
html += '<div class="detail-label">\u{1F50C} gRPC Calls</div>';
|
|
1636
1494
|
html += '<div class="detail-items">';
|
|
1637
1495
|
screen.grpcCalls.forEach(g => {
|
|
1638
|
-
html += '<div class="detail-item"><span class="tag
|
|
1496
|
+
html += '<div class="detail-item"><span class="tag tag-grpc">gRPC</span><span class="name">' + g + '</span></div>';
|
|
1639
1497
|
});
|
|
1640
1498
|
html += '</div></div>';
|
|
1641
1499
|
}
|
|
@@ -1643,21 +1501,21 @@ export class PageMapGenerator {
|
|
|
1643
1501
|
// Model Access
|
|
1644
1502
|
if (screen.modelAccess && screen.modelAccess.length > 0) {
|
|
1645
1503
|
html += '<div class="detail-section">';
|
|
1646
|
-
html += '<div class="detail-label"
|
|
1504
|
+
html += '<div class="detail-label">\u{1F4BE} Models Used</div>';
|
|
1647
1505
|
html += '<div class="detail-items">';
|
|
1648
1506
|
screen.modelAccess.forEach(m => {
|
|
1649
|
-
html += '<div class="detail-item"><span class="tag
|
|
1507
|
+
html += '<div class="detail-item"><span class="tag tag-model">Model</span><span class="name">' + m + '</span></div>';
|
|
1650
1508
|
});
|
|
1651
1509
|
html += '</div></div>';
|
|
1652
1510
|
}
|
|
1653
1511
|
|
|
1654
1512
|
// Controller Action info
|
|
1655
1513
|
html += '<div class="detail-section">';
|
|
1656
|
-
html += '<div class="detail-label"
|
|
1657
|
-
html += '<div
|
|
1514
|
+
html += '<div class="detail-label">\u{1F3AE} Controller Action</div>';
|
|
1515
|
+
html += '<div class="code-block">';
|
|
1658
1516
|
html += '<div style="font-family:monospace;font-size:12px">' + screen.controller + '#' + screen.action + '</div>';
|
|
1659
1517
|
if (screen.controllerInfo) {
|
|
1660
|
-
html += '<div
|
|
1518
|
+
html += '<div class="subtext">app/controllers/' + screen.controllerInfo.filePath;
|
|
1661
1519
|
if (screen.actionLine) html += ':' + screen.actionLine;
|
|
1662
1520
|
html += '</div>';
|
|
1663
1521
|
|
|
@@ -1670,7 +1528,7 @@ export class PageMapGenerator {
|
|
|
1670
1528
|
});
|
|
1671
1529
|
if (applicableFilters.length > 0) {
|
|
1672
1530
|
html += '<div style="margin-top:8px;font-size:11px">';
|
|
1673
|
-
html += '<span
|
|
1531
|
+
html += '<span class="text-success">Before filters:</span> ' + applicableFilters.map(f => f.name).join(', ');
|
|
1674
1532
|
html += '</div>';
|
|
1675
1533
|
}
|
|
1676
1534
|
}
|
|
@@ -1686,7 +1544,7 @@ export class PageMapGenerator {
|
|
|
1686
1544
|
|
|
1687
1545
|
if (meaningfulCalls.length > 0) {
|
|
1688
1546
|
html += '<div class="detail-section">';
|
|
1689
|
-
html += '<div class="detail-label"
|
|
1547
|
+
html += '<div class="detail-label">\u{1F517} Method Calls</div>';
|
|
1690
1548
|
html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px;font-family:monospace;font-size:11px;max-height:120px;overflow-y:auto">';
|
|
1691
1549
|
meaningfulCalls.forEach((call, i) => {
|
|
1692
1550
|
html += '<div style="padding:2px 0;color:var(--accent)">' + (i+1) + '. ' + call + '</div>';
|
|
@@ -1695,7 +1553,7 @@ export class PageMapGenerator {
|
|
|
1695
1553
|
}
|
|
1696
1554
|
}
|
|
1697
1555
|
|
|
1698
|
-
showModal('
|
|
1556
|
+
showModal('\u{1F5BC}\uFE0F ' + screen.controller + '/' + screen.action, html);
|
|
1699
1557
|
}
|
|
1700
1558
|
|
|
1701
1559
|
// Show Rails page detail with API info
|
|
@@ -1711,7 +1569,7 @@ export class PageMapGenerator {
|
|
|
1711
1569
|
const routeCtrlName = routeCtrlParts.pop().replace(/_/g, '');
|
|
1712
1570
|
|
|
1713
1571
|
controllerInfo = railsControllers.find(c => {
|
|
1714
|
-
const filePathNormalized = c.filePath.replace(/_controller
|
|
1572
|
+
const filePathNormalized = c.filePath.replace(/_controller.rb$/, '').replace(/_/g, '');
|
|
1715
1573
|
if (filePathNormalized === routeCtrl.replace(/_/g, '')) return true;
|
|
1716
1574
|
if (c.name === routeCtrlName || c.name.replace(/_/g, '') === routeCtrlName) return true;
|
|
1717
1575
|
const className = c.className.toLowerCase().replace('controller', '').replace(/::/g, '/');
|
|
@@ -1735,7 +1593,7 @@ export class PageMapGenerator {
|
|
|
1735
1593
|
html += '<div class="detail-label">Method & Path</div>';
|
|
1736
1594
|
html += '<div class="detail-value">';
|
|
1737
1595
|
html += '<span style="background:' + methodColor + ';color:white;padding:2px 8px;border-radius:4px;font-weight:600;margin-right:8px">' + (route.method || 'GET') + '</span>';
|
|
1738
|
-
html += '<span
|
|
1596
|
+
html += '<span class="mono">' + route.path.replace(/:([a-z_]+)/g, '<span class="text-warning">:$1</span>') + '</span>';
|
|
1739
1597
|
html += '</div></div>';
|
|
1740
1598
|
|
|
1741
1599
|
html += '<div class="detail-section">';
|
|
@@ -1746,7 +1604,7 @@ export class PageMapGenerator {
|
|
|
1746
1604
|
// Response Type
|
|
1747
1605
|
if (actionDetails) {
|
|
1748
1606
|
html += '<div class="detail-section">';
|
|
1749
|
-
html += '<div class="detail-label"
|
|
1607
|
+
html += '<div class="detail-label">\u{1F4E1} Response Type</div>';
|
|
1750
1608
|
html += '<div class="detail-value">';
|
|
1751
1609
|
const responseTypes = [];
|
|
1752
1610
|
if (actionDetails.rendersJson) responseTypes.push('<span style="background:#3b82f6;color:white;padding:2px 8px;border-radius:4px;font-size:11px;margin-right:4px">JSON</span>');
|
|
@@ -1757,12 +1615,12 @@ export class PageMapGenerator {
|
|
|
1757
1615
|
responseTypes.push('<span style="background:#8b5cf6;color:white;padding:2px 8px;border-radius:4px;font-size:11px;margin-right:4px">' + f.toUpperCase() + '</span>');
|
|
1758
1616
|
});
|
|
1759
1617
|
}
|
|
1760
|
-
html += responseTypes.length > 0 ? responseTypes.join('') : '<span
|
|
1618
|
+
html += responseTypes.length > 0 ? responseTypes.join('') : '<span class="text-muted">Unknown</span>';
|
|
1761
1619
|
html += '</div></div>';
|
|
1762
1620
|
|
|
1763
1621
|
if (actionDetails.redirectsTo) {
|
|
1764
1622
|
html += '<div class="detail-section">';
|
|
1765
|
-
html += '<div class="detail-label"
|
|
1623
|
+
html += '<div class="detail-label">\u21AA\uFE0F Redirects To</div>';
|
|
1766
1624
|
html += '<div class="detail-value" style="font-family:monospace;font-size:12px;background:var(--bg3);padding:8px;border-radius:4px">' + actionDetails.redirectsTo + '</div>';
|
|
1767
1625
|
html += '</div>';
|
|
1768
1626
|
}
|
|
@@ -1772,10 +1630,10 @@ export class PageMapGenerator {
|
|
|
1772
1630
|
const view = pageInfo?.view || route.view;
|
|
1773
1631
|
if (view) {
|
|
1774
1632
|
html += '<div class="detail-section">';
|
|
1775
|
-
html += '<div class="detail-label"
|
|
1633
|
+
html += '<div class="detail-label">\u{1F4C4} View Template</div>';
|
|
1776
1634
|
html += '<div class="detail-value" style="font-family:monospace;font-size:12px">app/views/' + view.path + '</div>';
|
|
1777
1635
|
if (view.partials && view.partials.length > 0) {
|
|
1778
|
-
html += '<div
|
|
1636
|
+
html += '<div class="subtext">Partials: ' + view.partials.slice(0, 5).join(', ') + '</div>';
|
|
1779
1637
|
}
|
|
1780
1638
|
if (view.instanceVars && view.instanceVars.length > 0) {
|
|
1781
1639
|
html += '<div style="margin-top:4px;font-size:11px;color:var(--text2)">Instance vars: @' + view.instanceVars.slice(0, 5).join(', @') + '</div>';
|
|
@@ -1786,8 +1644,8 @@ export class PageMapGenerator {
|
|
|
1786
1644
|
// Before/After Filters
|
|
1787
1645
|
if (controllerInfo && (controllerInfo.beforeActions.length > 0 || controllerInfo.afterActions.length > 0)) {
|
|
1788
1646
|
html += '<div class="detail-section">';
|
|
1789
|
-
html += '<div class="detail-label"
|
|
1790
|
-
html += '<div
|
|
1647
|
+
html += '<div class="detail-label">\u{1F512} Filters Applied</div>';
|
|
1648
|
+
html += '<div class="code-block">';
|
|
1791
1649
|
|
|
1792
1650
|
const applicableBeforeFilters = controllerInfo.beforeActions.filter(f => {
|
|
1793
1651
|
if (f.only && f.only.length > 0) return f.only.includes(route.action);
|
|
@@ -1799,7 +1657,7 @@ export class PageMapGenerator {
|
|
|
1799
1657
|
html += '<div style="font-size:11px;margin-bottom:4px"><span style="color:#22c55e;font-weight:600">Before:</span> ';
|
|
1800
1658
|
html += applicableBeforeFilters.map(f => {
|
|
1801
1659
|
let info = f.name;
|
|
1802
|
-
if (f.if) info += ' <span
|
|
1660
|
+
if (f.if) info += ' <span class="text-muted">(if: ' + f.if + ')</span>';
|
|
1803
1661
|
return info;
|
|
1804
1662
|
}).join(', ');
|
|
1805
1663
|
html += '</div>';
|
|
@@ -1827,10 +1685,10 @@ export class PageMapGenerator {
|
|
|
1827
1685
|
const services = pageInfo?.services || actionDetails?.servicesCalled || [];
|
|
1828
1686
|
if (services.length > 0) {
|
|
1829
1687
|
html += '<div class="detail-section">';
|
|
1830
|
-
html += '<div class="detail-label"
|
|
1688
|
+
html += '<div class="detail-label">\u2699\uFE0F Services Called</div>';
|
|
1831
1689
|
html += '<div class="detail-items">';
|
|
1832
1690
|
services.forEach(s => {
|
|
1833
|
-
html += '<div class="detail-item"><span class="tag
|
|
1691
|
+
html += '<div class="detail-item"><span class="tag tag-service">Service</span><span class="name">' + s + '</span></div>';
|
|
1834
1692
|
});
|
|
1835
1693
|
html += '</div></div>';
|
|
1836
1694
|
}
|
|
@@ -1839,10 +1697,10 @@ export class PageMapGenerator {
|
|
|
1839
1697
|
const grpcCalls = pageInfo?.grpcCalls || [];
|
|
1840
1698
|
if (grpcCalls.length > 0) {
|
|
1841
1699
|
html += '<div class="detail-section">';
|
|
1842
|
-
html += '<div class="detail-label"
|
|
1700
|
+
html += '<div class="detail-label">\u{1F50C} gRPC Calls</div>';
|
|
1843
1701
|
html += '<div class="detail-items">';
|
|
1844
1702
|
grpcCalls.forEach(g => {
|
|
1845
|
-
html += '<div class="detail-item"><span class="tag
|
|
1703
|
+
html += '<div class="detail-item"><span class="tag tag-grpc">gRPC</span><span class="name">' + g + '</span></div>';
|
|
1846
1704
|
});
|
|
1847
1705
|
html += '</div></div>';
|
|
1848
1706
|
}
|
|
@@ -1851,10 +1709,10 @@ export class PageMapGenerator {
|
|
|
1851
1709
|
const models = pageInfo?.modelAccess || actionDetails?.modelsCalled || [];
|
|
1852
1710
|
if (models.length > 0) {
|
|
1853
1711
|
html += '<div class="detail-section">';
|
|
1854
|
-
html += '<div class="detail-label"
|
|
1712
|
+
html += '<div class="detail-label">\u{1F4BE} Models Accessed</div>';
|
|
1855
1713
|
html += '<div class="detail-items">';
|
|
1856
1714
|
models.forEach(m => {
|
|
1857
|
-
html += '<div class="detail-item"><span class="tag
|
|
1715
|
+
html += '<div class="detail-item"><span class="tag tag-model">Model</span><span class="name">' + m + '</span></div>';
|
|
1858
1716
|
});
|
|
1859
1717
|
html += '</div></div>';
|
|
1860
1718
|
}
|
|
@@ -1868,17 +1726,17 @@ export class PageMapGenerator {
|
|
|
1868
1726
|
|
|
1869
1727
|
if (meaningfulCalls.length > 0) {
|
|
1870
1728
|
html += '<div class="detail-section">';
|
|
1871
|
-
html += '<div class="detail-label"
|
|
1729
|
+
html += '<div class="detail-label">\u{1F517} Method Calls in Action</div>';
|
|
1872
1730
|
html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px;max-height:150px;overflow-y:auto">';
|
|
1873
1731
|
html += '<div style="font-family:monospace;font-size:11px;line-height:1.6">';
|
|
1874
1732
|
meaningfulCalls.forEach((call, i) => {
|
|
1875
|
-
html += '<div
|
|
1733
|
+
html += '<div class="accordion-item">';
|
|
1876
1734
|
html += '<span style="color:var(--text2);margin-right:8px">' + (i+1) + '.</span>';
|
|
1877
|
-
html += '<span
|
|
1735
|
+
html += '<span class="text-accent">' + call + '</span>';
|
|
1878
1736
|
html += '</div>';
|
|
1879
1737
|
});
|
|
1880
1738
|
if (actionDetails.methodCalls.length > 15) {
|
|
1881
|
-
html += '<div
|
|
1739
|
+
html += '<div class="note">...and ' + (actionDetails.methodCalls.length - 15) + ' more</div>';
|
|
1882
1740
|
}
|
|
1883
1741
|
html += '</div></div></div>';
|
|
1884
1742
|
}
|
|
@@ -1886,20 +1744,20 @@ export class PageMapGenerator {
|
|
|
1886
1744
|
|
|
1887
1745
|
// Source Files
|
|
1888
1746
|
html += '<div class="detail-section">';
|
|
1889
|
-
html += '<div class="detail-label"
|
|
1747
|
+
html += '<div class="detail-label">\u{1F4C1} Source Files</div>';
|
|
1890
1748
|
html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px;font-family:monospace;font-size:11px">';
|
|
1891
1749
|
|
|
1892
1750
|
if (controllerInfo) {
|
|
1893
|
-
html += '<div
|
|
1894
|
-
html += '<span
|
|
1751
|
+
html += '<div class="detail-item flex items-center py-1">';
|
|
1752
|
+
html += '<span class="text-muted w-20">Controller:</span>';
|
|
1895
1753
|
html += '<span>app/controllers/' + controllerInfo.filePath;
|
|
1896
|
-
if (actionDetails && actionDetails.line) html += ':<span
|
|
1754
|
+
if (actionDetails && actionDetails.line) html += ':<span class="text-success">' + actionDetails.line + '</span>';
|
|
1897
1755
|
html += '</span></div>';
|
|
1898
1756
|
}
|
|
1899
1757
|
|
|
1900
1758
|
if (view) {
|
|
1901
|
-
html += '<div
|
|
1902
|
-
html += '<span
|
|
1759
|
+
html += '<div class="detail-item flex items-center py-1">';
|
|
1760
|
+
html += '<span class="text-muted w-20">View:</span>';
|
|
1903
1761
|
html += '<span>app/views/' + view.path + '</span>';
|
|
1904
1762
|
html += '</div>';
|
|
1905
1763
|
}
|
|
@@ -1908,13 +1766,13 @@ export class PageMapGenerator {
|
|
|
1908
1766
|
// Controller Info
|
|
1909
1767
|
if (controllerInfo) {
|
|
1910
1768
|
html += '<div class="detail-section">';
|
|
1911
|
-
html += '<div class="detail-label"
|
|
1912
|
-
html += '<div
|
|
1913
|
-
html += '<div
|
|
1769
|
+
html += '<div class="detail-label">\u{1F4CB} Controller Info</div>';
|
|
1770
|
+
html += '<div class="code-block">';
|
|
1771
|
+
html += '<div class="section-title">' + controllerInfo.className + '</div>';
|
|
1914
1772
|
html += '<div style="font-size:11px;color:var(--text2)">extends ' + controllerInfo.parentClass + '</div>';
|
|
1915
1773
|
if (controllerInfo.concerns && controllerInfo.concerns.length > 0) {
|
|
1916
1774
|
html += '<div style="margin-top:6px;font-size:11px">';
|
|
1917
|
-
html += '<span
|
|
1775
|
+
html += '<span class="text-muted">Concerns:</span> ' + controllerInfo.concerns.join(', ');
|
|
1918
1776
|
html += '</div>';
|
|
1919
1777
|
}
|
|
1920
1778
|
html += '</div></div>';
|
|
@@ -1922,7 +1780,7 @@ export class PageMapGenerator {
|
|
|
1922
1780
|
|
|
1923
1781
|
if (!pageInfo && !actionDetails) {
|
|
1924
1782
|
html += '<div style="padding:12px;color:var(--text2);font-size:12px;background:var(--bg3);border-radius:6px;margin-top:8px">';
|
|
1925
|
-
html += '
|
|
1783
|
+
html += '\u26A0\uFE0F No detailed action information found. The controller or action may not be analyzed yet.';
|
|
1926
1784
|
html += '</div>';
|
|
1927
1785
|
}
|
|
1928
1786
|
|
|
@@ -1953,7 +1811,7 @@ export class PageMapGenerator {
|
|
|
1953
1811
|
html += '<div class="detail-label">Partials Used</div>';
|
|
1954
1812
|
html += '<div class="detail-items">';
|
|
1955
1813
|
view.partials.forEach(p => {
|
|
1956
|
-
html += '<div class="detail-item"><span class="tag
|
|
1814
|
+
html += '<div class="detail-item"><span class="tag tag-partial">PARTIAL</span><span class="name">' + p + '</span></div>';
|
|
1957
1815
|
});
|
|
1958
1816
|
html += '</div></div>';
|
|
1959
1817
|
}
|
|
@@ -1963,7 +1821,7 @@ export class PageMapGenerator {
|
|
|
1963
1821
|
html += '<div class="detail-label">Instance Variables</div>';
|
|
1964
1822
|
html += '<div class="detail-items">';
|
|
1965
1823
|
view.instanceVars.slice(0, 10).forEach(v => {
|
|
1966
|
-
html += '<div class="detail-item"><span class="tag
|
|
1824
|
+
html += '<div class="detail-item"><span class="tag tag-var">@</span><span class="name">' + v + '</span></div>';
|
|
1967
1825
|
});
|
|
1968
1826
|
if (view.instanceVars.length > 10) {
|
|
1969
1827
|
html += '<div style="padding:4px 8px;color:var(--text2);font-size:11px">...and ' + (view.instanceVars.length - 10) + ' more</div>';
|
|
@@ -1971,7 +1829,7 @@ export class PageMapGenerator {
|
|
|
1971
1829
|
html += '</div></div>';
|
|
1972
1830
|
}
|
|
1973
1831
|
|
|
1974
|
-
showModal('
|
|
1832
|
+
showModal('\u{1F4C4} ' + view.controller + '/' + view.action, html);
|
|
1975
1833
|
}
|
|
1976
1834
|
|
|
1977
1835
|
function toggleGroup(el) {
|
|
@@ -1991,14 +1849,14 @@ export class PageMapGenerator {
|
|
|
1991
1849
|
hiddenItems.forEach(item => {
|
|
1992
1850
|
item.style.display = 'none';
|
|
1993
1851
|
});
|
|
1994
|
-
moreBtn.innerHTML = '
|
|
1852
|
+
moreBtn.innerHTML = '\u25BC Show ' + hiddenItems.length + ' more variables';
|
|
1995
1853
|
moreBtn.setAttribute('data-expanded', 'false');
|
|
1996
1854
|
} else {
|
|
1997
1855
|
// Expand: show all items
|
|
1998
1856
|
hiddenItems.forEach(item => {
|
|
1999
1857
|
item.style.display = '';
|
|
2000
1858
|
});
|
|
2001
|
-
moreBtn.innerHTML = '
|
|
1859
|
+
moreBtn.innerHTML = '\u25B2 Show less';
|
|
2002
1860
|
moreBtn.setAttribute('data-expanded', 'true');
|
|
2003
1861
|
}
|
|
2004
1862
|
}
|
|
@@ -2077,11 +1935,11 @@ export class PageMapGenerator {
|
|
|
2077
1935
|
// Parse operations to extract path info and depth
|
|
2078
1936
|
const parsedOps = graphqlOps.map(df => {
|
|
2079
1937
|
const rawName = df.operationName || '';
|
|
2080
|
-
// Pattern: "
|
|
2081
|
-
const arrowCount = (rawName.match(
|
|
1938
|
+
// Pattern: "\u2192 QueryName (via HookA)" or "\u2192 \u2192 QueryName (via HookA)" etc.
|
|
1939
|
+
const arrowCount = (rawName.match(/\u2192/g) || []).length;
|
|
2082
1940
|
|
|
2083
1941
|
// Extract query name and path
|
|
2084
|
-
let queryName = rawName.replace(/^[
|
|
1942
|
+
let queryName = rawName.replace(/^[\u2192\\s]+/, '').replace(/^\\u2192\\s*/g, '');
|
|
2085
1943
|
let sourcePath = '';
|
|
2086
1944
|
|
|
2087
1945
|
// Extract "(via X)" for hook
|
|
@@ -2099,7 +1957,7 @@ export class PageMapGenerator {
|
|
|
2099
1957
|
}
|
|
2100
1958
|
|
|
2101
1959
|
// Further clean the query name
|
|
2102
|
-
queryName = queryName.replace(/^[
|
|
1960
|
+
queryName = queryName.replace(/^[\u2192\\s]+/, '').trim();
|
|
2103
1961
|
|
|
2104
1962
|
return {
|
|
2105
1963
|
...df,
|
|
@@ -2162,13 +2020,13 @@ export class PageMapGenerator {
|
|
|
2162
2020
|
|
|
2163
2021
|
sortedPaths.forEach(pathName => {
|
|
2164
2022
|
const ops = groupedByPath.get(pathName);
|
|
2165
|
-
const depthIndicator = pathName === 'Direct' ? '' : '
|
|
2023
|
+
const depthIndicator = pathName === 'Direct' ? '' : '\u21B3 ';
|
|
2166
2024
|
const pathLabel = pathName === 'Direct' ? 'Direct (this page)' : pathName;
|
|
2167
2025
|
|
|
2168
2026
|
// Path header with depth visual
|
|
2169
2027
|
dataHtml += '<div class="data-path-group" style="margin:8px 0">' +
|
|
2170
2028
|
'<div class="data-path-header" style="font-size:11px;color:var(--text2);margin-bottom:4px;padding-left:'+(ops[0]?.depth * 8)+'px">' +
|
|
2171
|
-
depthIndicator + '<span
|
|
2029
|
+
depthIndicator + '<span class="text-accent">' + pathLabel + '</span> (' + ops.length + ')' +
|
|
2172
2030
|
'</div>';
|
|
2173
2031
|
|
|
2174
2032
|
ops.forEach(op => {
|
|
@@ -2189,7 +2047,7 @@ export class PageMapGenerator {
|
|
|
2189
2047
|
dataHtml += '<div class="detail-section"><h4>Used Components</h4>';
|
|
2190
2048
|
uniqueComponentRefs.forEach(df => {
|
|
2191
2049
|
const name = df.operationName || '';
|
|
2192
|
-
dataHtml += '<div class="detail-item" style="cursor:default"><span class="tag
|
|
2050
|
+
dataHtml += '<div class="detail-item" style="cursor:default"><span class="tag tag-default">COMPONENT</span> '+name+'</div>';
|
|
2193
2051
|
});
|
|
2194
2052
|
dataHtml += '</div>';
|
|
2195
2053
|
}
|
|
@@ -2214,7 +2072,7 @@ export class PageMapGenerator {
|
|
|
2214
2072
|
const methodColors = {GET:'#22c55e',POST:'#3b82f6',PUT:'#f59e0b',DELETE:'#ef4444',PATCH:'#8b5cf6'};
|
|
2215
2073
|
const color = methodColors[api.method] || '#6b7280';
|
|
2216
2074
|
restApiHtml += '<div class="detail-item api-item" onclick="event.stopPropagation(); showApiDetail(\\''+api.id.replace(/'/g, "\\\\'")+'\\')">' +
|
|
2217
|
-
'<div class="api-row"><span class="tag" style="background:'+color+'">'+api.method+'</span><span class="api-url">'+api.url+'</span></div>' +
|
|
2075
|
+
'<div class="api-row"><span class="tag" style="background:'+color+';color:white">'+api.method+'</span><span class="api-url">'+api.url+'</span></div>' +
|
|
2218
2076
|
(api.filePath ? '<div class="api-route">'+api.callType+' in '+api.filePath+'</div>' : '') +
|
|
2219
2077
|
'</div>';
|
|
2220
2078
|
});
|
|
@@ -2360,8 +2218,8 @@ export class PageMapGenerator {
|
|
|
2360
2218
|
const groupPages = groups[g];
|
|
2361
2219
|
html += '<div style="margin-bottom:12px">';
|
|
2362
2220
|
html += '<div class="detail-item" style="cursor:pointer;background:var(--bg3);border-radius:4px" onclick="toggleGroupList(this)">';
|
|
2363
|
-
html += '<div class="detail-label" style="display:flex;align-items:center;gap:6px"><span class="group-toggle"
|
|
2364
|
-
html += '<span
|
|
2221
|
+
html += '<div class="detail-label" style="display:flex;align-items:center;gap:6px"><span class="group-toggle">\u25B8</span>/'+g+'</div>';
|
|
2222
|
+
html += '<span class="text-accent">'+groupPages.length+' pages</span></div>';
|
|
2365
2223
|
html += '<div class="group-page-list" style="display:none;margin-left:16px;margin-top:4px">';
|
|
2366
2224
|
groupPages.sort((a,b) => a.path.localeCompare(b.path)).forEach(p => {
|
|
2367
2225
|
const isAuth = p.authentication?.required;
|
|
@@ -2369,7 +2227,7 @@ export class PageMapGenerator {
|
|
|
2369
2227
|
html += '<div class="detail-item rel-item" style="cursor:pointer;padding:6px 8px" onclick="event.stopPropagation(); selectPage(\\''+p.path+'\\')">'+
|
|
2370
2228
|
'<span style="font-family:monospace;font-size:11px;color:var(--text)">'+p.path+'</span>'+
|
|
2371
2229
|
(isAuth ? '<span class="tag tag-auth" style="margin-left:6px;font-size:9px">AUTH</span>' : '')+
|
|
2372
|
-
(isDynamic ? '<span class="tag" style="margin-left:6px
|
|
2230
|
+
(isDynamic ? '<span class="tag tag-info" style="margin-left:6px" title="Dynamic Route">DYNAMIC</span>' : '')+
|
|
2373
2231
|
'</div>';
|
|
2374
2232
|
});
|
|
2375
2233
|
html += '</div></div>';
|
|
@@ -2386,10 +2244,10 @@ export class PageMapGenerator {
|
|
|
2386
2244
|
const toggle = el.querySelector('.group-toggle');
|
|
2387
2245
|
if (list.style.display === 'none') {
|
|
2388
2246
|
list.style.display = 'block';
|
|
2389
|
-
toggle.textContent = '
|
|
2247
|
+
toggle.textContent = '\u25BE';
|
|
2390
2248
|
} else {
|
|
2391
2249
|
list.style.display = 'none';
|
|
2392
|
-
toggle.textContent = '
|
|
2250
|
+
toggle.textContent = '\u25B8';
|
|
2393
2251
|
}
|
|
2394
2252
|
};
|
|
2395
2253
|
|
|
@@ -2434,7 +2292,7 @@ export class PageMapGenerator {
|
|
|
2434
2292
|
const indent = depth * 12;
|
|
2435
2293
|
let html = '<div class="rel-item" style="padding-left:'+(10+indent)+'px" onclick="event.stopPropagation(); selectPage(\\''+page.path+'\\')">';
|
|
2436
2294
|
html += '<div class="rel-header">';
|
|
2437
|
-
html += '<span style="color:var(--text2);font-size:10px">'+'
|
|
2295
|
+
html += '<span style="color:var(--text2);font-size:10px">'+'\u2500'.repeat(depth > 0 ? 1 : 0)+(depth > 0 ? ' ' : '')+'</span>';
|
|
2438
2296
|
html += '<span class="rel-path">'+page.path+'</span>';
|
|
2439
2297
|
if (page.children && page.children.length > 0) {
|
|
2440
2298
|
html += '<span style="color:var(--text2);font-size:9px;margin-left:auto">'+page.children.length+' children</span>';
|
|
@@ -2482,7 +2340,7 @@ export class PageMapGenerator {
|
|
|
2482
2340
|
const fragments = Array.from(gqlMap.values()).filter(o => o.type === 'fragment');
|
|
2483
2341
|
|
|
2484
2342
|
if (queries.length > 0) {
|
|
2485
|
-
html += '<div
|
|
2343
|
+
html += '<div class="subtext-accent">Queries ('+queries.length+')</div>';
|
|
2486
2344
|
queries.slice(0, 20).forEach(op => {
|
|
2487
2345
|
html += '<div class="detail-item data-op" onclick="event.stopPropagation(); showDataDetail(\\''+op.name.replace(/'/g, "\\\\'")+'\\')">' +
|
|
2488
2346
|
'<span class="tag tag-query">QUERY</span> '+op.name+'</div>';
|
|
@@ -2493,7 +2351,7 @@ export class PageMapGenerator {
|
|
|
2493
2351
|
}
|
|
2494
2352
|
|
|
2495
2353
|
if (mutations.length > 0) {
|
|
2496
|
-
html += '<div
|
|
2354
|
+
html += '<div class="subtext-accent">Mutations ('+mutations.length+')</div>';
|
|
2497
2355
|
mutations.slice(0, 10).forEach(op => {
|
|
2498
2356
|
html += '<div class="detail-item data-op" onclick="event.stopPropagation(); showDataDetail(\\''+op.name.replace(/'/g, "\\\\'")+'\\')">' +
|
|
2499
2357
|
'<span class="tag tag-mutation">MUTATION</span> '+op.name+'</div>';
|
|
@@ -2504,10 +2362,10 @@ export class PageMapGenerator {
|
|
|
2504
2362
|
}
|
|
2505
2363
|
|
|
2506
2364
|
if (fragments.length > 0) {
|
|
2507
|
-
html += '<div
|
|
2365
|
+
html += '<div class="subtext-accent">Fragments ('+fragments.length+')</div>';
|
|
2508
2366
|
fragments.slice(0, 5).forEach(op => {
|
|
2509
2367
|
html += '<div class="detail-item data-op" onclick="event.stopPropagation(); showDataDetail(\\''+op.name.replace(/'/g, "\\\\'")+'\\')">' +
|
|
2510
|
-
'<span class="tag
|
|
2368
|
+
'<span class="tag tag-default">FRAGMENT</span> '+op.name+'</div>';
|
|
2511
2369
|
});
|
|
2512
2370
|
if (fragments.length > 5) {
|
|
2513
2371
|
html += '<div style="color:var(--text2);font-size:10px;padding:4px">... and '+(fragments.length-5)+' more fragments</div>';
|
|
@@ -2532,7 +2390,7 @@ export class PageMapGenerator {
|
|
|
2532
2390
|
const methodColors = {GET:'#22c55e',POST:'#3b82f6',PUT:'#f59e0b',DELETE:'#ef4444',PATCH:'#8b5cf6'};
|
|
2533
2391
|
const color = methodColors[api.method] || '#6b7280';
|
|
2534
2392
|
html += '<div class="detail-item api-item" onclick="event.stopPropagation(); showApiDetail(\\''+api.id.replace(/'/g, "\\\\'")+'\\')">' +
|
|
2535
|
-
'<div class="api-row"><span class="tag" style="background:'+color+'">'+api.method+'</span><span class="api-url">'+api.url+'</span></div>' +
|
|
2393
|
+
'<div class="api-row"><span class="tag" style="background:'+color+';color:white">'+api.method+'</span><span class="api-url">'+api.url+'</span></div>' +
|
|
2536
2394
|
'<div class="api-route">'+api.callType+' in '+api.filePath+'</div>' +
|
|
2537
2395
|
'</div>';
|
|
2538
2396
|
});
|
|
@@ -2567,7 +2425,7 @@ export class PageMapGenerator {
|
|
|
2567
2425
|
|
|
2568
2426
|
if (api.category && api.category !== 'internal') {
|
|
2569
2427
|
html += '<div class="detail-section"><h4>Category</h4>' +
|
|
2570
|
-
'<span class="tag
|
|
2428
|
+
'<span class="tag tag-accent">'+api.category.toUpperCase()+'</span></div>';
|
|
2571
2429
|
}
|
|
2572
2430
|
|
|
2573
2431
|
// Show in modal
|
|
@@ -2616,12 +2474,12 @@ export class PageMapGenerator {
|
|
|
2616
2474
|
};
|
|
2617
2475
|
|
|
2618
2476
|
function showDataDetail(rawName, sourcePath) {
|
|
2619
|
-
// Clean up name: remove "
|
|
2477
|
+
// Clean up name: remove "\u2192 " prefix and " (ComponentName)" suffix
|
|
2620
2478
|
const name = rawName
|
|
2621
|
-
.replace(/^[
|
|
2479
|
+
.replace(/^[\u2192\\->\\s]+/, '')
|
|
2622
2480
|
.replace(/\\s*\\([^)]+\\)\\s*$/, '');
|
|
2623
2481
|
|
|
2624
|
-
// Convert SCREAMING_CASE to PascalCase (e.g., COMPANY_QUERY
|
|
2482
|
+
// Convert SCREAMING_CASE to PascalCase (e.g., COMPANY_QUERY \u2192 CompanyQuery)
|
|
2625
2483
|
const toPascalCase = (str) => {
|
|
2626
2484
|
if (!/^[A-Z][A-Z0-9_]*$/.test(str)) return str;
|
|
2627
2485
|
return str.toLowerCase().split('_').map(word =>
|
|
@@ -2674,11 +2532,11 @@ export class PageMapGenerator {
|
|
|
2674
2532
|
// Source info
|
|
2675
2533
|
if (sourcePath) {
|
|
2676
2534
|
const isHook = sourcePath.startsWith('use');
|
|
2677
|
-
html += '<div class="detail-section"><h4>Source</h4><div class="detail-item" style="font-size:12px">via '+(isHook?'Hook':'Component')+': <span
|
|
2535
|
+
html += '<div class="detail-section"><h4>Source</h4><div class="detail-item" style="font-size:12px">via '+(isHook?'Hook':'Component')+': <span class="text-accent">'+sourcePath+'</span></div></div>';
|
|
2678
2536
|
}
|
|
2679
2537
|
|
|
2680
2538
|
// Operation Name with copy button
|
|
2681
|
-
html += '<div class="detail-section"><h4 style="display:flex;justify-content:space-between;align-items:center">Operation Name<button class="copy-btn" onclick="copyToClipboard(\\''+op.name+'\\', this)" title="Copy operation name"
|
|
2539
|
+
html += '<div class="detail-section"><h4 style="display:flex;justify-content:space-between;align-items:center">Operation Name<button class="copy-btn" onclick="copyToClipboard(\\''+op.name+'\\', this)" title="Copy operation name">\u{1F4CB}</button></h4>';
|
|
2682
2540
|
html += '<code style="background:#0f172a;color:#93c5fd;padding:4px 8px;border-radius:4px;font-family:monospace">'+op.name+'</code></div>';
|
|
2683
2541
|
|
|
2684
2542
|
if (op.returnType) {
|
|
@@ -2697,7 +2555,7 @@ export class PageMapGenerator {
|
|
|
2697
2555
|
// Escape for data attribute
|
|
2698
2556
|
const gqlCodeEscaped = gqlCode.replace(/'/g, "\\\\'").replace(/"/g, '"');
|
|
2699
2557
|
|
|
2700
|
-
html += '<div class="detail-section"><h4 style="display:flex;justify-content:space-between;align-items:center">GraphQL<button class="copy-btn" onclick="copyGqlCode(this)" data-code="'+gqlCodeEscaped+'" title="Copy GraphQL"
|
|
2558
|
+
html += '<div class="detail-section"><h4 style="display:flex;justify-content:space-between;align-items:center">GraphQL<button class="copy-btn" onclick="copyGqlCode(this)" data-code="'+gqlCodeEscaped+'" title="Copy GraphQL">\u{1F4CB}</button></h4>';
|
|
2701
2559
|
html += '<pre style="background:#0f172a;color:#e2e8f0;padding:12px;border-radius:6px;font-size:11px;overflow-x:auto;white-space:pre;max-height:300px;overflow-y:auto">' + gqlCode + '</pre></div>';
|
|
2702
2560
|
} else if (op.variables?.length) {
|
|
2703
2561
|
html += '<div class="detail-section"><h4>Variables</h4>';
|
|
@@ -2709,7 +2567,7 @@ export class PageMapGenerator {
|
|
|
2709
2567
|
op.usedIn.slice(0,8).forEach(f => { html += '<div class="detail-item">'+f+'</div>'; });
|
|
2710
2568
|
if (op.usedIn.length > 8) {
|
|
2711
2569
|
const remaining = op.usedIn.slice(8);
|
|
2712
|
-
html += '<div class="expand-more" onclick="expandMore(\\'usedIn\\', '+JSON.stringify(remaining).replace(/"/g, '"')+', this)" style="color:var(--accent);font-size:11px;cursor:pointer;padding:4px 0"
|
|
2570
|
+
html += '<div class="expand-more" onclick="expandMore(\\'usedIn\\', '+JSON.stringify(remaining).replace(/"/g, '"')+', this)" style="color:var(--accent);font-size:11px;cursor:pointer;padding:4px 0">\u25B8 Show '+(op.usedIn.length-8)+' more files</div>';
|
|
2713
2571
|
}
|
|
2714
2572
|
html += '</div>';
|
|
2715
2573
|
}
|
|
@@ -2752,7 +2610,7 @@ export class PageMapGenerator {
|
|
|
2752
2610
|
});
|
|
2753
2611
|
if (queries.length > 5) {
|
|
2754
2612
|
const remaining = queries.slice(5).map(o => ({name: o.name}));
|
|
2755
|
-
html += '<div class="expand-more" onclick="expandMore(\\'query\\', '+JSON.stringify(remaining).replace(/"/g, '"')+', this)"
|
|
2613
|
+
html += '<div class="expand-more" onclick="expandMore(\\'query\\', '+JSON.stringify(remaining).replace(/"/g, '"')+', this)" class="expand-more">\u25B8 Show ' + (queries.length - 5) + ' more queries</div>';
|
|
2756
2614
|
}
|
|
2757
2615
|
}
|
|
2758
2616
|
|
|
@@ -2764,7 +2622,7 @@ export class PageMapGenerator {
|
|
|
2764
2622
|
});
|
|
2765
2623
|
if (mutations.length > 5) {
|
|
2766
2624
|
const remaining = mutations.slice(5).map(o => ({name: o.name}));
|
|
2767
|
-
html += '<div class="expand-more" onclick="expandMore(\\'mutation\\', '+JSON.stringify(remaining).replace(/"/g, '"')+', this)"
|
|
2625
|
+
html += '<div class="expand-more" onclick="expandMore(\\'mutation\\', '+JSON.stringify(remaining).replace(/"/g, '"')+', this)" class="expand-more">\u25B8 Show ' + (mutations.length - 5) + ' more mutations</div>';
|
|
2768
2626
|
}
|
|
2769
2627
|
}
|
|
2770
2628
|
|
|
@@ -2772,11 +2630,11 @@ export class PageMapGenerator {
|
|
|
2772
2630
|
html += '<div style="margin:8px 0;font-size:10px;color:var(--text2)">Fragments ('+fragments.length+')</div>';
|
|
2773
2631
|
fragments.slice(0,3).forEach(op => {
|
|
2774
2632
|
html += '<div class="detail-item data-op" onclick="showDataDetail(\\''+op.name.replace(/'/g, "\\\\'")+'\\')">' +
|
|
2775
|
-
'<span class="tag
|
|
2633
|
+
'<span class="tag tag-default">FRAGMENT</span> '+op.name+'</div>';
|
|
2776
2634
|
});
|
|
2777
2635
|
if (fragments.length > 3) {
|
|
2778
2636
|
const remaining = fragments.slice(3).map(o => ({name: o.name}));
|
|
2779
|
-
html += '<div class="expand-more" onclick="expandMore(\\'fragment\\', '+JSON.stringify(remaining).replace(/"/g, '"')+', this)"
|
|
2637
|
+
html += '<div class="expand-more" onclick="expandMore(\\'fragment\\', '+JSON.stringify(remaining).replace(/"/g, '"')+', this)" class="expand-more">\u25B8 Show ' + (fragments.length - 3) + ' more fragments</div>';
|
|
2780
2638
|
}
|
|
2781
2639
|
}
|
|
2782
2640
|
|
|
@@ -2799,9 +2657,9 @@ export class PageMapGenerator {
|
|
|
2799
2657
|
html += '</div>';
|
|
2800
2658
|
}
|
|
2801
2659
|
} else {
|
|
2802
|
-
// Clean up the name: remove "
|
|
2660
|
+
// Clean up the name: remove "\u2192 " prefix and " (ComponentName)" suffix
|
|
2803
2661
|
let cleanName = name
|
|
2804
|
-
.replace(/^[
|
|
2662
|
+
.replace(/^[\u2192\\->\\s]+/, '') // Remove arrow prefix
|
|
2805
2663
|
.replace(/\\s*\\([^)]+\\)\\s*$/, ''); // Remove parenthetical component reference
|
|
2806
2664
|
|
|
2807
2665
|
// Extract the core component name - remove ALL common suffixes iteratively
|
|
@@ -2892,7 +2750,7 @@ export class PageMapGenerator {
|
|
|
2892
2750
|
});
|
|
2893
2751
|
if (queries.length > 8) {
|
|
2894
2752
|
const remaining = queries.slice(8).map(o => ({name: o.name}));
|
|
2895
|
-
html += '<div class="expand-more" onclick="expandMore(\\'query\\', '+JSON.stringify(remaining).replace(/"/g, '"')+', this)"
|
|
2753
|
+
html += '<div class="expand-more" onclick="expandMore(\\'query\\', '+JSON.stringify(remaining).replace(/"/g, '"')+', this)" class="expand-more">\u25B8 Show ' + (queries.length - 8) + ' more</div>';
|
|
2896
2754
|
}
|
|
2897
2755
|
}
|
|
2898
2756
|
|
|
@@ -2904,7 +2762,7 @@ export class PageMapGenerator {
|
|
|
2904
2762
|
});
|
|
2905
2763
|
if (mutations.length > 5) {
|
|
2906
2764
|
const remaining = mutations.slice(5).map(o => ({name: o.name}));
|
|
2907
|
-
html += '<div class="expand-more" onclick="expandMore(\\'mutation\\', '+JSON.stringify(remaining).replace(/"/g, '"')+', this)"
|
|
2765
|
+
html += '<div class="expand-more" onclick="expandMore(\\'mutation\\', '+JSON.stringify(remaining).replace(/"/g, '"')+', this)" class="expand-more">\u25B8 Show ' + (mutations.length - 5) + ' more</div>';
|
|
2908
2766
|
}
|
|
2909
2767
|
}
|
|
2910
2768
|
|
|
@@ -2912,11 +2770,11 @@ export class PageMapGenerator {
|
|
|
2912
2770
|
html += '<div style="margin:8px 0 6px;font-size:10px;color:var(--text2)">Fragments ('+fragments.length+')</div>';
|
|
2913
2771
|
fragments.slice(0, 3).forEach(op => {
|
|
2914
2772
|
html += '<div class="detail-item data-op" onclick="showDataDetail(\\''+op.name.replace(/'/g, "\\\\'")+'\\')">' +
|
|
2915
|
-
'<span class="tag
|
|
2773
|
+
'<span class="tag tag-default">FRAGMENT</span> '+op.name+'</div>';
|
|
2916
2774
|
});
|
|
2917
2775
|
if (fragments.length > 3) {
|
|
2918
2776
|
const remaining = fragments.slice(3).map(o => ({name: o.name}));
|
|
2919
|
-
html += '<div class="expand-more" onclick="expandMore(\\'fragment\\', '+JSON.stringify(remaining).replace(/"/g, '"')+', this)"
|
|
2777
|
+
html += '<div class="expand-more" onclick="expandMore(\\'fragment\\', '+JSON.stringify(remaining).replace(/"/g, '"')+', this)" class="expand-more">\u25B8 Show ' + (fragments.length - 3) + ' more</div>';
|
|
2920
2778
|
}
|
|
2921
2779
|
}
|
|
2922
2780
|
|
|
@@ -2973,7 +2831,7 @@ export class PageMapGenerator {
|
|
|
2973
2831
|
window.copyToClipboard = function(text, btn) {
|
|
2974
2832
|
navigator.clipboard.writeText(text).then(() => {
|
|
2975
2833
|
const originalText = btn.textContent;
|
|
2976
|
-
btn.textContent = '
|
|
2834
|
+
btn.textContent = '\u2713';
|
|
2977
2835
|
btn.classList.add('copied');
|
|
2978
2836
|
setTimeout(() => {
|
|
2979
2837
|
btn.textContent = originalText;
|
|
@@ -3459,104 +3317,21 @@ export class PageMapGenerator {
|
|
|
3459
3317
|
}
|
|
3460
3318
|
</script>
|
|
3461
3319
|
</body>
|
|
3462
|
-
</html
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
const colors = [
|
|
3466
|
-
'#ef4444',
|
|
3467
|
-
'#f97316',
|
|
3468
|
-
'#eab308',
|
|
3469
|
-
'#22c55e',
|
|
3470
|
-
'#14b8a6',
|
|
3471
|
-
'#3b82f6',
|
|
3472
|
-
'#8b5cf6',
|
|
3473
|
-
'#ec4899',
|
|
3474
|
-
];
|
|
3475
|
-
let idx = 0;
|
|
3476
|
-
return Array.from(groups.entries())
|
|
3477
|
-
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
3478
|
-
.map(([name, pages]) => {
|
|
3479
|
-
const color = colors[idx++ % colors.length];
|
|
3480
|
-
const sorted = pages.sort((a, b) => a.path.localeCompare(b.path));
|
|
3481
|
-
// Build depth map based on actual parent-child relationships
|
|
3482
|
-
const pathSet = new Set(sorted.map((p) => p.path));
|
|
3483
|
-
const depthMap = new Map();
|
|
3484
|
-
// Calculate depth for each page based on closest existing ancestor
|
|
3485
|
-
for (const p of sorted) {
|
|
3486
|
-
const segments = p.path.split('/').filter(Boolean);
|
|
3487
|
-
let depth = 0;
|
|
3488
|
-
// Find closest existing ancestor
|
|
3489
|
-
for (let i = segments.length - 1; i >= 1; i--) {
|
|
3490
|
-
const ancestorPath = '/' + segments.slice(0, i).join('/');
|
|
3491
|
-
if (pathSet.has(ancestorPath)) {
|
|
3492
|
-
depth = (depthMap.get(ancestorPath) ?? 0) + 1;
|
|
3493
|
-
break;
|
|
3494
|
-
}
|
|
3495
|
-
}
|
|
3496
|
-
depthMap.set(p.path, depth);
|
|
3497
|
-
}
|
|
3498
|
-
const pagesHtml = sorted
|
|
3499
|
-
.map((p) => {
|
|
3500
|
-
const type = this.getPageType(p.path);
|
|
3501
|
-
const queries = (p.dataFetching || []).filter((d) => !d.type?.includes('Mutation')).length;
|
|
3502
|
-
const mutations = (p.dataFetching || []).filter((d) => d.type?.includes('Mutation')).length;
|
|
3503
|
-
const depth = depthMap.get(p.path) ?? 0;
|
|
3504
|
-
const pageNode = p;
|
|
3505
|
-
const repoName = pageNode.repo || '';
|
|
3506
|
-
// Only show repo tag if there are multiple repositories
|
|
3507
|
-
const showRepoTag = allPages.some((pg) => pg.repo && pg.repo !== repoName);
|
|
3508
|
-
// Create short name: take last part or abbreviate long names
|
|
3509
|
-
const shortRepoName = repoName
|
|
3510
|
-
.split('/')
|
|
3511
|
-
.pop()
|
|
3512
|
-
?.split('-')
|
|
3513
|
-
.map((s) => s.substring(0, 4))
|
|
3514
|
-
.join('-') || repoName.substring(0, 8);
|
|
3515
|
-
const repoTag = showRepoTag && repoName
|
|
3516
|
-
? `<span class="tag tag-repo" title="${repoName}">${shortRepoName}</span>`
|
|
3517
|
-
: '';
|
|
3518
|
-
// Detect SPA component pages (PascalCase path or in components/pages)
|
|
3519
|
-
const isSpaComponent = /^\/[A-Z]/.test(p.path) || (p.filePath && p.filePath.includes('components/pages'));
|
|
3520
|
-
const displayPath = isSpaComponent && p.filePath
|
|
3521
|
-
? p.filePath.replace(/\.tsx?$/, '').replace(/^(frontend\/src\/|src\/)/, '')
|
|
3522
|
-
: p.path;
|
|
3523
|
-
const spaTag = isSpaComponent
|
|
3524
|
-
? '<span class="tag" style="background:#6366f1;color:white" title="SPA Component Page">SPA</span>'
|
|
3525
|
-
: '';
|
|
3526
|
-
return `<div class="page-item" data-path="${p.path}" data-repo="${repoName}" onclick="selectPage('${p.path}')" style="--depth:${depth}">
|
|
3527
|
-
<span class="page-type" style="--type-color:${type.color}">${type.label}</span>
|
|
3528
|
-
<span class="page-path">${displayPath}</span>
|
|
3320
|
+
</html>`}buildTreeHtml(s,r){let c=["#ef4444","#f97316","#eab308","#22c55e","#14b8a6","#3b82f6","#8b5cf6","#ec4899"],v=0;return Array.from(s.entries()).sort((t,l)=>t[0].localeCompare(l[0])).map(([t,l])=>{let e=c[v++%c.length],i=l.sort((a,m)=>a.path.localeCompare(m.path)),o=new Set(i.map(a=>a.path)),p=new Map;for(let a of i){let m=a.path.split("/").filter(Boolean),u=0;for(let h=m.length-1;h>=1;h--){let y="/"+m.slice(0,h).join("/");if(o.has(y)){u=(p.get(y)??0)+1;break}}p.set(a.path,u);}let f=i.map(a=>{let m=this.getPageType(a.path),u=(a.dataFetching||[]).filter(g=>!g.type?.includes("Mutation")).length,h=(a.dataFetching||[]).filter(g=>g.type?.includes("Mutation")).length,y=p.get(a.path)??0,n=a.repo||"",w=r.some(g=>g.repo&&g.repo!==n),C=n.split("/").pop()?.split("-").map(g=>g.substring(0,4)).join("-")||n.substring(0,8),x=w&&n?`<span class="tag tag-repo" title="${n}">${C}</span>`:"",d=/^\/[A-Z]/.test(a.path)||a.filePath&&a.filePath.includes("components/pages"),b=d&&a.filePath?a.filePath.replace(/\.tsx?$/,"").replace(/^(frontend\/src\/|src\/)/,""):a.path,R=d?'<span class="tag tag-info" title="SPA Component Page">SPA</span>':"";return `<div class="page-item" data-path="${a.path}" data-repo="${n}" onclick="selectPage('${a.path}')" style="--depth:${y}">
|
|
3321
|
+
<span class="page-type" style="--type-color:${m.color}">${m.label}</span>
|
|
3322
|
+
<span class="page-path">${b}</span>
|
|
3529
3323
|
<div class="page-tags">
|
|
3530
|
-
${
|
|
3531
|
-
${
|
|
3532
|
-
${
|
|
3533
|
-
${
|
|
3534
|
-
${
|
|
3324
|
+
${x}
|
|
3325
|
+
${R}
|
|
3326
|
+
${a.authentication?.required?'<span class="tag tag-auth">AUTH</span>':""}
|
|
3327
|
+
${u>0?`<span class="tag tag-query">Q:${u}</span>`:""}
|
|
3328
|
+
${h>0?`<span class="tag tag-mutation">M:${h}</span>`:""}
|
|
3535
3329
|
</div>
|
|
3536
|
-
</div
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
<span class="group-arrow">▼</span>
|
|
3542
|
-
<span class="group-name">/${name}</span>
|
|
3543
|
-
<span class="group-count">${pages.length}</span>
|
|
3330
|
+
</div>`}).join("");return `<div class="group">
|
|
3331
|
+
<div class="group-header" onclick="toggleGroup(this)" style="--group-color:${e}">
|
|
3332
|
+
<span class="group-arrow">\u25BC</span>
|
|
3333
|
+
<span class="group-name">/${t}</span>
|
|
3334
|
+
<span class="group-count">${l.length}</span>
|
|
3544
3335
|
</div>
|
|
3545
|
-
<div class="group-content">${
|
|
3546
|
-
</div
|
|
3547
|
-
})
|
|
3548
|
-
.join('');
|
|
3549
|
-
}
|
|
3550
|
-
getPageType(path) {
|
|
3551
|
-
const last = path.split('/').filter(Boolean).pop() || '';
|
|
3552
|
-
if (last === 'new' || path.endsWith('/new'))
|
|
3553
|
-
return { label: 'CREATE', color: '#22c55e' };
|
|
3554
|
-
if (last === 'edit' || path.includes('/edit'))
|
|
3555
|
-
return { label: 'EDIT', color: '#f59e0b' };
|
|
3556
|
-
if (last.startsWith('[') || last.startsWith(':'))
|
|
3557
|
-
return { label: 'DETAIL', color: '#3b82f6' };
|
|
3558
|
-
if (path.includes('setting'))
|
|
3559
|
-
return { label: 'SETTINGS', color: '#6b7280' };
|
|
3560
|
-
return { label: 'LIST', color: '#06b6d4' };
|
|
3561
|
-
}
|
|
3562
|
-
}
|
|
3336
|
+
<div class="group-content">${f}</div>
|
|
3337
|
+
</div>`}).join("")}getPageType(s){let r=s.split("/").filter(Boolean).pop()||"";return r==="new"||s.endsWith("/new")?{label:"CREATE",color:"#22c55e"}:r==="edit"||s.includes("/edit")?{label:"EDIT",color:"#f59e0b"}:r.startsWith("[")||r.startsWith(":")?{label:"DETAIL",color:"#3b82f6"}:s.includes("setting")?{label:"SETTINGS",color:"#6b7280"}:{label:"LIST",color:"#06b6d4"}}};export{S as a};
|