


























🕒 阅读时间:4 分钟 📝 字数:1207 👀 阅读量: Loading...
在前文中,已经实现了非常简陋的功能,下一步准备粗略地写一个md编辑,然后再通过fetch请求将数据发送到后端。
Vditor是一个非常好用的前端Markdown编辑器,它支持实时预览、代码高亮、表格等功能,还内置katex,mermaid等功能,非常适合用来编辑博客文章。于是我使用这个来粗略实现一下编辑器
---
const { options = {},post = {} } = Astro.props;
export const prerender = false;
---
<div class="editor-container">
<link rel="stylesheet" href="https://unpkg.com/vditor/dist/index.css" />
<div class="save-status" id="save-status">就绪</div>
<div id="Editor"></div>
<script is:inline define:vars={{options,post}}>
window.editorOptions = options || {};
window.post = post || {};
</script>
<script>
import Vditor from 'vditor'
// Declare editorOptions on window
declare global {
interface Window {
editorOptions: any,
post: any;
}
}
<!--省略-->
let vditor = new Vditor('Editor', {
...window.editorOptions,//自定义的配置
after: () => {
//console.log('Vditor 初始化完成');
//afte钩子,实现初始化内容
if (window.editorOptions.value) {
vditor.setValue(window.editorOptions.value);
}
updateSaveStatus('就绪', 'ready'); // 监听自定义 HMR 事件
if (import.meta.hot) {
import.meta.hot.on('locastrol-content-update', (data) => {
//console.log('[Locastrol] 检测到内容文件更新,但已阻止页面重载:', data);
updateSaveStatus('内容已同步', 'synced');
setTimeout(() => {
updateSaveStatus('就绪', 'ready');
}, 2000);
})
}
},
input: ()=>{
// 清除之前的自动保存定时器
if (autoSaveTimeout) {
clearTimeout(autoSaveTimeout);
}
if (!isAutoSaving) {
updateSaveStatus('正在编辑...', 'editing');
}
// 设置新的自动保存定时器(防抖)
autoSaveTimeout = setTimeout(()=>{
autoSave(vditor.getValue());
}, 1500);
}
});
<!--省略-->
:::warning
特别注意的是,定义<script define:vars><script>会导致import Vditor from 'vditor'失效
因为此时script变成了module类型,阻止了Vditor的正常加载
:::
:::tip
下面是一个技巧,来变相传参 ,这样参数才能正确写入到Vditor中
<script is:inline define:vars={{options,post}}>
window.editorOptions = options || {};
window.post = post || {};
</script>
<script>
import Vditor from 'vditor'
// Declare editorOptions on window
declare global {
interface Window {
editorOptions: any,
post: any;
}
}
...
</script>
:::
:::danger 但同时,vars会裸暴露在前端
所以不要一股脑直接传递一个结构体对象,而是具体精细的传递需要的参数
特别注意不要把私密信息从astro构建时传递到前端运行时
:::
由于我后台使用了astro:content来加载内容,所以每次内容更新都会导致页面重载,这就导致了后台md编辑器无法正常工作
所以需要阻止页面重载,理论上可以通过import.meta.hot来监听astro:content的更新事件,然后阻止页面重载
但是我问了Copilot,并没有很好地实现,反复折磨下,终于给出了相对可行的方案
在开发Astro博客后台时,遇到的核心问题是:前台页面需要正常刷新以显示最新内容,而后台编辑页面绝对不能刷新,否则会丢失用户正在编辑的内容。
这个问题的根源在于Astro的HMR(Hot Module Replacement)机制:
src/content/posts/目录下的markdown文件发生变化时astro:content模块的更新理论上这么说,但是我客户端基本没实现,问了ai也没正确实现 :(
if (import.meta.hot) {
import.meta.hot.on("locastrol-content-update", (data) => {
// 拦截自定义更新事件,阻止页面重载
updateSaveStatus("内容已同步", "synced");
});
}
工作原理:
import.meta.hot是Vite提供的HMR APIlocastrol-content-update来接收内容更新通知这个是容易实现的,但是只是实现了一半,因为astro:content会导致所有.astro页面刷新
前台页面(正常刷新):
astro:content导入后台页面(不能阻止刷新):
astro:content,也没法阻止刷新采用WebSocket消息拦截 + HMR更新阻止。
1. 客户端状态追踪系统
// 客户端状态管理
const clientStates = new Map<string, ClientState>();
interface ClientState {
isEditorPage: boolean;
url: string;
lastActivity: number;
}
x-client-id请求头唯一标识每个客户端2. 心跳机制
// 心跳API处理
if (url === "/api/locastrol/heartbeat" && req.method === "POST") {
clientStates.set(clientId, {
isEditorPage: Boolean(isEditorPage),
url: clientUrl,
lastActivity: Date.now(),
});
}
3. WebSocket消息拦截
// 完全劫持WebSocket的send方法
const originalSend = server.ws.send;
server.ws.send = function (payload: any, client?: any) {
// 检查是否有编辑器页面活跃
let hasActiveEditor = false;
for (const [clientId, state] of clientStates) {
if (state.isEditorPage && Date.now() - state.lastActivity < 30000) {
hasActiveEditor = true;
break;
}
}
// 如果有编辑器页面活跃,完全阻止所有WebSocket刷新消息
if (hasActiveEditor && typeof payload === "object" && payload !== null) {
if (
payload.type === "full-reload" ||
payload.type === "update" ||
payload.type === "connected" ||
payload.type === "error"
) {
return; // 完全阻止消息发送
}
}
return originalSend.call(this, payload, client);
};
4. HMR更新拦截
// HMR更新拦截
handleHotUpdate({ file, server }) {
// 检查是否有编辑器页面活跃
let hasActiveEditor = false;
for (const [clientId, state] of clientStates) {
if (state.isEditorPage && (Date.now() - state.lastActivity < 30000)) {
hasActiveEditor = true;
break;
}
}
if (hasActiveEditor) {
return []; // 阻止HMR更新
} else {
return undefined; // 允许正常更新
}
}
实际工作流程:
1. 用户打开编辑器页面
↓
2. 客户端开始发送心跳包(每30秒)
↓
3. 服务器标记该客户端为"编辑器活跃"
↓
4. 文件系统发生变化
↓
5. Vite尝试触发HMR更新
↓
6. locastrol检测到活跃编辑器
↓
7. 同时拦截HMR事件和WebSocket消息
↓
8. 编辑器页面保持不刷新,前台页面正常刷新
这种实现方案的核心优势在于从根源上阻止了所有可能导致页面刷新的WebSocket消息和HMR事件,而不是依赖客户端的事件监听,确保了编辑器页面的绝对稳定性。
:::tip 但实际上,前台页面倒是能够正常刷新,但是热重载失效了,算了,勉强先这样吧 :::
下个月又要开始课设了,估计没时间写这个后台插件了,只能暂时搁置咯
已经有好几个搁置了,都推到暑假了…没人催的话,我大概会忘记吧…😀💧
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。