Skip to content

Instantly share code, notes, and snippets.

@Moyf
Last active April 24, 2025 16:29
Show Gist options
  • Save Moyf/9c3c16ce4e72057c0f747ec06ff54107 to your computer and use it in GitHub Desktop.
Save Moyf/9c3c16ce4e72057c0f747ec06ff54107 to your computer and use it in GitHub Desktop.
[Obsidian] Dataviewjs code for Obsidian to query specific term in current note

最简版本:

```dataviewjs
await dv.view("queryTermInFile", { term: "💡" })
```

最多选项版本: [term::💡] [info::灵感摘录]

```dataviewjs
let term = dv.current().term;
if (!term) {
  dv.paragraph("未能获取 term 关键字");
  return;
}
await dv.view(
  "queryTermInFile", 
{
  term: term,
  bShowInfo: true,
  info: dv.current().info,
  bShowLink: true,
  bShowHeading: true,
  excludeTerms: []
})
```
// ========================================
// 作者:Moy
// 日期:2024.05.22
// 版本:1.0.4
// ========================================
class Query {
/**
* 用来查询的类
* @param {string} _term 需要查询的关键字
* @param {boolean} _bShowInfo 是否显示第一行的查询信息
* @param {string} _info 查询信息的内容
* @param {boolean} _bShowLink 是否显示跳转链接
* @param {boolean} _bShowHeading 是否显示标题
* @param {string[]} _excludeTerms 需要排除的关键字
*/
constructor(_term, _bShowInfo=true, _info="", _bShowLink=true, _bShowHeading=false, _excludeTerms=[]) {
this.term = (_term == "加粗") ? "**" : ((_term == "高亮") ? "==" : _term);
const { output, isMulti } = processMultiValues(this.term);
this.term = output;
this.isMultiTerm = isMulti;
// 正则的处理
this.reg = null;
if (this.term.startsWith("/") && this.term.endsWith("/")) {
// 判断 正则表达式是否合法
try {
console.log("判断到正则表达式:", this.term.substring(1, this.term.length-1));
this.reg = new RegExp(this.term.substring(1, this.term.length-1), "i");
} catch (error) {
this.term = "(错误的正则表达式)";
}
}
this.bShowInfo = _bShowInfo;
this.info = processMultiValues(_info).output;
this.bShowLink = _bShowLink;
this.bShowHeading = _bShowHeading;
this.excludeTerms = _excludeTerms;
// 链接的图标
this.linkIcon = "»";
// 链接的字号
this.fontSize = "1em";
}
/**
* 判断是否包含关键字(或符合正则表达式)
* @param {string} text 需要判断的文本
* @returns {boolean} 返回是否包含关键字(或符合正则表达式)
*/
Verify(text) {
// 过滤掉需要排除的关键字
if (this.excludeTerms.some(f => text.contains(f))){
return false;
}
// 过滤掉 term 和 dv.view 关键字,避免把检索代码也显示出来
if (text.contains("dv.view") || text.contains("term")){
return false;
}
// 匹配给定的关键字
if (this.reg) {
return this.reg.test(text);
} else {
return text.includes(this.term);
}
}
/**
* 获取标题
* @param {number} line 行号
* @returns {string} 返回标题
*/
FetchHeadings(headings, line) {
let heading = "";
let isLastHeading = true;
if (!headings || headings.length == 0) {
return heading;
}
for (let i = 1; i < headings.length; i++) {
// 判断在哪个标题内
if (headings[i].position.start.line > line) {
// console.log("标题:", headings[i-1].heading);
heading = headings[i-1].heading;
isLastHeading = false;
break;
}
}
if (isLastHeading) {
heading = headings[headings.length-1].heading;
}
return heading;
}
/**
* 获取并输出最终的显示结果
* @returns {string} 返回最终的显示结果
*/
async GetResult() {
const term = this.term;
const curFile = await dv.current().file;
const curFilePath = curFile.path;
const curFileName = curFile.name;
const curTFile = await app.vault.getFileByPath(curFilePath);
const fileCache = app.metadataCache.getFileCache(curTFile);
const headings = fileCache.headings;
if (!curTFile) {
dv.paragraph(`正在获取含 [${term}] 的行...`);
return;
}
// 利用 AdvURI 的方案
const encodedName = encodeURIComponent(curFileName);
const extraAttr = `style="font-size: ${this.fontSize}" title="跳转到对应行" `;
const linkPrefix = ` <a ${extraAttr} href="obsidian://advanced-uri?filename=${encodedName}&line=`;
const linkSuffix = `">${this.linkIcon}</a>`;
const noteContent = await app.vault.cachedRead(curTFile);
const lines = noteContent.split("\n")
// 先存成对象,保证原来的行号
.map((line, index) => ({ content: line.trim(), index }))
// 过滤
.filter( ( {content} ) => this.Verify(content))
// 后处理
.map(( {content, index} ) => {
// 处理原先的列表符号,避免多层嵌套
if (content.startsWith("- ") || content.startsWith("* ") || content.startsWith("+ ") ) {
content = content.substring(2);
}
const line = index+1;
// 添加标题
let heading = this.bShowHeading ? this.FetchHeadings(headings, line) : "";
// 添加跳转链接
const jumpLink = `${linkPrefix}${index+1}${linkSuffix}`;
return { content , jumpLink, heading };
});
if (this.bShowInfo) {
if (this.info){
dv.paragraph(`> ${this.info}:`);
} else {
if (this.reg) {
dv.paragraph(`> 正则匹配 ${this.term} 的行:`);
} else {
dv.paragraph(`> 包含 [${term}] 的行:`);
}
}
}
if (lines.length) {
// 直接调用 list 来显示,会导致样式渲染问题
// dv.list(lines.map( ({content, jumpLink}) => `${content} ${this.bShowLink ? jumpLink : ""}`));
// 为了兼容下划线啥的……避免被作为样式渲染
const divContainer = document.createElement('div');
const listContainer = document.createElement('ul', { cls: "dataview dataview-class", attr: { alt: "Nice!" }});
let lastHeading = "";
lines.forEach( ({content, jumpLink, heading}) => {
// 添加标题
if (heading != lastHeading) {
const headingContainer = document.createElement('div');
let isFirstLine = lastHeading == "";
lastHeading = heading;
headingContainer.innerHTML = (isFirstLine?"":"<br>") + `▌ ${lastHeading}`;
// 设置下划线
// headingContainer.style.textDecoration = "underline";
listContainer.appendChild(headingContainer);
}
// 手工实现列表 = =。
const itemContainer = document.createElement('li');
itemContainer.appendChild(dv.span(content))
// itemContainer.innerHTML = content;
if (this.bShowLink) {
const linkContainer = document.createElement('span');
linkContainer.innerHTML = jumpLink;
itemContainer.appendChild(linkContainer);
// itemContainer.innerHTML += jumpLink;
}
listContainer.appendChild(itemContainer);
});
divContainer.appendChild(listContainer);
dv.container.appendChild(divContainer);
return;
}
if (this.term == "(未定义)") {
dv.paragraph(`(请在笔记属性里输入有效的关键字,或者直接指定 \`term: 关键字\` )`);
return;
}
if (this.isMultiTerm) {
dv.paragraph(`*(检测到页面内存在多个关键字,仅显示第一项 [${this.term}] 的查询结果)*`);
} else {
dv.paragraph(`- 没有找到含 [${term}] 的行`);
}
}
}
function processMultiValues(input) {
/**
* 处理多个值的情况
* @param {string | string[]} input 输入的值
* @returns {object} 返回处理后的值和是否为多个值
*/
let output = input;
if (typeof(input) != "string" && input.length > 1) {
output = "(未定义)";
// 设为第一个非空的值
const filteredTerms = input.filter(t => t && t.trim() != "");
if (filteredTerms.length > 0) {
output = filteredTerms[0];
}
return {output, isMulti: true};
} else {
return {output, isMulti: false};
}
}
/* ---------------------------------------- */
// 运行代码
/* ---------------------------------------- */
console.log("🔍 Querying...");
let { term, bShowInfo, info, bShowLink, bShowHeading, excludeTerms } = input;
if (!term) term = "(未定义)";
let query = new Query( term, bShowInfo, info, bShowLink, bShowHeading, excludeTerms );
query.GetResult();
@Moyf
Copy link
Author

Moyf commented May 22, 2024

v1.0.2 更新

  • 支持正则表达式匹配
  • 合并了 filter 函数,优化匹配过程
  • 优化结果列表的渲染,避免被 _ 之类的奇奇怪怪字符影响链接显示效果

@Moyf
Copy link
Author

Moyf commented May 22, 2024

v1.0.3 更新

  • 支持显示文本所在的标题

@Moyf
Copy link
Author

Moyf commented May 22, 2024

v1.0.4 更新

  • 修复笔记内不存在任何标题时的报错问题

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment