fix: 浮动工具栏开关按钮无效 — 新增 toggleFloatingToolbar/isFloatingToolbar API
CI / test (18.x) (push) Canceled after 0s
CI / test (20.x) (push) Canceled after 0s
CI / test (22.x) (push) Canceled after 0s
CI / test (24.x) (push) Canceled after 0s

This commit is contained in:
2026-07-25 15:06:35 +08:00
parent 8f578599cf
commit 19f9ba91a8
2 changed files with 41 additions and 25 deletions
+2 -9
View File
@@ -229,15 +229,8 @@ bar.addEventListener('click', function(e){
else if (cmd === 'outline') { toggleOutline(btn); }
else if (cmd === 'wrap') { editor.toggleWordWrap(); btn.classList.toggle('on', !editor.isWordWrap()); }
else if (cmd === 'float') {
var hasFloat = editor._floatingToolbar && editor._floatingToolbar.parentNode;
if (hasFloat) {
if (editor._floatingToolbar.parentNode) editor._floatingToolbar.parentNode.removeChild(editor._floatingToolbar);
editor._floatingToolbar = null;
} else {
editor.config.floatingToolbar = true;
editor._initFloatingToolbar();
}
btn.classList.toggle('on', !!editor._floatingToolbar);
editor.toggleFloatingToolbar();
btn.classList.toggle('on', editor.isFloatingToolbar());
}
else if (cmd === 'toast') {
var types = ['success','error','warning','info'];
+39 -16
View File
@@ -84,6 +84,7 @@ export class MarkdownEditor {
_i18nCtx!: InstanceI18n;
_floatingToolbar!: HTMLElement | null;
_selectionTimer!: ReturnType<typeof setInterval> | null;
_floatingEnabled!: boolean;
constructor(container: string | HTMLElement, options: EditorOptions = {}) {
if (!isBrowser()) {
@@ -108,6 +109,7 @@ export class MarkdownEditor {
this._outlineTimer = null; this._zenMode = false; this._wordWrap = true; this._syncing = false;
this._zenMouseHandler = null;
this._floatingToolbar = null; this._selectionTimer = null;
this._floatingEnabled = this.config.floatingToolbar !== false;
this._renderFn = (typeof this.config.render === 'function') ? this.config.render : parseMarkdown;
this._highlightFn = (typeof this.config.highlight === 'function') ? this.config.highlight : null;
@@ -716,13 +718,15 @@ export class MarkdownEditor {
document.head.appendChild(style);
}
if (!this._floatingEnabled) return;
const ta = this.textarea;
const show = () => {
if (this._destroyed || this.config.readOnly) return;
if (this._destroyed || this.config.readOnly || !this._floatingEnabled) return;
const start = ta.selectionStart; const end = ta.selectionEnd;
if (start === end) { this._hideFloatingToolbar(); return; }
if (!this._floatingToolbar) this._buildFloatingToolbar();
if (!this._floatingToolbar || !this._floatingToolbar.parentNode) this._buildFloatingToolbar();
// Position the toolbar near the selection
const rect = ta.getBoundingClientRect();
@@ -731,10 +735,9 @@ export class MarkdownEditor {
const currentLine = lines.length - 1;
const lineHeight = parseInt(getComputedStyle(ta).lineHeight, 10) || 22;
let top = rect.top + currentLine * lineHeight - 38; // above the selection
let top = rect.top + currentLine * lineHeight - 38;
let left = rect.left + (ta.selectionDirection !== 'backward' ? (ta.selectionEnd - ta.selectionStart) / 2 : 0) * 8 + 10;
// Clamp to stay within the editor pane
if (top < rect.top) top = rect.top + currentLine * lineHeight + lineHeight + 4;
if (left < rect.left) left = rect.left + 4;
const maxLeft = rect.right - 200;
@@ -747,35 +750,55 @@ export class MarkdownEditor {
const hide = () => { this._hideFloatingToolbar(); };
// Detect selection changes via events + throttled polling
let lastSelStart = ta.selectionStart;
let lastSelEnd = ta.selectionEnd;
const check = () => {
if (this._destroyed || !document.body.contains(ta)) return;
this._selectionTimer = setInterval(() => {
if (this._destroyed || !document.body.contains(ta) || !this._floatingEnabled) return;
const s = ta.selectionStart; const e = ta.selectionEnd;
if (s !== lastSelStart || e !== lastSelEnd) {
lastSelStart = s; lastSelEnd = e;
const text = this._value.slice(s, e);
this._emit('selectionChange', { start: s, end: e, text });
}
};
// Use longer interval for polling (won't block event loop as much)
this._selectionTimer = setInterval(check, 500) as any;
}, 500) as any;
this._cleanups.push(() => { if (this._selectionTimer) { clearInterval(this._selectionTimer); this._selectionTimer = null; } });
ta.addEventListener('mouseup', () => setTimeout(show, 0));
ta.addEventListener('mouseup', () => { if (this._floatingEnabled) setTimeout(show, 0); });
ta.addEventListener('keyup', () => {
const s = ta.selectionStart; const e = ta.selectionEnd;
if (s !== e) setTimeout(show, 0);
if (!this._floatingEnabled) return;
if (ta.selectionStart !== ta.selectionEnd) setTimeout(show, 0);
else setTimeout(hide, 0);
this._emit('cursorMove', this.getCursorPosition());
});
ta.addEventListener('blur', () => setTimeout(hide, 300));
ta.addEventListener('click', () => setTimeout(() => {
if (ta.selectionStart === ta.selectionEnd) hide();
}, 0));
ta.addEventListener('click', () => { if (ta.selectionStart === ta.selectionEnd) setTimeout(hide, 0); });
}
toggleFloatingToolbar(): this {
this._floatingEnabled = !this._floatingEnabled;
if (this._floatingEnabled) {
// Re-enable: restart the interval
if (!this._selectionTimer) {
const ta = this.textarea;
this._selectionTimer = setInterval(() => {
if (this._destroyed || !this._floatingEnabled) return;
const s = ta.selectionStart; const e = ta.selectionEnd;
if (s !== e) { /* will show via keyup/mouseup */ }
}, 500) as any;
this._cleanups.push(() => { if (this._selectionTimer) { clearInterval(this._selectionTimer); this._selectionTimer = null; } });
}
} else {
// Disable: clear interval and hide toolbar
if (this._selectionTimer) { clearInterval(this._selectionTimer); this._selectionTimer = null; }
if (this._floatingToolbar) {
if (this._floatingToolbar.parentNode) this._floatingToolbar.parentNode.removeChild(this._floatingToolbar);
this._floatingToolbar = null;
}
}
return this;
}
isFloatingToolbar(): boolean { return this._floatingEnabled; }
_buildFloatingToolbar(): void {
const bar = document.createElement('div');
bar.className = 'me-float-toolbar';