@lapis-notes/file-explorer 0.1.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 +7 -0
- package/LICENSE.md +4 -0
- package/README.md +32 -0
- package/dist/LapisExplorerView.svelte +21 -0
- package/dist/LapisExplorerView.svelte.d.ts +7 -0
- package/dist/LapisLandingView.svelte +19 -0
- package/dist/LapisLandingView.svelte.d.ts +7 -0
- package/dist/explorer-selection.d.ts +12 -0
- package/dist/explorer-selection.js +25 -0
- package/dist/explorer-settings.d.ts +7 -0
- package/dist/explorer-settings.js +16 -0
- package/dist/explorer-tree-subscription.d.ts +2 -0
- package/dist/explorer-tree-subscription.js +12 -0
- package/dist/explorer-vault-entries.d.ts +5 -0
- package/dist/explorer-vault-entries.js +14 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +378 -0
- package/dist/native-explorer-actions.d.ts +8 -0
- package/dist/native-explorer-actions.js +74 -0
- package/dist/open-explorer-file.d.ts +3 -0
- package/dist/open-explorer-file.js +20 -0
- package/dist/register-explorer-settings.d.ts +33 -0
- package/dist/register-explorer-settings.js +45 -0
- package/dist/vault-palette-files.d.ts +9 -0
- package/dist/vault-palette-files.js +53 -0
- package/dist/vault-path-visibility.d.ts +4 -0
- package/dist/vault-path-visibility.js +9 -0
- package/package.json +65 -0
package/CHANGELOG.md
ADDED
package/LICENSE.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# @lapis-notes/file-explorer
|
|
2
|
+
|
|
3
|
+
Reusable File Explorer plugin for applications built on the Lapis framework.
|
|
4
|
+
|
|
5
|
+
The package owns Explorer views, commands, palette entries, and presentation
|
|
6
|
+
settings. Applications retain vault selection, session boot, persistence
|
|
7
|
+
policy, and host integration.
|
|
8
|
+
|
|
9
|
+
## Public surface
|
|
10
|
+
|
|
11
|
+
- `FileExplorerPlugin`
|
|
12
|
+
- `createFileExplorerPlugin`
|
|
13
|
+
- `ExplorerPanel`
|
|
14
|
+
- `FileExplorerViewType`
|
|
15
|
+
- `FILE_EXPLORER_SELECTION_CHANGE_EVENT`
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { FileExplorerPlugin } from "@lapis-notes/file-explorer";
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Install `@lapis-notes/api`, `@lapismd/design-core`, and Svelte from npm with
|
|
24
|
+
compatible versions.
|
|
25
|
+
|
|
26
|
+
## Common scripts
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
pnpm --filter @lapis-notes/file-explorer build
|
|
30
|
+
pnpm --filter @lapis-notes/file-explorer check
|
|
31
|
+
pnpm --filter @lapis-notes/file-explorer test
|
|
32
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
WorkspaceExplorer,
|
|
4
|
+
type ExplorerController,
|
|
5
|
+
} from "@lapismd/design-core/workspace/explorer";
|
|
6
|
+
|
|
7
|
+
let { controller }: { controller: ExplorerController } = $props();
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<div class="lapis-editor-demo-explorer" data-testid="lapis-editor-explorer">
|
|
11
|
+
<WorkspaceExplorer {controller} />
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<style>
|
|
15
|
+
.lapis-editor-demo-explorer {
|
|
16
|
+
width: 100%;
|
|
17
|
+
height: 100%;
|
|
18
|
+
min-width: 0;
|
|
19
|
+
min-height: 0;
|
|
20
|
+
}
|
|
21
|
+
</style>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ExplorerController } from "@lapismd/design-core/workspace/explorer";
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
controller: ExplorerController;
|
|
4
|
+
};
|
|
5
|
+
declare const LapisExplorerView: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
6
|
+
type LapisExplorerView = ReturnType<typeof LapisExplorerView>;
|
|
7
|
+
export default LapisExplorerView;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { WorkspaceEmpty } from "@lapismd/design-core/workspace/empty";
|
|
3
|
+
import type { WorkspaceAction } from "@lapismd/design-core/workspace/core";
|
|
4
|
+
|
|
5
|
+
let { actions }: { actions: WorkspaceAction[] } = $props();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<div class="lapis-editor-demo-landing" data-testid="lapis-editor-landing">
|
|
9
|
+
<WorkspaceEmpty {actions} surface="page" />
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<style>
|
|
13
|
+
.lapis-editor-demo-landing {
|
|
14
|
+
width: 100%;
|
|
15
|
+
height: 100%;
|
|
16
|
+
min-width: 0;
|
|
17
|
+
min-height: 0;
|
|
18
|
+
}
|
|
19
|
+
</style>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { WorkspaceAction } from "@lapismd/design-core/workspace/core";
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
actions: WorkspaceAction[];
|
|
4
|
+
};
|
|
5
|
+
declare const LapisLandingView: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
6
|
+
type LapisLandingView = ReturnType<typeof LapisLandingView>;
|
|
7
|
+
export default LapisLandingView;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { App } from "@lapis-notes/api";
|
|
2
|
+
export declare const FILE_EXPLORER_SELECTION_CHANGE_EVENT = "file-explorer:selection-change";
|
|
3
|
+
export type ExplorerSelectionController = {
|
|
4
|
+
selectedPath: string;
|
|
5
|
+
setSelectedPath(path: string): void;
|
|
6
|
+
selectRoot(): void;
|
|
7
|
+
revealPath(path: string, options?: {
|
|
8
|
+
flash?: boolean;
|
|
9
|
+
}): void;
|
|
10
|
+
};
|
|
11
|
+
export declare function notifyFileExplorerSelection(app: App, path: string): void;
|
|
12
|
+
export declare function bindExplorerSelectionNotifications(app: App, controller: ExplorerSelectionController): void;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const FILE_EXPLORER_SELECTION_CHANGE_EVENT = "file-explorer:selection-change";
|
|
2
|
+
function workspaceEvents(app) {
|
|
3
|
+
return app.workspace;
|
|
4
|
+
}
|
|
5
|
+
export function notifyFileExplorerSelection(app, path) {
|
|
6
|
+
workspaceEvents(app).trigger(FILE_EXPLORER_SELECTION_CHANGE_EVENT, path);
|
|
7
|
+
}
|
|
8
|
+
export function bindExplorerSelectionNotifications(app, controller) {
|
|
9
|
+
const notify = () => notifyFileExplorerSelection(app, controller.selectedPath);
|
|
10
|
+
const setSelectedPath = controller.setSelectedPath.bind(controller);
|
|
11
|
+
const selectRoot = controller.selectRoot.bind(controller);
|
|
12
|
+
const revealPath = controller.revealPath.bind(controller);
|
|
13
|
+
controller.setSelectedPath = (path) => {
|
|
14
|
+
setSelectedPath(path);
|
|
15
|
+
notify();
|
|
16
|
+
};
|
|
17
|
+
controller.selectRoot = () => {
|
|
18
|
+
selectRoot();
|
|
19
|
+
notify();
|
|
20
|
+
};
|
|
21
|
+
controller.revealPath = (path, options) => {
|
|
22
|
+
revealPath(path, options);
|
|
23
|
+
notify();
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const EXPLORER_SETTINGS_SECTION_ID = "lapis-file-explorer";
|
|
2
|
+
export declare const EXPLORER_SETTING_IDS: {
|
|
3
|
+
readonly autoRevealCurrentFile: "workspace.fileExplorer.autoRevealCurrentFile";
|
|
4
|
+
readonly showHiddenFiles: "workspace.fileExplorer.showHiddenFiles";
|
|
5
|
+
readonly paletteFileExtensions: "workspace.fileExplorer.paletteFileExtensions";
|
|
6
|
+
};
|
|
7
|
+
export declare const DEFAULT_VAULT_PALETTE_FILE_EXTENSIONS: readonly ["md", "markdown", "txt", "text", "json", "data", "yaml", "yml"];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const EXPLORER_SETTINGS_SECTION_ID = "lapis-file-explorer";
|
|
2
|
+
export const EXPLORER_SETTING_IDS = {
|
|
3
|
+
autoRevealCurrentFile: "workspace.fileExplorer.autoRevealCurrentFile",
|
|
4
|
+
showHiddenFiles: "workspace.fileExplorer.showHiddenFiles",
|
|
5
|
+
paletteFileExtensions: "workspace.fileExplorer.paletteFileExtensions",
|
|
6
|
+
};
|
|
7
|
+
export const DEFAULT_VAULT_PALETTE_FILE_EXTENSIONS = [
|
|
8
|
+
"md",
|
|
9
|
+
"markdown",
|
|
10
|
+
"txt",
|
|
11
|
+
"text",
|
|
12
|
+
"json",
|
|
13
|
+
"data",
|
|
14
|
+
"yaml",
|
|
15
|
+
"yml",
|
|
16
|
+
];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function subscribeExplorerVaultTreeChanges(app, onChange) {
|
|
2
|
+
const loaded = app.vault.on("load", onChange);
|
|
3
|
+
const created = app.vault.on("create", onChange);
|
|
4
|
+
const deleted = app.vault.on("delete", onChange);
|
|
5
|
+
const renamed = app.vault.on("rename", onChange);
|
|
6
|
+
return () => {
|
|
7
|
+
app.vault.offref(loaded);
|
|
8
|
+
app.vault.offref(created);
|
|
9
|
+
app.vault.offref(deleted);
|
|
10
|
+
app.vault.offref(renamed);
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type TAbstractFile } from "@lapis-notes/api";
|
|
2
|
+
import type { ExplorerNode } from "@lapismd/design-core/workspace/explorer";
|
|
3
|
+
export declare function toExplorerNode(file: TAbstractFile): ExplorerNode;
|
|
4
|
+
/** Pass every vault path except `/`. Hidden-name filtering belongs to Design Core. */
|
|
5
|
+
export declare function listExplorerVaultEntries(files: readonly TAbstractFile[]): ExplorerNode[];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { TFolder } from "@lapis-notes/api";
|
|
2
|
+
export function toExplorerNode(file) {
|
|
3
|
+
return {
|
|
4
|
+
path: file.path.replace(/^\/+/, ""),
|
|
5
|
+
name: file.name,
|
|
6
|
+
kind: file instanceof TFolder ? "folder" : "file",
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
/** Pass every vault path except `/`. Hidden-name filtering belongs to Design Core. */
|
|
10
|
+
export function listExplorerVaultEntries(files) {
|
|
11
|
+
return files
|
|
12
|
+
.filter((file) => file.path.replace(/^\/+/, "").length > 0)
|
|
13
|
+
.map(toExplorerNode);
|
|
14
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { View, type PluginConstructor, type WorkspaceLeaf } from "@lapis-notes/api";
|
|
2
|
+
export declare class FileExplorerView extends View {
|
|
3
|
+
#private;
|
|
4
|
+
constructor(leaf: WorkspaceLeaf, loading: boolean);
|
|
5
|
+
get selectedPath(): string;
|
|
6
|
+
getViewType(): string;
|
|
7
|
+
getDisplayText(): string;
|
|
8
|
+
revealPath(path: string): void;
|
|
9
|
+
applyShowHiddenFiles(value: boolean): void;
|
|
10
|
+
load(): void;
|
|
11
|
+
unload(): void;
|
|
12
|
+
protected onOpen(): Promise<void>;
|
|
13
|
+
protected onClose(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
export declare class LapisLandingView extends View {
|
|
16
|
+
#private;
|
|
17
|
+
getViewType(): string;
|
|
18
|
+
getDisplayText(): string;
|
|
19
|
+
load(): void;
|
|
20
|
+
unload(): void;
|
|
21
|
+
protected onOpen(): Promise<void>;
|
|
22
|
+
protected onClose(): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
export declare function createFileExplorerPlugin(options?: {
|
|
25
|
+
loading?: boolean;
|
|
26
|
+
}): PluginConstructor;
|
|
27
|
+
export declare const FileExplorerPlugin: PluginConstructor;
|
|
28
|
+
export declare const FileExplorerViewType = "file-explorer";
|
|
29
|
+
export { FILE_EXPLORER_SELECTION_CHANGE_EVENT } from "./explorer-selection";
|
|
30
|
+
export { default as ExplorerPanel } from "./LapisExplorerView.svelte";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import { Notice, Plugin, TFile, View, basename, dirname, joinPath, } from "@lapis-notes/api";
|
|
2
|
+
import { getWorkspaceHostBinding } from "@lapis-notes/api/workspace-host";
|
|
3
|
+
import { ExplorerController } from "@lapismd/design-core/workspace/explorer";
|
|
4
|
+
import { mount, unmount } from "svelte";
|
|
5
|
+
import ExplorerPanel from "./LapisExplorerView.svelte";
|
|
6
|
+
import LapisLanding from "./LapisLandingView.svelte";
|
|
7
|
+
import { appendNativeExplorerMenu, copyExplorerText, } from "./native-explorer-actions";
|
|
8
|
+
import { openExplorerFile } from "./open-explorer-file";
|
|
9
|
+
import { DEFAULT_VAULT_PALETTE_FILE_EXTENSIONS, EXPLORER_SETTING_IDS, } from "./explorer-settings";
|
|
10
|
+
import { listExplorerVaultEntries } from "./explorer-vault-entries";
|
|
11
|
+
import { registerExplorerSettings } from "./register-explorer-settings";
|
|
12
|
+
import { subscribeExplorerVaultTreeChanges } from "./explorer-tree-subscription";
|
|
13
|
+
import { listVaultPaletteFiles, VAULT_PALETTE_FILES_TAB, VAULT_PALETTE_RECENT_GROUP, } from "./vault-palette-files";
|
|
14
|
+
import { bindExplorerSelectionNotifications } from "./explorer-selection";
|
|
15
|
+
const EXPLORER_MANIFEST = {
|
|
16
|
+
id: "lapis-file-explorer",
|
|
17
|
+
name: "Explorer",
|
|
18
|
+
author: "Lapis Notes",
|
|
19
|
+
version: "0.1.0",
|
|
20
|
+
minAppVersion: "0.0.1",
|
|
21
|
+
description: "Lapis file explorer backed by the API vault.",
|
|
22
|
+
};
|
|
23
|
+
const EXPLORER_SCHEMA = {
|
|
24
|
+
id: "workspace",
|
|
25
|
+
title: "Workspace",
|
|
26
|
+
type: "object",
|
|
27
|
+
properties: {
|
|
28
|
+
[EXPLORER_SETTING_IDS.autoRevealCurrentFile]: {
|
|
29
|
+
title: "Auto-reveal current file",
|
|
30
|
+
description: "Reveal the active file in Explorer.",
|
|
31
|
+
type: "boolean",
|
|
32
|
+
default: true,
|
|
33
|
+
},
|
|
34
|
+
[EXPLORER_SETTING_IDS.showHiddenFiles]: {
|
|
35
|
+
title: "Show hidden files",
|
|
36
|
+
description: "Show dotted names, including .obsidian, .trash, and .lapis.",
|
|
37
|
+
type: "boolean",
|
|
38
|
+
default: false,
|
|
39
|
+
},
|
|
40
|
+
[EXPLORER_SETTING_IDS.paletteFileExtensions]: {
|
|
41
|
+
title: "File palette extensions",
|
|
42
|
+
description: "Extensions shown by Go to file, without leading dots. An empty list hides every file from the Files palette.",
|
|
43
|
+
type: "array",
|
|
44
|
+
items: { type: "string" },
|
|
45
|
+
default: [...DEFAULT_VAULT_PALETTE_FILE_EXTENSIONS],
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
function readShowHiddenFiles(app) {
|
|
50
|
+
return app.configuration
|
|
51
|
+
.getConfiguration()
|
|
52
|
+
.get(EXPLORER_SETTING_IDS.showHiddenFiles, false);
|
|
53
|
+
}
|
|
54
|
+
function displayError(error) {
|
|
55
|
+
return error instanceof Error ? error.message : String(error);
|
|
56
|
+
}
|
|
57
|
+
function parentPath(path) {
|
|
58
|
+
const parent = dirname(path);
|
|
59
|
+
return parent === "/" ? "" : parent;
|
|
60
|
+
}
|
|
61
|
+
async function uniquePath(app, parent, requestedName) {
|
|
62
|
+
const dot = requestedName.lastIndexOf(".");
|
|
63
|
+
const base = dot > 0 ? requestedName.slice(0, dot) : requestedName;
|
|
64
|
+
const extension = dot > 0 ? requestedName.slice(dot) : "";
|
|
65
|
+
for (let suffix = 0;; suffix += 1) {
|
|
66
|
+
const name = `${base}${suffix === 0 ? "" : ` ${suffix + 1}`}${extension}`;
|
|
67
|
+
const path = parent ? joinPath(parent, name) : name;
|
|
68
|
+
if (!(await app.vault.exists(path)))
|
|
69
|
+
return path;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function withNotice(operation) {
|
|
73
|
+
try {
|
|
74
|
+
return await operation();
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
new Notice(displayError(error));
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function createExplorerController(app, loading) {
|
|
82
|
+
const controller = new ExplorerController({
|
|
83
|
+
loading,
|
|
84
|
+
tree: {
|
|
85
|
+
listEntries: () => listExplorerVaultEntries(app.vault.getAllLoadedFiles()),
|
|
86
|
+
subscribe: (onChange) => subscribeExplorerVaultTreeChanges(app, onChange),
|
|
87
|
+
},
|
|
88
|
+
selection: {
|
|
89
|
+
subscribe: (onActivePath) => {
|
|
90
|
+
// Dedupe so breadcrumb/folder revealPath is not immediately overwritten
|
|
91
|
+
// when focus moves within the same active file leaf.
|
|
92
|
+
let lastPath;
|
|
93
|
+
const update = () => {
|
|
94
|
+
const path = app.workspace.activeEditor?.file?.path ?? null;
|
|
95
|
+
if (path === lastPath)
|
|
96
|
+
return;
|
|
97
|
+
lastPath = path;
|
|
98
|
+
onActivePath(path);
|
|
99
|
+
};
|
|
100
|
+
const ref = app.workspace.on("active-leaf-change", update);
|
|
101
|
+
update();
|
|
102
|
+
return () => app.workspace.offref(ref);
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
preferences: {
|
|
106
|
+
getAutoReveal: () => app.configuration
|
|
107
|
+
.getConfiguration()
|
|
108
|
+
.get(EXPLORER_SETTING_IDS.autoRevealCurrentFile, true),
|
|
109
|
+
setAutoReveal: (value) => app.configuration.updateConfigurationOption(EXPLORER_SETTING_IDS.autoRevealCurrentFile, value),
|
|
110
|
+
getShowHiddenFiles: () => readShowHiddenFiles(app),
|
|
111
|
+
setShowHiddenFiles: (value) => app.configuration.updateConfigurationOption(EXPLORER_SETTING_IDS.showHiddenFiles, value),
|
|
112
|
+
},
|
|
113
|
+
actions: {
|
|
114
|
+
openFile: (path, options) => withNotice(async () => {
|
|
115
|
+
const file = app.vault.getFileByPath(path);
|
|
116
|
+
if (!file)
|
|
117
|
+
throw new Error(`Unable to find file: ${path}`);
|
|
118
|
+
await openExplorerFile(app, file, options?.disposition ?? "current");
|
|
119
|
+
}),
|
|
120
|
+
createFile: (parent) => withNotice(async () => {
|
|
121
|
+
const path = await uniquePath(app, parent, "Untitled.md");
|
|
122
|
+
const file = await app.vault.create(path, "");
|
|
123
|
+
await app.openFile(file);
|
|
124
|
+
return path;
|
|
125
|
+
}),
|
|
126
|
+
createFolder: (parent) => withNotice(async () => {
|
|
127
|
+
const path = await uniquePath(app, parent, "New folder");
|
|
128
|
+
await app.vault.createFolder(path);
|
|
129
|
+
return path;
|
|
130
|
+
}),
|
|
131
|
+
rename: (path, nextName) => withNotice(async () => {
|
|
132
|
+
const file = app.vault.getAbstractFileByPath(path);
|
|
133
|
+
if (!file)
|
|
134
|
+
throw new Error(`Unable to find path: ${path}`);
|
|
135
|
+
const nextPath = joinPath(parentPath(path), nextName);
|
|
136
|
+
if (await app.vault.exists(nextPath)) {
|
|
137
|
+
throw new Error(`A file already exists at ${nextPath}`);
|
|
138
|
+
}
|
|
139
|
+
await app.fileManager.renameFile(file, nextPath);
|
|
140
|
+
return nextPath;
|
|
141
|
+
}),
|
|
142
|
+
move: (path, destinationFolderPath) => withNotice(async () => {
|
|
143
|
+
const file = app.vault.getAbstractFileByPath(path);
|
|
144
|
+
if (!file)
|
|
145
|
+
throw new Error(`Unable to find path: ${path}`);
|
|
146
|
+
const nextPath = joinPath(destinationFolderPath, basename(path));
|
|
147
|
+
if (await app.vault.exists(nextPath)) {
|
|
148
|
+
throw new Error(`A file already exists at ${nextPath}`);
|
|
149
|
+
}
|
|
150
|
+
await app.fileManager.renameFile(file, nextPath);
|
|
151
|
+
return nextPath;
|
|
152
|
+
}),
|
|
153
|
+
delete: (path) => withNotice(async () => {
|
|
154
|
+
const file = app.vault.getAbstractFileByPath(path);
|
|
155
|
+
if (!file)
|
|
156
|
+
throw new Error(`Unable to find path: ${path}`);
|
|
157
|
+
await app.vault.trash(file, false);
|
|
158
|
+
}),
|
|
159
|
+
copyText: copyExplorerText,
|
|
160
|
+
},
|
|
161
|
+
buildItemMenu: (menu, node) => {
|
|
162
|
+
appendNativeExplorerMenu(menu, node, app);
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
bindExplorerSelectionNotifications(app, controller);
|
|
166
|
+
return controller;
|
|
167
|
+
}
|
|
168
|
+
export class FileExplorerView extends View {
|
|
169
|
+
#controller;
|
|
170
|
+
#component = null;
|
|
171
|
+
constructor(leaf, loading) {
|
|
172
|
+
super(leaf);
|
|
173
|
+
this.icon = "folder-closed";
|
|
174
|
+
this.#controller = createExplorerController(this.app, loading);
|
|
175
|
+
}
|
|
176
|
+
get selectedPath() {
|
|
177
|
+
return this.#controller.selectedPath;
|
|
178
|
+
}
|
|
179
|
+
getViewType() {
|
|
180
|
+
return "file-explorer";
|
|
181
|
+
}
|
|
182
|
+
getDisplayText() {
|
|
183
|
+
return "Files";
|
|
184
|
+
}
|
|
185
|
+
revealPath(path) {
|
|
186
|
+
this.#controller.revealPath(path.replace(/^\/+/, ""));
|
|
187
|
+
}
|
|
188
|
+
applyShowHiddenFiles(value) {
|
|
189
|
+
this.#controller.applyShowHiddenFiles(value);
|
|
190
|
+
}
|
|
191
|
+
load() {
|
|
192
|
+
this.unload();
|
|
193
|
+
this.containerEl.replaceChildren();
|
|
194
|
+
this.#component = mount(ExplorerPanel, {
|
|
195
|
+
target: this.containerEl,
|
|
196
|
+
props: { controller: this.#controller },
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
unload() {
|
|
200
|
+
if (this.#component)
|
|
201
|
+
void unmount(this.#component);
|
|
202
|
+
this.#component = null;
|
|
203
|
+
}
|
|
204
|
+
onOpen() {
|
|
205
|
+
return Promise.resolve();
|
|
206
|
+
}
|
|
207
|
+
onClose() {
|
|
208
|
+
return Promise.resolve();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function isFileExplorerView(view) {
|
|
212
|
+
return view instanceof FileExplorerView;
|
|
213
|
+
}
|
|
214
|
+
export class LapisLandingView extends View {
|
|
215
|
+
#component = null;
|
|
216
|
+
getViewType() {
|
|
217
|
+
return "lapis-landing";
|
|
218
|
+
}
|
|
219
|
+
getDisplayText() {
|
|
220
|
+
return "New tab";
|
|
221
|
+
}
|
|
222
|
+
load() {
|
|
223
|
+
this.unload();
|
|
224
|
+
this.containerEl.replaceChildren();
|
|
225
|
+
const actions = [
|
|
226
|
+
{
|
|
227
|
+
id: "create-note",
|
|
228
|
+
label: "Create new note",
|
|
229
|
+
icon: "square-pen",
|
|
230
|
+
onSelect: async () => {
|
|
231
|
+
const path = await uniquePath(this.app, "", "Untitled.md");
|
|
232
|
+
const file = await this.app.vault.create(path, "");
|
|
233
|
+
await this.app.openFile(file);
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
id: "go-to-file",
|
|
238
|
+
label: "Go to file",
|
|
239
|
+
icon: "search",
|
|
240
|
+
onSelect: () => {
|
|
241
|
+
void this.app.commands.executeCommand("app:go-to-file");
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
id: "close",
|
|
246
|
+
label: "Close",
|
|
247
|
+
icon: "x",
|
|
248
|
+
onSelect: () => {
|
|
249
|
+
this.leaf.detach();
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
];
|
|
253
|
+
this.#component = mount(LapisLanding, {
|
|
254
|
+
target: this.containerEl,
|
|
255
|
+
props: { actions },
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
unload() {
|
|
259
|
+
if (this.#component)
|
|
260
|
+
void unmount(this.#component);
|
|
261
|
+
this.#component = null;
|
|
262
|
+
}
|
|
263
|
+
onOpen() {
|
|
264
|
+
return Promise.resolve();
|
|
265
|
+
}
|
|
266
|
+
onClose() {
|
|
267
|
+
return Promise.resolve();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
export function createFileExplorerPlugin(options = {}) {
|
|
271
|
+
return class FileExplorerPlugin extends Plugin {
|
|
272
|
+
constructor(app) {
|
|
273
|
+
super(app, EXPLORER_MANIFEST);
|
|
274
|
+
}
|
|
275
|
+
async onload() {
|
|
276
|
+
this.app.configuration.schema.register(EXPLORER_SCHEMA);
|
|
277
|
+
this.register(() => {
|
|
278
|
+
this.app.configuration.schema.unregister(EXPLORER_SCHEMA);
|
|
279
|
+
});
|
|
280
|
+
registerExplorerSettings(this);
|
|
281
|
+
const configurationRef = this.app.configuration.on("updated", ({ key, value }) => {
|
|
282
|
+
if (key !== EXPLORER_SETTING_IDS.showHiddenFiles)
|
|
283
|
+
return;
|
|
284
|
+
this.syncShowHiddenFiles(Boolean(value));
|
|
285
|
+
});
|
|
286
|
+
this.register(() => {
|
|
287
|
+
this.app.configuration.offref(configurationRef);
|
|
288
|
+
});
|
|
289
|
+
this.registerSidebarView(FileExplorerViewType, (leaf) => new FileExplorerView(leaf, options.loading ?? false), {
|
|
290
|
+
side: "left",
|
|
291
|
+
title: "Files",
|
|
292
|
+
icon: "folder-closed",
|
|
293
|
+
}, {
|
|
294
|
+
kind: "command",
|
|
295
|
+
command: {
|
|
296
|
+
id: "open-explorer",
|
|
297
|
+
name: "Open Explorer",
|
|
298
|
+
callback: () => void this.openExplorer(),
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
this.registerView("lapis-landing", (leaf) => new LapisLandingView(leaf), {
|
|
302
|
+
kind: "internal",
|
|
303
|
+
});
|
|
304
|
+
this.addCommand({
|
|
305
|
+
id: "reveal-path",
|
|
306
|
+
name: "Reveal path in file explorer",
|
|
307
|
+
callback: (path) => {
|
|
308
|
+
if (typeof path !== "string" || path.length === 0)
|
|
309
|
+
return;
|
|
310
|
+
this.app.workspace.iterateAllLeaves((leaf) => {
|
|
311
|
+
if (!isFileExplorerView(leaf.view))
|
|
312
|
+
return;
|
|
313
|
+
leaf.view.revealPath(path);
|
|
314
|
+
});
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
this.addCommand({
|
|
318
|
+
id: "toggle-hidden-files",
|
|
319
|
+
name: "Toggle show hidden files",
|
|
320
|
+
callback: () => {
|
|
321
|
+
void this.app.configuration.updateConfigurationOption(EXPLORER_SETTING_IDS.showHiddenFiles, !readShowHiddenFiles(this.app));
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
const { controller } = getWorkspaceHostBinding(this.app.workspace);
|
|
325
|
+
this.register(controller.commands.registerPaletteProvider({
|
|
326
|
+
id: "lapis-vault-files",
|
|
327
|
+
tab: VAULT_PALETTE_FILES_TAB,
|
|
328
|
+
emptyQueryLimit: 5,
|
|
329
|
+
search: (query) => {
|
|
330
|
+
const starterFiles = query.trim().length === 0;
|
|
331
|
+
const recentPaths = starterFiles
|
|
332
|
+
? new Set(this.app.workspace
|
|
333
|
+
.getRecentFiles()
|
|
334
|
+
.map((recent) => recent.path))
|
|
335
|
+
: null;
|
|
336
|
+
return listVaultPaletteFiles(this.app, query).map((file) => ({
|
|
337
|
+
id: `vault-file:${file.path}`,
|
|
338
|
+
title: file.name,
|
|
339
|
+
subtitle: file.path,
|
|
340
|
+
icon: "file",
|
|
341
|
+
providerId: "lapis-vault-files",
|
|
342
|
+
tab: VAULT_PALETTE_FILES_TAB.id,
|
|
343
|
+
group: recentPaths?.has(file.path)
|
|
344
|
+
? VAULT_PALETTE_RECENT_GROUP
|
|
345
|
+
: undefined,
|
|
346
|
+
run: () => this.app.openFile(file),
|
|
347
|
+
}));
|
|
348
|
+
},
|
|
349
|
+
}));
|
|
350
|
+
await this.app.configuration.materializeSchemaDefaults();
|
|
351
|
+
}
|
|
352
|
+
syncShowHiddenFiles(value) {
|
|
353
|
+
this.app.workspace.iterateAllLeaves((leaf) => {
|
|
354
|
+
if (!isFileExplorerView(leaf.view))
|
|
355
|
+
return;
|
|
356
|
+
leaf.view.applyShowHiddenFiles(value);
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
async openExplorer() {
|
|
360
|
+
const existing = this.app.workspace.getLeavesOfType(FileExplorerViewType)[0];
|
|
361
|
+
const target = existing ??
|
|
362
|
+
this.app.workspace.ensureSideLeaf(FileExplorerViewType, "left");
|
|
363
|
+
if (!existing) {
|
|
364
|
+
await target.setViewState({ type: FileExplorerViewType, state: {} });
|
|
365
|
+
}
|
|
366
|
+
this.app.workspace.activateLeaf(target, {
|
|
367
|
+
focusRootHost: false,
|
|
368
|
+
source: "api",
|
|
369
|
+
operation: "open-explorer",
|
|
370
|
+
});
|
|
371
|
+
await this.app.workspace.revealLeaf(target);
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
export const FileExplorerPlugin = createFileExplorerPlugin();
|
|
376
|
+
export const FileExplorerViewType = "file-explorer";
|
|
377
|
+
export { FILE_EXPLORER_SELECTION_CHANGE_EVENT } from "./explorer-selection";
|
|
378
|
+
export { default as ExplorerPanel } from "./LapisExplorerView.svelte";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type App } from "@lapis-notes/api";
|
|
2
|
+
import { NativeDesktopVaultAdapter, type NativeDesktopPlatformOs } from "@lapis-notes/api/desktop-native";
|
|
3
|
+
import type { ExplorerNode } from "@lapismd/design-core/workspace/explorer";
|
|
4
|
+
import type { WorkspaceMenu } from "@lapismd/design-core/workspace/core";
|
|
5
|
+
export declare function hasNativeExplorerActions(adapter: App["vault"]["adapter"]): adapter is NativeDesktopVaultAdapter;
|
|
6
|
+
export declare function revealInFileManagerLabel(os: NativeDesktopPlatformOs | undefined): string;
|
|
7
|
+
export declare function copyExplorerText(label: string, value: string): Promise<void>;
|
|
8
|
+
export declare function appendNativeExplorerMenu(menu: WorkspaceMenu, node: ExplorerNode, app: App): void;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { createLapisFileUrl, Notice } from "@lapis-notes/api";
|
|
2
|
+
import { getNativeDesktopPlatform, hasNativeDesktopCapability, NativeDesktopVaultAdapter, openNativeDesktopVaultPath, resolveNativeDesktopVaultPath, revealNativeDesktopVaultPath, } from "@lapis-notes/api/desktop-native";
|
|
3
|
+
export function hasNativeExplorerActions(adapter) {
|
|
4
|
+
return (adapter instanceof NativeDesktopVaultAdapter &&
|
|
5
|
+
hasNativeDesktopCapability("file-system-actions"));
|
|
6
|
+
}
|
|
7
|
+
export function revealInFileManagerLabel(os) {
|
|
8
|
+
if (os === "macos")
|
|
9
|
+
return "Reveal in Finder";
|
|
10
|
+
if (os === "windows")
|
|
11
|
+
return "Reveal in File Explorer";
|
|
12
|
+
return "Reveal in file manager";
|
|
13
|
+
}
|
|
14
|
+
export async function copyExplorerText(label, value) {
|
|
15
|
+
if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
|
|
16
|
+
await navigator.clipboard.writeText(value);
|
|
17
|
+
}
|
|
18
|
+
new Notice(`Copied ${label}`);
|
|
19
|
+
}
|
|
20
|
+
function vaultPath(node) {
|
|
21
|
+
return node.path === "/" ? "/" : node.path;
|
|
22
|
+
}
|
|
23
|
+
export function appendNativeExplorerMenu(menu, node, app) {
|
|
24
|
+
const adapter = app.vault.adapter;
|
|
25
|
+
if (!hasNativeExplorerActions(adapter))
|
|
26
|
+
return;
|
|
27
|
+
const copyPath = menu.entries.find((entry) => entry.kind === "submenu" && entry.title === "Copy Path");
|
|
28
|
+
if (copyPath?.kind === "submenu") {
|
|
29
|
+
copyPath.menu.addItem((item) => item.setTitle("From system root").onClick(() => {
|
|
30
|
+
void copySystemRootPath(adapter, node);
|
|
31
|
+
}));
|
|
32
|
+
if (node.kind === "file") {
|
|
33
|
+
copyPath.menu.addItem((item) => item.setTitle("As Lapis URL").onClick(() => {
|
|
34
|
+
void copyExplorerText("Lapis URL", createLapisFileUrl(app.vault.getName(), vaultPath(node)));
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
menu.addSeparator();
|
|
39
|
+
menu.addItem((item) => item.setTitle("Open in default app").onClick(() => {
|
|
40
|
+
void openVaultPath(adapter, node);
|
|
41
|
+
}));
|
|
42
|
+
menu.addItem((item) => item
|
|
43
|
+
.setTitle(revealInFileManagerLabel(getNativeDesktopPlatform()?.os))
|
|
44
|
+
.onClick(() => {
|
|
45
|
+
void revealVaultPath(adapter, node);
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
async function copySystemRootPath(adapter, node) {
|
|
49
|
+
try {
|
|
50
|
+
const absolute = await resolveNativeDesktopVaultPath(adapter.rootPath, vaultPath(node));
|
|
51
|
+
if (!absolute)
|
|
52
|
+
throw new Error("Unable to resolve the system path");
|
|
53
|
+
await copyExplorerText("system path", absolute);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
new Notice(error instanceof Error ? error.message : String(error));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function openVaultPath(adapter, node) {
|
|
60
|
+
try {
|
|
61
|
+
await openNativeDesktopVaultPath(adapter.rootPath, vaultPath(node));
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
new Notice(error instanceof Error ? error.message : String(error));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function revealVaultPath(adapter, node) {
|
|
68
|
+
try {
|
|
69
|
+
await revealNativeDesktopVaultPath(adapter.rootPath, vaultPath(node));
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
new Notice(error instanceof Error ? error.message : String(error));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { findOpenFileLeaf, } from "@lapis-notes/api";
|
|
2
|
+
export async function openExplorerFile(app, file, disposition) {
|
|
3
|
+
if (disposition === "current") {
|
|
4
|
+
await app.openFile(file);
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
if (disposition === "reveal-or-new-tab") {
|
|
8
|
+
const existingLeaf = findOpenFileLeaf(app.workspace, file);
|
|
9
|
+
if (existingLeaf) {
|
|
10
|
+
app.workspace.activateLeaf(existingLeaf, {
|
|
11
|
+
operation: "open-explorer-existing-file",
|
|
12
|
+
});
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const leaf = app.workspace.getLeaf("tab");
|
|
17
|
+
app.workspace.activeLeaf = leaf;
|
|
18
|
+
await leaf.openFile(file);
|
|
19
|
+
await app.workspace.revealLeaf(leaf);
|
|
20
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Plugin } from "@lapis-notes/api";
|
|
2
|
+
export declare function createExplorerSettingsSection(): {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
description: string;
|
|
6
|
+
icon: "folder-closed";
|
|
7
|
+
order: number;
|
|
8
|
+
navigationGroupId: string;
|
|
9
|
+
sourcePluginId: string;
|
|
10
|
+
fields: ({
|
|
11
|
+
id: "workspace.fileExplorer.autoRevealCurrentFile";
|
|
12
|
+
type: "boolean";
|
|
13
|
+
title: string;
|
|
14
|
+
description: string;
|
|
15
|
+
default: boolean;
|
|
16
|
+
itemType?: undefined;
|
|
17
|
+
} | {
|
|
18
|
+
id: "workspace.fileExplorer.showHiddenFiles";
|
|
19
|
+
type: "boolean";
|
|
20
|
+
title: string;
|
|
21
|
+
description: string;
|
|
22
|
+
default: boolean;
|
|
23
|
+
itemType?: undefined;
|
|
24
|
+
} | {
|
|
25
|
+
id: "workspace.fileExplorer.paletteFileExtensions";
|
|
26
|
+
type: "list";
|
|
27
|
+
itemType: "string";
|
|
28
|
+
title: string;
|
|
29
|
+
description: string;
|
|
30
|
+
default: ("text" | "data" | "md" | "markdown" | "txt" | "json" | "yaml" | "yml")[];
|
|
31
|
+
})[];
|
|
32
|
+
};
|
|
33
|
+
export declare function registerExplorerSettings(plugin: Plugin): void;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { getWorkspaceHostBinding } from "@lapis-notes/api/workspace-host";
|
|
2
|
+
import { DEFAULT_VAULT_PALETTE_FILE_EXTENSIONS, EXPLORER_SETTING_IDS, EXPLORER_SETTINGS_SECTION_ID, } from "./explorer-settings";
|
|
3
|
+
export function createExplorerSettingsSection() {
|
|
4
|
+
return {
|
|
5
|
+
id: EXPLORER_SETTINGS_SECTION_ID,
|
|
6
|
+
title: "Explorer",
|
|
7
|
+
description: "File tree reveal, visibility, and file-palette behavior.",
|
|
8
|
+
icon: "folder-closed",
|
|
9
|
+
order: 22,
|
|
10
|
+
navigationGroupId: "core-plugins",
|
|
11
|
+
sourcePluginId: "lapis-file-explorer",
|
|
12
|
+
fields: [
|
|
13
|
+
{
|
|
14
|
+
id: EXPLORER_SETTING_IDS.autoRevealCurrentFile,
|
|
15
|
+
type: "boolean",
|
|
16
|
+
title: "Auto-reveal current file",
|
|
17
|
+
description: "Reveal the active file in Explorer.",
|
|
18
|
+
default: true,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: EXPLORER_SETTING_IDS.showHiddenFiles,
|
|
22
|
+
type: "boolean",
|
|
23
|
+
title: "Show hidden files",
|
|
24
|
+
description: "Show dotted names, including .obsidian, .trash, and .lapis.",
|
|
25
|
+
default: false,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: EXPLORER_SETTING_IDS.paletteFileExtensions,
|
|
29
|
+
type: "list",
|
|
30
|
+
itemType: "string",
|
|
31
|
+
title: "File palette extensions",
|
|
32
|
+
description: "Extensions shown by Go to file, without leading dots. An empty list hides every file from the Files palette.",
|
|
33
|
+
default: [...DEFAULT_VAULT_PALETTE_FILE_EXTENSIONS],
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export function registerExplorerSettings(plugin) {
|
|
39
|
+
if (!plugin.app.workspace)
|
|
40
|
+
return;
|
|
41
|
+
const binding = getWorkspaceHostBinding(plugin.app.workspace);
|
|
42
|
+
if (!binding)
|
|
43
|
+
return;
|
|
44
|
+
plugin.register(binding.controller.registerSettingsSection(createExplorerSettingsSection()));
|
|
45
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { App, TFile } from "@lapis-notes/api";
|
|
2
|
+
export declare const VAULT_PALETTE_FILES_TAB: {
|
|
3
|
+
readonly id: "files";
|
|
4
|
+
readonly label: "Files";
|
|
5
|
+
readonly order: 20;
|
|
6
|
+
};
|
|
7
|
+
export declare const VAULT_PALETTE_RECENT_GROUP = "Recent";
|
|
8
|
+
export declare const VAULT_PALETTE_EMPTY_QUERY_LIMIT = 25;
|
|
9
|
+
export declare function listVaultPaletteFiles(app: App, query: string): TFile[];
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { DEFAULT_VAULT_PALETTE_FILE_EXTENSIONS, EXPLORER_SETTING_IDS, } from "./explorer-settings";
|
|
2
|
+
import { isVisibleExplorerPath } from "./vault-path-visibility";
|
|
3
|
+
export const VAULT_PALETTE_FILES_TAB = {
|
|
4
|
+
id: "files",
|
|
5
|
+
label: "Files",
|
|
6
|
+
order: 20,
|
|
7
|
+
};
|
|
8
|
+
export const VAULT_PALETTE_RECENT_GROUP = "Recent";
|
|
9
|
+
export const VAULT_PALETTE_EMPTY_QUERY_LIMIT = 25;
|
|
10
|
+
function readShowHiddenFiles(app) {
|
|
11
|
+
return app.configuration
|
|
12
|
+
.getConfiguration()
|
|
13
|
+
.get(EXPLORER_SETTING_IDS.showHiddenFiles, false);
|
|
14
|
+
}
|
|
15
|
+
function readPaletteFileExtensions(app) {
|
|
16
|
+
const configured = app.configuration
|
|
17
|
+
.getConfiguration()
|
|
18
|
+
.get(EXPLORER_SETTING_IDS.paletteFileExtensions, [
|
|
19
|
+
...DEFAULT_VAULT_PALETTE_FILE_EXTENSIONS,
|
|
20
|
+
]);
|
|
21
|
+
if (!Array.isArray(configured)) {
|
|
22
|
+
return new Set(DEFAULT_VAULT_PALETTE_FILE_EXTENSIONS);
|
|
23
|
+
}
|
|
24
|
+
return new Set(configured
|
|
25
|
+
.filter((extension) => typeof extension === "string")
|
|
26
|
+
.map((extension) => extension.trim().replace(/^\.+/, "").toLowerCase())
|
|
27
|
+
.filter(Boolean));
|
|
28
|
+
}
|
|
29
|
+
function isPaletteVisibleFile(file, showHiddenFiles, paletteFileExtensions) {
|
|
30
|
+
return (isVisibleExplorerPath(file.path, {
|
|
31
|
+
showHidden: showHiddenFiles,
|
|
32
|
+
}) && paletteFileExtensions.has(file.extension.toLowerCase()));
|
|
33
|
+
}
|
|
34
|
+
export function listVaultPaletteFiles(app, query) {
|
|
35
|
+
const normalized = query.trim().toLocaleLowerCase();
|
|
36
|
+
const showHiddenFiles = readShowHiddenFiles(app);
|
|
37
|
+
const paletteFileExtensions = readPaletteFileExtensions(app);
|
|
38
|
+
const isVisible = (file) => isPaletteVisibleFile(file, showHiddenFiles, paletteFileExtensions);
|
|
39
|
+
if (!normalized) {
|
|
40
|
+
const recent = app.workspace
|
|
41
|
+
.getRecentFiles()
|
|
42
|
+
.filter(isVisible)
|
|
43
|
+
.slice(0, VAULT_PALETTE_EMPTY_QUERY_LIMIT);
|
|
44
|
+
if (recent.length > 0)
|
|
45
|
+
return recent;
|
|
46
|
+
}
|
|
47
|
+
return app.vault
|
|
48
|
+
.getFiles()
|
|
49
|
+
.filter(isVisible)
|
|
50
|
+
.filter((file) => file.path.toLocaleLowerCase().includes(normalized))
|
|
51
|
+
.sort((left, right) => left.path.localeCompare(right.path))
|
|
52
|
+
.slice(0, normalized ? undefined : VAULT_PALETTE_EMPTY_QUERY_LIMIT);
|
|
53
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function isHiddenExplorerPath(path) {
|
|
2
|
+
return path
|
|
3
|
+
.replace(/^\/+/, "")
|
|
4
|
+
.split("/")
|
|
5
|
+
.some((segment) => segment.startsWith("."));
|
|
6
|
+
}
|
|
7
|
+
export function isVisibleExplorerPath(path, options = {}) {
|
|
8
|
+
return options.showHidden === true || !isHiddenExplorerPath(path);
|
|
9
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lapis-notes/file-explorer",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Reusable Lapis Notes file explorer plugin",
|
|
5
|
+
"license": "AGPL-3.0-or-later",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/lapismd/lapis-notes.git",
|
|
9
|
+
"directory": "packages/file-explorer"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/lapismd/lapis-notes/tree/main/packages/file-explorer#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/lapismd/lapis-notes/issues"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"type": "module",
|
|
19
|
+
"files": [
|
|
20
|
+
"README.md",
|
|
21
|
+
"CHANGELOG.md",
|
|
22
|
+
"LICENSE.md",
|
|
23
|
+
"dist",
|
|
24
|
+
"!dist/**/*.test.*",
|
|
25
|
+
"!dist/**/*.spec.*"
|
|
26
|
+
],
|
|
27
|
+
"sideEffects": [
|
|
28
|
+
"**/*.css"
|
|
29
|
+
],
|
|
30
|
+
"svelte": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"svelte": "./dist/index.js",
|
|
36
|
+
"import": "./dist/index.js",
|
|
37
|
+
"default": "./dist/index.js"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@lapis-notes/api": "^0.1.0",
|
|
42
|
+
"@lapismd/design-core": "^0.1.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"svelte": "^5.0.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@sveltejs/package": "^2.5.0",
|
|
49
|
+
"@sveltejs/vite-plugin-svelte": "^5.1.1",
|
|
50
|
+
"@tsconfig/svelte": "^5.0.5",
|
|
51
|
+
"publint": "^0.3.12",
|
|
52
|
+
"svelte": "^5.38.2",
|
|
53
|
+
"svelte-check": "^4.3.1",
|
|
54
|
+
"typescript": "~5.8.3",
|
|
55
|
+
"vite": "^6.4.2",
|
|
56
|
+
"vitest": "^4.1.8"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "pnpm run prepack",
|
|
60
|
+
"check": "pnpm --dir ../.. check:no-tailwind packages/file-explorer/src && pnpm check:svelte && pnpm check:types",
|
|
61
|
+
"check:svelte": "svelte-check --tsconfig ./tsconfig.app.json --fail-on-warnings --output human",
|
|
62
|
+
"check:types": "tsc -p tsconfig.node.json --noEmit",
|
|
63
|
+
"test": "vitest run --passWithNoTests"
|
|
64
|
+
}
|
|
65
|
+
}
|