From f49d937f19063e7e51c6d5642d5cd317dd9b2f24 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RQ919RC\\Pc" <1300399510@qq.com> Date: Tue, 18 Nov 2025 19:20:31 +0800 Subject: [PATCH] =?UTF-8?q?feat(editor):=20=E6=96=B0=E5=A2=9E=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E4=B8=8A=E4=BC=A0=E5=8A=9F=E8=83=BD=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E7=BC=96=E8=BE=91=E5=99=A8=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加视频上传功能,支持提取视频第一帧作为封面 - 优化图片和视频上传的数量限制检查 - 修复编辑器内容为空判断逻辑,增加视频元素检测 - 改进链接插入功能,自动填充选中文本 - 调整表情选择插入方式,使用execCommand实现 - 优化附件提取逻辑,支持视频元素解析 - 添加编辑器点击事件处理,更新选区状态 - 修复样式问题,调整按钮悬停效果 --- component/bi/bi.js | 12 +- component/bi/bi.txt | 2 +- component/latest-list/latest-list.txt | 31 +- css/details.css | 1 + css/details.less | 1 + css/edit.css | 14 +- css/edit.less | 18 +- details.html | 4 +- edit.html | 196 +++++----- img/video-icon.png | Bin 0 -> 4612 bytes js/details.js | 446 +++++++++++++++++++---- js/edit.js | 492 +++++++++++++++++--------- 12 files changed, 873 insertions(+), 344 deletions(-) create mode 100644 img/video-icon.png diff --git a/component/bi/bi.js b/component/bi/bi.js index cb801a3..1e692fe 100644 --- a/component/bi/bi.js +++ b/component/bi/bi.js @@ -1,6 +1,6 @@ // 1. 创建组件模板和样式(内置到 JS 中,无需外部 HTML/Template) const template = document.createElement("template"); -template.innerHTML = `
已获得
个寄托币
投币
你当前共有
寄托币 [挣币攻略]
作者设置了阅读限制,解锁所有内容仅需 寄托币
投币解锁
你共有 寄托币
你的寄托币不够,快去发帖挣币吧
攒币指南
`; +template.innerHTML = `
已获得
个寄托币
投币
你当前共有
寄托币 [挣币攻略]
作者设置了阅读限制,解锁所有内容仅需 寄托币
投币解锁
你共有 寄托币
你的寄托币不够,快去发帖挣币吧
攒币指南
`; // 2. 定义组件类 class BiCard extends HTMLElement { @@ -68,6 +68,8 @@ class BiCard extends HTMLElement { this.coinsEl.textContent = this.coins; this.defaultcoinnum = defaultcoinnum; + this.input.placeholder = `输入投币数,默认${defaultcoinnum}个币`; + this.coinsAreaEl.style.display = "flex"; if (type == "unlock") { @@ -100,6 +102,8 @@ class BiCard extends HTMLElement { vote: "投票", }; + console.log("obj[this.pagetpye]", this.pagetpye); + this.pagetpyeText.textContent = obj[this.pagetpye]; } @@ -116,7 +120,7 @@ class BiCard extends HTMLElement { .map((item) => { return `
${item.rank}
- +
${item.user.nickname || "匿名用户"}
@@ -133,7 +137,7 @@ class BiCard extends HTMLElement { }); } - coinSubmit(type) { + coinSubmit() { const num = Number(this.input.value || this.defaultcoinnum) || 0; this.fetchData(`https://api.gter.net/v2/api/forum/postTopicCoin`, { token: this.token, @@ -155,7 +159,7 @@ class BiCard extends HTMLElement { const coins = data.coins; this.coinsEl.textContent = coins || 0; - if (this.pagetpye == "forum") document.querySelector(".action-bar-item.coins .text").textContent = coins || 0; + if (this.pagetpye == "thread") document.querySelector(".action-bar-item.coins .text").textContent = coins || 0; if (this.pagetpye == "vote") document.querySelector(".coinText").textContent = coins || 0; if (this.pagetpye == "mj") document.querySelector(".coinText").textContent = coins || 0; if (this.pagetpye == "offer") document.querySelector(".broadside-text.cursorpointer.coinText").textContent = (coins || 0) + " 寄托币"; diff --git a/component/bi/bi.txt b/component/bi/bi.txt index 5e591cf..31c8bca 100644 --- a/component/bi/bi.txt +++ b/component/bi/bi.txt @@ -332,7 +332,7 @@ border-color: #50e3c2 !important; } - +
diff --git a/component/latest-list/latest-list.txt b/component/latest-list/latest-list.txt index abdabba..50c674f 100644 --- a/component/latest-list/latest-list.txt +++ b/component/latest-list/latest-list.txt @@ -9,8 +9,10 @@
-
最新
-
精华
+
最新 +
+
精华 +
-
+
\ No newline at end of file diff --git a/css/details.css b/css/details.css index b3229f9..492bf31 100644 --- a/css/details.css +++ b/css/details.css @@ -172,6 +172,7 @@ } #details .matter .matter-left .html img { max-width: 100%; + display: block; } #details .matter .matter-left .html video { margin: 0 auto; diff --git a/css/details.less b/css/details.less index c8913dd..7283cc8 100644 --- a/css/details.less +++ b/css/details.less @@ -198,6 +198,7 @@ img { max-width: 100%; + display: block; } video { diff --git a/css/edit.css b/css/edit.css index 64028a2..7838ad0 100644 --- a/css/edit.css +++ b/css/edit.css @@ -77,6 +77,9 @@ height: 36px; background-color: #fbfbfb; padding-left: 25px; + position: sticky; + top: 0; + z-index: 10; } #edit .edit-container .editor-toolbar .toolbar-item { cursor: pointer; @@ -132,7 +135,6 @@ width: 100%; height: 100%; z-index: 1; - background-color: rgba(0, 0, 0, 0.20392157); display: none; } #edit .edit-container .editor-toolbar .toolbar-item .link-box-mask { @@ -227,7 +229,7 @@ margin-top: 8px; } #edit .edit-container .editor-toolbar .toolbar-item.link .link-box .btn:hover { - background-color: #c2eff6; + background-color: #23e0b6; } #edit .edit-container .editor-toolbar .toolbar-item.h2.pitch { background-color: #f6f6bd; @@ -334,12 +336,16 @@ color: #7f7f7f; font-size: 14px; margin-right: 15px; + transition: background-color 0.3s ease; } #edit .edit-container .action-buttons .right-section .draft-btn .icon { width: 20px; height: 20px; margin-right: 6px; } +#edit .edit-container .action-buttons .right-section .draft-btn:hover { + background-color: #ebebeb; +} #edit .edit-container .action-buttons .right-section .publish-btn { width: 150px; height: 40px; @@ -350,4 +356,8 @@ font-size: 16px; color: #000000; background-color: #50e3c2; + transition: background-color 0.3s ease; +} +#edit .edit-container .action-buttons .right-section .publish-btn:hover { + background-color: #40d1aa; } diff --git a/css/edit.less b/css/edit.less index 06d6796..81fb623 100644 --- a/css/edit.less +++ b/css/edit.less @@ -86,6 +86,10 @@ height: 36px; background-color: rgba(251, 251, 251, 1); padding-left: 25px; + position: sticky; + top: 0; + z-index: 10; + .toolbar-item { .icon { width: 16px; @@ -146,7 +150,7 @@ width: 100%; height: 100%; z-index: 1; - background-color: rgba(0, 0, 0, 0.20392157); + // background-color: rgba(0, 0, 0, 0.20392157); display: none; } @@ -254,7 +258,7 @@ color: #000000; margin-top: 8px; &:hover { - background-color: rgba(194, 239, 246, 1); + background-color: rgb(35, 224, 182); } } } @@ -382,12 +386,17 @@ color: #7f7f7f; font-size: 14px; margin-right: 15px; + transition: background-color 0.3s ease; .icon { width: 20px; height: 20px; margin-right: 6px; } + + &:hover { + background-color: rgb(235, 235, 235); + } } .publish-btn { width: 150px; @@ -399,6 +408,11 @@ font-size: 16px; color: #000000; background-color: rgba(80, 227, 194, 1); + transition: background-color 0.3s ease; + + &:hover { + background-color: rgba(64, 209, 170, 1); + } } } } diff --git a/details.html b/details.html index e924f15..e3c7da7 100644 --- a/details.html +++ b/details.html @@ -17,6 +17,8 @@
+
i8aD88Xy1aK9
+
@@ -38,8 +40,6 @@ 首页 -
{{ info.title || info.content }}
diff --git a/edit.html b/edit.html index 46bdaad..27d94e0 100644 --- a/edit.html +++ b/edit.html @@ -1,102 +1,116 @@ - - - - 发布帖子 - 轻论坛 - - - - - - -
-
-
- -
-
发帖
-
发帖奖励 3 个寄托币/篇,每天最高奖励3篇
-
- + + + + 发布帖子 - 轻论坛 + + + + + + + +
+
+
+ + + +
+
发帖
+
发帖奖励 3 个寄托币/篇,每天最高奖励3篇
+
+ +
+
+
+ +
+ +
{{ info?.title?.length ? titleLength - info?.title?.length : titleLength }}
+
+ + +
+
+ 段落标题 + 段落标题 +
+
+ 图片 + 图片 + +
+
+ 视频 + 视频 + +
+
+ 表情 + 表情 +
+
+
{{ emoji }}
+
+
+
-
- -
- + + +
+ + + + + +
+
+ +
+
匿名发布
- - -
-
- 段落标题 - 段落标题 -
-
- 图片 - 图片 - -
-
- 表情 - 表情 -
-
-
{{ emoji }}
-
-
- -
- - -
- - -
-
#{{ item.title }}
-
- - -
-
- -
-
匿名发布
-
-
-
- - 存草稿 -
-
发表帖子
+
+
+ + 存草稿
+
发表帖子
+
- - - - - + + + + + + \ No newline at end of file diff --git a/img/video-icon.png b/img/video-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..dfcca77d027b4bac260f1395f7987d8716d3abbc GIT binary patch literal 4612 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

{ getUserInfoWin(); - - setTimeout(() => (permissions.value = window["permissions"] || ["comment.edit", "comment.delete", "offercollege.hide", "offersummary.hide", "mj.hide", "topic:manager", "topic:hide"]), 1000); }); - let isLogin = ref(true); - let realname = ref(1); // 是否已经实名 - let userInfoWin = ref({ - authority: ["comment.edit", "comment.delete", "offercollege.hide", "offersummary.hide", "mj.hide", "topic:manager", "topic:hide"], - avatar: "https://nas.gter.net:9008/avatar/97K4EWIMLrsbGTWXslC2WFVSEKWOikN42jDKLNjtax7HL4xtfMOJSdU9oWFhY2E~/middle?random=1761733169", - groupid: 3, - nickname: "肖荣豪", - realname: 1, - token: "01346a38444d71aaadb3adad52b52c39", - uid: 500144, - uin: 4238049, - }); + let isLogin = ref(false); + let realname = ref(0); // 是否已经实名 + let userInfoWin = ref({}); let permissions = ref([]); @@ -42,6 +32,7 @@ const appSectionIndex = createApp({ userInfoWin.value = user; if (user?.uin > 0 || user?.uid > 0) isLogin.value = true; permissions.value = user?.authority || []; + ismanager.value = permissions.value.indexOf("topic:manager") >= 0; }; document.addEventListener("getUser", checkUser); }; @@ -78,28 +69,65 @@ const appSectionIndex = createApp({ let timestamp = ref(""); let updatedTime = ref(""); let token = ""; + let tokentoken = ref(""); let uniqid = ""; let sectionn = ref([]); let tags = ref([]); + let uniqidRef = ref(null); + onMounted(() => { - const params = getUrlParams(); - uniqid = params.uniqid || ""; + uniqid = uniqidRef.value.innerText; init(); + + window.addEventListener("scroll", handleScroll); + + checkWConfig(); }); + const checkWConfig = () => { + const wConfig = JSON.parse(localStorage.getItem("wConfig")) || {}; + console.log("wConfig", wConfig); + + if (wConfig.time) { + const time = new Date(wConfig.time); + const now = new Date(); + if (now - time > 24 * 60 * 60 * 1000) getWConfig(); + else { + const config = wConfig.config || {}; + maxPicture.value = config.topic_image_count; + } + } else { + getWConfig(); + } + }; + + const getWConfig = () => { + ajaxGet("/v2/api/config/website").then((res) => { + if (res.code == 200) { + let data = res["data"] || {}; + const config = data.config || {}; + maxPicture.value = config.topic_image_count; + + data.time = new Date().toISOString(); + localStorage.setItem("wConfig", JSON.stringify(data)); + } + }); + }; + const init = () => { - ajaxget(`/v2/api/forum/getTopicDetails?uniqid=${uniqid}`).then((res) => { + ajaxGet(`/v2/api/forum/getTopicDetails?uniqid=${uniqid}`).then((res) => { if (res.code != 200) { creationAlertBox("error", res.message || "主题不存在"); + setTimeout(() => redirectToExternalWebsite(`/`), 3000); return; } const data = res.data; - console.log(data, "data"); let targetInfo = data.info; + console.log("data", data); if (!targetInfo.hidden) targetInfo.hidden = 0; @@ -114,30 +142,129 @@ const appSectionIndex = createApp({ authorInfo.value = Array.isArray(data.authorInfo) ? null : data.authorInfo; ismyself.value = data.ismyself || false; + const sectionNameSet = new Set(targetInfo.sectionn.map((item) => item.name)); + const newTag = targetInfo.tags.filter((tagName) => !sectionNameSet.has(tagName)); + sectionn.value = targetInfo.sectionn; - tags.value = targetInfo.tags; + tags.value = newTag; timestamp.value = strtimeago(targetInfo.release_at, 4); updatedTime.value = targetInfo.updated_at ? strtimeago(targetInfo.updated_at, 4) : null; + + // targetInfo.content = "[attach]976054[/attach]\n\n[attachimg]1008585[/attachimg]\n[attach]850105[/attach]"; + // targetInfo.attachments = { + // images: [ + // { + // aid: 1008585, + // url: "https://o.x-php.com/Zvt57TuJSUvkyhw-xG_Y2l-U_pokcHeD1NFX9ddrB_WbUGy8P79gQxcXHOeQ4soV7NkzNDQyOQ~~", + // thumb: "https://o.x-php.com/Zvt57TuJSUvkyhw-xG_Y2l-U_pokcHeD1NFX9ddrB_WbUWy8K_hyVFweFbPD7IZK4sVMAmnF5Vzp9Fkg0jQ0Mjk~", + // }, + // ], + // files: [], + // videos: [ + // { + // aid: 976054, + // posterurl: "https://o.x-php.com/Zvt57TuJSUvkyhw-xG_Y2l-U_pokc36P1NFX9ddrB_WbUWy8K_hyVFweFrPD7IZK4sVMAm3Btwy-9Fkg0jQ0Mjk~", + // url: "https://o.x-php.com/Zvt57TuJSUvkyhw-xG7Y2l-c-ZwscHvqqsgFptxhXa6RWi26P-BuTQFFE7SQttkb8LQ0NDI5", + // thumb: "https://o.x-php.com/Zvt57TuJSUvkyhw-xG7Y2l-c-ZwscHvqqsgFptxhXa6QWi2uePJ5Bg8VFLPIqoYV7MtbCG2RtAz_-kVNNDQyOQ~~", + // }, + // { + // aid: 850105, + // posterurl: "https://o.x-php.com/Zvt57TuJSUvkyhw-xG_Y2l-U_pokc32G1NFX9ddrB_WbUWy8K_hyVFweFrPD7IZK4sVMA2eU5Vvl9Fkg0jQ0Mjk~", + // url: "https://o.x-php.com/Zvt57TuJSUvkyhw-xG7Y2l-d-5otdXrqqsgFptxhXa6RWi26P-BuTQNAEuHBs9kb8LQ0NDI5", + // thumb: "https://o.x-php.com/Zvt57TuJSUvkyhw-xG7Y2l-d-5otdXrqqsgFptxhXa6QWi2uePJ5Bg8VFLPIqoYV7MsICzzBsFj_-kVNNDQyOQ~~", + // }, + // ], + // }; + if (targetInfo.content) targetInfo.content = restoreHtml(targetInfo.content, targetInfo.attachments); + info.value = targetInfo; token = data.token; + tokentoken.value = data.token; - getAuthorInfo(); - getTopicOperation(); + if (info.value["anonymous"] == 0) getAuthorInfo(); - getCoinConfig(); + isLogin.value = data.islogin; + + if (isLogin.value) getTopicOperation(); getRelatedTopics(); - getComment(); + getQrcode(); + }); + }; + + const restoreHtml = (formattedText, attachments, type) => { + const imageList = attachments?.images || []; + const filesList = attachments?.files || []; + const videosList = attachments?.videos || []; + + let html = formattedText; + + // 1. 还原换行符为
标签 + html = html.replace(/\n/g, "
"); + + // 2. 还原块级标签的换行标记 + html = html.replace(/

/g, "
"); + html = html.replace(/<\/div>
/g, "
"); + + // 3. 还原标签标记为span.blue + html = html.replace(/\[tag\]([^[]+)\[\/tag\]/gi, '#$1 '); + + // 4. 还原粗体标记为h2标签 + html = html.replace(/\[b\]([^[]+)\[\/b\]/gi, "

$1

"); + + // 5. 统一在单次遍历中按出现顺序替换 attach/attachimg + const byAid = new Map(); + imageList.forEach((e) => byAid.set(Number(e.aid), { type: "image", ...e })); + filesList.forEach((e) => byAid.set(Number(e.aid), { type: "file", ...e })); + videosList.forEach((e) => byAid.set(Number(e.aid), { type: "video", ...e })); + + html = html.replace(/\[(attachimg|attach)\](\d+)\[\/\1\]/gi, (match, tag, aidStr) => { + const aid = Number(aidStr); + const item = byAid.get(aid); + if (!item) return match; + byAid.delete(aid); + if (item.type === "image") { + return `
`; + } + if (item.type === "file") { + return `
${item.filename}【点击下载附件】
`; + } + return `
`; + }); + + // 6. 还原填充标签 + html = html.replace(/([^<]+<\/span>)\s+/gi, '$1 '); + + // 7. 清理多余的
标签 + html = html.replace(/

/g, "
"); + + if (type != "comment") { + byAid.forEach((item, aid) => { + if (item.type === "image") html += `
`; + else if (item.type === "file") html += `
${item.name || item.filename}【点击下载附件】

`; + else html += `
`; + }); + } + + return html; + }; + + let QRcode = ref(""); + const getQrcode = () => { + ajaxGet(`/v2/api/forum/getQrcode?token=${token}`).then((res) => { + if (res.code != 200) return; + const data = res.data || []; + QRcode.value = data.url || ""; }); }; let count = ref(0); let medal = ref([]); const getAuthorInfo = () => { - ajaxget(`/v2/api/forum/getSpaceDetail?uid=${authorInfo.value.uid || 0}&uin=${authorInfo.value.uin || 0}`).then((res) => { + ajaxGet(`/v2/api/forum/getSpaceDetail?uid=${authorInfo.value.uid || 0}&uin=${authorInfo.value.uin || 0}`).then((res) => { const data = res.data; const countList = data.count || []; count.value = countList.reduce((sum, item) => { @@ -153,7 +280,7 @@ const appSectionIndex = createApp({ let recentlyList = ref([]); const getCreationList = (token) => { - ajaxget(`/v2/api/forum/getSpaceTopicList?token=${token}&simple=1`).then((res) => { + ajaxGet(`/v2/api/forum/getSpaceTopicList?token=${token}&simple=1`).then((res) => { const data = res.data; recentlyList.value = data.data || []; recentlyList.value = recentlyList.value.slice(0, 8); @@ -167,19 +294,30 @@ const appSectionIndex = createApp({ ajax(`/v2/api/forum/getTopicOperation`, { token, actions: ["like", "collection"], - }).then((res) => { - const data = res.data; - const like = data.like; - const collection = data.collection; + }) + .then((res) => { + console.log("res", res); - islike.value = like.status; - iscollect.value = collection.status; - }); + const data = res.data; + const like = data.like; + const collection = data.collection; + + islike.value = like.status; + iscollect.value = collection.status; + }) + .catch((err) => { + console.log("err", err); + }); }; let isLikeGif = ref(false); const likeClick = () => { + if (!isLogin.value) { + goLogin(); + return; + } + ajax(`/v2/api/forum/postTopicLike`, { token, }).then((res) => { @@ -217,19 +355,29 @@ const appSectionIndex = createApp({ let defaultcoinnum = 0; const getCoinConfig = () => { - ajaxget(`/v2/api/forum/getCoinConfig`).then((res) => { + if (!isLogin.value) { + goLogin(); + return; + } + + ajaxGet(`/v2/api/forum/getCoinConfig`).then((res) => { const data = res.data; strategy.value = data.config.strategy.url || 0; mybalance.value = data.mybalance || 0; defaultcoinnum = data.defaultcoinnum || 0; + + // openCoinBox(); }); }; let coinsState = ref(false); const openCoinBox = () => { - coinsState.value = true; - document.body.style.overflow = "hidden"; - if (!coinListRequest) getCoinRankList(); + BiComponent.initComponent(); + + // getCoinConfig(); + // coinsState.value = true; + // document.body.style.overflow = "hidden"; + // if (!coinListRequest) getCoinRankList(); }; const closeCoinBox = () => { @@ -270,7 +418,7 @@ const appSectionIndex = createApp({ let coinList = ref([]); let coinListRequest = false; // 控制请求次数 const getCoinRankList = () => { - ajaxget(`/v2/api/forum/getCoinRankList?token=${token}&limit=1000`).then((res) => { + ajaxGet(`/v2/api/forum/getCoinRankList?token=${token}&limit=1000`).then((res) => { const data = res.data; coinNubmer.value = data.nubmer; coinList.value = data.data; @@ -281,7 +429,7 @@ const appSectionIndex = createApp({ let relatedList = ref([]); let relatedTime = ref(""); const getRelatedTopics = () => { - ajaxget(`/v2/api/forum/getRelatedTopics?uniqid=${uniqid}&limit=8`).then((res) => { + ajaxGet(`/v2/api/forum/getRelatedTopics?uniqid=${uniqid}&limit=8`).then((res) => { const data = res.data; relatedTime.value = data.updated_at || ""; relatedList.value = data.list || []; @@ -295,9 +443,10 @@ const appSectionIndex = createApp({ let commentTotalCount = ref(0); const getComment = () => { - if (commentPage.value == 0 || isgetCommentSate) return; + console.log("commentPage.value", commentPage.value); + if (commentPage.value == 0 || isgetCommentSate || !token) return; isgetCommentSate = true; - ajaxget(`/v2/api/forum/getCommentList?token=${token}&page=${commentPage.value}&limit=1500`) + ajaxGet(`/v2/api/forum/getCommentList?token=${token}&page=${commentPage.value}&limit=20`) .then((res) => { if (res.code != 200) { creationAlertBox("error", res.message || ""); @@ -309,26 +458,31 @@ const appSectionIndex = createApp({ element.timestamp = strtimeago(element.created_at, 4); element["picture"] = []; element["isReplyBoxShow"] = 0; + + if (element["content"]) element["content"] = restoreHtml(element["content"], element.attachments, "comment"); + if (element.child.length > 0) { element.child.forEach((el) => { el["picture"] = []; el.timestamp = strtimeago(element.created_at, 4); el["isReplyBoxShow"] = 0; + + if (el["content"]) el["content"] = restoreHtml(el["content"], el.attachments, "comment"); }); } }); - if (commentPage.value > 1) { - for (let index = 0; index < data.data.length; index++) { - if (alreadyCommentIdList.includes(data.data[index].id)) { - data.data.splice(index, 1); - index--; - } - } - } + // if (commentPage.value > 1) { + // for (let index = 0; index < data.data.length; index++) { + // if (alreadyCommentIdList.includes(data.data[index].id)) { + // data.data.splice(index, 1); + // index--; + // } + // } + // } commentList.value = commentList.value.concat(data.data); - commentTotalCount.value = data.count; + commentTotalCount.value = data.commentcount; commentPage.value = data.count > commentList.value.length ? commentPage.value + 1 : 0; }) .finally(() => { @@ -339,8 +493,8 @@ const appSectionIndex = createApp({ let picture = ref([]); const openUserInfo = (index, i) => { - if (i != undefined && commentList.value[index].child[i].user["uin"] > 0) commentList.value[index].child[i]["avatarState"] = true; - if (i == undefined && index != undefined && commentList.value[index].user["uin"] > 0) commentList.value[index]["avatarState"] = true; + if (i != undefined && (commentList.value[index].child[i].user["uin"] > 0 || commentList.value[index].child[i].user["uid"] > 0)) commentList.value[index].child[i]["avatarState"] = true; + if (i == undefined && index != undefined && (commentList.value[index].user["uin"] > 0 || commentList.value[index].user["uid"] > 0)) commentList.value[index]["avatarState"] = true; }; const closeUserInfo = (index, i) => { @@ -348,6 +502,8 @@ const appSectionIndex = createApp({ else if (index != undefined) commentList.value[index]["avatarState"] = false; }; + let isReplyBoxShow = ref(true); + // 打开 回答-评论 的子评论 const openAnswerCommentsChild = (index, i) => { if (realname.value == 0 && userInfoWin.value?.uin > 0) { @@ -364,6 +520,8 @@ const appSectionIndex = createApp({ if (i == null) commentList.value[index]["childState"] = true; else commentList.value[index].child[i]["childState"] = true; + + isReplyBoxShow.value = false; }; // 关闭 回答-评论 的子评论 @@ -378,21 +536,24 @@ const appSectionIndex = createApp({ }); } }); + + isReplyBoxShow.value = true; }; - let dialogSrc = ref(""); const handleAnswerText = (e) => { if (e.target.tagName === "IMG") { var src = e.target.getAttribute("src"); - dialogSrc.value = src; - window.addEventListener("keydown", handleKeydown); - } - }; + const div = document.createElement("div"); + div.innerHTML = `
`; + div.className = "detail-image-mask flexcenter"; + div.addEventListener("click", () => { + document.body.style.overflow = "auto"; + div.remove(); + }); - const handleKeydown = (event) => { - if (event.key !== "Escape") return; - dialogSrc.value = ""; - window.removeEventListener("keydown", handleKeydown); // 取消监听 + document.body.appendChild(div); + document.body.style.overflow = "hidden"; + } }; // 回答-评论 点赞 @@ -407,7 +568,7 @@ const appSectionIndex = createApp({ return; } - ajax("https://api.gter.net/v2/api/forum/likeComment", { + ajax("/v2/api/forum/likeComment", { token, }).then((res) => { if (res.code != 200) { @@ -471,8 +632,8 @@ const appSectionIndex = createApp({ editEmojiState.value = false; }; - const TAHomePage = (uin) => goHomePage(uin); - const sendMessage = (uin) => goSendMessage(uin); + const TAHomePage = (token) => goHomePage(token); + const sendMessage = (token) => goSendMessage(token); let emojiData = ref(["😀", "😁", "😆", "😅", "😂", "😉", "😍", "🥰", "😘", "🤥", "😪", "😵‍💫", "🤓", "🥺", "😋", "😜", "🤪", "😎", "🤩", "🥳", "😔", "🙁", "😭", "😡", "😳", "🤗", "🤔", "🤭", "🤫", "😯", "😵", "🙄", "🥴", "🤢", "🤑", "🤠", "👌", "✌️", "🤟", "🤘", "🤙", "👍", "👎", "✊", "👏", "🤝", "🙏", "💪", "❎️", "✳️", "✴️", "❇️", "#️⃣", "*️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟", "🆗", "🈶", "🉐", "🉑", "🌹", "🥀", "🌸", "🌺", "🌷", "🌲", "☘️", "🍀", "🍁", "🌙", "⭐", "🌍", "☀️", "⭐️", "🌟", "☁️", "🌈", "☂️", "❄️", "☃️", "☄️", "🔥", "💧", "🍎", "🍐", "🍊", "🍉", "🍓", "🍑", "🍔", "🍟", "🍕", "🥪", "🍜", "🍡", "🍨", "🍦", "🎂", "🍰", "🍭", "🍿", "🍩", "🧃", "🍹", "🍒", "🥝", "🥒", "🥦", "🥨", "🌭", "🥘", "🍱", "🍢", "🥮", "🍩", "🍪", "🧁", "🍵", "🍶", "🍻", "🥂", "🧋", "🎉", "🎁", "🧧", "🎃", "🎄", "🧨", "✨️", "🎈", "🎊", "🎋", "🎍", "🎀", "🎖️", "🏆️", "🏅", "💌", "📬", "🚗", "🚕", "🚲", "🛵", "🚀", "🚁", "⛵", "🚢", "🔮", "🧸", "🀄️"]); @@ -497,7 +658,7 @@ const appSectionIndex = createApp({ if (!isLogin.value) goLogin(); }; - const maxPicture = 10; + let maxPicture = ref(10); const handleFileUpload = (event, index, i) => { closeEmoji(); @@ -518,13 +679,11 @@ const appSectionIndex = createApp({ else target = picture.value; } - if (target.length >= maxPicture) { - creationAlertBox("error", `最多只能上传 ${maxPicture} 张图片`); + if (target.length >= maxPicture.value) { + creationAlertBox("error", `最多只能上传 ${maxPicture.value} 张图片`); return; } - console.log("现有图片", target); - const reader = new FileReader(); reader.onload = (e) => { const base64 = e.target.result; @@ -580,7 +739,7 @@ const appSectionIndex = createApp({ const getUploadConfig = () => { return new Promise((resolve, reject) => { - ajax("https://api.gter.net/v1/config/upload?type=comment").then((res) => { + ajaxGet("/v2/api/config/upload?type=comment").then((res) => { let data = res.data; uploadConfig = data; resolve(); @@ -641,6 +800,8 @@ const appSectionIndex = createApp({ images: image, }; + console.log("userInfoWin", userInfoWin.value); + ajax("/v2/api/forum/postComment", { content, token, @@ -670,6 +831,7 @@ const appSectionIndex = createApp({ attachments, picture: [], timestamp, + user: { ...userInfoWin.value }, }; commentList.value[index]["child"].push(targetData); @@ -686,6 +848,7 @@ const appSectionIndex = createApp({ attachments, picture: [], timestamp, + user: { ...userInfoWin.value }, }; commentList.value[index]["child"].unshift(targetData); commentList.value[index]["childnum"]++; @@ -701,6 +864,7 @@ const appSectionIndex = createApp({ attachments, picture: [], timestamp, + user: { ...userInfoWin.value }, }; commentList.value.unshift(targetData); inputTextarea.value = ""; @@ -839,8 +1003,8 @@ const appSectionIndex = createApp({ else target = picture.value; } - if (target.length >= maxPicture) { - creationAlertBox("error", `最多只能上传 ${maxPicture} 张图片`); + if (target.length >= maxPicture.value) { + creationAlertBox("error", `最多只能上传 ${maxPicture.value} 张图片`); return; } @@ -870,6 +1034,39 @@ const appSectionIndex = createApp({ } }; + const alsoCommentsData = (index, i) => { + if (!isLogin.value) { + goLogin(); + return; + } + + const parentid = commentList.value[index]["id"]; + + ajax("/v2/api/forum/childrenList", { + token, + parentid, + limit: 2000, + page: 1, + childlimit: 3, + }).then((res) => { + if (res.code != 200) { + creationAlertBox("error", res.message || "操作成功"); + return; + } + let data = res.data; + + data.data.forEach((element, index) => { + element.timestamp = strtimeago(element.created_at, 4); + element["isReplyBoxShow"] = 0; + element["picture"] = []; + }); + + let merged = [...commentList.value[index]["child"], ...data.data.filter((item2) => !commentList.value[index]["child"].find((item1) => item1.id == item2.id))]; + + commentList.value[index]["child"] = merged; + }); + }; + // 自动输入框增高 const autoResize = (e) => { e.target.style.height = "auto"; // 重置高度 @@ -916,7 +1113,108 @@ const appSectionIndex = createApp({ }); }; - return { openDiscuss, commentDelete, handleInputPaste, autoResize, editCommentState, selectEditEmoji, closeEditEmoji, openEditEmoji, closeEdit, openEdit, closeEditFileUpload, postEditComment, submitAnswerComments, closePictureUpload, closeFileUpload, picture, editToken, editPicture, editInput, editEmojiState, handleFileUpload, inputTextarea, judgeLogin, handleEditFile, selectEmoji, emojiData, emojiMaskState, emojiState, closeEmoji, openEmoji, closeAnswerCommentsChild, openAnswerCommentsChild, dialogSrc, handleAnswerText, sendMessage, TAHomePage, operateAnswerCommentsLike, closeUserInfo, openUserInfo, permissions, commentList, commentPage, commentTotalCount, picture, userInfoWin, relatedList, relatedTime, coinNubmer, coinList, coinAmount, coinSubmit, strategy, mybalance, coinsState, openCoinBox, closeCoinBox, isLikeGif, likeClick, collectClick, islike, iscollect, recentlyList, medal, count, sectionn, tags, authorInfo, info, timestamp, updatedTime }; + let show = ref(false); + let ismanager = ref(false); + const cutShow = () => { + show.value = !show.value; // 修改为切换显示状态 + }; + + let reportState = ref(false); + let reportToken = ref(""); + provide("reportState", reportState); + + // 举报 + const report = (token) => { + cutShow(); + reportState.value = true; + reportToken.value = token; + }; + + // 隐藏 + const hide = () => { + const target = info.value; + managerHide(token, target.hidden, "thread").then((value) => { + target.hidden = value; + info.value = target; + cutShow(); + }); + }; + + // 推荐 + const recommend = () => { + const target = info.value; + managerRecommend(token, target.recommend).then((value) => { + target.recommend = value; + info.value = target; + cutShow(); + }); + }; + + // 精华 + const essence = () => { + const target = info.value; + managerEssence(token, target.best).then((value) => { + target.best = value; + info.value = target; + cutShow(); + }); + }; + + const copyLinkClick = () => { + copyForumUid(location.href); + }; + + const goPersonalHomepage = (token) => { + if (!token) return; + redirectToExternalWebsite(`/u/${token}`); + }; + + let searchInput = ref(""); + let defaultSearchText = ref("屯特"); + const goSearch = () => { + const searchText = searchInput.value || defaultSearchText.value; + redirectToExternalWebsite("/search/" + searchText); + }; + + const edit = () => { + redirectToExternalWebsite(`/publish?uniqid=${info.value.uniqid}`); + }; + + let pitchInputState = ref(false); + + const sidebarFixed = ref(false); + + const handleScroll = () => { + matterHeight.value = -(matterRef.value.offsetHeight - window.innerHeight); + sidebarHeight.value = -(sidebarRef.value.offsetHeight - window.innerHeight); + if (matterHeight.value > 0) matterHeight.value = 12; + if (sidebarHeight.value > 0) sidebarHeight.value = 12; + + const scrollTop = document.documentElement.scrollTop || document.body.scrollTop; + const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight; + const clientHeight = window.innerHeight; + + // 列表下 滑动到底部 获取新数据 + if (scrollTop + clientHeight >= scrollHeight - 200) getComment(); + }; + + const matterRef = ref(null); + const sidebarRef = ref(null); + + const deleteItem = () => { + managerDelete(token) + .then(() => redirectToExternalWebsite("/", "_self")) + .finally(() => cutShow()); + }; + + let sidebarHeight = ref(0); + let matterHeight = ref(0); + + const share = () => { + ajax(`/v2/api/forum/postTopicShare`, { token }); + }; + + return { uniqidRef, share, reportToken, isReplyBoxShow, matterHeight, sidebarHeight, deleteItem, maxPicture, sidebarFixed, matterRef, sidebarRef, pitchInputState, ismyself, edit, searchInput, defaultSearchText, goSearch, goPersonalHomepage, QRcode, alsoCommentsData, copyLinkClick, reportState, tokentoken, essence, recommend, hide, report, cutShow, ismanager, show, openDiscuss, commentDelete, handleInputPaste, autoResize, editCommentState, selectEditEmoji, closeEditEmoji, openEditEmoji, closeEdit, openEdit, closeEditFileUpload, postEditComment, submitAnswerComments, closePictureUpload, closeFileUpload, picture, editToken, editPicture, editInput, editEmojiState, handleFileUpload, inputTextarea, judgeLogin, handleEditFile, selectEmoji, emojiData, emojiMaskState, emojiState, closeEmoji, openEmoji, closeAnswerCommentsChild, openAnswerCommentsChild, handleAnswerText, sendMessage, TAHomePage, operateAnswerCommentsLike, closeUserInfo, openUserInfo, permissions, commentList, commentPage, commentTotalCount, picture, userInfoWin, relatedList, relatedTime, coinNubmer, coinList, coinAmount, coinSubmit, strategy, mybalance, coinsState, openCoinBox, closeCoinBox, isLikeGif, likeClick, collectClick, islike, iscollect, recentlyList, medal, count, sectionn, tags, authorInfo, info, timestamp, updatedTime }; }, }); @@ -929,5 +1227,7 @@ appSectionIndex.component("itemTenement", itemTenement); appSectionIndex.component("latestList", latestList); appSectionIndex.component("slideshowBox", slideshowBox); appSectionIndex.component("like", like); +appSectionIndex.component("report", report); +appSectionIndex.component("headTop", headTop); appSectionIndex.mount("#details"); diff --git a/js/edit.js b/js/edit.js index 269fbac..84eb501 100644 --- a/js/edit.js +++ b/js/edit.js @@ -6,12 +6,18 @@ const editApp = createApp({ setup() { let titleLength = ref(200); + let uniqid = ref(""); onMounted(() => { + const params = getUrlParams(); + uniqid.value = params.uniqid || ""; + getUserInfoWin(); cUpload(); init(); + checkWConfig(); + // 添加selectionchange事件监听,当鼠标选中区域内容时更新lastSelection document.addEventListener("selectionchange", handleSelectionChange); }); @@ -21,19 +27,46 @@ const editApp = createApp({ document.removeEventListener("selectionchange", handleSelectionChange); }); - let isLogin = ref(true); - let realname = ref(1); // 是否已经实名 - let userInfoWin = ref({ - authority: ["comment.edit", "comment.delete", "offercollege.hide", "offersummary.hide", "mj.hide", "topic:manager", "topic:hide"], - avatar: "https://nas.gter.net:9008/avatar/97K4EWIMLrsbGTWXslC2WFVSEKWOikN42jDKLNjtax7HL4xtfMOJSdU9oWFhY2E~/middle?random=1761733169", - groupid: 3, - nickname: "肖荣豪", - realname: 1, - token: "01346a38444d71aaadb3adad52b52c39", - uid: 500144, - uin: 4238049, - }); + let imageLength = 10; + let videoLength = 5; + const checkWConfig = () => { + const wConfig = JSON.parse(localStorage.getItem("wConfig")) || {}; + console.log("wConfig", wConfig); + + if (wConfig.time) { + const time = new Date(wConfig.time); + const now = new Date(); + if (now - time > 24 * 60 * 60 * 1000) getWConfig(); + else { + const config = wConfig.config || {}; + titleLength.value = config.max_topic_title_length; + imageLength = config.topic_image_count || 0; + videoLength = config.topic_video_count || 0; + } + } else { + getWConfig(); + } + }; + + const getWConfig = () => { + ajaxGet("/v2/api/config/website").then((res) => { + if (res.code == 200) { + let data = res["data"] || {}; + const config = data.config || {}; + titleLength.value = config.max_topic_title_length; + imageLength = config.topic_image_count || 0; + videoLength = config.topic_video_count || 0; + + data.time = new Date().toISOString(); + localStorage.setItem("wConfig", JSON.stringify(data)); + } + }); + }; + + let isLogin = ref(false); + let realname = ref(0); // 是否已经实名 + let userInfoWin = ref({}); let permissions = ref([]); const getUserInfoWin = () => { @@ -82,22 +115,21 @@ const editApp = createApp({ let token = ref(""); let infoImages = []; const init = () => { - ajax("/v2/api/forum/postPublishInit") + ajax("/v2/api/forum/postPublishInit", { + uniqid: uniqid.value, + }) .then((res) => { const data = res.data; if (res.code != 200) { - creationAlertBox(res.message || "操作失败"); + creationAlertBox("error", res.message || "操作失败"); return; } const infoTarget = data.info || {}; - infoImages = infoTarget.attachments?.images || []; - - if (infoTarget.content) infoTarget.content = restoreHtml(infoTarget.content, infoImages); + if (infoTarget.content) infoTarget.content = restoreHtml(infoTarget.content, infoTarget.attachments); info.value = infoTarget; - tagList.value = data.tagList; token.value = data.token; nextTick(() => { @@ -109,7 +141,12 @@ const editApp = createApp({ }); }; - const restoreHtml = (formattedText, imageList) => { + const restoreHtml = (formattedText, attachments) => { + const imageList = attachments?.images || []; + + const filesList = attachments?.files || []; + const videosList = attachments?.videos || []; + let html = formattedText; // 1. 还原换行符为
标签 @@ -130,22 +167,52 @@ const editApp = createApp({ // 查找对应的图片信息 const image = imageList.find((img) => img.aid == aid); if (image) { - return ``; + imageList.splice(imageList.indexOf(image), 1); + return `
`; } return match; // 未找到对应图片时保留原始标记 }); + html = html.replace(/\[attach\](\d+)\[\/attach\]/gi, (match, aid) => { + // 查找对应的图片信息 + const image = imageList.find((img) => img.aid == aid); + if (image) { + imageList.splice(imageList.indexOf(image), 1); + return `
`; + } + + // 查找对应的视频信息 + const video = videosList.find((v) => v.aid == aid); + if (video) { + console.log("video", video); + videosList.splice(videosList.indexOf(video), 1); + return ``; + } + + return match; // 未找到对应图片时保留原始标记 + }); + // 6. 还原填充标签 html = html.replace(/([^<]+<\/span>)\s+/gi, '$1 '); // 7. 清理多余的
标签 html = html.replace(/

/g, "
"); + imageList.forEach((element) => { + html += `
`; + }); + + // video 不要预加载 + videosList.forEach((element) => { + html += `
`; + }); + return html; }; onMounted(() => { setTimeout(() => focusLastNode(), 1000); + // document.addEventListener("keydown", handleUndoKeydown); }); const editorRef = ref(null); @@ -166,51 +233,89 @@ const editApp = createApp({ const maxSize = 20 * 1024 * 1024; // 20MB const insertImage = (event) => { - let config = uConfigData; + const images = extractImages(editorRef.value); + + const count = imageLength - images.length || 0; + + if (count == 0) { + creationAlertBox("error", `最多只能上传 ${imageLength} 张图片`); + return; + } + const target = event.target.files[0]; if (!target) return; // 处理未选择文件的情况 if (target.size > maxSize) { - creationAlertBox("文件大小不能超过 20MB"); + creationAlertBox("error", "文件大小不能超过 20MB"); return; } loading.value = true; - // 不要删除,后面会用 - const formData = new FormData(); - formData.append(config.requestName, target); // 文件数据 - formData.append("name", target.name); // 文件名 - formData.append("type", "image"); // 文件名 - formData.append("data", config.params.data); // 文件名 + uploading(target, target.name, "image").then((data) => { + const selection = window.getSelection(); + editorRef.value.focus(); + if (lastSelection) { + selection.removeAllRanges(); + selection.addRange(lastSelection); + } + const html = `
`; + document.execCommand("insertHTML", false, html); + judgeIsEmpty(); + }); + }; - ajax(config.url, formData) - .then((res) => { - const data = res.data; - try { - const range = lastSelection; - const img = document.createElement("img"); + const insertVideo = async (event) => { + const videos = extractVideos(editorRef.value); - img.src = data.url; - img.setAttribute("data-aid", data.aid); - range.insertNode(img); - const div = document.createElement("div"); - range.insertNode(div); - judgeIsEmpty(); - } catch (error) { - console.error("插入图片出错:", error); - } - }) - .finally(() => { - loading.value = false; - }); + const count = videoLength - videos.length || 0; + + if (count == 0) { + creationAlertBox("error", `最多只能上传 ${videoLength} 个视频`); + return; + } + + const videoFile = event.target.files[0]; + + if (!videoFile) return; // 处理未选择文件的情况 + + if (videoFile.size > maxSize) { + creationAlertBox("error", "文件大小不能超过 20MB"); + return; + } + + loading.value = true; + + console.log("videoFile", videoFile); + + // 步骤1:提取视频第一帧(等待提取完成) + const coverFile = await getVideoFirstFrame(videoFile); + console.log("第一帧提取成功", coverFile); + + // 步骤2:先上传视频文件(type 传 'video',按后端要求调整) + const videoUploadRes = await uploading(videoFile, videoFile.name, "video"); + console.log("视频上传成功", videoUploadRes); + + // 步骤3:再上传第一帧封面(type 传 'cover',按后端要求调整) + const coverUploadRes = await uploading(coverFile, coverFile.name, "image"); + console.log("封面上传成功", coverUploadRes); + + console.log("最终", videoUploadRes, videoUploadRes); + + const selection = window.getSelection(); + editorRef.value.focus(); + if (lastSelection) { + selection.removeAllRanges(); + selection.addRange(lastSelection); + } + const html = `
`; + document.execCommand("insertHTML", false, html); + judgeIsEmpty(); }; let isEmpty = ref(true); const onEditorInput = (event) => { - console.log("onEditorInput"); - const selection = window.getSelection(); if (selection.rangeCount > 0) { @@ -221,7 +326,7 @@ const editApp = createApp({ judgeIsEmpty(); - debouncedGetTagList(); + // debouncedGetTagList(); }; // 防抖函数 @@ -260,26 +365,28 @@ const editApp = createApp({ }; const getTagList = () => { + if (!isLogin.value) { + goLogin(); + return; + } const content = editorRef.value.innerText; - axios - .post("https://api.gter.net/v2/api/forum/postPublishTags", { - content, - }) - .then((res) => { - res = res.data; - if (res.code != 200) return; - let data = res.data || []; + ajax("/v2/api/forum/postPublishTags", { + content, + }).then((res) => { + res = res.data; + if (res.code != 200) return; + let data = res.data || []; - // 随机生成一下数据 - for (let i = 0; i < 5; i++) { - data.push({ - title: getRandomChinese() + getRandomChinese(), - tagId: generateRandomString(), - }); - } + // 随机生成一下数据 + for (let i = 0; i < 5; i++) { + data.push({ + title: getRandomChinese() + getRandomChinese(), + tagId: generateRandomString(), + }); + } - tagList.value = data; - }); + tagList.value = data; + }); }; const debouncedGetTagList = debounce(getTagList, 500); @@ -296,7 +403,7 @@ const editApp = createApp({ // 判断是否为空 const judgeIsEmpty = () => { const text = editorRef.value.innerText; - isEmpty.value = text.length == 0 && !editorRef.value.querySelector("img"); + isEmpty.value = text.length == 0 && !editorRef.value.querySelector("img") && !editorRef.value.querySelector("video"); }; // 处理选中文本变化的函数 @@ -309,7 +416,6 @@ const editApp = createApp({ const commonAncestor = range.commonAncestorContainer; if (editorRef.value.contains(commonAncestor)) { console.log("选中区域在编辑器内", range); - lastSelection = range; } } @@ -366,36 +472,6 @@ const editApp = createApp({ const cutAnonymity = () => (info.value.anonymous = info.value.anonymous ? 0 : 1); - const insertLabel = (id) => { - const index = tagList.value.findIndex((item) => item.tagId == id); - if (index == -1) return; - const label = tagList.value[index].title; - - const span = document.createElement("span"); - span.innerHTML = `#${label} `; - lastSelection.insertNode(span); - - // 移动光标到元素后面并确保光标位置被正确设置和获取 - const newRange = document.createRange(); - newRange.setStartAfter(span); - newRange.setEndAfter(span); - - // 更新选择范围 - const selection = window.getSelection(); - selection.removeAllRanges(); - selection.addRange(newRange); - lastSelection = newRange; - - // 手动触发selectionchange事件,确保其他组件知道光标位置变化 - const selectionChangeEvent = new Event("selectionchange", { bubbles: true }); - document.dispatchEvent(selectionChangeEvent); - - judgeIsEmpty(); - - // 删除 tagList 中当前标签 - tagList.value.splice(index, 1); - }; - let emojiState = ref(false); const optionEmoji = ref(["😀", "😁", "😆", "😅", "😂", "😉", "😍", "🥰", "😘", "🤥", "😪", "😵‍💫", "🤓", "🥺", "😋", "😜", "🤪", "😎", "🤩", "🥳", "😔", "🙁", "😭", "😡", "😳", "🤗", "🤔", "🤭", "🤫", "😯", "😵", "🙄", "🥴", "🤢", "🤑", "🤠", "👌", "✌️", "🤟", "🤘", "🤙", "👍", "👎", "✊", "👏", "🤝", "🙏", "💪", "❎️", "✳️", "✴️", "❇️", "#️⃣", "*️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟", "🆗", "🈶", "🉐", "🉑", "🌹", "🥀", "🌸", "🌺", "🌷", "🌲", "☘️", "🍀", "🍁", "🌙", "⭐", "🌍", "☀️", "⭐️", "🌟", "☁️", "🌈", "☂️", "❄️", "☃️", "☄️", "🔥", "💧", "🍎", "🍐", "🍊", "🍉", "🍓", "🍑", "🍔", "🍟", "🍕", "🥪", "🍜", "🍡", "🍨", "🍦", "🎂", "🍰", "🍭", "🍿", "🍩", "🧃", "🍹", "🍒", "🥝", "🥒", "🥦", "🥨", "🌭", "🥘", "🍱", "🍢", "🥮", "🍩", "🍪", "🧁", "🍵", "🍶", "🍻", "🥂", "🧋", "🎉", "🎁", "🧧", "🎃", "🎄", "🧨", "✨️", "🎈", "🎊", "🎋", "🎍", "🎀", "🎖️", "🏆️", "🏅", "💌", "📬", "🚗", "🚕", "🚲", "🛵", "🚀", "🚁", "⛵", "🚢", "🔮", "🧸", "🀄️"]); @@ -405,22 +481,13 @@ const editApp = createApp({ const closeEmoji = () => (emojiState.value = false); const selectEmoji = (emoji) => { - const textNode = document.createTextNode(emoji); - lastSelection.insertNode(textNode); - - // 移动光标到emoji后面并确保光标位置被正确设置和获取 - const newRange = document.createRange(); - newRange.setStartAfter(textNode); - newRange.setEndAfter(textNode); - - // 更新选择范围 const selection = window.getSelection(); - selection.removeAllRanges(); - lastSelection = newRange; - - // 手动触发selectionchange事件,确保其他组件知道光标位置变化 - const selectionChangeEvent = new Event("selectionchange", { bubbles: true }); - document.dispatchEvent(selectionChangeEvent); + editorRef.value.focus(); + if (lastSelection) { + selection.removeAllRanges(); + selection.addRange(lastSelection); + } + document.execCommand("insertText", false, emoji); closeEmoji(); judgeIsEmpty(); }; @@ -430,14 +497,21 @@ const editApp = createApp({ const infoTarget = { ...info.value } || {}; let content = editorRef.value.innerHTML; - const images = extractImages(content); + const images = extractImages(editorRef.value); + const videos = extractVideos(editorRef.value); + infoTarget.attachments = infoTarget.attachments || {}; infoTarget.attachments.images = images; + infoTarget.attachments.videos = videos; + info.value["attachments"] = info.value["attachments"] || {}; info.value["attachments"]["images"] = images; - console.log("转换前:", content); + info.value["attachments"]["videos"] = videos; + + console.log(content); content = formatContent(content); - console.log("转换后:", content); + console.log(content); + const data = { ...infoTarget, content, @@ -456,8 +530,8 @@ const editApp = createApp({ creationAlertBox("success", res.message || "操作成功"); const back = () => { - if (status == 1) redirectToExternalWebsite("./details.html?uniqid=" + data.uniqid); - else redirectToExternalWebsite("./index.html"); + if (status == 1) redirectToExternalWebsite("/details/" + data.uniqid, "_self"); + else redirectToExternalWebsite("/", "_self"); }; setTimeout(() => back(), 1500); @@ -468,15 +542,12 @@ const editApp = createApp({ // 1. 替换图片标签 html = html.replace(/]*data-aid="(\d+)"[^>]*>/gi, "[attachimg]$1[/attachimg]"); + // 1.1 替换视频标签 + html = html.replace(/]*aid="(\d+)"[^>]*>[\s\S]*?<\/video>/gi, "[attach]$1[/attach]"); + // 2. 替换H2标签 html = html.replace(/]*>([\s\S]*?)<\/h2>/gi, "[b]$1[/b]"); - // 3. 替换标签(保留与前后内容的连续性) - html = html.replace(/#([^<]+)<\/span>/gi, "[tag]$1[/tag]"); - - // 4. 移除无关标签(如空的) - html = html.replace(/[^<]*<\/span>/gi, ""); - // 5. 处理块级标签换行(仅
等块级标签前后换行,保持行内内容连续) // 块级标签:div、p、h1-h6等,这里以div为例 html = html.replace(/<\/div>\s*/gi, "
\n"); // 闭合div后换行 @@ -485,8 +556,8 @@ const editApp = createApp({ // 6. 处理
为换行 html = html.replace(//gi, "\n"); - // 7. 移除所有剩余HTML标签 - html = html.replace(/<[^>]+>/gi, ""); + // 7. 移除所有剩余HTML标签 a标签除外 + html = html.replace(/<(?!(a\b|\/a\b))[^>]+>/gi, ""); // 8. 清理连续换行(最多保留两个空行,避免过多空行) html = html.replace(/\n{3,}/g, "\n\n"); @@ -496,29 +567,67 @@ const editApp = createApp({ return html; }; - const extractImages = (html) => { + const extractImages = (dom) => { const images = []; - // 正则匹配 img 标签,提取 src(url)和 data-aid - const imgRegex = /]*src="([^"]+)"[^>]*data-aid="(\d+)"[^>]*>/gi; - let match; - // 循环匹配所有图片标签 - while ((match = imgRegex.exec(html)) !== null) { + // 直接查找页面中所有带 data-aid 的 img 标签 + const imgElements = dom.querySelectorAll("img"); + + imgElements.forEach((imgEl) => { + const url = imgEl.getAttribute("src")?.trim() || ""; + const aid = imgEl.dataset.aid?.trim() || ""; // 用 dataset 简化自定义属性读取 + images.push({ - url: match[1], // 图片的 src 地址 - aid: Number(match[2]), // 图片的 data-aid 属性值 + url, + aid: Number(aid), }); - } - + }); return images; }; + const extractVideos = (dom) => { + // 1. 查找页面中所有