Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions src-tauri/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}

Expand All @@ -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<dyn std::error::Error>> {
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) => {
Expand Down
65 changes: 65 additions & 0 deletions src/features/editor/lib/cursorCentering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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,
Expand Down
7 changes: 6 additions & 1 deletion src/features/editor/lib/cursorCenteringConfig.ts
Original file line number Diff line number Diff line change
@@ -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,
}

Expand All @@ -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 = {
Expand Down
Loading