|
|
|
/** |
|
* 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)]) |
|
); |
|
} |