@pixui-dev/pixui-richtext-helper 0.2.12-dev.1 → 0.2.12
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/RichTextCore.d.ts +25 -2
- package/dist/RichTextCore.js +223 -56
- package/package.json +1 -1
- package/readme.md +5 -0
package/dist/RichTextCore.d.ts
CHANGED
|
@@ -12,6 +12,20 @@ export interface LinkClickParams {
|
|
|
12
12
|
href: string;
|
|
13
13
|
id: string;
|
|
14
14
|
}
|
|
15
|
+
/** 单个阶段耗时 */
|
|
16
|
+
export interface ConvertDebugPhase {
|
|
17
|
+
name: string;
|
|
18
|
+
ms: number;
|
|
19
|
+
}
|
|
20
|
+
/** 转换调试统计 */
|
|
21
|
+
export interface ConvertDebugStats {
|
|
22
|
+
totalMs: number;
|
|
23
|
+
highLevel: ConvertDebugPhase[];
|
|
24
|
+
detail: ConvertDebugPhase[];
|
|
25
|
+
segmentsCount: number;
|
|
26
|
+
version: string;
|
|
27
|
+
timestamp: number;
|
|
28
|
+
}
|
|
15
29
|
/*****************************************************
|
|
16
30
|
* RichTextCore 主类
|
|
17
31
|
*****************************************************/
|
|
@@ -20,6 +34,8 @@ export declare class RichTextCore {
|
|
|
20
34
|
private static hrefIdCnt;
|
|
21
35
|
/** 所有链接节点统一的 class 名 */
|
|
22
36
|
private static readonly LINK_NODE_CLASS;
|
|
37
|
+
/** 上一次转换的调试统计(仅在 debug 模式下填充) */
|
|
38
|
+
static lastDebugStats: ConvertDebugStats | undefined;
|
|
23
39
|
/**
|
|
24
40
|
* 富文本转换为 PixUI innerHTML 兼容格式
|
|
25
41
|
* @param str 原始富文本 HTML
|
|
@@ -32,6 +48,8 @@ export declare class RichTextCore {
|
|
|
32
48
|
lineHeightScale?: number;
|
|
33
49
|
convertPxToEm?: boolean;
|
|
34
50
|
addCosUrlImageMogr2_w?: number;
|
|
51
|
+
debug?: boolean;
|
|
52
|
+
onDebugStats?: (stats: ConvertDebugStats) => void;
|
|
35
53
|
}): string;
|
|
36
54
|
/**
|
|
37
55
|
* 绑定链接点击事件
|
|
@@ -65,12 +83,17 @@ export declare class RichTextCore {
|
|
|
65
83
|
private static handleLists;
|
|
66
84
|
/** 将连续 <br> 替换为透明占位文字,确保 PixUI 换行 */
|
|
67
85
|
private static replaceBrWithPlaceholder;
|
|
68
|
-
/** 处理 <a>
|
|
86
|
+
/** 处理 <a> 标签:文本链接直接转为 key,图片链接直接下放到 img 上 */
|
|
69
87
|
private static handleAnchorTag;
|
|
70
88
|
/** 用 text 标签包裹纯文本节点 */
|
|
71
89
|
private static wrapTextNodes;
|
|
72
90
|
/** strong / em / u / s / span 等标签替换成 text */
|
|
73
91
|
private static replaceStyleTags;
|
|
92
|
+
/**
|
|
93
|
+
* 规范化:将列表项文本列中的裸文本片段包裹为 <key>
|
|
94
|
+
* 作用域限定在三列布局生成后的列表行:div[data-list-item="true"] > text
|
|
95
|
+
*/
|
|
96
|
+
private static normalizeListItemPlainTextToKey;
|
|
74
97
|
/** p → div */
|
|
75
98
|
private static replacePTag;
|
|
76
99
|
/** img 的 href 转移处理 & 记录链接 */
|
|
@@ -87,7 +110,7 @@ export declare class RichTextCore {
|
|
|
87
110
|
private static adjustLineHeightAndLetterSpacing;
|
|
88
111
|
/** 把 div 的 text-align 转移到子 text 上 */
|
|
89
112
|
private static transferTextAlign;
|
|
90
|
-
/** 删除空 text
|
|
113
|
+
/** 删除空 text 节点;并在安全场景下清理空 key 节点(不影响占位/换行) */
|
|
91
114
|
private static removeEmptyText;
|
|
92
115
|
/** 补默认 heading 字体大小 */
|
|
93
116
|
private static fixHeadingFontSize;
|
package/dist/RichTextCore.js
CHANGED
|
@@ -207,29 +207,85 @@ var RichTextCore = /** @class */ (function () {
|
|
|
207
207
|
* @param config.addCosUrlImageMogr2_w 添加cos url图片的mogr2_w参数,如果是cos的图片,可以使用这个配置设置图片宽度最大值。组件会增加参数为 xxx.jpg?imageMogr2/thumbnail/<width>x的参数。参数值取min(img节点样式宽度,addCosUrlImageMogr2_w)
|
|
208
208
|
*/
|
|
209
209
|
RichTextCore.convertRichTextToPixuiStyle = function (str, config) {
|
|
210
|
+
var debug = !!(config === null || config === void 0 ? void 0 : config.debug);
|
|
211
|
+
var now = function () { return Date.now(); };
|
|
212
|
+
var highLevelPhases = [];
|
|
213
|
+
var detailTotals = {};
|
|
214
|
+
var measure = function (name, fn) {
|
|
215
|
+
if (!debug)
|
|
216
|
+
return fn();
|
|
217
|
+
var s = now();
|
|
218
|
+
var r = fn();
|
|
219
|
+
var e = now();
|
|
220
|
+
highLevelPhases.push({ name: name, ms: e - s });
|
|
221
|
+
return r;
|
|
222
|
+
};
|
|
210
223
|
// 重置全局状态
|
|
211
224
|
RichTextCore.linkNodes = [];
|
|
212
225
|
RichTextCore.hrefIdCnt = 0;
|
|
213
226
|
// href 属性先统一做 URI 编码
|
|
214
|
-
var preEncodedStr = str.replace(/href=(['
|
|
215
|
-
|
|
216
|
-
});
|
|
217
|
-
var $1 = cheerio.load(preEncodedStr, null, false);
|
|
227
|
+
var preEncodedStr = measure("preEncodeHref", function () { return str.replace(/href=(["'])(.*?)\1/g, function (_m, quote, hrefVal) { return "href=".concat(quote).concat(encodeURIComponent(hrefVal)).concat(quote); }); });
|
|
228
|
+
var $1 = measure("cheerioLoad", function () { return cheerio.load(preEncodedStr, null, false); });
|
|
218
229
|
//检查html,如果有text节点,说明是转换过的,直接返回
|
|
219
230
|
if ($1("text").length > 0) {
|
|
231
|
+
if (debug) {
|
|
232
|
+
var stats = {
|
|
233
|
+
totalMs: highLevelPhases.reduce(function (a, b) { return a + b.ms; }, 0),
|
|
234
|
+
highLevel: highLevelPhases,
|
|
235
|
+
detail: Object.entries(detailTotals).map(function (_a) {
|
|
236
|
+
var name = _a[0], ms = _a[1];
|
|
237
|
+
return ({ name: name, ms: ms });
|
|
238
|
+
}),
|
|
239
|
+
segmentsCount: 0,
|
|
240
|
+
version: version_1.LIB_VERSION,
|
|
241
|
+
timestamp: now(),
|
|
242
|
+
};
|
|
243
|
+
RichTextCore.lastDebugStats = stats;
|
|
244
|
+
(config === null || config === void 0 ? void 0 : config.onDebugStats) && config.onDebugStats(stats);
|
|
245
|
+
}
|
|
220
246
|
return str;
|
|
221
247
|
}
|
|
222
248
|
var oriNodeCount = $1("*").length;
|
|
223
249
|
console.log("pixui-richHelper转换---- 版本:" + " " + version_1.LIB_VERSION + " " + " 原始节点数:" + oriNodeCount);
|
|
224
250
|
// ---------- 第 1 步:段落分割(基础实现:根节点切分) ----------
|
|
225
|
-
var segments = RichTextCore.splitIntoSegments($1);
|
|
251
|
+
var segments = measure("splitIntoSegments", function () { return RichTextCore.splitIntoSegments($1); });
|
|
226
252
|
// ---------- 第 2/3 步:遍历 + 展平 ----------
|
|
227
253
|
// 为保证兼容性,我们直接对 HTML 进行逐段处理并在内部构建 PixUI 结构。
|
|
228
254
|
// 这样既保持分段概念,又可复用旧逻辑的稳定性。
|
|
229
|
-
var processedSegments =
|
|
255
|
+
var processedSegments = measure("processSegments", function () {
|
|
256
|
+
return segments.map(function (seg) {
|
|
257
|
+
return RichTextCore.processSegment(seg, {
|
|
258
|
+
lineHeightScale: config === null || config === void 0 ? void 0 : config.lineHeightScale,
|
|
259
|
+
convertPxToEm: config === null || config === void 0 ? void 0 : config.convertPxToEm,
|
|
260
|
+
addCosUrlImageMogr2_w: config === null || config === void 0 ? void 0 : config.addCosUrlImageMogr2_w,
|
|
261
|
+
debug: debug,
|
|
262
|
+
debugTotals: detailTotals,
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
});
|
|
230
266
|
// ---------- 第 4 步:拼装 ----------
|
|
231
|
-
var Result = processedSegments.join("");
|
|
232
|
-
|
|
267
|
+
var Result = measure("joinSegments", function () { return processedSegments.join(""); });
|
|
268
|
+
if (debug)
|
|
269
|
+
console.log(Result);
|
|
270
|
+
if (debug) {
|
|
271
|
+
var detailList = Object.entries(detailTotals)
|
|
272
|
+
.map(function (_a) {
|
|
273
|
+
var name = _a[0], ms = _a[1];
|
|
274
|
+
return ({ name: name, ms: ms });
|
|
275
|
+
})
|
|
276
|
+
.sort(function (a, b) { return b.ms - a.ms; });
|
|
277
|
+
var totalMs = highLevelPhases.reduce(function (a, b) { return a + b.ms; }, 0);
|
|
278
|
+
var stats = {
|
|
279
|
+
totalMs: totalMs,
|
|
280
|
+
highLevel: highLevelPhases,
|
|
281
|
+
detail: detailList,
|
|
282
|
+
segmentsCount: segments.length,
|
|
283
|
+
version: version_1.LIB_VERSION,
|
|
284
|
+
timestamp: now(),
|
|
285
|
+
};
|
|
286
|
+
RichTextCore.lastDebugStats = stats;
|
|
287
|
+
(config === null || config === void 0 ? void 0 : config.onDebugStats) && config.onDebugStats(stats);
|
|
288
|
+
}
|
|
233
289
|
return Result;
|
|
234
290
|
};
|
|
235
291
|
/**
|
|
@@ -382,39 +438,73 @@ var RichTextCore = /** @class */ (function () {
|
|
|
382
438
|
* 处理单个段落片段,返回转换后的片段 HTML
|
|
383
439
|
*/
|
|
384
440
|
RichTextCore.processSegment = function (segmentHtml, config) {
|
|
441
|
+
var _this = this;
|
|
442
|
+
var debug = !!(config === null || config === void 0 ? void 0 : config.debug);
|
|
443
|
+
var totals = config === null || config === void 0 ? void 0 : config.debugTotals;
|
|
444
|
+
var now = function () { return Date.now(); };
|
|
445
|
+
var time = function (name, fn) {
|
|
446
|
+
if (!debug)
|
|
447
|
+
return fn();
|
|
448
|
+
var s = now();
|
|
449
|
+
fn();
|
|
450
|
+
var e = now();
|
|
451
|
+
if (totals)
|
|
452
|
+
totals[name] = (totals[name] || 0) + (e - s);
|
|
453
|
+
};
|
|
385
454
|
var $ = cheerio.load(segmentHtml, null, false);
|
|
386
|
-
//
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
455
|
+
// 预处理:data-list → class、class → style、标题后补 <br>、margin→padding 合并
|
|
456
|
+
time("preprocess", function () { return _this.preprocess($); });
|
|
457
|
+
// 规范图片尺寸单位:img 的 width/height 数字补 px
|
|
458
|
+
time("fixImgSizeUnit", function () { return _this.fixImgSizeUnit($); });
|
|
459
|
+
// COS 图片缩略参数:按最小可用宽度收敛/追加 imageMogr2/thumbnail/<w>x
|
|
460
|
+
time("handleCosImageMogr2", function () { return _this.handleCosImageMogr2($, config); });
|
|
461
|
+
// 合并img的width,height属性到 style,属性存在以 style 优先,移除 width/height 属性
|
|
462
|
+
time("consolidateImgSizeStyle", function () { return _this.consolidateImgSizeStyle($); });
|
|
463
|
+
// 列表规范化:ol/ul/li 转三列布局(缩进/标号/文本),保留缩进与方向。节点结构:段落div(缩进key节点、标号key节点、text节点区域)
|
|
464
|
+
time("handleLists", function () { return _this.handleLists($, (config === null || config === void 0 ? void 0 : config.lineHeightScale) || 1); });
|
|
465
|
+
// 换行处理:<br> → 透明占位 div,确保 PixUI 内正确换行
|
|
466
|
+
time("replaceBrWithPlaceholder", function () { return _this.replaceBrWithPlaceholder($); });
|
|
467
|
+
// 超链接:<a> → 可点击的 div,汇聚子样式并记录 data-link-*
|
|
468
|
+
time("handleAnchorTag", function () { return _this.handleAnchorTag($); });
|
|
469
|
+
// 文本包装:将普通文本包入 <text>(跳过列表/链接/换行占位)
|
|
470
|
+
time("wrapTextNodes", function () { return _this.wrapTextNodes($); });
|
|
471
|
+
// 样式标签:strong/em/u/s/span → 文本节点(列表文本列下改为 <key>)
|
|
472
|
+
time("replaceStyleTags", function () { return _this.replaceStyleTags($); });
|
|
473
|
+
// 列表文本列规范化:裸文本统一包裹为 <key>,清理空白
|
|
474
|
+
time("normalizeListItemPlainTextToKey", function () { return _this.normalizeListItemPlainTextToKey($); });
|
|
475
|
+
// 段落:<p> → <div>,补 flex-shrink 与 width:100%
|
|
476
|
+
time("replacePTag", function () { return _this.replacePTag($); });
|
|
477
|
+
// 图片链接:img 的 href 转移至 data-link-href,并登记点击节点
|
|
478
|
+
time("handleImgHref", function () { return _this.handleImgHref($); });
|
|
479
|
+
// 防收缩:常见块级元素补 flex-shrink:0(不作用于列表项)
|
|
480
|
+
time("addFlexShrink", function () { return _this.addFlexShrink($); });
|
|
481
|
+
// 扁平化:合并 text 样式并去掉嵌套的父 <text>
|
|
482
|
+
time("flattenNestedText", function () { return _this.flattenNestedText($); });
|
|
483
|
+
// 同级文本:拆分为 <key> 片段并统一包入父 <text>
|
|
484
|
+
time("convertSiblingTextToDiv", function () { return _this.convertSiblingTextToDiv($); });
|
|
485
|
+
// 首行缩进:text-indent 转透明占位
|
|
486
|
+
time("handleTextIndent", function () { return _this.handleTextIndent($); });
|
|
487
|
+
// 行高与字间距:聚合到父 <text> 并按配置缩放 line-height
|
|
488
|
+
time("adjustLineHeightAndLetterSpacing", function () { return _this.adjustLineHeightAndLetterSpacing($, config); });
|
|
489
|
+
// 文本对齐与方向:从 div 下沉至子 <text>
|
|
490
|
+
time("transferTextAlign", function () { return _this.transferTextAlign($); });
|
|
491
|
+
// 清理空 <text>
|
|
492
|
+
time("removeEmptyText", function () { return _this.removeEmptyText($); });
|
|
405
493
|
// this.fixHeadingFontSize($);
|
|
406
494
|
// 最后:修正 img 结束标签、nbsp
|
|
407
495
|
var res = $.html();
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
496
|
+
time("finalizeHtmlCleanup", function () {
|
|
497
|
+
res = res
|
|
498
|
+
.replaceAll(/<img([^>]+)>/g, "<img$1 />")
|
|
499
|
+
.replaceAll(/ /g, " ")
|
|
500
|
+
.replaceAll(/&/g, "&")
|
|
501
|
+
.replaceAll(/"/g, '"')
|
|
502
|
+
.replaceAll("\"", '"');
|
|
503
|
+
// 统一进行px到em转换
|
|
504
|
+
if (config === null || config === void 0 ? void 0 : config.convertPxToEm) {
|
|
505
|
+
res = _this.convertAllPxToEm(res);
|
|
506
|
+
}
|
|
507
|
+
});
|
|
418
508
|
return res;
|
|
419
509
|
};
|
|
420
510
|
/**
|
|
@@ -758,7 +848,7 @@ var RichTextCore = /** @class */ (function () {
|
|
|
758
848
|
$(this).replaceWith("<div style=\"".concat(mergeStyles("color: transparent", "flex-shrink: 0", "font-size: 20px"), "\" class='pixui-richtext-br-placeholder'>1</div>"));
|
|
759
849
|
});
|
|
760
850
|
};
|
|
761
|
-
/** 处理 <a>
|
|
851
|
+
/** 处理 <a> 标签:文本链接直接转为 key,图片链接直接下放到 img 上 */
|
|
762
852
|
RichTextCore.handleAnchorTag = function ($) {
|
|
763
853
|
var self = this;
|
|
764
854
|
$("a").each(function () {
|
|
@@ -785,10 +875,33 @@ var RichTextCore = /** @class */ (function () {
|
|
|
785
875
|
});
|
|
786
876
|
var finalStyle = mergeStyles.apply(void 0, __spreadArray([originalStyle], mergedStyles, false));
|
|
787
877
|
var encodedHref = href || ""; // 已在预处理阶段编码,避免二次编码
|
|
788
|
-
|
|
789
|
-
|
|
878
|
+
var hasImg = $(this).find("img").length > 0;
|
|
879
|
+
if (!hasImg) {
|
|
880
|
+
// 文本超链接 → 直接输出可点击的 <key>
|
|
881
|
+
if (href) {
|
|
882
|
+
self.linkNodes.push({ type: LinkNodeType.DIV, href: href, id: id });
|
|
883
|
+
}
|
|
884
|
+
$(this).replaceWith("<key style=\"".concat(finalStyle, "\" id=\"").concat(id, "\" class=\"").concat(RichTextCore.LINK_NODE_CLASS, " PA_RichTextHref_ATag\" data-link-type=\"div\" data-link-href=\"").concat(encodedHref, "\">").concat(textContent, "</key>"));
|
|
885
|
+
}
|
|
886
|
+
else {
|
|
887
|
+
// 图片超链接 → 将链接属性下放到 <img>,去掉 <a>
|
|
888
|
+
var $a = $(this);
|
|
889
|
+
$a.find("img").each(function () {
|
|
890
|
+
// 合并 a 的布局相关样式到 img(img 自身优先)
|
|
891
|
+
var aStyleObj = parseStyleString(finalStyle);
|
|
892
|
+
var imgStyleObj = parseStyleString($(this).attr("style") || "");
|
|
893
|
+
Object.keys(aStyleObj).forEach(function (k) {
|
|
894
|
+
if (!imgStyleObj[k])
|
|
895
|
+
imgStyleObj[k] = aStyleObj[k];
|
|
896
|
+
});
|
|
897
|
+
$(this).attr("style", serializeStyleObject(imgStyleObj));
|
|
898
|
+
// 设置链接标记(id 将在 handleImgHref 统一设置)
|
|
899
|
+
$(this).attr("data-link-type", "img");
|
|
900
|
+
$(this).attr("data-link-href", encodedHref);
|
|
901
|
+
});
|
|
902
|
+
// 用 <a> 的内部内容替换掉 <a>
|
|
903
|
+
$a.replaceWith($a.html() || "");
|
|
790
904
|
}
|
|
791
|
-
$(this).replaceWith("<div style=\"".concat(finalStyle, "\" id=\"").concat(id, "\" class=\"").concat(RichTextCore.LINK_NODE_CLASS, " PA_RichTextHref_ATag\" data-link-type=\"div\" data-link-href=\"").concat(encodedHref, "\">").concat(textContent, "</div>"));
|
|
792
905
|
});
|
|
793
906
|
};
|
|
794
907
|
/** 用 text 标签包裹纯文本节点 */
|
|
@@ -864,6 +977,30 @@ var RichTextCore = /** @class */ (function () {
|
|
|
864
977
|
});
|
|
865
978
|
}
|
|
866
979
|
};
|
|
980
|
+
/**
|
|
981
|
+
* 规范化:将列表项文本列中的裸文本片段包裹为 <key>
|
|
982
|
+
* 作用域限定在三列布局生成后的列表行:div[data-list-item="true"] > text
|
|
983
|
+
*/
|
|
984
|
+
RichTextCore.normalizeListItemPlainTextToKey = function ($) {
|
|
985
|
+
$("div[data-list-item='true'] > text").each(function () {
|
|
986
|
+
// 遍历直接子内容,遇到纯文本且非空则包裹为 <key>
|
|
987
|
+
$(this)
|
|
988
|
+
.contents()
|
|
989
|
+
.each(function () {
|
|
990
|
+
if (this.type === "text") {
|
|
991
|
+
var raw = this.data;
|
|
992
|
+
if (raw && raw.trim().length > 0) {
|
|
993
|
+
var wrapped = "<key style=\"".concat(mergeStyles("flex-shrink: 0", "word-break: break-word"), "\">").concat(raw, "</key>");
|
|
994
|
+
$(this).replaceWith(wrapped);
|
|
995
|
+
}
|
|
996
|
+
else {
|
|
997
|
+
// 空白文本直接移除,避免无意义节点
|
|
998
|
+
$(this).remove();
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
});
|
|
1002
|
+
});
|
|
1003
|
+
};
|
|
867
1004
|
/** p → div */
|
|
868
1005
|
RichTextCore.replacePTag = function ($) {
|
|
869
1006
|
$("p").each(function () {
|
|
@@ -910,21 +1047,29 @@ var RichTextCore = /** @class */ (function () {
|
|
|
910
1047
|
};
|
|
911
1048
|
/** 把嵌套的 text 展平 */
|
|
912
1049
|
RichTextCore.flattenNestedText = function ($) {
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
var
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1050
|
+
var flattenSameTagNested = function (tagName) {
|
|
1051
|
+
var selector = "".concat(tagName, " > ").concat(tagName);
|
|
1052
|
+
while ($(selector).length > 0) {
|
|
1053
|
+
$(tagName).each(function () {
|
|
1054
|
+
var $parent = $(this);
|
|
1055
|
+
var parentStyle = $parent.attr("style") || "";
|
|
1056
|
+
// 合并父样式到直接子同名标签
|
|
1057
|
+
$parent.children(tagName).each(function () {
|
|
1058
|
+
var $child = $(this);
|
|
1059
|
+
var childStyle = $child.attr("style") || "";
|
|
1060
|
+
var mergedStyle = mergeStyles(parentStyle, childStyle);
|
|
1061
|
+
$child.attr("style", mergedStyle);
|
|
1062
|
+
});
|
|
1063
|
+
// 若仍存在子同名标签,则用内部内容替换父节点
|
|
1064
|
+
if ($parent.children(tagName).length > 0) {
|
|
1065
|
+
$parent.replaceWith($parent.html() || "");
|
|
1066
|
+
}
|
|
922
1067
|
});
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
1068
|
+
}
|
|
1069
|
+
};
|
|
1070
|
+
// 先处理 text > text,再处理 key > key
|
|
1071
|
+
flattenSameTagNested("text");
|
|
1072
|
+
flattenSameTagNested("key");
|
|
928
1073
|
};
|
|
929
1074
|
/** 将同一行的 text 节点转为 div 并外包一层 text */
|
|
930
1075
|
RichTextCore.convertSiblingTextToDiv = function ($) {
|
|
@@ -1092,13 +1237,35 @@ var RichTextCore = /** @class */ (function () {
|
|
|
1092
1237
|
});
|
|
1093
1238
|
});
|
|
1094
1239
|
};
|
|
1095
|
-
/** 删除空 text
|
|
1240
|
+
/** 删除空 text 节点;并在安全场景下清理空 key 节点(不影响占位/换行) */
|
|
1096
1241
|
RichTextCore.removeEmptyText = function ($) {
|
|
1097
1242
|
$("text").each(function () {
|
|
1098
1243
|
var t = $(this).html() || "";
|
|
1099
1244
|
if (t.trim() === "")
|
|
1100
1245
|
$(this).remove();
|
|
1101
1246
|
});
|
|
1247
|
+
// 仅清理列表“文本列”中的空 key:div[data-list-item=true] > text 后代
|
|
1248
|
+
// 排除:
|
|
1249
|
+
// 1) 列表三列布局中用于缩进/标号的 <key>(这些是 div[data-list-item=true] 的直接子元素,不在 text 内)
|
|
1250
|
+
// 2) 任何带有非空 style 且含有可见内容的节点
|
|
1251
|
+
$("div[data-list-item='true'] > text").each(function () {
|
|
1252
|
+
$(this)
|
|
1253
|
+
.find("key")
|
|
1254
|
+
.each(function () {
|
|
1255
|
+
var $key = $(this);
|
|
1256
|
+
var html = $key.html() || "";
|
|
1257
|
+
var textOnly = html.replace(/<[^>]*>/g, "");
|
|
1258
|
+
var hasVisibleText = textOnly.trim().length > 0;
|
|
1259
|
+
if (hasVisibleText)
|
|
1260
|
+
return;
|
|
1261
|
+
// 保留显式占位用途的 key:如果样式包含 width/height/font-size 等,且为非空数值,则视作占位
|
|
1262
|
+
var styleObj = parseStyleString($key.attr("style") || "");
|
|
1263
|
+
var isPlaceholderByStyle = ["width", "height", "font-size", "padding-left", "padding-right", "margin-left", "margin-right"].some(function (k) { return !!styleObj[k]; });
|
|
1264
|
+
if (!isPlaceholderByStyle) {
|
|
1265
|
+
$key.remove();
|
|
1266
|
+
}
|
|
1267
|
+
});
|
|
1268
|
+
});
|
|
1102
1269
|
};
|
|
1103
1270
|
/** 补默认 heading 字体大小 */
|
|
1104
1271
|
RichTextCore.fixHeadingFontSize = function ($) {
|
package/package.json
CHANGED