Folder overview settings view

This commit is contained in:
Lost Paul 2025-01-03 13:39:51 +01:00
parent 5c4c20d170
commit b65d624e3b
8 changed files with 497 additions and 570 deletions

View file

@ -36,6 +36,14 @@ export class Commands {
}
});
this.plugin.addCommand({
id: 'open-folder-overview-view',
name: 'Change folder overview settings',
callback: () => {
this.plugin.activateOverviewView();
}
});
this.plugin.addCommand({
id: 'create-folder-note',
name: 'Create folder note with a new folder for the active note in the current folder',

View file

@ -181,7 +181,7 @@ export class FolderOverview {
files = sourceFolder.children;
}
files = await this.filterFiles(files, plugin, sourceFolderPath, this.yaml.depth, this.pathBlacklist);
if (!this.yaml.includeTypes.includes('folder')) {
@ -361,8 +361,6 @@ export class FolderOverview {
return allFiles;
}
fileMenu(file: TFile, e: MouseEvent) {
const plugin = this.plugin;
const fileMenu = new Menu();
@ -466,6 +464,65 @@ export function getCodeBlockEndLine(text: string, startLine: number, count = 1)
return line;
}
export async function getOverviews(plugin: FolderNotesPlugin, file: TFile) {
// is an object with unkown keys
const overviews: { [key: string]: string }[] = [];
const content = await plugin.app.vault.read(file);
if (!content) return overviews;
const yamlBlocks = content.match(/```folder-overview\n([\s\S]*?)```/g);
if (!yamlBlocks) return overviews;
for (const block of yamlBlocks) {
const yaml = parseYaml(block.replace('```folder-overview\n', '').replace('```', ''));
if (!yaml) continue;
overviews.push(yaml);
}
return overviews;
}
export async function updateYamlById(plugin: FolderNotesPlugin, overviewId: string, file: TFile, newYaml: yamlSettings) {
plugin.app.vault.process(file, (text) => {
const yamlBlocks = text.match(/```folder-overview\n([\s\S]*?)```/g);
if (!yamlBlocks) return text;
for (const block of yamlBlocks) {
const yaml = parseYaml(block.replace('```folder-overview\n', '').replace('```', ''));
if (!yaml) continue;
if (yaml.id === overviewId) {
const stringYaml = stringifyYaml(newYaml);
const newBlock = `\`\`\`folder-overview\n${stringYaml}\`\`\``;
text = text.replace(block, newBlock);
}
}
return text;
});
}
export function parseOverviewTitle(overview: yamlSettings, plugin: FolderNotesPlugin, folder: TFolder | null) {
const sourceFolderPath = overview.folderPath;
let sourceFolder: TFolder | undefined | null;
let title = overview.title;
if (sourceFolderPath !== '/') {
if (sourceFolderPath === '') {
sourceFolder = folder;
} else {
sourceFolder = plugin.app.vault.getAbstractFileByPath(sourceFolderPath) as TFolder;
}
}
if (sourceFolder && sourceFolderPath !== '/') {
title = title.replace('{{folderName}}', sourceFolder.name);
} else if (sourceFolderPath == '/') {
title = title.replace('{{folderName}}', 'Vault');
} else {
title = title.replace('{{folderName}}', '');
}
return title;
}
class CustomMarkdownRenderChild extends MarkdownRenderChild {
folderOverview: FolderOverview;

View file

@ -1,10 +1,11 @@
import { App, Modal, Setting, MarkdownPostProcessorContext, stringifyYaml, TFile, TFolder } from 'obsidian';
import { App, Modal, Setting, MarkdownPostProcessorContext, stringifyYaml, TFile, TFolder, SettingTab } from 'obsidian';
import { yamlSettings, includeTypes, FolderOverview } from 'src/folderOverview/FolderOverview';
import FolderNotesPlugin from '../main';
import { ListComponent } from 'src/functions/ListComponent';
import { updateYaml } from 'src/folderOverview/FolderOverview';
import { FolderSuggest } from 'src/suggesters/FolderSuggester';
import { getFolderPathFromString } from 'src/functions/utils';
import { createOverviewSettings } from "src/folderOverview/settings";
export class FolderOverviewSettings extends Modal {
plugin: FolderNotesPlugin;
@ -54,313 +55,25 @@ export class FolderOverviewSettings extends Modal {
updateYaml(this.plugin, this.ctx, this.el, this.yaml);
}
onOpen() {
this.display();
}
display() {
const { contentEl } = this;
this.display(contentEl, this.yaml, this.plugin, false, this.display, this.el, this.ctx);
}
display(contentEl: HTMLElement, yaml: yamlSettings, plugin: FolderNotesPlugin, defaultSettings: boolean, display: CallableFunction, el?: HTMLElement, ctx?: MarkdownPostProcessorContext, file?: TFile | null, settingsTab?: SettingTab, modal?: FolderOverviewSettings) {
modal = this ?? modal;
contentEl.empty();
// close when user presses enter
contentEl.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
this.close();
modal.close();
}
});
if (!this.defaultSettings) {
if (!modal.defaultSettings) {
contentEl.createEl('h2', { text: 'Folder overview settings' });
} else {
contentEl.createEl('h2', { text: 'Default folder overview settings' });
}
new Setting(contentEl)
.setName('Show the title')
.setDesc('Choose if the title should be shown')
.addToggle((toggle) =>
toggle
.setValue(this.yaml.showTitle)
.onChange(async (value) => {
this.yaml.showTitle = value;
this.display();
if (this.defaultSettings) {
return this.plugin.saveSettings();
}
await updateYaml(this.plugin, this.ctx, this.el, this.yaml);;
})
);
if (this.yaml.showTitle) {
new Setting(contentEl)
.setName('Title')
.setDesc('Choose the title of the folder overview')
.addText((text) =>
text
.setValue(this.yaml?.title || '{{folderName}} overview')
.onChange(async (value) => {
this.yaml.title = value;
if (this.defaultSettings) {
return this.plugin.saveSettings();
}
await updateYaml(this.plugin, this.ctx, this.el, this.yaml);;
})
);
}
new Setting(contentEl)
.setName('Folder path for the overview')
.setDesc('Choose the folder path for the overview')
.addSearch((search) => {
new FolderSuggest(search.inputEl, this.plugin, false)
search
.setPlaceholder('Folder path')
.setValue(this.yaml?.folderPath || '')
.onChange(async (value) => {
if (!(this.app.vault.getAbstractFileByPath(value) instanceof TFolder) && value !== '') return;
this.yaml.folderPath = value;
if (this.defaultSettings) {
return this.plugin.saveSettings();
}
await updateYaml(this.plugin, this.ctx, this.el, this.yaml);;
});
});
new Setting(contentEl)
.setName('Overview style')
.setDesc('Choose the style of the overview (grid style soon)')
.addDropdown((dropdown) =>
dropdown
.addOption('list', 'List')
.addOption('explorer', 'Explorer')
.setValue(this.yaml?.style || 'list')
.onChange(async (value: 'list') => {
this.yaml.style = value;
this.display();
if (this.defaultSettings) {
return this.plugin.saveSettings();
}
await updateYaml(this.plugin, this.ctx, this.el, this.yaml);
})
);
if (this.yaml.style === 'explorer') {
new Setting(contentEl)
.setName('Store collapsed condition')
.setDesc('Choose if the collapsed condition should be stored stored until you restart Obsidian')
.addToggle((toggle) =>
toggle
.setValue(this.yaml.storeFolderCondition)
.onChange(async (value) => {
this.yaml.storeFolderCondition = value;
if (this.defaultSettings) {
return this.plugin.saveSettings();
}
await updateYaml(this.plugin, this.ctx, this.el, this.yaml);;
})
);
}
const setting = new Setting(contentEl);
setting.setName('Include types');
const list = new ListComponent(setting.settingEl, this.yaml.includeTypes || [], ['markdown', 'folder']);
list.on('update', (values) => {
this.yaml.includeTypes = values;
if (this.defaultSettings) {
return this.plugin.saveSettings();
}
updateYaml(this.plugin, this.ctx, this.el, this.yaml);
this.display();
});
if ((this.yaml?.includeTypes?.length || 0) < 8 && !this.yaml.includeTypes?.includes('all')) {
setting.addDropdown((dropdown) => {
if (!this.yaml.includeTypes) this.yaml.includeTypes = this.plugin.settings.defaultOverview.includeTypes || [];
this.yaml.includeTypes = this.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 (!this.yaml.includeTypes?.includes(option.value as includeTypes)) {
dropdown.addOption(option.value, option.label);
}
});
dropdown.addOption('+', '+');
dropdown.setValue('+');
dropdown.onChange(async (value) => {
if (value === 'all') {
this.yaml.includeTypes = this.yaml.includeTypes?.filter((type: string) => type === 'folder');
list.setValues(this.yaml.includeTypes);
}
await list.addValue(value.toLowerCase());
this.display();
if (this.defaultSettings) {
return this.plugin.saveSettings();
}
await updateYaml(this.plugin, this.ctx, this.el, this.yaml);
});
});
}
let disableFileTag;
this.yaml.includeTypes?.forEach((type: string) => {
type === 'folder' || type === 'markdown' ? (disableFileTag = true) : null;
});
if (disableFileTag) {
new Setting(contentEl)
.setName('Disable file tag')
.setDesc('Choose if the file tag should be shown after the file name')
.addToggle((toggle) => {
toggle
.setValue(this.yaml.disableFileTag)
.onChange(async (value) => {
this.yaml.disableFileTag = value;
if (this.defaultSettings) {
return this.plugin.saveSettings();
}
await updateYaml(this.plugin, this.ctx, this.el, this.yaml);
});
});
}
new Setting(contentEl)
.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(this.yaml.showFolderNotes)
.onChange(async (value) => {
this.yaml.showFolderNotes = value;
if (this.defaultSettings) {
return this.plugin.saveSettings();
}
await updateYaml(this.plugin, this.ctx, this.el, this.yaml);
})
);
new Setting(contentEl)
.setName('File depth')
.setDesc('File & folder = +1 depth')
.addSlider((slider) =>
slider
.setValue(this.yaml?.depth || 2)
.setLimits(1, 10, 1)
.setDynamicTooltip()
.onChange(async (value) => {
this.yaml.depth = value;
if (this.defaultSettings) {
return this.plugin.saveSettings();
}
await updateYaml(this.plugin, this.ctx, this.el, this.yaml);
})
);
new Setting(contentEl)
.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(this.yaml?.sortBy || 'name')
.onChange(async (value: 'name' | 'created' | 'modified') => {
this.yaml.sortBy = value;
if (this.defaultSettings) {
return this.plugin.saveSettings();
}
await updateYaml(this.plugin, this.ctx, this.el, this.yaml);
})
)
.addDropdown((dropdown) => {
dropdown
.addOption('desc', 'Descending')
.addOption('asc', 'Ascending')
if (this.yaml.sortByAsc) {
dropdown.setValue('asc');
} else {
dropdown.setValue('desc');
}
dropdown.onChange(async (value) => {
if (value === 'desc') {
this.yaml.sortByAsc = false;
} else {
this.yaml.sortByAsc = true;
}
if (this.defaultSettings) {
return this.plugin.saveSettings();
}
await updateYaml(this.plugin, this.ctx, this.el, this.yaml);
});
});
if (this.yaml.style === 'list') {
new Setting(contentEl)
.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(this.yaml.showEmptyFolders)
.onChange(async (value) => {
this.yaml.showEmptyFolders = value;
this.yaml.onlyIncludeSubfolders = false;
this.display();
if (this.defaultSettings) {
return this.plugin.saveSettings();
}
await updateYaml(this.plugin, this.ctx, this.el, this.yaml);
});
});
if (this.yaml.showEmptyFolders) {
new Setting(contentEl)
.setName('Only show first empty subfolders of current folder')
.addToggle((toggle) => {
toggle
.setValue(this.yaml.onlyIncludeSubfolders)
.onChange(async (value) => {
this.yaml.onlyIncludeSubfolders = value;
if (this.defaultSettings) {
return this.plugin.saveSettings();
}
await updateYaml(this.plugin, this.ctx, this.el, this.yaml);
});
});
}
}
if (this.yaml.style === 'explorer') {
new Setting(contentEl)
.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(this.yaml.disableCollapseIcon)
.onChange(async (value) => {
this.yaml.disableCollapseIcon = value;
if (this.defaultSettings) {
return this.plugin.saveSettings();
}
await updateYaml(this.plugin, this.ctx, this.el, this.yaml);
});
});
new Setting(contentEl)
.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(this.yaml.alwaysCollapse)
.onChange(async (value) => {
this.yaml.alwaysCollapse = value;
if (this.defaultSettings) {
return this.plugin.saveSettings();
}
await updateYaml(this.plugin, this.ctx, this.el, this.yaml);
});
});
}
createOverviewSettings(contentEl, yaml, plugin, defaultSettings, display, el, ctx, undefined, undefined, modal);
}
onClose() {

View file

@ -0,0 +1,273 @@
import { MarkdownPostProcessorContext, Setting, TFile, TFolder } from 'obsidian';
import { updateYaml, updateYamlById, yamlSettings, includeTypes } from './FolderOverview';
import { FolderSuggest } from 'src/suggesters/FolderSuggester';
import { ListComponent } from 'src/functions/ListComponent';
import FolderNotesPlugin from 'src/main';
import { Callback } from 'front-matter-plugin-api-provider';
import { SettingsTab } from 'src/settings/SettingsTab';
import { FolderOverviewSettings } from './ModalSettings';
export async function createOverviewSettings(contentEl: HTMLElement, yaml: yamlSettings, plugin: FolderNotesPlugin, defaultSettings: boolean, display: CallableFunction, el?: HTMLElement, ctx?: MarkdownPostProcessorContext, file?: TFile | null, settingsTab?: SettingsTab, modal?: FolderOverviewSettings) {
new Setting(contentEl)
.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);;
display(contentEl, yaml, plugin, defaultSettings, display, el, ctx, file, settingsTab, modal);
})
);
if (yaml.showTitle) {
new Setting(contentEl)
.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);;
})
);
}
new Setting(contentEl)
.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) => {
if (!(plugin.app.vault.getAbstractFileByPath(value) instanceof TFolder) && value !== '') return;
yaml.folderPath = value;
updateSettings(contentEl, yaml, plugin, defaultSettings, el, ctx, file);;
});
});
new Setting(contentEl)
.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);
display(contentEl, yaml, plugin, defaultSettings, display, el, ctx, file, settingsTab, modal);
})
);
if (yaml.style === 'explorer') {
new Setting(contentEl)
.setName('Store collapsed condition')
.setDesc('Choose if the collapsed condition should be stored 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);;
})
);
}
const setting = new Setting(contentEl);
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);
display(contentEl, yaml, plugin, defaultSettings, display, el, ctx, file, settingsTab, modal);
});
if ((yaml?.includeTypes?.length || 0) < 8 && !yaml.includeTypes?.includes('all')) {
setting.addDropdown((dropdown) => {
if (!yaml.includeTypes) yaml.includeTypes = plugin.settings.defaultOverview.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);
display(contentEl, yaml, plugin, defaultSettings, display, el, ctx, file, settingsTab, modal);
});
});
}
let disableFileTag;
yaml.includeTypes?.forEach((type: string) => {
type === 'folder' || type === 'markdown' ? (disableFileTag = true) : null;
});
if (disableFileTag) {
new Setting(contentEl)
.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);
});
});
}
new Setting(contentEl)
.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);
})
);
new Setting(contentEl)
.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);
})
);
new Setting(contentEl)
.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) => {
if (value === 'desc') {
yaml.sortByAsc = false;
} else {
yaml.sortByAsc = true;
}
updateSettings(contentEl, yaml, plugin, defaultSettings, el, ctx, file);
});
});
if (yaml.style === 'list') {
new Setting(contentEl)
.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);
display(contentEl, yaml, plugin, defaultSettings, display, el, ctx, file, settingsTab, modal);
});
});
if (yaml.showEmptyFolders) {
new Setting(contentEl)
.setName('Only show first empty subfolders of current folder')
.addToggle((toggle) => {
toggle
.setValue(yaml.onlyIncludeSubfolders)
.onChange(async (value) => {
yaml.onlyIncludeSubfolders = value;
updateSettings(contentEl, yaml, plugin, defaultSettings, el, ctx, file);
});
});
}
}
if (yaml.style === 'explorer') {
new Setting(contentEl)
.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);
});
});
new Setting(contentEl)
.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);
});
});
}
}
async function updateSettings(contentEl: HTMLElement, yaml: yamlSettings, plugin: FolderNotesPlugin, defaultSettings: boolean, el?: HTMLElement, ctx?: MarkdownPostProcessorContext, file?: TFile | null) {
if (defaultSettings) {
plugin.updateOverviewView();
return plugin.saveSettings();
}
if (el && ctx) {
await updateYaml(plugin, ctx, el, yaml);
}
if (file) {
await updateYamlById(plugin, yaml.id, file, yaml);
}
}

