fix: 修复编辑器代码块换行滚动跳动 + 移除视频和网络图片功能
- 修复 wangeditor 选区同步 scrollIntoView 导致代码块换行大幅跳动的问题 - 移除 getBoundingClientRect 替换 hack,改用元素自身真实位置计算滚动 - 将 block 参数从 end 改为 nearest,behavior 从 smooth 改为 instant - 修复 #editor-container 与 .w-e-scroll 双重滚动容器冲突 - #editor-container 和 .w-e-text-container 设为 overflow:hidden - .w-e-scroll 作为唯一滚动容器设 overflow-y:auto - updateCodeLabels 改为节流执行并保存/恢复滚动位置 - 新增 ensureCursorVisible 替代内置滚动保证光标可见 - 滚动同步函数统一引用 .w-e-scroll 容器 - 工具栏排除视频相关按钮和网络图片功能 - .gitignore 添加 .codebuddy
This commit is contained in:
@ -15,12 +15,18 @@
|
||||
}
|
||||
#editor-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
}
|
||||
/* .w-e-scroll 是 wangEditor 内部唯一的滚动容器 */
|
||||
.editor-pane .w-e-text-container {
|
||||
height: 100% !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
.editor-pane .w-e-scroll {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.editor-pane .w-e-text-container [data-slate-editor] {
|
||||
min-height: 100%;
|
||||
|
||||
@ -273,6 +273,7 @@
|
||||
#editor-container {
|
||||
background: #fff;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* wangEditor 内部文本容器撑满 */
|
||||
|
||||
@ -61,9 +61,12 @@ function initEditor() {
|
||||
const editorConfig = {
|
||||
placeholder: '在此输入正文...',
|
||||
onChange() {
|
||||
// 同步更新代码块语言标签(立即生效)
|
||||
// 延迟更新代码块语言标签(同步操作 DOM 会干扰 wangEditor 内部同步,导致滚动跳动)
|
||||
updateCodeLabels();
|
||||
|
||||
// 替代 wangEditor 内置 scrollIntoView(已禁用),仅在光标不可见时微调滚动
|
||||
ensureCursorVisible();
|
||||
|
||||
clearTimeout(syncTimer);
|
||||
syncTimer = setTimeout(() => {
|
||||
updateStats();
|
||||
@ -128,7 +131,14 @@ function initEditor() {
|
||||
createToolbar({
|
||||
editor,
|
||||
selector: '#toolbar-container',
|
||||
config: {},
|
||||
config: {
|
||||
excludeKeys: [
|
||||
// 移除视频相关按钮:不允许视频上传,不在前端提供视频按钮
|
||||
'group-video', 'insertVideo', 'uploadVideo',
|
||||
// 移除"网络图片"功能,仅保留上传图片
|
||||
'insertImage',
|
||||
],
|
||||
},
|
||||
mode: 'default',
|
||||
});
|
||||
|
||||
@ -144,16 +154,48 @@ function initEditor() {
|
||||
saveDraft();
|
||||
});
|
||||
|
||||
// 滚动同步
|
||||
// 滚动同步(.w-e-scroll 是编辑区唯一滚动容器)
|
||||
previewCont.addEventListener('scroll', syncScrollToEditor);
|
||||
setTimeout(() => {
|
||||
const scroller = document.getElementById('editor-container') || editorPane;
|
||||
scroller.addEventListener('scroll', syncScrollToPreview);
|
||||
const scroller = (document.getElementById('editor-container') || editorPane).querySelector('.w-e-scroll');
|
||||
if (scroller) scroller.addEventListener('scroll', syncScrollToPreview);
|
||||
}, 500);
|
||||
|
||||
console.log('%c📝 MetaLab Editor (wangEditor) ready', 'color:#6366f1;font-weight:bold');
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
确保光标可见 — 替代 wangEditor 内置 scrollIntoView
|
||||
wangEditor 默认使用 block:"end" 会在代码块换行时导致大幅滚动,
|
||||
已在 wangeditor.js 中禁用该调用,此处用最小偏移量保证光标可见
|
||||
================================================================ */
|
||||
function ensureCursorVisible() {
|
||||
if (!editor) return;
|
||||
requestAnimationFrame(() => {
|
||||
try {
|
||||
const sel = window.getSelection();
|
||||
if (!sel || !sel.rangeCount) return;
|
||||
const range = sel.getRangeAt(0);
|
||||
|
||||
// .w-e-scroll 是编辑区滚动容器
|
||||
const scroller = document.querySelector('#editor-container .w-e-scroll');
|
||||
if (!scroller) return;
|
||||
|
||||
const cursorRect = range.getBoundingClientRect();
|
||||
const scrollRect = scroller.getBoundingClientRect();
|
||||
|
||||
// 光标低于可见区 → 向下滚到光标可见
|
||||
if (cursorRect.bottom > scrollRect.bottom) {
|
||||
scroller.scrollTop += cursorRect.bottom - scrollRect.bottom + 8;
|
||||
}
|
||||
// 光标高于可见区 → 向上滚到光标可见
|
||||
else if (cursorRect.top < scrollRect.top) {
|
||||
scroller.scrollTop -= scrollRect.top - cursorRect.top + 8;
|
||||
}
|
||||
} catch (_) { /* 忽略 */ }
|
||||
});
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
字数统计
|
||||
================================================================ */
|
||||
@ -168,9 +210,20 @@ function getEditorHtml() {
|
||||
/* ================================================================
|
||||
代码块语言标签 — 从 <code class="language-xxx"> 提取并注入标签
|
||||
================================================================ */
|
||||
// 代码块语言标签更新节流定时器
|
||||
let codeLabelTimer = null;
|
||||
|
||||
function updateCodeLabels() {
|
||||
if (!editor) return;
|
||||
|
||||
// 节流:避免 onChange 中频繁调用导致滚动跳动
|
||||
clearTimeout(codeLabelTimer);
|
||||
codeLabelTimer = setTimeout(_updateCodeLabelsImpl, 300);
|
||||
}
|
||||
|
||||
function _updateCodeLabelsImpl() {
|
||||
if (!editor) return;
|
||||
|
||||
// wangEditor 编辑区 <code> 不带 class="language-xxx",
|
||||
// 但 getHtml() 输出带 class="language-xxx"。
|
||||
// 用 DOMParser 解析 HTML 获取语言,再注入到编辑区对应 pre。
|
||||
@ -184,6 +237,11 @@ function updateCodeLabels() {
|
||||
if (!editorEl) return;
|
||||
const domPres = editorEl.querySelectorAll('pre');
|
||||
|
||||
// 保存当前滚动位置,防止 DOM 操作引起跳动
|
||||
// .w-e-scroll 是 wangEditor 内部的滚动容器
|
||||
const scroller = editorEl.querySelector('.w-e-scroll');
|
||||
const savedScrollTop = scroller ? scroller.scrollTop : 0;
|
||||
|
||||
htmlPres.forEach((htmlPre, idx) => {
|
||||
const domPre = domPres[idx];
|
||||
if (!domPre || domPre.querySelector('.code-lang-label')) return;
|
||||
@ -200,6 +258,11 @@ function updateCodeLabels() {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 恢复滚动位置
|
||||
if (scroller && scroller.scrollTop !== savedScrollTop) {
|
||||
scroller.scrollTop = savedScrollTop;
|
||||
}
|
||||
} catch (_) { /* 忽略 */ }
|
||||
}
|
||||
|
||||
@ -256,10 +319,16 @@ function togglePreview() {
|
||||
================================================================ */
|
||||
let scrollSyncing = false;
|
||||
|
||||
function getEditorScroller() {
|
||||
const el = document.getElementById('editor-container') || editorPane;
|
||||
return el ? (el.querySelector('.w-e-scroll') || el) : null;
|
||||
}
|
||||
|
||||
function syncScrollToPreview() {
|
||||
if (!previewVisible || scrollSyncing) return;
|
||||
scrollSyncing = true;
|
||||
const scroller = document.getElementById('editor-container') || editorPane;
|
||||
const scroller = getEditorScroller();
|
||||
if (!scroller) { scrollSyncing = false; return; }
|
||||
const ratio = scroller.scrollTop / Math.max(1, scroller.scrollHeight - scroller.clientHeight);
|
||||
previewCont.scrollTop = ratio * Math.max(1, previewCont.scrollHeight - previewCont.clientHeight);
|
||||
requestAnimationFrame(() => { scrollSyncing = false; });
|
||||
@ -268,8 +337,9 @@ function syncScrollToPreview() {
|
||||
function syncScrollToEditor() {
|
||||
if (!previewVisible || scrollSyncing) return;
|
||||
scrollSyncing = true;
|
||||
const scroller = getEditorScroller();
|
||||
if (!scroller) { scrollSyncing = false; return; }
|
||||
const ratio = previewCont.scrollTop / Math.max(1, previewCont.scrollHeight - previewCont.clientHeight);
|
||||
const scroller = document.getElementById('editor-container') || editorPane;
|
||||
scroller.scrollTop = ratio * Math.max(1, scroller.scrollHeight - scroller.clientHeight);
|
||||
requestAnimationFrame(() => { scrollSyncing = false; });
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user