Added more setting options

This commit is contained in:
Lost Paul 2023-03-15 21:02:00 +01:00
parent 1bd05cf0be
commit 0469221b90
6 changed files with 327 additions and 7 deletions

12
src/commands.ts Normal file
View file

@ -0,0 +1,12 @@
import { App } from 'obsidian';
import FolderNotesPlugin from './main';
export class Commands {
plugin: FolderNotesPlugin;
app: App;
constructor(app: App, plugin: FolderNotesPlugin) {
this.plugin = plugin;
this.app = app;
}
registerCommands() {
}
}

View file

@ -11,6 +11,7 @@ export default class FolderNotesPlugin extends Plugin {
await this.loadSettings();
this.settingsTab = new SettingsTab(this.app, this);
this.addSettingTab(this.settingsTab);
document.body.classList.add('folder-notes-plugin')
if (this.settings.hideFolderNote) {
document.body.classList.add('hide-folder-note');
} else {
@ -24,13 +25,12 @@ export default class FolderNotesPlugin extends Plugin {
if (!path) return;
const folder = this.app.vault.getAbstractFileByPath(path);
if (folder) return;
this.createFolderNote(path);
this.createFolderNote(path, true);
}
}));
this.registerEvent(this.app.vault.on('rename', (file: TAbstractFile, oldPath: string) => {
if (!this.settings.autoRename) return;
if (!this.settings.syncFolderName) return;
if (file instanceof TFolder) {
const folder = this.app.vault.getAbstractFileByPath(file?.path);
const oldName = oldPath.substring(oldPath.lastIndexOf('/' || '\\'));
@ -70,6 +70,11 @@ export default class FolderNotesPlugin extends Plugin {
async handleFolderClick(event: MouseEvent) {
if (!(event.target instanceof HTMLElement)) return;
event.stopImmediatePropagation();
if (!document.body.classList.contains('folder-notes-plugin')) {
event.target.onclick = null;
event.target.click();
return;
}
const folder = event.target.parentElement?.getAttribute('data-path');
const path = folder + '/' + event.target.innerText + '.md';
@ -104,11 +109,12 @@ export default class FolderNotesPlugin extends Plugin {
}
}
async createFolderNote(path: string) {
async createFolderNote(path: string, useModal?: boolean) {
const leaf = this.app.workspace.getLeaf(false);
const file = await this.app.vault.create(path, '');
await leaf.openFile(file);
if (!this.settings.autoCreate) return;
if (!useModal) return;
const folder = this.app.vault.getAbstractFileByPath(path.substring(0, path.lastIndexOf('/' || '\\')));
if (!(folder instanceof TFolder)) return;
const modal = new FolderNameModal(this.app, this, folder);
@ -126,6 +132,7 @@ export default class FolderNotesPlugin extends Plugin {
onunload() {
console.log('unloading folder notes plugin');
this.observer.disconnect();
document.body.classList.remove('folder-notes-plugin');
}
async loadSettings() {

View file

@ -4,19 +4,19 @@ export interface FolderNotesSettings {
syncFolderName: boolean;
ctrlKey: boolean;
altKey: boolean;
autoRename: boolean;
hideFolderNote: boolean;
templatePath: string;
autoCreate: boolean;
excludeFolders: string[];
}
export const DEFAULT_SETTINGS: FolderNotesSettings = {
syncFolderName: true,
ctrlKey: true,
altKey: false,
autoRename: true,
hideFolderNote: true,
templatePath: '',
autoCreate: true,
autoCreate: false,
excludeFolders: [],
};
export class SettingsTab extends PluginSettingTab {
plugin: FolderNotesPlugin;
@ -48,5 +48,46 @@ export class SettingsTab extends PluginSettingTab {
this.display();
})
);
new Setting(containerEl)
.setName('Sync folder name')
.setDesc('Automatically rename the folder note when the folder name is changed')
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.syncFolderName)
.onChange(async (value) => {
this.plugin.settings.syncFolderName = value;
await this.plugin.saveSettings();
this.display();
})
);
new Setting(containerEl)
.setName('Auto create folder note')
.setDesc('Automatically create a folder note when a new folder is created')
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.autoCreate)
.onChange(async (value) => {
this.plugin.settings.autoCreate = value;
await this.plugin.saveSettings();
this.display();
})
);
new Setting(containerEl)
.setName('Key for creating folder note')
.setDesc('The key combination to create a folder note')
.addDropdown((dropdown) => {
dropdown
.addOption('ctrl', 'Ctrl + Click')
.addOption('alt', 'Alt + Click')
.setValue(this.plugin.settings.ctrlKey ? 'ctrl' : 'alt')
.onChange(async (value) => {
this.plugin.settings.ctrlKey = value === 'ctrl';
this.plugin.settings.altKey = value === 'alt';
await this.plugin.saveSettings();
this.display();
});
});
}
}

View file

@ -0,0 +1,54 @@
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes and https://github.com/SilentVoid13/Templater
import { TAbstractFile, TFile } from "obsidian";
import { TextInputSuggest } from "./suggest";
import FolderNotesPlugin from "../main";
export enum FileSuggestMode {
TemplateFiles,
ScriptFiles,
}
export class FileSuggest extends TextInputSuggest<TFile> {
constructor(
public inputEl: HTMLInputElement,
private plugin: FolderNotesPlugin
) {
super(inputEl);
}
get_error_msg(mode: FileSuggestMode): string {
switch (mode) {
case FileSuggestMode.TemplateFiles:
return `Templates folder doesn't exist`;
case FileSuggestMode.ScriptFiles:
return `User Scripts folder doesn't exist`;
}
}
getSuggestions(input_str: string): TFile[] {
const files: TFile[] = [];
const lower_input_str = input_str.toLowerCase();
this.plugin.app.vault.getFiles().forEach((file: TAbstractFile) => {
if (
file instanceof TFile &&
file.path.toLowerCase().contains(lower_input_str)
) {
files.push(file);
}
});
return files;
}
renderSuggestion(file: TFile, el: HTMLElement): void {
el.setText(file.path);
}
selectSuggestion(file: TFile): void {
this.inputEl.value = file.path;
this.inputEl.trigger("input");
this.close();
}
}

202
src/suggesters/suggest.ts Normal file
View file

@ -0,0 +1,202 @@
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes and https://github.com/SilentVoid13/Templater
import { ISuggestOwner, Scope } from "obsidian";
import { createPopper, Instance as PopperInstance } from "@popperjs/core";
const wrapAround = (value: number, size: number): number => {
return ((value % size) + size) % size;
};
class Suggest<T> {
private owner: ISuggestOwner<T>;
private values: T[];
private suggestions: HTMLDivElement[];
private selectedItem: number;
private containerEl: HTMLElement;
constructor(
owner: ISuggestOwner<T>,
containerEl: HTMLElement,
scope: Scope
) {
this.owner = owner;
this.containerEl = containerEl;
containerEl.on(
"click",
".suggestion-item",
this.onSuggestionClick.bind(this)
);
containerEl.on(
"mousemove",
".suggestion-item",
this.onSuggestionMouseover.bind(this)
);
scope.register([], "ArrowUp", (event) => {
if (!event.isComposing) {
this.setSelectedItem(this.selectedItem - 1, true);
return false;
}
});
scope.register([], "ArrowDown", (event) => {
if (!event.isComposing) {
this.setSelectedItem(this.selectedItem + 1, true);
return false;
}
});
scope.register([], "Enter", (event) => {
if (!event.isComposing) {
this.useSelectedItem(event);
return false;
}
});
}
onSuggestionClick(event: MouseEvent, el: HTMLDivElement): void {
event.preventDefault();
const item = this.suggestions.indexOf(el);
this.setSelectedItem(item, false);
this.useSelectedItem(event);
}
onSuggestionMouseover(_event: MouseEvent, el: HTMLDivElement): void {
const item = this.suggestions.indexOf(el);
this.setSelectedItem(item, false);
}
setSuggestions(values: T[]) {
this.containerEl.empty();
const suggestionEls: HTMLDivElement[] = [];
values.forEach((value) => {
const suggestionEl = this.containerEl.createDiv("suggestion-item");
this.owner.renderSuggestion(value, suggestionEl);
suggestionEls.push(suggestionEl);
});
this.values = values;
this.suggestions = suggestionEls;
this.setSelectedItem(0, false);
}
useSelectedItem(event: MouseEvent | KeyboardEvent) {
const currentValue = this.values[this.selectedItem];
if (currentValue) {
this.owner.selectSuggestion(currentValue, event);
}
}
setSelectedItem(selectedIndex: number, scrollIntoView: boolean) {
const normalizedIndex = wrapAround(
selectedIndex,
this.suggestions.length
);
const prevSelectedSuggestion = this.suggestions[this.selectedItem];
const selectedSuggestion = this.suggestions[normalizedIndex];
prevSelectedSuggestion?.removeClass("is-selected");
selectedSuggestion?.addClass("is-selected");
this.selectedItem = normalizedIndex;
if (scrollIntoView) {
selectedSuggestion.scrollIntoView(false);
}
}
}
export abstract class TextInputSuggest<T> implements ISuggestOwner<T> {
protected inputEl: HTMLInputElement | HTMLTextAreaElement;
private popper: PopperInstance;
private scope: Scope;
private suggestEl: HTMLElement;
private suggest: Suggest<T>;
constructor(inputEl: HTMLInputElement | HTMLTextAreaElement) {
this.inputEl = inputEl;
this.scope = new Scope();
this.suggestEl = createDiv("suggestion-container");
const suggestion = this.suggestEl.createDiv("suggestion");
this.suggest = new Suggest(this, suggestion, this.scope);
this.scope.register([], "Escape", this.close.bind(this));
this.inputEl.addEventListener("input", this.onInputChanged.bind(this));
this.inputEl.addEventListener("focus", this.onInputChanged.bind(this));
this.inputEl.addEventListener("blur", this.close.bind(this));
this.suggestEl.on(
"mousedown",
".suggestion-container",
(event: MouseEvent) => {
event.preventDefault();
}
);
}
onInputChanged(): void {
const inputStr = this.inputEl.value;
const suggestions = this.getSuggestions(inputStr);
if (!suggestions) {
this.close();
return;
}
if (suggestions.length > 0) {
this.suggest.setSuggestions(suggestions);
// @ts-ignore
this.open(app.dom.appContainerEl, this.inputEl);
} else {
this.close();
}
}
open(container: HTMLElement, inputEl: HTMLElement): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
app.keymap.pushScope(this.scope);
container.appendChild(this.suggestEl);
this.popper = createPopper(inputEl, this.suggestEl, {
placement: "bottom-start",
modifiers: [
{
name: "sameWidth",
enabled: true,
fn: ({ state, instance }) => {
// Note: positioning needs to be calculated twice -
// first pass - positioning it according to the width of the popper
// second pass - position it with the width bound to the reference element
// we need to early exit to avoid an infinite loop
const targetWidth = `${state.rects.reference.width}px`;
if (state.styles.popper.width === targetWidth) {
return;
}
state.styles.popper.width = targetWidth;
instance.update();
},
phase: "beforeWrite",
requires: ["computeStyles"],
},
],
});
}
close(): void {
app.keymap.popScope(this.scope);
this.suggest.setSuggestions([]);
if (this.popper) this.popper.destroy();
this.suggestEl.detach();
}
abstract getSuggestions(inputStr: string): T[];
abstract renderSuggestion(item: T, el: HTMLElement): void;
abstract selectSuggestion(item: T): void;
}

View file

@ -10,4 +10,8 @@
.hide-folder-note .is-folder-note {
display: none;
}
.nav-folder-collapse-indicator:hover {
cursor: pointer;
}