@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.
Files changed (45) hide show
  1. package/dist/cli/commands/init.d.ts.map +1 -1
  2. package/dist/cli/commands/init.js +12 -7
  3. package/dist/cli/commands/init.js.map +1 -1
  4. package/dist/cli/commands/serve.d.ts.map +1 -1
  5. package/dist/cli/commands/serve.js +18 -1
  6. package/dist/cli/commands/serve.js.map +1 -1
  7. package/dist/cli/commands/status.d.ts.map +1 -1
  8. package/dist/cli/commands/status.js +10 -5
  9. package/dist/cli/commands/status.js.map +1 -1
  10. package/dist/dashboard/_archive/wizard-client.d.ts +2 -0
  11. package/dist/dashboard/_archive/wizard-client.d.ts.map +1 -0
  12. package/dist/dashboard/_archive/wizard-client.js +412 -0
  13. package/dist/dashboard/_archive/wizard-client.js.map +1 -0
  14. package/dist/dashboard/_archive/wizard.d.ts +9 -0
  15. package/dist/dashboard/_archive/wizard.d.ts.map +1 -0
  16. package/dist/dashboard/_archive/wizard.js +317 -0
  17. package/dist/dashboard/_archive/wizard.js.map +1 -0
  18. package/dist/dashboard/help.d.ts +2 -0
  19. package/dist/dashboard/help.d.ts.map +1 -0
  20. package/dist/dashboard/help.js +73 -0
  21. package/dist/dashboard/help.js.map +1 -0
  22. package/dist/dashboard/layout.d.ts.map +1 -1
  23. package/dist/dashboard/layout.js +1 -0
  24. package/dist/dashboard/layout.js.map +1 -1
  25. package/dist/dashboard/server.d.ts +1 -1
  26. package/dist/dashboard/server.d.ts.map +1 -1
  27. package/dist/dashboard/server.js +39 -51
  28. package/dist/dashboard/server.js.map +1 -1
  29. package/dist/dashboard/settings-client.d.ts +1 -1
  30. package/dist/dashboard/settings-client.d.ts.map +1 -1
  31. package/dist/dashboard/settings-client.js +367 -162
  32. package/dist/dashboard/settings-client.js.map +1 -1
  33. package/dist/dashboard/settings.d.ts +5 -1
  34. package/dist/dashboard/settings.d.ts.map +1 -1
  35. package/dist/dashboard/settings.js +60 -45
  36. package/dist/dashboard/settings.js.map +1 -1
  37. package/dist/dashboard/wizard-client.d.ts +1 -1
  38. package/dist/dashboard/wizard-client.d.ts.map +1 -1
  39. package/dist/dashboard/wizard-client.js +226 -80
  40. package/dist/dashboard/wizard-client.js.map +1 -1
  41. package/dist/dashboard/wizard.d.ts +2 -2
  42. package/dist/dashboard/wizard.d.ts.map +1 -1
  43. package/dist/dashboard/wizard.js +113 -32
  44. package/dist/dashboard/wizard.js.map +1 -1
  45. package/package.json +1 -1
@@ -5,72 +5,87 @@ function escapeHtml(str) {
5
5
  .replace(/>/g, ">")
6
6
  .replace(/"/g, """);
7
7
  }
