|
// ======================================== |
|
// 作者: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(); |
v1.0.2 更新
_
之类的奇奇怪怪字符影响链接显示效果