@skspwork/config-doc 2.0.4 → 2.0.6

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 (78) hide show
  1. package/package.json +2 -3
  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 +345 -27
  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/83ebf4df338dae62.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/playwright-report/index.html +1 -1
  30. package/packages/web/.next/static/chunks/83ebf4df338dae62.js +1 -0
  31. package/packages/web/.next/static/chunks/862e384b52cfebf3.css +3 -0
  32. package/packages/web/.next/standalone/.next/server/chunks/[root-of-the-server]__1a68b1f3._.js +0 -3
  33. package/packages/web/.next/standalone/.next/server/chunks/[root-of-the-server]__2c94dfea._.js +0 -3
  34. package/packages/web/.next/standalone/.next/static/chunks/9726c2cde77e0916.js +0 -1
  35. package/packages/web/.next/standalone/.next/static/chunks/cd878566fda12635.css +0 -3
  36. package/packages/web/.next/standalone/app/api/config/load/route.ts +0 -57
  37. package/packages/web/.next/standalone/app/api/config/save/route.ts +0 -73
  38. package/packages/web/.next/standalone/app/api/export/route.ts +0 -75
  39. package/packages/web/.next/standalone/app/api/export/settings/route.ts +0 -144
  40. package/packages/web/.next/standalone/app/api/files/browse/route.ts +0 -46
  41. package/packages/web/.next/standalone/app/api/project/route.ts +0 -41
  42. package/packages/web/.next/standalone/app/globals.css +0 -26
  43. package/packages/web/.next/standalone/app/icon.svg +0 -41
  44. package/packages/web/.next/standalone/app/layout.tsx +0 -34
  45. package/packages/web/.next/standalone/app/page.tsx +0 -135
  46. package/packages/web/.next/standalone/components/ConfigFileTabs.tsx +0 -188
  47. package/packages/web/.next/standalone/components/ConfigTree.tsx +0 -176
  48. package/packages/web/.next/standalone/components/EditableList.tsx +0 -337
  49. package/packages/web/.next/standalone/components/ExportDialog.tsx +0 -234
  50. package/packages/web/.next/standalone/components/FieldsEditor.tsx +0 -92
  51. package/packages/web/.next/standalone/components/FileBrowser.tsx +0 -290
  52. package/packages/web/.next/standalone/components/Header.tsx +0 -37
  53. package/packages/web/.next/standalone/components/PropertyEditor.tsx +0 -102
  54. package/packages/web/.next/standalone/components/TagEditor.tsx +0 -86
  55. package/packages/web/.next/standalone/components/Toast.tsx +0 -91
  56. package/packages/web/.next/standalone/eslint.config.mjs +0 -18
  57. package/packages/web/.next/standalone/hooks/useConfigManager.ts +0 -653
  58. package/packages/web/.next/standalone/lib/configManagerUtils.ts +0 -84
  59. package/packages/web/.next/standalone/lib/configParser.ts +0 -155
  60. package/packages/web/.next/standalone/lib/fileSystem.ts +0 -186
  61. package/packages/web/.next/standalone/lib/getRootPath.ts +0 -45
  62. package/packages/web/.next/standalone/lib/htmlGenerator.ts +0 -865
  63. package/packages/web/.next/standalone/lib/jsonUtils.ts +0 -26
  64. package/packages/web/.next/standalone/lib/markdownGenerator.ts +0 -110
  65. package/packages/web/.next/standalone/lib/markdownTableGenerator.ts +0 -103
  66. package/packages/web/.next/standalone/lib/storage.ts +0 -104
  67. package/packages/web/.next/standalone/lib/utils.ts +0 -89
  68. package/packages/web/.next/standalone/next.config.ts +0 -10
  69. package/packages/web/.next/standalone/package-lock.json +0 -8216
  70. package/packages/web/.next/standalone/playwright.config.ts +0 -27
  71. package/packages/web/.next/standalone/postcss.config.mjs +0 -7
  72. package/packages/web/.next/standalone/test-results/.last-run.json +0 -4
  73. package/packages/web/.next/standalone/tsconfig.json +0 -34
  74. package/packages/web/.next/standalone/tsconfig.tsbuildinfo +0 -1
  75. package/packages/web/.next/standalone/types/index.ts +0 -74
  76. package/packages/web/.next/standalone/vitest.config.ts +0 -14
  77. package/packages/web/.next/static/chunks/9726c2cde77e0916.js +0 -1
  78. package/packages/web/.next/static/chunks/cd878566fda12635.css +0 -3
