mirror of
https://github.com/ether/etherpad-lite.git
synced 2026-07-17 16:47:05 +00:00
fix: preserve line attributes on neighboring lines during drag-and-drop (#7461)
* fix: preserve line attributes on neighboring lines during drag-and-drop On Chrome and Safari, when dragging a line in a list, the browser's contentEditable engine merges the removed line with its neighbor, corrupting the neighbor's line attributes (e.g., changing its list type). The drop handler now captures line attributes of the lines adjacent to the dragged content before the browser processes the drop. After incorporateUserChanges runs, it checks if those attributes were corrupted and restores them. Note: this bug cannot be reproduced in Playwright's headless browsers (DnD in contentEditable iframes isn't supported), so manual testing with Chrome/Safari is required. Fixes #3120 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: also save/restore lines with no list type during DnD Lines with no list attribute can get corrupted to inherit the dragged line's list type. Now saves all adjacent lines (including those with no list type) and properly removes corrupted attributes when restoring. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: null-safety for atKey in drop handler Guard against atKey returning null for dynamically inserted nodes that aren't in the rep.lines index. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
72dc94f1b9
commit
bd73785431
1 changed files with 57 additions and 0 deletions
|
|
@ -3337,6 +3337,11 @@ function Ace2Inner(editorInfo, cssManagers) {
|
|||
// in order to make content be observed by incorporateUserChanges() (see
|
||||
// observeSuspiciousNodes() for more info)
|
||||
const selection = getSelection();
|
||||
// Capture line attributes of neighboring lines before the browser processes
|
||||
// the drop. Chrome/Safari can corrupt these attributes when merging lines
|
||||
// after removing the dragged content.
|
||||
// See https://github.com/ether/etherpad-lite/issues/3120
|
||||
let savedLineAttrs: {lineNum: number, listType: string}[] | null = null;
|
||||
if (selection) {
|
||||
const firstLineSelected = topLevel(selection.startPoint.node);
|
||||
const lastLineSelected = topLevel(selection.endPoint.node);
|
||||
|
|
@ -3346,6 +3351,32 @@ function Ace2Inner(editorInfo, cssManagers) {
|
|||
|
||||
const neighbor = lineBeforeSelection || lineAfterSelection;
|
||||
neighbor.appendChild(targetDoc.createElement('style'));
|
||||
|
||||
// Save attributes of lines adjacent to the dragged content
|
||||
savedLineAttrs = [];
|
||||
const startEntry = firstLineSelected.id && rep.lines.atKey(firstLineSelected.id);
|
||||
const endEntry = lastLineSelected.id && rep.lines.atKey(lastLineSelected.id);
|
||||
if (!startEntry || !endEntry) {
|
||||
// Can't determine line numbers — skip attribute saving
|
||||
savedLineAttrs = null;
|
||||
}
|
||||
const startLine = startEntry ? rep.lines.indexOfEntry(startEntry) : -1;
|
||||
const endLine = endEntry ? rep.lines.indexOfEntry(endEntry) : -1;
|
||||
// Save attributes of lines adjacent to the selection, including lines
|
||||
// with NO list type (empty string). A line with no list type can get
|
||||
// corrupted to inherit the dragged line's type during browser merging.
|
||||
if (savedLineAttrs && endLine >= 0 && endLine + 1 < rep.lines.length()) {
|
||||
savedLineAttrs.push({
|
||||
lineNum: endLine + 1,
|
||||
listType: getLineListType(endLine + 1) || '',
|
||||
});
|
||||
}
|
||||
if (savedLineAttrs && startLine > 0) {
|
||||
savedLineAttrs.push({
|
||||
lineNum: startLine - 1,
|
||||
listType: getLineListType(startLine - 1) || '',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Call drop hook
|
||||
|
|
@ -3355,6 +3386,32 @@ function Ace2Inner(editorInfo, cssManagers) {
|
|||
documentAttributeManager,
|
||||
e,
|
||||
});
|
||||
|
||||
// After the browser processes the drop and incorporateUserChanges runs,
|
||||
// restore any corrupted line attributes.
|
||||
if (savedLineAttrs && savedLineAttrs.length > 0) {
|
||||
scheduler.setTimeout(() => {
|
||||
inCallStackIfNecessary('dropRestore', () => {
|
||||
incorporateUserChanges();
|
||||
// Check if any saved line attributes were corrupted
|
||||
for (const {lineNum, listType} of savedLineAttrs!) {
|
||||
if (lineNum < rep.lines.length()) {
|
||||
const currentType = getLineListType(lineNum) || '';
|
||||
if (currentType !== listType) {
|
||||
if (listType) {
|
||||
// Restore the original list attribute
|
||||
documentAttributeManager.setAttributeOnLine(lineNum, 'list', listType);
|
||||
} else {
|
||||
// Line should have no list type — remove the corrupted one
|
||||
documentAttributeManager.removeAttributeOnLine(lineNum, 'list');
|
||||
documentAttributeManager.removeAttributeOnLine(lineNum, 'start');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
|
||||
$(targetDoc.documentElement).on('compositionstart', () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue