@mattli/dotmd 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +12 -7
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/serve.d.ts.map +1 -1
- package/dist/cli/commands/serve.js +18 -1
- package/dist/cli/commands/serve.js.map +1 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +10 -5
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/dashboard/_archive/wizard-client.d.ts +2 -0
- package/dist/dashboard/_archive/wizard-client.d.ts.map +1 -0
- package/dist/dashboard/_archive/wizard-client.js +412 -0
- package/dist/dashboard/_archive/wizard-client.js.map +1 -0
- package/dist/dashboard/_archive/wizard.d.ts +9 -0
- package/dist/dashboard/_archive/wizard.d.ts.map +1 -0
- package/dist/dashboard/_archive/wizard.js +317 -0
- package/dist/dashboard/_archive/wizard.js.map +1 -0
- package/dist/dashboard/help.d.ts +2 -0
- package/dist/dashboard/help.d.ts.map +1 -0
- package/dist/dashboard/help.js +73 -0
- package/dist/dashboard/help.js.map +1 -0
- package/dist/dashboard/layout.d.ts.map +1 -1
- package/dist/dashboard/layout.js +1 -0
- package/dist/dashboard/layout.js.map +1 -1
- package/dist/dashboard/server.d.ts +1 -1
- package/dist/dashboard/server.d.ts.map +1 -1
- package/dist/dashboard/server.js +39 -51
- package/dist/dashboard/server.js.map +1 -1
- package/dist/dashboard/settings-client.d.ts +1 -1
- package/dist/dashboard/settings-client.d.ts.map +1 -1
- package/dist/dashboard/settings-client.js +367 -162
- package/dist/dashboard/settings-client.js.map +1 -1
- package/dist/dashboard/settings.d.ts +5 -1
- package/dist/dashboard/settings.d.ts.map +1 -1
- package/dist/dashboard/settings.js +60 -45
- package/dist/dashboard/settings.js.map +1 -1
- package/dist/dashboard/wizard-client.d.ts +1 -1
- package/dist/dashboard/wizard-client.d.ts.map +1 -1
- package/dist/dashboard/wizard-client.js +226 -80
- package/dist/dashboard/wizard-client.js.map +1 -1
- package/dist/dashboard/wizard.d.ts +2 -2
- package/dist/dashboard/wizard.d.ts.map +1 -1
- package/dist/dashboard/wizard.js +113 -32
- package/dist/dashboard/wizard.js.map +1 -1
- package/package.json +1 -1
|
@@ -5,72 +5,87 @@ function escapeHtml(str) {
|
|
|
5
5
|
.replace(/>/g, ">")
|
|
6
6
|
.replace(/"/g, """);
|
|
7
7
|
}
|
|
8
|
-
function
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return `<span data-chip data-value="${escapeHtml(value)}" data-selected="${selected}"
|
|
13
|
-
class="inline-flex items-center px-3 py-1.5 rounded-full text-sm font-medium border cursor-pointer select-none transition-colors ${selectedClasses}">
|
|
14
|
-
${escapeHtml(label)}
|
|
8
|
+
function folderChipHtml(value) {
|
|
9
|
+
return `<span data-folder-chip="${escapeHtml(value)}"
|
|
10
|
+
class="inline-flex items-center gap-1 px-3 py-1.5 rounded-full text-sm font-medium bg-blue-100 text-blue-800 border border-blue-300">
|
|
11
|
+
${escapeHtml(value)} <button type="button" class="ml-1 text-blue-500 hover:text-blue-700 font-bold">×</button>
|
|
15
12
|
</span>`;
|
|
16
13
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
14
|
+
function patternChipHtml(value) {
|
|
15
|
+
return `<span data-pattern-chip="${escapeHtml(value)}"
|
|
16
|
+
class="inline-flex items-center gap-1 px-3 py-1.5 rounded-full text-sm font-medium bg-blue-100 text-blue-800 border border-blue-300">
|
|
17
|
+
${escapeHtml(value)} <button type="button" class="ml-1 text-blue-500 hover:text-blue-700 font-bold">×</button>
|
|
18
|
+
</span>`;
|
|
19
|
+
}
|
|
20
|
+
export function setupPage(config, suggestedRoots, suggestedPatterns, options = {}) {
|
|
21
|
+
const { isInit = false } = options;
|
|
22
|
+
// Folders: show configured roots as chips
|
|
23
|
+
const folderChips = config.scan_roots
|
|
24
|
+
.map((r) => folderChipHtml(r))
|
|
27
25
|
.join("\n ");
|
|
28
|
-
// Patterns: show
|
|
26
|
+
// Patterns: show configured patterns as chips, unselected suggested as addable
|
|
29
27
|
const configPatternSet = new Set(config.patterns);
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
if (!configPatternSet.has(p))
|
|
33
|
-
allPatterns.push(p);
|
|
34
|
-
}
|
|
35
|
-
const patternChips = allPatterns
|
|
36
|
-
.map((p) => chipHtml(p, p, configPatternSet.has(p)))
|
|
28
|
+
const patternChips = config.patterns
|
|
29
|
+
.map((p) => patternChipHtml(p))
|
|
37
30
|
.join("\n ");
|
|
31
|
+
const suggestedChips = suggestedPatterns
|
|
32
|
+
.map((p) => {
|
|
33
|
+
const hidden = configPatternSet.has(p) ? " hidden" : "";
|
|
34
|
+
return `<span data-suggested-pattern="${escapeHtml(p)}"
|
|
35
|
+
class="inline-flex items-center gap-1 px-3 py-1.5 rounded-full text-sm font-medium bg-gray-100 text-gray-600 border border-gray-300 cursor-pointer hover:bg-gray-200 transition-colors select-none${hidden}">
|
|
36
|
+
+ ${escapeHtml(p)}
|
|
37
|
+
</span>`;
|
|
38
|
+
})
|
|
39
|
+
.join("\n ");
|
|
40
|
+
const allSuggestedHidden = suggestedPatterns.every((p) => configPatternSet.has(p));
|
|
41
|
+
const title = isInit ? "Set up dotmd" : "Settings";
|
|
42
|
+
const subtitle = isInit
|
|
43
|
+
? "Pick the folders and file patterns dotmd should track, then confirm the discovered files below."
|
|
44
|
+
: "";
|
|
45
|
+
const saveLabel = isInit ? "Start Tracking" : "Save";
|
|
46
|
+
const saveRedirect = isInit ? "true" : "false";
|
|
38
47
|
return `
|
|
39
|
-
<div class="max-w-3xl mx-auto">
|
|
40
|
-
<h1 class="text-2xl font-bold mb-
|
|
48
|
+
<div class="max-w-3xl mx-auto pb-24">
|
|
49
|
+
<h1 class="text-2xl font-bold mb-2">${title}</h1>
|
|
50
|
+
${subtitle ? `<p class="text-gray-500 mb-6">${subtitle}</p>` : '<div class="mb-4"></div>'}
|
|
41
51
|
|
|
42
52
|
<!-- Folders -->
|
|
43
53
|
<section class="mb-8">
|
|
44
54
|
<h2 class="text-lg font-semibold mb-2">Folders</h2>
|
|
45
|
-
<p class="text-gray-500 text-sm mb-4">Directories to scan for instruction files
|
|
46
|
-
<div id="
|
|
47
|
-
${
|
|
55
|
+
<p class="text-gray-500 text-sm mb-4">Directories to scan for instruction files.</p>
|
|
56
|
+
<div id="folder-list" class="flex flex-wrap gap-2 mb-2">
|
|
57
|
+
${folderChips}
|
|
48
58
|
</div>
|
|
59
|
+
<p id="no-folders-msg" class="text-gray-400 text-sm mb-4 ${config.scan_roots.length > 0 ? "hidden" : ""}">No folders selected. Use the browser below to add folders.</p>
|
|
49
60
|
<button id="open-browser" type="button"
|
|
50
61
|
class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg text-sm font-medium hover:bg-gray-200 transition-colors border border-gray-300">
|
|
51
62
|
Browse for folder...
|
|
52
63
|
</button>
|
|
53
64
|
<div id="folder-browser" class="hidden mt-3 bg-white border border-gray-200 rounded-lg overflow-hidden">
|
|
54
|
-
<div id="browser-header" class="px-4 py-2 bg-gray-50 border-b border-gray-200 flex items-center
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
<span id="browser-path" class="text-gray-500 font-mono text-xs"></span>
|
|
58
|
-
</div>
|
|
59
|
-
<button id="browser-add" type="button" class="px-3 py-1 bg-blue-600 text-white rounded text-xs font-medium hover:bg-blue-700">Add this folder</button>
|
|
65
|
+
<div id="browser-header" class="px-4 py-2 bg-gray-50 border-b border-gray-200 flex items-center gap-2 text-sm">
|
|
66
|
+
<button id="browser-up" type="button" class="text-blue-600 hover:underline font-medium">Up</button>
|
|
67
|
+
<span id="browser-path" class="text-gray-500 font-mono text-xs"></span>
|
|
60
68
|
</div>
|
|
61
69
|
<div id="browser-list" class="max-h-64 overflow-y-auto divide-y divide-gray-100"></div>
|
|
62
70
|
</div>
|
|
63
71
|
</section>
|
|
64
72
|
|
|
65
73
|
<!-- Patterns -->
|
|
66
|
-
<section class="mb-8">
|
|
74
|
+
<section class="mb-8 pt-8 border-t border-gray-200">
|
|
67
75
|
<h2 class="text-lg font-semibold mb-2">Patterns</h2>
|
|
68
|
-
<p class="text-gray-500 text-sm mb-4">Filename patterns to match within your folders
|
|
69
|
-
<div id="
|
|
76
|
+
<p class="text-gray-500 text-sm mb-4">Filename patterns to match within your folders.</p>
|
|
77
|
+
<div id="pattern-list" class="flex flex-wrap gap-2 mb-2">
|
|
70
78
|
${patternChips}
|
|
71
79
|
</div>
|
|
80
|
+
<p id="no-patterns-msg" class="text-gray-400 text-sm mb-4 ${config.patterns.length > 0 ? "hidden" : ""}">No patterns selected.</p>
|
|
81
|
+
<div id="suggestions-section" class="mb-4${allSuggestedHidden ? " hidden" : ""}">
|
|
82
|
+
<p class="text-gray-500 text-xs uppercase tracking-wide font-semibold mb-2">Suggestions</p>
|
|
83
|
+
<div id="suggested-patterns" class="flex flex-wrap gap-2">
|
|
84
|
+
${suggestedChips}
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
72
87
|
<div class="flex gap-2">
|
|
73
|
-
<input id="custom-input" type="text" placeholder="
|
|
88
|
+
<input id="custom-input" type="text" placeholder="rules/*.md"
|
|
74
89
|
class="flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:border-blue-400">
|
|
75
90
|
<button id="add-custom" type="button"
|
|
76
91
|
class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg text-sm font-medium hover:bg-gray-200 transition-colors border border-gray-300">Add</button>
|
|
@@ -78,7 +93,7 @@ export function settingsPage(config, suggestedRoots, suggestedPatterns) {
|
|
|
78
93
|
</section>
|
|
79
94
|
|
|
80
95
|
<!-- File Preview -->
|
|
81
|
-
<section class="mb-8">
|
|
96
|
+
<section class="mb-8 pt-8 border-t border-gray-200">
|
|
82
97
|
<h2 class="text-lg font-semibold mb-2">Tracked Files</h2>
|
|
83
98
|
<p class="text-gray-500 text-sm mb-4">Files matching your current folders and patterns. Uncheck any you don't want to track.</p>
|
|
84
99
|
<div id="file-preview" class="bg-white border border-gray-200 rounded-lg p-4 min-h-[100px]">
|
|
@@ -87,12 +102,12 @@ export function settingsPage(config, suggestedRoots, suggestedPatterns) {
|
|
|
87
102
|
</section>
|
|
88
103
|
|
|
89
104
|
<!-- Save -->
|
|
90
|
-
<div class="flex items-center gap-4">
|
|
91
|
-
<
|
|
105
|
+
<div class="flex items-center justify-end gap-4 pt-8 border-t border-gray-200">
|
|
106
|
+
<span id="save-status" class="text-sm text-green-600 hidden">Settings saved.</span>
|
|
107
|
+
<button id="save-btn" type="button" data-redirect="${saveRedirect}"
|
|
92
108
|
class="px-6 py-2.5 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors">
|
|
93
|
-
|
|
109
|
+
${saveLabel}
|
|
94
110
|
</button>
|
|
95
|
-
<span id="save-status" class="text-sm text-green-600 hidden">Settings saved.</span>
|
|
96
111
|
</div>
|
|
97
112
|
</div>`;
|
|
98
113
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../src/dashboard/settings.ts"],"names":[],"mappings":"AAGA,SAAS,UAAU,CAAC,GAAW;IAC7B,OAAO,GAAG;SACP,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7B,CAAC;AAED,SAAS,
|
|
1
|
+
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../src/dashboard/settings.ts"],"names":[],"mappings":"AAGA,SAAS,UAAU,CAAC,GAAW;IAC7B,OAAO,GAAG;SACP,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7B,CAAC;AAED,SAAS,cAAc,CAAC,KAAa;IACnC,OAAO,2BAA2B,UAAU,CAAC,KAAK,CAAC;;MAE/C,UAAU,CAAC,KAAK,CAAC;UACb,CAAC;AACX,CAAC;AAED,SAAS,eAAe,CAAC,KAAa;IACpC,OAAO,4BAA4B,UAAU,CAAC,KAAK,CAAC;;MAEhD,UAAU,CAAC,KAAK,CAAC;UACb,CAAC;AACX,CAAC;AAOD,MAAM,UAAU,SAAS,CACvB,MAAmB,EACnB,cAAwB,EACxB,iBAA2B,EAC3B,UAA4B,EAAE;IAE9B,MAAM,EAAE,MAAM,GAAG,KAAK,EAAE,GAAG,OAAO,CAAC;IAEnC,0CAA0C;IAC1C,MAAM,WAAW,GAAG,MAAM,CAAC,UAAU;SAClC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;SAC7B,IAAI,CAAC,YAAY,CAAC,CAAC;IAEtB,+EAA+E;IAC/E,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAClD,MAAM,YAAY,GAAG,MAAM,CAAC,QAAQ;SACjC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;SAC9B,IAAI,CAAC,YAAY,CAAC,CAAC;IAEtB,MAAM,cAAc,GAAG,iBAAiB;SACrC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,MAAM,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,OAAO,iCAAiC,UAAU,CAAC,CAAC,CAAC;wMAC6I,MAAM;QACtM,UAAU,CAAC,CAAC,CAAC;UACX,CAAC;IACP,CAAC,CAAC;SACD,IAAI,CAAC,YAAY,CAAC,CAAC;IACtB,MAAM,kBAAkB,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CACvD,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,CACxB,CAAC;IAEF,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,UAAU,CAAC;IACnD,MAAM,QAAQ,GAAG,MAAM;QACrB,CAAC,CAAC,iGAAiG;QACnG,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,SAAS,GAAG,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAC;IACrD,MAAM,YAAY,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;IAE/C,OAAO;;4CAEmC,KAAK;QACzC,QAAQ,CAAC,CAAC,CAAC,iCAAiC,QAAQ,MAAM,CAAC,CAAC,CAAC,0BAA0B;;;;;;;YAOnF,WAAW;;mEAE4C,MAAM,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;YAmBnG,YAAY;;oEAE4C,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;mDAC3D,kBAAkB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;;;cAGxE,cAAc;;;;;;;;;;;;;;;;;;;;;;;6DAuBiC,YAAY;;YAE7D,SAAS;;;WAGV,CAAC;AACZ,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const WIZARD_CLIENT_SCRIPT = "<script>\ndocument.addEventListener('DOMContentLoaded', function() {\n // --- Chip toggling ---\n document.querySelectorAll('[data-chip]').forEach(function(chip) {\n chip.addEventListener('click', function() {\n var isSelected = chip.getAttribute('data-selected') === 'true';\n chip.setAttribute('data-selected', isSelected ? 'false' : 'true');\n if (isSelected) {\n chip.classList.remove('bg-blue-100', 'text-blue-800', 'border-blue-300');\n chip.classList.add('bg-gray-100', 'text-gray-600', 'border-gray-300');\n } else {\n chip.classList.remove('bg-gray-100', 'text-gray-600', 'border-gray-300');\n chip.classList.add('bg-blue-100', 'text-blue-800', 'border-blue-300');\n }\n });\n });\n\n // --- Folder browser ---\n var openBrowserBtn = document.getElementById('open-browser');\n var folderBrowser = document.getElementById('folder-browser');\n var browserPath = document.getElementById('browser-path');\n var browserList = document.getElementById('browser-list');\n var browserUp = document.getElementById('browser-up');\n var customList = document.getElementById('custom-list');\n var browserAdd = document.getElementById('browser-add');\n var currentBrowseDir = null;\n\n if (browserAdd) {\n browserAdd.addEventListener('click', function() {\n if (currentBrowseDir) addFolderChip(currentBrowseDir);\n });\n }\n\n function addFolderChip(folderPath) {\n if (!customList) return;\n // Don't add duplicates\n var existing = customList.querySelectorAll('[data-custom-value]');\n for (var i = 0; i < existing.length; i++) {\n if (existing[i].getAttribute('data-custom-value') === folderPath) return;\n }\n // Also check if it's already a suggested chip that's selected\n var chips = document.querySelectorAll('[data-chip]');\n for (var i = 0; i < chips.length; i++) {\n if (chips[i].getAttribute('data-value') === folderPath) return;\n }\n\n var tag = document.createElement('span');\n tag.setAttribute('data-custom-value', folderPath);\n tag.className = 'inline-flex items-center gap-1 px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800 border border-blue-300';\n tag.innerHTML = folderPath + ' <button type=\"button\" class=\"ml-1 text-blue-500 hover:text-blue-700 font-bold\">×</button>';\n tag.querySelector('button').addEventListener('click', function() {\n tag.remove();\n });\n customList.appendChild(tag);\n }\n\n function loadBrowserDir(dir) {\n fetch('/api/setup/browse?dir=' + encodeURIComponent(dir || '~'))\n .then(function(res) { return res.json(); })\n .then(function(data) {\n if (data.error) return;\n currentBrowseDir = data.current;\n if (browserPath) browserPath.textContent = data.current;\n if (browserUp) {\n browserUp.style.display = data.parent ? '' : 'none';\n browserUp.onclick = function() { loadBrowserDir(data.parent); };\n }\n if (!browserList) return;\n browserList.innerHTML = '';\n\n data.dirs.forEach(function(entry) {\n var row = document.createElement('div');\n row.className = 'px-4 py-2 hover:bg-gray-50 cursor-pointer flex items-center gap-2 text-sm';\n row.innerHTML = '<span class=\"text-gray-400\">📁</span> ' + entry.name;\n row.addEventListener('click', function() {\n loadBrowserDir(entry.path);\n });\n browserList.appendChild(row);\n });\n });\n }\n\n if (openBrowserBtn && folderBrowser) {\n openBrowserBtn.addEventListener('click', function() {\n var isHidden = folderBrowser.classList.contains('hidden');\n if (isHidden) {\n folderBrowser.classList.remove('hidden');\n openBrowserBtn.textContent = 'Hide browser';\n loadBrowserDir('~');\n } else {\n folderBrowser.classList.add('hidden');\n openBrowserBtn.textContent = 'Browse folders...';\n }\n });\n }\n\n // --- Custom input for patterns (step 2) ---\n var addBtn = document.getElementById('add-custom');\n var customInput = document.getElementById('custom-input');\n\n function addCustomItem() {\n if (!customInput || !customList) return;\n var value = customInput.value.trim();\n if (!value) return;\n\n var existing = customList.querySelectorAll('[data-custom-value]');\n for (var i = 0; i < existing.length; i++) {\n if (existing[i].getAttribute('data-custom-value') === value) return;\n }\n\n var tag = document.createElement('span');\n tag.setAttribute('data-custom-value', value);\n tag.className = 'inline-flex items-center gap-1 px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800 border border-blue-300';\n tag.innerHTML = value + ' <button type=\"button\" class=\"ml-1 text-blue-500 hover:text-blue-700 font-bold\">×</button>';\n tag.querySelector('button').addEventListener('click', function() {\n tag.remove();\n });\n customList.appendChild(tag);\n customInput.value = '';\n }\n\n if (addBtn) {\n addBtn.addEventListener('click', addCustomItem);\n }\n if (customInput) {\n customInput.addEventListener('keydown', function(e) {\n if (e.key === 'Enter') {\n e.preventDefault();\n addCustomItem();\n }\n });\n }\n\n // --- Collect selected values ---\n function getSelectedValues() {\n var values = [];\n document.querySelectorAll('[data-chip][data-selected=\"true\"]').forEach(function(chip) {\n values.push(chip.getAttribute('data-value'));\n });\n if (customList) {\n customList.querySelectorAll('[data-custom-value]').forEach(function(tag) {\n values.push(tag.getAttribute('data-custom-value'));\n });\n }\n return values;\n }\n\n // --- Step 1: Next button ---\n var nextBtn = document.getElementById('next-btn');\n if (nextBtn && nextBtn.getAttribute('data-step') === '1') {\n nextBtn.addEventListener('click', function() {\n var roots = getSelectedValues();\n if (roots.length === 0) {\n alert('Please select at least one folder.');\n return;\n }\n var params = new URLSearchParams();\n roots.forEach(function(r) { params.append('roots', r); });\n window.location.href = '/setup/step2?' + params.toString();\n });\n }\n\n // --- Step 2: Preview button ---\n if (nextBtn && nextBtn.getAttribute('data-step') === '2') {\n nextBtn.addEventListener('click', function() {\n var patterns = getSelectedValues();\n if (patterns.length === 0) {\n alert('Please select at least one pattern.');\n return;\n }\n // Get roots from URL\n var urlParams = new URLSearchParams(window.location.search);\n var roots = urlParams.getAll('roots');\n var params = new URLSearchParams();\n roots.forEach(function(r) { params.append('roots', r); });\n patterns.forEach(function(p) { params.append('patterns', p); });\n window.location.href = '/setup/step3?' + params.toString();\n });\n }\n\n // --- Step 3: Select/deselect by group ---\n document.querySelectorAll('[data-group-select]').forEach(function(btn) {\n btn.addEventListener('click', function() {\n var group = btn.getAttribute('data-group-select');\n var container = document.querySelector('[data-group=\"' + group + '\"]');\n if (container) {\n container.querySelectorAll('input[name=\"files\"]').forEach(function(cb) {\n cb.checked = true;\n });\n }\n });\n });\n document.querySelectorAll('[data-group-deselect]').forEach(function(btn) {\n btn.addEventListener('click', function() {\n var group = btn.getAttribute('data-group-deselect');\n var container = document.querySelector('[data-group=\"' + group + '\"]');\n if (container) {\n container.querySelectorAll('input[name=\"files\"]').forEach(function(cb) {\n cb.checked = false;\n });\n }\n });\n });\n\n // --- Step 3: Select all / Deselect all ---\n var selectAllBtn = document.getElementById('select-all');\n var deselectAllBtn = document.getElementById('deselect-all');\n\n if (selectAllBtn) {\n selectAllBtn.addEventListener('click', function() {\n document.querySelectorAll('input[name=\"files\"]').forEach(function(cb) {\n cb.checked = true;\n });\n });\n }\n if (deselectAllBtn) {\n deselectAllBtn.addEventListener('click', function() {\n document.querySelectorAll('input[name=\"files\"]').forEach(function(cb) {\n cb.checked = false;\n });\n });\n }\n\n // --- Step 3: Confirm button ---\n var confirmBtn = document.getElementById('confirm-btn');\n if (confirmBtn) {\n confirmBtn.addEventListener('click', function() {\n confirmBtn.disabled = true;\n confirmBtn.textContent = 'Saving...';\n\n var urlParams = new URLSearchParams(window.location.search);\n var roots = urlParams.getAll('roots');\n var patterns = urlParams.getAll('patterns');\n\n var excludedFiles = [];\n document.querySelectorAll('input[name=\"files\"]').forEach(function(cb) {\n if (!cb.checked) {\n excludedFiles.push(cb.value);\n }\n });\n\n fetch('/api/setup/confirm', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ roots: roots, patterns: patterns, excludedFiles: excludedFiles })\n })\n .then(function(res) { return res.json(); })\n .then(function(data) {\n if (data.success) {\n window.location.href = '/setup/complete?count=' + data.trackedCount;\n } else {\n alert('Error saving config. Please try again.');\n confirmBtn.disabled = false;\n confirmBtn.textContent = 'Confirm & Start Tracking';\n }\n })\n .catch(function() {\n alert('Error saving config. Please try again.');\n confirmBtn.disabled = false;\n confirmBtn.textContent = 'Confirm & Start Tracking';\n });\n });\n }\n});\n</script>";
|
|
1
|
+
export declare const WIZARD_CLIENT_SCRIPT = "<script>\ndocument.addEventListener('DOMContentLoaded', function() {\n // --- Pattern list (step 2) ---\n var patternList = document.getElementById('pattern-list');\n var suggestedPatternsDiv = document.getElementById('suggested-patterns');\n var noPatternsMsg = document.getElementById('no-patterns-msg');\n\n function updateNoPatternsMsg() {\n if (!noPatternsMsg) return;\n if (getSelectedValues().length === 0) {\n noPatternsMsg.classList.remove('hidden');\n } else {\n noPatternsMsg.classList.add('hidden');\n }\n }\n\n function addPatternChip(value) {\n if (!patternList) return;\n // Check for duplicates\n var existing = patternList.querySelectorAll('[data-pattern-chip]');\n for (var i = 0; i < existing.length; i++) {\n if (existing[i].getAttribute('data-pattern-chip') === value) return;\n }\n\n var tag = document.createElement('span');\n tag.setAttribute('data-pattern-chip', value);\n tag.className = 'inline-flex items-center gap-1 px-3 py-1.5 rounded-full text-sm font-medium bg-blue-100 text-blue-800 border border-blue-300';\n tag.innerHTML = value + ' <button type=\"button\" class=\"ml-1 text-blue-500 hover:text-blue-700 font-bold\">×</button>';\n tag.querySelector('button').addEventListener('click', function() {\n tag.remove();\n updateNextBtn();\n updateNoPatternsMsg();\n });\n patternList.appendChild(tag);\n updateNextBtn();\n updateNoPatternsMsg();\n }\n\n // Wire up x buttons on server-rendered pattern chips\n if (patternList) {\n patternList.querySelectorAll('[data-pattern-chip] button').forEach(function(btn) {\n btn.addEventListener('click', function() {\n btn.parentElement.remove();\n updateNextBtn();\n updateNoPatternsMsg();\n });\n });\n }\n\n // Wire up suggested pattern chips \u2014 click to add\n if (suggestedPatternsDiv) {\n suggestedPatternsDiv.querySelectorAll('[data-suggested-pattern]').forEach(function(chip) {\n chip.addEventListener('click', function() {\n var value = chip.getAttribute('data-suggested-pattern');\n addPatternChip(value);\n chip.remove();\n // Hide suggestions section if empty\n if (suggestedPatternsDiv.children.length === 0) {\n suggestedPatternsDiv.parentElement.style.display = 'none';\n }\n });\n });\n }\n\n // --- Folder list (step 1) ---\n var folderList = document.getElementById('folder-list');\n var noFoldersMsg = document.getElementById('no-folders-msg');\n\n function getAddedFolders() {\n var folders = [];\n if (!folderList) return folders;\n folderList.querySelectorAll('[data-folder-chip]').forEach(function(chip) {\n folders.push(chip.getAttribute('data-folder-chip'));\n });\n return folders;\n }\n\n function isFolderAdded(folderPath) {\n var folders = getAddedFolders();\n for (var i = 0; i < folders.length; i++) {\n if (folders[i] === folderPath) return true;\n }\n return false;\n }\n\n function isCoveredByParent(folderPath) {\n var folders = getAddedFolders();\n for (var i = 0; i < folders.length; i++) {\n // Check if an existing folder is a parent of this one\n if (folderPath !== folders[i] && folderPath.indexOf(folders[i] + '/') === 0) return true;\n }\n return false;\n }\n\n function updateNoFoldersMsg() {\n if (!noFoldersMsg) return;\n if (getAddedFolders().length === 0) {\n noFoldersMsg.classList.remove('hidden');\n } else {\n noFoldersMsg.classList.add('hidden');\n }\n }\n\n function removeFolderChip(chip) {\n chip.remove();\n updateNextBtn();\n updateNoFoldersMsg();\n }\n\n function addFolderChip(folderPath) {\n if (!folderList) return;\n if (isFolderAdded(folderPath)) return;\n\n var tag = document.createElement('span');\n tag.setAttribute('data-folder-chip', folderPath);\n tag.className = 'inline-flex items-center gap-1 px-3 py-1.5 rounded-full text-sm font-medium bg-blue-100 text-blue-800 border border-blue-300';\n tag.innerHTML = folderPath + ' <button type=\"button\" class=\"ml-1 text-blue-500 hover:text-blue-700 font-bold\">×</button>';\n tag.querySelector('button').addEventListener('click', function() {\n removeFolderChip(tag);\n });\n folderList.appendChild(tag);\n updateNextBtn();\n updateNoFoldersMsg();\n }\n\n // Wire up x buttons on server-rendered folder chips\n if (folderList) {\n folderList.querySelectorAll('[data-folder-chip] button').forEach(function(btn) {\n btn.addEventListener('click', function() {\n removeFolderChip(btn.parentElement);\n });\n });\n }\n\n // --- Folder browser ---\n var openBrowserBtn = document.getElementById('open-browser');\n var folderBrowser = document.getElementById('folder-browser');\n var browserPath = document.getElementById('browser-path');\n var browserList = document.getElementById('browser-list');\n var browserUp = document.getElementById('browser-up');\n\n var currentBrowserDir = null;\n\n function loadBrowserDir(dir) {\n fetch('/api/setup/browse?dir=' + encodeURIComponent(dir || '~'))\n .then(function(res) { return res.json(); })\n .then(function(data) {\n if (data.error) return;\n currentBrowserDir = data.current;\n if (browserPath) browserPath.textContent = data.current;\n if (browserUp) {\n browserUp.style.display = data.parent ? '' : 'none';\n browserUp.onclick = function() { loadBrowserDir(data.parent); };\n }\n if (!browserList) return;\n browserList.innerHTML = '';\n\n data.dirs.forEach(function(entry) {\n var added = isFolderAdded(entry.path);\n var covered = isCoveredByParent(entry.path);\n\n var row = document.createElement('div');\n row.className = 'px-4 py-2 hover:bg-gray-50 flex items-center gap-2 text-sm';\n\n var folderIcon = document.createElement('span');\n folderIcon.innerHTML = '📁';\n folderIcon.className = covered ? 'text-gray-300' : 'text-gray-400';\n\n var nameSpan = document.createElement('span');\n nameSpan.textContent = entry.name;\n nameSpan.className = covered\n ? 'cursor-pointer flex-1 text-gray-300'\n : 'cursor-pointer flex-1 hover:text-blue-600';\n nameSpan.addEventListener('click', function() {\n loadBrowserDir(entry.path);\n });\n\n row.appendChild(folderIcon);\n row.appendChild(nameSpan);\n\n if (added) {\n var removeBtn = document.createElement('button');\n removeBtn.textContent = 'Remove';\n removeBtn.type = 'button';\n removeBtn.className = 'text-red-500 hover:text-red-700 text-xs font-medium flex-shrink-0';\n removeBtn.addEventListener('click', function(e) {\n e.stopPropagation();\n var chips = folderList.querySelectorAll('[data-folder-chip]');\n for (var i = 0; i < chips.length; i++) {\n if (chips[i].getAttribute('data-folder-chip') === entry.path) {\n removeFolderChip(chips[i]);\n break;\n }\n }\n loadBrowserDir(currentBrowserDir);\n });\n row.appendChild(removeBtn);\n } else if (!covered) {\n var addBtn = document.createElement('button');\n addBtn.textContent = 'Add';\n addBtn.type = 'button';\n addBtn.className = 'text-blue-600 hover:text-blue-800 text-xs font-medium flex-shrink-0';\n addBtn.addEventListener('click', function(e) {\n e.stopPropagation();\n addFolderChip(entry.path);\n loadBrowserDir(currentBrowserDir);\n });\n row.appendChild(addBtn);\n }\n\n browserList.appendChild(row);\n });\n });\n }\n\n if (openBrowserBtn && folderBrowser) {\n openBrowserBtn.addEventListener('click', function() {\n var isHidden = folderBrowser.classList.contains('hidden');\n if (isHidden) {\n folderBrowser.classList.remove('hidden');\n openBrowserBtn.textContent = 'Hide browser';\n loadBrowserDir('~');\n } else {\n folderBrowser.classList.add('hidden');\n openBrowserBtn.textContent = 'Browse for folder...';\n }\n });\n }\n\n // --- Custom input for patterns (step 2) ---\n var addCustomBtn = document.getElementById('add-custom');\n var customInput = document.getElementById('custom-input');\n\n function addCustomPattern() {\n if (!customInput || !patternList) return;\n var value = customInput.value.trim();\n if (!value) return;\n addPatternChip(value);\n customInput.value = '';\n }\n\n if (addCustomBtn) {\n addCustomBtn.addEventListener('click', addCustomPattern);\n }\n if (customInput) {\n customInput.addEventListener('keydown', function(e) {\n if (e.key === 'Enter') {\n e.preventDefault();\n addCustomPattern();\n }\n });\n }\n\n // --- Update Next button disabled state ---\n function updateNextBtn() {\n var btn = document.getElementById('next-btn');\n if (!btn) return;\n var hasSelection = getSelectedValues().length > 0;\n btn.disabled = !hasSelection;\n if (hasSelection) {\n btn.classList.remove('opacity-50', 'cursor-not-allowed');\n } else {\n btn.classList.add('opacity-50', 'cursor-not-allowed');\n }\n }\n\n // --- Collect selected values ---\n function getSelectedValues() {\n var values = [];\n // Folder chips (step 1)\n document.querySelectorAll('[data-folder-chip]').forEach(function(chip) {\n values.push(chip.getAttribute('data-folder-chip'));\n });\n // Pattern chips (step 2)\n document.querySelectorAll('[data-pattern-chip]').forEach(function(chip) {\n values.push(chip.getAttribute('data-pattern-chip'));\n });\n return values;\n }\n\n // --- Initial button state ---\n updateNextBtn();\n\n // --- Step 1: Next button ---\n var nextBtn = document.getElementById('next-btn');\n if (nextBtn && nextBtn.getAttribute('data-step') === '1') {\n nextBtn.addEventListener('click', function() {\n var roots = getSelectedValues();\n if (roots.length === 0) return;\n var params = new URLSearchParams();\n roots.forEach(function(r) { params.append('roots', r); });\n // Preserve patterns if returning from step 2\n var urlParams = new URLSearchParams(window.location.search);\n urlParams.getAll('patterns').forEach(function(p) { params.append('patterns', p); });\n window.location.href = '/setup/step2?' + params.toString();\n });\n }\n\n // --- Step 2: Back button ---\n var backBtn = document.getElementById('back-btn');\n if (backBtn) {\n backBtn.addEventListener('click', function() {\n var urlParams = new URLSearchParams(window.location.search);\n var roots = urlParams.getAll('roots');\n var patterns = getSelectedValues();\n var params = new URLSearchParams();\n roots.forEach(function(r) { params.append('roots', r); });\n patterns.forEach(function(p) { params.append('patterns', p); });\n window.location.href = '/setup?' + params.toString();\n });\n }\n\n // --- Step 2: Preview button ---\n if (nextBtn && nextBtn.getAttribute('data-step') === '2') {\n nextBtn.addEventListener('click', function() {\n var patterns = getSelectedValues();\n if (patterns.length === 0) return;\n var urlParams = new URLSearchParams(window.location.search);\n var roots = urlParams.getAll('roots');\n var params = new URLSearchParams();\n roots.forEach(function(r) { params.append('roots', r); });\n patterns.forEach(function(p) { params.append('patterns', p); });\n window.location.href = '/setup/step3?' + params.toString();\n });\n }\n\n // --- Step 3: Select/deselect by group ---\n document.querySelectorAll('[data-group-select]').forEach(function(btn) {\n btn.addEventListener('click', function() {\n var group = btn.getAttribute('data-group-select');\n var container = document.querySelector('[data-group=\"' + group + '\"]');\n if (container) {\n container.querySelectorAll('input[name=\"files\"]').forEach(function(cb) {\n cb.checked = true;\n });\n }\n });\n });\n document.querySelectorAll('[data-group-deselect]').forEach(function(btn) {\n btn.addEventListener('click', function() {\n var group = btn.getAttribute('data-group-deselect');\n var container = document.querySelector('[data-group=\"' + group + '\"]');\n if (container) {\n container.querySelectorAll('input[name=\"files\"]').forEach(function(cb) {\n cb.checked = false;\n });\n }\n });\n });\n\n // --- Step 3: Select all / Deselect all ---\n var selectAllBtn = document.getElementById('select-all');\n var deselectAllBtn = document.getElementById('deselect-all');\n\n if (selectAllBtn) {\n selectAllBtn.addEventListener('click', function() {\n document.querySelectorAll('input[name=\"files\"]').forEach(function(cb) {\n cb.checked = true;\n });\n });\n }\n if (deselectAllBtn) {\n deselectAllBtn.addEventListener('click', function() {\n document.querySelectorAll('input[name=\"files\"]').forEach(function(cb) {\n cb.checked = false;\n });\n });\n }\n\n // --- Step 3: Confirm button ---\n var confirmBtn = document.getElementById('confirm-btn');\n if (confirmBtn) {\n confirmBtn.addEventListener('click', function() {\n confirmBtn.disabled = true;\n confirmBtn.textContent = 'Saving...';\n\n var urlParams = new URLSearchParams(window.location.search);\n var roots = urlParams.getAll('roots');\n var patterns = urlParams.getAll('patterns');\n\n var excludedFiles = [];\n document.querySelectorAll('input[name=\"files\"]').forEach(function(cb) {\n if (!cb.checked) {\n excludedFiles.push(cb.value);\n }\n });\n\n fetch('/api/setup/confirm', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ roots: roots, patterns: patterns, excludedFiles: excludedFiles })\n })\n .then(function(res) { return res.json(); })\n .then(function(data) {\n if (data.success) {\n window.location.href = '/setup/complete?count=' + data.trackedCount;\n } else {\n alert('Error saving config. Please try again.');\n confirmBtn.disabled = false;\n confirmBtn.textContent = 'Confirm & Start Tracking';\n }\n })\n .catch(function() {\n alert('Error saving config. Please try again.');\n confirmBtn.disabled = false;\n confirmBtn.textContent = 'Confirm & Start Tracking';\n });\n });\n }\n});\n</script>";
|
|
2
2
|
//# sourceMappingURL=wizard-client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wizard-client.d.ts","sourceRoot":"","sources":["../../src/dashboard/wizard-client.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oBAAoB,
|
|
1
|
+
{"version":3,"file":"wizard-client.d.ts","sourceRoot":"","sources":["../../src/dashboard/wizard-client.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oBAAoB,2pdA0ZvB,CAAC"}
|
|
@@ -1,65 +1,152 @@
|
|
|
1
1
|
export const WIZARD_CLIENT_SCRIPT = `<script>
|
|
2
2
|
document.addEventListener('DOMContentLoaded', function() {
|
|
3
|
-
// ---
|
|
4
|
-
document.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
3
|
+
// --- Pattern list (step 2) ---
|
|
4
|
+
var patternList = document.getElementById('pattern-list');
|
|
5
|
+
var suggestedPatternsDiv = document.getElementById('suggested-patterns');
|
|
6
|
+
var noPatternsMsg = document.getElementById('no-patterns-msg');
|
|
7
|
+
|
|
8
|
+
function updateNoPatternsMsg() {
|
|
9
|
+
if (!noPatternsMsg) return;
|
|
10
|
+
if (getSelectedValues().length === 0) {
|
|
11
|
+
noPatternsMsg.classList.remove('hidden');
|
|
12
|
+
} else {
|
|
13
|
+
noPatternsMsg.classList.add('hidden');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function addPatternChip(value) {
|
|
18
|
+
if (!patternList) return;
|
|
19
|
+
// Check for duplicates
|
|
20
|
+
var existing = patternList.querySelectorAll('[data-pattern-chip]');
|
|
21
|
+
for (var i = 0; i < existing.length; i++) {
|
|
22
|
+
if (existing[i].getAttribute('data-pattern-chip') === value) return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
var tag = document.createElement('span');
|
|
26
|
+
tag.setAttribute('data-pattern-chip', value);
|
|
27
|
+
tag.className = 'inline-flex items-center gap-1 px-3 py-1.5 rounded-full text-sm font-medium bg-blue-100 text-blue-800 border border-blue-300';
|
|
28
|
+
tag.innerHTML = value + ' <button type="button" class="ml-1 text-blue-500 hover:text-blue-700 font-bold">×</button>';
|
|
29
|
+
tag.querySelector('button').addEventListener('click', function() {
|
|
30
|
+
tag.remove();
|
|
31
|
+
updateNextBtn();
|
|
32
|
+
updateNoPatternsMsg();
|
|
15
33
|
});
|
|
16
|
-
|
|
34
|
+
patternList.appendChild(tag);
|
|
35
|
+
updateNextBtn();
|
|
36
|
+
updateNoPatternsMsg();
|
|
37
|
+
}
|
|
17
38
|
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
39
|
+
// Wire up x buttons on server-rendered pattern chips
|
|
40
|
+
if (patternList) {
|
|
41
|
+
patternList.querySelectorAll('[data-pattern-chip] button').forEach(function(btn) {
|
|
42
|
+
btn.addEventListener('click', function() {
|
|
43
|
+
btn.parentElement.remove();
|
|
44
|
+
updateNextBtn();
|
|
45
|
+
updateNoPatternsMsg();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
27
49
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
50
|
+
// Wire up suggested pattern chips — click to add
|
|
51
|
+
if (suggestedPatternsDiv) {
|
|
52
|
+
suggestedPatternsDiv.querySelectorAll('[data-suggested-pattern]').forEach(function(chip) {
|
|
53
|
+
chip.addEventListener('click', function() {
|
|
54
|
+
var value = chip.getAttribute('data-suggested-pattern');
|
|
55
|
+
addPatternChip(value);
|
|
56
|
+
chip.remove();
|
|
57
|
+
// Hide suggestions section if empty
|
|
58
|
+
if (suggestedPatternsDiv.children.length === 0) {
|
|
59
|
+
suggestedPatternsDiv.parentElement.style.display = 'none';
|
|
60
|
+
}
|
|
61
|
+
});
|
|
31
62
|
});
|
|
32
63
|
}
|
|
33
64
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
65
|
+
// --- Folder list (step 1) ---
|
|
66
|
+
var folderList = document.getElementById('folder-list');
|
|
67
|
+
var noFoldersMsg = document.getElementById('no-folders-msg');
|
|
68
|
+
|
|
69
|
+
function getAddedFolders() {
|
|
70
|
+
var folders = [];
|
|
71
|
+
if (!folderList) return folders;
|
|
72
|
+
folderList.querySelectorAll('[data-folder-chip]').forEach(function(chip) {
|
|
73
|
+
folders.push(chip.getAttribute('data-folder-chip'));
|
|
74
|
+
});
|
|
75
|
+
return folders;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function isFolderAdded(folderPath) {
|
|
79
|
+
var folders = getAddedFolders();
|
|
80
|
+
for (var i = 0; i < folders.length; i++) {
|
|
81
|
+
if (folders[i] === folderPath) return true;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function isCoveredByParent(folderPath) {
|
|
87
|
+
var folders = getAddedFolders();
|
|
88
|
+
for (var i = 0; i < folders.length; i++) {
|
|
89
|
+
// Check if an existing folder is a parent of this one
|
|
90
|
+
if (folderPath !== folders[i] && folderPath.indexOf(folders[i] + '/') === 0) return true;
|
|
40
91
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function updateNoFoldersMsg() {
|
|
96
|
+
if (!noFoldersMsg) return;
|
|
97
|
+
if (getAddedFolders().length === 0) {
|
|
98
|
+
noFoldersMsg.classList.remove('hidden');
|
|
99
|
+
} else {
|
|
100
|
+
noFoldersMsg.classList.add('hidden');
|
|
45
101
|
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function removeFolderChip(chip) {
|
|
105
|
+
chip.remove();
|
|
106
|
+
updateNextBtn();
|
|
107
|
+
updateNoFoldersMsg();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function addFolderChip(folderPath) {
|
|
111
|
+
if (!folderList) return;
|
|
112
|
+
if (isFolderAdded(folderPath)) return;
|
|
46
113
|
|
|
47
114
|
var tag = document.createElement('span');
|
|
48
|
-
tag.setAttribute('data-
|
|
49
|
-
tag.className = 'inline-flex items-center gap-1 px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800 border border-blue-300';
|
|
115
|
+
tag.setAttribute('data-folder-chip', folderPath);
|
|
116
|
+
tag.className = 'inline-flex items-center gap-1 px-3 py-1.5 rounded-full text-sm font-medium bg-blue-100 text-blue-800 border border-blue-300';
|
|
50
117
|
tag.innerHTML = folderPath + ' <button type="button" class="ml-1 text-blue-500 hover:text-blue-700 font-bold">×</button>';
|
|
51
118
|
tag.querySelector('button').addEventListener('click', function() {
|
|
52
|
-
tag
|
|
119
|
+
removeFolderChip(tag);
|
|
53
120
|
});
|
|
54
|
-
|
|
121
|
+
folderList.appendChild(tag);
|
|
122
|
+
updateNextBtn();
|
|
123
|
+
updateNoFoldersMsg();
|
|
55
124
|
}
|
|
56
125
|
|
|
126
|
+
// Wire up x buttons on server-rendered folder chips
|
|
127
|
+
if (folderList) {
|
|
128
|
+
folderList.querySelectorAll('[data-folder-chip] button').forEach(function(btn) {
|
|
129
|
+
btn.addEventListener('click', function() {
|
|
130
|
+
removeFolderChip(btn.parentElement);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// --- Folder browser ---
|
|
136
|
+
var openBrowserBtn = document.getElementById('open-browser');
|
|
137
|
+
var folderBrowser = document.getElementById('folder-browser');
|
|
138
|
+
var browserPath = document.getElementById('browser-path');
|
|
139
|
+
var browserList = document.getElementById('browser-list');
|
|
140
|
+
var browserUp = document.getElementById('browser-up');
|
|
141
|
+
|
|
142
|
+
var currentBrowserDir = null;
|
|
143
|
+
|
|
57
144
|
function loadBrowserDir(dir) {
|
|
58
145
|
fetch('/api/setup/browse?dir=' + encodeURIComponent(dir || '~'))
|
|
59
146
|
.then(function(res) { return res.json(); })
|
|
60
147
|
.then(function(data) {
|
|
61
148
|
if (data.error) return;
|
|
62
|
-
|
|
149
|
+
currentBrowserDir = data.current;
|
|
63
150
|
if (browserPath) browserPath.textContent = data.current;
|
|
64
151
|
if (browserUp) {
|
|
65
152
|
browserUp.style.display = data.parent ? '' : 'none';
|
|
@@ -69,12 +156,58 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
69
156
|
browserList.innerHTML = '';
|
|
70
157
|
|
|
71
158
|
data.dirs.forEach(function(entry) {
|
|
159
|
+
var added = isFolderAdded(entry.path);
|
|
160
|
+
var covered = isCoveredByParent(entry.path);
|
|
161
|
+
|
|
72
162
|
var row = document.createElement('div');
|
|
73
|
-
row.className = 'px-4 py-2 hover:bg-gray-50
|
|
74
|
-
|
|
75
|
-
|
|
163
|
+
row.className = 'px-4 py-2 hover:bg-gray-50 flex items-center gap-2 text-sm';
|
|
164
|
+
|
|
165
|
+
var folderIcon = document.createElement('span');
|
|
166
|
+
folderIcon.innerHTML = '📁';
|
|
167
|
+
folderIcon.className = covered ? 'text-gray-300' : 'text-gray-400';
|
|
168
|
+
|
|
169
|
+
var nameSpan = document.createElement('span');
|
|
170
|
+
nameSpan.textContent = entry.name;
|
|
171
|
+
nameSpan.className = covered
|
|
172
|
+
? 'cursor-pointer flex-1 text-gray-300'
|
|
173
|
+
: 'cursor-pointer flex-1 hover:text-blue-600';
|
|
174
|
+
nameSpan.addEventListener('click', function() {
|
|
76
175
|
loadBrowserDir(entry.path);
|
|
77
176
|
});
|
|
177
|
+
|
|
178
|
+
row.appendChild(folderIcon);
|
|
179
|
+
row.appendChild(nameSpan);
|
|
180
|
+
|
|
181
|
+
if (added) {
|
|
182
|
+
var removeBtn = document.createElement('button');
|
|
183
|
+
removeBtn.textContent = 'Remove';
|
|
184
|
+
removeBtn.type = 'button';
|
|
185
|
+
removeBtn.className = 'text-red-500 hover:text-red-700 text-xs font-medium flex-shrink-0';
|
|
186
|
+
removeBtn.addEventListener('click', function(e) {
|
|
187
|
+
e.stopPropagation();
|
|
188
|
+
var chips = folderList.querySelectorAll('[data-folder-chip]');
|
|
189
|
+
for (var i = 0; i < chips.length; i++) {
|
|
190
|
+
if (chips[i].getAttribute('data-folder-chip') === entry.path) {
|
|
191
|
+
removeFolderChip(chips[i]);
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
loadBrowserDir(currentBrowserDir);
|
|
196
|
+
});
|
|
197
|
+
row.appendChild(removeBtn);
|
|
198
|
+
} else if (!covered) {
|
|
199
|
+
var addBtn = document.createElement('button');
|
|
200
|
+
addBtn.textContent = 'Add';
|
|
201
|
+
addBtn.type = 'button';
|
|
202
|
+
addBtn.className = 'text-blue-600 hover:text-blue-800 text-xs font-medium flex-shrink-0';
|
|
203
|
+
addBtn.addEventListener('click', function(e) {
|
|
204
|
+
e.stopPropagation();
|
|
205
|
+
addFolderChip(entry.path);
|
|
206
|
+
loadBrowserDir(currentBrowserDir);
|
|
207
|
+
});
|
|
208
|
+
row.appendChild(addBtn);
|
|
209
|
+
}
|
|
210
|
+
|
|
78
211
|
browserList.appendChild(row);
|
|
79
212
|
});
|
|
80
213
|
});
|
|
@@ -89,86 +222,99 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
89
222
|
loadBrowserDir('~');
|
|
90
223
|
} else {
|
|
91
224
|
folderBrowser.classList.add('hidden');
|
|
92
|
-
openBrowserBtn.textContent = 'Browse
|
|
225
|
+
openBrowserBtn.textContent = 'Browse for folder...';
|
|
93
226
|
}
|
|
94
227
|
});
|
|
95
228
|
}
|
|
96
229
|
|
|
97
230
|
// --- Custom input for patterns (step 2) ---
|
|
98
|
-
var
|
|
231
|
+
var addCustomBtn = document.getElementById('add-custom');
|
|
99
232
|
var customInput = document.getElementById('custom-input');
|
|
100
233
|
|
|
101
|
-
function
|
|
102
|
-
if (!customInput || !
|
|
234
|
+
function addCustomPattern() {
|
|
235
|
+
if (!customInput || !patternList) return;
|
|
103
236
|
var value = customInput.value.trim();
|
|
104
237
|
if (!value) return;
|
|
105
|
-
|
|
106
|
-
var existing = customList.querySelectorAll('[data-custom-value]');
|
|
107
|
-
for (var i = 0; i < existing.length; i++) {
|
|
108
|
-
if (existing[i].getAttribute('data-custom-value') === value) return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
var tag = document.createElement('span');
|
|
112
|
-
tag.setAttribute('data-custom-value', value);
|
|
113
|
-
tag.className = 'inline-flex items-center gap-1 px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800 border border-blue-300';
|
|
114
|
-
tag.innerHTML = value + ' <button type="button" class="ml-1 text-blue-500 hover:text-blue-700 font-bold">×</button>';
|
|
115
|
-
tag.querySelector('button').addEventListener('click', function() {
|
|
116
|
-
tag.remove();
|
|
117
|
-
});
|
|
118
|
-
customList.appendChild(tag);
|
|
238
|
+
addPatternChip(value);
|
|
119
239
|
customInput.value = '';
|
|
120
240
|
}
|
|
121
241
|
|
|
122
|
-
if (
|
|
123
|
-
|
|
242
|
+
if (addCustomBtn) {
|
|
243
|
+
addCustomBtn.addEventListener('click', addCustomPattern);
|
|
124
244
|
}
|
|
125
245
|
if (customInput) {
|
|
126
246
|
customInput.addEventListener('keydown', function(e) {
|
|
127
247
|
if (e.key === 'Enter') {
|
|
128
248
|
e.preventDefault();
|
|
129
|
-
|
|
249
|
+
addCustomPattern();
|
|
130
250
|
}
|
|
131
251
|
});
|
|
132
252
|
}
|
|
133
253
|
|
|
254
|
+
// --- Update Next button disabled state ---
|
|
255
|
+
function updateNextBtn() {
|
|
256
|
+
var btn = document.getElementById('next-btn');
|
|
257
|
+
if (!btn) return;
|
|
258
|
+
var hasSelection = getSelectedValues().length > 0;
|
|
259
|
+
btn.disabled = !hasSelection;
|
|
260
|
+
if (hasSelection) {
|
|
261
|
+
btn.classList.remove('opacity-50', 'cursor-not-allowed');
|
|
262
|
+
} else {
|
|
263
|
+
btn.classList.add('opacity-50', 'cursor-not-allowed');
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
134
267
|
// --- Collect selected values ---
|
|
135
268
|
function getSelectedValues() {
|
|
136
269
|
var values = [];
|
|
137
|
-
|
|
138
|
-
|
|
270
|
+
// Folder chips (step 1)
|
|
271
|
+
document.querySelectorAll('[data-folder-chip]').forEach(function(chip) {
|
|
272
|
+
values.push(chip.getAttribute('data-folder-chip'));
|
|
273
|
+
});
|
|
274
|
+
// Pattern chips (step 2)
|
|
275
|
+
document.querySelectorAll('[data-pattern-chip]').forEach(function(chip) {
|
|
276
|
+
values.push(chip.getAttribute('data-pattern-chip'));
|
|
139
277
|
});
|
|
140
|
-
if (customList) {
|
|
141
|
-
customList.querySelectorAll('[data-custom-value]').forEach(function(tag) {
|
|
142
|
-
values.push(tag.getAttribute('data-custom-value'));
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
278
|
return values;
|
|
146
279
|
}
|
|
147
280
|
|
|
281
|
+
// --- Initial button state ---
|
|
282
|
+
updateNextBtn();
|
|
283
|
+
|
|
148
284
|
// --- Step 1: Next button ---
|
|
149
285
|
var nextBtn = document.getElementById('next-btn');
|
|
150
286
|
if (nextBtn && nextBtn.getAttribute('data-step') === '1') {
|
|
151
287
|
nextBtn.addEventListener('click', function() {
|
|
152
288
|
var roots = getSelectedValues();
|
|
153
|
-
if (roots.length === 0)
|
|
154
|
-
alert('Please select at least one folder.');
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
289
|
+
if (roots.length === 0) return;
|
|
157
290
|
var params = new URLSearchParams();
|
|
158
291
|
roots.forEach(function(r) { params.append('roots', r); });
|
|
292
|
+
// Preserve patterns if returning from step 2
|
|
293
|
+
var urlParams = new URLSearchParams(window.location.search);
|
|
294
|
+
urlParams.getAll('patterns').forEach(function(p) { params.append('patterns', p); });
|
|
159
295
|
window.location.href = '/setup/step2?' + params.toString();
|
|
160
296
|
});
|
|
161
297
|
}
|
|
162
298
|
|
|
299
|
+
// --- Step 2: Back button ---
|
|
300
|
+
var backBtn = document.getElementById('back-btn');
|
|
301
|
+
if (backBtn) {
|
|
302
|
+
backBtn.addEventListener('click', function() {
|
|
303
|
+
var urlParams = new URLSearchParams(window.location.search);
|
|
304
|
+
var roots = urlParams.getAll('roots');
|
|
305
|
+
var patterns = getSelectedValues();
|
|
306
|
+
var params = new URLSearchParams();
|
|
307
|
+
roots.forEach(function(r) { params.append('roots', r); });
|
|
308
|
+
patterns.forEach(function(p) { params.append('patterns', p); });
|
|
309
|
+
window.location.href = '/setup?' + params.toString();
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
163
313
|
// --- Step 2: Preview button ---
|
|
164
314
|
if (nextBtn && nextBtn.getAttribute('data-step') === '2') {
|
|
165
315
|
nextBtn.addEventListener('click', function() {
|
|
166
316
|
var patterns = getSelectedValues();
|
|
167
|
-
if (patterns.length === 0)
|
|
168
|
-
alert('Please select at least one pattern.');
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
// Get roots from URL
|
|
317
|
+
if (patterns.length === 0) return;
|
|
172
318
|
var urlParams = new URLSearchParams(window.location.search);
|
|
173
319
|
var roots = urlParams.getAll('roots');
|
|
174
320
|
var params = new URLSearchParams();
|