dataview + runjs实现生成归档和分类列表

效果:

命令面板

image

生成后的归档文件

生成后的分类文件

操作方法

新建一个runjs脚本文档,比如在runjs目录新建“归档和分类.md”,输入下面两段代码

点击这里查看runjs代码👇
```js RunJS="生成归档"
// 归档文件名
const archiveFileName = '归档.md';
//排除的文件或文件夹
const excludes = [
    //"demo.md",
    //"demo/demo.md",
];
// 仅在下列文件夹中的文件才显示
const onlyFolers = [
	//"开发笔记",
	//"javascript",
];
// 格式 {yearmonth:[link, link]}
const files = {};
const dv = DataviewAPI;
dv.pages().sort(p => p.file.ctime, 'desc')
	.map(p => {
		// 排除的文件或文件夹则跳过
		if (excludes.includes(p.file.path) ||
			excludes.includes(p.file.folder) ||
			(onlyFolers && onlyFolers.length && !onlyFolers.some(f=>new RegExp(`^${f}`).test(p.file.folder)))
		) return;
		// 初始化数组
		const key = p.file.ctime.year+''+p.file.ctime.month;
	    if(!files[key]) files[key] = [];
	    // 把相同年月的放到一个数组中
	    files[key].push(p.file.link);
	});
// 把年月倒序排序
const yearmonths = Object.keys(files)
	.sort((a, b) => b - a);

// 输出年月及文件列表
let content = '';
yearmonths.forEach((year_month)=>{
	const links = files[year_month]
	content += "### " + year_month.substr(0, 4) + '年' + year_month.substr(4, 2) + '月' + "\n\n"
    content += dv.markdownList(links) + "\n";
});
app.vault.adapter.write(archiveFileName, content);
```
```js RunJS="生成分类"
// 分类文件名
const cateFileName = '分类.md';
//排除的文件或文件夹
const excludes = [
    //"demo.md",
    //"demo/demo.md",
];
// 仅在下列文件夹中的文件才显示
const onlyFolers = [
	//"开发笔记",
	//"javascript",
];
//排序,仅支持文件夹排序
const sortBy = [
    //"开发笔记",
    //"javascript",
];
// 格式 {"folder":[link, link]}
const files = {};
const dv = DataviewAPI;
dv.pages()
	.sort(p => p.file.ctime, 'desc')
	.map(p => {
		// 脚本自身及排除的文件或文件夹则跳过
		if (excludes.includes(p.file.path) ||
			excludes.includes(p.file.folder) ||
			(onlyFolers && onlyFolers.length && !onlyFolers.some(f=>new RegExp(`^${f}`).test(p.file.folder)))
		) return;
		// 初始化数组
	    if(!files[p.file.folder]) files[p.file.folder] = [];
	    // 把相同的分类放到一个数组中
	    files[p.file.folder].push(p.file.link);
	});
// 把文件夹按名称正序排序
let folders = Object.keys(files)
	.sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
// 把未分类的文件放到最后
if(folders[0] === "") {
    const shift = folders.shift()
    folders.push(shift);
}
// 第一次把folders按sortby排序
const sortByIndex = sortBy.reduce((acc, curr, index) => {
    acc[curr] = index;
    return acc;
}, {});
folders.sort((a, b) => {
    // 获取a和b在sortBy中的索引,如果不存在则默认为sortBy.length(即放在末尾)
    const indexA = sortByIndex[a] !== undefined ? sortByIndex[a] : sortBy.length;
    const indexB = sortByIndex[b] !== undefined ? sortByIndex[b] : sortBy.length;
    // 按照索引值进行排序,索引越小越靠前
    return indexA - indexB;
});
//第二次把folders按sortby前缀排序
let newFolders = [];
sortBy.forEach((item) => {
    newFolders = [...newFolders, ...folders.filter(f=>f.startsWith(item))];
});
// 第三次,把folders中剩余的合并到newFolers
newFolders = [...newFolders, ...folders.filter(f=>!newFolders.includes(f))]
// 去重
folders =  newFolders.filter((item, index, array) => array.indexOf(item) === index);
// 嵌套输出文件夹及文件列表
const tree = {};
let content = '';
folders.forEach(folder => {
    let subs = folder.split('/');
    let currentNode = tree;
    let path = '';
    subs.forEach((sub, index) => {
        //path += (index ? '/' : '') + sub;
        // 如果不在目录结构的列表才输出,防止重复输出父分类和祖先分类
        if(!currentNode[sub]){
            //输出标题
            content += `${"\t".repeat(index)}- ${"#".repeat(index+2)} ${sub||"未分类"}\n\n`
            // 输出链接
            //content += `${"\t".repeat(index+1)}- list\n`
            let links = files[folder]
            links = dv.markdownList(links).replace(/- \[\[/g, `${"\t".repeat(index+1)}- [[`); 
            content += links + "\n"
        }

        // 实时计算目录结构
        if (!currentNode[sub]) {
            currentNode[sub] = {};
        }
        currentNode = currentNode[sub];
    });
});
/* 不嵌套输出文件夹及文件列表
let content = '';
folders.forEach((folder)=>{
	const links = files[folder]
    content += "### " + (folder.replace(/\//g, " / ")||"未分类") + "\n\n"
    content += dv.markdownList(links) + "\n";
});*/
app.vault.adapter.write(cateFileName, content);

然后,在runjs插件设置中,把生成归档和生成分类添加到命令中,如下图

这时候就已经完成了。

可以通过“RunJS: 生成归档”和“RunJS: 生成分类”命令生成相关文档了,如图

也可以通过左侧runjs面板里双击对应的名称生成,如图

image

但,也可以通过自定义一个常用命令的面板来执行,如果你需要请参考下面的步骤。

新建常用命令面板

新建一个常用命令面板,比如在panels目录新建“常用命令.md”,然后输入以下代码

点击这里查看常用命令代码👇
```dataviewjs
// 本脚本的文件的名字
const currFileName = dv.current()?.file?.name || "常用命令";

// 打开时设置为预览模式
const activeLeaf = this.app.workspace.activeLeaf;
if(activeLeaf?.view?.file?.basename === currFileName){
	const state = activeLeaf?.view.getState();
	state.mode = "preview"
	activeLeaf?.setViewState({type: "markdown", state: state});
}

// 创建按钮
const createButton = function(name, callback, useStyle = '') {
	const style = `float:left;margin-right:10px;margin-top:10px;${useStyle}`;
	const button = dv.el('button', name || 'Button', {attr:{style:style}});
	if(typeof callback === 'function') button.onclick = callback;
}

createButton('生成归档', function() {
	//查看commandId
    //app.commands.listCommands().filter(f=>f.id.contains('runjs'))
	app.commands.executeCommandById('runjs:command-0');
	this.firstChild.textContent = '已生成';
	setTimeout(() => {
		this.firstChild.textContent = '生成归档';
	}, 1500);
});

createButton('生成分类', function() {
	//查看commandId
    //app.commands.listCommands().filter(f=>f.id.contains('runjs'))
	app.commands.executeCommandById('runjs:command-1');
	this.firstChild.textContent = '已生成';
	setTimeout(() => {
		this.firstChild.textContent = '生成分类';
	}, 1500);
});
```

注意:这里的app.commands.executeCommandById('runjs:command-0');app.commands.executeCommandById('runjs:command-1'); 中的runjs:command-0和runjs:command-1就是刚才生成归档和生成分类的命令id,如果你不清楚这个id是什么(通常runjs命令列表,第一个就是runjs:command-0,第二个runjs:command-1,以此类推),可以在devtools控制面板中输入app.commands.listCommands().filter(f=>f.id.contains('runjs'))查看,如图

最终效果图如下

image

修改侧边栏图标

如果你想修改常用命令面板的图标,可参考:dataview实现获取最近修改文件列表,支持搜索

最终效果如下

image

定时生成或外部执行

如果你想定时生成归档或分类文档或外部执行,可以在脚本中使用以下命令

// mac
open --background "obsidian://advanced-uri?vault=vault_name&commandid=runjs%253Acommand-0"

//linux
xdg-open "obsidian://advanced-uri?vault=vault_name&commandid=runjs%253Acommand-0"

//windows
start /b "obsidian://advanced-uri?vault=vault_name&commandid=runjs%253Acommand-0"

注意:这里的vault_name和unjs%253Acommand-0根据你自己的情况修改。