refactor(editor): 优化富文本编辑器内容格式转换逻辑

- 改进 HTML 到 Markdown 的转换规则,处理块级元素和行内元素的嵌套关系
- 优化图片和视频标签的解析逻辑,确保附件正确提取
- 修复换行符处理问题,避免产生多余空行
- 增强居中文本和标题的格式转换准确性
- 清理调试日志和冗余代码
This commit is contained in:
A1300399510
2025-11-30 23:54:41 +08:00
parent 1b5cf79300
commit 57bf0ed3eb
5 changed files with 182 additions and 80 deletions

View File

@@ -160,14 +160,21 @@
line-height: 26px; line-height: 26px;
margin-bottom: 66px; margin-bottom: 66px;
} }
#details .matter .matter-left .html * {
background: transparent !important;
color: #555555 !important;
}
#details .matter .matter-left .html a { #details .matter .matter-left .html a {
text-decoration: underline; text-decoration: underline;
color: #04b0d5; color: #04b0d5 !important;
}
#details .matter .matter-left .html a * {
color: #04b0d5 !important;
} }
#details .matter .matter-left .html .blue { #details .matter .matter-left .html .blue {
font-size: 15px; font-size: 15px;
line-height: 26px; line-height: 26px;
color: #026277; color: #026277 !important;
margin: 0 4px; margin: 0 4px;
text-decoration: none; text-decoration: none;
} }
@@ -176,19 +183,17 @@
display: inline-block; display: inline-block;
} }
#details .matter .matter-left .html video { #details .matter .matter-left .html video {
margin: 0 auto; margin: 0 auto 5px;
height: 300px;
display: block;
} }
#details .matter .matter-left .html h1 { #details .matter .matter-left .html h1 {
font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif; font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif;
font-weight: 650; font-weight: 650;
color: #000000; color: #000000 !important;
font-size: 18px; font-size: 18px;
line-height: 30px; line-height: 30px;
} }
#details .matter .matter-left .html tr,
#details .matter .matter-left .html td {
background: transparent;
}
#details .matter .matter-left .last-time { #details .matter .matter-left .last-time {
color: #aaaaaa; color: #aaaaaa;
font-size: 13px; font-size: 13px;

View File

