image-gin

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.

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.mdmodalEl 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.