@lapis-notes/file-explorer 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +133 -0
- package/LICENSE.md +3 -3
- package/README.md +6 -0
- package/dist/ArtworkIcon.svelte +18 -0
- package/dist/ArtworkIcon.svelte.d.ts +8 -0
- package/dist/explorer-file-icons.d.ts +7 -0
- package/dist/explorer-file-icons.js +55 -0
- package/dist/explorer-imports.d.ts +3 -0
- package/dist/explorer-imports.js +24 -0
- package/dist/index.js +83 -45
- package/dist/native-explorer-actions.d.ts +1 -1
- package/dist/native-explorer-actions.js +25 -22
- package/dist/open-explorer-file.js +7 -0
- package/dist/register-explorer-settings.d.ts +4 -2
- package/dist/register-explorer-settings.js +17 -4
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,138 @@
|
|
|
1
1
|
# @lapis-notes/file-explorer
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`55be8dc`](https://github.com/lapismd/lapis-notes/commit/55be8dce6f7b19b0e8036be86a3cf5305db25317)
|
|
8
|
+
Thanks [@stevejuma](https://github.com/stevejuma)! - Let file-view plugins
|
|
9
|
+
contribute a fileIcon ID for their filename patterns or registered extensions.
|
|
10
|
+
Add App-owned SVG and icon-pack registration with plugin lifecycle cleanup,
|
|
11
|
+
preserve legacy icon registrations, and refresh Explorer artwork on editor,
|
|
12
|
+
plugin and file changes.
|
|
13
|
+
|
|
14
|
+
Release the scoped Design Core resolver first and raise the dependent minimum
|
|
15
|
+
ranges to the releases containing these contracts before publishing consumers.
|
|
16
|
+
|
|
17
|
+
Upgrade API-owned Zod to the patched package while retaining its public v3
|
|
18
|
+
schema entry for compatibility.
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- [`fbba8d4`](https://github.com/lapismd/lapis-notes/commit/fbba8d48498c34939fec475e6bed67a682045820)
|
|
23
|
+
Thanks [@stevejuma](https://github.com/stevejuma)! - Retain plugin-state and
|
|
24
|
+
compatibility-file drafts through close and conflict recovery, drain admitted
|
|
25
|
+
plugin/file operations before teardown, and preserve Properties mutations
|
|
26
|
+
after initial read failures.
|
|
27
|
+
|
|
28
|
+
- [`d8946b9`](https://github.com/lapismd/lapis-notes/commit/d8946b941569484b813eb0c1e8b1c5729fffc1a7)
|
|
29
|
+
Thanks [@stevejuma](https://github.com/stevejuma)! - Add owned
|
|
30
|
+
diagnostic-resource navigation and themed recovery-banner actions. Accept
|
|
31
|
+
Design Core 0.5.1 alongside the existing 0.4 range.
|
|
32
|
+
|
|
33
|
+
- [`023259d`](https://github.com/lapismd/lapis-notes/commit/023259d96d4ff0414fec6427924ed917f89405a3)
|
|
34
|
+
Thanks [@stevejuma](https://github.com/stevejuma)! - Add an optional exclusive
|
|
35
|
+
file-creation transport and route Explorer new-note actions through the
|
|
36
|
+
FileManager creation intent. Preserve existing destinations on collisions and
|
|
37
|
+
keep legacy Vault creation semantics explicit.
|
|
38
|
+
|
|
39
|
+
- [`717f0ff`](https://github.com/lapismd/lapis-notes/commit/717f0ff3956a5d33cba10311fca9c41a06e53b21)
|
|
40
|
+
Thanks [@stevejuma](https://github.com/stevejuma)! - Connect the existing
|
|
41
|
+
Explorer file-drop surface to FileManager new-file creation. Preserve binary
|
|
42
|
+
bytes and existing destinations, validate bounded input before reading it, and
|
|
43
|
+
retain App admission through asynchronous import and projection.
|
|
44
|
+
|
|
45
|
+
- Require released Design Core 0.6 for the close-guard, plugin-flush and
|
|
46
|
+
centered panel contracts used by current source. Retain portable registry
|
|
47
|
+
dependency ranges for the shared application runtime.
|
|
48
|
+
|
|
49
|
+
- [`bac3e5e`](https://github.com/lapismd/lapis-notes/commit/bac3e5eda6f467dbc86ec88c9f27a275f2b27953)
|
|
50
|
+
Thanks [@stevejuma](https://github.com/stevejuma)! - Expose optional
|
|
51
|
+
implemented system trash through the neutral native adapter with explicit
|
|
52
|
+
unsupported fallback and validated acknowledgements. Attribute Explorer
|
|
53
|
+
native/copy feedback to its owning App and reject unavailable clipboard
|
|
54
|
+
support without reporting success.
|
|
55
|
+
|
|
56
|
+
- [`501c50e`](https://github.com/lapismd/lapis-notes/commit/501c50e049264560e13e29c75a74c089922c7297)
|
|
57
|
+
Thanks [@stevejuma](https://github.com/stevejuma)! - Project owned App notices
|
|
58
|
+
through the existing workspace notification UI and retire their subscription
|
|
59
|
+
on host disposal. Pass the explicit Explorer App when reporting file-action
|
|
60
|
+
failures.
|
|
61
|
+
|
|
62
|
+
- [`98b333f`](https://github.com/lapismd/lapis-notes/commit/98b333f73ff5ba8d5f28c52e4e3a2ceaa5ade0fb)
|
|
63
|
+
Thanks [@stevejuma](https://github.com/stevejuma)! - Protect and flush
|
|
64
|
+
registered file writers before FileManager trash or deletion, including folder
|
|
65
|
+
descendants. Route Explorer deletion through that guard so failed writes
|
|
66
|
+
retain the file and dirty draft until explicit recovery.
|
|
67
|
+
- Updated dependencies
|
|
68
|
+
[[`bdd4923`](https://github.com/lapismd/lapis-notes/commit/bdd492357135de2cc0175fa42413709b2a38cd63),
|
|
69
|
+
[`86e85f6`](https://github.com/lapismd/lapis-notes/commit/86e85f6e814430c1755147c913eb91090a439bf4),
|
|
70
|
+
[`631aeec`](https://github.com/lapismd/lapis-notes/commit/631aeec539e2fb061dc80c5c3a30a248ee15c529),
|
|
71
|
+
[`fdee7e4`](https://github.com/lapismd/lapis-notes/commit/fdee7e4defde17a1009e16138d78c93c3ead8e2f),
|
|
72
|
+
[`a91d5e2`](https://github.com/lapismd/lapis-notes/commit/a91d5e2eb5ec037029f6a7a2df316a9bd9440af7),
|
|
73
|
+
[`fbba8d4`](https://github.com/lapismd/lapis-notes/commit/fbba8d48498c34939fec475e6bed67a682045820),
|
|
74
|
+
[`d8946b9`](https://github.com/lapismd/lapis-notes/commit/d8946b941569484b813eb0c1e8b1c5729fffc1a7),
|
|
75
|
+
[`ebf033a`](https://github.com/lapismd/lapis-notes/commit/ebf033af0fc0752be5ce5ffa7d55784163d291b4),
|
|
76
|
+
[`023259d`](https://github.com/lapismd/lapis-notes/commit/023259d96d4ff0414fec6427924ed917f89405a3),
|
|
77
|
+
[`63a5e7f`](https://github.com/lapismd/lapis-notes/commit/63a5e7fe40eea45762b5842b588c742076c7dd1d),
|
|
78
|
+
[`2bffe48`](https://github.com/lapismd/lapis-notes/commit/2bffe484b38761af197afe7ab9b0d7b3affb50d7),
|
|
79
|
+
[`86f6316`](https://github.com/lapismd/lapis-notes/commit/86f63166c3d5be648c595ef4e230ea6c4081bc48),
|
|
80
|
+
[`edf1d76`](https://github.com/lapismd/lapis-notes/commit/edf1d76f4f3aa0442c1d9153d812f29b7d14e5c2),
|
|
81
|
+
[`b3be91d`](https://github.com/lapismd/lapis-notes/commit/b3be91daecbd43187bacf8cfdc39f8311d700720),
|
|
82
|
+
[`5ef0a63`](https://github.com/lapismd/lapis-notes/commit/5ef0a6331c1b846b70357f990d919977a561112c),
|
|
83
|
+
[`bac3e5e`](https://github.com/lapismd/lapis-notes/commit/bac3e5eda6f467dbc86ec88c9f27a275f2b27953),
|
|
84
|
+
[`978b987`](https://github.com/lapismd/lapis-notes/commit/978b987ba4c95408a63e49fad11bc44bab020758),
|
|
85
|
+
[`ad4490c`](https://github.com/lapismd/lapis-notes/commit/ad4490ca1cd808002765582a17a5c81fa5335e0d),
|
|
86
|
+
[`74f544e`](https://github.com/lapismd/lapis-notes/commit/74f544e2aa8a967524c0c218f3db7e1a495a1a17),
|
|
87
|
+
[`55be8dc`](https://github.com/lapismd/lapis-notes/commit/55be8dce6f7b19b0e8036be86a3cf5305db25317),
|
|
88
|
+
[`501c50e`](https://github.com/lapismd/lapis-notes/commit/501c50e049264560e13e29c75a74c089922c7297),
|
|
89
|
+
[`98b333f`](https://github.com/lapismd/lapis-notes/commit/98b333f73ff5ba8d5f28c52e4e3a2ceaa5ade0fb),
|
|
90
|
+
[`535a6cc`](https://github.com/lapismd/lapis-notes/commit/535a6cc7b39f397cf8761a06db7b831386121928),
|
|
91
|
+
[`8a27bc3`](https://github.com/lapismd/lapis-notes/commit/8a27bc36a7a7fa81594e517908a6f5ed8ca77313),
|
|
92
|
+
[`109e208`](https://github.com/lapismd/lapis-notes/commit/109e208a63253dcc1a7abad3fd5a9b13c39d3def),
|
|
93
|
+
[`1e64589`](https://github.com/lapismd/lapis-notes/commit/1e6458922b486c07f36a5bc7d85d020b5805cd49),
|
|
94
|
+
[`f828eae`](https://github.com/lapismd/lapis-notes/commit/f828eaeba8411aa342d7ab4954393e84612af2f3)]:
|
|
95
|
+
- @lapis-notes/api@0.2.0
|
|
96
|
+
|
|
97
|
+
## 0.1.3
|
|
98
|
+
|
|
99
|
+
### Patch Changes
|
|
100
|
+
|
|
101
|
+
- [#36](https://github.com/lapismd/lapis-notes/pull/36)
|
|
102
|
+
[`17060a1`](https://github.com/lapismd/lapis-notes/commit/17060a1df152c9fc2302c93767cd7172868ff030)
|
|
103
|
+
Thanks [@stevejuma](https://github.com/stevejuma)! - Consume published Design
|
|
104
|
+
Core 0.5.0 with prepared browser CSS and bundled font assets through normal
|
|
105
|
+
npm ranges.
|
|
106
|
+
- Updated dependencies
|
|
107
|
+
[[`17060a1`](https://github.com/lapismd/lapis-notes/commit/17060a1df152c9fc2302c93767cd7172868ff030)]:
|
|
108
|
+
- @lapis-notes/api@0.1.5
|
|
109
|
+
|
|
110
|
+
## 0.1.2
|
|
111
|
+
|
|
112
|
+
### Patch Changes
|
|
113
|
+
|
|
114
|
+
- [#4](https://github.com/lapismd/lapis-notes/pull/4)
|
|
115
|
+
[`c1ad3c4`](https://github.com/lapismd/lapis-notes/commit/c1ad3c4acee2eac1ae87c22f66e946d2e0db3bf2)
|
|
116
|
+
Thanks [@stevejuma](https://github.com/stevejuma)! - Require published Design
|
|
117
|
+
Core 0.3.2 for native window-controls spacing, bold settings headings,
|
|
118
|
+
selected-tab indicators and mobile shell teardown. Consume the npm release
|
|
119
|
+
through portable semver ranges and lockfile integrity.
|
|
120
|
+
|
|
121
|
+
- Updated dependencies
|
|
122
|
+
[[`39f886d`](https://github.com/lapismd/lapis-notes/commit/39f886d017a28d916b3f6cac7d08b7723a04f45b),
|
|
123
|
+
[`c1ad3c4`](https://github.com/lapismd/lapis-notes/commit/c1ad3c4acee2eac1ae87c22f66e946d2e0db3bf2),
|
|
124
|
+
[`1e949fc`](https://github.com/lapismd/lapis-notes/commit/1e949fc9d51b795a50760e1146a9599602746c6b)]:
|
|
125
|
+
- @lapis-notes/api@0.1.2
|
|
126
|
+
|
|
127
|
+
## 0.1.1
|
|
128
|
+
|
|
129
|
+
### Patch Changes
|
|
130
|
+
|
|
131
|
+
- Reuse the active landing leaf when opening a file through File Explorer.
|
|
132
|
+
|
|
133
|
+
- Updated dependencies []:
|
|
134
|
+
- @lapis-notes/api@0.1.1
|
|
135
|
+
|
|
3
136
|
## 0.1.0
|
|
4
137
|
|
|
5
138
|
### Minor Changes
|
package/LICENSE.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
Apache-2.0
|
|
2
2
|
|
|
3
|
-
This package is licensed under the
|
|
4
|
-
See <https://www.
|
|
3
|
+
This package is licensed under the Apache License 2.0.
|
|
4
|
+
See <https://www.apache.org/licenses/LICENSE-2.0>.
|
package/README.md
CHANGED
|
@@ -30,3 +30,9 @@ pnpm --filter @lapis-notes/file-explorer build
|
|
|
30
30
|
pnpm --filter @lapis-notes/file-explorer check
|
|
31
31
|
pnpm --filter @lapis-notes/file-explorer test
|
|
32
32
|
```
|
|
33
|
+
|
|
34
|
+
Plugin-contributed file icons resolve through the owning App's editor-view and
|
|
35
|
+
icon registries. Contributions can reference built-in names, registered custom
|
|
36
|
+
SVG IDs or icon-pack entries. Registration, plugin enablement, editor
|
|
37
|
+
associations and rename refresh rows while Design Core retains filename
|
|
38
|
+
fallback, row accessibility and rename presentation.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
|
|
4
|
+
let {
|
|
5
|
+
svg,
|
|
6
|
+
class: className = "",
|
|
7
|
+
...rest
|
|
8
|
+
}: { svg: string; class?: string } & HTMLAttributes<HTMLSpanElement> =
|
|
9
|
+
$props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<span
|
|
13
|
+
{...rest}
|
|
14
|
+
class={["ui-workspace-icon", className].filter(Boolean).join(" ")}
|
|
15
|
+
data-ui-component="workspace-icon"
|
|
16
|
+
>
|
|
17
|
+
{@html svg}
|
|
18
|
+
</span>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
svg: string;
|
|
4
|
+
class?: string;
|
|
5
|
+
} & HTMLAttributes<HTMLSpanElement>;
|
|
6
|
+
declare const ArtworkIcon: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type ArtworkIcon = ReturnType<typeof ArtworkIcon>;
|
|
8
|
+
export default ArtworkIcon;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { App } from "@lapis-notes/api";
|
|
2
|
+
/** Publish the selected file-view artwork through Design Core's scoped resolver. */
|
|
3
|
+
export declare function createExplorerFileIcons(app: App): {
|
|
4
|
+
nameForPath(path: string): string | undefined;
|
|
5
|
+
publish(): Promise<void>;
|
|
6
|
+
release(): void;
|
|
7
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { registerWorkspaceIcon } from "@lapismd/design-core/workspace/icon";
|
|
2
|
+
import ArtworkIcon from "./ArtworkIcon.svelte";
|
|
3
|
+
import { listExplorerVaultEntries } from "./explorer-vault-entries";
|
|
4
|
+
function artworkComponent(svg) {
|
|
5
|
+
const Icon = (anchor, props) => ArtworkIcon(anchor, { ...props, svg });
|
|
6
|
+
return Icon;
|
|
7
|
+
}
|
|
8
|
+
/** Publish the selected file-view artwork through Design Core's scoped resolver. */
|
|
9
|
+
export function createExplorerFileIcons(app) {
|
|
10
|
+
const disposers = new Map();
|
|
11
|
+
const published = new Map();
|
|
12
|
+
let generation = 0;
|
|
13
|
+
function names() {
|
|
14
|
+
return new Set(listExplorerVaultEntries(app.vault.getAllLoadedFiles()).flatMap((node) => {
|
|
15
|
+
if (node.kind !== "file")
|
|
16
|
+
return [];
|
|
17
|
+
const name = app.workspace.getFileIconForPath(node.path);
|
|
18
|
+
return name ? [name] : [];
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
nameForPath(path) {
|
|
23
|
+
const name = app.workspace.getFileIconForPath(path);
|
|
24
|
+
return name && published.has(name) ? name : undefined;
|
|
25
|
+
},
|
|
26
|
+
async publish() {
|
|
27
|
+
const current = ++generation;
|
|
28
|
+
const selected = names();
|
|
29
|
+
const artwork = await Promise.all([...selected].map(async (name) => [name, await app.icons.resolve(name)]));
|
|
30
|
+
if (current !== generation)
|
|
31
|
+
return;
|
|
32
|
+
for (const [name, svg] of artwork) {
|
|
33
|
+
if (!svg || published.get(name) === svg)
|
|
34
|
+
continue;
|
|
35
|
+
disposers.get(name)?.();
|
|
36
|
+
published.set(name, svg);
|
|
37
|
+
disposers.set(name, registerWorkspaceIcon(name, artworkComponent(svg)));
|
|
38
|
+
}
|
|
39
|
+
for (const name of [...disposers.keys()]) {
|
|
40
|
+
if (selected.has(name))
|
|
41
|
+
continue;
|
|
42
|
+
disposers.get(name)?.();
|
|
43
|
+
disposers.delete(name);
|
|
44
|
+
published.delete(name);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
release() {
|
|
48
|
+
generation += 1;
|
|
49
|
+
for (const dispose of disposers.values())
|
|
50
|
+
dispose();
|
|
51
|
+
disposers.clear();
|
|
52
|
+
published.clear();
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const MAX_IMPORT_BYTES = 64 * 1024 * 1024;
|
|
2
|
+
/** Reuse the Explorer drop surface and App-owned exclusive new-file intent. */
|
|
3
|
+
export function importExplorerFiles(app, folder, files) {
|
|
4
|
+
return app.fileEditingSessions.track(async () => {
|
|
5
|
+
const imports = files.map((file) => {
|
|
6
|
+
const name = file.name;
|
|
7
|
+
if (!name || name === "." || name === ".." || /[\\/\0]/u.test(name))
|
|
8
|
+
throw new Error("Import requires a single valid filename");
|
|
9
|
+
if (!Number.isFinite(file.size) ||
|
|
10
|
+
file.size < 0 ||
|
|
11
|
+
file.size > MAX_IMPORT_BYTES)
|
|
12
|
+
throw new Error("Imported files must be at most 64 MiB");
|
|
13
|
+
return { file, path: folder ? `${folder}/${name}` : name };
|
|
14
|
+
});
|
|
15
|
+
for (const { file, path } of imports) {
|
|
16
|
+
const bytes = await file.arrayBuffer();
|
|
17
|
+
if (bytes.byteLength > MAX_IMPORT_BYTES)
|
|
18
|
+
throw new Error("Imported files must be at most 64 MiB");
|
|
19
|
+
await app.fileManager.createFile(path, bytes, {
|
|
20
|
+
mtime: file.lastModified,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { Notice, Plugin, TFile, View, basename, dirname, joinPath, } from "@lapis-notes/api";
|
|
2
|
-
import { getWorkspaceHostBinding } from "@lapis-notes/api/workspace-host";
|
|
3
2
|
import { ExplorerController } from "@lapismd/design-core/workspace/explorer";
|
|
4
3
|
import { mount, unmount } from "svelte";
|
|
5
4
|
import ExplorerPanel from "./LapisExplorerView.svelte";
|
|
6
5
|
import LapisLanding from "./LapisLandingView.svelte";
|
|
7
6
|
import { appendNativeExplorerMenu, copyExplorerText, } from "./native-explorer-actions";
|
|
8
7
|
import { openExplorerFile } from "./open-explorer-file";
|
|
8
|
+
import { importExplorerFiles } from "./explorer-imports";
|
|
9
9
|
import { DEFAULT_VAULT_PALETTE_FILE_EXTENSIONS, EXPLORER_SETTING_IDS, } from "./explorer-settings";
|
|
10
10
|
import { listExplorerVaultEntries } from "./explorer-vault-entries";
|
|
11
|
-
import { registerExplorerSettings } from "./register-explorer-settings";
|
|
11
|
+
import { readWorkspaceHostBinding, registerExplorerSettings, } from "./register-explorer-settings";
|
|
12
12
|
import { subscribeExplorerVaultTreeChanges } from "./explorer-tree-subscription";
|
|
13
13
|
import { listVaultPaletteFiles, VAULT_PALETTE_FILES_TAB, VAULT_PALETTE_RECENT_GROUP, } from "./vault-palette-files";
|
|
14
14
|
import { bindExplorerSelectionNotifications } from "./explorer-selection";
|
|
15
|
+
import { createExplorerFileIcons } from "./explorer-file-icons";
|
|
15
16
|
const EXPLORER_MANIFEST = {
|
|
16
17
|
id: "lapis-file-explorer",
|
|
17
18
|
name: "Explorer",
|
|
@@ -69,21 +70,37 @@ async function uniquePath(app, parent, requestedName) {
|
|
|
69
70
|
return path;
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
|
-
async function withNotice(operation) {
|
|
73
|
+
async function withNotice(app, operation) {
|
|
73
74
|
try {
|
|
74
|
-
return await
|
|
75
|
+
return await (app.vault.supportsConditionalTextWrites
|
|
76
|
+
? app.fileEditingSessions.track(operation)
|
|
77
|
+
: operation());
|
|
75
78
|
}
|
|
76
79
|
catch (error) {
|
|
77
|
-
new Notice(displayError(error));
|
|
80
|
+
new Notice(displayError(error), undefined, app);
|
|
78
81
|
throw error;
|
|
79
82
|
}
|
|
80
83
|
}
|
|
81
84
|
function createExplorerController(app, loading) {
|
|
85
|
+
const fileIcons = createExplorerFileIcons(app);
|
|
82
86
|
const controller = new ExplorerController({
|
|
83
87
|
loading,
|
|
88
|
+
getIcon: (node) => node.kind === "file" ? fileIcons.nameForPath(node.path) : undefined,
|
|
84
89
|
tree: {
|
|
85
90
|
listEntries: () => listExplorerVaultEntries(app.vault.getAllLoadedFiles()),
|
|
86
|
-
subscribe: (onChange) =>
|
|
91
|
+
subscribe: (onChange) => {
|
|
92
|
+
const refreshIcons = () => {
|
|
93
|
+
void fileIcons.publish().then(onChange);
|
|
94
|
+
};
|
|
95
|
+
const stopTree = subscribeExplorerVaultTreeChanges(app, onChange);
|
|
96
|
+
const stopIcons = app.workspace.onFileIconsChanged(refreshIcons);
|
|
97
|
+
refreshIcons();
|
|
98
|
+
return () => {
|
|
99
|
+
stopTree();
|
|
100
|
+
stopIcons();
|
|
101
|
+
fileIcons.release();
|
|
102
|
+
};
|
|
103
|
+
},
|
|
87
104
|
},
|
|
88
105
|
selection: {
|
|
89
106
|
subscribe: (onActivePath) => {
|
|
@@ -111,24 +128,25 @@ function createExplorerController(app, loading) {
|
|
|
111
128
|
setShowHiddenFiles: (value) => app.configuration.updateConfigurationOption(EXPLORER_SETTING_IDS.showHiddenFiles, value),
|
|
112
129
|
},
|
|
113
130
|
actions: {
|
|
114
|
-
|
|
131
|
+
importExternalFiles: (folder, files) => withNotice(app, () => importExplorerFiles(app, folder, files)),
|
|
132
|
+
openFile: (path, options) => withNotice(app, async () => {
|
|
115
133
|
const file = app.vault.getFileByPath(path);
|
|
116
134
|
if (!file)
|
|
117
135
|
throw new Error(`Unable to find file: ${path}`);
|
|
118
136
|
await openExplorerFile(app, file, options?.disposition ?? "current");
|
|
119
137
|
}),
|
|
120
|
-
createFile: (parent) => withNotice(async () => {
|
|
138
|
+
createFile: (parent) => withNotice(app, async () => {
|
|
121
139
|
const path = await uniquePath(app, parent, "Untitled.md");
|
|
122
|
-
const file = await app.
|
|
140
|
+
const file = await app.fileManager.createFile(path, "");
|
|
123
141
|
await app.openFile(file);
|
|
124
142
|
return path;
|
|
125
143
|
}),
|
|
126
|
-
createFolder: (parent) => withNotice(async () => {
|
|
144
|
+
createFolder: (parent) => withNotice(app, async () => {
|
|
127
145
|
const path = await uniquePath(app, parent, "New folder");
|
|
128
146
|
await app.vault.createFolder(path);
|
|
129
147
|
return path;
|
|
130
148
|
}),
|
|
131
|
-
rename: (path, nextName) => withNotice(async () => {
|
|
149
|
+
rename: (path, nextName) => withNotice(app, async () => {
|
|
132
150
|
const file = app.vault.getAbstractFileByPath(path);
|
|
133
151
|
if (!file)
|
|
134
152
|
throw new Error(`Unable to find path: ${path}`);
|
|
@@ -139,7 +157,7 @@ function createExplorerController(app, loading) {
|
|
|
139
157
|
await app.fileManager.renameFile(file, nextPath);
|
|
140
158
|
return nextPath;
|
|
141
159
|
}),
|
|
142
|
-
move: (path, destinationFolderPath) => withNotice(async () => {
|
|
160
|
+
move: (path, destinationFolderPath) => withNotice(app, async () => {
|
|
143
161
|
const file = app.vault.getAbstractFileByPath(path);
|
|
144
162
|
if (!file)
|
|
145
163
|
throw new Error(`Unable to find path: ${path}`);
|
|
@@ -150,13 +168,13 @@ function createExplorerController(app, loading) {
|
|
|
150
168
|
await app.fileManager.renameFile(file, nextPath);
|
|
151
169
|
return nextPath;
|
|
152
170
|
}),
|
|
153
|
-
delete: (path) => withNotice(async () => {
|
|
171
|
+
delete: (path) => withNotice(app, async () => {
|
|
154
172
|
const file = app.vault.getAbstractFileByPath(path);
|
|
155
173
|
if (!file)
|
|
156
174
|
throw new Error(`Unable to find path: ${path}`);
|
|
157
|
-
await app.
|
|
175
|
+
await app.fileManager.trashFile(file);
|
|
158
176
|
}),
|
|
159
|
-
copyText: copyExplorerText,
|
|
177
|
+
copyText: (label, value) => withNotice(app, () => copyExplorerText(app, label, value)),
|
|
160
178
|
},
|
|
161
179
|
buildItemMenu: (menu, node) => {
|
|
162
180
|
appendNativeExplorerMenu(menu, node, app);
|
|
@@ -227,11 +245,11 @@ export class LapisLandingView extends View {
|
|
|
227
245
|
id: "create-note",
|
|
228
246
|
label: "Create new note",
|
|
229
247
|
icon: "square-pen",
|
|
230
|
-
onSelect: async () => {
|
|
248
|
+
onSelect: () => withNotice(this.app, async () => {
|
|
231
249
|
const path = await uniquePath(this.app, "", "Untitled.md");
|
|
232
|
-
const file = await this.app.
|
|
250
|
+
const file = await this.app.fileManager.createFile(path, "");
|
|
233
251
|
await this.app.openFile(file);
|
|
234
|
-
},
|
|
252
|
+
}),
|
|
235
253
|
},
|
|
236
254
|
{
|
|
237
255
|
id: "go-to-file",
|
|
@@ -277,7 +295,7 @@ export function createFileExplorerPlugin(options = {}) {
|
|
|
277
295
|
this.register(() => {
|
|
278
296
|
this.app.configuration.schema.unregister(EXPLORER_SCHEMA);
|
|
279
297
|
});
|
|
280
|
-
registerExplorerSettings(this);
|
|
298
|
+
const settingsRegistered = registerExplorerSettings(this);
|
|
281
299
|
const configurationRef = this.app.configuration.on("updated", ({ key, value }) => {
|
|
282
300
|
if (key !== EXPLORER_SETTING_IDS.showHiddenFiles)
|
|
283
301
|
return;
|
|
@@ -321,32 +339,52 @@ export function createFileExplorerPlugin(options = {}) {
|
|
|
321
339
|
void this.app.configuration.updateConfigurationOption(EXPLORER_SETTING_IDS.showHiddenFiles, !readShowHiddenFiles(this.app));
|
|
322
340
|
},
|
|
323
341
|
});
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
342
|
+
let paletteAttached = false;
|
|
343
|
+
let paletteDisposed = false;
|
|
344
|
+
this.register(() => {
|
|
345
|
+
paletteDisposed = true;
|
|
346
|
+
});
|
|
347
|
+
const attachVaultPalette = () => {
|
|
348
|
+
if (paletteDisposed || paletteAttached)
|
|
349
|
+
return;
|
|
350
|
+
const binding = readWorkspaceHostBinding(this.app.workspace);
|
|
351
|
+
if (!binding)
|
|
352
|
+
return;
|
|
353
|
+
paletteAttached = true;
|
|
354
|
+
this.register(binding.controller.commands.registerPaletteProvider({
|
|
355
|
+
id: "lapis-vault-files",
|
|
356
|
+
tab: VAULT_PALETTE_FILES_TAB,
|
|
357
|
+
emptyQueryLimit: 5,
|
|
358
|
+
search: (query) => {
|
|
359
|
+
const starterFiles = query.trim().length === 0;
|
|
360
|
+
const recentPaths = starterFiles
|
|
361
|
+
? new Set(this.app.workspace
|
|
362
|
+
.getRecentFiles()
|
|
363
|
+
.map((recent) => recent.path))
|
|
364
|
+
: null;
|
|
365
|
+
return listVaultPaletteFiles(this.app, query).map((file) => ({
|
|
366
|
+
id: `vault-file:${file.path}`,
|
|
367
|
+
title: file.name,
|
|
368
|
+
subtitle: file.path,
|
|
369
|
+
icon: "file",
|
|
370
|
+
providerId: "lapis-vault-files",
|
|
371
|
+
tab: VAULT_PALETTE_FILES_TAB.id,
|
|
372
|
+
group: recentPaths?.has(file.path)
|
|
373
|
+
? VAULT_PALETTE_RECENT_GROUP
|
|
374
|
+
: undefined,
|
|
375
|
+
run: () => this.app.openFile(file),
|
|
376
|
+
}));
|
|
377
|
+
},
|
|
378
|
+
}));
|
|
379
|
+
};
|
|
380
|
+
attachVaultPalette();
|
|
381
|
+
if (!paletteAttached || !settingsRegistered) {
|
|
382
|
+
this.app.workspace.onLayoutReady(() => {
|
|
383
|
+
if (!settingsRegistered)
|
|
384
|
+
registerExplorerSettings(this);
|
|
385
|
+
attachVaultPalette();
|
|
386
|
+
});
|
|
387
|
+
}
|
|
350
388
|
await this.app.configuration.materializeSchemaDefaults();
|
|
351
389
|
}
|
|
352
390
|
syncShowHiddenFiles(value) {
|
|
@@ -4,5 +4,5 @@ import type { ExplorerNode } from "@lapismd/design-core/workspace/explorer";
|
|
|
4
4
|
import type { WorkspaceMenu } from "@lapismd/design-core/workspace/core";
|
|
5
5
|
export declare function hasNativeExplorerActions(adapter: App["vault"]["adapter"]): adapter is NativeDesktopVaultAdapter;
|
|
6
6
|
export declare function revealInFileManagerLabel(os: NativeDesktopPlatformOs | undefined): string;
|
|
7
|
-
export declare function copyExplorerText(label: string, value: string): Promise<void>;
|
|
7
|
+
export declare function copyExplorerText(app: App, label: string, value: string): Promise<void>;
|
|
8
8
|
export declare function appendNativeExplorerMenu(menu: WorkspaceMenu, node: ExplorerNode, app: App): void;
|
|
@@ -11,11 +11,11 @@ export function revealInFileManagerLabel(os) {
|
|
|
11
11
|
return "Reveal in File Explorer";
|
|
12
12
|
return "Reveal in file manager";
|
|
13
13
|
}
|
|
14
|
-
export async function copyExplorerText(label, value) {
|
|
15
|
-
if (typeof navigator
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
new Notice(`Copied ${label}
|
|
14
|
+
export async function copyExplorerText(app, label, value) {
|
|
15
|
+
if (typeof navigator === "undefined" || !navigator.clipboard?.writeText)
|
|
16
|
+
throw new Error("Clipboard is unavailable");
|
|
17
|
+
await navigator.clipboard.writeText(value);
|
|
18
|
+
new Notice(`Copied ${label}`, undefined, app);
|
|
19
19
|
}
|
|
20
20
|
function vaultPath(node) {
|
|
21
21
|
return node.path === "/" ? "/" : node.path;
|
|
@@ -26,49 +26,52 @@ export function appendNativeExplorerMenu(menu, node, app) {
|
|
|
26
26
|
return;
|
|
27
27
|
const copyPath = menu.entries.find((entry) => entry.kind === "submenu" && entry.title === "Copy Path");
|
|
28
28
|
if (copyPath?.kind === "submenu") {
|
|
29
|
-
copyPath.menu.addItem((item) => item
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
copyPath.menu.addItem((item) => item
|
|
30
|
+
.setTitle("From system root")
|
|
31
|
+
.onClick(() => copySystemRootPath(app, adapter, node)));
|
|
32
32
|
if (node.kind === "file") {
|
|
33
33
|
copyPath.menu.addItem((item) => item.setTitle("As Lapis URL").onClick(() => {
|
|
34
|
-
|
|
34
|
+
return copyExplorerText(app, "Lapis URL", createLapisFileUrl(app.vault.getName(), vaultPath(node))).catch((error) => {
|
|
35
|
+
report(app, error);
|
|
36
|
+
});
|
|
35
37
|
}));
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
menu.addSeparator();
|
|
39
|
-
menu.addItem((item) => item
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
menu.addItem((item) => item
|
|
42
|
+
.setTitle("Open in default app")
|
|
43
|
+
.onClick(() => openVaultPath(app, adapter, node)));
|
|
42
44
|
menu.addItem((item) => item
|
|
43
45
|
.setTitle(revealInFileManagerLabel(getNativeDesktopPlatform()?.os))
|
|
44
|
-
.onClick(() =>
|
|
45
|
-
void revealVaultPath(adapter, node);
|
|
46
|
-
}));
|
|
46
|
+
.onClick(() => revealVaultPath(app, adapter, node)));
|
|
47
47
|
}
|
|
48
|
-
async function copySystemRootPath(adapter, node) {
|
|
48
|
+
async function copySystemRootPath(app, adapter, node) {
|
|
49
49
|
try {
|
|
50
50
|
const absolute = await resolveNativeDesktopVaultPath(adapter.rootPath, vaultPath(node));
|
|
51
51
|
if (!absolute)
|
|
52
52
|
throw new Error("Unable to resolve the system path");
|
|
53
|
-
await copyExplorerText("system path", absolute);
|
|
53
|
+
await copyExplorerText(app, "system path", absolute);
|
|
54
54
|
}
|
|
55
55
|
catch (error) {
|
|
56
|
-
|
|
56
|
+
report(app, error);
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
-
async function openVaultPath(adapter, node) {
|
|
59
|
+
async function openVaultPath(app, adapter, node) {
|
|
60
60
|
try {
|
|
61
61
|
await openNativeDesktopVaultPath(adapter.rootPath, vaultPath(node));
|
|
62
62
|
}
|
|
63
63
|
catch (error) {
|
|
64
|
-
|
|
64
|
+
report(app, error);
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
-
async function revealVaultPath(adapter, node) {
|
|
67
|
+
async function revealVaultPath(app, adapter, node) {
|
|
68
68
|
try {
|
|
69
69
|
await revealNativeDesktopVaultPath(adapter.rootPath, vaultPath(node));
|
|
70
70
|
}
|
|
71
71
|
catch (error) {
|
|
72
|
-
|
|
72
|
+
report(app, error);
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
|
+
function report(app, error) {
|
|
76
|
+
new Notice(error instanceof Error ? error.message : String(error), undefined, app);
|
|
77
|
+
}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { findOpenFileLeaf, } from "@lapis-notes/api";
|
|
2
2
|
export async function openExplorerFile(app, file, disposition) {
|
|
3
3
|
if (disposition === "current") {
|
|
4
|
+
const activeRootLeaf = app.workspace.activeRootLeaf;
|
|
5
|
+
if (activeRootLeaf?.view.getViewType() === "lapis-landing") {
|
|
6
|
+
app.workspace.activeLeaf = activeRootLeaf;
|
|
7
|
+
await activeRootLeaf.openFile(file);
|
|
8
|
+
await app.workspace.revealLeaf(activeRootLeaf);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
4
11
|
await app.openFile(file);
|
|
5
12
|
return;
|
|
6
13
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Plugin } from "@lapis-notes/api";
|
|
2
|
+
import { getWorkspaceHostBinding, type WorkspaceHostBinding } from "@lapis-notes/api/workspace-host";
|
|
2
3
|
export declare function createExplorerSettingsSection(): {
|
|
3
4
|
id: string;
|
|
4
5
|
title: string;
|
|
@@ -27,7 +28,8 @@ export declare function createExplorerSettingsSection(): {
|
|
|
27
28
|
itemType: "string";
|
|
28
29
|
title: string;
|
|
29
30
|
description: string;
|
|
30
|
-
default: ("text" | "
|
|
31
|
+
default: ("text" | "md" | "markdown" | "txt" | "json" | "data" | "yaml" | "yml")[];
|
|
31
32
|
})[];
|
|
32
33
|
};
|
|
33
|
-
export declare function
|
|
34
|
+
export declare function readWorkspaceHostBinding(workspace: Parameters<typeof getWorkspaceHostBinding>[0]): WorkspaceHostBinding | null;
|
|
35
|
+
export declare function registerExplorerSettings(plugin: Plugin): boolean;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getWorkspaceHostBinding } from "@lapis-notes/api/workspace-host";
|
|
1
|
+
import { getWorkspaceHostBinding, } from "@lapis-notes/api/workspace-host";
|
|
2
2
|
import { DEFAULT_VAULT_PALETTE_FILE_EXTENSIONS, EXPLORER_SETTING_IDS, EXPLORER_SETTINGS_SECTION_ID, } from "./explorer-settings";
|
|
3
3
|
export function createExplorerSettingsSection() {
|
|
4
4
|
return {
|
|
@@ -35,11 +35,24 @@ export function createExplorerSettingsSection() {
|
|
|
35
35
|
],
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
|
+
export function readWorkspaceHostBinding(workspace) {
|
|
39
|
+
try {
|
|
40
|
+
return getWorkspaceHostBinding(workspace);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error instanceof Error &&
|
|
44
|
+
error.message === "Workspace host binding is not initialized") {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
38
50
|
export function registerExplorerSettings(plugin) {
|
|
39
51
|
if (!plugin.app.workspace)
|
|
40
|
-
return;
|
|
41
|
-
const binding =
|
|
52
|
+
return false;
|
|
53
|
+
const binding = readWorkspaceHostBinding(plugin.app.workspace);
|
|
42
54
|
if (!binding)
|
|
43
|
-
return;
|
|
55
|
+
return false;
|
|
44
56
|
plugin.register(binding.controller.registerSettingsSection(createExplorerSettingsSection()));
|
|
57
|
+
return true;
|
|
45
58
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lapis-notes/file-explorer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Reusable Lapis Notes file explorer plugin",
|
|
5
|
-
"license": "
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/lapismd/lapis-notes.git",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@lapis-notes/api": "^0.
|
|
42
|
-
"@lapismd/design-core": "^0.
|
|
41
|
+
"@lapis-notes/api": "^0.2.0",
|
|
42
|
+
"@lapismd/design-core": "^0.7.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"svelte": "^5.0.0"
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"@sveltejs/vite-plugin-svelte": "^5.1.1",
|
|
50
50
|
"@tsconfig/svelte": "^5.0.5",
|
|
51
51
|
"publint": "^0.3.12",
|
|
52
|
-
"svelte": "
|
|
52
|
+
"svelte": "5.56.10",
|
|
53
53
|
"svelte-check": "^4.3.1",
|
|
54
54
|
"typescript": "~5.8.3",
|
|
55
55
|
"vite": "^6.4.2",
|