Lossless Changelog
product

Drop Gate — every image asks where it should go

Image Gin now intercepts every image you drag or paste into a note and pops a single confirmation modal: vault, ImageKit, or Imgur? Built for writers who handle private client imagery and don't want a default-on third-party uploader silently shipping screenshots to a public CDN. Default is the vault. ImageKit is for less-public material that still wants a CDN. Imgur is there when something is genuinely shareable.

Michael StatonMusic Editoraugmented with Claude Code on Claude Opus 4.7 (1M context)

Why Care?

We use Obsidian to write about companies. The artifacts those companies share with us — growth charts, dashboard screenshots, photos of a whiteboard — are not always meant to leave a private context. The default failure mode used to be silent: drag a file in, and the bytes go where Obsidian (or some default-on third-party uploader plugin) decides without asking. That's fine for a recipe. It's a phone call we don't want to make for anything client-sensitive.

The Drop Gate puts a deliberate decision between the drop and the destination. One modal, three destinations:

  • Vault attachments — Obsidian's standard private behavior. Bytes stay in the vault.

  • ImageKit — your own private CDN. We use it when material isn't strictly secret but we don't want to host it forever ourselves. This is our default for less-public information.

  • Imgur — anonymous public upload. Free, fast, durable. Use when something is genuinely shareable.

Off by default. Toggle it on in Settings → Image Gin → Drag-Drop / Paste Confirmation Gate.

What's New?

A new feature inside Image Gin, layered alongside the existing Recraft / Magnific / Ideogram / ImageKit pipelines. No new plugin to install — same plugin, a new section in settings.

  • Intercepts every image drop and paste in a markdown view via Obsidian's editor-drop and editor-paste workspace events.

  • The modal shows the dropped file(s) (name, size, type) and a radio group of all three destinations. Configured destinations are selectable; un-configured ones are visible-but-disabled with a hint pointing at the right setting. You can see what's possible even before you've turned anything on.

  • Three destinations behind a small DropGateDestination interface so a fourth (S3, Bunny, Cloudinary, an internal SharePoint of doom) is a class plus a settings panel:

    • VaultDestination — re-dispatches a synthetic event copy into Obsidian's internal clipboardManager so the default attachment-handling code runs cleanly. Falls back to Vault.createBinary + FileManager.generateMarkdownLink if clipboardManager is unavailable.

    • ImageKitDestination — wraps Image Gin's existing ImageKitService. Same multipart-upload pipeline that already powers "Convert Local Images to Remote." A drop-gate-specific folder setting (dropGate.imageKitFolder) overrides the main upload folder when set, so ad-hoc dropped images can land somewhere different from generated images.

    • ImgurDestination — anonymous client-ID upload to api.imgur.com/3/image. Free, no account.

  • Two policy modes: always-confirm (modal on every image, the default) and external-only (vault drops fall through silently; modal only appears if at least one external destination is enabled).

  • Per-session "remember choice" checkbox in the modal — skips the modal for subsequent drops in the current note. Resets on active-leaf-change. Never persists across Obsidian restarts.

  • Conflict detection. If the third-party obsidian-imgur-plugin is also enabled, both plugins handle every drop and the user gets two modals back-to-back — preventDefault() blocks the browser default, not other plugins. We surface a persistent Notice on plugin load telling the user to disable the other one and configure Imgur from inside Image Gin.

  • Reset command in the palette: "Drop Gate: Reset session-remembered destination."

How it feels

Drop a screenshot into a note. Modal opens, wide enough to read (~720px). Three destinations visible — the ones you've configured are selectable; the ones you haven't carry a one-line nudge to settings. Pick ImageKit. Modal closes. Image uploads. Markdown link lands at your cursor. Cursor moves to the next line. Editor refocuses. Console says [image-gin/drop-gate] ImageKit upload → https://ik.imagekit.io/.... You keep typing.

If you'd rather not be asked every time, set the policy to external-only and your vault drops happen silently — only ImageKit / Imgur destinations bring up the modal.

The arc

We started with the third-party obsidian-imgur-plugin by Kirill Gavrilov. Its drag-confirm pattern is the right idea but biased the wrong way for our workflow: it asks "should we upload to Imgur or paste locally?" The answer for client-sensitive imagery is "neither, until you tell me where." We wanted vault-first, third-party host second, and only with deliberate confirmation each time.

We sketched a standalone plugin, then folded it back into Image Gin once it was clear that:

  • Image Gin already owns the ImageKit upload pipeline (the most useful private destination).

  • The DropGateDestination interface is small enough that it doesn't justify its own plugin; it sits naturally next to the existing Recraft / Magnific / Ideogram services.

  • One settings tab is easier than two.

So this is a feature add inside Image Gin, not a new sibling plugin. The ImageKitDestination is a thin adapter over ImageKitService.uploadFile() — no duplicated upload logic.

Architecture

src/
├── handlers/DropGateHandlers.ts       editor-drop + editor-paste; re-entry guard;
│                                      synchronous preventDefault; cursor capture
├── modals/DropGateModal.ts            deferred-promise modal; modalEl-attached
│                                      for proper widening; renders all destinations
│                                      with disabled-state for un-configured ones
├── destinations/
│   ├── types.ts                       DropGateDestination + DropGateContext
│   │                                  (carries the captured insertPos)
│   ├── VaultDestination.ts            clipboardManager re-dispatch + explicit-API fallback
│   ├── ImageKitDestination.ts         wraps existing ImageKitService.uploadFile
│   └── ImgurDestination.ts            anonymous client-ID upload
├── utils/dropGateEvents.ts            DragEventCopy, PasteEventCopy (re-entry guards),
│                                      allFilesAreImages, fileNameFor
├── settings/settings.ts               +dropGate +imgur blocks; settings-tab UI
└── styles/drop-gate.css               modal styles, scoped to .image-gin-drop-gate-modal

main.ts grew by ~40 lines: instantiate three destinations, wire two workspace.on(...) calls, register a reset command, run the conflict check.

Load-bearing details

Three implementation details we expect to forget and want findable when we do:

TS
// 1. Re-entry guard. Without this, our own VaultDestination's
//    re-dispatch loops back through the handler. Infinite loop.
if (e instanceof DragEventCopy) return

// 2. Synchronous preventDefault — must come BEFORE any await,
//    otherwise the browser already gave up on cancelling the default.
e.preventDefault()

// 3. Capture cursor synchronously, before awaiting the modal.
//    By the time the user picks a destination, editor.getCursor()
//    is unreliable — focus may have drifted. We pass insertPos
//    through DropGateContext and use editor.replaceRange(md, pos)
//    instead of editor.replaceSelection(md).
const insertPos = editor.getCursor()

The vault fall-through uses an undocumented internal: view.currentMode.clipboardManager.handleDrop(DragEventCopy.create(e, files)). It's not in obsidian.d.ts but stable in practice; we cast through unknown and fall back to Vault.createBinary + FileManager.generateMarkdownLink if the surface ever disappears.

The editor-drop callback signature in obsidian.d.ts is (evt: DragEvent, editor: Editor, info: MarkdownView | MarkdownFileInfo) => any. MarkdownFileInfo is the canvas / non-markdown variant; we narrow with info instanceof MarkdownView and bail on canvas drops. Canvas support is a follow-up.

Settings surface

Two new sections in the Image Gin settings tab, both default-off:

SectionSettingDefault
Drop GateEnable drop gateOff
Drop GatePolicy modeAlways confirm
Drop GateDefault destinationVault attachments
Drop GateShow "remember for session" checkboxOn
Drop GateImageKit folder for drop-gate uploadsempty (inherits main)
ImgurEnable Imgur destinationOff
ImgurImgur client IDempty

ImageKit's main settings (private key, endpoint, main upload folder) live in their existing section above — they pre-date this feature. The drop-gate uses them whenever imageKit.enabled is on.

What to test

  1. Toggle Drop Gate on. Drop a PNG into a markdown note → modal appears with all three destinations. Vault is pre-selected. Click Insert → image lands in vault attachments, markdown link inserted at cursor.

  2. Configure ImageKit and drop again → ImageKit row becomes selectable. Pick it → image uploads, markdown link with the ImageKit URL inserted.

  3. Configure Imgur (anonymous client ID) and drop again → Imgur row becomes selectable.

  4. Tick "Remember for this session," drop a second image in the same note → no modal, goes straight to your remembered destination.

  5. Switch to a different note → modal returns on next drop.

  6. Drop a non-image file → falls through to Obsidian's default behavior; modal does not appear.

  7. With the obsidian-imgur-plugin community plugin also enabled → you'll see a persistent Notice on Image Gin's load telling you to disable it. Disable it, reload, drops go through Image Gin only.

Reference

  • Plan: content-farm/context-v/plans/Image-Drop-Confirmation-Gate.md — design rationale, threat model, phasing.

  • Inspiration / interception pattern: gavvvr/obsidian-imgur-plugin (MIT). Disable it if you enable Image Gin's Drop Gate; both register editor-drop and you'll see two modals otherwise.

  • Modal widening mechanic: perplexed/context-v/issues/Widen-Modals-in-Obsidian-using-CSS.md — modalEl vs. contentEl. The 720px width and 88vh height only take effect because the class is on modalEl.

  • ImageKit upload pipeline: existing src/services/imagekitService.ts, reused via the ImageKitDestination adapter.

Next

  • Canvas support — MarkdownFileInfo rather than MarkdownView; needs a parallel handler.

  • Optional imagery_policy: frontmatter gate ("always confirm for this folder/file"), once we see whether always-on is friction-light enough not to need it.

  • Filename / preview thumbnail in the modal — currently shows name + size + type only.