View file

@ -0,0 +1,96 @@
import { ItemView, Setting, TFile, WorkspaceLeaf, MarkdownPostProcessorContext } from 'obsidian';
import FolderNotesPlugin from '../main';
import { FolderOverview, getOverviews, yamlSettings, parseOverviewTitle } from './FolderOverview';
import { FileSuggest } from 'src/suggesters/FileSuggester';
import { createOverviewSettings } from './settings';
export const FOLDER_OVERVIEW_VIEW = 'folder-overview-view';
export class FolderOverviewView extends ItemView {
plugin: FolderNotesPlugin;
activeFile: TFile | null;
overviewId: string | null;
yaml: yamlSettings;
defaultSettings: boolean;
contentEl: HTMLElement = this.containerEl.children[1] as HTMLElement;
constructor(leaf: WorkspaceLeaf, plugin: FolderNotesPlugin) {
super(leaf);
this.plugin = plugin;
this.registerEvent(
this.plugin.app.workspace.on('file-open', (file) => {
this.activeFile = file;
this.display(this.contentEl, this.yaml, this.plugin, this.defaultSettings, this.display, undefined, undefined, file);
})
);
}
getViewType() {
return FOLDER_OVERVIEW_VIEW;
}
getDisplayText() {
return 'Folder Overview settings';
}
getIcon() {
return 'settings';
}
async onOpen() {
this.display(this.contentEl, this.yaml, this.plugin, this.defaultSettings, this.display, undefined, undefined, this.activeFile);
}
async display(contentEl: HTMLElement, yaml: yamlSettings, plugin: FolderNotesPlugin, defaultSettings: boolean, display: CallableFunction, el?: HTMLElement, ctx?: MarkdownPostProcessorContext, file?: TFile | null) {
contentEl.empty();
contentEl.createEl('h4', { text: 'Folder Overview settings' });
const activeFile = plugin.app.workspace.getActiveFile();
if (!activeFile) {
// coulnd't get active file
contentEl.createEl('p', { text: 'No active file found' });
return;
}
const overviews = await getOverviews(plugin, activeFile);
const overviewSetting = new Setting(contentEl);
overviewSetting.setName('Select overview');
overviewSetting.setClass('fn-select-overview-setting');
overviewSetting.addDropdown((cb) => {
const options = overviews.reduce((acc, overview) => {
acc[overview.id] = parseOverviewTitle(overview as any as yamlSettings, plugin, activeFile.parent);
return acc;
}, {} as Record<string, string>);
cb.addOptions(options);
cb.addOption('default', 'Default');
cb.setValue(yaml?.id ?? 'default');
if (cb.getValue() === 'default' || defaultSettings) {
yaml = plugin.settings.defaultOverview;
defaultSettings = true;
cb.setValue('default');
} else {
yaml = overviews.find((overview) => overview.id === yaml.id) as any as yamlSettings;
defaultSettings = false;
}
cb.onChange(async (value) => {
if (value === 'default') {
yaml = plugin.settings.defaultOverview;
defaultSettings = true;
} else {
yaml = overviews.find((overview) => overview.id === value) as any as yamlSettings;
defaultSettings = false;
}
display(contentEl, yaml, plugin, defaultSettings, display, undefined, undefined, activeFile);
});
});
createOverviewSettings(contentEl, yaml, plugin, defaultSettings, display, undefined, undefined, activeFile);
// execute display function when the default settings are changed
}
}

