dataview实现获取最近修改文件列表,支持搜索,右键菜单

查看版本2

可以拖动到左侧或右侧面板中方便查看。

效果

代码

```dataviewjs
// 本脚本的文件的名字
const currFileName = dv.current()?.file?.name || "Recently Modified";
// 打开时设置为预览模式
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 searchStyle = `
background-color: transparent;
border: 1px solid #888;
border-radius: 5px;
margin-left: 20px;
height: 30px;
width: 80%;
padding: 0 5px;
color: var(--text-normal);
`;
const search = dv.el('input', '', { attr: { "style": searchStyle } });
search.addEventListener('input', function(e) {
    const keyword = e.target.value.trim().toLowerCase();
    const listItems = this.parentElement.querySelectorAll('.list-view-ul li');
    listItems.forEach(item => {
        const text = item.textContent.toLowerCase();
        if (keyword === '' || text.includes(keyword)) {
            item.style.display = 'list-item';
        } else {
            item.style.display = 'none';
        }
        // 关键词高亮
        const itemWords = item.querySelector('a span');
        if (keyword) {
            const regex = new RegExp(`(${keyword})`, 'gi');
            const highlightedText = itemWords.textContent.replace(regex, `<mark>$1</mark>`);
            itemWords.innerHTML = highlightedText;
        } else {
            // 如果搜索框为空,移除高亮样式,恢复原始文本
            itemWords.innerHTML = itemWords.textContent;
        }
    });
});

// 最近修改文件列表
let lastActiveLeaf;
dv.list(
    dv.pages()
    //.filter(p => moment(p.file.mday.toString()).format('MD') === moment().format('MD'))
    .sort(p => p.file.mtime, 'desc')
    .limit(100)
    .map(p => {
        const a=dv.el('a', p.file.name, { attr: { "data-href": p.file.path } });
        a.addEventListener('click', function(e) {
			let newLeaf = lastActiveLeaf || app.workspace.getLeaf('tab');
			// 当前激活文档不在root split则新标签打开
			if(!newLeaf.containerEl.closest(".mod-root")){
				newLeaf = app.workspace.getLeaf('tab');
			}
			// Ctrl或Meta键时则新标签打开
			if (isCtrlDown) {
			    newLeaf = app.workspace.getLeaf('tab');
			}
			// 如果叶子已关闭则新标签打开
			if(!newLeaf.containerEl.querySelector('.workspace-leaf-content')) {
			    newLeaf = app.workspace.getLeaf('tab');
			}
			newLeaf.openFile(app.vault.getAbstractFileByPath(this.getAttribute("data-href")));
	        setTimeout(() => {
		        lastActiveLeaf = app.workspace.activeLeaf;
	        }, 100);
        });
        return a;
    })
)

// 用于记录Ctrl键是否被按下
let isCtrlDown = false;
const handleKeyDown = function(event) {
    if (event.key === 'Control'|| event.key === 'Meta') {
        isCtrlDown = true;
    }
};
document.removeEventListener('keydown', handleKeyDown);
document.addEventListener('keydown', handleKeyDown);
const handleKeyUp = function(event) {
    if (event.key === 'Control'|| event.key === 'Meta') {
        isCtrlDown = false;
    }
};
document.removeEventListener('keyup', handleKeyUp);
document.addEventListener('keyup', handleKeyUp);
```

文件打开规则:

  1. 如果激活文档不在root split中(即不在中间主窗口中)则新标签打开

  2. 如果ctrl + 点击则新标签打开

  3. 其他情况则在当前激活文档中打开(即替换当前激活文档)

修改侧边栏脚本图标

如果在左侧面板中,可以在runjs插件加载时执行脚本中输入以下代码:

// 修改获取最近修改文档脚本图标
const modifyRecentlyIcon = () => {
    // "Recently Modified"脚本名字,根据你自己的脚本名修改
    const RecentlyScriptName = "Recently Modified";
    document.querySelectorAll(".mod-left-split .workspace-tab-header-container .workspace-tab-header").forEach((item)=>{
		if (item.querySelector(".workspace-tab-header-inner-title").textContent === RecentlyScriptName) {
			item.querySelector(".workspace-tab-header-inner-icon").innerHTML = '📝';
			return false;
		}
	});
}
modifyRecentlyIcon();
document.querySelector(".mod-left-split .workspace-tab-header-container-inner").addEventListener('click', ()=>{
	setTimeout(() => {
	    modifyRecentlyIcon();
	}, 300);
});

如果在右侧面板中,可参考上面思路自行实现。

更完美方式实现在当前激活叶子打开文档

