README.md

3 min read Original article ↗
/** * Recent Files Tracker for Obsidian * * Displays recently modified files from configurable folders. * * Features: * - Track multiple folders with custom icons * - Group by subfolders * - Configurable time periods and status indicators * * Usage: * 1. Copy this code into a Dataview JS code block * 2. Modify the USER_CONFIG section to match your needs * 3. Run the script */ // User configuration - Required const CONFIG = { // REQUIRED: Folders to track folders: [ // Examples (replace with your own): { path: "Projects", displayName: "Projects", icon: "📊", extractSubfolders: true }, { path: "Meetings", displayName: "Meeting Notes", icon: "🗣️", extractSubfolders: false }, { path: "Daily Notes", displayName: "Daily Notes", icon: "📝", extractSubfolders: false } ], // Time settings recentDays: 14, // How far back to look hoursRecent: 8, // "Recent" threshold in hours hoursToday: 24, // "Today" threshold in hours // Display settings maxFilesPerGroup: 5, // Max files per folder dateFormat: "MMM d", // Date format for older files // Status labels status: { recent: "🔵 Recent", today: "🟢 Today", earlier: "🟠 Earlier" }, prioritizeProjects: true, // Show projects first defaultIcon: "📄" // Icon for unlisted folders }; // Time constants const TIME = { MS: { MINUTE: 60000, HOUR: 3600000, DAY: 86400000 }, FORMAT_THRESHOLD_DAYS: 7 }; // Get human-readable relative time function getRelativeTime(timestamp) { const diffMs = new Date() - new Date(timestamp); const diffMins = Math.floor(diffMs / TIME.MS.MINUTE); const diffHours = Math.floor(diffMs / TIME.MS.HOUR); const diffDays = Math.floor(diffMs / TIME.MS.DAY); if (diffMins < 1) return "Just now"; if (diffMins < 60) return `${diffMins}m ago`; if (diffHours < 24) return `${diffHours}h ago`; if (diffDays === 1) return "Yesterday"; if (diffDays < TIME.FORMAT_THRESHOLD_DAYS) return `${diffDays} days ago`; return dv.date(timestamp).toFormat(CONFIG.dateFormat); } // Get folder configuration by path function getFolderConfig(path) { return CONFIG.folders.find(folder => path.includes(folder.path)) || null; } // Get status based on modification time function getStatus(mtime) { const now = dv.date("today"); if (mtime >= now.minus({hours: CONFIG.hoursRecent})) return CONFIG.status.recent; if (mtime >= now.minus({hours: CONFIG.hoursToday})) return CONFIG.status.today; return CONFIG.status.earlier; } // Build query and get recent files const folderQuery = CONFIG.folders.map(f => `"${f.path}"`).join(" OR "); const recentFiles = dv.pages(folderQuery) .where(p => p.file.mtime >= dv.date("today").minus({days: CONFIG.recentDays})); // Group files by folder/subfolder const fileGroups = recentFiles.groupBy(p => { for (const folder of CONFIG.folders) { if (p.file.path.includes(`${folder.path}/`)) { if (folder.extractSubfolders) { const match = p.file.path.match(new RegExp(`${folder.path}\/([^\/]+)`)); return match ? `${folder.path}/${match[1]}` : p.file.folder; } return folder.path; } } return p.file.folder; }); // Sort groups by priority const sortedGroups = fileGroups.sort((a, b) => { const aKey = a?.key || ''; const bKey = b?.key || ''; // Prioritize projects if configured if (CONFIG.prioritizeProjects) { const projectPath = (CONFIG.folders.find(f => f.path.toLowerCase().includes("projects"))?.path || "Projects").toLowerCase(); const aIsProject = aKey.toLowerCase().includes(projectPath); const bIsProject = bKey.toLowerCase().includes(projectPath); if (aIsProject && !bIsProject) return -1; if (!aIsProject && bIsProject) return 1; } // Preserve folder order from config for (const folder of CONFIG.folders) { const aMatches = aKey.startsWith(folder.path); const bMatches = bKey.startsWith(folder.path); if (aMatches && !bMatches) return -1; if (!aMatches && bMatches) return 1; if (aMatches && bMatches) return aKey.localeCompare(bKey); } return aKey.localeCompare(bKey); }); // Display each group for (const group of sortedGroups) { let folderName = group.key; let icon = CONFIG.defaultIcon + " "; const folderConfig = getFolderConfig(group.key); if (folderConfig) { if (folderConfig.extractSubfolders && group.key !== folderConfig.path) { const subfolderName = group.key.split('/').pop(); folderName = `${folderConfig.displayName}: ${subfolderName}`; } else { folderName = folderConfig.displayName; } icon = folderConfig.icon + " "; } else { folderName = group.key.split('/').pop(); } // Display header and table dv.header(3, `${icon}${folderName}`); dv.table( ["Modified", "Item", "Status"], group.rows .sort(p => p.file.mtime, 'desc') .slice(0, CONFIG.maxFilesPerGroup) .map(p => [getRelativeTime(p.file.mtime), p.file.link, getStatus(p.file.mtime)]) ); }