@underverse-ui/underverse 1.0.149 → 1.0.150
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/api-reference.json +1 -1
- package/dist/index.cjs +583 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +583 -26
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/api-reference.json
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -25836,6 +25836,8 @@ var import_core5 = require("@tiptap/core");
|
|
|
25836
25836
|
var import_state2 = require("@tiptap/pm/state");
|
|
25837
25837
|
|
|
25838
25838
|
// src/components/UEditor/clipboard-tables.ts
|
|
25839
|
+
var DEFAULT_HTML_TABLE_CELL_BACKGROUND_COLOR = "#ffffff";
|
|
25840
|
+
var DEFAULT_HTML_TABLE_TEXT_COLOR = "#000000";
|
|
25839
25841
|
function getClipboardData(dataTransfer, type) {
|
|
25840
25842
|
try {
|
|
25841
25843
|
return dataTransfer.getData(type) ?? "";
|
|
@@ -25856,6 +25858,411 @@ function extractClipboardHtmlFragment(html) {
|
|
|
25856
25858
|
function normalizeClipboardCellText(value) {
|
|
25857
25859
|
return value.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\u00a0/g, " ").replace(/[ \t]+\n/g, "\n").replace(/\n[ \t]+/g, "\n").replace(/\n+$/g, "").replace(/^\n+/g, "").trim();
|
|
25858
25860
|
}
|
|
25861
|
+
function parseStyleDeclarations(styleText) {
|
|
25862
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
25863
|
+
if (!styleText) return declarations;
|
|
25864
|
+
for (const declaration of styleText.split(";")) {
|
|
25865
|
+
const separatorIndex = declaration.indexOf(":");
|
|
25866
|
+
if (separatorIndex <= 0) continue;
|
|
25867
|
+
const property = declaration.slice(0, separatorIndex).trim().toLowerCase();
|
|
25868
|
+
const value = cleanStyleValue(declaration.slice(separatorIndex + 1));
|
|
25869
|
+
if (!property || !value) continue;
|
|
25870
|
+
declarations.set(property, value);
|
|
25871
|
+
}
|
|
25872
|
+
return declarations;
|
|
25873
|
+
}
|
|
25874
|
+
function mergeStyleDeclarations(...sources) {
|
|
25875
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
25876
|
+
for (const source of sources) {
|
|
25877
|
+
if (!source) continue;
|
|
25878
|
+
for (const [property, value] of source.entries()) {
|
|
25879
|
+
declarations.set(property, value);
|
|
25880
|
+
}
|
|
25881
|
+
}
|
|
25882
|
+
return declarations;
|
|
25883
|
+
}
|
|
25884
|
+
function extractCssClassNames(selectorText) {
|
|
25885
|
+
const classNames = /* @__PURE__ */ new Set();
|
|
25886
|
+
const classNamePattern = /\.([_a-zA-Z-][\w-]*)/g;
|
|
25887
|
+
let match;
|
|
25888
|
+
while ((match = classNamePattern.exec(selectorText)) !== null) {
|
|
25889
|
+
classNames.add(match[1]);
|
|
25890
|
+
}
|
|
25891
|
+
return classNames;
|
|
25892
|
+
}
|
|
25893
|
+
function parseClipboardCssClassStyles(doc) {
|
|
25894
|
+
const styleMap = /* @__PURE__ */ new Map();
|
|
25895
|
+
for (const styleElement of Array.from(doc.querySelectorAll("style"))) {
|
|
25896
|
+
const cssText = (styleElement.textContent ?? "").replace(/<!--|-->/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
25897
|
+
const rulePattern = /([^{}]+)\{([^{}]*)\}/g;
|
|
25898
|
+
let match;
|
|
25899
|
+
while ((match = rulePattern.exec(cssText)) !== null) {
|
|
25900
|
+
const classNames = extractCssClassNames(match[1]);
|
|
25901
|
+
if (classNames.size === 0) continue;
|
|
25902
|
+
const declarations = parseStyleDeclarations(match[2]);
|
|
25903
|
+
if (declarations.size === 0) continue;
|
|
25904
|
+
for (const className of classNames) {
|
|
25905
|
+
styleMap.set(className, mergeStyleDeclarations(styleMap.get(className), declarations));
|
|
25906
|
+
}
|
|
25907
|
+
}
|
|
25908
|
+
}
|
|
25909
|
+
return styleMap;
|
|
25910
|
+
}
|
|
25911
|
+
function getElementStyleDeclarations(element, styleMap) {
|
|
25912
|
+
const classDeclarations = Array.from(element.classList).map((className) => styleMap.get(className));
|
|
25913
|
+
const inlineDeclarations = parseStyleDeclarations(element.getAttribute("style"));
|
|
25914
|
+
return mergeStyleDeclarations(...classDeclarations, inlineDeclarations);
|
|
25915
|
+
}
|
|
25916
|
+
function cleanStyleValue(value) {
|
|
25917
|
+
const normalized = value?.trim();
|
|
25918
|
+
if (!normalized) return null;
|
|
25919
|
+
if (/[\0<>;{}]/.test(normalized)) return null;
|
|
25920
|
+
if (/\b(?:expression|url|(?:repeating-)?(?:linear|radial|conic)-gradient)\s*\(/i.test(normalized)) return null;
|
|
25921
|
+
return normalized;
|
|
25922
|
+
}
|
|
25923
|
+
function normalizeColorValue(value) {
|
|
25924
|
+
const normalized = cleanStyleValue(value);
|
|
25925
|
+
if (!normalized) return null;
|
|
25926
|
+
if (/^(?:auto|inherit|initial|none|transparent|unset)$/i.test(normalized)) return null;
|
|
25927
|
+
return normalized;
|
|
25928
|
+
}
|
|
25929
|
+
function normalizeTextColorValue(value) {
|
|
25930
|
+
const normalized = normalizeColorValue(value);
|
|
25931
|
+
if (!normalized) return null;
|
|
25932
|
+
if (/^(?:automatic|windowtext|black|#000|#000000|rgb\(\s*0\s*,\s*0\s*,\s*0\s*\))$/i.test(normalized)) {
|
|
25933
|
+
return DEFAULT_HTML_TABLE_TEXT_COLOR;
|
|
25934
|
+
}
|
|
25935
|
+
return normalized;
|
|
25936
|
+
}
|
|
25937
|
+
function isWhiteColor(value) {
|
|
25938
|
+
if (!value) return false;
|
|
25939
|
+
return /^(?:white|#fff|#ffffff|rgb\(\s*255\s*,\s*255\s*,\s*255\s*\))$/i.test(value.trim());
|
|
25940
|
+
}
|
|
25941
|
+
function parseCssColorRgb(value) {
|
|
25942
|
+
const normalized = normalizeColorValue(value);
|
|
25943
|
+
if (!normalized) return null;
|
|
25944
|
+
const lowerColor = normalized.toLowerCase();
|
|
25945
|
+
if (lowerColor === "white") return { r: 255, g: 255, b: 255 };
|
|
25946
|
+
if (lowerColor === "black") return { r: 0, g: 0, b: 0 };
|
|
25947
|
+
const hexMatch = lowerColor.match(/^#([\da-f]{3}|[\da-f]{6})$/i);
|
|
25948
|
+
if (hexMatch) {
|
|
25949
|
+
const hex = hexMatch[1];
|
|
25950
|
+
const fullHex = hex.length === 3 ? hex.split("").map((part) => part + part).join("") : hex;
|
|
25951
|
+
return {
|
|
25952
|
+
r: Number.parseInt(fullHex.slice(0, 2), 16),
|
|
25953
|
+
g: Number.parseInt(fullHex.slice(2, 4), 16),
|
|
25954
|
+
b: Number.parseInt(fullHex.slice(4, 6), 16)
|
|
25955
|
+
};
|
|
25956
|
+
}
|
|
25957
|
+
const rgbMatch = lowerColor.match(/^rgba?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)/);
|
|
25958
|
+
if (rgbMatch) {
|
|
25959
|
+
return {
|
|
25960
|
+
r: Math.max(0, Math.min(255, Number.parseFloat(rgbMatch[1]))),
|
|
25961
|
+
g: Math.max(0, Math.min(255, Number.parseFloat(rgbMatch[2]))),
|
|
25962
|
+
b: Math.max(0, Math.min(255, Number.parseFloat(rgbMatch[3])))
|
|
25963
|
+
};
|
|
25964
|
+
}
|
|
25965
|
+
return null;
|
|
25966
|
+
}
|
|
25967
|
+
function getRelativeLuminance(value) {
|
|
25968
|
+
const rgb = parseCssColorRgb(value);
|
|
25969
|
+
if (!rgb) return null;
|
|
25970
|
+
const toLinear = (channel) => {
|
|
25971
|
+
const normalized = channel / 255;
|
|
25972
|
+
return normalized <= 0.03928 ? normalized / 12.92 : ((normalized + 0.055) / 1.055) ** 2.4;
|
|
25973
|
+
};
|
|
25974
|
+
return 0.2126 * toLinear(rgb.r) + 0.7152 * toLinear(rgb.g) + 0.0722 * toLinear(rgb.b);
|
|
25975
|
+
}
|
|
25976
|
+
function isLightTextColor(value) {
|
|
25977
|
+
const luminance = getRelativeLuminance(value);
|
|
25978
|
+
return luminance !== null && luminance >= 0.72;
|
|
25979
|
+
}
|
|
25980
|
+
function isDarkReadableBackground(value) {
|
|
25981
|
+
const luminance = getRelativeLuminance(value);
|
|
25982
|
+
return luminance !== null && luminance <= 0.45;
|
|
25983
|
+
}
|
|
25984
|
+
function splitCssTokens(value) {
|
|
25985
|
+
const tokens = [];
|
|
25986
|
+
let current = "";
|
|
25987
|
+
let depth = 0;
|
|
25988
|
+
for (const char of value) {
|
|
25989
|
+
if (char === "(") depth += 1;
|
|
25990
|
+
if (char === ")") depth = Math.max(0, depth - 1);
|
|
25991
|
+
if (/\s/.test(char) && depth === 0) {
|
|
25992
|
+
if (current) {
|
|
25993
|
+
tokens.push(current);
|
|
25994
|
+
current = "";
|
|
25995
|
+
}
|
|
25996
|
+
continue;
|
|
25997
|
+
}
|
|
25998
|
+
current += char;
|
|
25999
|
+
}
|
|
26000
|
+
if (current) tokens.push(current);
|
|
26001
|
+
return tokens;
|
|
26002
|
+
}
|
|
26003
|
+
function extractColorFromShorthand(value) {
|
|
26004
|
+
const normalized = cleanStyleValue(value);
|
|
26005
|
+
if (!normalized) return null;
|
|
26006
|
+
const explicitColor = normalized.match(/#[\da-f]{3,8}\b|rgba?\([^)]+\)|hsla?\([^)]+\)/i);
|
|
26007
|
+
if (explicitColor) return explicitColor[0];
|
|
26008
|
+
const ignoredKeywords = /* @__PURE__ */ new Set([
|
|
26009
|
+
"border-box",
|
|
26010
|
+
"center",
|
|
26011
|
+
"contain",
|
|
26012
|
+
"content-box",
|
|
26013
|
+
"cover",
|
|
26014
|
+
"fixed",
|
|
26015
|
+
"inherit",
|
|
26016
|
+
"initial",
|
|
26017
|
+
"left",
|
|
26018
|
+
"local",
|
|
26019
|
+
"none",
|
|
26020
|
+
"no-repeat",
|
|
26021
|
+
"padding-box",
|
|
26022
|
+
"repeat",
|
|
26023
|
+
"repeat-x",
|
|
26024
|
+
"repeat-y",
|
|
26025
|
+
"right",
|
|
26026
|
+
"scroll",
|
|
26027
|
+
"top",
|
|
26028
|
+
"transparent",
|
|
26029
|
+
"unset"
|
|
26030
|
+
]);
|
|
26031
|
+
return splitCssTokens(normalized).find((token) => !ignoredKeywords.has(token.toLowerCase())) ?? null;
|
|
26032
|
+
}
|
|
26033
|
+
function getBackgroundColor(styles) {
|
|
26034
|
+
return normalizeColorValue(styles.get("background-color")) ?? normalizeColorValue(extractColorFromShorthand(styles.get("background")));
|
|
26035
|
+
}
|
|
26036
|
+
var BORDER_STYLES = /* @__PURE__ */ new Set([
|
|
26037
|
+
"dashed",
|
|
26038
|
+
"dotted",
|
|
26039
|
+
"double",
|
|
26040
|
+
"groove",
|
|
26041
|
+
"hidden",
|
|
26042
|
+
"inset",
|
|
26043
|
+
"none",
|
|
26044
|
+
"outset",
|
|
26045
|
+
"ridge",
|
|
26046
|
+
"solid"
|
|
26047
|
+
]);
|
|
26048
|
+
var BORDER_WIDTH_KEYWORDS = /* @__PURE__ */ new Set(["medium", "thick", "thin"]);
|
|
26049
|
+
function normalizeBorderStyle(value) {
|
|
26050
|
+
const normalized = cleanStyleValue(value);
|
|
26051
|
+
if (!normalized) return null;
|
|
26052
|
+
const styles = splitCssTokens(normalized).filter((token) => BORDER_STYLES.has(token.toLowerCase()));
|
|
26053
|
+
const usefulStyles = styles.filter((style) => !/^(?:hidden|none)$/i.test(style));
|
|
26054
|
+
return usefulStyles.length > 0 ? usefulStyles.join(" ") : null;
|
|
26055
|
+
}
|
|
26056
|
+
function normalizeBorderWidth(value) {
|
|
26057
|
+
const normalized = cleanStyleValue(value);
|
|
26058
|
+
if (!normalized) return null;
|
|
26059
|
+
const widths = splitCssTokens(normalized).filter((token) => {
|
|
26060
|
+
const lowerToken = token.toLowerCase();
|
|
26061
|
+
return BORDER_WIDTH_KEYWORDS.has(lowerToken) || /^\d*\.?\d+(?:px|pt|pc|in|cm|mm|em|rem)?$/i.test(token);
|
|
26062
|
+
});
|
|
26063
|
+
return widths.length > 0 ? widths.join(" ") : null;
|
|
26064
|
+
}
|
|
26065
|
+
function parseBorderShorthand(value) {
|
|
26066
|
+
const normalized = cleanStyleValue(value);
|
|
26067
|
+
if (!normalized) return null;
|
|
26068
|
+
const tokens = splitCssTokens(normalized);
|
|
26069
|
+
let borderStyle = null;
|
|
26070
|
+
let borderWidth = null;
|
|
26071
|
+
const colorTokens = [];
|
|
26072
|
+
for (const token of tokens) {
|
|
26073
|
+
const lowerToken = token.toLowerCase();
|
|
26074
|
+
if (!borderStyle && BORDER_STYLES.has(lowerToken)) {
|
|
26075
|
+
borderStyle = lowerToken;
|
|
26076
|
+
continue;
|
|
26077
|
+
}
|
|
26078
|
+
if (!borderWidth && (BORDER_WIDTH_KEYWORDS.has(lowerToken) || /^\d*\.?\d+(?:px|pt|pc|in|cm|mm|em|rem)?$/i.test(token))) {
|
|
26079
|
+
borderWidth = token;
|
|
26080
|
+
continue;
|
|
26081
|
+
}
|
|
26082
|
+
colorTokens.push(token);
|
|
26083
|
+
}
|
|
26084
|
+
if (borderStyle && /^(?:hidden|none)$/i.test(borderStyle)) return null;
|
|
26085
|
+
return {
|
|
26086
|
+
borderColor: normalizeColorValue(colorTokens.join(" ")),
|
|
26087
|
+
borderStyle,
|
|
26088
|
+
borderWidth
|
|
26089
|
+
};
|
|
26090
|
+
}
|
|
26091
|
+
function getFirstParsedBorder(styles) {
|
|
26092
|
+
for (const property of ["border", "border-top", "border-right", "border-bottom", "border-left"]) {
|
|
26093
|
+
const border = parseBorderShorthand(styles.get(property));
|
|
26094
|
+
if (border) return border;
|
|
26095
|
+
}
|
|
26096
|
+
return null;
|
|
26097
|
+
}
|
|
26098
|
+
function getBorderAttrs(styles) {
|
|
26099
|
+
const parsedBorder = getFirstParsedBorder(styles);
|
|
26100
|
+
return {
|
|
26101
|
+
borderColor: normalizeColorValue(styles.get("border-color")) ?? parsedBorder?.borderColor ?? void 0,
|
|
26102
|
+
borderStyle: normalizeBorderStyle(styles.get("border-style")) ?? parsedBorder?.borderStyle ?? void 0,
|
|
26103
|
+
borderWidth: normalizeBorderWidth(styles.get("border-width")) ?? parsedBorder?.borderWidth ?? void 0
|
|
26104
|
+
};
|
|
26105
|
+
}
|
|
26106
|
+
function parsePositiveInteger(value, max = 100) {
|
|
26107
|
+
if (!value) return null;
|
|
26108
|
+
const parsed = Number.parseInt(value, 10);
|
|
26109
|
+
if (!Number.isFinite(parsed) || parsed < 1) return null;
|
|
26110
|
+
return Math.min(parsed, max);
|
|
26111
|
+
}
|
|
26112
|
+
function parseCssSize(value) {
|
|
26113
|
+
const normalized = cleanStyleValue(value);
|
|
26114
|
+
if (!normalized) return null;
|
|
26115
|
+
const match = normalized.match(/^(\d+(?:\.\d+)?)(px|pt)?$/i);
|
|
26116
|
+
if (!match) return null;
|
|
26117
|
+
const amount = Number.parseFloat(match[1]);
|
|
26118
|
+
if (!Number.isFinite(amount) || amount <= 0) return null;
|
|
26119
|
+
return Math.round(match[2]?.toLowerCase() === "pt" ? amount * (4 / 3) : amount);
|
|
26120
|
+
}
|
|
26121
|
+
function getCellWidth(cell, styles, colspan) {
|
|
26122
|
+
if (colspan !== 1) return null;
|
|
26123
|
+
const width = parseCssSize(cell.getAttribute("data-colwidth") ?? cell.getAttribute("width") ?? styles.get("width"));
|
|
26124
|
+
return width ? [width] : null;
|
|
26125
|
+
}
|
|
26126
|
+
function getTableRowAttrs(row, styles) {
|
|
26127
|
+
const rowHeight = parseCssSize(
|
|
26128
|
+
row.getAttribute("data-row-height") ?? row.getAttribute("height") ?? styles.get("height")
|
|
26129
|
+
);
|
|
26130
|
+
return rowHeight ? { rowHeight } : void 0;
|
|
26131
|
+
}
|
|
26132
|
+
function getTableCellAttrs(cell, styles, defaultBackgroundColor) {
|
|
26133
|
+
const colspan = parsePositiveInteger(cell.getAttribute("colspan")) ?? 1;
|
|
26134
|
+
const rowspan = parsePositiveInteger(cell.getAttribute("rowspan")) ?? 1;
|
|
26135
|
+
const backgroundColor = getBackgroundColor(styles) ?? normalizeColorValue(cell.getAttribute("data-background-color")) ?? normalizeColorValue(cell.getAttribute("bgcolor")) ?? defaultBackgroundColor;
|
|
26136
|
+
const borderAttrs = getBorderAttrs(styles);
|
|
26137
|
+
const colwidth = getCellWidth(cell, styles, colspan);
|
|
26138
|
+
const attrs = {};
|
|
26139
|
+
if (backgroundColor) attrs.backgroundColor = backgroundColor;
|
|
26140
|
+
if (borderAttrs.borderColor) attrs.borderColor = borderAttrs.borderColor;
|
|
26141
|
+
if (borderAttrs.borderStyle) attrs.borderStyle = borderAttrs.borderStyle;
|
|
26142
|
+
if (borderAttrs.borderWidth) attrs.borderWidth = borderAttrs.borderWidth;
|
|
26143
|
+
if (colspan > 1) attrs.colspan = colspan;
|
|
26144
|
+
if (rowspan > 1) attrs.rowspan = rowspan;
|
|
26145
|
+
if (colwidth) attrs.colwidth = colwidth;
|
|
26146
|
+
return Object.keys(attrs).length > 0 ? attrs : void 0;
|
|
26147
|
+
}
|
|
26148
|
+
function marksEqual(left, right) {
|
|
26149
|
+
return JSON.stringify(left ?? []) === JSON.stringify(right ?? []);
|
|
26150
|
+
}
|
|
26151
|
+
function mergeMarks(base2, additions) {
|
|
26152
|
+
const next = [...base2 ?? []];
|
|
26153
|
+
for (const addition of additions ?? []) {
|
|
26154
|
+
const existingIndex = next.findIndex((mark) => mark.type === addition.type);
|
|
26155
|
+
if (existingIndex >= 0) {
|
|
26156
|
+
const existingMark = next[existingIndex];
|
|
26157
|
+
next[existingIndex] = {
|
|
26158
|
+
...existingMark,
|
|
26159
|
+
attrs: {
|
|
26160
|
+
...existingMark.attrs ?? {},
|
|
26161
|
+
...addition.attrs ?? {}
|
|
26162
|
+
}
|
|
26163
|
+
};
|
|
26164
|
+
continue;
|
|
26165
|
+
}
|
|
26166
|
+
next.push(addition);
|
|
26167
|
+
}
|
|
26168
|
+
return next.length > 0 ? next : void 0;
|
|
26169
|
+
}
|
|
26170
|
+
function getMarkColor(marks, markType) {
|
|
26171
|
+
const mark = marks?.find((candidate) => candidate.type === markType);
|
|
26172
|
+
const color = mark?.attrs?.color;
|
|
26173
|
+
return typeof color === "string" ? color : null;
|
|
26174
|
+
}
|
|
26175
|
+
function replaceTextStyleColor(marks, color) {
|
|
26176
|
+
let replaced = false;
|
|
26177
|
+
const next = (marks ?? []).map((mark) => {
|
|
26178
|
+
if (mark.type !== "textStyle") return mark;
|
|
26179
|
+
replaced = true;
|
|
26180
|
+
return {
|
|
26181
|
+
...mark,
|
|
26182
|
+
attrs: {
|
|
26183
|
+
...mark.attrs ?? {},
|
|
26184
|
+
color
|
|
26185
|
+
}
|
|
26186
|
+
};
|
|
26187
|
+
});
|
|
26188
|
+
if (!replaced) {
|
|
26189
|
+
next.unshift({ type: "textStyle", attrs: { color } });
|
|
26190
|
+
}
|
|
26191
|
+
return next;
|
|
26192
|
+
}
|
|
26193
|
+
function ensureReadableSpreadsheetSegments(segments, cellBackgroundColor) {
|
|
26194
|
+
return segments.map((segment) => {
|
|
26195
|
+
const textColor = getMarkColor(segment.marks, "textStyle");
|
|
26196
|
+
if (!isLightTextColor(textColor)) return segment;
|
|
26197
|
+
const inlineBackgroundColor = getMarkColor(segment.marks, "highlight");
|
|
26198
|
+
if (isDarkReadableBackground(inlineBackgroundColor) || isDarkReadableBackground(cellBackgroundColor)) {
|
|
26199
|
+
return segment;
|
|
26200
|
+
}
|
|
26201
|
+
return {
|
|
26202
|
+
...segment,
|
|
26203
|
+
marks: replaceTextStyleColor(segment.marks, DEFAULT_HTML_TABLE_TEXT_COLOR)
|
|
26204
|
+
};
|
|
26205
|
+
});
|
|
26206
|
+
}
|
|
26207
|
+
function getElementInlineMarks(element, styles) {
|
|
26208
|
+
const marks = [];
|
|
26209
|
+
const tagName = element.tagName;
|
|
26210
|
+
const color = normalizeTextColorValue(styles.get("color") ?? element.getAttribute("color"));
|
|
26211
|
+
const backgroundColor = getBackgroundColor(styles);
|
|
26212
|
+
const fontWeight = styles.get("font-weight")?.toLowerCase();
|
|
26213
|
+
const fontStyle = styles.get("font-style")?.toLowerCase();
|
|
26214
|
+
const textDecoration = styles.get("text-decoration")?.toLowerCase();
|
|
26215
|
+
if (color) {
|
|
26216
|
+
marks.push({ type: "textStyle", attrs: { color } });
|
|
26217
|
+
}
|
|
26218
|
+
if (backgroundColor && !isWhiteColor(backgroundColor)) {
|
|
26219
|
+
marks.push({ type: "highlight", attrs: { color: backgroundColor } });
|
|
26220
|
+
}
|
|
26221
|
+
if (tagName === "B" || tagName === "STRONG" || fontWeight === "bold" || /^\d+$/.test(fontWeight ?? "") && Number(fontWeight) >= 600) {
|
|
26222
|
+
marks.push({ type: "bold" });
|
|
26223
|
+
}
|
|
26224
|
+
if (tagName === "I" || tagName === "EM" || fontStyle === "italic") {
|
|
26225
|
+
marks.push({ type: "italic" });
|
|
26226
|
+
}
|
|
26227
|
+
if (tagName === "U" || textDecoration?.includes("underline")) {
|
|
26228
|
+
marks.push({ type: "underline" });
|
|
26229
|
+
}
|
|
26230
|
+
return marks.length > 0 ? marks : void 0;
|
|
26231
|
+
}
|
|
26232
|
+
function appendTextSegment(segments, segment) {
|
|
26233
|
+
if (!segment.text) return;
|
|
26234
|
+
const lastSegment = segments[segments.length - 1];
|
|
26235
|
+
if (lastSegment && marksEqual(lastSegment.marks, segment.marks)) {
|
|
26236
|
+
lastSegment.text += segment.text;
|
|
26237
|
+
return;
|
|
26238
|
+
}
|
|
26239
|
+
segments.push(segment);
|
|
26240
|
+
}
|
|
26241
|
+
function segmentsEndWithNewline(segments) {
|
|
26242
|
+
return segments.length > 0 && segments[segments.length - 1].text.endsWith("\n");
|
|
26243
|
+
}
|
|
26244
|
+
function normalizeClipboardTextSegments(segments) {
|
|
26245
|
+
const normalizedSegments = [];
|
|
26246
|
+
for (const segment of segments) {
|
|
26247
|
+
appendTextSegment(normalizedSegments, {
|
|
26248
|
+
text: segment.text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\u00a0/g, " "),
|
|
26249
|
+
marks: segment.marks
|
|
26250
|
+
});
|
|
26251
|
+
}
|
|
26252
|
+
while (normalizedSegments.length > 0) {
|
|
26253
|
+
const firstSegment = normalizedSegments[0];
|
|
26254
|
+
firstSegment.text = firstSegment.text.replace(/^\s+/, "");
|
|
26255
|
+
if (firstSegment.text) break;
|
|
26256
|
+
normalizedSegments.shift();
|
|
26257
|
+
}
|
|
26258
|
+
while (normalizedSegments.length > 0) {
|
|
26259
|
+
const lastSegment = normalizedSegments[normalizedSegments.length - 1];
|
|
26260
|
+
lastSegment.text = lastSegment.text.replace(/\s+$/, "");
|
|
26261
|
+
if (lastSegment.text) break;
|
|
26262
|
+
normalizedSegments.pop();
|
|
26263
|
+
}
|
|
26264
|
+
return normalizedSegments;
|
|
26265
|
+
}
|
|
25859
26266
|
function getClipboardCellText(node) {
|
|
25860
26267
|
if (node.nodeType === Node.TEXT_NODE) {
|
|
25861
26268
|
return node.textContent ?? "";
|
|
@@ -25873,57 +26280,205 @@ function getClipboardCellText(node) {
|
|
|
25873
26280
|
}
|
|
25874
26281
|
return childText;
|
|
25875
26282
|
}
|
|
25876
|
-
function
|
|
26283
|
+
function getClipboardCellSegments(node, styleMap, inheritedMarks) {
|
|
26284
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
26285
|
+
return [{ text: node.textContent ?? "", marks: inheritedMarks }];
|
|
26286
|
+
}
|
|
26287
|
+
if (!(node instanceof HTMLElement)) {
|
|
26288
|
+
return [];
|
|
26289
|
+
}
|
|
26290
|
+
if (node.tagName === "BR") {
|
|
26291
|
+
return [{ text: "\n", marks: inheritedMarks }];
|
|
26292
|
+
}
|
|
26293
|
+
const styles = getElementStyleDeclarations(node, styleMap);
|
|
26294
|
+
const marks = mergeMarks(inheritedMarks, getElementInlineMarks(node, styles));
|
|
26295
|
+
const segments = [];
|
|
26296
|
+
for (const childNode of Array.from(node.childNodes)) {
|
|
26297
|
+
for (const segment of getClipboardCellSegments(childNode, styleMap, marks)) {
|
|
26298
|
+
appendTextSegment(segments, segment);
|
|
26299
|
+
}
|
|
26300
|
+
}
|
|
26301
|
+
if ((node.tagName === "P" || node.tagName === "DIV" || node.tagName === "LI") && segments.length > 0 && !segmentsEndWithNewline(segments)) {
|
|
26302
|
+
appendTextSegment(segments, { text: "\n" });
|
|
26303
|
+
}
|
|
26304
|
+
return segments;
|
|
26305
|
+
}
|
|
26306
|
+
function getClipboardCellChildSegments(cell, styleMap, inheritedMarks) {
|
|
26307
|
+
const segments = [];
|
|
26308
|
+
for (const childNode of Array.from(cell.childNodes)) {
|
|
26309
|
+
for (const segment of getClipboardCellSegments(childNode, styleMap, inheritedMarks)) {
|
|
26310
|
+
appendTextSegment(segments, segment);
|
|
26311
|
+
}
|
|
26312
|
+
}
|
|
26313
|
+
return normalizeClipboardTextSegments(segments);
|
|
26314
|
+
}
|
|
26315
|
+
function getHtmlTableRows(table, styleMap) {
|
|
25877
26316
|
const rows = Array.from(table.querySelectorAll("tr")).map(
|
|
25878
|
-
(row) =>
|
|
25879
|
-
|
|
25880
|
-
|
|
25881
|
-
|
|
26317
|
+
(row) => ({
|
|
26318
|
+
attrs: getTableRowAttrs(row, getElementStyleDeclarations(row, styleMap)),
|
|
26319
|
+
cells: Array.from(row.children).filter((cell) => cell instanceof HTMLTableCellElement).map((cell) => {
|
|
26320
|
+
const styles = getElementStyleDeclarations(cell, styleMap);
|
|
26321
|
+
const textColor = normalizeTextColorValue(styles.get("color")) ?? DEFAULT_HTML_TABLE_TEXT_COLOR;
|
|
26322
|
+
const inheritedMarks = [{ type: "textStyle", attrs: { color: textColor } }];
|
|
26323
|
+
const attrs = getTableCellAttrs(cell, styles, DEFAULT_HTML_TABLE_CELL_BACKGROUND_COLOR);
|
|
26324
|
+
const segments = ensureReadableSpreadsheetSegments(
|
|
26325
|
+
getClipboardCellChildSegments(cell, styleMap, inheritedMarks),
|
|
26326
|
+
attrs?.backgroundColor
|
|
26327
|
+
);
|
|
26328
|
+
return {
|
|
26329
|
+
text: normalizeClipboardCellText(getClipboardCellText(cell)),
|
|
26330
|
+
isHeader: cell.tagName === "TH",
|
|
26331
|
+
attrs,
|
|
26332
|
+
segments: segments.length > 0 ? segments : void 0,
|
|
26333
|
+
textColor
|
|
26334
|
+
};
|
|
26335
|
+
})
|
|
26336
|
+
})
|
|
25882
26337
|
);
|
|
25883
|
-
return rows.filter((row) => row.length > 0);
|
|
26338
|
+
return rows.filter((row) => row.cells.length > 0);
|
|
26339
|
+
}
|
|
26340
|
+
function createTextMarks(cell) {
|
|
26341
|
+
return cell.textColor ? [{ type: "textStyle", attrs: { color: cell.textColor } }] : void 0;
|
|
25884
26342
|
}
|
|
25885
|
-
function createParagraphContent(text) {
|
|
26343
|
+
function createParagraphContent(text, marks) {
|
|
25886
26344
|
return text ? {
|
|
25887
26345
|
type: "paragraph",
|
|
25888
|
-
content: [{ type: "text", text }]
|
|
26346
|
+
content: [{ type: "text", text, ...marks ? { marks } : {} }]
|
|
25889
26347
|
} : { type: "paragraph" };
|
|
25890
26348
|
}
|
|
26349
|
+
function createParagraphContentFromSegments(segments) {
|
|
26350
|
+
const paragraphs = [[]];
|
|
26351
|
+
for (const segment of segments) {
|
|
26352
|
+
const parts = segment.text.split("\n");
|
|
26353
|
+
parts.forEach((part, index) => {
|
|
26354
|
+
if (part) {
|
|
26355
|
+
paragraphs[paragraphs.length - 1].push({ text: part, marks: segment.marks });
|
|
26356
|
+
}
|
|
26357
|
+
if (index < parts.length - 1) {
|
|
26358
|
+
paragraphs.push([]);
|
|
26359
|
+
}
|
|
26360
|
+
});
|
|
26361
|
+
}
|
|
26362
|
+
return paragraphs.map((paragraphSegments) => {
|
|
26363
|
+
const content = paragraphSegments.map((segment) => ({
|
|
26364
|
+
type: "text",
|
|
26365
|
+
text: segment.text,
|
|
26366
|
+
...segment.marks ? { marks: segment.marks } : {}
|
|
26367
|
+
}));
|
|
26368
|
+
return content.length > 0 ? { type: "paragraph", content } : { type: "paragraph" };
|
|
26369
|
+
});
|
|
26370
|
+
}
|
|
25891
26371
|
function createTableCellContent(cell) {
|
|
25892
26372
|
const lines = cell.text.split("\n");
|
|
25893
|
-
const
|
|
26373
|
+
const marks = createTextMarks(cell);
|
|
26374
|
+
const paragraphs = cell.segments && cell.segments.length > 0 ? createParagraphContentFromSegments(cell.segments) : (lines.length > 0 ? lines : [""]).map((line) => createParagraphContent(line, marks));
|
|
25894
26375
|
return {
|
|
25895
26376
|
type: cell.isHeader ? "tableHeader" : "tableCell",
|
|
26377
|
+
...cell.attrs ? { attrs: cell.attrs } : {},
|
|
25896
26378
|
content: paragraphs.length > 0 ? paragraphs : [{ type: "paragraph" }]
|
|
25897
26379
|
};
|
|
25898
26380
|
}
|
|
25899
|
-
function
|
|
25900
|
-
const
|
|
26381
|
+
function getRowspanLimitedCell(cell, remainingRowCount) {
|
|
26382
|
+
const attrs = cell.attrs;
|
|
26383
|
+
if (!attrs?.rowspan || attrs.rowspan <= remainingRowCount) return cell;
|
|
26384
|
+
if (remainingRowCount <= 1) {
|
|
26385
|
+
const { rowspan: _rowspan, ...nextAttrs } = attrs;
|
|
26386
|
+
return {
|
|
26387
|
+
...cell,
|
|
26388
|
+
attrs: Object.keys(nextAttrs).length > 0 ? nextAttrs : void 0
|
|
26389
|
+
};
|
|
26390
|
+
}
|
|
26391
|
+
return {
|
|
26392
|
+
...cell,
|
|
26393
|
+
attrs: {
|
|
26394
|
+
...attrs,
|
|
26395
|
+
rowspan: remainingRowCount
|
|
26396
|
+
}
|
|
26397
|
+
};
|
|
26398
|
+
}
|
|
26399
|
+
function normalizeTableRows(rows) {
|
|
26400
|
+
const positionedRows = [];
|
|
26401
|
+
let rowspans = [];
|
|
26402
|
+
let columnCount = 0;
|
|
26403
|
+
rows.forEach((row, rowIndex) => {
|
|
26404
|
+
const coveredColumns = rowspans.map((span) => span > 0);
|
|
26405
|
+
const nextRowspans = rowspans.map((span) => Math.max(0, span - 1));
|
|
26406
|
+
const positionedCells = [];
|
|
26407
|
+
let columnIndex = 0;
|
|
26408
|
+
for (const rawCell of row.cells) {
|
|
26409
|
+
while (coveredColumns[columnIndex]) columnIndex += 1;
|
|
26410
|
+
const remainingRowCount = rows.length - rowIndex;
|
|
26411
|
+
const cell = getRowspanLimitedCell(rawCell, remainingRowCount);
|
|
26412
|
+
const colspan = Math.max(1, cell.attrs?.colspan ?? 1);
|
|
26413
|
+
const rowspan = Math.max(1, cell.attrs?.rowspan ?? 1);
|
|
26414
|
+
positionedCells.push({ startColumn: columnIndex, colspan, cell });
|
|
26415
|
+
if (rowspan > 1) {
|
|
26416
|
+
for (let offset = 0; offset < colspan; offset += 1) {
|
|
26417
|
+
const spannedColumn = columnIndex + offset;
|
|
26418
|
+
nextRowspans[spannedColumn] = Math.max(nextRowspans[spannedColumn] ?? 0, rowspan - 1);
|
|
26419
|
+
}
|
|
26420
|
+
}
|
|
26421
|
+
columnIndex += colspan;
|
|
26422
|
+
}
|
|
26423
|
+
const lastCoveredColumn = coveredColumns.reduce((lastIndex, covered, index) => covered ? index : lastIndex, -1);
|
|
26424
|
+
const lastFutureRowspanColumn = nextRowspans.reduce((lastIndex, span, index) => span > 0 ? index : lastIndex, -1);
|
|
26425
|
+
columnCount = Math.max(columnCount, columnIndex, lastCoveredColumn + 1, lastFutureRowspanColumn + 1);
|
|
26426
|
+
positionedRows.push({
|
|
26427
|
+
attrs: row.attrs,
|
|
26428
|
+
cells: positionedCells,
|
|
26429
|
+
coveredColumns
|
|
26430
|
+
});
|
|
26431
|
+
rowspans = nextRowspans;
|
|
26432
|
+
});
|
|
26433
|
+
return { positionedRows, columnCount };
|
|
26434
|
+
}
|
|
26435
|
+
function createNormalizedRowContent(row, columnCount, fillerCellAttrs) {
|
|
26436
|
+
const content = [];
|
|
26437
|
+
const cellByStartColumn = new Map(row.cells.map((cell) => [cell.startColumn, cell]));
|
|
26438
|
+
let columnIndex = 0;
|
|
26439
|
+
while (columnIndex < columnCount) {
|
|
26440
|
+
if (row.coveredColumns[columnIndex]) {
|
|
26441
|
+
columnIndex += 1;
|
|
26442
|
+
continue;
|
|
26443
|
+
}
|
|
26444
|
+
const positionedCell = cellByStartColumn.get(columnIndex);
|
|
26445
|
+
if (positionedCell) {
|
|
26446
|
+
content.push(createTableCellContent(positionedCell.cell));
|
|
26447
|
+
columnIndex += positionedCell.colspan;
|
|
26448
|
+
continue;
|
|
26449
|
+
}
|
|
26450
|
+
content.push(createTableCellContent({ text: "", isHeader: false, attrs: fillerCellAttrs }));
|
|
26451
|
+
columnIndex += 1;
|
|
26452
|
+
}
|
|
26453
|
+
return content;
|
|
26454
|
+
}
|
|
26455
|
+
function createTableContent(rows, minColumnCount = 1, fillerCellAttrs) {
|
|
26456
|
+
const tableRows = rows.filter((row) => row.cells.length > 0);
|
|
25901
26457
|
if (tableRows.length === 0) return null;
|
|
25902
|
-
const
|
|
26458
|
+
const { positionedRows, columnCount } = normalizeTableRows(tableRows);
|
|
25903
26459
|
if (columnCount < minColumnCount) return null;
|
|
25904
26460
|
return {
|
|
25905
26461
|
type: "table",
|
|
25906
|
-
content:
|
|
25907
|
-
|
|
25908
|
-
|
|
25909
|
-
|
|
25910
|
-
|
|
25911
|
-
return {
|
|
25912
|
-
type: "tableRow",
|
|
25913
|
-
content: normalizedRow.map(createTableCellContent)
|
|
25914
|
-
};
|
|
25915
|
-
})
|
|
26462
|
+
content: positionedRows.map((row) => ({
|
|
26463
|
+
type: "tableRow",
|
|
26464
|
+
...row.attrs ? { attrs: row.attrs } : {},
|
|
26465
|
+
content: createNormalizedRowContent(row, columnCount, fillerCellAttrs)
|
|
26466
|
+
}))
|
|
25916
26467
|
};
|
|
25917
26468
|
}
|
|
25918
26469
|
function getClipboardTableContent(dataTransfer) {
|
|
25919
26470
|
const html = getClipboardData(dataTransfer, "text/html");
|
|
25920
26471
|
if (!/<table(?:\s|>)/i.test(html)) return null;
|
|
25921
26472
|
if (typeof DOMParser === "undefined") return null;
|
|
26473
|
+
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
25922
26474
|
const fragment = extractClipboardHtmlFragment(html);
|
|
25923
|
-
const
|
|
25924
|
-
const
|
|
26475
|
+
const fragmentDoc = new DOMParser().parseFromString(fragment, "text/html");
|
|
26476
|
+
const styleMap = parseClipboardCssClassStyles(doc);
|
|
26477
|
+
const table = fragmentDoc.querySelector("table") ?? doc.querySelector("table");
|
|
25925
26478
|
if (!(table instanceof HTMLTableElement)) return null;
|
|
25926
|
-
return createTableContent(getHtmlTableRows(table)
|
|
26479
|
+
return createTableContent(getHtmlTableRows(table, styleMap), 1, {
|
|
26480
|
+
backgroundColor: DEFAULT_HTML_TABLE_CELL_BACKGROUND_COLOR
|
|
26481
|
+
});
|
|
25927
26482
|
}
|
|
25928
26483
|
function parseClipboardTsvRows(text) {
|
|
25929
26484
|
const rows = [];
|
|
@@ -25980,7 +26535,9 @@ function getClipboardTsvTableContent(dataTransfer) {
|
|
|
25980
26535
|
if (!text.includes(" ")) return null;
|
|
25981
26536
|
const rows = parseClipboardTsvRows(text);
|
|
25982
26537
|
return createTableContent(
|
|
25983
|
-
rows.map((row) =>
|
|
26538
|
+
rows.map((row) => ({
|
|
26539
|
+
cells: row.map((cell) => ({ text: normalizeClipboardCellText(cell), isHeader: false }))
|
|
26540
|
+
})),
|
|
25984
26541
|
2
|
|
25985
26542
|
);
|
|
25986
26543
|
}
|