diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..08a0573 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,63 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.1.2] - 2026-04-25 +### Added +- This changelog and license. + +### Changed +- Default modifier key is now platform-aware: Windows and Linux default to Ctrl, + while macOS defaults to Alt/Option. +- Multi-select drag behavior is now more precise. Dragging a selected item sends + all selected items; dragging an unselected item sends only that item. +- Updated settings copy to describe the current platform defaults and the effect + of always intercepting file-tree drags. +- Reworked the README for a public audience, including current usage defaults, + manual install steps, Bun-based build commands, platform notes, and debugging + guidance. + +### Fixed +- Moved the default behavior suppression to take place after the drag capture + operations succeed to avoid null behavior. +- Release script now aborts if either required file (manifest.json, main.js) are missing, + or if the manifest doesn't have a valid minAppVersion. +- Drag icon handling no longer assumes `icon.png` is bundled. If no icon file is + present, the plugin falls back to a tiny embedded native image so external + drag still has a non-empty icon for macOS. + +### Removed +- Original hand-written main.js test script before running esbuild. + +## [0.1.1] - 2026-04-25 + +### Changed +- Default modifier key is now Ctrl (Cmd on macOS) instead of Alt. Holding Ctrl + during drag matches Windows Explorer's "force copy" semantic, so the file + drops cleanly without a "release modifier first" step. Alt remains + selectable in settings for users who prefer it. + +### Fixed +- TypeScript build no longer fails on `Electron.*` type references — proper + type imports replaced bare global references. +- `buildDragItem` return type now satisfies Electron's `Item` interface + (single-file drags include the required `file` field). + +## [0.1.0] - 2026-04-25 + +### Added +- Initial release. +- Drag any file from Obsidian's file explorer to external applications using + the OS's native drag-and-drop, instead of Obsidian's default + `obsidian://open?...` URI. +- Multi-select drag support — Ctrl/Cmd-click multiple files, then drag any + one to bring them all. +- Folder drag support (experimental). +- Configurable modifier key (Ctrl, Alt, Shift, or none). +- Optional debug logging to developer console. +- Bundled drag image using the Obsidian community brand black icon. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..62c0404 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Xander Berry + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c622904 --- /dev/null +++ b/README.md @@ -0,0 +1,115 @@ +# Drag Out + +Drag files from Obsidian's file explorer to other apps using native OS +drag-and-drop instead of dropping an `obsidian://` URL. + +This is useful for browser upload fields, email attachments, chat apps, file +managers, and other places that expect an actual file from your filesystem. + +**Status:** experimental, desktop only. Tested primarily on Windows so far; +macOS and Linux feedback is welcome. + +## What It Does + +Obsidian's file tree is a virtual UI. By default, dragging a note out of the +file tree gives other apps a link like `obsidian://open?vault=Vault&file=Note.md` +instead of the file itself. + +Drag Out intercepts file-tree drags when your configured modifier key is held +and uses Electron's `webContents.startDrag` API to expose the real on-disk file +path. Normal Obsidian drag behavior is left alone when the modifier is not held. + +## Installation + +Drag Out is not in the Obsidian community plugin marketplace yet. To install it +manually: + +1. Download the release assets: `main.js` and `manifest.json`. If the release + includes `icon.png`, download that too. +2. Open your vault folder. +3. Create this folder if it does not already exist: + +```text +.obsidian/plugins/drag-out/ +``` + +4. Put the downloaded files in that folder. +5. In Obsidian, go to **Settings > Community plugins > Installed plugins**, + refresh the plugin list, and enable **Drag Out**. + +If Restricted Mode is on, Obsidian will ask you to turn it off before enabling +community plugins. + +## Usage + +Hold the modifier key while dragging a file or folder from Obsidian's left +sidebar file explorer to another app. + +The default modifier is: + +- **Ctrl** on Windows and Linux +- **Option / Alt** on macOS + +You can change the modifier in **Settings > Drag Out**. Available options are +Ctrl/Cmd, Alt/Option, Shift, or None. + +Multi-select is supported: select multiple files in Obsidian's file explorer, +then modifier-drag one of the selected files. If you drag an unselected file +while other files are selected, only the file you dragged is sent. + +## Why A Modifier Key? + +Electron's external drag API takes over the current drag operation. That means +Obsidian's own file-tree drag behavior, such as moving notes between folders, +cannot run during the same drag. + +Using a modifier keeps normal Obsidian drags unchanged. If you always want file +drags to go to external apps, set the modifier to **None** in the plugin +settings. + +## Platform Notes + +- **Windows:** tested most heavily. Holding the modifier while dragging starts + an external file drag, so Obsidian's internal drop targets will not respond + during that drag. This is related to Electron's external drag behavior + ([Electron #7118](https://github.com/electron/electron/issues/7118)). +- **macOS:** expected to work, but needs more real-world testing. The default + modifier is Option/Alt to avoid conflicting with common Cmd interactions. +- **Linux:** expected to work where Obsidian's Electron drag API is available, + but currently less tested than Windows. +- **Mobile:** not supported. The plugin is marked `isDesktopOnly`. +- **Cloud placeholders:** files that are not hydrated locally yet, such as + OneDrive placeholders, may need to download before the target app can use + them. + +## Building From Source + +This repo uses Bun for its scripts. + +```bash +bun install +bun run build +``` + +For development watch mode: + +```bash +bun run dev +``` + +The build reads `main.ts` and writes the bundled plugin entrypoint to +`main.js`. `manifest.json` is maintained separately. + +## Debugging + +Enable **Debug logging** in the plugin settings, then open Obsidian's developer +console with `Ctrl+Shift+I` and try a drag. A successful external drag logs +the resolved filesystem paths. + +If you see `Drag Out: could not access Electron's webContents`, please report +your Obsidian version, operating system, and whether you installed from a +release or built from source. + +## License + +MIT diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..e85917e --- /dev/null +++ b/bun.lock @@ -0,0 +1,247 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "obsidian-drag-out", + "devDependencies": { + "@types/node": "^20.0.0", + "builtin-modules": "3.3.0", + "electron": "^39.0.0", + "esbuild": "0.24.0", + "obsidian": "^1.4.11", + "tslib": "2.7.0", + "typescript": "5.5.4", + }, + }, + }, + "packages": { + "@codemirror/state": ["@codemirror/state@6.5.0", "", { "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } }, "sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw=="], + + "@codemirror/view": ["@codemirror/view@6.38.6", "", { "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw=="], + + "@electron/get": ["@electron/get@2.0.3", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.24.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.24.0", "", { "os": "android", "cpu": "arm" }, "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.24.0", "", { "os": "android", "cpu": "arm64" }, "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.24.0", "", { "os": "android", "cpu": "x64" }, "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.24.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.24.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.24.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.24.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.24.0", "", { "os": "linux", "cpu": "arm" }, "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.24.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.24.0", "", { "os": "linux", "cpu": "ia32" }, "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.24.0", "", { "os": "linux", "cpu": "none" }, "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.24.0", "", { "os": "linux", "cpu": "none" }, "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.24.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.24.0", "", { "os": "linux", "cpu": "none" }, "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.24.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.24.0", "", { "os": "linux", "cpu": "x64" }, "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.24.0", "", { "os": "none", "cpu": "x64" }, "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.24.0", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.24.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.24.0", "", { "os": "sunos", "cpu": "x64" }, "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.24.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.24.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.24.0", "", { "os": "win32", "cpu": "x64" }, "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA=="], + + "@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.2", "", {}, "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="], + + "@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="], + + "@szmarczak/http-timer": ["@szmarczak/http-timer@4.0.6", "", { "dependencies": { "defer-to-connect": "^2.0.0" } }, "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w=="], + + "@types/cacheable-request": ["@types/cacheable-request@6.0.3", "", { "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", "@types/node": "*", "@types/responselike": "^1.0.0" } }, "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw=="], + + "@types/codemirror": ["@types/codemirror@5.60.8", "", { "dependencies": { "@types/tern": "*" } }, "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/http-cache-semantics": ["@types/http-cache-semantics@4.2.0", "", {}, "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q=="], + + "@types/keyv": ["@types/keyv@3.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg=="], + + "@types/node": ["@types/node@20.19.39", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw=="], + + "@types/responselike": ["@types/responselike@1.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw=="], + + "@types/tern": ["@types/tern@0.23.9", "", { "dependencies": { "@types/estree": "*" } }, "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw=="], + + "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="], + + "boolean": ["boolean@3.2.0", "", {}, "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw=="], + + "buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], + + "builtin-modules": ["builtin-modules@3.3.0", "", {}, "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw=="], + + "cacheable-lookup": ["cacheable-lookup@5.0.4", "", {}, "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="], + + "cacheable-request": ["cacheable-request@7.0.4", "", { "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", "http-cache-semantics": "^4.0.0", "keyv": "^4.0.0", "lowercase-keys": "^2.0.0", "normalize-url": "^6.0.1", "responselike": "^2.0.0" } }, "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg=="], + + "clone-response": ["clone-response@1.0.3", "", { "dependencies": { "mimic-response": "^1.0.0" } }, "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA=="], + + "crelt": ["crelt@1.0.6", "", {}, "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], + + "defer-to-connect": ["defer-to-connect@2.0.1", "", {}, "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg=="], + + "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], + + "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], + + "detect-node": ["detect-node@2.1.0", "", {}, "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="], + + "electron": ["electron@39.8.9", "", { "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^22.7.7", "extract-zip": "^2.0.1" }, "bin": { "electron": "cli.js" } }, "sha512-0zgNu0MmWR/kzFm+B+LlvpLOX8l3wt1z+BJIYv2Uxc2bEqWBT3jNsX1IhPVP5OJijjZO5ewCOIy0n/bsfJfjdw=="], + + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + + "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es6-error": ["es6-error@4.1.1", "", {}, "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="], + + "esbuild": ["esbuild@0.24.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.24.0", "@esbuild/android-arm": "0.24.0", "@esbuild/android-arm64": "0.24.0", "@esbuild/android-x64": "0.24.0", "@esbuild/darwin-arm64": "0.24.0", "@esbuild/darwin-x64": "0.24.0", "@esbuild/freebsd-arm64": "0.24.0", "@esbuild/freebsd-x64": "0.24.0", "@esbuild/linux-arm": "0.24.0", "@esbuild/linux-arm64": "0.24.0", "@esbuild/linux-ia32": "0.24.0", "@esbuild/linux-loong64": "0.24.0", "@esbuild/linux-mips64el": "0.24.0", "@esbuild/linux-ppc64": "0.24.0", "@esbuild/linux-riscv64": "0.24.0", "@esbuild/linux-s390x": "0.24.0", "@esbuild/linux-x64": "0.24.0", "@esbuild/netbsd-x64": "0.24.0", "@esbuild/openbsd-arm64": "0.24.0", "@esbuild/openbsd-x64": "0.24.0", "@esbuild/sunos-x64": "0.24.0", "@esbuild/win32-arm64": "0.24.0", "@esbuild/win32-ia32": "0.24.0", "@esbuild/win32-x64": "0.24.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="], + + "fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="], + + "fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], + + "get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="], + + "global-agent": ["global-agent@3.0.0", "", { "dependencies": { "boolean": "^3.0.1", "es6-error": "^4.1.1", "matcher": "^3.0.0", "roarr": "^2.15.3", "semver": "^7.3.2", "serialize-error": "^7.0.1" } }, "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q=="], + + "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "got": ["got@11.8.6", "", { "dependencies": { "@sindresorhus/is": "^4.0.0", "@szmarczak/http-timer": "^4.0.5", "@types/cacheable-request": "^6.0.1", "@types/responselike": "^1.0.0", "cacheable-lookup": "^5.0.3", "cacheable-request": "^7.0.2", "decompress-response": "^6.0.0", "http2-wrapper": "^1.0.0-beta.5.2", "lowercase-keys": "^2.0.0", "p-cancelable": "^2.0.0", "responselike": "^2.0.0" } }, "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], + + "http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="], + + "http2-wrapper": ["http2-wrapper@1.0.3", "", { "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.0.0" } }, "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], + + "jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "lowercase-keys": ["lowercase-keys@2.0.0", "", {}, "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="], + + "matcher": ["matcher@3.0.0", "", { "dependencies": { "escape-string-regexp": "^4.0.0" } }, "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng=="], + + "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], + + "moment": ["moment@2.29.4", "", {}, "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "normalize-url": ["normalize-url@6.1.0", "", {}, "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="], + + "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="], + + "obsidian": ["obsidian@1.12.3", "", { "dependencies": { "@types/codemirror": "5.60.8", "moment": "2.29.4" }, "peerDependencies": { "@codemirror/state": "6.5.0", "@codemirror/view": "6.38.6" } }, "sha512-HxWqe763dOqzXjnNiHmAJTRERN8KILBSqxDSEqbeSr7W8R8Jxezzbca+nz1LiiqXnMpM8lV2jzAezw3CZ4xNUw=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "p-cancelable": ["p-cancelable@2.1.1", "", {}, "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="], + + "pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="], + + "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="], + + "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], + + "quick-lru": ["quick-lru@5.1.1", "", {}, "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="], + + "resolve-alpn": ["resolve-alpn@1.2.1", "", {}, "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g=="], + + "responselike": ["responselike@2.0.1", "", { "dependencies": { "lowercase-keys": "^2.0.0" } }, "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw=="], + + "roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="], + + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "semver-compare": ["semver-compare@1.0.0", "", {}, "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="], + + "serialize-error": ["serialize-error@7.0.1", "", { "dependencies": { "type-fest": "^0.13.1" } }, "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw=="], + + "sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], + + "style-mod": ["style-mod@4.1.3", "", {}, "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="], + + "sumchecker": ["sumchecker@3.0.1", "", { "dependencies": { "debug": "^4.1.0" } }, "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg=="], + + "tslib": ["tslib@2.7.0", "", {}, "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA=="], + + "type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="], + + "typescript": ["typescript@5.5.4", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], + + "w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="], + + "@types/cacheable-request/@types/node": ["@types/node@22.19.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q=="], + + "@types/keyv/@types/node": ["@types/node@22.19.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q=="], + + "@types/responselike/@types/node": ["@types/node@22.19.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q=="], + + "@types/yauzl/@types/node": ["@types/node@22.19.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q=="], + + "clone-response/mimic-response": ["mimic-response@1.0.1", "", {}, "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="], + + "electron/@types/node": ["@types/node@22.19.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q=="], + + "global-agent/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + } +} diff --git a/esbuild.config.mjs b/esbuild.config.mjs new file mode 100644 index 0000000..dd71302 --- /dev/null +++ b/esbuild.config.mjs @@ -0,0 +1,48 @@ +import esbuild from "esbuild"; +import process from "process"; +import builtins from "builtin-modules"; + +const banner = `/* +THIS IS A GENERATED/BUNDLED FILE BY ESBUILD +if you want to view the source, please visit the github repository of this plugin +*/ +`; + +const prod = (process.argv[2] === "production"); + +const context = await esbuild.context({ + banner: { js: banner }, + entryPoints: ["main.ts"], + bundle: true, + external: [ + "obsidian", + "electron", + "@electron/remote", + "@codemirror/autocomplete", + "@codemirror/collab", + "@codemirror/commands", + "@codemirror/language", + "@codemirror/lint", + "@codemirror/search", + "@codemirror/state", + "@codemirror/view", + "@lezer/common", + "@lezer/highlight", + "@lezer/lr", + ...builtins + ], + format: "cjs", + target: "es2018", + logLevel: "info", + sourcemap: prod ? false : "inline", + treeShaking: true, + outfile: "main.js", + minify: prod, +}); + +if (prod) { + await context.rebuild(); + process.exit(0); +} else { + await context.watch(); +} diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..fd310e8 Binary files /dev/null and b/icon.png differ diff --git a/main.js b/main.js new file mode 100644 index 0000000..ea466dd --- /dev/null +++ b/main.js @@ -0,0 +1,6 @@ +/* +THIS IS A GENERATED/BUNDLED FILE BY ESBUILD +if you want to view the source, please visit the github repository of this plugin +*/ + +var y=Object.create;var u=Object.defineProperty;var b=Object.getOwnPropertyDescriptor;var D=Object.getOwnPropertyNames;var C=Object.getPrototypeOf,w=Object.prototype.hasOwnProperty;var S=(s,n)=>{for(var t in n)u(s,t,{get:n[t],enumerable:!0})},m=(s,n,t,e)=>{if(n&&typeof n=="object"||typeof n=="function")for(let i of D(n))!w.call(s,i)&&i!==t&&u(s,i,{get:()=>n[i],enumerable:!(e=b(n,i))||e.enumerable});return s};var E=(s,n,t)=>(t=s!=null?y(C(s)):{},m(n||!s||!s.__esModule?u(t,"default",{value:s,enumerable:!0}):t,s)),P=s=>m(u({},"__esModule",{value:!0}),s);var L={};S(L,{default:()=>f});module.exports=P(L);var a=require("obsidian"),v=require("fs"),h=E(require("path")),A={modifierKey:process.platform==="darwin"?"alt":"ctrl",debugLogging:!1},I=".nav-file-title, .nav-folder-title",O="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=",f=class extends a.Plugin{constructor(){super(...arguments);this.settings=A;this.iconPath=null}async onload(){await this.loadSettings(),this.resolveIconPath(),this.registerDomEvent(document,"dragstart",t=>this.handleDragStart(t),{capture:!0}),this.addSettingTab(new p(this.app,this)),this.log("Drag Out loaded")}onunload(){this.log("Drag Out unloaded")}resolveIconPath(){let t=this.app.vault.adapter;if(!(t instanceof a.FileSystemAdapter)){this.iconPath=null;return}let e=h.join(t.getBasePath(),this.app.vault.configDir,"plugins",this.manifest.id),i=h.join(e,"icon.png");this.iconPath=(0,v.existsSync)(i)?i:null}isModifierPressed(t){switch(this.settings.modifierKey){case"alt":return t.altKey;case"shift":return t.shiftKey;case"ctrl":return t.ctrlKey||t.metaKey;case"none":return!0}}handleDragStart(t){if(!this.isModifierPressed(t))return;let e=t.target;if(!e)return;let i=e.closest(I);if(!i)return;let c=i.getAttribute("data-path");if(!c){this.log("file tree item missing data-path attribute");return}let o=this.app.vault.adapter;if(!(o instanceof a.FileSystemAdapter))return;let d=o.getBasePath(),g=this.collectSelectedAbsolutePaths(c,d);if(g.length===0){this.log("no paths resolved, falling through to default drag");return}let r=this.getWebContents();if(!r){new a.Notice("Drag Out: could not access Electron's webContents. Check the developer console (Ctrl+Shift+I) for details.");return}try{let l=this.buildDragItem(g);r.startDrag(l),t.preventDefault(),t.stopPropagation(),this.log("startDrag invoked",g)}catch(l){console.error("[DragOut] startDrag failed:",l),new a.Notice("Drag Out: drag failed. See console for details.")}}buildDragItem(t){let e=this.getDragIcon();return t.length===1?{file:t[0],icon:e}:{file:t[0],files:t,icon:e}}getDragIcon(){if(this.iconPath)return this.iconPath;let t=this.requireElectron(),e=t.nativeImage.createFromDataURL(O);return e.isEmpty()?t.nativeImage.createEmpty():e}collectSelectedAbsolutePaths(t,e){let i=document.querySelectorAll(".nav-file-title.is-selected, .nav-folder-title.is-selected"),c=new Set,o=[],d=r=>{let l=h.join(e,r);c.has(l)||(c.add(l),o.push(l))};if(i.length===0)return d(t),o;let g=Array.from(i).map(r=>r.getAttribute("data-path")).filter(r=>!!r);return g.includes(t)?(g.forEach(d),o):(d(t),o)}getWebContents(){var t,e;try{let i=(t=window.require)==null?void 0:t.call(window,"@electron/remote");if(i!=null&&i.getCurrentWebContents)return i.getCurrentWebContents()}catch(i){}try{let i=this.requireElectron();if((e=i.remote)!=null&&e.getCurrentWebContents)return i.remote.getCurrentWebContents()}catch(i){}return null}requireElectron(){var e;return((e=window.require)!=null?e:require)("electron")}log(...t){this.settings.debugLogging&&console.log("[DragOut]",...t)}async loadSettings(){this.settings=Object.assign({},A,await this.loadData())}async saveSettings(){await this.saveData(this.settings)}},p=class extends a.PluginSettingTab{constructor(n,t){super(n,t),this.plugin=t}display(){let{containerEl:n}=this;n.empty(),new a.Setting(n).setName("Modifier key").setDesc("Hold this key while dragging a file from the file explorer to drag the actual file (instead of an obsidian:// link). Pick 'None' to always use external drag \u2014 but be aware this disables Obsidian's built-in file-tree drag behavior.").addDropdown(t=>t.addOption("ctrl","Ctrl / Cmd (default Win/Linux)").addOption("alt","Alt / Option (default Mac)").addOption("shift","Shift").addOption("none","None \u2014 always intercept").setValue(this.plugin.settings.modifierKey).onChange(async e=>{this.plugin.settings.modifierKey=e,await this.plugin.saveSettings()})),new a.Setting(n).setName("Debug logging").setDesc("Log drag events to the developer console (Ctrl+Shift+I). Useful for troubleshooting; leave off otherwise.").addToggle(t=>t.setValue(this.plugin.settings.debugLogging).onChange(async e=>{this.plugin.settings.debugLogging=e,await this.plugin.saveSettings()}))}}; diff --git a/main.ts b/main.ts new file mode 100644 index 0000000..bf24522 --- /dev/null +++ b/main.ts @@ -0,0 +1,338 @@ +import { + App, + FileSystemAdapter, + Notice, + Plugin, + PluginSettingTab, + Setting, +} from "obsidian"; +import type { WebContents, NativeImage } from "electron"; +import { existsSync } from "fs"; +import * as path from "path"; + +type ModifierKey = "alt" | "shift" | "ctrl" | "none"; + +interface DragOutSettings { + /** Modifier key required to trigger external drag. "none" = always on. */ + modifierKey: ModifierKey; + /** Show a brief notice on first successful drag (helpful for verifying setup). */ + debugLogging: boolean; +} + +const DEFAULT_SETTINGS: DragOutSettings = { + modifierKey: process.platform === "darwin" ? "alt" : "ctrl", + debugLogging: false, +}; + +/** + * Selectors for items in Obsidian's file tree that we want to intercept + * drags from. .nav-file-title is a file, .nav-folder-title is a folder. + */ +const FILE_TREE_SELECTOR = ".nav-file-title, .nav-folder-title"; + +// Electron requires a non-empty drag icon on macOS. Keep a tiny fallback so +// releases still work even when no icon.png is bundled. +const FALLBACK_ICON_DATA_URL = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII="; + +export default class DragOutPlugin extends Plugin { + settings: DragOutSettings = DEFAULT_SETTINGS; + private iconPath: string | null = null; + + async onload() { + await this.loadSettings(); + + // Resolve absolute path to our bundled icon (used as drag image). + this.resolveIconPath(); + + // Register dragstart at capture phase so we run before Obsidian's + // own handler that sets the obsidian:// URI on the dataTransfer. + this.registerDomEvent( + document, + "dragstart", + (evt) => this.handleDragStart(evt), + { capture: true } + ); + + this.addSettingTab(new DragOutSettingTab(this.app, this)); + + this.log("Drag Out loaded"); + } + + onunload() { + this.log("Drag Out unloaded"); + } + + private resolveIconPath(): void { + const adapter = this.app.vault.adapter; + if (!(adapter instanceof FileSystemAdapter)) { + this.iconPath = null; + return; + } + const pluginDir = path.join( + adapter.getBasePath(), + this.app.vault.configDir, + "plugins", + this.manifest.id + ); + const candidateIconPath = path.join(pluginDir, "icon.png"); + this.iconPath = existsSync(candidateIconPath) ? candidateIconPath : null; + } + + private isModifierPressed(evt: DragEvent): boolean { + switch (this.settings.modifierKey) { + case "alt": + return evt.altKey; + case "shift": + return evt.shiftKey; + case "ctrl": + // Ctrl on Windows/Linux, Cmd on Mac + return evt.ctrlKey || evt.metaKey; + case "none": + return true; + } + } + + private handleDragStart(evt: DragEvent): void { + if (!this.isModifierPressed(evt)) return; + + const target = evt.target as HTMLElement | null; + if (!target) return; + + // Only intercept drags that originate inside the file tree. + const fileItem = target.closest(FILE_TREE_SELECTOR) as HTMLElement | null; + if (!fileItem) return; + + const relativePath = fileItem.getAttribute("data-path"); + if (!relativePath) { + this.log("file tree item missing data-path attribute"); + return; + } + + const adapter = this.app.vault.adapter; + if (!(adapter instanceof FileSystemAdapter)) { + // Not a desktop / FS-backed vault. Nothing we can do. + return; + } + const basePath = adapter.getBasePath(); + + // Collect all selected paths in case of multi-select drag. + const absolutePaths = this.collectSelectedAbsolutePaths( + relativePath, + basePath + ); + + if (absolutePaths.length === 0) { + this.log("no paths resolved, falling through to default drag"); + return; + } + + // Reach into Electron and call webContents.startDrag. + const webContents = this.getWebContents(); + if (!webContents) { + new Notice( + "Drag Out: could not access Electron's webContents. " + + "Check the developer console (Ctrl+Shift+I) for details." + ); + return; + } + + try { + const dragItem = this.buildDragItem(absolutePaths); + webContents.startDrag(dragItem); + // Suppress Obsidian's default behavior (which writes an obsidian:// URI + // onto the clipboard / dataTransfer). We're taking over the drag. + evt.preventDefault(); + evt.stopPropagation(); + this.log("startDrag invoked", absolutePaths); + } catch (err) { + console.error("[DragOut] startDrag failed:", err); + new Notice("Drag Out: drag failed. See console for details."); + } + } + + /** + * Build the argument object passed to webContents.startDrag. + * - Single path: pass `file`. + * - Multiple paths: pass `files` (overrides `file`). + * - `icon` is required and must be non-empty on macOS. + */ + private buildDragItem(absolutePaths: string[]): { + file: string; + files?: string[]; + icon: string | NativeImage; + } { + const icon = this.getDragIcon(); + + if (absolutePaths.length === 1) { + return { file: absolutePaths[0], icon }; + } + return { file: absolutePaths[0], files: absolutePaths, icon }; + } + + private getDragIcon(): string | NativeImage { + if (this.iconPath) { + return this.iconPath; + } + + const electron = this.requireElectron(); + const fallbackIcon = + electron.nativeImage.createFromDataURL(FALLBACK_ICON_DATA_URL); + + return fallbackIcon.isEmpty() + ? electron.nativeImage.createEmpty() + : fallbackIcon; + } + + /** + * Walk the visible file tree to find every currently-selected item. + * If nothing is "selected" (single-click drag), we just return the + * item the drag started on. Folders pass through too — Electron's + * startDrag accepts folder paths on Windows and macOS. + */ + private collectSelectedAbsolutePaths( + currentRelative: string, + basePath: string + ): string[] { + const selectedNodes = document.querySelectorAll( + ".nav-file-title.is-selected, .nav-folder-title.is-selected" + ); + + const seen = new Set(); + const out: string[] = []; + + const push = (rel: string) => { + const abs = path.join(basePath, rel); + if (!seen.has(abs)) { + seen.add(abs); + out.push(abs); + } + }; + + if (selectedNodes.length === 0) { + push(currentRelative); + return out; + } + + const selectedRelativePaths = Array.from(selectedNodes) + .map((el) => el.getAttribute("data-path")) + .filter((rel): rel is string => !!rel); + + if (!selectedRelativePaths.includes(currentRelative)) { + push(currentRelative); + return out; + } + + selectedRelativePaths.forEach(push); + + return out; + } + + /** + * Resolve Electron's webContents from the renderer. Tries the modern + * @electron/remote first, then falls back to the legacy electron.remote. + * Returns null if neither is reachable. + */ + private getWebContents(): WebContents | null { + // Path 1: @electron/remote (modern, requires opt-in by host) + try { + const electronRemote = (window as any).require?.( + "@electron/remote" + ); + if (electronRemote?.getCurrentWebContents) { + return electronRemote.getCurrentWebContents(); + } + } catch (_) { + /* fall through */ + } + + // Path 2: legacy electron.remote (pre-Electron 14 — Obsidian still + // exposes this on at least some versions). + try { + const electron = this.requireElectron(); + if ((electron as any).remote?.getCurrentWebContents) { + return (electron as any).remote.getCurrentWebContents(); + } + } catch (_) { + /* fall through */ + } + + return null; + } + + private requireElectron(): typeof import("electron") { + // Use window.require to bypass any bundler that might have rewritten + // a top-level require call into something else. + const req = (window as any).require ?? require; + return req("electron"); + } + + private log(...args: unknown[]): void { + if (this.settings.debugLogging) { + console.log("[DragOut]", ...args); + } + } + + async loadSettings(): Promise { + this.settings = Object.assign( + {}, + DEFAULT_SETTINGS, + await this.loadData() + ); + } + + async saveSettings(): Promise { + await this.saveData(this.settings); + } +} + +class DragOutSettingTab extends PluginSettingTab { + plugin: DragOutPlugin; + + constructor(app: App, plugin: DragOutPlugin) { + super(app, plugin); + this.plugin = plugin; + } + + display(): void { + const { containerEl } = this; + containerEl.empty(); + + new Setting(containerEl) + .setName("Modifier key") + .setDesc( + "Hold this key while dragging a file from the file explorer " + + "to drag the actual file (instead of an obsidian:// link). " + + "Pick 'None' to always use external drag — but be aware " + + "this disables Obsidian's built-in file-tree drag behavior." + ) + .addDropdown((dd) => + dd + .addOption("ctrl", "Ctrl / Cmd (default Win/Linux)") + .addOption("alt", "Alt / Option (default Mac)") + .addOption("shift", "Shift") + .addOption("none", "None — always intercept") + .setValue(this.plugin.settings.modifierKey) + .onChange(async (value) => { + this.plugin.settings.modifierKey = + value as ModifierKey; + await this.plugin.saveSettings(); + }) + ); + + new Setting(containerEl) + .setName("Debug logging") + .setDesc( + "Log drag events to the developer console (Ctrl+Shift+I). " + + "Useful for troubleshooting; leave off otherwise." + ) + .addToggle((tg) => + tg + .setValue(this.plugin.settings.debugLogging) + .onChange(async (value) => { + this.plugin.settings.debugLogging = value; + await this.plugin.saveSettings(); + }) + ); + } +} diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..62eb382 --- /dev/null +++ b/manifest.json @@ -0,0 +1,10 @@ +{ + "id": "drag-out", + "name": "Drag Out", + "version": "0.1.1", + "minAppVersion": "1.5.0", + "description": "Drag files from Obsidian's file explorer to other applications (local filesystem, browser uploads, email attachments, etc.) with native OS drag handles instead of obsidian:// URIs.", + "author": "Xander Berry", + "authorUrl": "https://github.com/xwberry", + "isDesktopOnly": true +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..520265c --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "obsidian-drag-out", + "version": "0.1.1", + "description": "Drag files from Obsidian's file explorer to other applications (local filesystem, browser uploads, email attachments, etc.) with native OS drag handles instead of obsidian:// URIs.", + "main": "main.js", + "scripts": { + "dev": "node esbuild.config.mjs", + "build": "tsc -noEmit -skipLibCheck && bun esbuild.config.mjs production", + "release": "bun release.mjs" + }, + "keywords": ["obsidian", "plugin", "drag-and-drop"], + "author": "Xander Berry", + "authorUrl": "https://github.com/xwberry", + "license": "MIT", + "devDependencies": { + "@types/node": "^20.0.0", + "builtin-modules": "3.3.0", + "electron": "^39.0.0", + "esbuild": "0.24.0", + "obsidian": "^1.4.11", + "tslib": "2.7.0", + "typescript": "5.5.4" + } +} diff --git a/release.mjs b/release.mjs new file mode 100644 index 0000000..6acdf24 --- /dev/null +++ b/release.mjs @@ -0,0 +1,103 @@ +/* + * release.mjs + * + * Run after bumping the version in package.json (e.g. via `bun pm version patch`). + * + * Steps: + * 1. Read version from package.json + * 2. Sync into manifest.json + * 3. Add entry to versions.json (mapping version -> manifest.minAppVersion) + * 4. Run production build + * 5. Copy release artifacts into dist// + * + * Usage: bun run release + */ + +import { readFileSync, writeFileSync, mkdirSync, copyFileSync, existsSync } from "node:fs"; +import { join } from "node:path"; +import { execSync } from "node:child_process"; + +const RELEASE_FILES = ["main.js", "manifest.json", "icon.png"]; + +// --- 1. Read target version from package.json ------------------------------ +const pkg = JSON.parse(readFileSync("package.json", "utf8")); +const minAppVersion = manifest.minAppVersion; +if (!minAppVersion || typeof minAppVersion !== "string") { + console.error("manifest.json has no valid 'minAppVersion'. Aborting."); + process.exit(1); +} +const version = pkg.version; +if (!version) { + console.error("package.json has no 'version' field. Aborting."); + process.exit(1); +} +console.log(`Releasing version ${version}\n`); + +// --- 2. Sync manifest.json ------------------------------------------------- +const manifest = JSON.parse(readFileSync("manifest.json", "utf8")); +const previousManifestVersion = manifest.version; +manifest.version = version; +writeFileSync("manifest.json", JSON.stringify(manifest, null, 2) + "\n"); +console.log(` manifest.json: ${previousManifestVersion} -> ${version}`); + +// --- 3. Update versions.json ----------------------------------------------- +const versions = JSON.parse(readFileSync("versions.json", "utf8")); +if (versions[version]) { + console.log(` versions.json: entry for ${version} already exists (${versions[version]})`); +} else { + versions[version] = minAppVersion; + writeFileSync("versions.json", JSON.stringify(versions, null, 2) + "\n"); + console.log(` versions.json: added ${version} -> ${minAppVersion}`); +} + +// --- 4. Build production --------------------------------------------------- +console.log("\nBuilding production bundle..."); +try { + execSync("bun run build", { stdio: "inherit" }); +} catch (err) { + console.error("\nBuild failed. Aborting release."); + process.exit(1); +} + +// --- 5. Copy artifacts to dist// ---------------------------------- +const distDir = join("dist", version); +mkdirSync(distDir, { recursive: true }); + +console.log(`\nStaging release files in ${distDir}/`); +const missingRequired = []; +const missingOptional = []; +for (const file of RELEASE_FILES) { + if (!existsSync(file)) { + if (file === "icon.png") { + missingOptional.push(file); + } else { + missingRequired.push(file); + } + continue; + } + copyFileSync(file, join(distDir, file)); + console.log(` ${file}`); +} + +if (missingOptional.length > 0) { + console.warn(`\nWARNING: missing optional files (skipped): ${missingOptional.join(", ")}`); +} +if (missingRequired.length > 0) { + console.error(`\nERROR: missing required release files (aborting): ${missingRequired.join(", ")}`); + process.exit(1); +} + +// --- Done ------------------------------------------------------------------ +console.log(` +Release ${version} prepared. + +Next steps: + git add -A + git commit -m "Release ${version}" + git push && git push --tags + +Then on GitHub: + - Draft a new release at tag v${version} + - Attach release files from ${distDir}/ as LOOSE assets (main.js, manifest.json, and icon.png if present; do not zip) + - Publish +`); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f2263a9 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "inlineSourceMap": true, + "inlineSources": true, + "module": "ESNext", + "target": "ES2018", + "allowJs": true, + "noImplicitAny": true, + "moduleResolution": "node", + "importHelpers": true, + "isolatedModules": true, + "strictNullChecks": true, + "esModuleInterop": true, + "lib": [ + "DOM", + "ES5", + "ES6", + "ES7", + "ES2018" + ] + }, + "include": [ + "**/*.ts" + ] +} diff --git a/versions.json b/versions.json new file mode 100644 index 0000000..ff37074 --- /dev/null +++ b/versions.json @@ -0,0 +1,3 @@ +{ + "0.1.1": "1.5.0" +}