diff --git a/css/details.css b/css/details.css
index 96abd2d..df13da2 100644
--- a/css/details.css
+++ b/css/details.css
@@ -173,18 +173,22 @@
}
#details .matter .matter-left .html img {
max-width: 100%;
- display: block;
+ display: inline-block;
}
#details .matter .matter-left .html video {
margin: 0 auto;
}
-#details .matter .matter-left .html h2 {
+#details .matter .matter-left .html h1 {
font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif;
font-weight: 650;
color: #000000;
font-size: 18px;
line-height: 30px;
}
+#details .matter .matter-left .html tr,
+#details .matter .matter-left .html td {
+ background: transparent;
+}
#details .matter .matter-left .last-time {
color: #aaaaaa;
font-size: 13px;
diff --git a/css/details.less b/css/details.less
index b634c27..d00e8cb 100644
--- a/css/details.less
+++ b/css/details.less
@@ -200,20 +200,25 @@
img {
max-width: 100%;
- display: block;
+ display: inline-block;
}
video {
margin: 0 auto;
}
- h2 {
+ h1 {
font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif;
font-weight: 650;
color: #000000;
font-size: 18px;
line-height: 30px;
}
+
+ tr,
+ td {
+ background: transparent;
+ }
}
.last-time {
diff --git a/css/edit.css b/css/edit.css
index bfb171a..c92ff83 100644
--- a/css/edit.css
+++ b/css/edit.css
@@ -76,8 +76,10 @@
#edit .edit-container #editor—wrapper {
z-index: 100;
}
-#edit .edit-container #editor—wrapper .bold {
- font-weight: bold;
+#edit .edit-container #editor—wrapper .bold,
+#edit .edit-container #editor—wrapper .bold span {
+ font-weight: bolder;
+ font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif;
}
#edit .edit-container #editor—wrapper .editor-toolbar {
height: 36px;
@@ -262,14 +264,14 @@
#edit .edit-container #editor—wrapper .editor-toolbar .toolbar-item.link .link-box .btn:hover {
background-color: #23e0b6;
}
-#edit .edit-container #editor—wrapper .editor-toolbar .toolbar-item.h2.pitch {
+#edit .edit-container #editor—wrapper .editor-toolbar .toolbar-item.h1.pitch {
background-color: #f6f6bd;
}
#edit .edit-container #editor—wrapper .editor-toolbar .toolbar-item.active > button {
background-color: #f6f6bd;
}
#edit .edit-container #editor—wrapper #editor-container {
- min-height: 500px;
+ height: 500px;
max-height: 80vh;
font-size: 18px;
line-height: 26px;
@@ -280,6 +282,17 @@
text-decoration: underline;
color: #04b0d5;
}
+#edit .edit-container #editor—wrapper #editor-container h1,
+#edit .edit-container #editor—wrapper #editor-container h1 span {
+ font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif;
+ font-weight: 650;
+ color: #000000;
+ font-size: 18px;
+ line-height: 30px;
+}
+#edit .edit-container #editor—wrapper #editor-container video {
+ max-width: 100%;
+}
#edit .edit-container .content-input {
min-height: 509px;
font-family: "PingFangSC-Regular", "PingFang SC", sans-serif;
@@ -306,12 +319,12 @@
max-width: 100%;
height: 220px;
}
-#edit .edit-container .content-input h2 {
+#edit .edit-container .content-input h1 {
+ font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif;
+ font-weight: 650;
color: #000000;
font-size: 18px;
line-height: 30px;
- font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif;
- font-weight: 650;
}
#edit .edit-container .content-input .blue {
color: #026277;
diff --git a/css/edit.less b/css/edit.less
index c9e541f..b73f6ec 100644
--- a/css/edit.less
+++ b/css/edit.less
@@ -85,8 +85,10 @@
#editor—wrapper {
z-index: 100;
- .bold {
- font-weight: bold;
+ .bold,
+ .bold span {
+ font-weight: bolder;
+ font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif;
}
.editor-toolbar {
@@ -305,7 +307,7 @@
}
}
- &.h2 {
+ &.h1 {
&.pitch {
background-color: rgba(246, 246, 189, 1);
}
@@ -320,7 +322,7 @@
}
#editor-container {
- min-height: 500px;
+ height: 500px;
max-height: 80vh;
font-size: 18px;
line-height: 26px;
@@ -331,6 +333,19 @@
text-decoration: underline;
color: #04b0d5;
}
+
+ h1,
+ h1 span {
+ font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif;
+ font-weight: 650;
+ color: #000000;
+ font-size: 18px;
+ line-height: 30px;
+ }
+
+ video {
+ max-width: 100%;
+ }
}
}
@@ -362,13 +377,19 @@
height: 220px;
}
- h2 {
- color: #000000;
- font-size: 18px;
- line-height: 30px;
+ h1 {
+ // color: #000000;
+ // font-size: 18px;
+ // line-height: 30px;
+
+ // font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif;
+ // font-weight: 650;
font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif;
font-weight: 650;
+ color: #000000;
+ font-size: 18px;
+ line-height: 30px;
}
.blue {
diff --git a/edit.html b/edit.html
index 0c0e4fb..ca8ba02 100644
--- a/edit.html
+++ b/edit.html
@@ -40,7 +40,6 @@
diff --git a/js/details.js b/js/details.js
index 95fe5e1..ade618e 100644
--- a/js/details.js
+++ b/js/details.js
@@ -130,6 +130,25 @@ const appSectionIndex = createApp({
if (!targetInfo.hidden) targetInfo.hidden = 0;
+ targetInfo.attachments = {
+ images: [
+ {
+ aid: 708161,
+ url: "https://o.x-php.com/Zvt57TuJSUvkyhw-xG7Y2l-S_pItc37qqsgFptxhXa6RWi26P-BuTQYWFOfCsdkb8LQ0NDI5",
+ },
+ ],
+ files: [],
+ videos: [
+ {
+ aid: 1009770,
+ posterid: 1009849,
+ posterurl: "https://o.x-php.com/Zvt57TuJSUvkyhw-xG_Y2l-U_polfXuP1NFX9ddrB_WbUGy8P79gQxdHR-HKts0V7NkzNDQyOQ~~",
+ url: "https://o.x-php.com/Zvt57TuJSUvkyhw-xG_Y2l-U_polcniG1NFX9ddrB_WbUGy8P79gQxcSFbqQ78MV7NkzNDQyOQ~~",
+ },
+ ],
+ };
+
+ // targetInfo.content = '红红火火恍恍惚惚
[b]红红火火恍恍惚惚有[/b]\n
\n
[attach]1009770[/attach]
\n
\n
[img=96]708161[/img]
65456456456456465 111
\n
';
targetInfo.content = '如果你热爱古典文献,又希望在现代职场大展身手——这个项目可能就是你的“本命”!作为香港最正统的中国语言文学项目,它既传承经典,又为你打跨境传播等全新赛道!\n\n🌟 项目核心亮点权威认证:中国语言文学专业认证,考公考编无障碍\n古今结合:深耕古典文献与理论,同时对接AI内容创作等新兴领域\n语言友好:全程中文授课(普通话+粤语),无语言适应压力\n规模可观:每年录取150+,机会相对较多\n\n点击前往 [港校项目库] 查看 \n中国语言文学\n手机扫码查看\n[attachimg]1008942[/attachimg]\n\n🎯 谁最适合申请?中文系、汉语言、古代文学等对口专业背景\n希望在教育、传媒、AI内容或国际中文教育领域发展\n看重学校声誉与专业正统性的同学\n💼 毕业出路超多元除了教师、公务员等传统路径,毕业生还活跃于:\n✔ 跨境文化传播\n✔ AI内容策划与生成\n✔ 国际中文教育\n✔ 出版与编辑行业\n📌 申请指南专业背景:严格限定中文相关专业,暂不接受跨专业申请\n成绩要求:985/211同学建议86+\n语言成绩:雅思7.0(小分5.5)即可\n面试体验:氛围轻松,专业问题较少\n💡 内部消息参考前几轮拿到面试邀请的同学基本都能录取\n985背景优势明显,建议尽早提交申请\n双非同学如背景特别匹配也可尝试\n🤝 欢迎交流你对中国文学在AI时代的发展有什么想法?或者对哪个就业方向,申请问题欢迎在评论区分享交流!\n欢迎加入寄托香港群交流\n\n[attachimg]969489[/attachimg]';
// 替换换行
@@ -194,6 +213,25 @@ const appSectionIndex = createApp({
// 4. 还原粗体标记为h2标签
html = html.replace(/\[b\]([\s\S]*?)\[\/b\]/gi, "$1
");
+ // 5. 还原【新增图片格式】[img=width,height]aid[/img] 或 [img]aid[/img]
+ html = html.replace(/\[img(?:=([0-9]+(?:\.[0-9]+)?)(?:,([0-9]+(?:\.[0-9]+)?))?)?\](\d+)\[\/img\]/gi, (match, width, height, aid) => {
+ const image = imageList.find((img) => String(img.aid) === String(aid)); // 统一字符串比较,避免类型问题
+ if (!image) return match;
+
+ // 从列表中移除已匹配的图片(避免重复使用)
+ const index = imageList.findIndex((img) => String(img.aid) === String(aid));
+ if (index > -1) imageList.splice(index, 1);
+
+ // 拼接img标签(带宽高样式,宽高为0则不设置)
+ let style = "";
+ const w = width ? Number(width) : 0;
+ const h = height ? Number(height) : 0;
+ if (w > 0 && h > 0) style = `style="width: ${w}px; height: ${h}px;"`;
+ else if (w > 0) style = `style="width: ${w}px;"`;
+
+ return `
`;
+ });
+
console.log(html);
// 5. 统一在单次遍历中按出现顺序替换 attach/attachimg
diff --git a/js/edit copy.js b/js/edit copy.js
new file mode 100644
index 0000000..bdc64c3
--- /dev/null
+++ b/js/edit copy.js
@@ -0,0 +1,761 @@
+// 简单版本的论坛编辑器,确保图片插入功能正常
+const { createApp, ref, computed, onMounted, nextTick, onUnmounted } = Vue;
+const { headTop } = await import(withVer("../component/head-top/head-top.js"));
+
+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);
+ });
+
+ // 组件卸载时移除事件监听
+ onUnmounted(() => {
+ document.removeEventListener("selectionchange", handleSelectionChange);
+ });
+
+ 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 = () => {
+ const checkUser = () => {
+ const user = window.userInfoWin;
+ if (!user) return;
+ document.removeEventListener("getUser", checkUser);
+ realname.value = user.realname;
+ userInfoWin.value = user;
+ if (user?.uin > 0 || user?.uid > 0) isLogin.value = true;
+ permissions.value = user?.authority || [];
+ };
+ document.addEventListener("getUser", checkUser);
+ };
+
+ const openAttest = () => {
+ const handleAttestClose = () => {
+ document.removeEventListener("closeAttest", handleAttestClose);
+ realname.value = window.userInfoWin?.realname || 0;
+ };
+ // 启动认证流程时添加监听
+ document.addEventListener("closeAttest", handleAttestClose);
+ loadAttest(2);
+ };
+
+ // 跳转登录
+ const goLogin = () => {
+ if (typeof window === "undefined") return;
+ if (window["userInfoWin"] && Object.keys(window["userInfoWin"]).length !== 0) {
+ if (window["userInfoWin"]["uid"]) isLogin.value = true;
+ else ajax_login();
+ } else ajax_login();
+ };
+
+ let uConfigData = {};
+
+ const cUpload = () => {
+ ajaxGet(`/v2/api/config/upload?type=topic`).then((res) => {
+ const data = res.data;
+ uConfigData = data;
+ });
+ };
+
+ let info = ref({});
+ let tagList = ref([]);
+ let token = ref("");
+ let infoImages = [];
+ const init = () => {
+ ajax("/v2/api/forum/postPublishInit", {
+ uniqid: uniqid.value,
+ })
+ .then((res) => {
+ const data = res.data;
+ if (res.code != 200) {
+ creationAlertBox("error", res.message || "操作失败");
+ return;
+ }
+
+ const infoTarget = data.info || {};
+
+ if (infoTarget.content) infoTarget.content = restoreHtml(infoTarget.content, infoTarget.attachments);
+
+ info.value = infoTarget;
+ token.value = data.token;
+
+ nextTick(() => {
+ judgeIsEmpty();
+ });
+ })
+ .catch((err) => {
+ console.log("err", err);
+ });
+ };
+
+ const restoreHtml = (formattedText, attachments) => {
+ 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\]([\s\S]*?)\[\/b\]/gi, "
$1
");
+
+ // 5. 还原图片标记为img标签(使用提供的imageList)
+ html = html.replace(/\[attachimg\](\d+)\[\/attachimg\]/gi, (match, aid) => {
+ // 查找对应的图片信息
+ const image = imageList.find((img) => img.aid == aid);
+ if (image) {
+ 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);
+
+ const focusLastNode = () => {
+ const newRange = document.createRange();
+ const textNode = document.createTextNode("");
+ editorRef.value.appendChild(textNode);
+ newRange.setStartAfter(textNode, 0);
+ newRange.setEndAfter(textNode, 0);
+ lastSelection = newRange;
+ };
+
+ let lastSelection = null;
+
+ let loading = ref(false);
+
+ const maxSize = 20 * 1024 * 1024; // 20MB
+
+ const insertImage = (event) => {
+ 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("error", "文件大小不能超过 20MB");
+ return;
+ }
+
+ loading.value = true;
+
+ 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();
+ });
+ };
+
+ const insertVideo = async (event) => {
+ const videos = extractVideos(editorRef.value);
+
+ 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) => {
+ const selection = window.getSelection();
+
+ if (selection.rangeCount > 0) {
+ lastSelection = selection.getRangeAt(0);
+ // console.log("更新选区");
+ updatePTitleStatus();
+ }
+
+ judgeIsEmpty();
+
+ // debouncedGetTagList();
+ };
+
+ // 防抖函数
+ const debounce = (fn, delay = 500) => {
+ let timer = null;
+ return function () {
+ if (timer) {
+ clearTimeout(timer);
+ }
+ timer = setTimeout(() => {
+ fn.apply(this, arguments);
+ timer = null;
+ }, delay);
+ };
+ };
+
+ const getRandomChinese = () => {
+ // 中文 Unicode 范围:\u4e00 - \u9fa5(共约 2 万个汉字)
+ const start = 0x4e00; // 起始编码
+ const end = 0x9fa5; // 结束编码
+ // 生成范围内的随机整数,转为字符
+ return String.fromCodePoint(Math.floor(Math.random() * (end - start + 1) + start));
+ };
+
+ const generateRandomString = (length = 5) => {
+ // 定义字符集:包含大小写字母和数字
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ let result = "";
+ // 循环生成指定长度的随机字符
+ for (let i = 0; i < length; i++) {
+ // 从字符集中随机取一个字符
+ const randomIndex = Math.floor(Math.random() * chars.length);
+ result += chars[randomIndex];
+ }
+ return result;
+ };
+
+ const getTagList = () => {
+ if (!isLogin.value) {
+ goLogin();
+ return;
+ }
+ const content = editorRef.value.innerText;
+ 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(),
+ });
+ }
+
+ tagList.value = data;
+ });
+ };
+
+ const debouncedGetTagList = debounce(getTagList, 500);
+
+ let isBottomState = ref(false); // 底部按钮 显示
+ const onEditorFocus = () => {
+ isBottomState.value = true;
+ };
+
+ const onEditorBlur = () => {
+ isBottomState.value = false;
+ };
+
+ // 判断是否为空
+ const judgeIsEmpty = () => {
+ const text = editorRef.value.innerText;
+ isEmpty.value = text.length == 0 && !editorRef.value.querySelector("img") && !editorRef.value.querySelector("video");
+ };
+
+ // 处理选中文本变化的函数
+ const handleSelectionChange = () => {
+ const selection = window.getSelection();
+ // 确保有选中内容且选中区域在编辑器内
+ if (selection.rangeCount > 0) {
+ const range = selection.getRangeAt(0);
+ // 检查选中区域是否在编辑器内
+ const commonAncestor = range.commonAncestorContainer;
+ if (editorRef.value.contains(commonAncestor)) {
+ console.log("选中区域在编辑器内", range);
+ lastSelection = range;
+ }
+ }
+ };
+
+ const isPTitle = ref(false);
+
+ const paragraphTitle = () => {
+ editorRef.value.focus();
+ if (!lastSelection) return;
+ const selection = window.getSelection();
+ selection.removeAllRanges();
+ selection.addRange(lastSelection);
+
+ // 使用try-catch确保即使命令执行失败也能恢复滚动位置
+ try {
+ document.execCommand("formatBlock", false, isPTitle.value ? "P" : "H2");
+ } catch (error) {
+ console.error("应用段落格式失败:", error);
+ }
+
+ // 更新状态
+ setTimeout(() => updatePTitleStatus(), 100);
+ };
+
+ const updatePTitleStatus = () => {
+ if (lastSelection) {
+ let parentElement = lastSelection.commonAncestorContainer;
+ // 死循环,直到遇到终止条件
+ while (true) {
+ // 如果没有父元素了(到达文档根节点),退出循环返回false
+ if (!parentElement) {
+ isPTitle.value = false;
+ return;
+ }
+
+ // 遇到id为"editor"的元素,返回false
+ if (parentElement.id === "editor") {
+ isPTitle.value = false;
+ return;
+ }
+
+ // 遇到nodeName为"H2"的元素,返回true(注意nodeName是大写的)
+ if (parentElement.nodeName === "H2") {
+ isPTitle.value = true;
+ return;
+ }
+
+ // 继续向上查找父元素
+ parentElement = parentElement.parentElement;
+ }
+ }
+ };
+
+ const cutAnonymity = () => (info.value.anonymous = info.value.anonymous ? 0 : 1);
+
+ let emojiState = ref(false);
+
+ const optionEmoji = ref(["😀", "😁", "😆", "😅", "😂", "😉", "😍", "🥰", "😘", "🤥", "😪", "😵💫", "🤓", "🥺", "😋", "😜", "🤪", "😎", "🤩", "🥳", "😔", "🙁", "😭", "😡", "😳", "🤗", "🤔", "🤭", "🤫", "😯", "😵", "🙄", "🥴", "🤢", "🤑", "🤠", "👌", "✌️", "🤟", "🤘", "🤙", "👍", "👎", "✊", "👏", "🤝", "🙏", "💪", "❎️", "✳️", "✴️", "❇️", "#️⃣", "*️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟", "🆗", "🈶", "🉐", "🉑", "🌹", "🥀", "🌸", "🌺", "🌷", "🌲", "☘️", "🍀", "🍁", "🌙", "⭐", "🌍", "☀️", "⭐️", "🌟", "☁️", "🌈", "☂️", "❄️", "☃️", "☄️", "🔥", "💧", "🍎", "🍐", "🍊", "🍉", "🍓", "🍑", "🍔", "🍟", "🍕", "🥪", "🍜", "🍡", "🍨", "🍦", "🎂", "🍰", "🍭", "🍿", "🍩", "🧃", "🍹", "🍒", "🥝", "🥒", "🥦", "🥨", "🌭", "🥘", "🍱", "🍢", "🥮", "🍩", "🍪", "🧁", "🍵", "🍶", "🍻", "🥂", "🧋", "🎉", "🎁", "🧧", "🎃", "🎄", "🧨", "✨️", "🎈", "🎊", "🎋", "🎍", "🎀", "🎖️", "🏆️", "🏅", "💌", "📬", "🚗", "🚕", "🚲", "🛵", "🚀", "🚁", "⛵", "🚢", "🔮", "🧸", "🀄️"]);
+
+ const openEmoji = () => (emojiState.value = true);
+
+ const closeEmoji = () => (emojiState.value = false);
+
+ const selectEmoji = (emoji) => {
+ const selection = window.getSelection();
+ editorRef.value.focus();
+ if (lastSelection) {
+ selection.removeAllRanges();
+ selection.addRange(lastSelection);
+ }
+ document.execCommand("insertText", false, emoji);
+ closeEmoji();
+ judgeIsEmpty();
+ };
+
+ let format = ref("");
+ const submit = (status) => {
+ if (realname.value == 0 && userInfoWin.value?.uin > 0) {
+ openAttest();
+ return;
+ }
+
+ if (!isLogin.value) {
+ goLogin();
+ return;
+ }
+
+ const infoTarget = { ...info.value } || {};
+ let content = editorRef.value.innerHTML;
+
+ 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;
+ info.value["attachments"]["videos"] = videos;
+
+ content = formatContent(content);
+
+ const data = {
+ ...infoTarget,
+ content,
+ };
+
+ ajax("/v2/api/forum/postPublishTopic", {
+ info: data,
+ token: token.value,
+ status,
+ }).then((res) => {
+ const data = res.data;
+ if (res.code != 200) {
+ creationAlertBox("error", res.message);
+ return;
+ }
+
+ creationAlertBox("success", res.message || "操作成功");
+ const back = () => {
+ if (status == 1) redirectToExternalWebsite("/details/" + data.uniqid, "_self");
+ else redirectToExternalWebsite("/", "_self");
+ };
+
+ setTimeout(() => back(), 1500);
+ });
+ };
+
+ const formatContent = (html) => {
+ // 1. 替换图片标签
+ html = html.replace(/
]*data-aid="(\d+)"[^>]*>/gi, "[attachimg]$1[/attachimg]");
+
+ // 1.1 替换视频标签
+ html = html.replace(/