@@ -36,6 +36,7 @@
.content { .content {
flex-direction: column; flex-direction: column;
.name { .name {
font-size: 14px; font-size: 14px;
color: #333333; color: #333333;
@@ -55,6 +56,7 @@
.operate { .operate {
position: relative; position: relative;
.view { .view {
.icon { .icon {
width: 13px; width: 13px;
@@ -185,15 +187,25 @@
line-height: 26px; line-height: 26px;
margin-bottom: 66px; margin-bottom: 66px;
* {
background: transparent !important;
color: #555555 !important;
}
a { a {
text-decoration: underline; text-decoration: underline;
color: #04b0d5; color: #04b0d5 !important;
* {
color: #04b0d5 !important;
}
} }
.blue { .blue {
font-size: 15px; font-size: 15px;
line-height: 26px; line-height: 26px;
color: #026277; color: #026277 !important;
margin: 0 4px; margin: 0 4px;
text-decoration: none; text-decoration: none;
} }
@@ -204,21 +216,20 @@
} }
video { video {
margin: 0 auto; margin: 0 auto 5px;
height: 300px;
display: block;
} }
h1 { h1 {
font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif; font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif;
font-weight: 650; font-weight: 650;
color: #000000; color: #000000 !important;
font-size: 18px; font-size: 18px;
line-height: 30px; line-height: 30px;
} }
tr,
td {
background: transparent;
}
} }
.last-time { .last-time {
@@ -244,6 +255,7 @@
height: 20px; height: 20px;
margin-right: 6px; margin-right: 6px;
} }
font-size: 14px; font-size: 14px;
color: #333333; color: #333333;
cursor: pointer; cursor: pointer;
@@ -254,6 +266,7 @@
&.share { &.share {
position: relative; position: relative;
&:hover { &:hover {
.share-box { .share-box {
display: flex; display: flex;
@@ -353,6 +366,7 @@
padding-top: 20px; padding-top: 20px;
padding-bottom: 12px; padding-bottom: 12px;
border-bottom: 1px solid #ebebeb; border-bottom: 1px solid #ebebeb;
.text { .text {
font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif; font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif;
font-weight: 650; font-weight: 650;
@@ -494,6 +508,7 @@
.author-info { .author-info {
color: #7f7f7f; color: #7f7f7f;
font-size: 13px; font-size: 13px;
.amount { .amount {
font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif; font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif;
font-weight: 650; font-weight: 650;
@@ -508,6 +523,7 @@
width: calc(100% - 16px); width: calc(100% - 16px);
padding-bottom: 22px; padding-bottom: 22px;
margin-left: 16px; margin-left: 16px;
.medal-title { .medal-title {
font-size: 14px; font-size: 14px;
color: #7f7f7f; color: #7f7f7f;
@@ -551,6 +567,7 @@
&:hover { &:hover {
color: #000000; color: #000000;
} }
.dot { .dot {
width: 6px; width: 6px;
height: 6px; height: 6px;
@@ -608,6 +625,7 @@
font-weight: 400; font-weight: 400;
color: #7f7f7f; color: #7f7f7f;
font-size: 14px; font-size: 14px;
.sum { .sum {
font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif; font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif;
font-weight: 650; font-weight: 650;
@@ -682,6 +700,7 @@
.coins-total { .coins-total {
color: #7f7f7f; color: #7f7f7f;
font-size: 14px; font-size: 14px;
.sum { .sum {
font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif; font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif;
font-weight: 650; font-weight: 650;
@@ -713,6 +732,7 @@
.user { .user {
color: #555555; color: #555555;
font-size: 13px; font-size: 13px;
.avatar { .avatar {
width: 32px; width: 32px;
height: 32px; height: 32px;
@@ -724,6 +744,7 @@
.amount { .amount {
color: #000000; color: #000000;
font-size: 16px; font-size: 16px;
.text { .text {
font-size: 13px; font-size: 13px;
margin-left: 2px; margin-left: 2px;
@@ -883,8 +904,7 @@
cursor: pointer; cursor: pointer;
} }
.answer-discuss .input-box .bottom .operate .item { .answer-discuss .input-box .bottom .operate .item {}
}
.answer-discuss .input-box .bottom .operate .item .emoji-box { .answer-discuss .input-box .bottom .operate .item .emoji-box {
width: 582px; width: 582px;
@@ -1421,29 +1441,35 @@
background-color: rgba(0, 0, 0, 0.5); background-color: rgba(0, 0, 0, 0.5);
z-index: 12; z-index: 12;
} }
.answer-discuss .edit-comment .box { .answer-discuss .edit-comment .box {
width: 650px; width: 650px;
border-radius: 10px; border-radius: 10px;
background: #fff; background: #fff;
padding: 20px 15px; padding: 20px 15px;
} }
.answer-discuss .edit-comment .box .text { .answer-discuss .edit-comment .box .text {
font-size: 18px; font-size: 18px;
font-weight: 650; font-weight: 650;
margin-bottom: 15px; margin-bottom: 15px;
color: #000; color: #000;
} }
.answer-discuss .edit-comment .box .input-box { .answer-discuss .edit-comment .box .input-box {
margin-right: 0; margin-right: 0;
padding-top: 10px; padding-top: 10px;
} }
.answer-discuss .edit-comment .box .input-box .bottom { .answer-discuss .edit-comment .box .input-box .bottom {
// border-top: 1px solid #ebebeb; // border-top: 1px solid #ebebeb;
} }
.answer-discuss .edit-comment .box .btn-list { .answer-discuss .edit-comment .box .btn-list {
padding: 15px 0; padding: 15px 0;
justify-content: flex-end; justify-content: flex-end;
} }
.answer-discuss .edit-comment .box .btn-list .btn { .answer-discuss .edit-comment .box .btn-list .btn {
font-size: 14px; font-size: 14px;
color: #333; color: #333;
@@ -1457,6 +1483,7 @@
border: 1px solid #ebebeb; border: 1px solid #ebebeb;
margin-left: 20px; margin-left: 20px;
} }
.answer-discuss .edit-comment .box .btn-list .btn.send { .answer-discuss .edit-comment .box .btn-list .btn.send {
background-color: #fddf6d; background-color: #fddf6d;
border: 1px solid #fddf6d; border: 1px solid #fddf6d;
@@ -1469,6 +1496,7 @@
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
} }
.answer-discuss .emoji-box-mask { .answer-discuss .emoji-box-mask {
position: fixed; position: fixed;
top: 0; top: 0;
@@ -1483,6 +1511,7 @@
@media screen and (max-width: 850px) { @media screen and (max-width: 850px) {
#details { #details {
padding: 10px 10px 0; padding: 10px 10px 0;
.head-top { .head-top {
display: none; display: none;
} }
@@ -1494,6 +1523,7 @@
.matter { .matter {
.matter-left { .matter-left {
margin-right: 0 !important; margin-right: 0 !important;
.action-bar { .action-bar {
margin-right: 0 !important; margin-right: 0 !important;
justify-content: space-around; justify-content: space-around;
@@ -1508,10 +1538,13 @@
.related-head { .related-head {
padding-left: 14px; padding-left: 14px;
} }
.list { .list {
padding: 14px; padding: 14px;
.item { .item {
width: 100% !important; width: 100% !important;
&:not(:last-child) { &:not(:last-child) {
margin-bottom: 7px; margin-bottom: 7px;
} }
@@ -1523,10 +1556,12 @@
} }
} }
} }
.sidebar-box { .sidebar-box {
display: none; display: none;
} }
} }
.answer-discuss { .answer-discuss {
padding: 15px; padding: 15px;
@@ -1594,6 +1629,7 @@
.comments-item { .comments-item {
.comments-header { .comments-header {
font-size: 12px; font-size: 12px;
.comments-title { .comments-title {
height: 14px !important; height: 14px !important;
} }
@@ -1610,6 +1646,7 @@
.comments-box { .comments-box {
.comments-item { .comments-item {
.comments-header { .comments-header {
.comment-icon, .comment-icon,
.like-box { .like-box {
margin-left: 15px; margin-left: 15px;

View File

@@ -398,12 +398,16 @@
} }
#appIndex .header-content-box .header-content-right .offer-box { #appIndex .header-content-box .header-content-right .offer-box {
width: 240px; width: 240px;
height: 214px;
background-color: #fff; background-color: #fff;
border: 1px solid #e9eef2; border: 1px solid #e9eef2;
border-radius: 10px; border-radius: 10px;
padding: 17px 10px;
overflow: hidden; overflow: hidden;
height: 64px;
padding: 15px 10px;
}
#appIndex .header-content-box .header-content-right .offer-box.big {
height: 214px;
padding: 17px 10px;
} }
#appIndex .header-content-box .header-content-right .offer-box.small { #appIndex .header-content-box .header-content-right .offer-box.small {
height: 64px; height: 64px;

View File

@@ -18,9 +18,11 @@
&:not(:last-child) { &:not(:last-child) {
margin-right: 12px; margin-right: 12px;
} }
a { a {
display: block; display: block;
} }
img { img {
width: 468px; width: 468px;
height: 60px; height: 60px;
@@ -37,6 +39,7 @@
border-radius: 20px 20px 20px 0; border-radius: 20px 20px 20px 0;
margin-top: 0; margin-top: 0;
margin-bottom: 10px; margin-bottom: 10px;
.icon { .icon {
width: 15px; width: 15px;
height: 12px; height: 12px;
@@ -91,6 +94,7 @@
.people { .people {
position: relative; position: relative;
justify-content: space-between; justify-content: space-between;
&::after { &::after {
content: ""; content: "";
position: absolute; position: absolute;
@@ -119,6 +123,7 @@
width: 26px; width: 26px;
height: 26px; height: 26px;
border-radius: 50%; border-radius: 50%;
.img { .img {
width: 26px; width: 26px;
height: 26px; height: 26px;
@@ -128,12 +133,15 @@
&:nth-child(4) { &:nth-child(4) {
margin-right: -9px; margin-right: -9px;
} }
&:nth-child(3) { &:nth-child(3) {
margin-right: -9px; margin-right: -9px;
} }
&:nth-child(2) { &:nth-child(2) {
margin-right: -7px; margin-right: -7px;
} }
&:nth-child(2) { &:nth-child(2) {
margin-right: -5px; margin-right: -5px;
} }
@@ -145,6 +153,7 @@
.topic-list { .topic-list {
.item { .item {
cursor: pointer; cursor: pointer;
&:not(:last-child) { &:not(:last-child) {
margin-bottom: 1px; margin-bottom: 1px;
} }
@@ -222,8 +231,10 @@
border-radius: 10px; border-radius: 10px;
padding-left: 12px; padding-left: 12px;
margin-top: 12px; margin-top: 12px;
.adv { .adv {
margin-right: 26px; margin-right: 26px;
.adv-icon { .adv-icon {
width: 295px; width: 295px;
height: 118px; height: 118px;
@@ -279,6 +290,7 @@
.header-content-right { .header-content-right {
width: 240px; width: 240px;
.post-entrance { .post-entrance {
background-color: #fff; background-color: #fff;
border: 1px solid #e9eef2; border: 1px solid #e9eef2;
@@ -357,9 +369,11 @@
height: 140px; height: 140px;
margin-bottom: 10px; margin-bottom: 10px;
display: block; display: block;
a { a {
display: block; display: block;
} }
img { img {
width: 240px; width: 240px;
height: 140px; height: 140px;
@@ -394,12 +408,15 @@
&:nth-child(1) { &:nth-child(1) {
border-top-left-radius: 8px; border-top-left-radius: 8px;
} }
&:nth-child(2) { &:nth-child(2) {
border-top-right-radius: 8px; border-top-right-radius: 8px;
} }
&:nth-child(3) { &:nth-child(3) {
border-bottom-left-radius: 8px; border-bottom-left-radius: 8px;
} }
&:nth-child(4) { &:nth-child(4) {
border-bottom-right-radius: 8px; border-bottom-right-radius: 8px;
} }
@@ -429,6 +446,7 @@
line-height: 22px; line-height: 22px;
position: relative; position: relative;
.title { .title {
font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif; font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif;
font-weight: 650; font-weight: 650;
@@ -455,6 +473,7 @@
padding: 20px; padding: 20px;
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.168627450980392); box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.168627450980392);
flex-direction: column; flex-direction: column;
.QRcode { .QRcode {
width: 100px; width: 100px;
height: 100px; height: 100px;
@@ -472,13 +491,21 @@
.offer-box { .offer-box {
width: 240px; width: 240px;
height: 214px; // height: 214px;
background-color: #fff; background-color: #fff;
border: 1px solid #e9eef2; border: 1px solid #e9eef2;
border-radius: 10px; border-radius: 10px;
padding: 17px 10px; // padding: 17px 10px;
overflow: hidden; overflow: hidden;
height: 64px;
padding: 15px 10px;
&.big {
height: 214px;
padding: 17px 10px;
}
&.small { &.small {
height: 64px; height: 64px;
padding: 15px 10px; padding: 15px 10px;
@@ -521,6 +548,7 @@
.admission { .admission {
margin-bottom: 30px; margin-bottom: 30px;
.admission-header { .admission-header {
margin-bottom: 24px; margin-bottom: 24px;
display: inline-flex; display: inline-flex;
@@ -543,6 +571,7 @@
.admission-list { .admission-list {
flex-wrap: wrap; flex-wrap: wrap;
.admission-item { .admission-item {
width: 291px; width: 291px;
height: 103px; height: 103px;
@@ -602,6 +631,7 @@
&:not(:last-child) { &:not(:last-child) {
margin-bottom: 10px; margin-bottom: 10px;
} }
.item { .item {
font-family: "PingFangSC-Regular", "PingFang SC", sans-serif; font-family: "PingFangSC-Regular", "PingFang SC", sans-serif;
font-weight: 400; font-weight: 400;
@@ -622,6 +652,7 @@
} }
} }
} }
.item-box { .item-box {
margin-bottom: 12px; margin-bottom: 12px;
} }
@@ -641,6 +672,7 @@
height: 220px; height: 220px;
margin-bottom: 12px; margin-bottom: 12px;
cursor: pointer; cursor: pointer;
img { img {
width: 291px; width: 291px;
height: 220px; height: 220px;

View File

@@ -53,8 +53,6 @@ const editApp = createApp({
cUpload(); cUpload();
// console.log(valueA.value);
valueUrl = valueA.value.innerText; valueUrl = valueA.value.innerText;
if (location.hostname == "127.0.0.1") { if (location.hostname == "127.0.0.1") {
@@ -67,6 +65,7 @@ const editApp = createApp({
isLogin.value = true; isLogin.value = true;
} }
}); });
let imageLength = 10; let imageLength = 10;
@@ -167,19 +166,13 @@ const editApp = createApp({
const infoTarget = data.info || {}; const infoTarget = data.info || {};
// if (location.hostname == "127.0.0.1") // console.log("content", infoTarget.content);
// infoTarget.content = `<p><img src="https://oss.x-php.com/Zvt57TuJSUvkyhw-xG_Y2l-U_polfHeD1NFX9ddrB_WbUGy8P79gQxccQOeR45kV7NkzNDQyOQ~~?aid=1009985" alt="图片描述" data-href="https://i-operation.csdnimg.cn/ad/ad_pic/a0beaaca1e2047e0ae5c0783e02b3c0a.png" style=""/></p><div data-w-e-type="video" data-w-e-is-void>
// <video poster="https://o.x-php.com/Zvt57TuJSUvkyhw-xG_Y2l-U_polcniH1NFX9ddrB_WbUGy8P79gQxcSQbvLtMsV7NkzNDQyOQ~~?aid=1009771" controls="true" width="auto" height="auto"><source src="https://o.x-php.com/Zvt57TuJSUvkyhw-xG_Y2l-U_polcniG1NFX9ddrB_WbUGy8P79gQxcSFbqQ78MV7NkzNDQyOQ~~?aid=1009770" type="video/mp4"/></video>
// </div><p><br></p>`;
console.log("content", infoTarget.content);
if (infoTarget.content) infoTarget.content = restoreHtml(infoTarget.content, infoTarget.attachments); if (infoTarget.content) infoTarget.content = restoreHtml(infoTarget.content, infoTarget.attachments);
console.log("content", infoTarget.content); // console.log("content", infoTarget.content);
info.value = infoTarget; info.value = infoTarget;
token.value = data.token; token.value = data.token;
console.log("data", data);
initEditor(); initEditor();
}) })
@@ -216,18 +209,9 @@ const editApp = createApp({
["insertImage"]: { ["insertImage"]: {
onInsertedImage(imageNode) { onInsertedImage(imageNode) {
console.log("insertImage");
// if (imageNode == null) return;
const { src, alt, url, href } = imageNode; const { src, alt, url, href } = imageNode;
console.log("src", src);
console.log("alt", alt);
console.log("url", url);
console.log("href", href);
}, },
async parseImageSrc(src) { async parseImageSrc(src) {
console.log("parseImageSrc", src);
// 如果图片链接中已经包含了 ?aid= ,则说明是本站图片,直接返回,无需处理 // 如果图片链接中已经包含了 ?aid= ,则说明是本站图片,直接返回,无需处理
if (src.includes("?aid=")) return src; if (src.includes("?aid=")) return src;
@@ -323,6 +307,7 @@ const editApp = createApp({
editor = createEditor({ editor = createEditor({
selector: "#editor-container", selector: "#editor-container",
html: infoTarget.content, html: infoTarget.content,
// html: "",
config: editorConfig, config: editorConfig,
mode: "default", mode: "default",
}); });
@@ -409,10 +394,10 @@ const editApp = createApp({
let html = formattedText || ""; let html = formattedText || "";
// 0.5 [p] -> <p> // 0.5 [p] -> <p>
html = html.replace(/\[p\]([\s\S]*?)\[\/p\]/gi, "<p>$1</p>"); // html = html.replace(/\[p\]([\s\S]*?)\[\/p\]/gi, "<p>$1</p>");
// 0. 基础清理:换行符转 <br>div 转 p (保持原有逻辑) // 0. 基础清理:换行符转 <br>div 转 p (保持原有逻辑)
html = html.replace(/\n/g, "<br>"); html = html.replace(/(\r\n|\n|\r)/g, "<br>");
html = html.replace(/<div(\s|>)/gi, "<p$1"); html = html.replace(/<div(\s|>)/gi, "<p$1");
html = html.replace(/<\/div>/g, "</p>"); html = html.replace(/<\/div>/g, "</p>");
html = html.replace(/<br><div>/g, "<div>"); html = html.replace(/<br><div>/g, "<div>");
@@ -432,6 +417,10 @@ const editApp = createApp({
// 普通居中 // 普通居中
html = html.replace(/\[align=center\]([\s\S]*?)\[\/align\]/gi, '<p style="text-align: center;">$1</p>'); html = html.replace(/\[align=center\]([\s\S]*?)\[\/align\]/gi, '<p style="text-align: center;">$1</p>');
// 移除 align 后紧跟的换行符(因为 align 转换为了块级元素 p/h1其后的换行符通常是多余的
html = html.replace(/(<p style="text-align: center;">.*?<\/p>)\s*<br>/gi, "$1");
html = html.replace(/(<h1 style="text-align: center;">.*?<\/h1>)\s*<br>/gi, "$1");
// 定义图片处理函数 // 定义图片处理函数
const processImg = (aid, width, height, href) => { const processImg = (aid, width, height, href) => {
const image = imageList.find((img) => String(img.aid) === String(aid)); const image = imageList.find((img) => String(img.aid) === String(aid));
@@ -519,29 +508,62 @@ const editApp = createApp({
// 确保所有顶层元素都是块级元素 (Slate/WangEditor 要求) // 确保所有顶层元素都是块级元素 (Slate/WangEditor 要求)
const blockTags = ['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'UL', 'OL', 'PRE', 'TABLE', 'FIGURE', 'HR']; const blockTags = ['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'UL', 'OL', 'PRE', 'TABLE', 'FIGURE', 'HR'];
const children = Array.from(__c.childNodes); const newContainer = document.createElement('div');
let wrapper = null; let currentInlineNodes = [];
let lastWasBlock = false;
children.forEach((node) => { const flushInlines = () => {
if (currentInlineNodes.length > 0) {
if (currentInlineNodes.length > 0) {
const p = document.createElement('p');
currentInlineNodes.forEach(node => p.appendChild(node));
newContainer.appendChild(p);
}
currentInlineNodes = [];
}
};
Array.from(__c.childNodes).forEach((node) => {
// 判断是否为块级元素 // 判断是否为块级元素
const isBlock = node.nodeType === 1 && blockTags.includes(node.tagName); const isBlock = node.nodeType === 1 && blockTags.includes(node.tagName);
if (isBlock) { if (isBlock) {
wrapper = null; // 遇到块级元素,重置包裹容器 flushInlines();
newContainer.appendChild(node);
// 记录最后添加的是块级元素
lastWasBlock = true;
} else if (node.nodeName === 'BR') {
if (currentInlineNodes.length > 0) {
flushInlines();
lastWasBlock = false; // 刚刚结束了一个段落,不算紧挨着块级
} else { } else {
// 如果是行内元素文本、图片、span、strong等放入 p 标签 // 如果前面紧挨着块级元素,忽略这个 BR避免块级元素后的换行产生空行
// 忽略纯空白文本节点(如果它们在块级元素之间),避免产生过多的空段落 if (lastWasBlock) {
// 但为了保险起见(避免吞掉有意义的空格),这里暂时全部包裹 // 忽略
lastWasBlock = false; // 消耗掉块级后的换行状态,避免连续 BR 被吞
if (!wrapper) { } else {
wrapper = document.createElement('p'); const p = document.createElement('p');
__c.insertBefore(wrapper, node); p.innerHTML = '<br>';
newContainer.appendChild(p);
lastWasBlock = false;
} }
wrapper.appendChild(node); }
} else {
// 过滤掉块级元素之间或开头的纯空白文本节点,避免产生空的 P 标签
if (node.nodeType === 3 && !node.textContent.trim()) {
if (currentInlineNodes.length > 0) {
currentInlineNodes.push(node);
}
} else {
currentInlineNodes.push(node);
}
lastWasBlock = false;
} }
}); });
html = __c.innerHTML; flushInlines();
html = newContainer.innerHTML;
console.log("初始化显示的html", html); console.log("初始化显示的html", html);
return html; return html;
@@ -563,6 +585,8 @@ const editApp = createApp({
// 提交 // 提交
const submit = (status) => { const submit = (status) => {
if (location.hostname == "127.0.0.1") status = 0
if (realname.value == 0 && userInfoWin.value?.uin > 0) { if (realname.value == 0 && userInfoWin.value?.uin > 0) {
openAttest(); openAttest();
return; return;
@@ -587,18 +611,15 @@ const editApp = createApp({
info.value["attachments"]["images"] = images; info.value["attachments"]["images"] = images;
info.value["attachments"]["videos"] = videos; info.value["attachments"]["videos"] = videos;
console.log("原始html", content); // console.log("原始html", content);
content = formatContent(content); content = formatContent(content);
console.log("最终html", content); // console.log("最终html", content);
const data = { const data = {
...infoTarget, ...infoTarget,
content, content,
}; };
console.log("data", data); // console.log("data", data);
if (location.hostname == "127.0.0.1") {
status = 0;
}
ajax("/v2/api/forum/postPublishTopic", { ajax("/v2/api/forum/postPublishTopic", {
info: data, info: data,
@@ -623,6 +644,8 @@ const editApp = createApp({
const formatContent = (html) => { const formatContent = (html) => {
if (!html) return ""; if (!html) return "";
// <p><br></p> 转换为单个换行符
html = html.replace(/<p><br><\/p>/gi, "\n");
// 1. 处理居中 [align=center] (p, div, h1-h6) // 1. 处理居中 [align=center] (p, div, h1-h6)
html = html.replace(/<(p|div|h[1-6])[^>]*style="[^"]*text-align:\s*center;[^"]*"[^>]*>([\s\S]*?)<\/\1>/gi, (match, tag, content) => { html = html.replace(/<(p|div|h[1-6])[^>]*style="[^"]*text-align:\s*center;[^"]*"[^>]*>([\s\S]*?)<\/\1>/gi, (match, tag, content) => {
@@ -637,7 +660,10 @@ const editApp = createApp({
html = html.replace(/<h1[^>]*>([\s\S]*?)<\/h1>/gi, "[section]$1[/section]"); html = html.replace(/<h1[^>]*>([\s\S]*?)<\/h1>/gi, "[section]$1[/section]");
// 2.5 处理段落 [p] // 2.5 处理段落 [p]
html = html.replace(/<p[^>]*>([\s\S]*?)<\/p>/gi, "[p]$1[/p]"); // 优先处理空段落 <p><br></p>,将其替换为单个换行符,避免后续双重换行
html = html.replace(/<p[^>]*>\s*<br\s*\/?>\s*<\/p>/gi, "\n");
// 处理普通段落结束符
html = html.replace(/<\/p>/gi, "\n");
// 3. 处理加粗 [b] (对应 strong, b) // 3. 处理加粗 [b] (对应 strong, b)
html = html.replace(/<(strong|b)[^>]*>([\s\S]*?)<\/\1>/gi, "[b]$2[/b]"); html = html.replace(/<(strong|b)[^>]*>([\s\S]*?)<\/\1>/gi, "[b]$2[/b]");
@@ -703,14 +729,16 @@ const editApp = createApp({
// 处理 data-href包裹在 [url] 中 // 处理 data-href包裹在 [url] 中
const hrefMatch = imgTag.match(/data-href="([^"]+)"/i); const hrefMatch = imgTag.match(/data-href="([^"]+)"/i);
if (hrefMatch && hrefMatch[1]) { if (hrefMatch && hrefMatch[1]) result = `[url=${hrefMatch[1]}]${result}[/url]`;
result = `[url=${hrefMatch[1]}]${result}[/url]`;
}
return result; return result;
}); });
// 5. 处理视频 [attach] // 5. 处理视频 [attach]
html = html.replace(/<div[^>]*data-w-e-type="video"[^>]*>([\s\S]*?)<\/div>/gi, (match, content) => {
return content.trim(); // 去掉包裹视频的 div并去除首尾空白防止产生额外换行
});
html = html.replace(/<video[^>]*>[\s\S]*?<\/video>/gi, (videoTag) => { html = html.replace(/<video[^>]*>[\s\S]*?<\/video>/gi, (videoTag) => {
let aid = ""; let aid = "";
const dataAid = videoTag.match(/data-aid="(\d+)"/i); const dataAid = videoTag.match(/data-aid="(\d+)"/i);
@@ -742,7 +770,7 @@ const editApp = createApp({
// 7. 换行处理 // 7. 换行处理
html = html.replace(/<br\s*\/?>/gi, "\n"); html = html.replace(/<br\s*\/?>/gi, "\n");
// html = html.replace(/<\/(p|div)>/gi, "\n"); // 块级元素结束视为换行
html = html.replace(/<\/(div)>/gi, "\n"); // 块级元素结束视为换行 html = html.replace(/<\/(div)>/gi, "\n"); // 块级元素结束视为换行
// 8. 清理剩余标签和解码 // 8. 清理剩余标签和解码
@@ -781,8 +809,6 @@ const editApp = createApp({
}); });
}); });
console.log("提取完成的图片列表:", images);
return images; return images;
}; };
@@ -817,7 +843,6 @@ const editApp = createApp({
}); });
}); });
console.log("提取完成的视频列表:", result);
return result; return result;
}; };
@@ -842,7 +867,6 @@ const editApp = createApp({
}, },
onUploadProgress: (e) => { onUploadProgress: (e) => {
progress.value = Math.round((e.loaded / e.total) * 100); progress.value = Math.round((e.loaded / e.total) * 100);
console.log("progress.value", progress.value);
}, },
withCredentials: true, withCredentials: true,
}) })