fable-editor 1.3.0 → 1.3.1
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/dist/angular/esm2022/core/editor.mjs +160 -1
- package/dist/angular/fesm2022/fable-editor.mjs +159 -0
- package/dist/angular/fesm2022/fable-editor.mjs.map +1 -1
- package/dist/core/editor.d.ts.map +1 -1
- package/dist/core/index.cjs +3 -3
- package/dist/core/index.mjs +562 -485
- package/dist/react/index.cjs +13 -13
- package/dist/react/index.mjs +938 -861
- package/package.json +1 -1
|
@@ -1980,6 +1980,152 @@ function firstStrongDir(text) {
|
|
|
1980
1980
|
}
|
|
1981
1981
|
return null;
|
|
1982
1982
|
}
|
|
1983
|
+
/* ---------------------------------------------------------- email-export table
|
|
1984
|
+
borders — used only by getContentForEmail(). Word describes a cell's borders
|
|
1985
|
+
as three parallel longhands (`border-color` / `border-style` / `border-width`),
|
|
1986
|
+
one value per side, and a browser re-copy of Word content keeps that split.
|
|
1987
|
+
Mail pipelines allowlist CSS one property at a time, and any of them that
|
|
1988
|
+
keeps `border-color` and `border-width` while dropping `border-style` erases
|
|
1989
|
+
the border outright — the initial `border-style` is `none`, so a width and a
|
|
1990
|
+
colour on their own draw nothing. Re-emitting every side as a single
|
|
1991
|
+
`border-<side>: <width> <style> <colour>` (collapsed to one `border:` when all
|
|
1992
|
+
four agree) makes a side survive or vanish as a unit instead. A side left with
|
|
1993
|
+
a width or a colour but no style is read back as `solid`, which also repairs
|
|
1994
|
+
content that was already flattened that way before it came back into the
|
|
1995
|
+
editor, and `windowtext` — a deprecated system colour Word still emits —
|
|
1996
|
+
becomes plain black. */
|
|
1997
|
+
const BORDER_SIDES = ['top', 'right', 'bottom', 'left'];
|
|
1998
|
+
const BORDER_STYLE_KEYWORD = /^(none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset)$/i;
|
|
1999
|
+
const BORDER_WIDTH_VALUE = /^(thin|medium|thick|[+-]?(\d+\.?\d*|\.\d+)(px|pt|em|rem|ex|ch|cm|mm|in|pc|q)?)$/i;
|
|
2000
|
+
const BORDER_ZERO_WIDTH = /^[+-]?0*(\.0*)?(px|pt|em|rem|ex|ch|cm|mm|in|pc|q)?$/i;
|
|
2001
|
+
/* border-* properties that describe something other than one of the four sides,
|
|
2002
|
+
plus the CSS-wide keywords a shorthand cannot express. */
|
|
2003
|
+
const BORDER_NOT_A_SIDE = /^border-(collapse|spacing|radius|image|block|inline|start|end)/i;
|
|
2004
|
+
const BORDER_SIDE_PROP = /^border(?:-(top|right|bottom|left))?(?:-(width|style|color))?$/;
|
|
2005
|
+
const CSS_WIDE_KEYWORD = /^(inherit|initial|unset|revert|revert-layer)$/i;
|
|
2006
|
+
/* Split on top-level whitespace, so `rgb(0, 0, 0) solid` stays two values. */
|
|
2007
|
+
function splitCssValues(value) {
|
|
2008
|
+
const out = [];
|
|
2009
|
+
let depth = 0;
|
|
2010
|
+
let cur = '';
|
|
2011
|
+
for (const ch of value) {
|
|
2012
|
+
if (ch === '(')
|
|
2013
|
+
depth++;
|
|
2014
|
+
else if (ch === ')')
|
|
2015
|
+
depth--;
|
|
2016
|
+
if (depth === 0 && /\s/.test(ch)) {
|
|
2017
|
+
if (cur)
|
|
2018
|
+
out.push(cur);
|
|
2019
|
+
cur = '';
|
|
2020
|
+
}
|
|
2021
|
+
else
|
|
2022
|
+
cur += ch;
|
|
2023
|
+
}
|
|
2024
|
+
if (cur)
|
|
2025
|
+
out.push(cur);
|
|
2026
|
+
return out;
|
|
2027
|
+
}
|
|
2028
|
+
/* 1–4 values in CSS box order → an explicit [top, right, bottom, left]. */
|
|
2029
|
+
function boxSides(parts) {
|
|
2030
|
+
const [t, r = t, b = t, l = r] = parts;
|
|
2031
|
+
return [t, r, b, l];
|
|
2032
|
+
}
|
|
2033
|
+
/* `1pt solid windowtext` in any order; whatever the author left out falls back
|
|
2034
|
+
to its initial value, exactly as the real shorthand resets it. */
|
|
2035
|
+
function parseBorderShorthand(value) {
|
|
2036
|
+
const side = { width: 'medium', style: 'none', color: '' };
|
|
2037
|
+
splitCssValues(value).forEach((part) => {
|
|
2038
|
+
if (BORDER_STYLE_KEYWORD.test(part))
|
|
2039
|
+
side.style = part.toLowerCase();
|
|
2040
|
+
else if (BORDER_WIDTH_VALUE.test(part))
|
|
2041
|
+
side.width = part;
|
|
2042
|
+
else
|
|
2043
|
+
side.color = part;
|
|
2044
|
+
});
|
|
2045
|
+
return side;
|
|
2046
|
+
}
|
|
2047
|
+
function emailBorderColor(color) {
|
|
2048
|
+
if (!color || /^currentcolor$/i.test(color))
|
|
2049
|
+
return '';
|
|
2050
|
+
return /^windowtext$/i.test(color) ? '#000000' : color;
|
|
2051
|
+
}
|
|
2052
|
+
/* One side's declaration value, or '' when that side was never mentioned. */
|
|
2053
|
+
function sideBorderValue(side) {
|
|
2054
|
+
if (side.width === null && side.style === null && side.color === null)
|
|
2055
|
+
return '';
|
|
2056
|
+
const style = side.style ?? 'solid';
|
|
2057
|
+
if (style === 'none' || style === 'hidden')
|
|
2058
|
+
return 'none';
|
|
2059
|
+
if (side.width && BORDER_ZERO_WIDTH.test(side.width))
|
|
2060
|
+
return 'none';
|
|
2061
|
+
const width = side.width && side.width !== 'medium' ? side.width : '';
|
|
2062
|
+
return [width, style, emailBorderColor(side.color || '')].filter(Boolean).join(' ');
|
|
2063
|
+
}
|
|
2064
|
+
function hardenTableBorders(root) {
|
|
2065
|
+
root.querySelectorAll('table,td,th').forEach((el) => {
|
|
2066
|
+
const raw = el.getAttribute('style') || '';
|
|
2067
|
+
if (!/border/i.test(raw))
|
|
2068
|
+
return;
|
|
2069
|
+
const sides = {
|
|
2070
|
+
top: { width: null, style: null, color: null },
|
|
2071
|
+
right: { width: null, style: null, color: null },
|
|
2072
|
+
bottom: { width: null, style: null, color: null },
|
|
2073
|
+
left: { width: null, style: null, color: null }
|
|
2074
|
+
};
|
|
2075
|
+
const rest = [];
|
|
2076
|
+
let sawBorder = false;
|
|
2077
|
+
let bail = false;
|
|
2078
|
+
raw.split(';').forEach((decl) => {
|
|
2079
|
+
const i = decl.indexOf(':');
|
|
2080
|
+
if (i < 1) {
|
|
2081
|
+
if (decl.trim())
|
|
2082
|
+
rest.push(decl.trim());
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
2085
|
+
const prop = decl.slice(0, i).trim().toLowerCase();
|
|
2086
|
+
const value = decl.slice(i + 1).trim();
|
|
2087
|
+
const m = BORDER_SIDE_PROP.exec(prop);
|
|
2088
|
+
if (!m || BORDER_NOT_A_SIDE.test(prop)) {
|
|
2089
|
+
rest.push(prop + ':' + value);
|
|
2090
|
+
return;
|
|
2091
|
+
}
|
|
2092
|
+
/* `border-color:inherit` and friends only mean anything against a
|
|
2093
|
+
parent this export has no say over — leave the cell untouched. */
|
|
2094
|
+
if (CSS_WIDE_KEYWORD.test(value)) {
|
|
2095
|
+
bail = true;
|
|
2096
|
+
return;
|
|
2097
|
+
}
|
|
2098
|
+
sawBorder = true;
|
|
2099
|
+
const one = m[1];
|
|
2100
|
+
const part = m[2];
|
|
2101
|
+
if (!part) {
|
|
2102
|
+
/* `border` / `border-<side>` resets all three components. */
|
|
2103
|
+
const parsed = parseBorderShorthand(value);
|
|
2104
|
+
(one ? [one] : BORDER_SIDES.slice()).forEach((s) => {
|
|
2105
|
+
sides[s] = { ...parsed };
|
|
2106
|
+
});
|
|
2107
|
+
}
|
|
2108
|
+
else if (one) {
|
|
2109
|
+
sides[one][part] = value;
|
|
2110
|
+
}
|
|
2111
|
+
else {
|
|
2112
|
+
const per = boxSides(splitCssValues(value));
|
|
2113
|
+
BORDER_SIDES.forEach((s, n) => {
|
|
2114
|
+
sides[s][part] = per[n];
|
|
2115
|
+
});
|
|
2116
|
+
}
|
|
2117
|
+
});
|
|
2118
|
+
if (bail || !sawBorder)
|
|
2119
|
+
return;
|
|
2120
|
+
const values = BORDER_SIDES.map((s) => sideBorderValue(sides[s]));
|
|
2121
|
+
const out = rest.slice();
|
|
2122
|
+
if (values.every((v) => v && v === values[0]))
|
|
2123
|
+
out.push('border:' + values[0]);
|
|
2124
|
+
else
|
|
2125
|
+
BORDER_SIDES.forEach((s, n) => values[n] && out.push(`border-${s}:` + values[n]));
|
|
2126
|
+
el.setAttribute('style', out.join(';'));
|
|
2127
|
+
});
|
|
2128
|
+
}
|
|
1983
2129
|
/* Text belonging to el itself, skipping subtrees rooted at a descendant that
|
|
1984
2130
|
already carries its own explicit rtl/ltr dir — that subtree's direction is
|
|
1985
2131
|
already independently decided and shouldn't sway el's own detection. */
|
|
@@ -2559,6 +2705,19 @@ class FableEditor {
|
|
|
2559
2705
|
if (!/(^|;)\s*height\s*:/i.test(img.getAttribute('style') || ''))
|
|
2560
2706
|
setStyle(img, 'height:auto');
|
|
2561
2707
|
});
|
|
2708
|
+
/* Borders last, so the per-side shorthands it writes are the final word on
|
|
2709
|
+
every cell. Also pin border-collapse inline and mirror it onto the legacy
|
|
2710
|
+
cellspacing attribute: a mail pipeline that drops border-collapse leaves
|
|
2711
|
+
the table in `separate` mode, where any cellspacing of its own would open
|
|
2712
|
+
gaps between Word's deliberately shared cell edges. */
|
|
2713
|
+
hardenTableBorders(box);
|
|
2714
|
+
box.querySelectorAll('table').forEach((tbl) => {
|
|
2715
|
+
const style = tbl.getAttribute('style') || '';
|
|
2716
|
+
if (!/border-collapse\s*:/i.test(style))
|
|
2717
|
+
setStyle(tbl, 'border-collapse:collapse');
|
|
2718
|
+
if (!/border-spacing\s*:/i.test(style) && !tbl.hasAttribute('cellspacing'))
|
|
2719
|
+
tbl.setAttribute('cellspacing', '0');
|
|
2720
|
+
});
|
|
2562
2721
|
const dir = found || this.ed.getAttribute('dir') || this.dir();
|
|
2563
2722
|
box.querySelectorAll('table:not([dir])').forEach((tbl) => {
|
|
2564
2723
|
tbl.setAttribute('dir', dir);
|