x-ipe 1.0.23__py3-none-any.whl → 1.0.25__py3-none-any.whl
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.
- x_ipe/app.py +32 -1
- x_ipe/handlers/terminal_handlers.py +6 -0
- x_ipe/handlers/voice_handlers.py +5 -0
- x_ipe/resources/copilot-instructions.md +19 -6
- x_ipe/resources/skills/lesson-learned/SKILL.md +208 -0
- x_ipe/resources/skills/lesson-learned/references/examples.md +238 -0
- x_ipe/resources/skills/project-quality-board-management/SKILL.md +135 -298
- x_ipe/resources/skills/project-quality-board-management/references/evaluation-principles.md +213 -0
- x_ipe/resources/skills/project-quality-board-management/references/evaluation-procedures.md +214 -0
- x_ipe/resources/skills/project-quality-board-management/templates/quality-report.md +70 -18
- x_ipe/resources/skills/task-execution-guideline/SKILL.md +2 -2
- x_ipe/resources/skills/task-execution-guideline/templates/task-record.yaml +1 -1
- x_ipe/resources/skills/task-type-code-implementation/SKILL.md +72 -270
- x_ipe/resources/skills/task-type-code-implementation/references/implementation-guidelines.md +432 -0
- x_ipe/resources/skills/task-type-code-refactor-v2/SKILL.md +127 -353
- x_ipe/resources/skills/task-type-code-refactor-v2/references/refactoring-techniques.md +373 -0
- x_ipe/resources/skills/task-type-feature-breakdown/SKILL.md +31 -243
- x_ipe/resources/skills/task-type-feature-breakdown/references/breakdown-guidelines.md +330 -0
- x_ipe/resources/skills/task-type-feature-refinement/SKILL.md +27 -180
- x_ipe/resources/skills/task-type-feature-refinement/references/specification-writing-guide.md +267 -0
- x_ipe/resources/skills/task-type-idea-mockup/SKILL.md +38 -276
- x_ipe/resources/skills/task-type-idea-mockup/references/mockup-guidelines.md +299 -0
- x_ipe/resources/skills/task-type-idea-to-architecture/SKILL.md +20 -218
- x_ipe/resources/skills/task-type-idea-to-architecture/references/architecture-patterns.md +342 -0
- x_ipe/resources/skills/task-type-ideation/SKILL.md +10 -266
- x_ipe/resources/skills/task-type-ideation/references/folder-naming-guide.md +55 -0
- x_ipe/resources/skills/task-type-ideation/references/tool-usage-guide.md +236 -0
- x_ipe/resources/skills/task-type-ideation-v2/SKILL.md +488 -0
- x_ipe/resources/skills/task-type-ideation-v2/references/examples.md +377 -0
- x_ipe/resources/skills/task-type-ideation-v2/references/folder-naming-guide.md +74 -0
- x_ipe/resources/skills/task-type-ideation-v2/references/tool-usage-guide.md +145 -0
- x_ipe/resources/skills/task-type-ideation-v2/references/visualization-guide.md +160 -0
- x_ipe/resources/skills/task-type-ideation-v2/templates/idea-summary.md +86 -0
- x_ipe/resources/skills/task-type-refactoring-analysis/SKILL.md +83 -145
- x_ipe/resources/skills/task-type-refactoring-analysis/references/output-schema.md +172 -0
- x_ipe/resources/skills/task-type-technical-design/SKILL.md +28 -214
- x_ipe/resources/skills/task-type-technical-design/references/design-templates.md +422 -0
- x_ipe/resources/skills/task-type-test-generation/SKILL.md +47 -332
- x_ipe/resources/skills/task-type-test-generation/references/test-patterns.md +368 -0
- x_ipe/resources/skills/tool-tracing-creator/SKILL.md +312 -0
- x_ipe/resources/skills/tool-tracing-creator/references/examples.md +324 -0
- x_ipe/resources/skills/tool-tracing-instrumentation/SKILL.md +373 -0
- x_ipe/resources/skills/tool-tracing-instrumentation/references/examples.md +264 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/SKILL.md +486 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/10. example-gate-conditions.md +73 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/11. reference-quality-standards.md +127 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/2. reference-section-order.md +127 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/3. example-step-based-code-review.md +84 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/4. example-step-based-feature-implementation.md +113 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/5. example-function-based-validation.md +73 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/6. example-function-based-analysis.md +94 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/7. example-task-io-code-implementation.md +36 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/8. example-structured-summary.md +43 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/9. example-dor-dod.md +77 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/examples.md +429 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/skill-general-guidelines-v2.md +611 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-meta.md +153 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-task-based.md +324 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-task-category.md +109 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-tool.md +205 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-meta.md +334 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-task-based.md +279 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-tool.md +175 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-workflow-orchestration.md +329 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/SKILL.md +487 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/references/examples.md +377 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/references/folder-naming-guide.md +74 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/references/tool-usage-guide.md +145 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/references/visualization-guide.md +160 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/templates/idea-summary.md +86 -0
- x_ipe/routes/__init__.py +2 -0
- x_ipe/routes/ideas_routes.py +289 -0
- x_ipe/routes/kb_routes.py +80 -0
- x_ipe/routes/main_routes.py +18 -0
- x_ipe/routes/project_routes.py +7 -0
- x_ipe/routes/proxy_routes.py +10 -2
- x_ipe/routes/quality_evaluation_routes.py +193 -0
- x_ipe/routes/settings_routes.py +6 -0
- x_ipe/routes/tools_routes.py +6 -0
- x_ipe/routes/tracing_routes.py +232 -0
- x_ipe/routes/uiux_feedback_routes.py +50 -0
- x_ipe/services/__init__.py +5 -0
- x_ipe/services/config_service.py +6 -0
- x_ipe/services/file_service.py +20 -0
- x_ipe/services/homepage_service.py +160 -0
- x_ipe/services/ideas_service.py +535 -2
- x_ipe/services/kb_service.py +378 -0
- x_ipe/services/proxy_service.py +37 -7
- x_ipe/services/settings_service.py +13 -0
- x_ipe/services/skills_service.py +4 -0
- x_ipe/services/terminal_service.py +24 -0
- x_ipe/services/themes_service.py +4 -0
- x_ipe/services/tools_config_service.py +4 -0
- x_ipe/services/tracing_service.py +333 -0
- x_ipe/services/uiux_feedback_service.py +148 -1
- x_ipe/services/voice_input_service_v2.py +11 -0
- x_ipe/static/css/base.css +7 -0
- x_ipe/static/css/homepage-infinity.css +330 -0
- x_ipe/static/css/kb-core.css +301 -0
- x_ipe/static/css/quality-evaluation.css +345 -0
- x_ipe/static/css/sidebar.css +14 -4
- x_ipe/static/css/terminal.css +23 -0
- x_ipe/static/css/tracing-dashboard.css +796 -0
- x_ipe/static/css/uiux-feedback.css +7 -1
- x_ipe/static/css/workplace.css +636 -0
- x_ipe/static/img/homepage-infinity-loop.png +0 -0
- x_ipe/static/js/features/confirm-dialog.js +169 -0
- x_ipe/static/js/features/folder-view.js +742 -0
- x_ipe/static/js/features/homepage-infinity.js +314 -0
- x_ipe/static/js/features/kb-core.js +371 -0
- x_ipe/static/js/features/quality-evaluation.js +387 -0
- x_ipe/static/js/features/sidebar.js +255 -12
- x_ipe/static/js/features/tracing-dashboard.js +855 -0
- x_ipe/static/js/features/tracing-graph.js +1031 -0
- x_ipe/static/js/features/tree-drag.js +227 -0
- x_ipe/static/js/features/tree-search.js +228 -0
- x_ipe/static/js/features/workplace.js +661 -33
- x_ipe/static/js/init.js +76 -0
- x_ipe/static/js/terminal-v2.js +45 -14
- x_ipe/static/js/terminal.js +50 -49
- x_ipe/static/js/uiux-feedback.js +75 -16
- x_ipe/templates/base.html +24 -0
- x_ipe/templates/index.html +10 -1
- x_ipe/templates/knowledge-base.html +110 -0
- x_ipe/templates/workplace.html +4 -0
- x_ipe/tracing/__init__.py +37 -0
- x_ipe/tracing/buffer.py +135 -0
- x_ipe/tracing/context.py +125 -0
- x_ipe/tracing/decorator.py +288 -0
- x_ipe/tracing/middleware.py +197 -0
- x_ipe/tracing/parser.py +235 -0
- x_ipe/tracing/redactor.py +111 -0
- x_ipe/tracing/writer.py +122 -0
- {x_ipe-1.0.23.dist-info → x_ipe-1.0.25.dist-info}/METADATA +2 -2
- {x_ipe-1.0.23.dist-info → x_ipe-1.0.25.dist-info}/RECORD +138 -65
- x_ipe/app.py.bak +0 -1333
- x_ipe/resources/skills/x-ipe-skill-creator/SKILL.md +0 -329
- x_ipe/resources/skills/x-ipe-skill-creator/references/output-patterns.md +0 -169
- x_ipe/resources/skills/x-ipe-skill-creator/references/skill-structure.md +0 -162
- x_ipe/resources/skills/x-ipe-skill-creator/references/workflows.md +0 -110
- x_ipe/resources/skills/x-ipe-skill-creator/templates/references/examples.md +0 -113
- x_ipe/resources/skills/x-ipe-skill-creator/templates/skill-category-skill.md +0 -296
- x_ipe/resources/skills/x-ipe-skill-creator/templates/task-type-skill.md +0 -269
- {x_ipe-1.0.23.dist-info → x_ipe-1.0.25.dist-info}/WHEEL +0 -0
- {x_ipe-1.0.23.dist-info → x_ipe-1.0.25.dist-info}/entry_points.txt +0 -0
- {x_ipe-1.0.23.dist-info → x_ipe-1.0.25.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TreeDragManager - Drag-drop file/folder reorganization
|
|
3
|
+
* FEATURE-008 CR-006: Folder Tree UX Enhancement
|
|
4
|
+
*
|
|
5
|
+
* Provides:
|
|
6
|
+
* - Drag-and-drop to move files/folders
|
|
7
|
+
* - Visual feedback (dragging state, valid/invalid targets)
|
|
8
|
+
* - Validation (prevent drop into self/children)
|
|
9
|
+
* - API integration for move operations
|
|
10
|
+
*/
|
|
11
|
+
class TreeDragManager {
|
|
12
|
+
constructor(options) {
|
|
13
|
+
this.treeContainer = options.treeContainer;
|
|
14
|
+
this.onMove = options.onMove; // Callback: async (sourcePath, targetPath) => boolean
|
|
15
|
+
this.onMoveComplete = options.onMoveComplete || null; // Called after successful move
|
|
16
|
+
this.draggedItem = null;
|
|
17
|
+
this.draggedPath = null;
|
|
18
|
+
this.draggedType = null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Initialize drag-drop functionality
|
|
23
|
+
*/
|
|
24
|
+
init() {
|
|
25
|
+
this._bindEvents();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Bind drag-drop event listeners using event delegation
|
|
30
|
+
*/
|
|
31
|
+
_bindEvents() {
|
|
32
|
+
// Use event delegation on tree container
|
|
33
|
+
this.treeContainer.addEventListener('dragstart', this._onDragStart.bind(this));
|
|
34
|
+
this.treeContainer.addEventListener('dragend', this._onDragEnd.bind(this));
|
|
35
|
+
this.treeContainer.addEventListener('dragover', this._onDragOver.bind(this));
|
|
36
|
+
this.treeContainer.addEventListener('dragleave', this._onDragLeave.bind(this));
|
|
37
|
+
this.treeContainer.addEventListener('drop', this._onDrop.bind(this));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Make an item draggable
|
|
42
|
+
* @param {HTMLElement} item - Tree item element
|
|
43
|
+
*/
|
|
44
|
+
enableDrag(item) {
|
|
45
|
+
item.setAttribute('draggable', 'true');
|
|
46
|
+
item.dataset.draggable = 'true';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Handle drag start
|
|
51
|
+
*/
|
|
52
|
+
_onDragStart(e) {
|
|
53
|
+
const item = e.target.closest('.tree-item[data-draggable="true"]');
|
|
54
|
+
if (!item) return;
|
|
55
|
+
|
|
56
|
+
this.draggedItem = item;
|
|
57
|
+
this.draggedPath = item.dataset.path;
|
|
58
|
+
this.draggedType = item.dataset.type;
|
|
59
|
+
|
|
60
|
+
// Set drag data
|
|
61
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
62
|
+
e.dataTransfer.setData('text/plain', this.draggedPath);
|
|
63
|
+
|
|
64
|
+
// Add dragging class after a small delay (to allow drag image to be captured)
|
|
65
|
+
setTimeout(() => {
|
|
66
|
+
item.classList.add('dragging');
|
|
67
|
+
}, 0);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Handle drag end
|
|
72
|
+
*/
|
|
73
|
+
_onDragEnd(e) {
|
|
74
|
+
if (this.draggedItem) {
|
|
75
|
+
this.draggedItem.classList.remove('dragging');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Clear all drag states
|
|
79
|
+
this.treeContainer.querySelectorAll('.drag-over, .drag-invalid').forEach(el => {
|
|
80
|
+
el.classList.remove('drag-over', 'drag-invalid');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
this.draggedItem = null;
|
|
84
|
+
this.draggedPath = null;
|
|
85
|
+
this.draggedType = null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Handle drag over
|
|
90
|
+
*/
|
|
91
|
+
_onDragOver(e) {
|
|
92
|
+
e.preventDefault();
|
|
93
|
+
|
|
94
|
+
if (!this.draggedItem) return;
|
|
95
|
+
|
|
96
|
+
const target = e.target.closest('.tree-item[data-type="folder"]');
|
|
97
|
+
if (!target || target === this.draggedItem) {
|
|
98
|
+
e.dataTransfer.dropEffect = 'none';
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const targetPath = target.dataset.path;
|
|
103
|
+
|
|
104
|
+
if (this._isValidDrop(targetPath)) {
|
|
105
|
+
e.dataTransfer.dropEffect = 'move';
|
|
106
|
+
target.classList.add('drag-over');
|
|
107
|
+
target.classList.remove('drag-invalid');
|
|
108
|
+
} else {
|
|
109
|
+
e.dataTransfer.dropEffect = 'none';
|
|
110
|
+
target.classList.add('drag-invalid');
|
|
111
|
+
target.classList.remove('drag-over');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Handle drag leave
|
|
117
|
+
*/
|
|
118
|
+
_onDragLeave(e) {
|
|
119
|
+
const target = e.target.closest('.tree-item');
|
|
120
|
+
if (target) {
|
|
121
|
+
// Only remove if we're actually leaving (not entering a child)
|
|
122
|
+
const relatedTarget = e.relatedTarget;
|
|
123
|
+
if (!target.contains(relatedTarget)) {
|
|
124
|
+
target.classList.remove('drag-over', 'drag-invalid');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Handle drop
|
|
131
|
+
*/
|
|
132
|
+
async _onDrop(e) {
|
|
133
|
+
e.preventDefault();
|
|
134
|
+
|
|
135
|
+
const target = e.target.closest('.tree-item[data-type="folder"]');
|
|
136
|
+
|
|
137
|
+
if (!target || !this.draggedItem) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const targetPath = target.dataset.path;
|
|
142
|
+
|
|
143
|
+
// Clear visual states
|
|
144
|
+
target.classList.remove('drag-over', 'drag-invalid');
|
|
145
|
+
|
|
146
|
+
if (!this._isValidDrop(targetPath)) {
|
|
147
|
+
this._showInvalidFeedback(target);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Perform the move
|
|
152
|
+
const sourcePath = this.draggedPath;
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
// Show loading state
|
|
156
|
+
this.draggedItem.classList.add('moving');
|
|
157
|
+
|
|
158
|
+
if (this.onMove) {
|
|
159
|
+
const success = await this.onMove(sourcePath, targetPath);
|
|
160
|
+
|
|
161
|
+
if (success && this.onMoveComplete) {
|
|
162
|
+
this.onMoveComplete(sourcePath, targetPath);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.error('Move failed:', error);
|
|
167
|
+
this._showInvalidFeedback(this.draggedItem);
|
|
168
|
+
} finally {
|
|
169
|
+
if (this.draggedItem) {
|
|
170
|
+
this.draggedItem.classList.remove('moving');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Check if drop is valid
|
|
177
|
+
* @param {string} targetPath - Target folder path
|
|
178
|
+
* @returns {boolean}
|
|
179
|
+
*/
|
|
180
|
+
_isValidDrop(targetPath) {
|
|
181
|
+
if (!this.draggedPath) return false;
|
|
182
|
+
|
|
183
|
+
// Cannot drop onto self
|
|
184
|
+
if (this.draggedPath === targetPath) return false;
|
|
185
|
+
|
|
186
|
+
// Cannot drop folder into its own child
|
|
187
|
+
if (this.draggedType === 'folder') {
|
|
188
|
+
if (targetPath.startsWith(this.draggedPath + '/')) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Show invalid drop feedback (shake animation)
|
|
198
|
+
* @param {HTMLElement} target - Target element
|
|
199
|
+
*/
|
|
200
|
+
_showInvalidFeedback(target) {
|
|
201
|
+
if (!target) return;
|
|
202
|
+
|
|
203
|
+
target.classList.add('drag-invalid');
|
|
204
|
+
target.style.animation = 'shake 0.3s ease';
|
|
205
|
+
|
|
206
|
+
setTimeout(() => {
|
|
207
|
+
target.classList.remove('drag-invalid');
|
|
208
|
+
target.style.animation = '';
|
|
209
|
+
}, 300);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Destroy the drag manager
|
|
214
|
+
*/
|
|
215
|
+
destroy() {
|
|
216
|
+
this.treeContainer.removeEventListener('dragstart', this._onDragStart);
|
|
217
|
+
this.treeContainer.removeEventListener('dragend', this._onDragEnd);
|
|
218
|
+
this.treeContainer.removeEventListener('dragover', this._onDragOver);
|
|
219
|
+
this.treeContainer.removeEventListener('dragleave', this._onDragLeave);
|
|
220
|
+
this.treeContainer.removeEventListener('drop', this._onDrop);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Export for module usage
|
|
225
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
226
|
+
module.exports = TreeDragManager;
|
|
227
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TreeSearchManager - Search/filter tree functionality
|
|
3
|
+
* FEATURE-008 CR-006: Folder Tree UX Enhancement
|
|
4
|
+
*
|
|
5
|
+
* Provides:
|
|
6
|
+
* - Search bar in tree header
|
|
7
|
+
* - Real-time filtering with debounce
|
|
8
|
+
* - Preserves parent folder context for matches
|
|
9
|
+
* - Clear button and keyboard shortcuts
|
|
10
|
+
*/
|
|
11
|
+
class TreeSearchManager {
|
|
12
|
+
constructor(options) {
|
|
13
|
+
this.treeContainer = options.treeContainer;
|
|
14
|
+
this.onSearch = options.onSearch || null; // Optional callback for API-based search
|
|
15
|
+
this.searchInput = null;
|
|
16
|
+
this.clearBtn = null;
|
|
17
|
+
this.debounceTimer = null;
|
|
18
|
+
this.debounceDelay = 150;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Initialize search functionality
|
|
23
|
+
*/
|
|
24
|
+
init() {
|
|
25
|
+
this._createSearchBar();
|
|
26
|
+
this._bindEvents();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Create the search bar UI
|
|
31
|
+
*/
|
|
32
|
+
_createSearchBar() {
|
|
33
|
+
const header = this.treeContainer.closest('.workplace-sidebar-content')
|
|
34
|
+
?.querySelector('.workplace-sidebar-header');
|
|
35
|
+
|
|
36
|
+
if (!header) {
|
|
37
|
+
console.warn('TreeSearchManager: Could not find sidebar header');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Check if search bar already exists
|
|
42
|
+
if (header.parentElement.querySelector('.tree-search-container')) {
|
|
43
|
+
this.searchInput = header.parentElement.querySelector('.tree-search-input');
|
|
44
|
+
this.clearBtn = header.parentElement.querySelector('.tree-search-clear');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const searchHtml = `
|
|
49
|
+
<div class="tree-search-container">
|
|
50
|
+
<div class="tree-search-wrapper">
|
|
51
|
+
<i class="bi bi-search tree-search-icon"></i>
|
|
52
|
+
<input type="text"
|
|
53
|
+
class="tree-search-input"
|
|
54
|
+
placeholder="Filter files and folders..."
|
|
55
|
+
aria-label="Search files and folders">
|
|
56
|
+
<button class="tree-search-clear"
|
|
57
|
+
type="button"
|
|
58
|
+
title="Clear search"
|
|
59
|
+
style="display: none;">
|
|
60
|
+
<i class="bi bi-x-lg"></i>
|
|
61
|
+
</button>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
`;
|
|
65
|
+
|
|
66
|
+
header.insertAdjacentHTML('afterend', searchHtml);
|
|
67
|
+
this.searchInput = header.nextElementSibling.querySelector('.tree-search-input');
|
|
68
|
+
this.clearBtn = header.nextElementSibling.querySelector('.tree-search-clear');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Bind event listeners
|
|
73
|
+
*/
|
|
74
|
+
_bindEvents() {
|
|
75
|
+
if (!this.searchInput) return;
|
|
76
|
+
|
|
77
|
+
// Input handler with debounce
|
|
78
|
+
this.searchInput.addEventListener('input', (e) => {
|
|
79
|
+
const query = e.target.value;
|
|
80
|
+
|
|
81
|
+
// Show/hide clear button
|
|
82
|
+
this.clearBtn.style.display = query ? 'flex' : 'none';
|
|
83
|
+
|
|
84
|
+
// Debounce the filter
|
|
85
|
+
clearTimeout(this.debounceTimer);
|
|
86
|
+
this.debounceTimer = setTimeout(() => {
|
|
87
|
+
this._filterTree(query);
|
|
88
|
+
}, this.debounceDelay);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Clear button handler
|
|
92
|
+
this.clearBtn.addEventListener('click', () => {
|
|
93
|
+
this.clear();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Keyboard shortcuts
|
|
97
|
+
this.searchInput.addEventListener('keydown', (e) => {
|
|
98
|
+
if (e.key === 'Escape') {
|
|
99
|
+
this.clear();
|
|
100
|
+
this.searchInput.blur();
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Filter tree items based on query
|
|
107
|
+
* @param {string} query - Search query
|
|
108
|
+
*/
|
|
109
|
+
_filterTree(query) {
|
|
110
|
+
const normalizedQuery = query.toLowerCase().trim();
|
|
111
|
+
|
|
112
|
+
// If callback provided, use API-based search
|
|
113
|
+
if (this.onSearch) {
|
|
114
|
+
this.onSearch(normalizedQuery);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Client-side filtering
|
|
119
|
+
const items = this.treeContainer.querySelectorAll('.tree-item');
|
|
120
|
+
|
|
121
|
+
if (!normalizedQuery) {
|
|
122
|
+
// Show all items and collapse folders
|
|
123
|
+
items.forEach(item => {
|
|
124
|
+
item.style.display = '';
|
|
125
|
+
item.classList.remove('search-match', 'search-parent', 'expanded');
|
|
126
|
+
const childContainer = item.querySelector('.tree-children');
|
|
127
|
+
if (childContainer) {
|
|
128
|
+
childContainer.style.display = 'none';
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Find matching items and their parent paths
|
|
135
|
+
const matchingPaths = new Set();
|
|
136
|
+
const parentPaths = new Set();
|
|
137
|
+
|
|
138
|
+
items.forEach(item => {
|
|
139
|
+
const label = item.querySelector('.tree-label');
|
|
140
|
+
const name = label?.textContent?.toLowerCase() || '';
|
|
141
|
+
const path = item.dataset.path;
|
|
142
|
+
|
|
143
|
+
if (name.includes(normalizedQuery)) {
|
|
144
|
+
matchingPaths.add(path);
|
|
145
|
+
|
|
146
|
+
// Add all parent paths for context
|
|
147
|
+
if (path) {
|
|
148
|
+
const parts = path.split('/');
|
|
149
|
+
for (let i = 1; i < parts.length; i++) {
|
|
150
|
+
parentPaths.add(parts.slice(0, i).join('/'));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Apply visibility and styling
|
|
157
|
+
items.forEach(item => {
|
|
158
|
+
const path = item.dataset.path;
|
|
159
|
+
const isMatch = matchingPaths.has(path);
|
|
160
|
+
const isParent = parentPaths.has(path);
|
|
161
|
+
|
|
162
|
+
if (isMatch || isParent) {
|
|
163
|
+
item.style.display = '';
|
|
164
|
+
item.classList.toggle('search-match', isMatch);
|
|
165
|
+
item.classList.toggle('search-parent', isParent && !isMatch);
|
|
166
|
+
|
|
167
|
+
// Expand parent folders
|
|
168
|
+
if (isParent) {
|
|
169
|
+
const childContainer = item.querySelector('.tree-children');
|
|
170
|
+
if (childContainer) {
|
|
171
|
+
childContainer.style.display = 'block';
|
|
172
|
+
}
|
|
173
|
+
item.classList.add('expanded');
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
item.style.display = 'none';
|
|
177
|
+
item.classList.remove('search-match', 'search-parent');
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Clear the search
|
|
184
|
+
*/
|
|
185
|
+
clear() {
|
|
186
|
+
if (!this.searchInput) return;
|
|
187
|
+
|
|
188
|
+
this.searchInput.value = '';
|
|
189
|
+
this.clearBtn.style.display = 'none';
|
|
190
|
+
this._filterTree('');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Get current search query
|
|
195
|
+
* @returns {string}
|
|
196
|
+
*/
|
|
197
|
+
getQuery() {
|
|
198
|
+
return this.searchInput?.value || '';
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Set search query programmatically
|
|
203
|
+
* @param {string} query
|
|
204
|
+
*/
|
|
205
|
+
setQuery(query) {
|
|
206
|
+
if (!this.searchInput) return;
|
|
207
|
+
|
|
208
|
+
this.searchInput.value = query;
|
|
209
|
+
this.clearBtn.style.display = query ? 'flex' : 'none';
|
|
210
|
+
this._filterTree(query);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Destroy the search manager
|
|
215
|
+
*/
|
|
216
|
+
destroy() {
|
|
217
|
+
clearTimeout(this.debounceTimer);
|
|
218
|
+
const container = this.searchInput?.closest('.tree-search-container');
|
|
219
|
+
if (container) {
|
|
220
|
+
container.remove();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Export for module usage
|
|
226
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
227
|
+
module.exports = TreeSearchManager;
|
|
228
|
+
}
|