如果想让"Recently Modified"脚本未激活时(即未点开脚本文档前)也能获取当前激活叶子,可以通过active-leaf-change事件结合全局变量实现,比如可在runjs插件加载时执行脚本中输入以下代码:

// 把最后一个被激活的叶子,设置为全局变量
(() => {
    // "Recently Modified"脚本名字,根据你自己的脚本名修改
    const RecentlyScriptName = "Recently Modified";
	let lastLeafTimer;
	const onActiveLeafChange = async (activeLeaf) => {
		// 定时防止无效触发,只取最后一个触发
		if(lastLeafTimer) clearTimeout(lastLeafTimer)
        lastLeafTimer = setTimeout(async () => {
	         if(activeLeaf?.view?.file?.basename !== RecentlyScriptName) {
	             window.lastActiveLeaf = activeLeaf;
	         }
		}, 42);
	};
	this.app.workspace.on('active-leaf-change', onActiveLeafChange);
	onActiveLeafChange(this.app.workspace.activeLeaf);
})();

说明:RecentlyScriptName = "Recently Modified" 这是"Recently Modified"脚本名字,根据你自己的脚本名修改。

然后修改 “Recently Modified” 脚本中的判断代码,比如:

let newLeaf = lastActiveLeaf || app.workspace.getLeaf('tab');

修改为

let newLeaf = window.lastActiveLeaf || lastActiveLeaf || app.workspace.getLeaf('tab');

5 个赞

更新为第2版

更新内容:

  1. UI美化
  2. 增加右键菜单
  3. 增加HoverPopover
  4. 增加tooltip
  5. 增加多语言