@@ -1,337 +0,0 @@
1
- 'use client';
2
-
3
- import { useState, useMemo } from 'react';
4
- import { PencilIcon, PlusIcon, XIcon, SaveIcon, GripVerticalIcon } from 'lucide-react';
5
-
6
- export interface EditingItem {
7
- originalName: string;
8
- newName: string;
9
- isNew: boolean;
10
- }
11
-
12
- interface EditableListWrapperProps {
13
- /** セクションのラベル */
14
- label: string;
15
- /** 現在のアイテム一覧 */
16
- items: string[];
17
- /** アイテム一覧が変更されたときのコールバック */
18
- onItemsChange: (items: string[]) => void;
19
- /** 編集ボタンのtitle属性 */
20
- editButtonTitle: string;
21
- /** 編集モードの説明テキスト */
22
- editModeDescription: string;
23
- /** 入力欄のプレースホルダー */
24
- inputPlaceholder: string;
25
- /** 新規追加入力欄のプレースホルダー */
26
- newItemPlaceholder: string;
27
- /** 削除ボタンのtitle属性 */
28
- deleteButtonTitle: string;
29
- /** 追加ボタンのtitle属性 */
30
- addButtonTitle: string;
31
- /** 重複エラーメッセージ */
32
- duplicateErrorMessage: string;
33
- /** 名前変更時に呼ばれるコールバック(旧名 -> 新名のマップ) */
34
- onRename?: (renamedMap: Record<string, string>) => void;
35
- /** 通常モードの表示内容 */
36
- children: React.ReactNode;
37
- }
38
-
39
- /**
40
- * 編集可能なリストを管理する共通コンポーネント
41
- * タグやフィールドの編集UIで使用
42
- */
43
- export function EditableListWrapper({
44
- label,
45
- items,
46
- onItemsChange,
47
- editButtonTitle,
48
- editModeDescription,
49
- inputPlaceholder,
50
- newItemPlaceholder,
51
- deleteButtonTitle,
52
- addButtonTitle,
53
- duplicateErrorMessage,
54
- onRename,
55
- children
56
- }: EditableListWrapperProps) {
57
- const [isEditMode, setIsEditMode] = useState(false);
58
- const [editingItems, setEditingItems] = useState<EditingItem[]>([]);
59
- const [newItemName, setNewItemName] = useState('');
60
-
61
- // ドラッグ&ドロップ用の状態
62
- const [dragIndex, setDragIndex] = useState<number | null>(null);
63
- const [dragOverIndex, setDragOverIndex] = useState<number | null>(null);
64
-
65
- // ドラッグ中のプレビュー用に並び替えた配列を計算
66
- const displayItems = useMemo(() => {
67
- if (dragIndex === null || dragOverIndex === null || dragIndex === dragOverIndex) {
68
- return editingItems.map((item, index) => ({ item, originalIndex: index }));
69
- }
70
-
71
- // ドラッグ中は見た目上の並び替えを表示
72
- const result = editingItems.map((item, index) => ({ item, originalIndex: index }));
73
- const [draggedItem] = result.splice(dragIndex, 1);
74
- result.splice(dragOverIndex, 0, draggedItem);
75
- return result;
76
- }, [editingItems, dragIndex, dragOverIndex]);
77
-
78
- // 編集モードに入る
79
- const enterEditMode = () => {
80
- const itemList = items.map(name => ({
81
- originalName: name,
82
- newName: name,
83
- isNew: false
84
- }));
85
- setEditingItems(itemList);
86
- setIsEditMode(true);
87
- setNewItemName('');
88
- };
89
-
90
- // 編集モードをキャンセル
91
- const cancelEditMode = () => {
92
- setIsEditMode(false);
93
- setEditingItems([]);
94
- setNewItemName('');
95
- setDragIndex(null);
96
- setDragOverIndex(null);
97
- };
98
-
99
- // アイテムを追加(編集モード内)
100
- const handleAddItem = () => {
101
- const trimmedName = newItemName.trim();
102
- if (trimmedName && !editingItems.some(item => item.newName === trimmedName)) {
103
- setEditingItems([...editingItems, {
104
- originalName: '',
105
- newName: trimmedName,
106
- isNew: true
107
- }]);
108
- setNewItemName('');
109
- }
110
- };
111
-
112
- // アイテムを削除(編集モード内)
113
- const handleRemoveItem = (originalIndex: number) => {
114
- setEditingItems(editingItems.filter((_, i) => i !== originalIndex));
115
- };
116
-
117
- // アイテム名を変更(編集モード内)
118
- const handleNameChange = (originalIndex: number, newName: string) => {
119
- setEditingItems(editingItems.map((item, i) =>
120
- i === originalIndex ? { ...item, newName } : item
121
- ));
122
- };
123
-
124
- // ドラッグ開始
125
- const handleDragStart = (originalIndex: number) => {
126
- setDragIndex(originalIndex);
127
- };
128
-
129
- // ドラッグオーバー(表示上のインデックスで処理)
130
- const handleDragOver = (e: React.DragEvent, displayIndex: number) => {
131
- e.preventDefault();
132
- if (dragIndex === null) return;
133
-
134
- // 表示上のインデックスをそのまま使用
135
- if (dragOverIndex !== displayIndex) {
136
- setDragOverIndex(displayIndex);
137
- }
138
- };
139
-
140
- // ドロップ時にデータを確定
141
- const handleDrop = () => {
142
- if (dragIndex === null || dragOverIndex === null || dragIndex === dragOverIndex) {
143
- setDragIndex(null);
144
- setDragOverIndex(null);
145
- return;
146
- }
147
-
148
- // 配列の並び替えを確定
149
- const newItems = [...editingItems];
150
- const [draggedItem] = newItems.splice(dragIndex, 1);
151
- newItems.splice(dragOverIndex, 0, draggedItem);
152
- setEditingItems(newItems);
153
-
154
- setDragIndex(null);
155
- setDragOverIndex(null);
156
- };
157
-
158
- // ドラッグ終了(キャンセル時など)
159
- const handleDragEnd = () => {
160
- setDragIndex(null);
161
- setDragOverIndex(null);
162
- };
163
-
164
- // 保存処理
165
- const handleSave = () => {
166
- const newItems: string[] = [];
167
- const renamedMap: Record<string, string> = {};
168
-
169
- for (const item of editingItems) {
170
- const trimmedName = item.newName.trim();
171
- if (!trimmedName) continue;
172
-
173
- newItems.push(trimmedName);
174
-
175
- // 名前変更の追跡
176
- if (!item.isNew && item.originalName !== trimmedName) {
177
- renamedMap[item.originalName] = trimmedName;
178
- }
179
- }
180
-
181
- // 入力中のアイテムがあれば追加
182
- const pendingName = newItemName.trim();
183
- if (pendingName && !newItems.includes(pendingName)) {
184
- newItems.push(pendingName);
185
- }
186
-
187
- // 名前変更コールバック
188
- if (onRename && Object.keys(renamedMap).length > 0) {
189
- onRename(renamedMap);
190
- }
191
-
192
- onItemsChange(newItems);
193
- setIsEditMode(false);
194
- setEditingItems([]);
195
- setNewItemName('');
196
- };
197
-
198
- const handleKeyDown = (e: React.KeyboardEvent) => {
199
- if (e.key === 'Enter') {
200
- e.preventDefault();
201
- handleAddItem();
202
- }
203
- };
204
-
205
- // バリデーション: 重複チェック
206
- const hasDuplicateNames = () => {
207
- const names = editingItems.map(item => item.newName.trim()).filter(Boolean);
208
- return new Set(names).size !== names.length;
209
- };
210
-
211
- // バリデーション: 空の名前チェック
212
- const hasEmptyNames = () => {
213
- return editingItems.some(item => !item.newName.trim());
214
- };
215
-
216
- const canSave = !hasDuplicateNames() && !hasEmptyNames();
217
-
218
- return (
219
- <div>
220
- <div className="flex items-center justify-between mb-2">
221
- <label className="block text-sm font-semibold text-gray-700">
222
- {label}
223
- </label>
224
- {!isEditMode && (
225
- <button
226
- onClick={enterEditMode}
227
- className="text-gray-500 hover:text-blue-600 transition-colors p-1"
228
- title={editButtonTitle}
229
- >
230
- <PencilIcon className="w-4 h-4" />
231
- </button>
232
- )}
233
- </div>
234
-
235
- {isEditMode ? (
236
- <div className="space-y-3 p-4 bg-blue-50 rounded-lg border-2 border-blue-200">
237
- <div className="text-xs font-medium text-blue-700 mb-2">
238
- {editModeDescription}(ドラッグで並び替え可能)
239
- </div>
240
-
241
- {/* 既存アイテムの編集 */}
242
- <div className="space-y-2">
243
- {displayItems.map(({ item, originalIndex }, displayIndex) => {
244
- const isDragging = dragIndex === originalIndex;
245
-
246
- return (
247
- <div
248
- key={originalIndex}
249
- draggable
250
- onDragStart={() => handleDragStart(originalIndex)}
251
- onDragOver={(e) => handleDragOver(e, displayIndex)}
252
- onDrop={handleDrop}
253
- onDragEnd={handleDragEnd}
254
- className={`flex items-center gap-2 transition-all ${
255
- isDragging ? 'opacity-50' : ''
256
- }`}
257
- >
258
- <div
259
- className="cursor-grab text-gray-400 hover:text-gray-600 p-1"
260
- title="ドラッグして並び替え"
261
- >
262
- <GripVerticalIcon className="w-4 h-4" />
263
- </div>
264
- <input
265
- type="text"
266
- value={item.newName}
267
- onChange={(e) => handleNameChange(originalIndex, e.target.value)}
268
- placeholder={inputPlaceholder}
269
- className={`flex-1 px-3 py-2 border-2 rounded-lg text-sm focus:ring-2 transition-all duration-200 ${
270
- !item.newName.trim()
271
- ? 'border-red-300 focus:border-red-400 focus:ring-red-200'
272
- : 'border-gray-200 focus:border-blue-400 focus:ring-blue-200'
273
- }`}
274
- />
275
- <button
276
- onClick={() => handleRemoveItem(originalIndex)}
277
- className="text-gray-400 hover:text-red-600 transition-colors p-2"
278
- title={deleteButtonTitle}
279
- >
280
- <XIcon className="w-4 h-4" />
281
- </button>
282
- </div>
283
- );
284
- })}
285
- </div>
286
-
287
- {/* 新規アイテム追加 */}
288
- <div className="flex items-center gap-2 pt-2 border-t border-blue-200">
289
- <input
290
- type="text"
291
- value={newItemName}
292
- onChange={(e) => setNewItemName(e.target.value)}
293
- onKeyDown={handleKeyDown}
294
- placeholder={newItemPlaceholder}
295
- className="flex-1 px-3 py-2 border-2 border-gray-200 rounded-lg text-sm focus:border-blue-400 focus:ring-2 focus:ring-blue-200 transition-all duration-200"
296
- />
297
- <button
298
- onClick={handleAddItem}
299
- disabled={!newItemName.trim() || editingItems.some(item => item.newName.trim() === newItemName.trim())}
300
- className="px-3 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors disabled:bg-gray-300 disabled:cursor-not-allowed"
301
- title={addButtonTitle}
302
- >
303
- <PlusIcon className="w-4 h-4" />
304
- </button>
305
- </div>
306
-
307
- {/* エラーメッセージ */}
308
- {hasDuplicateNames() && (
309
- <div className="text-xs text-red-600 mt-2">
310
- {duplicateErrorMessage}
311
- </div>
312
- )}
313
-
314
- {/* 保存・キャンセルボタン */}
315
- <div className="flex items-center gap-2 pt-3 border-t border-blue-200">
316
- <button
317
- onClick={handleSave}
318
- disabled={!canSave}
319
- className="flex-1 flex items-center justify-center gap-2 px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors disabled:bg-gray-300 disabled:cursor-not-allowed text-sm font-medium"
320
- >
321
- <SaveIcon className="w-4 h-4" />
322
- 保存
323
- </button>
324
- <button
325
- onClick={cancelEditMode}
326
- className="flex-1 px-4 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 transition-colors text-sm font-medium"
327
- >
328
- キャンセル
329
- </button>
330
- </div>
331
- </div>
332
- ) : (
333
- children
334
- )}
335
- </div>
336
- );
337
- }
@@ -1,234 +0,0 @@
1
- import { useState, useEffect, useMemo } from 'react';
2
- import { ExportSettings, ExportFormat } from '@/types';
3
- import { XIcon, DownloadIcon, FolderIcon } from 'lucide-react';
4
- import { FileBrowser } from './FileBrowser';
5
-
6
- interface ExportDialogProps {
7
- isOpen: boolean;
8
- onClose: () => void;
9
- onExport: (settings: ExportSettings) => void;
10
- currentSettings?: ExportSettings;
11
- rootPath?: string;
12
- }
13
-
14
- const DEFAULT_SETTINGS: ExportSettings = {
15
- format: 'html',
16
- autoExport: true,
17
- fileName: 'config-doc',
18
- outputDir: '.config_doc/output'
19
- };
20
-
21
- export function ExportDialog({ isOpen, onClose, onExport, currentSettings, rootPath = '.' }: ExportDialogProps) {
22
- const [settings, setSettings] = useState<ExportSettings>(currentSettings || DEFAULT_SETTINGS);
23
- const [isExporting, setIsExporting] = useState(false);
24
- const [isFolderBrowserOpen, setIsFolderBrowserOpen] = useState(false);
25
-
26
- useEffect(() => {
27
- if (currentSettings) {
28
- setSettings(currentSettings);
29
- }
30
- }, [currentSettings]);
31
-
32
- // フォーマットに応じて出力先パスを決定
33
- const absoluteOutputPath = useMemo(() => {
34
- const normalized = rootPath.replace(/\//g, '\\');
35
- const outputDir = settings.outputDir?.trim() || '';
36
- const fileName = settings.fileName || 'config-doc';
37
- const extension = (settings.format === 'markdown' || settings.format === 'markdown-table') ? 'md' : 'html';
38
- if (outputDir) {
39
- const normalizedOutputDir = outputDir.replace(/\//g, '\\');
40
- return `${normalized}\\${normalizedOutputDir}\\${fileName}.${extension}`;
41
- }
42
- return `${normalized}\\${fileName}.${extension}`;
43
- }, [settings.format, settings.fileName, settings.outputDir, rootPath]);
44
-
45
- if (!isOpen) return null;
46
-
47
- const handleExport = async () => {
48
- setIsExporting(true);
49
- try {
50
- await onExport(settings);
51
- onClose();
52
- } catch (error) {
53
- console.error('Export failed:', error);
54
- } finally {
55
- setIsExporting(false);
56
- }
57
- };
58
-
59
- return (
60
- <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
61
- <div className="bg-white rounded-lg shadow-xl max-w-md w-full mx-4">
62
- {/* ヘッダー */}
63
- <div className="flex items-center justify-between p-6 border-b">
64
- <h2 className="text-xl font-semibold text-gray-800">エクスポート設定</h2>
65
- <button
66
- onClick={onClose}
67
- className="text-gray-400 hover:text-gray-600 transition-colors"
68
- >
69
- <XIcon className="w-5 h-5" />
70
- </button>
71
- </div>
72
-
73
- {/* コンテンツ */}
74
- <div className="p-6 space-y-6">
75
-
76
- {/* 出力先フォルダ */}
77
- <div>
78
- <label className="block text-sm font-medium text-gray-700 mb-2">
79
- 出力先フォルダ
80
- </label>
81
- <div className="flex gap-2">
82
- <input
83
- type="text"
84
- value={settings.outputDir || ''}
85
- onChange={(e) => setSettings({ ...settings, outputDir: e.target.value })}
86
- className="flex-1 px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
87
- placeholder="(空欄でプロジェクトルート)"
88
- />
89
- <button
90
- type="button"
91
- onClick={() => setIsFolderBrowserOpen(true)}
92
- className="px-3 py-2 border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500"
93
- title="フォルダを選択"
94
- >
95
- <FolderIcon className="w-5 h-5 text-gray-500" />
96
- </button>
97
- </div>
98
- <p className="mt-1 text-xs text-gray-500">
99
- 相対パスを入力またはフォルダを選択(空欄でプロジェクトルート、チーム共有設定)
100
- </p>
101
- </div>
102
-
103
- {/* ファイル名 */}
104
- <div>
105
- <label className="block text-sm font-medium text-gray-700 mb-2">
106
- ファイル名
107
- </label>
108
- <input
109
- type="text"
110
- value={settings.fileName ?? ''}
111
- onChange={(e) => setSettings({ ...settings, fileName: e.target.value })}
112
- className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
113
- placeholder="config-doc"
114
- />
115
- <p className="mt-1 text-xs text-gray-500">
116
- 拡張子なしのファイル名を指定します(空欄でconfig-doc、チーム共有設定)
117
- </p>
118
- </div>
119
-
120
- {/* 出力形式 */}
121
- <div>
122
- <label className="block text-sm font-medium text-gray-700 mb-2">
123
- 出力形式
124
- </label>
125
- <select
126
- value={settings.format}
127
- onChange={(e) => setSettings({ ...settings, format: e.target.value as ExportFormat })}
128
- className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
129
- >
130
- <option value="html">HTML</option>
131
- <option value="markdown">Markdown</option>
132
- <option value="markdown-table">Markdown (テーブル形式)</option>
133
- </select>
134
- <p className="mt-1 text-xs text-gray-500">
135
- {settings.format === 'html'
136
- ? 'スタイル付きのHTMLファイルとして出力します'
137
- : settings.format === 'markdown-table'
138
- ? 'Markdownテーブル形式で出力します(プロパティ名、説明、値、備考)'
139
- : 'テキストベースのMarkdownファイルとして出力します'}
140
- </p>
141
- </div>
142
-
143
- {/* 出力先パス */}
144
- <div>
145
- <label className="block text-sm font-medium text-gray-700 mb-2">
146
- 出力先パス
147
- </label>
148
- <div className="px-3 py-2 bg-gray-50 border border-gray-300 rounded-md text-sm text-gray-700 font-mono break-all">
149
- {absoluteOutputPath}
150
- </div>
151
- </div>
152
-
153
- {/* 自動エクスポート */}
154
- <div className="flex items-start">
155
- <div className="flex items-center h-5">
156
- <input
157
- id="auto-export"
158
- type="checkbox"
159
- checked={settings.autoExport}
160
- onChange={(e) => setSettings({ ...settings, autoExport: e.target.checked })}
161
- className="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
162
- />
163
- </div>
164
- <div className="ml-3">
165
- <label htmlFor="auto-export" className="text-sm font-medium text-gray-700 cursor-pointer">
166
- 保存時に自動エクスポート
167
- </label>
168
- <p className="text-xs text-gray-500 mt-1">
169
- ドキュメントを保存したときに自動的にHTMLファイルを更新します
170
- </p>
171
- </div>
172
- </div>
173
-
174
- {/* 最終エクスポート日時 */}
175
- {settings.lastExportedAt && (
176
- <div className="pt-4 border-t">
177
- <p className="text-xs text-gray-500">
178
- 最終エクスポート: {new Date(settings.lastExportedAt).toLocaleString('ja-JP')}
179
- </p>
180
- </div>
181
- )}
182
- </div>
183
-
184
- {/* フッター */}
185
- <div className="flex items-center justify-end gap-3 p-6 border-t bg-gray-50 rounded-b-lg">
186
- <button
187
- onClick={onClose}
188
- className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
189
- >
190
- キャンセル
191
- </button>
192
- <button
193
- onClick={handleExport}
194
- disabled={isExporting}
195
- className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
196
- >
197
- <DownloadIcon className="w-4 h-4" />
198
- {isExporting ? 'エクスポート中...' : 'エクスポート'}
199
- </button>
200
- </div>
201
- </div>
202
-
203
- {/* フォルダ選択ダイアログ */}
204
- <FileBrowser
205
- isOpen={isFolderBrowserOpen}
206
- currentPath={rootPath}
207
- onSelect={(paths) => {
208
- if (paths.length > 0) {
209
- // 絶対パスから相対パスに変換
210
- const selectedPath = paths[0];
211
- const normalizedRoot = rootPath.replace(/\\/g, '/');
212
- const normalizedSelected = selectedPath.replace(/\\/g, '/');
213
- let relativePath = normalizedSelected;
214
- if (normalizedSelected.startsWith(normalizedRoot)) {
215
- relativePath = normalizedSelected.slice(normalizedRoot.length);
216
- if (relativePath.startsWith('/')) {
217
- relativePath = relativePath.slice(1);
218
- }
219
- }
220
- // 空の場合は現在のディレクトリ
221
- if (!relativePath) {
222
- relativePath = '.';
223
- }
224
- setSettings({ ...settings, outputDir: relativePath });
225
- }
226
- setIsFolderBrowserOpen(false);
227
- }}
228
- onClose={() => setIsFolderBrowserOpen(false)}
229
- folderSelectMode={true}
230
- title="出力先フォルダを選択"
231
- />
232
- </div>
233
- );
234
- }
@@ -1,92 +0,0 @@
1
- 'use client';
2
-
3
- import { EditableListWrapper } from './EditableList';
4
-
5
- interface FieldsEditorProps {
6
- fields: Record<string, string>;
7
- projectFields: Record<string, string>;
8
- onFieldsChange: (fields: Record<string, string>) => void;
9
- onUpdateProjectFields?: (fields: Record<string, string>) => void;
10
- }
11
-
12
- export function FieldsEditor({
13
- fields,
14
- projectFields,
15
- onFieldsChange,
16
- onUpdateProjectFields
17
- }: FieldsEditorProps) {
18
- // フィールド名の一覧
19
- const fieldNames = Object.keys(projectFields);
20
-
21
- // 名前変更時にフィールドの値も更新
22
- const handleRename = (renamedMap: Record<string, string>) => {
23
- const newFields: Record<string, string> = {};
24
- for (const [oldName, value] of Object.entries(fields)) {
25
- const newName = renamedMap[oldName] || oldName;
26
- newFields[newName] = value;
27
- }
28
- onFieldsChange(newFields);
29
- };
30
-
31
- // フィールド一覧が変更されたとき
32
- const handleFieldNamesChange = (newNames: string[]) => {
33
- // 新しいフィールド構造を構築
34
- const newFields: Record<string, string> = {};
35
- for (const name of newNames) {
36
- newFields[name] = fields[name] || '';
37
- }
38
-
39
- // フィールドを更新(ローカル状態)
40
- onFieldsChange(newFields);
41
-
42
- // プロジェクト全体に適用
43
- if (onUpdateProjectFields) {
44
- const newProjectFields: Record<string, string> = {};
45
- for (const name of newNames) {
46
- newProjectFields[name] = '';
47
- }
48
- onUpdateProjectFields(newProjectFields);
49
- }
50
- };
51
-
52
- // フィールド値の変更(通常モード)
53
- const handleFieldValueChange = (label: string, value: string) => {
54
- onFieldsChange({
55
- ...fields,
56
- [label]: value
57
- });
58
- };
59
-
60
- return (
61
- <EditableListWrapper
62
- label="フィールド"
63
- items={fieldNames}
64
- onItemsChange={handleFieldNamesChange}
65
- editButtonTitle="フィールドを編集"
66
- editModeDescription="フィールドの追加・削除・名前変更"
67
- inputPlaceholder="フィールド名"
68
- newItemPlaceholder="新しいフィールド名を入力"
69
- deleteButtonTitle="フィールドを削除"
70
- addButtonTitle="フィールドを追加"
71
- duplicateErrorMessage="同じ名前のフィールドがあります"
72
- onRename={handleRename}
73
- >
74
- {/* 通常モード(フィールド値の入力) */}
75
- <div className="space-y-3">
76
- {Object.entries(fields).map(([label, value]) => (
77
- <div key={label}>
78
- <label className="block text-sm font-medium text-gray-700 mb-1">
79
- {label}
80
- </label>
81
- <textarea
82
- value={value}
83
- onChange={(e) => handleFieldValueChange(label, e.target.value)}
84
- className="w-full border-2 border-gray-200 rounded-lg p-3 min-h-[80px] text-sm focus:border-blue-400 focus:ring-2 focus:ring-blue-200 transition-all duration-200 shadow-sm hover:shadow-md"
85
- placeholder={`${label}を入力してください`}
86
- />
87
- </div>
88
- ))}
89
- </div>
90
- </EditableListWrapper>
91
- );
92
- }