mirror of
https://github.com/halogamer33/Obsidian-Fuzzy-Note-Creator.git
synced 2026-07-22 10:00:30 +00:00
227 lines
6.8 KiB
TypeScript
227 lines
6.8 KiB
TypeScript
import {
|
|
App,
|
|
FuzzySuggestModal,
|
|
normalizePath,
|
|
Instruction,
|
|
TFolder,
|
|
Platform,
|
|
Notice,
|
|
} from 'obsidian';
|
|
import { FuzzyNoteCreatorSettings } from './settingsTab';
|
|
import { NoteCreationModal } from './note-creation-modal';
|
|
|
|
interface Suggestion {
|
|
displayText: string;
|
|
path: string;
|
|
}
|
|
|
|
// Modals are elements in the UI that require interaction from the user
|
|
export class FolderSelectionModal extends FuzzySuggestModal<Suggestion> {
|
|
settings: FuzzyNoteCreatorSettings;
|
|
leafMode: string;
|
|
dirs: Suggestion[];
|
|
newDirSuggestion: Suggestion | null;
|
|
|
|
constructor(
|
|
app: App,
|
|
leafMode: string,
|
|
settings: FuzzyNoteCreatorSettings,
|
|
) {
|
|
super(app);
|
|
this.settings = settings;
|
|
this.leafMode = leafMode;
|
|
|
|
this.setPlaceholder('Path to the folder...');
|
|
|
|
if (settings.showInstructions) {
|
|
const instructions: Instruction[] = [
|
|
{ command: '↵', purpose: 'to select folder' },
|
|
{ command: 'esc', purpose: 'to dismiss' },
|
|
{ command: '↑↓', purpose: 'to navigate' },
|
|
{ command: '⭾ tab', purpose: 'to autocomplete folder' },
|
|
];
|
|
|
|
this.setInstructions(instructions);
|
|
}
|
|
|
|
// Autocomplete selected folder with tab.
|
|
this.inputEl.addEventListener(
|
|
'keydown',
|
|
async (event: KeyboardEvent) => {
|
|
if (event.key !== 'Tab') {
|
|
return;
|
|
}
|
|
|
|
event.preventDefault();
|
|
|
|
const suggestions = this.resultContainerEl.children;
|
|
for (let i = 0; i < suggestions.length; i++) {
|
|
if (!suggestions[i].classList.contains('is-selected')) {
|
|
continue;
|
|
}
|
|
|
|
this.inputEl.value = suggestions[i]
|
|
.getText()
|
|
.replace(' Press ↵ to create folder.', '');
|
|
|
|
// Trigger an input event to update suggestions
|
|
const event = new Event('input', {
|
|
bubbles: true,
|
|
cancelable: true,
|
|
});
|
|
this.inputEl.dispatchEvent(event);
|
|
|
|
break;
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
// ── End of constructor ──────────────────────────────────────────────
|
|
|
|
// Get the folders in the vault
|
|
getItems(): Suggestion[] {
|
|
let dirs: Suggestion[] = [];
|
|
|
|
const abstractFiles = this.app.vault.getAllLoadedFiles();
|
|
for (let i = 0; i < abstractFiles.length; i++) {
|
|
if (abstractFiles[i] instanceof TFolder) {
|
|
dirs.push({
|
|
displayText: abstractFiles[i].path,
|
|
path: abstractFiles[i].path,
|
|
});
|
|
}
|
|
}
|
|
|
|
if (
|
|
this.settings.currentFolderRecommendation ||
|
|
this.settings.currentFolderFirst
|
|
) {
|
|
const activeFile = this.app.workspace.getActiveFile();
|
|
if (!activeFile) {
|
|
return dirs;
|
|
}
|
|
|
|
const currentPath = activeFile.parent!.path;
|
|
|
|
if (this.settings.currentFolderRecommendation) {
|
|
dirs.unshift({
|
|
displayText: this.settings.currentFolderRecommendationName,
|
|
path: currentPath,
|
|
});
|
|
}
|
|
|
|
if (this.settings.currentFolderFirst) {
|
|
dirs.unshift({
|
|
displayText: currentPath,
|
|
path: currentPath,
|
|
});
|
|
}
|
|
}
|
|
|
|
this.dirs = dirs;
|
|
return dirs;
|
|
}
|
|
|
|
getItemText(suggestion: Suggestion): string {
|
|
this.newDirSuggestion = null;
|
|
|
|
return suggestion.displayText;
|
|
}
|
|
|
|
onChooseItem(suggestion: Suggestion) {
|
|
if (this.newDirSuggestion) {
|
|
// `normalizePath` is an obsidian function but I still need to do
|
|
// some more work to normalize the path down the line.
|
|
let path = normalizePath(this.newDirSuggestion.path);
|
|
const windowsCompatibility: boolean =
|
|
Platform.isWin || this.settings.windowsNoteTitleCompatibility;
|
|
|
|
path = path.replace('\\', '/');
|
|
let pathArray = path
|
|
.split('/')
|
|
.map((link) => link.trim())
|
|
.filter((link) => link.length !== 0);
|
|
path = pathArray.join('/');
|
|
|
|
if (
|
|
windowsCompatibility &&
|
|
path.match(/(<|>|:|"|\||\?|\*)/) !== null
|
|
) {
|
|
new Notice(
|
|
`The note title must not include any of this characters: < > : " ? | *`,
|
|
2000,
|
|
);
|
|
new FolderSelectionModal(
|
|
this.app,
|
|
this.leafMode,
|
|
this.settings,
|
|
).open();
|
|
return;
|
|
}
|
|
|
|
new NoteCreationModal(
|
|
this.app,
|
|
path,
|
|
this.leafMode,
|
|
this.settings,
|
|
).open();
|
|
return;
|
|
}
|
|
|
|
const normalizedPath = normalizePath(suggestion.path);
|
|
new NoteCreationModal(
|
|
this.app,
|
|
normalizedPath,
|
|
this.leafMode,
|
|
this.settings,
|
|
).open();
|
|
}
|
|
|
|
onNoSuggestion() {
|
|
this.resultContainerEl.replaceChildren();
|
|
const input = this.inputEl.value;
|
|
const dirs = this.dirs.filter((dir) => {
|
|
if (dir.path != '/') {
|
|
dir.path += '/';
|
|
}
|
|
return dir;
|
|
});
|
|
|
|
const existingDirs = dirs.filter((dir) => {
|
|
if (input.startsWith(dir.path)) {
|
|
return true;
|
|
}
|
|
});
|
|
|
|
let longestDir = '';
|
|
for (let i = 0; i < existingDirs.length; i++) {
|
|
if (existingDirs[i].path.length > longestDir.length) {
|
|
longestDir = existingDirs[i].path;
|
|
}
|
|
}
|
|
|
|
const newDir = input.replace(longestDir, '');
|
|
const existingDir = longestDir;
|
|
|
|
const newDirSuggestion = this.resultContainerEl.createDiv({
|
|
cls: ['suggestion-item', 'is-selected'],
|
|
});
|
|
|
|
newDirSuggestion.createEl('span', { text: existingDir });
|
|
newDirSuggestion.createEl('span', {
|
|
cls: 'suggestion-highlight',
|
|
text: newDir,
|
|
});
|
|
newDirSuggestion.createEl('kbd', {
|
|
text: ' Press ↵ to create folder.',
|
|
});
|
|
|
|
this.newDirSuggestion = {
|
|
displayText: `${existingDir}${newDir}`,
|
|
path: `${existingDir}${newDir}`,
|
|
};
|
|
|
|
// console.log(this.newDirSuggestion);
|
|
}
|
|
}
|