### Get In-Use Attachments and Folder Utilities (TypeScript) Source: https://context7.com/husjon/obsidian-file-cleaner-redux/llms.txt Provides core helper functions for Obsidian plugins. It includes functions to get all attachments referenced in the vault, list all folders, retrieve files and subfolders within a specific folder, determine file extensions to check based on settings, and verify if a plugin is installed. These utilities are crucial for managing vault organization and cleaning up unused files. ```typescript import { App, TFile, TFolder } from "obsidian"; import { FileCleanerSettings } from "../settings"; // Get all attachments that are linked from any file export function getInUseAttachments(app: App): string[] { return Object.values(app.metadataCache.resolvedLinks) .flatMap((links) => Object.keys(links)) .filter((file) => !file.endsWith(".md")); } // Get all folders in the vault export function getFolders(app: App): TFolder[] { return app.vault .getAllLoadedFiles() .filter((node) => "children" in node) as TFolder[]; } // Get files in a specific folder (non-recursive) export function getFilesInFolder(folder: TFolder): TFile[] { return folder.children.filter( (node) => !("children" in node) ) as TFile[]; } // Get subfolders in a specific folder export function getSubFoldersInFolder(folder: TFolder): TFolder[] { return folder.children.filter( (node) => "children" in node ) as TFolder[]; } // Get file extensions to check based on settings export function getExtensions(settings: FileCleanerSettings): string[] { const extensions = settings.attachmentExtensions.filter( (ext) => ext !== "*" ); // Wildcard support - ".*" matches any extension if (settings.attachmentExtensions.includes("*")) { extensions.push(".*"); } return extensions; } // Check if a plugin is installed export function userHasPlugin(id: string, app: App): boolean { const plugins = (app as any).plugins.plugins; return Object.prototype.hasOwnProperty.call(plugins, id); } ``` -------------------------------- ### Handle Obsidian Canvas Files: Check Empty and Get Attachments (TypeScript) Source: https://context7.com/husjon/obsidian-file-cleaner-redux/llms.txt These functions manage Obsidian canvas files. `checkCanvas` detects if a canvas file is empty (either by size or lack of nodes/edges). `getCanvasAttachments` iterates through all canvas files, extracting attachment references from file nodes and text nodes (wikilinks and markdown links). It requires the Obsidian `App` and `TFile` types. ```typescript import { App, TFile } from "obsidian"; interface CanvasNode { id: string; type: string; file?: string; text?: string; } export async function checkCanvas(file: TFile, app: App): Promise { if (file.extension !== "canvas") return false; // Empty canvas files are 28 bytes or less if (file.stat.size <= 28) return true; const rawContent = await app.vault.cachedRead(file); const canvas = JSON.parse(rawContent); // Check for canvas with no nodes or edges if ( canvas.edges?.length === 0 && canvas.nodes?.length === 0 ) { return true; } return false; } export async function getCanvasAttachments(app: App): Promise { const canvasFiles = app.vault .getFiles() .filter((file) => file.extension === "canvas"); const attachments: string[] = []; for (const file of canvasFiles) { if (file.stat.size === 0) continue; const raw = await app.vault.read(file); const data = JSON.parse(raw); if (!data.nodes) continue; // Extract file references from file nodes const fileNodes = data.nodes .filter((node: CanvasNode) => node.type === "file" && !node.file?.endsWith(".md")) .map((node: CanvasNode) => node.file); // Extract attachment links from text nodes (wikilinks and markdown links) const textNodes = data.nodes .filter((node: CanvasNode) => node.type === "text") .flatMap((node: CanvasNode) => { const matches: string[] = []; // Wikilink: ![[path|label]] or [[path]] for (const match of node.text?.matchAll(/[!]?[[(.*?)]]/g) || []) { matches.push(match[1].split("|")[0]); } // Markdown: ![label](path) for (const match of node.text?.matchAll(/[!][.*?]\((.*)\)/g) || []) { matches.push(match[1].replace(/%20/gi, " ")); } return matches; }); attachments.push(...fileNodes, ...textNodes); } return attachments; } ``` -------------------------------- ### Deletion Confirmation Modal Class (TypeScript) Source: https://context7.com/husjon/obsidian-file-cleaner-redux/llms.txt Implements a Svelte-based modal for Obsidian, allowing users to preview and confirm file deletions. It takes an array of files and folders to be deleted and user settings as input. The modal displays the items for deletion and handles the user's confirmation or cancellation, integrating with Obsidian's modal system and Svelte for UI rendering. ```typescript import { App, Modal, TAbstractFile } from "obsidian"; import { mount, unmount } from "svelte"; import type { FileCleanerSettings } from "src/settings"; import DeletionConfirmationModalComponent from "./DeletionConfirmationModalComponent.svelte"; export class DeletionConfirmationModal extends Modal { private component: ReturnType | undefined; filesAndFolders: TAbstractFile[] = []; settings: FileCleanerSettings; constructor({ app, filesAndFolders, settings, }: { app: App; filesAndFolders: TAbstractFile[]; settings: FileCleanerSettings; }) { super(app); this.filesAndFolders = filesAndFolders; this.settings = settings; this.modalEl.style.maxWidth = "90%"; this.open(); } async onOpen(): Promise { this.titleEl.innerText = "Deletion confirmation"; this.component = mount(DeletionConfirmationModalComponent, { target: this.contentEl, props: { app: this.app, settings: this.settings, filesAndFolders: this.filesAndFolders, closeModal: () => this.close(), }, }); } async onClose() { unmount(this.component); } } ``` -------------------------------- ### FileCleanerSettings Interface - Plugin Configuration Source: https://context7.com/husjon/obsidian-file-cleaner-redux/llms.txt Defines the structure for all configurable settings within the File Cleaner Redux plugin. This interface specifies options for deletion destinations, folder and file filtering, cleanup behavior, and integration with external plugins. ```typescript import { Deletion } from "./enums"; export enum ExcludeInclude { Exclude = 0, Include = 1, } export interface FileCleanerSettings { // Where deleted files go: "system", "obsidian", or "permanent" deletionDestination: Deletion; // Days before trash cleanup (-1 to disable) obsidianTrashCleanupAge: number; // Folder filtering mode excludeInclude: ExcludeInclude; excludedFolders: string[]; // Attachment extension filtering attachmentsExcludeInclude: ExcludeInclude; attachmentExtensions: string[]; // Cleanup behavior options deletionConfirmation: boolean; runOnStartup: boolean; removeFolders: boolean; // Markdown file options deleteEmptyMarkdownFiles: boolean; deleteEmptyMarkdownFilesWithBacklinks: boolean; ignoredFrontmatter: string[]; ignoreAllFrontmatter: boolean; codeblockTypes: string[]; // File age threshold in days fileAgeThreshold: number; // UI options closeNewTabs: boolean; deleteEmptyFileOnClose: boolean; // External plugin support ExternalPlugins: { Excalidraw: { TreatAsAttachments: boolean }; }; } export const DEFAULT_SETTINGS: FileCleanerSettings = { deletionDestination: Deletion.SystemTrash, obsidianTrashCleanupAge: -1, excludeInclude: ExcludeInclude.Exclude, excludedFolders: [], attachmentsExcludeInclude: ExcludeInclude.Include, attachmentExtensions: [], deletionConfirmation: true, runOnStartup: false, removeFolders: false, ignoredFrontmatter: [], ignoreAllFrontmatter: false, codeblockTypes: [], deleteEmptyMarkdownFiles: true, deleteEmptyMarkdownFilesWithBacklinks: false, fileAgeThreshold: 0, closeNewTabs: false, deleteEmptyFileOnClose: false, ExternalPlugins: { Excalidraw: { TreatAsAttachments: false }, }, }; ``` -------------------------------- ### Check Markdown File Emptiness and Backlinks (TypeScript) Source: https://context7.com/husjon/obsidian-file-cleaner-redux/llms.txt The `checkMarkdown` function determines if a markdown file should be deleted. It checks for backlinks, zero-byte files, whitespace-only content, and frontmatter configurations based on the plugin settings. Files with no backlinks (or if configured to delete them) and empty content are flagged for removal. ```typescript import { App, TFile } from "obsidian"; import { FileCleanerSettings } from "../settings"; export async function checkMarkdown( file: TFile, app: App, settings: FileCleanerSettings ): Promise { if (file.extension !== "md") return false; if (!settings.deleteEmptyMarkdownFiles) return false; // Check for backlinks - skip if file has references and setting disabled const metadata = app.metadataCache; // @ts-expect-error (getBacklinksForFile not in type definitions) const links = metadata.getBacklinksForFile(file).data as Map; if (links.size > 0 && !settings.deleteEmptyMarkdownFilesWithBacklinks) { return false; } // Check for zero-byte files if (file.stat.size === 0) return true; // Check for whitespace-only content const content = await app.vault.cachedRead(file); if (content.trim().length === 0) return true; // Check for frontmatter-only files with ignored properties const fileCache = app.metadataCache.getFileCache(file); if (fileCache.sections?.length === 1 && fileCache.frontmatter) { if (settings.ignoreAllFrontmatter) return true; const frontmatterKeys = Object.keys(fileCache.frontmatter); return frontmatterKeys.every((key) => settings.ignoredFrontmatter.includes(key) ); } return false; } ``` -------------------------------- ### Scan Vault for Files and Folders to Clean (TypeScript) Source: https://context7.com/husjon/obsidian-file-cleaner-redux/llms.txt The `scanVault` function iterates through the Obsidian vault, identifying files and folders that meet the cleanup criteria defined in the settings. It considers in-use attachments, excluded folders, file extensions, and empty markdown files. The `isFolderExcluded` function checks if a given folder path matches any of the patterns in the excluded folders list. ```typescript import { App, TFile, TFolder } from "obsidian"; import { FileCleanerSettings, ExcludeInclude } from "./settings"; import { getInUseAttachments, getFolders, getFilesInFolder, getExtensions, } from "./helpers/helpers"; import { getCanvasAttachments } from "./helpers/canvas"; import { checkMarkdown } from "./helpers/markdown"; export async function scanVault(app: App, settings: FileCleanerSettings) { // Get all attachments currently referenced by files const inUseAttachments = getInUseAttachments(app); inUseAttachments.push(...(await getCanvasAttachments(app))); // Get all folders sorted by depth (deepest first) const folders = getFolders(app) .filter((folder) => folder.path !== "/") .sort((a, b) => b.path.localeCompare(a.path)) .reverse(); folders.push(app.vault.getFolderByPath("/")); const filesToRemove: TFile[] = []; const foldersToRemove: TFolder[] = []; const extensions = getExtensions(settings); for (const folder of folders) { // Skip excluded folders if ( settings.excludeInclude === ExcludeInclude.Exclude && isFolderExcluded(folder, settings) ) continue; const files = getFilesInFolder(folder); let childrenCount = files.length; for (const file of files) { // Skip files that are in use if (inUseAttachments.includes(file.path)) continue; if (await checkFile(app, settings, file, extensions)) { filesToRemove.push(file); childrenCount -= 1; } } // Mark empty folders for removal if (childrenCount === 0 && settings.removeFolders && !folder.isRoot()) { foldersToRemove.push(folder); } } return { filesToRemove, foldersToRemove }; } export function isFolderExcluded( folder: TFolder, settings: FileCleanerSettings ): boolean { return settings.excludedFolders.some((pattern) => folder.path.match(RegExp(`^${pattern}`)) ); } ``` -------------------------------- ### FileCleanerPlugin Class - Obsidian Plugin Core Source: https://context7.com/husjon/obsidian-file-cleaner-redux/llms.txt The main plugin class for File Cleaner Redux. It extends Obsidian's Plugin class, manages settings, and registers core functionalities like ribbon icons and commands. It orchestrates the vault scanning and cleanup process. ```typescript import { Plugin, TFile } from "obsidian"; import { FileCleanerSettings, DEFAULT_SETTINGS } from "./settings"; import { scanVault, runCleanup } from "./util"; export default class FileCleanerPlugin extends Plugin { settings: FileCleanerSettings; async onload() { await this.loadSettings(); // Add ribbon icon for quick access this.addRibbonIcon("trash", "Clean files", this.runVaultCleanup); // Register command for keyboard shortcuts this.addCommand({ id: "clean-files", name: "Clean files", callback: this.runVaultCleanup, }); // Optional: run cleanup on startup if (this.settings.runOnStartup) { setTimeout(this.runVaultCleanup, 1000); } } private runVaultCleanup = async () => { const { filesToRemove, foldersToRemove } = await scanVault( this.app, this.settings ); runCleanup(filesToRemove, foldersToRemove, this.app, this.settings); }; async loadSettings() { this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); } async saveSettings() { await this.saveData(this.settings); } } ``` -------------------------------- ### Remove Files with System, Obsidian, or Permanent Deletion (TypeScript) Source: https://context7.com/husjon/obsidian-file-cleaner-redux/llms.txt The `removeFile` function handles the deletion of a single file based on the user's chosen deletion method (System Trash, Obsidian Trash, or Permanent). The `removeFiles` function processes an array of files, calling `removeFile` for each one and providing user feedback via notices. ```typescript import { App, Notice, TAbstractFile } from "obsidian"; import { FileCleanerSettings } from "../settings"; import { Deletion } from "../enums"; export enum Deletion { SystemTrash = "system", ObsidianTrash = "obsidian", Permanent = "permanent", } export async function removeFile( file: TAbstractFile, app: App, settings: FileCleanerSettings ) { // Check if file still exists if (!(await app.vault.adapter.exists(file.path))) return; switch (settings.deletionDestination) { case Deletion.Permanent: await app.vault.delete(file, true); break; case Deletion.SystemTrash: await app.vault.trash(file, true); break; case Deletion.ObsidianTrash: await app.vault.trash(file, false); break; } } export async function removeFiles( files: TAbstractFile[], app: App, settings: FileCleanerSettings ) { if (files.length > 0) { for (const file of files) { await removeFile(file, app, settings); } new Notice("Clean successful"); } else { new Notice("No file to clean"); } } ``` -------------------------------- ### Translate Function for Internationalization (TypeScript) Source: https://context7.com/husjon/obsidian-file-cleaner-redux/llms.txt Provides a utility function to retrieve localized strings based on the system's current locale. It supports English (en) and Chinese (zh-cn) by default, using Moment.js for locale detection. If the system locale is not supported, it defaults to English. This function is essential for making the plugin accessible to a global user base. ```typescript import * as moment from "moment"; import enUS from "./locales/en"; import zhCN from "./locales/zh-cn"; import type { Locale } from "./locales/locale"; const LOCALES: { [locale: string]: Locale } = { en: enUS, "zh-cn": zhCN, }; export default function translate(): Locale { const systemLocale = moment.locale(); return LOCALES[systemLocale] || enUS; } // Usage example: // translate().Settings.Folders.Header => "Folders" // translate().Notifications.CleanSuccessful => "Clean successful" // translate().Modals.DeletionConfirmation.Title => "Deletion confirmation" ``` -------------------------------- ### Parse Codeblocks for Attachments: Extract from Markdown Files (TypeScript) Source: https://context7.com/husjon/obsidian-file-cleaner-redux/llms.txt This function, `getCodeblockAttachments`, extracts attachment references from specific codeblock types within Markdown files. It filters codeblocks by a provided language regular expression and parses both wikilink and markdown link formats. It relies on `getMarkdownCodeblocks` and Obsidian's `metadataCache` for resolving file paths. Dependencies include Obsidian `App` and `TFile`. ```typescript import { App, TFile } from "obsidian"; type CodeBlock = { content: string; language: string; }; export async function getCodeblockAttachments( app: App, languageFilter?: RegExp ): Promise { if (!languageFilter) return []; const markdownFiles = app.vault .getFiles() .filter((file) => file.extension === "md"); const attachments: string[] = []; for (const file of markdownFiles) { const codeblocks = await getMarkdownCodeblocks(file, app); for (const block of codeblocks) { if (!block?.language.match(languageFilter)) continue; // Extract wikilink attachments: ![[path|label]] for (const match of block.content.matchAll(/[!]?[[(.*?)]]/g)) { const filePath = match[1].split("|")[0]; const resolved = app.metadataCache.getFirstLinkpathDest(filePath, file.path); if (resolved) attachments.push(resolved.path); } // Extract markdown attachments: ![label](path) for (const match of block.content.matchAll(/[!][.*?]\((.*?)\)/g)) { const resolved = app.metadataCache.getFirstLinkpathDest(match[1], file.path); if (resolved) attachments.push(resolved.path); } } } return attachments; } export async function getMarkdownCodeblocks( file: TFile, app: App ): Promise { const cache = app.metadataCache.getFileCache(file); if (!cache?.sections) return []; const fileContent = await app.vault.cachedRead(file); return cache.sections .filter((section) => section.type === "code") .map((section) => { const content = fileContent.slice( section.position.start.offset, section.position.end.offset ); return parseCodeblock(content); }) .filter(Boolean) as CodeBlock[]; } function parseCodeblock(codeblock: string): CodeBlock | null { const fence = codeblock.match(/^[`~]{3,}/g); if (!fence) return null; const content = codeblock .replace(RegExp(`^${fence}+`), "") .replace(RegExp(`${fence}+$`), ""); const language = content.split(/[ ]+/)[0]; return { language, content: content.replace(RegExp(`^${language}`), "").trim(), }; } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.