mirror of
https://github.com/wenlzhang/obsidian-folder-navigator.git
synced 2026-07-22 05:41:23 +00:00
feat: add create new folder option in folder navigator modal
This commit is contained in:
parent
95e5d2937f
commit
d6f18b52c2
4 changed files with 236 additions and 13 deletions
|
|
@ -5,6 +5,7 @@ import {
|
|||
WorkspaceLeaf,
|
||||
View,
|
||||
FuzzyMatch,
|
||||
Notice,
|
||||
} from "obsidian";
|
||||
import type FolderNavigatorPlugin from "./main";
|
||||
import { FolderDisplayMode } from "./settings";
|
||||
|
|
@ -53,6 +54,11 @@ interface FolderWithScore {
|
|||
match: FuzzyMatch<TFolder>;
|
||||
}
|
||||
|
||||
interface CreateFolderOption extends TFolder {
|
||||
_isCreateOption: boolean;
|
||||
_folderName: string;
|
||||
}
|
||||
|
||||
export class FolderSuggestModal extends FuzzySuggestModal<TFolder> {
|
||||
folders: TFolder[];
|
||||
plugin: FolderNavigatorPlugin;
|
||||
|
|
@ -439,6 +445,54 @@ export class FolderSuggestModal extends FuzzySuggestModal<TFolder> {
|
|||
this.plugin,
|
||||
);
|
||||
|
||||
// If query is provided, add "Create new folder" option at the end
|
||||
if (normalizedQuery.length > 0) {
|
||||
// Filter out separators to count real folder matches
|
||||
const realFolderMatches = limitedResults.filter(
|
||||
(result) => !(result.item as any)._isSeparator,
|
||||
);
|
||||
|
||||
// Only show create option if the exact folder doesn't already exist
|
||||
const exactMatch = realFolderMatches.some(
|
||||
(result) =>
|
||||
result.item.path.toLowerCase() === normalizedQuery ||
|
||||
result.item.name.toLowerCase() === normalizedQuery,
|
||||
);
|
||||
|
||||
if (!exactMatch) {
|
||||
const createOption = {} as CreateFolderOption;
|
||||
createOption._isCreateOption = true;
|
||||
createOption._folderName = query.trim();
|
||||
|
||||
// Add separator before create option if there are results
|
||||
if (limitedResults.length > 0) {
|
||||
const separator = {} as TFolder;
|
||||
(separator as any)._isSeparator = true;
|
||||
(separator as any)._separatorText = "—————";
|
||||
|
||||
return [
|
||||
...limitedResults,
|
||||
{
|
||||
item: separator,
|
||||
match: { score: 0, matches: [] },
|
||||
},
|
||||
{
|
||||
item: createOption as TFolder,
|
||||
match: { score: 0, matches: [] },
|
||||
},
|
||||
];
|
||||
} else {
|
||||
// No results, just show create option
|
||||
return [
|
||||
{
|
||||
item: createOption as TFolder,
|
||||
match: { score: 0, matches: [] },
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return limitedResults;
|
||||
}
|
||||
|
||||
|
|
@ -451,6 +505,10 @@ export class FolderSuggestModal extends FuzzySuggestModal<TFolder> {
|
|||
if ((folder as any)._isSeparator) {
|
||||
return (folder as any)._separatorText;
|
||||
}
|
||||
// For create folder option
|
||||
if ((folder as CreateFolderOption)._isCreateOption) {
|
||||
return `Create new folder: ${(folder as CreateFolderOption)._folderName}`;
|
||||
}
|
||||
return folder.path;
|
||||
}
|
||||
|
||||
|
|
@ -516,26 +574,55 @@ export class FolderSuggestModal extends FuzzySuggestModal<TFolder> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Event handler for when a folder is chosen from the suggestion list
|
||||
* Handle creating a new folder
|
||||
*/
|
||||
onChooseItem(folder: TFolder, evt: MouseEvent | KeyboardEvent): void {
|
||||
// Check if this is a separator and ignore if so
|
||||
if ((folder as any)._isSeparator) {
|
||||
return;
|
||||
private async handleCreateFolder(folderName: string): Promise<void> {
|
||||
try {
|
||||
log(`Creating new folder: "${folderName}"`, this.plugin);
|
||||
|
||||
// Get the base path from settings
|
||||
const basePath = this.plugin.settings.newFolderLocation;
|
||||
const fullPath = basePath
|
||||
? `${basePath}/${folderName}`
|
||||
: folderName;
|
||||
|
||||
log(`Full path for new folder: "${fullPath}"`, this.plugin);
|
||||
|
||||
// Check if folder already exists
|
||||
const existingFolder =
|
||||
this.app.vault.getAbstractFileByPath(fullPath);
|
||||
if (existingFolder) {
|
||||
new Notice(`Folder "${fullPath}" already exists`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the folder
|
||||
const newFolder = await this.app.vault.createFolder(fullPath);
|
||||
log(`Folder created successfully: "${fullPath}"`, this.plugin);
|
||||
|
||||
new Notice(`Folder "${fullPath}" created successfully`);
|
||||
|
||||
// Close the modal
|
||||
this.close();
|
||||
|
||||
// Navigate to the newly created folder
|
||||
setTimeout(() => {
|
||||
this.navigateToFolder(newFolder);
|
||||
}, 100);
|
||||
} catch (error) {
|
||||
logError(`Error creating folder: ${error}`);
|
||||
new Notice(`Failed to create folder: ${error.message || error}`);
|
||||
}
|
||||
}
|
||||
|
||||
log(
|
||||
`Folder selected: "${folder.path}" (name: "${folder.name}")`,
|
||||
this.plugin,
|
||||
);
|
||||
|
||||
/**
|
||||
* Navigate to a folder (same logic as in onChooseItem)
|
||||
*/
|
||||
private navigateToFolder(folder: TFolder): void {
|
||||
try {
|
||||
// Update folder history
|
||||
this.updateFolderHistory(folder);
|
||||
|
||||
// Close the modal first
|
||||
this.close();
|
||||
|
||||
// Make sure the file explorer is visible
|
||||
const fileExplorerLeaves =
|
||||
this.app.workspace.getLeavesOfType("file-explorer");
|
||||
|
|
@ -609,6 +696,35 @@ export class FolderSuggestModal extends FuzzySuggestModal<TFolder> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for when a folder is chosen from the suggestion list
|
||||
*/
|
||||
onChooseItem(folder: TFolder, evt: MouseEvent | KeyboardEvent): void {
|
||||
// Check if this is a separator and ignore if so
|
||||
if ((folder as any)._isSeparator) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is a create folder option
|
||||
if ((folder as CreateFolderOption)._isCreateOption) {
|
||||
this.handleCreateFolder((folder as CreateFolderOption)._folderName);
|
||||
return;
|
||||
}
|
||||
|
||||
log(
|
||||
`Folder selected: "${folder.path}" (name: "${folder.name}")`,
|
||||
this.plugin,
|
||||
);
|
||||
|
||||
// Close the modal first
|
||||
this.close();
|
||||
|
||||
// Navigate to the selected folder
|
||||
setTimeout(() => {
|
||||
this.navigateToFolder(folder);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand a folder by finding and clicking its title directly in the DOM
|
||||
*/
|
||||
|
|
@ -747,6 +863,25 @@ export class FolderSuggestModal extends FuzzySuggestModal<TFolder> {
|
|||
return;
|
||||
}
|
||||
|
||||
// Check if this is a create folder option
|
||||
if ((item.item as CreateFolderOption)._isCreateOption) {
|
||||
el.empty();
|
||||
const createEl = el.createDiv({
|
||||
cls: "folder-create-option",
|
||||
});
|
||||
const iconEl = createEl.createSpan({
|
||||
cls: "folder-create-icon",
|
||||
});
|
||||
iconEl.setText("➕ ");
|
||||
const textEl = createEl.createSpan({
|
||||
cls: "folder-create-text",
|
||||
});
|
||||
textEl.setText(
|
||||
`Create new folder: "${(item.item as CreateFolderOption)._folderName}"`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Custom rendering with keyword highlighting
|
||||
el.empty();
|
||||
const folder = item.item;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export interface PluginSettings {
|
|||
accessCount: number; // visit count
|
||||
}
|
||||
>;
|
||||
newFolderLocation: string; // Path where new folders should be created (empty string = root)
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: PluginSettings = {
|
||||
|
|
@ -30,4 +31,5 @@ export const DEFAULT_SETTINGS: PluginSettings = {
|
|||
recentFoldersToShow: 5,
|
||||
frequentFoldersToShow: 5,
|
||||
folderHistory: {},
|
||||
newFolderLocation: "", // Empty string means root folder
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,10 +5,43 @@ import {
|
|||
DropdownComponent,
|
||||
Modal,
|
||||
Notice,
|
||||
FuzzySuggestModal,
|
||||
TFolder,
|
||||
} from "obsidian";
|
||||
import FolderNavigatorPlugin from "./main";
|
||||
import { FolderDisplayMode } from "./settings";
|
||||
|
||||
/**
|
||||
* Modal for searching and selecting a folder for new folder creation location
|
||||
*/
|
||||
class FolderSelectionModal extends FuzzySuggestModal<TFolder> {
|
||||
plugin: FolderNavigatorPlugin;
|
||||
onSelect: (folder: TFolder) => void;
|
||||
|
||||
constructor(
|
||||
app: App,
|
||||
plugin: FolderNavigatorPlugin,
|
||||
onSelect: (folder: TFolder) => void,
|
||||
) {
|
||||
super(app);
|
||||
this.plugin = plugin;
|
||||
this.onSelect = onSelect;
|
||||
this.setPlaceholder("Search for a folder...");
|
||||
}
|
||||
|
||||
getItems(): TFolder[] {
|
||||
return this.app.vault.getAllFolders();
|
||||
}
|
||||
|
||||
getItemText(folder: TFolder): string {
|
||||
return folder.path || "/";
|
||||
}
|
||||
|
||||
onChooseItem(folder: TFolder): void {
|
||||
this.onSelect(folder);
|
||||
}
|
||||
}
|
||||
|
||||
export class SettingsTab extends PluginSettingTab {
|
||||
plugin: FolderNavigatorPlugin;
|
||||
|
||||
|
|
@ -89,6 +122,43 @@ export class SettingsTab extends PluginSettingTab {
|
|||
}),
|
||||
);
|
||||
|
||||
// New folder creation section
|
||||
new Setting(containerEl).setName("Folder creation").setHeading();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Default location for new folders")
|
||||
.setDesc(
|
||||
"Choose where new folders will be created when using the 'Create folder' option. Default is the vault root.",
|
||||
)
|
||||
.addButton((button) => {
|
||||
const displayPath =
|
||||
this.plugin.settings.newFolderLocation || "/ (Root)";
|
||||
button.setButtonText(displayPath).onClick(() => {
|
||||
const modal = new FolderSelectionModal(
|
||||
this.app,
|
||||
this.plugin,
|
||||
async (folder: TFolder) => {
|
||||
this.plugin.settings.newFolderLocation =
|
||||
folder.path;
|
||||
await this.plugin.saveSettings();
|
||||
button.setButtonText(folder.path || "/ (Root)");
|
||||
},
|
||||
);
|
||||
modal.open();
|
||||
});
|
||||
})
|
||||
.addExtraButton((button) => {
|
||||
button
|
||||
.setIcon("reset")
|
||||
.setTooltip("Reset to root folder")
|
||||
.onClick(async () => {
|
||||
this.plugin.settings.newFolderLocation = "";
|
||||
await this.plugin.saveSettings();
|
||||
// Update button text by recreating the setting
|
||||
this.display();
|
||||
});
|
||||
});
|
||||
|
||||
// Folder display preferences section
|
||||
new Setting(containerEl)
|
||||
.setName("Folder display preferences")
|
||||
|
|
|
|||
16
styles.css
16
styles.css
|
|
@ -18,3 +18,19 @@
|
|||
.suggestion-highlight {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.folder-create-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 0;
|
||||
color: var(--text-accent);
|
||||
}
|
||||
|
||||
.folder-create-icon {
|
||||
margin-right: 6px;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.folder-create-text {
|
||||
font-style: italic;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue