diff --git a/src-tauri/src/window.rs b/src-tauri/src/window.rs index 46fe023..12edac0 100644 --- a/src-tauri/src/window.rs +++ b/src-tauri/src/window.rs @@ -31,9 +31,13 @@ const RESTORE_ENABLED_KEY: &str = "windowStateRestoreEnabled"; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct WindowGeometry { + /// Logical x-coordinate of the window's top-left corner. x: f64, + /// Logical y-coordinate of the window's top-left corner. y: f64, + /// Logical width of the window's content area. width: f64, + /// Logical height of the window's content area. height: f64, } @@ -48,14 +52,17 @@ struct WindowGeometry { /// The window is created with `resizable(false)` and /// `maximizable(false)` so the splash screen cannot be resized. /// The frontend unlocks these constraints once the splash has faded -/// out. +/// out. The built-in Tauri drag-and-drop handler is disabled via +/// `disable_drag_drop_handler()` so the frontend can manage file +/// drop events with its own logic. pub fn create_main_window(app: &AppHandle) -> Result<(), Box> { let geometry = read_saved_geometry(app); let mut builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default()) .title(WINDOW_TITLE) .resizable(false) - .maximizable(false); + .maximizable(false) + .disable_drag_drop_handler(); match geometry { Some(geo) => { diff --git a/src/features/editor/lib/cursorCentering.ts b/src/features/editor/lib/cursorCentering.ts index 2b8f5d8..6086207 100644 --- a/src/features/editor/lib/cursorCentering.ts +++ b/src/features/editor/lib/cursorCentering.ts @@ -96,6 +96,12 @@ let docChangedInLastTr = false * the document actually changed (i.e. the user typed something). * - The scroll offset is clamped to `[0, maxScroll]` so that short * documents keep the cursor near the top (no centre-jump). + * + * **Drag-and-drop scroll fix:** + * Additionally, a DOM-level drag/drop listener captures the scroll + * container's `scrollTop` at drag-start and aggressively restores + * it after drop to prevent the browser from jumping to the old + * cursor position. */ export const cursorCenteringExtension = createExtension({ key: 'cursorCentering', @@ -110,6 +116,65 @@ export const cursorCenteringExtension = createExtension({ return {} }, }, + view(editorView) { + const container = findScrollContainer(editorView.dom) + let savedScrollTop = 0 + let guardScrollUntil = 0 + + const onDragStart = () => { + if (container) { + savedScrollTop = container.scrollTop + } + } + + const onDrop = () => { + if (!container) return + // Do NOT update savedScrollTop here — use the value from + // dragstart, which captures the scroll position BEFORE + // auto-scroll or selection-triggered scroll during drag. + + // Guard against scroll changes for the next 500ms. + guardScrollUntil = Date.now() + 500 + + const restore = () => { + if (Date.now() < guardScrollUntil) { + container.scrollTop = savedScrollTop + } + } + requestAnimationFrame(restore) + setTimeout(restore, 0) + setTimeout(restore, 16) + setTimeout(restore, 50) + setTimeout(restore, 100) + setTimeout(restore, 200) + setTimeout(restore, 300) + setTimeout(restore, 400) + } + + // Intercept scroll events on the container during the guard window + const onScroll = () => { + if ( + container && + Date.now() < guardScrollUntil && + container.scrollTop !== savedScrollTop + ) { + container.scrollTop = savedScrollTop + } + } + + // Use capture phase to intercept before any other handlers + document.addEventListener('dragstart', onDragStart, true) + editorView.dom.addEventListener('drop', onDrop, true) + container?.addEventListener('scroll', onScroll, { passive: false }) + + return { + destroy() { + document.removeEventListener('dragstart', onDragStart, true) + editorView.dom.removeEventListener('drop', onDrop, true) + container?.removeEventListener('scroll', onScroll) + }, + } + }, props: { scrollMargin: { top: 100, diff --git a/src/features/editor/lib/cursorCenteringConfig.ts b/src/features/editor/lib/cursorCenteringConfig.ts index a9f5205..49fb8a5 100644 --- a/src/features/editor/lib/cursorCenteringConfig.ts +++ b/src/features/editor/lib/cursorCenteringConfig.ts @@ -1,6 +1,8 @@ /** Default values used when no persisted setting exists. */ export const DEFAULT_CURSOR_CENTERING = { + /** Whether cursor centering is active by default. */ enabled: true, + /** Vertical position ratio (0 = top, 1 = bottom) at which the cursor is kept during typing. */ targetRatio: 0.6, } @@ -15,7 +17,10 @@ export const DEFAULT_CURSOR_CENTERING = { * Updated via the {@link useCursorCentering} hook, which also * persists the values to `configStore`. */ -export const cursorCenteringConfig = { ...DEFAULT_CURSOR_CENTERING } +export const cursorCenteringConfig: { + enabled: boolean + targetRatio: number +} = { ...DEFAULT_CURSOR_CENTERING } /** Store key names for persistence via `configStore`. */ export const CURSOR_CENTERING_STORE_KEYS = {