fix: 移除 setInterval 轮询,改用纯事件驱动 selectionChange — 修复 CI 卡死问题
This commit is contained in:
Vendored
+21
-58
@@ -2695,7 +2695,6 @@ class MarkdownEditor {
|
||||
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;
|
||||
@@ -3701,9 +3700,21 @@ class MarkdownEditor {
|
||||
if (!this._floatingEnabled)
|
||||
return;
|
||||
const ta = this.textarea;
|
||||
let _lastSelStart = ta.selectionStart;
|
||||
let _lastSelEnd = ta.selectionEnd;
|
||||
const emitSelectionChange = () => {
|
||||
const s = ta.selectionStart;
|
||||
const e = ta.selectionEnd;
|
||||
if (s !== _lastSelStart || e !== _lastSelEnd) {
|
||||
_lastSelStart = s;
|
||||
_lastSelEnd = e;
|
||||
this._emit('selectionChange', { start: s, end: e, text: this._value.slice(s, e) });
|
||||
}
|
||||
};
|
||||
const show = () => {
|
||||
if (this._destroyed || this.config.readOnly || !this._floatingEnabled)
|
||||
return;
|
||||
emitSelectionChange();
|
||||
const start = ta.selectionStart;
|
||||
const end = ta.selectionEnd;
|
||||
if (start === end) {
|
||||
@@ -3712,7 +3723,6 @@ class MarkdownEditor {
|
||||
}
|
||||
if (!this._floatingToolbar || !this._floatingToolbar.parentNode)
|
||||
this._buildFloatingToolbar();
|
||||
// Position the toolbar near the selection (toolbar is absolute inside editorPane)
|
||||
const taRect = ta.getBoundingClientRect();
|
||||
const paneRect = this.editorPane.getBoundingClientRect();
|
||||
const taStyle = getComputedStyle(ta);
|
||||
@@ -3720,24 +3730,19 @@ class MarkdownEditor {
|
||||
const paddingTop = parseFloat(taStyle.paddingTop) || 16;
|
||||
const paddingLeft = parseFloat(taStyle.paddingLeft) || 18;
|
||||
const fontSize = parseFloat(taStyle.fontSize) || 13.5;
|
||||
// Monospace: char width ≈ fontSize * 0.6
|
||||
const charWidth = fontSize * 0.6;
|
||||
const textBefore = ta.value.substring(0, start);
|
||||
const lines = textBefore.split('\n');
|
||||
const currentLine = lines.length - 1;
|
||||
const currentCol = textBefore.length - textBefore.lastIndexOf('\n') - 1;
|
||||
// Middle column of selection
|
||||
const midCol = currentCol + Math.floor((end - start) / 2 * ((ta.selectionDirection === 'backward') ? -1 : 1));
|
||||
// Vertical: line position in textarea content, then convert to pane-local
|
||||
const lineViewportTop = taRect.top + paddingTop + currentLine * lineHeight - ta.scrollTop;
|
||||
const lineLocalTop = lineViewportTop - paneRect.top;
|
||||
const toolbarHeight = 36;
|
||||
const gap = 4;
|
||||
let top = lineLocalTop - toolbarHeight - gap;
|
||||
if (top < gap) {
|
||||
if (top < gap)
|
||||
top = lineLocalTop + lineHeight + gap;
|
||||
}
|
||||
// Horizontal: column position in textarea content, convert to pane-local
|
||||
const colX = taRect.left + paddingLeft + midCol * charWidth - ta.scrollLeft;
|
||||
let left = colX - paneRect.left - 80;
|
||||
left = Math.max(4, Math.min(left, paneRect.width - 200));
|
||||
@@ -3746,29 +3751,9 @@ class MarkdownEditor {
|
||||
this._floatingToolbar.classList.add('me-visible');
|
||||
};
|
||||
const hide = () => { this._hideFloatingToolbar(); };
|
||||
let lastSelStart = ta.selectionStart;
|
||||
let lastSelEnd = ta.selectionEnd;
|
||||
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 });
|
||||
}
|
||||
}, 500);
|
||||
this._cleanups.push(() => { if (this._selectionTimer) {
|
||||
clearInterval(this._selectionTimer);
|
||||
this._selectionTimer = null;
|
||||
} });
|
||||
ta.addEventListener('mouseup', () => { if (this._floatingEnabled)
|
||||
setTimeout(show, 0); });
|
||||
ta.addEventListener('mouseup', () => setTimeout(show, 0));
|
||||
ta.addEventListener('keyup', () => {
|
||||
if (!this._floatingEnabled)
|
||||
return;
|
||||
emitSelectionChange();
|
||||
if (ta.selectionStart !== ta.selectionEnd)
|
||||
setTimeout(show, 0);
|
||||
else
|
||||
@@ -3776,33 +3761,15 @@ class MarkdownEditor {
|
||||
this._emit('cursorMove', this.getCursorPosition());
|
||||
});
|
||||
ta.addEventListener('blur', () => setTimeout(hide, 300));
|
||||
ta.addEventListener('click', () => { if (ta.selectionStart === ta.selectionEnd)
|
||||
setTimeout(hide, 0); });
|
||||
ta.addEventListener('click', () => {
|
||||
emitSelectionChange();
|
||||
if (ta.selectionStart === ta.selectionEnd)
|
||||
hide();
|
||||
});
|
||||
}
|
||||
toggleFloatingToolbar() {
|
||||
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;
|
||||
ta.selectionStart;
|
||||
ta.selectionEnd;
|
||||
}, 500);
|
||||
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._floatingEnabled) {
|
||||
if (this._floatingToolbar) {
|
||||
if (this._floatingToolbar.parentNode)
|
||||
this._floatingToolbar.parentNode.removeChild(this._floatingToolbar);
|
||||
@@ -4334,10 +4301,6 @@ class MarkdownEditor {
|
||||
clearTimeout(this._historyTimer);
|
||||
if (this._outlineTimer)
|
||||
clearTimeout(this._outlineTimer);
|
||||
if (this._selectionTimer) {
|
||||
clearInterval(this._selectionTimer);
|
||||
this._selectionTimer = null;
|
||||
}
|
||||
this._cleanups.forEach((fn) => { try {
|
||||
fn();
|
||||
}
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
-1
@@ -291,7 +291,6 @@ declare class MarkdownEditor {
|
||||
_themeCtx: any;
|
||||
_i18nCtx: InstanceI18n;
|
||||
_floatingToolbar: HTMLElement | null;
|
||||
_selectionTimer: ReturnType<typeof setInterval> | null;
|
||||
_floatingEnabled: boolean;
|
||||
constructor(container: string | HTMLElement, options?: EditorOptions);
|
||||
_buildDOM(): void;
|
||||
|
||||
Vendored
+21
-58
@@ -2691,7 +2691,6 @@ class MarkdownEditor {
|
||||
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;
|
||||
@@ -3697,9 +3696,21 @@ class MarkdownEditor {
|
||||
if (!this._floatingEnabled)
|
||||
return;
|
||||
const ta = this.textarea;
|
||||
let _lastSelStart = ta.selectionStart;
|
||||
let _lastSelEnd = ta.selectionEnd;
|
||||
const emitSelectionChange = () => {
|
||||
const s = ta.selectionStart;
|
||||
const e = ta.selectionEnd;
|
||||
if (s !== _lastSelStart || e !== _lastSelEnd) {
|
||||
_lastSelStart = s;
|
||||
_lastSelEnd = e;
|
||||
this._emit('selectionChange', { start: s, end: e, text: this._value.slice(s, e) });
|
||||
}
|
||||
};
|
||||
const show = () => {
|
||||
if (this._destroyed || this.config.readOnly || !this._floatingEnabled)
|
||||
return;
|
||||
emitSelectionChange();
|
||||
const start = ta.selectionStart;
|
||||
const end = ta.selectionEnd;
|
||||
if (start === end) {
|
||||
@@ -3708,7 +3719,6 @@ class MarkdownEditor {
|
||||
}
|
||||
if (!this._floatingToolbar || !this._floatingToolbar.parentNode)
|
||||
this._buildFloatingToolbar();
|
||||
// Position the toolbar near the selection (toolbar is absolute inside editorPane)
|
||||
const taRect = ta.getBoundingClientRect();
|
||||
const paneRect = this.editorPane.getBoundingClientRect();
|
||||
const taStyle = getComputedStyle(ta);
|
||||
@@ -3716,24 +3726,19 @@ class MarkdownEditor {
|
||||
const paddingTop = parseFloat(taStyle.paddingTop) || 16;
|
||||
const paddingLeft = parseFloat(taStyle.paddingLeft) || 18;
|
||||
const fontSize = parseFloat(taStyle.fontSize) || 13.5;
|
||||
// Monospace: char width ≈ fontSize * 0.6
|
||||
const charWidth = fontSize * 0.6;
|
||||
const textBefore = ta.value.substring(0, start);
|
||||
const lines = textBefore.split('\n');
|
||||
const currentLine = lines.length - 1;
|
||||
const currentCol = textBefore.length - textBefore.lastIndexOf('\n') - 1;
|
||||
// Middle column of selection
|
||||
const midCol = currentCol + Math.floor((end - start) / 2 * ((ta.selectionDirection === 'backward') ? -1 : 1));
|
||||
// Vertical: line position in textarea content, then convert to pane-local
|
||||
const lineViewportTop = taRect.top + paddingTop + currentLine * lineHeight - ta.scrollTop;
|
||||
const lineLocalTop = lineViewportTop - paneRect.top;
|
||||
const toolbarHeight = 36;
|
||||
const gap = 4;
|
||||
let top = lineLocalTop - toolbarHeight - gap;
|
||||
if (top < gap) {
|
||||
if (top < gap)
|
||||
top = lineLocalTop + lineHeight + gap;
|
||||
}
|
||||
// Horizontal: column position in textarea content, convert to pane-local
|
||||
const colX = taRect.left + paddingLeft + midCol * charWidth - ta.scrollLeft;
|
||||
let left = colX - paneRect.left - 80;
|
||||
left = Math.max(4, Math.min(left, paneRect.width - 200));
|
||||
@@ -3742,29 +3747,9 @@ class MarkdownEditor {
|
||||
this._floatingToolbar.classList.add('me-visible');
|
||||
};
|
||||
const hide = () => { this._hideFloatingToolbar(); };
|
||||
let lastSelStart = ta.selectionStart;
|
||||
let lastSelEnd = ta.selectionEnd;
|
||||
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 });
|
||||
}
|
||||
}, 500);
|
||||
this._cleanups.push(() => { if (this._selectionTimer) {
|
||||
clearInterval(this._selectionTimer);
|
||||
this._selectionTimer = null;
|
||||
} });
|
||||
ta.addEventListener('mouseup', () => { if (this._floatingEnabled)
|
||||
setTimeout(show, 0); });
|
||||
ta.addEventListener('mouseup', () => setTimeout(show, 0));
|
||||
ta.addEventListener('keyup', () => {
|
||||
if (!this._floatingEnabled)
|
||||
return;
|
||||
emitSelectionChange();
|
||||
if (ta.selectionStart !== ta.selectionEnd)
|
||||
setTimeout(show, 0);
|
||||
else
|
||||
@@ -3772,33 +3757,15 @@ class MarkdownEditor {
|
||||
this._emit('cursorMove', this.getCursorPosition());
|
||||
});
|
||||
ta.addEventListener('blur', () => setTimeout(hide, 300));
|
||||
ta.addEventListener('click', () => { if (ta.selectionStart === ta.selectionEnd)
|
||||
setTimeout(hide, 0); });
|
||||
ta.addEventListener('click', () => {
|
||||
emitSelectionChange();
|
||||
if (ta.selectionStart === ta.selectionEnd)
|
||||
hide();
|
||||
});
|
||||
}
|
||||
toggleFloatingToolbar() {
|
||||
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;
|
||||
ta.selectionStart;
|
||||
ta.selectionEnd;
|
||||
}, 500);
|
||||
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._floatingEnabled) {
|
||||
if (this._floatingToolbar) {
|
||||
if (this._floatingToolbar.parentNode)
|
||||
this._floatingToolbar.parentNode.removeChild(this._floatingToolbar);
|
||||
@@ -4330,10 +4297,6 @@ class MarkdownEditor {
|
||||
clearTimeout(this._historyTimer);
|
||||
if (this._outlineTimer)
|
||||
clearTimeout(this._outlineTimer);
|
||||
if (this._selectionTimer) {
|
||||
clearInterval(this._selectionTimer);
|
||||
this._selectionTimer = null;
|
||||
}
|
||||
this._cleanups.forEach((fn) => { try {
|
||||
fn();
|
||||
}
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+21
-58
@@ -2697,7 +2697,6 @@ dl{margin:.8em 0}dt{font-weight:650;margin:.6em 0 .2em}dd{margin:0 0 .3em 1.6em;
|
||||
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;
|
||||
@@ -3703,9 +3702,21 @@ dl{margin:.8em 0}dt{font-weight:650;margin:.6em 0 .2em}dd{margin:0 0 .3em 1.6em;
|
||||
if (!this._floatingEnabled)
|
||||
return;
|
||||
const ta = this.textarea;
|
||||
let _lastSelStart = ta.selectionStart;
|
||||
let _lastSelEnd = ta.selectionEnd;
|
||||
const emitSelectionChange = () => {
|
||||
const s = ta.selectionStart;
|
||||
const e = ta.selectionEnd;
|
||||
if (s !== _lastSelStart || e !== _lastSelEnd) {
|
||||
_lastSelStart = s;
|
||||
_lastSelEnd = e;
|
||||
this._emit('selectionChange', { start: s, end: e, text: this._value.slice(s, e) });
|
||||
}
|
||||
};
|
||||
const show = () => {
|
||||
if (this._destroyed || this.config.readOnly || !this._floatingEnabled)
|
||||
return;
|
||||
emitSelectionChange();
|
||||
const start = ta.selectionStart;
|
||||
const end = ta.selectionEnd;
|
||||
if (start === end) {
|
||||
@@ -3714,7 +3725,6 @@ dl{margin:.8em 0}dt{font-weight:650;margin:.6em 0 .2em}dd{margin:0 0 .3em 1.6em;
|
||||
}
|
||||
if (!this._floatingToolbar || !this._floatingToolbar.parentNode)
|
||||
this._buildFloatingToolbar();
|
||||
// Position the toolbar near the selection (toolbar is absolute inside editorPane)
|
||||
const taRect = ta.getBoundingClientRect();
|
||||
const paneRect = this.editorPane.getBoundingClientRect();
|
||||
const taStyle = getComputedStyle(ta);
|
||||
@@ -3722,24 +3732,19 @@ dl{margin:.8em 0}dt{font-weight:650;margin:.6em 0 .2em}dd{margin:0 0 .3em 1.6em;
|
||||
const paddingTop = parseFloat(taStyle.paddingTop) || 16;
|
||||
const paddingLeft = parseFloat(taStyle.paddingLeft) || 18;
|
||||
const fontSize = parseFloat(taStyle.fontSize) || 13.5;
|
||||
// Monospace: char width ≈ fontSize * 0.6
|
||||
const charWidth = fontSize * 0.6;
|
||||
const textBefore = ta.value.substring(0, start);
|
||||
const lines = textBefore.split('\n');
|
||||
const currentLine = lines.length - 1;
|
||||
const currentCol = textBefore.length - textBefore.lastIndexOf('\n') - 1;
|
||||
// Middle column of selection
|
||||
const midCol = currentCol + Math.floor((end - start) / 2 * ((ta.selectionDirection === 'backward') ? -1 : 1));
|
||||
// Vertical: line position in textarea content, then convert to pane-local
|
||||
const lineViewportTop = taRect.top + paddingTop + currentLine * lineHeight - ta.scrollTop;
|
||||
const lineLocalTop = lineViewportTop - paneRect.top;
|
||||
const toolbarHeight = 36;
|
||||
const gap = 4;
|
||||
let top = lineLocalTop - toolbarHeight - gap;
|
||||
if (top < gap) {
|
||||
if (top < gap)
|
||||
top = lineLocalTop + lineHeight + gap;
|
||||
}
|
||||
// Horizontal: column position in textarea content, convert to pane-local
|
||||
const colX = taRect.left + paddingLeft + midCol * charWidth - ta.scrollLeft;
|
||||
let left = colX - paneRect.left - 80;
|
||||
left = Math.max(4, Math.min(left, paneRect.width - 200));
|
||||
@@ -3748,29 +3753,9 @@ dl{margin:.8em 0}dt{font-weight:650;margin:.6em 0 .2em}dd{margin:0 0 .3em 1.6em;
|
||||
this._floatingToolbar.classList.add('me-visible');
|
||||
};
|
||||
const hide = () => { this._hideFloatingToolbar(); };
|
||||
let lastSelStart = ta.selectionStart;
|
||||
let lastSelEnd = ta.selectionEnd;
|
||||
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 });
|
||||
}
|
||||
}, 500);
|
||||
this._cleanups.push(() => { if (this._selectionTimer) {
|
||||
clearInterval(this._selectionTimer);
|
||||
this._selectionTimer = null;
|
||||
} });
|
||||
ta.addEventListener('mouseup', () => { if (this._floatingEnabled)
|
||||
setTimeout(show, 0); });
|
||||
ta.addEventListener('mouseup', () => setTimeout(show, 0));
|
||||
ta.addEventListener('keyup', () => {
|
||||
if (!this._floatingEnabled)
|
||||
return;
|
||||
emitSelectionChange();
|
||||
if (ta.selectionStart !== ta.selectionEnd)
|
||||
setTimeout(show, 0);
|
||||
else
|
||||
@@ -3778,33 +3763,15 @@ dl{margin:.8em 0}dt{font-weight:650;margin:.6em 0 .2em}dd{margin:0 0 .3em 1.6em;
|
||||
this._emit('cursorMove', this.getCursorPosition());
|
||||
});
|
||||
ta.addEventListener('blur', () => setTimeout(hide, 300));
|
||||
ta.addEventListener('click', () => { if (ta.selectionStart === ta.selectionEnd)
|
||||
setTimeout(hide, 0); });
|
||||
ta.addEventListener('click', () => {
|
||||
emitSelectionChange();
|
||||
if (ta.selectionStart === ta.selectionEnd)
|
||||
hide();
|
||||
});
|
||||
}
|
||||
toggleFloatingToolbar() {
|
||||
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;
|
||||
ta.selectionStart;
|
||||
ta.selectionEnd;
|
||||
}, 500);
|
||||
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._floatingEnabled) {
|
||||
if (this._floatingToolbar) {
|
||||
if (this._floatingToolbar.parentNode)
|
||||
this._floatingToolbar.parentNode.removeChild(this._floatingToolbar);
|
||||
@@ -4336,10 +4303,6 @@ dl{margin:.8em 0}dt{font-weight:650;margin:.6em 0 .2em}dd{margin:0 0 .3em 1.6em;
|
||||
clearTimeout(this._historyTimer);
|
||||
if (this._outlineTimer)
|
||||
clearTimeout(this._outlineTimer);
|
||||
if (this._selectionTimer) {
|
||||
clearInterval(this._selectionTimer);
|
||||
this._selectionTimer = null;
|
||||
}
|
||||
this._cleanups.forEach((fn) => { try {
|
||||
fn();
|
||||
}
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user