求一个功能,
一个目标列表中有多个子目标, 当子目标全部勾选后, 父目标自动勾选,
当子目标取消勾选后, 父目标也自动取消勾选
支持多层级目标列表, 即当三级目标列表完成后, 二级目标列表自动勾选, 当此二级目标列表为最后一个未勾选的, 这一级目标列表自动勾选
求一个功能,
一个目标列表中有多个子目标, 当子目标全部勾选后, 父目标自动勾选,
当子目标取消勾选后, 父目标也自动取消勾选
支持多层级目标列表, 即当三级目标列表完成后, 二级目标列表自动勾选, 当此二级目标列表为最后一个未勾选的, 这一级目标列表自动勾选
思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。
融合块、大纲和双向链接,重构你的思维。
代码片段分为 CSS 与 JS 两种代码,添加在 [设置 - 外观 - 代码片段] 中,这些代码会在思源笔记加载时自动执行,用于改善笔记的样式或功能。
用户在该标签下分享代码片段时需在帖子标题前添加 [css] 或 [js] 用于区分代码片段类型。
提问之前请先看《提问的智慧》,好的问题比好的答案更有价值。
试试这个

/*
// 自动勾选取消父任务
see https://ld246.com/article/1755052956165
version 0.0.2
0.0.2 增加新需求
需求:
一个任务列表中有多个子任务, 当子任务全部勾选后, 父任务自动勾选,
当子任务有一个取消勾选后, 父任务也自动取消勾选
支持多层级任务列表, 比如,当三级任务列表都勾选后, 二级任务列表自动勾选,
当此二级任务列表为最后一个未勾选的, 那么这第一级任务列表也自动勾选
新增需求:
1. 当一个已完成的父任务新增一个子任务时,由于新子任务默认是未完成(未勾选),所以父任务应自动取消勾选。
2. 当父任务下只剩一个未完成子任务时,若删除该子任务,则父任务应自动变为已完成(勾选)。
*/
setTimeout(() => {
const container = document.querySelector('.layout__center, #editor');
if (!container) return;
container.addEventListener('click', async (e) => {
if (e.altKey || e.shiftKey || e.ctrlKey || e.metaKey || !e.target.closest('.protyle-action--task')) return;
await new Promise(resolve => setTimeout(resolve, 50));
// 获取祖先元素并模拟点击
const item = e.target.closest('[data-subtype="t"][data-type="NodeListItem"]');
clickParentItems(item);
}, true);
// 观察 container 的子树变化(基于规律的处理,可能有bug)
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
// 处理新增的子任务
for (const node of mutation.addedNodes) {
if (node.nodeType !== 1) continue;
if (node.matches('div[data-type="NodeListItem"][data-subtype="t"]')) {
clickParentItems(node);
}
else if (node.matches('div[data-type="NodeList"][data-subtype="t"]')) {
const items = node.querySelectorAll('div[data-type="NodeListItem"][data-subtype="t"]');
items.forEach(item => clickParentItems(item));
} else {
const items = node.querySelectorAll('div[data-type="NodeListItem"][data-subtype="t"]');
if (items.length) items.forEach(item => clickParentItems(item));
}
}
// 处理删除的子任务
for (const node of mutation.removedNodes) {
if (node.nodeType !== 1) continue;
if (node.matches('div[data-type="NodeListItem"][data-subtype="t"]')) {
clickParentItems(mutation.target.querySelector('div[data-type="NodeListItem"][data-subtype="t"]'));
}
}
}
});
observer.observe(container, { childList: true, subtree: true, attributes: false });
function clickParentItems(item) {
const parentItems = getParentsItems(item);
if (parentItems.length === 0) return;
for (const item of parentItems) {
const list = item?.closest('[data-type="NodeList"][data-subtype="t"]');
const hasUnDone = list?.querySelector(':scope > [data-subtype="t"][data-type="NodeListItem"] > .protyle-action--task use[*|href="#iconUncheck"]');
const parentItem = item.parentElement?.closest('[data-type="NodeListItem"][data-subtype="t"]');
const parentTaskCheck = parentItem?.querySelector('.protyle-action--task');
const parentIsChecked = parentTaskCheck?.querySelector('use[*|href="#iconUncheck"]') ? false : true;
// 当存在不可勾选的任务项目时,终止,不再向上级遍历,如果你想继续遍历可把此处的return改为continue
if (!parentTaskCheck) return;
if (hasUnDone) {
// 当有未完成的子任务时,如果父任务已勾选需求要取消
if (parentIsChecked) parentTaskCheck.click();
} else {
// 当全部勾选了子任务时,如果父任务未勾选则勾选
if (!parentIsChecked) parentTaskCheck.click();
}
}
}
// 获取祖先任务项
function getParentsItems(item, includeSelf = true) {
if (!item) return [];
const items = [];
// 是否包含自己
if (includeSelf) {
items.push(item);
}
// 向上遍历所有符合条件的祖先 NodeListItem
let current = item;
while (true) {
const parentItem = current.parentElement?.closest('[data-subtype="t"][data-type="NodeListItem"]');
if (!parentItem) break;
items.push(parentItem);
current = parentItem;
}
return items;
}
}, 2000);
题外话:
今天发生了一件奇怪的事,我写代码时看了下时间 6:08,写完,又看了下时间还是 6:08,秒没注意。
难道刚刚时间静止了?之前也出现过这种错觉。
好吧,如果以后写代码时都能时间静止就好了,就可以无敌了
后人有诗叹曰
:
屏光冷映键飞忙,
回看时针竟未航。
若得封时锁宙宇,
Deadline 前自徜徉。
试试这个

