fix: 浮动工具栏坐标修正 — 转为 editorPane 局部坐标,紧贴选中文字
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:20:14 +08:00
parent d96f124a8f
commit a0bf5f3032
8 changed files with 60 additions and 58 deletions
+14 -14
View File
@@ -3712,31 +3712,31 @@ class MarkdownEditor {
} }
if (!this._floatingToolbar || !this._floatingToolbar.parentNode) if (!this._floatingToolbar || !this._floatingToolbar.parentNode)
this._buildFloatingToolbar(); this._buildFloatingToolbar();
// Position the toolbar near the selection // Position the toolbar near the selection (toolbar is absolute inside editorPane)
const rect = ta.getBoundingClientRect(); const taRect = ta.getBoundingClientRect();
const paneRect = this.editorPane.getBoundingClientRect();
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 lineHeight = parseInt(getComputedStyle(ta).lineHeight, 10) || 22; const lineHeight = parseInt(getComputedStyle(ta).lineHeight, 10) || 22;
const paddingTop = parseInt(getComputedStyle(ta).paddingTop, 10) || 16; const paddingTop = parseInt(getComputedStyle(ta).paddingTop, 10) || 16;
const scrollOffset = ta.scrollTop; const scrollOffset = ta.scrollTop;
// Line top relative to textarea content area // Line top relative to viewport
const lineTop = rect.top + paddingTop + currentLine * lineHeight - scrollOffset; const lineViewportTop = taRect.top + paddingTop + currentLine * lineHeight - scrollOffset;
// Convert to editorPane-local coordinates
const lineLocalTop = lineViewportTop - paneRect.top;
const toolbarHeight = 36; const toolbarHeight = 36;
const gap = 4; const gap = 4;
// Try above first // Try above first
let top = lineTop - toolbarHeight - gap; let top = lineLocalTop - toolbarHeight - gap;
// If not enough room above, place below the selection // If not enough room above, place below the selection
if (top < rect.top + gap) { if (top < gap) {
top = lineTop + lineHeight + gap; top = lineLocalTop + lineHeight + gap;
} }
// Center horizontally near the middle of selection // Horizontal: center near selection, in pane-local coords
let left = rect.left + (ta.selectionDirection !== 'backward' ? (ta.selectionEnd - ta.selectionStart) / 2 : 0) * 8 + 10; const selMidX = taRect.left + (ta.selectionEnd - ta.selectionStart) / 2 * (taRect.width / Math.max(1, ta.value.length || 1));
if (left < rect.left) let left = selMidX - paneRect.left - 80; // ~half toolbar width (5*28 + gaps)
left = rect.left + 4; left = Math.max(4, Math.min(left, paneRect.width - 200));
const maxLeft = rect.right - 200;
if (left > maxLeft)
left = maxLeft;
this._floatingToolbar.style.top = top + 'px'; this._floatingToolbar.style.top = top + 'px';
this._floatingToolbar.style.left = left + 'px'; this._floatingToolbar.style.left = left + 'px';
this._floatingToolbar.classList.add('me-visible'); this._floatingToolbar.classList.add('me-visible');
+1 -1
View File
File diff suppressed because one or more lines are too long
+14 -14
View File
@@ -3708,31 +3708,31 @@ class MarkdownEditor {
} }
if (!this._floatingToolbar || !this._floatingToolbar.parentNode) if (!this._floatingToolbar || !this._floatingToolbar.parentNode)
this._buildFloatingToolbar(); this._buildFloatingToolbar();
// Position the toolbar near the selection // Position the toolbar near the selection (toolbar is absolute inside editorPane)
const rect = ta.getBoundingClientRect(); const taRect = ta.getBoundingClientRect();
const paneRect = this.editorPane.getBoundingClientRect();
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 lineHeight = parseInt(getComputedStyle(ta).lineHeight, 10) || 22; const lineHeight = parseInt(getComputedStyle(ta).lineHeight, 10) || 22;
const paddingTop = parseInt(getComputedStyle(ta).paddingTop, 10) || 16; const paddingTop = parseInt(getComputedStyle(ta).paddingTop, 10) || 16;
const scrollOffset = ta.scrollTop; const scrollOffset = ta.scrollTop;
// Line top relative to textarea content area // Line top relative to viewport
const lineTop = rect.top + paddingTop + currentLine * lineHeight - scrollOffset; const lineViewportTop = taRect.top + paddingTop + currentLine * lineHeight - scrollOffset;
// Convert to editorPane-local coordinates
const lineLocalTop = lineViewportTop - paneRect.top;
const toolbarHeight = 36; const toolbarHeight = 36;
const gap = 4; const gap = 4;
// Try above first // Try above first
let top = lineTop - toolbarHeight - gap; let top = lineLocalTop - toolbarHeight - gap;
// If not enough room above, place below the selection // If not enough room above, place below the selection
if (top < rect.top + gap) { if (top < gap) {
top = lineTop + lineHeight + gap; top = lineLocalTop + lineHeight + gap;
} }
// Center horizontally near the middle of selection // Horizontal: center near selection, in pane-local coords
let left = rect.left + (ta.selectionDirection !== 'backward' ? (ta.selectionEnd - ta.selectionStart) / 2 : 0) * 8 + 10; const selMidX = taRect.left + (ta.selectionEnd - ta.selectionStart) / 2 * (taRect.width / Math.max(1, ta.value.length || 1));
if (left < rect.left) let left = selMidX - paneRect.left - 80; // ~half toolbar width (5*28 + gaps)
left = rect.left + 4; left = Math.max(4, Math.min(left, paneRect.width - 200));
const maxLeft = rect.right - 200;
if (left > maxLeft)
left = maxLeft;
this._floatingToolbar.style.top = top + 'px'; this._floatingToolbar.style.top = top + 'px';
this._floatingToolbar.style.left = left + 'px'; this._floatingToolbar.style.left = left + 'px';
this._floatingToolbar.classList.add('me-visible'); this._floatingToolbar.classList.add('me-visible');
+1 -1
View File
File diff suppressed because one or more lines are too long
+14 -14
View File
@@ -3714,31 +3714,31 @@ 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 // Position the toolbar near the selection (toolbar is absolute inside editorPane)
const rect = ta.getBoundingClientRect(); const taRect = ta.getBoundingClientRect();
const paneRect = this.editorPane.getBoundingClientRect();
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 lineHeight = parseInt(getComputedStyle(ta).lineHeight, 10) || 22; const lineHeight = parseInt(getComputedStyle(ta).lineHeight, 10) || 22;
const paddingTop = parseInt(getComputedStyle(ta).paddingTop, 10) || 16; const paddingTop = parseInt(getComputedStyle(ta).paddingTop, 10) || 16;
const scrollOffset = ta.scrollTop; const scrollOffset = ta.scrollTop;
// Line top relative to textarea content area // Line top relative to viewport
const lineTop = rect.top + paddingTop + currentLine * lineHeight - scrollOffset; const lineViewportTop = taRect.top + paddingTop + currentLine * lineHeight - scrollOffset;
// Convert to editorPane-local coordinates
const lineLocalTop = lineViewportTop - paneRect.top;
const toolbarHeight = 36; const toolbarHeight = 36;
const gap = 4; const gap = 4;
// Try above first // Try above first
let top = lineTop - toolbarHeight - gap; let top = lineLocalTop - toolbarHeight - gap;
// If not enough room above, place below the selection // If not enough room above, place below the selection
if (top < rect.top + gap) { if (top < gap) {
top = lineTop + lineHeight + gap; top = lineLocalTop + lineHeight + gap;
} }
// Center horizontally near the middle of selection // Horizontal: center near selection, in pane-local coords
let left = rect.left + (ta.selectionDirection !== 'backward' ? (ta.selectionEnd - ta.selectionStart) / 2 : 0) * 8 + 10; const selMidX = taRect.left + (ta.selectionEnd - ta.selectionStart) / 2 * (taRect.width / Math.max(1, ta.value.length || 1));
if (left < rect.left) let left = selMidX - paneRect.left - 80; // ~half toolbar width (5*28 + gaps)
left = rect.left + 4; left = Math.max(4, Math.min(left, paneRect.width - 200));
const maxLeft = rect.right - 200;
if (left > maxLeft)
left = maxLeft;
this._floatingToolbar.style.top = top + 'px'; this._floatingToolbar.style.top = top + 'px';
this._floatingToolbar.style.left = left + 'px'; this._floatingToolbar.style.left = left + 'px';
this._floatingToolbar.classList.add('me-visible'); this._floatingToolbar.classList.add('me-visible');
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+14 -12
View File
@@ -728,8 +728,9 @@ export class MarkdownEditor {
if (!this._floatingToolbar || !this._floatingToolbar.parentNode) this._buildFloatingToolbar(); if (!this._floatingToolbar || !this._floatingToolbar.parentNode) this._buildFloatingToolbar();
// Position the toolbar near the selection // Position the toolbar near the selection (toolbar is absolute inside editorPane)
const rect = ta.getBoundingClientRect(); const taRect = ta.getBoundingClientRect();
const paneRect = this.editorPane.getBoundingClientRect();
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;
@@ -737,23 +738,24 @@ export class MarkdownEditor {
const paddingTop = parseInt(getComputedStyle(ta).paddingTop, 10) || 16; const paddingTop = parseInt(getComputedStyle(ta).paddingTop, 10) || 16;
const scrollOffset = ta.scrollTop; const scrollOffset = ta.scrollTop;
// Line top relative to textarea content area // Line top relative to viewport
const lineTop = rect.top + paddingTop + currentLine * lineHeight - scrollOffset; const lineViewportTop = taRect.top + paddingTop + currentLine * lineHeight - scrollOffset;
// Convert to editorPane-local coordinates
const lineLocalTop = lineViewportTop - paneRect.top;
const toolbarHeight = 36; const toolbarHeight = 36;
const gap = 4; const gap = 4;
// Try above first // Try above first
let top = lineTop - toolbarHeight - gap; let top = lineLocalTop - toolbarHeight - gap;
// If not enough room above, place below the selection // If not enough room above, place below the selection
if (top < rect.top + gap) { if (top < gap) {
top = lineTop + lineHeight + gap; top = lineLocalTop + lineHeight + gap;
} }
// Center horizontally near the middle of selection // Horizontal: center near selection, in pane-local coords
let left = rect.left + (ta.selectionDirection !== 'backward' ? (ta.selectionEnd - ta.selectionStart) / 2 : 0) * 8 + 10; const selMidX = taRect.left + (ta.selectionEnd - ta.selectionStart) / 2 * (taRect.width / Math.max(1, ta.value.length || 1));
if (left < rect.left) left = rect.left + 4; let left = selMidX - paneRect.left - 80; // ~half toolbar width (5*28 + gaps)
const maxLeft = rect.right - 200; left = Math.max(4, Math.min(left, paneRect.width - 200));
if (left > maxLeft) left = maxLeft;
this._floatingToolbar!.style.top = top + 'px'; this._floatingToolbar!.style.top = top + 'px';
this._floatingToolbar!.style.left = left + 'px'; this._floatingToolbar!.style.left = left + 'px';