好家伙!原来没留意——发帖还要扣分的,亏本分享
虽然对我来说,分有啥用啊~所以我更新了……
看到隔壁讨论的需求,标签搜索功能 - 链滴
本来想直接回复,字数超了

论坛有过分享的
[js] 大纲过滤器, 支持大部分面板的过滤 (大纲, 标签, 书签, 书签 +, 文件树, 反链) - 链滴
站在大佬肩膀上按个人需求完善了下,按需修改吧,我才学的 js
再次更新版,兼容了 Achuan 的【块引脚注插件更新 v1.4.1:新增脚注查看面板,支持查看文档的所有脚注 - 链滴】,但不确定是否存在其他兼容性问题,也不确定后续更新是否出现变化。
(() => {
// add filters for all各种过滤器
// 原作者版本https://ld246.com/article/1736828257787 https://github.com/leeyaunlong/siyuan_scripts/
// chuchen接力版:增加按钮循环控制,css优化,支持拼音模糊搜索:首字母或全拼
// 支持混合逻辑语法:'空格 '分割关键词(AND逻辑),'竖线|'分割关键词(OR逻辑),'英文感叹号!'开头的关键词(NOT逻辑),暂不支持转义
// 支持关键词高亮,支持Achuan大佬的最新的脚注面板https://ld246.com/article/1752516343833
// 支持按钮隐藏,在任意reset按钮连续右键3次恢复
// 待完善功能,支持闪卡过滤器,支持手机版
// ================= 配置区 =================
// 过滤器的基本配置,包括每个过滤器的id、占位符、目标选择器和插入位置
const FILTER_CONFIGS = [
{ id: 'tree_filter_container', placeholder: "文档树过滤器", selector: '.fn__flex-1.fn__flex-column.file-tree.sy__file', position: 'BEFORE_BEGIN' },
{ id: 'outline_filter_container', placeholder: "大纲过滤器", selector: '.fn__flex-1.fn__flex-column.file-tree.sy__outline', position: 'BEFORE_BEGIN' },
{ id: 'bmsp_filter_container', placeholder: "书签+过滤器", selector: '.fn__flex-1.b3-list.b3-list--background.custom-bookmark-body', position: 'BEFORE_END' },
{ id: 'tags_filter_container', placeholder: "标签过滤器", selector: '.fn__flex-1.fn__flex-column.file-tree.sy__tag', position: 'APPEND' },
{ id: 'bms_filter_container', placeholder: "书签过滤器", selector: '.fn__flex-1.fn__flex-column.file-tree.sy__bookmark', position: 'APPEND' },
{ id: 'backlink_filter_container', placeholder: "反链过滤器", selector: '.fn__flex-1.fn__flex-column.file-tree.sy__backlink', position: 'BEFORE_BEGIN' },
{ id: 'footnote_filter_container', placeholder: "脚注过滤器", selector: '.footnote-dock__content', position: 'INSERT_BEFORE' },
// { id: 'flashcard_filter_container', placeholder: "闪卡过滤器", selector: '[data - key= "dialog-viewcards"] .fn__flex - column', position: 'APPEND' },
];
// ================= 样式注入 =================
// 高亮样式
addStyle(`
.filter_highlight {
background-color: var(--b3-card-success-background) !important;
}
`);
// =============== 新增:按钮显示控制参数 ===============
const showFilterButtonDefault = true;
const SIYUAN_APP_ID = 'custom-snippet';
const SIYUAN_KEY = 'myPlugin_showFilterButton';
// =============== 思源本地存储API封装(带Token) ===============
async function setSiyuanLocalStorageItem(key, value, appId) {
const token = localStorage.getItem('authToken') || '';
const requestBody = {
key: key,
val: value, // 直接用布尔值
app: appId
};
const response = await fetch('/api/storage/setLocalStorageVal', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Token ${token}`
},
body: JSON.stringify(requestBody)
});
return response.json();
}
async function getSiyuanLocalStorageItem(key, appId) {
const token = localStorage.getItem('authToken') || '';
const requestBody = {
app: appId
};
const response = await fetch('/api/storage/getLocalStorage', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Token ${token}`
},
body: JSON.stringify(requestBody)
});
const result = await response.json();
if (result.code === 0 && result.data && typeof result.data === 'object') {
let val = result.data[key];
if (val === undefined) return null;
if (val === false || val === 'false') return false;
if (val === true || val === 'true') return true;
return val;
}
return null;
}
// ================= 工具函数区 =================
// 判断是否为移动端
function isMobile() {
return !!document.getElementById("sidebar");
}
// 等待某个元素出现,常用于异步加载场景
function whenElementExist(selector, node = document, timeout = 5000) {
return new Promise((resolve, reject) => {
const start = Date.now();
function check() {
let el;
try {
el = typeof selector === 'function' ? selector() : node.querySelector(selector);
} catch (err) {
return reject(err);
}
if (el) {
resolve(el);
} else if (Date.now() - start >= timeout) {
reject(new Error(`Timed out after ${timeout}ms waiting for element ${selector}`));
} else {
requestAnimationFrame(check);
}
}
check();
});
}
// 等待所有匹配元素出现
function whenAllElementsExist(selector) {
return new Promise(resolve => {
const check = () => {
const elements = document.querySelectorAll(selector);
if (elements.length > 0) resolve(Array.from(elements));
else requestAnimationFrame(check);
};
check();
});
}
// 动态插入样式
function addStyle(css) {
const style = document.createElement('style');
style.innerHTML = css;
document.head.appendChild(style);
}
// 防抖函数,减少高频事件触发
function debounce(fn, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// ================= 过滤器核心逻辑区 =================
// 动态加载拼音库,支持拼音模糊搜索
const loadPinyin = new Promise((resolve) => {
if (window.pinyinPro) return resolve(window.pinyinPro);
const sources = [
'https://cdn.bootcdn.net/ajax/libs/pinyin-pro/3.26.0/index.min.js',
'https://unpkg.com/pinyin-pro@3.18.2/dist/index.js',
'https://cdn.jsdelivr.net/npm/pinyin-pro@3.18.2/dist/index.js',
'/plugins/myFonts/pinyin-pro.min.js'
];
let retryCount = 0;
const tryNextSource = () => {
if (retryCount >= sources.length) {
console.warn('代码片段-过滤器-所有拼音源加载失败,启用纯文本过滤');
return resolve(null);
}
const script = document.createElement('script');
script.src = sources[retryCount];
script.onload = () => resolve(window.pinyinPro);
script.onerror = () => {
retryCount++;
tryNextSource();
};
document.head.appendChild(script);
};
tryNextSource();
});
// 创建并插入过滤器输入框及其事件
function addFilterBox({ id, placeholder, selector, position }) {
removeExistingInput(id); // 确保先移除旧输入框,避免重复绑定
const container = createFilterContainer(id, placeholder);
insertInputContainer(container, selector, position);
if (document.querySelector(selector + '.fn__none')) {
container.remove();
}
const input = container.querySelector('input');
const resetButton = container.querySelector('button.reset');
const closeButton = container.querySelector('button.close');
// 使用防抖,减少高频输入时的DOM操作
input.addEventListener('input', debounce(async function () {
await handleInputEvent(input, selector, id);
}, 150));
resetButton.addEventListener('click', function () {
input.value = '';
resetFilterDisplay(selector);
});
closeButton.addEventListener('click', function () {
input.value = '';
resetFilterDisplay(selector);
container.remove();
});
}
// 移除已存在的输入框,避免重复插入
function removeExistingInput(id) {
const existingInput = document.getElementById(id);
if (existingInput) existingInput.remove();
}
// 创建输入框容器和按钮
function createFilterContainer(id, placeholder) {
const container = document.createElement('div');
container.id = id;
container.style.display = 'flex';
container.style.alignItems = 'center';
const input = document.createElement('input');
input.id = 'filter_input' + id;
input.type = 'text';
input.placeholder = placeholder;
input.style.flex = '1';
input.style.minWidth = 0;
input.style.borderWidth = '1px';
input.style.borderStyle = 'dashed';
input.style.borderColor = 'var(--b3-theme-on-surface)';
input.style.backgroundColor = 'var(--b3-theme-surface)';
input.style.color = 'var(--b3-theme-on-background)';
input.className = 'b3-text-field fn__block';
const resetButton = document.createElement('button');
resetButton.textContent = '↺';
resetButton.className = 'reset';
resetButton.style.color = 'var(--color-text-3)';
resetButton.style.fontWeight = "900";
resetButton.style.cursor = 'pointer';
resetButton.style.backgroundColor = 'var(--b3-theme-surface-light)';
resetButton.style.borderWidth = '1px';
resetButton.style.borderStyle = 'dotted';
resetButton.style.borderColor = 'var(--b3-theme-on-surface)';
resetButton.style.marginRight = '3px';
// =============== 新增:reset右键连续3次恢复按钮 ===============
let resetRightClickCount = 0;
resetButton.addEventListener('contextmenu', function (e) {
getSiyuanLocalStorageItem(SIYUAN_KEY, SIYUAN_APP_ID).then(val => {
if (val === false || val === 'false') {
e.preventDefault();
resetRightClickCount++;
if (resetRightClickCount >= 3) {
setSiyuanLocalStorageItem(SIYUAN_KEY, true, SIYUAN_APP_ID).then(() => {
location.reload();
});
}
setTimeout(() => { resetRightClickCount = 0; }, 1500);
}
});
});
const closeButton = document.createElement('button');
closeButton.textContent = '⨉';
closeButton.className = 'close';
closeButton.style.color = 'var(--color-text-3)';
closeButton.style.fontWeight = "900";
closeButton.style.cursor = 'pointer';
closeButton.style.backgroundColor = 'var(--b3-theme-surface-light)';
closeButton.style.borderWidth = '1px';
closeButton.style.borderStyle = 'dotted';
closeButton.style.borderColor = 'var(--b3-theme-on-surface)';
closeButton.style.marginLeft = '3px';
container.appendChild(resetButton);
container.appendChild(input);
container.appendChild(closeButton);
return container;
}
// 根据配置插入输入框到指定位置
function insertInputContainer(container, selector, position) {
const targetElement = document.querySelector(selector);
if (targetElement) {
if (position === 'BEFORE') {
targetElement.parentElement.insertBefore(container, targetElement);
} else if (position === 'BEFORE_BEGIN') {
targetElement.parentElement.insertAdjacentElement('beforeBegin', container);
} else if (position === 'APPEND') {
targetElement.appendChild(container);
} else if (position === 'BEFORE_END') {
targetElement.parentElement.insertAdjacentElement('beforeEnd', container);
} else if (position === 'PREPEND') {
targetElement.prepend(container);
} else if (position === 'AFTER_BEGIN') {
targetElement.parentElement.insertAdjacentElement('afterBegin', container);
} else if (position === 'INSERT_BEFORE') {
targetElement.insertAdjacentElement('beforeBegin', container);
}
}
}
// 重置过滤器显示,恢复所有项
function resetFilterDisplay(selector) {
// 1. 普通面板
const spans1 = document.querySelectorAll(selector + ' li span.b3-list-item__text.ariaLabel');
spans1.forEach(span => {
const listItem = span.parentElement;
listItem.style.display = '';
if (span.dataset.originalText) {
span.textContent = span.dataset.originalText;
} else {
span.innerHTML = span.textContent; // 移除高亮
}
});
// 2. 脚注面板
const spans2 = [
...document.querySelectorAll(selector + ' .protyle-wysiwyg[contenteditable="true"] div[contenteditable="true"]'),
...document.querySelectorAll(selector + ' .footnote-item span')
];
spans2.forEach(span => {
if (span.parentElement && span.parentElement.style) {
span.parentElement.style.display = '';
}
// 只对纯文本节点移除高亮
if (span.childNodes.length === 1 && span.childNodes[0].nodeType === 3) {
span.innerHTML = span.textContent;
}
});
}
// 处理输入事件,执行过滤和高亮(批量DOM操作优化)
async function handleInputEvent(input, selector, id) {
const filterText = input.value.toLowerCase();
const orGroups = filterText.split('|').map(group =>
group.split(' ').filter(k => k.trim()).map(term => ({
value: term.replace(/^!/, ''),
isNot: term.startsWith('!')
}))
);
// 针对不同容器id,采用不同的节点获取方式
let spans;
if (id === 'backlink_filter_container') {
spans = [
...document.querySelectorAll(selector + ' .p[contenteditable="true"]'),
...document.querySelectorAll(selector + ' li span.b3-list-item__text.ariaLabel')
];
} else if (id === 'footnote_filter_container') {
// 专项优化:整体过滤+原文高亮+拼音缓存
const items = document.querySelectorAll('.footnote-item');
const pinyinCache = new Map();
function getPinyin(text, pinyin) {
if (pinyinCache.has(text)) return pinyinCache.get(text);
const initials = pinyin.pinyin(text, { pattern: 'first', type: 'array', toneType: 'none', multiple: true }).join('').toLowerCase();
const full = pinyin.pinyin(text, { pattern: 'pinyin', type: 'array', toneType: 'none', multiple: true }).join('').toLowerCase();
const result = { initials, full };
pinyinCache.set(text, result);
return result;
}
try {
const pinyin = await loadPinyin;
if (!pinyin?.pinyin) throw new Error('代码片段-过滤器拼音库初始化失败');
const highlightTerms = orGroups.flat().map(({ value }) => value).filter(Boolean);
items.forEach(item => {
const text = item.innerText;
const lowerText = text.toLowerCase();
const { initials: pinyinInitials, full: pinyinFull } = getPinyin(text, pinyin);
const matchAnyGroup = orGroups.some(group =>
group.every(({ value, isNot }) => {
const hasMatch = [
lowerText.includes(value),
pinyinInitials.includes(value),
pinyinFull.includes(value)
].some(Boolean);
return isNot ? !hasMatch : hasMatch;
})
);
item.style.display = matchAnyGroup ? '' : 'none';
// 只对原文命中做高亮
if (matchAnyGroup) {
const spans = item.querySelectorAll('div[contenteditable="true"]');
spans.forEach(span => {
const spanText = span.textContent;
if (!filterText) {
// 清空时还原原始HTML
if (span.dataset.originalHtml) {
span.innerHTML = span.dataset.originalHtml;
delete span.dataset.originalHtml;
}
} else if (span.childNodes.length === 1 && span.childNodes[0].nodeType === 3) {
// 高亮前缓存原始HTML
if (!span.dataset.originalHtml) {
span.dataset.originalHtml = span.innerHTML;
}
let innerHTML = spanText;
highlightTerms.forEach(value => {
if (value) {
const reg = new RegExp(value, 'gi');
innerHTML = innerHTML.replace(reg, match => `<span class=\"filter_highlight\">${match}</span>`);
}
});
span.innerHTML = innerHTML;
}
});
}
});
} catch (e) {
// 降级为纯文本过滤+高亮
items.forEach(item => {
const text = item.innerText;
const lowerText = text.toLowerCase();
const matchAnyGroup = orGroups.some(group =>
group.every(({ value, isNot }) => {
const hasMatch = lowerText.includes(value);
return isNot ? !hasMatch : hasMatch;
})
);
item.style.display = matchAnyGroup ? '' : 'none';
if (matchAnyGroup) {
const spans = item.querySelectorAll('div[contenteditable="true"]');
spans.forEach(span => {
const spanText = span.textContent;
if (!filterText) {
if (span.dataset.originalHtml) {
span.innerHTML = span.dataset.originalHtml;
delete span.dataset.originalHtml;
}
} else if (span.childNodes.length === 1 && span.childNodes[0].nodeType === 3) {
if (!span.dataset.originalHtml) {
span.dataset.originalHtml = span.innerHTML;
}
let innerHTML = spanText;
orGroups.flat().forEach(({ value }) => {
if (value) {
const reg = new RegExp(value, 'gi');
innerHTML = innerHTML.replace(reg, match => `<span class=\"filter_highlight\">${match}</span>`);
}
});
span.innerHTML = innerHTML;
}
});
}
});
}
return;
} else {
spans = document.querySelectorAll(selector + ' li span.b3-list-item__text.ariaLabel');
}
try {
const pinyin = await loadPinyin;
if (!pinyin?.pinyin) throw new Error('代码片段-过滤器拼音库初始化失败');
const highlightTerms = orGroups.flatMap(group =>
group.filter(term => !term.isNot).map(term => term.value)
);
// 批量DOM操作:先收集所有变更,再统一apply
const updates = [];
spans.forEach(span => {
const originalText = span.dataset.originalText || span.textContent;
if (!span.dataset.originalText) {
span.dataset.originalText = originalText;
}
const listItem = span.parentElement;
const text = span.textContent;
const pinyinInitials = pinyin.pinyin(text, {
pattern: 'first', type: 'array', toneType: 'none', multiple: true
}).join('').toLowerCase();
const pinyinFull = pinyin.pinyin(text, {
pattern: 'pinyin', type: 'array', toneType: 'none', multiple: true
}).join('').toLowerCase();
const matchAnyGroup = orGroups.some(group => {
return group.every(({ value, isNot }) => {
const hasMatch = [
text.toLowerCase().includes(value),
pinyinInitials.includes(value),
pinyinFull.includes(value)
].some(Boolean);
return isNot ? !hasMatch : hasMatch;
});
});
updates.push({
span,
listItem,
matchAnyGroup,
originalText,
highlightTerms
});
});
// 统一apply
updates.forEach(({span, listItem, matchAnyGroup, originalText, highlightTerms}) => {
span.textContent = originalText;
listItem.style.display = matchAnyGroup ? '' : 'none';
if (matchAnyGroup && highlightTerms.length > 0) {
span.innerHTML = '';
span.appendChild(buildHighlightFragment(originalText, pinyin, highlightTerms));
}
});
} catch (e) {
const highlightTerms = orGroups.flatMap(group =>
group.filter(term => !term.isNot).map(term => term.value)
);
// 批量DOM操作:先收集所有变更,再统一apply
const updates = [];
spans.forEach(span => {
const originalText = span.dataset.originalText || span.textContent;
updates.push({span, originalText});
});
updates.forEach(({span, originalText}) => {
span.textContent = originalText;
});
spans.forEach(span => {
fallbackHighlightSpan(span, orGroups, highlightTerms);
});
}
}
// 高亮匹配项(支持拼音)
function highlightSpan(span, pinyin, orGroups, highlightTerms) {
const originalText = span.dataset.originalText || span.textContent;
span.textContent = originalText;
if (!span.dataset.originalText) {
span.dataset.originalText = originalText;
}
const listItem = span.parentElement;
const text = span.textContent;
const pinyinInitials = pinyin.pinyin(text, {
pattern: 'first', type: 'array', toneType: 'none', multiple: true
}).join('').toLowerCase();
const pinyinFull = pinyin.pinyin(text, {
pattern: 'pinyin', type: 'array', toneType: 'none', multiple: true
}).join('').toLowerCase();
const matchAnyGroup = orGroups.some(group => {
return group.every(({ value, isNot }) => {
const hasMatch = [
text.toLowerCase().includes(value),
pinyinInitials.includes(value),
pinyinFull.includes(value)
].some(Boolean);
return isNot ? !hasMatch : hasMatch;
});
});
listItem.style.display = matchAnyGroup ? '' : 'none';
if (matchAnyGroup && highlightTerms.length > 0) {
span.innerHTML = '';
span.appendChild(buildHighlightFragment(originalText, pinyin, highlightTerms));
}
}
// 构建高亮片段
function buildHighlightFragment(originalText, pinyin, highlightTerms) {
const fragment = document.createDocumentFragment();
let lastIndex = 0;
const pinyinMap = originalText.split('').map((char, index) => ({
char: char,
initials: pinyin.pinyin(char, { pattern: 'first', toneType: 'none' })[0]?.toLowerCase() || '',
index: index
}));
const matchPositions = new Set();
const lowerOriginal = originalText.toLowerCase();
highlightTerms.forEach(term => {
const lowerTerm = term.toLowerCase();
let pos = -1;
while ((pos = lowerOriginal.indexOf(lowerTerm, pos + 1)) !== -1) {
for (let i = 0; i < term.length; i++) {
matchPositions.add(pos + i);
}
}
pinyinMap.forEach((p, index) => {
if (p.initials === lowerTerm) {
matchPositions.add(index);
}
});
const initialsSequence = pinyinMap.map(p => p.initials).join('');
if (/^[a-z]{2,}$/.test(lowerTerm)) {
let pos = -1;
while ((pos = initialsSequence.indexOf(lowerTerm, pos + 1)) !== -1) {
for (let i = 0; i < lowerTerm.length; i++) {
const charIndex = pos + i;
if (charIndex < pinyinMap.length) {
matchPositions.add(pinyinMap[charIndex].index);
}
}
}
}
});
const sortedPositions = Array.from(matchPositions).sort((a, b) => a - b);
const regexParts = [];
let currentStart = sortedPositions[0];
let currentEnd = currentStart + 1;
for (let i = 1; i < sortedPositions.length; i++) {
if (sortedPositions[i] === currentEnd) {
currentEnd++;
} else {
regexParts.push(originalText.slice(currentStart, currentEnd));
currentStart = sortedPositions[i];
currentEnd = currentStart + 1;
}
}
if (sortedPositions.length > 0) {
regexParts.push(originalText.slice(currentStart, currentEnd));
}
const regexPattern = regexParts
.map(str => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
.join('|');
if (regexPattern) {
const regex = new RegExp(`(${regexPattern})`, 'gi');
let match;
while ((match = regex.exec(originalText)) !== null) {
if (match.index > lastIndex) {
fragment.appendChild(document.createTextNode(
originalText.slice(lastIndex, match.index)
));
}
const highlight = document.createElement('span');
highlight.className = 'filter_highlight';
highlight.textContent = match[0];
fragment.appendChild(highlight);
lastIndex = regex.lastIndex;
}
}
if (lastIndex < originalText.length) {
fragment.appendChild(document.createTextNode(
originalText.slice(lastIndex)
));
}
return fragment;
}
// 降级高亮处理(无拼音库时)
function fallbackHighlightSpan(span, orGroups, highlightTerms) {
const originalText = span.dataset.originalText || span.textContent;
span.textContent = originalText;
const text = span.textContent.toLowerCase();
const matchAnyGroup = orGroups.some(group =>
group.every(({ value, isNot }) => {
const hasMatch = text.includes(value);
return isNot ? !hasMatch : hasMatch;
})
);
span.parentElement.style.display = matchAnyGroup ? '' : 'none';
if (matchAnyGroup && highlightTerms.length > 0) {
let innerHTML = originalText;
highlightTerms.forEach(term => {
const reg = new RegExp(term, 'gi');
innerHTML = innerHTML.replace(reg, match => `<span class="filter_highlight">${match}</span>`);
});
span.innerHTML = innerHTML;
}
}
// ================= 监听与交互区 =================
// 监听tab切换,动态刷新过滤器
let tabObservers = [];
let layoutDockObserver = null; // 全局唯一observer,避免重复创建
function startTabObserver(parentSelector, activeClass, observerArray, callback) {
if (observerArray.length > 0) return;
whenAllElementsExist(parentSelector).then((parentElements) => {
parentElements.forEach((parentElement) => {
const observer = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => {
if (node.classList?.contains(activeClass)) {
callback();
}
});
}
if (mutation.type === 'attributes' && mutation.target.classList?.contains(activeClass)) {
callback();
}
}
});
observer.observe(parentElement, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['class']
});
observerArray.push(observer);
});
}).catch(() => {
const documentObserver = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.target.classList?.contains(activeClass)) {
callback();
}
}
});
documentObserver.observe(document, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['class']
});
observerArray.push(documentObserver);
});
}
// 停止tab监听
function stopTabObserver(observerArray) {
observerArray.forEach(observer => observer.disconnect());
observerArray.length = 0;
}
// 添加dock按钮,控制过滤器开关
function addButton(pin) {
let flag = false;
const button = document.createElement('span');
button.className = 'dock__item ariaLabel';
button.textContent = '🕸️';
button.setAttribute('aria-label', ' 关闭筛选过滤器\n 右击可隐藏按钮');
button.onclick = (event) => {
event.preventDefault();
event.stopPropagation();
removeAllFilters();
if (flag) {
addAllFilters();
startTabObserver('.layout-tab-container.fn__flex-1', 'layout__tab--active', tabObservers, addAllFilters);
} else {
stopTabObserver(tabObservers);
}
flag = !flag;
button.setAttribute('aria-label', flag ? ' 开启筛选过滤器\n 右击可隐藏按钮' : ' 关闭筛选过滤器\n 右击可隐藏按钮');
button.textContent = flag ? '🕸︎' : '🕸️';
};
button.addEventListener('contextmenu', (e) => {
e.preventDefault();
if (confirm(' 隐藏过滤器按钮?\n可通过reset按钮连续右键3次恢复')) {
setSiyuanLocalStorageItem(SIYUAN_KEY, false, SIYUAN_APP_ID).then(() => {
button.remove();
});
}
});
pin.before(button);
}
// 注册清理函数,卸载时自动清理所有过滤器和监听
function registerCleanup() {
const cleanup = () => {
removeAllFilters();
stopTabObserver(tabObservers);
// 断开全局唯一observer,防止内存泄漏
if (layoutDockObserver) {
layoutDockObserver.disconnect();
layoutDockObserver = null;
}
document.querySelectorAll('.dock__item[aria-label*="筛选过滤器"]').forEach(btn => btn.remove());
document.querySelectorAll('style[data-style="filter_highlight"]').forEach(style => style.remove());
};
const currentScript = document.currentScript?.src;
if (!currentScript) {
console.error('无法确定当前脚本路径,loadSnippets后续清理函数未注册。');
return;
}
const currentFilename = currentScript.split('/').pop();
if (window.__registerCleanupHandler) {
window.__registerCleanupHandler(currentFilename, cleanup);
} else {
console.error('清理注册接口不可用');
}
}
// ================= 主流程区 =================
// 批量添加所有过滤器
function addAllFilters() {
FILTER_CONFIGS.forEach(config => {
addFilterBox(config);
});
}
// 批量移除所有过滤器
function removeAllFilters() {
FILTER_CONFIGS.forEach(config => {
const fCs = document.getElementById(config.id);
if (fCs) {
fCs.remove();
}
});
}
// 主入口,初始化所有功能
async function main() {
if (isMobile()) return;
whenElementExist('#dockLeft .dock__items .dock__item--pin').then(async (pin) => {
let show = await getSiyuanLocalStorageItem(SIYUAN_KEY, SIYUAN_APP_ID);
if (show === null) show = showFilterButtonDefault;
if (show === true || show === 'true') addButton(pin);
addAllFilters();
startTabObserver('.layout-tab-container.fn__flex-1', 'layout__tab--active', tabObservers, addAllFilters);
});
// 监听闪卡弹窗,弹出时自动添加过滤器
const targetBody = document.querySelector('body');
const config2 = { childList: true, subtree: true };
const callback2 = function (mutationsList, observer) {
for (let mutation of mutationsList) {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE && node.matches('[data-key="dialog-viewcards"].b3-dialog--open')) {
addAllFilters();
}
});
}
}
};
// 保证全局唯一observer,避免重复创建
if (layoutDockObserver) {
layoutDockObserver.disconnect();
}
layoutDockObserver = new MutationObserver(callback2);
layoutDockObserver.observe(targetBody, config2);
registerCleanup(); // 每次都注册清理,防止泄漏
}
// 立即执行主流程
main();
})();
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于