/*
// 自动勾选取消父任务
see https://ld246.com/article/1755052956165
version 0.0.2
0.0.2 增加新需求
需求:
一个任务列表中有多个子任务, 当子任务全部勾选后, 父任务自动勾选,
当子任务有一个取消勾选后, 父任务也自动取消勾选
支持多层级任务列表, 比如,当三级任务列表都勾选后, 二级任务列表自动勾选,
当此二级任务列表为最后一个未勾选的, 那么这第一级任务列表也自动勾选
新增需求:
1. 当一个已完成的父任务新增一个子任务时,由于新子任务默认是未完成(未勾选),所以父任务应自动取消勾选。
2. 当父任务下只剩一个未完成子任务时,若删除该子任务,则父任务应自动变为已完成(勾选)。
*/
setTimeout(() => {
const container = document.querySelector('.layout__center, #editor');
if (!container) return;
container.addEventListener('click', async (e) => {
if (e.altKey || e.shiftKey || e.ctrlKey || e.metaKey || !e.target.closest('.protyle-action--task')) return;
await new Promise(resolve => setTimeout(resolve, 50));
// 获取祖先元素并模拟点击
const item = e.target.closest('[data-subtype="t"][data-type="NodeListItem"]');
clickParentItems(item);
}, true);
// 观察 container 的子树变化(基于规律的处理,可能有bug)
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
// 处理新增的子任务
for (const node of mutation.addedNodes) {
if (node.nodeType !== 1) continue;
if (node.matches('div[data-type="NodeListItem"][data-subtype="t"]')) {
clickParentItems(node);
}
else if (node.matches('div[data-type="NodeList"][data-subtype="t"]')) {
const items = node.querySelectorAll('div[data-type="NodeListItem"][data-subtype="t"]');
items.forEach(item => clickParentItems(item));
} else {
const items = node.querySelectorAll('div[data-type="NodeListItem"][data-subtype="t"]');
if (items.length) items.forEach(item => clickParentItems(item));
}
}
// 处理删除的子任务
for (const node of mutation.removedNodes) {
if (node.nodeType !== 1) continue;
if (node.matches('div[data-type="NodeListItem"][data-subtype="t"]')) {
clickParentItems(mutation.target.querySelector('div[data-type="NodeListItem"][data-subtype="t"]'));
}
}
}
});
observer.observe(container, { childList: true, subtree: true, attributes: false });
function clickParentItems(item) {
const parentItems = getParentsItems(item);
if (parentItems.length === 0) return;
for (const item of parentItems) {
const list = item?.closest('[data-type="NodeList"][data-subtype="t"]');
const hasUnDone = list?.querySelector(':scope > [data-subtype="t"][data-type="NodeListItem"] > .protyle-action--task use[*|href="#iconUncheck"]');
const parentItem = item.parentElement?.closest('[data-type="NodeListItem"][data-subtype="t"]');
const parentTaskCheck = parentItem?.querySelector('.protyle-action--task');
const parentIsChecked = parentTaskCheck?.querySelector('use[*|href="#iconUncheck"]') ? false : true;
// 当存在不可勾选的任务项目时,终止,不再向上级遍历,如果你想继续遍历可把此处的return改为continue
if (!parentTaskCheck) return;
if (hasUnDone) {
// 当有未完成的子任务时,如果父任务已勾选需求要取消
if (parentIsChecked) parentTaskCheck.click();
} else {
// 当全部勾选了子任务时,如果父任务未勾选则勾选
if (!parentIsChecked) parentTaskCheck.click();
}
}
}
// 获取祖先任务项
function getParentsItems(item, includeSelf = true) {
if (!item) return [];
const items = [];
// 是否包含自己
if (includeSelf) {
items.push(item);
}
// 向上遍历所有符合条件的祖先 NodeListItem
let current = item;
while (true) {
const parentItem = current.parentElement?.closest('[data-subtype="t"][data-type="NodeListItem"]');
if (!parentItem) break;
items.push(parentItem);
current = parentItem;
}
return items;
}
}, 2000);
题外话:
今天发生了一件奇怪的事,我写代码时看了下时间 6:08,写完,又看了下时间还是 6:08,秒没注意。
难道刚刚时间静止了?之前也出现过这种错觉。
好吧,如果以后写代码时都能时间静止就好了,就可以无敌了
后人有诗叹曰
:
屏光冷映键飞忙,
回看时针竟未航。
若得封时锁宙宇,
Deadline 前自徜徉。
正如楼上 @Fighter93 大佬所说,这两个新增需求,让复杂度陡然上升。
列表结构本身就复杂,再加上动态增加,删除,粘贴,移动等操作,让复杂度陡增。
以下代码基于规律的实现,进行了简单测试,复杂时可能会有 bug。
请根据自己的使用场景测试无误后使用,由此产生的任何后果,作者不承担任何责任。
0.0.2 版更新内容如下:
新增需求:
代码已在之前的帖子中更新。
还有一种实现方式,暴力法,即只要任务结点发生变化,均扫描一遍任务项进行判断,但这种方法,也有缺陷,即当用户手动取消勾选后,当别的任务修改时,可能会根据规律又自动勾选。由于这种方式实现简单,可借助 ai 实现,我测试时,已分别用 deepseek 和 chatgpt 成功实现,如需要这种的,请自行咨询 ai。
jsoup 是一款 Java 的 HTML 解析器,可直接解析某个 URL 地址、HTML 文本内容。它提供了一套非常省力的 API,可通过 DOM,CSS 以及类似于 jQuery 的操作方法来取出和操作数据。
Lute 是一款结构化的 Markdown 引擎,支持 Go 和 JavaScript。
uTools 是一个极简、插件化、跨平台的现代桌面软件。通过自由选配丰富的插件,打造你得心应手的工具集合。
OpenStack 是一个云操作系统,通过数据中心可控制大型的计算、存储、网络等资源池。所有的管理通过前端界面管理员就可以完成,同样也可以通过 Web 接口让最终用户部署资源。
Elasticsearch 是一个基于 Lucene 的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于 RESTful 接口。Elasticsearch 是用 Java 开发的,并作为 Apache 许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。
单点登录(Single Sign On)是目前比较流行的企业业务整合的解决方案之一。SSO 的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
Sphinx 是一个基于 SQL 的全文检索引擎,可以结合 MySQL、PostgreSQL 做全文搜索,它可以提供比数据库本身更专业的搜索功能,使得应用程序更容易实现专业化的全文检索。
NGINX 是一个高性能的 HTTP 和反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器。 NGINX 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的,第一个公开版本 0.1.0 发布于 2004 年 10 月 4 日。
Electron 基于 Chromium 和 Node.js,让你可以使用 HTML、CSS 和 JavaScript 构建应用。它是一个由 GitHub 及众多贡献者组成的活跃社区共同维护的开源项目,兼容 Mac、Windows 和 Linux,它构建的应用可在这三个操作系统上面运行。
域名(Domain Name),简称域名、网域,是由一串用点分隔的名字组成的 Internet 上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的电子方位(有时也指地理位置)。
禅道是一款国产的开源项目管理软件,她的核心管理思想基于敏捷方法 scrum,内置了产品管理和项目管理,同时又根据国内研发现状补充了测试管理、计划管理、发布管理、文档管理、事务管理等功能,在一个软件中就可以将软件研发中的需求、任务、bug、用例、计划、发布等要素有序的跟踪管理起来,完整地覆盖了项目管理的核心流程。
知乎是网络问答社区,连接各行各业的用户。用户分享着彼此的知识、经验和见解,为中文互联网源源不断地提供多种多样的信息。
Vue.js(读音 /vju ː/,类似于 view)是一个构建数据驱动的 Web 界面库。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。
宕机,多指一些网站、游戏、网络应用等服务器一种区别于正常运行的状态,也叫“Down 机”、“当机”或“死机”。宕机状态不仅仅是指服务器“挂掉了”、“死机了”状态,也包括服务器假死、停用、关闭等一些原因而导致出现的不能够正常运行的状态。
LeetCode(力扣)是一个全球极客挚爱的高质量技术成长平台,想要学习和提升专业能力从这里开始,充足技术干货等你来啃,轻松拿下 Dream Offer!
Telegram 是一个非盈利性、基于云端的即时消息服务。它提供了支持各大操作系统平台的开源的客户端,也提供了很多强大的 APIs 给开发者创建自己的客户端和机器人。
千千块(自定义块 css 和 js)
可以用 ai 提示词来无限创作思源笔记
京东是中国最大的自营式电商企业,2015 年第一季度在中国自营式 B2C 电商市场的占有率为 56.3%。2014 年 5 月,京东在美国纳斯达克证券交易所正式挂牌上市(股票代码:JD),是中国第一个成功赴美上市的大型综合型电商平台,与腾讯、百度等中国互联网巨头共同跻身全球前十大互联网公司排行榜。
Notion - The all-in-one workspace for your notes, tasks, wikis, and databases.
星云链是一个开源公链,业内简单的将其称为区块链上的谷歌。其实它不仅仅是区块链搜索引擎,一个公链的所有功能,它基本都有,比如你可以用它来开发部署你的去中心化的 APP,你可以在上面编写智能合约,发送交易等等。3 分钟快速接入星云链 (NAS) 测试网
红帽提供的 PaaS 云,支持多种编程语言,为开发人员提供了更为灵活的框架、存储选择。
OkHttp 是一款 HTTP & HTTP/2 客户端库,专为 Android 和 Java 应用打造。
flomo 是新一代 「卡片笔记」 ,专注在碎片化时代,促进你的记录,帮你积累更多知识资产。