no message
This commit is contained in:
@@ -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");
|
||||
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user