ep_data_tables 0.0.9 → 0.0.96

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ep_data_tables",
3
- "version": "0.0.9",
3
+ "version": "0.0.96",
4
4
  "description": "BETA - etherpad tables plugin, compatible with other character/line based styling and other features",
5
5
  "author": {
6
6
  "name": "DCastelone",
@@ -1,4 +1,15 @@
1
1
  const ATTR_TABLE_JSON = 'tbljson';
2
+ // Global guard: Chrome/macOS sometimes loads the same plugin script twice due to a
3
+ // subtle preload/import timing quirk, causing duplicate aceInitialized hooks that
4
+ // explode into large duplicate-row changesets. Bail early on re-entry.
5
+ if (typeof window !== 'undefined') {
6
+ if (window.__epDataTablesLoaded) {
7
+ console.debug('[ep_data_tables] Duplicate client_hooks.js load suppressed');
8
+ // eslint-disable-next-line no-useless-return
9
+ return; // Abort evaluation of the rest of the module
10
+ }
11
+ window.__epDataTablesLoaded = true;
12
+ }
2
13
  const ATTR_CELL = 'td';
3
14
  const ATTR_CLASS_PREFIX = 'tbljson-';
4
15
  const log = (...m) => console.debug('[ep_data_tables:client_hooks]', ...m);
@@ -600,7 +611,7 @@ function buildTableFromDelimitedHTML(metadata, innerHTMLSegments) {
600
611
  if (!metadata || typeof metadata.tblId === 'undefined' || typeof metadata.row === 'undefined') {
601
612
  console.error(`[ep_data_tables] ${funcName}: Invalid or missing metadata. Aborting.`);
602
613
  // log(`${funcName}: END - Error`);
603
- return '<table class="dataTable dataTable-error"><tbody><tr><td>Error: Missing table metadata</td></tr></tbody></table>';
614
+ return '<table class="dataTable dataTable-error" writingsuggestions="false" autocorrect="off" autocapitalize="off" spellcheck="false"><tbody><tr><td>Error: Missing table metadata</td></tr></tbody></table>';
604
615
  }
605
616
 
606
617
  const numCols = innerHTMLSegments.length;
@@ -623,7 +634,8 @@ function buildTableFromDelimitedHTML(metadata, innerHTMLSegments) {
623
634
  const cellsHtml = innerHTMLSegments.map((segment, index) => {
624
635
  const textOnly = (segment || '').replace(/<[^>]*>/g, '').replace(/&nbsp;/ig, ' ').trim();
625
636
  let modifiedSegment = segment || '';
626
- const isEmpty = !segment || textOnly === '';
637
+ const containsImage = /\bimage-placeholder\b/.test(modifiedSegment);
638
+ const isEmpty = (!segment || textOnly === '') && !containsImage;
627
639
  if (isEmpty) {
628
640
  const cellClass = encodedTbljsonClass ? `${encodedTbljsonClass} tblCell-${index}` : `tblCell-${index}`;
629
641
  modifiedSegment = `<span class="${cellClass}">&nbsp;</span>`;
@@ -679,7 +691,7 @@ function buildTableFromDelimitedHTML(metadata, innerHTMLSegments) {
679
691
  const resizeHandle = !isLastColumn ?
680
692
  `<div class="ep-data_tables-resize-handle" data-column="${index}" style="position: absolute; top: 0; right: -2px; width: 4px; height: 100%; cursor: col-resize; background: transparent; z-index: 10;"></div>` : '';
681
693
 
682
- const tdContent = `<td style="${cellStyle}" data-column="${index}" draggable="false">${modifiedSegment}${resizeHandle}</td>`;
694
+ const tdContent = `<td style="${cellStyle}" data-column="${index}" draggable="false" autocorrect="off" autocapitalize="off" spellcheck="false">${modifiedSegment}${resizeHandle}</td>`;
683
695
  return tdContent;
684
696
  }).join('');
685
697
  // log(`${funcName}: Joined all cellsHtml:`, cellsHtml);
@@ -687,7 +699,7 @@ function buildTableFromDelimitedHTML(metadata, innerHTMLSegments) {
687
699
  const firstRowClass = metadata.row === 0 ? ' dataTable-first-row' : '';
688
700
  // log(`${funcName}: First row class applied: '${firstRowClass}'`);
689
701
 
690
- 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>`;
702
+ const tableHtml = `<table class="dataTable${firstRowClass}" writingsuggestions="false" autocorrect="off" autocapitalize="off" spellcheck="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>`;
691
703
  // log(`${funcName}: Generated final table HTML:`, tableHtml);
692
704
  // log(`${funcName}: END - Success`);
693
705
  return tableHtml;
@@ -862,8 +874,8 @@ exports.acePostWriteDomLineHTML = function (hook_name, args, cb) {
862
874
  .replace(/<span class="ep-data_tables-caret-anchor"[^>]*><\/span>/ig, '')
863
875
  .replace(/\r?\n/g, ' ')
864
876
  .replace(/<br\s*\/?>/gi, ' ')
865
- .replace(/\u00A0/gu, ' ')
866
- .replace(/[\u200B\u200C\u200D\uFEFF]/g, '')
877
+ // .replace(/\u00A0/gu, ' ')
878
+ // .replace(/[\u200B\u200C\u200D\uFEFF]/g, '')
867
879
  .replace(/\s+/g, ' ');
868
880
  const htmlSegments = sanitizedHTMLForSplit.split(DELIMITER);
869
881
 
@@ -1692,33 +1704,53 @@ exports.aceInitialized = (h, ctx) => {
1692
1704
  ed.ep_data_tables_editor = editor;
1693
1705
  // log(`${logPrefix}: Stored editor reference as ed.ep_data_tables_editor.`);
1694
1706
 
1695
- let $inner;
1696
- try {
1697
- // log(`${callWithAceLogPrefix} Attempting to find inner iframe body for listener attachment.`);
1707
+ // Retry logic for iframe access to handle timing/race conditions
1708
+ const tryGetIframeBody = (attempt = 0) => {
1709
+ if (attempt > 0) {
1710
+ console.log(`${callWithAceLogPrefix} Retry attempt ${attempt}/5 to access iframe body`);
1711
+ }
1712
+
1698
1713
  const $iframeOuter = $('iframe[name="ace_outer"]');
1699
1714
  if ($iframeOuter.length === 0) {
1700
- console.error(`${callWithAceLogPrefix} ERROR: Could not find outer iframe (ace_outer).`);
1701
- // log(`${callWithAceLogPrefix} Failed to find ace_outer.`);
1715
+ if (attempt < 5) {
1716
+ setTimeout(() => tryGetIframeBody(attempt + 1), 100);
1717
+ return;
1718
+ }
1719
+ console.error(`${callWithAceLogPrefix} ERROR: Could not find outer iframe (ace_outer) after ${attempt} attempts.`);
1702
1720
  return;
1703
1721
  }
1704
- // log(`${callWithAceLogPrefix} Found ace_outer:`, $iframeOuter);
1705
1722
 
1706
1723
  const $iframeInner = $iframeOuter.contents().find('iframe[name="ace_inner"]');
1707
1724
  if ($iframeInner.length === 0) {
1708
- console.error(`${callWithAceLogPrefix} ERROR: Could not find inner iframe (ace_inner).`);
1709
- // log(`${callWithAceLogPrefix} Failed to find ace_inner within ace_outer.`);
1725
+ if (attempt < 5) {
1726
+ setTimeout(() => tryGetIframeBody(attempt + 1), 100);
1727
+ return;
1728
+ }
1729
+ console.error(`${callWithAceLogPrefix} ERROR: Could not find inner iframe (ace_inner) after ${attempt} attempts.`);
1710
1730
  return;
1711
1731
  }
1712
- // log(`${callWithAceLogPrefix} Found ace_inner:`, $iframeInner);
1713
1732
 
1714
1733
  const innerDocBody = $iframeInner.contents().find('body');
1715
1734
  if (innerDocBody.length === 0) {
1716
- console.error(`${callWithAceLogPrefix} ERROR: Could not find body element in inner iframe.`);
1717
- // log(`${callWithAceLogPrefix} Failed to find body in ace_inner.`);
1735
+ if (attempt < 5) {
1736
+ setTimeout(() => tryGetIframeBody(attempt + 1), 100);
1737
+ return;
1738
+ }
1739
+ console.error(`${callWithAceLogPrefix} ERROR: Could not find body element in inner iframe after ${attempt} attempts.`);
1718
1740
  return;
1719
1741
  }
1720
- $inner = $(innerDocBody[0]);
1721
- // log(`${callWithAceLogPrefix} Successfully found inner iframe body:`, $inner);
1742
+
1743
+ const $inner = $(innerDocBody[0]);
1744
+ if (attempt > 0) {
1745
+ console.log(`${callWithAceLogPrefix} Successfully found iframe body on attempt ${attempt + 1}`);
1746
+ }
1747
+
1748
+ // SUCCESS - Now attach all listeners and set attributes
1749
+ attachListeners($inner, $iframeOuter, $iframeInner, innerDocBody);
1750
+ };
1751
+
1752
+ const attachListeners = ($inner, $iframeOuter, $iframeInner, innerDocBody) => {
1753
+ try {
1722
1754
 
1723
1755
  const mobileSuggestionBlocker = (evt) => {
1724
1756
  const t = evt && evt.inputType || '';
@@ -1899,37 +1931,38 @@ exports.aceInitialized = (h, ctx) => {
1899
1931
  };
1900
1932
 
1901
1933
  // IME/autocorrect diagnostics: capture-phase logging and newline soft-normalization for table lines
1902
- const logIMEEvent = (rawEvt, tag) => {
1903
- try {
1904
- const e = rawEvt && (rawEvt.originalEvent || rawEvt);
1905
- const rep = ed.ace_getRep && ed.ace_getRep();
1906
- const selStart = rep && rep.selStart;
1907
- const lineNum = selStart ? selStart[0] : -1;
1908
- let isTableLine = false;
1909
- if (lineNum >= 0) {
1910
- let s = docManager && docManager.getAttributeOnLine ? docManager.getAttributeOnLine(lineNum, ATTR_TABLE_JSON) : null;
1911
- if (!s) {
1912
- const meta = getTableLineMetadata(lineNum, ed, docManager);
1913
- isTableLine = !!meta && typeof meta.cols === 'number';
1914
- } else {
1915
- isTableLine = true;
1916
- }
1917
- }
1918
- if (!isTableLine) return;
1919
- const payload = {
1920
- tag,
1921
- type: e && e.type,
1922
- inputType: e && e.inputType,
1923
- data: typeof (e && e.data) === 'string' ? e.data : null,
1924
- isComposing: !!(e && e.isComposing),
1925
- key: e && e.key,
1926
- code: e && e.code,
1927
- which: e && e.which,
1928
- keyCode: e && e.keyCode,
1929
- };
1930
- console.debug('[ep_data_tables:ime-diag]', payload);
1931
- } catch (_) {}
1932
- };
1934
+ // COMMENTED OUT FOR PRODUCTION - Uncomment for debugging IME/composition issues
1935
+ // const logIMEEvent = (rawEvt, tag) => {
1936
+ // try {
1937
+ // const e = rawEvt && (rawEvt.originalEvent || rawEvt);
1938
+ // const rep = ed.ace_getRep && ed.ace_getRep();
1939
+ // const selStart = rep && rep.selStart;
1940
+ // const lineNum = selStart ? selStart[0] : -1;
1941
+ // let isTableLine = false;
1942
+ // if (lineNum >= 0) {
1943
+ // let s = docManager && docManager.getAttributeOnLine ? docManager.getAttributeOnLine(lineNum, ATTR_TABLE_JSON) : null;
1944
+ // if (!s) {
1945
+ // const meta = getTableLineMetadata(lineNum, ed, docManager);
1946
+ // isTableLine = !!meta && typeof meta.cols === 'number';
1947
+ // } else {
1948
+ // isTableLine = true;
1949
+ // }
1950
+ // }
1951
+ // if (!isTableLine) return;
1952
+ // const payload = {
1953
+ // tag,
1954
+ // type: e && e.type,
1955
+ // inputType: e && e.inputType,
1956
+ // data: typeof (e && e.data) === 'string' ? e.data : null,
1957
+ // isComposing: !!(e && e.isComposing),
1958
+ // key: e && e.key,
1959
+ // code: e && e.code,
1960
+ // which: e && e.which,
1961
+ // keyCode: e && e.keyCode,
1962
+ // };
1963
+ // console.debug('[ep_data_tables:ime-diag]', payload);
1964
+ // } catch (_) {}
1965
+ // };
1933
1966
 
1934
1967
  const softBreakNormalizer = (rawEvt) => {
1935
1968
  try {
@@ -1937,6 +1970,15 @@ exports.aceInitialized = (h, ctx) => {
1937
1970
  if (!e || e._epDataTablesNormalized) return;
1938
1971
  const t = e.inputType || '';
1939
1972
  const dataStr = typeof e.data === 'string' ? e.data : '';
1973
+
1974
+ // If this NBSP is flanked by ZWSPs we are inside an image placeholder.
1975
+ // In that case leave it untouched so caret math stays correct.
1976
+ if (dataStr === '\u00A0' && rep && rep.selStart) {
1977
+ const lineText = rep.lines.atIndex(rep.selStart[0])?.text || '';
1978
+ const pos = rep.selStart[1]; // caret is before the NBSP
1979
+ if (lineText.slice(pos - 1, pos + 2) === '\u200B\u00A0\u200B') return;
1980
+ }
1981
+
1940
1982
  const hasSoftWs = /[\r\n\u00A0]/.test(dataStr); // include NBSP (U+00A0)
1941
1983
  const isSoftBreak = t === 'insertParagraph' || t === 'insertLineBreak' || hasSoftWs;
1942
1984
  if (!isSoftBreak) return;
@@ -1970,9 +2012,10 @@ exports.aceInitialized = (h, ctx) => {
1970
2012
 
1971
2013
  if ($inner && $inner.length > 0 && $inner[0].addEventListener) {
1972
2014
  const el = $inner[0];
1973
- ['beforeinput','input','textInput','compositionstart','compositionupdate','compositionend','keydown','keyup'].forEach((t) => {
1974
- el.addEventListener(t, (ev) => logIMEEvent(ev, 'capture'), true);
1975
- });
2015
+ // COMMENTED OUT FOR PRODUCTION - Uncomment for debugging IME/composition issues
2016
+ // ['beforeinput','input','textInput','compositionstart','compositionupdate','compositionend','keydown','keyup'].forEach((t) => {
2017
+ // el.addEventListener(t, (ev) => logIMEEvent(ev, 'capture'), true);
2018
+ // });
1976
2019
  el.addEventListener('beforeinput', softBreakNormalizer, true);
1977
2020
  }
1978
2021
 
@@ -2002,20 +2045,16 @@ exports.aceInitialized = (h, ctx) => {
2002
2045
  el.setAttribute('autocomplete', 'off');
2003
2046
  el.setAttribute('autocapitalize', 'off');
2004
2047
  el.setAttribute('spellcheck', 'false');
2048
+ el.setAttribute('data-gramm', 'false');
2049
+ el.setAttribute('data-enable-grammarly', 'false');
2005
2050
  };
2006
2051
  disableAuto(innerDocBody[0] || innerDocBody);
2007
2052
  } catch (_) {}
2008
- } catch (e) {
2009
- console.error(`${callWithAceLogPrefix} ERROR: Exception while trying to find inner iframe body:`, e);
2010
- // log(`${callWithAceLogPrefix} Exception details:`, { message: e.message, stack: e.stack });
2011
- return;
2012
- }
2013
-
2014
- if (!$inner || $inner.length === 0) {
2015
- console.error(`${callWithAceLogPrefix} ERROR: $inner is not valid after attempting to find iframe body. Cannot attach listeners.`);
2016
- // log(`${callWithAceLogPrefix} $inner is invalid. Aborting.`);
2017
- return;
2018
- }
2053
+
2054
+ if (!$inner || $inner.length === 0) {
2055
+ console.error(`${callWithAceLogPrefix} ERROR: $inner is not valid. Cannot attach listeners.`);
2056
+ return;
2057
+ }
2019
2058
 
2020
2059
  // log(`${callWithAceLogPrefix} Attaching cut event listener to $inner (inner iframe body).`);
2021
2060
  $inner.on('cut', (evt) => {
@@ -3618,6 +3657,13 @@ exports.aceInitialized = (h, ctx) => {
3618
3657
  setupGlobalHandlers();
3619
3658
 
3620
3659
  // log(`${callWithAceLogPrefix} Column resize listeners attached successfully.`);
3660
+ } catch (e) {
3661
+ console.error(`${callWithAceLogPrefix} ERROR: Exception while attaching listeners:`, e);
3662
+ }
3663
+ }; // End of attachListeners function
3664
+
3665
+ // Start the retry process to access iframes and attach all listeners
3666
+ tryGetIframeBody(0);
3621
3667
 
3622
3668
  }, 'tablePasteAndResizeListeners', true);
3623
3669
  // log(`${logPrefix} ace_callWithAce for listeners setup completed.`);
@@ -4890,3 +4936,4 @@ exports.aceUndoRedo = (hook, ctx) => {
4890
4936
  // log(`${logPrefix} Error details:`, { message: e.message, stack: e.stack });
4891
4937
  }
4892
4938
  };
4939
+