no message

This commit is contained in:
A1300399510
2025-12-12 00:45:06 +08:00
parent 5ea00d4790
commit 88fafe076b
2 changed files with 53 additions and 311 deletions

View File

@@ -3,7 +3,7 @@ const { createApp, ref, computed, onMounted, nextTick, onUnmounted } = Vue;
const editApp = createApp({
setup() {
const LANG = location.href.indexOf("lang=en") > 0 ? "en" : "zh-CN";
const { Editor, FileUploader } = window.textbus;
const title = ref("");
const saveStatus = ref("");
@@ -75,20 +75,16 @@ const editApp = createApp({
// 提交
const submit = (status) => {
const infoTarget = { ...info.value } || {};
// 获取 TextBus 内容
// TextBus 1.0: editor.getContents().content
// Fallback to generic HTML retrieval if needed
// 获取 HTML 内容
let content = "";
if (editor && typeof editor.getContents === 'function') {
const contents = editor.getContents();
content = (typeof contents === 'string') ? contents : (contents.content || "");
} else if (editor && typeof editor.getHTML === 'function') {
if (editor && typeof editor.getHTML === 'function') {
content = editor.getHTML();
} else if (editor && editor.output) {
content = editor.output.content; // Fallback if getHTML isn't direct
}
// 临时创建一个 div 来解析 content 中的图片和视频
const tempDiv = document.createElement('div');
// 创建临时 DOM 用于提取图片和视频
const tempDiv = document.createElement("div");
tempDiv.innerHTML = content;
const images = extractImages(tempDiv);
@@ -152,7 +148,6 @@ const editApp = createApp({
}
const infoTarget = data.info || {};
// if (infoTarget.content) infoTarget.content = `<div style=\"text-align: center;\"><strong>2026年度研究生课程火热招生中!</strong></div><div>\n<strong>2026</strong>年度研究生课程火热招生中!</div>`
info.value = infoTarget;
token.value = data.token;
@@ -230,250 +225,54 @@ const editApp = createApp({
});
};
const initEditor = () => {
if (!window.textbus) {
console.error("TextBus is not loaded");
return;
// 自定义上传适配器
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 || "",
// 配置工具栏:去除音频、源码、组件库、插入段落
// 根据 TextBus 文档,配置 providers.provide 可以在一定程度上控制工具栏,
// 但对于标准版 @textbus/editor最直接的方式是使用 toolbar 配置项(如果支持)
// 或者通过 CSS 隐藏不需要的按钮(作为兜底方案,因为 CDN 版本配置灵活性有限)
// 尝试配置工具栏项,仅保留需要的
// 注意TextBus 的工具栏配置键名可能需要根据具体版本调整
// 下面是一个常见的 TextBus 工具栏配置示例
toolbar: [
['undo', 'redo'],
['bold', 'italic', 'underline', 'strikeThrough'],
['heading'],
['ol', 'ul'],
['fontSize', 'fontFamily', 'color', 'backgroundColor'],
['image'], // 先放图片
['video'], // 再放视频
['link', 'unlink'],
['textAlign', 'textIndent'],
['table'],
// ['clean'], // 去掉清除格式
// ['source'], // 去掉源码
// ['audio'], // 去掉音频 (TextBus 默认可能有也可能没有,这里显式不加)
// ['block'], // 去掉插入段落/组件库
],
uploader: function (type) {
return new Promise((resolve, reject) => {
// 1. 数量限制检查
let content = "";
if (editor && typeof editor.getContents === 'function') {
const contents = editor.getContents();
content = (typeof contents === 'string') ? contents : (contents.content || "");
} else if (editor && typeof editor.getHTML === 'function') {
content = editor.getHTML();
}
const tempDiv = document.createElement('div');
tempDiv.innerHTML = content;
if (type === 'image') {
const currentImages = tempDiv.querySelectorAll('img').length;
if (currentImages >= imageLength) {
creationAlertBox("error", `最多只能上传 ${imageLength} 张图片`);
return;
}
} else if (type === 'video') {
const currentVideos = tempDiv.querySelectorAll('video').length;
if (currentVideos >= videoLength) {
creationAlertBox("error", `最多只能上传 ${videoLength} 个视频`);
return;
}
}
// 视频上传逻辑
if (type === 'video') {
const fileInput = document.createElement("input");
fileInput.setAttribute("type", "file");
fileInput.setAttribute("accept", "video/*");
fileInput.style.cssText = "position: absolute; left: -9999px; top: -9999px; opacity: 0";
document.body.appendChild(fileInput);
fileInput.onchange = async (e) => {
const file = e.target.files[0];
if (!file) return;
// 大小限制检查 (视频通常允许更大,这里暂时统一限制,如需单独限制请调整)
const maxSize = 1 * 1024 * 1024; // 1M
if (file.size > maxSize) {
creationAlertBox("error", "文件大小不能超过 1MB");
document.body.removeChild(fileInput);
return;
}
try {
// 1. 上传视频文件
const res = await uploading(file, file.name, "video");
const videoUrl = res.url + (res.aid ? `?aid=${res.aid}` : '');
// 2. 尝试获取封面 (可选)
// TextBus 插入视频通常只需要 URL或者 { src, poster }
// 由于 uploading 返回的是 URL我们直接返回
resolve(videoUrl);
} catch (err) {
console.error(err);
} finally {
document.body.removeChild(fileInput);
}
};
fileInput.click();
return;
}
const fileInput = document.createElement("input");
fileInput.setAttribute("type", "file");
fileInput.setAttribute("accept", "image/png, image/jpeg, image/jpg, image/gif");
fileInput.style.cssText = "position: absolute; left: -9999px; top: -9999px; opacity: 0";
document.body.appendChild(fileInput);
fileInput.onchange = async (e) => {
const file = e.target.files[0];
if (!file) return;
// 2. 大小限制检查
const maxSize = 1 * 1024 * 1024; // 1M
if (file.size > maxSize) {
creationAlertBox("error", "文件大小不能超过 1MB");
document.body.removeChild(fileInput);
return;
}
try {
const res = await uploading(file, file.name, "image");
// TextBus 期望返回 URL
resolve(res.url + (res.aid ? `?aid=${res.aid}` : ''));
} catch (err) {
console.error(err);
// reject(err); // TextBus 可能会捕获错误并提示
} finally {
document.body.removeChild(fileInput);
}
};
fileInput.click();
});
}
providers: [{
provide: FileUploader,
useFactory: () => new CustomUploader()
}],
// 默认情况下xnote 使用悬浮/气泡菜单
// 我们不配置 toolbar 容器,让其使用默认行为
};
try {
// 查找 createEditor 函数
let createEditor = null;
if (window.textbus && typeof window.textbus.createEditor === 'function') {
createEditor = window.textbus.createEditor;
} else if (window.textbus && window.textbus.editor && typeof window.textbus.editor.createEditor === 'function') {
createEditor = window.textbus.editor.createEditor;
} else if (window.textbus && window.textbus.default && typeof window.textbus.default.createEditor === 'function') {
createEditor = window.textbus.default.createEditor;
}
editor = new Editor(editorConfig);
editor.mount(document.getElementById("editor-text-area"));
if (!createEditor) {
console.error("TextBus createEditor not found", window.textbus);
creationAlertBox("error", "TextBus 初始化失败: createEditor 未找到");
return;
}
// 初始化编辑器
// 根据源码推测createEditor(config) 返回 editor 实例,然后调用 mount(selector)
editor = createEditor(editorConfig);
if (editor && typeof editor.mount === 'function') {
editor.mount("#editor-text-area");
// 手动隐藏不需要的工具栏按钮 (JS 兜底方案)
setTimeout(() => {
const hideButtons = () => {
const buttons = document.querySelectorAll('.textbus-toolbar-btn, .textbus-btn, button');
const targets = ['音频', '插入音频', 'Audio', '源代码', '查看源码', 'Source', '组件库', 'Components', '段落', '插入段落', 'Paragraph', '清除格式', 'Clean', '格式化', 'Format', '格式刷', 'Brush'];
let imageBtn = null;
let videoBtn = null;
buttons.forEach(btn => {
const title = btn.getAttribute('title') || btn.getAttribute('aria-label') || '';
if (targets.some(t => title.includes(t))) {
btn.style.display = 'none';
}
// 备用:检查图标 class
if (btn.querySelector('.textbus-icon-music') ||
btn.querySelector('.textbus-icon-code') ||
btn.querySelector('.textbus-icon-components') ||
btn.querySelector('.textbus-icon-paragraph') ||
btn.querySelector('.textbus-icon-clean') // 清除格式
) {
btn.style.display = 'none';
}
// 单独处理格式刷图标(通常是刷子形状)
if (btn.querySelector('.textbus-icon-brush')) {
btn.style.display = 'none';
}
// 查找图片和视频按钮
if (title.includes('图片') || title.includes('Image') || btn.querySelector('.textbus-icon-image')) {
imageBtn = btn;
}
if (title.includes('视频') || title.includes('Video') || btn.querySelector('.textbus-icon-video')) {
videoBtn = btn;
}
});
// 强制调整顺序:视频放在图片后面
if (imageBtn && videoBtn && imageBtn.parentNode === videoBtn.parentNode) {
// 如果视频按钮不在图片按钮的紧邻后面,则移动
if (imageBtn.nextElementSibling !== videoBtn) {
imageBtn.parentNode.insertBefore(videoBtn, imageBtn.nextElementSibling);
}
}
};
hideButtons();
// 监听 DOM 变化以防重新渲染
const observer = new MutationObserver(hideButtons);
const toolbar = document.querySelector('.textbus-toolbar') || document.querySelector('.textbus-ui-top');
if (toolbar) {
observer.observe(toolbar, { childList: true, subtree: true });
}
}, 100);
} else {
// 兼容旧版本或直接传入 selector 的情况
// 如果 createEditor 返回的不是带有 mount 的对象,可能是旧版本
console.warn("Editor instance does not have mount method, assuming auto-mount or different API");
}
if (editor && editor.onChange) {
// 监听内容变化
if (editor.onChange) {
editor.onChange.subscribe(() => {
saveStatus.value = "有未保存的更改";
});
}
} catch (error) {
console.log("TextBus init error", error);
creationAlertBox("error", "TextBus 初始化异常: " + error.message);
console.log("error", error);
}
// 点击空白处 focus 编辑器 (TextBus 可能不需要这个,但保留逻辑以防万一)
// document.getElementById("editor-text-area").addEventListener("click", (e) => {
// if (e.target.id === "editor-text-area") {
// // editor.focus();
// }
// });
// 点击空白处 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");
@@ -510,10 +309,9 @@ const editApp = createApp({
});
onUnmounted(() => {
if (editor && typeof editor.destroy === 'function') {
editor.destroy();
if (editor == null) return;
if (editor.destroy) editor.destroy();
editor = null;
}
});
return {
@@ -527,4 +325,3 @@ const editApp = createApp({
},
});
editApp.mount("#edit");

View File

@@ -1,3 +1,4 @@
<!DOCTYPE html>
<html lang="en">
@@ -8,55 +9,13 @@
<title>发布主题</title>
<link href="https://framework.x-php.com/gter/forum/css/normalize.min.css" rel="stylesheet">
<link href="https://framework.x-php.com/gter/forum/css/editorStyle.css" rel="stylesheet">
<link rel="stylesheet" href="/css/textbus.min.css">
<script src="https://framework.x-php.com/gter/forum/js/vue.global.js"></script>
<script src="/js/textbus.min.js"></script>
<!-- <link rel="stylesheet" href="https://unpkg.com/@textbus/editor/bundles/textbus.min.css"> -->
<!-- <script src="https://unpkg.com/@textbus/editor/bundles/textbus.min.js"></script> -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css">
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.js"></script>
<!-- <link rel="stylesheet" href="/css/textbus.min.css"> -->
<!-- <script src="/js/textbus.min.js"></script> -->
<style>
/* 隐藏 TextBus 工具栏按钮 */
/* 由于我们无法确定具体的 class 且 JS 配置可能未生效,我们尝试使用更通用的属性选择器 */
/* 音频 */
.textbus-toolbar-btn[title="音频"],
.textbus-toolbar-btn[title="插入音频"],
.textbus-toolbar-btn:has(.textbus-icon-music) {
display: none !important;
}
/* 源代码 */
.textbus-toolbar-btn[title="源代码"],
.textbus-toolbar-btn[title="查看源码"],
.textbus-toolbar-btn:has(.textbus-icon-code) {
display: none !important;
}
/* 组件库 */
.textbus-toolbar-btn[title="组件库"],
.textbus-toolbar-btn:has(.textbus-icon-components) {
display: none !important;
}
/* 插入段落/其他 */
.textbus-toolbar-btn[title="插入段落"],
.textbus-toolbar-btn:has(.textbus-icon-paragraph) {
display: none !important;
}
/* 清除格式 */
.textbus-toolbar-btn[title="清除格式"],
.textbus-toolbar-btn:has(.textbus-icon-clean) {
display: none !important;
}
/* 格式刷 */
.textbus-toolbar-btn[title="格式刷"],
.textbus-toolbar-btn:has(.textbus-icon-brush) {
display: none !important;
}
html,
body {
background-color: #fff;
@@ -94,7 +53,7 @@
width: 100vh;
margin: 20px auto 20px auto;
background-color: #fff;
/* padding: 10px; */
padding: 10px;
border: 1px solid #e8e8e8;
box-shadow: 0 2px 10px rgb(0 0 0 / 12%);
}
@@ -110,30 +69,18 @@
outline: none;
width: 100%;
line-height: 1;
padding: 0 20px;
box-sizing: border-box;
}
.textbus-toolbar-wrapper {
border-radius: 0;
border-left: 0;
border-right: 0;
}
#editor-text-area {
/* margin-top: 20px; */
margin-top: 20px;
/* height: 500px; */
/* max-height: 80vh; */
height: calc(100vh - 310px);
height: calc(100vh - 370px);
font-size: 18px;
line-height: 1.5;
color: rgb(51, 51, 51);
}
.textbus-container {
height: 100%;
}
.bottom-bar {
@@ -273,24 +220,22 @@
<div class="bottom-bar action-buttons flexacenter">
<div class="left-section flexacenter" @click="cutAnonymity">
<img v-if="info.anonymous == 1" class="icon-pitch"
src="https://framework.x-php.com/gter/forum/img/tick-box.svg" />
<img v-if="info.anonymous == 1" class="icon-pitch" src="https://framework.x-php.com/gter/forum/img/tick-box.svg" />
<div v-else class="icon"></div>
<div class="text">匿名发布</div>
</div>
<div class="right-section flexcenter">
<div class="draft-btn flexcenter" @click="submit(0)"><img class="icon"
src="https://framework.x-php.com/gter/forum/img/draft-icon.png?v=iem44eqj4HfC"> 存草稿 </div>
<div class="draft-btn flexcenter" @click="submit(0)"><img class="icon" src="https://framework.x-php.com/gter/forum/img/draft-icon.png?v=iem44eqj4HfC"> 存草稿 </div>
<div id="save-btn" class="publish-btn flexcenter" @click="submit(1)">保存</div>
</div>
</div>
</div>
<!-- <script type="module" src="https://framework.x-php.com/gter/forum/js/editor.js"></script> -->
<script type="module" src="https://framework.x-php.com/gter/forum/js/editor.js"></script>
<script src="https://framework.x-php.com/gter/forum/js/axios.min.js"></script>
<script src="https://framework.x-php.com/gter/forum/js/public.js"></script>
<script type="module" src="/js/publish_admin.js"></script>
<script type="module" src="https://framework.x-php.com/gter/forum/js/publish_admin.js"></script>
</body>
</html>