@wtdlee/repomap 0.3.0 → 0.3.1
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/chunk-3PWXDB7B.js +153 -0
- package/dist/{generators/page-map-generator.js → chunk-3YFXZAP7.js} +322 -358
- package/dist/chunk-6F4PWJZI.js +1 -0
- package/dist/{generators/rails-map-generator.js → chunk-E4WRODSI.js} +86 -94
- package/dist/chunk-GNBMJMET.js +2519 -0
- package/dist/{server/doc-server.js → chunk-M6YNU536.js} +702 -303
- package/dist/chunk-OWM6WNLE.js +2610 -0
- package/dist/chunk-SSU6QFTX.js +1058 -0
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +348 -452
- package/dist/dataflow-analyzer-BfAiqVUp.d.ts +180 -0
- package/dist/env-detector-EEMVUEIA.js +1 -0
- package/dist/generators/index.d.ts +431 -3
- package/dist/generators/index.js +2 -3
- package/dist/index.d.ts +53 -10
- package/dist/index.js +8 -11
- package/dist/page-map-generator-6MJGPBVA.js +1 -0
- package/dist/rails-UWSDRS33.js +1 -0
- package/dist/rails-map-generator-D2URLMVJ.js +2 -0
- package/dist/server/index.d.ts +33 -1
- package/dist/server/index.js +7 -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,128 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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,
|
|
1
|
+
// src/generators/page-map-generator.ts
|
|
2
|
+
var PageMapGenerator = class {
|
|
3
|
+
graphqlOps = [];
|
|
4
|
+
apiCalls = [];
|
|
5
|
+
components = [];
|
|
6
|
+
generatePageMapHtml(report, options) {
|
|
7
|
+
const allPages = [];
|
|
8
|
+
const envResult = options?.envResult;
|
|
9
|
+
const railsAnalysis = options?.railsAnalysis;
|
|
10
|
+
const activeTab = options?.activeTab || "pages";
|
|
11
|
+
const repoName = report.repositories[0]?.displayName || report.repositories[0]?.name || "Repository";
|
|
12
|
+
for (const repoResult of report.repositories) {
|
|
13
|
+
this.graphqlOps.push(...repoResult.analysis?.graphqlOperations || []);
|
|
14
|
+
this.apiCalls.push(...repoResult.analysis?.apiCalls || []);
|
|
15
|
+
const comps = repoResult.analysis?.components || [];
|
|
16
|
+
for (const comp of comps) {
|
|
17
|
+
this.components.push({
|
|
18
|
+
name: comp.name,
|
|
19
|
+
filePath: comp.filePath,
|
|
20
|
+
type: comp.type,
|
|
21
|
+
dependencies: comp.dependencies || []
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
for (const repoResult of report.repositories) {
|
|
26
|
+
const pages = repoResult.analysis?.pages || [];
|
|
27
|
+
for (const page of pages) {
|
|
28
|
+
allPages.push({
|
|
29
|
+
...page,
|
|
30
|
+
repo: repoResult.name,
|
|
31
|
+
children: [],
|
|
32
|
+
parent: null,
|
|
33
|
+
depth: 0
|
|
46
34
|
});
|
|
35
|
+
}
|
|
47
36
|
}
|
|
48
|
-
buildHierarchy(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
37
|
+
const { rootPages, relations } = this.buildHierarchy(allPages);
|
|
38
|
+
return this.renderPageMapHtml(allPages, rootPages, relations, repoName, {
|
|
39
|
+
envResult,
|
|
40
|
+
railsAnalysis,
|
|
41
|
+
activeTab
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
buildHierarchy(pages) {
|
|
45
|
+
const pathMap = /* @__PURE__ */ new Map();
|
|
46
|
+
const relations = [];
|
|
47
|
+
for (const page of pages) {
|
|
48
|
+
pathMap.set(page.path, page);
|
|
49
|
+
}
|
|
50
|
+
for (const page of pages) {
|
|
51
|
+
const segments = page.path.split("/").filter(Boolean);
|
|
52
|
+
for (let i = segments.length - 1; i >= 1; i--) {
|
|
53
|
+
const parentPath = "/" + segments.slice(0, i).join("/");
|
|
54
|
+
const parent = pathMap.get(parentPath);
|
|
55
|
+
if (parent) {
|
|
56
|
+
page.parent = parentPath;
|
|
57
|
+
page.depth = parent.depth + 1;
|
|
58
|
+
if (!parent.children.includes(page.path)) {
|
|
59
|
+
parent.children.push(page.path);
|
|
60
|
+
}
|
|
61
|
+
relations.push({
|
|
62
|
+
from: parentPath,
|
|
63
|
+
to: page.path,
|
|
64
|
+
type: "parent-child",
|
|
65
|
+
description: `Sub-page of ${parentPath}`
|
|
66
|
+
});
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (!page.parent) {
|
|
71
|
+
page.depth = Math.max(0, segments.length - 1);
|
|
72
|
+
}
|
|
73
|
+
if (page.layout) {
|
|
74
|
+
for (const other of pages) {
|
|
75
|
+
if (other.path !== page.path && other.layout === page.layout) {
|
|
76
|
+
const existing = relations.find(
|
|
77
|
+
(r) => r.type === "same-layout" && (r.from === page.path && r.to === other.path || r.from === other.path && r.to === page.path)
|
|
78
|
+
);
|
|
79
|
+
if (!existing) {
|
|
80
|
+
relations.push({
|
|
81
|
+
from: page.path,
|
|
82
|
+
to: other.path,
|
|
83
|
+
type: "same-layout",
|
|
84
|
+
description: `Both use ${page.layout}`
|
|
85
|
+
});
|
|
95
86
|
}
|
|
87
|
+
}
|
|
96
88
|
}
|
|
97
|
-
|
|
98
|
-
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const rootPages = pages.filter((p) => !p.parent).sort((a, b) => a.path.localeCompare(b.path));
|
|
92
|
+
return { rootPages, relations };
|
|
93
|
+
}
|
|
94
|
+
renderPageMapHtml(allPages, rootPages, relations, repoName, options) {
|
|
95
|
+
const envResult = options?.envResult;
|
|
96
|
+
const railsAnalysis = options?.railsAnalysis;
|
|
97
|
+
const activeTab = options?.activeTab || "pages";
|
|
98
|
+
const graphqlOpsJson = JSON.stringify(
|
|
99
|
+
this.graphqlOps.map((op) => ({
|
|
100
|
+
name: op.name,
|
|
101
|
+
type: op.type,
|
|
102
|
+
variables: op.variables,
|
|
103
|
+
fields: op.fields,
|
|
104
|
+
returnType: op.returnType,
|
|
105
|
+
usedIn: op.usedIn
|
|
106
|
+
}))
|
|
107
|
+
);
|
|
108
|
+
const componentsJson = JSON.stringify(this.components);
|
|
109
|
+
const railsRoutesJson = railsAnalysis ? JSON.stringify(railsAnalysis.routes.routes) : "[]";
|
|
110
|
+
const railsControllersJson = railsAnalysis ? JSON.stringify(railsAnalysis.controllers.controllers) : "[]";
|
|
111
|
+
const railsModelsJson = railsAnalysis ? JSON.stringify(railsAnalysis.models.models) : "[]";
|
|
112
|
+
const railsViewsJson = railsAnalysis ? JSON.stringify(railsAnalysis.views) : '{ "views": [], "pages": [], "summary": {} }';
|
|
113
|
+
const railsReactJson = railsAnalysis ? JSON.stringify(railsAnalysis.react) : '{ "components": [], "entryPoints": [], "summary": {} }';
|
|
114
|
+
const railsGrpcJson = railsAnalysis ? JSON.stringify(railsAnalysis.grpc) : '{ "services": [] }';
|
|
115
|
+
const railsSummaryJson = railsAnalysis ? JSON.stringify(railsAnalysis.summary) : "null";
|
|
116
|
+
const hasRails = envResult?.hasRails || false;
|
|
117
|
+
const hasNextjs = envResult?.hasNextjs || false;
|
|
118
|
+
const hasReact = envResult?.hasReact || false;
|
|
119
|
+
const groups = /* @__PURE__ */ new Map();
|
|
120
|
+
for (const page of allPages) {
|
|
121
|
+
const seg = page.path.split("/").filter(Boolean)[0] || "root";
|
|
122
|
+
if (!groups.has(seg)) groups.set(seg, []);
|
|
123
|
+
groups.get(seg)?.push(page);
|
|
99
124
|
}
|
|
100
|
-
|
|
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>
|
|
125
|
+
return `<!DOCTYPE html>
|
|
140
126
|
<html lang="en">
|
|
141
127
|
<head>
|
|
142
128
|
<meta charset="UTF-8">
|
|
@@ -147,23 +133,21 @@ export class PageMapGenerator {
|
|
|
147
133
|
<body>
|
|
148
134
|
<header class="header">
|
|
149
135
|
<div style="display:flex;align-items:center;gap:24px">
|
|
150
|
-
<h1 style="cursor:pointer" onclick="location.href='/'"
|
|
136
|
+
<h1 style="cursor:pointer" onclick="location.href='/'">\u{1F4CA} ${repoName}</h1>
|
|
151
137
|
<nav style="display:flex;gap:4px">
|
|
152
|
-
<a href="/page-map" class="nav-link ${activeTab ===
|
|
153
|
-
${hasRails ? `<a href="/rails-map" class="nav-link ${activeTab ===
|
|
138
|
+
<a href="/page-map" class="nav-link ${activeTab === "pages" ? "active" : ""}">Page Map</a>
|
|
139
|
+
${hasRails ? `<a href="/rails-map" class="nav-link ${activeTab === "rails" ? "active" : ""}">Rails Map</a>` : ""}
|
|
154
140
|
<a href="/docs" class="nav-link">Docs</a>
|
|
155
141
|
<a href="/api/report" class="nav-link" target="_blank">API</a>
|
|
156
142
|
</nav>
|
|
157
143
|
</div>
|
|
158
144
|
<div style="display:flex;gap:12px;align-items:center">
|
|
159
145
|
<!-- Environment filter badges -->
|
|
160
|
-
${hasRails && hasNextjs
|
|
161
|
-
? `<div class="env-filters" style="display:flex;gap:4px;margin-right:8px">
|
|
146
|
+
${hasRails && hasNextjs ? `<div class="env-filters" style="display:flex;gap:4px;margin-right:8px">
|
|
162
147
|
<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
|
-
: ''}
|
|
148
|
+
<button class="env-badge" data-env="nextjs" onclick="filterByEnv('nextjs')">\u269B\uFE0F Next.js</button>
|
|
149
|
+
<button class="env-badge" data-env="rails" onclick="filterByEnv('rails')">\u{1F6E4}\uFE0F Rails</button>
|
|
150
|
+
</div>` : ""}
|
|
167
151
|
<input class="search" type="text" placeholder="Search pages, queries..." oninput="filter(this.value)">
|
|
168
152
|
<div class="tabs">
|
|
169
153
|
<button class="tab active" onclick="setView('tree')">List</button>
|
|
@@ -199,13 +183,12 @@ export class PageMapGenerator {
|
|
|
199
183
|
<h3 style="margin-top:16px;font-size:10px;text-transform:uppercase;color:var(--text2);letter-spacing:1px">Frontend</h3>
|
|
200
184
|
<div class="stats" id="stats-container">
|
|
201
185
|
<div class="stat" data-filter="pages"><div class="stat-val">${allPages.length}</div><div class="stat-label">Pages</div></div>
|
|
202
|
-
<div class="stat" data-filter="hierarchies"><div class="stat-val">${relations.filter((r) => r.type ===
|
|
186
|
+
<div class="stat" data-filter="hierarchies"><div class="stat-val">${relations.filter((r) => r.type === "parent-child").length}</div><div class="stat-label">Hierarchies</div></div>
|
|
203
187
|
<div class="stat" data-filter="graphql"><div class="stat-val">${this.graphqlOps.length}</div><div class="stat-label">GraphQL</div></div>
|
|
204
188
|
<div class="stat" data-filter="restapi"><div class="stat-val">${this.apiCalls.length}</div><div class="stat-label">REST API</div></div>
|
|
205
189
|
</div>
|
|
206
190
|
|
|
207
|
-
${hasRails && railsAnalysis
|
|
208
|
-
? `
|
|
191
|
+
${hasRails && railsAnalysis ? `
|
|
209
192
|
<!-- Rails Stats -->
|
|
210
193
|
<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
194
|
<div class="stats" id="rails-stats">
|
|
@@ -213,25 +196,24 @@ export class PageMapGenerator {
|
|
|
213
196
|
<div class="stat" data-filter="rails-controllers" onclick="showRailsControllers(); this.blur();"><div class="stat-val">${railsAnalysis.summary.totalControllers}</div><div class="stat-label">Controllers</div></div>
|
|
214
197
|
<div class="stat" data-filter="rails-models" onclick="showRailsModels(); this.blur();"><div class="stat-val">${railsAnalysis.summary.totalModels}</div><div class="stat-label">Models</div></div>
|
|
215
198
|
<div class="stat" data-filter="rails-grpc" onclick="showRailsGrpc(); this.blur();"><div class="stat-val">${railsAnalysis.summary.totalGrpcServices}</div><div class="stat-label">gRPC</div></div>
|
|
216
|
-
<div class="stat" data-filter="rails-react" onclick="showReactComponents(); this.blur();"><div class="stat-val">${railsAnalysis.summary.totalReactComponents}</div><div class="stat-label"
|
|
199
|
+
<div class="stat" data-filter="rails-react" onclick="showReactComponents(); this.blur();"><div class="stat-val">${railsAnalysis.summary.totalReactComponents}</div><div class="stat-label">\u269B React</div></div>
|
|
217
200
|
</div>
|
|
218
|
-
`
|
|
219
|
-
: ''}
|
|
201
|
+
` : ""}
|
|
220
202
|
</aside>
|
|
221
203
|
|
|
222
204
|
<div class="content">
|
|
223
205
|
<!-- Pages Tree View (for all screens - Next.js/React/Rails) -->
|
|
224
|
-
<div class="tree-view ${activeTab ===
|
|
225
|
-
${allPages.length > 0 ? this.buildTreeHtml(groups, allPages) :
|
|
226
|
-
<div id="page-map-react-components-section" style="${hasRails ?
|
|
206
|
+
<div class="tree-view ${activeTab === "pages" ? "active" : ""}" id="tree-view" data-tab="pages">
|
|
207
|
+
${allPages.length > 0 ? this.buildTreeHtml(groups, allPages) : ""}
|
|
208
|
+
<div id="page-map-react-components-section" style="${hasRails ? "margin-top:20px;border-top:1px solid var(--bg3);padding-top:20px" : ""}">
|
|
227
209
|
</div>
|
|
228
|
-
<div id="page-map-rails-section" style="${allPages.length > 0 && hasRails ?
|
|
229
|
-
${hasRails && allPages.length === 0 ? '<div style="padding:20px;color:var(--text2)">Loading screens...</div>' :
|
|
210
|
+
<div id="page-map-rails-section" style="${allPages.length > 0 && hasRails ? "margin-top:20px;border-top:1px solid var(--bg3);padding-top:20px" : ""}">
|
|
211
|
+
${hasRails && allPages.length === 0 ? '<div style="padding:20px;color:var(--text2)">Loading screens...</div>' : ""}
|
|
230
212
|
</div>
|
|
231
213
|
</div>
|
|
232
214
|
|
|
233
215
|
<!-- Rails Routes View (dedicated) -->
|
|
234
|
-
<div class="tree-view ${activeTab ===
|
|
216
|
+
<div class="tree-view ${activeTab === "rails" ? "active" : ""}" id="rails-tree-view" data-tab="rails">
|
|
235
217
|
<div id="rails-routes-container">
|
|
236
218
|
${hasRails ? '<div style="padding:20px;color:var(--text2)">Loading Rails routes...</div>' : '<div style="padding:40px;text-align:center;color:var(--text2)">No Rails environment detected</div>'}
|
|
237
219
|
</div>
|
|
@@ -254,7 +236,7 @@ export class PageMapGenerator {
|
|
|
254
236
|
<div class="detail" id="detail">
|
|
255
237
|
<div class="detail-header">
|
|
256
238
|
<div class="detail-title" id="detail-title"></div>
|
|
257
|
-
<button class="detail-close" onclick="closeDetail()"
|
|
239
|
+
<button class="detail-close" onclick="closeDetail()">\xD7</button>
|
|
258
240
|
</div>
|
|
259
241
|
<div class="detail-body" id="detail-body"></div>
|
|
260
242
|
</div>
|
|
@@ -263,10 +245,10 @@ export class PageMapGenerator {
|
|
|
263
245
|
<div class="modal-box">
|
|
264
246
|
<div class="modal-head">
|
|
265
247
|
<div style="display:flex;align-items:center;gap:8px">
|
|
266
|
-
<button id="modal-back" class="modal-back" onclick="modalBack()" style="display:none"
|
|
248
|
+
<button id="modal-back" class="modal-back" onclick="modalBack()" style="display:none">\u2190</button>
|
|
267
249
|
<h3 id="modal-title"></h3>
|
|
268
250
|
</div>
|
|
269
|
-
<button class="modal-close" onclick="closeModal()"
|
|
251
|
+
<button class="modal-close" onclick="closeModal()">\xD7</button>
|
|
270
252
|
</div>
|
|
271
253
|
<div class="modal-body" id="modal-body"></div>
|
|
272
254
|
</div>
|
|
@@ -398,7 +380,7 @@ export class PageMapGenerator {
|
|
|
398
380
|
let html = '<div style="max-height:60vh;overflow-y:auto">';
|
|
399
381
|
for (const [ns, routes] of routesByNamespace) {
|
|
400
382
|
html += '<div style="margin-bottom:16px">';
|
|
401
|
-
html += '<div style="font-weight:600;margin-bottom:8px;color:var(--accent)"
|
|
383
|
+
html += '<div style="font-weight:600;margin-bottom:8px;color:var(--accent)">\u{1F4C2} ' + ns + ' (' + routes.length + ')</div>';
|
|
402
384
|
html += '<table style="width:100%;border-collapse:collapse;font-size:12px">';
|
|
403
385
|
html += '<tr style="background:var(--bg3)"><th style="padding:6px;text-align:left">Method</th><th style="padding:6px;text-align:left">Path</th><th style="padding:6px;text-align:left">Controller#Action</th></tr>';
|
|
404
386
|
routes.slice(0, 20).forEach(r => {
|
|
@@ -416,7 +398,7 @@ export class PageMapGenerator {
|
|
|
416
398
|
}
|
|
417
399
|
html += '</div>';
|
|
418
400
|
|
|
419
|
-
showModal('
|
|
401
|
+
showModal('\u{1F6E4}\uFE0F Rails Routes (' + railsRoutes.length + ')', html);
|
|
420
402
|
}
|
|
421
403
|
|
|
422
404
|
function showRailsControllers() {
|
|
@@ -443,7 +425,7 @@ export class PageMapGenerator {
|
|
|
443
425
|
});
|
|
444
426
|
html += '</div>';
|
|
445
427
|
|
|
446
|
-
showModal('
|
|
428
|
+
showModal('\u{1F3AE} Rails Controllers (' + railsControllers.length + ')', html);
|
|
447
429
|
}
|
|
448
430
|
|
|
449
431
|
function showRailsModels() {
|
|
@@ -455,10 +437,10 @@ export class PageMapGenerator {
|
|
|
455
437
|
let html = '<div style="max-height:60vh;overflow-y:auto">';
|
|
456
438
|
railsModels.forEach(model => {
|
|
457
439
|
html += '<div style="background:var(--bg3);padding:12px;border-radius:6px;margin-bottom:8px">';
|
|
458
|
-
html += '<div style="font-weight:600;margin-bottom:4px"
|
|
440
|
+
html += '<div style="font-weight:600;margin-bottom:4px">\u{1F4E6} ' + model.className + '</div>';
|
|
459
441
|
html += '<div style="display:flex;gap:16px;font-size:11px;color:var(--text2);margin-bottom:8px">';
|
|
460
|
-
html += '<span
|
|
461
|
-
html += '<span
|
|
442
|
+
html += '<span>\u{1F4CE} ' + (model.associations?.length || 0) + ' associations</span>';
|
|
443
|
+
html += '<span>\u2713 ' + (model.validations?.length || 0) + ' validations</span>';
|
|
462
444
|
html += '</div>';
|
|
463
445
|
if (model.associations && model.associations.length > 0) {
|
|
464
446
|
html += '<div style="display:flex;flex-wrap:wrap;gap:4px">';
|
|
@@ -473,7 +455,7 @@ export class PageMapGenerator {
|
|
|
473
455
|
});
|
|
474
456
|
html += '</div>';
|
|
475
457
|
|
|
476
|
-
showModal('
|
|
458
|
+
showModal('\u{1F4E6} Rails Models (' + railsModels.length + ')', html);
|
|
477
459
|
}
|
|
478
460
|
|
|
479
461
|
function showReactComponents() {
|
|
@@ -503,18 +485,18 @@ export class PageMapGenerator {
|
|
|
503
485
|
|
|
504
486
|
html += '<div style="background:var(--bg3);padding:12px;border-radius:6px;margin-bottom:8px;cursor:pointer" onclick="showReactComponentDetail(\\'' + encodeURIComponent(JSON.stringify(comp)) + '\\')">';
|
|
505
487
|
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 style="color:#61dafb;margin-right:6px"
|
|
488
|
+
html += '<div style="font-weight:600;display:flex;align-items:center"><span style="color:#61dafb;margin-right:6px">\u269B</span>' + comp.name + ssrBadge + '</div>';
|
|
507
489
|
html += '<span style="font-size:11px;color:var(--text2)">' + usageCount + ' usage' + (usageCount !== 1 ? 's' : '') + '</span>';
|
|
508
490
|
html += '</div>';
|
|
509
491
|
|
|
510
492
|
// Entry point info
|
|
511
493
|
if (comp.entryFile) {
|
|
512
|
-
html += '<div style="font-size:10px;color:var(--text2);margin-top:4px;font-family:monospace"
|
|
494
|
+
html += '<div style="font-size:10px;color:var(--text2);margin-top:4px;font-family:monospace">\u{1F4E5} entries/' + comp.entryFile + '</div>';
|
|
513
495
|
}
|
|
514
496
|
|
|
515
497
|
// Source file info
|
|
516
498
|
if (comp.sourceFile) {
|
|
517
|
-
html += '<div style="font-size:10px;color:var(--accent);margin-top:2px;font-family:monospace"
|
|
499
|
+
html += '<div style="font-size:10px;color:var(--accent);margin-top:2px;font-family:monospace">\u{1F4C4} ' + comp.sourceFile + '</div>';
|
|
518
500
|
}
|
|
519
501
|
|
|
520
502
|
// Usage preview
|
|
@@ -534,7 +516,7 @@ export class PageMapGenerator {
|
|
|
534
516
|
});
|
|
535
517
|
|
|
536
518
|
html += '</div>';
|
|
537
|
-
showModal('
|
|
519
|
+
showModal('\u269B React Components (' + railsReact.components.length + ')', html);
|
|
538
520
|
}
|
|
539
521
|
|
|
540
522
|
function showReactComponentDetail(encodedData) {
|
|
@@ -544,7 +526,7 @@ export class PageMapGenerator {
|
|
|
544
526
|
|
|
545
527
|
// Component Info
|
|
546
528
|
html += '<div class="detail-section">';
|
|
547
|
-
html += '<div class="detail-label"
|
|
529
|
+
html += '<div class="detail-label">\u269B Component Name</div>';
|
|
548
530
|
html += '<div style="display:flex;align-items:center;gap:8px">';
|
|
549
531
|
html += '<span style="font-family:monospace;font-size:16px;font-weight:600">' + comp.name + '</span>';
|
|
550
532
|
if (comp.ssr) {
|
|
@@ -557,7 +539,7 @@ export class PageMapGenerator {
|
|
|
557
539
|
// Entry Point
|
|
558
540
|
if (comp.entryFile) {
|
|
559
541
|
html += '<div class="detail-section">';
|
|
560
|
-
html += '<div class="detail-label"
|
|
542
|
+
html += '<div class="detail-label">\u{1F4E5} Entry Point</div>';
|
|
561
543
|
html += '<div class="code-path">';
|
|
562
544
|
html += comp.entryFile;
|
|
563
545
|
html += '</div></div>';
|
|
@@ -566,7 +548,7 @@ export class PageMapGenerator {
|
|
|
566
548
|
// Source File
|
|
567
549
|
if (comp.sourceFile || comp.importPath) {
|
|
568
550
|
html += '<div class="detail-section">';
|
|
569
|
-
html += '<div class="detail-label"
|
|
551
|
+
html += '<div class="detail-label">\u{1F4C4} Source File</div>';
|
|
570
552
|
html += '<div class="code-path" style="color:var(--accent)">';
|
|
571
553
|
html += comp.sourceFile || comp.importPath;
|
|
572
554
|
html += '</div></div>';
|
|
@@ -575,7 +557,7 @@ export class PageMapGenerator {
|
|
|
575
557
|
// Usage in Views
|
|
576
558
|
if (comp.usedIn && comp.usedIn.length > 0) {
|
|
577
559
|
html += '<div class="detail-section">';
|
|
578
|
-
html += '<div class="detail-label"
|
|
560
|
+
html += '<div class="detail-label">\u{1F4CD} Used in Views (' + comp.usedIn.length + ')</div>';
|
|
579
561
|
html += '<div class="detail-items">';
|
|
580
562
|
|
|
581
563
|
comp.usedIn.forEach(usage => {
|
|
@@ -600,7 +582,7 @@ export class PageMapGenerator {
|
|
|
600
582
|
html += '</div></div>';
|
|
601
583
|
}
|
|
602
584
|
|
|
603
|
-
showModal('
|
|
585
|
+
showModal('\u269B ' + comp.name, html, true);
|
|
604
586
|
}
|
|
605
587
|
|
|
606
588
|
function showRailsGrpc() {
|
|
@@ -612,7 +594,7 @@ export class PageMapGenerator {
|
|
|
612
594
|
let html = '<div style="max-height:60vh;overflow-y:auto">';
|
|
613
595
|
railsGrpc.services.forEach(svc => {
|
|
614
596
|
html += '<div style="background:var(--bg3);padding:12px;border-radius:6px;margin-bottom:8px">';
|
|
615
|
-
html += '<div style="font-weight:600;margin-bottom:4px"
|
|
597
|
+
html += '<div style="font-weight:600;margin-bottom:4px">\u{1F50C} ' + svc.className + '</div>';
|
|
616
598
|
if (svc.namespace) {
|
|
617
599
|
html += '<div style="font-size:11px;color:var(--text2);margin-bottom:8px">namespace: ' + svc.namespace + '</div>';
|
|
618
600
|
}
|
|
@@ -628,7 +610,7 @@ export class PageMapGenerator {
|
|
|
628
610
|
});
|
|
629
611
|
html += '</div>';
|
|
630
612
|
|
|
631
|
-
showModal('
|
|
613
|
+
showModal('\u{1F50C} gRPC Services (' + railsGrpc.services.length + ')', html);
|
|
632
614
|
}
|
|
633
615
|
|
|
634
616
|
// Render Rails routes in tree view
|
|
@@ -674,7 +656,7 @@ export class PageMapGenerator {
|
|
|
674
656
|
controllerInfo = railsControllers.find(c => {
|
|
675
657
|
// Strategy 1: Match by filePath (most accurate)
|
|
676
658
|
// filePath: "api/v1/users_controller.rb" or "users_controller.rb"
|
|
677
|
-
const filePathNormalized = c.filePath.replace(/_controller
|
|
659
|
+
const filePathNormalized = c.filePath.replace(/_controller.rb$/, '').replace(/_/g, '');
|
|
678
660
|
if (filePathNormalized === routeCtrl.replace(/_/g, '')) return true;
|
|
679
661
|
|
|
680
662
|
// Strategy 2: Match by controller name (without namespace)
|
|
@@ -771,7 +753,7 @@ export class PageMapGenerator {
|
|
|
771
753
|
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
754
|
const coverageClass = coverage > 70 ? 'coverage-high' : coverage > 40 ? 'coverage-mid' : 'coverage-low';
|
|
773
755
|
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)
|
|
756
|
+
html += '<div class="coverage-text" title="' + coverageTooltip + '">Action Details Coverage: <span class="' + coverageClass + '">' + coverage + '%</span> (' + totalWithActionInfo + '/' + combinedData.length + ' routes analyzed) \u2139\uFE0F</div>';
|
|
775
757
|
html += '</div>';
|
|
776
758
|
}
|
|
777
759
|
html += '</div>';
|
|
@@ -782,8 +764,8 @@ export class PageMapGenerator {
|
|
|
782
764
|
|
|
783
765
|
html += '<div class="group">';
|
|
784
766
|
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"
|
|
767
|
+
html += '<span class="group-toggle">\u25BC</span>';
|
|
768
|
+
html += '<span class="group-name">\u{1F4C2} ' + ns + '</span>';
|
|
787
769
|
html += '<span class="group-count">' + routes.length + '</span>';
|
|
788
770
|
html += '</div>';
|
|
789
771
|
const routeListId = 'routes-' + ns.replace(/[^a-zA-Z0-9]/g, '-');
|
|
@@ -803,7 +785,7 @@ export class PageMapGenerator {
|
|
|
803
785
|
if (displayPath.includes('=>') || displayPath.includes('redirect')) {
|
|
804
786
|
// Extract just the route pattern before any redirect logic
|
|
805
787
|
const match = displayPath.match(/^([^"]+)"/);
|
|
806
|
-
displayPath = match ? match[1].trim() + '
|
|
788
|
+
displayPath = match ? match[1].trim() + ' \u2192 redirect' : displayPath.slice(0, 60) + '...';
|
|
807
789
|
}
|
|
808
790
|
if (displayPath.length > 80) {
|
|
809
791
|
displayPath = displayPath.slice(0, 77) + '...';
|
|
@@ -817,7 +799,7 @@ export class PageMapGenerator {
|
|
|
817
799
|
if (action) {
|
|
818
800
|
if (action.rendersJson) indicators += '<span class="route-tag route-tag-json" title="Returns JSON">JSON</span>';
|
|
819
801
|
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"
|
|
802
|
+
if (action.redirectsTo) indicators += '<span class="route-tag route-tag-redirect" title="Redirects">\u2192</span>';
|
|
821
803
|
}
|
|
822
804
|
if (route.hasView) indicators += '<span class="route-tag route-tag-view" title="Has View Template">View</span>';
|
|
823
805
|
if (route.services.length > 0) indicators += '<span class="route-tag route-tag-svc" title="Uses Services: ' + route.services.join(', ') + '">Svc</span>';
|
|
@@ -846,7 +828,7 @@ export class PageMapGenerator {
|
|
|
846
828
|
html += '</div>';
|
|
847
829
|
|
|
848
830
|
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 + ')"
|
|
831
|
+
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
832
|
}
|
|
851
833
|
|
|
852
834
|
html += '</div>';
|
|
@@ -935,7 +917,7 @@ export class PageMapGenerator {
|
|
|
935
917
|
// Response Type - NEW
|
|
936
918
|
if (action) {
|
|
937
919
|
html += '<div class="detail-section">';
|
|
938
|
-
html += '<div class="detail-label"
|
|
920
|
+
html += '<div class="detail-label">\u{1F4E1} Response Type</div>';
|
|
939
921
|
html += '<div class="detail-value">';
|
|
940
922
|
const responseTypes = [];
|
|
941
923
|
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>');
|
|
@@ -952,7 +934,7 @@ export class PageMapGenerator {
|
|
|
952
934
|
// Redirect destination if exists
|
|
953
935
|
if (action.redirectsTo) {
|
|
954
936
|
html += '<div class="detail-section">';
|
|
955
|
-
html += '<div class="detail-label"
|
|
937
|
+
html += '<div class="detail-label">\u21AA\uFE0F Redirects To</div>';
|
|
956
938
|
html += '<div class="detail-value" style="font-family:monospace;font-size:12px;background:var(--bg3);padding:8px;border-radius:4px">' + action.redirectsTo + '</div>';
|
|
957
939
|
html += '</div>';
|
|
958
940
|
}
|
|
@@ -961,7 +943,7 @@ export class PageMapGenerator {
|
|
|
961
943
|
// View info
|
|
962
944
|
if (route.hasView && route.view) {
|
|
963
945
|
html += '<div class="detail-section">';
|
|
964
|
-
html += '<div class="detail-label"
|
|
946
|
+
html += '<div class="detail-label">\u{1F4C4} View Template</div>';
|
|
965
947
|
html += '<div class="detail-value" style="font-family:monospace;font-size:12px">app/views/' + route.view.path + '</div>';
|
|
966
948
|
if (route.view.partials && route.view.partials.length > 0) {
|
|
967
949
|
html += '<div style="margin-top:6px;font-size:11px;color:var(--text2)">Partials: ' + route.view.partials.slice(0, 5).join(', ') + (route.view.partials.length > 5 ? '...' : '') + '</div>';
|
|
@@ -975,7 +957,7 @@ export class PageMapGenerator {
|
|
|
975
957
|
// Before/After Filters - NEW
|
|
976
958
|
if (ctrl && (ctrl.beforeActions.length > 0 || ctrl.afterActions.length > 0)) {
|
|
977
959
|
html += '<div class="detail-section">';
|
|
978
|
-
html += '<div class="detail-label"
|
|
960
|
+
html += '<div class="detail-label">\u{1F512} Filters Applied to This Action</div>';
|
|
979
961
|
html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px">';
|
|
980
962
|
|
|
981
963
|
// Filter before_actions that apply to this action
|
|
@@ -1023,7 +1005,7 @@ export class PageMapGenerator {
|
|
|
1023
1005
|
const services = route.services && route.services.length > 0 ? route.services : (action?.servicesCalled || []);
|
|
1024
1006
|
if (services.length > 0) {
|
|
1025
1007
|
html += '<div class="detail-section">';
|
|
1026
|
-
html += '<div class="detail-label"
|
|
1008
|
+
html += '<div class="detail-label">\u2699\uFE0F Services Called</div>';
|
|
1027
1009
|
html += '<div class="detail-items">';
|
|
1028
1010
|
services.forEach(s => {
|
|
1029
1011
|
html += '<div class="detail-item"><span class="tag" style="background:#8b5cf6">Service</span><span class="name" style="font-family:monospace">' + s + '</span></div>';
|
|
@@ -1034,7 +1016,7 @@ export class PageMapGenerator {
|
|
|
1034
1016
|
// gRPC Calls
|
|
1035
1017
|
if (route.grpcCalls && route.grpcCalls.length > 0) {
|
|
1036
1018
|
html += '<div class="detail-section">';
|
|
1037
|
-
html += '<div class="detail-label"
|
|
1019
|
+
html += '<div class="detail-label">\u{1F50C} gRPC Calls</div>';
|
|
1038
1020
|
html += '<div class="detail-items">';
|
|
1039
1021
|
route.grpcCalls.forEach(g => {
|
|
1040
1022
|
html += '<div class="detail-item"><span class="tag" style="background:#06b6d4">gRPC</span><span class="name" style="font-family:monospace">' + g + '</span></div>';
|
|
@@ -1046,7 +1028,7 @@ export class PageMapGenerator {
|
|
|
1046
1028
|
const models = route.modelAccess && route.modelAccess.length > 0 ? route.modelAccess : (action?.modelsCalled || []);
|
|
1047
1029
|
if (models.length > 0) {
|
|
1048
1030
|
html += '<div class="detail-section">';
|
|
1049
|
-
html += '<div class="detail-label"
|
|
1031
|
+
html += '<div class="detail-label">\u{1F4BE} Models Accessed</div>';
|
|
1050
1032
|
html += '<div class="detail-items">';
|
|
1051
1033
|
models.forEach(m => {
|
|
1052
1034
|
html += '<div class="detail-item"><span class="tag" style="background:#f59e0b">Model</span><span class="name" style="font-family:monospace">' + m + '</span></div>';
|
|
@@ -1064,7 +1046,7 @@ export class PageMapGenerator {
|
|
|
1064
1046
|
|
|
1065
1047
|
if (meaningfulCalls.length > 0) {
|
|
1066
1048
|
html += '<div class="detail-section">';
|
|
1067
|
-
html += '<div class="detail-label"
|
|
1049
|
+
html += '<div class="detail-label">\u{1F517} Method Calls in Action</div>';
|
|
1068
1050
|
html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px;max-height:150px;overflow-y:auto">';
|
|
1069
1051
|
html += '<div style="font-family:monospace;font-size:11px;line-height:1.6">';
|
|
1070
1052
|
meaningfulCalls.forEach((call, i) => {
|
|
@@ -1082,7 +1064,7 @@ export class PageMapGenerator {
|
|
|
1082
1064
|
|
|
1083
1065
|
// Source Files - NEW
|
|
1084
1066
|
html += '<div class="detail-section">';
|
|
1085
|
-
html += '<div class="detail-label"
|
|
1067
|
+
html += '<div class="detail-label">\u{1F4C1} Source Files</div>';
|
|
1086
1068
|
html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px;font-family:monospace;font-size:11px">';
|
|
1087
1069
|
|
|
1088
1070
|
if (route.line > 0) {
|
|
@@ -1111,7 +1093,7 @@ export class PageMapGenerator {
|
|
|
1111
1093
|
// Controller Info Summary
|
|
1112
1094
|
if (ctrl) {
|
|
1113
1095
|
html += '<div class="detail-section">';
|
|
1114
|
-
html += '<div class="detail-label"
|
|
1096
|
+
html += '<div class="detail-label">\u{1F4CB} Controller Info</div>';
|
|
1115
1097
|
html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px">';
|
|
1116
1098
|
html += '<div style="font-weight:600;margin-bottom:4px">' + ctrl.className + '</div>';
|
|
1117
1099
|
html += '<div style="font-size:11px;color:var(--text2)">extends ' + ctrl.parentClass + '</div>';
|
|
@@ -1158,14 +1140,14 @@ export class PageMapGenerator {
|
|
|
1158
1140
|
|
|
1159
1141
|
let html = '';
|
|
1160
1142
|
html += '<div style="padding:12px;background:var(--bg3);border-radius:8px;margin-bottom:12px">';
|
|
1161
|
-
html += '<div style="font-weight:600;margin-bottom:8px;display:flex;align-items:center;gap:8px"><span style="color:#61dafb"
|
|
1143
|
+
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
1144
|
html += '<div style="display:flex;gap:16px;flex-wrap:wrap;font-size:12px;color:var(--text2)">';
|
|
1163
1145
|
html += '<span>' + components.length + ' components</span>';
|
|
1164
|
-
html += '<span
|
|
1146
|
+
html += '<span>\u2022</span>';
|
|
1165
1147
|
html += '<span style="color:#22c55e">' + ssrCount + ' SSR</span>';
|
|
1166
|
-
html += '<span
|
|
1148
|
+
html += '<span>\u2022</span>';
|
|
1167
1149
|
html += '<span style="color:#3b82f6">' + (components.length - ssrCount) + ' client</span>';
|
|
1168
|
-
html += '<span
|
|
1150
|
+
html += '<span>\u2022</span>';
|
|
1169
1151
|
html += '<span style="color:#8b5cf6">' + withUsageCount + ' with usage</span>';
|
|
1170
1152
|
html += '</div></div>';
|
|
1171
1153
|
|
|
@@ -1178,8 +1160,8 @@ export class PageMapGenerator {
|
|
|
1178
1160
|
if (withEntry.length > 0) {
|
|
1179
1161
|
html += '<div class="group" data-group="react-with-entry">';
|
|
1180
1162
|
html += '<div class="group-header" onclick="toggleGroup(this)">';
|
|
1181
|
-
html += '<span class="group-toggle"
|
|
1182
|
-
html += '<span style="color:#61dafb;margin-right:4px"
|
|
1163
|
+
html += '<span class="group-toggle">\u25BC</span>';
|
|
1164
|
+
html += '<span style="color:#61dafb;margin-right:4px">\u{1F4E5}</span>';
|
|
1183
1165
|
html += '<span class="group-name">With Entry Points (' + withEntry.length + ')</span>';
|
|
1184
1166
|
html += '</div>';
|
|
1185
1167
|
html += '<div class="group-items">';
|
|
@@ -1204,14 +1186,14 @@ export class PageMapGenerator {
|
|
|
1204
1186
|
urlInfo = '/' + usage.controller.replace(/_/g, '/') + '/' + usage.action;
|
|
1205
1187
|
}
|
|
1206
1188
|
} else if (comp.entryFile) {
|
|
1207
|
-
// Infer URL from entry file name (e.g., tickets.tsx
|
|
1189
|
+
// Infer URL from entry file name (e.g., tickets.tsx \u2192 /tickets)
|
|
1208
1190
|
const fileName = comp.entryFile.split('/').pop().replace(/\\.(tsx?|jsx?)$/, '');
|
|
1209
1191
|
urlInfo = '/' + fileName.replace(/_/g, '-');
|
|
1210
1192
|
}
|
|
1211
1193
|
|
|
1212
1194
|
html += '<div class="page-item" data-path="' + comp.name.toLowerCase() + '" onclick="showReactComponentDetail(\\'' + encodeURIComponent(JSON.stringify(comp)) + '\\')">';
|
|
1213
1195
|
html += '<div class="page-info">';
|
|
1214
|
-
html += '<span class="page-name" style="display:flex;align-items:center"><span style="color:#61dafb;margin-right:6px"
|
|
1196
|
+
html += '<span class="page-name" style="display:flex;align-items:center"><span style="color:#61dafb;margin-right:6px">\u269B</span>' + comp.name + '</span>';
|
|
1215
1197
|
html += '<span class="page-path" style="font-size:10px;color:var(--accent)">' + urlInfo + '</span>';
|
|
1216
1198
|
html += '</div>';
|
|
1217
1199
|
html += '<div class="page-tags">' + tags.join('') + '</div>';
|
|
@@ -1225,8 +1207,8 @@ export class PageMapGenerator {
|
|
|
1225
1207
|
if (withoutEntry.length > 0) {
|
|
1226
1208
|
html += '<div class="group" data-group="react-view-only">';
|
|
1227
1209
|
html += '<div class="group-header" onclick="toggleGroup(this)">';
|
|
1228
|
-
html += '<span class="group-toggle"
|
|
1229
|
-
html += '<span style="color:#f59e0b;margin-right:4px"
|
|
1210
|
+
html += '<span class="group-toggle">\u25BC</span>';
|
|
1211
|
+
html += '<span style="color:#f59e0b;margin-right:4px">\u{1F441}\uFE0F</span>';
|
|
1230
1212
|
html += '<span class="group-name">View-only Components (' + withoutEntry.length + ')</span>';
|
|
1231
1213
|
html += '</div>';
|
|
1232
1214
|
html += '<div class="group-items">';
|
|
@@ -1253,7 +1235,7 @@ export class PageMapGenerator {
|
|
|
1253
1235
|
|
|
1254
1236
|
html += '<div class="page-item" data-path="' + comp.name.toLowerCase() + '" onclick="showReactComponentDetail(\\'' + encodeURIComponent(JSON.stringify(comp)) + '\\')">';
|
|
1255
1237
|
html += '<div class="page-info">';
|
|
1256
|
-
html += '<span class="page-name" style="display:flex;align-items:center"><span style="color:#61dafb;margin-right:6px"
|
|
1238
|
+
html += '<span class="page-name" style="display:flex;align-items:center"><span style="color:#61dafb;margin-right:6px">\u269B</span>' + comp.name + '</span>';
|
|
1257
1239
|
html += '<span class="page-path" style="font-size:10px;color:var(--accent)">' + (urlInfo || 'View-only') + '</span>';
|
|
1258
1240
|
html += '</div>';
|
|
1259
1241
|
html += '<div class="page-tags">' + tags.join('') + '</div>';
|
|
@@ -1302,7 +1284,7 @@ export class PageMapGenerator {
|
|
|
1302
1284
|
const ctrlName = view.controller.split('/').pop().replace(/_/g, '');
|
|
1303
1285
|
const ctrl = railsControllers?.find(c => {
|
|
1304
1286
|
if (c.name === view.controller || c.name === ctrlName) return true;
|
|
1305
|
-
const filePathNormalized = c.filePath.replace(/_controller
|
|
1287
|
+
const filePathNormalized = c.filePath.replace(/_controller.rb$/, '').replace(/_/g, '');
|
|
1306
1288
|
return filePathNormalized === view.controller.replace(/_/g, '');
|
|
1307
1289
|
});
|
|
1308
1290
|
const action = ctrl?.actions?.find(a => a.name === view.action);
|
|
@@ -1364,14 +1346,14 @@ export class PageMapGenerator {
|
|
|
1364
1346
|
|
|
1365
1347
|
let html = '';
|
|
1366
1348
|
html += '<div style="padding:12px;background:var(--bg3);border-radius:8px;margin-bottom:12px">';
|
|
1367
|
-
html += '<div style="font-weight:600;margin-bottom:8px"
|
|
1349
|
+
html += '<div style="font-weight:600;margin-bottom:8px">\u{1F5BC}\uFE0F Rails Screens (View Templates)</div>';
|
|
1368
1350
|
html += '<div style="display:flex;gap:16px;flex-wrap:wrap;font-size:12px;color:var(--text2)">';
|
|
1369
1351
|
html += '<span>' + enrichedViews.length + ' screens</span>';
|
|
1370
|
-
html += '<span
|
|
1352
|
+
html += '<span>\u2022</span>';
|
|
1371
1353
|
html += '<span>' + sortedControllers.length + ' sections</span>';
|
|
1372
|
-
html += '<span
|
|
1354
|
+
html += '<span>\u2022</span>';
|
|
1373
1355
|
html += '<span style="color:#8b5cf6">' + totalWithServices + ' with services</span>';
|
|
1374
|
-
html += '<span
|
|
1356
|
+
html += '<span>\u2022</span>';
|
|
1375
1357
|
html += '<span style="color:#06b6d4">' + totalWithPartials + ' with partials</span>';
|
|
1376
1358
|
html += '</div></div>';
|
|
1377
1359
|
|
|
@@ -1385,8 +1367,8 @@ export class PageMapGenerator {
|
|
|
1385
1367
|
|
|
1386
1368
|
html += '<div class="group">';
|
|
1387
1369
|
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"
|
|
1370
|
+
html += '<span class="group-toggle">\u25BC</span>';
|
|
1371
|
+
html += '<span class="group-name">\u{1F4C1} ' + ctrl + '</span>';
|
|
1390
1372
|
html += '<span class="group-count">' + controllerViews.length + ' screens</span>';
|
|
1391
1373
|
html += '</div>';
|
|
1392
1374
|
html += '<div class="group-items" id="' + screenListId + '">';
|
|
@@ -1400,14 +1382,14 @@ export class PageMapGenerator {
|
|
|
1400
1382
|
indicators += '<span class="route-tag route-tag-template" title="' + view.template.toUpperCase() + ' template">' + view.template.toUpperCase() + '</span>';
|
|
1401
1383
|
if (view.reactComponents && view.reactComponents.length > 0) {
|
|
1402
1384
|
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 + '"
|
|
1385
|
+
indicators += '<span class="route-tag route-tag-react" title="React: ' + rcNames + '">\u269B ' + view.reactComponents.length + '</span>';
|
|
1404
1386
|
}
|
|
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(', @') + '"
|
|
1387
|
+
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>';
|
|
1388
|
+
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
1389
|
if (view.services.length > 0) indicators += '<span class="route-tag route-tag-svc" title="Services: ' + view.services.join(', ') + '">Svc</span>';
|
|
1408
1390
|
if (view.grpcCalls.length > 0) indicators += '<span class="route-tag route-tag-grpc" title="gRPC: ' + view.grpcCalls.join(', ') + '">gRPC</span>';
|
|
1409
1391
|
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"
|
|
1392
|
+
if (!view.hasRoute) indicators += '<span class="route-tag route-tag-warn" title="No matching route found">\u26A0\uFE0F</span>';
|
|
1411
1393
|
|
|
1412
1394
|
// Display: URL path (if route exists) or controller/action
|
|
1413
1395
|
const displayName = view.hasRoute ? view.path.replace(/:([a-z_]+)/g, '<span style="color:#f59e0b">:$1</span>') : view.controller + '#' + view.action;
|
|
@@ -1428,7 +1410,7 @@ export class PageMapGenerator {
|
|
|
1428
1410
|
|
|
1429
1411
|
if (hasMoreScreens) {
|
|
1430
1412
|
html += '<div id="' + screenListId + '-more" style="padding:8px 12px;cursor:pointer;color:var(--accent);font-size:11px" onclick="toggleMoreItems(\\'' + screenListId + '\\', ' + controllerViews.length + ')">';
|
|
1431
|
-
html += '
|
|
1413
|
+
html += '\u25BC Show ' + (controllerViews.length - screenLimit) + ' more screens';
|
|
1432
1414
|
html += '</div>';
|
|
1433
1415
|
}
|
|
1434
1416
|
|
|
@@ -1446,7 +1428,7 @@ export class PageMapGenerator {
|
|
|
1446
1428
|
|
|
1447
1429
|
// URL/Route info
|
|
1448
1430
|
html += '<div class="detail-section">';
|
|
1449
|
-
html += '<div class="detail-label"
|
|
1431
|
+
html += '<div class="detail-label">\u{1F310} URL Path</div>';
|
|
1450
1432
|
if (screen.hasRoute) {
|
|
1451
1433
|
html += '<div class="detail-value" style="font-family:monospace">' + screen.path.replace(/:([a-z_]+)/g, '<span style="color:#f59e0b">:$1</span>') + '</div>';
|
|
1452
1434
|
} else {
|
|
@@ -1456,7 +1438,7 @@ export class PageMapGenerator {
|
|
|
1456
1438
|
|
|
1457
1439
|
// View Template info
|
|
1458
1440
|
html += '<div class="detail-section">';
|
|
1459
|
-
html += '<div class="detail-label"
|
|
1441
|
+
html += '<div class="detail-label">\u{1F4C4} View Template</div>';
|
|
1460
1442
|
html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px;font-family:monospace;font-size:12px">';
|
|
1461
1443
|
html += '<div style="color:var(--accent)">app/views/' + screen.viewPath + '</div>';
|
|
1462
1444
|
html += '<div style="margin-top:6px;display:flex;gap:8px">';
|
|
@@ -1466,7 +1448,7 @@ export class PageMapGenerator {
|
|
|
1466
1448
|
// Instance Variables (data passed to view) with type info
|
|
1467
1449
|
if (screen.instanceVars && screen.instanceVars.length > 0) {
|
|
1468
1450
|
html += '<div class="detail-section">';
|
|
1469
|
-
html += '<div class="detail-label"
|
|
1451
|
+
html += '<div class="detail-label">\u{1F4E6} Data Available in View (@variables)</div>';
|
|
1470
1452
|
|
|
1471
1453
|
// Build assignment map from controller analysis
|
|
1472
1454
|
const assignmentMap = {};
|
|
@@ -1487,16 +1469,16 @@ export class PageMapGenerator {
|
|
|
1487
1469
|
|
|
1488
1470
|
// Function to find matching model for a variable name
|
|
1489
1471
|
function findModelForVar(varName) {
|
|
1490
|
-
// Direct match: @company
|
|
1472
|
+
// Direct match: @company \u2192 Company
|
|
1491
1473
|
const pascalCase = varName.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('');
|
|
1492
1474
|
if (modelNames.has(pascalCase)) return { model: pascalCase, confidence: 'exact' };
|
|
1493
1475
|
|
|
1494
|
-
// Singular form: @companies
|
|
1476
|
+
// Singular form: @companies \u2192 Company
|
|
1495
1477
|
const singular = varName.replace(/ies$/, 'y').replace(/s$/, '');
|
|
1496
1478
|
const singularPascal = singular.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('');
|
|
1497
1479
|
if (modelNames.has(singularPascal)) return { model: singularPascal, confidence: 'plural' };
|
|
1498
1480
|
|
|
1499
|
-
// current_X pattern: @current_user
|
|
1481
|
+
// current_X pattern: @current_user \u2192 User
|
|
1500
1482
|
if (varName.startsWith('current_')) {
|
|
1501
1483
|
const rest = varName.replace('current_', '');
|
|
1502
1484
|
const restPascal = rest.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('');
|
|
@@ -1571,7 +1553,7 @@ export class PageMapGenerator {
|
|
|
1571
1553
|
// "Show more" button
|
|
1572
1554
|
if (hasMore) {
|
|
1573
1555
|
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 += '
|
|
1556
|
+
html += '\u25BC Show ' + (screen.instanceVars.length - initialLimit) + ' more variables';
|
|
1575
1557
|
html += '</div>';
|
|
1576
1558
|
}
|
|
1577
1559
|
|
|
@@ -1581,7 +1563,7 @@ export class PageMapGenerator {
|
|
|
1581
1563
|
// React Components loaded in this view
|
|
1582
1564
|
if (screen.reactComponents && screen.reactComponents.length > 0) {
|
|
1583
1565
|
html += '<div class="detail-section">';
|
|
1584
|
-
html += '<div class="detail-label"
|
|
1566
|
+
html += '<div class="detail-label">\u269B\uFE0F React Components</div>';
|
|
1585
1567
|
html += '<div class="detail-items">';
|
|
1586
1568
|
screen.reactComponents.forEach(rc => {
|
|
1587
1569
|
html += '<div class="detail-item">';
|
|
@@ -1605,7 +1587,7 @@ export class PageMapGenerator {
|
|
|
1605
1587
|
const partialListId = 'partials-' + Math.random().toString(36).substr(2, 9);
|
|
1606
1588
|
|
|
1607
1589
|
html += '<div class="detail-section">';
|
|
1608
|
-
html += '<div class="detail-label"
|
|
1590
|
+
html += '<div class="detail-label">\u{1F9E9} Partials Used (' + screen.partials.length + ')</div>';
|
|
1609
1591
|
html += '<div class="detail-items" id="' + partialListId + '">';
|
|
1610
1592
|
screen.partials.forEach((p, idx) => {
|
|
1611
1593
|
const hiddenClass = idx >= partialLimit ? ' style="display:none" data-hidden="true"' : '';
|
|
@@ -1613,7 +1595,7 @@ export class PageMapGenerator {
|
|
|
1613
1595
|
});
|
|
1614
1596
|
html += '</div>';
|
|
1615
1597
|
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 + ')"
|
|
1598
|
+
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
1599
|
}
|
|
1618
1600
|
html += '</div>';
|
|
1619
1601
|
}
|
|
@@ -1621,7 +1603,7 @@ export class PageMapGenerator {
|
|
|
1621
1603
|
// Services Called
|
|
1622
1604
|
if (screen.services && screen.services.length > 0) {
|
|
1623
1605
|
html += '<div class="detail-section">';
|
|
1624
|
-
html += '<div class="detail-label"
|
|
1606
|
+
html += '<div class="detail-label">\u2699\uFE0F Services Called</div>';
|
|
1625
1607
|
html += '<div class="detail-items">';
|
|
1626
1608
|
screen.services.forEach(s => {
|
|
1627
1609
|
html += '<div class="detail-item"><span class="tag" style="background:#8b5cf6">Service</span><span class="name" style="font-family:monospace">' + s + '</span></div>';
|
|
@@ -1632,7 +1614,7 @@ export class PageMapGenerator {
|
|
|
1632
1614
|
// gRPC Calls
|
|
1633
1615
|
if (screen.grpcCalls && screen.grpcCalls.length > 0) {
|
|
1634
1616
|
html += '<div class="detail-section">';
|
|
1635
|
-
html += '<div class="detail-label"
|
|
1617
|
+
html += '<div class="detail-label">\u{1F50C} gRPC Calls</div>';
|
|
1636
1618
|
html += '<div class="detail-items">';
|
|
1637
1619
|
screen.grpcCalls.forEach(g => {
|
|
1638
1620
|
html += '<div class="detail-item"><span class="tag" style="background:#06b6d4">gRPC</span><span class="name" style="font-family:monospace">' + g + '</span></div>';
|
|
@@ -1643,7 +1625,7 @@ export class PageMapGenerator {
|
|
|
1643
1625
|
// Model Access
|
|
1644
1626
|
if (screen.modelAccess && screen.modelAccess.length > 0) {
|
|
1645
1627
|
html += '<div class="detail-section">';
|
|
1646
|
-
html += '<div class="detail-label"
|
|
1628
|
+
html += '<div class="detail-label">\u{1F4BE} Models Used</div>';
|
|
1647
1629
|
html += '<div class="detail-items">';
|
|
1648
1630
|
screen.modelAccess.forEach(m => {
|
|
1649
1631
|
html += '<div class="detail-item"><span class="tag" style="background:#f59e0b">Model</span><span class="name" style="font-family:monospace">' + m + '</span></div>';
|
|
@@ -1653,7 +1635,7 @@ export class PageMapGenerator {
|
|
|
1653
1635
|
|
|
1654
1636
|
// Controller Action info
|
|
1655
1637
|
html += '<div class="detail-section">';
|
|
1656
|
-
html += '<div class="detail-label"
|
|
1638
|
+
html += '<div class="detail-label">\u{1F3AE} Controller Action</div>';
|
|
1657
1639
|
html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px">';
|
|
1658
1640
|
html += '<div style="font-family:monospace;font-size:12px">' + screen.controller + '#' + screen.action + '</div>';
|
|
1659
1641
|
if (screen.controllerInfo) {
|
|
@@ -1686,7 +1668,7 @@ export class PageMapGenerator {
|
|
|
1686
1668
|
|
|
1687
1669
|
if (meaningfulCalls.length > 0) {
|
|
1688
1670
|
html += '<div class="detail-section">';
|
|
1689
|
-
html += '<div class="detail-label"
|
|
1671
|
+
html += '<div class="detail-label">\u{1F517} Method Calls</div>';
|
|
1690
1672
|
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
1673
|
meaningfulCalls.forEach((call, i) => {
|
|
1692
1674
|
html += '<div style="padding:2px 0;color:var(--accent)">' + (i+1) + '. ' + call + '</div>';
|
|
@@ -1695,7 +1677,7 @@ export class PageMapGenerator {
|
|
|
1695
1677
|
}
|
|
1696
1678
|
}
|
|
1697
1679
|
|
|
1698
|
-
showModal('
|
|
1680
|
+
showModal('\u{1F5BC}\uFE0F ' + screen.controller + '/' + screen.action, html);
|
|
1699
1681
|
}
|
|
1700
1682
|
|
|
1701
1683
|
// Show Rails page detail with API info
|
|
@@ -1711,7 +1693,7 @@ export class PageMapGenerator {
|
|
|
1711
1693
|
const routeCtrlName = routeCtrlParts.pop().replace(/_/g, '');
|
|
1712
1694
|
|
|
1713
1695
|
controllerInfo = railsControllers.find(c => {
|
|
1714
|
-
const filePathNormalized = c.filePath.replace(/_controller
|
|
1696
|
+
const filePathNormalized = c.filePath.replace(/_controller.rb$/, '').replace(/_/g, '');
|
|
1715
1697
|
if (filePathNormalized === routeCtrl.replace(/_/g, '')) return true;
|
|
1716
1698
|
if (c.name === routeCtrlName || c.name.replace(/_/g, '') === routeCtrlName) return true;
|
|
1717
1699
|
const className = c.className.toLowerCase().replace('controller', '').replace(/::/g, '/');
|
|
@@ -1746,7 +1728,7 @@ export class PageMapGenerator {
|
|
|
1746
1728
|
// Response Type
|
|
1747
1729
|
if (actionDetails) {
|
|
1748
1730
|
html += '<div class="detail-section">';
|
|
1749
|
-
html += '<div class="detail-label"
|
|
1731
|
+
html += '<div class="detail-label">\u{1F4E1} Response Type</div>';
|
|
1750
1732
|
html += '<div class="detail-value">';
|
|
1751
1733
|
const responseTypes = [];
|
|
1752
1734
|
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>');
|
|
@@ -1762,7 +1744,7 @@ export class PageMapGenerator {
|
|
|
1762
1744
|
|
|
1763
1745
|
if (actionDetails.redirectsTo) {
|
|
1764
1746
|
html += '<div class="detail-section">';
|
|
1765
|
-
html += '<div class="detail-label"
|
|
1747
|
+
html += '<div class="detail-label">\u21AA\uFE0F Redirects To</div>';
|
|
1766
1748
|
html += '<div class="detail-value" style="font-family:monospace;font-size:12px;background:var(--bg3);padding:8px;border-radius:4px">' + actionDetails.redirectsTo + '</div>';
|
|
1767
1749
|
html += '</div>';
|
|
1768
1750
|
}
|
|
@@ -1772,7 +1754,7 @@ export class PageMapGenerator {
|
|
|
1772
1754
|
const view = pageInfo?.view || route.view;
|
|
1773
1755
|
if (view) {
|
|
1774
1756
|
html += '<div class="detail-section">';
|
|
1775
|
-
html += '<div class="detail-label"
|
|
1757
|
+
html += '<div class="detail-label">\u{1F4C4} View Template</div>';
|
|
1776
1758
|
html += '<div class="detail-value" style="font-family:monospace;font-size:12px">app/views/' + view.path + '</div>';
|
|
1777
1759
|
if (view.partials && view.partials.length > 0) {
|
|
1778
1760
|
html += '<div style="margin-top:6px;font-size:11px;color:var(--text2)">Partials: ' + view.partials.slice(0, 5).join(', ') + '</div>';
|
|
@@ -1786,7 +1768,7 @@ export class PageMapGenerator {
|
|
|
1786
1768
|
// Before/After Filters
|
|
1787
1769
|
if (controllerInfo && (controllerInfo.beforeActions.length > 0 || controllerInfo.afterActions.length > 0)) {
|
|
1788
1770
|
html += '<div class="detail-section">';
|
|
1789
|
-
html += '<div class="detail-label"
|
|
1771
|
+
html += '<div class="detail-label">\u{1F512} Filters Applied</div>';
|
|
1790
1772
|
html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px">';
|
|
1791
1773
|
|
|
1792
1774
|
const applicableBeforeFilters = controllerInfo.beforeActions.filter(f => {
|
|
@@ -1827,7 +1809,7 @@ export class PageMapGenerator {
|
|
|
1827
1809
|
const services = pageInfo?.services || actionDetails?.servicesCalled || [];
|
|
1828
1810
|
if (services.length > 0) {
|
|
1829
1811
|
html += '<div class="detail-section">';
|
|
1830
|
-
html += '<div class="detail-label"
|
|
1812
|
+
html += '<div class="detail-label">\u2699\uFE0F Services Called</div>';
|
|
1831
1813
|
html += '<div class="detail-items">';
|
|
1832
1814
|
services.forEach(s => {
|
|
1833
1815
|
html += '<div class="detail-item"><span class="tag" style="background:#8b5cf6">Service</span><span class="name" style="font-family:monospace">' + s + '</span></div>';
|
|
@@ -1839,7 +1821,7 @@ export class PageMapGenerator {
|
|
|
1839
1821
|
const grpcCalls = pageInfo?.grpcCalls || [];
|
|
1840
1822
|
if (grpcCalls.length > 0) {
|
|
1841
1823
|
html += '<div class="detail-section">';
|
|
1842
|
-
html += '<div class="detail-label"
|
|
1824
|
+
html += '<div class="detail-label">\u{1F50C} gRPC Calls</div>';
|
|
1843
1825
|
html += '<div class="detail-items">';
|
|
1844
1826
|
grpcCalls.forEach(g => {
|
|
1845
1827
|
html += '<div class="detail-item"><span class="tag" style="background:#06b6d4">gRPC</span><span class="name" style="font-family:monospace">' + g + '</span></div>';
|
|
@@ -1851,7 +1833,7 @@ export class PageMapGenerator {
|
|
|
1851
1833
|
const models = pageInfo?.modelAccess || actionDetails?.modelsCalled || [];
|
|
1852
1834
|
if (models.length > 0) {
|
|
1853
1835
|
html += '<div class="detail-section">';
|
|
1854
|
-
html += '<div class="detail-label"
|
|
1836
|
+
html += '<div class="detail-label">\u{1F4BE} Models Accessed</div>';
|
|
1855
1837
|
html += '<div class="detail-items">';
|
|
1856
1838
|
models.forEach(m => {
|
|
1857
1839
|
html += '<div class="detail-item"><span class="tag" style="background:#f59e0b">Model</span><span class="name" style="font-family:monospace">' + m + '</span></div>';
|
|
@@ -1868,7 +1850,7 @@ export class PageMapGenerator {
|
|
|
1868
1850
|
|
|
1869
1851
|
if (meaningfulCalls.length > 0) {
|
|
1870
1852
|
html += '<div class="detail-section">';
|
|
1871
|
-
html += '<div class="detail-label"
|
|
1853
|
+
html += '<div class="detail-label">\u{1F517} Method Calls in Action</div>';
|
|
1872
1854
|
html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px;max-height:150px;overflow-y:auto">';
|
|
1873
1855
|
html += '<div style="font-family:monospace;font-size:11px;line-height:1.6">';
|
|
1874
1856
|
meaningfulCalls.forEach((call, i) => {
|
|
@@ -1886,7 +1868,7 @@ export class PageMapGenerator {
|
|
|
1886
1868
|
|
|
1887
1869
|
// Source Files
|
|
1888
1870
|
html += '<div class="detail-section">';
|
|
1889
|
-
html += '<div class="detail-label"
|
|
1871
|
+
html += '<div class="detail-label">\u{1F4C1} Source Files</div>';
|
|
1890
1872
|
html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px;font-family:monospace;font-size:11px">';
|
|
1891
1873
|
|
|
1892
1874
|
if (controllerInfo) {
|
|
@@ -1908,7 +1890,7 @@ export class PageMapGenerator {
|
|
|
1908
1890
|
// Controller Info
|
|
1909
1891
|
if (controllerInfo) {
|
|
1910
1892
|
html += '<div class="detail-section">';
|
|
1911
|
-
html += '<div class="detail-label"
|
|
1893
|
+
html += '<div class="detail-label">\u{1F4CB} Controller Info</div>';
|
|
1912
1894
|
html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px">';
|
|
1913
1895
|
html += '<div style="font-weight:600;margin-bottom:4px">' + controllerInfo.className + '</div>';
|
|
1914
1896
|
html += '<div style="font-size:11px;color:var(--text2)">extends ' + controllerInfo.parentClass + '</div>';
|
|
@@ -1922,7 +1904,7 @@ export class PageMapGenerator {
|
|
|
1922
1904
|
|
|
1923
1905
|
if (!pageInfo && !actionDetails) {
|
|
1924
1906
|
html += '<div style="padding:12px;color:var(--text2);font-size:12px;background:var(--bg3);border-radius:6px;margin-top:8px">';
|
|
1925
|
-
html += '
|
|
1907
|
+
html += '\u26A0\uFE0F No detailed action information found. The controller or action may not be analyzed yet.';
|
|
1926
1908
|
html += '</div>';
|
|
1927
1909
|
}
|
|
1928
1910
|
|
|
@@ -1971,7 +1953,7 @@ export class PageMapGenerator {
|
|
|
1971
1953
|
html += '</div></div>';
|
|
1972
1954
|
}
|
|
1973
1955
|
|
|
1974
|
-
showModal('
|
|
1956
|
+
showModal('\u{1F4C4} ' + view.controller + '/' + view.action, html);
|
|
1975
1957
|
}
|
|
1976
1958
|
|
|
1977
1959
|
function toggleGroup(el) {
|
|
@@ -1991,14 +1973,14 @@ export class PageMapGenerator {
|
|
|
1991
1973
|
hiddenItems.forEach(item => {
|
|
1992
1974
|
item.style.display = 'none';
|
|
1993
1975
|
});
|
|
1994
|
-
moreBtn.innerHTML = '
|
|
1976
|
+
moreBtn.innerHTML = '\u25BC Show ' + hiddenItems.length + ' more variables';
|
|
1995
1977
|
moreBtn.setAttribute('data-expanded', 'false');
|
|
1996
1978
|
} else {
|
|
1997
1979
|
// Expand: show all items
|
|
1998
1980
|
hiddenItems.forEach(item => {
|
|
1999
1981
|
item.style.display = '';
|
|
2000
1982
|
});
|
|
2001
|
-
moreBtn.innerHTML = '
|
|
1983
|
+
moreBtn.innerHTML = '\u25B2 Show less';
|
|
2002
1984
|
moreBtn.setAttribute('data-expanded', 'true');
|
|
2003
1985
|
}
|
|
2004
1986
|
}
|
|
@@ -2077,11 +2059,11 @@ export class PageMapGenerator {
|
|
|
2077
2059
|
// Parse operations to extract path info and depth
|
|
2078
2060
|
const parsedOps = graphqlOps.map(df => {
|
|
2079
2061
|
const rawName = df.operationName || '';
|
|
2080
|
-
// Pattern: "
|
|
2081
|
-
const arrowCount = (rawName.match(
|
|
2062
|
+
// Pattern: "\u2192 QueryName (via HookA)" or "\u2192 \u2192 QueryName (via HookA)" etc.
|
|
2063
|
+
const arrowCount = (rawName.match(/\u2192/g) || []).length;
|
|
2082
2064
|
|
|
2083
2065
|
// Extract query name and path
|
|
2084
|
-
let queryName = rawName.replace(/^[
|
|
2066
|
+
let queryName = rawName.replace(/^[\u2192\\s]+/, '').replace(/^\\u2192\\s*/g, '');
|
|
2085
2067
|
let sourcePath = '';
|
|
2086
2068
|
|
|
2087
2069
|
// Extract "(via X)" for hook
|
|
@@ -2099,7 +2081,7 @@ export class PageMapGenerator {
|
|
|
2099
2081
|
}
|
|
2100
2082
|
|
|
2101
2083
|
// Further clean the query name
|
|
2102
|
-
queryName = queryName.replace(/^[
|
|
2084
|
+
queryName = queryName.replace(/^[\u2192\\s]+/, '').trim();
|
|
2103
2085
|
|
|
2104
2086
|
return {
|
|
2105
2087
|
...df,
|
|
@@ -2162,7 +2144,7 @@ export class PageMapGenerator {
|
|
|
2162
2144
|
|
|
2163
2145
|
sortedPaths.forEach(pathName => {
|
|
2164
2146
|
const ops = groupedByPath.get(pathName);
|
|
2165
|
-
const depthIndicator = pathName === 'Direct' ? '' : '
|
|
2147
|
+
const depthIndicator = pathName === 'Direct' ? '' : '\u21B3 ';
|
|
2166
2148
|
const pathLabel = pathName === 'Direct' ? 'Direct (this page)' : pathName;
|
|
2167
2149
|
|
|
2168
2150
|
// Path header with depth visual
|
|
@@ -2360,7 +2342,7 @@ export class PageMapGenerator {
|
|
|
2360
2342
|
const groupPages = groups[g];
|
|
2361
2343
|
html += '<div style="margin-bottom:12px">';
|
|
2362
2344
|
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"
|
|
2345
|
+
html += '<div class="detail-label" style="display:flex;align-items:center;gap:6px"><span class="group-toggle">\u25B8</span>/'+g+'</div>';
|
|
2364
2346
|
html += '<span style="color:var(--accent)">'+groupPages.length+' pages</span></div>';
|
|
2365
2347
|
html += '<div class="group-page-list" style="display:none;margin-left:16px;margin-top:4px">';
|
|
2366
2348
|
groupPages.sort((a,b) => a.path.localeCompare(b.path)).forEach(p => {
|
|
@@ -2386,10 +2368,10 @@ export class PageMapGenerator {
|
|
|
2386
2368
|
const toggle = el.querySelector('.group-toggle');
|
|
2387
2369
|
if (list.style.display === 'none') {
|
|
2388
2370
|
list.style.display = 'block';
|
|
2389
|
-
toggle.textContent = '
|
|
2371
|
+
toggle.textContent = '\u25BE';
|
|
2390
2372
|
} else {
|
|
2391
2373
|
list.style.display = 'none';
|
|
2392
|
-
toggle.textContent = '
|
|
2374
|
+
toggle.textContent = '\u25B8';
|
|
2393
2375
|
}
|
|
2394
2376
|
};
|
|
2395
2377
|
|
|
@@ -2434,7 +2416,7 @@ export class PageMapGenerator {
|
|
|
2434
2416
|
const indent = depth * 12;
|
|
2435
2417
|
let html = '<div class="rel-item" style="padding-left:'+(10+indent)+'px" onclick="event.stopPropagation(); selectPage(\\''+page.path+'\\')">';
|
|
2436
2418
|
html += '<div class="rel-header">';
|
|
2437
|
-
html += '<span style="color:var(--text2);font-size:10px">'+'
|
|
2419
|
+
html += '<span style="color:var(--text2);font-size:10px">'+'\u2500'.repeat(depth > 0 ? 1 : 0)+(depth > 0 ? ' ' : '')+'</span>';
|
|
2438
2420
|
html += '<span class="rel-path">'+page.path+'</span>';
|
|
2439
2421
|
if (page.children && page.children.length > 0) {
|
|
2440
2422
|
html += '<span style="color:var(--text2);font-size:9px;margin-left:auto">'+page.children.length+' children</span>';
|
|
@@ -2616,12 +2598,12 @@ export class PageMapGenerator {
|
|
|
2616
2598
|
};
|
|
2617
2599
|
|
|
2618
2600
|
function showDataDetail(rawName, sourcePath) {
|
|
2619
|
-
// Clean up name: remove "
|
|
2601
|
+
// Clean up name: remove "\u2192 " prefix and " (ComponentName)" suffix
|
|
2620
2602
|
const name = rawName
|
|
2621
|
-
.replace(/^[
|
|
2603
|
+
.replace(/^[\u2192\\->\\s]+/, '')
|
|
2622
2604
|
.replace(/\\s*\\([^)]+\\)\\s*$/, '');
|
|
2623
2605
|
|
|
2624
|
-
// Convert SCREAMING_CASE to PascalCase (e.g., COMPANY_QUERY
|
|
2606
|
+
// Convert SCREAMING_CASE to PascalCase (e.g., COMPANY_QUERY \u2192 CompanyQuery)
|
|
2625
2607
|
const toPascalCase = (str) => {
|
|
2626
2608
|
if (!/^[A-Z][A-Z0-9_]*$/.test(str)) return str;
|
|
2627
2609
|
return str.toLowerCase().split('_').map(word =>
|
|
@@ -2678,7 +2660,7 @@ export class PageMapGenerator {
|
|
|
2678
2660
|
}
|
|
2679
2661
|
|
|
2680
2662
|
// 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"
|
|
2663
|
+
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
2664
|
html += '<code style="background:#0f172a;color:#93c5fd;padding:4px 8px;border-radius:4px;font-family:monospace">'+op.name+'</code></div>';
|
|
2683
2665
|
|
|
2684
2666
|
if (op.returnType) {
|
|
@@ -2697,7 +2679,7 @@ export class PageMapGenerator {
|
|
|
2697
2679
|
// Escape for data attribute
|
|
2698
2680
|
const gqlCodeEscaped = gqlCode.replace(/'/g, "\\\\'").replace(/"/g, '"');
|
|
2699
2681
|
|
|
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"
|
|
2682
|
+
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
2683
|
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
2684
|
} else if (op.variables?.length) {
|
|
2703
2685
|
html += '<div class="detail-section"><h4>Variables</h4>';
|
|
@@ -2709,7 +2691,7 @@ export class PageMapGenerator {
|
|
|
2709
2691
|
op.usedIn.slice(0,8).forEach(f => { html += '<div class="detail-item">'+f+'</div>'; });
|
|
2710
2692
|
if (op.usedIn.length > 8) {
|
|
2711
2693
|
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"
|
|
2694
|
+
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
2695
|
}
|
|
2714
2696
|
html += '</div>';
|
|
2715
2697
|
}
|
|
@@ -2752,7 +2734,7 @@ export class PageMapGenerator {
|
|
|
2752
2734
|
});
|
|
2753
2735
|
if (queries.length > 5) {
|
|
2754
2736
|
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)" style="color:var(--accent);font-size:10px;cursor:pointer;padding:4px 0"
|
|
2737
|
+
html += '<div class="expand-more" onclick="expandMore(\\'query\\', '+JSON.stringify(remaining).replace(/"/g, '"')+', this)" style="color:var(--accent);font-size:10px;cursor:pointer;padding:4px 0">\u25B8 Show ' + (queries.length - 5) + ' more queries</div>';
|
|
2756
2738
|
}
|
|
2757
2739
|
}
|
|
2758
2740
|
|
|
@@ -2764,7 +2746,7 @@ export class PageMapGenerator {
|
|
|
2764
2746
|
});
|
|
2765
2747
|
if (mutations.length > 5) {
|
|
2766
2748
|
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)" style="color:var(--accent);font-size:10px;cursor:pointer;padding:4px 0"
|
|
2749
|
+
html += '<div class="expand-more" onclick="expandMore(\\'mutation\\', '+JSON.stringify(remaining).replace(/"/g, '"')+', this)" style="color:var(--accent);font-size:10px;cursor:pointer;padding:4px 0">\u25B8 Show ' + (mutations.length - 5) + ' more mutations</div>';
|
|
2768
2750
|
}
|
|
2769
2751
|
}
|
|
2770
2752
|
|
|
@@ -2776,7 +2758,7 @@ export class PageMapGenerator {
|
|
|
2776
2758
|
});
|
|
2777
2759
|
if (fragments.length > 3) {
|
|
2778
2760
|
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)" style="color:var(--accent);font-size:10px;cursor:pointer;padding:4px 0"
|
|
2761
|
+
html += '<div class="expand-more" onclick="expandMore(\\'fragment\\', '+JSON.stringify(remaining).replace(/"/g, '"')+', this)" style="color:var(--accent);font-size:10px;cursor:pointer;padding:4px 0">\u25B8 Show ' + (fragments.length - 3) + ' more fragments</div>';
|
|
2780
2762
|
}
|
|
2781
2763
|
}
|
|
2782
2764
|
|
|
@@ -2799,9 +2781,9 @@ export class PageMapGenerator {
|
|
|
2799
2781
|
html += '</div>';
|
|
2800
2782
|
}
|
|
2801
2783
|
} else {
|
|
2802
|
-
// Clean up the name: remove "
|
|
2784
|
+
// Clean up the name: remove "\u2192 " prefix and " (ComponentName)" suffix
|
|
2803
2785
|
let cleanName = name
|
|
2804
|
-
.replace(/^[
|
|
2786
|
+
.replace(/^[\u2192\\->\\s]+/, '') // Remove arrow prefix
|
|
2805
2787
|
.replace(/\\s*\\([^)]+\\)\\s*$/, ''); // Remove parenthetical component reference
|
|
2806
2788
|
|
|
2807
2789
|
// Extract the core component name - remove ALL common suffixes iteratively
|
|
@@ -2892,7 +2874,7 @@ export class PageMapGenerator {
|
|
|
2892
2874
|
});
|
|
2893
2875
|
if (queries.length > 8) {
|
|
2894
2876
|
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)" style="color:var(--accent);font-size:10px;cursor:pointer;padding:4px 0"
|
|
2877
|
+
html += '<div class="expand-more" onclick="expandMore(\\'query\\', '+JSON.stringify(remaining).replace(/"/g, '"')+', this)" style="color:var(--accent);font-size:10px;cursor:pointer;padding:4px 0">\u25B8 Show ' + (queries.length - 8) + ' more</div>';
|
|
2896
2878
|
}
|
|
2897
2879
|
}
|
|
2898
2880
|
|
|
@@ -2904,7 +2886,7 @@ export class PageMapGenerator {
|
|
|
2904
2886
|
});
|
|
2905
2887
|
if (mutations.length > 5) {
|
|
2906
2888
|
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)" style="color:var(--accent);font-size:10px;cursor:pointer;padding:4px 0"
|
|
2889
|
+
html += '<div class="expand-more" onclick="expandMore(\\'mutation\\', '+JSON.stringify(remaining).replace(/"/g, '"')+', this)" style="color:var(--accent);font-size:10px;cursor:pointer;padding:4px 0">\u25B8 Show ' + (mutations.length - 5) + ' more</div>';
|
|
2908
2890
|
}
|
|
2909
2891
|
}
|
|
2910
2892
|
|
|
@@ -2916,7 +2898,7 @@ export class PageMapGenerator {
|
|
|
2916
2898
|
});
|
|
2917
2899
|
if (fragments.length > 3) {
|
|
2918
2900
|
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)" style="color:var(--accent);font-size:10px;cursor:pointer;padding:4px 0"
|
|
2901
|
+
html += '<div class="expand-more" onclick="expandMore(\\'fragment\\', '+JSON.stringify(remaining).replace(/"/g, '"')+', this)" style="color:var(--accent);font-size:10px;cursor:pointer;padding:4px 0">\u25B8 Show ' + (fragments.length - 3) + ' more</div>';
|
|
2920
2902
|
}
|
|
2921
2903
|
}
|
|
2922
2904
|
|
|
@@ -2973,7 +2955,7 @@ export class PageMapGenerator {
|
|
|
2973
2955
|
window.copyToClipboard = function(text, btn) {
|
|
2974
2956
|
navigator.clipboard.writeText(text).then(() => {
|
|
2975
2957
|
const originalText = btn.textContent;
|
|
2976
|
-
btn.textContent = '
|
|
2958
|
+
btn.textContent = '\u2713';
|
|
2977
2959
|
btn.classList.add('copied');
|
|
2978
2960
|
setTimeout(() => {
|
|
2979
2961
|
btn.textContent = originalText;
|
|
@@ -3460,103 +3442,85 @@ export class PageMapGenerator {
|
|
|
3460
3442
|
</script>
|
|
3461
3443
|
</body>
|
|
3462
3444
|
</html>`;
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
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}">
|
|
3445
|
+
}
|
|
3446
|
+
buildTreeHtml(groups, allPages) {
|
|
3447
|
+
const colors = [
|
|
3448
|
+
"#ef4444",
|
|
3449
|
+
"#f97316",
|
|
3450
|
+
"#eab308",
|
|
3451
|
+
"#22c55e",
|
|
3452
|
+
"#14b8a6",
|
|
3453
|
+
"#3b82f6",
|
|
3454
|
+
"#8b5cf6",
|
|
3455
|
+
"#ec4899"
|
|
3456
|
+
];
|
|
3457
|
+
let idx = 0;
|
|
3458
|
+
return Array.from(groups.entries()).sort((a, b) => a[0].localeCompare(b[0])).map(([name, pages]) => {
|
|
3459
|
+
const color = colors[idx++ % colors.length];
|
|
3460
|
+
const sorted = pages.sort((a, b) => a.path.localeCompare(b.path));
|
|
3461
|
+
const pathSet = new Set(sorted.map((p) => p.path));
|
|
3462
|
+
const depthMap = /* @__PURE__ */ new Map();
|
|
3463
|
+
for (const p of sorted) {
|
|
3464
|
+
const segments = p.path.split("/").filter(Boolean);
|
|
3465
|
+
let depth = 0;
|
|
3466
|
+
for (let i = segments.length - 1; i >= 1; i--) {
|
|
3467
|
+
const ancestorPath = "/" + segments.slice(0, i).join("/");
|
|
3468
|
+
if (pathSet.has(ancestorPath)) {
|
|
3469
|
+
depth = (depthMap.get(ancestorPath) ?? 0) + 1;
|
|
3470
|
+
break;
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
depthMap.set(p.path, depth);
|
|
3474
|
+
}
|
|
3475
|
+
const pagesHtml = sorted.map((p) => {
|
|
3476
|
+
const type = this.getPageType(p.path);
|
|
3477
|
+
const queries = (p.dataFetching || []).filter(
|
|
3478
|
+
(d) => !d.type?.includes("Mutation")
|
|
3479
|
+
).length;
|
|
3480
|
+
const mutations = (p.dataFetching || []).filter(
|
|
3481
|
+
(d) => d.type?.includes("Mutation")
|
|
3482
|
+
).length;
|
|
3483
|
+
const depth = depthMap.get(p.path) ?? 0;
|
|
3484
|
+
const pageNode = p;
|
|
3485
|
+
const repoName = pageNode.repo || "";
|
|
3486
|
+
const showRepoTag = allPages.some(
|
|
3487
|
+
(pg) => pg.repo && pg.repo !== repoName
|
|
3488
|
+
);
|
|
3489
|
+
const shortRepoName = repoName.split("/").pop()?.split("-").map((s) => s.substring(0, 4)).join("-") || repoName.substring(0, 8);
|
|
3490
|
+
const repoTag = showRepoTag && repoName ? `<span class="tag tag-repo" title="${repoName}">${shortRepoName}</span>` : "";
|
|
3491
|
+
const isSpaComponent = /^\/[A-Z]/.test(p.path) || p.filePath && p.filePath.includes("components/pages");
|
|
3492
|
+
const displayPath = isSpaComponent && p.filePath ? p.filePath.replace(/\.tsx?$/, "").replace(/^(frontend\/src\/|src\/)/, "") : p.path;
|
|
3493
|
+
const spaTag = isSpaComponent ? '<span class="tag" style="background:#6366f1;color:white" title="SPA Component Page">SPA</span>' : "";
|
|
3494
|
+
return `<div class="page-item" data-path="${p.path}" data-repo="${repoName}" onclick="selectPage('${p.path}')" style="--depth:${depth}">
|
|
3527
3495
|
<span class="page-type" style="--type-color:${type.color}">${type.label}</span>
|
|
3528
3496
|
<span class="page-path">${displayPath}</span>
|
|
3529
3497
|
<div class="page-tags">
|
|
3530
3498
|
${repoTag}
|
|
3531
3499
|
${spaTag}
|
|
3532
|
-
${p.authentication?.required ? '<span class="tag tag-auth">AUTH</span>' :
|
|
3533
|
-
${queries > 0 ? `<span class="tag tag-query">Q:${queries}</span>` :
|
|
3534
|
-
${mutations > 0 ? `<span class="tag tag-mutation">M:${mutations}</span>` :
|
|
3500
|
+
${p.authentication?.required ? '<span class="tag tag-auth">AUTH</span>' : ""}
|
|
3501
|
+
${queries > 0 ? `<span class="tag tag-query">Q:${queries}</span>` : ""}
|
|
3502
|
+
${mutations > 0 ? `<span class="tag tag-mutation">M:${mutations}</span>` : ""}
|
|
3535
3503
|
</div>
|
|
3536
3504
|
</div>`;
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
return `<div class="group">
|
|
3505
|
+
}).join("");
|
|
3506
|
+
return `<div class="group">
|
|
3540
3507
|
<div class="group-header" onclick="toggleGroup(this)" style="--group-color:${color}">
|
|
3541
|
-
<span class="group-arrow"
|
|
3508
|
+
<span class="group-arrow">\u25BC</span>
|
|
3542
3509
|
<span class="group-name">/${name}</span>
|
|
3543
3510
|
<span class="group-count">${pages.length}</span>
|
|
3544
3511
|
</div>
|
|
3545
3512
|
<div class="group-content">${pagesHtml}</div>
|
|
3546
3513
|
</div>`;
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
return { label: 'LIST', color: '#06b6d4' };
|
|
3561
|
-
}
|
|
3562
|
-
}
|
|
3514
|
+
}).join("");
|
|
3515
|
+
}
|
|
3516
|
+
getPageType(path) {
|
|
3517
|
+
const last = path.split("/").filter(Boolean).pop() || "";
|
|
3518
|
+
if (last === "new" || path.endsWith("/new")) return { label: "CREATE", color: "#22c55e" };
|
|
3519
|
+
if (last === "edit" || path.includes("/edit")) return { label: "EDIT", color: "#f59e0b" };
|
|
3520
|
+
if (last.startsWith("[") || last.startsWith(":")) return { label: "DETAIL", color: "#3b82f6" };
|
|
3521
|
+
if (path.includes("setting")) return { label: "SETTINGS", color: "#6b7280" };
|
|
3522
|
+
return { label: "LIST", color: "#06b6d4" };
|
|
3523
|
+
}
|
|
3524
|
+
};
|
|
3525
|
+
|
|
3526
|
+
export { PageMapGenerator };
|