【DV脚本】展示最近创建和修改的笔记文件(按天周月季年和世纪分区分组显示各天的文件树卡片)

【DV脚本】展示最近创建和修改的笔记文件(按天周月季年和世纪分组显示各天的文件树卡片)

  • 这个脚本显示视图的方式:按最近一天,一周,一月,一季,一年,十年,一世纪分区显示,并且在每个分区
    • 显示创建时间卡片分组
    • 显示修改时间卡片分组
    • 每个卡片分组包含若干个特定日期的卡片
    • 每个卡片有两种类型:创建时间和修改时间
    • 每个卡片含有一个文件树,显示着对应的文件
    • 文件如果包含属性 categorieskws, 则会在对应的列表项末尾添加显示这些属性的值
  • 这些分区有如下的折叠状态
    • 第一个分区显示今天创建和修改的笔记,且一个卡片默认展开,一个卡片默认折叠
    • 从第二个分区到最后一个分区的分区都需要通过点击按钮来显示,且所有卡片默认折叠

预览

视图上半部分

视图下半部分

点击 最近一周 按钮的视图变化结果

脚本内容

const isEnableTimeSpan = false;

const allPages = dv.pages();

function main(){
	dv.container.style.overflow = "visible"
	initStyle();
	show("1 days");	
	showWithButton("6 days","1 days", "Week");
	showWithButton("23 days", "7 days", "Month");
	showWithButton("60 days", "30 days", "Season");
	showWithButton("275 days", "90 days", "Year");
	showWithButton("3285 days", "365 days", "Decade");
	showWithButton("32850 days", "3650 days", "Century");
}
function show(durationStr = "1 days") {
	const div = document.createElement("div");
	dv.container.appendChild(div)
	const pagesCTime = getPagesInThePeriod(durationStr,null,"ctime");
	const pagesMTime = getPagesInThePeriod(durationStr,null,"mtime");
	dv.header(2, "🔎ctime (Day: "+durationStr+", "+pagesCTime.length+" created files in total)",{container:div});
	showCardGroup(pagesCTime,"Day", "ctime",div);
	dv.header(2, "🔎mtime (Day: "+durationStr+", "+pagesMTime.length+" modified files in total)",{container:div});
	showCardGroup(pagesMTime,"Day", "mtime",div);
}

function showWithButton(durationStr = "7 days", skipedDurationStr = "1 days", periodTypeInfo = "Period") {
	const br = document.createElement("br");
	dv.container.appendChild(br);
	const button = document.createElement("input");
	button.type = "button";
	const pagesCTime = getPagesInThePeriod(durationStr,skipedDurationStr,"ctime");
	const pagesMTime = getPagesInThePeriod(durationStr,skipedDurationStr,"mtime");
	button.value = periodTypeInfo+": Generate View where period equals "+durationStr+" more, skiped "+skipedDurationStr+"\n"+"file statics: "+pagesCTime.length+" created files, "+ pagesMTime.length+" modified files";
	dv.container.appendChild(button);
	const div = document.createElement("div");
	dv.container.appendChild(div);
	button.onclick = ()=>{
		br.remove();
		button.remove();
		
		dv.header(2, "🔎ctime ("+periodTypeInfo+": "+durationStr+", skiped "+skipedDurationStr+", "+pagesCTime.length+" created files in total)",{container:div});
		const con0201 = document.createElement("div");
		div.appendChild(con0201);
		
		showCardGroup(pagesCTime, periodTypeInfo, "ctime",con0201,false);
		dv.header(2, "🔎mtime ("+periodTypeInfo+": "+durationStr+", skiped "+skipedDurationStr+", "+pagesMTime.length+" modified files in total)",{container:div});
		const con0202 = document.createElement("div");
		div.appendChild(con0202);
		showCardGroup(pagesMTime,periodTypeInfo,"mtime",con0202,false);
	}
}

function initStyle(){
	const style = document.createElement("style");
	style.id = "20250918114602-style";
	const styleInDoc = document.getElementById(style.id);
	style.appendChild(document.createTextNode(`
	details.my-details[open]>summary::marker{
		color: rgba(0,255,0,0);
	}
	details.my-details[open]:hover>summary::marker{
		color: red;
	}
	div.file-item:hover ,details.my-details>summary:hover{
		background-color: rgba(0,255,0,0.2)
	}
	`))
	if (styleInDoc){
		styleInDoc.replaceWith(style);
	} else {
		document.head.appendChild(style);
	}
}

