From 5173962ee8d4003ee3ca3bf3418cd96d356179c9 Mon Sep 17 00:00:00 2001 From: Lost Paul <70213368+LostPaul@users.noreply.github.com> Date: Sun, 11 Aug 2024 15:58:05 +0200 Subject: [PATCH] Improved the list setting type --- src/events/EventEmitter.ts | 22 +++ src/folderOverview/ModalSettings.ts | 56 +++--- src/functions/ListComponent.ts | 115 ++++------- src/settings/FolderOverviewSettings.ts | 251 ++++++++++++++++++++++++- src/settings/GeneralSettings.ts | 11 +- 5 files changed, 338 insertions(+), 117 deletions(-) create mode 100644 src/events/EventEmitter.ts diff --git a/src/events/EventEmitter.ts b/src/events/EventEmitter.ts new file mode 100644 index 0000000..81a7429 --- /dev/null +++ b/src/events/EventEmitter.ts @@ -0,0 +1,22 @@ +export class CustomEventEmitter { + private events: { [key: string]: Array<(data?: any) => void> } = {}; + + on(event: string, listener: (data?: any) => void) { + if (!this.events[event]) { + this.events[event] = []; + } + this.events[event].push(listener); + } + + off(event: string, listener: (data?: any) => void) { + if (!this.events[event]) return; + + this.events[event] = this.events[event].filter((l) => l !== listener); + } + + emit(event: string, data?: any) { + if (!this.events[event]) return; + + this.events[event].forEach((listener) => listener(data)); + } +} \ No newline at end of file diff --git a/src/folderOverview/ModalSettings.ts b/src/folderOverview/ModalSettings.ts index aeb499d..db4bc8a 100644 --- a/src/folderOverview/ModalSettings.ts +++ b/src/folderOverview/ModalSettings.ts @@ -1,7 +1,7 @@ import { App, Modal, Setting, MarkdownPostProcessorContext, stringifyYaml, TFile, TFolder } from 'obsidian'; import { yamlSettings, includeTypes, FolderOverview } from 'src/folderOverview/FolderOverview'; import FolderNotesPlugin from '../main'; -import ListComponent from 'src/functions/ListComponent'; +import { ListComponent } from 'src/functions/ListComponent'; import { updateYaml } from 'src/folderOverview/FolderOverview'; import { FolderSuggest } from 'src/suggesters/FolderSuggester'; import { getFolderPathFromString } from 'src/functions/utils'; @@ -152,12 +152,16 @@ export class FolderOverviewSettings extends Modal { } const setting = new Setting(contentEl); setting.setName('Include types'); - const list = setting.createList((list: ListComponent) => - list - .addModal(this) - .setValues(this.yaml?.includeTypes || this.plugin.settings.defaultOverview.includeTypes || []) - .addResetButton() - ); + 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 || []; @@ -184,10 +188,8 @@ export class FolderOverviewSettings extends Modal { dropdown.onChange(async (value) => { if (value === 'all') { this.yaml.includeTypes = this.yaml.includeTypes?.filter((type: string) => type === 'folder'); - // @ts-ignore list.setValues(this.yaml.includeTypes); } - // @ts-ignore await list.addValue(value.toLowerCase()); this.display(); if (this.defaultSettings) { @@ -197,10 +199,12 @@ export class FolderOverviewSettings extends Modal { }); }); } + let disableFileTag; this.yaml.includeTypes?.forEach((type: string) => { type === 'folder' || type === 'markdown' ? (disableFileTag = true) : null; }); + if (disableFileTag) { new Setting(contentEl) .setName('Disable file tag') @@ -217,6 +221,7 @@ export class FolderOverviewSettings extends Modal { }); }); } + 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') @@ -232,23 +237,21 @@ export class FolderOverviewSettings extends Modal { }) ); - if (this.yaml.style !== 'explorer') { - new Setting(contentEl) - .setName('File depth') - .setDesc('File & folder = +1 depth') - .addSlider((slider) => - slider - .setValue(this.yaml?.depth || 2) - .setLimits(1, 10, 1) - .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('File depth') + .setDesc('File & folder = +1 depth') + .addSlider((slider) => + slider + .setValue(this.yaml?.depth || 2) + .setLimits(1, 10, 1) + .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') @@ -288,6 +291,7 @@ export class FolderOverviewSettings extends Modal { 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') diff --git a/src/functions/ListComponent.ts b/src/functions/ListComponent.ts index 235ef06..df34fff 100644 --- a/src/functions/ListComponent.ts +++ b/src/functions/ListComponent.ts @@ -1,53 +1,50 @@ -import { Setting } from 'obsidian'; -import { FolderOverviewSettings } from '../folderOverview/ModalSettings'; -import { includeTypes } from 'src/folderOverview/FolderOverview'; -import { updateYaml } from 'src/folderOverview/FolderOverview'; -import { SettingsTab } from 'src/settings/SettingsTab'; -export default class ListComponent { +import { CustomEventEmitter } from "src/events/EventEmitter"; + +export class ListComponent { + emitter: CustomEventEmitter; containerEl: HTMLElement; controlEl: HTMLElement; emptyStateEl: HTMLElement; listEl: HTMLElement; values: string[]; - modal: FolderOverviewSettings | undefined; - settings: SettingsTab | undefined; - constructor(containerEl: HTMLElement) { + defaultValues: string[]; + constructor(containerEl: HTMLElement, values: string[] = [], defaultValues: string[] = []) { + this.emitter = new CustomEventEmitter(); this.containerEl = containerEl; this.controlEl = containerEl.querySelector('.setting-item-control') || containerEl; this.listEl = this.controlEl.createDiv('setting-command-hotkeys'); + this.addResetButton(); + this.setValues(values); + this.defaultValues = defaultValues; } - addModal(modal: FolderOverviewSettings) { - this.modal = modal; - this.values = modal.yaml.includeTypes || []; - return this; + + on(event: string, listener: (data?: any) => void) { + this.emitter.on(event, listener); } - addSettings(settings: SettingsTab) { - this.settings = settings; - return this; + + off(event: string, listener: (data?: any) => void) { + this.emitter.off(event, listener); } + + private emit(event: string, data?: any) { + this.emitter.emit(event, data); + } + setValues(values: string[]) { - this.listEl.empty(); + this.removeElements(); this.values = values; - if (this.modal) { - this.modal.yaml.includeTypes = values as includeTypes[]; - } if (values.length !== 0) { values.forEach((value) => { this.addElement(value); }); } - if (this.modal && this.modal.defaultSettings) { - this.modal.plugin.saveSettings(); - return this; - } else if (this.settings) { - this.settings.plugin.settings.supportedFileTypes = values; - this.settings.plugin.saveSettings(); - return this; - } - if (!this.modal) return this; - updateYaml(this.modal.plugin, this.modal.ctx, this.modal.el, this.modal.yaml); - return this; + this.emit('update', this.values); } + + removeElements() { + this.listEl.empty(); + } + addElement(value: string) { this.listEl.createSpan('setting-hotkey', (span) => { if (value.toLocaleLowerCase() === 'md') { @@ -55,8 +52,8 @@ export default class ListComponent { } else { span.innerText = value; } + span.setAttribute('extension', value); const removeSpan = span.createEl('span', { cls: 'ofn-list-item-remove setting-hotkey-icon' }); - // add svg icon const svg = ''; const svgElement = removeSpan.createEl('span', { cls: 'ofn-list-item-remove-icon' }); svgElement.innerHTML = svg; @@ -66,60 +63,28 @@ export default class ListComponent { }); }); } + async addValue(value: string) { this.values.push(value); this.addElement(value); - if (this.settings) { - this.settings.plugin.settings.supportedFileTypes = this.values; - this.settings.plugin.saveSettings(); - } - if (!this.modal) return this; - this.modal.yaml.includeTypes = this.values as includeTypes[]; - return this; + this.emit('add', value); + this.emit('update', this.values); } + addResetButton() { const resetButton = this.controlEl.createEl('span', { cls: 'clickable-icon setting-restore-hotkey-button' }); const svg = ''; resetButton.innerHTML = svg; resetButton.onClickEvent((e) => { - if (this.modal) { - this.modal.plugin.loadSettings(); - this.setValues(this.modal.plugin.settings.defaultOverview.includeTypes || []); - this.modal.display(); - } else if (this.settings) { - this.setValues(['md', 'canvas']); - this.settings.display(); - } + this.setValues(this.defaultValues); }); return this; } + removeValue(value: string) { - if (value === 'all') { - if (this.modal) { - this.modal.plugin.loadSettings(); - this.setValues(this.modal.plugin.settings.defaultOverview.includeTypes || []); - this.modal.display(); - } else if (this.settings) { - this.setValues(['md', 'canvas']); - this.settings.display(); - } - } else { - this.values = this.values.filter((v) => v !== value); - this.setValues(this.values); - if (this.modal) { - this.modal.display(); - } else if (this.settings) { - this.settings.display(); - } - } + this.values = this.values.filter((v) => v !== value); + this.listEl.find(`[extension="${value}"]`).remove(); + this.emit('remove', value); + this.emit('update', this.values); } -} - -function createList(this: Setting, cb: (list: ListComponent) => ListComponent) { - const list = new ListComponent(this.settingEl); - cb(list); - return list; -} - -// ListComponent.prototype.setValues = setValues; -(Setting as any).prototype.createList = createList; +} \ No newline at end of file diff --git a/src/settings/FolderOverviewSettings.ts b/src/settings/FolderOverviewSettings.ts index 91e05b9..5b93733 100644 --- a/src/settings/FolderOverviewSettings.ts +++ b/src/settings/FolderOverviewSettings.ts @@ -1,18 +1,249 @@ -import { Setting } from "obsidian"; +import { Setting, TFolder } from "obsidian"; import { SettingsTab } from "./SettingsTab"; -import { FolderOverviewSettings } from '../folderOverview/ModalSettings'; +import { ListComponent } from 'src/functions/ListComponent'; +import { includeTypes } from 'src/folderOverview/FolderOverview'; +import { FolderSuggest } from "src/suggesters/FolderSuggester"; export async function renderFolderOverview(settingsTab: SettingsTab) { + const { plugin } = settingsTab; + let overviewSettings = plugin.settings.defaultOverview; const containerEl = settingsTab.settingsPage; + containerEl.createEl('p', { text: 'Change the default settings for folder overviews', cls: 'setting-item-description' }); + new Setting(containerEl) - .setName('Manage folder overview defaults') - .setDesc('Manage the default settings for the folder overview plugin') - .addButton((button) => - button - .setButtonText('Manage') - .setCta() - .onClick(async () => { - new FolderOverviewSettings(settingsTab.plugin.app, settingsTab.plugin, settingsTab.plugin.settings.defaultOverview, null, null, true).open(); + .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(); + }); + }); + } + } diff --git a/src/settings/GeneralSettings.ts b/src/settings/GeneralSettings.ts index 183c728..68afa80 100644 --- a/src/settings/GeneralSettings.ts +++ b/src/settings/GeneralSettings.ts @@ -1,6 +1,6 @@ import { Setting, Platform, SettingTab } from "obsidian"; import { SettingsTab } from "./SettingsTab"; -import ListComponent from '../functions/ListComponent'; +import { ListComponent } from '../functions/ListComponent'; import AddSupportedFileModal from '../modals/AddSupportedFileType'; import { FrontMatterTitlePluginHandler } from '../events/FrontMatterTitle'; import ConfirmationModal from "../modals/ConfirmCreation"; @@ -90,10 +90,10 @@ export async function renderGeneral(settingsTab: SettingsTab) { 'Adding more file types may cause performance issues becareful when adding more file types and don\'t add too many.', ) setting0.setDesc(desc0); - const list = setting0.createList((list: ListComponent) => { - list.addSettings(settingsTab) - list.setValues(settingsTab.plugin.settings.supportedFileTypes || ['md', 'canvas']); - list.addResetButton(); + const list = new ListComponent(setting0.settingEl,settingsTab.plugin.settings.supportedFileTypes || [], ['md', 'canvas'] ); + list.on('update', async (values: string[]) => { + settingsTab.plugin.settings.supportedFileTypes = values; + await settingsTab.plugin.saveSettings(); }) if (!settingsTab.plugin.settings.supportedFileTypes.includes('md') || !settingsTab.plugin.settings.supportedFileTypes.includes('canvas') || !settingsTab.plugin.settings.supportedFileTypes.includes('excalidraw')) { @@ -116,7 +116,6 @@ export async function renderGeneral(settingsTab: SettingsTab) { if (value === 'custom') { return new AddSupportedFileModal(settingsTab.app, settingsTab.plugin, settingsTab, list as ListComponent).open(); } - // @ts-ignore await list.addValue(value.toLowerCase()); settingsTab.display(); settingsTab.plugin.saveSettings();