Files
PC-Light-Forum/js/publish_admin.js
A1300399510 88fafe076b no message
2025-12-12 00:45:06 +08:00

328 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 简单版本的论坛编辑器,确保图片插入功能正常
const { createApp, ref, computed, onMounted, nextTick, onUnmounted } = Vue;
const editApp = createApp({
setup() {
const { Editor, FileUploader } = window.textbus;
const title = ref("");
const saveStatus = ref("");
const uniqid = ref("");
const info = ref({});
const token = ref("");
let editor = null;
const draftKey = "publish_admin_draft";
let uConfigData = {};
let imageLength = 10;
let videoLength = 5;
const formatTime = (d) => {
const pad = (n) => String(n).padStart(2, "0");
return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
};
const extractImages = (dom) => {
const images = [];
const imgElements = dom.querySelectorAll("img");
imgElements.forEach((imgEl) => {
let url = imgEl.getAttribute("src")?.trim() || "";
const urlObj = new URL(url);
const aid = urlObj.searchParams.get("aid");
const queryIndex = url.indexOf("?");
const cleanUrl = queryIndex !== -1 ? url.substring(0, queryIndex) : url;
if (Number(aid)) {
images.push({
url: cleanUrl,
aid: Number(aid),
});
}
});
return images;
};
const extractVideos = (dom) => {
const videoElements = dom.querySelectorAll("video");
const result = [];
videoElements.forEach((videoEl) => {
const posterurl = videoEl.getAttribute("poster")?.trim() || ""; // 视频地址
const urlObj = new URL(posterurl);
const posterid = urlObj.searchParams.get("aid");
const sourceEl = videoEl.querySelector("source");
const url = sourceEl.getAttribute("src") || null;
const obj = new URL(url);
const aid = obj.searchParams.get("aid");
const queryIndex = url.indexOf("?");
const cleanUrl = queryIndex !== -1 ? url.substring(0, queryIndex) : url;
const queryIndex2 = posterurl.indexOf("?");
const cleanPosterurl = queryIndex2 !== -1 ? posterurl.substring(0, queryIndex2) : posterurl;
result.push({
aid: Number(aid),
posterid: Number(posterid),
url: cleanUrl,
posterurl: cleanPosterurl,
});
});
return result;
};
const cutAnonymity = () => (info.value.anonymous = info.value.anonymous ? 0 : 1);
// 提交
const submit = (status) => {
const infoTarget = { ...info.value } || {};
// 获取 HTML 内容
let content = "";
if (editor && typeof editor.getHTML === 'function') {
content = editor.getHTML();
} else if (editor && editor.output) {
content = editor.output.content; // Fallback if getHTML isn't direct
}
// 创建临时 DOM 用于提取图片和视频
const tempDiv = document.createElement("div");
tempDiv.innerHTML = content;
const images = extractImages(tempDiv);
const videos = extractVideos(tempDiv);
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;
infoTarget.title = title.value;
const data = {
...infoTarget,
content,
};
ajax("/v2/api/forum/postPublishTopic", {
info: data,
token: token.value,
status,
htmledit: 1,
}).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 cUpload = () => {
ajaxGet(`/v2/api/config/upload?type=topic`).then((res) => {
const data = res.data;
uConfigData = data;
});
};
const init = () => {
ajax("/v2/api/forum/postPublishInit", {
uniqid: uniqid.value,
htmledit: 1,
})
.then((res) => {
const data = res.data;
if (res.code != 200) {
creationAlertBox("error", res.message || "操作失败");
return;
}
const infoTarget = data.info || {};
info.value = infoTarget;
token.value = data.token;
if (infoTarget.title) title.value = infoTarget.title;
nextTick(() => {
initEditor();
});
})
.catch((err) => {
console.log("err", err);
});
};
// 上传图片/视频 获取url
const uploading = (file, name, type) => {
return new Promise((resolve, reject) => {
const upload = () => {
let config = uConfigData;
const formData = new FormData();
formData.append(config.requestName, file); // 文件数据
formData.append("name", name); // 文件名
formData.append("type", type); // 文件名
if (config.params && config.params.data) {
formData.append("data", config.params.data);
}
const xhr = new XMLHttpRequest();
xhr.open("POST", config.url, true);
xhr.withCredentials = true; // 允许携带 Cookie
// 监听上传进度
xhr.upload.onprogress = function (event) {
if (event.lengthComputable) {
// const percentComplete = (event.loaded / event.total) * 100;
// progress.value = Math.round(percentComplete);
}
};
xhr.onload = function () {
if (xhr.status === 200) {
const res = JSON.parse(xhr.responseText);
if (res.code == 200) {
const data = res.data;
resolve(data);
} else {
creationAlertBox("error", res.message || "上传失败");
reject(res);
}
} else {
creationAlertBox("error", "上传失败");
reject(new Error("Upload failed"));
}
};
xhr.onerror = function () {
creationAlertBox("error", "网络错误,上传失败");
reject(new Error("Network error"));
};
xhr.send(formData);
};
if (!uConfigData || !uConfigData.url) {
ajaxGet(`/v2/api/config/upload?type=topic`).then((res) => {
const data = res.data;
uConfigData = data;
upload();
});
} else {
upload();
}
});
};
// 自定义上传适配器
class CustomUploader extends FileUploader {
uploadFile(type, file) {
// type 可能是 'image' 或 'video' 等,取决于调用方
// uploading 函数接受 (file, name, type)
return uploading(file, file.name, type).then(res => {
// 构造带 aid 的 url
return `${res.url}?aid=${res.aid}`;
});
}
}
const initEditor = () => {
const editorConfig = {
content: info.value?.content || "",
providers: [{
provide: FileUploader,
useFactory: () => new CustomUploader()
}],
// 默认情况下xnote 使用悬浮/气泡菜单
// 我们不配置 toolbar 容器,让其使用默认行为
};
try {
editor = new Editor(editorConfig);
editor.mount(document.getElementById("editor-text-area"));
// 监听内容变化
if (editor.onChange) {
editor.onChange.subscribe(() => {
saveStatus.value = "有未保存的更改";
});
}
} catch (error) {
console.log("error", error);
}
// 点击空白处 focus 编辑器
document.getElementById("editor-text-area").addEventListener("click", (e) => {
// 如果点击的是容器本身(空白处),则聚焦
if (e.target.id === "editor-text-area") {
// editor.focus() 如果存在
// Textbus editor 实例通常不需要手动 focus除非是 command
}
});
};
// 提取视频第一帧作为封面
const getVideoFirstFrame = (file) => {
return new Promise((resolve) => {
const video = document.createElement("video");
video.src = URL.createObjectURL(file);
video.currentTime = 1; // 截取第 1 秒
video.onloadeddata = () => {
video.currentTime = 1;
};
video.onseeked = () => {
const canvas = document.createElement("canvas");
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.getContext("2d").drawImage(video, 0, 0, canvas.width, canvas.height);
canvas.toBlob((blob) => {
const coverFile = new File([blob], "cover.jpg", { type: "image/jpeg" });
resolve(coverFile);
}, "image/jpeg");
};
});
};
const handleTitleInput = () => {
saveStatus.value = "有未保存的更改";
};
onMounted(() => {
const params = getUrlParams();
uniqid.value = params.uniqid || "";
cUpload();
nextTick(() => {
init();
});
});
onUnmounted(() => {
if (editor == null) return;
if (editor.destroy) editor.destroy();
editor = null;
});
return {
title,
saveStatus,
submit,
handleTitleInput,
cutAnonymity,
info,
};
},
});
editApp.mount("#edit");