function getPagesInThePeriod(durationStr,skipedDurationStr,timeType){
	const today = dv.date('today');
	const period = dv.duration(durationStr);
	const skipedPeriod = dv.duration(skipedDurationStr)
	const startday = today - period + dv.duration("1 days") - skipedPeriod;
	const stopday = (skipedDurationStr?(today-skipedPeriod):today) + dv.duration("1 days");;
	
	const pages = allPages
	    .where(p => p.file[timeType] >= startday && p.file[timeType] < stopday)
	    .sort(p => p.file[timeType], 'desc');
	    
	return pages;
}

function showCardGroup(pagesInThePeriod,periodTypeInfo,timeType,container=dv.container,cardOpen=true){	
	if (pagesInThePeriod?.length === 0){
		dv.paragraph("⚠️No Files",{container})
		return container;
	}
	const groups = pagesInThePeriod.groupBy(p=>dv.func.dateformat(p.file[timeType],"yyyy-MM-dd")).sort(g=>Math.max(g.rows.file[timeType]),"desc")
	
	groups.forEach((g,i)=>{
		const groupDiv = document.createElement("div");
		groupDiv.style.borderStyle = "solid";
		groupDiv.style.borderColor = "rgba(0,0,0,0.3)";
		groupDiv.style.borderWidth = "0.3em";
		groupDiv.style.borderRadius = "1.2em";
		groupDiv.style.marginTop = "0.2em";
		groupDiv.style.marginBottom = "0.2em";
		container.appendChild(groupDiv);
		
		
		const details = document.createElement("details");
		if(cardOpen||i < 1){
			details.open = true;
		}
		details.classList.add("my-details");
		
		const summary = document.createElement("summary");
		groupDiv.ondblclick = (e)=>{
			if ([groupDiv,details].includes(e.srcElement)){
				summary.click();
			}
		}
		details.appendChild(summary);
		summary.innerText = "📅"+g.key+" ("+timeType+": "+g.rows.length+" files in total, "+periodTypeInfo+")";
		
		summary.style.backgroundColor = "rgba(0,0,0,0.2)";
		summary.style.borderStyle = "solid";
		summary.style.borderWidth = "0.1em";
		summary.style.borderRadius = "2em";
		summary.style.borderColor = "rgba(0,0,0,0.2)";
		
		async function dispCardContent(container){
			const tree = getFileTree(g.rows);
			const cardContent = renderNodes(tree.rootNode.children, timeType);
			await dv.api.renderValue(cardContent, container, dv.component,dv.currentFilePath);
		}
		
		const cardContentDiv = document.createElement("div");
		cardContentDiv.style.marginLeft = "0.1em";
		cardContentDiv.style.marginRight = "2em";
		details.appendChild(cardContentDiv);
		
		if (g.rows.length < 50){
			dispCardContent(cardContentDiv);
		}else {
			const loadingHint = document.createElement("div");
			loadingHint.innerText = "Loading";
			cardContentDiv.appendChild(loadingHint);
			dispCardContent(cardContentDiv).then(d=>{
				loadingHint.remove();
			})
		}
		
		groupDiv.appendChild(details);
		
	})
	return container;
}

function getFileTree(pages){
	const rootNode = {
		name: "",
		type: "folder",
		displayName: "",
		children: [],
		isRoot: true
	};
	const folderNodes = [rootNode];
	const fileNodes = [];
	pages.forEach(p=>{
		const folder = p.file.folder;
		const fileNode = {
			name: p.file.path,
			type: "file",
			displayName: p.file.link,
			children: [],
			data: p
		}
		fileNodes.push(fileNode);
		
		if (folder.length === 0){
			rootNode.children.push(fileNode);
			return;
		}
		
		const folderParts = folder.split("/");
		let curNode = fileNode;
		let curFolderNode = null;
		let i = folderParts.length;
		for (; i > 0; --i){
			const folderName = folderParts.slice(0,i).join("/");
			const qFolderNode = folderNodes.find(n=>n.name === folderName);
			if(qFolderNode){
				qFolderNode.children.push(curNode)
				break;
			}
			
			curFolderNode = {
				name: folderName,
				type: "folder",
				displayName: folderParts[i-1],
				children: [curNode]
			};
			folderNodes.push(curFolderNode)
			curNode = curFolderNode;
		}
		
		if (i === 0){
			rootNode.children.push(curNode);
		}
	})
	return {
		rootNode,
		folderNodes,
		fileNodes
	}
}

