@skspwork/config-doc 2.0.4 → 2.0.5
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/package.json +2 -2
- package/packages/web/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/packages/web/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/packages/web/.next/standalone/.next/server/app/_not-found.rsc +2 -2
- package/packages/web/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/packages/web/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/packages/web/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/packages/web/.next/standalone/.next/server/app/api/config/save/route.js +1 -1
- package/packages/web/.next/standalone/.next/server/app/api/config/save/route.js.nft.json +1 -1
- package/packages/web/.next/standalone/.next/server/app/api/export/route.js +3 -3
- package/packages/web/.next/standalone/.next/server/app/api/export/route.js.nft.json +1 -1
- package/packages/web/.next/standalone/.next/server/app/index.html +1 -1
- package/packages/web/.next/standalone/.next/server/app/index.rsc +3 -3
- package/packages/web/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/packages/web/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/packages/web/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/packages/web/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/packages/web/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/packages/web/.next/standalone/.next/server/chunks/[root-of-the-server]__40e87302._.js +3 -0
- package/packages/web/.next/standalone/.next/server/chunks/[root-of-the-server]__93da9fce._.js +1 -1
- package/packages/web/.next/standalone/.next/server/chunks/[root-of-the-server]__c9655ac8._.js +3 -0
- package/packages/web/.next/standalone/.next/server/chunks/[root-of-the-server]__e19366f6._.js +1 -1
- package/packages/web/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_d09de205.js +345 -27
- package/packages/web/.next/standalone/.next/server/chunks/ssr/app_page_tsx_55b2e5ee._.js +1 -1
- package/packages/web/.next/standalone/.next/server/pages/404.html +1 -1
- package/packages/web/.next/standalone/.next/static/chunks/02de70e4c30afe2f.js +1 -0
- package/packages/web/.next/standalone/.next/static/chunks/862e384b52cfebf3.css +3 -0
- package/packages/web/.next/standalone/app/api/config/metadata/route.ts +5 -3
- package/packages/web/.next/standalone/playwright-report/index.html +1 -1
- package/packages/web/.next/static/chunks/02de70e4c30afe2f.js +1 -0
- package/packages/web/.next/static/chunks/862e384b52cfebf3.css +3 -0
- package/packages/web/.next/standalone/.next/server/chunks/[root-of-the-server]__1a68b1f3._.js +0 -3
- package/packages/web/.next/standalone/.next/server/chunks/[root-of-the-server]__2c94dfea._.js +0 -3
- package/packages/web/.next/standalone/.next/static/chunks/9726c2cde77e0916.js +0 -1
- package/packages/web/.next/standalone/.next/static/chunks/cd878566fda12635.css +0 -3
- package/packages/web/.next/standalone/app/api/config/load/route.ts +0 -57
- package/packages/web/.next/standalone/app/api/config/save/route.ts +0 -73
- package/packages/web/.next/standalone/app/api/export/route.ts +0 -75
- package/packages/web/.next/standalone/app/api/export/settings/route.ts +0 -144
- package/packages/web/.next/standalone/app/api/files/browse/route.ts +0 -46
- package/packages/web/.next/standalone/app/api/project/route.ts +0 -41
- package/packages/web/.next/standalone/app/globals.css +0 -26
- package/packages/web/.next/standalone/app/icon.svg +0 -41
- package/packages/web/.next/standalone/app/layout.tsx +0 -34
- package/packages/web/.next/standalone/app/page.tsx +0 -135
- package/packages/web/.next/standalone/components/ConfigFileTabs.tsx +0 -188
- package/packages/web/.next/standalone/components/ConfigTree.tsx +0 -176
- package/packages/web/.next/standalone/components/EditableList.tsx +0 -337
- package/packages/web/.next/standalone/components/ExportDialog.tsx +0 -234
- package/packages/web/.next/standalone/components/FieldsEditor.tsx +0 -92
- package/packages/web/.next/standalone/components/FileBrowser.tsx +0 -290
- package/packages/web/.next/standalone/components/Header.tsx +0 -37
- package/packages/web/.next/standalone/components/PropertyEditor.tsx +0 -102
- package/packages/web/.next/standalone/components/TagEditor.tsx +0 -86
- package/packages/web/.next/standalone/components/Toast.tsx +0 -91
- package/packages/web/.next/standalone/eslint.config.mjs +0 -18
- package/packages/web/.next/standalone/hooks/useConfigManager.ts +0 -653
- package/packages/web/.next/standalone/lib/configManagerUtils.ts +0 -84
- package/packages/web/.next/standalone/lib/configParser.ts +0 -155
- package/packages/web/.next/standalone/lib/fileSystem.ts +0 -186
- package/packages/web/.next/standalone/lib/getRootPath.ts +0 -45
- package/packages/web/.next/standalone/lib/htmlGenerator.ts +0 -865
- package/packages/web/.next/standalone/lib/jsonUtils.ts +0 -26
- package/packages/web/.next/standalone/lib/markdownGenerator.ts +0 -110
- package/packages/web/.next/standalone/lib/markdownTableGenerator.ts +0 -103
- package/packages/web/.next/standalone/lib/storage.ts +0 -104
- package/packages/web/.next/standalone/lib/utils.ts +0 -89
- package/packages/web/.next/standalone/next.config.ts +0 -10
- package/packages/web/.next/standalone/package-lock.json +0 -8216
- package/packages/web/.next/standalone/playwright.config.ts +0 -27
- package/packages/web/.next/standalone/postcss.config.mjs +0 -7
- package/packages/web/.next/standalone/test-results/.last-run.json +0 -4
- package/packages/web/.next/standalone/tsconfig.json +0 -34
- package/packages/web/.next/standalone/tsconfig.tsbuildinfo +0 -1
- package/packages/web/.next/standalone/types/index.ts +0 -74
- package/packages/web/.next/standalone/vitest.config.ts +0 -14
- package/packages/web/.next/static/chunks/9726c2cde77e0916.js +0 -1
- package/packages/web/.next/static/chunks/cd878566fda12635.css +0 -3
|
@@ -1,865 +0,0 @@
|
|
|
1
|
-
import { FileSystemService } from './fileSystem';
|
|
2
|
-
import { StorageService } from './storage';
|
|
3
|
-
import { escapeHtml } from './utils';
|
|
4
|
-
import { ProjectConfigFiles, ConfigDocs } from '@/types';
|
|
5
|
-
import { sortTagsByOrder } from './configManagerUtils';
|
|
6
|
-
|
|
7
|
-
interface ConfigWithDocs {
|
|
8
|
-
filePath: string;
|
|
9
|
-
fileName: string;
|
|
10
|
-
configData: any;
|
|
11
|
-
docs: ConfigDocs;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export class HtmlGenerator {
|
|
15
|
-
private fsService: FileSystemService;
|
|
16
|
-
private storageService: StorageService;
|
|
17
|
-
|
|
18
|
-
constructor(rootPath: string) {
|
|
19
|
-
this.fsService = new FileSystemService(rootPath);
|
|
20
|
-
this.storageService = new StorageService(this.fsService);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async generateHtml(): Promise<string> {
|
|
24
|
-
// プロジェクト設定を読み込む
|
|
25
|
-
const settings = await this.fsService.loadProjectSettings();
|
|
26
|
-
if (!settings || !settings.configFiles || settings.configFiles.length === 0) {
|
|
27
|
-
return this.generateEmptyHtml();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// フィールドの順序を取得
|
|
31
|
-
const fieldKeys = settings.fields ? Object.keys(settings.fields) : [];
|
|
32
|
-
|
|
33
|
-
// タグの順序を取得
|
|
34
|
-
const availableTags = settings.availableTags || [];
|
|
35
|
-
|
|
36
|
-
// 各設定ファイルとそのドキュメントを読み込む
|
|
37
|
-
const configs: ConfigWithDocs[] = [];
|
|
38
|
-
for (const filePath of settings.configFiles) {
|
|
39
|
-
try {
|
|
40
|
-
const fileName = filePath.split(/[/\\]/).pop() || 'config.json';
|
|
41
|
-
const docsFileName = this.storageService.getDocsFileName(filePath);
|
|
42
|
-
|
|
43
|
-
const configData = await this.fsService.loadConfigFile(filePath);
|
|
44
|
-
const docs = await this.fsService.loadConfigDocs(docsFileName);
|
|
45
|
-
|
|
46
|
-
configs.push({
|
|
47
|
-
filePath,
|
|
48
|
-
fileName,
|
|
49
|
-
configData,
|
|
50
|
-
docs: docs || {
|
|
51
|
-
configFilePath: filePath,
|
|
52
|
-
lastModified: new Date().toISOString(),
|
|
53
|
-
properties: {}
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
} catch (error) {
|
|
57
|
-
console.error(`Failed to load config: ${filePath}`, error);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// 互換性のため ProjectConfigFiles 形式に変換
|
|
62
|
-
const metadata: ProjectConfigFiles = {
|
|
63
|
-
projectName: settings.projectName,
|
|
64
|
-
createdAt: '',
|
|
65
|
-
lastModified: new Date().toISOString(),
|
|
66
|
-
configFiles: []
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
return this.generateFullHtml(metadata, configs, fieldKeys, availableTags);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
private generateEmptyHtml(): string {
|
|
73
|
-
return `<!DOCTYPE html>
|
|
74
|
-
<html lang="ja">
|
|
75
|
-
<head>
|
|
76
|
-
<meta charset="UTF-8">
|
|
77
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
78
|
-
<title>ConfigDoc - ドキュメント</title>
|
|
79
|
-
${this.getStyles()}
|
|
80
|
-
</head>
|
|
81
|
-
<body>
|
|
82
|
-
<div class="container">
|
|
83
|
-
<header>
|
|
84
|
-
<h1>ConfigDoc ドキュメント</h1>
|
|
85
|
-
<p class="subtitle">設定ファイルのドキュメントがまだありません</p>
|
|
86
|
-
</header>
|
|
87
|
-
<main>
|
|
88
|
-
<p>設定ファイルを選択してドキュメントを作成してください。</p>
|
|
89
|
-
</main>
|
|
90
|
-
</div>
|
|
91
|
-
</body>
|
|
92
|
-
</html>`;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
private generateFullHtml(metadata: ProjectConfigFiles, configs: ConfigWithDocs[], fieldKeys: string[], availableTags: string[]): string {
|
|
96
|
-
const configsJson = JSON.stringify(configs, null, 2);
|
|
97
|
-
const fieldKeysJson = JSON.stringify(fieldKeys);
|
|
98
|
-
const availableTagsJson = JSON.stringify(availableTags);
|
|
99
|
-
|
|
100
|
-
return `<!DOCTYPE html>
|
|
101
|
-
<html lang="ja">
|
|
102
|
-
<head>
|
|
103
|
-
<meta charset="UTF-8">
|
|
104
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
105
|
-
<title>${escapeHtml(metadata.projectName)} - ConfigDoc</title>
|
|
106
|
-
${this.getStyles()}
|
|
107
|
-
</head>
|
|
108
|
-
<body>
|
|
109
|
-
<div class="container">
|
|
110
|
-
<header>
|
|
111
|
-
<div class="header-content">
|
|
112
|
-
<h1>${escapeHtml(metadata.projectName)}</h1>
|
|
113
|
-
<p class="meta">最終更新: ${new Date(metadata.lastModified).toLocaleString('ja-JP')}</p>
|
|
114
|
-
</div>
|
|
115
|
-
</header>
|
|
116
|
-
|
|
117
|
-
<div class="content">
|
|
118
|
-
<aside class="sidebar">
|
|
119
|
-
<div class="search-box">
|
|
120
|
-
<input type="text" id="searchInput" placeholder="検索..." />
|
|
121
|
-
</div>
|
|
122
|
-
<div class="config-tabs" id="configTabs"></div>
|
|
123
|
-
<div class="tree-container" id="treeContainer"></div>
|
|
124
|
-
</aside>
|
|
125
|
-
<main class="main-content">
|
|
126
|
-
<div id="propertyDetail" class="property-detail">
|
|
127
|
-
<p class="placeholder">左側のツリーからプロパティを選択してください</p>
|
|
128
|
-
</div>
|
|
129
|
-
</main>
|
|
130
|
-
</div>
|
|
131
|
-
</div>
|
|
132
|
-
|
|
133
|
-
<script>
|
|
134
|
-
const configs = ${configsJson};
|
|
135
|
-
const fieldKeys = ${fieldKeysJson};
|
|
136
|
-
const availableTags = ${availableTagsJson};
|
|
137
|
-
let activeConfigIndex = 0;
|
|
138
|
-
let selectedPath = '';
|
|
139
|
-
let currentSearchQuery = '';
|
|
140
|
-
|
|
141
|
-
// タグをavailableTagsの順序でソート
|
|
142
|
-
function sortTagsByOrder(tags) {
|
|
143
|
-
if (!tags || tags.length === 0) return [];
|
|
144
|
-
return [...tags].sort((a, b) => {
|
|
145
|
-
const indexA = availableTags.indexOf(a);
|
|
146
|
-
const indexB = availableTags.indexOf(b);
|
|
147
|
-
if (indexA !== -1 && indexB !== -1) return indexA - indexB;
|
|
148
|
-
if (indexA === -1) return 1;
|
|
149
|
-
if (indexB === -1) return -1;
|
|
150
|
-
return 0;
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
${this.getScripts()}
|
|
155
|
-
</script>
|
|
156
|
-
</body>
|
|
157
|
-
</html>`;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
private getStyles(): string {
|
|
161
|
-
return `<style>
|
|
162
|
-
* {
|
|
163
|
-
margin: 0;
|
|
164
|
-
padding: 0;
|
|
165
|
-
box-sizing: border-box;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
body {
|
|
169
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
170
|
-
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
|
171
|
-
line-height: 1.6;
|
|
172
|
-
color: #333;
|
|
173
|
-
background-color: #f5f5f5;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
.container {
|
|
177
|
-
max-width: 1400px;
|
|
178
|
-
margin: 0 auto;
|
|
179
|
-
padding: 20px;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
header {
|
|
183
|
-
background: white;
|
|
184
|
-
padding: 30px;
|
|
185
|
-
border-radius: 8px;
|
|
186
|
-
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
187
|
-
margin-bottom: 20px;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
.header-content {
|
|
191
|
-
display: flex;
|
|
192
|
-
align-items: baseline;
|
|
193
|
-
justify-content: space-between;
|
|
194
|
-
gap: 20px;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
header h1 {
|
|
198
|
-
font-size: 2rem;
|
|
199
|
-
color: #2563eb;
|
|
200
|
-
margin: 0;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
.meta {
|
|
204
|
-
color: #999;
|
|
205
|
-
font-size: 0.9rem;
|
|
206
|
-
white-space: nowrap;
|
|
207
|
-
margin: 0;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
.content {
|
|
211
|
-
display: flex;
|
|
212
|
-
gap: 20px;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
.sidebar {
|
|
216
|
-
flex: 0 0 350px;
|
|
217
|
-
background: white;
|
|
218
|
-
border-radius: 8px;
|
|
219
|
-
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
220
|
-
overflow: hidden;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
.search-box {
|
|
224
|
-
padding: 15px;
|
|
225
|
-
border-bottom: 1px solid #e5e7eb;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
#searchInput {
|
|
229
|
-
width: 100%;
|
|
230
|
-
padding: 10px 15px;
|
|
231
|
-
border: 1px solid #ddd;
|
|
232
|
-
border-radius: 4px;
|
|
233
|
-
font-size: 1rem;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
#searchInput:focus {
|
|
237
|
-
outline: none;
|
|
238
|
-
border-color: #2563eb;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
.config-tabs {
|
|
242
|
-
border-bottom: 1px solid #e5e7eb;
|
|
243
|
-
padding: 10px;
|
|
244
|
-
display: flex;
|
|
245
|
-
flex-direction: column;
|
|
246
|
-
gap: 5px;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
.config-tab {
|
|
250
|
-
display: flex;
|
|
251
|
-
flex-direction: column;
|
|
252
|
-
gap: 2px;
|
|
253
|
-
padding: 10px 12px;
|
|
254
|
-
background: #f9fafb;
|
|
255
|
-
border: 1px solid #e5e7eb;
|
|
256
|
-
border-radius: 4px;
|
|
257
|
-
cursor: pointer;
|
|
258
|
-
transition: all 0.2s;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
.config-tab:hover {
|
|
262
|
-
background: #f3f4f6;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
.config-tab.active {
|
|
266
|
-
background: #2563eb;
|
|
267
|
-
color: white;
|
|
268
|
-
border-color: #2563eb;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
.config-tab.active .config-tab-path {
|
|
272
|
-
color: rgba(255, 255, 255, 0.8);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
.config-tab.hidden {
|
|
276
|
-
display: none;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
.config-tab-filename {
|
|
280
|
-
font-weight: 500;
|
|
281
|
-
font-size: 0.9rem;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
.config-tab-path {
|
|
285
|
-
font-size: 0.75rem;
|
|
286
|
-
color: #9ca3af;
|
|
287
|
-
overflow: hidden;
|
|
288
|
-
text-overflow: ellipsis;
|
|
289
|
-
white-space: nowrap;
|
|
290
|
-
max-width: 100%;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
.tree-container {
|
|
294
|
-
padding: 10px;
|
|
295
|
-
max-height: 600px;
|
|
296
|
-
overflow-y: auto;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
.tree-container > ul {
|
|
300
|
-
list-style: none;
|
|
301
|
-
padding: 0;
|
|
302
|
-
margin: 0;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
.tree-node {
|
|
306
|
-
margin-left: 0;
|
|
307
|
-
padding-left: 20px;
|
|
308
|
-
border-left: 1px solid #e5e7eb;
|
|
309
|
-
list-style: none;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
.tree-node.hidden {
|
|
313
|
-
display: none;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
.tree-item {
|
|
317
|
-
padding: 6px 10px;
|
|
318
|
-
margin: 2px 0;
|
|
319
|
-
cursor: pointer;
|
|
320
|
-
border-radius: 6px;
|
|
321
|
-
transition: all 0.15s ease;
|
|
322
|
-
display: flex;
|
|
323
|
-
align-items: center;
|
|
324
|
-
gap: 6px;
|
|
325
|
-
font-size: 0.9rem;
|
|
326
|
-
position: relative;
|
|
327
|
-
background: transparent;
|
|
328
|
-
border: 1px solid transparent;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
.tree-item:hover {
|
|
332
|
-
background: #f9fafb;
|
|
333
|
-
border-color: #e5e7eb;
|
|
334
|
-
transform: translateX(2px);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
.tree-item.selected {
|
|
338
|
-
background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
|
|
339
|
-
color: #1e40af;
|
|
340
|
-
font-weight: 500;
|
|
341
|
-
border-color: #93c5fd;
|
|
342
|
-
box-shadow: 0 1px 3px rgba(59, 130, 246, 0.1);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
.tree-item.has-doc {
|
|
346
|
-
position: relative;
|
|
347
|
-
padding-left: 28px;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
.tree-item.has-doc::before {
|
|
351
|
-
content: '📝';
|
|
352
|
-
position: absolute;
|
|
353
|
-
left: 8px;
|
|
354
|
-
font-size: 0.85rem;
|
|
355
|
-
filter: drop-shadow(0 1px 1px rgba(0,0,0,0.1));
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
.tree-toggle {
|
|
359
|
-
cursor: pointer;
|
|
360
|
-
user-select: none;
|
|
361
|
-
display: inline-flex;
|
|
362
|
-
align-items: center;
|
|
363
|
-
justify-content: center;
|
|
364
|
-
width: 18px;
|
|
365
|
-
height: 18px;
|
|
366
|
-
border-radius: 3px;
|
|
367
|
-
background: #f3f4f6;
|
|
368
|
-
color: #6b7280;
|
|
369
|
-
font-size: 0.7rem;
|
|
370
|
-
transition: all 0.15s ease;
|
|
371
|
-
flex-shrink: 0;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
.tree-toggle:hover {
|
|
375
|
-
background: #e5e7eb;
|
|
376
|
-
color: #374151;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
.tree-label {
|
|
380
|
-
flex: 1;
|
|
381
|
-
overflow: hidden;
|
|
382
|
-
text-overflow: ellipsis;
|
|
383
|
-
white-space: nowrap;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
.tree-item-parent {
|
|
387
|
-
font-weight: 500;
|
|
388
|
-
color: #374151;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
.tree-item-leaf {
|
|
392
|
-
color: #6b7280;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
.main-content {
|
|
396
|
-
flex: 1;
|
|
397
|
-
background: white;
|
|
398
|
-
border-radius: 8px;
|
|
399
|
-
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
400
|
-
padding: 30px;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
.property-detail h2 {
|
|
404
|
-
color: #2563eb;
|
|
405
|
-
margin-bottom: 20px;
|
|
406
|
-
padding-bottom: 10px;
|
|
407
|
-
border-bottom: 2px solid #e5e7eb;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
.property-file {
|
|
411
|
-
font-size: 0.85rem;
|
|
412
|
-
color: #6b7280;
|
|
413
|
-
background: #f9fafb;
|
|
414
|
-
padding: 6px 10px;
|
|
415
|
-
border-radius: 4px;
|
|
416
|
-
margin-bottom: 12px;
|
|
417
|
-
font-family: 'Courier New', monospace;
|
|
418
|
-
overflow: hidden;
|
|
419
|
-
text-overflow: ellipsis;
|
|
420
|
-
white-space: nowrap;
|
|
421
|
-
cursor: help;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
.property-path {
|
|
425
|
-
background: #f3f4f6;
|
|
426
|
-
padding: 10px 15px;
|
|
427
|
-
border-radius: 4px;
|
|
428
|
-
margin-bottom: 20px;
|
|
429
|
-
font-family: 'Courier New', monospace;
|
|
430
|
-
font-size: 0.9rem;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
.property-value {
|
|
434
|
-
background: #fef3c7;
|
|
435
|
-
padding: 10px 15px;
|
|
436
|
-
border-radius: 4px;
|
|
437
|
-
margin-bottom: 20px;
|
|
438
|
-
font-family: 'Courier New', monospace;
|
|
439
|
-
white-space: pre-wrap;
|
|
440
|
-
word-break: break-all;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
.doc-section {
|
|
444
|
-
margin-bottom: 30px;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
.doc-section h3 {
|
|
448
|
-
color: #374151;
|
|
449
|
-
margin-bottom: 10px;
|
|
450
|
-
font-size: 1.1rem;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
.doc-section p {
|
|
454
|
-
color: #4b5563;
|
|
455
|
-
line-height: 1.8;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
.doc-section p.field-value {
|
|
459
|
-
white-space: pre-wrap;
|
|
460
|
-
word-break: break-word;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
.placeholder {
|
|
464
|
-
color: #9ca3af;
|
|
465
|
-
text-align: center;
|
|
466
|
-
padding: 60px 20px;
|
|
467
|
-
font-size: 1.1rem;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
.no-doc-message {
|
|
471
|
-
background: #fef3c7;
|
|
472
|
-
padding: 20px;
|
|
473
|
-
border-radius: 4px;
|
|
474
|
-
border-left: 4px solid #f59e0b;
|
|
475
|
-
margin-top: 20px;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
.tag-list {
|
|
479
|
-
display: flex;
|
|
480
|
-
flex-wrap: wrap;
|
|
481
|
-
gap: 8px;
|
|
482
|
-
margin-top: 10px;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
.tag {
|
|
486
|
-
display: inline-block;
|
|
487
|
-
padding: 4px 12px;
|
|
488
|
-
background: #3b82f6;
|
|
489
|
-
color: white;
|
|
490
|
-
border-radius: 6px;
|
|
491
|
-
font-size: 0.85rem;
|
|
492
|
-
font-weight: 500;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
.hidden {
|
|
496
|
-
display: none;
|
|
497
|
-
}
|
|
498
|
-
</style>`;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
private getScripts(): string {
|
|
502
|
-
return `
|
|
503
|
-
// ツリー構造を構築
|
|
504
|
-
function buildTree(obj, path = '', docs = {}) {
|
|
505
|
-
const tree = [];
|
|
506
|
-
|
|
507
|
-
for (const key in obj) {
|
|
508
|
-
const currentPath = path ? \`\${path}:\${key}\` : key;
|
|
509
|
-
const value = obj[key];
|
|
510
|
-
const hasDoc = docs.properties && docs.properties[currentPath];
|
|
511
|
-
|
|
512
|
-
if (typeof value === 'object' && value !== null) {
|
|
513
|
-
if (Array.isArray(value)) {
|
|
514
|
-
// 配列の場合:オブジェクト要素があれば展開
|
|
515
|
-
const hasObjectElements = value.some(
|
|
516
|
-
item => item && typeof item === 'object' && !Array.isArray(item)
|
|
517
|
-
);
|
|
518
|
-
if (hasObjectElements) {
|
|
519
|
-
const children = [];
|
|
520
|
-
value.forEach((item, index) => {
|
|
521
|
-
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
|
522
|
-
const elementPath = \`\${currentPath}[\${index}]\`;
|
|
523
|
-
children.push({
|
|
524
|
-
key: \`[\${index}]\`,
|
|
525
|
-
path: elementPath,
|
|
526
|
-
value: item,
|
|
527
|
-
hasChildren: true,
|
|
528
|
-
hasDoc: !!(docs.properties && docs.properties[elementPath]),
|
|
529
|
-
children: buildTree(item, elementPath, docs)
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
});
|
|
533
|
-
tree.push({
|
|
534
|
-
key,
|
|
535
|
-
path: currentPath,
|
|
536
|
-
value: value,
|
|
537
|
-
hasChildren: children.length > 0,
|
|
538
|
-
hasDoc: !!hasDoc,
|
|
539
|
-
children: children
|
|
540
|
-
});
|
|
541
|
-
} else {
|
|
542
|
-
// プリミティブ配列は展開しない
|
|
543
|
-
tree.push({
|
|
544
|
-
key,
|
|
545
|
-
path: currentPath,
|
|
546
|
-
value: value,
|
|
547
|
-
hasChildren: false,
|
|
548
|
-
hasDoc: !!hasDoc,
|
|
549
|
-
children: []
|
|
550
|
-
});
|
|
551
|
-
}
|
|
552
|
-
} else {
|
|
553
|
-
// オブジェクトの場合(既存ロジック)
|
|
554
|
-
tree.push({
|
|
555
|
-
key,
|
|
556
|
-
path: currentPath,
|
|
557
|
-
value: value,
|
|
558
|
-
hasChildren: true,
|
|
559
|
-
hasDoc: !!hasDoc,
|
|
560
|
-
children: buildTree(value, currentPath, docs)
|
|
561
|
-
});
|
|
562
|
-
}
|
|
563
|
-
} else {
|
|
564
|
-
tree.push({
|
|
565
|
-
key,
|
|
566
|
-
path: currentPath,
|
|
567
|
-
value: value,
|
|
568
|
-
hasChildren: false,
|
|
569
|
-
hasDoc: !!hasDoc,
|
|
570
|
-
children: []
|
|
571
|
-
});
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
return tree;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
// ツリーをレンダリング
|
|
579
|
-
function renderTree(nodes, containerEl, level = 0) {
|
|
580
|
-
const ul = document.createElement('ul');
|
|
581
|
-
ul.className = level === 0 ? '' : 'tree-node';
|
|
582
|
-
|
|
583
|
-
nodes.forEach(node => {
|
|
584
|
-
const li = document.createElement('li');
|
|
585
|
-
const itemDiv = document.createElement('div');
|
|
586
|
-
|
|
587
|
-
// クラス名を組み立て
|
|
588
|
-
let className = 'tree-item';
|
|
589
|
-
if (node.hasDoc) className += ' has-doc';
|
|
590
|
-
if (node.hasChildren) className += ' tree-item-parent';
|
|
591
|
-
else className += ' tree-item-leaf';
|
|
592
|
-
itemDiv.className = className;
|
|
593
|
-
itemDiv.dataset.path = node.path;
|
|
594
|
-
|
|
595
|
-
if (node.hasChildren) {
|
|
596
|
-
const toggle = document.createElement('span');
|
|
597
|
-
toggle.className = 'tree-toggle';
|
|
598
|
-
toggle.innerHTML = '▾';
|
|
599
|
-
itemDiv.appendChild(toggle);
|
|
600
|
-
|
|
601
|
-
const label = document.createElement('span');
|
|
602
|
-
label.className = 'tree-label';
|
|
603
|
-
label.textContent = node.key;
|
|
604
|
-
itemDiv.appendChild(label);
|
|
605
|
-
|
|
606
|
-
li.appendChild(itemDiv);
|
|
607
|
-
|
|
608
|
-
const childrenUl = document.createElement('ul');
|
|
609
|
-
childrenUl.className = 'tree-node';
|
|
610
|
-
renderTree(node.children, childrenUl, level + 1);
|
|
611
|
-
li.appendChild(childrenUl);
|
|
612
|
-
|
|
613
|
-
toggle.addEventListener('click', (e) => {
|
|
614
|
-
e.stopPropagation();
|
|
615
|
-
childrenUl.classList.toggle('hidden');
|
|
616
|
-
toggle.innerHTML = childrenUl.classList.contains('hidden') ? '▸' : '▾';
|
|
617
|
-
});
|
|
618
|
-
} else {
|
|
619
|
-
const label = document.createElement('span');
|
|
620
|
-
label.className = 'tree-label';
|
|
621
|
-
label.textContent = node.key;
|
|
622
|
-
itemDiv.appendChild(label);
|
|
623
|
-
li.appendChild(itemDiv);
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
itemDiv.addEventListener('click', () => {
|
|
627
|
-
document.querySelectorAll('.tree-item').forEach(el => el.classList.remove('selected'));
|
|
628
|
-
itemDiv.classList.add('selected');
|
|
629
|
-
selectedPath = node.path;
|
|
630
|
-
showPropertyDetail(node);
|
|
631
|
-
});
|
|
632
|
-
|
|
633
|
-
ul.appendChild(li);
|
|
634
|
-
});
|
|
635
|
-
|
|
636
|
-
containerEl.appendChild(ul);
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
// プロパティ詳細を表示
|
|
640
|
-
function showPropertyDetail(node) {
|
|
641
|
-
const detailEl = document.getElementById('propertyDetail');
|
|
642
|
-
const config = configs[activeConfigIndex];
|
|
643
|
-
const doc = config.docs.properties && config.docs.properties[node.path];
|
|
644
|
-
|
|
645
|
-
let html = \`<h2>\${escapeHtml(node.key)}</h2>\`;
|
|
646
|
-
html += \`<div class="property-file" title="\${escapeHtml(config.filePath)}">ファイル: \${escapeHtml(config.filePath)}</div>\`;
|
|
647
|
-
html += \`<div class="property-path">パス: \${escapeHtml(node.path)}</div>\`;
|
|
648
|
-
html += \`<div class="property-value">値: \${escapeHtml(JSON.stringify(node.value, null, 2))}</div>\`;
|
|
649
|
-
|
|
650
|
-
if (doc) {
|
|
651
|
-
if (doc.tags && doc.tags.length > 0) {
|
|
652
|
-
const sortedTags = sortTagsByOrder(doc.tags);
|
|
653
|
-
html += \`<div class="doc-section">
|
|
654
|
-
<h3>タグ</h3>
|
|
655
|
-
<div class="tag-list">\`;
|
|
656
|
-
sortedTags.forEach(tag => {
|
|
657
|
-
html += \`<span class="tag">\${escapeHtml(tag)}</span>\`;
|
|
658
|
-
});
|
|
659
|
-
html += \`</div>
|
|
660
|
-
</div>\`;
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
// フィールドをprojectFieldsの順序で表示
|
|
664
|
-
fieldKeys.forEach(label => {
|
|
665
|
-
const value = doc.fields[label];
|
|
666
|
-
if (value) {
|
|
667
|
-
html += \`<div class="doc-section">
|
|
668
|
-
<h3>\${escapeHtml(label)}</h3>
|
|
669
|
-
<p class="field-value">\${escapeHtml(value)}</p>
|
|
670
|
-
</div>\`;
|
|
671
|
-
}
|
|
672
|
-
});
|
|
673
|
-
|
|
674
|
-
if (doc.modifiedAt) {
|
|
675
|
-
html += \`<div class="doc-section">
|
|
676
|
-
<p style="color: #9ca3af; font-size: 0.9rem;">
|
|
677
|
-
最終更新: \${new Date(doc.modifiedAt).toLocaleString('ja-JP')}
|
|
678
|
-
</p>
|
|
679
|
-
</div>\`;
|
|
680
|
-
}
|
|
681
|
-
} else {
|
|
682
|
-
html += \`<div class="no-doc-message">
|
|
683
|
-
<p>このプロパティにはまだドキュメントが作成されていません。</p>
|
|
684
|
-
</div>\`;
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
detailEl.innerHTML = html;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
// 設定タブを描画
|
|
691
|
-
function renderConfigTabs(matchedConfigIndexes = null) {
|
|
692
|
-
const tabsEl = document.getElementById('configTabs');
|
|
693
|
-
tabsEl.innerHTML = '';
|
|
694
|
-
|
|
695
|
-
configs.forEach((config, index) => {
|
|
696
|
-
const tab = document.createElement('div');
|
|
697
|
-
let className = 'config-tab';
|
|
698
|
-
|
|
699
|
-
if (index === activeConfigIndex) {
|
|
700
|
-
className += ' active';
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
// 検索中で、マッチしない設定ファイルは非表示
|
|
704
|
-
if (matchedConfigIndexes !== null && !matchedConfigIndexes.includes(index)) {
|
|
705
|
-
className += ' hidden';
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
tab.className = className;
|
|
709
|
-
tab.innerHTML = \`
|
|
710
|
-
<div class="config-tab-filename">\${escapeHtml(config.fileName)}</div>
|
|
711
|
-
<div class="config-tab-path" title="\${escapeHtml(config.filePath)}">\${escapeHtml(config.filePath)}</div>
|
|
712
|
-
\`;
|
|
713
|
-
tab.addEventListener('click', () => {
|
|
714
|
-
activeConfigIndex = index;
|
|
715
|
-
renderConfigTabs(matchedConfigIndexes);
|
|
716
|
-
renderCurrentConfig();
|
|
717
|
-
// タブ切り替え後、検索フィルタを適用
|
|
718
|
-
if (currentSearchQuery) {
|
|
719
|
-
applySearchFilter(currentSearchQuery);
|
|
720
|
-
}
|
|
721
|
-
});
|
|
722
|
-
tabsEl.appendChild(tab);
|
|
723
|
-
});
|
|
724
|
-
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
// 現在の設定を描画
|
|
728
|
-
function renderCurrentConfig() {
|
|
729
|
-
const treeEl = document.getElementById('treeContainer');
|
|
730
|
-
treeEl.innerHTML = '';
|
|
731
|
-
|
|
732
|
-
const config = configs[activeConfigIndex];
|
|
733
|
-
const tree = buildTree(config.configData, '', config.docs);
|
|
734
|
-
renderTree(tree, treeEl);
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
// 検索フィルタを適用(ツリー項目のフィルタリング - プロパティ名、パス、説明、備考を検索)
|
|
738
|
-
function applySearchFilter(query) {
|
|
739
|
-
const config = configs[activeConfigIndex];
|
|
740
|
-
const items = document.querySelectorAll('.tree-item');
|
|
741
|
-
|
|
742
|
-
items.forEach(item => {
|
|
743
|
-
const text = item.textContent.toLowerCase();
|
|
744
|
-
const path = item.dataset.path.toLowerCase();
|
|
745
|
-
let matched = false;
|
|
746
|
-
|
|
747
|
-
// プロパティ名とパスで検索
|
|
748
|
-
if (text.includes(query) || path.includes(query)) {
|
|
749
|
-
matched = true;
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
// ドキュメント(フィールド内容)でも検索
|
|
753
|
-
if (!matched) {
|
|
754
|
-
const doc = config.docs.properties && config.docs.properties[item.dataset.path];
|
|
755
|
-
if (doc && doc.fields) {
|
|
756
|
-
for (const [label, value] of Object.entries(doc.fields)) {
|
|
757
|
-
if (value && value.toLowerCase().includes(query)) {
|
|
758
|
-
matched = true;
|
|
759
|
-
break;
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
if (matched) {
|
|
766
|
-
item.style.display = '';
|
|
767
|
-
// 親要素も表示
|
|
768
|
-
let parent = item.parentElement;
|
|
769
|
-
while (parent) {
|
|
770
|
-
if (parent.classList.contains('tree-node')) {
|
|
771
|
-
parent.classList.remove('hidden');
|
|
772
|
-
}
|
|
773
|
-
parent = parent.parentElement;
|
|
774
|
-
}
|
|
775
|
-
} else {
|
|
776
|
-
item.style.display = 'none';
|
|
777
|
-
}
|
|
778
|
-
});
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
// 検索機能(全設定ファイル対象)
|
|
782
|
-
function setupSearch() {
|
|
783
|
-
const searchInput = document.getElementById('searchInput');
|
|
784
|
-
searchInput.addEventListener('input', (e) => {
|
|
785
|
-
const query = e.target.value.toLowerCase();
|
|
786
|
-
currentSearchQuery = query;
|
|
787
|
-
|
|
788
|
-
if (!query.trim()) {
|
|
789
|
-
// 検索クエリが空の場合、すべてのタブを表示
|
|
790
|
-
renderConfigTabs(null);
|
|
791
|
-
renderCurrentConfig();
|
|
792
|
-
return;
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
// 全設定ファイルから検索してマッチした項目を収集
|
|
796
|
-
let foundInConfigs = [];
|
|
797
|
-
configs.forEach((config, index) => {
|
|
798
|
-
const tree = buildTree(config.configData, '', config.docs);
|
|
799
|
-
const hasMatch = searchInTreeForConfig(tree, query, config);
|
|
800
|
-
if (hasMatch) {
|
|
801
|
-
foundInConfigs.push(index);
|
|
802
|
-
}
|
|
803
|
-
});
|
|
804
|
-
|
|
805
|
-
// マッチした設定ファイルのタブのみ表示
|
|
806
|
-
renderConfigTabs(foundInConfigs);
|
|
807
|
-
|
|
808
|
-
// マッチした最初の設定ファイルに切り替え(現在のタブがマッチしていない場合)
|
|
809
|
-
if (foundInConfigs.length > 0 && !foundInConfigs.includes(activeConfigIndex)) {
|
|
810
|
-
activeConfigIndex = foundInConfigs[0];
|
|
811
|
-
renderConfigTabs(foundInConfigs);
|
|
812
|
-
renderCurrentConfig();
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
// 現在表示中のツリー項目をフィルタリング
|
|
816
|
-
applySearchFilter(query);
|
|
817
|
-
});
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
// ツリー内を検索してマッチするか判定(特定の設定ファイル用)
|
|
821
|
-
function searchInTreeForConfig(nodes, query, config) {
|
|
822
|
-
for (const node of nodes) {
|
|
823
|
-
const text = node.key.toLowerCase();
|
|
824
|
-
const path = node.path.toLowerCase();
|
|
825
|
-
|
|
826
|
-
// プロパティ名とパスで検索
|
|
827
|
-
if (text.includes(query) || path.includes(query)) {
|
|
828
|
-
return true;
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
// ドキュメント(フィールド内容)でも検索
|
|
832
|
-
const doc = config.docs.properties && config.docs.properties[node.path];
|
|
833
|
-
if (doc && doc.fields) {
|
|
834
|
-
for (const [label, value] of Object.entries(doc.fields)) {
|
|
835
|
-
if (value && value.toLowerCase().includes(query)) {
|
|
836
|
-
return true;
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
if (node.children && node.children.length > 0) {
|
|
842
|
-
if (searchInTreeForConfig(node.children, query, config)) {
|
|
843
|
-
return true;
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
return false;
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
// HTML エスケープ
|
|
851
|
-
function escapeHtml(text) {
|
|
852
|
-
const div = document.createElement('div');
|
|
853
|
-
div.textContent = text;
|
|
854
|
-
return div.innerHTML;
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
// 初期化
|
|
858
|
-
if (configs.length > 0) {
|
|
859
|
-
renderConfigTabs();
|
|
860
|
-
renderCurrentConfig();
|
|
861
|
-
setupSearch();
|
|
862
|
-
}
|
|
863
|
-
`;
|
|
864
|
-
}
|
|
865
|
-
}
|