View file

@ -1,4 +1,4 @@
import { Plugin, TFile, TFolder, TAbstractFile, MarkdownPostProcessorContext, parseYaml, Notice, Keymap, MenuItem, requireApiVersion } from 'obsidian';
import { Plugin, TFile, TFolder, TAbstractFile, MarkdownPostProcessorContext, parseYaml, Notice, Keymap, WorkspaceLeaf, requireApiVersion } from 'obsidian';
import { DEFAULT_SETTINGS, FolderNotesSettings, SettingsTab } from './settings/SettingsTab';
import { Commands } from './Commands';
import { FileExplorerWorkspaceLeaf } from './globals';
@ -17,6 +17,7 @@ import { addCSSClassToTitleEL, getEl, loadFileClasses } from './functions/styleF
import { getExcludedFolder } from './ExcludeFolders/functions/folderFunctions';
import { ClipBoardManager, FileExplorerView, InternalPlugin } from 'obsidian-typings'
import { getFocusedItem } from './functions/utils';
import { FOLDER_OVERVIEW_VIEW, FolderOverviewView } from './folderOverview/view';
export default class FolderNotesPlugin extends Plugin {
observer: MutationObserver;
settings: FolderNotesSettings;
@ -74,7 +75,7 @@ export default class FolderNotesPlugin extends Plugin {
if (!folderNote) return;
openFolderNote(this, folderNote);
}
const hoveredElement = this.hoveredElement;
if (this.hoverLinkTriggered) return;
if (!hoveredElement) return;
@ -133,18 +134,14 @@ export default class FolderNotesPlugin extends Plugin {
});
if (this.app.workspace.layoutReady) {
// loadFileClasses(undefined, this);
// // this.registerEvent(this.app.workspace.on('layout-change', () => {
// // loadFileClasses(undefined, this);
// // this.tabManager?.updateTabs();
// // }));
this.registerView(FOLDER_OVERVIEW_VIEW, (leaf: WorkspaceLeaf) => {
return new FolderOverviewView(leaf, this);
});
} else {
this.app.workspace.onLayoutReady(async () => {
// loadFileClasses(undefined, this)
// // this.registerEvent(this.app.workspace.on('layout-change', () => {
// // loadFileClasses(undefined, this);
// // this.tabManager?.updateTabs();
// // }));
this.registerView(FOLDER_OVERVIEW_VIEW, (leaf: WorkspaceLeaf) => {
return new FolderOverviewView(leaf, this);
});
});
}
}
@ -164,7 +161,7 @@ export default class FolderNotesPlugin extends Plugin {
// @ts-ignore
const editMode = view.editMode ?? view.sourceMode ?? this.app.workspace.activeEditor?.editMode;
if (!editMode) { return; }
const plugin = this;
// @ts-ignore
@ -233,6 +230,31 @@ export default class FolderNotesPlugin extends Plugin {
}
}
async activateOverviewView() {
const { workspace } = this.app;
let leaf: WorkspaceLeaf | null = null;
const leaves = workspace.getLeavesOfType(FOLDER_OVERVIEW_VIEW);
if (leaves.length > 0) {
leaf = leaves[0];
} else {
leaf = workspace.getRightLeaf(false);
await leaf?.setViewState({ type: FOLDER_OVERVIEW_VIEW, active: true });
}
if (!leaf) return;
workspace.revealLeaf(leaf);
}
async updateOverviewView() {
const { workspace } = this.app;
const leaf = workspace.getLeavesOfType(FOLDER_OVERVIEW_VIEW)[0];
if (!leaf) return;
const view = leaf.view as FolderOverviewView;
view.display(view.contentEl, view.yaml, this, view.defaultSettings, view.display, undefined, undefined, view.activeFile);
}
isEmptyFolderNoteFolder(folder: TFolder): boolean {
let attachmentFolderPath = this.app.vault.getConfig('attachmentFolderPath') as string;
const cleanAttachmentFolderPath = attachmentFolderPath?.replace('./', '') || '';

View file

@ -3,6 +3,7 @@ import { SettingsTab } from "./SettingsTab";
import { ListComponent } from 'src/functions/ListComponent';
import { includeTypes } from 'src/folderOverview/FolderOverview';
import { FolderSuggest } from "src/suggesters/FolderSuggester";
import { createOverviewSettings } from "src/folderOverview/settings";
export async function renderFolderOverview(settingsTab: SettingsTab) {
const { plugin } = settingsTab;
@ -10,251 +11,5 @@ export async function renderFolderOverview(settingsTab: SettingsTab) {
const containerEl = settingsTab.settingsPage;
containerEl.createEl('p', { text: 'Change the default settings for folder overviews', cls: 'setting-item-description' });
new Setting(containerEl)
.setName('Show the title')
.setDesc('Choose if the title should be shown')
.addToggle((toggle) =>
toggle
.setValue(overviewSettings.showTitle)
.onChange(async (value) => {
overviewSettings.showTitle = value;
settingsTab.display();
plugin.saveSettings();
})
);
if (overviewSettings.showTitle) {
new Setting(containerEl)
.setName('Title')
.setDesc('Choose the title of the folder overview')
.addText((text) =>
text
.setValue(overviewSettings?.title || '{{folderName}} overview')
.onChange(async (value) => {
overviewSettings.title = value;
plugin.saveSettings();
})
);
}
new Setting(containerEl)
.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(overviewSettings?.folderPath || '')
.onChange(async (value) => {
if (!(this.app.vault.getAbstractFileByPath(value) instanceof TFolder) && value !== '') return;
overviewSettings.folderPath = value;
plugin.saveSettings();
});
});
new Setting(containerEl)
.setName('Overview style')
.setDesc('Choose the style of the overview (grid style soon)')
.addDropdown((dropdown) =>
dropdown
.addOption('list', 'List')
.addOption('explorer', 'Explorer')
.setValue(overviewSettings?.style || 'list')
.onChange(async (value: 'list') => {
overviewSettings.style = value;
settingsTab.display();
plugin.saveSettings();
})
);
if (overviewSettings.style === 'explorer') {
new Setting(containerEl)
.setName('Store collapsed condition')
.setDesc('Choose if the collapsed condition should be stored stored until you restart Obsidian')
.addToggle((toggle) =>
toggle
.setValue(overviewSettings.storeFolderCondition)
.onChange(async (value) => {
overviewSettings.storeFolderCondition = value;
plugin.saveSettings();
})
);
}
const setting = new Setting(containerEl)
.setName('Include types');
const list = new ListComponent(setting.settingEl, overviewSettings?.includeTypes || [], ['markdown', 'folder']);
list.on('update', (values) => {
overviewSettings.includeTypes = values;
settingsTab.display();
plugin.saveSettings();
});
if ((overviewSettings?.includeTypes?.length || 0) < 8 && !overviewSettings.includeTypes?.includes('all')) {
setting.addDropdown((dropdown) => {
overviewSettings.includeTypes = overviewSettings.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 (!overviewSettings.includeTypes?.includes(option.value as includeTypes)) {
dropdown.addOption(option.value, option.label);
}
});
dropdown.addOption('+', '+');
dropdown.setValue('+');
dropdown.onChange(async (value) => {
if (value === 'all') {
overviewSettings.includeTypes = overviewSettings.includeTypes?.filter((type: string) => type === 'folder');
list.setValues(overviewSettings.includeTypes);
}
await list.addValue(value.toLowerCase());
settingsTab.display();
plugin.saveSettings();
});
});
}
let disableFileTag;
overviewSettings.includeTypes?.forEach((type: string) => {
type === 'folder' || type === 'markdown' ? (disableFileTag = true) : null;
});
if (disableFileTag) {
new Setting(containerEl)
.setName('Disable file tag')
.setDesc('Choose if the file tag should be shown after the file name')
.addToggle((toggle) => {
toggle
.setValue(overviewSettings.disableFileTag)
.onChange(async (value) => {
overviewSettings.disableFileTag = value;
plugin.saveSettings();
});
});
}
new Setting(containerEl)
.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(overviewSettings.showFolderNotes)
.onChange(async (value) => {
overviewSettings.showFolderNotes = value;
plugin.saveSettings();
})
);
if (overviewSettings.style !== 'explorer') {
new Setting(containerEl)
.setName('File depth')
.setDesc('File & folder = +1 depth')
.addSlider((slider) =>
slider
.setValue(overviewSettings?.depth || 2)
.setLimits(1, 10, 1)
.onChange(async (value) => {
overviewSettings.depth = value;
plugin.saveSettings();
})
);
}
new Setting(containerEl)
.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(overviewSettings?.sortBy || 'name')
.onChange(async (value: 'name' | 'created' | 'modified') => {
overviewSettings.sortBy = value;
plugin.saveSettings();
})
)
.addDropdown((dropdown) => {
dropdown
.addOption('desc', 'Descending')
.addOption('asc', 'Ascending')
if (overviewSettings.sortByAsc) {
dropdown.setValue('asc');
} else {
dropdown.setValue('desc');
}
dropdown.onChange(async (value) => {
if (value === 'desc') {
overviewSettings.sortByAsc = false;
} else {
overviewSettings.sortByAsc = true;
}
plugin.saveSettings();
});
});
if (overviewSettings.style === 'list') {
new Setting(containerEl)
.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(overviewSettings.showEmptyFolders)
.onChange(async (value) => {
overviewSettings.showEmptyFolders = value;
overviewSettings.onlyIncludeSubfolders = false;
settingsTab.display();
plugin.saveSettings();
});
});
if (overviewSettings.showEmptyFolders) {
new Setting(containerEl)
.setName('Only show first empty subfolders of current folder')
.addToggle((toggle) => {
toggle
.setValue(overviewSettings.onlyIncludeSubfolders)
.onChange(async (value) => {
overviewSettings.onlyIncludeSubfolders = value;
plugin.saveSettings();
});
});
}
}
if (overviewSettings.style === 'explorer') {
new Setting(containerEl)
.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(overviewSettings.disableCollapseIcon)
.onChange(async (value) => {
overviewSettings.disableCollapseIcon = value;
plugin.saveSettings();
});
});
new Setting(containerEl)
.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(overviewSettings.alwaysCollapse)
.onChange(async (value) => {
overviewSettings.alwaysCollapse = value;
plugin.saveSettings();
});
});
}
createOverviewSettings(containerEl, overviewSettings, plugin, true, settingsTab.display, undefined, undefined, undefined, settingsTab);
}