8
- function chipHtml(value, label, selected) {
9
- const selectedClasses = selected
10
- ? "bg-blue-100 text-blue-800 border-blue-300"
11
- : "bg-gray-100 text-gray-600 border-gray-300";
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">&times;</button>
15
12
  </span>`;
16
13
  }
17
- export function settingsPage(config, suggestedRoots, suggestedPatterns) {
18
- // Roots: show current config roots as selected, suggested ones not in config as deselected
19
- const configRootSet = new Set(config.scan_roots);
20
- const allRoots = [...config.scan_roots];
21
- for (const r of suggestedRoots) {
22
- if (!configRootSet.has(r))
23
- allRoots.push(r);
24
- }
25
- const rootChips = allRoots
26
- .map((r) => chipHtml(r, r, configRootSet.has(r)))
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">&times;</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 current config patterns as selected, suggested ones not in config as deselected
26
+ // Patterns: show configured patterns as chips, unselected suggested as addable
29
27
  const configPatternSet = new Set(config.patterns);
30
- const allPatterns = [...config.patterns];
31
- for (const p of suggestedPatterns) {
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-6">Settings</h1>
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. Click to toggle.</p>
46
- <div id="roots-list" class="flex flex-wrap gap-2 mb-4">
47
- ${rootChips}
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 justify-between text-sm">
55
- <div class="flex items-center gap-2">
56
- <button id="browser-up" type="button" class="text-blue-600 hover:underline font-medium">Up</button>
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. Click to toggle.</p>
69
- <div id="patterns-list" class="flex flex-wrap gap-2 mb-4">
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="*.md"
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
- <button id="save-btn" type="button"
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
- Save
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,QAAQ,CACf,KAAa,EACb,KAAa,EACb,QAAiB;IAEjB,MAAM,eAAe,GAAG,QAAQ;QAC9B,CAAC,CAAC,2CAA2C;QAC7C,CAAC,CAAC,2CAA2C,CAAC;IAChD,OAAO,+BAA+B,UAAU,CAAC,KAAK,CAAC,oBAAoB,QAAQ;uIACkD,eAAe;MAChJ,UAAU,CAAC,KAAK,CAAC;UACb,CAAC;AACX,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,MAAmB,EACnB,cAAwB,EACxB,iBAA2B;IAE3B,2FAA2F;IAC3F,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAG,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;IACxC,KAAK,MAAM,CAAC,IAAI,cAAc,EAAE,CAAC;QAC/B,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9C,CAAC;IACD,MAAM,SAAS,GAAG,QAAQ;SACvB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;SAChD,IAAI,CAAC,YAAY,CAAC,CAAC;IAEtB,iGAAiG;IACjG,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAClD,MAAM,WAAW,GAAG,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IACzC,KAAK,MAAM,CAAC,IAAI,iBAAiB,EAAE,CAAC;QAClC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC;YAAE,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpD,CAAC;IACD,MAAM,YAAY,GAAG,WAAW;SAC7B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;SACnD,IAAI,CAAC,YAAY,CAAC,CAAC;IAEtB,OAAO;;;;;;;;;YASG,SAAS;;;;;;;;;;;;;;;;;;;;;;;YAuBT,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;WA2Bb,CAAC;AACZ,CAAC"}
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\">&times;</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\">&#128193;</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\">&times;</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\">&times;</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\">&times;</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 = '&#128193;';\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,83TAwQvB,CAAC"}
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
- // --- Chip toggling ---
4
- document.querySelectorAll('[data-chip]').forEach(function(chip) {
5
- chip.addEventListener('click', function() {
6
- var isSelected = chip.getAttribute('data-selected') === 'true';
7
- chip.setAttribute('data-selected', isSelected ? 'false' : 'true');
8
- if (isSelected) {
9
- chip.classList.remove('bg-blue-100', 'text-blue-800', 'border-blue-300');
10
- chip.classList.add('bg-gray-100', 'text-gray-600', 'border-gray-300');
11
- } else {
12
- chip.classList.remove('bg-gray-100', 'text-gray-600', 'border-gray-300');
13
- chip.classList.add('bg-blue-100', 'text-blue-800', 'border-blue-300');
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">&times;</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
- // --- Folder browser ---
19
- var openBrowserBtn = document.getElementById('open-browser');
20
- var folderBrowser = document.getElementById('folder-browser');
21
- var browserPath = document.getElementById('browser-path');
22
- var browserList = document.getElementById('browser-list');
23
- var browserUp = document.getElementById('browser-up');
24
- var customList = document.getElementById('custom-list');
25
- var browserAdd = document.getElementById('browser-add');
26
- var currentBrowseDir = null;
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
- if (browserAdd) {
29
- browserAdd.addEventListener('click', function() {
30
- if (currentBrowseDir) addFolderChip(currentBrowseDir);
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
- function addFolderChip(folderPath) {
35
- if (!customList) return;
36
- // Don't add duplicates
37
- var existing = customList.querySelectorAll('[data-custom-value]');
38
- for (var i = 0; i < existing.length; i++) {
39
- if (existing[i].getAttribute('data-custom-value') === folderPath) return;
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
- // Also check if it's already a suggested chip that's selected
42
- var chips = document.querySelectorAll('[data-chip]');
43
- for (var i = 0; i < chips.length; i++) {
44
- if (chips[i].getAttribute('data-value') === folderPath) return;
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-custom-value', folderPath);
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">&times;</button>';
51
118
  tag.querySelector('button').addEventListener('click', function() {
52
- tag.remove();
119
+ removeFolderChip(tag);
53
120
  });
54
- customList.appendChild(tag);
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
- currentBrowseDir = data.current;
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 cursor-pointer flex items-center gap-2 text-sm';
74
- row.innerHTML = '<span class="text-gray-400">&#128193;</span> ' + entry.name;
75
- row.addEventListener('click', function() {
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 = '&#128193;';
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 folders...';
225
+ openBrowserBtn.textContent = 'Browse for folder...';
93
226
  }
94
227
  });
95
228
  }
96
229
 
97
230
  // --- Custom input for patterns (step 2) ---
98
- var addBtn = document.getElementById('add-custom');
231
+ var addCustomBtn = document.getElementById('add-custom');
99
232
  var customInput = document.getElementById('custom-input');
100
233
 
101
- function addCustomItem() {
102
- if (!customInput || !customList) return;
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">&times;</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 (addBtn) {
123
- addBtn.addEventListener('click', addCustomItem);
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
- addCustomItem();
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
- document.querySelectorAll('[data-chip][data-selected="true"]').forEach(function(chip) {
138
- values.push(chip.getAttribute('data-value'));
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();