Make folder overview an independent plugin progress

This commit is contained in:
Lost Paul 2025-01-07 21:39:34 +01:00
parent 62c4b5e149
commit 2e2d343aed
14 changed files with 62 additions and 56 deletions

View file

@ -6,7 +6,9 @@
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"version": "node version-bump.mjs && git add manifest.json versions.json"
"version": "node version-bump.mjs && git add manifest.json versions.json",
"folder-overview": "node ./src/folderOverview/esbuild.config.mjs",
"fv": "npm run folder-overview"
},
"keywords": [],
"author": "Alan Grainger",

View file

View file

@ -15,7 +15,7 @@ esbuild.build({
banner: {
js: banner,
},
entryPoints: ['./src/main.ts'],
entryPoints: ['./src/folderOverview/src/main.ts'],
bundle: true,
external: [
'obsidian',
@ -38,6 +38,6 @@ esbuild.build({
logLevel: 'info',
sourcemap: prod ? false : 'inline',
treeShaking: true,
outfile: 'main.js',
outfile: './src/folderOverview/main.js',
conditions: ['types'],
}).catch(() => process.exit(1));

View file

@ -198,7 +198,7 @@ export class FileExplorerOverview {
}
if (excludedFolder?.excludeFromFolderOverview) { return; }
const svg = '<svg xmlns='http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon right-triangle"><path d="M3 8L12 17L21 8'></path></svg>';
const svg = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon right-triangle"><path d="M3 8L12 17L21 8"></path></svg>';
if (yaml.includeTypes.includes('folder')) {
folderElement = childrenElement.createDiv({
cls: 'tree-item nav-folder',

View file

@ -50,13 +50,10 @@ export class FolderOverview {
eventListeners: (() => void)[] = [];
constructor(plugin: FolderNotesPlugin | FolderOverviewPlugin, ctx: MarkdownPostProcessorContext, source: string, el: HTMLElement, defaultSettings: overviewSettings) {
console.log('creating folder overview');
console.log('defaultSettings', defaultSettings);
this.plugin = plugin;
this.emitter = new CustomEventEmitter();
let yaml: overviewSettings = parseYaml(source);
if (!yaml) { yaml = {} as overviewSettings; }
console.log('yaml', yaml);
const includeTypes = yaml?.includeTypes || defaultSettings.includeTypes || ['folder', 'markdown'];
this.ctx = ctx;
this.source = source;
@ -82,7 +79,6 @@ export class FolderOverview {
disableCollapseIcon: yaml?.disableCollapseIcon ?? defaultSettings.disableCollapseIcon,
alwaysCollapse: yaml?.alwaysCollapse ?? defaultSettings.alwaysCollapse,
};
console.log('this.yaml', this.yaml);
const customChild = new CustomMarkdownRenderChild(el, this);
@ -174,7 +170,7 @@ export class FolderOverview {
if (!sourceFolder && sourceFolderPath == '') {
sourceFolderPath = '/';
}
if (!(sourceFolder instanceof TFolder)) { return; }
if (!(sourceFolder instanceof TFolder) && sourceFolderPath !== '/') { return; }
if (sourceFolderPath == '/') {
const rootFiles: TAbstractFile[] = [];
@ -184,11 +180,10 @@ export class FolderOverview {
}
});
files = rootFiles;
} else if (sourceFolder) {
} else if (sourceFolder instanceof TFolder) {
files = sourceFolder.children;
}
files = await this.filterFiles(files, plugin, sourceFolderPath, this.yaml.depth, this.pathBlacklist);
if (!this.yaml.includeTypes.includes('folder')) {
@ -252,20 +247,19 @@ export class FolderOverview {
}
}
}
if (this.yaml.includeTypes.length > 1 && (!this.yaml.showEmptyFolders || this.yaml.onlyIncludeSubfolders) && this.yaml.style === 'list') {
this.removeEmptyFolders(ul, 1, this.yaml);
}
// if (this.yaml.includeTypes.length > 1 && (!this.yaml.showEmptyFolders || this.yaml.onlyIncludeSubfolders) && this.yaml.style === 'list') {
// this.removeEmptyFolders(ul, 1, this.yaml);
// }
}
addEditButton(root: HTMLElement) {
console.log('adding edit button');
const editButton = root.createEl('button', { cls: 'folder-overview-edit-button' });
editButton.innerText = 'Edit overview';
editButton.addEventListener('click', (e) => {
e.stopImmediatePropagation();
e.preventDefault();
e.stopPropagation();
console.log({ 'this.plugin': this.plugin, 'this.yaml': this.yaml, 'this.ctx': this.ctx, 'this.el': this.el });
new FolderOverviewSettings(this.plugin.app as App, this.plugin, this.yaml, this.ctx, this.el, this.plugin.settings as overviewSettings).open();
}, { capture: true });
}
@ -274,9 +268,10 @@ export class FolderOverview {
const filteredFiles = await Promise.all(files.map(async (file) => {
const folderPath = getFolderPathFromString(file.path);
const isBlacklisted = pathBlacklist.includes(file.path);
const isSubfolder = folderPath.startsWith(sourceFolderPath) && sourceFolderPath !== '/';
const isSubfolder = sourceFolderPath === '/' || folderPath.startsWith(sourceFolderPath);
const isSourceFile = file.path === this.sourceFilePath;
let isExcludedFromOverview = false;
if (plugin instanceof FolderNotesPlugin) {
isExcludedFromOverview = (await getExcludedFolder(plugin, file.path, true))?.excludeFromFolderOverview ?? false;
}
@ -285,7 +280,7 @@ export class FolderOverview {
return null;
}
const fileDepth = file.path.split('/').length - sourceFolderPath.split('/').length;
const fileDepth = file.path.split('/').length - (sourceFolderPath === '/' ? 0 : sourceFolderPath.split('/').length);
return fileDepth <= depth ? file : null;
}));
@ -293,6 +288,7 @@ export class FolderOverview {
}
sortFiles(files: TAbstractFile[]): TAbstractFile[] {
const yaml = this.yaml;
@ -333,24 +329,6 @@ export class FolderOverview {
return files;
}
removeEmptyFolders(ul: HTMLUListElement | HTMLLIElement, depth: number, yaml: overviewSettings) {
const childrensToRemove: ChildNode[] = [];
ul.childNodes.forEach((el) => {
if ((el.childNodes[0] as HTMLElement)?.classList && (el.childNodes[0] as HTMLElement)?.classList.contains('internal-link')) { return; }
const childrens = (el as Element).querySelector('ul');
if (!childrens || childrens === null) { return; }
if (childrens && !childrens?.hasChildNodes() && !(el instanceof HTMLUListElement)) {
childrensToRemove.push(el);
} else if (el instanceof HTMLUListElement || el instanceof HTMLLIElement) {
this.removeEmptyFolders(el, depth + 1, yaml);
}
});
childrensToRemove.forEach((el) => {
if (yaml.onlyIncludeSubfolders && depth === 1) { return; }
el.remove();
});
}
getAllFiles(files: TAbstractFile[], sourceFolderPath: string, depth: number) {
const allFiles: TAbstractFile[] = [];

View file

@ -1,5 +1,5 @@
import { MarkdownPostProcessorContext, TFolder, TFile, Plugin } from 'obsidian';
import { getFolderNote } from '../../functions/folderNoteFunctions';
import { extractFolderName, getFolderNote } from '../../functions/folderNoteFunctions';
import { FolderOverview, overviewSettings } from './FolderOverview';
import { getFolderPathFromString } from '../../functions/utils';
import FolderOverviewPlugin from 'src/main';
@ -41,6 +41,14 @@ export function renderListOverview(plugin: FolderOverviewPlugin | FolderNotesPlu
}
export function addFolderList(plugin: FolderOverviewPlugin | FolderNotesPlugin | FolderNotesPlugin, list: HTMLUListElement | HTMLLIElement, pathBlacklist: string[], folder: TFolder, folderOverview: FolderOverview) {
const isFirstLevelSub = folder.path.split('/').length === folderOverview.yaml.folderPath.split('/').length + 1;
if (!folderOverview.yaml.showEmptyFolders && folder.children.length === 0 && !folderOverview.yaml.onlyIncludeSubfolders) {
return;
} else if (folderOverview.yaml.onlyIncludeSubfolders && !isFirstLevelSub && folder.children.length === 0) {
return;
}
const folderItem = list.createEl('li', { cls: 'folder-overview-list folder-list' });
if (plugin instanceof FolderNotesPlugin) {
const folderNote = getFolderNote(plugin, folder.path);
@ -115,6 +123,9 @@ function addFileList(plugin: FolderOverviewPlugin | FolderNotesPlugin, list: HTM
if (!folderOverview.yaml.showFolderNotes) {
if (pathBlacklist.includes(file.path)) return;
if (plugin instanceof FolderNotesPlugin && extractFolderName(plugin.settings.folderNoteName, file.basename) === file.parent?.name) {
return;
}
}
const listItem = list.createEl('li', { cls: 'folder-overview-list file-link' });

View file

@ -21,8 +21,6 @@ export class FolderOverviewSettings extends Modal {
if (!yaml) {
this.yaml = this.defaultSettings;
} else if (ctx) {
console.log('yaml 3: ', yaml);
console.log('settings: ', defaultSettings);
const includeTypes = yaml?.includeTypes || defaultSettings.includeTypes || ['folder', 'markdown'];
this.yaml = {
id: yaml?.id ?? crypto.randomUUID(),
@ -55,8 +53,6 @@ export class FolderOverviewSettings extends Modal {
onOpen() {
const { contentEl } = this;
console.log('yaml 6: ', this.yaml);
console.log('settings: ', this.defaultSettings);
this.display(contentEl, this.yaml, this.plugin, this.defaultSettings, this.display, this.el, this.ctx);
}
@ -75,8 +71,6 @@ export class FolderOverviewSettings extends Modal {
contentEl.createEl('h2', { text: 'Default folder overview settings' });
}
console.log('yaml 4: ', yaml);
console.log('settings: ', defaultSettings);
createOverviewSettings(contentEl, yaml, plugin, defaultSettings, display, el, ctx, undefined, undefined, modal);
}

View file

@ -1,4 +1,4 @@
import { MarkdownPostProcessorContext, Plugin, PluginSettingTab, Setting, TFile, TFolder } from 'obsidian';
import { MarkdownPostProcessorContext, Plugin, Plugin$1, PluginSettingTab, Setting, TFile, TFolder } from 'obsidian';
import { updateYaml, updateYamlById, overviewSettings, includeTypes } from './FolderOverview';
import { FolderSuggest } from './suggesters/FolderSuggester';
import { ListComponent } from './utils/ListComponent';
@ -29,8 +29,25 @@ export const DEFAULT_SETTINGS: overviewSettings = {
alwaysCollapse: false,
}
export class SettingsTab extends PluginSettingTab {
plugin: FolderOverviewPlugin;
constructor(plugin: FolderOverviewPlugin) {
super(plugin.app, plugin);
}
display() {
const { containerEl } = this;
this.display = this.display.bind(this);
containerEl.empty();
createOverviewSettings(containerEl, this.plugin.settings, this.plugin, this.plugin.settings, this.display, undefined, undefined, undefined, this);
}
}
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) {
console.log('yaml 5: ', yaml);
new Setting(contentEl)
.setName('Show the title')
.setDesc('Choose if the title should be shown')
@ -242,7 +259,7 @@ export async function createOverviewSettings(contentEl: HTMLElement, yaml: overv
if (yaml.showEmptyFolders) {
new Setting(contentEl)
.setName('Only show first empty subfolders of current folder')
.setName('Only show empty folders which are on the first level of the folder overview')
.addToggle((toggle) => {
toggle
.setValue(yaml.onlyIncludeSubfolders)
@ -282,7 +299,7 @@ export async function createOverviewSettings(contentEl: HTMLElement, yaml: overv
}
async function updateSettings(contentEl: HTMLElement, yaml: overviewSettings, plugin: FolderOverviewPlugin | FolderNotesPlugin, defaultSettings: overviewSettings, el?: HTMLElement, ctx?: MarkdownPostProcessorContext, file?: TFile | null) {
if (defaultSettings) {
if (!yaml.id) {
return plugin.saveSettings();
}

View file

@ -54,7 +54,7 @@ export class ListComponent {
}
span.setAttribute('extension', value);
const removeSpan = span.createEl('span', { cls: 'ofn-list-item-remove setting-hotkey-icon' });
const svg = '<svg xmlns='http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18'></line></svg>';
const svg = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>';
const svgElement = removeSpan.createEl('span', { cls: 'ofn-list-item-remove-icon' });
svgElement.innerHTML = svg;
removeSpan.onClickEvent((e) => {
@ -73,7 +73,7 @@ export class ListComponent {
addResetButton() {
const resetButton = this.controlEl.createEl('span', { cls: 'clickable-icon setting-restore-hotkey-button' });
const svg = '<svg xmlns='http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-rotate-ccw"><path d="M3 2v6h6"></path><path d="M3 13a9 9 0 1 0 3-7.7L3 8'></path></svg>';
const svg = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-rotate-ccw"><path d="M3 2v6h6"></path><path d="M3 13a9 9 0 1 0 3-7.7L3 8"></path></svg>';
resetButton.innerHTML = svg;
resetButton.onClickEvent((e) => {
this.setValues(this.defaultValues);

View file

@ -18,6 +18,11 @@ export class FolderOverviewView extends ItemView {
this.plugin = plugin;
this.display = this.display.bind(this);
if (plugin instanceof FolderOverviewPlugin) {
this.defaultSettings = this.plugin.settings as overviewSettings;
} else {
this.defaultSettings = plugin.settings.defaultOverview;
}
this.registerEvent(
this.plugin.app.workspace.on('file-open', (file) => {
@ -95,9 +100,8 @@ export class FolderOverviewView extends ItemView {
cb.addOption('default', 'Default');
cb.setValue(yaml?.id ?? 'default');
console.log(yaml);
if (cb.getValue() === 'default' || defaultSettings) {
if (cb.getValue() === 'default' || !yaml?.id.trim()) {
yaml = defaultSettings;
cb.setValue('default');
} else {

View file

@ -54,7 +54,7 @@ export class ListComponent {
}
span.setAttribute('extension', value);
const removeSpan = span.createEl('span', { cls: 'ofn-list-item-remove setting-hotkey-icon' });
const svg = '<svg xmlns='http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18'></line></svg>';
const svg = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>';
const svgElement = removeSpan.createEl('span', { cls: 'ofn-list-item-remove-icon' });
svgElement.innerHTML = svg;
removeSpan.onClickEvent((e) => {
@ -73,7 +73,7 @@ export class ListComponent {
addResetButton() {
const resetButton = this.controlEl.createEl('span', { cls: 'clickable-icon setting-restore-hotkey-button' });
const svg = '<svg xmlns='http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-rotate-ccw"><path d="M3 2v6h6"></path><path d="M3 13a9 9 0 1 0 3-7.7L3 8'></path></svg>';
const svg = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-rotate-ccw"><path d="M3 2v6h6"></path><path d="M3 13a9 9 0 1 0 3-7.7L3 8"></path></svg>';
resetButton.innerHTML = svg;
resetButton.onClickEvent((e) => {
this.setValues(this.defaultValues);

View file

@ -27,7 +27,7 @@ export default class AddSupportedFileModal extends Modal {
});
contentEl.createEl('h2', { text: 'Extension name' });
new Setting(contentEl)
.setName('Enter the name of the extension (only the short form, e.g. 'md')')
.setName('Enter the name of the extension (only the short form, e.g. "md")')
.addText((text) =>
text
.setValue('')

View file

@ -23,7 +23,7 @@ export default class DeleteConfirmationModal extends Modal {
modalContent.createEl('p', { text: 'It will be moved to your system trash.' });
break;
case 'obsidianTrash':
modalContent.createEl('p', { text: 'It will be moved to your Obsidian trash, which is located in the '.trash' hidden folder in your vault.' });
modalContent.createEl('p', { text: 'It will be moved to your Obsidian trash, which is located in the ".trash" hidden folder in your vault.' });
break;
case 'delete':
modalContent.createEl('p', { text: 'It will be permanently deleted.' }).setCssStyles({ color: 'red' });

View file

@ -36,7 +36,7 @@ export async function renderGeneral(settingsTab: SettingsTab) {
if (settingsTab.plugin.settings.newFolderNoteName !== '{{folder_name}}') {
new Setting(containerEl)
.setName('Use folder name instead of folder note name in the tab title')
.setDesc('When you\'re using a folder note name like 'folder note' and have multiple folder notes open you can\'t separate them anymore by their name. This setting uses the folder name instead and allows you to indentify the different files.')
.setDesc('When you\'re using a folder note name like "folder note" and have multiple folder notes open you can\'t separate them anymore by their name. This setting uses the folder name instead and allows you to indentify the different files.')
.addToggle((toggle) =>
toggle
.setValue(settingsTab.plugin.settings.tabManagerEnabled)