代码
```dataviewjs
// 显示条数
const maxNum = 100;
// 本脚本的文件的名字
const currFileName = dv.current()?.file?.name || "Recently Modified";
// 打开时设置为预览模式
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});
	activeLeaf?.containerEl.classList.add('recently-modified-view');
}
// 检查recently-modified-view是否设置成功
if(!document.querySelector('recently-modified-view')){
	app.workspace.getLeavesOfType('markdown').forEach(leaf => {
		if(leaf.view.file?.basename === currFileName){
			leaf?.containerEl.classList.add('recently-modified-view');
			if (leaf?.view?.getState()?.mode !== 'preview') {
				leaf?.setViewState({type: "markdown", state: state});
			}
		}
	})
}

// 多语言,默认为中文
const langMap = {
	en: {
		"标题:": "Title: ",
		"创建于:": "Created on: ",
		"更新于:": "Updated on: ",
		"所在文件夹:": "Folder: ",
		"根目录": "Root directory",
		"搜索...": "Search...",
	}
}
function t(str) {
    const lang = moment.locale();
    if(langMap[lang] && langMap[lang][str]) {
        return langMap[lang][str];
    }
    return str;
}

//添加样式
const style = `
.recently-modified-view .search-input {
    background-color: transparent;
    border: 1px solid #888;
    border-radius: 5px;
    height: 27px;
    width: 80%;
    padding: 0 5px;
    color: var(--text-normal);
    margin-top: 2px;
    margin-left: 2px;
}
.recently-modified-view .search-input:focus {
    box-shadow: 0 0 0 2px var(--background-modifier-border-focus);
	border-color: var(--background-modifier-border-focus);
    transition: box-shadow 0.15s ease-in-out, border 0.15s ease-in-out;
}
.recently-modified-view .markdown-preview-view ul{
    padding-left: 0;
    user-select: none;
}
.recently-modified-view .markdown-preview-view ul > li{
    list-style: none;
    padding: 4px 8px;
    border-radius: var(--radius-s);
}
.recently-modified-view .markdown-preview-view ul > li:hover{
    background-color: var(--nav-item-background-hover);
}
.recently-modified-view .markdown-preview-view ul > li.menu-hover {
    background-color: var(--nav-item-background-hover);
}
.recently-modified-view .markdown-preview-view ul > li a {
    text-decoration: none;
    color: var(--nav-item-color);
    width: 100%;
    display: list-item;
}
.recently-modified-view li:has(a[data-starred='true'])::after {
    content: "✩";
    position: absolute;
    top: 4px;
    right: 4px;
}
.recently-modified-view .inline-title {
    font-size: 23px;
}
/* 修改tooltip对齐方式 */
body:has(.recently-modified-view) .tooltip{
	text-align: left;
}
`;
const dvStyle = document.head.querySelector("#recently-modified-style");
if(dvStyle) dvStyle.remove();
document.head.appendChild(
	createEl("style", {
		attr: { id: "recently-modified-style" },
		text: style,
		type: "text/css",
	})
);

// 关键词搜索
const search = dv.el('input', '', { attr: { "class": "search-input", "placeholder": t("搜索...") } });
search.addEventListener('input', function(e) {
    const keyword = e.target.value.trim().toLowerCase();
    const listItems = this.parentElement.querySelectorAll('.list-view-ul li');
    listItems.forEach(item => {
        const text = item.textContent.toLowerCase();
        if (keyword === '' || text.includes(keyword)) {
            item.style.display = 'list-item';
        } else {
            item.style.display = 'none';
        }
        // 关键词高亮
        const itemWords = item.querySelector('a span');
        if (keyword) {
            const regex = new RegExp(`(${keyword})`, 'gi');
            const highlightedText = itemWords.textContent.replace(regex, `<mark>$1</mark>`);
            itemWords.innerHTML = highlightedText;
        } else {
            // 如果搜索框为空,移除高亮样式,恢复原始文本
            itemWords.innerHTML = itemWords.textContent;
        }
    });
});

// 最近修改文件列表
let lastActiveLeaf;
dv.list(
    dv.pages()
    //.filter(p => moment(p.file.mday.toString()).format('MD') === moment().format('MD'))
    .sort(p => p.file.mtime, 'desc')
    .limit(maxNum||100)
    .map(p => {
        let title = `${t('标题:')}${p.file.name}\n`;
		title += `${t('创建于:')}${moment(Number(p.file.ctime)).format('YYYY-MM-DD HH:mm:ss')}\n`;
		title += `${t('更新于:')}${moment(Number(p.file.mtime)).format('YYYY-MM-DD HH:mm:ss')}\n`;
		title += `${t('所在文件夹:')}${p.file.folder||t('根目录')}`;
        const a=dv.el('a', p.file.name, { attr: { "data-href": p.file.path, "data-starred": p.file.starred, "aria-label": title, "data-tooltip-position": "right" } });
        //click
        a.addEventListener('click', function(e) {
			let newLeaf = window.lastActiveLeaf || lastActiveLeaf || app.workspace.getLeaf('tab');
			// 当前激活文档不在root split则新标签打开
			if(!newLeaf.containerEl.closest(".mod-root")){
				newLeaf = app.workspace.getLeaf('tab');
			}
			// Ctrl或Meta键时则新标签打开
			if (isCtrlDown) {
			    newLeaf = app.workspace.getLeaf('tab');
			}
			// 如果叶子已关闭则新标签打开
			if(!newLeaf.containerEl.querySelector('.workspace-leaf-content')) {
			    newLeaf = app.workspace.getLeaf('tab');
			}
			newLeaf.openFile(app.vault.getAbstractFileByPath(this.getAttribute("data-href")));
	        setTimeout(() => {
		        lastActiveLeaf = app.workspace.activeLeaf;
	        }, 100);
        });
        // hover popover
        a.addEventListener('mouseover', function(event) {
            if(!isCtrlDown) return;
            if (!this?.getAttribute("data-href")) return;
            app.workspace.trigger('hover-link', {
                event,
                source: 'preview',
                hoverParent: this.closest('.dataview.list-view-ul'),
                targetEl: this,
                linktext: this.getAttribute("data-href"),
            });
        });
        // context menu
        a.addEventListener('contextmenu', function(event) {
            if (!this?.getAttribute("data-href")) return false;
            this.closest("li").classList.add('menu-hover');
            const file = app.vault.getAbstractFileByPath(this.getAttribute("data-href"));
            const menu = new obsidian.Menu();
            menu.addItem((item) =>
            item.setSection('action')
                .setTitle('Open in new tab')
                .setIcon('file-plus')
                .onClick(() => {
                    const leaf = app.workspace.getLeaf('tab');
                    leaf.openFile(file);
                })
            );
            app.workspace.trigger(
                'file-menu',
                menu,
                file,
                'link-context-menu',
            );
            menu.showAtPosition({ x: event.clientX, y: event.clientY });
            menu.onHide(() => {
                document.querySelectorAll('.recently-modified-view li.menu-hover')?.forEach(item=>item?.classList.remove('menu-hover'));
            });
            // 阻止浏默认的右键菜单
		    return false;
        });
        return a;
    })
)

// 用于记录Ctrl键是否被按下
let isCtrlDown = false;
const handleKeyDown = function(event) {
    if (event.key === 'Control'|| event.key === 'Meta') {
        isCtrlDown = true;
    }
};
document.removeEventListener('keydown', handleKeyDown);
document.addEventListener('keydown', handleKeyDown);
const handleKeyUp = function(event) {
    if (event.key === 'Control'|| event.key === 'Meta') {
        isCtrlDown = false;
    }
};
document.removeEventListener('keyup', handleKeyUp);
document.addEventListener('keyup', handleKeyUp);
```

使用方法参考 1楼

效果

3 个赞

能按照日期分组不