lostpaul_obsidian-folder-ov.../src/settings.ts
2025-06-20 17:35:28 +02:00

428 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { MarkdownPostProcessorContext, normalizePath, PluginSettingTab, Setting, TFile, TFolder } from 'obsidian';
import { updateYaml, updateYamlById, overviewSettings, includeTypes } from './FolderOverview';
import { FolderSuggest } from './suggesters/FolderSuggester';
import { ListComponent } from './utils/ListComponent';
import { FolderOverviewSettings } from './modals/Settings';
import FolderOverviewPlugin from './main';
import FolderNotesPlugin from '../../main';
export const DEFAULT_SETTINGS: overviewSettings = {
id: '',
folderPath: '',
title: '{{folderName}} overview',
showTitle: false,
depth: 3,
includeTypes: ['folder', 'markdown'],
style: 'list',
disableFileTag: false,
sortBy: 'name',
sortByAsc: true,
showEmptyFolders: false,
onlyIncludeSubfolders: false,
storeFolderCondition: true,
showFolderNotes: false,
disableCollapseIcon: true,
alwaysCollapse: false,
autoSync: true,
allowDragAndDrop: true,
};
export class SettingsTab extends PluginSettingTab {
plugin: FolderOverviewPlugin;
constructor(plugin: FolderOverviewPlugin) {
super(plugin.app, plugin);
}
display() {
const { containerEl } = this;
containerEl.empty();
containerEl.createEl('p', { text: 'Edit the default settings of folder overviews', cls: 'setting-item-description' });
this.display = this.display.bind(this);
createOverviewSettings(containerEl, this.plugin.settings, this.plugin, this.plugin.settings, this.display, undefined, undefined, undefined, this);
}
}
const createOrReplaceSetting = (
container: HTMLElement,
section: string,
changedSection: string | null,
renderSetting: (el: HTMLElement) => void
) => {
let sectionContainer = container.querySelector(`.setting-${section}`);
if (sectionContainer) {
if (changedSection === section || changedSection === 'all') {
sectionContainer.empty();
renderSetting(sectionContainer as HTMLElement);
return;
} else {
return;
}
}
sectionContainer = container.createDiv({ cls: `setting-${section} overview-setting-item-fv` });
renderSetting(sectionContainer as HTMLElement);
};
export async function createOverviewSettings(contentEl: HTMLElement, yaml: overviewSettings, plugin: FolderOverviewPlugin | FolderNotesPlugin, defaultSettings: overviewSettings, display: CallableFunction, el?: HTMLElement, ctx?: MarkdownPostProcessorContext, file?: TFile | null, settingsTab?: PluginSettingTab, modal?: FolderOverviewSettings, changedSection?: string | null) {
changedSection = changedSection ?? null;
createOrReplaceSetting(contentEl, 'auto-sync', changedSection, (settingEl) => {
new Setting(settingEl)
.setName('Auto sync')
.setDesc('Choose if the overview should automatically update when you delete, create or rename a file/folder')
.addToggle((toggle) =>
toggle
.setValue(yaml.autoSync)
.onChange(async (value) => {
yaml.autoSync = value;
updateSettings(contentEl, yaml, plugin, defaultSettings, el, ctx, file);
refresh(contentEl, yaml, plugin, defaultSettings, display, el, ctx, file, settingsTab, modal);
})
);
});
createOrReplaceSetting(contentEl, 'allow-drag-and-drop', changedSection, (settingEl) => {
new Setting(settingEl)
.setName('Allow drag and drop')
.setDesc('Choose if you want to allow drag and drop in the overview')
.addToggle((toggle) =>
toggle
.setValue(yaml.allowDragAndDrop)
.onChange(async (value) => {
yaml.allowDragAndDrop = value;
updateSettings(contentEl, yaml, plugin, defaultSettings, el, ctx, file);
refresh(contentEl, yaml, plugin, defaultSettings, display, el, ctx, file, settingsTab, modal);
})
);
});
createOrReplaceSetting(contentEl, 'showTitle', changedSection, (settingEl) => {
new Setting(settingEl)
.setName('Show the title')
.setDesc('Choose if the title should be shown')
.addToggle((toggle) =>
toggle
.setValue(yaml.showTitle)
.onChange(async (value) => {
yaml.showTitle = value;
updateSettings(contentEl, yaml, plugin, defaultSettings, el, ctx, file);
refresh(contentEl, yaml, plugin, defaultSettings, display, el, ctx, file, settingsTab, modal);
})
);
});
createOrReplaceSetting(contentEl, 'title-container-fn', changedSection, (settingEl) => {
new Setting(settingEl)
.setName('Title')
.setDesc('Choose the title of the folder overview')
.addText((text) =>
text
.setValue(yaml?.title || '{{folderName}} overview')
.onChange(async (value) => {
yaml.title = value;
updateSettings(contentEl, yaml, plugin, defaultSettings, el, ctx, file);
})
);
});
createOrReplaceSetting(contentEl, 'folder-path', changedSection, (settingEl) => {
const folderPathSetting = new Setting(settingEl)
.setName('Folder path for the overview')
.setDesc('Choose the folder path for the overview')
.addSearch((search) => {
new FolderSuggest(search.inputEl, plugin, false);
search
.setPlaceholder('Folder path')
.setValue(yaml?.folderPath || '')
.onChange(async (value) => {
const whiteList = ['Files parent folder path', 'Path of folder linked to the file'];
if (value.trim() !== '' && !whiteList.includes(value.trim())) {
value = normalizePath(value);
}
if (!whiteList.includes(value.trim())) {
if (!(plugin.app.vault.getAbstractFileByPath(value) instanceof TFolder) && value !== '') return;
}
yaml.folderPath = value;
updateSettings(contentEl, yaml, plugin, defaultSettings, el, ctx, file);
});
});
folderPathSetting.settingEl.classList.add('fn-overview-folder-path');
});
createOrReplaceSetting(contentEl, 'overview-style', changedSection, (settingEl) => {
new Setting(settingEl)
.setName('Overview style')
.setDesc('Choose the style of the overview (grid style soon)')
.addDropdown((dropdown) =>
dropdown
.addOption('list', 'List')
.addOption('explorer', 'Explorer')
.setValue(yaml?.style || 'list')
.onChange(async (value: 'list') => {
yaml.style = value;
updateSettings(contentEl, yaml, plugin, defaultSettings, el, ctx, file);
refresh(contentEl, yaml, plugin, defaultSettings, display, el, ctx, file, settingsTab, modal);
})
);
});
createOrReplaceSetting(contentEl, 'include-types', changedSection, (settingEl) => {
const setting = new Setting(settingEl);
setting.setName('Include types');
const list = new ListComponent(setting.settingEl, yaml.includeTypes || [], ['markdown', 'folder']);
list.on('update', (values) => {
yaml.includeTypes = values;
updateSettings(contentEl, yaml, plugin, defaultSettings, el, ctx, file);
refresh(contentEl, yaml, plugin, defaultSettings, display, el, ctx, file, settingsTab, modal, 'include-types');
});
if ((yaml?.includeTypes?.length || 0) < 8 && !yaml.includeTypes?.includes('all')) {
setting.addDropdown((dropdown) => {
if (!yaml.includeTypes) yaml.includeTypes = (plugin instanceof FolderNotesPlugin) ? plugin.settings.defaultOverview.includeTypes : plugin.settings.includeTypes || [];
yaml.includeTypes = yaml.includeTypes.map((type: string) => type.toLowerCase()) as includeTypes[];
const options = [
{ value: 'markdown', label: 'Markdown' },
{ value: 'folder', label: 'Folder' },
{ value: 'canvas', label: 'Canvas' },
{ value: 'pdf', label: 'PDF' },
{ value: 'image', label: 'Image' },
{ value: 'audio', label: 'Audio' },
{ value: 'video', label: 'Video' },
{ value: 'other', label: 'All other file types' },
{ value: 'all', label: 'All file types' },
];
options.forEach((option) => {
if (!yaml.includeTypes?.includes(option.value as includeTypes)) {
dropdown.addOption(option.value, option.label);
}
});
dropdown.addOption('+', '+');
dropdown.setValue('+');
dropdown.onChange(async (value) => {
if (value === 'all') {
yaml.includeTypes = yaml.includeTypes?.filter((type: string) => type === 'folder');
list.setValues(yaml.includeTypes);
}
await list.addValue(value.toLowerCase());
updateSettings(contentEl, yaml, plugin, defaultSettings, el, ctx, file);
refresh(contentEl, yaml, plugin, defaultSettings, display, el, ctx, file, settingsTab, modal, 'include-types');
});
});
}
});
createOrReplaceSetting(contentEl, 'file-tag', changedSection, (settingEl) => {
new Setting(settingEl)
.setName('Disable file tag')
.setDesc('Choose if the file tag should be shown after the file name')
.addToggle((toggle) => {
toggle
.setValue(yaml.disableFileTag)
.onChange(async (value) => {
yaml.disableFileTag = value;
updateSettings(contentEl, yaml, plugin, defaultSettings, el, ctx, file);
});
});
});
createOrReplaceSetting(contentEl, 'show-folder-notes', changedSection, (settingEl) => {
new Setting(settingEl)
.setName('Show folder notes')
.setDesc('Choose if folder notes (the note itself and not the folder name) should be shown in the overview')
.addToggle((toggle) =>
toggle
.setValue(yaml.showFolderNotes)
.onChange(async (value) => {
yaml.showFolderNotes = value;
updateSettings(contentEl, yaml, plugin, defaultSettings, el, ctx, file);
})
);
});
createOrReplaceSetting(contentEl, 'file-depth', changedSection, (settingEl) => {
new Setting(settingEl)
.setName('File depth')
.setDesc('File & folder = +1 depth')
.addSlider((slider) =>
slider
.setValue(yaml?.depth || 2)
.setLimits(1, 10, 1)
.setDynamicTooltip()
.onChange(async (value) => {
yaml.depth = value;
updateSettings(contentEl, yaml, plugin, defaultSettings, el, ctx, file);
})
);
});
createOrReplaceSetting(contentEl, 'sort-files', changedSection, (settingEl) => {
new Setting(settingEl)
.setName('Sort files by')
.setDesc('Choose how the files should be sorted')
.addDropdown((dropdown) =>
dropdown
.addOption('name', 'Name')
.addOption('created', 'Created')
.addOption('modified', 'Modified')
.setValue(yaml?.sortBy || 'name')
.onChange(async (value: 'name' | 'created' | 'modified') => {
yaml.sortBy = value;
updateSettings(contentEl, yaml, plugin, defaultSettings, el, ctx, file);
})
)
.addDropdown((dropdown) => {
dropdown
.addOption('desc', 'Descending')
.addOption('asc', 'Ascending');
if (yaml.sortByAsc) {
dropdown.setValue('asc');
} else {
dropdown.setValue('desc');
}
dropdown.onChange(async (value) => {
yaml.sortByAsc = value === 'asc';
updateSettings(contentEl, yaml, plugin, defaultSettings, el, ctx, file);
});
});
});
createOrReplaceSetting(contentEl, 'show-empty-folders', changedSection, (settingEl) => {
new Setting(settingEl)
.setName('Show folder names of folders that appear empty in the folder overview')
.setDesc('Show the names of folders that appear to have no files/folders in the folder overview. That\'s mostly the case when you set the file depth to 1.')
.addToggle((toggle) => {
toggle
.setValue(yaml.showEmptyFolders)
.onChange(async (value) => {
yaml.showEmptyFolders = value;
yaml.onlyIncludeSubfolders = false;
updateSettings(contentEl, yaml, plugin, defaultSettings, el, ctx, file);
refresh(contentEl, yaml, plugin, defaultSettings, display, el, ctx, file, settingsTab, modal);
});
});
});
createOrReplaceSetting(contentEl, 'show-empty-folders-only-first-level', changedSection, (settingEl) => {
new Setting(settingEl)
.setName('Only show empty folders which are on the first level of the folder overview')
.addToggle((toggle) => {
toggle
.setValue(yaml.onlyIncludeSubfolders)
.onChange(async (value) => {
yaml.onlyIncludeSubfolders = value;
updateSettings(contentEl, yaml, plugin, defaultSettings, el, ctx, file);
});
});
});
createOrReplaceSetting(contentEl, 'disable-collapse-icon', changedSection, (settingEl) => {
new Setting(settingEl)
.setName('Disable collapse icon for folder notes')
.setDesc('Remove the collapse icon next to the folder name for folder notes when they only contain the folder note itself')
.addToggle((toggle) => {
toggle
.setValue(yaml.disableCollapseIcon)
.onChange(async (value) => {
yaml.disableCollapseIcon = value;
updateSettings(contentEl, yaml, plugin, defaultSettings, el, ctx, file);
});
});
});
createOrReplaceSetting(contentEl, 'store-collapse-condition', changedSection, (settingEl) => {
new Setting(settingEl)
.setName('Store collapsed condition')
.setDesc('Choose if the collapsed condition should be stored until you restart Obsidian')
.addToggle((toggle) =>
toggle
.setValue(yaml.storeFolderCondition)
.onChange(async (value) => {
yaml.storeFolderCondition = value;
updateSettings(contentEl, yaml, plugin, defaultSettings, el, ctx, file);
})
);
});
createOrReplaceSetting(contentEl, 'collapse-all-by-default', changedSection, (settingEl) => {
new Setting(settingEl)
.setName('Collapse all in the tree by default')
.setDesc('Collapse every folder in the file explorer in the overview by default')
.addToggle((toggle) => {
toggle
.setValue(yaml.alwaysCollapse)
.onChange(async (value) => {
yaml.alwaysCollapse = value;
updateSettings(contentEl, yaml, plugin, defaultSettings, el, ctx, file);
});
});
});
updateSettings(contentEl, yaml, plugin, defaultSettings, el, ctx, file);
}
async function updateSettings(contentEl: HTMLElement, yaml: overviewSettings, plugin: FolderOverviewPlugin | FolderNotesPlugin, defaultSettings: overviewSettings, el?: HTMLElement, ctx?: MarkdownPostProcessorContext, file?: TFile | null) {
let showDisableFileTag = false;
yaml.includeTypes?.forEach((type: string) => {
if (type !== 'markdown' && type !== 'folder') {
showDisableFileTag = true;
}
});
if (yaml.includeTypes.length === 0) {
showDisableFileTag = false;
}
toggleSections(contentEl, {
'setting-title-container-fn': yaml.showTitle,
'setting-store-collapse-condition': yaml.style === 'explorer',
'setting-file-tag': showDisableFileTag,
'setting-show-empty-folders': yaml.style === 'list',
'setting-show-empty-folders-only-first-level': yaml.showEmptyFolders && yaml.style === 'list',
'setting-disable-collapse-icon': yaml.style === 'explorer',
'setting-collapse-all-by-default': yaml.style === 'explorer',
'setting-allow-drag-and-drop': yaml.style === 'explorer',
});
if (!yaml.id) {
plugin.saveSettings();
if (file === undefined) {
plugin.updateOverviewView(plugin);
}
return;
}
if (el && ctx) {
await updateYaml(plugin, ctx, el, yaml);
}
if (file) {
await updateYamlById(plugin, yaml.id, file, yaml);
}
}
function refresh(contentEl: HTMLElement, yaml: overviewSettings, plugin: FolderOverviewPlugin | FolderNotesPlugin, defaultSettings: overviewSettings, display: CallableFunction, el?: HTMLElement, ctx?: MarkdownPostProcessorContext, file?: TFile | null, settingsTab?: PluginSettingTab, modal?: FolderOverviewSettings, changedSection?: string) {
if (file) {
contentEl = contentEl.parentElement as HTMLElement;
}
display(contentEl, yaml, plugin, defaultSettings, display, el, ctx, file, settingsTab, modal, changedSection);
}
function toggleSections(contentEl: HTMLElement, sections: Record<string, boolean>) {
Object.entries(sections).forEach(([sectionClass, shouldShow]) => {
const sections = contentEl.querySelectorAll(`.${sectionClass}`);
sections.forEach((section) => {
if (shouldShow && section) {
section.classList.remove('hide');
} else {
section?.classList.add('hide');
}
});
});
}