elspais 0.9.3__py3-none-any.whl → 0.11.0__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.
- elspais/cli.py +99 -1
- elspais/commands/hash_cmd.py +72 -26
- elspais/commands/reformat_cmd.py +458 -0
- elspais/commands/trace.py +157 -3
- elspais/commands/validate.py +44 -16
- elspais/core/models.py +2 -0
- elspais/core/parser.py +68 -24
- elspais/reformat/__init__.py +50 -0
- elspais/reformat/detector.py +119 -0
- elspais/reformat/hierarchy.py +246 -0
- elspais/reformat/line_breaks.py +220 -0
- elspais/reformat/prompts.py +123 -0
- elspais/reformat/transformer.py +264 -0
- elspais/sponsors/__init__.py +432 -0
- elspais/trace_view/__init__.py +54 -0
- elspais/trace_view/coverage.py +183 -0
- elspais/trace_view/generators/__init__.py +12 -0
- elspais/trace_view/generators/base.py +329 -0
- elspais/trace_view/generators/csv.py +122 -0
- elspais/trace_view/generators/markdown.py +175 -0
- elspais/trace_view/html/__init__.py +31 -0
- elspais/trace_view/html/generator.py +1006 -0
- elspais/trace_view/html/templates/base.html +283 -0
- elspais/trace_view/html/templates/components/code_viewer_modal.html +14 -0
- elspais/trace_view/html/templates/components/file_picker_modal.html +20 -0
- elspais/trace_view/html/templates/components/legend_modal.html +69 -0
- elspais/trace_view/html/templates/components/review_panel.html +118 -0
- elspais/trace_view/html/templates/partials/review/help/help-panel.json +244 -0
- elspais/trace_view/html/templates/partials/review/help/onboarding.json +77 -0
- elspais/trace_view/html/templates/partials/review/help/tooltips.json +237 -0
- elspais/trace_view/html/templates/partials/review/review-comments.js +928 -0
- elspais/trace_view/html/templates/partials/review/review-data.js +961 -0
- elspais/trace_view/html/templates/partials/review/review-help.js +679 -0
- elspais/trace_view/html/templates/partials/review/review-init.js +177 -0
- elspais/trace_view/html/templates/partials/review/review-line-numbers.js +429 -0
- elspais/trace_view/html/templates/partials/review/review-packages.js +1029 -0
- elspais/trace_view/html/templates/partials/review/review-position.js +540 -0
- elspais/trace_view/html/templates/partials/review/review-resize.js +115 -0
- elspais/trace_view/html/templates/partials/review/review-status.js +659 -0
- elspais/trace_view/html/templates/partials/review/review-sync.js +992 -0
- elspais/trace_view/html/templates/partials/review-styles.css +2238 -0
- elspais/trace_view/html/templates/partials/scripts.js +1741 -0
- elspais/trace_view/html/templates/partials/styles.css +1756 -0
- elspais/trace_view/models.py +353 -0
- elspais/trace_view/review/__init__.py +60 -0
- elspais/trace_view/review/branches.py +1149 -0
- elspais/trace_view/review/models.py +1205 -0
- elspais/trace_view/review/position.py +609 -0
- elspais/trace_view/review/server.py +1056 -0
- elspais/trace_view/review/status.py +470 -0
- elspais/trace_view/review/storage.py +1367 -0
- elspais/trace_view/scanning.py +213 -0
- elspais/trace_view/specs/README.md +84 -0
- elspais/trace_view/specs/tv-d00001-template-architecture.md +36 -0
- elspais/trace_view/specs/tv-d00002-css-extraction.md +37 -0
- elspais/trace_view/specs/tv-d00003-js-extraction.md +43 -0
- elspais/trace_view/specs/tv-d00004-build-embedding.md +40 -0
- elspais/trace_view/specs/tv-d00005-test-format.md +78 -0
- elspais/trace_view/specs/tv-d00010-review-data-models.md +33 -0
- elspais/trace_view/specs/tv-d00011-review-storage.md +33 -0
- elspais/trace_view/specs/tv-d00012-position-resolution.md +33 -0
- elspais/trace_view/specs/tv-d00013-git-branches.md +31 -0
- elspais/trace_view/specs/tv-d00014-review-api-server.md +31 -0
- elspais/trace_view/specs/tv-d00015-status-modifier.md +27 -0
- elspais/trace_view/specs/tv-d00016-js-integration.md +33 -0
- elspais/trace_view/specs/tv-p00001-html-generator.md +33 -0
- elspais/trace_view/specs/tv-p00002-review-system.md +29 -0
- {elspais-0.9.3.dist-info → elspais-0.11.0.dist-info}/METADATA +33 -18
- elspais-0.11.0.dist-info/RECORD +101 -0
- elspais-0.9.3.dist-info/RECORD +0 -40
- {elspais-0.9.3.dist-info → elspais-0.11.0.dist-info}/WHEEL +0 -0
- {elspais-0.9.3.dist-info → elspais-0.11.0.dist-info}/entry_points.txt +0 -0
- {elspais-0.9.3.dist-info → elspais-0.11.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TraceView Review Init Module
|
|
3
|
+
* Orchestrates review mode initialization and provides global functions.
|
|
4
|
+
* IMPLEMENTS REQUIREMENTS: REQ-d00092
|
|
5
|
+
*/
|
|
6
|
+
window.TraceView = window.TraceView || {};
|
|
7
|
+
TraceView.review = TraceView.review || {};
|
|
8
|
+
window.ReviewSystem = window.ReviewSystem || {};
|
|
9
|
+
var RS = window.ReviewSystem;
|
|
10
|
+
|
|
11
|
+
(function(review, RS) {
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
let reviewModeActive = false;
|
|
15
|
+
let selectedCardElement = null;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Toggle review mode on/off
|
|
19
|
+
* @param {boolean} enabled - Optional: explicitly set mode state
|
|
20
|
+
* @returns {boolean} Current review mode state
|
|
21
|
+
*/
|
|
22
|
+
function toggleReviewMode(enabled) {
|
|
23
|
+
if (enabled === undefined) {
|
|
24
|
+
reviewModeActive = !reviewModeActive;
|
|
25
|
+
} else {
|
|
26
|
+
reviewModeActive = !!enabled;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const column = document.getElementById('review-column');
|
|
30
|
+
const btn = document.getElementById('btnReviewMode');
|
|
31
|
+
const packagesPanel = document.getElementById('reviewPackagesPanel');
|
|
32
|
+
|
|
33
|
+
// Use classList.toggle for body class (REQ-d00092-G)
|
|
34
|
+
document.body.classList.toggle('review-mode-active', reviewModeActive);
|
|
35
|
+
|
|
36
|
+
if (reviewModeActive) {
|
|
37
|
+
if (column) column.classList.remove('hidden');
|
|
38
|
+
if (btn) btn.classList.add('active');
|
|
39
|
+
if (packagesPanel) packagesPanel.style.display = 'block';
|
|
40
|
+
|
|
41
|
+
// Initialize if first activation
|
|
42
|
+
if (review.init && !review._initialized) {
|
|
43
|
+
review.init();
|
|
44
|
+
review._initialized = true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// If there are already open REQ cards, apply interactive line numbers to all of them
|
|
48
|
+
if (window.TraceView && window.TraceView.state && window.TraceView.state.reqCardStack) {
|
|
49
|
+
const openCards = window.TraceView.state.reqCardStack;
|
|
50
|
+
|
|
51
|
+
// Apply line numbers to ALL open cards
|
|
52
|
+
openCards.forEach(cardReqId => {
|
|
53
|
+
const cardElement = document.getElementById(`req-card-${cardReqId}`);
|
|
54
|
+
if (cardElement && review.applyLineNumbersToCard) {
|
|
55
|
+
review.applyLineNumbersToCard(cardElement, cardReqId);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Select the most recently opened card (first in stack) for review
|
|
60
|
+
if (openCards.length > 0) {
|
|
61
|
+
const reqId = openCards[0];
|
|
62
|
+
const req = window.REQ_CONTENT_DATA ? window.REQ_CONTENT_DATA[reqId] : null;
|
|
63
|
+
document.dispatchEvent(new CustomEvent('traceview:req-selected', {
|
|
64
|
+
detail: { reqId, req }
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
if (column) column.classList.add('hidden');
|
|
70
|
+
if (btn) btn.classList.remove('active');
|
|
71
|
+
if (packagesPanel) packagesPanel.style.display = 'none';
|
|
72
|
+
|
|
73
|
+
// Clear selection when deactivating (REQ-d00092-G)
|
|
74
|
+
clearCurrentSelection();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Dispatch event for review mode change (REQ-d00092-E)
|
|
78
|
+
document.dispatchEvent(new CustomEvent('rs:review-mode-changed', {
|
|
79
|
+
detail: { active: reviewModeActive }
|
|
80
|
+
}));
|
|
81
|
+
|
|
82
|
+
return reviewModeActive;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Clear current selection (card and line selections)
|
|
87
|
+
*/
|
|
88
|
+
function clearCurrentSelection() {
|
|
89
|
+
if (selectedCardElement) {
|
|
90
|
+
selectedCardElement.classList.remove('rs-selected');
|
|
91
|
+
// Clear line selections using RS namespace
|
|
92
|
+
if (RS.clearAllLineSelections) {
|
|
93
|
+
RS.clearAllLineSelections();
|
|
94
|
+
}
|
|
95
|
+
selectedCardElement = null;
|
|
96
|
+
}
|
|
97
|
+
review.selectedReqId = null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Apply line numbers to the currently selected requirement card
|
|
102
|
+
* @param {HTMLElement} cardElement - The card element
|
|
103
|
+
* @param {string} reqId - Requirement ID
|
|
104
|
+
*/
|
|
105
|
+
function applyLineNumbersToReqCard(cardElement, reqId) {
|
|
106
|
+
// Use RS namespace to apply line numbers (REQ-d00092)
|
|
107
|
+
if (RS.applyLineNumbersToCard) {
|
|
108
|
+
RS.applyLineNumbersToCard(cardElement, reqId);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Select a requirement for review
|
|
114
|
+
* @param {string} reqId - Requirement ID to select
|
|
115
|
+
*/
|
|
116
|
+
function selectReqForReview(reqId) {
|
|
117
|
+
if (!reviewModeActive) {
|
|
118
|
+
toggleReviewMode(true);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Clear previous selection
|
|
122
|
+
clearCurrentSelection();
|
|
123
|
+
|
|
124
|
+
review.selectedReqId = reqId;
|
|
125
|
+
const req = window.REQ_CONTENT_DATA ? window.REQ_CONTENT_DATA[reqId] : null;
|
|
126
|
+
|
|
127
|
+
// Find and mark the card element
|
|
128
|
+
const cardElement = document.querySelector(`[data-req-id="${reqId}"]`);
|
|
129
|
+
if (cardElement) {
|
|
130
|
+
selectedCardElement = cardElement;
|
|
131
|
+
cardElement.classList.add('rs-selected');
|
|
132
|
+
|
|
133
|
+
// Apply line numbers to the selected card (REQ-d00092-E)
|
|
134
|
+
applyLineNumbersToReqCard(cardElement, reqId);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
document.dispatchEvent(new CustomEvent('traceview:req-selected', {
|
|
138
|
+
detail: { reqId, req }
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Check if review mode is currently active
|
|
144
|
+
* @returns {boolean} True if review mode is active
|
|
145
|
+
*/
|
|
146
|
+
function isReviewModeActive() {
|
|
147
|
+
return reviewModeActive;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Auto-initialize on DOMContentLoaded if review mode data exists
|
|
151
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
152
|
+
if (document.body.getAttribute('data-review-mode') === 'true') {
|
|
153
|
+
if (window.REVIEW_DATA) {
|
|
154
|
+
console.log('Review mode auto-initializing...');
|
|
155
|
+
// Don't auto-enable review mode, just set up the system
|
|
156
|
+
if (review.init && !review._initialized) {
|
|
157
|
+
review.init();
|
|
158
|
+
review._initialized = true;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Export functions to review namespace
|
|
165
|
+
review.toggleReviewMode = toggleReviewMode;
|
|
166
|
+
review.selectReqForReview = selectReqForReview;
|
|
167
|
+
review.isReviewModeActive = isReviewModeActive;
|
|
168
|
+
review.clearCurrentSelection = clearCurrentSelection;
|
|
169
|
+
review.applyLineNumbersToReqCard = applyLineNumbersToReqCard;
|
|
170
|
+
|
|
171
|
+
// Export to ReviewSystem alias (RS pattern)
|
|
172
|
+
RS.toggleReviewMode = toggleReviewMode;
|
|
173
|
+
RS.selectReqForReview = selectReqForReview;
|
|
174
|
+
RS.isReviewModeActive = isReviewModeActive;
|
|
175
|
+
RS.clearCurrentSelection = clearCurrentSelection;
|
|
176
|
+
|
|
177
|
+
})(TraceView.review, RS);
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TraceView Review Line Numbers Module
|
|
3
|
+
* Add line numbers to requirement content for click-to-comment.
|
|
4
|
+
* IMPLEMENTS REQUIREMENTS: REQ-d00092
|
|
5
|
+
*/
|
|
6
|
+
window.TraceView = window.TraceView || {};
|
|
7
|
+
TraceView.review = TraceView.review || {};
|
|
8
|
+
window.ReviewSystem = window.ReviewSystem || {};
|
|
9
|
+
var RS = window.ReviewSystem;
|
|
10
|
+
|
|
11
|
+
// Global selection state (REQ-d00092-H)
|
|
12
|
+
window.selectedLineNumber = null;
|
|
13
|
+
window.selectedLineRange = null;
|
|
14
|
+
|
|
15
|
+
(function(review, RS) {
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
// Track current selection state
|
|
19
|
+
let currentReqId = null;
|
|
20
|
+
let selectionType = 'general'; // 'line', 'block', or 'general'
|
|
21
|
+
|
|
22
|
+
// Drag selection state
|
|
23
|
+
let isDragging = false;
|
|
24
|
+
let dragStartLine = null;
|
|
25
|
+
let dragContainer = null;
|
|
26
|
+
let dragReqId = null;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Convert markdown content to line-numbered HTML view
|
|
30
|
+
* @param {string} content - Raw markdown/text content
|
|
31
|
+
* @param {string} reqId - The requirement ID
|
|
32
|
+
* @returns {HTMLElement} Container element with line-numbered content
|
|
33
|
+
*/
|
|
34
|
+
function convertToLineNumberedView(content, reqId) {
|
|
35
|
+
const container = document.createElement('div');
|
|
36
|
+
container.className = 'rs-line-numbers-container';
|
|
37
|
+
container.setAttribute('data-req-id', reqId);
|
|
38
|
+
|
|
39
|
+
// Create hint bar (shows click instructions in review mode)
|
|
40
|
+
const hint = document.createElement('div');
|
|
41
|
+
hint.className = 'rs-line-numbers-hint';
|
|
42
|
+
hint.textContent = 'Click or drag to select lines for commenting';
|
|
43
|
+
container.appendChild(hint);
|
|
44
|
+
|
|
45
|
+
// Create table structure for line numbers
|
|
46
|
+
const table = document.createElement('div');
|
|
47
|
+
table.className = 'rs-lines-table';
|
|
48
|
+
|
|
49
|
+
const lines = (content || '').split('\n');
|
|
50
|
+
lines.forEach((line, index) => {
|
|
51
|
+
const row = document.createElement('div');
|
|
52
|
+
row.className = 'rs-line-row';
|
|
53
|
+
row.setAttribute('data-line', index + 1);
|
|
54
|
+
row.style.cursor = 'pointer';
|
|
55
|
+
|
|
56
|
+
// Mousedown handler to start selection/drag
|
|
57
|
+
row.addEventListener('mousedown', function(event) {
|
|
58
|
+
event.preventDefault(); // Prevent text selection
|
|
59
|
+
handleLineMouseDown(event, reqId, index + 1, container);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Mouseover handler for drag selection
|
|
63
|
+
row.addEventListener('mouseover', function(event) {
|
|
64
|
+
if (isDragging && dragContainer === container) {
|
|
65
|
+
handleLineDragOver(reqId, index + 1, container);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Line number
|
|
70
|
+
const lineNum = document.createElement('span');
|
|
71
|
+
lineNum.className = 'rs-line-number';
|
|
72
|
+
lineNum.textContent = index + 1;
|
|
73
|
+
|
|
74
|
+
// Line text
|
|
75
|
+
const lineText = document.createElement('span');
|
|
76
|
+
lineText.className = 'rs-line-text';
|
|
77
|
+
lineText.textContent = line || ' '; // Preserve empty lines
|
|
78
|
+
|
|
79
|
+
row.appendChild(lineNum);
|
|
80
|
+
row.appendChild(lineText);
|
|
81
|
+
table.appendChild(row);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
container.appendChild(table);
|
|
85
|
+
return container;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Handle mousedown on a line - start selection or drag
|
|
90
|
+
* @param {MouseEvent} event - Mouse event
|
|
91
|
+
* @param {string} reqId - Requirement ID
|
|
92
|
+
* @param {number} lineNumber - Line number
|
|
93
|
+
* @param {HTMLElement} container - Container element
|
|
94
|
+
*/
|
|
95
|
+
function handleLineMouseDown(event, reqId, lineNumber, container) {
|
|
96
|
+
// Start drag tracking
|
|
97
|
+
isDragging = true;
|
|
98
|
+
dragStartLine = lineNumber;
|
|
99
|
+
dragContainer = container;
|
|
100
|
+
dragReqId = reqId;
|
|
101
|
+
|
|
102
|
+
// Select the starting line
|
|
103
|
+
selectSingleLine(reqId, lineNumber, container);
|
|
104
|
+
|
|
105
|
+
// Dispatch initial selection event
|
|
106
|
+
dispatchSelectionEvent(reqId, lineNumber);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Handle mouseover during drag - extend selection
|
|
111
|
+
* @param {string} reqId - Requirement ID
|
|
112
|
+
* @param {number} lineNumber - Line number being hovered
|
|
113
|
+
* @param {HTMLElement} container - Container element
|
|
114
|
+
*/
|
|
115
|
+
function handleLineDragOver(reqId, lineNumber, container) {
|
|
116
|
+
if (!isDragging || dragStartLine === null) return;
|
|
117
|
+
|
|
118
|
+
if (lineNumber !== dragStartLine) {
|
|
119
|
+
// Extend selection to range
|
|
120
|
+
selectLineRange(reqId, dragStartLine, lineNumber, container);
|
|
121
|
+
} else {
|
|
122
|
+
// Back to single line
|
|
123
|
+
selectSingleLine(reqId, lineNumber, container);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Handle mouseup - finalize selection
|
|
129
|
+
*/
|
|
130
|
+
function handleLineMouseUp() {
|
|
131
|
+
if (isDragging && dragReqId) {
|
|
132
|
+
// Dispatch final selection event
|
|
133
|
+
dispatchSelectionEvent(dragReqId, window.selectedLineNumber);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Reset drag state
|
|
137
|
+
isDragging = false;
|
|
138
|
+
dragStartLine = null;
|
|
139
|
+
dragContainer = null;
|
|
140
|
+
dragReqId = null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Dispatch selection event for line selection
|
|
145
|
+
* @param {string} reqId - Requirement ID
|
|
146
|
+
* @param {number} lineNumber - Primary line number
|
|
147
|
+
*/
|
|
148
|
+
function dispatchSelectionEvent(reqId, lineNumber) {
|
|
149
|
+
// Dispatch event for line selection (REQ-d00092-C)
|
|
150
|
+
document.dispatchEvent(new CustomEvent('rs:line-selected', {
|
|
151
|
+
detail: {
|
|
152
|
+
reqId: reqId,
|
|
153
|
+
lineNumber: lineNumber,
|
|
154
|
+
lineRange: window.selectedLineRange,
|
|
155
|
+
selectionType: selectionType
|
|
156
|
+
}
|
|
157
|
+
}));
|
|
158
|
+
|
|
159
|
+
// Also update Review Panel focus to this REQ
|
|
160
|
+
const req = window.REQ_CONTENT_DATA ? window.REQ_CONTENT_DATA[reqId] : null;
|
|
161
|
+
document.dispatchEvent(new CustomEvent('traceview:req-selected', {
|
|
162
|
+
detail: { reqId: reqId, req: req }
|
|
163
|
+
}));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Handle click on a line (legacy support, also handles shift-click)
|
|
168
|
+
* @param {Event} event - Click event
|
|
169
|
+
* @param {string} reqId - Requirement ID
|
|
170
|
+
* @param {number} lineNumber - Line number clicked
|
|
171
|
+
* @param {HTMLElement} container - Container element
|
|
172
|
+
*/
|
|
173
|
+
function handleLineClick(event, reqId, lineNumber, container) {
|
|
174
|
+
if (event.shiftKey && window.selectedLineNumber !== null) {
|
|
175
|
+
// Shift-click: select range
|
|
176
|
+
selectLineRange(reqId, window.selectedLineNumber, lineNumber, container);
|
|
177
|
+
} else {
|
|
178
|
+
// Single click: select single line
|
|
179
|
+
selectSingleLine(reqId, lineNumber, container);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
dispatchSelectionEvent(reqId, lineNumber);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Global mouseup handler to finalize drag selection
|
|
186
|
+
document.addEventListener('mouseup', handleLineMouseUp);
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Select a single line
|
|
190
|
+
* @param {string} reqId - Requirement ID
|
|
191
|
+
* @param {number} lineNumber - Line number to select
|
|
192
|
+
* @param {HTMLElement} container - Container element (optional)
|
|
193
|
+
*/
|
|
194
|
+
function selectSingleLine(reqId, lineNumber, container) {
|
|
195
|
+
currentReqId = reqId;
|
|
196
|
+
window.selectedLineNumber = lineNumber;
|
|
197
|
+
window.selectedLineRange = null;
|
|
198
|
+
selectionType = 'line';
|
|
199
|
+
|
|
200
|
+
// Clear previous selection
|
|
201
|
+
if (container) {
|
|
202
|
+
const prev = container.querySelectorAll('.rs-line-row.selected');
|
|
203
|
+
prev.forEach(el => el.classList.remove('selected'));
|
|
204
|
+
|
|
205
|
+
// Highlight selected line
|
|
206
|
+
const row = container.querySelector(`.rs-line-row[data-line="${lineNumber}"]`);
|
|
207
|
+
if (row) {
|
|
208
|
+
row.classList.add('selected');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Select a range of lines
|
|
215
|
+
* @param {string} reqId - Requirement ID
|
|
216
|
+
* @param {number} startLine - Start line number
|
|
217
|
+
* @param {number} endLine - End line number
|
|
218
|
+
* @param {HTMLElement} container - Container element (optional)
|
|
219
|
+
*/
|
|
220
|
+
function selectLineRange(reqId, startLine, endLine, container) {
|
|
221
|
+
currentReqId = reqId;
|
|
222
|
+
const start = Math.min(startLine, endLine);
|
|
223
|
+
const end = Math.max(startLine, endLine);
|
|
224
|
+
window.selectedLineNumber = start;
|
|
225
|
+
window.selectedLineRange = { start: start, end: end };
|
|
226
|
+
selectionType = 'block';
|
|
227
|
+
|
|
228
|
+
// Clear previous selection
|
|
229
|
+
if (container) {
|
|
230
|
+
const prev = container.querySelectorAll('.rs-line-row.selected');
|
|
231
|
+
prev.forEach(el => el.classList.remove('selected'));
|
|
232
|
+
|
|
233
|
+
// Highlight range
|
|
234
|
+
for (let i = start; i <= end; i++) {
|
|
235
|
+
const row = container.querySelector(`.rs-line-row[data-line="${i}"]`);
|
|
236
|
+
if (row) {
|
|
237
|
+
row.classList.add('selected');
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Apply line numbers to a requirement card
|
|
245
|
+
* @param {HTMLElement} cardElement - The card element containing the requirement
|
|
246
|
+
* @param {string} reqId - The requirement ID
|
|
247
|
+
*/
|
|
248
|
+
function applyLineNumbersToCard(cardElement, reqId) {
|
|
249
|
+
if (!cardElement) return;
|
|
250
|
+
|
|
251
|
+
// Find the body section of the requirement card
|
|
252
|
+
const bodySection = cardElement.querySelector('.req-body-section');
|
|
253
|
+
if (!bodySection) return;
|
|
254
|
+
|
|
255
|
+
// Check if already fully processed (has our container with click handlers)
|
|
256
|
+
if (bodySection.querySelector('.rs-line-numbers-container')) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Check if line numbers already exist (from scripts.js renderMarkdownWithLines)
|
|
261
|
+
// If so, just add click handlers instead of recreating
|
|
262
|
+
const existingTable = bodySection.querySelector('.rs-lines-table');
|
|
263
|
+
if (existingTable) {
|
|
264
|
+
// Add wrapper container with hint
|
|
265
|
+
const container = document.createElement('div');
|
|
266
|
+
container.className = 'rs-line-numbers-container';
|
|
267
|
+
container.setAttribute('data-req-id', reqId);
|
|
268
|
+
|
|
269
|
+
// Create hint bar
|
|
270
|
+
const hint = document.createElement('div');
|
|
271
|
+
hint.className = 'rs-line-numbers-hint';
|
|
272
|
+
hint.textContent = 'Click or drag to select lines for commenting';
|
|
273
|
+
container.appendChild(hint);
|
|
274
|
+
|
|
275
|
+
// Move existing table into container
|
|
276
|
+
existingTable.parentNode.insertBefore(container, existingTable);
|
|
277
|
+
container.appendChild(existingTable);
|
|
278
|
+
|
|
279
|
+
// Add drag selection handlers to entire rows
|
|
280
|
+
const rows = existingTable.querySelectorAll('.rs-line-row');
|
|
281
|
+
rows.forEach(row => {
|
|
282
|
+
const lineNumber = parseInt(row.getAttribute('data-line'), 10);
|
|
283
|
+
if (lineNumber) {
|
|
284
|
+
// Mousedown to start selection/drag
|
|
285
|
+
row.addEventListener('mousedown', function(event) {
|
|
286
|
+
event.preventDefault(); // Prevent text selection
|
|
287
|
+
handleLineMouseDown(event, reqId, lineNumber, container);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// Mouseover for drag selection
|
|
291
|
+
row.addEventListener('mouseover', function(event) {
|
|
292
|
+
if (isDragging && dragContainer === container) {
|
|
293
|
+
handleLineDragOver(reqId, lineNumber, container);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
row.style.cursor = 'pointer';
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
bodySection.classList.add('rs-with-line-numbers');
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// No existing line numbers - create from scratch
|
|
306
|
+
const content = bodySection.textContent || '';
|
|
307
|
+
const container = convertToLineNumberedView(content, reqId);
|
|
308
|
+
|
|
309
|
+
// Replace content with line-numbered version
|
|
310
|
+
bodySection.innerHTML = '';
|
|
311
|
+
bodySection.appendChild(container);
|
|
312
|
+
bodySection.classList.add('rs-with-line-numbers');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Get current line selection state
|
|
317
|
+
* @returns {Object} Selection state with type, lineNumber, and lineRange
|
|
318
|
+
*/
|
|
319
|
+
function getLineSelection() {
|
|
320
|
+
return {
|
|
321
|
+
type: selectionType,
|
|
322
|
+
lineNumber: window.selectedLineNumber,
|
|
323
|
+
lineRange: window.selectedLineRange,
|
|
324
|
+
reqId: currentReqId
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Clear line selection for a specific card
|
|
330
|
+
* @param {HTMLElement} cardElement - The card element
|
|
331
|
+
*/
|
|
332
|
+
function clearLineSelection(cardElement) {
|
|
333
|
+
if (!cardElement) return;
|
|
334
|
+
|
|
335
|
+
const highlights = cardElement.querySelectorAll('.rs-line-row.selected');
|
|
336
|
+
highlights.forEach(el => el.classList.remove('selected'));
|
|
337
|
+
|
|
338
|
+
// Reset global state if this was the active selection
|
|
339
|
+
const container = cardElement.querySelector('.rs-line-numbers-container');
|
|
340
|
+
if (container && container.getAttribute('data-req-id') === currentReqId) {
|
|
341
|
+
window.selectedLineNumber = null;
|
|
342
|
+
window.selectedLineRange = null;
|
|
343
|
+
selectionType = 'general';
|
|
344
|
+
currentReqId = null;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Clear all line selections across all cards
|
|
350
|
+
*/
|
|
351
|
+
function clearAllLineSelections() {
|
|
352
|
+
const containers = document.querySelectorAll('.rs-line-numbers-container');
|
|
353
|
+
containers.forEach(container => {
|
|
354
|
+
const highlights = container.querySelectorAll('.rs-line-row.selected');
|
|
355
|
+
highlights.forEach(el => el.classList.remove('selected'));
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// Reset global state
|
|
359
|
+
window.selectedLineNumber = null;
|
|
360
|
+
window.selectedLineRange = null;
|
|
361
|
+
selectionType = 'general';
|
|
362
|
+
currentReqId = null;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Highlight a specific line in a requirement card
|
|
367
|
+
* @param {HTMLElement} cardElement - The card element
|
|
368
|
+
* @param {number} lineNumber - The line number to highlight
|
|
369
|
+
*/
|
|
370
|
+
function highlightLine(cardElement, lineNumber) {
|
|
371
|
+
if (!cardElement) return;
|
|
372
|
+
|
|
373
|
+
// Remove existing highlights
|
|
374
|
+
const existingHighlights = cardElement.querySelectorAll('.rs-line-row.selected');
|
|
375
|
+
existingHighlights.forEach(el => el.classList.remove('selected'));
|
|
376
|
+
|
|
377
|
+
// Add highlight to target line
|
|
378
|
+
const targetRow = cardElement.querySelector(`.rs-line-row[data-line="${lineNumber}"]`);
|
|
379
|
+
if (targetRow) {
|
|
380
|
+
targetRow.classList.add('selected');
|
|
381
|
+
targetRow.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Clear all line highlights in a card (alias for clearLineSelection)
|
|
387
|
+
* @param {HTMLElement} cardElement - The card element
|
|
388
|
+
*/
|
|
389
|
+
function clearLineHighlights(cardElement) {
|
|
390
|
+
clearLineSelection(cardElement);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Handle keyboard events for line selection (REQ-d00092-I)
|
|
395
|
+
* @param {KeyboardEvent} event - Keyboard event
|
|
396
|
+
*/
|
|
397
|
+
function handleKeyboard(event) {
|
|
398
|
+
if (event.key === 'Escape') {
|
|
399
|
+
clearAllLineSelections();
|
|
400
|
+
document.dispatchEvent(new CustomEvent('rs:line-selection-cleared'));
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Bind keyboard handler (REQ-d00092-I)
|
|
405
|
+
document.addEventListener('keydown', handleKeyboard);
|
|
406
|
+
|
|
407
|
+
// Export to review namespace
|
|
408
|
+
review.convertToLineNumberedView = convertToLineNumberedView;
|
|
409
|
+
review.applyLineNumbersToCard = applyLineNumbersToCard;
|
|
410
|
+
review.getLineSelection = getLineSelection;
|
|
411
|
+
review.clearLineSelection = clearLineSelection;
|
|
412
|
+
review.clearAllLineSelections = clearAllLineSelections;
|
|
413
|
+
review.highlightLine = highlightLine;
|
|
414
|
+
review.clearLineHighlights = clearLineHighlights;
|
|
415
|
+
review.selectSingleLine = selectSingleLine;
|
|
416
|
+
review.selectLineRange = selectLineRange;
|
|
417
|
+
review.handleLineClick = handleLineClick;
|
|
418
|
+
review.handleKeyboard = handleKeyboard;
|
|
419
|
+
|
|
420
|
+
// Export to ReviewSystem alias (RS) pattern for tests
|
|
421
|
+
RS.convertToLineNumberedView = convertToLineNumberedView;
|
|
422
|
+
RS.applyLineNumbersToCard = applyLineNumbersToCard;
|
|
423
|
+
RS.getLineSelection = getLineSelection;
|
|
424
|
+
RS.clearLineSelection = clearLineSelection;
|
|
425
|
+
RS.clearAllLineSelections = clearAllLineSelections;
|
|
426
|
+
RS.highlightLine = highlightLine;
|
|
427
|
+
RS.clearLineHighlights = clearLineHighlights;
|
|
428
|
+
|
|
429
|
+
})(TraceView.review, RS);
|