mirror of
https://github.com/lostpaul/obsidian-folder-notes.git
synced 2026-07-22 07:40:24 +00:00
File explorer clone progress
This commit is contained in:
parent
5e066e8f17
commit
2d34383cd0
10 changed files with 635 additions and 827 deletions
147
src/commands.ts
147
src/commands.ts
|
|
@ -1,4 +1,4 @@
|
|||
import { App, TFolder, Menu, TAbstractFile, Notice, TFile, Editor, MarkdownView, Platform } from 'obsidian';
|
||||
import { App, TFolder, Menu, TAbstractFile, Notice, TFile, Editor, MarkdownView, Platform, stringifyYaml } from 'obsidian';
|
||||
import FolderNotesPlugin from './main';
|
||||
import { getFolderNote, createFolderNote, deleteFolderNote, turnIntoFolderNote, openFolderNote, extractFolderName } from './functions/folderNoteFunctions';
|
||||
import { ExcludedFolder } from './excludedFolder';
|
||||
|
|
@ -10,65 +10,25 @@ export class Commands {
|
|||
this.app = app;
|
||||
}
|
||||
registerCommands() {
|
||||
this.plugin.registerEvent(this.plugin.app.workspace.on('editor-menu', (menu: Menu, editor: Editor, view: MarkdownView) => {
|
||||
const text = editor.getSelection().trim();
|
||||
menu.addItem((item) => {
|
||||
item.setTitle('Create folder overview')
|
||||
.setIcon('edit')
|
||||
.onClick(() => {
|
||||
editor.replaceSelection('```folder-overview\n```\n');
|
||||
});
|
||||
});
|
||||
if (!text || text.trim() === '') return;
|
||||
menu.addItem((item) => {
|
||||
item.setTitle('Create folder note')
|
||||
.setIcon('edit')
|
||||
.onClick(() => {
|
||||
const file = view.file;
|
||||
if (!(file instanceof TFile)) return;
|
||||
const blacklist = ['*', '\\', '"', '/', '<', '>', '?', '|', ':'];
|
||||
for (const char of blacklist) {
|
||||
if (text.includes(char)) {
|
||||
new Notice('File name cannot contain any of the following characters: * " \\ / < > : | ?');
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (text.endsWith('.')) {
|
||||
new Notice('File name cannot end with a dot');
|
||||
return;
|
||||
}
|
||||
let folder: TAbstractFile | null;
|
||||
const folderPath = this.plugin.getFolderPathFromString(file.path);
|
||||
if (folderPath === '') {
|
||||
folder = this.plugin.app.vault.getAbstractFileByPath(text);
|
||||
if (folder instanceof TFolder) {
|
||||
return new Notice('Folder note already exists');
|
||||
} else {
|
||||
this.plugin.app.vault.createFolder(text);
|
||||
createFolderNote(this.plugin, text, false);
|
||||
}
|
||||
} else {
|
||||
folder = this.plugin.app.vault.getAbstractFileByPath(folderPath + '/' + text);
|
||||
if (folder instanceof TFolder) {
|
||||
return new Notice('Folder note already exists');
|
||||
}
|
||||
if (this.plugin.settings.storageLocation === 'parentFolder') {
|
||||
if (this.app.vault.getAbstractFileByPath(folderPath + '/' + text + this.plugin.settings.folderNoteType)) {
|
||||
return new Notice('File already exists');
|
||||
}
|
||||
}
|
||||
this.plugin.app.vault.createFolder(folderPath + '/' + text);
|
||||
createFolderNote(this.plugin, folderPath + '/' + text, false);
|
||||
}
|
||||
const fileName = this.plugin.settings.folderNoteName.replace('{{folder_name}}', text);
|
||||
if (fileName === text) {
|
||||
editor.replaceSelection(`[[${fileName}]]`);
|
||||
} else {
|
||||
editor.replaceSelection(`[[${fileName}|${text}]]`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}));
|
||||
this.editorCommands();
|
||||
this.fileCommands();
|
||||
this.regularCommands();
|
||||
}
|
||||
regularCommands() {
|
||||
this.plugin.addCommand({
|
||||
id: 'turn-into-folder-note',
|
||||
name: 'Make current active note a folder note',
|
||||
callback: () => {
|
||||
const file = this.app.workspace.getActiveFile();
|
||||
if (!(file instanceof TFile)) return;
|
||||
const folder = file.parent;
|
||||
if (!(folder instanceof TFolder)) return;
|
||||
const folderNote = getFolderNote(this.plugin, folder.path);
|
||||
turnIntoFolderNote(this.plugin, file, folder, folderNote);
|
||||
}
|
||||
});
|
||||
}
|
||||
fileCommands() {
|
||||
this.plugin.registerEvent(this.app.workspace.on('file-menu', (menu: Menu, file: TAbstractFile) => {
|
||||
let folder: TAbstractFile | TFolder | null = file.parent;
|
||||
if (file instanceof TFile) {
|
||||
|
|
@ -190,4 +150,71 @@ export class Commands {
|
|||
});
|
||||
}));
|
||||
}
|
||||
editorCommands() {
|
||||
this.plugin.registerEvent(this.plugin.app.workspace.on('editor-menu', (menu: Menu, editor: Editor, view: MarkdownView) => {
|
||||
const text = editor.getSelection().trim();
|
||||
const fileContent = editor.getValue().trim();
|
||||
const line = editor.getCursor().line;
|
||||
const lineText = editor.getLine(line);
|
||||
if (!fileContent.includes('```folder-overview') && lineText.trim() == '') {
|
||||
menu.addItem((item) => {
|
||||
item.setTitle('Create folder overview')
|
||||
.setIcon('edit')
|
||||
.onClick(() => {
|
||||
const yaml = stringifyYaml(this.plugin.settings.defaultOverview)
|
||||
editor.replaceSelection(`\`\`\`folder-overview\n${yaml}\`\`\`\n`);
|
||||
});
|
||||
});
|
||||
}
|
||||
if (!text || text.trim() === '') return;
|
||||
menu.addItem((item) => {
|
||||
item.setTitle('Create folder note')
|
||||
.setIcon('edit')
|
||||
.onClick(() => {
|
||||
const file = view.file;
|
||||
if (!(file instanceof TFile)) return;
|
||||
const blacklist = ['*', '\\', '"', '/', '<', '>', '?', '|', ':'];
|
||||
for (const char of blacklist) {
|
||||
if (text.includes(char)) {
|
||||
new Notice('File name cannot contain any of the following characters: * " \\ / < > : | ?');
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (text.endsWith('.')) {
|
||||
new Notice('File name cannot end with a dot');
|
||||
return;
|
||||
}
|
||||
let folder: TAbstractFile | null;
|
||||
const folderPath = this.plugin.getFolderPathFromString(file.path);
|
||||
if (folderPath === '') {
|
||||
folder = this.plugin.app.vault.getAbstractFileByPath(text);
|
||||
if (folder instanceof TFolder) {
|
||||
return new Notice('Folder note already exists');
|
||||
} else {
|
||||
this.plugin.app.vault.createFolder(text);
|
||||
createFolderNote(this.plugin, text, false);
|
||||
}
|
||||
} else {
|
||||
folder = this.plugin.app.vault.getAbstractFileByPath(folderPath + '/' + text);
|
||||
if (folder instanceof TFolder) {
|
||||
return new Notice('Folder note already exists');
|
||||
}
|
||||
if (this.plugin.settings.storageLocation === 'parentFolder') {
|
||||
if (this.app.vault.getAbstractFileByPath(folderPath + '/' + text + this.plugin.settings.folderNoteType)) {
|
||||
return new Notice('File already exists');
|
||||
}
|
||||
}
|
||||
this.plugin.app.vault.createFolder(folderPath + '/' + text);
|
||||
createFolderNote(this.plugin, folderPath + '/' + text, false);
|
||||
}
|
||||
const fileName = this.plugin.settings.folderNoteName.replace('{{folder_name}}', text);
|
||||
if (fileName === text) {
|
||||
editor.replaceSelection(`[[${fileName}]]`);
|
||||
} else {
|
||||
editor.replaceSelection(`[[${fileName}|${text}]]`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,380 +0,0 @@
|
|||
import { MarkdownPostProcessorContext, parseYaml, TAbstractFile, TFolder, TFile } from 'obsidian';
|
||||
import { extractFolderName, getFolderNote } from './functions/folderNoteFunctions';
|
||||
import FolderNotesPlugin from './main';
|
||||
import { FolderOverviewSettings } from './modals/folderOverview';
|
||||
import { getExcludedFolder } from './excludedFolder';
|
||||
export type yamlSettings = {
|
||||
title?: string;
|
||||
disableTitle?: boolean;
|
||||
depth?: number;
|
||||
type?: 'folder' | 'markdown' | 'canvas' | 'other' | 'pdf' | 'images' | 'audio' | 'video' | 'all';
|
||||
includeTypes?: string[];
|
||||
style?: 'list' | 'grid';
|
||||
disableFileTag?: boolean;
|
||||
sortBy?: 'name' | 'created' | 'modified' | 'nameAsc' | 'createdAsc' | 'modifiedAsc';
|
||||
showEmptyFolders?: boolean;
|
||||
onlyIncludeSubfolders?: boolean;
|
||||
};
|
||||
|
||||
export function createCanvasOverview() {
|
||||
// console.log('test');
|
||||
}
|
||||
|
||||
export function createOverview(plugin: FolderNotesPlugin, source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) {
|
||||
// parse source to yaml
|
||||
let yaml: yamlSettings = parseYaml(source);
|
||||
if (!yaml) {
|
||||
yaml = {
|
||||
title: plugin.settings.defaultOverview.title,
|
||||
disableTitle: plugin.settings.defaultOverview.disableTitle,
|
||||
depth: plugin.settings.defaultOverview.depth,
|
||||
type: plugin.settings.defaultOverview.type,
|
||||
includeTypes: plugin.settings.defaultOverview.includeTypes,
|
||||
style: plugin.settings.defaultOverview.style,
|
||||
disableFileTag: plugin.settings.defaultOverview.disableFileTag,
|
||||
sortBy: plugin.settings.defaultOverview.sortBy,
|
||||
showEmptyFolders: plugin.settings.defaultOverview.showEmptyFolders,
|
||||
onlyIncludeSubfolders: plugin.settings.defaultOverview.onlyIncludeSubfolders,
|
||||
};
|
||||
}
|
||||
const depth = yaml?.depth || 1;
|
||||
let title = yaml?.title || plugin.settings.defaultOverview.title || '{{folderName}} overview';
|
||||
const folderName = extractFolderName(plugin.settings.folderNoteName, plugin.removeExtension(plugin.getFolderNameFromPathString(ctx.sourcePath)));
|
||||
title = title.replaceAll('{{folderName}}', folderName || '');
|
||||
const disableTitle = yaml?.disableTitle || plugin.settings.defaultOverview.disableTitle || false;
|
||||
let includeTypes: string[] = yaml?.includeTypes || plugin.settings.defaultOverview.includeTypes || ['folder', 'markdown'];
|
||||
includeTypes = includeTypes.map((type) => type.toLowerCase());
|
||||
const style: 'list' | 'grid' = yaml?.style || 'list';
|
||||
const disableFileTag = yaml?.disableFileTag || plugin.settings.defaultOverview.disableFileTag || false;
|
||||
const showEmptyFolders = yaml?.showEmptyFolders || plugin.settings.defaultOverview.showEmptyFolders || false;
|
||||
const pathBlacklist: string[] = [];
|
||||
|
||||
|
||||
el.parentElement?.classList.add('folder-overview-container');
|
||||
const root = el.createEl('div', { cls: 'folder-overview' });
|
||||
const titleEl = root.createEl('h1', { cls: 'folder-overview-title' });
|
||||
const ul = root.createEl('ul', { cls: 'folder-overview-list' });
|
||||
if (!disableTitle) {
|
||||
titleEl.innerText = title;
|
||||
}
|
||||
if (includeTypes.length === 0) { return; }
|
||||
let files: TAbstractFile[] = [];
|
||||
const sourceFile = plugin.app.vault.getAbstractFileByPath(ctx.sourcePath);
|
||||
if (!sourceFile) return;
|
||||
const sourceFolderPath = plugin.getFolderPathFromString(ctx.sourcePath);
|
||||
let sourceFolder: TFolder | undefined;
|
||||
if (sourceFile.parent instanceof TFolder) {
|
||||
sourceFolder = sourceFile.parent;
|
||||
} else {
|
||||
sourceFolder = plugin.app.vault.getAbstractFileByPath(plugin.getFolderPathFromString(ctx.sourcePath)) as TFolder;
|
||||
}
|
||||
|
||||
files = sourceFolder.children;
|
||||
|
||||
files = files.filter((file) => {
|
||||
const folderPath = plugin.getFolderPathFromString(file.path);
|
||||
if (!folderPath.startsWith(sourceFolderPath)) { return false; }
|
||||
const excludedFolder = getExcludedFolder(plugin, file.path);
|
||||
if (excludedFolder?.excludeFromFolderOverview) { return false; }
|
||||
if (file.path === ctx.sourcePath) { return false; }
|
||||
if ((file.path.split('/').length - sourceFolderPath.split('/').length) - 1 < depth) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if (!includeTypes.includes('folder')) {
|
||||
files = getAllFiles(files, sourceFolderPath, depth);
|
||||
}
|
||||
if (files.length === 0) {
|
||||
// create button to edit the overview
|
||||
const editButton = root.createEl('button', { cls: 'folder-overview-edit-button' });
|
||||
editButton.innerText = 'Edit overview';
|
||||
editButton.addEventListener('click', (e) => {
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
new FolderOverviewSettings(plugin.app, plugin, parseYaml(source), ctx, el).open();
|
||||
}, { capture: true });
|
||||
}
|
||||
files = sortFiles(files, yaml, plugin);
|
||||
|
||||
if (style === 'grid') {
|
||||
const grid = root.createEl('div', { cls: 'folder-overview-grid' });
|
||||
files.forEach(async (file) => {
|
||||
const gridItem = grid.createEl('div', { cls: 'folder-overview-grid-item' });
|
||||
const gridArticle = gridItem.createEl('article', { cls: 'folder-overview-grid-item-article' });
|
||||
if (file instanceof TFile) {
|
||||
const fileContent = await plugin.app.vault.read(file);
|
||||
// skip --- yaml
|
||||
const descriptionEl = gridArticle.createEl('p', { cls: 'folder-overview-grid-item-description' });
|
||||
let description = fileContent.split('\n')[0];
|
||||
if (description.length > 64) {
|
||||
description = description.slice(0, 64) + '...';
|
||||
}
|
||||
descriptionEl.innerText = description;
|
||||
const link = gridArticle.createEl('a', { cls: 'folder-overview-grid-item-link internal-link' });
|
||||
const title = link.createEl('h1', { cls: 'folder-overview-grid-item-link-title' });
|
||||
title.innerText = file.name.replace('.md', '').replace('.canvas', '');
|
||||
link.href = file.path;
|
||||
} else if (file instanceof TFolder) {
|
||||
const folderItem = gridArticle.createEl('div', { cls: 'folder-overview-grid-item-folder' });
|
||||
const folderName = folderItem.createEl('h1', { cls: 'folder-overview-grid-item-folder-name' });
|
||||
folderName.innerText = file.name;
|
||||
}
|
||||
});
|
||||
} else if (style === 'list') {
|
||||
files.forEach((file) => {
|
||||
if (file instanceof TFolder) {
|
||||
const folderItem = addFolderList(plugin, ul, pathBlacklist, file);
|
||||
goThroughFolders(plugin, folderItem, file, depth, sourceFolderPath, ctx, yaml, pathBlacklist, includeTypes, disableFileTag);
|
||||
} else if (file instanceof TFile) {
|
||||
addFileList(plugin, ul, pathBlacklist, file, includeTypes, disableFileTag);
|
||||
}
|
||||
});
|
||||
} else if (style === 'explorer') {
|
||||
} else if (style === 'testing') {
|
||||
cloneFileExplorerView(plugin, ctx, root, yaml, pathBlacklist);
|
||||
}
|
||||
if (includeTypes.length > 1 && style !== 'grid' && (!showEmptyFolders || yaml.onlyIncludeSubfolders)) {
|
||||
removeEmptyFolders(ul, 1, yaml);
|
||||
}
|
||||
}
|
||||
function cloneFileExplorerView(plugin: FolderNotesPlugin, ctx: MarkdownPostProcessorContext, root: HTMLElement, yaml: yamlSettings, pathBlacklist: string[]) {
|
||||
const folder = plugin.getEL(plugin.getFolderPathFromString(ctx.sourcePath))
|
||||
const folderElement = folder?.parentElement;
|
||||
console.log(folderElement)
|
||||
if (!folderElement) return;
|
||||
const newFolderElement = folderElement.cloneNode(true) as HTMLElement;
|
||||
newFolderElement.querySelectorAll('div.tree-item-icon').forEach((el) => {
|
||||
if (el instanceof HTMLElement) {
|
||||
el.onclick = () => {
|
||||
handleCollapseClick(el, plugin, yaml, pathBlacklist);
|
||||
}
|
||||
}
|
||||
});
|
||||
root.appendChild(newFolderElement);
|
||||
}
|
||||
function handleCollapseClick(el: HTMLElement, plugin: FolderNotesPlugin, yaml: yamlSettings, pathBlacklist: string[]) {
|
||||
el.classList.toggle('is-collapsed');
|
||||
if (el.classList.contains('is-collapsed')) {
|
||||
console.log(el.parentElement?.parentElement);
|
||||
console.log(el.parentElement?.parentElement?.childNodes[1])
|
||||
console.log(el.parentElement?.parentElement?.querySelector('tree-item-children'))
|
||||
el.parentElement?.parentElement?.childNodes[1]?.remove();
|
||||
} else {
|
||||
console.log(el.parentElement)
|
||||
const path = el.parentElement?.getAttribute('data-path');
|
||||
if (!path) return;
|
||||
const folder = plugin.app.vault.getAbstractFileByPath(path);
|
||||
if (!(folder instanceof TFolder)) return;
|
||||
const folderElement = el.parentElement?.parentElement;
|
||||
if (!folderElement) return;
|
||||
console.log('folderElement', folderElement);
|
||||
const childrenElement = folderElement.createDiv({ cls: 'tree-item-children nav-folder-children' });
|
||||
let files = sortFiles(folder.children, {}, plugin);
|
||||
files = filterFiles(files, plugin, path, yaml.depth || 1, pathBlacklist);
|
||||
files.forEach((child) => {
|
||||
if (child instanceof TFolder) {
|
||||
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 folderElement = childrenElement.createDiv({
|
||||
cls: 'tree-item nav-folder',
|
||||
});
|
||||
const folderTitle = folderElement.createDiv({
|
||||
cls: 'tree-item-self is-clickable nav-folder-title',
|
||||
attr: {
|
||||
'data-path': child.path,
|
||||
'draggable': 'true'
|
||||
},
|
||||
})
|
||||
const collapseIcon = folderTitle.createDiv({
|
||||
cls: 'tree-item-icon collapse-icon nav-folder-collapse-indicator',
|
||||
});
|
||||
collapseIcon.innerHTML = svg;
|
||||
collapseIcon.onclick = () => {
|
||||
handleCollapseClick(collapseIcon, plugin, yaml, pathBlacklist);
|
||||
}
|
||||
folderTitle.createDiv({
|
||||
cls: 'tree-item-inner nav-folder-title-content',
|
||||
text: child.name,
|
||||
});
|
||||
} else if (child instanceof TFile) {
|
||||
const fileElement = childrenElement.createDiv({
|
||||
cls: 'tree-item nav-file',
|
||||
});
|
||||
const fileTitle = fileElement.createDiv({
|
||||
cls: 'tree-item-self is-clickable nav-file-title',
|
||||
attr: {
|
||||
'data-path': child.path,
|
||||
'draggable': 'true'
|
||||
},
|
||||
})
|
||||
fileTitle.createDiv({
|
||||
cls: 'tree-item-inner nav-file-title-content',
|
||||
text: child.basename,
|
||||
});
|
||||
if (child.extension !== 'md') {
|
||||
fileTitle.createDiv({
|
||||
cls: 'nav-file-tag',
|
||||
text: child.extension
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
// move folders above files
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function goThroughFolders(plugin: FolderNotesPlugin, list: HTMLLIElement | HTMLUListElement, folder: TFolder,
|
||||
depth: number, sourceFolderPath: string, ctx: MarkdownPostProcessorContext, yaml: yamlSettings, pathBlacklist: string[], includeTypes: string[], disableFileTag: boolean) {
|
||||
if (sourceFolderPath === '') {
|
||||
depth--;
|
||||
}
|
||||
let files = filterFiles(folder.children, plugin, sourceFolderPath, depth, pathBlacklist);
|
||||
files = sortFiles(files, yaml, plugin);
|
||||
const ul = list.createEl('ul', { cls: 'folder-overview-list' });
|
||||
files.forEach((file) => {
|
||||
if (file instanceof TFolder) {
|
||||
const folderItem = addFolderList(plugin, ul, pathBlacklist, file);
|
||||
goThroughFolders(plugin, folderItem, file, depth, sourceFolderPath, ctx, yaml, pathBlacklist, includeTypes, disableFileTag);
|
||||
} else if (file instanceof TFile) {
|
||||
addFileList(plugin, ul, pathBlacklist, file, includeTypes, disableFileTag);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function filterFiles(files: TAbstractFile[], plugin: FolderNotesPlugin, sourceFolderPath: string, depth: number, pathBlacklist: string[]) {
|
||||
return files.filter((file) => {
|
||||
console.log('pathBlacklist', pathBlacklist);
|
||||
if (pathBlacklist.includes(file.path)) { return false; }
|
||||
const folderPath = plugin.getFolderPathFromString(file.path);
|
||||
if (!folderPath.startsWith(sourceFolderPath)) { return false; }
|
||||
const excludedFolder = getExcludedFolder(plugin, file.path);
|
||||
if (excludedFolder?.excludeFromFolderOverview) { return false; }
|
||||
if ((file.path.split('/').length - sourceFolderPath.split('/').length) - 1 < depth) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function sortFiles(files: TAbstractFile[], yaml: yamlSettings, plugin: FolderNotesPlugin) {
|
||||
if (!yaml?.sortBy) {
|
||||
yaml.sortBy = plugin.settings.defaultOverview.sortBy || 'name';
|
||||
}
|
||||
return files.sort((a, b) => {
|
||||
// sort by folder first
|
||||
if (a instanceof TFolder && !(b instanceof TFolder)) {
|
||||
return -1;
|
||||
}
|
||||
if (!(a instanceof TFolder) && b instanceof TFolder) {
|
||||
return 1;
|
||||
}
|
||||
if (!(a instanceof TFile) || !(b instanceof TFile)) { return -1; }
|
||||
if (yaml.sortBy === 'created') {
|
||||
if (a.stat.ctime > b.stat.ctime) {
|
||||
return -1;
|
||||
} else if (a.stat.ctime < b.stat.ctime) {
|
||||
return 1;
|
||||
}
|
||||
} else if (yaml.sortBy === 'createdAsc') {
|
||||
if (a.stat.ctime < b.stat.ctime) {
|
||||
return -1;
|
||||
} else if (a.stat.ctime > b.stat.ctime) {
|
||||
return 1;
|
||||
}
|
||||
} else if (yaml.sortBy === 'modified') {
|
||||
if (a.stat.mtime > b.stat.mtime) {
|
||||
return -1;
|
||||
} else if (a.stat.mtime < b.stat.mtime) {
|
||||
return 1;
|
||||
}
|
||||
} else if (yaml.sortBy === 'modifiedAsc') {
|
||||
if (a.stat.mtime < b.stat.mtime) {
|
||||
return -1;
|
||||
} else if (a.stat.mtime > b.stat.mtime) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
// sort by name
|
||||
if (a.name < b.name && yaml.sortBy === 'name') {
|
||||
return -1;
|
||||
} else if (a.name > b.name && yaml.sortBy === 'nameAsc') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a.name > b.name && yaml.sortBy === 'name') {
|
||||
return 1;
|
||||
} else if (a.name < b.name && yaml.sortBy === 'nameAsc') {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
function removeEmptyFolders(ul: HTMLUListElement | HTMLLIElement, depth: number, yaml: yamlSettings) {
|
||||
const childrensToRemove: ChildNode[] = [];
|
||||
ul.childNodes.forEach((el) => {
|
||||
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) {
|
||||
removeEmptyFolders(el, depth + 1, yaml);
|
||||
}
|
||||
});
|
||||
childrensToRemove.forEach((el) => {
|
||||
if (yaml.onlyIncludeSubfolders && depth === 1) { return; }
|
||||
el.remove();
|
||||
});
|
||||
}
|
||||
|
||||
function addFolderList(plugin: FolderNotesPlugin, list: HTMLUListElement | HTMLLIElement, pathBlacklist: string[], folder: TFolder) {
|
||||
const folderItem = list.createEl('li', { cls: 'folder-overview-list folder-list' });
|
||||
const folderNote = getFolderNote(plugin, folder.path);
|
||||
if (folderNote instanceof TFile) {
|
||||
const folderNoteLink = folderItem.createEl('a', { cls: 'folder-overview-list-item folder-name-item internal-link', href: folderNote.path });
|
||||
folderNoteLink.innerText = folder.name;
|
||||
pathBlacklist.push(folderNote.path);
|
||||
} else {
|
||||
const folderName = folderItem.createEl('span', { cls: 'folder-overview-list-item folder-name-item' });
|
||||
folderName.innerText = folder.name;
|
||||
}
|
||||
return folderItem;
|
||||
}
|
||||
|
||||
function addFileList(plugin: FolderNotesPlugin, list: HTMLUListElement | HTMLLIElement, pathBlacklist: string[], file: TFile, includeTypes: string[], disableFileTag: boolean) {
|
||||
if (includeTypes.length > 0 && !includeTypes.includes('all')) {
|
||||
if (file.extension === 'md' && !includeTypes.includes('markdown')) return;
|
||||
if (file.extension === 'canvas' && !includeTypes.includes('canvas')) return;
|
||||
if (file.extension === 'pdf' && !includeTypes.includes('pdf')) return;
|
||||
const imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp'];
|
||||
if (imageTypes.includes(file.extension) && !includeTypes.includes('image')) return;
|
||||
const videoTypes = ['mp4', 'webm', 'ogv', 'mov', 'mkv'];
|
||||
if (videoTypes.includes(file.extension) && !includeTypes.includes('video')) return;
|
||||
const audioTypes = ['mp3', 'wav', 'm4a', '3gp', 'flac', 'ogg', 'oga', 'opus'];
|
||||
if (audioTypes.includes(file.extension) && includeTypes.includes('audio')) return;
|
||||
const allTypes = ['md', 'canvas', 'pdf', ...imageTypes, ...videoTypes, ...audioTypes];
|
||||
if (!allTypes.includes(file.extension) && !includeTypes.includes('other')) return;
|
||||
}
|
||||
if (pathBlacklist.includes(file.path)) return;
|
||||
const listItem = list.createEl('li', { cls: 'folder-overview-list file-link' });
|
||||
const nameItem = listItem.createEl('div', { cls: 'folder-overview-list-item' });
|
||||
const link = nameItem.createEl('a', { cls: 'internal-link', href: file.path });
|
||||
link.innerText = file.basename;
|
||||
if (file.extension !== 'md' && !disableFileTag) {
|
||||
nameItem.createDiv({ cls: 'nav-file-tag' }).innerText = file.extension;
|
||||
}
|
||||
}
|
||||
|
||||
function getAllFiles(files: TAbstractFile[], sourceFolderPath: string, depth: number) {
|
||||
const allFiles: TAbstractFile[] = [];
|
||||
files.forEach((file) => {
|
||||
if (file instanceof TFolder) {
|
||||
if ((file.path.split('/').length - sourceFolderPath.split('/').length) - 1 < depth - 1) {
|
||||
allFiles.push(...getAllFiles(file.children, sourceFolderPath, depth));
|
||||
}
|
||||
} else {
|
||||
allFiles.push(file);
|
||||
}
|
||||
});
|
||||
return allFiles;
|
||||
}
|
||||
|
|
@ -1,19 +1,22 @@
|
|||
import { MarkdownPostProcessorContext, parseYaml, TAbstractFile, TFolder, TFile } from 'obsidian';
|
||||
import { extractFolderName, getFolderNote } from './functions/folderNoteFunctions';
|
||||
import FolderNotesPlugin from './main';
|
||||
import { FolderOverviewSettings } from './modals/folderOverview';
|
||||
import { getExcludedFolder } from './excludedFolder';
|
||||
import { MarkdownPostProcessorContext, parseYaml, TAbstractFile, TFolder, TFile, stringifyYaml, Notice } from 'obsidian';
|
||||
import { extractFolderName, getFolderNote } from '../functions/folderNoteFunctions';
|
||||
import FolderNotesPlugin from '../main';
|
||||
import { FolderOverviewSettings } from './modalSettings';
|
||||
import { getExcludedFolder } from '../excludedFolder';
|
||||
export type includeTypes = 'folder' | 'markdown' | 'canvas' | 'other' | 'pdf' | 'images' | 'audio' | 'video' | 'all';
|
||||
export type yamlSettings = {
|
||||
folderPath: string;
|
||||
title: string;
|
||||
disableTitle: boolean;
|
||||
depth: number;
|
||||
type: 'folder' | 'markdown' | 'canvas' | 'other' | 'pdf' | 'images' | 'audio' | 'video' | 'all';
|
||||
includeTypes: string[];
|
||||
style: 'list' | 'grid';
|
||||
includeTypes: includeTypes[];
|
||||
style: 'list' | 'grid' | 'explorer';
|
||||
disableFileTag: boolean;
|
||||
sortBy: 'name' | 'created' | 'modified' | 'nameAsc' | 'createdAsc' | 'modifiedAsc';
|
||||
sortBy: 'name' | 'created' | 'modified';
|
||||
sortByAsc: boolean;
|
||||
showEmptyFolders: boolean;
|
||||
onlyIncludeSubfolders: boolean;
|
||||
storeFolderCondition: boolean;
|
||||
};
|
||||
export class FolderOverview {
|
||||
yaml: yamlSettings;
|
||||
|
|
@ -26,68 +29,77 @@ export class FolderOverview {
|
|||
folders: TFolder[] = [];
|
||||
constructor(plugin: FolderNotesPlugin, ctx: MarkdownPostProcessorContext, source: string, el: HTMLElement) {
|
||||
let yaml: yamlSettings = parseYaml(source);
|
||||
if (!yaml) { yaml = {} as yamlSettings; }
|
||||
const includeTypes = yaml?.includeTypes || plugin.settings.defaultOverview.includeTypes || ['folder', 'markdown'];
|
||||
this.plugin = plugin;
|
||||
this.ctx = ctx;
|
||||
this.source = source;
|
||||
this.el = el;
|
||||
this.yaml = {
|
||||
title: yaml?.title || plugin.settings.defaultOverview.title || '{{folderName}} overview',
|
||||
disableTitle: yaml?.disableTitle || plugin.settings.defaultOverview.disableTitle || false,
|
||||
depth: yaml?.depth || plugin.settings.defaultOverview.depth || 1,
|
||||
folderPath: yaml?.folderPath || plugin.getFolderPathFromString(ctx.sourcePath),
|
||||
title: yaml?.title || plugin.settings.defaultOverview.title,
|
||||
disableTitle: yaml?.disableTitle || plugin.settings.defaultOverview.disableTitle,
|
||||
depth: yaml?.depth || plugin.settings.defaultOverview.depth,
|
||||
style: yaml?.style || 'list',
|
||||
includeTypes: includeTypes.map((type) => type.toLowerCase()),
|
||||
disableFileTag: yaml?.disableFileTag || plugin.settings.defaultOverview.disableFileTag || false,
|
||||
sortBy: yaml?.sortBy || plugin.settings.defaultOverview.sortBy || 'name',
|
||||
showEmptyFolders: yaml?.showEmptyFolders || plugin.settings.defaultOverview.showEmptyFolders || false,
|
||||
onlyIncludeSubfolders: yaml?.onlyIncludeSubfolders || plugin.settings.defaultOverview.onlyIncludeSubfolders || false,
|
||||
type: yaml?.type || plugin.settings.defaultOverview.type || 'folder',
|
||||
includeTypes: includeTypes.map((type) => type.toLowerCase()) as includeTypes[],
|
||||
disableFileTag: yaml?.disableFileTag || plugin.settings.defaultOverview.disableFileTag,
|
||||
sortBy: yaml?.sortBy || plugin.settings.defaultOverview.sortBy,
|
||||
sortByAsc: yaml?.sortByAsc || plugin.settings.defaultOverview.sortByAsc,
|
||||
showEmptyFolders: yaml?.showEmptyFolders || plugin.settings.defaultOverview.showEmptyFolders,
|
||||
onlyIncludeSubfolders: yaml?.onlyIncludeSubfolders || plugin.settings.defaultOverview.onlyIncludeSubfolders,
|
||||
storeFolderCondition: yaml?.storeFolderCondition || plugin.settings.defaultOverview.storeFolderCondition
|
||||
}
|
||||
}
|
||||
create(plugin: FolderNotesPlugin, source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) {
|
||||
el.empty();
|
||||
el.parentElement?.classList.add('folder-overview-container');
|
||||
const root = el.createEl('div', { cls: 'folder-overview' });
|
||||
const titleEl = root.createEl('h1', { cls: 'folder-overview-title' });
|
||||
const ul = root.createEl('ul', { cls: 'folder-overview-list' });
|
||||
if (!this.yaml.disableTitle) {
|
||||
titleEl.innerText = this.yaml.title || '';
|
||||
}
|
||||
if (this.yaml.includeTypes.length === 0) { return; }
|
||||
let files: TAbstractFile[] = [];
|
||||
const sourceFile = plugin.app.vault.getAbstractFileByPath(ctx.sourcePath);
|
||||
if (!sourceFile) return;
|
||||
const sourceFolderPath = plugin.getFolderPathFromString(ctx.sourcePath);
|
||||
const sourceFolderPath = this.yaml.folderPath || plugin.getFolderPathFromString(ctx.sourcePath);
|
||||
let sourceFolder: TFolder | undefined;
|
||||
if (sourceFile.parent instanceof TFolder) {
|
||||
sourceFolder = sourceFile.parent;
|
||||
} else {
|
||||
sourceFolder = plugin.app.vault.getAbstractFileByPath(plugin.getFolderPathFromString(ctx.sourcePath)) as TFolder;
|
||||
if (sourceFolderPath !== '') {
|
||||
sourceFolder = plugin.app.vault.getAbstractFileByPath(this.yaml.folderPath) as TFolder;
|
||||
}
|
||||
|
||||
files = sourceFolder.children;
|
||||
|
||||
files = files.filter((file) => {
|
||||
const folderPath = plugin.getFolderPathFromString(file.path);
|
||||
if (!folderPath.startsWith(sourceFolderPath)) { return false; }
|
||||
const excludedFolder = getExcludedFolder(plugin, file.path);
|
||||
if (excludedFolder?.excludeFromFolderOverview) { return false; }
|
||||
if (file.path === ctx.sourcePath) { return false; }
|
||||
if ((file.path.split('/').length - sourceFolderPath.split('/').length) - 1 < this.yaml.depth) {
|
||||
return true;
|
||||
if (!this.yaml.disableTitle) {
|
||||
if (sourceFolder && sourceFolderPath !== '') {
|
||||
titleEl.innerText = this.yaml.title.replace('{{folderName}}', sourceFolder.name);
|
||||
} else if (sourceFolderPath == '' ) {
|
||||
titleEl.innerText = this.yaml.title.replace('{{folderName}}', 'Vault');
|
||||
} else {
|
||||
titleEl.innerText = this.yaml.title.replace('{{folderName}}', '');
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!sourceFolder && sourceFolderPath !== '') { return new Notice('Couldn\'t find the folder'); }
|
||||
if (sourceFolderPath == '') {
|
||||
const rootFolders: TAbstractFile[] = [];
|
||||
plugin.app.vault.getAllLoadedFiles().filter(f => f instanceof TFolder).forEach((file) => {
|
||||
if (file instanceof TFolder && !file.path.includes('/')) {
|
||||
rootFolders.push(file);
|
||||
}
|
||||
});
|
||||
files = rootFolders;
|
||||
} else if (sourceFolder) {
|
||||
files = sourceFolder.children;
|
||||
}
|
||||
console.log(files);
|
||||
files = this.filterFiles(files, plugin, sourceFolderPath, this.yaml.depth, this.pathBlacklist);
|
||||
if (!this.yaml.includeTypes.includes('folder')) {
|
||||
files = this.getAllFiles(files, sourceFolderPath, this.yaml.depth);
|
||||
}
|
||||
if (files.length === 0) {
|
||||
// create button to edit the overview
|
||||
const editButton = root.createEl('button', { cls: 'folder-overview-edit-button' });
|
||||
editButton.innerText = 'Edit overview';
|
||||
editButton.addEventListener('click', (e) => {
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
new FolderOverviewSettings(plugin.app, plugin, parseYaml(source), ctx, el).open();
|
||||
console.log(this)
|
||||
new FolderOverviewSettings(plugin.app, plugin, this.yaml, ctx, el).open();
|
||||
}, { capture: true });
|
||||
}
|
||||
files = this.sortFiles(files, this.yaml, plugin);
|
||||
|
|
@ -126,28 +138,42 @@ export class FolderOverview {
|
|||
}
|
||||
});
|
||||
} else if (this.yaml.style === 'explorer') {
|
||||
} else if (this.yaml.style === 'testing') {
|
||||
console.log('testing');
|
||||
this.cloneFileExplorerView(plugin, ctx, root, this.yaml, this.pathBlacklist);
|
||||
if (this.plugin.app.workspace.layoutReady) {
|
||||
this.cloneFileExplorerView(plugin, ctx, root, this.yaml, this.pathBlacklist);
|
||||
} else {
|
||||
this.plugin.app.workspace.onLayoutReady(() => {
|
||||
this.cloneFileExplorerView(plugin, ctx, root, this.yaml, this.pathBlacklist);
|
||||
});
|
||||
}
|
||||
}
|
||||
if (this.yaml.includeTypes.length > 1 && this.yaml.style !== 'grid' && (!this.yaml.showEmptyFolders || this.yaml.onlyIncludeSubfolders)) {
|
||||
if (this.yaml.style !== 'list') { return; }
|
||||
if (this.yaml.includeTypes.length > 1 && (!this.yaml.showEmptyFolders || this.yaml.onlyIncludeSubfolders)) {
|
||||
this.removeEmptyFolders(ul, 1, this.yaml);
|
||||
}
|
||||
}
|
||||
cloneFileExplorerView(plugin: FolderNotesPlugin, ctx: MarkdownPostProcessorContext, root: HTMLElement, yaml: yamlSettings, pathBlacklist: string[]) {
|
||||
const folder = plugin.getEL(plugin.getFolderPathFromString(ctx.sourcePath))
|
||||
const folderElement = folder?.parentElement;
|
||||
const tFolder = plugin.app.vault.getAbstractFileByPath(plugin.getFolderPathFromString(ctx.sourcePath));
|
||||
console.log(folderElement)
|
||||
const folder = plugin.getEL(this.yaml.folderPath)
|
||||
let folderElement = folder?.parentElement;
|
||||
const tFolder = plugin.app.vault.getAbstractFileByPath(this.yaml.folderPath);
|
||||
if (!folderElement && yaml.folderPath.trim() !== '') return;
|
||||
folderElement = document.querySelector('div.nav-files-container') as HTMLElement;
|
||||
if (!folderElement) return;
|
||||
const newFolderElement = folderElement.cloneNode(true) as HTMLElement;
|
||||
newFolderElement.querySelectorAll('div.nav-folder-title ').forEach((el) => {
|
||||
const folder = plugin.app.vault.getAbstractFileByPath(el.getAttribute('data-path') || '');
|
||||
if (!(folder instanceof TFolder)) return;
|
||||
if (el.parentElement?.classList.contains('is-collapsed')) {
|
||||
folder.collapsed = true;
|
||||
if (this.yaml.storeFolderCondition) {
|
||||
if (folder.collapsed) {
|
||||
el.classList.add('is-collapsed');
|
||||
} else {
|
||||
el.classList.remove('is-collapsed');
|
||||
}
|
||||
} else {
|
||||
folder.collapsed = false;
|
||||
if (el.parentElement?.classList.contains('is-collapsed')) {
|
||||
folder.collapsed = true;
|
||||
} else {
|
||||
folder.collapsed = false;
|
||||
}
|
||||
}
|
||||
if (el.classList.contains('has-folder-note')) {
|
||||
const folderNote = getFolderNote(plugin, folder.path);
|
||||
|
|
@ -156,6 +182,14 @@ export class FolderOverview {
|
|||
});
|
||||
if (tFolder instanceof TFolder) {
|
||||
this.addFiles(tFolder.children, root);
|
||||
} else if (yaml.folderPath.trim() === '') {
|
||||
const rootFolders: TAbstractFile[] = [];
|
||||
plugin.app.vault.getAllLoadedFiles().filter(f => f instanceof TFolder).forEach((file) => {
|
||||
if (file instanceof TFolder && !file.path.includes('/')) {
|
||||
rootFolders.push(file);
|
||||
}
|
||||
});
|
||||
this.addFiles(rootFolders, root);
|
||||
}
|
||||
newFolderElement.querySelectorAll('div.tree-item-icon').forEach((el) => {
|
||||
if (el instanceof HTMLElement) {
|
||||
|
|
@ -170,10 +204,8 @@ export class FolderOverview {
|
|||
}
|
||||
|
||||
async addFiles(files: TAbstractFile[], childrenElement: HTMLElement) {
|
||||
const folders = files.filter((file) => file instanceof TFolder);
|
||||
const filesWithoutFolders = files.filter((file) => !(file instanceof TFolder));
|
||||
console.log(folders);
|
||||
console.log(filesWithoutFolders);
|
||||
const folders = files.filter((file) => file instanceof TFolder).sort();
|
||||
const filesWithoutFolders = this.sortFiles(files.filter((file) => !(file instanceof TFolder)), this.yaml, this.plugin);
|
||||
for (const child of folders) {
|
||||
if (child instanceof TFolder) {
|
||||
const folderNote = getFolderNote(this.plugin, child.path);
|
||||
|
|
@ -194,8 +226,6 @@ export class FolderOverview {
|
|||
if (!child.collapsed) {
|
||||
folderTitle.classList.remove('is-collapsed');
|
||||
const childrenElement = folderElement?.createDiv({ cls: 'tree-item-children nav-folder-children' });
|
||||
console.log(folderElement);
|
||||
console.log(child.children);
|
||||
this.addFiles(child.children, childrenElement);
|
||||
} else {
|
||||
folderTitle.classList.add('is-collapsed');
|
||||
|
|
@ -221,7 +251,7 @@ export class FolderOverview {
|
|||
if (child instanceof TFile) {
|
||||
if (this.pathBlacklist.includes(child.path)) { continue; }
|
||||
const extension = child.extension.toLowerCase() == 'md' ? 'markdown' : child.extension.toLowerCase();
|
||||
if (!this.yaml.includeTypes.includes(extension)) { continue; }
|
||||
if (!this.yaml.includeTypes.includes(extension as includeTypes)) { continue; }
|
||||
const fileElement = childrenElement.createDiv({
|
||||
cls: 'tree-item nav-file',
|
||||
});
|
||||
|
|
@ -290,7 +320,7 @@ export class FolderOverview {
|
|||
return files.filter((file) => {
|
||||
if (pathBlacklist.includes(file.path)) { return false; }
|
||||
const folderPath = plugin.getFolderPathFromString(file.path);
|
||||
if (!folderPath.startsWith(sourceFolderPath)) { return false; }
|
||||
if (!folderPath.startsWith(sourceFolderPath) && sourceFolderPath !== '') { return false; }
|
||||
const excludedFolder = getExcludedFolder(plugin, file.path);
|
||||
if (excludedFolder?.excludeFromFolderOverview) { return false; }
|
||||
if ((file.path.split('/').length - sourceFolderPath.split('/').length) - 1 < depth) {
|
||||
|
|
@ -304,7 +334,6 @@ export class FolderOverview {
|
|||
yaml.sortBy = plugin.settings.defaultOverview.sortBy || 'name';
|
||||
}
|
||||
return files.sort((a, b) => {
|
||||
// sort by folder first
|
||||
if (a instanceof TFolder && !(b instanceof TFolder)) {
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -312,13 +341,48 @@ export class FolderOverview {
|
|||
return 1;
|
||||
}
|
||||
if ((a instanceof TFolder) && (b instanceof TFolder)) {
|
||||
if (yaml.sortBy === 'name') {
|
||||
if (a.name > b.name) {
|
||||
return -1;
|
||||
} else if (a.name < b.name) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (!(a instanceof TFile) || !(b instanceof TFile)) { return -1; }
|
||||
if (yaml.sortBy === 'created') {
|
||||
if (yaml.sortByAsc) {
|
||||
if (a.stat.ctime < b.stat.ctime) {
|
||||
return -1;
|
||||
} else if (a.stat.ctime > b.stat.ctime) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (a.stat.ctime > b.stat.ctime) {
|
||||
return -1;
|
||||
} else if (a.stat.ctime < b.stat.ctime) {
|
||||
return 1;
|
||||
}
|
||||
} else if (yaml.sortBy === 'modified') {
|
||||
if (yaml.sortByAsc) {
|
||||
if (a.stat.mtime < b.stat.mtime) {
|
||||
return -1;
|
||||
} else if (a.stat.mtime > b.stat.mtime) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (a.stat.mtime > b.stat.mtime) {
|
||||
return -1;
|
||||
} else if (a.stat.mtime < b.stat.mtime) {
|
||||
return 1;
|
||||
}
|
||||
} else if (yaml.sortBy === 'name') {
|
||||
if (yaml.sortByAsc) {
|
||||
if (a.name < b.name) {
|
||||
|
||||
return -1;
|
||||
} else if (a.name > b.name) {
|
||||
return 1;
|
||||
}
|
||||
} else if (yaml.sortBy === 'nameAsc') {
|
||||
} else {
|
||||
if (a.name > b.name) {
|
||||
return -1;
|
||||
} else if (a.name < b.name) {
|
||||
|
|
@ -326,44 +390,6 @@ export class FolderOverview {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (!(a instanceof TFile) || !(b instanceof TFile)) { return -1; }
|
||||
if (yaml.sortBy === 'created') {
|
||||
if (a.stat.ctime > b.stat.ctime) {
|
||||
return -1;
|
||||
} else if (a.stat.ctime < b.stat.ctime) {
|
||||
return 1;
|
||||
}
|
||||
} else if (yaml.sortBy === 'createdAsc') {
|
||||
if (a.stat.ctime < b.stat.ctime) {
|
||||
return -1;
|
||||
} else if (a.stat.ctime > b.stat.ctime) {
|
||||
return 1;
|
||||
}
|
||||
} else if (yaml.sortBy === 'modified') {
|
||||
if (a.stat.mtime > b.stat.mtime) {
|
||||
return -1;
|
||||
} else if (a.stat.mtime < b.stat.mtime) {
|
||||
return 1;
|
||||
}
|
||||
} else if (yaml.sortBy === 'modifiedAsc') {
|
||||
if (a.stat.mtime < b.stat.mtime) {
|
||||
return -1;
|
||||
} else if (a.stat.mtime > b.stat.mtime) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
// sort by name
|
||||
if (a.name < b.name && yaml.sortBy === 'name') {
|
||||
return -1;
|
||||
} else if (a.name > b.name && yaml.sortBy === 'nameAsc') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a.name > b.name && yaml.sortBy === 'name') {
|
||||
return 1;
|
||||
} else if (a.name < b.name && yaml.sortBy === 'nameAsc') {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
|
@ -436,4 +462,43 @@ export class FolderOverview {
|
|||
});
|
||||
return allFiles;
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateYaml(plugin: FolderNotesPlugin, ctx: MarkdownPostProcessorContext, el: HTMLElement, yaml: yamlSettings) {
|
||||
console.log('update yaml');
|
||||
const file = plugin.app.vault.getAbstractFileByPath(ctx.sourcePath);
|
||||
if (!(file instanceof TFile)) return;
|
||||
let stringYaml = stringifyYaml(yaml);
|
||||
await plugin.app.vault.process(file, (text) => {
|
||||
const info = ctx.getSectionInfo(el);
|
||||
// check if stringYaml ends with a newline
|
||||
if (stringYaml[stringYaml.length - 1] !== '\n') {
|
||||
stringYaml += '\n';
|
||||
}
|
||||
if (info) {
|
||||
const { lineStart } = info;
|
||||
console.log(text);
|
||||
const lineEnd = getCodeBlockEndLine(text, lineStart);
|
||||
console.log(lineStart, lineEnd);
|
||||
if (lineEnd === -1 || !lineEnd) return text;
|
||||
const lineLength = lineEnd - lineStart;
|
||||
const lines = text.split('\n');
|
||||
lines.splice(lineStart, lineLength + 1, `\`\`\`folder-overview\n${stringYaml}\`\`\``);
|
||||
return lines.join('\n');
|
||||
}
|
||||
return `\`\`\`folder-overview\n${stringYaml}\`\`\``;
|
||||
});
|
||||
}
|
||||
export function getCodeBlockEndLine(text: string, startLine: number, count = 1) {
|
||||
let line = startLine + 1;
|
||||
const lines = text.split('\n');
|
||||
while (line < lines.length) {
|
||||
if (count > 20) { return -1; }
|
||||
if (lines[line].startsWith('```')) {
|
||||
return line;
|
||||
}
|
||||
line++;
|
||||
count++;
|
||||
}
|
||||
return line;
|
||||
}
|
||||
295
src/folderOverview/modalSettings.ts
Normal file
295
src/folderOverview/modalSettings.ts
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
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 { updateYaml } from 'src/folderOverview/FolderOverview';
|
||||
import { FolderSuggest } from 'src/suggesters/FolderSuggester';
|
||||
export class FolderOverviewSettings extends Modal {
|
||||
plugin: FolderNotesPlugin;
|
||||
app: App;
|
||||
yaml: yamlSettings;
|
||||
ctx: MarkdownPostProcessorContext;
|
||||
el: HTMLElement;
|
||||
defaultSettings: boolean;
|
||||
constructor(app: App, plugin: FolderNotesPlugin, yaml: yamlSettings, ctx: MarkdownPostProcessorContext | null, el: HTMLElement | null, defaultSettings?: boolean) {
|
||||
super(app);
|
||||
this.plugin = plugin;
|
||||
this.app = app;
|
||||
if (!yaml) {
|
||||
this.yaml = this.plugin.settings.defaultOverview;
|
||||
} else {
|
||||
this.yaml = yaml;
|
||||
}
|
||||
if (ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
if (el) {
|
||||
this.el = el;
|
||||
}
|
||||
if (defaultSettings) {
|
||||
this.yaml = this.plugin.settings.defaultOverview;
|
||||
this.defaultSettings = true;
|
||||
}
|
||||
}
|
||||
onOpen() {
|
||||
this.display();
|
||||
}
|
||||
display() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
// close when user presses enter
|
||||
contentEl.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
console.log(this.yaml);
|
||||
contentEl.createEl('h2', { text: 'Folder overview settings' });
|
||||
new Setting(contentEl)
|
||||
.setName('Disable the title')
|
||||
.setDesc('Choose if the title should be shown')
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.yaml.disableTitle || false)
|
||||
.onChange(async (value) => {
|
||||
console.log(this.yaml)
|
||||
this.yaml.disableTitle = value;
|
||||
this.display();
|
||||
if (this.defaultSettings) {
|
||||
return this.plugin.saveSettings();
|
||||
}
|
||||
await updateYaml(this.plugin, this.ctx, this.el, this.yaml);;
|
||||
})
|
||||
);
|
||||
if (!this.yaml.disableTitle) {
|
||||
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)
|
||||
search
|
||||
.setPlaceholder('Folder path')
|
||||
.setValue(this.yaml?.folderPath || '')
|
||||
.onChange(async (value) => {
|
||||
if (value === '' || value === '/') {
|
||||
this.yaml.folderPath = '';
|
||||
await updateYaml(this.plugin, this.ctx, this.el, this.yaml);;
|
||||
return;
|
||||
}
|
||||
if (!(this.app.vault.getAbstractFileByPath(value) instanceof TFolder)) 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 || this.plugin.settings.defaultOverview.storeFolderCondition || true)
|
||||
.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 = setting.createList((list: ListComponent) =>
|
||||
list
|
||||
.addModal(this)
|
||||
.setValues(this.yaml?.includeTypes || this.plugin.settings.defaultOverview.includeTypes || [])
|
||||
.addResetButton()
|
||||
);
|
||||
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');
|
||||
// @ts-ignore
|
||||
list.setValues(this.yaml.includeTypes);
|
||||
}
|
||||
// @ts-ignore
|
||||
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 || this.plugin.settings.defaultOverview.disableFileTag || false)
|
||||
.onChange(async (value) => {
|
||||
this.yaml.disableFileTag = 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('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')
|
||||
.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) => {
|
||||
this.yaml.sortByAsc = value === 'asc';
|
||||
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 || this.plugin.settings.defaultOverview.showEmptyFolders || false)
|
||||
.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 || this.plugin.settings.defaultOverview.onlyIncludeSubfolders || false)
|
||||
.onChange(async (value) => {
|
||||
this.yaml.onlyIncludeSubfolders = value;
|
||||
if (this.defaultSettings) {
|
||||
return this.plugin.saveSettings();
|
||||
}
|
||||
await updateYaml(this.plugin, this.ctx, this.el, this.yaml);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
onClose() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
import { Setting } from 'obsidian';
|
||||
import { FolderOverviewSettings } from '../modals/folderOverview';
|
||||
import { FolderOverviewSettings } from '../folderOverview/modalSettings';
|
||||
import { includeTypes } from 'src/folderOverview/FolderOverview';
|
||||
import { updateYaml } from 'src/folderOverview/FolderOverview';
|
||||
export default class ListComponent {
|
||||
containerEl: HTMLElement;
|
||||
controlEl: HTMLElement;
|
||||
|
|
@ -20,13 +22,17 @@ export default class ListComponent {
|
|||
setValues(values: string[]) {
|
||||
this.listEl.empty();
|
||||
this.values = values;
|
||||
this.modal.yaml.includeTypes = values;
|
||||
this.modal.yaml.includeTypes = values as includeTypes[];
|
||||
if (values.length !== 0) {
|
||||
values.forEach((value) => {
|
||||
this.addElement(value);
|
||||
});
|
||||
}
|
||||
this.modal.updateYaml();
|
||||
if (this.modal.defaultSettings) {
|
||||
this.modal.plugin.saveSettings();
|
||||
return this;
|
||||
}
|
||||
updateYaml(this.modal.plugin, this.modal.ctx, this.modal.el, this.modal.yaml);
|
||||
return this;
|
||||
}
|
||||
addElement(value: string) {
|
||||
|
|
@ -46,7 +52,7 @@ export default class ListComponent {
|
|||
async addValue(value: string) {
|
||||
this.values.push(value);
|
||||
this.addElement(value);
|
||||
this.modal.yaml.includeTypes = this.values;
|
||||
this.modal.yaml.includeTypes = this.values as includeTypes[];
|
||||
return this;
|
||||
}
|
||||
addResetButton() {
|
||||
|
|
|
|||
|
|
@ -126,7 +126,6 @@ export function extractFolderName(template: string, changedFileName: string) {
|
|||
|
||||
export function getFolderNote(plugin: FolderNotesPlugin, folderPath: string, storageLocation?: string, file?: TFile) {
|
||||
if (!folderPath) return null;
|
||||
console.log(folderPath);
|
||||
const folder = {
|
||||
path: folderPath,
|
||||
name: plugin.getFolderNameFromPathString(folderPath),
|
||||
|
|
|
|||
60
src/main.ts
60
src/main.ts
|
|
@ -1,4 +1,4 @@
|
|||
import { Plugin, TFile, TFolder, TAbstractFile, MarkdownPostProcessorContext, parseYaml } from 'obsidian';
|
||||
import { Plugin, TFile, TFolder, TAbstractFile, MarkdownPostProcessorContext, parseYaml, Notice } from 'obsidian';
|
||||
import { DEFAULT_SETTINGS, FolderNotesSettings, SettingsTab } from './settings';
|
||||
import { Commands } from './commands';
|
||||
import { FileExplorerWorkspaceLeaf } from './globals';
|
||||
|
|
@ -7,9 +7,8 @@ import { handleFileRename, handleFolderRename } from './events/handleRename';
|
|||
import { createFolderNote, extractFolderName, getFolderNote, getFolder } from './functions/folderNoteFunctions';
|
||||
import { getExcludedFolder } from './excludedFolder';
|
||||
import { FrontMatterTitlePluginHandler } from './events/frontMatterTitle';
|
||||
import { createOverview as createFolderOverview } from './folderOverview';
|
||||
import { FolderOverviewSettings } from './modals/folderOverview';
|
||||
import { FolderOverview } from './folderOverviewClass';
|
||||
import { FolderOverviewSettings } from './folderOverview/modalSettings';
|
||||
import { FolderOverview } from './folderOverview/FolderOverview';
|
||||
import './functions/ListComponent';
|
||||
export default class FolderNotesPlugin extends Plugin {
|
||||
observer: MutationObserver;
|
||||
|
|
@ -160,25 +159,18 @@ export default class FolderNotesPlugin extends Plugin {
|
|||
if (!this.settings.syncDelete) { return; }
|
||||
this.app.vault.delete(folderNote);
|
||||
}));
|
||||
this.registerMarkdownCodeBlockProcessor('folder-overview', (source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) => {
|
||||
const observer = new MutationObserver(() => {
|
||||
const editButton = el.parentElement?.childNodes.item(1);
|
||||
if (editButton) {
|
||||
editButton.addEventListener('click', (e) => {
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
new FolderOverviewSettings(this.app, this, parseYaml(source), ctx, el).open();
|
||||
}, { capture: true });
|
||||
}
|
||||
if (this.app.workspace.layoutReady) {
|
||||
this.registerMarkdownCodeBlockProcessor('folder-overview', (source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) => {
|
||||
this.handleOverviewBlock(source, el, ctx);
|
||||
});
|
||||
observer.observe(el, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
} else {
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
console.log('layout ready');
|
||||
this.registerMarkdownCodeBlockProcessor('folder-overview', (source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) => {
|
||||
this.handleOverviewBlock(source, el, ctx);
|
||||
});
|
||||
});
|
||||
const folderOverview = new FolderOverview(this, ctx, source, el);
|
||||
folderOverview.create(this, parseYaml(source), el, ctx);
|
||||
});
|
||||
}
|
||||
if (this.app.workspace.layoutReady) {
|
||||
this.loadFileClasses();
|
||||
} else {
|
||||
|
|
@ -186,6 +178,32 @@ export default class FolderNotesPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
handleOverviewBlock(source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) {
|
||||
console.log('handling overview block');
|
||||
const observer = new MutationObserver(() => {
|
||||
const editButton = el.parentElement?.childNodes.item(1);
|
||||
if (editButton) {
|
||||
editButton.addEventListener('click', (e) => {
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
new FolderOverviewSettings(this.app, this, parseYaml(source), ctx, el).open();
|
||||
}, { capture: true });
|
||||
}
|
||||
});
|
||||
observer.observe(el, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
try {
|
||||
const folderOverview = new FolderOverview(this, ctx, source, el);
|
||||
folderOverview.create(this, parseYaml(source), el, ctx);
|
||||
} catch (e) {
|
||||
new Notice('Error creating folder overview (folder notes plugin) - check console for more details');
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
getFileNameFromPathString(path: string): string {
|
||||
return path.substring(path.lastIndexOf('/' || '\\') >= 0 ? path.lastIndexOf('/' || '\\') + 1 : 0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,246 +0,0 @@
|
|||
import { App, Modal, Setting, MarkdownPostProcessorContext, stringifyYaml, TFile } from 'obsidian';
|
||||
import { yamlSettings } from 'src/folderOverview';
|
||||
import FolderNotesPlugin from '../main';
|
||||
import ListComponent from 'src/functions/ListComponent';
|
||||
export class FolderOverviewSettings extends Modal {
|
||||
plugin: FolderNotesPlugin;
|
||||
app: App;
|
||||
yaml: yamlSettings;
|
||||
ctx: MarkdownPostProcessorContext;
|
||||
el: HTMLElement;
|
||||
constructor(app: App, plugin: FolderNotesPlugin, yaml: yamlSettings, ctx: MarkdownPostProcessorContext, el: HTMLElement) {
|
||||
super(app);
|
||||
this.plugin = plugin;
|
||||
this.app = app;
|
||||
this.yaml = yaml;
|
||||
this.ctx = ctx;
|
||||
this.el = el;
|
||||
if (!this.yaml) {
|
||||
this.yaml = {
|
||||
title: this.plugin.settings.defaultOverview.title,
|
||||
disableTitle: this.plugin.settings.defaultOverview.disableTitle,
|
||||
depth: this.plugin.settings.defaultOverview.depth,
|
||||
type: this.plugin.settings.defaultOverview.type,
|
||||
includeTypes: this.plugin.settings.defaultOverview.includeTypes,
|
||||
style: this.plugin.settings.defaultOverview.style,
|
||||
disableFileTag: this.plugin.settings.defaultOverview.disableFileTag,
|
||||
sortBy: this.plugin.settings.defaultOverview.sortBy,
|
||||
showEmptyFolders: this.plugin.settings.defaultOverview.showEmptyFolders,
|
||||
onlyIncludeSubfolders: this.plugin.settings.defaultOverview.onlyIncludeSubfolders,
|
||||
};
|
||||
}
|
||||
}
|
||||
onOpen() {
|
||||
this.display();
|
||||
}
|
||||
display() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
// close when user presses enter
|
||||
contentEl.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
contentEl.createEl('h2', { text: 'Folder overview settings' });
|
||||
new Setting(contentEl)
|
||||
.setName('Disable the title')
|
||||
.setDesc('Choose if the title should be shown')
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.yaml.disableTitle || false)
|
||||
.onChange(async (value) => {
|
||||
this.yaml.disableTitle = value;
|
||||
this.display();
|
||||
await this.updateYaml();
|
||||
})
|
||||
);
|
||||
if (!this.yaml.disableTitle) {
|
||||
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;
|
||||
await this.updateYaml();
|
||||
})
|
||||
);
|
||||
}
|
||||
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()
|
||||
);
|
||||
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());
|
||||
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)) {
|
||||
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');
|
||||
// @ts-ignore
|
||||
list.setValues(this.yaml.includeTypes);
|
||||
}
|
||||
// @ts-ignore
|
||||
await list.addValue(value.toLowerCase());
|
||||
await this.updateYaml();
|
||||
this.display();
|
||||
});
|
||||
});
|
||||
}
|
||||
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 || this.plugin.settings.defaultOverview.disableFileTag || false)
|
||||
.onChange(async (value) => {
|
||||
this.yaml.disableFileTag = value;
|
||||
await this.updateYaml();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
await this.updateYaml();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('Overview style')
|
||||
.setDesc('Choose the style of the overview (grid style soon)')
|
||||
.addDropdown((dropdown) =>
|
||||
dropdown
|
||||
.addOption('list', 'List')
|
||||
.setValue(this.yaml?.style || 'list')
|
||||
.onChange(async (value: 'list') => {
|
||||
this.yaml.style = value;
|
||||
await this.updateYaml();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('Sort files by')
|
||||
.setDesc('Choose how the files should be sorted')
|
||||
.addDropdown((dropdown) =>
|
||||
dropdown
|
||||
.addOption('name', 'Name descending')
|
||||
.addOption('created', 'Created descending')
|
||||
.addOption('modified', 'Modified descending')
|
||||
.addOption('nameAsc', 'Name ascending')
|
||||
.addOption('createdAsc', 'Created ascending')
|
||||
.addOption('modifiedAsc', 'Modified ascending')
|
||||
.setValue(this.yaml?.sortBy || 'name')
|
||||
.onChange(async (value: 'name' | 'created' | 'modified' | 'nameAsc' | 'createdAsc' | 'modifiedAsc') => {
|
||||
this.yaml.sortBy = value;
|
||||
await this.updateYaml();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('Show folder names of empty folders')
|
||||
.setDesc('That includes subfolders of the current folder when you are on file depth 1')
|
||||
.addToggle((toggle) => {
|
||||
toggle
|
||||
.setValue(this.yaml.showEmptyFolders || this.plugin.settings.defaultOverview.showEmptyFolders || false)
|
||||
.onChange(async (value) => {
|
||||
this.yaml.showEmptyFolders = value;
|
||||
this.yaml.onlyIncludeSubfolders = false;
|
||||
this.display();
|
||||
await this.updateYaml();
|
||||
});
|
||||
});
|
||||
|
||||
if (this.yaml.showEmptyFolders) {
|
||||
new Setting(contentEl)
|
||||
.setName('Only show first empty subfolders of current folder')
|
||||
.addToggle((toggle) => {
|
||||
toggle
|
||||
.setValue(this.yaml.onlyIncludeSubfolders || this.plugin.settings.defaultOverview.onlyIncludeSubfolders || false)
|
||||
.onChange(async (value) => {
|
||||
this.yaml.onlyIncludeSubfolders = value;
|
||||
await this.updateYaml();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
onClose() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
async updateYaml() {
|
||||
const file = this.plugin.app.vault.getAbstractFileByPath(this.ctx.sourcePath);
|
||||
if (!(file instanceof TFile)) return;
|
||||
let stringYaml = stringifyYaml(this.yaml);
|
||||
await this.plugin.app.vault.process(file, (text) => {
|
||||
const info = this.ctx.getSectionInfo(this.el);
|
||||
// check if stringYaml ends with a newline
|
||||
if (stringYaml[stringYaml.length - 1] !== '\n') {
|
||||
stringYaml += '\n';
|
||||
}
|
||||
if (info) {
|
||||
const { lineStart } = info;
|
||||
const lineEnd = this.getCodeBlockEndLine(text, lineStart);
|
||||
if (lineEnd === -1 || !lineEnd) return text;
|
||||
const lineLength = lineEnd - lineStart;
|
||||
const lines = text.split('\n');
|
||||
lines.splice(lineStart, lineLength + 1, `\`\`\`folder-overview\n${stringYaml}\`\`\``);
|
||||
return lines.join('\n');
|
||||
}
|
||||
return `\`\`\`folder-overview\n${stringYaml}\`\`\``;
|
||||
});
|
||||
}
|
||||
getCodeBlockEndLine(text: string, startLine: number, count = 1) {
|
||||
let line = startLine + 1;
|
||||
const lines = text.split('\n');
|
||||
while (line < lines.length) {
|
||||
if (count > 20) { return -1; }
|
||||
if (lines[line].startsWith('```')) {
|
||||
return line;
|
||||
}
|
||||
line++;
|
||||
count++;
|
||||
}
|
||||
return line;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -5,7 +5,8 @@ import { extractFolderName, getFolderNote } from './functions/folderNoteFunction
|
|||
import { addExcludeFolderListItem, ExcludedFolder, addExcludedFolder, ExcludePattern, addExcludePatternListItem } from './excludedFolder';
|
||||
import { FrontMatterTitlePluginHandler } from './events/frontMatterTitle';
|
||||
import ConfirmationModal from "./modals/confirmCreation";
|
||||
import { yamlSettings } from './folderOverview';
|
||||
import { yamlSettings } from './folderOverview/FolderOverview';
|
||||
import { FolderOverviewSettings } from './folderOverview/modalSettings';
|
||||
export interface FolderNotesSettings {
|
||||
syncFolderName: boolean;
|
||||
ctrlKey: boolean;
|
||||
|
|
@ -61,12 +62,18 @@ export const DEFAULT_SETTINGS: FolderNotesSettings = {
|
|||
syncDelete: false,
|
||||
showRenameConfirmation: true,
|
||||
defaultOverview: {
|
||||
folderPath: '',
|
||||
title: '{{folderName}} overview',
|
||||
disableTitle: false,
|
||||
depth: 3,
|
||||
includeTypes: ['folder', 'markdown'],
|
||||
style: 'list',
|
||||
disableFileTag: false,
|
||||
sortBy: 'name',
|
||||
sortByAsc: true,
|
||||
showEmptyFolders: false,
|
||||
onlyIncludeSubfolders: false,
|
||||
storeFolderCondition: true,
|
||||
},
|
||||
useSubmenus: true,
|
||||
syncMove: true,
|
||||
|
|
@ -127,6 +134,18 @@ export class SettingsTab extends PluginSettingTab {
|
|||
})
|
||||
);
|
||||
|
||||
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(this.plugin.app, this.plugin, this.plugin.settings.defaultOverview, null, null, true).open();
|
||||
})
|
||||
);
|
||||
|
||||
const setting = new Setting(containerEl);
|
||||
const desc = document.createDocumentFragment();
|
||||
desc.append(
|
||||
|
|
@ -210,11 +229,9 @@ export class SettingsTab extends PluginSettingTab {
|
|||
.onChange(async (value) => {
|
||||
this.plugin.settings.syncMove = value;
|
||||
await this.plugin.saveSettings();
|
||||
}
|
||||
)
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const disableSetting = new Setting(containerEl);
|
||||
disableSetting.setName('Disable folder collapsing');
|
||||
disableSetting.setDesc('Disable the ability to collapse folders by clicking exactly on the folder name');
|
||||
|
|
@ -449,16 +466,16 @@ export class SettingsTab extends PluginSettingTab {
|
|||
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Create folder note for every folder')
|
||||
.setDesc('Create a folder note for every folder in the vault')
|
||||
.addButton((cb) => {
|
||||
cb.setIcon('plus');
|
||||
cb.setTooltip('Create folder notes');
|
||||
cb.onClick(async () => {
|
||||
|
||||
new ConfirmationModal(this.app, this.plugin).open();
|
||||
.setName('Create folder note for every folder')
|
||||
.setDesc('Create a folder note for every folder in the vault')
|
||||
.addButton((cb) => {
|
||||
cb.setIcon('plus');
|
||||
cb.setTooltip('Create folder notes');
|
||||
cb.onClick(async () => {
|
||||
|
||||
new ConfirmationModal(this.app, this.plugin).open();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
const manageExcluded = new Setting(containerEl)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ export enum FileSuggestMode {
|
|||
export class FolderSuggest extends TextInputSuggest<TFolder> {
|
||||
constructor(
|
||||
public inputEl: HTMLInputElement,
|
||||
private plugin: FolderNotesPlugin
|
||||
private plugin: FolderNotesPlugin,
|
||||
public folder?: TFolder,
|
||||
) {
|
||||
super(inputEl);
|
||||
}
|
||||
|
|
@ -29,7 +30,13 @@ export class FolderSuggest extends TextInputSuggest<TFolder> {
|
|||
getSuggestions(input_str: string): TFolder[] {
|
||||
const folders: TFolder[] = [];
|
||||
const lower_input_str = input_str.toLowerCase();
|
||||
this.plugin.app.vault.getAllLoadedFiles().forEach((folder: TAbstractFile) => {
|
||||
let files: TAbstractFile[] = [];
|
||||
if (this.folder) {
|
||||
files = this.folder.children;
|
||||
} else {
|
||||
files = this.plugin.app.vault.getAllLoadedFiles();
|
||||
}
|
||||
files.forEach((folder: TAbstractFile) => {
|
||||
if (
|
||||
folder instanceof TFolder &&
|
||||
folder.path.toLowerCase().contains(lower_input_str) &&
|
||||
|
|
|
|||
Loading…
Reference in a new issue