ep_data_tables 0.0.2 → 0.0.3
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/collectContentPre.js +4 -0
- package/ep.json +0 -2
- package/package.json +1 -1
- package/static/js/client_hooks.js +206 -290
- package/static/js/collector_hooks.js +0 -13
package/collectContentPre.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// Ensure settings.toolbar exists early to avoid load-order error with ep_font_color
|
|
4
|
+
const settings = require('ep_etherpad-lite/node/utils/Settings');
|
|
5
|
+
if (!settings.toolbar) settings.toolbar = {};
|
|
6
|
+
|
|
3
7
|
// Using console.log for server-side logging, similar to other Etherpad server-side files.
|
|
4
8
|
const log = (...m) => console.log('[ep_data_tables:collectContentPre_SERVER]', ...m);
|
|
5
9
|
|
package/ep.json
CHANGED
|
@@ -10,8 +10,6 @@
|
|
|
10
10
|
},
|
|
11
11
|
"client_hooks": {
|
|
12
12
|
"collectContentPre" : "ep_data_tables/static/js/client_hooks:collectContentPre",
|
|
13
|
-
"collectContentLineBreak": "ep_data_tables/static/js/collector_hooks:collectContentLineBreak",
|
|
14
|
-
"collectContentLineText" : "ep_data_tables/static/js/collector_hooks:collectContentLineText",
|
|
15
13
|
"aceKeyEvent" : "ep_data_tables/static/js/client_hooks:aceKeyEvent",
|
|
16
14
|
"aceStartLineAndCharForPoint" : "ep_data_tables/static/js/client_hooks:aceStartLineAndCharForPoint",
|
|
17
15
|
"aceEndLineAndCharForPoint" : "ep_data_tables/static/js/client_hooks:aceEndLineAndCharForPoint",
|
package/package.json
CHANGED
|
@@ -472,6 +472,12 @@ exports.collectContentPre = (hook, ctx) => {
|
|
|
472
472
|
// the span would be mistaken for a real cell boundary.
|
|
473
473
|
const hiddenDelimRegexPrimary = /<span class="ep-data_tables-delim"[^>]*>.*?<\/span>/ig;
|
|
474
474
|
segmentHTML = segmentHTML.replace(hiddenDelimRegexPrimary, '');
|
|
475
|
+
// Remove caret-anchor spans (invisible, non-semantic)
|
|
476
|
+
const caretAnchorRegex = /<span class="ep-data_tables-caret-anchor"[^>]*><\/span>/ig;
|
|
477
|
+
segmentHTML = segmentHTML.replace(caretAnchorRegex, '');
|
|
478
|
+
// If, after stripping tags/entities, the content is empty, serialize as empty string
|
|
479
|
+
const textCheck = segmentHTML.replace(/<[^>]*>/g, '').replace(/ /ig, ' ').trim();
|
|
480
|
+
if (textCheck === '') segmentHTML = '';
|
|
475
481
|
|
|
476
482
|
const hidden = index === 0 ? '' :
|
|
477
483
|
/* keep the char in the DOM but make it visually disappear and non-editable */
|
|
@@ -482,7 +488,7 @@ exports.collectContentPre = (hook, ctx) => {
|
|
|
482
488
|
|
|
483
489
|
if (cellHTMLSegments.length !== existingMetadata.cols) {
|
|
484
490
|
// log(`${funcName}: WARNING Line ${lineNum}: Reconstructed cell count (${cellHTMLSegments.length}) does not match metadata cols (${existingMetadata.cols}). Padding/truncating.`);
|
|
485
|
-
while (cellHTMLSegments.length < existingMetadata.cols) cellHTMLSegments.push('
|
|
491
|
+
while (cellHTMLSegments.length < existingMetadata.cols) cellHTMLSegments.push('');
|
|
486
492
|
if (cellHTMLSegments.length > existingMetadata.cols) cellHTMLSegments.length = existingMetadata.cols;
|
|
487
493
|
}
|
|
488
494
|
|
|
@@ -520,15 +526,21 @@ exports.collectContentPre = (hook, ctx) => {
|
|
|
520
526
|
const resizeHandleRegex = /<div class="ep-data_tables-resize-handle"[^>]*><\/div>/ig;
|
|
521
527
|
segmentHTML = segmentHTML.replace(resizeHandleRegex, '');
|
|
522
528
|
if (index > 0) {
|
|
523
|
-
const hiddenDelimRegex = new RegExp(
|
|
529
|
+
const hiddenDelimRegex = new RegExp('^<span class="ep-data_tables-delim" contenteditable="false">' + DELIMITER + '(<\\/span>)?<\\/span>', 'i');
|
|
524
530
|
segmentHTML = segmentHTML.replace(hiddenDelimRegex, '');
|
|
525
531
|
}
|
|
532
|
+
// Remove caret-anchor spans (invisible, non-semantic)
|
|
533
|
+
const caretAnchorRegex = /<span class="ep-data_tables-caret-anchor"[^>]*><\/span>/ig;
|
|
534
|
+
segmentHTML = segmentHTML.replace(caretAnchorRegex, '');
|
|
535
|
+
// If, after stripping tags/entities, the content is empty, serialize as empty string
|
|
536
|
+
const textCheck = segmentHTML.replace(/<[^>]*>/g, '').replace(/ /ig, ' ').trim();
|
|
537
|
+
if (textCheck === '') segmentHTML = '';
|
|
526
538
|
return segmentHTML;
|
|
527
539
|
});
|
|
528
540
|
|
|
529
541
|
if (cellHTMLSegments.length !== domCols) {
|
|
530
542
|
// log(`${funcName}: WARNING Line ${lineNum} (Fallback): Reconstructed cell count (${cellHTMLSegments.length}) does not match DOM cols (${domCols}).`);
|
|
531
|
-
while(cellHTMLSegments.length < domCols) cellHTMLSegments.push('
|
|
543
|
+
while(cellHTMLSegments.length < domCols) cellHTMLSegments.push('');
|
|
532
544
|
if(cellHTMLSegments.length > domCols) cellHTMLSegments.length = domCols;
|
|
533
545
|
}
|
|
534
546
|
|
|
@@ -672,24 +684,41 @@ function buildTableFromDelimitedHTML(metadata, innerHTMLSegments) {
|
|
|
672
684
|
// Basic styling - can be moved to CSS later
|
|
673
685
|
const tdStyle = `padding: 5px 7px; word-wrap:break-word; vertical-align: top; border: 1px solid #000; position: relative;`; // Added position: relative
|
|
674
686
|
|
|
687
|
+
// Precompute encoded tbljson class so empty cells can carry the same marker
|
|
688
|
+
let encodedTbljsonClass = '';
|
|
689
|
+
try {
|
|
690
|
+
encodedTbljsonClass = `tbljson-${enc(JSON.stringify(metadata))}`;
|
|
691
|
+
} catch (_) { encodedTbljsonClass = ''; }
|
|
692
|
+
|
|
675
693
|
// Map the HTML segments directly into TD elements with column widths
|
|
676
694
|
const cellsHtml = innerHTMLSegments.map((segment, index) => {
|
|
677
695
|
// Build the hidden delimiter *inside* the first author span so the caret
|
|
678
|
-
// cannot sit between delimiter and text.
|
|
679
|
-
|
|
696
|
+
// cannot sit between delimiter and text. For empty cells, synthesize a span
|
|
697
|
+
// that carries tbljson and tblCell-N so caret anchoring remains stable.
|
|
698
|
+
const textOnly = (segment || '').replace(/<[^>]*>/g, '').replace(/ /ig, ' ').trim();
|
|
699
|
+
let modifiedSegment = segment || '';
|
|
700
|
+
const isEmpty = !segment || textOnly === '';
|
|
701
|
+
if (isEmpty) {
|
|
702
|
+
const cellClass = encodedTbljsonClass ? `${encodedTbljsonClass} tblCell-${index}` : `tblCell-${index}`;
|
|
703
|
+
modifiedSegment = `<span class="${cellClass}"> </span>`;
|
|
704
|
+
}
|
|
680
705
|
if (index > 0) {
|
|
681
706
|
const delimSpan = `<span class="ep-data_tables-delim" contenteditable="false">${HIDDEN_DELIM}</span>`;
|
|
682
707
|
// If the rendered segment already starts with a <span …> (which will be
|
|
683
708
|
// the usual author-colour wrapper) inject the delimiter right after that
|
|
684
709
|
// opening tag; otherwise just prefix it.
|
|
685
710
|
modifiedSegment = modifiedSegment.replace(/^(<span[^>]*>)/i, `$1${delimSpan}`);
|
|
686
|
-
if (modifiedSegment
|
|
711
|
+
if (!/^<span[^>]*>/i.test(modifiedSegment)) modifiedSegment = `${delimSpan}${modifiedSegment}`;
|
|
687
712
|
}
|
|
688
713
|
|
|
689
|
-
// --- NEW: Always embed the invisible caret-anchor as *last* child *within* the first
|
|
714
|
+
// --- NEW: Always embed the invisible caret-anchor as *last* child *within* the first span ---
|
|
690
715
|
const caretAnchorSpan = '<span class="ep-data_tables-caret-anchor" contenteditable="false"></span>';
|
|
691
716
|
const anchorInjected = modifiedSegment.replace(/<\/span>\s*$/i, `${caretAnchorSpan}</span>`);
|
|
692
|
-
modifiedSegment = (anchorInjected !== modifiedSegment)
|
|
717
|
+
modifiedSegment = (anchorInjected !== modifiedSegment)
|
|
718
|
+
? anchorInjected
|
|
719
|
+
: (isEmpty
|
|
720
|
+
? `<span class="${encodedTbljsonClass ? `${encodedTbljsonClass} ` : ''}tblCell-${index}">${modifiedSegment}${caretAnchorSpan}</span>`
|
|
721
|
+
: `${modifiedSegment}${caretAnchorSpan}`);
|
|
693
722
|
|
|
694
723
|
// Width & other decorations remain unchanged
|
|
695
724
|
const widthPercent = columnWidths[index] || (100 / numCols);
|
|
@@ -922,7 +951,10 @@ exports.acePostWriteDomLineHTML = function (hook_name, args, cb) {
|
|
|
922
951
|
// the embedded delimiter character they carry doesn't inflate or shrink
|
|
923
952
|
// the segment count.
|
|
924
953
|
const spanDelimRegex = new RegExp('<span class="ep-data_tables-delim"[^>]*>' + DELIMITER + '<\\/span>', 'ig');
|
|
925
|
-
const sanitizedHTMLForSplit = (delimitedTextFromLine || '')
|
|
954
|
+
const sanitizedHTMLForSplit = (delimitedTextFromLine || '')
|
|
955
|
+
.replace(spanDelimRegex, '')
|
|
956
|
+
// strip caret anchors from raw line html before split
|
|
957
|
+
.replace(/<span class="ep-data_tables-caret-anchor"[^>]*><\/span>/ig, '');
|
|
926
958
|
const htmlSegments = sanitizedHTMLForSplit.split(DELIMITER);
|
|
927
959
|
|
|
928
960
|
// log(`${logPrefix} NodeID#${nodeId}: *** SEGMENT ANALYSIS ***`);
|
|
@@ -949,57 +981,83 @@ exports.acePostWriteDomLineHTML = function (hook_name, args, cb) {
|
|
|
949
981
|
let finalHtmlSegments = htmlSegments;
|
|
950
982
|
|
|
951
983
|
if (htmlSegments.length !== rowMetadata.cols) {
|
|
952
|
-
// log(`${logPrefix} NodeID#${nodeId}: *** MISMATCH DETECTED
|
|
953
|
-
// log(`${logPrefix} NodeID#${nodeId}: WARNING - Parsed segment count (${htmlSegments.length}) does not match metadata cols (${rowMetadata.cols}). Auto-reconstructing table structure.`);
|
|
954
|
-
console.warn(`[ep_data_tables] ${funcName} NodeID#${nodeId}: Parsed segment count (${htmlSegments.length}) mismatch with metadata cols (${rowMetadata.cols}). Segments:`, htmlSegments);
|
|
955
|
-
|
|
956
|
-
// *** ENHANCED DEBUG: Analyze why we have a mismatch ***
|
|
957
|
-
// log(`${logPrefix} NodeID#${nodeId}: *** MISMATCH ANALYSIS ***`);
|
|
958
|
-
// log(`${logPrefix} NodeID#${nodeId}: Expected columns: ${rowMetadata.cols}`);
|
|
959
|
-
// log(`${logPrefix} NodeID#${nodeId}: Actual segments: ${htmlSegments.length}`);
|
|
960
|
-
// log(`${logPrefix} NodeID#${nodeId}: Delimiter count found: ${delimiterCount}`);
|
|
961
|
-
// log(`${logPrefix} NodeID#${nodeId}: Expected delimiter count: ${rowMetadata.cols - 1}`);
|
|
984
|
+
// log(`${logPrefix} NodeID#${nodeId}: *** MISMATCH DETECTED *** - Attempting reconstruction.`);
|
|
962
985
|
|
|
963
986
|
// Check if this is an image selection issue
|
|
964
987
|
const hasImageSelected = delimitedTextFromLine.includes('currently-selected');
|
|
965
988
|
const hasImageContent = delimitedTextFromLine.includes('image:');
|
|
966
|
-
// log(`${logPrefix} NodeID#${nodeId}: Has selected image: ${hasImageSelected}`);
|
|
967
|
-
// log(`${logPrefix} NodeID#${nodeId}: Has image content: ${hasImageContent}`);
|
|
968
|
-
|
|
969
989
|
if (hasImageSelected) {
|
|
970
990
|
// log(`${logPrefix} NodeID#${nodeId}: *** POTENTIAL CAUSE: Image selection state may be affecting segment parsing ***`);
|
|
971
991
|
}
|
|
972
|
-
|
|
973
|
-
//
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
992
|
+
|
|
993
|
+
// First attempt: reconstruct using DOM spans that carry tblCell-N classes
|
|
994
|
+
let usedClassReconstruction = false;
|
|
995
|
+
try {
|
|
996
|
+
const cols = Math.max(0, Number(rowMetadata.cols) || 0);
|
|
997
|
+
const grouped = Array.from({ length: cols }, () => '');
|
|
998
|
+
const candidates = Array.from(node.querySelectorAll('[class*="tblCell-"]'));
|
|
999
|
+
|
|
1000
|
+
const classNum = (el) => {
|
|
1001
|
+
if (!el || !el.classList) return -1;
|
|
1002
|
+
for (const cls of el.classList) {
|
|
1003
|
+
const m = /^tblCell-(\d+)$/.exec(cls);
|
|
1004
|
+
if (m) return parseInt(m[1], 10);
|
|
1005
|
+
}
|
|
1006
|
+
return -1;
|
|
1007
|
+
};
|
|
1008
|
+
const hasAncestorWithSameCell = (el, n) => {
|
|
1009
|
+
let p = el?.parentElement;
|
|
1010
|
+
while (p) {
|
|
1011
|
+
if (p.classList && p.classList.contains(`tblCell-${n}`)) return true;
|
|
1012
|
+
p = p.parentElement;
|
|
1013
|
+
}
|
|
1014
|
+
return false;
|
|
1015
|
+
};
|
|
1016
|
+
|
|
1017
|
+
for (const el of candidates) {
|
|
1018
|
+
const n = classNum(el);
|
|
1019
|
+
if (n >= 0 && n < cols) {
|
|
1020
|
+
if (!hasAncestorWithSameCell(el, n)) {
|
|
1021
|
+
grouped[n] += el.outerHTML || '';
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
const usable = grouped.some(s => s && s.trim() !== '');
|
|
1026
|
+
if (usable) {
|
|
1027
|
+
finalHtmlSegments = grouped.map(s => (s && s.trim() !== '') ? s : ' ');
|
|
1028
|
+
usedClassReconstruction = true;
|
|
1029
|
+
console.warn(`[ep_data_tables] ${funcName} NodeID#${nodeId}: Reconstructed ${finalHtmlSegments.length} segments from tblCell-N classes.`);
|
|
1030
|
+
}
|
|
1031
|
+
} catch (e) {
|
|
1032
|
+
console.debug(`[ep_data_tables] ${funcName} NodeID#${nodeId}: Class-based reconstruction error; falling back.`, e);
|
|
988
1033
|
}
|
|
989
|
-
|
|
1034
|
+
|
|
1035
|
+
// Fallback: reconstruct from string segments
|
|
1036
|
+
if (!usedClassReconstruction) {
|
|
1037
|
+
const reconstructedSegments = [];
|
|
1038
|
+
if (htmlSegments.length === 1 && rowMetadata.cols > 1) {
|
|
1039
|
+
reconstructedSegments.push(htmlSegments[0]);
|
|
1040
|
+
for (let i = 1; i < rowMetadata.cols; i++) {
|
|
1041
|
+
reconstructedSegments.push(' ');
|
|
1042
|
+
}
|
|
1043
|
+
} else if (htmlSegments.length > rowMetadata.cols) {
|
|
1044
|
+
for (let i = 0; i < rowMetadata.cols - 1; i++) {
|
|
1045
|
+
reconstructedSegments.push(htmlSegments[i] || ' ');
|
|
1046
|
+
}
|
|
990
1047
|
const remainingSegments = htmlSegments.slice(rowMetadata.cols - 1);
|
|
991
1048
|
reconstructedSegments.push(remainingSegments.join('|') || ' ');
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
for (let i = 0; i < rowMetadata.cols; i++) {
|
|
996
|
-
reconstructedSegments.push(htmlSegments[i] || ' ');
|
|
1049
|
+
} else {
|
|
1050
|
+
for (let i = 0; i < rowMetadata.cols; i++) {
|
|
1051
|
+
reconstructedSegments.push(htmlSegments[i] || ' ');
|
|
997
1052
|
}
|
|
1053
|
+
}
|
|
1054
|
+
finalHtmlSegments = reconstructedSegments;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// Only warn if we still don't have the right number of segments
|
|
1058
|
+
if (finalHtmlSegments.length !== rowMetadata.cols) {
|
|
1059
|
+
console.warn(`[ep_data_tables] ${funcName} NodeID#${nodeId}: Could not reconstruct to expected ${rowMetadata.cols} segments. Got ${finalHtmlSegments.length}.`);
|
|
998
1060
|
}
|
|
999
|
-
|
|
1000
|
-
// log(`${logPrefix} NodeID#${nodeId}: Reconstructed ${reconstructedSegments.length} segments to match expected ${rowMetadata.cols} columns.`);
|
|
1001
|
-
finalHtmlSegments = reconstructedSegments;
|
|
1002
|
-
|
|
1003
1061
|
} else {
|
|
1004
1062
|
// log(`${logPrefix} NodeID#${nodeId}: Segment count matches metadata cols (${rowMetadata.cols}). Using original segments.`);
|
|
1005
1063
|
}
|
|
@@ -1533,12 +1591,12 @@ exports.aceKeyEvent = (h, ctx) => {
|
|
|
1533
1591
|
// --- Check for Ctrl+X (Cut) key combination ---
|
|
1534
1592
|
const isCutKey = (evt.ctrlKey || evt.metaKey) && (evt.key === 'x' || evt.key === 'X' || evt.keyCode === 88);
|
|
1535
1593
|
if (isCutKey && hasSelection) {
|
|
1536
|
-
|
|
1594
|
+
log(`${logPrefix} Ctrl+X (Cut) detected with selection. Letting cut event handler manage this.`);
|
|
1537
1595
|
// Let the cut event handler handle this - we don't need to preventDefault here
|
|
1538
1596
|
// as the cut event will handle the operation and prevent default
|
|
1539
1597
|
return false; // Allow the cut event to be triggered
|
|
1540
1598
|
} else if (isCutKey && !hasSelection) {
|
|
1541
|
-
|
|
1599
|
+
log(`${logPrefix} Ctrl+X (Cut) detected but no selection. Allowing default.`);
|
|
1542
1600
|
return false; // Allow default - nothing to cut
|
|
1543
1601
|
}
|
|
1544
1602
|
|
|
@@ -1892,38 +1950,37 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
1892
1950
|
}
|
|
1893
1951
|
|
|
1894
1952
|
// *** CUT EVENT LISTENER ***
|
|
1895
|
-
|
|
1953
|
+
log(`${callWithAceLogPrefix} Attaching cut event listener to $inner (inner iframe body).`);
|
|
1896
1954
|
$inner.on('cut', (evt) => {
|
|
1897
1955
|
const cutLogPrefix = '[ep_data_tables:cutHandler]';
|
|
1898
|
-
|
|
1956
|
+
console.log(`${cutLogPrefix} CUT EVENT TRIGGERED. Event object:`, evt);
|
|
1899
1957
|
|
|
1900
|
-
|
|
1958
|
+
console.log(`${cutLogPrefix} Getting current editor representation (rep).`);
|
|
1901
1959
|
const rep = ed.ace_getRep();
|
|
1902
1960
|
if (!rep || !rep.selStart) {
|
|
1903
|
-
|
|
1961
|
+
console.warn(`${cutLogPrefix} WARNING: Could not get representation or selection. Allowing default cut.`);
|
|
1904
1962
|
console.warn(`${cutLogPrefix} Could not get rep or selStart.`);
|
|
1905
1963
|
return; // Allow default
|
|
1906
1964
|
}
|
|
1907
|
-
|
|
1965
|
+
console.log(`${cutLogPrefix} Rep obtained. selStart:`, rep.selStart, `selEnd:`, rep.selEnd);
|
|
1908
1966
|
const selStart = rep.selStart;
|
|
1909
1967
|
const selEnd = rep.selEnd;
|
|
1910
1968
|
const lineNum = selStart[0];
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
if (
|
|
1915
|
-
|
|
1916
|
-
return; // Allow default - nothing to cut
|
|
1969
|
+
console.log(`${cutLogPrefix} Current line number: ${lineNum}. Column start: ${selStart[1]}, Column end: ${selEnd[1]}.`);
|
|
1970
|
+
// Determine if there is a selection in the editor representation
|
|
1971
|
+
const hasSelectionInRep = !(selStart[0] === selEnd[0] && selStart[1] === selEnd[1]);
|
|
1972
|
+
if (!hasSelectionInRep) {
|
|
1973
|
+
console.log(`${cutLogPrefix} No selection detected in rep; deferring decision until table-line check.`);
|
|
1917
1974
|
}
|
|
1918
1975
|
|
|
1919
1976
|
// Check if selection spans multiple lines
|
|
1920
1977
|
if (selStart[0] !== selEnd[0]) {
|
|
1921
|
-
|
|
1978
|
+
console.warn(`${cutLogPrefix} WARNING: Selection spans multiple lines. Preventing cut to protect table structure.`);
|
|
1922
1979
|
evt.preventDefault();
|
|
1923
1980
|
return;
|
|
1924
1981
|
}
|
|
1925
1982
|
|
|
1926
|
-
|
|
1983
|
+
console.log(`${cutLogPrefix} Checking if line ${lineNum} is a table line by fetching '${ATTR_TABLE_JSON}' attribute.`);
|
|
1927
1984
|
let lineAttrString = docManager.getAttributeOnLine(lineNum, ATTR_TABLE_JSON);
|
|
1928
1985
|
let tableMetadata = null;
|
|
1929
1986
|
|
|
@@ -1940,11 +1997,18 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
1940
1997
|
}
|
|
1941
1998
|
|
|
1942
1999
|
if (!tableMetadata || typeof tableMetadata.cols !== 'number' || typeof tableMetadata.tblId === 'undefined' || typeof tableMetadata.row === 'undefined') {
|
|
1943
|
-
|
|
2000
|
+
console.log(`${cutLogPrefix} Line ${lineNum} is NOT a recognised table line. Allowing default cut.`);
|
|
1944
2001
|
return; // Not a table line
|
|
1945
2002
|
}
|
|
1946
2003
|
|
|
1947
|
-
|
|
2004
|
+
console.log(`${cutLogPrefix} Line ${lineNum} IS a table line. Metadata:`, tableMetadata);
|
|
2005
|
+
|
|
2006
|
+
// If inside a table line but the rep shows no selection, prevent default to protect structure
|
|
2007
|
+
if (!hasSelectionInRep) {
|
|
2008
|
+
console.log(`${cutLogPrefix} Preventing default CUT on table line with collapsed selection to protect delimiters.`);
|
|
2009
|
+
evt.preventDefault();
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
1948
2012
|
|
|
1949
2013
|
// Validate selection is within cell boundaries
|
|
1950
2014
|
const lineText = rep.lines.atIndex(lineNum)?.text || '';
|
|
@@ -1994,37 +2058,37 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
1994
2058
|
selEnd[1] = cellEndCol; // clamp
|
|
1995
2059
|
}
|
|
1996
2060
|
if (targetCellIndex === -1 || selEnd[1] > cellEndCol) {
|
|
1997
|
-
|
|
2061
|
+
console.warn(`${cutLogPrefix} WARNING: Selection spans cell boundaries or is outside cells. Preventing cut to protect table structure.`);
|
|
1998
2062
|
evt.preventDefault();
|
|
1999
2063
|
return;
|
|
2000
2064
|
}
|
|
2001
2065
|
|
|
2002
2066
|
// If we reach here, the selection is entirely within a single cell - allow cut and preserve table structure
|
|
2003
|
-
|
|
2067
|
+
console.log(`${cutLogPrefix} Selection is entirely within cell ${targetCellIndex}. Intercepting cut to preserve table structure.`);
|
|
2004
2068
|
evt.preventDefault();
|
|
2005
2069
|
|
|
2006
2070
|
try {
|
|
2007
2071
|
// Get the selected text to copy to clipboard
|
|
2008
2072
|
const selectedText = lineText.substring(selStart[1], selEnd[1]);
|
|
2009
|
-
|
|
2073
|
+
console.log(`${cutLogPrefix} Selected text to cut: "${selectedText}"`);
|
|
2010
2074
|
|
|
2011
2075
|
// Copy to clipboard manually
|
|
2012
2076
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
2013
2077
|
navigator.clipboard.writeText(selectedText).then(() => {
|
|
2014
|
-
|
|
2078
|
+
console.log(`${cutLogPrefix} Successfully copied to clipboard via Navigator API.`);
|
|
2015
2079
|
}).catch((err) => {
|
|
2016
2080
|
console.warn(`${cutLogPrefix} Failed to copy to clipboard via Navigator API:`, err);
|
|
2017
2081
|
});
|
|
2018
2082
|
} else {
|
|
2019
2083
|
// Fallback for older browsers
|
|
2020
|
-
|
|
2084
|
+
console.log(`${cutLogPrefix} Using fallback clipboard method.`);
|
|
2021
2085
|
const textArea = document.createElement('textarea');
|
|
2022
2086
|
textArea.value = selectedText;
|
|
2023
2087
|
document.body.appendChild(textArea);
|
|
2024
2088
|
textArea.select();
|
|
2025
2089
|
try {
|
|
2026
2090
|
document.execCommand('copy');
|
|
2027
|
-
|
|
2091
|
+
console.log(`${cutLogPrefix} Successfully copied to clipboard via execCommand fallback.`);
|
|
2028
2092
|
} catch (err) {
|
|
2029
2093
|
console.warn(`${cutLogPrefix} Failed to copy to clipboard via fallback:`, err);
|
|
2030
2094
|
}
|
|
@@ -2032,14 +2096,14 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
2032
2096
|
}
|
|
2033
2097
|
|
|
2034
2098
|
// Now perform the deletion within the cell using ace operations
|
|
2035
|
-
|
|
2099
|
+
console.log(`${cutLogPrefix} Performing deletion via ed.ace_callWithAce.`);
|
|
2036
2100
|
ed.ace_callWithAce((aceInstance) => {
|
|
2037
2101
|
const callAceLogPrefix = `${cutLogPrefix}[ace_callWithAceOps]`;
|
|
2038
|
-
|
|
2102
|
+
console.log(`${callAceLogPrefix} Entered ace_callWithAce for cut operations. selStart:`, selStart, `selEnd:`, selEnd);
|
|
2039
2103
|
|
|
2040
|
-
|
|
2104
|
+
console.log(`${callAceLogPrefix} Calling aceInstance.ace_performDocumentReplaceRange to delete selected text.`);
|
|
2041
2105
|
aceInstance.ace_performDocumentReplaceRange(selStart, selEnd, '');
|
|
2042
|
-
|
|
2106
|
+
console.log(`${callAceLogPrefix} ace_performDocumentReplaceRange successful.`);
|
|
2043
2107
|
|
|
2044
2108
|
// --- Ensure cell is not left empty (zero-length) ---
|
|
2045
2109
|
const repAfterDeletion = aceInstance.ace_getRep();
|
|
@@ -2048,7 +2112,7 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
2048
2112
|
const cellTextAfterDeletion = cellsAfterDeletion[targetCellIndex] || '';
|
|
2049
2113
|
|
|
2050
2114
|
if (cellTextAfterDeletion.length === 0) {
|
|
2051
|
-
|
|
2115
|
+
console.log(`${callAceLogPrefix} Cell ${targetCellIndex} became empty after cut – inserting single space to preserve structure.`);
|
|
2052
2116
|
const insertPos = [lineNum, selStart[1]]; // Start of the now-empty cell
|
|
2053
2117
|
aceInstance.ace_performDocumentReplaceRange(insertPos, insertPos, ' ');
|
|
2054
2118
|
|
|
@@ -2060,9 +2124,9 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
2060
2124
|
);
|
|
2061
2125
|
}
|
|
2062
2126
|
|
|
2063
|
-
|
|
2127
|
+
console.log(`${callAceLogPrefix} Preparing to re-apply tbljson attribute to line ${lineNum}.`);
|
|
2064
2128
|
const repAfterCut = aceInstance.ace_getRep();
|
|
2065
|
-
|
|
2129
|
+
console.log(`${callAceLogPrefix} Fetched rep after cut for applyMeta. Line ${lineNum} text now: "${repAfterCut.lines.atIndex(lineNum).text}"`);
|
|
2066
2130
|
|
|
2067
2131
|
ed.ep_data_tables_applyMeta(
|
|
2068
2132
|
lineNum,
|
|
@@ -2074,20 +2138,20 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
2074
2138
|
null,
|
|
2075
2139
|
docManager
|
|
2076
2140
|
);
|
|
2077
|
-
|
|
2141
|
+
console.log(`${callAceLogPrefix} tbljson attribute re-applied successfully via ep_data_tables_applyMeta.`);
|
|
2078
2142
|
|
|
2079
2143
|
const newCaretPos = [lineNum, selStart[1]];
|
|
2080
|
-
|
|
2144
|
+
console.log(`${callAceLogPrefix} Setting caret position to: [${newCaretPos}].`);
|
|
2081
2145
|
aceInstance.ace_performSelectionChange(newCaretPos, newCaretPos, false);
|
|
2082
|
-
|
|
2146
|
+
console.log(`${callAceLogPrefix} Selection change successful.`);
|
|
2083
2147
|
|
|
2084
|
-
|
|
2148
|
+
console.log(`${callAceLogPrefix} Cut operations within ace_callWithAce completed successfully.`);
|
|
2085
2149
|
}, 'tableCutTextOperations', true);
|
|
2086
2150
|
|
|
2087
|
-
|
|
2151
|
+
console.log(`${cutLogPrefix} Cut operation completed successfully.`);
|
|
2088
2152
|
} catch (error) {
|
|
2089
2153
|
console.error(`${cutLogPrefix} ERROR during cut operation:`, error);
|
|
2090
|
-
|
|
2154
|
+
console.log(`${cutLogPrefix} Cut operation failed. Error details:`, { message: error.message, stack: error.stack });
|
|
2091
2155
|
}
|
|
2092
2156
|
});
|
|
2093
2157
|
|
|
@@ -2281,6 +2345,18 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
2281
2345
|
const dropLogPrefix = '[ep_data_tables:dropHandler]';
|
|
2282
2346
|
// log(`${dropLogPrefix} DROP EVENT TRIGGERED. Event object:`, evt);
|
|
2283
2347
|
|
|
2348
|
+
// Block drops directly targeted at a table element regardless of selection state
|
|
2349
|
+
const targetEl = evt.target;
|
|
2350
|
+
if (targetEl && typeof targetEl.closest === 'function' && targetEl.closest('table.dataTable')) {
|
|
2351
|
+
evt.preventDefault();
|
|
2352
|
+
evt.stopPropagation();
|
|
2353
|
+
if (evt.originalEvent && evt.originalEvent.dataTransfer) {
|
|
2354
|
+
try { evt.originalEvent.dataTransfer.dropEffect = 'none'; } catch (_) {}
|
|
2355
|
+
}
|
|
2356
|
+
console.warn('[ep_data_tables] Drop prevented on table to protect structure.');
|
|
2357
|
+
return;
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2284
2360
|
// log(`${dropLogPrefix} Getting current editor representation (rep).`);
|
|
2285
2361
|
const rep = ed.ace_getRep();
|
|
2286
2362
|
if (!rep || !rep.selStart) {
|
|
@@ -2314,6 +2390,17 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
2314
2390
|
$inner.on('dragover', (evt) => {
|
|
2315
2391
|
const dragLogPrefix = '[ep_data_tables:dragoverHandler]';
|
|
2316
2392
|
|
|
2393
|
+
// If hovering over a table, signal not-allowed and block default
|
|
2394
|
+
const targetEl = evt.target;
|
|
2395
|
+
if (targetEl && typeof targetEl.closest === 'function' && targetEl.closest('table.dataTable')) {
|
|
2396
|
+
if (evt.originalEvent && evt.originalEvent.dataTransfer) {
|
|
2397
|
+
try { evt.originalEvent.dataTransfer.dropEffect = 'none'; } catch (_) {}
|
|
2398
|
+
}
|
|
2399
|
+
evt.preventDefault();
|
|
2400
|
+
evt.stopPropagation();
|
|
2401
|
+
return;
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2317
2404
|
const rep = ed.ace_getRep();
|
|
2318
2405
|
if (!rep || !rep.selStart) {
|
|
2319
2406
|
return; // Allow default
|
|
@@ -2337,6 +2424,18 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
2337
2424
|
}
|
|
2338
2425
|
});
|
|
2339
2426
|
|
|
2427
|
+
// Guard against dragenter into table areas
|
|
2428
|
+
$inner.on('dragenter', (evt) => {
|
|
2429
|
+
const targetEl = evt.target;
|
|
2430
|
+
if (targetEl && typeof targetEl.closest === 'function' && targetEl.closest('table.dataTable')) {
|
|
2431
|
+
if (evt.originalEvent && evt.originalEvent.dataTransfer) {
|
|
2432
|
+
try { evt.originalEvent.dataTransfer.dropEffect = 'none'; } catch (_) {}
|
|
2433
|
+
}
|
|
2434
|
+
evt.preventDefault();
|
|
2435
|
+
evt.stopPropagation();
|
|
2436
|
+
}
|
|
2437
|
+
});
|
|
2438
|
+
|
|
2340
2439
|
// *** EXISTING PASTE LISTENER ***
|
|
2341
2440
|
// log(`${callWithAceLogPrefix} Attaching paste event listener to $inner (inner iframe body).`);
|
|
2342
2441
|
$inner.on('paste', (evt) => {
|
|
@@ -2791,14 +2890,15 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
2791
2890
|
// *** DRAG PREVENTION FOR TABLE ELEMENTS ***
|
|
2792
2891
|
const preventTableDrag = (evt) => {
|
|
2793
2892
|
const target = evt.target;
|
|
2794
|
-
//
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
target.tagName === 'TBODY' && target.closest('table.dataTable')) {
|
|
2799
|
-
// log('[ep_data_tables:dragPrevention] Preventing drag operation on table element:', target.tagName);
|
|
2893
|
+
// Block ANY drag gesture that originates within a table (including spans/text inside cells)
|
|
2894
|
+
const inTable = target && typeof target.closest === 'function' && target.closest('table.dataTable');
|
|
2895
|
+
if (inTable) {
|
|
2896
|
+
// log('[ep_data_tables:dragPrevention] Preventing drag operation originating from inside table');
|
|
2800
2897
|
evt.preventDefault();
|
|
2801
2898
|
evt.stopPropagation();
|
|
2899
|
+
if (evt.originalEvent && evt.originalEvent.dataTransfer) {
|
|
2900
|
+
try { evt.originalEvent.dataTransfer.effectAllowed = 'none'; } catch (_) {}
|
|
2901
|
+
}
|
|
2802
2902
|
return false;
|
|
2803
2903
|
}
|
|
2804
2904
|
};
|
|
@@ -2808,6 +2908,21 @@ exports.aceInitialized = (h, ctx) => {
|
|
|
2808
2908
|
$inner.on('drag', preventTableDrag);
|
|
2809
2909
|
$inner.on('dragend', preventTableDrag);
|
|
2810
2910
|
// log(`${callWithAceLogPrefix} Attached drag prevention handlers to inner body`);
|
|
2911
|
+
|
|
2912
|
+
// Attach drag prevention broadly to cover iframe boundaries and the host document
|
|
2913
|
+
if (innerDoc.length > 0) {
|
|
2914
|
+
innerDoc.on('dragstart', preventTableDrag);
|
|
2915
|
+
innerDoc.on('drag', preventTableDrag);
|
|
2916
|
+
innerDoc.on('dragend', preventTableDrag);
|
|
2917
|
+
}
|
|
2918
|
+
if (outerDoc.length > 0) {
|
|
2919
|
+
outerDoc.on('dragstart', preventTableDrag);
|
|
2920
|
+
outerDoc.on('drag', preventTableDrag);
|
|
2921
|
+
outerDoc.on('dragend', preventTableDrag);
|
|
2922
|
+
}
|
|
2923
|
+
$(document).on('dragstart', preventTableDrag);
|
|
2924
|
+
$(document).on('drag', preventTableDrag);
|
|
2925
|
+
$(document).on('dragend', preventTableDrag);
|
|
2811
2926
|
};
|
|
2812
2927
|
|
|
2813
2928
|
// Setup the global handlers
|
|
@@ -4239,203 +4354,4 @@ exports.aceUndoRedo = (hook, ctx) => {
|
|
|
4239
4354
|
console.error(`${logPrefix} Error during undo/redo validation:`, e);
|
|
4240
4355
|
// log(`${logPrefix} Error details:`, { message: e.message, stack: e.stack });
|
|
4241
4356
|
}
|
|
4242
|
-
};
|
|
4243
|
-
|
|
4244
|
-
// *** ADDED: postAceInit hook for attaching listeners ***
|
|
4245
|
-
exports.postAceInit = (hookName, ctx) => {
|
|
4246
|
-
const func = '[ep_data_tables:postAceInit]';
|
|
4247
|
-
// log(`${func} START`);
|
|
4248
|
-
const editorInfo = ctx.ace; // Get editorInfo from context
|
|
4249
|
-
|
|
4250
|
-
if (!editorInfo) {
|
|
4251
|
-
console.error(`${func} ERROR: editorInfo (ctx.ace) is not available.`);
|
|
4252
|
-
return;
|
|
4253
|
-
}
|
|
4254
|
-
|
|
4255
|
-
const attachReconnectHandler = () => {
|
|
4256
|
-
try {
|
|
4257
|
-
const padObj = window.pad;
|
|
4258
|
-
const socket = padObj && padObj.socket;
|
|
4259
|
-
if (!socket) return false; // Not ready yet
|
|
4260
|
-
|
|
4261
|
-
if (socket.ep_data_tables_reconnect_listener_attached) return true;
|
|
4262
|
-
|
|
4263
|
-
let triggered = false;
|
|
4264
|
-
const triggerHardReconnect = (evtName) => {
|
|
4265
|
-
if (triggered) return;
|
|
4266
|
-
triggered = true;
|
|
4267
|
-
console.log(`[ep_data_tables] Socket.IO event '${evtName}' – invoking pad.forceReconnect()`);
|
|
4268
|
-
if (window.pad && typeof window.pad.forceReconnect === 'function') {
|
|
4269
|
-
try { window.pad.forceReconnect(); } catch(e) { console.error('[ep_data_tables] pad.forceReconnect() failed', e); window.location.reload(); }
|
|
4270
|
-
} else {
|
|
4271
|
-
window.location.reload();
|
|
4272
|
-
}
|
|
4273
|
-
};
|
|
4274
|
-
|
|
4275
|
-
socket.on('reconnect_attempt', () => triggerHardReconnect('reconnect_attempt'));
|
|
4276
|
-
socket.on('reconnect', () => triggerHardReconnect('reconnect'));
|
|
4277
|
-
socket.on('connect', () => { if (socket.disconnectedPreviously) triggerHardReconnect('connect'); });
|
|
4278
|
-
socket.on('disconnect', () => { socket.disconnectedPreviously = true; });
|
|
4279
|
-
|
|
4280
|
-
socket.ep_data_tables_reconnect_listener_attached = true;
|
|
4281
|
-
console.log('[ep_data_tables] Reconnect handler fully attached to pad.socket');
|
|
4282
|
-
return true;
|
|
4283
|
-
} catch (e) {
|
|
4284
|
-
console.error('[ep_data_tables] Error attaching reconnect listener:', e);
|
|
4285
|
-
return false;
|
|
4286
|
-
}
|
|
4287
|
-
};
|
|
4288
|
-
|
|
4289
|
-
// Keep trying until it attaches (no max attempts)
|
|
4290
|
-
if (!attachReconnectHandler()) {
|
|
4291
|
-
const intervalId = setInterval(() => {
|
|
4292
|
-
if (attachReconnectHandler()) clearInterval(intervalId);
|
|
4293
|
-
}, 500);
|
|
4294
|
-
}
|
|
4295
|
-
|
|
4296
|
-
// Setup mousedown listener via callWithAce
|
|
4297
|
-
editorInfo.ace_callWithAce((ace) => {
|
|
4298
|
-
const editor = ace.editor;
|
|
4299
|
-
const inner = ace.editor.container; // Use the main container
|
|
4300
|
-
|
|
4301
|
-
if (!editor || !inner) {
|
|
4302
|
-
console.error(`${func} ERROR: ace.editor or ace.editor.container not found within ace_callWithAce.`);
|
|
4303
|
-
return;
|
|
4304
|
-
}
|
|
4305
|
-
|
|
4306
|
-
// log(`${func} Inside callWithAce for attaching mousedown listeners.`);
|
|
4307
|
-
|
|
4308
|
-
// Initialize shared state on the editor object
|
|
4309
|
-
if (!editor.ep_data_tables_last_clicked) {
|
|
4310
|
-
editor.ep_data_tables_last_clicked = null;
|
|
4311
|
-
// log(`${func} Initialized ace.editor.ep_data_tables_last_clicked`);
|
|
4312
|
-
}
|
|
4313
|
-
|
|
4314
|
-
// log(`${func} Attempting to attach mousedown listener to editor container for cell selection...`);
|
|
4315
|
-
|
|
4316
|
-
inner.addEventListener('mousedown', (evt) => {
|
|
4317
|
-
const target = evt.target;
|
|
4318
|
-
const mousedownFuncName = '[ep_data_tables mousedown]';
|
|
4319
|
-
// log(`${mousedownFuncName} RAW MOUSE DOWN detected. Target:`, target);
|
|
4320
|
-
// log(`${mousedownFuncName} Target tagName: ${target.tagName}`);
|
|
4321
|
-
// log(`${mousedownFuncName} Target className: ${target.className}`);
|
|
4322
|
-
// log(`${mousedownFuncName} Target ID: ${target.id}`);
|
|
4323
|
-
|
|
4324
|
-
// Don't interfere with resize handle clicks
|
|
4325
|
-
if (target.classList && target.classList.contains('ep-data_tables-resize-handle')) {
|
|
4326
|
-
// log(`${mousedownFuncName} Click on resize handle, skipping cell selection logic.`);
|
|
4327
|
-
return;
|
|
4328
|
-
}
|
|
4329
|
-
|
|
4330
|
-
// *** ENHANCED DEBUG: Check for image-related elements ***
|
|
4331
|
-
const $target = $(target);
|
|
4332
|
-
const isImageElement = $target.closest('.inline-image, .image-placeholder, .image-inner, .image-resize-handle').length > 0;
|
|
4333
|
-
// log(`${mousedownFuncName} Is target or ancestor image-related?`, isImageElement);
|
|
4334
|
-
|
|
4335
|
-
if (isImageElement) {
|
|
4336
|
-
// log(`${mousedownFuncName} *** IMAGE ELEMENT DETECTED ***`);
|
|
4337
|
-
// log(`${mousedownFuncName} Closest image container:`, $target.closest('.inline-image, .image-placeholder')[0]);
|
|
4338
|
-
// log(`${mousedownFuncName} Is .inline-image:`, $target.hasClass('inline-image') || $target.closest('.inline-image').length > 0);
|
|
4339
|
-
// log(`${mousedownFuncName} Is .image-placeholder:`, $target.hasClass('image-placeholder') || $target.closest('.image-placeholder').length > 0);
|
|
4340
|
-
// log(`${mousedownFuncName} Is .image-inner:`, $target.hasClass('image-inner') || $target.closest('.image-inner').length > 0);
|
|
4341
|
-
// log(`${mousedownFuncName} Is .image-resize-handle:`, $target.hasClass('image-resize-handle') || $target.closest('.image-resize-handle').length > 0);
|
|
4342
|
-
}
|
|
4343
|
-
|
|
4344
|
-
// Check if the click is on an image or image-related element - if so, completely skip
|
|
4345
|
-
if (isImageElement) {
|
|
4346
|
-
// log(`${mousedownFuncName} Click detected on image element within table cell. Completely skipping table processing to avoid interference.`);
|
|
4347
|
-
return;
|
|
4348
|
-
}
|
|
4349
|
-
|
|
4350
|
-
// *** ENHANCED DEBUG: Check table context ***
|
|
4351
|
-
const clickedTD = target.closest('td');
|
|
4352
|
-
const clickedTR = target.closest('tr');
|
|
4353
|
-
const clickedTable = target.closest('table.dataTable');
|
|
4354
|
-
|
|
4355
|
-
// log(`${mousedownFuncName} Table context analysis:`);
|
|
4356
|
-
// log(`${mousedownFuncName} - Clicked TD:`, !!clickedTD);
|
|
4357
|
-
// log(`${mousedownFuncName} - Clicked TR:`, !!clickedTR);
|
|
4358
|
-
// log(`${mousedownFuncName} - Clicked table.dataTable:`, !!clickedTable);
|
|
4359
|
-
|
|
4360
|
-
if (clickedTable) {
|
|
4361
|
-
// log(`${mousedownFuncName} - Table tblId:`, clickedTable.getAttribute('data-tblId'));
|
|
4362
|
-
// log(`${mousedownFuncName} - Table row:`, clickedTable.getAttribute('data-row'));
|
|
4363
|
-
}
|
|
4364
|
-
if (clickedTD) {
|
|
4365
|
-
// log(`${mousedownFuncName} - TD data-column:`, clickedTD.getAttribute('data-column'));
|
|
4366
|
-
// log(`${mousedownFuncName} - TD innerHTML length:`, clickedTD.innerHTML?.length || 0);
|
|
4367
|
-
// log(`${mousedownFuncName} - TD contains images:`, clickedTD.querySelector('.inline-image, .image-placeholder') ? 'YES' : 'NO');
|
|
4368
|
-
}
|
|
4369
|
-
|
|
4370
|
-
// Clear previous selection state regardless of where click happened
|
|
4371
|
-
if (editor.ep_data_tables_last_clicked) {
|
|
4372
|
-
// log(`${mousedownFuncName} Clearing previous selection info.`);
|
|
4373
|
-
// TODO: Add visual class removal if needed
|
|
4374
|
-
}
|
|
4375
|
-
editor.ep_data_tables_last_clicked = null; // Clear state first
|
|
4376
|
-
|
|
4377
|
-
if (clickedTD && clickedTR && clickedTable) {
|
|
4378
|
-
// log(`${mousedownFuncName} Click detected inside table.dataTable td.`);
|
|
4379
|
-
try {
|
|
4380
|
-
const cellIndex = Array.from(clickedTR.children).indexOf(clickedTD);
|
|
4381
|
-
const lineNode = clickedTable.closest('div.ace-line');
|
|
4382
|
-
const tblId = clickedTable.getAttribute('data-tblId');
|
|
4383
|
-
|
|
4384
|
-
// log(`${mousedownFuncName} Cell analysis:`);
|
|
4385
|
-
// log(`${mousedownFuncName} - Cell index:`, cellIndex);
|
|
4386
|
-
// log(`${mousedownFuncName} - Line node:`, !!lineNode);
|
|
4387
|
-
// log(`${mousedownFuncName} - Line node ID:`, lineNode?.id);
|
|
4388
|
-
// log(`${mousedownFuncName} - Table ID:`, tblId);
|
|
4389
|
-
|
|
4390
|
-
// Ensure ace.rep and ace.rep.lines are available
|
|
4391
|
-
if (!ace.rep || !ace.rep.lines) {
|
|
4392
|
-
console.error(`${mousedownFuncName} ERROR: ace.rep or ace.rep.lines not available inside mousedown listener.`);
|
|
4393
|
-
return;
|
|
4394
|
-
}
|
|
4395
|
-
|
|
4396
|
-
if (lineNode && lineNode.id && tblId !== null && cellIndex !== -1) {
|
|
4397
|
-
const lineNum = ace.rep.lines.indexOfKey(lineNode.id);
|
|
4398
|
-
if (lineNum !== -1) {
|
|
4399
|
-
// Store the accurately determined cell info
|
|
4400
|
-
// Initialize relative position - might be refined later if needed
|
|
4401
|
-
const clickInfo = { lineNum, tblId, cellIndex, relativePos: 0 }; // Set initial relativePos to 0
|
|
4402
|
-
editor.ep_data_tables_last_clicked = clickInfo;
|
|
4403
|
-
// log(`${mousedownFuncName} Clicked cell (SUCCESS): Line=${lineNum}, TblId=${tblId}, CellIndex=${cellIndex}. Stored click info:`, clickInfo);
|
|
4404
|
-
|
|
4405
|
-
// --- NEW: Jump caret immediately for snappier UX ---
|
|
4406
|
-
try {
|
|
4407
|
-
const docMgr = ace.ep_data_tables_docManager;
|
|
4408
|
-
if (docMgr && typeof navigateToCell === 'function') {
|
|
4409
|
-
const navOk = navigateToCell(lineNum, cellIndex, ace, docMgr);
|
|
4410
|
-
// log(`${mousedownFuncName} Immediate navigateToCell result: ${navOk}`);
|
|
4411
|
-
}
|
|
4412
|
-
} catch (navErr) {
|
|
4413
|
-
console.error(`${mousedownFuncName} Error during immediate caret navigation:`, navErr);
|
|
4414
|
-
}
|
|
4415
|
-
|
|
4416
|
-
// TODO: Add visual class for selection if desired
|
|
4417
|
-
// log(`${mousedownFuncName} TEST: Skipped adding/removing selected-table-cell class`);
|
|
4418
|
-
|
|
4419
|
-
} else {
|
|
4420
|
-
// log(`${mousedownFuncName} Clicked cell (ERROR): Could not find line number for node ID: ${lineNode.id}`);
|
|
4421
|
-
}
|
|
4422
|
-
} else {
|
|
4423
|
-
// log(`${mousedownFuncName} Clicked cell (ERROR): Missing required info (lineNode, lineNode.id, tblId, or valid cellIndex).`, { lineNode, tblId, cellIndex });
|
|
4424
|
-
}
|
|
4425
|
-
} catch (e) {
|
|
4426
|
-
console.error(`${mousedownFuncName} Error processing table cell click:`, e);
|
|
4427
|
-
// log(`${mousedownFuncName} Error details:`, { message: e.message, stack: e.stack });
|
|
4428
|
-
editor.ep_data_tables_last_clicked = null; // Ensure state is clear on error
|
|
4429
|
-
}
|
|
4430
|
-
} else {
|
|
4431
|
-
// log(`${mousedownFuncName} Click was outside a table.dataTable td.`);
|
|
4432
|
-
}
|
|
4433
|
-
});
|
|
4434
|
-
// log(`${func} Mousedown listeners for cell selection attached successfully (inside callWithAce).`);
|
|
4435
|
-
|
|
4436
|
-
}, 'tableCellSelectionPostAce', true); // Unique name for callstack
|
|
4437
|
-
|
|
4438
|
-
// log(`${func} END`);
|
|
4439
|
-
};
|
|
4440
|
-
|
|
4441
|
-
// END OF FILE
|
|
4357
|
+
};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
const log = (...m) => console.debug('[ep_data_tables:collector_hooks]', ...m);
|
|
2
|
-
|
|
3
|
-
exports.collectContentPre = (hook, ctx) => {
|
|
4
|
-
return;
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
exports.collectContentLineBreak = (hook, ctx) => {
|
|
8
|
-
return true;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
exports.collectContentLineText = (_hook, ctx) => {
|
|
12
|
-
return ctx.text || '';
|
|
13
|
-
};
|