fix: 移除 setInterval 轮询,改用纯事件驱动 selectionChange — 修复 CI 卡死问题
This commit is contained in:
Vendored
+21
-58
@@ -2695,7 +2695,6 @@ class MarkdownEditor {
|
|||||||
this._syncing = false;
|
this._syncing = false;
|
||||||
this._zenMouseHandler = null;
|
this._zenMouseHandler = null;
|
||||||
this._floatingToolbar = null;
|
this._floatingToolbar = null;
|
||||||
this._selectionTimer = null;
|
|
||||||
this._floatingEnabled = this.config.floatingToolbar !== false;
|
this._floatingEnabled = this.config.floatingToolbar !== false;
|
||||||
this._renderFn = (typeof this.config.render === 'function') ? this.config.render : parseMarkdown;
|
this._renderFn = (typeof this.config.render === 'function') ? this.config.render : parseMarkdown;
|
||||||
this._highlightFn = (typeof this.config.highlight === 'function') ? this.config.highlight : null;
|
this._highlightFn = (typeof this.config.highlight === 'function') ? this.config.highlight : null;
|
||||||
@@ -3701,9 +3700,21 @@ class MarkdownEditor {
|
|||||||
if (!this._floatingEnabled)
|
if (!this._floatingEnabled)
|
||||||
return;
|
return;
|
||||||
const ta = this.textarea;
|
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 = () => {
|
const show = () => {
|
||||||
if (this._destroyed || this.config.readOnly || !this._floatingEnabled)
|
if (this._destroyed || this.config.readOnly || !this._floatingEnabled)
|
||||||
return;
|
return;
|
||||||
|
emitSelectionChange();
|
||||||
const start = ta.selectionStart;
|
const start = ta.selectionStart;
|
||||||
const end = ta.selectionEnd;
|
const end = ta.selectionEnd;
|
||||||
if (start === end) {
|
if (start === end) {
|
||||||
@@ -3712,7 +3723,6 @@ class MarkdownEditor {
|
|||||||
}
|
}
|
||||||
if (!this._floatingToolbar || !this._floatingToolbar.parentNode)
|
if (!this._floatingToolbar || !this._floatingToolbar.parentNode)
|
||||||
this._buildFloatingToolbar();
|
this._buildFloatingToolbar();
|
||||||
// Position the toolbar near the selection (toolbar is absolute inside editorPane)
|
|
||||||
const taRect = ta.getBoundingClientRect();
|
const taRect = ta.getBoundingClientRect();
|
||||||
const paneRect = this.editorPane.getBoundingClientRect();
|
const paneRect = this.editorPane.getBoundingClientRect();
|
||||||
const taStyle = getComputedStyle(ta);
|
const taStyle = getComputedStyle(ta);
|
||||||
@@ -3720,24 +3730,19 @@ class MarkdownEditor {
|
|||||||
const paddingTop = parseFloat(taStyle.paddingTop) || 16;
|
const paddingTop = parseFloat(taStyle.paddingTop) || 16;
|
||||||
const paddingLeft = parseFloat(taStyle.paddingLeft) || 18;
|
const paddingLeft = parseFloat(taStyle.paddingLeft) || 18;
|
||||||
const fontSize = parseFloat(taStyle.fontSize) || 13.5;
|
const fontSize = parseFloat(taStyle.fontSize) || 13.5;
|
||||||
// Monospace: char width ≈ fontSize * 0.6
|
|
||||||
const charWidth = fontSize * 0.6;
|
const charWidth = fontSize * 0.6;
|
||||||
const textBefore = ta.value.substring(0, start);
|
const textBefore = ta.value.substring(0, start);
|
||||||
const lines = textBefore.split('\n');
|
const lines = textBefore.split('\n');
|
||||||
const currentLine = lines.length - 1;
|
const currentLine = lines.length - 1;
|
||||||
const currentCol = textBefore.length - textBefore.lastIndexOf('\n') - 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));
|
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 lineViewportTop = taRect.top + paddingTop + currentLine * lineHeight - ta.scrollTop;
|
||||||
const lineLocalTop = lineViewportTop - paneRect.top;
|
const lineLocalTop = lineViewportTop - paneRect.top;
|
||||||
const toolbarHeight = 36;
|
const toolbarHeight = 36;
|
||||||
const gap = 4;
|
const gap = 4;
|
||||||
let top = lineLocalTop - toolbarHeight - gap;
|
let top = lineLocalTop - toolbarHeight - gap;
|
||||||
if (top < gap) {
|
if (top < gap)
|
||||||
top = lineLocalTop + lineHeight + gap;
|
top = lineLocalTop + lineHeight + gap;
|
||||||
}
|
|
||||||
// Horizontal: column position in textarea content, convert to pane-local
|
|
||||||
const colX = taRect.left + paddingLeft + midCol * charWidth - ta.scrollLeft;
|
const colX = taRect.left + paddingLeft + midCol * charWidth - ta.scrollLeft;
|
||||||
let left = colX - paneRect.left - 80;
|
let left = colX - paneRect.left - 80;
|
||||||
left = Math.max(4, Math.min(left, paneRect.width - 200));
|
left = Math.max(4, Math.min(left, paneRect.width - 200));
|
||||||
@@ -3746,29 +3751,9 @@ class MarkdownEditor {
|
|||||||
this._floatingToolbar.classList.add('me-visible');
|
this._floatingToolbar.classList.add('me-visible');
|
||||||
};
|
};
|
||||||
const hide = () => { this._hideFloatingToolbar(); };
|
const hide = () => { this._hideFloatingToolbar(); };
|
||||||
let lastSelStart = ta.selectionStart;
|
ta.addEventListener('mouseup', () => setTimeout(show, 0));
|
||||||
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('keyup', () => {
|
ta.addEventListener('keyup', () => {
|
||||||
if (!this._floatingEnabled)
|
emitSelectionChange();
|
||||||
return;
|
|
||||||
if (ta.selectionStart !== ta.selectionEnd)
|
if (ta.selectionStart !== ta.selectionEnd)
|
||||||
setTimeout(show, 0);
|
setTimeout(show, 0);
|
||||||
else
|
else
|
||||||
@@ -3776,33 +3761,15 @@ class MarkdownEditor {
|
|||||||
this._emit('cursorMove', this.getCursorPosition());
|
this._emit('cursorMove', this.getCursorPosition());
|
||||||
});
|
});
|
||||||
ta.addEventListener('blur', () => setTimeout(hide, 300));
|
ta.addEventListener('blur', () => setTimeout(hide, 300));
|
||||||
ta.addEventListener('click', () => { if (ta.selectionStart === ta.selectionEnd)
|
ta.addEventListener('click', () => {
|
||||||
setTimeout(hide, 0); });
|
emitSelectionChange();
|
||||||
|
if (ta.selectionStart === ta.selectionEnd)
|
||||||
|
hide();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
toggleFloatingToolbar() {
|
toggleFloatingToolbar() {
|
||||||
this._floatingEnabled = !this._floatingEnabled;
|
this._floatingEnabled = !this._floatingEnabled;
|
||||||
if (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._floatingToolbar) {
|
if (this._floatingToolbar) {
|
||||||
if (this._floatingToolbar.parentNode)
|
if (this._floatingToolbar.parentNode)
|
||||||
this._floatingToolbar.parentNode.removeChild(this._floatingToolbar);
|
this._floatingToolbar.parentNode.removeChild(this._floatingToolbar);
|
||||||
@@ -4334,10 +4301,6 @@ class MarkdownEditor {
|
|||||||
clearTimeout(this._historyTimer);
|
clearTimeout(this._historyTimer);
|
||||||
if (this._outlineTimer)
|
if (this._outlineTimer)
|
||||||
clearTimeout(this._outlineTimer);
|
clearTimeout(this._outlineTimer);
|
||||||
if (this._selectionTimer) {
|
|
||||||
clearInterval(this._selectionTimer);
|
|
||||||
this._selectionTimer = null;
|
|
||||||
}
|
|
||||||
this._cleanups.forEach((fn) => { try {
|
this._cleanups.forEach((fn) => { try {
|
||||||
fn();
|
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;
|
_themeCtx: any;
|
||||||
_i18nCtx: InstanceI18n;
|
_i18nCtx: InstanceI18n;
|
||||||
_floatingToolbar: HTMLElement | null;
|
_floatingToolbar: HTMLElement | null;
|
||||||
_selectionTimer: ReturnType<typeof setInterval> | null;
|
|
||||||
_floatingEnabled: boolean;
|
_floatingEnabled: boolean;
|
||||||
constructor(container: string | HTMLElement, options?: EditorOptions);
|
constructor(container: string | HTMLElement, options?: EditorOptions);
|
||||||
_buildDOM(): void;
|
_buildDOM(): void;
|
||||||
|
|||||||
Vendored
+21
-58
@@ -2691,7 +2691,6 @@ class MarkdownEditor {
|
|||||||
this._syncing = false;
|
this._syncing = false;
|
||||||
this._zenMouseHandler = null;
|
this._zenMouseHandler = null;
|
||||||
this._floatingToolbar = null;
|
this._floatingToolbar = null;
|
||||||
this._selectionTimer = null;
|
|
||||||
this._floatingEnabled = this.config.floatingToolbar !== false;
|
this._floatingEnabled = this.config.floatingToolbar !== false;
|
||||||
this._renderFn = (typeof this.config.render === 'function') ? this.config.render : parseMarkdown;
|
this._renderFn = (typeof this.config.render === 'function') ? this.config.render : parseMarkdown;
|
||||||
this._highlightFn = (typeof this.config.highlight === 'function') ? this.config.highlight : null;
|
this._highlightFn = (typeof this.config.highlight === 'function') ? this.config.highlight : null;
|
||||||
@@ -3697,9 +3696,21 @@ class MarkdownEditor {
|
|||||||
if (!this._floatingEnabled)
|
if (!this._floatingEnabled)
|
||||||
return;
|
return;
|
||||||
const ta = this.textarea;
|
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 = () => {
|
const show = () => {
|
||||||
if (this._destroyed || this.config.readOnly || !this._floatingEnabled)
|
if (this._destroyed || this.config.readOnly || !this._floatingEnabled)
|
||||||
return;
|
return;
|
||||||
|
emitSelectionChange();
|
||||||
const start = ta.selectionStart;
|
const start = ta.selectionStart;
|
||||||
const end = ta.selectionEnd;
|
const end = ta.selectionEnd;
|
||||||
if (start === end) {
|
if (start === end) {
|
||||||
@@ -3708,7 +3719,6 @@ class MarkdownEditor {
|
|||||||
}
|
}
|
||||||
if (!this._floatingToolbar || !this._floatingToolbar.parentNode)
|
if (!this._floatingToolbar || !this._floatingToolbar.parentNode)
|
||||||
this._buildFloatingToolbar();
|
this._buildFloatingToolbar();
|
||||||
// Position the toolbar near the selection (toolbar is absolute inside editorPane)
|
|
||||||
const taRect = ta.getBoundingClientRect();
|
const taRect = ta.getBoundingClientRect();
|
||||||
const paneRect = this.editorPane.getBoundingClientRect();
|
const paneRect = this.editorPane.getBoundingClientRect();
|
||||||
const taStyle = getComputedStyle(ta);
|
const taStyle = getComputedStyle(ta);
|
||||||
@@ -3716,24 +3726,19 @@ class MarkdownEditor {
|
|||||||
const paddingTop = parseFloat(taStyle.paddingTop) || 16;
|
const paddingTop = parseFloat(taStyle.paddingTop) || 16;
|
||||||
const paddingLeft = parseFloat(taStyle.paddingLeft) || 18;
|
const paddingLeft = parseFloat(taStyle.paddingLeft) || 18;
|
||||||
const fontSize = parseFloat(taStyle.fontSize) || 13.5;
|
const fontSize = parseFloat(taStyle.fontSize) || 13.5;
|
||||||
// Monospace: char width ≈ fontSize * 0.6
|
|
||||||
const charWidth = fontSize * 0.6;
|
const charWidth = fontSize * 0.6;
|
||||||
const textBefore = ta.value.substring(0, start);
|
const textBefore = ta.value.substring(0, start);
|
||||||
const lines = textBefore.split('\n');
|
const lines = textBefore.split('\n');
|
||||||
const currentLine = lines.length - 1;
|
const currentLine = lines.length - 1;
|
||||||
const currentCol = textBefore.length - textBefore.lastIndexOf('\n') - 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));
|
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 lineViewportTop = taRect.top + paddingTop + currentLine * lineHeight - ta.scrollTop;
|
||||||
const lineLocalTop = lineViewportTop - paneRect.top;
|
const lineLocalTop = lineViewportTop - paneRect.top;
|
||||||
const toolbarHeight = 36;
|
const toolbarHeight = 36;
|
||||||
const gap = 4;
|
const gap = 4;
|
||||||
let top = lineLocalTop - toolbarHeight - gap;
|
let top = lineLocalTop - toolbarHeight - gap;
|
||||||
if (top < gap) {
|
if (top < gap)
|
||||||
top = lineLocalTop + lineHeight + gap;
|
top = lineLocalTop + lineHeight + gap;
|
||||||
}
|
|
||||||
// Horizontal: column position in textarea content, convert to pane-local
|
|
||||||
const colX = taRect.left + paddingLeft + midCol * charWidth - ta.scrollLeft;
|
const colX = taRect.left + paddingLeft + midCol * charWidth - ta.scrollLeft;
|
||||||
let left = colX - paneRect.left - 80;
|
let left = colX - paneRect.left - 80;
|
||||||
left = Math.max(4, Math.min(left, paneRect.width - 200));
|
left = Math.max(4, Math.min(left, paneRect.width - 200));
|
||||||
@@ -3742,29 +3747,9 @@ class MarkdownEditor {
|
|||||||
this._floatingToolbar.classList.add('me-visible');
|
this._floatingToolbar.classList.add('me-visible');
|
||||||
};
|
};
|
||||||
const hide = () => { this._hideFloatingToolbar(); };
|
const hide = () => { this._hideFloatingToolbar(); };
|
||||||
let lastSelStart = ta.selectionStart;
|
ta.addEventListener('mouseup', () => setTimeout(show, 0));
|
||||||
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('keyup', () => {
|
ta.addEventListener('keyup', () => {
|
||||||
if (!this._floatingEnabled)
|
emitSelectionChange();
|
||||||
return;
|
|
||||||
if (ta.selectionStart !== ta.selectionEnd)
|
if (ta.selectionStart !== ta.selectionEnd)
|
||||||
setTimeout(show, 0);
|
setTimeout(show, 0);
|
||||||
else
|
else
|
||||||
@@ -3772,33 +3757,15 @@ class MarkdownEditor {
|
|||||||
this._emit('cursorMove', this.getCursorPosition());
|
this._emit('cursorMove', this.getCursorPosition());
|
||||||
});
|
});
|
||||||
ta.addEventListener('blur', () => setTimeout(hide, 300));
|
ta.addEventListener('blur', () => setTimeout(hide, 300));
|
||||||
ta.addEventListener('click', () => { if (ta.selectionStart === ta.selectionEnd)
|
ta.addEventListener('click', () => {
|
||||||
setTimeout(hide, 0); });
|
emitSelectionChange();
|
||||||
|
if (ta.selectionStart === ta.selectionEnd)
|
||||||
|
hide();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
toggleFloatingToolbar() {
|
toggleFloatingToolbar() {
|
||||||
this._floatingEnabled = !this._floatingEnabled;
|
this._floatingEnabled = !this._floatingEnabled;
|
||||||
if (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._floatingToolbar) {
|
if (this._floatingToolbar) {
|
||||||
if (this._floatingToolbar.parentNode)
|
if (this._floatingToolbar.parentNode)
|
||||||
this._floatingToolbar.parentNode.removeChild(this._floatingToolbar);
|
this._floatingToolbar.parentNode.removeChild(this._floatingToolbar);
|
||||||
@@ -4330,10 +4297,6 @@ class MarkdownEditor {
|
|||||||
clearTimeout(this._historyTimer);
|
clearTimeout(this._historyTimer);
|
||||||
if (this._outlineTimer)
|
if (this._outlineTimer)
|
||||||
clearTimeout(this._outlineTimer);
|
clearTimeout(this._outlineTimer);
|
||||||
if (this._selectionTimer) {
|
|
||||||
clearInterval(this._selectionTimer);
|
|
||||||
this._selectionTimer = null;
|
|
||||||
}
|
|
||||||
this._cleanups.forEach((fn) => { try {
|
this._cleanups.forEach((fn) => { try {
|
||||||
fn();
|
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._syncing = false;
|
||||||
this._zenMouseHandler = null;
|
this._zenMouseHandler = null;
|
||||||
this._floatingToolbar = null;
|
this._floatingToolbar = null;
|
||||||
this._selectionTimer = null;
|
|
||||||
this._floatingEnabled = this.config.floatingToolbar !== false;
|
this._floatingEnabled = this.config.floatingToolbar !== false;
|
||||||
this._renderFn = (typeof this.config.render === 'function') ? this.config.render : parseMarkdown;
|
this._renderFn = (typeof this.config.render === 'function') ? this.config.render : parseMarkdown;
|
||||||
this._highlightFn = (typeof this.config.highlight === 'function') ? this.config.highlight : null;
|
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)
|
if (!this._floatingEnabled)
|
||||||
return;
|
return;
|
||||||
const ta = this.textarea;
|
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 = () => {
|
const show = () => {
|
||||||
if (this._destroyed || this.config.readOnly || !this._floatingEnabled)
|
if (this._destroyed || this.config.readOnly || !this._floatingEnabled)
|
||||||
return;
|
return;
|
||||||
|
emitSelectionChange();
|
||||||
const start = ta.selectionStart;
|
const start = ta.selectionStart;
|
||||||
const end = ta.selectionEnd;
|
const end = ta.selectionEnd;
|
||||||
if (start === end) {
|
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)
|
if (!this._floatingToolbar || !this._floatingToolbar.parentNode)
|
||||||
this._buildFloatingToolbar();
|
this._buildFloatingToolbar();
|
||||||
// Position the toolbar near the selection (toolbar is absolute inside editorPane)
|
|
||||||
const taRect = ta.getBoundingClientRect();
|
const taRect = ta.getBoundingClientRect();
|
||||||
const paneRect = this.editorPane.getBoundingClientRect();
|
const paneRect = this.editorPane.getBoundingClientRect();
|
||||||
const taStyle = getComputedStyle(ta);
|
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 paddingTop = parseFloat(taStyle.paddingTop) || 16;
|
||||||
const paddingLeft = parseFloat(taStyle.paddingLeft) || 18;
|
const paddingLeft = parseFloat(taStyle.paddingLeft) || 18;
|
||||||
const fontSize = parseFloat(taStyle.fontSize) || 13.5;
|
const fontSize = parseFloat(taStyle.fontSize) || 13.5;
|
||||||
// Monospace: char width ≈ fontSize * 0.6
|
|
||||||
const charWidth = fontSize * 0.6;
|
const charWidth = fontSize * 0.6;
|
||||||
const textBefore = ta.value.substring(0, start);
|
const textBefore = ta.value.substring(0, start);
|
||||||
const lines = textBefore.split('\n');
|
const lines = textBefore.split('\n');
|
||||||
const currentLine = lines.length - 1;
|
const currentLine = lines.length - 1;
|
||||||
const currentCol = textBefore.length - textBefore.lastIndexOf('\n') - 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));
|
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 lineViewportTop = taRect.top + paddingTop + currentLine * lineHeight - ta.scrollTop;
|
||||||
const lineLocalTop = lineViewportTop - paneRect.top;
|
const lineLocalTop = lineViewportTop - paneRect.top;
|
||||||
const toolbarHeight = 36;
|
const toolbarHeight = 36;
|
||||||
const gap = 4;
|
const gap = 4;
|
||||||
let top = lineLocalTop - toolbarHeight - gap;
|
let top = lineLocalTop - toolbarHeight - gap;
|
||||||
if (top < gap) {
|
if (top < gap)
|
||||||
top = lineLocalTop + lineHeight + gap;
|
top = lineLocalTop + lineHeight + gap;
|
||||||
}
|
|
||||||
// Horizontal: column position in textarea content, convert to pane-local
|
|
||||||
const colX = taRect.left + paddingLeft + midCol * charWidth - ta.scrollLeft;
|
const colX = taRect.left + paddingLeft + midCol * charWidth - ta.scrollLeft;
|
||||||
let left = colX - paneRect.left - 80;
|
let left = colX - paneRect.left - 80;
|
||||||
left = Math.max(4, Math.min(left, paneRect.width - 200));
|
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');
|
this._floatingToolbar.classList.add('me-visible');
|
||||||
};
|
};
|
||||||
const hide = () => { this._hideFloatingToolbar(); };
|
const hide = () => { this._hideFloatingToolbar(); };
|
||||||
let lastSelStart = ta.selectionStart;
|
ta.addEventListener('mouseup', () => setTimeout(show, 0));
|
||||||
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('keyup', () => {
|
ta.addEventListener('keyup', () => {
|
||||||
if (!this._floatingEnabled)
|
emitSelectionChange();
|
||||||
return;
|
|
||||||
if (ta.selectionStart !== ta.selectionEnd)
|
if (ta.selectionStart !== ta.selectionEnd)
|
||||||
setTimeout(show, 0);
|
setTimeout(show, 0);
|
||||||
else
|
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());
|
this._emit('cursorMove', this.getCursorPosition());
|
||||||
});
|
});
|
||||||
ta.addEventListener('blur', () => setTimeout(hide, 300));
|
ta.addEventListener('blur', () => setTimeout(hide, 300));
|
||||||
ta.addEventListener('click', () => { if (ta.selectionStart === ta.selectionEnd)
|
ta.addEventListener('click', () => {
|
||||||
setTimeout(hide, 0); });
|
emitSelectionChange();
|
||||||
|
if (ta.selectionStart === ta.selectionEnd)
|
||||||
|
hide();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
toggleFloatingToolbar() {
|
toggleFloatingToolbar() {
|
||||||
this._floatingEnabled = !this._floatingEnabled;
|
this._floatingEnabled = !this._floatingEnabled;
|
||||||
if (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._floatingToolbar) {
|
if (this._floatingToolbar) {
|
||||||
if (this._floatingToolbar.parentNode)
|
if (this._floatingToolbar.parentNode)
|
||||||
this._floatingToolbar.parentNode.removeChild(this._floatingToolbar);
|
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);
|
clearTimeout(this._historyTimer);
|
||||||
if (this._outlineTimer)
|
if (this._outlineTimer)
|
||||||
clearTimeout(this._outlineTimer);
|
clearTimeout(this._outlineTimer);
|
||||||
if (this._selectionTimer) {
|
|
||||||
clearInterval(this._selectionTimer);
|
|
||||||
this._selectionTimer = null;
|
|
||||||
}
|
|
||||||
this._cleanups.forEach((fn) => { try {
|
this._cleanups.forEach((fn) => { try {
|
||||||
fn();
|
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
+21
-41
@@ -83,7 +83,6 @@ export class MarkdownEditor {
|
|||||||
_themeCtx: any;
|
_themeCtx: any;
|
||||||
_i18nCtx!: InstanceI18n;
|
_i18nCtx!: InstanceI18n;
|
||||||
_floatingToolbar!: HTMLElement | null;
|
_floatingToolbar!: HTMLElement | null;
|
||||||
_selectionTimer!: ReturnType<typeof setInterval> | null;
|
|
||||||
_floatingEnabled!: boolean;
|
_floatingEnabled!: boolean;
|
||||||
|
|
||||||
constructor(container: string | HTMLElement, options: EditorOptions = {}) {
|
constructor(container: string | HTMLElement, options: EditorOptions = {}) {
|
||||||
@@ -108,7 +107,7 @@ export class MarkdownEditor {
|
|||||||
this._shortcuts = []; this._contextMenuItems = []; this._customActions = {};
|
this._shortcuts = []; this._contextMenuItems = []; this._customActions = {};
|
||||||
this._outlineTimer = null; this._zenMode = false; this._wordWrap = true; this._syncing = false;
|
this._outlineTimer = null; this._zenMode = false; this._wordWrap = true; this._syncing = false;
|
||||||
this._zenMouseHandler = null;
|
this._zenMouseHandler = null;
|
||||||
this._floatingToolbar = null; this._selectionTimer = null;
|
this._floatingToolbar = null;
|
||||||
this._floatingEnabled = this.config.floatingToolbar !== false;
|
this._floatingEnabled = this.config.floatingToolbar !== false;
|
||||||
|
|
||||||
this._renderFn = (typeof this.config.render === 'function') ? this.config.render : parseMarkdown;
|
this._renderFn = (typeof this.config.render === 'function') ? this.config.render : parseMarkdown;
|
||||||
@@ -721,14 +720,25 @@ export class MarkdownEditor {
|
|||||||
if (!this._floatingEnabled) return;
|
if (!this._floatingEnabled) return;
|
||||||
|
|
||||||
const ta = this.textarea;
|
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 = () => {
|
const show = () => {
|
||||||
if (this._destroyed || this.config.readOnly || !this._floatingEnabled) return;
|
if (this._destroyed || this.config.readOnly || !this._floatingEnabled) return;
|
||||||
|
emitSelectionChange();
|
||||||
const start = ta.selectionStart; const end = ta.selectionEnd;
|
const start = ta.selectionStart; const end = ta.selectionEnd;
|
||||||
if (start === end) { this._hideFloatingToolbar(); return; }
|
if (start === end) { this._hideFloatingToolbar(); return; }
|
||||||
|
|
||||||
if (!this._floatingToolbar || !this._floatingToolbar.parentNode) this._buildFloatingToolbar();
|
if (!this._floatingToolbar || !this._floatingToolbar.parentNode) this._buildFloatingToolbar();
|
||||||
|
|
||||||
// Position the toolbar near the selection (toolbar is absolute inside editorPane)
|
|
||||||
const taRect = ta.getBoundingClientRect();
|
const taRect = ta.getBoundingClientRect();
|
||||||
const paneRect = this.editorPane.getBoundingClientRect();
|
const paneRect = this.editorPane.getBoundingClientRect();
|
||||||
const taStyle = getComputedStyle(ta);
|
const taStyle = getComputedStyle(ta);
|
||||||
@@ -736,27 +746,21 @@ export class MarkdownEditor {
|
|||||||
const paddingTop = parseFloat(taStyle.paddingTop) || 16;
|
const paddingTop = parseFloat(taStyle.paddingTop) || 16;
|
||||||
const paddingLeft = parseFloat(taStyle.paddingLeft) || 18;
|
const paddingLeft = parseFloat(taStyle.paddingLeft) || 18;
|
||||||
const fontSize = parseFloat(taStyle.fontSize) || 13.5;
|
const fontSize = parseFloat(taStyle.fontSize) || 13.5;
|
||||||
// Monospace: char width ≈ fontSize * 0.6
|
|
||||||
const charWidth = fontSize * 0.6;
|
const charWidth = fontSize * 0.6;
|
||||||
|
|
||||||
const textBefore = ta.value.substring(0, start);
|
const textBefore = ta.value.substring(0, start);
|
||||||
const lines = textBefore.split('\n');
|
const lines = textBefore.split('\n');
|
||||||
const currentLine = lines.length - 1;
|
const currentLine = lines.length - 1;
|
||||||
const currentCol = textBefore.length - textBefore.lastIndexOf('\n') - 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));
|
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 lineViewportTop = taRect.top + paddingTop + currentLine * lineHeight - ta.scrollTop;
|
||||||
const lineLocalTop = lineViewportTop - paneRect.top;
|
const lineLocalTop = lineViewportTop - paneRect.top;
|
||||||
const toolbarHeight = 36;
|
const toolbarHeight = 36;
|
||||||
const gap = 4;
|
const gap = 4;
|
||||||
let top = lineLocalTop - toolbarHeight - gap;
|
let top = lineLocalTop - toolbarHeight - gap;
|
||||||
if (top < gap) {
|
if (top < gap) top = lineLocalTop + lineHeight + gap;
|
||||||
top = lineLocalTop + lineHeight + gap;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Horizontal: column position in textarea content, convert to pane-local
|
|
||||||
const colX = taRect.left + paddingLeft + midCol * charWidth - ta.scrollLeft;
|
const colX = taRect.left + paddingLeft + midCol * charWidth - ta.scrollLeft;
|
||||||
let left = colX - paneRect.left - 80;
|
let left = colX - paneRect.left - 80;
|
||||||
left = Math.max(4, Math.min(left, paneRect.width - 200));
|
left = Math.max(4, Math.min(left, paneRect.width - 200));
|
||||||
@@ -768,46 +772,23 @@ export class MarkdownEditor {
|
|||||||
|
|
||||||
const hide = () => { this._hideFloatingToolbar(); };
|
const hide = () => { this._hideFloatingToolbar(); };
|
||||||
|
|
||||||
let lastSelStart = ta.selectionStart;
|
ta.addEventListener('mouseup', () => setTimeout(show, 0));
|
||||||
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) as any;
|
|
||||||
this._cleanups.push(() => { if (this._selectionTimer) { clearInterval(this._selectionTimer); this._selectionTimer = null; } });
|
|
||||||
|
|
||||||
ta.addEventListener('mouseup', () => { if (this._floatingEnabled) setTimeout(show, 0); });
|
|
||||||
ta.addEventListener('keyup', () => {
|
ta.addEventListener('keyup', () => {
|
||||||
if (!this._floatingEnabled) return;
|
emitSelectionChange();
|
||||||
if (ta.selectionStart !== ta.selectionEnd) setTimeout(show, 0);
|
if (ta.selectionStart !== ta.selectionEnd) setTimeout(show, 0);
|
||||||
else setTimeout(hide, 0);
|
else setTimeout(hide, 0);
|
||||||
this._emit('cursorMove', this.getCursorPosition());
|
this._emit('cursorMove', this.getCursorPosition());
|
||||||
});
|
});
|
||||||
ta.addEventListener('blur', () => setTimeout(hide, 300));
|
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 {
|
toggleFloatingToolbar(): this {
|
||||||
this._floatingEnabled = !this._floatingEnabled;
|
this._floatingEnabled = !this._floatingEnabled;
|
||||||
if (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) {
|
||||||
if (this._floatingToolbar.parentNode) this._floatingToolbar.parentNode.removeChild(this._floatingToolbar);
|
if (this._floatingToolbar.parentNode) this._floatingToolbar.parentNode.removeChild(this._floatingToolbar);
|
||||||
this._floatingToolbar = null;
|
this._floatingToolbar = null;
|
||||||
@@ -1161,7 +1142,6 @@ export class MarkdownEditor {
|
|||||||
if (this._renderRaf) cancelAnimationFrame(this._renderRaf);
|
if (this._renderRaf) cancelAnimationFrame(this._renderRaf);
|
||||||
if (this._historyTimer) clearTimeout(this._historyTimer);
|
if (this._historyTimer) clearTimeout(this._historyTimer);
|
||||||
if (this._outlineTimer) clearTimeout(this._outlineTimer);
|
if (this._outlineTimer) clearTimeout(this._outlineTimer);
|
||||||
if (this._selectionTimer) { clearInterval(this._selectionTimer); this._selectionTimer = null; }
|
|
||||||
this._cleanups.forEach((fn) => { try { fn(); } catch (_) {} }); this._cleanups = [];
|
this._cleanups.forEach((fn) => { try { fn(); } catch (_) {} }); this._cleanups = [];
|
||||||
this._shortcuts = []; this._contextMenuItems = []; this._customActions = {};
|
this._shortcuts = []; this._contextMenuItems = []; this._customActions = {};
|
||||||
if (this._floatingToolbar && this._floatingToolbar.parentNode) this._floatingToolbar.parentNode.removeChild(this._floatingToolbar);
|
if (this._floatingToolbar && this._floatingToolbar.parentNode) this._floatingToolbar.parentNode.removeChild(this._floatingToolbar);
|
||||||
|
|||||||
+10
-17
@@ -2179,13 +2179,10 @@ describe('MarkdownEditor - v0.2.3 浮动工具栏', () => {
|
|||||||
const c = document.createElement('div');
|
const c = document.createElement('div');
|
||||||
document.body.appendChild(c);
|
document.body.appendChild(c);
|
||||||
const ed = new MarkdownEditor(c, { value: 'hello world' });
|
const ed = new MarkdownEditor(c, { value: 'hello world' });
|
||||||
// 浮动工具栏应被注入 CSS
|
|
||||||
const style = document.getElementById('me-float-style');
|
const style = document.getElementById('me-float-style');
|
||||||
expect(style).not.toBeNull();
|
expect(style).not.toBeNull();
|
||||||
// _floatingToolbar 在首次 selection 时才创建 DOM
|
|
||||||
expect(ed._floatingToolbar).toBeNull();
|
expect(ed._floatingToolbar).toBeNull();
|
||||||
// _selectionTimer 已启动
|
expect(ed._floatingEnabled).toBe(true);
|
||||||
expect(ed._selectionTimer).not.toBeNull();
|
|
||||||
ed.destroy();
|
ed.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2194,8 +2191,7 @@ describe('MarkdownEditor - v0.2.3 浮动工具栏', () => {
|
|||||||
const c = document.createElement('div');
|
const c = document.createElement('div');
|
||||||
document.body.appendChild(c);
|
document.body.appendChild(c);
|
||||||
const ed = new MarkdownEditor(c, { value: 'hello', floatingToolbar: false });
|
const ed = new MarkdownEditor(c, { value: 'hello', floatingToolbar: false });
|
||||||
// 浮动工具栏应不启动(_selectionTimer 为 null)
|
expect(ed._floatingEnabled).toBe(false);
|
||||||
expect(ed._selectionTimer).toBeNull();
|
|
||||||
expect(ed._floatingToolbar).toBeNull();
|
expect(ed._floatingToolbar).toBeNull();
|
||||||
ed.destroy();
|
ed.destroy();
|
||||||
});
|
});
|
||||||
@@ -2224,26 +2220,23 @@ describe('MarkdownEditor - v0.2.3 浮动工具栏', () => {
|
|||||||
expect(ed._floatingToolbar).not.toBeNull();
|
expect(ed._floatingToolbar).not.toBeNull();
|
||||||
ed.destroy();
|
ed.destroy();
|
||||||
expect(ed._floatingToolbar).toBeNull();
|
expect(ed._floatingToolbar).toBeNull();
|
||||||
expect(ed._selectionTimer).toBeNull();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('selectionChange 事件触发', (done) => {
|
test('selectionChange 事件触发', () => {
|
||||||
document.body.innerHTML = '';
|
document.body.innerHTML = '';
|
||||||
const c = document.createElement('div');
|
const c = document.createElement('div');
|
||||||
document.body.appendChild(c);
|
document.body.appendChild(c);
|
||||||
const ed = new MarkdownEditor(c, { value: 'hello world' });
|
const ed = new MarkdownEditor(c, { value: 'hello world' });
|
||||||
const handler = jest.fn();
|
const handler = jest.fn();
|
||||||
ed.on('selectionChange', handler);
|
ed.on('selectionChange', handler);
|
||||||
|
// Fire keyup after setting selection — selectionChange is emitted synchronously in the handler
|
||||||
ed.textarea.setSelectionRange(0, 5);
|
ed.textarea.setSelectionRange(0, 5);
|
||||||
// selectionChange 通过 interval 检测,等 300ms
|
ed.textarea.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowRight', bubbles: true }));
|
||||||
setTimeout(() => {
|
expect(handler).toHaveBeenCalled();
|
||||||
expect(handler).toHaveBeenCalled();
|
expect(handler.mock.calls[0][0]).toHaveProperty('start', 0);
|
||||||
expect(handler.mock.calls[0][0]).toHaveProperty('start', 0);
|
expect(handler.mock.calls[0][0]).toHaveProperty('end', 5);
|
||||||
expect(handler.mock.calls[0][0]).toHaveProperty('end', 5);
|
expect(handler.mock.calls[0][0]).toHaveProperty('text', 'hello');
|
||||||
expect(handler.mock.calls[0][0]).toHaveProperty('text', 'hello');
|
ed.destroy();
|
||||||
ed.destroy();
|
|
||||||
done();
|
|
||||||
}, 800);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('cursorMove 事件在按键后触发', () => {
|
test('cursorMove 事件在按键后触发', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user