ep_data_tables 0.0.5 → 0.0.6

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.5",
3
+ "version": "0.0.6",
4
4
  "description": "BETA - etherpad tables plugin, compatible with other character/line based styling and other features",
5
5
  "author": {
6
6
  "name": "DCastelone",
@@ -17,6 +17,7 @@ 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');
20
21
  const DELIMITER = '\u241F'; // Internal column delimiter (␟)
21
22
  // Use the same rare character inside the hidden span so acePostWriteDomLineHTML can
22
23
  // still find delimiters when it splits node.innerHTML.
@@ -66,13 +67,14 @@ let isAndroidChromeComposition = false;
66
67
  let handledCurrentComposition = false;
67
68
  // Suppress all beforeinput insertText events during an Android Chrome IME composition
68
69
  let suppressBeforeInputInsertTextDuringComposition = false;
69
- // Helper to detect any Android browser (exclude iOS/Safari)
70
- function isAndroidUA() {
70
+ // Helper to detect Android Chromium-family browsers (exclude iOS and Firefox)
71
+ function isAndroidChromiumUA() {
71
72
  const ua = (navigator.userAgent || '').toLowerCase();
72
73
  const isAndroid = ua.includes('android');
73
74
  const isIOS = ua.includes('iphone') || ua.includes('ipad') || ua.includes('ipod') || ua.includes('crios');
74
- // Safari on Android is rare (WebKit ports), but our exclusion target is iOS Safari; we exclude all iOS above
75
- return isAndroid && !isIOS;
75
+ const isFirefox = ua.includes('firefox');
76
+ const isChromiumFamily = ua.includes('chrome') || ua.includes('edg') || ua.includes('opr') || ua.includes('samsungbrowser') || ua.includes('vivaldi') || ua.includes('brave');
77
+ return isAndroid && !isIOS && !isFirefox && isChromiumFamily;
76
78
  }
77
79
 
78
80
  // ─────────────────── Reusable Helper Functions ───────────────────
@@ -964,11 +966,33 @@ exports.acePostWriteDomLineHTML = function (hook_name, args, cb) {
964
966
  // NEW: Remove all hidden-delimiter <span> wrappers **before** we split so
965
967
  // the embedded delimiter character they carry doesn't inflate or shrink
966
968
  // the segment count.
967
- const spanDelimRegex = new RegExp('<span class="ep-data_tables-delim"[^>]*>' + DELIMITER + '<\\/span>', 'ig');
969
+ const spanDelimRegex = new RegExp('<span class="ep-data_tables-delim"[^>]*>' + DELIMITER + '</span>', 'ig');
970
+ // Safari-specific normalization: it may serialize the delimiter as entities and inject Apple spans
971
+ const delimiterEntityHexRE = /&#x241f;/ig; // hex entity for U+241F
972
+ const delimiterEntityDecRE = /&#9247;/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
+
968
980
  const sanitizedHTMLForSplit = (delimitedTextFromLine || '')
969
981
  .replace(spanDelimRegex, '')
970
982
  // strip caret anchors from raw line html before split
971
- .replace(/<span class="ep-data_tables-caret-anchor"[^>]*><\/span>/ig, '');
983
+ .replace(/<span class="ep-data_tables-caret-anchor"[^>]*><\/span>/ig, '')
984
+ // Safari may serialize the delimiter as HTML entities – convert back to raw char
985
+ .replace(delimiterEntityHexRE, DELIMITER)
986
+ .replace(delimiterEntityDecRE, DELIMITER)
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
+
972
996
  const htmlSegments = sanitizedHTMLForSplit.split(DELIMITER);
973
997
 
974
998
  // log(`${logPrefix} NodeID#${nodeId}: *** SEGMENT ANALYSIS ***`);
@@ -2195,7 +2219,7 @@ exports.aceInitialized = (h, ctx) => {
2195
2219
  // log(`${deleteLogPrefix} Current line number: ${lineNum}. Column start: ${selStart[1]}, Column end: ${selEnd[1]}.`);
2196
2220
 
2197
2221
  // Android Chrome IME: collapsed backspace/forward-delete often comes via beforeinput
2198
- const isAndroidChrome = isAndroidUA();
2222
+ const isAndroidChrome = isAndroidChromiumUA();
2199
2223
  const inputType = (evt.originalEvent && evt.originalEvent.inputType) || '';
2200
2224
 
2201
2225
  // Handle collapsed deletes on Android Chrome inside a table line to protect delimiters
@@ -2448,8 +2472,8 @@ exports.aceInitialized = (h, ctx) => {
2448
2472
  // Only intercept insert types
2449
2473
  if (!inputType || !inputType.startsWith('insert')) return;
2450
2474
 
2451
- // Target only Android browsers (exclude iOS)
2452
- if (!isAndroidUA()) return;
2475
+ // Target only Android Chromium-family browsers (exclude iOS and Firefox)
2476
+ if (!isAndroidChromiumUA()) return;
2453
2477
 
2454
2478
  // Get current selection and ensure we are inside a table line
2455
2479
  const rep = ed.ace_getRep();
@@ -2614,7 +2638,7 @@ exports.aceInitialized = (h, ctx) => {
2614
2638
 
2615
2639
  // Composition start marker (Android Chrome/table only)
2616
2640
  $inner.on('compositionstart', (evt) => {
2617
- if (!isAndroidUA()) return;
2641
+ if (!isAndroidChromiumUA()) return;
2618
2642
  const rep = ed.ace_getRep();
2619
2643
  if (!rep || !rep.selStart) return;
2620
2644
  const lineNum = rep.selStart[0];
@@ -2631,7 +2655,7 @@ exports.aceInitialized = (h, ctx) => {
2631
2655
  $inner.on('compositionupdate', (evt) => {
2632
2656
  const compLogPrefix = '[ep_data_tables:compositionHandler]';
2633
2657
 
2634
- if (!isAndroidUA()) return;
2658
+ if (!isAndroidChromiumUA()) return;
2635
2659
 
2636
2660
  const rep = ed.ace_getRep();
2637
2661
  if (!rep || !rep.selStart) return;