fable-editor 1.2.5 → 1.2.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.
@@ -1771,6 +1771,45 @@ function installTooltips() {
1771
1771
  document.addEventListener('mousedown', hideTip, true);
1772
1772
  addEventListener('scroll', hideTip, true);
1773
1773
  }
1774
+ /* ---------------------------------------------------------- email-export direction
1775
+ detection — used only by getContentForEmail(). Content pasted from Word/Outlook
1776
+ usually carries explicit dir="rtl"/"ltr" per paragraph, but plain-text paste
1777
+ (normalizeTextPaste's dir="auto") and some other sources carry no reliable
1778
+ explicit direction at all. For those blocks we fall back to a first-strong-
1779
+ character scan (the same idea as HTML5's dir="auto") instead of the app's
1780
+ configured UI language, which has nothing to do with what the pasted text
1781
+ actually is. */
1782
+ const EMAIL_RTL_CHAR = /[\u0590-\u05FF\u0600-\u06FF\u0750-\u077F\u0780-\u07BF\u08A0-\u08FF\uFB1D-\uFB4F\uFB50-\uFDFF\uFE70-\uFEFF]/;
1783
+ const EMAIL_STRONG_CHAR = /\p{L}/u;
1784
+ const EMAIL_BLOCK_SELECTOR = 'p,div,li,td,th,blockquote,h1,h2,h3,h4,h5,h6,pre,figcaption';
1785
+ function firstStrongDir(text) {
1786
+ for (const ch of text) {
1787
+ if (EMAIL_RTL_CHAR.test(ch))
1788
+ return 'rtl';
1789
+ if (EMAIL_STRONG_CHAR.test(ch))
1790
+ return 'ltr';
1791
+ }
1792
+ return null;
1793
+ }
1794
+ /* Text belonging to el itself, skipping subtrees rooted at a descendant that
1795
+ already carries its own explicit rtl/ltr dir — that subtree's direction is
1796
+ already independently decided and shouldn't sway el's own detection. */
1797
+ function ownText(el) {
1798
+ let text = '';
1799
+ el.childNodes.forEach((node) => {
1800
+ if (node.nodeType === Node.TEXT_NODE) {
1801
+ text += node.nodeValue || '';
1802
+ }
1803
+ else if (node.nodeType === Node.ELEMENT_NODE) {
1804
+ const child = node;
1805
+ const d = (child.getAttribute('dir') || '').toLowerCase();
1806
+ if (d === 'rtl' || d === 'ltr')
1807
+ return;
1808
+ text += ownText(child);
1809
+ }
1810
+ });
1811
+ return text;
1812
+ }
1774
1813
  class FableEditor {
1775
1814
  static instanceCounter = 0;
1776
1815
  options;
@@ -2254,8 +2293,13 @@ class FableEditor {
2254
2293
  * descendant that has no `text-align` of its own — even one with its own
2255
2294
  * dir="rtl". Elements that already carry their own dir (preserved as-is by
2256
2295
  * the paste engine) each get their own matching text-align only if they
2257
- * don't already have one, so mixed-direction content and any alignment the
2258
- * user explicitly set are left untouched. */
2296
+ * don't already have one. Blocks with no reliable explicit dir (missing, or
2297
+ * dir="auto" from plain-text paste) get their direction from a first-strong-
2298
+ * character scan of their own text instead of silently inheriting the
2299
+ * editor's configured UI language — mixed-direction content and any
2300
+ * alignment the user explicitly set are left untouched either way. Only
2301
+ * content with no strongly-directional character anywhere falls back to the
2302
+ * editor's current direction. */
2259
2303
  getContentForEmail() {
2260
2304
  const setStyle = (el, decl) => {
2261
2305
  const cur = (el.getAttribute('style') || '').trim();
@@ -2274,6 +2318,19 @@ class FableEditor {
2274
2318
  if (!/text-align\s*:/i.test(el.getAttribute('style') || ''))
2275
2319
  setStyle(el, `text-align:${d === 'rtl' ? 'right' : 'left'}`);
2276
2320
  });
2321
+ box.querySelectorAll(EMAIL_BLOCK_SELECTOR).forEach((el) => {
2322
+ const raw = (el.getAttribute('dir') || '').toLowerCase();
2323
+ if (raw === 'rtl' || raw === 'ltr')
2324
+ return;
2325
+ const d = firstStrongDir(ownText(el));
2326
+ if (!d)
2327
+ return;
2328
+ if (!found)
2329
+ found = d;
2330
+ el.setAttribute('dir', d);
2331
+ const hasAlign = /text-align\s*:/i.test(el.getAttribute('style') || '');
2332
+ setStyle(el, hasAlign ? `direction:${d}` : `direction:${d};text-align:${d === 'rtl' ? 'right' : 'left'}`);
2333
+ });
2277
2334
  const dir = found || this.ed.getAttribute('dir') || this.dir();
2278
2335
  box.querySelectorAll('table:not([dir])').forEach((tbl) => {
2279
2336
  tbl.setAttribute('dir', dir);