const isEnableLogUp = false;

function renderNode(node, timeType){
	if (node.type === "file"){
		const div = document.createElement("div");
		div.classList.add("file-item");
		div.style.marginLeft = "1em";
		
		let display = "📄"+node.displayName
		
		if (node.data.categories && node.data.categories.length !== 0){
			display += " "+dv.array(node.data.categories).distinct().map(k=>dv.value.isLink(k)?dv.fileLink(k.path,false,"📗"+k.path.replace(/\.md$/,"").replace(/^.*\//,"")):("📗"+k)).join(" ")
		}
		
		if (node.data.kws && node.data.kws.length !== 0){
			display += " "+dv.array(node.data.kws).distinct().map(k=>dv.value.isLink(k)?dv.fileLink(k.path,false,"🔖"+k.path.replace(/\.md$/,"").replace(/^.*\//,"")):("🔖"+k)).join(" ")
		}
		
		if (isEnableLogUp){
			let up = node.data.up;
			if (dv.value.isArray(up)){
				display+= "<br>&emsp;&emsp;"+up.map(k=>dv.value.isLink(k)?dv.fileLink(k.path,false,"🔼"+k.path.replace(/\.md$/,"").replace(/^.*\//,"")):("🔼"+k)).join(" ")
			}
		}
		
		dv.span(display, {container:div});
		
		if (isEnableTimeSpan){
			const timeSpan = document.createElement("span");
			timeSpan.innerText = "⏲️"+dv.func.dateformat(node.data.file[timeType],"yyyy-MM-dd");
			timeSpan.style.float = "right";
			div.appendChild(timeSpan);
		}
		return div;
	}
	
	const details = document.createElement("details");
	details.classList.add("my-details");
	details.open = true;
	
	if (node.children.length === 0){
		return details;
	}
	
	const summary = document.createElement("summary");
	details.appendChild(summary);
	
	let curFolderNode = node;
	let display = node.displayName;
	while (curFolderNode.children.length === 1 && curFolderNode.children[0].type === "folder"){
		curFolderNode = curFolderNode.children[0];
		display += "/"+curFolderNode.displayName;
	}
	
	const childFolders = dv.array(curFolderNode.children)
		.filter(c=>c.type === "folder")
		.sort(c=>c.name);
	const childFiles = dv.array(curFolderNode.children)
		.filter(c=>c.type === "file")
		.sort(c=>c.data.file[timeType],"desc");
	
	function getFileCountInTotal(folderNode){
		let folderNodes = [folderNode];
		let i = 0;
		for(; i < folderNodes.length; ++i){
			folderNodes = folderNodes.concat(folderNodes[i].children.filter(c=>c.type === "folder"));
		}
		i = 0;
		let sum = 0;
		for(; i < folderNodes.length; ++i){
			sum += folderNodes[i].children.filter(c=>c.type === "file").length;
		}
		return sum;
	}
	
	let infoArr = [];
	if (childFolders.length !== 0){
		infoArr.push(childFolders.length+" folders");
	}
	if (childFiles.length !== 0){
		infoArr.push(childFiles.length+" files");
	}
	const fileCountInTotal = getFileCountInTotal(curFolderNode);
	if (childFolders.length !== 0 && childFiles.length !== fileCountInTotal){
		infoArr.push(fileCountInTotal+" files in total");
	}
	const info = " ("+infoArr.join(", ")+")";
	
	dv.span("📁"+display+info, {container: summary});

	const div = document.createElement("div");
	div.style.marginLeft = "1em";
	details.appendChild(div);
	
	childFolders.forEach(c=>div.appendChild(renderNode(c, timeType)));
	childFiles.forEach(c=>div.appendChild(renderNode(c, timeType)));
	
	return details;	
}

function renderNodes(nodes, timeType){
	const div = document.createElement("div");
	dv.array(nodes).sort(n=>n.name).forEach(n=>div.appendChild(renderNode(n, timeType)));
	return div;
}

main();

2025-09-30

发现 Git 插件的 History View 有差不多一样的视图效果