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