Something went wrong. Try again.
A collaborative coding-agent orchestrator for atproto radl.app
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465// Where an image goes, decided when the reader dropped it rather than when the network came back.//// An upload takes as long as it takes, and the editor stays editable while it runs — that is the// point of uploading in the background. So the caret is not a place: by the time the blob and its// record are written, the reader may have typed three lines above the drop, taken the caret// somewhere else, or dropped a second file. `MarkdownEditor` therefore fixes an insertion point at// the moment of the drop, paste or pick and carries it forward across everything that happened// since. Its CodeMirror half gets that from CodeMirror, which maps positions through its own change// sets; its textarea half has no change set and gets it from `remap()` below, against the two// strings.//// Both rules are here, pure, so they can be held without a DOM.
import { imageMarkdownSpansIn } from '@radial/core'
/** * Where `pos` in `before` ends up in `after`, given that the reader edited one stretch of it. * * A textarea reports a new string, not a change, so the edit is recovered as the one span between * the common prefix and the common suffix — which is what a person typing, pasting or deleting in * one place actually did, and the worst a coarser guess costs is an image landing at the end of an * edit rather than inside it. * * The position associates FORWARD, matching the `assoc: 1` the CodeMirror half maps with: text * inserted exactly at the insertion point belongs before the image, and the image this component * just wrote leaves the point after itself, ready for the next file of the same drop. */export function remap(before: string, after: string, pos: number): number { const at = Math.max(0, Math.min(pos, before.length)) if (before === after) return at const limit = Math.min(before.length, after.length) let head = 0 while (head < limit && before[head] === after[head]) head += 1 let tail = 0 while ( tail < limit - head && before[before.length - 1 - tail] === after[after.length - 1 - tail] ) { tail += 1 } // After the edit, so it moves by what the edit added or removed; before it, so it does not move; // inside it, so it goes to where the edit ends — the text it used to sit in is gone. if (at >= before.length - tail) return at + (after.length - before.length) if (at <= head) return at return after.length - tail}
/** * If `from`–`to` is parked strictly inside a Radial image, the offset just past that image. * * The editor selects each new image's alt text so the reader can correct the filename it was guessed * from — and typing over that selection leaves a collapsed caret between `: number | undefined { for (const { from: start, to: end } of imageMarkdownSpansIn(doc)) { if ((from > start && from < end) || (to > start && to < end)) return end } return undefined}