View file

@ -1,4 +1,4 @@
import { App, Notice, PluginSettingTab, TFile, TFolder } from 'obsidian';
import { App, Notice, PluginSettingTab, TFile, TFolder, MarkdownPostProcessorContext } from 'obsidian';
import FolderNotesPlugin from '../main';
import { ExcludePattern } from 'src/ExcludeFolders/ExcludePattern';
import { ExcludedFolder } from 'src/ExcludeFolders/ExcludeFolder';
@ -111,6 +111,7 @@ export const DEFAULT_SETTINGS: FolderNotesSettings = {
storeFolderCondition: true,
showFolderNotes: false,
disableCollapseIcon: true,
alwaysCollapse: false,
},
useSubmenus: true,
syncMove: true,
@ -225,32 +226,34 @@ export class SettingsTab extends PluginSettingTab {
}
display(): void {
this.plugin.settingsOpened = true;
const { containerEl } = this;
display(contentEl?: HTMLElement, yaml?: yamlSettings, plugin?: FolderNotesPlugin, defaultSettings?: boolean, display?: CallableFunction, el?: HTMLElement, ctx?: MarkdownPostProcessorContext, file?: TFile | null, settingsTab?: this) {
plugin = this?.plugin ?? plugin;
plugin.settingsOpened = true;
settingsTab = this ?? settingsTab;
const { containerEl } = settingsTab;
containerEl.empty();
const tabBar = containerEl.createEl('nav', { cls: 'fn-settings-tab-bar' });
for (const [tabId, tabInfo] of Object.entries(this.TABS)) {
for (const [tabId, tabInfo] of Object.entries(settingsTab.TABS)) {
const tabEl = tabBar.createEl('div', { cls: 'fn-settings-tab' });
const tabName = tabEl.createEl('div', { cls: 'fn-settings-tab-name', text: tabInfo.name });
if (this.plugin.settings.settingsTab.toLocaleLowerCase() === tabId.toLocaleLowerCase()) {
if (plugin.settings.settingsTab.toLocaleLowerCase() === tabId.toLocaleLowerCase()) {
tabEl.addClass('fn-settings-tab-active');
}
tabEl.addEventListener('click', () => {
// @ts-ignore
for (const tabEl of tabBar.children) {
tabEl.removeClass('fn-settings-tab-active');
this.plugin.settings.settingsTab = tabId.toLocaleLowerCase();
this.plugin.saveSettings();
plugin.settings.settingsTab = tabId.toLocaleLowerCase();
plugin.saveSettings();
}
tabEl.addClass('fn-settings-tab-active');
this.renderSettingsPage(tabId);
settingsTab.renderSettingsPage(tabId);
});
}
this.settingsPage = containerEl.createDiv({ cls: 'fn-settings-page' });
this.renderSettingsPage(this.plugin.settings.settingsTab);
settingsTab.settingsPage = containerEl.createDiv({ cls: 'fn-settings-page' });
settingsTab.renderSettingsPage(plugin.settings.settingsTab);
}
updateFolderNotes(newTemplate: string) {