ep_data_tables 0.0.6 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/static/js/client_hooks.js +1040 -599
|
@@ -17,12 +17,13 @@ const ATTR_TABLE_JSON = 'tbljson';
|
|
|
17
17
|
const ATTR_CELL = 'td';
|
|
18
18
|
const ATTR_CLASS_PREFIX = 'tbljson-'; // For finding the class in DOM
|
|
19
19
|
const log = (...m) => console.debug('[ep_data_tables:client_hooks]', ...m);
|
|
20
|
-
log('version 0.0.6');
|
|
21
20
|
const DELIMITER = '\u241F'; // Internal column delimiter (␟)
|
|
22
21
|
// Use the same rare character inside the hidden span so acePostWriteDomLineHTML can
|
|
23
22
|
// still find delimiters when it splits node.innerHTML.
|
|
24
23
|
// Users never see this because the span is contenteditable=false and styled away.
|
|
25
24
|
const HIDDEN_DELIM = DELIMITER;
|
|
25
|
+
// InputEvent inputTypes used by mobile autocorrect/IME commit
|
|
26
|
+
const INPUTTYPE_REPLACEMENT_TYPES = new Set(['insertReplacementText', 'insertFromComposition']);
|
|
26
27
|
|
|
27
28
|
// helper for stable random ids
|
|
28
29
|
const rand = () => Math.random().toString(36).slice(2, 8);
|
|
@@ -67,14 +68,19 @@ let isAndroidChromeComposition = false;
|
|
|
67
68
|
let handledCurrentComposition = false;
|
|
68
69
|
// Suppress all beforeinput insertText events during an Android Chrome IME composition
|
|
69
70
|
let suppressBeforeInputInsertTextDuringComposition = false;
|
|
70
|
-
// Helper to detect Android
|
|
71
|
-
function
|
|
71
|
+
// Helper to detect any Android browser (exclude iOS/Safari)
|
|
72
|
+
function isAndroidUA() {
|
|
72
73
|
const ua = (navigator.userAgent || '').toLowerCase();
|
|
73
74
|
const isAndroid = ua.includes('android');
|
|
74
75
|
const isIOS = ua.includes('iphone') || ua.includes('ipad') || ua.includes('ipod') || ua.includes('crios');
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
// Safari on Android is rare (WebKit ports), but our exclusion target is iOS Safari; we exclude all iOS above
|
|
77
|
+
return isAndroid && !isIOS;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Helper to detect any iOS browser (Safari, Chrome iOS, Firefox iOS, Edge iOS)
|
|
81
|
+
function isIOSUA() {
|
|
82
|
+
const ua = (navigator.userAgent || '').toLowerCase();
|
|
83
|
+
return ua.includes('iphone') || ua.includes('ipad') || ua.includes('ipod') || ua.includes('ios') || ua.includes('crios') || ua.includes('fxios') || ua.includes('edgios');
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
// ─────────────────── Reusable Helper Functions ───────────────────
|
|
@@ -122,16 +128,16 @@ function getTableLineMetadata(lineNum, editorInfo, docManager) {
|
|
|
122
128
|
try {
|
|
123
129
|
const metadata = JSON.parse(attribs);
|
|
124
130
|
if (metadata && metadata.tblId) {
|
|
125
|
-
|
|
131
|
+
// log(`${funcName}: Found metadata via attribute for line ${lineNum}`);
|
|
126
132
|
return metadata;
|
|
127
133
|
}
|
|
128
134
|
} catch (e) {
|
|
129
|
-
|
|
135
|
+
// log(`${funcName}: Invalid JSON in tbljson attribute on line ${lineNum}:`, e.message);
|
|
130
136
|
}
|
|
131
137
|
}
|
|
132
138
|
|
|
133
139
|
// Fallback for block-styled lines.
|
|
134
|
-
|
|
140
|
+
// log(`${funcName}: No valid attribute on line ${lineNum}, checking DOM.`);
|
|
135
141
|
const rep = editorInfo.ace_getRep();
|
|
136
142
|
|
|
137
143
|
// This is the fix: Get the lineNode directly from the rep. It's more reliable
|
|
@@ -140,7 +146,7 @@ function getTableLineMetadata(lineNum, editorInfo, docManager) {
|
|
|
140
146
|
const lineNode = lineEntry?.lineNode;
|
|
141
147
|
|
|
142
148
|
if (!lineNode) {
|
|
143
|
-
|
|
149
|
+
// log(`${funcName}: Could not find line node in rep for line ${lineNum}`);
|
|
144
150
|
return null;
|
|
145
151
|
}
|
|
146
152
|
|
|
@@ -152,7 +158,7 @@ function getTableLineMetadata(lineNum, editorInfo, docManager) {
|
|
|
152
158
|
try {
|
|
153
159
|
const decodedString = atob(encodedData);
|
|
154
160
|
const metadata = JSON.parse(decodedString);
|
|
155
|
-
|
|
161
|
+
// log(`${funcName}: Reconstructed metadata from DOM for line ${lineNum}:`, metadata);
|
|
156
162
|
return metadata;
|
|
157
163
|
} catch (e) {
|
|
158
164
|
console.error(`${funcName}: Failed to decode/parse tbljson class on line ${lineNum}:`, e);
|
|
@@ -162,7 +168,7 @@ function getTableLineMetadata(lineNum, editorInfo, docManager) {
|
|
|
162
168
|
}
|
|
163
169
|
}
|
|
164
170
|
|
|
165
|
-
|
|
171
|
+
// log(`${funcName}: Could not find table metadata for line ${lineNum} in DOM.`);
|
|
166
172
|
return null;
|
|
167
173
|
} catch (e) {
|
|
168
174
|
console.error(`[ep_data_tables] ${funcName}: Error getting metadata for line ${lineNum}:`, e);
|
|
@@ -183,7 +189,7 @@ function getTableLineMetadata(lineNum, editorInfo, docManager) {
|
|
|
183
189
|
*/
|
|
184
190
|
function navigateToNextCell(currentLineNum, currentCellIndex, tableMetadata, shiftKey, editorInfo, docManager) {
|
|
185
191
|
const funcName = 'navigateToNextCell';
|
|
186
|
-
|
|
192
|
+
// log(`${funcName}: START - Current: Line=${currentLineNum}, Cell=${currentCellIndex}, Shift=${shiftKey}`);
|
|
187
193
|
|
|
188
194
|
try {
|
|
189
195
|
let targetRow = tableMetadata.row;
|
|
@@ -207,12 +213,12 @@ function navigateToNextCell(currentLineNum, currentCellIndex, tableMetadata, shi
|
|
|
207
213
|
}
|
|
208
214
|
}
|
|
209
215
|
|
|
210
|
-
|
|
216
|
+
// log(`${funcName}: Target coordinates - Row=${targetRow}, Col=${targetCol}`);
|
|
211
217
|
|
|
212
218
|
// Find the line number for the target row
|
|
213
219
|
const targetLineNum = findLineForTableRow(tableMetadata.tblId, targetRow, editorInfo, docManager);
|
|
214
220
|
if (targetLineNum === -1) {
|
|
215
|
-
|
|
221
|
+
// log(`${funcName}: Could not find line for target row ${targetRow}`);
|
|
216
222
|
return false;
|
|
217
223
|
}
|
|
218
224
|
|
|
@@ -236,25 +242,25 @@ function navigateToNextCell(currentLineNum, currentCellIndex, tableMetadata, shi
|
|
|
236
242
|
*/
|
|
237
243
|
function navigateToCellBelow(currentLineNum, currentCellIndex, tableMetadata, editorInfo, docManager) {
|
|
238
244
|
const funcName = 'navigateToCellBelow';
|
|
239
|
-
|
|
245
|
+
// log(`${funcName}: START - Current: Line=${currentLineNum}, Cell=${currentCellIndex}`);
|
|
240
246
|
|
|
241
247
|
try {
|
|
242
248
|
const targetRow = tableMetadata.row + 1;
|
|
243
249
|
const targetCol = currentCellIndex;
|
|
244
250
|
|
|
245
|
-
|
|
251
|
+
// log(`${funcName}: Target coordinates - Row=${targetRow}, Col=${targetCol}`);
|
|
246
252
|
|
|
247
253
|
// Find the line number for the target row
|
|
248
254
|
const targetLineNum = findLineForTableRow(tableMetadata.tblId, targetRow, editorInfo, docManager);
|
|
249
255
|
|
|
250
256
|
if (targetLineNum !== -1) {
|
|
251
257
|
// Found the row below, navigate to it.
|
|
252
|
-
|
|
258
|
+
// log(`${funcName}: Found line for target row ${targetRow}, navigating.`);
|
|
253
259
|
return navigateToCell(targetLineNum, targetCol, editorInfo, docManager);
|
|
254
260
|
} else {
|
|
255
261
|
// Could not find the row below, we must be on the last line.
|
|
256
262
|
// Create a new, empty line after the table.
|
|
257
|
-
|
|
263
|
+
// log(`${funcName}: Could not find next row. Creating new line after table.`);
|
|
258
264
|
const rep = editorInfo.ace_getRep();
|
|
259
265
|
const lineTextLength = rep.lines.atIndex(currentLineNum).text.length;
|
|
260
266
|
const endOfLinePos = [currentLineNum, lineTextLength];
|
|
@@ -272,7 +278,7 @@ function navigateToCellBelow(currentLineNum, currentCellIndex, tableMetadata, ed
|
|
|
272
278
|
// We've now exited the table, so clear the last-clicked state.
|
|
273
279
|
const editor = editorInfo.editor;
|
|
274
280
|
if (editor) editor.ep_data_tables_last_clicked = null;
|
|
275
|
-
|
|
281
|
+
// log(`${funcName}: Cleared last click info as we have exited the table.`);
|
|
276
282
|
|
|
277
283
|
return true; // We handled it.
|
|
278
284
|
}
|
|
@@ -292,12 +298,12 @@ function navigateToCellBelow(currentLineNum, currentCellIndex, tableMetadata, ed
|
|
|
292
298
|
*/
|
|
293
299
|
function findLineForTableRow(tblId, targetRow, editorInfo, docManager) {
|
|
294
300
|
const funcName = 'findLineForTableRow';
|
|
295
|
-
|
|
301
|
+
// log(`${funcName}: Searching for tblId=${tblId}, row=${targetRow}`);
|
|
296
302
|
|
|
297
303
|
try {
|
|
298
304
|
const rep = editorInfo.ace_getRep();
|
|
299
305
|
if (!rep || !rep.lines) {
|
|
300
|
-
|
|
306
|
+
// log(`${funcName}: Could not get rep or rep.lines`);
|
|
301
307
|
return -1;
|
|
302
308
|
}
|
|
303
309
|
|
|
@@ -315,7 +321,7 @@ function findLineForTableRow(tblId, targetRow, editorInfo, docManager) {
|
|
|
315
321
|
const domTblId = tableInDOM.getAttribute('data-tblId');
|
|
316
322
|
const domRow = tableInDOM.getAttribute('data-row');
|
|
317
323
|
if (domTblId === tblId && domRow !== null && parseInt(domRow, 10) === targetRow) {
|
|
318
|
-
|
|
324
|
+
// log(`${funcName}: Found target via DOM: line ${lineIndex}`);
|
|
319
325
|
return lineIndex;
|
|
320
326
|
}
|
|
321
327
|
}
|
|
@@ -325,7 +331,7 @@ function findLineForTableRow(tblId, targetRow, editorInfo, docManager) {
|
|
|
325
331
|
if (lineAttrString) {
|
|
326
332
|
const lineMetadata = JSON.parse(lineAttrString);
|
|
327
333
|
if (lineMetadata.tblId === tblId && lineMetadata.row === targetRow) {
|
|
328
|
-
|
|
334
|
+
// log(`${funcName}: Found target via attribute: line ${lineIndex}`);
|
|
329
335
|
return lineIndex;
|
|
330
336
|
}
|
|
331
337
|
}
|
|
@@ -334,7 +340,7 @@ function findLineForTableRow(tblId, targetRow, editorInfo, docManager) {
|
|
|
334
340
|
}
|
|
335
341
|
}
|
|
336
342
|
|
|
337
|
-
|
|
343
|
+
// log(`${funcName}: Target row not found`);
|
|
338
344
|
return -1;
|
|
339
345
|
} catch (e) {
|
|
340
346
|
console.error(`[ep_data_tables] ${funcName}: Error searching for line:`, e);
|
|
@@ -352,19 +358,19 @@ function findLineForTableRow(tblId, targetRow, editorInfo, docManager) {
|
|
|
352
358
|
*/
|
|
353
359
|
function navigateToCell(targetLineNum, targetCellIndex, editorInfo, docManager) {
|
|
354
360
|
const funcName = 'navigateToCell';
|
|
355
|
-
|
|
361
|
+
// log(`${funcName}: START - Target: Line=${targetLineNum}, Cell=${targetCellIndex}`);
|
|
356
362
|
let targetPos;
|
|
357
363
|
|
|
358
364
|
try {
|
|
359
365
|
const rep = editorInfo.ace_getRep();
|
|
360
366
|
if (!rep || !rep.lines) {
|
|
361
|
-
|
|
367
|
+
// log(`${funcName}: Could not get rep or rep.lines`);
|
|
362
368
|
return false;
|
|
363
369
|
}
|
|
364
370
|
|
|
365
371
|
const lineEntry = rep.lines.atIndex(targetLineNum);
|
|
366
372
|
if (!lineEntry) {
|
|
367
|
-
|
|
373
|
+
// log(`${funcName}: Could not get line entry for line ${targetLineNum}`);
|
|
368
374
|
return false;
|
|
369
375
|
}
|
|
370
376
|
|
|
@@ -372,7 +378,7 @@ function navigateToCell(targetLineNum, targetCellIndex, editorInfo, docManager)
|
|
|
372
378
|
const cells = lineText.split(DELIMITER);
|
|
373
379
|
|
|
374
380
|
if (targetCellIndex >= cells.length) {
|
|
375
|
-
|
|
381
|
+
// log(`${funcName}: Target cell ${targetCellIndex} doesn't exist (only ${cells.length} cells)`);
|
|
376
382
|
return false;
|
|
377
383
|
}
|
|
378
384
|
|
|
@@ -399,12 +405,12 @@ function navigateToCell(targetLineNum, targetCellIndex, editorInfo, docManager)
|
|
|
399
405
|
cellIndex: targetCellIndex,
|
|
400
406
|
relativePos: targetCellContent.length,
|
|
401
407
|
};
|
|
402
|
-
|
|
408
|
+
// log(`${funcName}: Pre-emptively updated stored click info:`, editor.ep_data_tables_last_clicked);
|
|
403
409
|
} else {
|
|
404
|
-
|
|
410
|
+
// log(`${funcName}: Could not get table metadata for target line ${targetLineNum}, cannot update click info.`);
|
|
405
411
|
}
|
|
406
412
|
} catch (e) {
|
|
407
|
-
|
|
413
|
+
// log(`${funcName}: Could not update stored click info before navigation:`, e.message);
|
|
408
414
|
}
|
|
409
415
|
|
|
410
416
|
// The previous attempts involving wrappers and poking the renderer have all
|
|
@@ -414,17 +420,17 @@ function navigateToCell(targetLineNum, targetCellIndex, editorInfo, docManager)
|
|
|
414
420
|
try {
|
|
415
421
|
// 1. Update the internal representation of the selection.
|
|
416
422
|
editorInfo.ace_performSelectionChange(targetPos, targetPos, false);
|
|
417
|
-
|
|
423
|
+
// log(`${funcName}: Updated internal selection to [${targetPos}]`);
|
|
418
424
|
|
|
419
425
|
// 2. Explicitly tell the editor to update the browser's visual selection
|
|
420
426
|
// to match the new internal representation. This is the correct way to
|
|
421
427
|
// make the caret appear in the new location without causing a race condition.
|
|
422
428
|
editorInfo.ace_updateBrowserSelectionFromRep();
|
|
423
|
-
|
|
429
|
+
// log(`${funcName}: Called updateBrowserSelectionFromRep to sync visual caret.`);
|
|
424
430
|
|
|
425
431
|
// 3. Ensure the editor has focus.
|
|
426
432
|
editorInfo.ace_focus();
|
|
427
|
-
|
|
433
|
+
// log(`${funcName}: Editor focused.`);
|
|
428
434
|
|
|
429
435
|
} catch(e) {
|
|
430
436
|
console.error(`[ep_data_tables] ${funcName}: Error during direct navigation update:`, e);
|
|
@@ -437,7 +443,7 @@ function navigateToCell(targetLineNum, targetCellIndex, editorInfo, docManager)
|
|
|
437
443
|
return false;
|
|
438
444
|
}
|
|
439
445
|
|
|
440
|
-
|
|
446
|
+
// log(`${funcName}: Navigation considered successful.`);
|
|
441
447
|
return true;
|
|
442
448
|
}
|
|
443
449
|
|
|
@@ -448,36 +454,36 @@ exports.collectContentPre = (hook, ctx) => {
|
|
|
448
454
|
const state = ctx.state;
|
|
449
455
|
const cc = ctx.cc; // ContentCollector instance
|
|
450
456
|
|
|
451
|
-
|
|
457
|
+
// log(`${funcName}: *** ENTRY POINT *** Hook: ${hook}, Node: ${node?.tagName}.${node?.className}`);
|
|
452
458
|
|
|
453
459
|
// ***** START Primary Path: Reconstruct from rendered table *****
|
|
454
460
|
if (node?.classList?.contains('ace-line')) {
|
|
455
461
|
const tableNode = node.querySelector('table.dataTable[data-tblId]');
|
|
456
462
|
if (tableNode) {
|
|
457
|
-
|
|
463
|
+
// log(`${funcName}: Found ace-line with rendered table. Attempting reconstruction from DOM.`);
|
|
458
464
|
|
|
459
465
|
const docManager = cc.documentAttributeManager;
|
|
460
466
|
const rep = cc.rep;
|
|
461
467
|
const lineNum = rep?.lines?.indexOfKey(node.id);
|
|
462
468
|
|
|
463
469
|
if (typeof lineNum === 'number' && lineNum >= 0 && docManager) {
|
|
464
|
-
|
|
470
|
+
// log(`${funcName}: Processing line ${lineNum} (NodeID: ${node.id}) for DOM reconstruction.`);
|
|
465
471
|
try {
|
|
466
472
|
const existingAttrString = docManager.getAttributeOnLine(lineNum, ATTR_TABLE_JSON);
|
|
467
|
-
|
|
473
|
+
// log(`${funcName}: Line ${lineNum} existing ${ATTR_TABLE_JSON} attribute: '${existingAttrString}'`);
|
|
468
474
|
|
|
469
475
|
if (existingAttrString) {
|
|
470
476
|
const existingMetadata = JSON.parse(existingAttrString);
|
|
471
477
|
if (existingMetadata && typeof existingMetadata.tblId !== 'undefined' &&
|
|
472
478
|
typeof existingMetadata.row !== 'undefined' && typeof existingMetadata.cols === 'number') {
|
|
473
|
-
|
|
479
|
+
// log(`${funcName}: Line ${lineNum} existing metadata is valid:`, existingMetadata);
|
|
474
480
|
|
|
475
481
|
const trNode = tableNode.querySelector('tbody > tr');
|
|
476
482
|
if (trNode) {
|
|
477
|
-
|
|
483
|
+
// log(`${funcName}: Line ${lineNum} found <tr> node for cell content extraction.`);
|
|
478
484
|
let cellHTMLSegments = Array.from(trNode.children).map((td, index) => {
|
|
479
485
|
let segmentHTML = td.innerHTML || '';
|
|
480
|
-
|
|
486
|
+
// log(`${funcName}: Line ${lineNum} TD[${index}] raw innerHTML (first 100): "${segmentHTML.substring(0,100)}"`);
|
|
481
487
|
|
|
482
488
|
const resizeHandleRegex = /<div class="ep-data_tables-resize-handle"[^>]*><\/div>/ig;
|
|
483
489
|
segmentHTML = segmentHTML.replace(resizeHandleRegex, '');
|
|
@@ -498,44 +504,44 @@ exports.collectContentPre = (hook, ctx) => {
|
|
|
498
504
|
const hidden = index === 0 ? '' :
|
|
499
505
|
/* keep the char in the DOM but make it visually disappear and non-editable */
|
|
500
506
|
`<span class="ep-data_tables-delim" contenteditable="false">${HIDDEN_DELIM}</span>`;
|
|
501
|
-
|
|
507
|
+
// log(`${funcName}: Line ${lineNum} TD[${index}] cleaned innerHTML (first 100): "${segmentHTML.substring(0,100)}"`);
|
|
502
508
|
return segmentHTML;
|
|
503
509
|
});
|
|
504
510
|
|
|
505
511
|
if (cellHTMLSegments.length !== existingMetadata.cols) {
|
|
506
|
-
|
|
512
|
+
// log(`${funcName}: WARNING Line ${lineNum}: Reconstructed cell count (${cellHTMLSegments.length}) does not match metadata cols (${existingMetadata.cols}). Padding/truncating.`);
|
|
507
513
|
while (cellHTMLSegments.length < existingMetadata.cols) cellHTMLSegments.push('');
|
|
508
514
|
if (cellHTMLSegments.length > existingMetadata.cols) cellHTMLSegments.length = existingMetadata.cols;
|
|
509
515
|
}
|
|
510
516
|
|
|
511
517
|
const canonicalLineText = cellHTMLSegments.join(DELIMITER);
|
|
512
518
|
state.line = canonicalLineText;
|
|
513
|
-
|
|
519
|
+
// log(`${funcName}: Line ${lineNum} successfully reconstructed ctx.state.line: "${canonicalLineText.substring(0, 200)}..."`);
|
|
514
520
|
|
|
515
521
|
state.lineAttributes = state.lineAttributes || [];
|
|
516
522
|
state.lineAttributes = state.lineAttributes.filter(attr => attr[0] !== ATTR_TABLE_JSON);
|
|
517
523
|
state.lineAttributes.push([ATTR_TABLE_JSON, existingAttrString]);
|
|
518
|
-
|
|
524
|
+
// log(`${funcName}: Line ${lineNum} ensured ${ATTR_TABLE_JSON} attribute is in state.lineAttributes.`);
|
|
519
525
|
|
|
520
|
-
|
|
526
|
+
// log(`${funcName}: Line ${lineNum} reconstruction complete. Returning undefined to prevent default DOM collection.`);
|
|
521
527
|
return undefined;
|
|
522
528
|
} else {
|
|
523
|
-
|
|
529
|
+
// log(`${funcName}: ERROR Line ${lineNum}: Could not find tbody > tr in rendered table for reconstruction.`);
|
|
524
530
|
}
|
|
525
531
|
} else {
|
|
526
|
-
|
|
532
|
+
// log(`${funcName}: ERROR Line ${lineNum}: Invalid or incomplete existing metadata from line attribute:`, existingMetadata);
|
|
527
533
|
}
|
|
528
534
|
} else {
|
|
529
|
-
|
|
535
|
+
// log(`${funcName}: WARNING Line ${lineNum}: No existing ${ATTR_TABLE_JSON} attribute found for reconstruction, despite table DOM presence. Table may be malformed or attribute lost.`);
|
|
530
536
|
const domTblId = tableNode.getAttribute('data-tblId');
|
|
531
537
|
const domRow = tableNode.getAttribute('data-row');
|
|
532
538
|
const trNode = tableNode.querySelector('tbody > tr');
|
|
533
539
|
if (domTblId && domRow !== null && trNode && trNode.children.length > 0) {
|
|
534
|
-
|
|
540
|
+
// log(`${funcName}: Line ${lineNum} FALLBACK: Attempting reconstruction using table DOM attributes as ${ATTR_TABLE_JSON} was missing.`);
|
|
535
541
|
const domCols = trNode.children.length;
|
|
536
542
|
const tempMetadata = {tblId: domTblId, row: parseInt(domRow, 10), cols: domCols};
|
|
537
543
|
const tempAttrString = JSON.stringify(tempMetadata);
|
|
538
|
-
|
|
544
|
+
// log(`${funcName}: Line ${lineNum} FALLBACK: Constructed temporary metadata: ${tempAttrString}`);
|
|
539
545
|
|
|
540
546
|
let cellHTMLSegments = Array.from(trNode.children).map((td, index) => {
|
|
541
547
|
let segmentHTML = td.innerHTML || '';
|
|
@@ -555,7 +561,7 @@ exports.collectContentPre = (hook, ctx) => {
|
|
|
555
561
|
});
|
|
556
562
|
|
|
557
563
|
if (cellHTMLSegments.length !== domCols) {
|
|
558
|
-
|
|
564
|
+
// log(`${funcName}: WARNING Line ${lineNum} (Fallback): Reconstructed cell count (${cellHTMLSegments.length}) does not match DOM cols (${domCols}).`);
|
|
559
565
|
while(cellHTMLSegments.length < domCols) cellHTMLSegments.push('');
|
|
560
566
|
if(cellHTMLSegments.length > domCols) cellHTMLSegments.length = domCols;
|
|
561
567
|
}
|
|
@@ -565,24 +571,24 @@ exports.collectContentPre = (hook, ctx) => {
|
|
|
565
571
|
state.lineAttributes = state.lineAttributes || [];
|
|
566
572
|
state.lineAttributes = state.lineAttributes.filter(attr => attr[0] !== ATTR_TABLE_JSON);
|
|
567
573
|
state.lineAttributes.push([ATTR_TABLE_JSON, tempAttrString]);
|
|
568
|
-
|
|
574
|
+
// log(`${funcName}: Line ${lineNum} FALLBACK: Successfully reconstructed line using DOM attributes. Returning undefined.`);
|
|
569
575
|
return undefined;
|
|
570
576
|
} else {
|
|
571
|
-
|
|
577
|
+
// log(`${funcName}: Line ${lineNum} FALLBACK: Could not reconstruct from DOM attributes due to missing info.`);
|
|
572
578
|
}
|
|
573
579
|
}
|
|
574
580
|
} catch (e) {
|
|
575
581
|
console.error(`[ep_data_tables] ${funcName}: Line ${lineNum} error during DOM reconstruction:`, e);
|
|
576
|
-
|
|
582
|
+
// log(`${funcName}: Line ${lineNum} Exception details:`, { message: e.message, stack: e.stack });
|
|
577
583
|
}
|
|
578
584
|
} else {
|
|
579
|
-
|
|
585
|
+
// log(`${funcName}: Could not get valid line number (${lineNum}), rep, or docManager for DOM reconstruction of ace-line.`);
|
|
580
586
|
}
|
|
581
587
|
} else {
|
|
582
|
-
|
|
588
|
+
// log(`${funcName}: Node is ace-line but no rendered table.dataTable[data-tblId] found. Allowing normal processing for: ${node?.className}`);
|
|
583
589
|
}
|
|
584
590
|
} else {
|
|
585
|
-
|
|
591
|
+
// log(`${funcName}: Node is not an ace-line (or node is null). Node: ${node?.tagName}.${node?.className}. Allowing normal processing.`);
|
|
586
592
|
}
|
|
587
593
|
// ***** END Primary Path *****
|
|
588
594
|
|
|
@@ -591,19 +597,19 @@ exports.collectContentPre = (hook, ctx) => {
|
|
|
591
597
|
const classes = ctx.cls ? ctx.cls.split(' ') : [];
|
|
592
598
|
let appliedAttribFromClass = false;
|
|
593
599
|
if (classes.length > 0) {
|
|
594
|
-
|
|
600
|
+
// log(`${funcName}: Secondary path - Checking classes on node ${node?.tagName}.${node?.className}: [${classes.join(', ')}]`);
|
|
595
601
|
for (const cls of classes) {
|
|
596
602
|
if (cls.startsWith('tbljson-')) {
|
|
597
|
-
|
|
603
|
+
// log(`${funcName}: Secondary path - Found tbljson class: ${cls} on node ${node?.tagName}.${node?.className}`);
|
|
598
604
|
const encodedMetadata = cls.substring(8);
|
|
599
605
|
try {
|
|
600
606
|
const decodedMetadata = dec(encodedMetadata);
|
|
601
607
|
if (decodedMetadata) {
|
|
602
608
|
cc.doAttrib(state, `${ATTR_TABLE_JSON}::${decodedMetadata}`);
|
|
603
609
|
appliedAttribFromClass = true;
|
|
604
|
-
|
|
610
|
+
// log(`${funcName}: Secondary path - Applied attribute to OP via cc.doAttrib for class ${cls.substring(0, 20)}... on ${node?.tagName}`);
|
|
605
611
|
} else {
|
|
606
|
-
|
|
612
|
+
// log(`${funcName}: Secondary path - ERROR - Decoded metadata is null or empty for class ${cls}`);
|
|
607
613
|
}
|
|
608
614
|
} catch (e) {
|
|
609
615
|
console.error(`[ep_data_tables] ${funcName}: Secondary path - Error processing tbljson class ${cls} on ${node?.tagName}:`, e);
|
|
@@ -612,49 +618,49 @@ exports.collectContentPre = (hook, ctx) => {
|
|
|
612
618
|
}
|
|
613
619
|
}
|
|
614
620
|
if (!appliedAttribFromClass && classes.some(c => c.startsWith('tbljson-'))) {
|
|
615
|
-
|
|
621
|
+
// log(`${funcName}: Secondary path - Found tbljson- class but failed to apply attribute.`);
|
|
616
622
|
} else if (!classes.some(c => c.startsWith('tbljson-'))) {
|
|
617
|
-
|
|
623
|
+
// log(`${funcName}: Secondary path - No tbljson- class found on this node.`);
|
|
618
624
|
}
|
|
619
625
|
} else {
|
|
620
|
-
|
|
626
|
+
// log(`${funcName}: Secondary path - Node ${node?.tagName}.${node?.className} has no ctx.cls or classes array is empty.`);
|
|
621
627
|
}
|
|
622
628
|
|
|
623
|
-
|
|
629
|
+
// log(`${funcName}: *** EXIT POINT *** For Node: ${node?.tagName}.${node?.className}. Applied from class: ${appliedAttribFromClass}`);
|
|
624
630
|
};
|
|
625
631
|
|
|
626
632
|
// ───────────── attribute → span‑class mapping (linestylefilter hook) ─────────
|
|
627
633
|
exports.aceAttribsToClasses = (hook, ctx) => {
|
|
628
634
|
const funcName = 'aceAttribsToClasses';
|
|
629
|
-
|
|
635
|
+
// log(`>>>> ${funcName}: Called with key: ${ctx.key}`); // log entry
|
|
630
636
|
if (ctx.key === ATTR_TABLE_JSON) {
|
|
631
|
-
|
|
637
|
+
// log(`${funcName}: Processing ATTR_TABLE_JSON.`);
|
|
632
638
|
// ctx.value is the raw JSON string from Etherpad's attribute pool
|
|
633
639
|
const rawJsonValue = ctx.value;
|
|
634
|
-
|
|
640
|
+
// log(`${funcName}: Received raw attribute value (ctx.value):`, rawJsonValue);
|
|
635
641
|
|
|
636
642
|
// Attempt to parse for logging purposes
|
|
637
643
|
let parsedMetadataForLog = '[JSON Parse Error]';
|
|
638
644
|
try {
|
|
639
645
|
parsedMetadataForLog = JSON.parse(rawJsonValue);
|
|
640
|
-
|
|
646
|
+
// log(`${funcName}: Value parsed for logging:`, parsedMetadataForLog);
|
|
641
647
|
} catch(e) {
|
|
642
|
-
|
|
648
|
+
// log(`${funcName}: Error parsing raw JSON value for logging:`, e);
|
|
643
649
|
// Continue anyway, enc() might still work if it's just a string
|
|
644
650
|
}
|
|
645
651
|
|
|
646
652
|
// Generate the class name by base64 encoding the raw JSON string.
|
|
647
653
|
// This ensures acePostWriteDomLineHTML receives the expected encoded format.
|
|
648
654
|
const className = `tbljson-${enc(rawJsonValue)}`;
|
|
649
|
-
|
|
655
|
+
// log(`${funcName}: Generated class name by encoding raw JSON: ${className}`);
|
|
650
656
|
return [className];
|
|
651
657
|
}
|
|
652
658
|
if (ctx.key === ATTR_CELL) {
|
|
653
659
|
// Keep this in case we want cell-specific styling later
|
|
654
|
-
|
|
660
|
+
//// log(`${funcName}: Processing ATTR_CELL: ${ctx.value}`); // Optional: Uncomment if needed
|
|
655
661
|
return [`tblCell-${ctx.value}`];
|
|
656
662
|
}
|
|
657
|
-
|
|
663
|
+
//// log(`${funcName}: Processing other key: ${ctx.key}`); // Optional: Uncomment if needed
|
|
658
664
|
return [];
|
|
659
665
|
};
|
|
660
666
|
|
|
@@ -673,15 +679,14 @@ function escapeHtml(text = '') {
|
|
|
673
679
|
};
|
|
674
680
|
return strText.replace(/[&<>"'']/g, function(m) { return map[m]; });
|
|
675
681
|
}
|
|
676
|
-
|
|
677
682
|
// NEW Helper function to build table HTML from pre-rendered delimited content with resize handles
|
|
678
683
|
function buildTableFromDelimitedHTML(metadata, innerHTMLSegments) {
|
|
679
684
|
const funcName = 'buildTableFromDelimitedHTML';
|
|
680
|
-
|
|
685
|
+
// log(`${funcName}: START`, { metadata, innerHTMLSegments });
|
|
681
686
|
|
|
682
687
|
if (!metadata || typeof metadata.tblId === 'undefined' || typeof metadata.row === 'undefined') {
|
|
683
688
|
console.error(`[ep_data_tables] ${funcName}: Invalid or missing metadata. Aborting.`);
|
|
684
|
-
|
|
689
|
+
// log(`${funcName}: END - Error`);
|
|
685
690
|
return '<table class="dataTable dataTable-error"><tbody><tr><td>Error: Missing table metadata</td></tr></tbody></table>'; // Return error table
|
|
686
691
|
}
|
|
687
692
|
|
|
@@ -736,6 +741,42 @@ function buildTableFromDelimitedHTML(metadata, innerHTMLSegments) {
|
|
|
736
741
|
? `<span class="${encodedTbljsonClass ? `${encodedTbljsonClass} ` : ''}tblCell-${index}">${modifiedSegment}${caretAnchorSpan}</span>`
|
|
737
742
|
: `${modifiedSegment}${caretAnchorSpan}`);
|
|
738
743
|
|
|
744
|
+
// Normalize: ensure first content span has correct tblCell-N and strip tblCell-* from subsequent spans
|
|
745
|
+
try {
|
|
746
|
+
const requiredCellClass = `tblCell-${index}`;
|
|
747
|
+
// Skip a leading delimiter span if present
|
|
748
|
+
const leadingDelimMatch = modifiedSegment.match(/^\s*<span[^>]*\bep-data_tables-delim\b[^>]*>[\s\S]*?<\/span>\s*/i);
|
|
749
|
+
const head = leadingDelimMatch ? leadingDelimMatch[0] : '';
|
|
750
|
+
const tail = leadingDelimMatch ? modifiedSegment.slice(head.length) : modifiedSegment;
|
|
751
|
+
|
|
752
|
+
const openSpanMatch = tail.match(/^\s*<span([^>]*)>/i);
|
|
753
|
+
if (!openSpanMatch) {
|
|
754
|
+
// No span at start: wrap with required cell class. Keep tbljson if available for stability.
|
|
755
|
+
const baseClasses = `${encodedTbljsonClass ? `${encodedTbljsonClass} ` : ''}${requiredCellClass}`;
|
|
756
|
+
modifiedSegment = `${head}<span class="${baseClasses}">${tail}</span>`;
|
|
757
|
+
} else {
|
|
758
|
+
const fullOpen = openSpanMatch[0];
|
|
759
|
+
const attrs = openSpanMatch[1] || '';
|
|
760
|
+
const classMatch = /\bclass\s*=\s*"([^"]*)"/i.exec(attrs);
|
|
761
|
+
let classList = classMatch ? classMatch[1].split(/\s+/).filter(Boolean) : [];
|
|
762
|
+
// Remove any stale tblCell-* from the first span's class list
|
|
763
|
+
classList = classList.filter(c => !/^tblCell-\d+$/.test(c));
|
|
764
|
+
// Ensure required cell class exists
|
|
765
|
+
classList.push(requiredCellClass);
|
|
766
|
+
const unique = Array.from(new Set(classList));
|
|
767
|
+
const newClassAttr = ` class="${unique.join(' ')}"`;
|
|
768
|
+
const attrsWithoutClass = classMatch ? attrs.replace(/\s*class\s*=\s*"[^"]*"/i, '') : attrs;
|
|
769
|
+
const rebuiltOpen = `<span${newClassAttr}${attrsWithoutClass}>`;
|
|
770
|
+
const afterOpen = tail.slice(fullOpen.length);
|
|
771
|
+
// Remove tblCell-* from any subsequent spans to avoid cascading mismatches (keep tbljson- as-is)
|
|
772
|
+
const cleanedTail = afterOpen.replace(/(<span[^>]*class=")([^"]*)(")/ig, (m, p1, classes, p3) => {
|
|
773
|
+
const filtered = classes.split(/\s+/).filter(c => c && !/^tblCell-\d+$/.test(c)).join(' ');
|
|
774
|
+
return p1 + filtered + p3;
|
|
775
|
+
});
|
|
776
|
+
modifiedSegment = head + rebuiltOpen + cleanedTail;
|
|
777
|
+
}
|
|
778
|
+
} catch (_) { /* ignore normalization errors */ }
|
|
779
|
+
|
|
739
780
|
// Width & other decorations remain unchanged
|
|
740
781
|
const widthPercent = columnWidths[index] || (100 / numCols);
|
|
741
782
|
const cellStyle = `${tdStyle} width: ${widthPercent}%;`;
|
|
@@ -747,17 +788,17 @@ function buildTableFromDelimitedHTML(metadata, innerHTMLSegments) {
|
|
|
747
788
|
const tdContent = `<td style="${cellStyle}" data-column="${index}" draggable="false">${modifiedSegment}${resizeHandle}</td>`;
|
|
748
789
|
return tdContent;
|
|
749
790
|
}).join('');
|
|
750
|
-
|
|
791
|
+
// log(`${funcName}: Joined all cellsHtml:`, cellsHtml);
|
|
751
792
|
|
|
752
793
|
// Add 'dataTable-first-row' class if it's the logical first row (row index 0)
|
|
753
794
|
const firstRowClass = metadata.row === 0 ? ' dataTable-first-row' : '';
|
|
754
|
-
|
|
795
|
+
// log(`${funcName}: First row class applied: '${firstRowClass}'`);
|
|
755
796
|
|
|
756
797
|
// Construct the final table HTML
|
|
757
798
|
// Rely on CSS for border-collapse, width etc. Add data attributes from metadata.
|
|
758
|
-
const tableHtml = `<table class="dataTable${firstRowClass}" data-tblId="${metadata.tblId}" data-row="${metadata.row}" style="width:100%; border-collapse: collapse; table-layout: fixed;" draggable="false"><tbody><tr>${cellsHtml}</tr></tbody></table>`;
|
|
759
|
-
|
|
760
|
-
|
|
799
|
+
const tableHtml = `<table class="dataTable${firstRowClass}" writingsuggestions="false" data-tblId="${metadata.tblId}" data-row="${metadata.row}" style="width:100%; border-collapse: collapse; table-layout: fixed;" draggable="false"><tbody><tr>${cellsHtml}</tr></tbody></table>`;
|
|
800
|
+
// log(`${funcName}: Generated final table HTML:`, tableHtml);
|
|
801
|
+
// log(`${funcName}: END - Success`);
|
|
761
802
|
return tableHtml;
|
|
762
803
|
}
|
|
763
804
|
|
|
@@ -770,28 +811,28 @@ exports.acePostWriteDomLineHTML = function (hook_name, args, cb) {
|
|
|
770
811
|
const logPrefix = '[ep_data_tables:acePostWriteDomLineHTML]'; // Consistent prefix
|
|
771
812
|
|
|
772
813
|
// *** STARTUP LOGGING ***
|
|
773
|
-
|
|
814
|
+
// log(`${logPrefix} ----- START ----- NodeID: ${nodeId} LineNum: ${lineNum}`);
|
|
774
815
|
if (!node || !nodeId) {
|
|
775
|
-
|
|
816
|
+
// log(`${logPrefix} ERROR - Received invalid node or node without ID. Aborting.`);
|
|
776
817
|
console.error(`[ep_data_tables] ${funcName}: Received invalid node or node without ID.`);
|
|
777
818
|
return cb();
|
|
778
819
|
}
|
|
779
820
|
|
|
780
821
|
// *** ENHANCED DEBUG: Log complete DOM state ***
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
822
|
+
// log(`${logPrefix} NodeID#${nodeId}: COMPLETE DOM STRUCTURE DEBUG:`);
|
|
823
|
+
// log(`${logPrefix} NodeID#${nodeId}: Node tagName: ${node.tagName}`);
|
|
824
|
+
// log(`${logPrefix} NodeID#${nodeId}: Node className: ${node.className}`);
|
|
825
|
+
// log(`${logPrefix} NodeID#${nodeId}: Node innerHTML length: ${node.innerHTML?.length || 0}`);
|
|
826
|
+
// log(`${logPrefix} NodeID#${nodeId}: Node innerHTML (first 500 chars): "${(node.innerHTML || '').substring(0, 500)}"`);
|
|
827
|
+
// log(`${logPrefix} NodeID#${nodeId}: Node children count: ${node.children?.length || 0}`);
|
|
787
828
|
|
|
788
829
|
// log all child elements and their classes
|
|
789
830
|
if (node.children) {
|
|
790
831
|
for (let i = 0; i < Math.min(node.children.length, 10); i++) {
|
|
791
832
|
const child = node.children[i];
|
|
792
|
-
|
|
833
|
+
// log(`${logPrefix} NodeID#${nodeId}: Child[${i}] tagName: ${child.tagName}, className: "${child.className}", innerHTML length: ${child.innerHTML?.length || 0}`);
|
|
793
834
|
if (child.className && child.className.includes('tbljson-')) {
|
|
794
|
-
|
|
835
|
+
// log(`${logPrefix} NodeID#${nodeId}: *** FOUND TBLJSON CLASS ON CHILD[${i}] ***`);
|
|
795
836
|
}
|
|
796
837
|
}
|
|
797
838
|
}
|
|
@@ -800,69 +841,69 @@ exports.acePostWriteDomLineHTML = function (hook_name, args, cb) {
|
|
|
800
841
|
let encodedJsonString = null;
|
|
801
842
|
|
|
802
843
|
// --- 1. Find and Parse Metadata Attribute ---
|
|
803
|
-
|
|
844
|
+
// log(`${logPrefix} NodeID#${nodeId}: Searching for tbljson-* class...`);
|
|
804
845
|
|
|
805
846
|
// ENHANCED Helper function to recursively search for tbljson class in all descendants
|
|
806
847
|
function findTbljsonClass(element, depth = 0, path = '') {
|
|
807
848
|
const indent = ' '.repeat(depth);
|
|
808
|
-
|
|
849
|
+
// log(`${logPrefix} NodeID#${nodeId}: ${indent}Searching element: ${element.tagName || 'unknown'}, path: ${path}`);
|
|
809
850
|
|
|
810
851
|
// Check the element itself
|
|
811
852
|
if (element.classList) {
|
|
812
|
-
|
|
853
|
+
// log(`${logPrefix} NodeID#${nodeId}: ${indent}Element has ${element.classList.length} classes: [${Array.from(element.classList).join(', ')}]`);
|
|
813
854
|
for (const cls of element.classList) {
|
|
814
855
|
if (cls.startsWith('tbljson-')) {
|
|
815
|
-
|
|
856
|
+
// log(`${logPrefix} NodeID#${nodeId}: ${indent}*** FOUND TBLJSON CLASS: ${cls.substring(8)} at depth ${depth}, path: ${path} ***`);
|
|
816
857
|
return cls.substring(8);
|
|
817
858
|
}
|
|
818
859
|
}
|
|
819
860
|
} else {
|
|
820
|
-
|
|
861
|
+
// log(`${logPrefix} NodeID#${nodeId}: ${indent}Element has no classList`);
|
|
821
862
|
}
|
|
822
863
|
|
|
823
864
|
// Recursively check all descendants
|
|
824
865
|
if (element.children) {
|
|
825
|
-
|
|
866
|
+
// log(`${logPrefix} NodeID#${nodeId}: ${indent}Element has ${element.children.length} children`);
|
|
826
867
|
for (let i = 0; i < element.children.length; i++) {
|
|
827
868
|
const child = element.children[i];
|
|
828
869
|
const childPath = `${path}>${child.tagName}[${i}]`;
|
|
829
870
|
const found = findTbljsonClass(child, depth + 1, childPath);
|
|
830
871
|
if (found) {
|
|
831
|
-
|
|
872
|
+
// log(`${logPrefix} NodeID#${nodeId}: ${indent}Returning found result from child: ${found}`);
|
|
832
873
|
return found;
|
|
833
874
|
}
|
|
834
875
|
}
|
|
835
876
|
} else {
|
|
836
|
-
|
|
877
|
+
// log(`${logPrefix} NodeID#${nodeId}: ${indent}Element has no children`);
|
|
837
878
|
}
|
|
838
879
|
|
|
839
|
-
|
|
880
|
+
// log(`${logPrefix} NodeID#${nodeId}: ${indent}No tbljson class found in this element or its children`);
|
|
840
881
|
return null;
|
|
841
882
|
}
|
|
842
883
|
|
|
843
884
|
// Search for tbljson class starting from the node
|
|
844
|
-
|
|
885
|
+
// log(`${logPrefix} NodeID#${nodeId}: Starting recursive search for tbljson class...`);
|
|
845
886
|
encodedJsonString = findTbljsonClass(node, 0, 'ROOT');
|
|
846
887
|
|
|
847
888
|
if (encodedJsonString) {
|
|
848
|
-
|
|
889
|
+
// log(`${logPrefix} NodeID#${nodeId}: *** SUCCESS: Found encoded tbljson class: ${encodedJsonString} ***`);
|
|
849
890
|
} else {
|
|
850
|
-
|
|
891
|
+
// log(`${logPrefix} NodeID#${nodeId}: *** NO TBLJSON CLASS FOUND ***`);
|
|
851
892
|
}
|
|
852
893
|
|
|
853
894
|
// If no attribute found, it's not a table line managed by us
|
|
854
895
|
if (!encodedJsonString) {
|
|
855
|
-
|
|
896
|
+
// log(`${logPrefix} NodeID#${nodeId}: No tbljson-* class found. Assuming not a table line. END.`);
|
|
856
897
|
|
|
857
898
|
// DEBUG: Add detailed logging to understand why tbljson class is missing
|
|
858
|
-
|
|
859
|
-
|
|
899
|
+
// log(`${logPrefix} NodeID#${nodeId}: DEBUG - Node tag: ${node.tagName}, Node classes:`, Array.from(node.classList || []));
|
|
900
|
+
// log(`${logPrefix} NodeID#${nodeId}: DEBUG - Node innerHTML (first 200 chars): "${(node.innerHTML || '').substring(0, 200)}"`);
|
|
860
901
|
|
|
861
902
|
// Check if there are any child elements with classes
|
|
862
903
|
if (node.children && node.children.length > 0) {
|
|
863
904
|
for (let i = 0; i < Math.min(node.children.length, 5); i++) {
|
|
864
905
|
const child = node.children[i];
|
|
865
|
-
|
|
906
|
+
// log(`${logPrefix} NodeID#${nodeId}: DEBUG - Child ${i} tag: ${child.tagName}, classes:`, Array.from(child.classList || []));
|
|
866
907
|
}
|
|
867
908
|
}
|
|
868
909
|
|
|
@@ -871,24 +912,24 @@ exports.acePostWriteDomLineHTML = function (hook_name, args, cb) {
|
|
|
871
912
|
if (existingTable) {
|
|
872
913
|
const existingTblId = existingTable.getAttribute('data-tblId');
|
|
873
914
|
const existingRow = existingTable.getAttribute('data-row');
|
|
874
|
-
|
|
915
|
+
// log(`${logPrefix} NodeID#${nodeId}: DEBUG - Found orphaned table! TblId: ${existingTblId}, Row: ${existingRow}`);
|
|
875
916
|
|
|
876
917
|
// This suggests the table exists but the tbljson class was lost
|
|
877
918
|
// Check if we're in a post-resize situation
|
|
878
919
|
if (existingTblId && existingRow !== null) {
|
|
879
|
-
|
|
920
|
+
// log(`${logPrefix} NodeID#${nodeId}: POTENTIAL ISSUE - Table exists but no tbljson class. This may be a post-resize issue.`);
|
|
880
921
|
|
|
881
922
|
// Try to look up what the metadata should be based on the table attributes
|
|
882
923
|
const tableCells = existingTable.querySelectorAll('td');
|
|
883
|
-
|
|
924
|
+
// log(`${logPrefix} NodeID#${nodeId}: Table has ${tableCells.length} cells`);
|
|
884
925
|
|
|
885
926
|
// log the current line's attribute state if we can get line number
|
|
886
927
|
if (lineNum !== undefined && args?.documentAttributeManager) {
|
|
887
928
|
try {
|
|
888
929
|
const currentLineAttr = args.documentAttributeManager.getAttributeOnLine(lineNum, ATTR_TABLE_JSON);
|
|
889
|
-
|
|
930
|
+
// log(`${logPrefix} NodeID#${nodeId}: Current line ${lineNum} tbljson attribute: ${currentLineAttr || 'NULL'}`);
|
|
890
931
|
} catch (e) {
|
|
891
|
-
|
|
932
|
+
// log(`${logPrefix} NodeID#${nodeId}: Error getting line attribute:`, e);
|
|
892
933
|
}
|
|
893
934
|
}
|
|
894
935
|
}
|
|
@@ -900,7 +941,7 @@ exports.acePostWriteDomLineHTML = function (hook_name, args, cb) {
|
|
|
900
941
|
// *** NEW CHECK: If table already rendered, skip regeneration ***
|
|
901
942
|
const existingTable = node.querySelector('table.dataTable[data-tblId]');
|
|
902
943
|
if (existingTable) {
|
|
903
|
-
|
|
944
|
+
// log(`${logPrefix} NodeID#${nodeId}: Table already exists in DOM. Skipping innerHTML replacement.`);
|
|
904
945
|
// Optionally, verify tblId matches metadata? For now, assume it's correct.
|
|
905
946
|
// const existingTblId = existingTable.getAttribute('data-tblId');
|
|
906
947
|
// try {
|
|
@@ -911,26 +952,26 @@ exports.acePostWriteDomLineHTML = function (hook_name, args, cb) {
|
|
|
911
952
|
return cb(); // Do nothing further
|
|
912
953
|
}
|
|
913
954
|
|
|
914
|
-
|
|
955
|
+
// log(`${logPrefix} NodeID#${nodeId}: Decoding and parsing metadata...`);
|
|
915
956
|
try {
|
|
916
957
|
const decoded = dec(encodedJsonString);
|
|
917
|
-
|
|
958
|
+
// log(`${logPrefix} NodeID#${nodeId}: Decoded string: ${decoded}`);
|
|
918
959
|
if (!decoded) throw new Error('Decoded string is null or empty.');
|
|
919
960
|
rowMetadata = JSON.parse(decoded);
|
|
920
|
-
|
|
961
|
+
// log(`${logPrefix} NodeID#${nodeId}: Parsed rowMetadata:`, rowMetadata);
|
|
921
962
|
|
|
922
963
|
// Validate essential metadata
|
|
923
964
|
if (!rowMetadata || typeof rowMetadata.tblId === 'undefined' || typeof rowMetadata.row === 'undefined' || typeof rowMetadata.cols !== 'number') {
|
|
924
965
|
throw new Error('Invalid or incomplete metadata (missing tblId, row, or cols).');
|
|
925
966
|
}
|
|
926
|
-
|
|
967
|
+
// log(`${logPrefix} NodeID#${nodeId}: Metadata validated successfully.`);
|
|
927
968
|
|
|
928
969
|
} catch(e) {
|
|
929
|
-
|
|
970
|
+
// log(`${logPrefix} NodeID#${nodeId}: FATAL ERROR - Failed to decode/parse/validate tbljson metadata. Rendering cannot proceed.`, e);
|
|
930
971
|
console.error(`[ep_data_tables] ${funcName} NodeID#${nodeId}: Failed to decode/parse/validate tbljson.`, encodedJsonString, e);
|
|
931
972
|
// Optionally render an error state in the node?
|
|
932
973
|
node.innerHTML = '<div style="color:red; border: 1px solid red; padding: 5px;">[ep_data_tables] Error: Invalid table metadata attribute found.</div>';
|
|
933
|
-
|
|
974
|
+
// log(`${logPrefix} NodeID#${nodeId}: Rendered error message in node. END.`);
|
|
934
975
|
return cb();
|
|
935
976
|
}
|
|
936
977
|
// --- End Metadata Parsing ---
|
|
@@ -944,131 +985,86 @@ exports.acePostWriteDomLineHTML = function (hook_name, args, cb) {
|
|
|
944
985
|
// After an edit, aceKeyEvent updates atext, and node.innerHTML reflects that new "EditedCell1|Cell2" string.
|
|
945
986
|
// When styling is applied, it will include spans like: <span class="author-xxx bold">Cell1</span>|<span class="author-yyy italic">Cell2</span>
|
|
946
987
|
const delimitedTextFromLine = node.innerHTML;
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
988
|
+
// log(`${logPrefix} NodeID#${nodeId}: Using node.innerHTML for delimited text to preserve styling.`);
|
|
989
|
+
// log(`${logPrefix} NodeID#${nodeId}: Raw innerHTML length: ${delimitedTextFromLine?.length || 0}`);
|
|
990
|
+
// log(`${logPrefix} NodeID#${nodeId}: Raw innerHTML (first 1000 chars): "${(delimitedTextFromLine || '').substring(0, 1000)}"`);
|
|
950
991
|
|
|
951
992
|
// *** ENHANCED DEBUG: Analyze delimiter presence ***
|
|
952
993
|
const delimiterCount = (delimitedTextFromLine || '').split(DELIMITER).length - 1;
|
|
953
|
-
|
|
954
|
-
|
|
994
|
+
// log(`${logPrefix} NodeID#${nodeId}: Delimiter '${DELIMITER}' count in innerHTML: ${delimiterCount}`);
|
|
995
|
+
// log(`${logPrefix} NodeID#${nodeId}: Expected delimiters for ${rowMetadata.cols} columns: ${rowMetadata.cols - 1}`);
|
|
955
996
|
|
|
956
997
|
// log all delimiter positions
|
|
957
998
|
let pos = -1;
|
|
958
999
|
const delimiterPositions = [];
|
|
959
1000
|
while ((pos = delimitedTextFromLine.indexOf(DELIMITER, pos + 1)) !== -1) {
|
|
960
1001
|
delimiterPositions.push(pos);
|
|
961
|
-
|
|
1002
|
+
// log(`${logPrefix} NodeID#${nodeId}: Delimiter found at position ${pos}, context: "${delimitedTextFromLine.substring(Math.max(0, pos - 20), pos + 21)}"`);
|
|
962
1003
|
}
|
|
963
|
-
|
|
1004
|
+
// log(`${logPrefix} NodeID#${nodeId}: All delimiter positions: [${delimiterPositions.join(', ')}]`);
|
|
964
1005
|
|
|
965
1006
|
// The DELIMITER const is defined at the top of this file.
|
|
966
1007
|
// NEW: Remove all hidden-delimiter <span> wrappers **before** we split so
|
|
967
1008
|
// the embedded delimiter character they carry doesn't inflate or shrink
|
|
968
1009
|
// the segment count.
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
const delimiterEntityHexRE = /␟/ig; // hex entity for U+241F
|
|
972
|
-
const delimiterEntityDecRE = /␟/g; // decimal entity for U+241F
|
|
973
|
-
const appleConvertedSpaceRE = /<span class="Apple-converted-space">[\s\u00A0]*<\/span>/ig;
|
|
974
|
-
const zeroWidthCharsRE = /[\u200B\u200C\u200D\uFEFF]/g;
|
|
975
|
-
|
|
976
|
-
const hexMatches = ((delimitedTextFromLine || '').match(delimiterEntityHexRE) || []).length;
|
|
977
|
-
const decMatches = ((delimitedTextFromLine || '').match(delimiterEntityDecRE) || []).length;
|
|
978
|
-
const appleSpaceMatches = ((delimitedTextFromLine || '').match(appleConvertedSpaceRE) || []).length;
|
|
979
|
-
|
|
1010
|
+
// Preserve the delimiter character when stripping its wrapper span so splitting works
|
|
1011
|
+
const spanDelimRegex = /<span class="ep-data_tables-delim"[^>]*>[\s\S]*?<\/span>/ig;
|
|
980
1012
|
const sanitizedHTMLForSplit = (delimitedTextFromLine || '')
|
|
981
|
-
.replace(spanDelimRegex,
|
|
1013
|
+
.replace(spanDelimRegex, DELIMITER)
|
|
982
1014
|
// strip caret anchors from raw line html before split
|
|
983
1015
|
.replace(/<span class="ep-data_tables-caret-anchor"[^>]*><\/span>/ig, '')
|
|
984
|
-
|
|
985
|
-
.replace(
|
|
986
|
-
.replace(
|
|
987
|
-
// Safari sometimes injects Apple-converted-space wrappers; collapse to a normal space
|
|
988
|
-
.replace(appleConvertedSpaceRE, ' ')
|
|
989
|
-
// Guard against invisible characters that can disturb splitting
|
|
990
|
-
.replace(zeroWidthCharsRE, '');
|
|
991
|
-
|
|
992
|
-
if ((hexMatches + decMatches + appleSpaceMatches) > 0) {
|
|
993
|
-
console.warn(`[ep_data_tables] ${funcName} NodeID#${nodeId}: Normalized Safari entities/spans before splitting: hex=${hexMatches}, dec=${decMatches}, appleSpaces=${appleSpaceMatches}`);
|
|
994
|
-
}
|
|
995
|
-
|
|
1016
|
+
.replace(/\r?\n/g, ' ')
|
|
1017
|
+
.replace(/\u00A0/gu, ' ')
|
|
1018
|
+
.replace(/<br\s*\/?>/gi, ' ');
|
|
996
1019
|
const htmlSegments = sanitizedHTMLForSplit.split(DELIMITER);
|
|
997
1020
|
|
|
998
|
-
|
|
999
|
-
|
|
1021
|
+
// log(`${logPrefix} NodeID#${nodeId}: *** SEGMENT ANALYSIS ***`);
|
|
1022
|
+
// log(`${logPrefix} NodeID#${nodeId}: Split resulted in ${htmlSegments.length} segments`);
|
|
1000
1023
|
for (let i = 0; i < htmlSegments.length; i++) {
|
|
1001
1024
|
const segment = htmlSegments[i] || '';
|
|
1002
|
-
|
|
1003
|
-
|
|
1025
|
+
// log(`${logPrefix} NodeID#${nodeId}: Segment[${i}] length: ${segment.length}`);
|
|
1026
|
+
// log(`${logPrefix} NodeID#${nodeId}: Segment[${i}] content (first 200 chars): "${segment.substring(0, 200)}"`);
|
|
1004
1027
|
if (segment.length > 200) {
|
|
1005
|
-
|
|
1028
|
+
// log(`${logPrefix} NodeID#${nodeId}: Segment[${i}] content (chars 200-400): "${segment.substring(200, 400)}"`);
|
|
1006
1029
|
}
|
|
1007
1030
|
if (segment.length > 400) {
|
|
1008
|
-
|
|
1031
|
+
// log(`${logPrefix} NodeID#${nodeId}: Segment[${i}] content (chars 400-600): "${segment.substring(400, 600)}"`);
|
|
1009
1032
|
}
|
|
1010
1033
|
// Check if segment contains image-related content
|
|
1011
1034
|
if (segment.includes('image:') || segment.includes('image-placeholder') || segment.includes('currently-selected')) {
|
|
1012
|
-
|
|
1035
|
+
// log(`${logPrefix} NodeID#${nodeId}: *** SEGMENT[${i}] CONTAINS IMAGE CONTENT ***`);
|
|
1013
1036
|
}
|
|
1037
|
+
// Diagnostics: surface suspicious class patterns that can precede breakage
|
|
1038
|
+
try {
|
|
1039
|
+
const tblCellMatches = segment.match(/\btblCell-(\d+)\b/g) || [];
|
|
1040
|
+
const tbljsonMatches = segment.match(/\btbljson-[A-Za-z0-9_-]+\b/g) || [];
|
|
1041
|
+
const uniqueCells = Array.from(new Set(tblCellMatches));
|
|
1042
|
+
if (uniqueCells.length > 1) {
|
|
1043
|
+
console.warn('[ep_data_tables][diag] segment contains multiple tblCell-* markers', { segIndex: i, uniqueCells });
|
|
1044
|
+
}
|
|
1045
|
+
// intentionally ignore duplicate tbljson-* occurrences – not root cause
|
|
1046
|
+
} catch (_) {}
|
|
1014
1047
|
}
|
|
1015
1048
|
|
|
1016
|
-
|
|
1049
|
+
// log(`${logPrefix} NodeID#${nodeId}: Parsed HTML segments (${htmlSegments.length}):`, htmlSegments.map(s => (s || '').substring(0,50) + (s && s.length > 50 ? '...' : '')));
|
|
1017
1050
|
|
|
1018
1051
|
// --- Enhanced Validation with Automatic Structure Reconstruction ---
|
|
1019
1052
|
let finalHtmlSegments = htmlSegments;
|
|
1020
1053
|
|
|
1021
1054
|
if (htmlSegments.length !== rowMetadata.cols) {
|
|
1022
|
-
|
|
1023
|
-
|
|
1055
|
+
// log(`${logPrefix} NodeID#${nodeId}: *** MISMATCH DETECTED *** - Attempting reconstruction.`);
|
|
1056
|
+
console.warn('[ep_data_tables][diag] Segment/column mismatch', { nodeId, lineNum, segs: htmlSegments.length, cols: rowMetadata.cols, tblId: rowMetadata.tblId, row: rowMetadata.row });
|
|
1057
|
+
|
|
1024
1058
|
// Check if this is an image selection issue
|
|
1025
1059
|
const hasImageSelected = delimitedTextFromLine.includes('currently-selected');
|
|
1026
1060
|
const hasImageContent = delimitedTextFromLine.includes('image:');
|
|
1027
1061
|
if (hasImageSelected) {
|
|
1028
|
-
|
|
1062
|
+
// log(`${logPrefix} NodeID#${nodeId}: *** POTENTIAL CAUSE: Image selection state may be affecting segment parsing ***`);
|
|
1029
1063
|
}
|
|
1030
1064
|
|
|
1031
|
-
// First attempt:
|
|
1065
|
+
// First attempt (DISABLED): class-based reconstruction caused content loss in real pads.
|
|
1066
|
+
// We now rely on sanitized delimiter splitting and safe padding/truncation instead.
|
|
1032
1067
|
let usedClassReconstruction = false;
|
|
1033
|
-
try {
|
|
1034
|
-
const cols = Math.max(0, Number(rowMetadata.cols) || 0);
|
|
1035
|
-
const grouped = Array.from({ length: cols }, () => '');
|
|
1036
|
-
const candidates = Array.from(node.querySelectorAll('[class*="tblCell-"]'));
|
|
1037
|
-
|
|
1038
|
-
const classNum = (el) => {
|
|
1039
|
-
if (!el || !el.classList) return -1;
|
|
1040
|
-
for (const cls of el.classList) {
|
|
1041
|
-
const m = /^tblCell-(\d+)$/.exec(cls);
|
|
1042
|
-
if (m) return parseInt(m[1], 10);
|
|
1043
|
-
}
|
|
1044
|
-
return -1;
|
|
1045
|
-
};
|
|
1046
|
-
const hasAncestorWithSameCell = (el, n) => {
|
|
1047
|
-
let p = el?.parentElement;
|
|
1048
|
-
while (p) {
|
|
1049
|
-
if (p.classList && p.classList.contains(`tblCell-${n}`)) return true;
|
|
1050
|
-
p = p.parentElement;
|
|
1051
|
-
}
|
|
1052
|
-
return false;
|
|
1053
|
-
};
|
|
1054
|
-
|
|
1055
|
-
for (const el of candidates) {
|
|
1056
|
-
const n = classNum(el);
|
|
1057
|
-
if (n >= 0 && n < cols) {
|
|
1058
|
-
if (!hasAncestorWithSameCell(el, n)) {
|
|
1059
|
-
grouped[n] += el.outerHTML || '';
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
const usable = grouped.some(s => s && s.trim() !== '');
|
|
1064
|
-
if (usable) {
|
|
1065
|
-
finalHtmlSegments = grouped.map(s => (s && s.trim() !== '') ? s : ' ');
|
|
1066
|
-
usedClassReconstruction = true;
|
|
1067
|
-
console.warn(`[ep_data_tables] ${funcName} NodeID#${nodeId}: Reconstructed ${finalHtmlSegments.length} segments from tblCell-N classes.`);
|
|
1068
|
-
}
|
|
1069
|
-
} catch (e) {
|
|
1070
|
-
console.debug(`[ep_data_tables] ${funcName} NodeID#${nodeId}: Class-based reconstruction error; falling back.`, e);
|
|
1071
|
-
}
|
|
1072
1068
|
|
|
1073
1069
|
// Fallback: reconstruct from string segments
|
|
1074
1070
|
if (!usedClassReconstruction) {
|
|
@@ -1097,14 +1093,14 @@ exports.acePostWriteDomLineHTML = function (hook_name, args, cb) {
|
|
|
1097
1093
|
console.warn(`[ep_data_tables] ${funcName} NodeID#${nodeId}: Could not reconstruct to expected ${rowMetadata.cols} segments. Got ${finalHtmlSegments.length}.`);
|
|
1098
1094
|
}
|
|
1099
1095
|
} else {
|
|
1100
|
-
|
|
1096
|
+
// log(`${logPrefix} NodeID#${nodeId}: Segment count matches metadata cols (${rowMetadata.cols}). Using original segments.`);
|
|
1101
1097
|
}
|
|
1102
1098
|
|
|
1103
1099
|
// --- 3. Build and Render Table ---
|
|
1104
|
-
|
|
1100
|
+
// log(`${logPrefix} NodeID#${nodeId}: Calling buildTableFromDelimitedHTML...`);
|
|
1105
1101
|
try {
|
|
1106
1102
|
const newTableHTML = buildTableFromDelimitedHTML(rowMetadata, finalHtmlSegments);
|
|
1107
|
-
|
|
1103
|
+
// log(`${logPrefix} NodeID#${nodeId}: Received new table HTML from helper. Replacing content.`);
|
|
1108
1104
|
|
|
1109
1105
|
// The old local findTbljsonElement is removed from here. We use the global one now.
|
|
1110
1106
|
const tbljsonElement = findTbljsonElement(node);
|
|
@@ -1117,24 +1113,24 @@ exports.acePostWriteDomLineHTML = function (hook_name, args, cb) {
|
|
|
1117
1113
|
const blockElements = ['center', 'div', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'pre', 'right', 'left', 'ul', 'ol', 'li', 'code'];
|
|
1118
1114
|
|
|
1119
1115
|
if (blockElements.includes(parentTag)) {
|
|
1120
|
-
|
|
1116
|
+
// log(`${logPrefix} NodeID#${nodeId}: Preserving block element ${parentTag} and replacing its content with table.`);
|
|
1121
1117
|
tbljsonElement.parentElement.innerHTML = newTableHTML;
|
|
1122
1118
|
} else {
|
|
1123
|
-
|
|
1119
|
+
// log(`${logPrefix} NodeID#${nodeId}: Parent element ${parentTag} is not a block element, replacing entire node content.`);
|
|
1124
1120
|
node.innerHTML = newTableHTML;
|
|
1125
1121
|
}
|
|
1126
1122
|
} else {
|
|
1127
1123
|
// Replace the node's content entirely with the generated table
|
|
1128
|
-
|
|
1124
|
+
// log(`${logPrefix} NodeID#${nodeId}: No nested block element found, replacing entire node content.`);
|
|
1129
1125
|
node.innerHTML = newTableHTML;
|
|
1130
1126
|
}
|
|
1131
1127
|
|
|
1132
|
-
|
|
1128
|
+
// log(`${logPrefix} NodeID#${nodeId}: Successfully replaced content with new table structure.`);
|
|
1133
1129
|
} catch (renderError) {
|
|
1134
|
-
|
|
1130
|
+
// log(`${logPrefix} NodeID#${nodeId}: ERROR during table building or rendering.`, renderError);
|
|
1135
1131
|
console.error(`[ep_data_tables] ${funcName} NodeID#${nodeId}: Error building/rendering table.`, renderError);
|
|
1136
1132
|
node.innerHTML = '<div style="color:red; border: 1px solid red; padding: 5px;">[ep_data_tables] Error: Failed to render table structure.</div>';
|
|
1137
|
-
|
|
1133
|
+
// log(`${logPrefix} NodeID#${nodeId}: Rendered build/render error message in node. END.`);
|
|
1138
1134
|
return cb();
|
|
1139
1135
|
}
|
|
1140
1136
|
// --- End Table Building ---
|
|
@@ -1142,7 +1138,7 @@ exports.acePostWriteDomLineHTML = function (hook_name, args, cb) {
|
|
|
1142
1138
|
// *** REMOVED CACHING LOGIC ***
|
|
1143
1139
|
// The old logic based on tableRowNodes cache is completely removed.
|
|
1144
1140
|
|
|
1145
|
-
|
|
1141
|
+
// log(`${logPrefix}: ----- END ----- NodeID: ${nodeId}`);
|
|
1146
1142
|
return cb();
|
|
1147
1143
|
};
|
|
1148
1144
|
|
|
@@ -1157,7 +1153,6 @@ function _getLineNumberOfElement(element) {
|
|
|
1157
1153
|
}
|
|
1158
1154
|
return count;
|
|
1159
1155
|
}
|
|
1160
|
-
|
|
1161
1156
|
// ───────────────────── Handle Key Events ─────────────────────
|
|
1162
1157
|
exports.aceKeyEvent = (h, ctx) => {
|
|
1163
1158
|
const funcName = 'aceKeyEvent';
|
|
@@ -1168,34 +1163,34 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1168
1163
|
|
|
1169
1164
|
const startLogTime = Date.now();
|
|
1170
1165
|
const logPrefix = '[ep_data_tables:aceKeyEvent]';
|
|
1171
|
-
|
|
1166
|
+
// log(`${logPrefix} START Key='${evt?.key}' Code=${evt?.keyCode} Type=${evt?.type} Modifiers={ctrl:${evt?.ctrlKey},alt:${evt?.altKey},meta:${evt?.metaKey},shift:${evt?.shiftKey}}`, { selStart: rep?.selStart, selEnd: rep?.selEnd });
|
|
1172
1167
|
|
|
1173
1168
|
if (!rep || !rep.selStart || !editorInfo || !evt || !docManager) {
|
|
1174
|
-
|
|
1169
|
+
// log(`${logPrefix} Skipping - Missing critical context.`);
|
|
1175
1170
|
return false;
|
|
1176
1171
|
}
|
|
1177
1172
|
|
|
1178
1173
|
// Get caret info from event context - may be stale
|
|
1179
1174
|
const reportedLineNum = rep.selStart[0];
|
|
1180
1175
|
const reportedCol = rep.selStart[1];
|
|
1181
|
-
|
|
1176
|
+
// log(`${logPrefix} Reported caret from rep: Line=${reportedLineNum}, Col=${reportedCol}`);
|
|
1182
1177
|
|
|
1183
1178
|
// --- Get Table Metadata for the reported line ---
|
|
1184
1179
|
let tableMetadata = null;
|
|
1185
1180
|
let lineAttrString = null; // Store for potential use later
|
|
1186
1181
|
try {
|
|
1187
1182
|
// Add debugging to see what's happening with attribute retrieval
|
|
1188
|
-
|
|
1183
|
+
// log(`${logPrefix} DEBUG: Attempting to get ${ATTR_TABLE_JSON} attribute from line ${reportedLineNum}`);
|
|
1189
1184
|
lineAttrString = docManager.getAttributeOnLine(reportedLineNum, ATTR_TABLE_JSON);
|
|
1190
|
-
|
|
1185
|
+
// log(`${logPrefix} DEBUG: getAttributeOnLine returned: ${lineAttrString ? `"${lineAttrString}"` : 'null/undefined'}`);
|
|
1191
1186
|
|
|
1192
1187
|
// Also check if there are any attributes on this line at all
|
|
1193
1188
|
if (typeof docManager.getAttributesOnLine === 'function') {
|
|
1194
1189
|
try {
|
|
1195
1190
|
const allAttribs = docManager.getAttributesOnLine(reportedLineNum);
|
|
1196
|
-
|
|
1191
|
+
// log(`${logPrefix} DEBUG: All attributes on line ${reportedLineNum}:`, allAttribs);
|
|
1197
1192
|
} catch(e) {
|
|
1198
|
-
|
|
1193
|
+
// log(`${logPrefix} DEBUG: Error getting all attributes:`, e);
|
|
1199
1194
|
}
|
|
1200
1195
|
}
|
|
1201
1196
|
|
|
@@ -1209,34 +1204,34 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1209
1204
|
if (tableInDOM) {
|
|
1210
1205
|
const domTblId = tableInDOM.getAttribute('data-tblId');
|
|
1211
1206
|
const domRow = tableInDOM.getAttribute('data-row');
|
|
1212
|
-
|
|
1207
|
+
// log(`${logPrefix} DEBUG: Found table in DOM without attribute! TblId=${domTblId}, Row=${domRow}`);
|
|
1213
1208
|
// Try to reconstruct the metadata from DOM
|
|
1214
1209
|
const domCells = tableInDOM.querySelectorAll('td');
|
|
1215
1210
|
if (domTblId && domRow !== null && domCells.length > 0) {
|
|
1216
|
-
|
|
1211
|
+
// log(`${logPrefix} DEBUG: Attempting to reconstruct metadata from DOM...`);
|
|
1217
1212
|
const reconstructedMetadata = {
|
|
1218
1213
|
tblId: domTblId,
|
|
1219
1214
|
row: parseInt(domRow, 10),
|
|
1220
1215
|
cols: domCells.length
|
|
1221
1216
|
};
|
|
1222
1217
|
lineAttrString = JSON.stringify(reconstructedMetadata);
|
|
1223
|
-
|
|
1218
|
+
// log(`${logPrefix} DEBUG: Reconstructed metadata: ${lineAttrString}`);
|
|
1224
1219
|
}
|
|
1225
1220
|
}
|
|
1226
1221
|
}
|
|
1227
1222
|
} catch(e) {
|
|
1228
|
-
|
|
1223
|
+
// log(`${logPrefix} DEBUG: Error checking DOM for table:`, e);
|
|
1229
1224
|
}
|
|
1230
1225
|
}
|
|
1231
1226
|
|
|
1232
1227
|
if (lineAttrString) {
|
|
1233
1228
|
tableMetadata = JSON.parse(lineAttrString);
|
|
1234
1229
|
if (!tableMetadata || typeof tableMetadata.cols !== 'number') {
|
|
1235
|
-
|
|
1230
|
+
// log(`${logPrefix} Line ${reportedLineNum} has attribute, but metadata invalid/missing cols.`);
|
|
1236
1231
|
tableMetadata = null; // Ensure it's null if invalid
|
|
1237
1232
|
}
|
|
1238
1233
|
} else {
|
|
1239
|
-
|
|
1234
|
+
// log(`${logPrefix} DEBUG: No ${ATTR_TABLE_JSON} attribute found on line ${reportedLineNum}`);
|
|
1240
1235
|
// Not a table line based on reported caret line
|
|
1241
1236
|
}
|
|
1242
1237
|
} catch(e) {
|
|
@@ -1247,7 +1242,7 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1247
1242
|
// Get last known good state
|
|
1248
1243
|
const editor = editorInfo.editor; // Get editor instance
|
|
1249
1244
|
const lastClick = editor?.ep_data_tables_last_clicked; // Read shared state
|
|
1250
|
-
|
|
1245
|
+
// log(`${logPrefix} Reading stored click/caret info:`, lastClick);
|
|
1251
1246
|
|
|
1252
1247
|
// --- Determine the TRUE target line, cell, and caret position ---
|
|
1253
1248
|
let currentLineNum = -1;
|
|
@@ -1262,22 +1257,22 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1262
1257
|
|
|
1263
1258
|
// ** Scenario 1: Try to trust lastClick info **
|
|
1264
1259
|
if (lastClick) {
|
|
1265
|
-
|
|
1260
|
+
// log(`${logPrefix} Attempting to validate stored click info for Line=${lastClick.lineNum}...`);
|
|
1266
1261
|
let storedLineAttrString = null;
|
|
1267
1262
|
let storedLineMetadata = null;
|
|
1268
1263
|
try {
|
|
1269
|
-
|
|
1264
|
+
// log(`${logPrefix} DEBUG: Getting ${ATTR_TABLE_JSON} attribute from stored line ${lastClick.lineNum}`);
|
|
1270
1265
|
storedLineAttrString = docManager.getAttributeOnLine(lastClick.lineNum, ATTR_TABLE_JSON);
|
|
1271
|
-
|
|
1266
|
+
// log(`${logPrefix} DEBUG: Stored line attribute result: ${storedLineAttrString ? `"${storedLineAttrString}"` : 'null/undefined'}`);
|
|
1272
1267
|
|
|
1273
1268
|
if (storedLineAttrString) {
|
|
1274
1269
|
storedLineMetadata = JSON.parse(storedLineAttrString);
|
|
1275
|
-
|
|
1270
|
+
// log(`${logPrefix} DEBUG: Parsed stored metadata:`, storedLineMetadata);
|
|
1276
1271
|
}
|
|
1277
1272
|
|
|
1278
1273
|
// Check if metadata is valid and tblId matches
|
|
1279
1274
|
if (storedLineMetadata && typeof storedLineMetadata.cols === 'number' && storedLineMetadata.tblId === lastClick.tblId) {
|
|
1280
|
-
|
|
1275
|
+
// log(`${logPrefix} Stored click info VALIDATED (Metadata OK and tblId matches). Trusting stored state.`);
|
|
1281
1276
|
trustedLastClick = true;
|
|
1282
1277
|
currentLineNum = lastClick.lineNum;
|
|
1283
1278
|
targetCellIndex = lastClick.cellIndex;
|
|
@@ -1286,10 +1281,10 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1286
1281
|
|
|
1287
1282
|
lineText = rep.lines.atIndex(currentLineNum)?.text || '';
|
|
1288
1283
|
cellTexts = lineText.split(DELIMITER);
|
|
1289
|
-
|
|
1284
|
+
// log(`${logPrefix} Using Line=${currentLineNum}, CellIndex=${targetCellIndex}. Text: "${lineText}"`);
|
|
1290
1285
|
|
|
1291
1286
|
if (cellTexts.length !== metadataForTargetLine.cols) {
|
|
1292
|
-
|
|
1287
|
+
// log(`${logPrefix} WARNING: Stored cell count mismatch for trusted line ${currentLineNum}.`);
|
|
1293
1288
|
}
|
|
1294
1289
|
|
|
1295
1290
|
cellStartCol = 0;
|
|
@@ -1297,20 +1292,20 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1297
1292
|
cellStartCol += (cellTexts[i]?.length ?? 0) + DELIMITER.length;
|
|
1298
1293
|
}
|
|
1299
1294
|
precedingCellsOffset = cellStartCol;
|
|
1300
|
-
|
|
1295
|
+
// log(`${logPrefix} Calculated cellStartCol=${cellStartCol} from trusted cellIndex=${targetCellIndex}.`);
|
|
1301
1296
|
|
|
1302
1297
|
if (typeof lastClick.relativePos === 'number' && lastClick.relativePos >= 0) {
|
|
1303
1298
|
const currentCellTextLength = cellTexts[targetCellIndex]?.length ?? 0;
|
|
1304
1299
|
relativeCaretPos = Math.max(0, Math.min(lastClick.relativePos, currentCellTextLength));
|
|
1305
|
-
|
|
1300
|
+
// log(`${logPrefix} Using and validated stored relative position: ${relativeCaretPos}.`);
|
|
1306
1301
|
} else {
|
|
1307
1302
|
relativeCaretPos = reportedCol - cellStartCol; // Use reportedCol for initial calc if relative is missing
|
|
1308
1303
|
const currentCellTextLength = cellTexts[targetCellIndex]?.length ?? 0;
|
|
1309
1304
|
relativeCaretPos = Math.max(0, Math.min(relativeCaretPos, currentCellTextLength));
|
|
1310
|
-
|
|
1305
|
+
// log(`${logPrefix} Stored relativePos missing, calculated from reportedCol (${reportedCol}): ${relativeCaretPos}`);
|
|
1311
1306
|
}
|
|
1312
1307
|
} else {
|
|
1313
|
-
|
|
1308
|
+
// log(`${logPrefix} Stored click info INVALID (Metadata missing/invalid or tblId mismatch). Clearing stored state.`);
|
|
1314
1309
|
if (editor) editor.ep_data_tables_last_clicked = null;
|
|
1315
1310
|
}
|
|
1316
1311
|
} catch (e) {
|
|
@@ -1321,7 +1316,7 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1321
1316
|
|
|
1322
1317
|
// ** Scenario 2: Fallback - Use reported line/col ONLY if stored info wasn't trusted **
|
|
1323
1318
|
if (!trustedLastClick) {
|
|
1324
|
-
|
|
1319
|
+
// log(`${logPrefix} Fallback: Using reported caret position Line=${reportedLineNum}, Col=${reportedCol}.`);
|
|
1325
1320
|
// Fetch metadata for the reported line again, in case it wasn't fetched or was invalid earlier
|
|
1326
1321
|
try {
|
|
1327
1322
|
lineAttrString = docManager.getAttributeOnLine(reportedLineNum, ATTR_TABLE_JSON);
|
|
@@ -1338,11 +1333,11 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1338
1333
|
if (tableInDOM) {
|
|
1339
1334
|
const domTblId = tableInDOM.getAttribute('data-tblId');
|
|
1340
1335
|
const domRow = tableInDOM.getAttribute('data-row');
|
|
1341
|
-
|
|
1336
|
+
// log(`${logPrefix} Fallback: Found table in DOM without attribute! TblId=${domTblId}, Row=${domRow}`);
|
|
1342
1337
|
// Try to reconstruct the metadata from DOM
|
|
1343
1338
|
const domCells = tableInDOM.querySelectorAll('td');
|
|
1344
1339
|
if (domTblId && domRow !== null && domCells.length > 0) {
|
|
1345
|
-
|
|
1340
|
+
// log(`${logPrefix} Fallback: Attempting to reconstruct metadata from DOM...`);
|
|
1346
1341
|
const reconstructedMetadata = {
|
|
1347
1342
|
tblId: domTblId,
|
|
1348
1343
|
row: parseInt(domRow, 10),
|
|
@@ -1350,31 +1345,31 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1350
1345
|
};
|
|
1351
1346
|
lineAttrString = JSON.stringify(reconstructedMetadata);
|
|
1352
1347
|
tableMetadata = reconstructedMetadata;
|
|
1353
|
-
|
|
1348
|
+
// log(`${logPrefix} Fallback: Reconstructed metadata: ${lineAttrString}`);
|
|
1354
1349
|
}
|
|
1355
1350
|
}
|
|
1356
1351
|
}
|
|
1357
1352
|
} catch(e) {
|
|
1358
|
-
|
|
1353
|
+
// log(`${logPrefix} Fallback: Error checking DOM for table:`, e);
|
|
1359
1354
|
}
|
|
1360
1355
|
}
|
|
1361
1356
|
} catch(e) { tableMetadata = null; } // Ignore errors here, handled below
|
|
1362
1357
|
|
|
1363
1358
|
if (!tableMetadata) {
|
|
1364
|
-
|
|
1359
|
+
// log(`${logPrefix} Fallback: Reported line ${reportedLineNum} is not a valid table line. Allowing default.`);
|
|
1365
1360
|
return false;
|
|
1366
1361
|
}
|
|
1367
1362
|
|
|
1368
1363
|
currentLineNum = reportedLineNum;
|
|
1369
1364
|
metadataForTargetLine = tableMetadata;
|
|
1370
|
-
|
|
1365
|
+
// log(`${logPrefix} Fallback: Processing based on reported line ${currentLineNum}.`);
|
|
1371
1366
|
|
|
1372
1367
|
lineText = rep.lines.atIndex(currentLineNum)?.text || '';
|
|
1373
1368
|
cellTexts = lineText.split(DELIMITER);
|
|
1374
|
-
|
|
1369
|
+
// log(`${logPrefix} Fallback: Fetched text for reported line ${currentLineNum}: "${lineText}"`);
|
|
1375
1370
|
|
|
1376
1371
|
if (cellTexts.length !== metadataForTargetLine.cols) {
|
|
1377
|
-
|
|
1372
|
+
// log(`${logPrefix} WARNING (Fallback): Cell count mismatch for reported line ${currentLineNum}.`);
|
|
1378
1373
|
}
|
|
1379
1374
|
|
|
1380
1375
|
// Calculate target cell based on reportedCol
|
|
@@ -1388,7 +1383,7 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1388
1383
|
relativeCaretPos = reportedCol - currentOffset;
|
|
1389
1384
|
cellStartCol = currentOffset;
|
|
1390
1385
|
precedingCellsOffset = cellStartCol;
|
|
1391
|
-
|
|
1386
|
+
// log(`${logPrefix} --> (Fallback Calc) Found target cell ${foundIndex}. RelativePos: ${relativeCaretPos}.`);
|
|
1392
1387
|
break;
|
|
1393
1388
|
}
|
|
1394
1389
|
if (i < cellTexts.length - 1 && reportedCol === cellEndCol + DELIMITER.length) {
|
|
@@ -1396,7 +1391,7 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1396
1391
|
relativeCaretPos = 0;
|
|
1397
1392
|
cellStartCol = currentOffset + cellLength + DELIMITER.length;
|
|
1398
1393
|
precedingCellsOffset = cellStartCol;
|
|
1399
|
-
|
|
1394
|
+
// log(`${logPrefix} --> (Fallback Calc) Caret at delimiter AFTER cell ${i}. Treating as start of cell ${foundIndex}.`);
|
|
1400
1395
|
break;
|
|
1401
1396
|
}
|
|
1402
1397
|
currentOffset += cellLength + DELIMITER.length;
|
|
@@ -1409,9 +1404,9 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1409
1404
|
for (let i = 0; i < foundIndex; i++) { cellStartCol += (cellTexts[i]?.length ?? 0) + DELIMITER.length; }
|
|
1410
1405
|
precedingCellsOffset = cellStartCol;
|
|
1411
1406
|
relativeCaretPos = cellTexts[foundIndex]?.length ?? 0;
|
|
1412
|
-
|
|
1407
|
+
// log(`${logPrefix} --> (Fallback Calc) Caret detected at END of last cell (${foundIndex}).`);
|
|
1413
1408
|
} else {
|
|
1414
|
-
|
|
1409
|
+
// log(`${logPrefix} (Fallback Calc) FAILED to determine target cell for caret col ${reportedCol}. Allowing default handling.`);
|
|
1415
1410
|
return false;
|
|
1416
1411
|
}
|
|
1417
1412
|
}
|
|
@@ -1420,12 +1415,12 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1420
1415
|
|
|
1421
1416
|
// --- Final Validation ---
|
|
1422
1417
|
if (currentLineNum < 0 || targetCellIndex < 0 || !metadataForTargetLine || targetCellIndex >= metadataForTargetLine.cols) {
|
|
1423
|
-
|
|
1418
|
+
// log(`${logPrefix} FAILED final validation: Line=${currentLineNum}, Cell=${targetCellIndex}, Metadata=${!!metadataForTargetLine}. Allowing default.`);
|
|
1424
1419
|
if (editor) editor.ep_data_tables_last_clicked = null;
|
|
1425
1420
|
return false;
|
|
1426
1421
|
}
|
|
1427
1422
|
|
|
1428
|
-
|
|
1423
|
+
// log(`${logPrefix} --> Final Target: Line=${currentLineNum}, CellIndex=${targetCellIndex}, RelativePos=${relativeCaretPos}`);
|
|
1429
1424
|
// --- End Cell/Position Determination ---
|
|
1430
1425
|
|
|
1431
1426
|
// --- START NEW: Handle Highlight Deletion/Replacement ---
|
|
@@ -1434,11 +1429,11 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1434
1429
|
const hasSelection = selStartActual[0] !== selEndActual[0] || selStartActual[1] !== selEndActual[1];
|
|
1435
1430
|
|
|
1436
1431
|
if (hasSelection) {
|
|
1437
|
-
|
|
1438
|
-
|
|
1432
|
+
// log(`${logPrefix} [selection] Active selection detected. Start:[${selStartActual[0]},${selStartActual[1]}], End:[${selEndActual[0]},${selEndActual[1]}]`);
|
|
1433
|
+
// log(`${logPrefix} [caretTrace] [selection] Initial rep.selStart: Line=${rep.selStart[0]}, Col=${rep.selStart[1]}`);
|
|
1439
1434
|
|
|
1440
1435
|
if (selStartActual[0] !== currentLineNum || selEndActual[0] !== currentLineNum) {
|
|
1441
|
-
|
|
1436
|
+
// log(`${logPrefix} [selection] Selection spans multiple lines (${selStartActual[0]}-${selEndActual[0]}) or is not on the current focused table line (${currentLineNum}). Preventing default action.`);
|
|
1442
1437
|
evt.preventDefault();
|
|
1443
1438
|
return true;
|
|
1444
1439
|
}
|
|
@@ -1486,7 +1481,7 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1486
1481
|
selectionEndColInLine = cellContentEndColInLine;
|
|
1487
1482
|
}
|
|
1488
1483
|
|
|
1489
|
-
|
|
1484
|
+
// log(`${logPrefix} [selection] Cell context for selection: targetCellIndex=${targetCellIndex}, cellStartColInLine=${cellContentStartColInLine}, cellEndColInLine=${cellContentEndColInLine}, currentCellFullText='${currentCellFullText}'`);
|
|
1490
1485
|
|
|
1491
1486
|
const isSelectionEntirelyWithinCell =
|
|
1492
1487
|
selectionStartColInLine >= cellContentStartColInLine &&
|
|
@@ -1518,10 +1513,10 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1518
1513
|
|
|
1519
1514
|
|
|
1520
1515
|
if (isSelectionEntirelyWithinCell && (isCurrentKeyDelete || isCurrentKeyBackspace || isCurrentKeyTyping)) {
|
|
1521
|
-
|
|
1516
|
+
// log(`${logPrefix} [selection] Handling key='${evt.key}' (Type: ${evt.type}) for valid intra-cell selection.`);
|
|
1522
1517
|
|
|
1523
1518
|
if (evt.type !== 'keydown') {
|
|
1524
|
-
|
|
1519
|
+
// log(`${logPrefix} [selection] Ignoring non-keydown event type ('${evt.type}') for selection handling. Allowing default.`);
|
|
1525
1520
|
return false;
|
|
1526
1521
|
}
|
|
1527
1522
|
evt.preventDefault();
|
|
@@ -1531,20 +1526,20 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1531
1526
|
let replacementText = '';
|
|
1532
1527
|
let newAbsoluteCaretCol = selectionStartColInLine;
|
|
1533
1528
|
const repBeforeEdit = editorInfo.ace_getRep(); // Get rep before edit for attribute helper
|
|
1534
|
-
|
|
1529
|
+
// log(`${logPrefix} [caretTrace] [selection] rep.selStart before ace_performDocumentReplaceRange: Line=${repBeforeEdit.selStart[0]}, Col=${repBeforeEdit.selStart[1]}`);
|
|
1535
1530
|
|
|
1536
1531
|
if (isCurrentKeyTyping) {
|
|
1537
1532
|
replacementText = evt.key;
|
|
1538
1533
|
newAbsoluteCaretCol = selectionStartColInLine + replacementText.length;
|
|
1539
|
-
|
|
1534
|
+
// log(`${logPrefix} [selection] -> Replacing selected range [[${rangeStart[0]},${rangeStart[1]}],[${rangeEnd[0]},${rangeEnd[1]}]] with text '${replacementText}'`);
|
|
1540
1535
|
} else { // Delete or Backspace
|
|
1541
|
-
|
|
1536
|
+
// log(`${logPrefix} [selection] -> Deleting selected range [[${rangeStart[0]},${rangeStart[1]}],[${rangeEnd[0]},${rangeEnd[1]}]]`);
|
|
1542
1537
|
// If whole cell is being wiped, keep a single space so cell isn't empty
|
|
1543
1538
|
const isWholeCell = selectionStartColInLine <= cellContentStartColInLine && selectionEndColInLine >= cellContentEndColInLine;
|
|
1544
1539
|
if (isWholeCell) {
|
|
1545
1540
|
replacementText = ' ';
|
|
1546
1541
|
newAbsoluteCaretCol = selectionStartColInLine + 1;
|
|
1547
|
-
|
|
1542
|
+
// log(`${logPrefix} [selection] Whole cell cleared – inserting single space to preserve caret/author span.`);
|
|
1548
1543
|
}
|
|
1549
1544
|
}
|
|
1550
1545
|
|
|
@@ -1563,44 +1558,44 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1563
1558
|
);
|
|
1564
1559
|
}
|
|
1565
1560
|
const repAfterReplace = editorInfo.ace_getRep();
|
|
1566
|
-
|
|
1561
|
+
// log(`${logPrefix} [caretTrace] [selection] rep.selStart after ace_performDocumentReplaceRange: Line=${repAfterReplace.selStart[0]}, Col=${repAfterReplace.selStart[1]}`);
|
|
1567
1562
|
|
|
1568
1563
|
|
|
1569
|
-
|
|
1564
|
+
// log(`${logPrefix} [selection] -> Re-applying tbljson line attribute...`);
|
|
1570
1565
|
const applyHelper = editorInfo.ep_data_tables_applyMeta;
|
|
1571
1566
|
if (applyHelper && typeof applyHelper === 'function' && repBeforeEdit) {
|
|
1572
1567
|
const attrStringToApply = (trustedLastClick || reportedLineNum === currentLineNum) ? lineAttrString : null;
|
|
1573
1568
|
applyHelper(currentLineNum, metadataForTargetLine.tblId, metadataForTargetLine.row, metadataForTargetLine.cols, repBeforeEdit, editorInfo, attrStringToApply, docManager);
|
|
1574
|
-
|
|
1569
|
+
// log(`${logPrefix} [selection] -> tbljson line attribute re-applied (using rep before edit).`);
|
|
1575
1570
|
} else {
|
|
1576
1571
|
console.error(`${logPrefix} [selection] -> FAILED to re-apply tbljson attribute (helper or repBeforeEdit missing).`);
|
|
1577
1572
|
const currentRepFallback = editorInfo.ace_getRep();
|
|
1578
1573
|
if (applyHelper && typeof applyHelper === 'function' && currentRepFallback) {
|
|
1579
|
-
|
|
1574
|
+
// log(`${logPrefix} [selection] -> Retrying attribute application with current rep...`);
|
|
1580
1575
|
applyHelper(currentLineNum, metadataForTargetLine.tblId, metadataForTargetLine.row, metadataForTargetLine.cols, currentRepFallback, editorInfo, null, docManager);
|
|
1581
|
-
|
|
1576
|
+
// log(`${logPrefix} [selection] -> tbljson line attribute re-applied (using current rep fallback).`);
|
|
1582
1577
|
} else {
|
|
1583
1578
|
console.error(`${logPrefix} [selection] -> FAILED to re-apply tbljson attribute even with fallback rep.`);
|
|
1584
1579
|
}
|
|
1585
1580
|
}
|
|
1586
1581
|
|
|
1587
|
-
|
|
1588
|
-
|
|
1582
|
+
// log(`${logPrefix} [selection] -> Setting selection/caret to: [${currentLineNum}, ${newAbsoluteCaretCol}]`);
|
|
1583
|
+
// log(`${logPrefix} [caretTrace] [selection] rep.selStart before ace_performSelectionChange: Line=${editorInfo.ace_getRep().selStart[0]}, Col=${editorInfo.ace_getRep().selStart[1]}`);
|
|
1589
1584
|
editorInfo.ace_performSelectionChange([currentLineNum, newAbsoluteCaretCol], [currentLineNum, newAbsoluteCaretCol], false);
|
|
1590
1585
|
const repAfterSelectionChange = editorInfo.ace_getRep();
|
|
1591
|
-
|
|
1586
|
+
// log(`${logPrefix} [caretTrace] [selection] rep.selStart after ace_performSelectionChange: Line=${repAfterSelectionChange.selStart[0]}, Col=${repAfterSelectionChange.selStart[1]}`);
|
|
1592
1587
|
|
|
1593
1588
|
// Add sync hint AFTER setting selection
|
|
1594
1589
|
editorInfo.ace_fastIncorp(1);
|
|
1595
1590
|
const repAfterFastIncorp = editorInfo.ace_getRep();
|
|
1596
|
-
|
|
1597
|
-
|
|
1591
|
+
// log(`${logPrefix} [caretTrace] [selection] rep.selStart after ace_fastIncorp: Line=${repAfterFastIncorp.selStart[0]}, Col=${repAfterFastIncorp.selStart[1]}`);
|
|
1592
|
+
// log(`${logPrefix} [selection] -> Requested sync hint (fastIncorp 1).`);
|
|
1598
1593
|
|
|
1599
1594
|
// --- Re-assert selection ---
|
|
1600
|
-
|
|
1595
|
+
// log(`${logPrefix} [caretTrace] [selection] Attempting to re-assert selection post-fastIncorp to [${currentLineNum}, ${newAbsoluteCaretCol}]`);
|
|
1601
1596
|
editorInfo.ace_performSelectionChange([currentLineNum, newAbsoluteCaretCol], [currentLineNum, newAbsoluteCaretCol], false);
|
|
1602
1597
|
const repAfterReassert = editorInfo.ace_getRep();
|
|
1603
|
-
|
|
1598
|
+
// log(`${logPrefix} [caretTrace] [selection] rep.selStart after re-asserting selection: Line=${repAfterReassert.selStart[0]}, Col=${repAfterReassert.selStart[1]}`);
|
|
1604
1599
|
|
|
1605
1600
|
const newRelativePos = newAbsoluteCaretCol - cellStartCol;
|
|
1606
1601
|
if (editor) {
|
|
@@ -1610,15 +1605,15 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1610
1605
|
cellIndex: targetCellIndex,
|
|
1611
1606
|
relativePos: newRelativePos < 0 ? 0 : newRelativePos
|
|
1612
1607
|
};
|
|
1613
|
-
|
|
1608
|
+
// log(`${logPrefix} [selection] -> Updated stored click/caret info:`, editor.ep_data_tables_last_clicked);
|
|
1614
1609
|
} else {
|
|
1615
|
-
|
|
1610
|
+
// log(`${logPrefix} [selection] -> Editor instance not found, cannot update ep_data_tables_last_clicked.`);
|
|
1616
1611
|
}
|
|
1617
1612
|
|
|
1618
|
-
|
|
1613
|
+
// log(`${logPrefix} END [selection] (Handled highlight modification) Key='${evt.key}' Type='${evt.type}'. Duration: ${Date.now() - startLogTime}ms`);
|
|
1619
1614
|
return true;
|
|
1620
1615
|
} catch (error) {
|
|
1621
|
-
|
|
1616
|
+
// log(`${logPrefix} [selection] ERROR during highlight modification:`, error);
|
|
1622
1617
|
console.error('[ep_data_tables] Error processing highlight modification:', error);
|
|
1623
1618
|
return true; // Still return true as we prevented default.
|
|
1624
1619
|
}
|
|
@@ -1629,12 +1624,12 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1629
1624
|
// --- Check for Ctrl+X (Cut) key combination ---
|
|
1630
1625
|
const isCutKey = (evt.ctrlKey || evt.metaKey) && (evt.key === 'x' || evt.key === 'X' || evt.keyCode === 88);
|
|
1631
1626
|
if (isCutKey && hasSelection) {
|
|
1632
|
-
|
|
1627
|
+
// log(`${logPrefix} Ctrl+X (Cut) detected with selection. Letting cut event handler manage this.`);
|
|
1633
1628
|
// Let the cut event handler handle this - we don't need to preventDefault here
|
|
1634
1629
|
// as the cut event will handle the operation and prevent default
|
|
1635
1630
|
return false; // Allow the cut event to be triggered
|
|
1636
1631
|
} else if (isCutKey && !hasSelection) {
|
|
1637
|
-
|
|
1632
|
+
// log(`${logPrefix} Ctrl+X (Cut) detected but no selection. Allowing default.`);
|
|
1638
1633
|
return false; // Allow default - nothing to cut
|
|
1639
1634
|
}
|
|
1640
1635
|
|
|
@@ -1645,7 +1640,7 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1645
1640
|
const isNavigationKey = [33, 34, 35, 36, 37, 38, 39, 40].includes(evt.keyCode);
|
|
1646
1641
|
const isTabKey = evt.key === 'Tab';
|
|
1647
1642
|
const isEnterKey = evt.key === 'Enter';
|
|
1648
|
-
|
|
1643
|
+
// log(`${logPrefix} Key classification: Typing=${isTypingKey}, Backspace=${isBackspaceKey}, Delete=${isDeleteKey}, Nav=${isNavigationKey}, Tab=${isTabKey}, Enter=${isEnterKey}, Cut=${isCutKey}`);
|
|
1649
1644
|
|
|
1650
1645
|
/*
|
|
1651
1646
|
* Prevent caret placement *after* the invisible caret-anchor.
|
|
@@ -1660,7 +1655,7 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1660
1655
|
if (evt.type === 'keydown' && !evt.ctrlKey && !evt.metaKey && !evt.altKey) {
|
|
1661
1656
|
// Right-arrow – if at the very end of a cell, move to the next cell.
|
|
1662
1657
|
if (evt.keyCode === 39 && relativeCaretPos >= currentCellTextLengthEarly && targetCellIndex < metadataForTargetLine.cols - 1) {
|
|
1663
|
-
|
|
1658
|
+
// log(`${logPrefix} ArrowRight at cell boundary – navigating to next cell to avoid anchor zone.`);
|
|
1664
1659
|
evt.preventDefault();
|
|
1665
1660
|
navigateToNextCell(currentLineNum, targetCellIndex, metadataForTargetLine, false, editorInfo, docManager);
|
|
1666
1661
|
return true;
|
|
@@ -1668,7 +1663,7 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1668
1663
|
|
|
1669
1664
|
// Left-arrow – if at the very start of a cell, move to the previous cell.
|
|
1670
1665
|
if (evt.keyCode === 37 && relativeCaretPos === 0 && targetCellIndex > 0) {
|
|
1671
|
-
|
|
1666
|
+
// log(`${logPrefix} ArrowLeft at cell boundary – navigating to previous cell to avoid anchor zone.`);
|
|
1672
1667
|
evt.preventDefault();
|
|
1673
1668
|
navigateToNextCell(currentLineNum, targetCellIndex, metadataForTargetLine, true, editorInfo, docManager);
|
|
1674
1669
|
return true;
|
|
@@ -1679,45 +1674,45 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1679
1674
|
|
|
1680
1675
|
// 1. Allow non-Tab navigation keys immediately
|
|
1681
1676
|
if (isNavigationKey && !isTabKey) {
|
|
1682
|
-
|
|
1677
|
+
// log(`${logPrefix} Allowing navigation key: ${evt.key}. Clearing click state.`);
|
|
1683
1678
|
if (editor) editor.ep_data_tables_last_clicked = null; // Clear state on navigation
|
|
1684
1679
|
return false;
|
|
1685
1680
|
}
|
|
1686
1681
|
|
|
1687
1682
|
// 2. Handle Tab - Navigate to next cell (only on keydown to avoid double navigation)
|
|
1688
1683
|
if (isTabKey) {
|
|
1689
|
-
|
|
1684
|
+
// log(`${logPrefix} Tab key pressed. Event type: ${evt.type}`);
|
|
1690
1685
|
evt.preventDefault();
|
|
1691
1686
|
|
|
1692
1687
|
// Only process keydown events for navigation to avoid double navigation
|
|
1693
1688
|
if (evt.type !== 'keydown') {
|
|
1694
|
-
|
|
1689
|
+
// log(`${logPrefix} Ignoring Tab ${evt.type} event to prevent double navigation.`);
|
|
1695
1690
|
return true;
|
|
1696
1691
|
}
|
|
1697
1692
|
|
|
1698
|
-
|
|
1693
|
+
// log(`${logPrefix} Processing Tab keydown - implementing cell navigation.`);
|
|
1699
1694
|
const success = navigateToNextCell(currentLineNum, targetCellIndex, metadataForTargetLine, evt.shiftKey, editorInfo, docManager);
|
|
1700
1695
|
if (!success) {
|
|
1701
|
-
|
|
1696
|
+
// log(`${logPrefix} Tab navigation failed, cell navigation not possible.`);
|
|
1702
1697
|
}
|
|
1703
1698
|
return true;
|
|
1704
1699
|
}
|
|
1705
1700
|
|
|
1706
1701
|
// 3. Handle Enter - Navigate to cell below (only on keydown to avoid double navigation)
|
|
1707
1702
|
if (isEnterKey) {
|
|
1708
|
-
|
|
1703
|
+
// log(`${logPrefix} Enter key pressed. Event type: ${evt.type}`);
|
|
1709
1704
|
evt.preventDefault();
|
|
1710
1705
|
|
|
1711
1706
|
// Only process keydown events for navigation to avoid double navigation
|
|
1712
1707
|
if (evt.type !== 'keydown') {
|
|
1713
|
-
|
|
1708
|
+
// log(`${logPrefix} Ignoring Enter ${evt.type} event to prevent double navigation.`);
|
|
1714
1709
|
return true;
|
|
1715
1710
|
}
|
|
1716
1711
|
|
|
1717
|
-
|
|
1712
|
+
// log(`${logPrefix} Processing Enter keydown - implementing cell navigation.`);
|
|
1718
1713
|
const success = navigateToCellBelow(currentLineNum, targetCellIndex, metadataForTargetLine, editorInfo, docManager);
|
|
1719
1714
|
if (!success) {
|
|
1720
|
-
|
|
1715
|
+
// log(`${logPrefix} Enter navigation failed, cell navigation not possible.`);
|
|
1721
1716
|
}
|
|
1722
1717
|
return true;
|
|
1723
1718
|
}
|
|
@@ -1726,25 +1721,25 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1726
1721
|
const currentCellTextLength = cellTexts[targetCellIndex]?.length ?? 0;
|
|
1727
1722
|
// Backspace at the very beginning of cell > 0
|
|
1728
1723
|
if (isBackspaceKey && relativeCaretPos === 0 && targetCellIndex > 0) {
|
|
1729
|
-
|
|
1724
|
+
// log(`${logPrefix} Intercepted Backspace at start of cell ${targetCellIndex}. Preventing default.`);
|
|
1730
1725
|
evt.preventDefault();
|
|
1731
1726
|
return true;
|
|
1732
1727
|
}
|
|
1733
1728
|
// NEW: Backspace at very beginning of first cell – would merge with previous line
|
|
1734
1729
|
if (isBackspaceKey && relativeCaretPos === 0 && targetCellIndex === 0) {
|
|
1735
|
-
|
|
1730
|
+
// log(`${logPrefix} Intercepted Backspace at start of first cell (line boundary). Preventing merge.`);
|
|
1736
1731
|
evt.preventDefault();
|
|
1737
1732
|
return true;
|
|
1738
1733
|
}
|
|
1739
1734
|
// Delete at the very end of cell < last cell
|
|
1740
1735
|
if (isDeleteKey && relativeCaretPos === currentCellTextLength && targetCellIndex < metadataForTargetLine.cols - 1) {
|
|
1741
|
-
|
|
1736
|
+
// log(`${logPrefix} Intercepted Delete at end of cell ${targetCellIndex}. Preventing default.`);
|
|
1742
1737
|
evt.preventDefault();
|
|
1743
1738
|
return true;
|
|
1744
1739
|
}
|
|
1745
1740
|
// NEW: Delete at very end of last cell – would merge with next line
|
|
1746
1741
|
if (isDeleteKey && relativeCaretPos === currentCellTextLength && targetCellIndex === metadataForTargetLine.cols - 1) {
|
|
1747
|
-
|
|
1742
|
+
// log(`${logPrefix} Intercepted Delete at end of last cell (line boundary). Preventing merge.`);
|
|
1748
1743
|
evt.preventDefault();
|
|
1749
1744
|
return true;
|
|
1750
1745
|
}
|
|
@@ -1756,7 +1751,7 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1756
1751
|
// Guard: internal Backspace at relativePos 1 (would delete delimiter) & Delete at relativePos 0
|
|
1757
1752
|
if ((isInternalBackspace && relativeCaretPos === 1 && targetCellIndex > 0) ||
|
|
1758
1753
|
(isInternalDelete && relativeCaretPos === 0 && targetCellIndex > 0)) {
|
|
1759
|
-
|
|
1754
|
+
// log(`${logPrefix} Attempt to erase protected delimiter – operation blocked.`);
|
|
1760
1755
|
evt.preventDefault();
|
|
1761
1756
|
return true;
|
|
1762
1757
|
}
|
|
@@ -1764,25 +1759,25 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1764
1759
|
if (isTypingKey || isInternalBackspace || isInternalDelete) {
|
|
1765
1760
|
// --- PREVENT TYPING DIRECTLY AFTER DELIMITER (relativeCaretPos===0) ---
|
|
1766
1761
|
if (isTypingKey && relativeCaretPos === 0 && targetCellIndex > 0) {
|
|
1767
|
-
|
|
1762
|
+
// log(`${logPrefix} Caret at forbidden position 0 (just after delimiter). Auto-advancing to position 1.`);
|
|
1768
1763
|
const safePosAbs = cellStartCol + 1;
|
|
1769
1764
|
editorInfo.ace_performSelectionChange([currentLineNum, safePosAbs], [currentLineNum, safePosAbs], false);
|
|
1770
1765
|
editorInfo.ace_updateBrowserSelectionFromRep();
|
|
1771
1766
|
relativeCaretPos = 1;
|
|
1772
|
-
|
|
1767
|
+
// log(`${logPrefix} Caret moved to safe position. New relativeCaretPos=${relativeCaretPos}`);
|
|
1773
1768
|
}
|
|
1774
1769
|
// *** Use the validated currentLineNum and currentCol derived from relativeCaretPos ***
|
|
1775
1770
|
const currentCol = cellStartCol + relativeCaretPos;
|
|
1776
|
-
|
|
1777
|
-
|
|
1771
|
+
// log(`${logPrefix} Handling INTERNAL key='${evt.key}' Type='${evt.type}' at Line=${currentLineNum}, Col=${currentCol} (CellIndex=${targetCellIndex}, RelativePos=${relativeCaretPos}).`);
|
|
1772
|
+
// log(`${logPrefix} [caretTrace] Initial rep.selStart for internal edit: Line=${rep.selStart[0]}, Col=${rep.selStart[1]}`);
|
|
1778
1773
|
|
|
1779
1774
|
// Only process keydown events for modifications
|
|
1780
1775
|
if (evt.type !== 'keydown') {
|
|
1781
|
-
|
|
1776
|
+
// log(`${logPrefix} Ignoring non-keydown event type ('${evt.type}') for handled key.`);
|
|
1782
1777
|
return false;
|
|
1783
1778
|
}
|
|
1784
1779
|
|
|
1785
|
-
|
|
1780
|
+
// log(`${logPrefix} Preventing default browser action for keydown event.`);
|
|
1786
1781
|
evt.preventDefault();
|
|
1787
1782
|
|
|
1788
1783
|
let newAbsoluteCaretCol = -1;
|
|
@@ -1790,56 +1785,56 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1790
1785
|
|
|
1791
1786
|
try {
|
|
1792
1787
|
repBeforeEdit = editorInfo.ace_getRep(); // Get rep *before* making changes
|
|
1793
|
-
|
|
1788
|
+
// log(`${logPrefix} [caretTrace] rep.selStart before ace_performDocumentReplaceRange: Line=${repBeforeEdit.selStart[0]}, Col=${repBeforeEdit.selStart[1]}`);
|
|
1794
1789
|
|
|
1795
1790
|
if (isTypingKey) {
|
|
1796
1791
|
const insertPos = [currentLineNum, currentCol];
|
|
1797
|
-
|
|
1792
|
+
// log(`${logPrefix} -> Inserting text '${evt.key}' at [${insertPos}]`);
|
|
1798
1793
|
editorInfo.ace_performDocumentReplaceRange(insertPos, insertPos, evt.key);
|
|
1799
1794
|
newAbsoluteCaretCol = currentCol + 1;
|
|
1800
1795
|
|
|
1801
1796
|
} else if (isInternalBackspace) {
|
|
1802
1797
|
const delRangeStart = [currentLineNum, currentCol - 1];
|
|
1803
1798
|
const delRangeEnd = [currentLineNum, currentCol];
|
|
1804
|
-
|
|
1799
|
+
// log(`${logPrefix} -> Deleting (Backspace) range [${delRangeStart}]-[${delRangeEnd}]`);
|
|
1805
1800
|
editorInfo.ace_performDocumentReplaceRange(delRangeStart, delRangeEnd, '');
|
|
1806
1801
|
newAbsoluteCaretCol = currentCol - 1;
|
|
1807
1802
|
|
|
1808
1803
|
} else if (isInternalDelete) {
|
|
1809
1804
|
const delRangeStart = [currentLineNum, currentCol];
|
|
1810
1805
|
const delRangeEnd = [currentLineNum, currentCol + 1];
|
|
1811
|
-
|
|
1806
|
+
// log(`${logPrefix} -> Deleting (Delete) range [${delRangeStart}]-[${delRangeEnd}]`);
|
|
1812
1807
|
editorInfo.ace_performDocumentReplaceRange(delRangeStart, delRangeEnd, '');
|
|
1813
1808
|
newAbsoluteCaretCol = currentCol; // Caret stays at the same column for delete
|
|
1814
1809
|
}
|
|
1815
1810
|
const repAfterReplace = editorInfo.ace_getRep();
|
|
1816
|
-
|
|
1811
|
+
// log(`${logPrefix} [caretTrace] rep.selStart after ace_performDocumentReplaceRange: Line=${repAfterReplace.selStart[0]}, Col=${repAfterReplace.selStart[1]}`);
|
|
1817
1812
|
|
|
1818
1813
|
|
|
1819
1814
|
// *** CRITICAL: Re-apply the line attribute after ANY modification ***
|
|
1820
|
-
|
|
1815
|
+
// log(`${logPrefix} -> Re-applying tbljson line attribute...`);
|
|
1821
1816
|
|
|
1822
1817
|
// DEBUG: Log the values before calculating attrStringToApply
|
|
1823
|
-
|
|
1824
|
-
|
|
1818
|
+
// log(`${logPrefix} DEBUG: Before calculating attrStringToApply - trustedLastClick=${trustedLastClick}, reportedLineNum=${reportedLineNum}, currentLineNum=${currentLineNum}`);
|
|
1819
|
+
// log(`${logPrefix} DEBUG: lineAttrString value:`, lineAttrString ? `"${lineAttrString}"` : 'null/undefined');
|
|
1825
1820
|
|
|
1826
1821
|
const applyHelper = editorInfo.ep_data_tables_applyMeta;
|
|
1827
1822
|
if (applyHelper && typeof applyHelper === 'function' && repBeforeEdit) {
|
|
1828
1823
|
// Pass the original lineAttrString if available AND if it belongs to the currentLineNum
|
|
1829
1824
|
const attrStringToApply = (trustedLastClick || reportedLineNum === currentLineNum) ? lineAttrString : null;
|
|
1830
1825
|
|
|
1831
|
-
|
|
1832
|
-
|
|
1826
|
+
// log(`${logPrefix} DEBUG: Calculated attrStringToApply:`, attrStringToApply ? `"${attrStringToApply}"` : 'null/undefined');
|
|
1827
|
+
// log(`${logPrefix} DEBUG: Condition result: (${trustedLastClick} || ${reportedLineNum} === ${currentLineNum}) = ${trustedLastClick || reportedLineNum === currentLineNum}`);
|
|
1833
1828
|
|
|
1834
1829
|
applyHelper(currentLineNum, metadataForTargetLine.tblId, metadataForTargetLine.row, metadataForTargetLine.cols, repBeforeEdit, editorInfo, attrStringToApply, docManager);
|
|
1835
|
-
|
|
1830
|
+
// log(`${logPrefix} -> tbljson line attribute re-applied (using rep before edit).`);
|
|
1836
1831
|
} else {
|
|
1837
1832
|
console.error(`${logPrefix} -> FAILED to re-apply tbljson attribute (helper or repBeforeEdit missing).`);
|
|
1838
1833
|
const currentRepFallback = editorInfo.ace_getRep();
|
|
1839
1834
|
if (applyHelper && typeof applyHelper === 'function' && currentRepFallback) {
|
|
1840
|
-
|
|
1835
|
+
// log(`${logPrefix} -> Retrying attribute application with current rep...`);
|
|
1841
1836
|
applyHelper(currentLineNum, metadataForTargetLine.tblId, metadataForTargetLine.row, metadataForTargetLine.cols, currentRepFallback, editorInfo, null, docManager); // Cannot guarantee old attr string is valid here
|
|
1842
|
-
|
|
1837
|
+
// log(`${logPrefix} -> tbljson line attribute re-applied (using current rep fallback).`);
|
|
1843
1838
|
} else {
|
|
1844
1839
|
console.error(`${logPrefix} -> FAILED to re-apply tbljson attribute even with fallback rep.`);
|
|
1845
1840
|
}
|
|
@@ -1848,26 +1843,26 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1848
1843
|
// Set caret position immediately
|
|
1849
1844
|
if (newAbsoluteCaretCol >= 0) {
|
|
1850
1845
|
const newCaretPos = [currentLineNum, newAbsoluteCaretCol]; // Use the trusted currentLineNum
|
|
1851
|
-
|
|
1852
|
-
|
|
1846
|
+
// log(`${logPrefix} -> Setting selection immediately to:`, newCaretPos);
|
|
1847
|
+
// log(`${logPrefix} [caretTrace] rep.selStart before ace_performSelectionChange: Line=${editorInfo.ace_getRep().selStart[0]}, Col=${editorInfo.ace_getRep().selStart[1]}`);
|
|
1853
1848
|
try {
|
|
1854
1849
|
editorInfo.ace_performSelectionChange(newCaretPos, newCaretPos, false);
|
|
1855
1850
|
const repAfterSelectionChange = editorInfo.ace_getRep();
|
|
1856
|
-
|
|
1857
|
-
|
|
1851
|
+
// log(`${logPrefix} [caretTrace] [selection] rep.selStart after ace_performSelectionChange: Line=${repAfterSelectionChange.selStart[0]}, Col=${repAfterSelectionChange.selStart[1]}`);
|
|
1852
|
+
// log(`${logPrefix} -> Selection set immediately.`);
|
|
1858
1853
|
|
|
1859
1854
|
// Add sync hint AFTER setting selection
|
|
1860
1855
|
editorInfo.ace_fastIncorp(1);
|
|
1861
1856
|
const repAfterFastIncorp = editorInfo.ace_getRep();
|
|
1862
|
-
|
|
1863
|
-
|
|
1857
|
+
// log(`${logPrefix} [caretTrace] [selection] rep.selStart after ace_fastIncorp: Line=${repAfterFastIncorp.selStart[0]}, Col=${repAfterFastIncorp.selStart[1]}`);
|
|
1858
|
+
// log(`${logPrefix} -> Requested sync hint (fastIncorp 1).`);
|
|
1864
1859
|
|
|
1865
1860
|
// --- Re-assert selection ---
|
|
1866
1861
|
const targetCaretPosForReassert = [currentLineNum, newAbsoluteCaretCol];
|
|
1867
|
-
|
|
1862
|
+
// log(`${logPrefix} [caretTrace] Attempting to re-assert selection post-fastIncorp to [${targetCaretPosForReassert[0]}, ${targetCaretPosForReassert[1]}]`);
|
|
1868
1863
|
editorInfo.ace_performSelectionChange(targetCaretPosForReassert, targetCaretPosForReassert, false);
|
|
1869
1864
|
const repAfterReassert = editorInfo.ace_getRep();
|
|
1870
|
-
|
|
1865
|
+
// log(`${logPrefix} [caretTrace] [selection] rep.selStart after re-asserting selection: Line=${repAfterReassert.selStart[0]}, Col=${repAfterReassert.selStart[1]}`);
|
|
1871
1866
|
|
|
1872
1867
|
// Store the updated caret info for the next event
|
|
1873
1868
|
const newRelativePos = newAbsoluteCaretCol - cellStartCol;
|
|
@@ -1877,19 +1872,19 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1877
1872
|
cellIndex: targetCellIndex,
|
|
1878
1873
|
relativePos: newRelativePos
|
|
1879
1874
|
};
|
|
1880
|
-
|
|
1881
|
-
|
|
1875
|
+
// log(`${logPrefix} -> Updated stored click/caret info:`, editor.ep_data_tables_last_clicked);
|
|
1876
|
+
// log(`${logPrefix} [caretTrace] Updated ep_data_tables_last_clicked. Line=${editor.ep_data_tables_last_clicked.lineNum}, Cell=${editor.ep_data_tables_last_clicked.cellIndex}, RelPos=${editor.ep_data_tables_last_clicked.relativePos}`);
|
|
1882
1877
|
|
|
1883
1878
|
|
|
1884
1879
|
} catch (selError) {
|
|
1885
1880
|
console.error(`${logPrefix} -> ERROR setting selection immediately:`, selError);
|
|
1886
1881
|
}
|
|
1887
1882
|
} else {
|
|
1888
|
-
|
|
1883
|
+
// log(`${logPrefix} -> Warning: newAbsoluteCaretCol not set, skipping selection update.`);
|
|
1889
1884
|
}
|
|
1890
1885
|
|
|
1891
1886
|
} catch (error) {
|
|
1892
|
-
|
|
1887
|
+
// log(`${logPrefix} ERROR during manual key handling:`, error);
|
|
1893
1888
|
console.error('[ep_data_tables] Error processing key event update:', error);
|
|
1894
1889
|
// Maybe return false to allow default as a fallback on error?
|
|
1895
1890
|
// For now, return true as we prevented default.
|
|
@@ -1897,7 +1892,7 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1897
1892
|
}
|
|
1898
1893
|
|
|
1899
1894
|
const endLogTime = Date.now();
|
|
1900
|
-
|
|
1895
|
+
// log(`${logPrefix} END (Handled Internal Edit Manually) Key='${evt.key}' Type='${evt.type}' -> Returned true. Duration: ${endLogTime - startLogTime}ms`);
|
|
1901
1896
|
return true; // We handled the key event
|
|
1902
1897
|
|
|
1903
1898
|
} // End if(isTypingKey || isInternalBackspace || isInternalDelete)
|
|
@@ -1905,90 +1900,204 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1905
1900
|
|
|
1906
1901
|
// Fallback for any other keys or edge cases not handled above
|
|
1907
1902
|
const endLogTimeFinal = Date.now();
|
|
1908
|
-
|
|
1903
|
+
// log(`${logPrefix} END (Fell Through / Unhandled Case) Key='${evt.key}' Type='${evt.type}'. Allowing default. Duration: ${endLogTimeFinal - startLogTime}ms`);
|
|
1909
1904
|
// Clear click state if it wasn't handled?
|
|
1910
1905
|
// if (editor?.ep_data_tables_last_clicked) editor.ep_data_tables_last_clicked = null;
|
|
1911
|
-
|
|
1906
|
+
// log(`${logPrefix} [caretTrace] Final rep.selStart at end of aceKeyEvent (if unhandled): Line=${rep.selStart[0]}, Col=${rep.selStart[1]}`);
|
|
1912
1907
|
return false; // Allow default browser/ACE handling
|
|
1913
1908
|
};
|
|
1914
|
-
|
|
1915
1909
|
// ───────────────────── ace init + public helpers ─────────────────────
|
|
1916
1910
|
exports.aceInitialized = (h, ctx) => {
|
|
1917
1911
|
const logPrefix = '[ep_data_tables:aceInitialized]';
|
|
1918
|
-
|
|
1912
|
+
// log(`${logPrefix} START`, { hook_name: h, context: ctx });
|
|
1919
1913
|
const ed = ctx.editorInfo;
|
|
1920
1914
|
const docManager = ctx.documentAttributeManager;
|
|
1921
1915
|
|
|
1922
|
-
|
|
1916
|
+
// log(`${logPrefix} Attaching ep_data_tables_applyMeta helper to editorInfo.`);
|
|
1923
1917
|
ed.ep_data_tables_applyMeta = applyTableLineMetadataAttribute;
|
|
1924
|
-
|
|
1918
|
+
// log(`${logPrefix}: Attached applyTableLineMetadataAttribute helper to ed.ep_data_tables_applyMeta successfully.`);
|
|
1925
1919
|
|
|
1926
1920
|
// Store the documentAttributeManager reference for later use
|
|
1927
|
-
|
|
1921
|
+
// log(`${logPrefix} Storing documentAttributeManager reference on editorInfo.`);
|
|
1928
1922
|
ed.ep_data_tables_docManager = docManager;
|
|
1929
|
-
|
|
1923
|
+
// log(`${logPrefix}: Stored documentAttributeManager reference as ed.ep_data_tables_docManager.`);
|
|
1930
1924
|
|
|
1931
1925
|
// *** ENHANCED: Paste event listener + Column resize listeners ***
|
|
1932
|
-
|
|
1926
|
+
// log(`${logPrefix} Preparing to attach paste and resize listeners via ace_callWithAce.`);
|
|
1933
1927
|
ed.ace_callWithAce((ace) => {
|
|
1934
1928
|
const callWithAceLogPrefix = '[ep_data_tables:aceInitialized:callWithAceForListeners]';
|
|
1935
|
-
|
|
1929
|
+
// log(`${callWithAceLogPrefix} Entered ace_callWithAce callback for listeners.`);
|
|
1936
1930
|
|
|
1937
1931
|
if (!ace || !ace.editor) {
|
|
1938
1932
|
console.error(`${callWithAceLogPrefix} ERROR: ace or ace.editor is not available. Cannot attach listeners.`);
|
|
1939
|
-
|
|
1933
|
+
// log(`${callWithAceLogPrefix} Aborting listener attachment due to missing ace.editor.`);
|
|
1940
1934
|
return;
|
|
1941
1935
|
}
|
|
1942
1936
|
const editor = ace.editor;
|
|
1943
|
-
|
|
1937
|
+
// log(`${callWithAceLogPrefix} ace.editor obtained successfully.`);
|
|
1944
1938
|
|
|
1945
1939
|
// Store editor reference for later use in table operations
|
|
1946
|
-
|
|
1940
|
+
// log(`${logPrefix} Storing editor reference on editorInfo.`);
|
|
1947
1941
|
ed.ep_data_tables_editor = editor;
|
|
1948
|
-
|
|
1942
|
+
// log(`${logPrefix}: Stored editor reference as ed.ep_data_tables_editor.`);
|
|
1949
1943
|
|
|
1950
1944
|
// Attempt to find the inner iframe body, similar to ep_image_insert
|
|
1951
1945
|
let $inner;
|
|
1952
1946
|
try {
|
|
1953
|
-
|
|
1947
|
+
// log(`${callWithAceLogPrefix} Attempting to find inner iframe body for listener attachment.`);
|
|
1954
1948
|
const $iframeOuter = $('iframe[name="ace_outer"]');
|
|
1955
1949
|
if ($iframeOuter.length === 0) {
|
|
1956
1950
|
console.error(`${callWithAceLogPrefix} ERROR: Could not find outer iframe (ace_outer).`);
|
|
1957
|
-
|
|
1951
|
+
// log(`${callWithAceLogPrefix} Failed to find ace_outer.`);
|
|
1958
1952
|
return;
|
|
1959
1953
|
}
|
|
1960
|
-
|
|
1954
|
+
// log(`${callWithAceLogPrefix} Found ace_outer:`, $iframeOuter);
|
|
1961
1955
|
|
|
1962
1956
|
const $iframeInner = $iframeOuter.contents().find('iframe[name="ace_inner"]');
|
|
1963
1957
|
if ($iframeInner.length === 0) {
|
|
1964
1958
|
console.error(`${callWithAceLogPrefix} ERROR: Could not find inner iframe (ace_inner).`);
|
|
1965
|
-
|
|
1959
|
+
// log(`${callWithAceLogPrefix} Failed to find ace_inner within ace_outer.`);
|
|
1966
1960
|
return;
|
|
1967
1961
|
}
|
|
1968
|
-
|
|
1962
|
+
// log(`${callWithAceLogPrefix} Found ace_inner:`, $iframeInner);
|
|
1969
1963
|
|
|
1970
1964
|
const innerDocBody = $iframeInner.contents().find('body');
|
|
1971
1965
|
if (innerDocBody.length === 0) {
|
|
1972
1966
|
console.error(`${callWithAceLogPrefix} ERROR: Could not find body element in inner iframe.`);
|
|
1973
|
-
|
|
1967
|
+
// log(`${callWithAceLogPrefix} Failed to find body in ace_inner.`);
|
|
1974
1968
|
return;
|
|
1975
1969
|
}
|
|
1976
1970
|
$inner = $(innerDocBody[0]); // Ensure it's a jQuery object of the body itself
|
|
1977
|
-
|
|
1971
|
+
// log(`${callWithAceLogPrefix} Successfully found inner iframe body:`, $inner);
|
|
1972
|
+
|
|
1973
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
1974
|
+
// Mobile suggestion / autocorrect guard (Android and iOS)
|
|
1975
|
+
// Many virtual keyboards commit the chosen suggestion AFTER the composition
|
|
1976
|
+
// session has ended. This arrives as a second `beforeinput` (or `input` on
|
|
1977
|
+
// Safari) with inputType = "insertReplacementText" or "insertFromComposition"
|
|
1978
|
+
// – sometimes just plain "insertText" with isComposing=false. The default DOM
|
|
1979
|
+
// mutation breaks our \u241F-delimited table lines, causing renderer failure.
|
|
1980
|
+
//
|
|
1981
|
+
// We intercept these in the capture phase, cancel the event, and inject a
|
|
1982
|
+
// single safe space via Ace APIs so the caret advances but the table
|
|
1983
|
+
// structure remains intact.
|
|
1984
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
1985
|
+
const mobileSuggestionBlocker = (evt) => {
|
|
1986
|
+
const t = evt && evt.inputType || '';
|
|
1987
|
+
const dataStr = (evt && typeof evt.data === 'string') ? evt.data : '';
|
|
1988
|
+
const isProblem = (
|
|
1989
|
+
t === 'insertReplacementText' ||
|
|
1990
|
+
t === 'insertFromComposition' ||
|
|
1991
|
+
(t === 'insertText' && !evt.isComposing && (!dataStr || dataStr.length > 1))
|
|
1992
|
+
);
|
|
1993
|
+
if (!isProblem) return;
|
|
1994
|
+
|
|
1995
|
+
// Only act if selection is inside a table line
|
|
1996
|
+
try {
|
|
1997
|
+
const repQuick = ed.ace_getRep && ed.ace_getRep();
|
|
1998
|
+
if (!repQuick || !repQuick.selStart) return; // don't block outside editor selection
|
|
1999
|
+
const lineNumQuick = repQuick.selStart[0];
|
|
2000
|
+
let metaStrQuick = docManager && docManager.getAttributeOnLine
|
|
2001
|
+
? docManager.getAttributeOnLine(lineNumQuick, ATTR_TABLE_JSON)
|
|
2002
|
+
: null;
|
|
2003
|
+
let metaQuick = null;
|
|
2004
|
+
if (metaStrQuick) { try { metaQuick = JSON.parse(metaStrQuick); } catch (_) {} }
|
|
2005
|
+
if (!metaQuick) metaQuick = getTableLineMetadata(lineNumQuick, ed, docManager);
|
|
2006
|
+
if (!metaQuick || typeof metaQuick.cols !== 'number') return; // not a table line → do not block
|
|
2007
|
+
} catch (_) { return; }
|
|
2008
|
+
|
|
2009
|
+
// Cancel the browser's DOM mutation early
|
|
2010
|
+
evt.preventDefault();
|
|
2011
|
+
if (typeof evt.stopImmediatePropagation === 'function') evt.stopImmediatePropagation();
|
|
2012
|
+
|
|
2013
|
+
// Replace selection with a single plain space using Ace APIs
|
|
2014
|
+
setTimeout(() => {
|
|
2015
|
+
try {
|
|
2016
|
+
ed.ace_callWithAce((aceInstance) => {
|
|
2017
|
+
aceInstance.ace_fastIncorp(10);
|
|
2018
|
+
const rep = aceInstance.ace_getRep();
|
|
2019
|
+
if (!rep || !rep.selStart) return;
|
|
2020
|
+
const lineNum = rep.selStart[0];
|
|
2021
|
+
|
|
2022
|
+
// Perform the safe insertion
|
|
2023
|
+
aceInstance.ace_performDocumentReplaceRange(rep.selStart, rep.selEnd, ' ');
|
|
2024
|
+
|
|
2025
|
+
// Try to re-apply table metadata attribute to stabilize renderer
|
|
2026
|
+
try {
|
|
2027
|
+
let metaStr = docManager && docManager.getAttributeOnLine
|
|
2028
|
+
? docManager.getAttributeOnLine(lineNum, ATTR_TABLE_JSON)
|
|
2029
|
+
: null;
|
|
2030
|
+
let meta = null;
|
|
2031
|
+
if (metaStr) { try { meta = JSON.parse(metaStr); } catch (_) {} }
|
|
2032
|
+
if (!meta) meta = getTableLineMetadata(lineNum, ed, docManager);
|
|
2033
|
+
if (meta && typeof meta.tblId !== 'undefined' && typeof meta.row !== 'undefined' && typeof meta.cols === 'number') {
|
|
2034
|
+
const repAfter = aceInstance.ace_getRep();
|
|
2035
|
+
ed.ep_data_tables_applyMeta(lineNum, meta.tblId, meta.row, meta.cols, repAfter, ed, null, docManager);
|
|
2036
|
+
}
|
|
2037
|
+
} catch (_) {}
|
|
2038
|
+
}, 'mobileSuggestionBlocker', true);
|
|
2039
|
+
} catch (e) {
|
|
2040
|
+
console.error('[ep_data_tables:mobileSuggestionBlocker] Error inserting space:', e);
|
|
2041
|
+
}
|
|
2042
|
+
}, 0);
|
|
2043
|
+
};
|
|
2044
|
+
|
|
2045
|
+
// Listen in capture phase so we win the race with browser/default handlers
|
|
2046
|
+
if ($inner && $inner.length > 0 && $inner[0].addEventListener) {
|
|
2047
|
+
$inner[0].addEventListener('beforeinput', mobileSuggestionBlocker, true);
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
// Fallback for Safari iOS which may not emit beforeinput reliably: use input
|
|
2051
|
+
if ($inner && $inner.length > 0 && $inner[0].addEventListener) {
|
|
2052
|
+
$inner[0].addEventListener('input', (evt) => {
|
|
2053
|
+
// Only run if we did NOT already block in beforeinput
|
|
2054
|
+
if (evt && evt.inputType === 'insertText' && typeof evt.data === 'string' && evt.data.length > 1) {
|
|
2055
|
+
mobileSuggestionBlocker(evt);
|
|
2056
|
+
}
|
|
2057
|
+
}, true);
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
// Android legacy fallback: some keyboards dispatch 'textInput' instead of beforeinput
|
|
2061
|
+
if (isAndroidUA && isAndroidUA() && $inner && $inner.length > 0 && $inner[0].addEventListener) {
|
|
2062
|
+
$inner[0].addEventListener('textInput', (evt) => {
|
|
2063
|
+
const s = typeof evt.data === 'string' ? evt.data : '';
|
|
2064
|
+
if (s && s.length > 1) {
|
|
2065
|
+
mobileSuggestionBlocker({
|
|
2066
|
+
inputType: 'insertText',
|
|
2067
|
+
isComposing: false,
|
|
2068
|
+
data: s,
|
|
2069
|
+
preventDefault: () => { try { evt.preventDefault(); } catch (_) {} },
|
|
2070
|
+
stopImmediatePropagation: () => { try { evt.stopImmediatePropagation(); } catch (_) {} },
|
|
2071
|
+
});
|
|
2072
|
+
}
|
|
2073
|
+
}, true);
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
// Reduce chance of autocorrect/spellcheck kicking in at all
|
|
2077
|
+
try {
|
|
2078
|
+
const disableAuto = (el) => {
|
|
2079
|
+
if (!el) return;
|
|
2080
|
+
el.setAttribute('autocorrect', 'off');
|
|
2081
|
+
el.setAttribute('autocomplete', 'off');
|
|
2082
|
+
el.setAttribute('autocapitalize', 'off');
|
|
2083
|
+
el.setAttribute('spellcheck', 'false');
|
|
2084
|
+
};
|
|
2085
|
+
disableAuto(innerDocBody[0] || innerDocBody);
|
|
2086
|
+
} catch (_) {}
|
|
1978
2087
|
} catch (e) {
|
|
1979
2088
|
console.error(`${callWithAceLogPrefix} ERROR: Exception while trying to find inner iframe body:`, e);
|
|
1980
|
-
|
|
2089
|
+
// log(`${callWithAceLogPrefix} Exception details:`, { message: e.message, stack: e.stack });
|
|
1981
2090
|
return;
|
|
1982
2091
|
}
|
|
1983
2092
|
|
|
1984
2093
|
if (!$inner || $inner.length === 0) {
|
|
1985
2094
|
console.error(`${callWithAceLogPrefix} ERROR: $inner is not valid after attempting to find iframe body. Cannot attach listeners.`);
|
|
1986
|
-
|
|
2095
|
+
// log(`${callWithAceLogPrefix} $inner is invalid. Aborting.`);
|
|
1987
2096
|
return;
|
|
1988
2097
|
}
|
|
1989
2098
|
|
|
1990
2099
|
// *** CUT EVENT LISTENER ***
|
|
1991
|
-
|
|
2100
|
+
// log(`${callWithAceLogPrefix} Attaching cut event listener to $inner (inner iframe body).`);
|
|
1992
2101
|
$inner.on('cut', (evt) => {
|
|
1993
2102
|
const cutLogPrefix = '[ep_data_tables:cutHandler]';
|
|
1994
2103
|
console.log(`${cutLogPrefix} CUT EVENT TRIGGERED. Event object:`, evt);
|
|
@@ -2194,32 +2303,32 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
2194
2303
|
});
|
|
2195
2304
|
|
|
2196
2305
|
// *** BEFOREINPUT EVENT LISTENER FOR CONTEXT-MENU DELETE ***
|
|
2197
|
-
|
|
2306
|
+
// log(`${callWithAceLogPrefix} Attaching beforeinput event listener to $inner (inner iframe body).`);
|
|
2198
2307
|
$inner.on('beforeinput', (evt) => {
|
|
2199
2308
|
const deleteLogPrefix = '[ep_data_tables:beforeinputDeleteHandler]';
|
|
2200
|
-
|
|
2309
|
+
// log(`${deleteLogPrefix} BEFOREINPUT EVENT TRIGGERED. inputType: "${evt.originalEvent.inputType}", event object:`, evt);
|
|
2201
2310
|
|
|
2202
2311
|
// Only intercept deletion-related input events
|
|
2203
2312
|
if (!evt.originalEvent.inputType || !evt.originalEvent.inputType.startsWith('delete')) {
|
|
2204
|
-
|
|
2313
|
+
// log(`${deleteLogPrefix} Not a deletion event (inputType: "${evt.originalEvent.inputType}"). Allowing default.`);
|
|
2205
2314
|
return; // Allow default for non-delete events
|
|
2206
2315
|
}
|
|
2207
2316
|
|
|
2208
|
-
|
|
2317
|
+
// log(`${deleteLogPrefix} Getting current editor representation (rep).`);
|
|
2209
2318
|
const rep = ed.ace_getRep();
|
|
2210
2319
|
if (!rep || !rep.selStart) {
|
|
2211
|
-
|
|
2320
|
+
// log(`${deleteLogPrefix} WARNING: Could not get representation or selection. Allowing default delete.`);
|
|
2212
2321
|
console.warn(`${deleteLogPrefix} Could not get rep or selStart.`);
|
|
2213
2322
|
return; // Allow default
|
|
2214
2323
|
}
|
|
2215
|
-
|
|
2324
|
+
// log(`${deleteLogPrefix} Rep obtained. selStart:`, rep.selStart, `selEnd:`, rep.selEnd);
|
|
2216
2325
|
const selStart = rep.selStart;
|
|
2217
2326
|
const selEnd = rep.selEnd;
|
|
2218
2327
|
const lineNum = selStart[0];
|
|
2219
|
-
|
|
2328
|
+
// log(`${deleteLogPrefix} Current line number: ${lineNum}. Column start: ${selStart[1]}, Column end: ${selEnd[1]}.`);
|
|
2220
2329
|
|
|
2221
2330
|
// Android Chrome IME: collapsed backspace/forward-delete often comes via beforeinput
|
|
2222
|
-
const isAndroidChrome =
|
|
2331
|
+
const isAndroidChrome = isAndroidUA();
|
|
2223
2332
|
const inputType = (evt.originalEvent && evt.originalEvent.inputType) || '';
|
|
2224
2333
|
|
|
2225
2334
|
// Handle collapsed deletes on Android Chrome inside a table line to protect delimiters
|
|
@@ -2315,12 +2424,12 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
2315
2424
|
|
|
2316
2425
|
// Check if selection spans multiple lines
|
|
2317
2426
|
if (selStart[0] !== selEnd[0]) {
|
|
2318
|
-
|
|
2427
|
+
// log(`${deleteLogPrefix} WARNING: Selection spans multiple lines. Preventing delete to protect table structure.`);
|
|
2319
2428
|
evt.preventDefault();
|
|
2320
2429
|
return;
|
|
2321
2430
|
}
|
|
2322
2431
|
|
|
2323
|
-
|
|
2432
|
+
// log(`${deleteLogPrefix} Checking if line ${lineNum} is a table line by fetching '${ATTR_TABLE_JSON}' attribute.`);
|
|
2324
2433
|
let lineAttrString = docManager.getAttributeOnLine(lineNum, ATTR_TABLE_JSON);
|
|
2325
2434
|
let tableMetadata = null;
|
|
2326
2435
|
|
|
@@ -2337,11 +2446,11 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
2337
2446
|
}
|
|
2338
2447
|
|
|
2339
2448
|
if (!tableMetadata || typeof tableMetadata.cols !== 'number' || typeof tableMetadata.tblId === 'undefined' || typeof tableMetadata.row === 'undefined') {
|
|
2340
|
-
|
|
2449
|
+
// log(`${deleteLogPrefix} Line ${lineNum} is NOT a recognised table line. Allowing default delete.`);
|
|
2341
2450
|
return; // Not a table line
|
|
2342
2451
|
}
|
|
2343
2452
|
|
|
2344
|
-
|
|
2453
|
+
// log(`${deleteLogPrefix} Line ${lineNum} IS a table line. Metadata:`, tableMetadata);
|
|
2345
2454
|
|
|
2346
2455
|
// Validate selection is within cell boundaries
|
|
2347
2456
|
const lineText = rep.lines.atIndex(lineNum)?.text || '';
|
|
@@ -2392,25 +2501,25 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
2392
2501
|
}
|
|
2393
2502
|
|
|
2394
2503
|
if (targetCellIndex === -1 || selEnd[1] > cellEndCol) {
|
|
2395
|
-
|
|
2504
|
+
// log(`${deleteLogPrefix} WARNING: Selection spans cell boundaries or is outside cells. Preventing delete to protect table structure.`);
|
|
2396
2505
|
evt.preventDefault();
|
|
2397
2506
|
return;
|
|
2398
2507
|
}
|
|
2399
2508
|
|
|
2400
2509
|
// If we reach here, the selection is entirely within a single cell - intercept delete and preserve table structure
|
|
2401
|
-
|
|
2510
|
+
// log(`${deleteLogPrefix} Selection is entirely within cell ${targetCellIndex}. Intercepting delete to preserve table structure.`);
|
|
2402
2511
|
evt.preventDefault();
|
|
2403
2512
|
|
|
2404
2513
|
try {
|
|
2405
2514
|
// No clipboard operations needed for delete - just perform the deletion within the cell using ace operations
|
|
2406
|
-
|
|
2515
|
+
// log(`${deleteLogPrefix} Performing deletion via ed.ace_callWithAce.`);
|
|
2407
2516
|
ed.ace_callWithAce((aceInstance) => {
|
|
2408
2517
|
const callAceLogPrefix = `${deleteLogPrefix}[ace_callWithAceOps]`;
|
|
2409
|
-
|
|
2518
|
+
// log(`${callAceLogPrefix} Entered ace_callWithAce for delete operations. selStart:`, selStart, `selEnd:`, selEnd);
|
|
2410
2519
|
|
|
2411
|
-
|
|
2520
|
+
// log(`${callAceLogPrefix} Calling aceInstance.ace_performDocumentReplaceRange to delete selected text.`);
|
|
2412
2521
|
aceInstance.ace_performDocumentReplaceRange(selStart, selEnd, '');
|
|
2413
|
-
|
|
2522
|
+
// log(`${callAceLogPrefix} ace_performDocumentReplaceRange successful.`);
|
|
2414
2523
|
|
|
2415
2524
|
// --- Ensure cell is not left empty (zero-length) ---
|
|
2416
2525
|
const repAfterDeletion = aceInstance.ace_getRep();
|
|
@@ -2419,7 +2528,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
2419
2528
|
const cellTextAfterDeletion = cellsAfterDeletion[targetCellIndex] || '';
|
|
2420
2529
|
|
|
2421
2530
|
if (cellTextAfterDeletion.length === 0) {
|
|
2422
|
-
|
|
2531
|
+
// log(`${callAceLogPrefix} Cell ${targetCellIndex} became empty after delete – inserting single space to preserve structure.`);
|
|
2423
2532
|
const insertPos = [lineNum, selStart[1]]; // Start of the now-empty cell
|
|
2424
2533
|
aceInstance.ace_performDocumentReplaceRange(insertPos, insertPos, ' ');
|
|
2425
2534
|
|
|
@@ -2431,9 +2540,9 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
2431
2540
|
);
|
|
2432
2541
|
}
|
|
2433
2542
|
|
|
2434
|
-
|
|
2543
|
+
// log(`${callAceLogPrefix} Preparing to re-apply tbljson attribute to line ${lineNum}.`);
|
|
2435
2544
|
const repAfterDelete = aceInstance.ace_getRep();
|
|
2436
|
-
|
|
2545
|
+
// log(`${callAceLogPrefix} Fetched rep after delete for applyMeta. Line ${lineNum} text now: "${repAfterDelete.lines.atIndex(lineNum).text}"`);
|
|
2437
2546
|
|
|
2438
2547
|
ed.ep_data_tables_applyMeta(
|
|
2439
2548
|
lineNum,
|
|
@@ -2445,22 +2554,22 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
2445
2554
|
null,
|
|
2446
2555
|
docManager
|
|
2447
2556
|
);
|
|
2448
|
-
|
|
2557
|
+
// log(`${callAceLogPrefix} tbljson attribute re-applied successfully via ep_data_tables_applyMeta.`);
|
|
2449
2558
|
|
|
2450
2559
|
// Determine new caret position – one char forward if we inserted a space
|
|
2451
2560
|
const newCaretAbsoluteCol = (cellTextAfterDeletion.length === 0) ? selStart[1] + 1 : selStart[1];
|
|
2452
2561
|
const newCaretPos = [lineNum, newCaretAbsoluteCol];
|
|
2453
|
-
|
|
2562
|
+
// log(`${callAceLogPrefix} Setting caret position to: [${newCaretPos}].`);
|
|
2454
2563
|
aceInstance.ace_performSelectionChange(newCaretPos, newCaretPos, false);
|
|
2455
|
-
|
|
2564
|
+
// log(`${callAceLogPrefix} Selection change successful.`);
|
|
2456
2565
|
|
|
2457
|
-
|
|
2566
|
+
// log(`${callAceLogPrefix} Delete operations within ace_callWithAce completed successfully.`);
|
|
2458
2567
|
}, 'tableDeleteTextOperations', true);
|
|
2459
2568
|
|
|
2460
|
-
|
|
2569
|
+
// log(`${deleteLogPrefix} Delete operation completed successfully.`);
|
|
2461
2570
|
} catch (error) {
|
|
2462
2571
|
console.error(`${deleteLogPrefix} ERROR during delete operation:`, error);
|
|
2463
|
-
|
|
2572
|
+
// log(`${deleteLogPrefix} Delete operation failed. Error details:`, { message: error.message, stack: error.stack });
|
|
2464
2573
|
}
|
|
2465
2574
|
});
|
|
2466
2575
|
|
|
@@ -2472,8 +2581,8 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
2472
2581
|
// Only intercept insert types
|
|
2473
2582
|
if (!inputType || !inputType.startsWith('insert')) return;
|
|
2474
2583
|
|
|
2475
|
-
// Target only Android
|
|
2476
|
-
if (!
|
|
2584
|
+
// Target only Android browsers (exclude iOS)
|
|
2585
|
+
if (!isAndroidUA()) return;
|
|
2477
2586
|
|
|
2478
2587
|
// Get current selection and ensure we are inside a table line
|
|
2479
2588
|
const rep = ed.ace_getRep();
|
|
@@ -2512,17 +2621,60 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
2512
2621
|
currentOffset += cellLength + DELIMITER.length;
|
|
2513
2622
|
}
|
|
2514
2623
|
|
|
2515
|
-
// Clamp selection end if it includes a trailing delimiter of the same cell
|
|
2516
|
-
if (targetCellIndex !== -1 && selEnd[1] === cellEndCol + DELIMITER.length) {
|
|
2517
|
-
selEnd[1] = cellEndCol;
|
|
2518
|
-
}
|
|
2519
|
-
|
|
2520
2624
|
// If selection spills outside the cell boundaries, block to protect structure
|
|
2521
2625
|
if (targetCellIndex === -1 || selEnd[1] > cellEndCol) {
|
|
2522
2626
|
evt.preventDefault();
|
|
2523
2627
|
return;
|
|
2524
2628
|
}
|
|
2525
2629
|
|
|
2630
|
+
// iOS soft break (insertParagraph/insertLineBreak) → replace with plain space via Ace
|
|
2631
|
+
if (inputType === 'insertParagraph' || inputType === 'insertLineBreak') {
|
|
2632
|
+
evt.preventDefault();
|
|
2633
|
+
evt.stopPropagation();
|
|
2634
|
+
if (typeof evt.stopImmediatePropagation === 'function') evt.stopImmediatePropagation();
|
|
2635
|
+
setTimeout(() => {
|
|
2636
|
+
try {
|
|
2637
|
+
ed.ace_callWithAce((aceInstance) => {
|
|
2638
|
+
aceInstance.ace_fastIncorp(10);
|
|
2639
|
+
const freshRep = aceInstance.ace_getRep();
|
|
2640
|
+
const freshSelStart = freshRep.selStart;
|
|
2641
|
+
const freshSelEnd = freshRep.selEnd;
|
|
2642
|
+
// Replace soft break with space
|
|
2643
|
+
aceInstance.ace_performDocumentReplaceRange(freshSelStart, freshSelEnd, ' ');
|
|
2644
|
+
|
|
2645
|
+
// Apply td attr to inserted space
|
|
2646
|
+
const afterRep = aceInstance.ace_getRep();
|
|
2647
|
+
const maxLen = Math.max(0, afterRep.lines.atIndex(lineNum)?.text?.length || 0);
|
|
2648
|
+
const startCol = Math.min(Math.max(freshSelStart[1], 0), maxLen);
|
|
2649
|
+
const endCol = Math.min(startCol + 1, maxLen);
|
|
2650
|
+
if (endCol > startCol) {
|
|
2651
|
+
aceInstance.ace_performDocumentApplyAttributesToRange(
|
|
2652
|
+
[lineNum, startCol], [lineNum, endCol], [[ATTR_CELL, String(targetCellIndex)]]
|
|
2653
|
+
);
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2656
|
+
// Re-apply line metadata
|
|
2657
|
+
ed.ep_data_tables_applyMeta(
|
|
2658
|
+
lineNum, tableMetadata.tblId, tableMetadata.row, tableMetadata.cols,
|
|
2659
|
+
afterRep, ed, null, docManager
|
|
2660
|
+
);
|
|
2661
|
+
|
|
2662
|
+
const newCaretPos = [lineNum, endCol];
|
|
2663
|
+
aceInstance.ace_performSelectionChange(newCaretPos, newCaretPos, false);
|
|
2664
|
+
aceInstance.ace_fastIncorp(10);
|
|
2665
|
+
}, 'iosSoftBreakToSpace', true);
|
|
2666
|
+
} catch (e) {
|
|
2667
|
+
console.error(`${autoLogPrefix} ERROR replacing soft break:`, e);
|
|
2668
|
+
}
|
|
2669
|
+
}, 0);
|
|
2670
|
+
return;
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
// Clamp selection end if it includes a trailing delimiter of the same cell
|
|
2674
|
+
if (targetCellIndex !== -1 && selEnd[1] === cellEndCol + DELIMITER.length) {
|
|
2675
|
+
selEnd[1] = cellEndCol;
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2526
2678
|
// Handle line breaks by routing to Enter behavior
|
|
2527
2679
|
if (inputType === 'insertParagraph' || inputType === 'insertLineBreak') {
|
|
2528
2680
|
evt.preventDefault();
|
|
@@ -2636,9 +2788,300 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
2636
2788
|
}
|
|
2637
2789
|
});
|
|
2638
2790
|
|
|
2791
|
+
// Desktop (non-Android/iOS) – sanitize NBSP and table delimiter on any insert* beforeinput
|
|
2792
|
+
$inner.on('beforeinput', (evt) => {
|
|
2793
|
+
const genericLogPrefix = '[ep_data_tables:beforeinputInsertTextGeneric]';
|
|
2794
|
+
const inputType = (evt.originalEvent && evt.originalEvent.inputType) || '';
|
|
2795
|
+
// log diagnostics for all insert* types on table lines
|
|
2796
|
+
if (!inputType || !inputType.startsWith('insert')) return;
|
|
2797
|
+
|
|
2798
|
+
// Skip on Android or iOS (they have dedicated handlers above)
|
|
2799
|
+
if (isAndroidUA() || isIOSUA()) return;
|
|
2800
|
+
|
|
2801
|
+
const rep = ed.ace_getRep();
|
|
2802
|
+
if (!rep || !rep.selStart) return;
|
|
2803
|
+
const selStart = rep.selStart;
|
|
2804
|
+
const selEnd = rep.selEnd;
|
|
2805
|
+
const lineNum = selStart[0];
|
|
2806
|
+
|
|
2807
|
+
// Ensure we are inside a table line by resolving metadata
|
|
2808
|
+
let lineAttrString = docManager.getAttributeOnLine(lineNum, ATTR_TABLE_JSON);
|
|
2809
|
+
let tableMetadata = null;
|
|
2810
|
+
if (lineAttrString) {
|
|
2811
|
+
try { tableMetadata = JSON.parse(lineAttrString); } catch (_) {}
|
|
2812
|
+
}
|
|
2813
|
+
if (!tableMetadata) tableMetadata = getTableLineMetadata(lineNum, ed, docManager);
|
|
2814
|
+
if (!tableMetadata || typeof tableMetadata.cols !== 'number') return; // not a table line
|
|
2815
|
+
console.debug(`${genericLogPrefix} event`, { inputType, data: evt.originalEvent && evt.originalEvent.data });
|
|
2816
|
+
|
|
2817
|
+
// Treat null data (composition/IME) as a single space to prevent NBSP/BR side effects
|
|
2818
|
+
const rawData = evt.originalEvent && typeof evt.originalEvent.data === 'string' ? evt.originalEvent.data : ' ';
|
|
2819
|
+
|
|
2820
|
+
// Replace NBSP and delimiter with plain space, remove zero-width chars
|
|
2821
|
+
const insertedText = rawData
|
|
2822
|
+
.replace(/\u00A0/g, ' ')
|
|
2823
|
+
.replace(new RegExp(DELIMITER, 'g'), ' ')
|
|
2824
|
+
.replace(/[\u200B\u200C\u200D\uFEFF]/g, '')
|
|
2825
|
+
.replace(/\s+/g, ' ');
|
|
2826
|
+
|
|
2827
|
+
if (!insertedText) { evt.preventDefault(); return; }
|
|
2828
|
+
|
|
2829
|
+
// Compute cell boundaries to keep selection within current cell
|
|
2830
|
+
const lineText = rep.lines.atIndex(lineNum)?.text || '';
|
|
2831
|
+
const cells = lineText.split(DELIMITER);
|
|
2832
|
+
let currentOffset = 0;
|
|
2833
|
+
let targetCellIndex = -1;
|
|
2834
|
+
let cellStartCol = 0;
|
|
2835
|
+
let cellEndCol = 0;
|
|
2836
|
+
for (let i = 0; i < cells.length; i++) {
|
|
2837
|
+
const len = cells[i]?.length ?? 0;
|
|
2838
|
+
const end = currentOffset + len;
|
|
2839
|
+
if (selStart[1] >= currentOffset && selStart[1] <= end) {
|
|
2840
|
+
targetCellIndex = i;
|
|
2841
|
+
cellStartCol = currentOffset;
|
|
2842
|
+
cellEndCol = end;
|
|
2843
|
+
break;
|
|
2844
|
+
}
|
|
2845
|
+
currentOffset += len + DELIMITER.length;
|
|
2846
|
+
}
|
|
2847
|
+
if (targetCellIndex === -1 || selEnd[1] > cellEndCol) { evt.preventDefault(); console.debug(`${genericLogPrefix} abort: selection outside cell`, { selStart, selEnd, cellStartCol, cellEndCol }); return; }
|
|
2848
|
+
|
|
2849
|
+
evt.preventDefault();
|
|
2850
|
+
evt.stopPropagation();
|
|
2851
|
+
if (typeof evt.stopImmediatePropagation === 'function') evt.stopImmediatePropagation();
|
|
2852
|
+
|
|
2853
|
+
try {
|
|
2854
|
+
ed.ace_callWithAce((ace) => {
|
|
2855
|
+
ace.ace_fastIncorp(10);
|
|
2856
|
+
const freshRep = ace.ace_getRep();
|
|
2857
|
+
const freshSelStart = freshRep.selStart;
|
|
2858
|
+
const freshSelEnd = freshRep.selEnd;
|
|
2859
|
+
|
|
2860
|
+
ace.ace_performDocumentReplaceRange(freshSelStart, freshSelEnd, insertedText);
|
|
2861
|
+
|
|
2862
|
+
// Re-apply td attribute on inserted range
|
|
2863
|
+
const afterRep = ace.ace_getRep();
|
|
2864
|
+
const lineEntry = afterRep.lines.atIndex(lineNum);
|
|
2865
|
+
const maxLen = lineEntry ? lineEntry.text.length : 0;
|
|
2866
|
+
const startCol = Math.min(Math.max(freshSelStart[1], 0), maxLen);
|
|
2867
|
+
const endCol = Math.min(startCol + insertedText.length, maxLen);
|
|
2868
|
+
if (endCol > startCol) {
|
|
2869
|
+
ace.ace_performDocumentApplyAttributesToRange([lineNum, startCol], [lineNum, endCol], [[ATTR_CELL, String(targetCellIndex)]]);
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
// Re-apply tbljson line attribute
|
|
2873
|
+
ed.ep_data_tables_applyMeta(lineNum, tableMetadata.tblId, tableMetadata.row, tableMetadata.cols, afterRep, ed, null, docManager);
|
|
2874
|
+
|
|
2875
|
+
// Move caret to end of inserted text
|
|
2876
|
+
const newCaretPos = [lineNum, endCol];
|
|
2877
|
+
ace.ace_performSelectionChange(newCaretPos, newCaretPos, false);
|
|
2878
|
+
}, 'tableGenericInsertText', true);
|
|
2879
|
+
} catch (e) {
|
|
2880
|
+
console.error(`${genericLogPrefix} ERROR handling generic insertText:`, e);
|
|
2881
|
+
}
|
|
2882
|
+
});
|
|
2883
|
+
|
|
2884
|
+
// iOS (all browsers) – intercept autocorrect/IME commit replacements to protect table structure
|
|
2885
|
+
$inner.on('beforeinput', (evt) => {
|
|
2886
|
+
const autoLogPrefix = '[ep_data_tables:beforeinputAutoReplaceHandler]';
|
|
2887
|
+
const inputType = (evt.originalEvent && evt.originalEvent.inputType) || '';
|
|
2888
|
+
|
|
2889
|
+
// Only handle on iOS family UAs
|
|
2890
|
+
if (!isIOSUA()) return;
|
|
2891
|
+
|
|
2892
|
+
// Get current selection and ensure we are inside a table line (define before computing hasSelection)
|
|
2893
|
+
const rep = ed.ace_getRep();
|
|
2894
|
+
if (!rep || !rep.selStart) return;
|
|
2895
|
+
const selStart = rep.selStart;
|
|
2896
|
+
const selEnd = rep.selEnd;
|
|
2897
|
+
const lineNum = selStart[0];
|
|
2898
|
+
|
|
2899
|
+
// Intercept replacement/IME commit types; also catch iOS insertText when it behaves like autocorrect
|
|
2900
|
+
const dataStr = (evt.originalEvent && typeof evt.originalEvent.data === 'string') ? evt.originalEvent.data : '';
|
|
2901
|
+
const hasSelection = !(selStart[0] === selEnd[0] && selStart[1] === selEnd[1]);
|
|
2902
|
+
const looksLikeIOSAutoReplace = inputType === 'insertText' && dataStr.length > 1; // multi-char commit (often replacement)
|
|
2903
|
+
const insertTextNull = inputType === 'insertText' && dataStr === '' && !hasSelection; // predictive commit with null data (often NBSP)
|
|
2904
|
+
const shouldHandle = INPUTTYPE_REPLACEMENT_TYPES.has(inputType) || looksLikeIOSAutoReplace || (inputType === 'insertText' && (hasSelection || insertTextNull));
|
|
2905
|
+
if (!shouldHandle) return;
|
|
2906
|
+
|
|
2907
|
+
// Resolve table metadata for this line
|
|
2908
|
+
let lineAttrString = docManager.getAttributeOnLine(lineNum, ATTR_TABLE_JSON);
|
|
2909
|
+
let tableMetadata = null;
|
|
2910
|
+
if (lineAttrString) {
|
|
2911
|
+
try { tableMetadata = JSON.parse(lineAttrString); } catch (_) {}
|
|
2912
|
+
}
|
|
2913
|
+
if (!tableMetadata) tableMetadata = getTableLineMetadata(lineNum, ed, docManager);
|
|
2914
|
+
if (!tableMetadata || typeof tableMetadata.cols !== 'number' || typeof tableMetadata.tblId === 'undefined' || typeof tableMetadata.row === 'undefined') {
|
|
2915
|
+
return; // not a table line
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2918
|
+
// Compute cell boundaries and target cell index
|
|
2919
|
+
const lineText = rep.lines.atIndex(lineNum)?.text || '';
|
|
2920
|
+
const cells = lineText.split(DELIMITER);
|
|
2921
|
+
let currentOffset = 0;
|
|
2922
|
+
let targetCellIndex = -1;
|
|
2923
|
+
let cellStartCol = 0;
|
|
2924
|
+
let cellEndCol = 0;
|
|
2925
|
+
for (let i = 0; i < cells.length; i++) {
|
|
2926
|
+
const cellLength = cells[i]?.length ?? 0;
|
|
2927
|
+
const cellEndColThisIteration = currentOffset + cellLength;
|
|
2928
|
+
if (selStart[1] >= currentOffset && selStart[1] <= cellEndColThisIteration) {
|
|
2929
|
+
targetCellIndex = i;
|
|
2930
|
+
cellStartCol = currentOffset;
|
|
2931
|
+
cellEndCol = cellEndColThisIteration;
|
|
2932
|
+
break;
|
|
2933
|
+
}
|
|
2934
|
+
currentOffset += cellLength + DELIMITER.length;
|
|
2935
|
+
}
|
|
2936
|
+
|
|
2937
|
+
// Clamp selection end if it includes a trailing delimiter of the same cell
|
|
2938
|
+
if (targetCellIndex !== -1 && selEnd[1] === cellEndCol + DELIMITER.length) {
|
|
2939
|
+
selEnd[1] = cellEndCol;
|
|
2940
|
+
}
|
|
2941
|
+
|
|
2942
|
+
// If selection spills outside the cell boundaries, block to protect structure
|
|
2943
|
+
if (targetCellIndex === -1 || selEnd[1] > cellEndCol) {
|
|
2944
|
+
evt.preventDefault();
|
|
2945
|
+
return;
|
|
2946
|
+
}
|
|
2947
|
+
|
|
2948
|
+
// Replacement text payload from beforeinput
|
|
2949
|
+
let insertedText = dataStr;
|
|
2950
|
+
if (!insertedText) {
|
|
2951
|
+
if (insertTextNull) {
|
|
2952
|
+
// Predictive text commit: Safari already mutated DOM ( <br>). Block and repair via Ace.
|
|
2953
|
+
evt.preventDefault();
|
|
2954
|
+
evt.stopPropagation();
|
|
2955
|
+
if (typeof evt.stopImmediatePropagation === 'function') evt.stopImmediatePropagation();
|
|
2956
|
+
|
|
2957
|
+
setTimeout(() => {
|
|
2958
|
+
try {
|
|
2959
|
+
ed.ace_callWithAce((aceInstance) => {
|
|
2960
|
+
aceInstance.ace_fastIncorp(10);
|
|
2961
|
+
const freshRep = aceInstance.ace_getRep();
|
|
2962
|
+
const freshSelStart = freshRep.selStart;
|
|
2963
|
+
const freshSelEnd = freshRep.selEnd;
|
|
2964
|
+
// Insert a plain space
|
|
2965
|
+
aceInstance.ace_performDocumentReplaceRange(freshSelStart, freshSelEnd, ' ');
|
|
2966
|
+
|
|
2967
|
+
// Re-apply cell attribute to the single inserted char
|
|
2968
|
+
const afterRep = aceInstance.ace_getRep();
|
|
2969
|
+
const maxLen = Math.max(0, afterRep.lines.atIndex(lineNum)?.text?.length || 0);
|
|
2970
|
+
const startCol = Math.min(Math.max(freshSelStart[1], 0), maxLen);
|
|
2971
|
+
const endCol = Math.min(startCol + 1, maxLen);
|
|
2972
|
+
if (endCol > startCol) {
|
|
2973
|
+
aceInstance.ace_performDocumentApplyAttributesToRange(
|
|
2974
|
+
[lineNum, startCol], [lineNum, endCol], [[ATTR_CELL, String(targetCellIndex)]]
|
|
2975
|
+
);
|
|
2976
|
+
}
|
|
2977
|
+
|
|
2978
|
+
// Re-apply table metadata line attribute
|
|
2979
|
+
ed.ep_data_tables_applyMeta(
|
|
2980
|
+
lineNum, tableMetadata.tblId, tableMetadata.row, tableMetadata.cols,
|
|
2981
|
+
afterRep, ed, null, docManager
|
|
2982
|
+
);
|
|
2983
|
+
|
|
2984
|
+
// Move caret to end of inserted text
|
|
2985
|
+
const newCaretPos = [lineNum, endCol];
|
|
2986
|
+
aceInstance.ace_performSelectionChange(newCaretPos, newCaretPos, false);
|
|
2987
|
+
aceInstance.ace_fastIncorp(10);
|
|
2988
|
+
}, 'iosPredictiveCommit', true);
|
|
2989
|
+
} catch (e) {
|
|
2990
|
+
console.error(`${autoLogPrefix} ERROR fixing predictive commit:`, e);
|
|
2991
|
+
}
|
|
2992
|
+
}, 0);
|
|
2993
|
+
return;
|
|
2994
|
+
} else {
|
|
2995
|
+
// No payload and not special: if it's a replacement, block default to avoid DOM mutation
|
|
2996
|
+
if (INPUTTYPE_REPLACEMENT_TYPES.has(inputType) || hasSelection) {
|
|
2997
|
+
evt.preventDefault();
|
|
2998
|
+
evt.stopPropagation();
|
|
2999
|
+
if (typeof evt.stopImmediatePropagation === 'function') evt.stopImmediatePropagation();
|
|
3000
|
+
}
|
|
3001
|
+
return;
|
|
3002
|
+
}
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3005
|
+
// Sanitize inserted text: remove our delimiter and zero-width characters; normalize whitespace
|
|
3006
|
+
insertedText = insertedText
|
|
3007
|
+
.replace(new RegExp(DELIMITER, 'g'), ' ')
|
|
3008
|
+
.replace(/[\u200B\u200C\u200D\uFEFF]/g, '')
|
|
3009
|
+
.replace(/\s+/g, ' ');
|
|
3010
|
+
|
|
3011
|
+
// Intercept the browser default and perform the edit via Ace APIs
|
|
3012
|
+
evt.preventDefault();
|
|
3013
|
+
evt.stopPropagation();
|
|
3014
|
+
if (typeof evt.stopImmediatePropagation === 'function') evt.stopImmediatePropagation();
|
|
3015
|
+
|
|
3016
|
+
try {
|
|
3017
|
+
setTimeout(() => {
|
|
3018
|
+
ed.ace_callWithAce((aceInstance) => {
|
|
3019
|
+
aceInstance.ace_fastIncorp(10);
|
|
3020
|
+
const freshRep = aceInstance.ace_getRep();
|
|
3021
|
+
const freshSelStart = freshRep.selStart;
|
|
3022
|
+
const freshSelEnd = freshRep.selEnd;
|
|
3023
|
+
|
|
3024
|
+
// Replace selection with sanitized text
|
|
3025
|
+
aceInstance.ace_performDocumentReplaceRange(freshSelStart, freshSelEnd, insertedText);
|
|
3026
|
+
|
|
3027
|
+
// Re-apply cell attribute to the newly inserted text (clamped to line length)
|
|
3028
|
+
const repAfterReplace = aceInstance.ace_getRep();
|
|
3029
|
+
const freshLineIndex = freshSelStart[0];
|
|
3030
|
+
const freshLineEntry = repAfterReplace.lines.atIndex(freshLineIndex);
|
|
3031
|
+
const maxLen = Math.max(0, (freshLineEntry && freshLineEntry.text) ? freshLineEntry.text.length : 0);
|
|
3032
|
+
const startCol = Math.min(Math.max(freshSelStart[1], 0), maxLen);
|
|
3033
|
+
const endColRaw = startCol + insertedText.length;
|
|
3034
|
+
const endCol = Math.min(endColRaw, maxLen);
|
|
3035
|
+
if (endCol > startCol) {
|
|
3036
|
+
aceInstance.ace_performDocumentApplyAttributesToRange(
|
|
3037
|
+
[freshLineIndex, startCol], [freshLineIndex, endCol], [[ATTR_CELL, String(targetCellIndex)]]
|
|
3038
|
+
);
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
// Re-apply table metadata line attribute
|
|
3042
|
+
ed.ep_data_tables_applyMeta(
|
|
3043
|
+
freshLineIndex,
|
|
3044
|
+
tableMetadata.tblId,
|
|
3045
|
+
tableMetadata.row,
|
|
3046
|
+
tableMetadata.cols,
|
|
3047
|
+
repAfterReplace,
|
|
3048
|
+
ed,
|
|
3049
|
+
null,
|
|
3050
|
+
docManager
|
|
3051
|
+
);
|
|
3052
|
+
|
|
3053
|
+
// Move caret to end of inserted text and update last-click state
|
|
3054
|
+
const newCaretCol = endCol;
|
|
3055
|
+
const newCaretPos = [freshLineIndex, newCaretCol];
|
|
3056
|
+
aceInstance.ace_performSelectionChange(newCaretPos, newCaretPos, false);
|
|
3057
|
+
aceInstance.ace_fastIncorp(10);
|
|
3058
|
+
|
|
3059
|
+
if (editor && editor.ep_data_tables_last_clicked && editor.ep_data_tables_last_clicked.tblId === tableMetadata.tblId) {
|
|
3060
|
+
// Recompute cellStartCol for fresh line
|
|
3061
|
+
const freshLineText = (freshLineEntry && freshLineEntry.text) || '';
|
|
3062
|
+
const freshCells = freshLineText.split(DELIMITER);
|
|
3063
|
+
let freshOffset = 0;
|
|
3064
|
+
for (let i = 0; i < targetCellIndex; i++) {
|
|
3065
|
+
freshOffset += (freshCells[i]?.length ?? 0) + DELIMITER.length;
|
|
3066
|
+
}
|
|
3067
|
+
const newRelativePos = newCaretCol - freshOffset;
|
|
3068
|
+
editor.ep_data_tables_last_clicked = {
|
|
3069
|
+
lineNum: freshLineIndex,
|
|
3070
|
+
tblId: tableMetadata.tblId,
|
|
3071
|
+
cellIndex: targetCellIndex,
|
|
3072
|
+
relativePos: newRelativePos < 0 ? 0 : newRelativePos,
|
|
3073
|
+
};
|
|
3074
|
+
}
|
|
3075
|
+
}, 'tableAutoReplaceTextOperations', true);
|
|
3076
|
+
}, 0);
|
|
3077
|
+
} catch (error) {
|
|
3078
|
+
console.error(`${autoLogPrefix} ERROR during auto-replace handling:`, error);
|
|
3079
|
+
}
|
|
3080
|
+
});
|
|
3081
|
+
|
|
2639
3082
|
// Composition start marker (Android Chrome/table only)
|
|
2640
3083
|
$inner.on('compositionstart', (evt) => {
|
|
2641
|
-
if (!
|
|
3084
|
+
if (!isAndroidUA()) return;
|
|
2642
3085
|
const rep = ed.ace_getRep();
|
|
2643
3086
|
if (!rep || !rep.selStart) return;
|
|
2644
3087
|
const lineNum = rep.selStart[0];
|
|
@@ -2650,12 +3093,11 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
2650
3093
|
handledCurrentComposition = false;
|
|
2651
3094
|
suppressBeforeInputInsertTextDuringComposition = false;
|
|
2652
3095
|
});
|
|
2653
|
-
|
|
2654
3096
|
// Android Chrome composition handling for whitespace (space) to prevent DOM mutation breaking delimiters
|
|
2655
3097
|
$inner.on('compositionupdate', (evt) => {
|
|
2656
3098
|
const compLogPrefix = '[ep_data_tables:compositionHandler]';
|
|
2657
3099
|
|
|
2658
|
-
if (!
|
|
3100
|
+
if (!isAndroidUA()) return;
|
|
2659
3101
|
|
|
2660
3102
|
const rep = ed.ace_getRep();
|
|
2661
3103
|
if (!rep || !rep.selStart) return;
|
|
@@ -2778,12 +3220,12 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
2778
3220
|
});
|
|
2779
3221
|
|
|
2780
3222
|
// *** DRAG AND DROP EVENT LISTENERS ***
|
|
2781
|
-
|
|
3223
|
+
// log(`${callWithAceLogPrefix} Attaching drag and drop event listeners to $inner (inner iframe body).`);
|
|
2782
3224
|
|
|
2783
3225
|
// Prevent drops that could damage table structure
|
|
2784
3226
|
$inner.on('drop', (evt) => {
|
|
2785
3227
|
const dropLogPrefix = '[ep_data_tables:dropHandler]';
|
|
2786
|
-
|
|
3228
|
+
// log(`${dropLogPrefix} DROP EVENT TRIGGERED. Event object:`, evt);
|
|
2787
3229
|
|
|
2788
3230
|
// Block drops directly targeted at a table element regardless of selection state
|
|
2789
3231
|
const targetEl = evt.target;
|
|
@@ -2797,19 +3239,19 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
2797
3239
|
return;
|
|
2798
3240
|
}
|
|
2799
3241
|
|
|
2800
|
-
|
|
3242
|
+
// log(`${dropLogPrefix} Getting current editor representation (rep).`);
|
|
2801
3243
|
const rep = ed.ace_getRep();
|
|
2802
3244
|
if (!rep || !rep.selStart) {
|
|
2803
|
-
|
|
3245
|
+
// log(`${dropLogPrefix} WARNING: Could not get representation or selection. Allowing default drop.`);
|
|
2804
3246
|
return; // Allow default
|
|
2805
3247
|
}
|
|
2806
3248
|
|
|
2807
3249
|
const selStart = rep.selStart;
|
|
2808
3250
|
const lineNum = selStart[0];
|
|
2809
|
-
|
|
3251
|
+
// log(`${dropLogPrefix} Current line number: ${lineNum}.`);
|
|
2810
3252
|
|
|
2811
3253
|
// Check if we're dropping onto a table line
|
|
2812
|
-
|
|
3254
|
+
// log(`${dropLogPrefix} Checking if line ${lineNum} is a table line by fetching '${ATTR_TABLE_JSON}' attribute.`);
|
|
2813
3255
|
let lineAttrString = docManager.getAttributeOnLine(lineNum, ATTR_TABLE_JSON);
|
|
2814
3256
|
let isTableLine = !!lineAttrString;
|
|
2815
3257
|
|
|
@@ -2819,7 +3261,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
2819
3261
|
}
|
|
2820
3262
|
|
|
2821
3263
|
if (isTableLine) {
|
|
2822
|
-
|
|
3264
|
+
// log(`${dropLogPrefix} Line ${lineNum} IS a table line. Preventing drop to protect table structure.`);
|
|
2823
3265
|
evt.preventDefault();
|
|
2824
3266
|
evt.stopPropagation();
|
|
2825
3267
|
console.warn('[ep_data_tables] Drop operation prevented to protect table structure. Please use copy/paste within table cells.');
|
|
@@ -2850,7 +3292,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
2850
3292
|
const lineNum = selStart[0];
|
|
2851
3293
|
|
|
2852
3294
|
// Check if we're dragging over a table line
|
|
2853
|
-
|
|
3295
|
+
// log(`${dragLogPrefix} Checking if line ${lineNum} is a table line by fetching '${ATTR_TABLE_JSON}' attribute.`);
|
|
2854
3296
|
let lineAttrString = docManager.getAttributeOnLine(lineNum, ATTR_TABLE_JSON);
|
|
2855
3297
|
let isTableLine = !!lineAttrString;
|
|
2856
3298
|
|
|
@@ -2859,7 +3301,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
2859
3301
|
}
|
|
2860
3302
|
|
|
2861
3303
|
if (isTableLine) {
|
|
2862
|
-
|
|
3304
|
+
// log(`${dragLogPrefix} Preventing dragover on table line ${lineNum} to control drop handling.`);
|
|
2863
3305
|
evt.preventDefault();
|
|
2864
3306
|
}
|
|
2865
3307
|
});
|
|
@@ -2877,67 +3319,67 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
2877
3319
|
});
|
|
2878
3320
|
|
|
2879
3321
|
// *** EXISTING PASTE LISTENER ***
|
|
2880
|
-
|
|
3322
|
+
// log(`${callWithAceLogPrefix} Attaching paste event listener to $inner (inner iframe body).`);
|
|
2881
3323
|
$inner.on('paste', (evt) => {
|
|
2882
3324
|
const pasteLogPrefix = '[ep_data_tables:pasteHandler]';
|
|
2883
|
-
|
|
3325
|
+
// log(`${pasteLogPrefix} PASTE EVENT TRIGGERED. Event object:`, evt);
|
|
2884
3326
|
|
|
2885
|
-
|
|
3327
|
+
// log(`${pasteLogPrefix} Getting current editor representation (rep).`);
|
|
2886
3328
|
const rep = ed.ace_getRep();
|
|
2887
3329
|
if (!rep || !rep.selStart) {
|
|
2888
|
-
|
|
3330
|
+
// log(`${pasteLogPrefix} WARNING: Could not get representation or selection. Allowing default paste.`);
|
|
2889
3331
|
console.warn(`${pasteLogPrefix} Could not get rep or selStart.`);
|
|
2890
3332
|
return; // Allow default
|
|
2891
3333
|
}
|
|
2892
|
-
|
|
3334
|
+
// log(`${pasteLogPrefix} Rep obtained. selStart:`, rep.selStart, `selEnd:`, rep.selEnd);
|
|
2893
3335
|
const selStart = rep.selStart;
|
|
2894
3336
|
const selEnd = rep.selEnd;
|
|
2895
3337
|
const lineNum = selStart[0];
|
|
2896
|
-
|
|
3338
|
+
// log(`${pasteLogPrefix} Current line number: ${lineNum}. Column start: ${selStart[1]}, Column end: ${selEnd[1]}.`);
|
|
2897
3339
|
|
|
2898
3340
|
// NEW: Check if selection spans multiple lines
|
|
2899
3341
|
if (selStart[0] !== selEnd[0]) {
|
|
2900
|
-
|
|
3342
|
+
// log(`${pasteLogPrefix} WARNING: Selection spans multiple lines. Preventing paste to protect table structure.`);
|
|
2901
3343
|
evt.preventDefault();
|
|
2902
3344
|
return;
|
|
2903
3345
|
}
|
|
2904
3346
|
|
|
2905
|
-
|
|
3347
|
+
// log(`${pasteLogPrefix} Checking if line ${lineNum} is a table line by fetching '${ATTR_TABLE_JSON}' attribute.`);
|
|
2906
3348
|
let lineAttrString = docManager.getAttributeOnLine(lineNum, ATTR_TABLE_JSON);
|
|
2907
3349
|
let tableMetadata = null;
|
|
2908
3350
|
|
|
2909
3351
|
if (!lineAttrString) {
|
|
2910
3352
|
// Block-styled row? Reconstruct metadata from the DOM.
|
|
2911
|
-
|
|
3353
|
+
// log(`${pasteLogPrefix} No '${ATTR_TABLE_JSON}' attribute found. Checking if this is a block-styled table row via DOM reconstruction.`);
|
|
2912
3354
|
const fallbackMeta = getTableLineMetadata(lineNum, ed, docManager);
|
|
2913
3355
|
if (fallbackMeta) {
|
|
2914
3356
|
tableMetadata = fallbackMeta;
|
|
2915
3357
|
lineAttrString = JSON.stringify(fallbackMeta);
|
|
2916
|
-
|
|
3358
|
+
// log(`${pasteLogPrefix} Block-styled table row detected. Reconstructed metadata:`, fallbackMeta);
|
|
2917
3359
|
}
|
|
2918
3360
|
}
|
|
2919
3361
|
|
|
2920
3362
|
if (!lineAttrString) {
|
|
2921
|
-
|
|
3363
|
+
// log(`${pasteLogPrefix} Line ${lineNum} is NOT a table line (no '${ATTR_TABLE_JSON}' attribute found and no DOM reconstruction possible). Allowing default paste.`);
|
|
2922
3364
|
return; // Not a table line
|
|
2923
3365
|
}
|
|
2924
|
-
|
|
3366
|
+
// log(`${pasteLogPrefix} Line ${lineNum} IS a table line. Attribute string: "${lineAttrString}".`);
|
|
2925
3367
|
|
|
2926
3368
|
try {
|
|
2927
|
-
|
|
3369
|
+
// log(`${pasteLogPrefix} Parsing table metadata from attribute string.`);
|
|
2928
3370
|
if (!tableMetadata) {
|
|
2929
3371
|
tableMetadata = JSON.parse(lineAttrString);
|
|
2930
3372
|
}
|
|
2931
|
-
|
|
3373
|
+
// log(`${pasteLogPrefix} Parsed table metadata:`, tableMetadata);
|
|
2932
3374
|
if (!tableMetadata || typeof tableMetadata.cols !== 'number' || typeof tableMetadata.tblId === 'undefined' || typeof tableMetadata.row === 'undefined') {
|
|
2933
|
-
|
|
3375
|
+
// log(`${pasteLogPrefix} WARNING: Invalid or incomplete table metadata on line ${lineNum}. Allowing default paste. Metadata:`, tableMetadata);
|
|
2934
3376
|
console.warn(`${pasteLogPrefix} Invalid table metadata for line ${lineNum}.`);
|
|
2935
3377
|
return; // Allow default
|
|
2936
3378
|
}
|
|
2937
|
-
|
|
3379
|
+
// log(`${pasteLogPrefix} Table metadata validated successfully: tblId=${tableMetadata.tblId}, row=${tableMetadata.row}, cols=${tableMetadata.cols}.`);
|
|
2938
3380
|
} catch(e) {
|
|
2939
3381
|
console.error(`${pasteLogPrefix} ERROR parsing table metadata for line ${lineNum}:`, e);
|
|
2940
|
-
|
|
3382
|
+
// log(`${pasteLogPrefix} Metadata parse error. Allowing default paste. Error details:`, { message: e.message, stack: e.stack });
|
|
2941
3383
|
return; // Allow default
|
|
2942
3384
|
}
|
|
2943
3385
|
|
|
@@ -2968,29 +3410,29 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
2968
3410
|
selEnd[1] = cellEndCol; // clamp
|
|
2969
3411
|
}
|
|
2970
3412
|
if (targetCellIndex === -1 || selEnd[1] > cellEndCol) {
|
|
2971
|
-
|
|
3413
|
+
// log(`${pasteLogPrefix} WARNING: Selection spans cell boundaries or is outside cells. Preventing paste to protect table structure.`);
|
|
2972
3414
|
evt.preventDefault();
|
|
2973
3415
|
return;
|
|
2974
3416
|
}
|
|
2975
3417
|
|
|
2976
|
-
|
|
3418
|
+
// log(`${pasteLogPrefix} Accessing clipboard data.`);
|
|
2977
3419
|
const clipboardData = evt.originalEvent.clipboardData || window.clipboardData;
|
|
2978
3420
|
if (!clipboardData) {
|
|
2979
|
-
|
|
3421
|
+
// log(`${pasteLogPrefix} WARNING: No clipboard data found. Allowing default paste.`);
|
|
2980
3422
|
return; // Allow default
|
|
2981
3423
|
}
|
|
2982
|
-
|
|
3424
|
+
// log(`${pasteLogPrefix} Clipboard data object obtained:`, clipboardData);
|
|
2983
3425
|
|
|
2984
3426
|
// Allow default handling (so ep_hyperlinked_text plugin can process) if rich HTML is present
|
|
2985
3427
|
const types = clipboardData.types || [];
|
|
2986
3428
|
if (types.includes('text/html') && clipboardData.getData('text/html')) {
|
|
2987
|
-
|
|
3429
|
+
// log(`${pasteLogPrefix} Detected text/html in clipboard – deferring to other plugins and default paste.`);
|
|
2988
3430
|
return; // Do not intercept
|
|
2989
3431
|
}
|
|
2990
3432
|
|
|
2991
|
-
|
|
3433
|
+
// log(`${pasteLogPrefix} Getting 'text/plain' from clipboard.`);
|
|
2992
3434
|
const pastedTextRaw = clipboardData.getData('text/plain');
|
|
2993
|
-
|
|
3435
|
+
// log(`${pasteLogPrefix} Pasted text raw: "${pastedTextRaw}" (Type: ${typeof pastedTextRaw})`);
|
|
2994
3436
|
|
|
2995
3437
|
// ENHANCED: More thorough sanitization of pasted content
|
|
2996
3438
|
let pastedText = pastedTextRaw
|
|
@@ -3000,18 +3442,18 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3000
3442
|
.replace(/\s+/g, " ") // Normalize whitespace
|
|
3001
3443
|
.trim(); // Trim leading/trailing whitespace
|
|
3002
3444
|
|
|
3003
|
-
|
|
3445
|
+
// log(`${pasteLogPrefix} Pasted text after sanitization: "${pastedText}"`);
|
|
3004
3446
|
|
|
3005
3447
|
if (typeof pastedText !== 'string' || pastedText.length === 0) {
|
|
3006
|
-
|
|
3448
|
+
// log(`${pasteLogPrefix} No plain text in clipboard or text is empty (after sanitization). Allowing default paste.`);
|
|
3007
3449
|
const types = clipboardData.types;
|
|
3008
|
-
|
|
3450
|
+
// log(`${pasteLogPrefix} Clipboard types available:`, types);
|
|
3009
3451
|
if (types && types.includes('text/html')) {
|
|
3010
|
-
|
|
3452
|
+
// log(`${pasteLogPrefix} Clipboard also contains HTML:`, clipboardData.getData('text/html'));
|
|
3011
3453
|
}
|
|
3012
3454
|
return; // Allow default if no plain text
|
|
3013
3455
|
}
|
|
3014
|
-
|
|
3456
|
+
// log(`${pasteLogPrefix} Plain text obtained from clipboard: "${pastedText}". Length: ${pastedText.length}.`);
|
|
3015
3457
|
|
|
3016
3458
|
// NEW: Check if paste would exceed cell boundaries
|
|
3017
3459
|
const currentCellText = cells[targetCellIndex] || '';
|
|
@@ -3025,18 +3467,18 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3025
3467
|
// see fit or set to `Infinity` to remove the cap entirely.
|
|
3026
3468
|
const MAX_CELL_LENGTH = 8000;
|
|
3027
3469
|
if (newCellLength > MAX_CELL_LENGTH) {
|
|
3028
|
-
|
|
3470
|
+
// log(`${pasteLogPrefix} WARNING: Paste would exceed maximum cell length (${newCellLength} > ${MAX_CELL_LENGTH}). Truncating paste.`);
|
|
3029
3471
|
const truncatedPaste = pastedText.substring(0, MAX_CELL_LENGTH - (currentCellText.length - selectionLength));
|
|
3030
3472
|
if (truncatedPaste.length === 0) {
|
|
3031
|
-
|
|
3473
|
+
// log(`${pasteLogPrefix} Paste would be completely truncated. Preventing paste.`);
|
|
3032
3474
|
evt.preventDefault();
|
|
3033
3475
|
return;
|
|
3034
3476
|
}
|
|
3035
|
-
|
|
3477
|
+
// log(`${pasteLogPrefix} Using truncated paste: "${truncatedPaste}"`);
|
|
3036
3478
|
pastedText = truncatedPaste;
|
|
3037
3479
|
}
|
|
3038
3480
|
|
|
3039
|
-
|
|
3481
|
+
// log(`${pasteLogPrefix} INTERCEPTING paste of plain text into table line ${lineNum}. PREVENTING DEFAULT browser action.`);
|
|
3040
3482
|
evt.preventDefault();
|
|
3041
3483
|
// Prevent other plugins from handling the same paste event once we
|
|
3042
3484
|
// have intercepted it inside a table cell.
|
|
@@ -3044,20 +3486,20 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3044
3486
|
if (typeof evt.stopImmediatePropagation === 'function') evt.stopImmediatePropagation();
|
|
3045
3487
|
|
|
3046
3488
|
try {
|
|
3047
|
-
|
|
3489
|
+
// log(`${pasteLogPrefix} Preparing to perform paste operations via ed.ace_callWithAce.`);
|
|
3048
3490
|
ed.ace_callWithAce((aceInstance) => {
|
|
3049
3491
|
const callAceLogPrefix = `${pasteLogPrefix}[ace_callWithAceOps]`;
|
|
3050
|
-
|
|
3492
|
+
// log(`${callAceLogPrefix} Entered ace_callWithAce for paste operations. selStart:`, selStart, `selEnd:`, selEnd);
|
|
3051
3493
|
|
|
3052
|
-
|
|
3494
|
+
// log(`${callAceLogPrefix} Original line text from initial rep: "${rep.lines.atIndex(lineNum).text}". SelStartCol: ${selStart[1]}, SelEndCol: ${selEnd[1]}.`);
|
|
3053
3495
|
|
|
3054
|
-
|
|
3496
|
+
// log(`${callAceLogPrefix} Calling aceInstance.ace_performDocumentReplaceRange to insert text: "${pastedText}".`);
|
|
3055
3497
|
aceInstance.ace_performDocumentReplaceRange(selStart, selEnd, pastedText);
|
|
3056
|
-
|
|
3498
|
+
// log(`${callAceLogPrefix} ace_performDocumentReplaceRange successful.`);
|
|
3057
3499
|
|
|
3058
|
-
|
|
3500
|
+
// log(`${callAceLogPrefix} Preparing to re-apply tbljson attribute to line ${lineNum}.`);
|
|
3059
3501
|
const repAfterReplace = aceInstance.ace_getRep();
|
|
3060
|
-
|
|
3502
|
+
// log(`${callAceLogPrefix} Fetched rep after replace for applyMeta. Line ${lineNum} text now: "${repAfterReplace.lines.atIndex(lineNum).text}"`);
|
|
3061
3503
|
|
|
3062
3504
|
ed.ep_data_tables_applyMeta(
|
|
3063
3505
|
lineNum,
|
|
@@ -3069,17 +3511,17 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3069
3511
|
null,
|
|
3070
3512
|
docManager
|
|
3071
3513
|
);
|
|
3072
|
-
|
|
3514
|
+
// log(`${callAceLogPrefix} tbljson attribute re-applied successfully via ep_data_tables_applyMeta.`);
|
|
3073
3515
|
|
|
3074
3516
|
const newCaretCol = selStart[1] + pastedText.length;
|
|
3075
3517
|
const newCaretPos = [lineNum, newCaretCol];
|
|
3076
|
-
|
|
3518
|
+
// log(`${callAceLogPrefix} New calculated caret position: [${newCaretPos}]. Setting selection.`);
|
|
3077
3519
|
aceInstance.ace_performSelectionChange(newCaretPos, newCaretPos, false);
|
|
3078
|
-
|
|
3520
|
+
// log(`${callAceLogPrefix} Selection change successful.`);
|
|
3079
3521
|
|
|
3080
|
-
|
|
3522
|
+
// log(`${callAceLogPrefix} Requesting fastIncorp(10) for sync.`);
|
|
3081
3523
|
aceInstance.ace_fastIncorp(10);
|
|
3082
|
-
|
|
3524
|
+
// log(`${callAceLogPrefix} fastIncorp requested.`);
|
|
3083
3525
|
|
|
3084
3526
|
// Update stored click/caret info
|
|
3085
3527
|
if (editor && editor.ep_data_tables_last_clicked && editor.ep_data_tables_last_clicked.tblId === tableMetadata.tblId) {
|
|
@@ -3090,23 +3532,23 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3090
3532
|
cellIndex: targetCellIndex,
|
|
3091
3533
|
relativePos: newRelativePos < 0 ? 0 : newRelativePos,
|
|
3092
3534
|
};
|
|
3093
|
-
|
|
3535
|
+
// log(`${callAceLogPrefix} Updated stored click/caret info:`, editor.ep_data_tables_last_clicked);
|
|
3094
3536
|
}
|
|
3095
3537
|
|
|
3096
|
-
|
|
3538
|
+
// log(`${callAceLogPrefix} Paste operations within ace_callWithAce completed successfully.`);
|
|
3097
3539
|
}, 'tablePasteTextOperations', true);
|
|
3098
|
-
|
|
3540
|
+
// log(`${pasteLogPrefix} ed.ace_callWithAce for paste operations was called.`);
|
|
3099
3541
|
|
|
3100
3542
|
} catch (error) {
|
|
3101
3543
|
console.error(`${pasteLogPrefix} CRITICAL ERROR during paste handling operation:`, error);
|
|
3102
|
-
|
|
3103
|
-
|
|
3544
|
+
// log(`${pasteLogPrefix} Error details:`, { message: error.message, stack: error.stack });
|
|
3545
|
+
// log(`${pasteLogPrefix} Paste handling FAILED. END OF HANDLER.`);
|
|
3104
3546
|
}
|
|
3105
3547
|
});
|
|
3106
|
-
|
|
3548
|
+
// log(`${callWithAceLogPrefix} Paste event listener attached.`);
|
|
3107
3549
|
|
|
3108
3550
|
// *** NEW: Column resize listeners ***
|
|
3109
|
-
|
|
3551
|
+
// log(`${callWithAceLogPrefix} Attaching column resize listeners...`);
|
|
3110
3552
|
|
|
3111
3553
|
// Get the iframe documents for proper event delegation
|
|
3112
3554
|
const $iframeOuter = $('iframe[name="ace_outer"]');
|
|
@@ -3114,16 +3556,16 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3114
3556
|
const innerDoc = $iframeInner.contents();
|
|
3115
3557
|
const outerDoc = $iframeOuter.contents();
|
|
3116
3558
|
|
|
3117
|
-
|
|
3559
|
+
// log(`${callWithAceLogPrefix} Found iframe documents: outer=${outerDoc.length}, inner=${innerDoc.length}`);
|
|
3118
3560
|
|
|
3119
3561
|
// Mousedown on resize handles
|
|
3120
3562
|
$inner.on('mousedown', '.ep-data_tables-resize-handle', (evt) => {
|
|
3121
3563
|
const resizeLogPrefix = '[ep_data_tables:resizeMousedown]';
|
|
3122
|
-
|
|
3564
|
+
// log(`${resizeLogPrefix} Resize handle mousedown detected`);
|
|
3123
3565
|
|
|
3124
3566
|
// Only handle left mouse button clicks
|
|
3125
3567
|
if (evt.button !== 0) {
|
|
3126
|
-
|
|
3568
|
+
// log(`${resizeLogPrefix} Ignoring non-left mouse button: ${evt.button}`);
|
|
3127
3569
|
return;
|
|
3128
3570
|
}
|
|
3129
3571
|
|
|
@@ -3134,7 +3576,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3134
3576
|
const isImageResizeHandle = $target.hasClass('image-resize-handle') || $target.closest('.image-resize-handle').length > 0;
|
|
3135
3577
|
|
|
3136
3578
|
if (isImageRelated || isImageResizeHandle) {
|
|
3137
|
-
|
|
3579
|
+
// log(`${resizeLogPrefix} Click detected on image-related element or image resize handle, ignoring for table resize`);
|
|
3138
3580
|
return;
|
|
3139
3581
|
}
|
|
3140
3582
|
|
|
@@ -3146,7 +3588,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3146
3588
|
const table = handle.closest('table.dataTable');
|
|
3147
3589
|
const lineNode = table.closest('div.ace-line');
|
|
3148
3590
|
|
|
3149
|
-
|
|
3591
|
+
// log(`${resizeLogPrefix} Parsed resize target: columnIndex=${columnIndex}, table=${!!table}, lineNode=${!!lineNode}`);
|
|
3150
3592
|
|
|
3151
3593
|
if (table && lineNode && !isNaN(columnIndex)) {
|
|
3152
3594
|
// Get table metadata
|
|
@@ -3160,7 +3602,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3160
3602
|
|
|
3161
3603
|
const lineNum = rep.lines.indexOfKey(lineNode.id);
|
|
3162
3604
|
|
|
3163
|
-
|
|
3605
|
+
// log(`${resizeLogPrefix} Table info: tblId=${tblId}, lineNum=${lineNum}`);
|
|
3164
3606
|
|
|
3165
3607
|
if (tblId && lineNum !== -1) {
|
|
3166
3608
|
try {
|
|
@@ -3168,17 +3610,17 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3168
3610
|
if (lineAttrString) {
|
|
3169
3611
|
const metadata = JSON.parse(lineAttrString);
|
|
3170
3612
|
if (metadata.tblId === tblId) {
|
|
3171
|
-
|
|
3613
|
+
// log(`${resizeLogPrefix} Starting resize with metadata:`, metadata);
|
|
3172
3614
|
startColumnResize(table, columnIndex, evt.clientX, metadata, lineNum);
|
|
3173
|
-
|
|
3615
|
+
// log(`${resizeLogPrefix} Started resize for column ${columnIndex}`);
|
|
3174
3616
|
|
|
3175
3617
|
// DEBUG: Verify global state is set
|
|
3176
|
-
|
|
3618
|
+
// log(`${resizeLogPrefix} Global resize state: isResizing=${isResizing}, targetTable=${!!resizeTargetTable}, targetColumn=${resizeTargetColumn}`);
|
|
3177
3619
|
} else {
|
|
3178
|
-
|
|
3620
|
+
// log(`${resizeLogPrefix} Table ID mismatch: ${metadata.tblId} vs ${tblId}`);
|
|
3179
3621
|
}
|
|
3180
3622
|
} else {
|
|
3181
|
-
|
|
3623
|
+
// log(`${resizeLogPrefix} No table metadata found for line ${lineNum}, trying DOM reconstruction...`);
|
|
3182
3624
|
|
|
3183
3625
|
// Fallback: Reconstruct metadata from DOM (same logic as ace_doDatatableOptions)
|
|
3184
3626
|
const rep = ed.ace_getRep();
|
|
@@ -3212,37 +3654,37 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3212
3654
|
cols: domCells.length,
|
|
3213
3655
|
columnWidths: columnWidths
|
|
3214
3656
|
};
|
|
3215
|
-
|
|
3657
|
+
// log(`${resizeLogPrefix} Reconstructed metadata from DOM:`, reconstructedMetadata);
|
|
3216
3658
|
|
|
3217
3659
|
startColumnResize(table, columnIndex, evt.clientX, reconstructedMetadata, lineNum);
|
|
3218
|
-
|
|
3660
|
+
// log(`${resizeLogPrefix} Started resize for column ${columnIndex} using reconstructed metadata`);
|
|
3219
3661
|
|
|
3220
3662
|
// DEBUG: Verify global state is set
|
|
3221
|
-
|
|
3663
|
+
// log(`${resizeLogPrefix} Global resize state: isResizing=${isResizing}, targetTable=${!!resizeTargetTable}, targetColumn=${resizeTargetColumn}`);
|
|
3222
3664
|
} else {
|
|
3223
|
-
|
|
3665
|
+
// log(`${resizeLogPrefix} DOM table found but no cells detected`);
|
|
3224
3666
|
}
|
|
3225
3667
|
} else {
|
|
3226
|
-
|
|
3668
|
+
// log(`${resizeLogPrefix} DOM table found but tblId mismatch or missing row: domTblId=${domTblId}, domRow=${domRow}`);
|
|
3227
3669
|
}
|
|
3228
3670
|
} else {
|
|
3229
|
-
|
|
3671
|
+
// log(`${resizeLogPrefix} No table found in DOM for line ${lineNum}`);
|
|
3230
3672
|
}
|
|
3231
3673
|
} else {
|
|
3232
|
-
|
|
3674
|
+
// log(`${resizeLogPrefix} Could not get line entry or lineNode for line ${lineNum}`);
|
|
3233
3675
|
}
|
|
3234
3676
|
} else {
|
|
3235
|
-
|
|
3677
|
+
// log(`${resizeLogPrefix} Could not get rep or rep.lines for DOM reconstruction`);
|
|
3236
3678
|
}
|
|
3237
3679
|
}
|
|
3238
3680
|
} catch (e) {
|
|
3239
3681
|
console.error(`${resizeLogPrefix} Error getting table metadata:`, e);
|
|
3240
3682
|
}
|
|
3241
3683
|
} else {
|
|
3242
|
-
|
|
3684
|
+
// log(`${resizeLogPrefix} Invalid line number (${lineNum}) or table ID (${tblId})`);
|
|
3243
3685
|
}
|
|
3244
3686
|
} else {
|
|
3245
|
-
|
|
3687
|
+
// log(`${resizeLogPrefix} Invalid resize target:`, { table: !!table, lineNode: !!lineNode, columnIndex });
|
|
3246
3688
|
}
|
|
3247
3689
|
});
|
|
3248
3690
|
|
|
@@ -3261,57 +3703,57 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3261
3703
|
|
|
3262
3704
|
// Mouseup handler with enhanced debugging
|
|
3263
3705
|
const handleMouseup = (evt) => {
|
|
3264
|
-
|
|
3706
|
+
// log(`${mouseupLogPrefix} Mouseup detected on ${evt.target.tagName || 'unknown'}. isResizing: ${isResizing}`);
|
|
3265
3707
|
|
|
3266
3708
|
if (isResizing) {
|
|
3267
|
-
|
|
3709
|
+
// log(`${mouseupLogPrefix} Processing resize completion...`);
|
|
3268
3710
|
evt.preventDefault();
|
|
3269
3711
|
evt.stopPropagation();
|
|
3270
3712
|
|
|
3271
3713
|
// Add a small delay to ensure all DOM updates are complete
|
|
3272
3714
|
setTimeout(() => {
|
|
3273
|
-
|
|
3715
|
+
// log(`${mouseupLogPrefix} Executing finishColumnResize after delay...`);
|
|
3274
3716
|
finishColumnResize(ed, docManager);
|
|
3275
|
-
|
|
3717
|
+
// log(`${mouseupLogPrefix} Resize completion finished.`);
|
|
3276
3718
|
}, 50);
|
|
3277
3719
|
} else {
|
|
3278
|
-
|
|
3720
|
+
// log(`${mouseupLogPrefix} Not in resize mode, ignoring mouseup.`);
|
|
3279
3721
|
}
|
|
3280
3722
|
};
|
|
3281
3723
|
|
|
3282
3724
|
// Attach to multiple contexts to ensure we catch the event
|
|
3283
|
-
|
|
3725
|
+
// log(`${callWithAceLogPrefix} Attaching global mousemove/mouseup handlers to multiple contexts...`);
|
|
3284
3726
|
|
|
3285
3727
|
// 1. Main document (outside iframes)
|
|
3286
3728
|
$(document).on('mousemove', handleMousemove);
|
|
3287
3729
|
$(document).on('mouseup', handleMouseup);
|
|
3288
|
-
|
|
3730
|
+
// log(`${callWithAceLogPrefix} Attached to main document`);
|
|
3289
3731
|
|
|
3290
3732
|
// 2. Outer iframe document
|
|
3291
3733
|
if (outerDoc.length > 0) {
|
|
3292
3734
|
outerDoc.on('mousemove', handleMousemove);
|
|
3293
3735
|
outerDoc.on('mouseup', handleMouseup);
|
|
3294
|
-
|
|
3736
|
+
// log(`${callWithAceLogPrefix} Attached to outer iframe document`);
|
|
3295
3737
|
}
|
|
3296
3738
|
|
|
3297
3739
|
// 3. Inner iframe document
|
|
3298
3740
|
if (innerDoc.length > 0) {
|
|
3299
3741
|
innerDoc.on('mousemove', handleMousemove);
|
|
3300
3742
|
innerDoc.on('mouseup', handleMouseup);
|
|
3301
|
-
|
|
3743
|
+
// log(`${callWithAceLogPrefix} Attached to inner iframe document`);
|
|
3302
3744
|
}
|
|
3303
3745
|
|
|
3304
3746
|
// 4. Inner iframe body (the editing area)
|
|
3305
3747
|
$inner.on('mousemove', handleMousemove);
|
|
3306
3748
|
$inner.on('mouseup', handleMouseup);
|
|
3307
|
-
|
|
3749
|
+
// log(`${callWithAceLogPrefix} Attached to inner iframe body`);
|
|
3308
3750
|
|
|
3309
3751
|
// 5. Add a failsafe - listen for any mouse events during resize
|
|
3310
3752
|
const failsafeMouseup = (evt) => {
|
|
3311
3753
|
if (isResizing) {
|
|
3312
|
-
|
|
3754
|
+
// log(`${mouseupLogPrefix} FAILSAFE: Detected mouse event during resize: ${evt.type}`);
|
|
3313
3755
|
if (evt.type === 'mouseup' || evt.type === 'mousedown' || evt.type === 'click') {
|
|
3314
|
-
|
|
3756
|
+
// log(`${mouseupLogPrefix} FAILSAFE: Triggering resize completion due to ${evt.type}`);
|
|
3315
3757
|
setTimeout(() => {
|
|
3316
3758
|
if (isResizing) { // Double-check we're still resizing
|
|
3317
3759
|
finishColumnResize(ed, docManager);
|
|
@@ -3325,7 +3767,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3325
3767
|
document.addEventListener('mouseup', failsafeMouseup, true);
|
|
3326
3768
|
document.addEventListener('mousedown', failsafeMouseup, true);
|
|
3327
3769
|
document.addEventListener('click', failsafeMouseup, true);
|
|
3328
|
-
|
|
3770
|
+
// log(`${callWithAceLogPrefix} Attached failsafe event handlers`);
|
|
3329
3771
|
|
|
3330
3772
|
// *** DRAG PREVENTION FOR TABLE ELEMENTS ***
|
|
3331
3773
|
const preventTableDrag = (evt) => {
|
|
@@ -3333,7 +3775,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3333
3775
|
// Block ANY drag gesture that originates within a table (including spans/text inside cells)
|
|
3334
3776
|
const inTable = target && typeof target.closest === 'function' && target.closest('table.dataTable');
|
|
3335
3777
|
if (inTable) {
|
|
3336
|
-
|
|
3778
|
+
// log('[ep_data_tables:dragPrevention] Preventing drag operation originating from inside table');
|
|
3337
3779
|
evt.preventDefault();
|
|
3338
3780
|
evt.stopPropagation();
|
|
3339
3781
|
if (evt.originalEvent && evt.originalEvent.dataTransfer) {
|
|
@@ -3347,7 +3789,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3347
3789
|
$inner.on('dragstart', preventTableDrag);
|
|
3348
3790
|
$inner.on('drag', preventTableDrag);
|
|
3349
3791
|
$inner.on('dragend', preventTableDrag);
|
|
3350
|
-
|
|
3792
|
+
// log(`${callWithAceLogPrefix} Attached drag prevention handlers to inner body`);
|
|
3351
3793
|
|
|
3352
3794
|
// Attach drag prevention broadly to cover iframe boundaries and the host document
|
|
3353
3795
|
if (innerDoc.length > 0) {
|
|
@@ -3368,15 +3810,15 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3368
3810
|
// Setup the global handlers
|
|
3369
3811
|
setupGlobalHandlers();
|
|
3370
3812
|
|
|
3371
|
-
|
|
3813
|
+
// log(`${callWithAceLogPrefix} Column resize listeners attached successfully.`);
|
|
3372
3814
|
|
|
3373
3815
|
}, 'tablePasteAndResizeListeners', true);
|
|
3374
|
-
|
|
3816
|
+
// log(`${logPrefix} ace_callWithAce for listeners setup completed.`);
|
|
3375
3817
|
|
|
3376
3818
|
// Helper function to apply the metadata attribute to a line
|
|
3377
3819
|
function applyTableLineMetadataAttribute (lineNum, tblId, rowIndex, numCols, rep, editorInfo, attributeString = null, documentAttributeManager = null) {
|
|
3378
3820
|
const funcName = 'applyTableLineMetadataAttribute';
|
|
3379
|
-
|
|
3821
|
+
// log(`${logPrefix}:${funcName}: START - Applying METADATA attribute to line ${lineNum}`, {tblId, rowIndex, numCols});
|
|
3380
3822
|
|
|
3381
3823
|
let finalMetadata;
|
|
3382
3824
|
|
|
@@ -3387,14 +3829,14 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3387
3829
|
if (providedMetadata.columnWidths && Array.isArray(providedMetadata.columnWidths) && providedMetadata.columnWidths.length === numCols) {
|
|
3388
3830
|
// Already has valid columnWidths, use as-is
|
|
3389
3831
|
finalMetadata = providedMetadata;
|
|
3390
|
-
|
|
3832
|
+
// log(`${logPrefix}:${funcName}: Using provided metadata with existing columnWidths`);
|
|
3391
3833
|
} else {
|
|
3392
3834
|
// Has metadata but missing/invalid columnWidths, extract from DOM
|
|
3393
3835
|
finalMetadata = providedMetadata;
|
|
3394
|
-
|
|
3836
|
+
// log(`${logPrefix}:${funcName}: Provided metadata missing columnWidths, attempting DOM extraction`);
|
|
3395
3837
|
}
|
|
3396
3838
|
} catch (e) {
|
|
3397
|
-
|
|
3839
|
+
// log(`${logPrefix}:${funcName}: Error parsing provided attributeString, will reconstruct:`, e);
|
|
3398
3840
|
finalMetadata = null;
|
|
3399
3841
|
}
|
|
3400
3842
|
}
|
|
@@ -3425,13 +3867,13 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3425
3867
|
columnWidths.push(100 / numCols);
|
|
3426
3868
|
}
|
|
3427
3869
|
});
|
|
3428
|
-
|
|
3870
|
+
// log(`${logPrefix}:${funcName}: Extracted column widths from DOM: ${columnWidths.map(w => w.toFixed(1) + '%').join(', ')}`);
|
|
3429
3871
|
}
|
|
3430
3872
|
}
|
|
3431
3873
|
}
|
|
3432
3874
|
}
|
|
3433
3875
|
} catch (e) {
|
|
3434
|
-
|
|
3876
|
+
// log(`${logPrefix}:${funcName}: Error extracting column widths from DOM:`, e);
|
|
3435
3877
|
}
|
|
3436
3878
|
|
|
3437
3879
|
// Build final metadata
|
|
@@ -3448,26 +3890,26 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3448
3890
|
}
|
|
3449
3891
|
|
|
3450
3892
|
const finalAttributeString = JSON.stringify(finalMetadata);
|
|
3451
|
-
|
|
3893
|
+
// log(`${logPrefix}:${funcName}: Final metadata attribute string: ${finalAttributeString}`);
|
|
3452
3894
|
|
|
3453
3895
|
try {
|
|
3454
3896
|
// Get current line info
|
|
3455
3897
|
const lineEntry = rep.lines.atIndex(lineNum);
|
|
3456
3898
|
if (!lineEntry) {
|
|
3457
|
-
|
|
3899
|
+
// log(`${logPrefix}:${funcName}: ERROR - Could not find line entry for line number ${lineNum}`);
|
|
3458
3900
|
return;
|
|
3459
3901
|
}
|
|
3460
3902
|
const lineLength = Math.max(1, lineEntry.text.length);
|
|
3461
|
-
|
|
3903
|
+
// log(`${logPrefix}:${funcName}: Line ${lineNum} text length: ${lineLength}`);
|
|
3462
3904
|
|
|
3463
3905
|
// Simple attribute application - just add the tbljson attribute
|
|
3464
3906
|
const attributes = [[ATTR_TABLE_JSON, finalAttributeString]];
|
|
3465
3907
|
const start = [lineNum, 0];
|
|
3466
3908
|
const end = [lineNum, lineLength];
|
|
3467
3909
|
|
|
3468
|
-
|
|
3910
|
+
// log(`${logPrefix}:${funcName}: Applying tbljson attribute to range [${start}]-[${end}]`);
|
|
3469
3911
|
editorInfo.ace_performDocumentApplyAttributesToRange(start, end, attributes);
|
|
3470
|
-
|
|
3912
|
+
// log(`${logPrefix}:${funcName}: Successfully applied tbljson attribute to line ${lineNum}`);
|
|
3471
3913
|
|
|
3472
3914
|
} catch(e) {
|
|
3473
3915
|
console.error(`[ep_data_tables] ${logPrefix}:${funcName}: Error applying metadata attribute on line ${lineNum}:`, e);
|
|
@@ -3477,59 +3919,59 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3477
3919
|
/** Insert a fresh rows×cols blank table at the caret */
|
|
3478
3920
|
ed.ace_createTableViaAttributes = (rows = 2, cols = 2) => {
|
|
3479
3921
|
const funcName = 'ace_createTableViaAttributes';
|
|
3480
|
-
|
|
3922
|
+
// log(`${funcName}: START - Refactored Phase 4 (Get Selection Fix)`, { rows, cols });
|
|
3481
3923
|
rows = Math.max(1, rows); cols = Math.max(1, cols);
|
|
3482
|
-
|
|
3924
|
+
// log(`${funcName}: Ensuring minimum 1 row, 1 col.`);
|
|
3483
3925
|
|
|
3484
3926
|
// --- Phase 1: Prepare Data ---
|
|
3485
3927
|
const tblId = rand();
|
|
3486
|
-
|
|
3928
|
+
// log(`${funcName}: Generated table ID: ${tblId}`);
|
|
3487
3929
|
const initialCellContent = ' '; // Start with a single space per cell
|
|
3488
3930
|
const lineTxt = Array.from({ length: cols }).fill(initialCellContent).join(DELIMITER);
|
|
3489
|
-
|
|
3931
|
+
// log(`${funcName}: Constructed initial line text for ${cols} cols: "${lineTxt}"`);
|
|
3490
3932
|
const block = Array.from({ length: rows }).fill(lineTxt).join('\n') + '\n';
|
|
3491
|
-
|
|
3933
|
+
// log(`${funcName}: Constructed block for ${rows} rows:\n${block}`);
|
|
3492
3934
|
|
|
3493
3935
|
// Get current selection BEFORE making changes using ace_getRep()
|
|
3494
|
-
|
|
3936
|
+
// log(`${funcName}: Getting current representation and selection...`);
|
|
3495
3937
|
const currentRepInitial = ed.ace_getRep();
|
|
3496
3938
|
if (!currentRepInitial || !currentRepInitial.selStart || !currentRepInitial.selEnd) {
|
|
3497
3939
|
console.error(`[ep_data_tables] ${funcName}: Could not get current representation or selection via ace_getRep(). Aborting.`);
|
|
3498
|
-
|
|
3940
|
+
// log(`${funcName}: END - Error getting initial rep/selection`);
|
|
3499
3941
|
return;
|
|
3500
3942
|
}
|
|
3501
3943
|
const start = currentRepInitial.selStart;
|
|
3502
3944
|
const end = currentRepInitial.selEnd;
|
|
3503
3945
|
const initialStartLine = start[0]; // Store the starting line number
|
|
3504
|
-
|
|
3946
|
+
// log(`${funcName}: Current selection from initial rep:`, { start, end });
|
|
3505
3947
|
|
|
3506
3948
|
// --- Phase 2: Insert Text Block ---
|
|
3507
|
-
|
|
3949
|
+
// log(`${funcName}: Phase 2 - Inserting text block...`);
|
|
3508
3950
|
ed.ace_performDocumentReplaceRange(start, end, block);
|
|
3509
|
-
|
|
3510
|
-
|
|
3951
|
+
// log(`${funcName}: Inserted block of delimited text lines.`);
|
|
3952
|
+
// log(`${funcName}: Requesting text sync (ace_fastIncorp 20)...`);
|
|
3511
3953
|
ed.ace_fastIncorp(20); // Sync text insertion
|
|
3512
|
-
|
|
3954
|
+
// log(`${funcName}: Text sync requested.`);
|
|
3513
3955
|
|
|
3514
3956
|
// --- Phase 3: Apply Metadata Attributes ---
|
|
3515
|
-
|
|
3957
|
+
// log(`${funcName}: Phase 3 - Applying metadata attributes to ${rows} inserted lines...`);
|
|
3516
3958
|
// Need rep to be updated after text insertion to apply attributes correctly
|
|
3517
3959
|
const currentRep = ed.ace_getRep(); // Get potentially updated rep
|
|
3518
3960
|
if (!currentRep || !currentRep.lines) {
|
|
3519
3961
|
console.error(`[ep_data_tables] ${funcName}: Could not get updated rep after text insertion. Cannot apply attributes reliably.`);
|
|
3520
|
-
|
|
3962
|
+
// log(`${funcName}: END - Error getting updated rep`);
|
|
3521
3963
|
// Maybe attempt to continue without rep? Risky.
|
|
3522
3964
|
return;
|
|
3523
3965
|
}
|
|
3524
|
-
|
|
3966
|
+
// log(`${funcName}: Fetched updated rep for attribute application.`);
|
|
3525
3967
|
|
|
3526
3968
|
for (let r = 0; r < rows; r++) {
|
|
3527
3969
|
const lineNumToApply = initialStartLine + r;
|
|
3528
|
-
|
|
3970
|
+
// log(`${funcName}: -> Processing row ${r} on line ${lineNumToApply}`);
|
|
3529
3971
|
|
|
3530
3972
|
const lineEntry = currentRep.lines.atIndex(lineNumToApply);
|
|
3531
3973
|
if (!lineEntry) {
|
|
3532
|
-
|
|
3974
|
+
// log(`${funcName}: Could not find line entry for ${lineNumToApply}, skipping attribute application.`);
|
|
3533
3975
|
continue;
|
|
3534
3976
|
}
|
|
3535
3977
|
const lineText = lineEntry.text || '';
|
|
@@ -3542,7 +3984,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3542
3984
|
if (cellContent.length > 0) { // Only apply to non-empty cells
|
|
3543
3985
|
const cellStart = [lineNumToApply, offset];
|
|
3544
3986
|
const cellEnd = [lineNumToApply, offset + cellContent.length];
|
|
3545
|
-
|
|
3987
|
+
// log(`${funcName}: Applying ${ATTR_CELL} attribute to Line ${lineNumToApply} Col ${c} Range ${offset}-${offset + cellContent.length}`);
|
|
3546
3988
|
ed.ace_performDocumentApplyAttributesToRange(cellStart, cellEnd, [[ATTR_CELL, String(c)]]);
|
|
3547
3989
|
}
|
|
3548
3990
|
offset += cellContent.length;
|
|
@@ -3555,30 +3997,30 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3555
3997
|
// Note: documentAttributeManager not available in this context for new table creation
|
|
3556
3998
|
applyTableLineMetadataAttribute(lineNumToApply, tblId, r, cols, currentRep, ed, null, null);
|
|
3557
3999
|
}
|
|
3558
|
-
|
|
3559
|
-
|
|
4000
|
+
// log(`${funcName}: Finished applying metadata attributes.`);
|
|
4001
|
+
// log(`${funcName}: Requesting attribute sync (ace_fastIncorp 20)...`);
|
|
3560
4002
|
ed.ace_fastIncorp(20); // Final sync after attributes
|
|
3561
|
-
|
|
4003
|
+
// log(`${funcName}: Attribute sync requested.`);
|
|
3562
4004
|
|
|
3563
4005
|
// --- Phase 4: Set Caret Position ---
|
|
3564
|
-
|
|
4006
|
+
// log(`${funcName}: Phase 4 - Setting final caret position...`);
|
|
3565
4007
|
const finalCaretLine = initialStartLine + rows; // Line number after the last inserted row
|
|
3566
4008
|
const finalCaretPos = [finalCaretLine, 0];
|
|
3567
|
-
|
|
4009
|
+
// log(`${funcName}: Target caret position:`, finalCaretPos);
|
|
3568
4010
|
try {
|
|
3569
4011
|
ed.ace_performSelectionChange(finalCaretPos, finalCaretPos, false);
|
|
3570
|
-
|
|
4012
|
+
// log(`${funcName}: Successfully set caret position.`);
|
|
3571
4013
|
} catch(e) {
|
|
3572
4014
|
console.error(`[ep_data_tables] ${funcName}: Error setting caret position after table creation:`, e);
|
|
3573
|
-
|
|
4015
|
+
// log(`[ep_data_tables] ${funcName}: Error details:`, { message: e.message, stack: e.stack });
|
|
3574
4016
|
}
|
|
3575
4017
|
|
|
3576
|
-
|
|
4018
|
+
// log(`${funcName}: END - Refactored Phase 4`);
|
|
3577
4019
|
};
|
|
3578
4020
|
|
|
3579
4021
|
ed.ace_doDatatableOptions = (action) => {
|
|
3580
4022
|
const funcName = 'ace_doDatatableOptions';
|
|
3581
|
-
|
|
4023
|
+
// log(`${funcName}: START - Processing action: ${action}`);
|
|
3582
4024
|
|
|
3583
4025
|
// Get the last clicked cell info to determine which table to operate on
|
|
3584
4026
|
const editor = ed.ep_data_tables_editor;
|
|
@@ -3589,12 +4031,12 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3589
4031
|
|
|
3590
4032
|
const lastClick = editor.ep_data_tables_last_clicked;
|
|
3591
4033
|
if (!lastClick || !lastClick.tblId) {
|
|
3592
|
-
|
|
4034
|
+
// log(`${funcName}: No table selected. Please click on a table cell first.`);
|
|
3593
4035
|
console.warn('[ep_data_tables] No table selected. Please click on a table cell first.');
|
|
3594
4036
|
return;
|
|
3595
4037
|
}
|
|
3596
4038
|
|
|
3597
|
-
|
|
4039
|
+
// log(`${funcName}: Operating on table ${lastClick.tblId}, clicked line ${lastClick.lineNum}, cell ${lastClick.cellIndex}`);
|
|
3598
4040
|
|
|
3599
4041
|
try {
|
|
3600
4042
|
// Get current representation and document manager
|
|
@@ -3611,7 +4053,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3611
4053
|
return;
|
|
3612
4054
|
}
|
|
3613
4055
|
|
|
3614
|
-
|
|
4056
|
+
// log(`${funcName}: Successfully obtained documentAttributeManager from stored reference.`);
|
|
3615
4057
|
|
|
3616
4058
|
// Find all lines that belong to this table
|
|
3617
4059
|
const tableLines = [];
|
|
@@ -3640,7 +4082,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3640
4082
|
cols: domCells.length
|
|
3641
4083
|
};
|
|
3642
4084
|
lineAttrString = JSON.stringify(reconstructedMetadata);
|
|
3643
|
-
|
|
4085
|
+
// log(`${funcName}: Reconstructed metadata from DOM for line ${lineIndex}: ${lineAttrString}`);
|
|
3644
4086
|
}
|
|
3645
4087
|
}
|
|
3646
4088
|
}
|
|
@@ -3668,13 +4110,13 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3668
4110
|
}
|
|
3669
4111
|
|
|
3670
4112
|
if (tableLines.length === 0) {
|
|
3671
|
-
|
|
4113
|
+
// log(`${funcName}: No table lines found for table ${lastClick.tblId}`);
|
|
3672
4114
|
return;
|
|
3673
4115
|
}
|
|
3674
4116
|
|
|
3675
4117
|
// Sort by row number to ensure correct order
|
|
3676
4118
|
tableLines.sort((a, b) => a.row - b.row);
|
|
3677
|
-
|
|
4119
|
+
// log(`${funcName}: Found ${tableLines.length} table lines`);
|
|
3678
4120
|
|
|
3679
4121
|
// Determine table dimensions and target indices with robust matching
|
|
3680
4122
|
const numRows = tableLines.length;
|
|
@@ -3688,7 +4130,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3688
4130
|
|
|
3689
4131
|
// If that fails, try to match by finding the row that contains the clicked table
|
|
3690
4132
|
if (targetRowIndex === -1) {
|
|
3691
|
-
|
|
4133
|
+
// log(`${funcName}: Direct line number match failed, searching by DOM structure...`);
|
|
3692
4134
|
const clickedLineEntry = currentRep.lines.atIndex(lastClick.lineNum);
|
|
3693
4135
|
if (clickedLineEntry && clickedLineEntry.lineNode) {
|
|
3694
4136
|
const clickedTable = clickedLineEntry.lineNode.querySelector('table.dataTable[data-tblId="' + lastClick.tblId + '"]');
|
|
@@ -3697,7 +4139,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3697
4139
|
if (clickedRowAttr !== null) {
|
|
3698
4140
|
const clickedRowNum = parseInt(clickedRowAttr, 10);
|
|
3699
4141
|
targetRowIndex = tableLines.findIndex(line => line.row === clickedRowNum);
|
|
3700
|
-
|
|
4142
|
+
// log(`${funcName}: Found target row by DOM attribute matching: row ${clickedRowNum}, index ${targetRowIndex}`);
|
|
3701
4143
|
}
|
|
3702
4144
|
}
|
|
3703
4145
|
}
|
|
@@ -3705,13 +4147,13 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3705
4147
|
|
|
3706
4148
|
// If still not found, default to first row but log the issue
|
|
3707
4149
|
if (targetRowIndex === -1) {
|
|
3708
|
-
|
|
4150
|
+
// log(`${funcName}: Warning: Could not find target row, defaulting to row 0`);
|
|
3709
4151
|
targetRowIndex = 0;
|
|
3710
4152
|
}
|
|
3711
4153
|
|
|
3712
4154
|
const targetColIndex = lastClick.cellIndex || 0;
|
|
3713
4155
|
|
|
3714
|
-
|
|
4156
|
+
// log(`${funcName}: Table dimensions: ${numRows} rows x ${numCols} cols. Target: row ${targetRowIndex}, col ${targetColIndex}`);
|
|
3715
4157
|
|
|
3716
4158
|
// Perform table operations with both text and metadata updates
|
|
3717
4159
|
let newNumCols = numCols;
|
|
@@ -3719,23 +4161,23 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3719
4161
|
|
|
3720
4162
|
switch (action) {
|
|
3721
4163
|
case 'addTblRowA': // Insert row above
|
|
3722
|
-
|
|
4164
|
+
// log(`${funcName}: Inserting row above row ${targetRowIndex}`);
|
|
3723
4165
|
success = addTableRowAboveWithText(tableLines, targetRowIndex, numCols, lastClick.tblId, ed, docManager);
|
|
3724
4166
|
break;
|
|
3725
4167
|
|
|
3726
4168
|
case 'addTblRowB': // Insert row below
|
|
3727
|
-
|
|
4169
|
+
// log(`${funcName}: Inserting row below row ${targetRowIndex}`);
|
|
3728
4170
|
success = addTableRowBelowWithText(tableLines, targetRowIndex, numCols, lastClick.tblId, ed, docManager);
|
|
3729
4171
|
break;
|
|
3730
4172
|
|
|
3731
4173
|
case 'addTblColL': // Insert column left
|
|
3732
|
-
|
|
4174
|
+
// log(`${funcName}: Inserting column left of column ${targetColIndex}`);
|
|
3733
4175
|
newNumCols = numCols + 1;
|
|
3734
4176
|
success = addTableColumnLeftWithText(tableLines, targetColIndex, ed, docManager);
|
|
3735
4177
|
break;
|
|
3736
4178
|
|
|
3737
4179
|
case 'addTblColR': // Insert column right
|
|
3738
|
-
|
|
4180
|
+
// log(`${funcName}: Inserting column right of column ${targetColIndex}`);
|
|
3739
4181
|
newNumCols = numCols + 1;
|
|
3740
4182
|
success = addTableColumnRightWithText(tableLines, targetColIndex, ed, docManager);
|
|
3741
4183
|
break;
|
|
@@ -3744,10 +4186,10 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3744
4186
|
// Show confirmation prompt for row deletion
|
|
3745
4187
|
const rowConfirmMessage = `Are you sure you want to delete Row ${targetRowIndex + 1} and all content within?`;
|
|
3746
4188
|
if (!confirm(rowConfirmMessage)) {
|
|
3747
|
-
|
|
4189
|
+
// log(`${funcName}: Row deletion cancelled by user`);
|
|
3748
4190
|
return;
|
|
3749
4191
|
}
|
|
3750
|
-
|
|
4192
|
+
// log(`${funcName}: Deleting row ${targetRowIndex}`);
|
|
3751
4193
|
success = deleteTableRowWithText(tableLines, targetRowIndex, ed, docManager);
|
|
3752
4194
|
break;
|
|
3753
4195
|
|
|
@@ -3755,16 +4197,16 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3755
4197
|
// Show confirmation prompt for column deletion
|
|
3756
4198
|
const colConfirmMessage = `Are you sure you want to delete Column ${targetColIndex + 1} and all content within?`;
|
|
3757
4199
|
if (!confirm(colConfirmMessage)) {
|
|
3758
|
-
|
|
4200
|
+
// log(`${funcName}: Column deletion cancelled by user`);
|
|
3759
4201
|
return;
|
|
3760
4202
|
}
|
|
3761
|
-
|
|
4203
|
+
// log(`${funcName}: Deleting column ${targetColIndex}`);
|
|
3762
4204
|
newNumCols = numCols - 1;
|
|
3763
4205
|
success = deleteTableColumnWithText(tableLines, targetColIndex, ed, docManager);
|
|
3764
4206
|
break;
|
|
3765
4207
|
|
|
3766
4208
|
default:
|
|
3767
|
-
|
|
4209
|
+
// log(`${funcName}: Unknown action: ${action}`);
|
|
3768
4210
|
return;
|
|
3769
4211
|
}
|
|
3770
4212
|
|
|
@@ -3773,11 +4215,11 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3773
4215
|
return;
|
|
3774
4216
|
}
|
|
3775
4217
|
|
|
3776
|
-
|
|
4218
|
+
// log(`${funcName}: Table operation completed successfully with text and metadata synchronization`);
|
|
3777
4219
|
|
|
3778
4220
|
} catch (error) {
|
|
3779
4221
|
console.error(`[ep_data_tables] ${funcName}: Error during table operation:`, error);
|
|
3780
|
-
|
|
4222
|
+
// log(`${funcName}: Error details:`, { message: error.message, stack: error.stack });
|
|
3781
4223
|
}
|
|
3782
4224
|
};
|
|
3783
4225
|
|
|
@@ -3830,7 +4272,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3830
4272
|
columnWidths.push(100 / numCols);
|
|
3831
4273
|
}
|
|
3832
4274
|
});
|
|
3833
|
-
|
|
4275
|
+
// log('[ep_data_tables] addTableRowAbove: Extracted column widths from DOM:', columnWidths);
|
|
3834
4276
|
}
|
|
3835
4277
|
}
|
|
3836
4278
|
}
|
|
@@ -3908,7 +4350,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3908
4350
|
columnWidths.push(100 / numCols);
|
|
3909
4351
|
}
|
|
3910
4352
|
});
|
|
3911
|
-
|
|
4353
|
+
// log('[ep_data_tables] addTableRowBelow: Extracted column widths from DOM:', columnWidths);
|
|
3912
4354
|
}
|
|
3913
4355
|
}
|
|
3914
4356
|
}
|
|
@@ -3973,7 +4415,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3973
4415
|
if (cellContent.length > 0) { // Only apply to non-empty cells
|
|
3974
4416
|
const cellStart = [tableLine.lineIndex, offset];
|
|
3975
4417
|
const cellEnd = [tableLine.lineIndex, offset + cellContent.length];
|
|
3976
|
-
|
|
4418
|
+
// log(`[ep_data_tables] ${funcName}: Applying ${ATTR_CELL} attribute to Line ${tableLine.lineIndex} Col ${c} Range ${offset}-${offset + cellContent.length}`);
|
|
3977
4419
|
editorInfo.ace_performDocumentApplyAttributesToRange(cellStart, cellEnd, [[ATTR_CELL, String(c)]]);
|
|
3978
4420
|
}
|
|
3979
4421
|
offset += cellContent.length;
|
|
@@ -3988,7 +4430,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
3988
4430
|
const newColCount = tableLine.cols + 1;
|
|
3989
4431
|
const equalWidth = 100 / newColCount;
|
|
3990
4432
|
const normalizedWidths = Array(newColCount).fill(equalWidth);
|
|
3991
|
-
|
|
4433
|
+
// log(`[ep_data_tables] addTableColumnLeft: Reset all column widths to equal distribution: ${newColCount} columns at ${equalWidth.toFixed(1)}% each`);
|
|
3992
4434
|
|
|
3993
4435
|
// Apply updated metadata
|
|
3994
4436
|
const newMetadata = { ...tableLine.metadata, cols: tableLine.cols + 1, columnWidths: normalizedWidths };
|
|
@@ -4040,7 +4482,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
4040
4482
|
if (cellContent.length > 0) { // Only apply to non-empty cells
|
|
4041
4483
|
const cellStart = [tableLine.lineIndex, offset];
|
|
4042
4484
|
const cellEnd = [tableLine.lineIndex, offset + cellContent.length];
|
|
4043
|
-
|
|
4485
|
+
// log(`[ep_data_tables] ${funcName}: Applying ${ATTR_CELL} attribute to Line ${tableLine.lineIndex} Col ${c} Range ${offset}-${offset + cellContent.length}`);
|
|
4044
4486
|
editorInfo.ace_performDocumentApplyAttributesToRange(cellStart, cellEnd, [[ATTR_CELL, String(c)]]);
|
|
4045
4487
|
}
|
|
4046
4488
|
offset += cellContent.length;
|
|
@@ -4055,7 +4497,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
4055
4497
|
const newColCount = tableLine.cols + 1;
|
|
4056
4498
|
const equalWidth = 100 / newColCount;
|
|
4057
4499
|
const normalizedWidths = Array(newColCount).fill(equalWidth);
|
|
4058
|
-
|
|
4500
|
+
// log(`[ep_data_tables] addTableColumnRight: Reset all column widths to equal distribution: ${newColCount} columns at ${equalWidth.toFixed(1)}% each`);
|
|
4059
4501
|
|
|
4060
4502
|
// Apply updated metadata
|
|
4061
4503
|
const newMetadata = { ...tableLine.metadata, cols: tableLine.cols + 1, columnWidths: normalizedWidths };
|
|
@@ -4078,7 +4520,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
4078
4520
|
// Special handling for deleting the first row (row index 0)
|
|
4079
4521
|
// Insert a blank line to prevent the table from getting stuck at line 1
|
|
4080
4522
|
if (targetRowIndex === 0) {
|
|
4081
|
-
|
|
4523
|
+
// log('[ep_data_tables] Deleting first row (row 0) - inserting blank line to prevent table from getting stuck');
|
|
4082
4524
|
const insertStart = [targetLine.lineIndex, 0];
|
|
4083
4525
|
editorInfo.ace_performDocumentReplaceRange(insertStart, insertStart, '\n');
|
|
4084
4526
|
|
|
@@ -4118,7 +4560,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
4118
4560
|
columnWidths.push(100 / targetLine.metadata.cols);
|
|
4119
4561
|
}
|
|
4120
4562
|
});
|
|
4121
|
-
|
|
4563
|
+
// log('[ep_data_tables] deleteTableRow: Extracted column widths from DOM:', columnWidths);
|
|
4122
4564
|
break;
|
|
4123
4565
|
}
|
|
4124
4566
|
}
|
|
@@ -4146,7 +4588,6 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
4146
4588
|
return false;
|
|
4147
4589
|
}
|
|
4148
4590
|
}
|
|
4149
|
-
|
|
4150
4591
|
function deleteTableColumnWithText(tableLines, targetColIndex, editorInfo, docManager) {
|
|
4151
4592
|
try {
|
|
4152
4593
|
// Update text content for all table lines using precise character deletion
|
|
@@ -4155,7 +4596,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
4155
4596
|
const cells = lineText.split(DELIMITER);
|
|
4156
4597
|
|
|
4157
4598
|
if (targetColIndex >= cells.length) {
|
|
4158
|
-
|
|
4599
|
+
// log(`[ep_data_tables] Warning: Target column ${targetColIndex} doesn't exist in line with ${cells.length} columns`);
|
|
4159
4600
|
continue;
|
|
4160
4601
|
}
|
|
4161
4602
|
|
|
@@ -4180,7 +4621,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
4180
4621
|
deleteStart -= DELIMITER.length;
|
|
4181
4622
|
}
|
|
4182
4623
|
|
|
4183
|
-
|
|
4624
|
+
// log(`[ep_data_tables] Deleting column ${targetColIndex} from line ${tableLine.lineIndex}: chars ${deleteStart}-${deleteEnd} from "${lineText}"`);
|
|
4184
4625
|
|
|
4185
4626
|
// Perform the precise deletion
|
|
4186
4627
|
const rangeStart = [tableLine.lineIndex, deleteStart];
|
|
@@ -4194,7 +4635,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
4194
4635
|
if (newColCount > 0) {
|
|
4195
4636
|
const equalWidth = 100 / newColCount;
|
|
4196
4637
|
const normalizedWidths = Array(newColCount).fill(equalWidth);
|
|
4197
|
-
|
|
4638
|
+
// log(`[ep_data_tables] deleteTableColumn: Reset all column widths to equal distribution: ${newColCount} columns at ${equalWidth.toFixed(1)}% each`);
|
|
4198
4639
|
|
|
4199
4640
|
// Update metadata
|
|
4200
4641
|
const newMetadata = { ...tableLine.metadata, cols: newColCount, columnWidths: normalizedWidths };
|
|
@@ -4212,7 +4653,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
4212
4653
|
|
|
4213
4654
|
// ... existing code ...
|
|
4214
4655
|
|
|
4215
|
-
|
|
4656
|
+
// log('aceInitialized: END - helpers defined.');
|
|
4216
4657
|
};
|
|
4217
4658
|
|
|
4218
4659
|
// ───────────────────── required no‑op stubs ─────────────────────
|
|
@@ -4222,11 +4663,11 @@ exports.aceEndLineAndCharForPoint = () => { return undefined; };
|
|
|
4222
4663
|
// NEW: Style protection for table cells
|
|
4223
4664
|
exports.aceSetAuthorStyle = (hook, ctx) => {
|
|
4224
4665
|
const logPrefix = '[ep_data_tables:aceSetAuthorStyle]';
|
|
4225
|
-
|
|
4666
|
+
// log(`${logPrefix} START`, { hook, ctx });
|
|
4226
4667
|
|
|
4227
4668
|
// If no selection or no style to apply, allow default
|
|
4228
4669
|
if (!ctx || !ctx.rep || !ctx.rep.selStart || !ctx.rep.selEnd || !ctx.key) {
|
|
4229
|
-
|
|
4670
|
+
// log(`${logPrefix} No selection or style key. Allowing default.`);
|
|
4230
4671
|
return;
|
|
4231
4672
|
}
|
|
4232
4673
|
|
|
@@ -4236,14 +4677,14 @@ exports.aceSetAuthorStyle = (hook, ctx) => {
|
|
|
4236
4677
|
|
|
4237
4678
|
// If selection spans multiple lines, prevent style application
|
|
4238
4679
|
if (startLine !== endLine) {
|
|
4239
|
-
|
|
4680
|
+
// log(`${logPrefix} Selection spans multiple lines. Preventing style application to protect table structure.`);
|
|
4240
4681
|
return false;
|
|
4241
4682
|
}
|
|
4242
4683
|
|
|
4243
4684
|
// Check if the line is a table line
|
|
4244
4685
|
const lineAttrString = ctx.documentAttributeManager?.getAttributeOnLine(startLine, ATTR_TABLE_JSON);
|
|
4245
4686
|
if (!lineAttrString) {
|
|
4246
|
-
|
|
4687
|
+
// log(`${logPrefix} Line ${startLine} is not a table line. Allowing default style application.`);
|
|
4247
4688
|
return;
|
|
4248
4689
|
}
|
|
4249
4690
|
|
|
@@ -4254,7 +4695,7 @@ exports.aceSetAuthorStyle = (hook, ctx) => {
|
|
|
4254
4695
|
];
|
|
4255
4696
|
|
|
4256
4697
|
if (BLOCKED_STYLES.includes(ctx.key)) {
|
|
4257
|
-
|
|
4698
|
+
// log(`${logPrefix} Blocked potentially harmful style '${ctx.key}' from being applied to table cell.`);
|
|
4258
4699
|
return false;
|
|
4259
4700
|
}
|
|
4260
4701
|
|
|
@@ -4262,7 +4703,7 @@ exports.aceSetAuthorStyle = (hook, ctx) => {
|
|
|
4262
4703
|
try {
|
|
4263
4704
|
const tableMetadata = JSON.parse(lineAttrString);
|
|
4264
4705
|
if (!tableMetadata || typeof tableMetadata.cols !== 'number') {
|
|
4265
|
-
|
|
4706
|
+
// log(`${logPrefix} Invalid table metadata. Preventing style application.`);
|
|
4266
4707
|
return false;
|
|
4267
4708
|
}
|
|
4268
4709
|
|
|
@@ -4288,7 +4729,7 @@ exports.aceSetAuthorStyle = (hook, ctx) => {
|
|
|
4288
4729
|
|
|
4289
4730
|
// If selection spans multiple cells, prevent style application
|
|
4290
4731
|
if (selectionStartCell !== selectionEndCell) {
|
|
4291
|
-
|
|
4732
|
+
// log(`${logPrefix} Selection spans multiple cells. Preventing style application to protect table structure.`);
|
|
4292
4733
|
return false;
|
|
4293
4734
|
}
|
|
4294
4735
|
|
|
@@ -4297,15 +4738,15 @@ exports.aceSetAuthorStyle = (hook, ctx) => {
|
|
|
4297
4738
|
const cellEndCol = cellStartCol + cells[selectionStartCell].length;
|
|
4298
4739
|
|
|
4299
4740
|
if (ctx.rep.selStart[1] <= cellStartCol || ctx.rep.selEnd[1] >= cellEndCol) {
|
|
4300
|
-
|
|
4741
|
+
// log(`${logPrefix} Selection includes cell delimiters. Preventing style application to protect table structure.`);
|
|
4301
4742
|
return false;
|
|
4302
4743
|
}
|
|
4303
4744
|
|
|
4304
|
-
|
|
4745
|
+
// log(`${logPrefix} Style '${ctx.key}' allowed within cell boundaries.`);
|
|
4305
4746
|
return; // Allow the style to be applied
|
|
4306
4747
|
} catch (e) {
|
|
4307
4748
|
console.error(`${logPrefix} Error processing style application:`, e);
|
|
4308
|
-
|
|
4749
|
+
// log(`${logPrefix} Error details:`, { message: e.message, stack: e.stack });
|
|
4309
4750
|
return false; // Prevent style application on error
|
|
4310
4751
|
}
|
|
4311
4752
|
};
|
|
@@ -4322,7 +4763,7 @@ exports.aceRegisterBlockElements = () => ['table'];
|
|
|
4322
4763
|
// NEW: Column resize helper functions (adapted from images plugin)
|
|
4323
4764
|
const startColumnResize = (table, columnIndex, startX, metadata, lineNum) => {
|
|
4324
4765
|
const funcName = 'startColumnResize';
|
|
4325
|
-
|
|
4766
|
+
// log(`${funcName}: Starting resize for column ${columnIndex}`);
|
|
4326
4767
|
|
|
4327
4768
|
isResizing = true;
|
|
4328
4769
|
resizeStartX = startX;
|
|
@@ -4336,7 +4777,7 @@ const startColumnResize = (table, columnIndex, startX, metadata, lineNum) => {
|
|
|
4336
4777
|
const numCols = metadata.cols;
|
|
4337
4778
|
resizeOriginalWidths = metadata.columnWidths ? [...metadata.columnWidths] : Array(numCols).fill(100 / numCols);
|
|
4338
4779
|
|
|
4339
|
-
|
|
4780
|
+
// log(`${funcName}: Original widths:`, resizeOriginalWidths);
|
|
4340
4781
|
|
|
4341
4782
|
// Create visual overlay instead of modifying table directly
|
|
4342
4783
|
createResizeOverlay(table, columnIndex);
|
|
@@ -4402,7 +4843,7 @@ const createResizeOverlay = (table, columnIndex) => {
|
|
|
4402
4843
|
|
|
4403
4844
|
const totalTableHeight = maxBottom - minTop;
|
|
4404
4845
|
|
|
4405
|
-
|
|
4846
|
+
// log(`createResizeOverlay: Found ${allTableRows.length} table rows, total height: ${totalTableHeight}px`);
|
|
4406
4847
|
|
|
4407
4848
|
// Calculate positioning using the same method as image plugin
|
|
4408
4849
|
let innerBodyRect, innerIframeRect, outerBodyRect;
|
|
@@ -4489,7 +4930,7 @@ const createResizeOverlay = (table, columnIndex) => {
|
|
|
4489
4930
|
// Append to outer body like image plugin does with its outline
|
|
4490
4931
|
padOuter.append(resizeOverlay);
|
|
4491
4932
|
|
|
4492
|
-
|
|
4933
|
+
// log('createResizeOverlay: Created Google Docs style blue line overlay spanning entire table height');
|
|
4493
4934
|
};
|
|
4494
4935
|
|
|
4495
4936
|
const updateColumnResize = (currentX) => {
|
|
@@ -4549,19 +4990,19 @@ const updateColumnResize = (currentX) => {
|
|
|
4549
4990
|
|
|
4550
4991
|
const finishColumnResize = (editorInfo, docManager) => {
|
|
4551
4992
|
if (!isResizing || !resizeTargetTable) {
|
|
4552
|
-
|
|
4993
|
+
// log('finishColumnResize: Not in resize mode');
|
|
4553
4994
|
return;
|
|
4554
4995
|
}
|
|
4555
4996
|
|
|
4556
4997
|
const funcName = 'finishColumnResize';
|
|
4557
|
-
|
|
4998
|
+
// log(`${funcName}: Finishing resize`);
|
|
4558
4999
|
|
|
4559
5000
|
// Calculate final widths from actual mouse movement
|
|
4560
5001
|
const tableRect = resizeTargetTable.getBoundingClientRect();
|
|
4561
5002
|
const deltaX = resizeCurrentX - resizeStartX;
|
|
4562
5003
|
const deltaPercent = (deltaX / tableRect.width) * 100;
|
|
4563
5004
|
|
|
4564
|
-
|
|
5005
|
+
// log(`${funcName}: Mouse moved ${deltaX}px (${deltaPercent.toFixed(1)}%)`);
|
|
4565
5006
|
|
|
4566
5007
|
const finalWidths = [...resizeOriginalWidths];
|
|
4567
5008
|
const currentColumn = resizeTargetColumn;
|
|
@@ -4575,7 +5016,7 @@ const finishColumnResize = (editorInfo, docManager) => {
|
|
|
4575
5016
|
finalWidths[currentColumn] += actualTransfer;
|
|
4576
5017
|
finalWidths[nextColumn] -= actualTransfer;
|
|
4577
5018
|
|
|
4578
|
-
|
|
5019
|
+
// log(`${funcName}: Transferred ${actualTransfer.toFixed(1)}% from column ${nextColumn} to column ${currentColumn}`);
|
|
4579
5020
|
}
|
|
4580
5021
|
|
|
4581
5022
|
// Normalize widths
|
|
@@ -4586,7 +5027,7 @@ const finishColumnResize = (editorInfo, docManager) => {
|
|
|
4586
5027
|
});
|
|
4587
5028
|
}
|
|
4588
5029
|
|
|
4589
|
-
|
|
5030
|
+
// log(`${funcName}: Final normalized widths:`, finalWidths.map(w => w.toFixed(1) + '%'));
|
|
4590
5031
|
|
|
4591
5032
|
// Clean up overlay
|
|
4592
5033
|
if (resizeOverlay) {
|
|
@@ -4606,7 +5047,7 @@ const finishColumnResize = (editorInfo, docManager) => {
|
|
|
4606
5047
|
// Apply updated metadata to ALL rows in the table (not just the resized row)
|
|
4607
5048
|
editorInfo.ace_callWithAce((ace) => {
|
|
4608
5049
|
const callWithAceLogPrefix = `${funcName}[ace_callWithAce]`;
|
|
4609
|
-
|
|
5050
|
+
// log(`${callWithAceLogPrefix}: Finding and updating all table rows with tblId: ${resizeTableMetadata.tblId}`);
|
|
4610
5051
|
|
|
4611
5052
|
try {
|
|
4612
5053
|
const rep = ace.ace_getRep();
|
|
@@ -4663,7 +5104,7 @@ const finishColumnResize = (editorInfo, docManager) => {
|
|
|
4663
5104
|
cols: domCells.length,
|
|
4664
5105
|
columnWidths: columnWidths
|
|
4665
5106
|
};
|
|
4666
|
-
|
|
5107
|
+
// log(`${callWithAceLogPrefix}: Reconstructed metadata from DOM for line ${lineIndex}:`, reconstructedMetadata);
|
|
4667
5108
|
tableLines.push({
|
|
4668
5109
|
lineIndex,
|
|
4669
5110
|
metadata: reconstructedMetadata
|
|
@@ -4678,7 +5119,7 @@ const finishColumnResize = (editorInfo, docManager) => {
|
|
|
4678
5119
|
}
|
|
4679
5120
|
}
|
|
4680
5121
|
|
|
4681
|
-
|
|
5122
|
+
// log(`${callWithAceLogPrefix}: Found ${tableLines.length} table lines to update`);
|
|
4682
5123
|
|
|
4683
5124
|
// Update all table lines with new column widths
|
|
4684
5125
|
for (const tableLine of tableLines) {
|
|
@@ -4696,7 +5137,7 @@ const finishColumnResize = (editorInfo, docManager) => {
|
|
|
4696
5137
|
const rangeStart = [tableLine.lineIndex, 0];
|
|
4697
5138
|
const rangeEnd = [tableLine.lineIndex, lineLength];
|
|
4698
5139
|
|
|
4699
|
-
|
|
5140
|
+
// log(`${callWithAceLogPrefix}: Updating line ${tableLine.lineIndex} (row ${tableLine.metadata.row}) with new column widths`);
|
|
4700
5141
|
|
|
4701
5142
|
// Apply the updated metadata attribute directly
|
|
4702
5143
|
ace.ace_performDocumentApplyAttributesToRange(rangeStart, rangeEnd, [
|
|
@@ -4704,15 +5145,15 @@ const finishColumnResize = (editorInfo, docManager) => {
|
|
|
4704
5145
|
]);
|
|
4705
5146
|
}
|
|
4706
5147
|
|
|
4707
|
-
|
|
5148
|
+
// log(`${callWithAceLogPrefix}: Successfully applied updated column widths to all ${tableLines.length} table rows`);
|
|
4708
5149
|
|
|
4709
5150
|
} catch (error) {
|
|
4710
5151
|
console.error(`${callWithAceLogPrefix}: Error applying updated metadata:`, error);
|
|
4711
|
-
|
|
5152
|
+
// log(`${callWithAceLogPrefix}: Error details:`, { message: error.message, stack: error.stack });
|
|
4712
5153
|
}
|
|
4713
5154
|
}, 'applyTableResizeToAllRows', true);
|
|
4714
5155
|
|
|
4715
|
-
|
|
5156
|
+
// log(`${funcName}: Column width update initiated for all table rows via ace_callWithAce`);
|
|
4716
5157
|
|
|
4717
5158
|
// Reset state
|
|
4718
5159
|
resizeStartX = 0;
|
|
@@ -4723,16 +5164,16 @@ const finishColumnResize = (editorInfo, docManager) => {
|
|
|
4723
5164
|
resizeTableMetadata = null;
|
|
4724
5165
|
resizeLineNum = -1;
|
|
4725
5166
|
|
|
4726
|
-
|
|
5167
|
+
// log(`${funcName}: Resize complete - state reset`);
|
|
4727
5168
|
};
|
|
4728
5169
|
|
|
4729
5170
|
// NEW: Undo/Redo protection
|
|
4730
5171
|
exports.aceUndoRedo = (hook, ctx) => {
|
|
4731
5172
|
const logPrefix = '[ep_data_tables:aceUndoRedo]';
|
|
4732
|
-
|
|
5173
|
+
// log(`${logPrefix} START`, { hook, ctx });
|
|
4733
5174
|
|
|
4734
5175
|
if (!ctx || !ctx.rep || !ctx.rep.selStart || !ctx.rep.selEnd) {
|
|
4735
|
-
|
|
5176
|
+
// log(`${logPrefix} No selection or context. Allowing default.`);
|
|
4736
5177
|
return;
|
|
4737
5178
|
}
|
|
4738
5179
|
|
|
@@ -4753,11 +5194,11 @@ exports.aceUndoRedo = (hook, ctx) => {
|
|
|
4753
5194
|
}
|
|
4754
5195
|
|
|
4755
5196
|
if (!hasTableLines) {
|
|
4756
|
-
|
|
5197
|
+
// log(`${logPrefix} No table lines affected. Allowing default undo/redo.`);
|
|
4757
5198
|
return;
|
|
4758
5199
|
}
|
|
4759
5200
|
|
|
4760
|
-
|
|
5201
|
+
// log(`${logPrefix} Table lines affected:`, { tableLines });
|
|
4761
5202
|
|
|
4762
5203
|
// Validate table structure after undo/redo
|
|
4763
5204
|
try {
|
|
@@ -4767,7 +5208,7 @@ exports.aceUndoRedo = (hook, ctx) => {
|
|
|
4767
5208
|
|
|
4768
5209
|
const tableMetadata = JSON.parse(lineAttrString);
|
|
4769
5210
|
if (!tableMetadata || typeof tableMetadata.cols !== 'number') {
|
|
4770
|
-
|
|
5211
|
+
// log(`${logPrefix} Invalid table metadata after undo/redo. Attempting recovery.`);
|
|
4771
5212
|
// Attempt to recover table structure
|
|
4772
5213
|
const lineText = ctx.rep.lines.atIndex(line)?.text || '';
|
|
4773
5214
|
const cells = lineText.split(DELIMITER);
|
|
@@ -4782,16 +5223,16 @@ exports.aceUndoRedo = (hook, ctx) => {
|
|
|
4782
5223
|
|
|
4783
5224
|
// Apply the recovered metadata
|
|
4784
5225
|
ctx.documentAttributeManager.setAttributeOnLine(line, ATTR_TABLE_JSON, JSON.stringify(newMetadata));
|
|
4785
|
-
|
|
5226
|
+
// log(`${logPrefix} Recovered table structure for line ${line}`);
|
|
4786
5227
|
} else {
|
|
4787
5228
|
// If we can't recover, remove the table attribute
|
|
4788
5229
|
ctx.documentAttributeManager.removeAttributeOnLine(line, ATTR_TABLE_JSON);
|
|
4789
|
-
|
|
5230
|
+
// log(`${logPrefix} Removed invalid table attribute from line ${line}`);
|
|
4790
5231
|
}
|
|
4791
5232
|
}
|
|
4792
5233
|
}
|
|
4793
5234
|
} catch (e) {
|
|
4794
5235
|
console.error(`${logPrefix} Error during undo/redo validation:`, e);
|
|
4795
|
-
|
|
5236
|
+
// log(`${logPrefix} Error details:`, { message: e.message, stack: e.stack });
|
|
4796
5237
|
}
|
|
4797
5238
|
};
|