mirror of
https://github.com/xwberry/obsidian-drag-out.git
synced 2026-07-22 06:54:07 +00:00
Add Drag Out Obsidian plugin for native drag-and-drop (#1)
* docs: add README for Drag Out plugin with installation, usage, and notes * build(esbuild): add esbuild config for bundling main.ts Add configuration to bundle main.ts with environment‑aware settings, minify in production, generate source maps in dev, and watch for changes. Excludes external modules and builtins. * build(main-orig.js): add CommonJS build of main.ts as plugin entry point * build(main.js): add esbuild-generated bundle for Drag Out plugin * feat(main.ts): add DragOut plugin for external file drag support Add DragOutPlugin class with settings, dragstart handling, icon resolution, and a setting tab. Enables dragging files/folders from Obsidian to external applications using a configurable modifier key. Includes debug logging and cross‑platform support. * feat: add manifest.json for Drag Out plugin to define metadata and versioning * chore(package): add package.json with project metadata and build scripts * chore(release): add release.mjs to automate version sync and build Adds a script that reads the package version, updates manifest.json and runs the production build, and copies release artifacts into dist/<version>/. for GitHub releases. * build: add tsconfig.json to configure TypeScript compiler settings * feat: add icon.png and versions.json for app branding and version tracking * remove original handwritten main.js pre-esbuild * docs(license): adds license boilerplate * docs(changelog): add initial changelog file with 0.1.2 entries * docs(readme): add Bun build note and correct formatting Add a note that build scripts rely on Bun, update code block language to text, fix indentation of bullet points, and adjust line breaks for consistency. * refactor(main.js): update minified bundle variable names and improve drag handling logic * refactor(DragOutPlugin): improve drag handling and selection logic - Move event suppression after startDrag to avoid early exit - Simplify selected path extraction with Array.from/map/filter - Ensure current item is added only if not already selected - Reorder comments for clarity and maintainability * refactor(release): separate optional and required files, abort on missing required files This change updates the release script to treat `icon.png` as an optional asset. Missing required files now trigger an error and halt the release, while missing optional files only emit a warning. * docs(changelog): update change notes for platform defaults, drag behavior, README, and icon fallback - Make default modifier key platform‑aware (Ctrl on Windows/Linux, Alt on macOS). - Clarify multi‑select drag behavior: selected items drag all, unselected drag one. - Add settings copy describing current platform defaults and drag interception. - Rework README for public audience, including Bun build commands and debugging tips. - Add fallback for missing drag icon, using a tiny embedded image on macOS. - Remove original hand‑written main.js test script. * docs(readme): rewrite and update installation, usage, and build instructions - Clarify plugin purpose and usage - Update build commands to use Bun - Add platform notes and modifier key details - Improve formatting and consistency * refactor(main.js): add base64 icon fallback and update modifier key text * feat(main.ts): add fallback drag icon for macOS and improve icon handling * docs(changelog): add minAppVersion validation to release script abort condition * refactor(release.mjs): validate minAppVersion and use it in versions.json
This commit is contained in:
parent
61bf0721f1
commit
9b76d70473
13 changed files with 1004 additions and 0 deletions
63
CHANGELOG.md
Normal file
63
CHANGELOG.md
Normal file
|
|
@ -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.
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -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.
|
||||
115
README.md
Normal file
115
README.md
Normal file
|
|
@ -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
|
||||
247
bun.lock
Normal file
247
bun.lock
Normal file
|
|
@ -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=="],
|
||||
}
|
||||
}
|
||||
48
esbuild.config.mjs
Normal file
48
esbuild.config.mjs
Normal file
|
|
@ -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();
|
||||
}
|
||||
BIN
icon.png
Normal file
BIN
icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.9 KiB |
6
main.js
Normal file
6
main.js
Normal file
|
|
@ -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()}))}};
|
||||
338
main.ts
Normal file
338
main.ts
Normal file
|
|
@ -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<string>();
|
||||
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<void> {
|
||||
this.settings = Object.assign(
|
||||
{},
|
||||
DEFAULT_SETTINGS,
|
||||
await this.loadData()
|
||||
);
|
||||
}
|
||||
|
||||
async saveSettings(): Promise<void> {
|
||||
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();
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
10
manifest.json
Normal file
10
manifest.json
Normal file
|
|
@ -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
|
||||
}
|
||||
24
package.json
Normal file
24
package.json
Normal file
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
103
release.mjs
Normal file
103
release.mjs
Normal file
|
|
@ -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/<version>/
|
||||
*
|
||||
* 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/<version>/ ----------------------------------
|
||||
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
|
||||
`);
|
||||
26
tsconfig.json
Normal file
26
tsconfig.json
Normal file
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
3
versions.json
Normal file
3
versions.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"0.1.1": "1.5.0"
|
||||
}
|
||||
Loading…
Reference in a new issue