├── .eslintignore ├── .eslintrc.cjs ├── .github └── workflows │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── CHANGELOG_EN.md ├── LICENSE ├── README.md ├── README_zh_CN.md ├── auto-imports.d.ts ├── components.d.ts ├── icon.png ├── package.json ├── plugin.json ├── preview.png ├── scripts ├── .gitignore └── make_dev_link.js ├── src ├── App.vue ├── api │ ├── ai.ts │ └── index.ts ├── assets │ └── imgs │ │ ├── button-en.jpg │ │ ├── button.png │ │ ├── location-en.jpg │ │ ├── location.png │ │ ├── overflow-en.jpg │ │ ├── overflow.png │ │ └── preview-en.jpg ├── components │ ├── AddHandleDate.vue │ ├── AiSummaryModal.vue │ ├── Setting.vue │ ├── TaskFilter.vue │ ├── TaskList.vue │ ├── Tree.vue │ ├── aiComp │ │ ├── ContentCard.vue │ │ ├── ContentCardGroup.vue │ │ └── HistoryContentCard.vue │ ├── infoCard │ │ ├── index.ts │ │ └── index.vue │ ├── settingComp │ │ └── SingleTaskHidden.vue │ └── taskMain │ │ └── SetTaskNodeTop.vue ├── hooks │ ├── useDatePicker.ts │ └── useResizeObserver.ts ├── i18n │ ├── en_US.json │ └── zh_CN.json ├── index.ts ├── store │ └── index.ts ├── styles │ ├── index.scss │ ├── themes.scss │ └── vc-calendar.scss ├── types │ ├── index.d.ts │ └── settings.d.ts └── utils │ ├── addButton.ts │ ├── addIcon.ts │ ├── addInfoToHtmlNode.ts │ ├── ai.ts │ ├── common.ts │ ├── compatible.ts │ ├── date.ts │ ├── eventBus.ts │ ├── func.ts │ ├── globalStroage.ts │ ├── handleTaskNode.ts │ ├── handleTreeData.ts │ ├── initLocalStorage.ts │ └── request.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | "eslint:recommended", 4 | "plugin:@typescript-eslint/recommended", 5 | "turbo", 6 | "prettier", 7 | ], 8 | 9 | parser: "@typescript-eslint/parser", 10 | 11 | plugins: ["@typescript-eslint", "prettier"], 12 | 13 | rules: { 14 | // Note: you must disable the base rule as it can report incorrect errors 15 | semi: "off", 16 | quotes: "off", 17 | "no-undef": "off", 18 | "@typescript-eslint/no-var-requires": "off", 19 | "@typescript-eslint/no-this-alias": "off", 20 | "@typescript-eslint/no-non-null-assertion": "off", 21 | "@typescript-eslint/no-unused-vars": "off", 22 | "@typescript-eslint/no-explicit-any": "off", 23 | "turbo/no-undeclared-env-vars": "off", 24 | "prettier/prettier": "error", 25 | }, 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create Release on Tag Push 2 | on: 3 | push: 4 | tags: 5 | - "v*" 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | steps: 13 | # Checkout 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | 17 | # Install Node.js 18 | - name: Install Node.js 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: 18 22 | registry-url: "https://registry.npmjs.org" 23 | 24 | # Install pnpm 25 | - name: Install pnpm 26 | uses: pnpm/action-setup@v4 27 | id: pnpm-install 28 | with: 29 | version: 8 30 | run_install: false 31 | 32 | # Get pnpm store directory 33 | - name: Get pnpm store directory 34 | id: pnpm-cache 35 | shell: bash 36 | run: | 37 | echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT 38 | 39 | # Setup pnpm cache 40 | - name: Setup pnpm cache 41 | uses: actions/cache@v3 42 | with: 43 | path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} 44 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 45 | restore-keys: | 46 | ${{ runner.os }}-pnpm-store- 47 | 48 | # Install dependencies 49 | - name: Install dependencies 50 | run: pnpm install 51 | 52 | # Build for production, 这一步会生成一个 package.zip 53 | - name: Build for production 54 | run: pnpm build 55 | 56 | - name: Release 57 | uses: ncipollo/release-action@v1 58 | with: 59 | allowUpdates: true 60 | artifactErrorsFailBuild: true 61 | artifacts: 'package.zip' 62 | token: ${{ secrets.GITHUB_TOKEN }} 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | .DS_Store 4 | pnpm-lock.yaml 5 | package.zip 6 | package 7 | node_modules 8 | dev 9 | dist 10 | build 11 | yarn.lock 12 | 13 | .env* -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 更新日志 2 | 3 | 4 | ## v1.1.8 5 | > **2025-05-19** 6 | 7 | **增强功能** 8 | 9 | - dock 栏中笔记本以及文档节点支持隐藏任务 10 | 11 | 12 | ## v1.1.5 13 | > **2025-05-12** 14 | 15 | **增强功能** 16 | 17 | - 兼容任务状态逻辑,以支持多种任务标记格式(如 "* [ ]" 和 "- [ ]") 18 | 19 | 20 | ## v1.1.4 21 | > **2025-03-25** 22 | 23 | **增强功能** 24 | 25 | - 支持自定义需要统计的任务节点数量(默认2000) 26 | 27 | 28 | ## v1.1.3 29 | 30 | > **2025-01-06** 31 | 32 | **增强功能** 33 | 34 | - 优化国际化英文语言的文案 35 | - 优化搜索功能,使其不区分大小写 #40 36 | ## v1.1.2 37 | 38 | > **2025-01-04** 39 | 40 | **修复缺陷** 41 | 42 | - 仅任务显示模式下,点击任务无法定位到文档指定位置的问题 43 | 44 | ## v1.1.1 45 | 46 | > **2025-01-04** 47 | 48 | **改进功能** 49 | 50 | - 新增一个显示模式:仅任务 51 | 52 | ## v1.1.0 53 | 54 | > **2024-11-15** 55 | 56 | **修复缺陷** 57 | 58 | - 修复某些场景下任务节点右上角附加信息不显示的问题并微调位置 59 | 60 | ## v1.0.10 61 | 62 | > **2024-11-12** 63 | 64 | **改进功能** 65 | 66 | - 支持配置是否在文档中的任务节点右上角显示附加信息 67 | 68 | ## v1.0.8 69 | 70 | > **2024-10-17** 71 | 72 | **改进功能** 73 | 74 | - 支持单个任务的隐藏和显示 75 | 76 | ## v1.0.6 77 | 78 | > **2024-09-19** 79 | 80 | **改进功能** 81 | 82 | - 任务节点右键菜单选项添加图标 83 | 84 | ## v1.0.5 85 | 86 | > **2024-09-02** 87 | 88 | **改进功能** 89 | 90 | - 优化部分图标 91 | 92 | ## v1.0.4 93 | 94 | > **2024-08-29** 95 | 96 | **改进功能** 97 | 98 | - 非中文模式隐藏 AI 入口 99 | 100 | ## v1.0.3 101 | 102 | > **2024-08-29** 103 | 104 | **修复缺陷** 105 | 106 | - [切换日间/夜间模式时,图标丢失](https://github.com/syh19/siyuan-plugin-task-list/issues/4) 107 | 108 | ## v1.0.2 109 | 110 | > **2024-08-17** 111 | 112 | **修复缺陷** 113 | 114 | - 修复显示异常 115 | 116 | ## v1.0.1 117 | 118 | > **2024-08-17** 119 | 120 | **修复缺陷** 121 | 122 | - 修复打包异常 123 | 124 | ## v1.0.0 125 | 126 | > **2024-08-17** 127 | 128 | **改进功能** 129 | 130 | - ✨✨✨ AI 吐槽模式来袭,让你的任务列表不再 boring ! 131 | 132 | ## v0.4.2 133 | 134 | > **2024-08-09** 135 | 136 | **修复缺陷** 137 | 138 | - 调整 dock 面板部分字体配色 [#32](https://github.com/syh19/siyuan-plugin-task-list/issues/32) 139 | 140 | ## v0.4.1 141 | 142 | > **2024-07-29** 143 | 144 | **修复缺陷** 145 | 146 | - 第一次使用插件时,没有设置隐藏任务时导致控制台报错,任务列表显示异常。 147 | 148 | **改进功能** 149 | 150 | - 第一次使用插件时初始化配置数据。 151 | 152 | ## v0.4.0 153 | 154 | > **2024-07-22** 155 | 156 | **改进功能** 157 | 158 | - 信息卡片支持配置隐藏字段及日期格式 [#30](https://github.com/syh19/siyuan-plugin-task-list/issues/30) 159 | 160 | ## v0.3.4 161 | 162 | > **2024-07-09** 163 | 164 | **修复缺陷** 165 | 166 | - 优化`dock`栏过窄时的显示效果 [#3](https://github.com/syh19/siyuan-plugin-task-list/issues/3) 167 | 168 | ## v0.3.3 169 | 170 | > **2024-07-02** 171 | 172 | **修复缺陷** 173 | 174 | - 弹窗关闭时控制台报错 [#28](https://github.com/syh19/siyuan-plugin-task-list/issues/28) 175 | - 静态日期范围筛选时,未包含结束日期当天的任务 [#29](https://github.com/syh19/siyuan-plugin-task-list/issues/29) 176 | 177 | **改进功能** 178 | 179 | - 已完成任务支持显示信息卡片 180 | 181 | ## v0.3.2 182 | 183 | > **2024-06-30** 184 | 185 | **修复缺陷** 186 | 187 | - 右键任务列表项,修复控制台报错 [#25](https://github.com/syh19/siyuan-plugin-task-list/issues/25) 188 | 189 | ## v0.3.1 190 | 191 | > **2024-06-21** 192 | 193 | **改进功能** 194 | 195 | - 上线前及时修改关于置顶的自定义属性名称 196 | 197 | ## v0.3.0 198 | 199 | > **2024-06-21** 200 | 201 | **改进功能** 202 | 203 | - ✨✨✨ `dock`栏中任务列表支持右键菜单 204 | - ✨✨✨ 新增任务置顶功能 [#21](https://github.com/syh19/siyuan-plugin-task-list/issues/21) 205 | - 将【添加任务处理时间】移动到右键菜单 206 | - 将信息卡片的触发区域收缩至图标自身 207 | - 优化`dock`栏中列表的高度 208 | 209 | ## v0.2.8 210 | 211 | > **2024-06-15** 212 | 213 | **修复缺陷** 214 | 215 | - 删除无用的控制台 log 216 | - 修复任务列表为空的提示语 217 | 218 | ## v0.2.7 219 | 220 | > **2024-06-15** 221 | 222 | **改进功能** 223 | 224 | - 优化任务列表为空的提示语 225 | - 您设置了任务的隐藏范围:所有笔记本 226 | - 您设置了任务的隐藏范围:当前笔记本 227 | - 您设置了任务的隐藏范围:当前文档或者文档及其子文档 228 | - 您对任务进行了过滤:当前日期下无任务 229 | - 您对任务进行了过滤:当前状态下无任务 230 | - 您对任务进行了过滤:当前维度下无任务 231 | 232 | **修复缺陷** 233 | 234 | - 初始化时及时获取文档的任务列表数据 [#23](https://github.com/syh19/siyuan-plugin-task-list/issues/23) 235 | 236 | ## v0.2.6 237 | 238 | > **2024-06-12** 239 | 240 | **修复缺陷** 241 | 242 | - 修复 TAB 选项层级过高的问题 [#22](https://github.com/syh19/siyuan-plugin-task-list/issues/22) 243 | 244 | ## v0.2.5 245 | 246 | > **2024-06-03** 247 | 248 | **改进功能** 249 | 250 | - ✨✨✨ 维度 TAB 支持显示具体的文档、笔记本、工作空间名称 251 | 252 | ## v0.2.4 253 | 254 | > **2024-05-29** 255 | 256 | **修复缺陷** 257 | 258 | - 每日任务数量信息可能显示异常 259 | 260 | ## v0.2.3 261 | 262 | > **2024-05-28** 263 | 264 | **改进功能** 265 | 266 | - `dock` 栏中的日历显示模式支持选择周视图或者日历视图 267 | - 缩小 `dock` 栏中日历尺寸大小,以显示更多条任务 268 | - ✨✨✨ `dock` 栏中的日历视图中 📍 标记 📍 每日待处理以及已完成的任务数量 269 | - 文档、笔记本、工作空间 `TAB` 支持同时显示当前维度下的任务数量 270 | 271 | ## v0.2.2 272 | 273 | > **2024-05-26** 274 | 275 | **改进功能** 276 | 277 | - 添加打开关闭插件`dock`栏的快捷键 #20 278 | - 取消任务列表的最大宽度限制 279 | 280 | **修复缺陷** 281 | 282 | - 筛选图标右上角红点显示时机不准确 283 | - `dock`栏中有周视图时任务列表高度不准确 284 | 285 | ## v0.2.1 286 | 287 | > **2024-05-20** 288 | 289 | **改进功能** 290 | 291 | - 任务维度 TAB 支持持久化存储 292 | 293 | ## v0.2.0 294 | 295 | > **2024-05-14** 296 | 297 | **改进功能** 298 | 299 | - ✨✨✨ 任务节点通过日历视图实现自定义处理时间 #7 #15 300 | - ✨✨✨ 实现日历视图对任务节点的过滤筛选 #7 #15 301 | 302 | ## v0.1.4 303 | 304 | > **2024-05-08** 305 | 306 | **修复缺陷** 307 | 308 | - 任务列表展示方式为“笔记本 & 文档 & 任务”时**排序功能**失效 #19 309 | 310 | ## v0.1.3 311 | 312 | > **2024-04-27** 313 | 314 | **改进功能** 315 | 316 | - 显示任务的数量 #14 317 | 318 | ## v0.1.2 319 | 320 | > **2024-04-24** 321 | 322 | **修复缺陷** 323 | 324 | - 从未在抽屉中保存任何设置项配置时导致插件报错 325 | 326 | ## v0.1.1 327 | 328 | > **2024-04-23** 329 | 330 | **修复缺陷** 331 | 332 | - 文档维度下任务节点无法正常显示 333 | 334 | ## v0.1.0 335 | 336 | > **2024-04-22** 337 | 338 | **改进功能** 339 | 340 | - 为任务节点添加基于时间的排序功能 #9 341 | - 缩小打包后的文件体积 342 | 343 | **修复缺陷** 344 | 345 | - 移动 `dock` 栏插件图标导致插件无法使用 346 | 347 | ## v0.0.7 348 | 349 | > **2024-04-14** 350 | 351 | **修复缺陷** 352 | 353 | - 信息卡片中的文本过长时可能显示异常 354 | - 完善自定义属性解析函数 355 | - [添加`DOM`元素判空逻辑](https://github.com/syh19/siyuan-plugin-task-list/issues/13) 356 | 357 | ## v0.0.6 358 | 359 | > **2024-04-08** 360 | 361 | **修复缺陷** 362 | 363 | - 紧急修复:修复任务完成状态的判断逻辑 364 | 365 | ## v0.0.5 366 | 367 | > **2024-04-08** 368 | 369 | **改进功能** 370 | 371 | - ✨✨✨ 任务节点通过自定义属性支持自动添加**完成时间** 372 | - 新增任务节点的信息卡片浮窗 373 | - [通过配置项支持任务列表模式与树形模式的切换](https://github.com/syh19/siyuan-plugin-task-list/issues/1) 374 | 375 | **修复缺陷** 376 | 377 | - 修复嵌套任务节点完成状态判断不准确的问题 378 | 379 | **其他** 380 | 381 | - 修改配置数据的存储结构并实现旧数据的自动迁移 382 | 383 | ## v0.0.4 384 | 385 | > **2024-04-01** 386 | 387 | **修复缺陷** 388 | 389 | - 通过伺服在浏览器中运行时报错导致插件无法使用 390 | 391 | ## v0.0.3 392 | 393 | > **2024-04-01** 394 | 395 | **改进功能** 396 | 397 | - [✨✨✨ 添加隐藏指定文档中的任务节点的配置,隐藏方式支持两种:**仅文档自身**和**文档及其子文档**](https://github.com/syh19/siyuan-plugin-task-list/issues/5) 398 | - 隐藏指定文档的任务节点配置支持**持久化存储** 399 | - [✨✨✨ 插件配置中取消仅`PC`端可用的限制,理论上全平台可用](https://github.com/syh19/siyuan-plugin-task-list/issues/10) 400 | 401 | **修复缺陷** 402 | 403 | - [点击任务节点进行定位时少数场景下可能定位到**嵌入块**中的任务](https://github.com/syh19/siyuan-plugin-task-list/issues/11) 404 | 405 | ## v0.0.2 406 | 407 | > **2024-03-24** 408 | 409 | **改进功能** 410 | 411 | - 初始状态下默认展开树中所有节点 412 | - 搜索输入框隐藏后,在输入框中有值的情况下,通过键盘回车键`Enter`可以重新触发数据的过滤操作 413 | 414 | **修复缺陷** 415 | 416 | - 某些情况下**已完成状态**下无法正常显示任务节点 417 | - **文档维度**下,嵌套太深的文档中的任务节点无法显示 418 | - 刷新数据以及切换【文档、笔记本、工作空间】TAB 后**展开收起**按钮可能与实际展开收起状态不一致 419 | 420 | ## v0.0.1 421 | 422 | > **2024-03-23** 423 | 424 | - 项目初步开发完成 425 | -------------------------------------------------------------------------------- /CHANGELOG_EN.md: -------------------------------------------------------------------------------- 1 | # CHANGE LOG 2 | 3 | ## v1.1.8 4 | > **2025-05-19** 5 | 6 | **Enhancement** 7 | - Support hiding tasks in notebook and document nodes in the dock panel 8 | 9 | 10 | ## v1.1.5 11 | > **2025-05-12** 12 | 13 | **Enhancement** 14 | 15 | - Compatible task status logic to support multiple task mark formats (such as "* [ ]" and "- [ ]") 16 | 17 | 18 | ## v1.1.4 19 | > **2025-03-25** 20 | 21 | **Enhancement** 22 | 23 | - Support customizing the number of task nodes to count (default 2000) 24 | 25 | ## v1.1.3 26 | 27 | > **2025-01-06** 28 | 29 | **Enhancement** 30 | 31 | - Optimize the internationalization of English language 32 | - Optimize the search function to be case-insensitive #40 33 | 34 | ## v1.1.2 35 | 36 | > **2025-01-04** 37 | 38 | **Bugfix** 39 | 40 | - Fix the issue where clicking on a task in the only task display mode does not locate the document to the specified position 41 | 42 | ## v1.1.1 43 | 44 | > **2025-01-04** 45 | 46 | **Enhancement** 47 | 48 | - Add a new display mode: only task 49 | 50 | ## v1.1.0 51 | 52 | > **2024-11-15** 53 | 54 | **Bugfix** 55 | 56 | - Fix the issue where the additional information near the task node in the document editing area is not displayed in some scenarios and adjust the position slightly 57 | 58 | ## v1.0.10 59 | 60 | > **2024-11-12** 61 | 62 | **Enhancement** 63 | 64 | - Support configure whether to display additional information near the task node in the document editing area 65 | 66 | ## v1.0.9 67 | 68 | > **2024-10-24** 69 | 70 | **Enhancement** 71 | 72 | - ✨✨✨ AI roasting mode is here, making your task list no longer boring! 73 | 74 | ## v1.0.8 75 | 76 | > **2024-10-17** 77 | 78 | **Enhancement** 79 | 80 | - Support single task hidden and show 81 | 82 | ## v1.0.6 83 | 84 | > **2024-09-19** 85 | 86 | **Enhancement** 87 | 88 | - Add icon to task node right-click menu options 89 | 90 | ## v1.0.5 91 | 92 | > **2024-08-29** 93 | 94 | **Bugfix** 95 | 96 | - [Icon lost when switching between day and night mode](https://github.com/syh19/siyuan-plugin-task-list/issues/4) 97 | 98 | ## v0.4.2 99 | 100 | > **2024-08-09** 101 | 102 | - Adjust the font color of some parts of the dock panel [#32](https://github.com/syh19/siyuan-plugin-task-list/issues/32) 103 | 104 | ## v0.4.1 105 | 106 | > **2024-07-29** 107 | 108 | **Bugfix** 109 | 110 | - When using the plug-in for the first time, if no hidden tasks are set, an error will be reported on the console and the task list will display abnormally. 111 | 112 | **Enhancement** 113 | 114 | - Initialize configuration data when using the plug-in for the first time. 115 | 116 | ## v0.4.0 117 | 118 | > **2024-07-22** 119 | 120 | **Enhancement** 121 | 122 | - Information card support configure hidden fields and date formats [#30](https://github.com/syh19/siyuan-plugin-task-list/issues/30) 123 | 124 | ## v0.3.4 125 | 126 | > **2024-07-09** 127 | 128 | **Bugfix** 129 | 130 | - Optimize the display effect when the `dock` column is too narrow. [#3](https://github.com/syh19/siyuan-plugin-task-list/issues/3) 131 | 132 | ## v0.3.3 133 | 134 | > **2024-07-02** 135 | 136 | **Bugfix** 137 | 138 | - The console reports an error when the pop-up window is closed. [#28](https://github.com/syh19/siyuan-plugin-task-list/issues/28) 139 | - When filtering with a static date range, tasks with an end date on the current day are not included. [#29](https://github.com/syh19/siyuan-plugin-task-list/issues/29) 140 | 141 | **Enhancement** 142 | 143 | - Completed tasks support displaying information cards 144 | 145 | ## v0.3.2 146 | 147 | > **2024-06-30** 148 | 149 | **Bugfix** 150 | 151 | - Right-click the task list item to fix the console error [#25](https://github.com/syh19/siyuan-plugin-task-list/issues/25) 152 | 153 | ## v0.3.1 154 | 155 | > **2024-06-21** 156 | 157 | **Enhancement** 158 | 159 | - Modify the custom attribute name on top in time before going online. 160 | 161 | ## v0.3.0 162 | 163 | > **2024-06-21** 164 | 165 | **Enhancement** 166 | 167 | - ✨✨✨ The task list in the `dock` bar supports right-click menu. 168 | - ✨✨✨ Added task pin function. [#21](https://github.com/syh19/siyuan-plugin-task-list/issues/21) 169 | - Move [add task handle date] to the right-click menu. 170 | - Shrink the trigger area of ​​the information card to the icon itself. 171 | - Optimize the height of the list in the `dock` bar. 172 | 173 | ## v0.2.8 174 | 175 | > **2024-06-15** 176 | 177 | **Bugfix** 178 | 179 | - Delete useless console logs 180 | - Fix the prompt when the task list is empty 181 | 182 | ## v0.2.7 183 | 184 | > **2024-06-15** 185 | 186 | **Enhancement** 187 | 188 | - Optimize the prompt when the task list is empty 189 | - You set the hidden scope of the task: all notebooks 190 | - You set the hidden scope of the task: current notebook 191 | - You set the hidden scope of the task: current document or a document and its subdocuments 192 | - You have filtered tasks: There are no tasks for the current date 193 | - You have filtered tasks: There are no tasks in the current state 194 | - You have filtered tasks: There are no tasks in the current dimension 195 | 196 | **Bugfix** 197 | 198 | - Get the task list data of the document in time during initialization [#23](https://github.com/syh19/siyuan-plugin-task-list/issues/23) 199 | 200 | ## v0.2.6 201 | 202 | > **2024-06-12** 203 | 204 | **Bugfix** 205 | 206 | - Fixed the issue where the TAB bar level is too high [#22](https://github.com/syh19/siyuan-plugin-task-list/issues/22) 207 | 208 | ## v0.2.5 209 | 210 | > **2024-06-03** 211 | 212 | **Enhancement** 213 | 214 | - ✨✨✨ Dimension TAB supports displaying specific document, notebook, and workspace names 215 | 216 | ## v0.2.4 217 | 218 | > **2024-05-29** 219 | 220 | **Bugfix** 221 | 222 | - Daily task quantity information may display abnormally 223 | 224 | ## v0.2.3 225 | 226 | > **2024-05-28** 227 | 228 | **Enhancement** 229 | 230 | - The calendar display mode in the dock bar supports choosing week view or month view 231 | - Reduce the size of the calendar in the dock bar to display more tasks 232 | - ✨✨✨ Mark the number of daily todo and done tasks in the calendar view in the dock bar 233 | - Documents, notebooks, and workspace TAB support simultaneously displaying the number of tasks in the current dimension 234 | 235 | ## v0.2.2 236 | 237 | > **2024-05-26** 238 | 239 | **Enhancement** 240 | 241 | - Add a hotkey key to open and close the plug-in `dock` bar #20 242 | - Remove the maximum width limit for task lists 243 | 244 | **Bugfix** 245 | 246 | - The timing of the red dot display in the upper right corner of the filter icon is inaccurate 247 | - The height of the task list is inaccurate when there is a week view in the `dock` bar 248 | 249 | ## v0.2.1 250 | 251 | > **2024-05-20** 252 | 253 | **Enhancement** 254 | 255 | - Task dimension TAB supports persistent storage 256 | 257 | ## v0.2.0 258 | 259 | > **2024-05-14** 260 | 261 | **Enhancement** 262 | 263 | - ✨✨✨ Task nodes implement customized processing time through calendar view #7 #15 264 | - ✨✨✨ Implement calendar view filtering of task nodes #7 #15 265 | 266 | ## v0.1.4 267 | 268 | > **2024-05-08** 269 | 270 | **Bugfix** 271 | 272 | - The **sorting function** fails when the task list display mode is "notebook & doc & task". #19 273 | 274 | ## v0.1.3 275 | 276 | > **2024-04-27** 277 | 278 | **Enhancement** 279 | 280 | - Display the number of tasks. #14 281 | 282 | ## v0.1.2 283 | 284 | > **2024-04-24** 285 | 286 | **Bugfix** 287 | 288 | - The plug-in reports an error when no setting configuration is saved in the drawer. 289 | 290 | ## v0.1.1 291 | 292 | > **2024-04-23** 293 | 294 | **Bugfix** 295 | 296 | - Task nodes cannot be displayed normally under the document dimension 297 | 298 | ## v0.1.0 299 | 300 | > **2024-04-22** 301 | 302 | **Enhancement** 303 | 304 | - Add time-based sorting functionality to task nodes #9 305 | - Reduce packaged file size 306 | 307 | **Bugfix** 308 | 309 | - Moving the `dock` bar plugin icon causes the plugin to become unusable 310 | 311 | ## v0.0.7 312 | 313 | > **2024-04-14** 314 | 315 | **Bugfix** 316 | 317 | - The text in the information card may display abnormally if it is too long 318 | - Improve custom attribute parsing function 319 | - [Add `DOM` element empty judgment logic](https://github.com/syh19/siyuan-plugin-task-list/issues/13) 320 | 321 | ## v0.0.6 322 | 323 | > **2024-04-08** 324 | 325 | **Bugfix** 326 | 327 | - Emergency repair: Repair the judgment logic of task completion status 328 | 329 | ## v0.0.5 330 | 331 | > **2024-04-08** 332 | 333 | **Enhancement** 334 | 335 | - ✨✨✨ Task nodes support automatically adding finished time through custom attributes 336 | - Added information card floating window for task nodes 337 | - [Support switching between task list mode and tree mode through configuration items 338 | ](https://github.com/syh19/siyuan-plugin-task-list/issues/1) 339 | 340 | **Bugfix** 341 | 342 | - Fixed the problem of inaccurate judgment of the completion status of nested task nodes 343 | 344 | **Others** 345 | 346 | - Modify the storage structure of configuration data and realize automatic migration of old data 347 | 348 | ## v0.0.4 349 | 350 | > **2024-04-01** 351 | 352 | **Bugfix** 353 | 354 | - When running in the browser through the server, an error occurs and the plug-in cannot be used. 355 | 356 | ## v0.0.3 357 | 358 | > **2024-04-01** 359 | 360 | **Enhancement** 361 | 362 | - [✨✨✨ Add the configuration of hiding task nodes in the specified document. Two hiding methods are supported: **Only the document itself** and **Document and children**](https://github.com/syh19/siyuan-plugin-task-list/issues/5) 363 | - Hide the task node configuration of the specified document support **persistent storage** 364 | - [✨✨✨ In the plug-in configuration, the restriction that it is only available on `PC` is removed. In theory, it is available on all platforms.](https://github.com/syh19/siyuan-plugin-task-list/issues/10) 365 | 366 | **Bugfix** 367 | 368 | - [When you click on a task node to locate it, in a few scenarios it is possible to locate the task in the **embedded block**.](https://github.com/syh19/siyuan-plugin-task-list/issues/11) 369 | 370 | ## v0.0.2 371 | 372 | > **2024-03-24** 373 | 374 | **Enhancement** 375 | 376 | - In the initial state, all nodes in the tree are expanded by default. 377 | - After the search input box is hidden, if there is a value in the input box, the data filtering operation can be re-triggered by pressing the `Enter` key on the keyboard. 378 | 379 | **Bugfix** 380 | 381 | - In some cases, task nodes cannot be displayed normally in the completed state. 382 | - Under the **document dimension**, task nodes in documents that are nested too deeply cannot be displayed. 383 | - After refreshing data and switching [Document, Notebook, Workspace] TAB, the **Expand and Collapse** button may be inconsistent with the actual expand and collapse state. 384 | 385 | ## v0.0.1 386 | 387 | > **2024-03-23** 388 | 389 | - Preliminary project development completed 390 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 SiYuan 思源笔记 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [中文](https://github.com/syh19/siyuan-plugin-task-list/blob/main/README_zh_CN.md) 2 | 3 | # Latest version `v1.1.8` change log 4 | 5 | **Enhancement** 6 | 7 | - Support hiding tasks in notebook and document nodes in the dock panel 8 | 9 | > - [For more information about the plugin, please view here](https://liuyun.io/article/1711344682726) 10 | > - [Please view the CHANGE LOG of all versions here](https://github.com/syh19/siyuan-plugin-task-list/blob/main/CHANGELOG_EN.md) 11 | 12 | --- 13 | 14 | # Task List Plugin 15 | 16 | 17 | 18 | When using Siyuan Notes, you will inevitably have some doubts that puzzle you during the learning or recording process. These doubts may not affect the subsequent learning of the content, so you want to record and skip them temporarily and solve them when you have time. At this time, you need a tool that can organize all the doubt points scattered throughout the workspace to facilitate subsequent viewing and resolution. 19 | 20 | Therefore, the **Task list plug-in** came into being, which can help you quickly generate a task list for all **task list block** type doubt points and place it in the right `Dock` to facilitate subsequent learning and organization. 21 | 22 | ## Ability 23 | 24 | **This plugin can be used directly without any configuration.** 25 | 26 | **Status**: All task list block type nodes (collectively referred to as **task nodes** for convenience) have two status, namely **unfinished** and **completed**. The status of the **task node** can be switched by clicking the check box in front of the **task node** in the note. 27 | 28 | **Scope**: **The Task List plug-in** summarizes all task nodes according to three dimensions: 29 | 30 | - Document: All task nodes in the currently active open document block 31 | - Notebook: All task nodes in the notebook to which the currently activated document block belongs 32 | - Workspace: All task nodes in all open notebooks in the entire workspace 33 | 34 | **Display form**: Task nodes in the **Notebook** and **Workspace** dimensions are displayed in a tree, which can clearly indicate the documents and notebooks to which each task node belongs; **Document** dimension The task nodes under are displayed directly in the list. 35 | 36 | Task node descriptions that are too long will be truncated, and the complete task node description will be displayed when the mouse is hovering over the task node. 37 | 38 | 39 | 40 | **Function Points:** 41 | 42 | **Quick positioning**: Clicking on a task node can automatically open the document where the task node is located, scroll the task node into the visible area, and temporarily highlight it to facilitate quick positioning. 43 | 44 | 45 | 46 | **Top Button Area:** 47 | 48 | 49 | 50 | - Refresh: Retrieve the latest data of the task list 51 | - Expand and collapse: Quickly expand and collapse all task nodes 52 | - Switch status: Switch the status of task nodes, including **Unfinished**, **Completed** and **All**; click the status copy next to the plugin to also switch 53 | - Search: Search for task nodes within the current scope and highlight the search terms 54 | 55 | # Feedback 56 | 57 | This plug-in uses `Vue3` and `Element Plus` to realize the drawing of `UI Layout` . Due to limited technical capabilities, there will inevitably be some problems during the use of the plug-in. If you encounter problems during use or have good suggestions, please leave a message [here](https://github.com/syh19/siyuan-plugin-task-list/issues) for feedback. 58 | 59 | - [Siyuan Notes official website](https://b3log.org/siyuan/en/?lang=en) 60 | - [SiYuan English Discussion Forum](https://liuyun.io/) 61 | - [Github Repository Address](https://github.com/syh19/siyuan-plugin-task-list) 62 | 63 | # Buy me a coffee 64 | 65 | I hope this plugin can be helpful to you. If you like this plugin. You can buy me a coffee. Thank you very much. 66 | 67 | sylwair 68 | -------------------------------------------------------------------------------- /README_zh_CN.md: -------------------------------------------------------------------------------- 1 | [English](https://github.com/syh19/siyuan-plugin-task-list/blob/main/README.md) 2 | 3 | # 最新版本`v1.1.8`更新记录 4 | 5 | **增强功能** 6 | 7 | - dock 栏中笔记本以及文档节点支持隐藏任务 8 | 9 | > - [所有功能的详情描述请看这里](https://ld246.com/article/1711244396256) 10 | > - [所有版本的更新记录请看这里](https://github.com/syh19/siyuan-plugin-task-list/blob/main/CHANGELOG.md) 11 | 12 | --- 13 | 14 | # 任务列表插件 15 | 16 | 17 | 18 | ## 初衷 19 | 20 | 在使用思源笔记时,学习或者记录过程中难免会有一些令自己疑惑的问题点,这些疑问点可能不影响后续内容的学习,所以想要暂时记录并跳过,等到有时间时再来解决。这个时候,就需要有一个工具能够将所有散落在整个工作空间的疑问点整理到一起,方便后续查看和解决。 21 | 22 | 因此,**任务列表**插件应运而生,它可以帮助你将所有的**任务列表块**类型的疑问点快速生成一个任务列表,放置在右侧`Dock`栏中,方便后续学习整理。 23 | 24 | ## 功能 25 | 26 | **本插件开箱即用,无需任何配置。** 27 | 28 | **状态**:所有的任务列表块类型的节点(为了方便后续统称**任务节点**)有两种状态,分别是**未完成**和**已完成**,通过点击笔记中任务节点前的复选框可以切换任务节点的状态。 29 | 30 | **所属范围**:**任务列表**插件将所有的任务节点按照三个维度进行汇总: 31 | 32 | - 文档:当前激活打开的文档块中的所有任务节点 33 | - 笔记本:当前激活打开的文档块所属笔记本中的所有任务节点 34 | - 工作空间:整个工作空间中所有打开的笔记本中的所有任务节点 35 | 36 | **展现形式**:**笔记本**和**工作空间**维度下的任务节点通过树的方式展示,可以清晰地表示出各个任务节点所属的文档以及笔记本;**文档**维度下的任务节点则直接展示在列表中。 37 | 38 | 过长的任务节点描述会被截断,鼠标悬停在任务节点上时会显示完整的任务节点描述。 39 | 40 | 41 | 42 | **功能点:** 43 | 44 | **快速定位**:点击任务节点可以自动打开该任务节点所在的文档,并将该任务节点滚动到可视区域内,同时做临时高亮处理,方便快速定位。 45 | 46 | 47 | 48 | **顶部按钮区域:** 49 | 50 | 51 | 52 | - 刷新:重新获取任务列表最新数据 53 | - 展开收起:快速展开收起所有任务节点 54 | - 切换状态:切换任务节点的状态,包括**未完成**、**已完成**以及**全部**;点击插件旁边的状态文案同样可以进行切换 55 | - 搜索:搜索当前范围内的任务节点,并对搜索词做高亮处理 56 | 57 | # 反馈 58 | 59 | 如果在使用过程中遇到问题或者有好的建议,欢迎在[这里](https://github.com/syh19/siyuan-plugin-task-list/issues)进行留言反馈。 60 | 61 | - [思源笔记官方网站](https://b3log.org/siyuan/) 62 | - [链滴社区-思源笔记版块](https://ld246.com/tag/siyuan) 63 | - [仓库地址](https://github.com/syh19/siyuan-plugin-task-list) 64 | 65 | # 捐赠 66 | 67 | 希望本插件能够对您有所帮助,如果您喜欢本插件的话,欢迎充电支持,万分感谢。 68 | 69 | 70 | -------------------------------------------------------------------------------- /auto-imports.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // noinspection JSUnusedGlobalSymbols 5 | // Generated by unplugin-auto-import 6 | export {} 7 | declare global { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /components.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // Generated by unplugin-vue-components 5 | // Read more: https://github.com/vuejs/core/pull/3399 6 | export {} 7 | 8 | declare module 'vue' { 9 | export interface GlobalComponents { 10 | AddHandleDate: typeof import('./src/components/AddHandleDate.vue')['default'] 11 | AiSummaryModal: typeof import('./src/components/AiSummaryModal.vue')['default'] 12 | ContentCard: typeof import('./src/components/aiComp/ContentCard.vue')['default'] 13 | ContentCardGroup: typeof import('./src/components/aiComp/ContentCardGroup.vue')['default'] 14 | ElBadge: typeof import('element-plus/es')['ElBadge'] 15 | ElButton: typeof import('element-plus/es')['ElButton'] 16 | ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] 17 | ElDialog: typeof import('element-plus/es')['ElDialog'] 18 | ElDivider: typeof import('element-plus/es')['ElDivider'] 19 | ElDrawer: typeof import('element-plus/es')['ElDrawer'] 20 | ElEmpty: typeof import('element-plus/es')['ElEmpty'] 21 | ElInput: typeof import('element-plus/es')['ElInput'] 22 | ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] 23 | ElOption: typeof import('element-plus/es')['ElOption'] 24 | ElRadio: typeof import('element-plus/es')['ElRadio'] 25 | ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] 26 | ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] 27 | ElSelect: typeof import('element-plus/es')['ElSelect'] 28 | ElSwitch: typeof import('element-plus/es')['ElSwitch'] 29 | ElTabPane: typeof import('element-plus/es')['ElTabPane'] 30 | ElTabs: typeof import('element-plus/es')['ElTabs'] 31 | ElTooltip: typeof import('element-plus/es')['ElTooltip'] 32 | HistoryContentCard: typeof import('./src/components/aiComp/HistoryContentCard.vue')['default'] 33 | InfoCard: typeof import('./src/components/infoCard/index.vue')['default'] 34 | SetTaskNodeTop: typeof import('./src/components/taskMain/SetTaskNodeTop.vue')['default'] 35 | Setting: typeof import('./src/components/Setting.vue')['default'] 36 | SingleTaskHidden: typeof import('./src/components/settingComp/SingleTaskHidden.vue')['default'] 37 | TaskFilter: typeof import('./src/components/TaskFilter.vue')['default'] 38 | TaskList: typeof import('./src/components/TaskList.vue')['default'] 39 | Tree: typeof import('./src/components/Tree.vue')['default'] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syh19/siyuan-plugin-task-list/c175e2d325e32de96a45ded3bd8677a372a23348/icon.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "siyuan-plugin-task-list", 3 | "version": "1.1.8", 4 | "type": "module", 5 | "description": "思源笔记插件:汇总散落在整个工作空间的任务,依托任务列表实现日程管理和待办事项管理", 6 | "repository": "https://github.com/syh19/siyuan-plugin-task-list", 7 | "homepage": "https://sylwair.com", 8 | "author": "sylwair", 9 | "license": "GPL-3.0", 10 | "scripts": { 11 | "make-link": "node --no-warnings ./scripts/make_dev_link.js", 12 | "start": "vite build --watch", 13 | "dev": "vite build --watch", 14 | "build": "vite build", 15 | "update": "pnpm update -L" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^20.11.6", 19 | "@vitejs/plugin-vue": "^5.0.4", 20 | "@vue/eslint-config-prettier": "^9.0.0", 21 | "@vue/eslint-config-typescript": "^12.0.0", 22 | "@vue/tsconfig": "^0.5.1", 23 | "fast-glob": "^3.3.2", 24 | "glob": "^10.3.10", 25 | "minimist": "^1.2.8", 26 | "rollup-plugin-livereload": "^2.0.5", 27 | "sass": "^1.70.0", 28 | "siyuan": "^0.9.2", 29 | "ts-node": "^10.9.2", 30 | "typescript": "^5.3.3", 31 | "unplugin-auto-import": "^0.17.5", 32 | "unplugin-vue-components": "^0.26.0", 33 | "vite": "^5.0.12", 34 | "vite-plugin-static-copy": "^1.0.1", 35 | "vite-plugin-zip-pack": "^1.2.0", 36 | "vue-tsc": "^1.8.27" 37 | }, 38 | "dependencies": { 39 | "@element-plus/icons-vue": "^2.3.1", 40 | "@imengyu/vue3-context-menu": "^1.4.1", 41 | "@popperjs/core": "^2.11.8", 42 | "@siyuan-community/siyuan-sdk": "^0.3.7", 43 | "axios": "^1.7.3", 44 | "element-plus": "^2.6.1", 45 | "html2canvas": "^1.4.1", 46 | "pinia": "^2.1.7", 47 | "pnpm": "^8.14.3", 48 | "v-calendar": "^3.1.2", 49 | "vue": "^3.4.15" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "siyuan-plugin-task-list", 3 | "author": "sylwair", 4 | "url": "https://github.com/syh19/siyuan-plugin-task-list", 5 | "version": "1.1.8", 6 | "minAppVersion": "2.10.13", 7 | "backends": [ 8 | "all" 9 | ], 10 | "frontends": [ 11 | "all" 12 | ], 13 | "displayName": { 14 | "default": "Task List", 15 | "zh_CN": "任务列表" 16 | }, 17 | "description": { 18 | "default": "Summarize tasks scattered throughout your workspace and rely on task list to achieve schedule management and to-do list management", 19 | "zh_CN": "汇总散落在整个工作空间的任务,依托任务列表实现日程管理待办事项管理" 20 | }, 21 | "readme": { 22 | "default": "README.md", 23 | "zh_CN": "README_zh_CN.md" 24 | }, 25 | "i18n": [ 26 | "en_US", 27 | "zh_CN" 28 | ], 29 | "funding": { 30 | "openCollective": "", 31 | "patreon": "", 32 | "github": "", 33 | "custom": [ 34 | "https://sylwair.com" 35 | ] 36 | }, 37 | "keywords": [ 38 | "task", 39 | "todo", 40 | "list", 41 | "任务", 42 | "代办", 43 | "列表" 44 | ] 45 | } -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syh19/siyuan-plugin-task-list/c175e2d325e32de96a45ded3bd8677a372a23348/preview.png -------------------------------------------------------------------------------- /scripts/.gitignore: -------------------------------------------------------------------------------- 1 | .venv 2 | build 3 | dist 4 | *.exe 5 | *.spec 6 | -------------------------------------------------------------------------------- /scripts/make_dev_link.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import readline from 'node:readline' 3 | 4 | 5 | //************************************ Write you dir here ************************************ 6 | 7 | //Please write the "workspace/data/plugins" directory here 8 | //请在这里填写你的 "workspace/data/plugins" 目录 9 | let targetDir = '' 10 | //Like this 11 | 12 | // const targetDir = `H:\\SiYuanDevSpace\\data\\plugins`; 13 | //******************************************************************************************** 14 | 15 | const log = console.log 16 | 17 | async function getSiYuanDir () { 18 | let url = 'http://127.0.0.1:6806/api/system/getWorkspaces' 19 | let header = { 20 | // "Authorization": `Token ${token}`, 21 | "Content-Type": "application/json", 22 | } 23 | let conf = {} 24 | try { 25 | let response = await fetch(url, { 26 | method: 'POST', 27 | headers: header 28 | }) 29 | if (response.ok) { 30 | conf = await response.json() 31 | } else { 32 | log(`HTTP-Error: ${response.status}`) 33 | return null 34 | } 35 | } catch (e) { 36 | log("Error:", e) 37 | log("Please make sure SiYuan is running!!!") 38 | return null 39 | } 40 | return conf.data 41 | } 42 | 43 | async function chooseTarget (workspaces) { 44 | let count = workspaces.length 45 | log(`Got ${count} SiYuan ${count > 1 ? 'workspaces' : 'workspace'}`) 46 | for (let i = 0; i < workspaces.length; i++) { 47 | log(`[${i}] ${workspaces[i].path}`) 48 | } 49 | 50 | if (count == 1) { 51 | return `${workspaces[0].path}/data/plugins` 52 | } else { 53 | const rl = readline.createInterface({ 54 | input: process.stdin, 55 | output: process.stdout 56 | }) 57 | let index = await new Promise((resolve, reject) => { 58 | rl.question(`Please select a workspace[0-${count - 1}]: `, (answer) => { 59 | resolve(answer) 60 | }) 61 | }) 62 | rl.close() 63 | return `${workspaces[index].path}/data/plugins` 64 | } 65 | } 66 | 67 | if (targetDir === '') { 68 | log('"targetDir" is empty, try to get SiYuan directory automatically....') 69 | let res = await getSiYuanDir() 70 | 71 | if (res === null) { 72 | log('Failed! You can set the plugin directory in scripts/make_dev_link.js and try again') 73 | process.exit(1) 74 | } 75 | 76 | targetDir = await chooseTarget(res) 77 | log(`Got target directory: ${targetDir}`) 78 | } 79 | 80 | //Check 81 | if (!fs.existsSync(targetDir)) { 82 | log(`Failed! plugin directory not exists: "${targetDir}"`) 83 | log(`Please set the plugin directory in scripts/make_dev_link.js`) 84 | process.exit(1) 85 | } 86 | 87 | 88 | //check if plugin.json exists 89 | if (!fs.existsSync('./plugin.json')) { 90 | console.error('Failed! plugin.json not found') 91 | process.exit(1) 92 | } 93 | 94 | //load plugin.json 95 | const plugin = JSON.parse(fs.readFileSync('./plugin.json', 'utf8')) 96 | const name = plugin?.name 97 | if (!name || name === '') { 98 | log('Failed! Please set plugin name in plugin.json') 99 | process.exit(1) 100 | } 101 | 102 | //dev directory 103 | const devDir = `./dev` 104 | //mkdir if not exists 105 | if (!fs.existsSync(devDir)) { 106 | fs.mkdirSync(devDir) 107 | } 108 | 109 | const targetPath = `${targetDir}/${name}` 110 | //如果已经存在,就退出 111 | if (fs.existsSync(targetPath)) { 112 | log(`Failed! Target directory ${targetPath} already exists`) 113 | } else { 114 | //创建软链接 115 | fs.symlinkSync(`${process.cwd()}/dev`, targetPath, 'junction') 116 | log(`Done! Created symlink ${targetPath}`) 117 | } 118 | 119 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/api/ai.ts: -------------------------------------------------------------------------------- 1 | import { request } from "@/utils/request"; 2 | import { taskListForAI } from "@/utils/ai"; 3 | import { i18n } from "@/utils/common"; 4 | export async function getAuthCodeInfo(id: string) { 5 | try { 6 | const response = await request.get(`/auth-code?id=${id}`); 7 | return response; 8 | } catch (error) { 9 | return new Error(error.message === "Network Error" ? "未连接网络" : error); 10 | } 11 | } 12 | 13 | export async function getAiSummary({ 14 | authCode, 15 | name, 16 | deviceId, 17 | retries = 3, 18 | }: { 19 | authCode: string; 20 | name: string; 21 | deviceId: string; 22 | retries?: number; 23 | }) { 24 | try { 25 | const response = await request.post("/ai", { 26 | taskList: taskListForAI, 27 | authCode, 28 | name, 29 | deviceId, 30 | lang: i18n.language, 31 | }); 32 | 33 | return response; 34 | } catch (error) { 35 | if (retries > 0) { 36 | console.log(`请求失败,剩余重试次数: ${retries - 1}`); 37 | return getAiSummary({ authCode, name, deviceId, retries: retries - 1 }); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/api/index.ts: -------------------------------------------------------------------------------- 1 | import * as sySDK from "@siyuan-community/siyuan-sdk"; 2 | import type { TSqlResItem, TResponse } from "@/types"; 3 | import * as utils from "@/utils/common"; 4 | import { 5 | IFile, 6 | IPayload, 7 | } from "@siyuan-community/siyuan-sdk/dist/types/kernel/api/file/putFile"; 8 | 9 | /* 初始化客户端 (默认使用 Axios 发起 XHR 请求) */ 10 | export const client = new sySDK.Client(); 11 | 12 | type TSqlReq = { 13 | isGetAll: boolean; 14 | status?: string; 15 | docId?: string; 16 | boxId?: string; 17 | }; 18 | 19 | /** 获取指定范围的taskNode节点列表 */ 20 | export async function getTaskListBySql( 21 | params: TSqlReq 22 | ): Promise>> { 23 | let stmtStr = "SELECT * FROM blocks WHERE type = 'i' AND subtype = 't'"; 24 | 25 | if (!params.isGetAll && false) { 26 | // 根据配置项排除特定任务 27 | const { data: storage } = await getLocalStorage(); 28 | if (storage["plugin-task-list-settings"]?.["nodeListForHideTask"]) { 29 | storage["plugin-task-list-settings"]?.["nodeListForHideTask"].forEach( 30 | (item: any) => { 31 | if (item.type === "box") { 32 | stmtStr += ` AND box != '${item.key}'`; 33 | } else if (item.type === "doc") { 34 | item.hideTaskInNodeStatus === 1 && 35 | (stmtStr += ` AND root_id != '${item.key}'`); 36 | item.hideTaskInNodeStatus === 2 && 37 | (stmtStr += ` AND path NOT LIKE '%/${item.key}%'`); 38 | } 39 | } 40 | ); 41 | } 42 | 43 | // 最开头的 '* [ ]'或者 '* [_]' 才表示当前任务节点 44 | if (params.status === "todo") { 45 | stmtStr += ` AND markdown LIKE '* [ ]%'`; 46 | } else if (params.status === "done") { 47 | stmtStr += ` AND markdown LIKE '* [_]%' AND markdown NOT LIKE '* [ ]%'`; // 已完成可能有两种:[x] 和 [X] 48 | } else if (params.status === "all") { 49 | stmtStr += ""; 50 | } 51 | 52 | params.docId && (stmtStr += ` AND root_id = '${params.docId}'`); 53 | params.boxId && (stmtStr += ` AND box = '${params.boxId}'`); 54 | } 55 | 56 | const { data: storage } = await getLocalStorage(); 57 | const taskCountLimit = storage["plugin-task-list-settings"]?.["taskCountLimit"] || 2000; 58 | stmtStr += ` ORDER BY created ASC LIMIT ${taskCountLimit}`; 59 | 60 | let taskRes: any = await client.sql({ 61 | stmt: stmtStr, 62 | // SELECT * FROM blocks WHERE type = 'i' AND subtype = 't' AND markdown LIKE '%[ ]%' AND root_id = 'xxx' AND box = 'xxx' ORDER BY created DESC LIMIT 1000 63 | }); 64 | let notebooks: any = await lsNotebooks(); 65 | utils.setNotebooks(notebooks); 66 | let notebooksObj = {}; 67 | notebooks.forEach((item: any) => { 68 | notebooksObj[item.id] = item.name; 69 | }); 70 | taskRes.data.forEach((item: any) => { 71 | item.boxName = notebooksObj[item.box]; 72 | }); 73 | return taskRes; 74 | } 75 | 76 | /** 77 | * 获取单个任务节点的信息 78 | * @param taskId 79 | * @returns {object} 单个任务节点的信息 80 | */ 81 | export async function getSingleTaskInfoBySql(taskId: string): Promise { 82 | let stmtStr: string = `SELECT * FROM blocks WHERE id='${taskId}'`; 83 | const res: any = await client.sql({ 84 | stmt: stmtStr, 85 | }); 86 | if (res?.code === 0) { 87 | return res.data[0]; 88 | } 89 | } 90 | 91 | /** 设置持久化本地存储数据 */ 92 | export async function setLocalStorage(params: { 93 | app?: string; 94 | val: { [key: string]: any }; 95 | }): Promise { 96 | const { data: previousVal } = await client.getLocalStorage(); 97 | params.val = { ...previousVal, ...params.val }; 98 | await client.setLocalStorage(params); 99 | } 100 | 101 | /** 102 | * 设置持久化本地存储数据 103 | * 设置单个键值对 104 | */ 105 | export async function setLocalStorageVal(params: { 106 | key: string; 107 | val: any; 108 | }): Promise { 109 | await client.setLocalStorageVal({ 110 | app: utils.plugin.app.appId, 111 | key: params.key, 112 | val: params.val, 113 | }); 114 | } 115 | 116 | /** 获取持久化本地存储数据 */ 117 | export async function getLocalStorage(): Promise { 118 | return await client.getLocalStorage(); 119 | } 120 | 121 | /** 122 | * 设置块属性 123 | * @param params 124 | */ 125 | export async function setBlockAttrs(params: { 126 | id: string; 127 | attrs: { [key: string]: any }; 128 | }): Promise { 129 | await client.setBlockAttrs(params); 130 | } 131 | 132 | /** 133 | * 获取块属性 134 | * @param params 135 | */ 136 | // export async function getBlockAttrs(params: { id: string }): Promise { 137 | // await client.getBlockAttrs(params) 138 | // } 139 | 140 | /** 141 | * 获取当前文档信息 142 | * @param params 143 | * @returns 144 | */ 145 | export async function getDocInfo(params: { id: string }): Promise { 146 | return await client.getDocInfo(params); 147 | } 148 | 149 | /** 150 | * 获取当前笔记本信息 151 | * @param params 152 | * @returns 153 | */ 154 | export async function getNotebookConf(params: { 155 | notebook: string; 156 | }): Promise { 157 | return await client.getNotebookConf(params); 158 | } 159 | 160 | export async function lsNotebooks(): Promise { 161 | let notebookRes = await client.lsNotebooks(); 162 | return notebookRes.data.notebooks; 163 | } 164 | 165 | export async function putFile(params: IFile): Promise { 166 | return await client.putFile(params); 167 | } 168 | 169 | export async function getFile(params: { 170 | path: string; 171 | emptyDataType: string; 172 | }): Promise { 173 | try { 174 | const res: any = await client.getFile(params, "arraybuffer"); 175 | const decoder = new TextDecoder("utf-8"); 176 | const jsonString = decoder.decode(res); 177 | return JSON.parse(jsonString); 178 | } catch (error) { 179 | if (params.emptyDataType === "string") { 180 | return ""; 181 | } else if (params.emptyDataType === "object") { 182 | return {}; 183 | } else if (params.emptyDataType === "array") { 184 | return []; 185 | } 186 | } 187 | } 188 | 189 | export async function removeFile(params: { path: string }): Promise { 190 | return await client.removeFile(params); 191 | } 192 | 193 | export async function readDir(params: { path: string }): Promise { 194 | return await client.readDir(params); 195 | } 196 | -------------------------------------------------------------------------------- /src/assets/imgs/button-en.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syh19/siyuan-plugin-task-list/c175e2d325e32de96a45ded3bd8677a372a23348/src/assets/imgs/button-en.jpg -------------------------------------------------------------------------------- /src/assets/imgs/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syh19/siyuan-plugin-task-list/c175e2d325e32de96a45ded3bd8677a372a23348/src/assets/imgs/button.png -------------------------------------------------------------------------------- /src/assets/imgs/location-en.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syh19/siyuan-plugin-task-list/c175e2d325e32de96a45ded3bd8677a372a23348/src/assets/imgs/location-en.jpg -------------------------------------------------------------------------------- /src/assets/imgs/location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syh19/siyuan-plugin-task-list/c175e2d325e32de96a45ded3bd8677a372a23348/src/assets/imgs/location.png -------------------------------------------------------------------------------- /src/assets/imgs/overflow-en.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syh19/siyuan-plugin-task-list/c175e2d325e32de96a45ded3bd8677a372a23348/src/assets/imgs/overflow-en.jpg -------------------------------------------------------------------------------- /src/assets/imgs/overflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syh19/siyuan-plugin-task-list/c175e2d325e32de96a45ded3bd8677a372a23348/src/assets/imgs/overflow.png -------------------------------------------------------------------------------- /src/assets/imgs/preview-en.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syh19/siyuan-plugin-task-list/c175e2d325e32de96a45ded3bd8677a372a23348/src/assets/imgs/preview-en.jpg -------------------------------------------------------------------------------- /src/components/AddHandleDate.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 122 | 123 | 130 | -------------------------------------------------------------------------------- /src/components/AiSummaryModal.vue: -------------------------------------------------------------------------------- 1 | 163 | 164 | 312 | 313 | 426 | -------------------------------------------------------------------------------- /src/components/Setting.vue: -------------------------------------------------------------------------------- 1 | 190 | 191 | 388 | 389 | 446 | -------------------------------------------------------------------------------- /src/components/TaskFilter.vue: -------------------------------------------------------------------------------- 1 | 156 | 157 | 312 | 313 | 373 | -------------------------------------------------------------------------------- /src/components/Tree.vue: -------------------------------------------------------------------------------- 1 | 72 | 73 | 315 | 316 | 456 | -------------------------------------------------------------------------------- /src/components/aiComp/ContentCard.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 90 | 91 | 169 | -------------------------------------------------------------------------------- /src/components/aiComp/ContentCardGroup.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 59 | 60 | 116 | -------------------------------------------------------------------------------- /src/components/aiComp/HistoryContentCard.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 69 | 70 | 133 | -------------------------------------------------------------------------------- /src/components/infoCard/index.ts: -------------------------------------------------------------------------------- 1 | import InfoCardVue from '@/components/infoCard/index.vue' 2 | import { render, h } from 'vue' 3 | 4 | class InfoCard { 5 | /** 在该元素上时才需要显示信息 */ 6 | reference?: HTMLElement 7 | /** 要显示的信息数据 */ 8 | info?: any 9 | 10 | constructor(info?: any, reference?: HTMLElement) { 11 | this.info = info 12 | this.reference = reference 13 | 14 | this.mount() 15 | } 16 | mount() { 17 | const VNode = h(InfoCardVue, { 18 | info: this.info, 19 | reference: this.reference, 20 | }) 21 | render(VNode, document.body) 22 | } 23 | 24 | update(info: any, reference: HTMLElement) { 25 | this.info = info 26 | this.reference = reference 27 | this.mount() 28 | } 29 | 30 | destroy() { 31 | render(null, document.body) 32 | } 33 | } 34 | 35 | export default new InfoCard() 36 | -------------------------------------------------------------------------------- /src/components/infoCard/index.vue: -------------------------------------------------------------------------------- 1 | 69 | 135 | 136 | 174 | -------------------------------------------------------------------------------- /src/components/settingComp/SingleTaskHidden.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 83 | 84 | 180 | -------------------------------------------------------------------------------- /src/components/taskMain/SetTaskNodeTop.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 35 | 36 | 45 | -------------------------------------------------------------------------------- /src/hooks/useDatePicker.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | import { i18n } from '@/utils/common' 3 | import eventBus from '@/utils/eventBus' 4 | 5 | /** 调用方 */ 6 | type Caller = 'addHandleDate' | 'dateFilter' | 'taskList' 7 | 8 | type Res = { 9 | datePickerLocale: any 10 | datePickerAttributes: any 11 | handleDayMouseEnter?: any 12 | } 13 | 14 | /** 15 | * 16 | * @param caller 调用该hook的主体 17 | * @returns 18 | */ 19 | export function useDatePicker(caller: Caller): Res { 20 | const datePickerLocale = ref({ 21 | id: i18n.language === 'English' ? 'en' : 'cn', 22 | firstDayOfWeek: 2, 23 | masks: { weekdays: 'WWW' }, 24 | }) 25 | 26 | const datePickerAttributes = ref([]) 27 | 28 | const initDatePickerAttributes = () => { 29 | if (caller === 'addHandleDate') { 30 | datePickerAttributes.value = [ 31 | { 32 | key: 'today', 33 | dot: true, 34 | // highlight: false, 35 | dates: new Date(), 36 | }, 37 | { 38 | key: 'handleDate', 39 | highlight: { 40 | style: 'background-color: var(--tl-color-todo-icon)', 41 | }, 42 | dates: new Date(), 43 | }, 44 | ] 45 | } else if (caller === 'dateFilter') { 46 | datePickerAttributes.value = [{ dates: new Date(), dot: true }] 47 | } else if (caller === 'taskList') { 48 | datePickerAttributes.value = [] 49 | } 50 | } 51 | 52 | initDatePickerAttributes() 53 | 54 | const updateDateTask = () => { 55 | eventBus.on('each-day-task-list-changed', (e: any) => { 56 | datePickerAttributes.value = [ 57 | e.todoTaskPopover, 58 | e.doneTaskPopover, 59 | // 今天的日期样式 60 | { 61 | content: 'blue', 62 | dates: new Date(), 63 | }, 64 | ] 65 | }) 66 | } 67 | if (caller === 'taskList') { 68 | updateDateTask() 69 | } 70 | 71 | // 72 | const handleDayMouseEnter = (day: any) => { 73 | const todoTaskNumInCurrentDay = 74 | datePickerAttributes.value[0].dateNumMap.get(day.id) 75 | const doneTaskNumInCurrentDay = 76 | datePickerAttributes.value[1].dateNumMap.get(day.id) 77 | 78 | datePickerAttributes.value[0].popover.label = 79 | i18n.dockCalendar.todoTaskNumPopover + todoTaskNumInCurrentDay 80 | datePickerAttributes.value[1].popover.label = 81 | i18n.dockCalendar.doneTaskNumPopover + doneTaskNumInCurrentDay 82 | } 83 | 84 | let res: Res = { datePickerLocale, datePickerAttributes } 85 | 86 | if (caller === 'taskList') { 87 | res.handleDayMouseEnter = handleDayMouseEnter 88 | } 89 | 90 | return res 91 | } 92 | -------------------------------------------------------------------------------- /src/hooks/useResizeObserver.ts: -------------------------------------------------------------------------------- 1 | import { ref, Ref } from 'vue' 2 | 3 | export function useResizeObserver(): Ref { 4 | /** dock 栏容器的宽度 */ 5 | const isSmallWidth = ref(false) 6 | 7 | const initResizeObserver = () => { 8 | const observer = new ResizeObserver((entries: any) => { 9 | for (let entry of entries) { 10 | const rect = getComputedStyle(entry.target) 11 | if (+rect.width.slice(0, -2) < 330) { 12 | isSmallWidth.value = true 13 | } else { 14 | isSmallWidth.value = false 15 | } 16 | } 17 | }) 18 | 19 | const rootDom = document.getElementById('siyuan-plugin-task-list') 20 | observer.observe(rootDom) 21 | } 22 | 23 | initResizeObserver() 24 | 25 | return isSmallWidth 26 | } 27 | -------------------------------------------------------------------------------- /src/i18n/en_US.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "English", 3 | "pluginTitle": "TaskList", 4 | "taskStatus": { 5 | "todo": "Todo", 6 | "done": "Done", 7 | "all": "All" 8 | }, 9 | "options": { 10 | "ai": "AI", 11 | "refresh": "Refresh", 12 | "expand": "Expand", 13 | "collapse": "Collapse", 14 | "switch": "Switch Status", 15 | "filter": "Filter", 16 | "search": "Search" 17 | }, 18 | "searchPlaceholder": "Please enter task to search", 19 | "range": { 20 | "doc": "Document", 21 | "box": "Notebook", 22 | "workspace": "Workspace" 23 | }, 24 | "docTreeEmptyText": "No tasks created yet", 25 | "emptyText": { 26 | "allNotebooksHidden": "You have set task hidden scope: all notebooks", 27 | "currentNotebook": "You have set task hidden scope: current notebook", 28 | "currentDoc": "You have set task hidden scope: current document or document and its subdocuments", 29 | "dateFilter": "You have filtered tasks: No tasks under current date", 30 | "statusFilter": "You have filtered tasks: No tasks under current status", 31 | "rangeStatus": "You have filtered tasks: No tasks under current dimension", 32 | "reallyNoTask": "Great, all tasks completed~" 33 | }, 34 | "setting": { 35 | "title": "Settings", 36 | "cancel": "Cancel", 37 | "confirm": "Confirm", 38 | "taskCountLimit": "Task Count Limit", 39 | "infoCardConfigDivider": "Info Card Configuration", 40 | "taskListConfigDivider": "Task List Configuration", 41 | "docTaskConfigDivider": "Document Task Display Configuration", 42 | "hideTaskTreeDesc": "Tasks in the nodes you checked will be hidden", 43 | "onlySelf": "Self Only", 44 | "selfAndChildren": "Self & Children", 45 | "singleTaskHiddenDesc": "The following tasks are hidden, you can show them here", 46 | "showTask": "Show Task", 47 | "noHiddenTask": "No Hidden Tasks", 48 | "taskListDisplayLabel": "Task List Display Mode", 49 | "boxDocTask": "Notebook & Doc & Task", 50 | "boxTask": "Notebook & Task", 51 | "onlyTask": "Task Only", 52 | "sortItem": { 53 | "sortBy": "Task Sort Method", 54 | "placeholder": "Please select sort method", 55 | "createdAsc": "Created Time ASC", 56 | "createdDesc": "Created Time DESC", 57 | "updatedAsc": "Updated Time ASC", 58 | "updatedDesc": "Updated Time DESC", 59 | "finishedAsc": "Finished Time ASC", 60 | "finishedDesc": "Finished Time DESC" 61 | }, 62 | "infoCardConfig": { 63 | "dateTimeDisplayMode": "Date Time Display Mode", 64 | "dateFormat": "yyyy-mm-dd", 65 | "dateTimeFormat": "yyyy-mm-dd hh:mm", 66 | "fieldsForHidden": "Fields Selected to Hide", 67 | "fieldsForHiddenPlaceholder": "Please select fields to hide" 68 | }, 69 | "docTaskConfig": { 70 | "isShowExtraInfoOnDocTask": "Show Extra Info Near Tasks" 71 | } 72 | }, 73 | "infoCard": { 74 | "taskName": "Task Name", 75 | "created": "Created Time", 76 | "handleAt": "Handle Time", 77 | "updated": "Updated Time", 78 | "finished": "Finished Time", 79 | "box": "Notebook", 80 | "docPath": "Doc Path" 81 | }, 82 | "filterConfig": { 83 | "title": "Task Filter Configuration", 84 | "taskFilterWay": "Task Filter Method", 85 | "monthRange": "Month View Range Filter", 86 | "dockCalendar": "Dock Bar Date Filter", 87 | "dockCalendarDisplayMode": "Calendar Display Mode", 88 | "dockWeekMode": "Week View", 89 | "dockMonthMode": "Month View", 90 | "dateRangeFormat": "Date Range Filter Format", 91 | "static": "Static Date Filter", 92 | "dynamic": "Dynamic Date Filter", 93 | "dateRangeValue": "Please Select Date Range", 94 | "clearBtn": "Clear", 95 | "dynamicDateRangeList": { 96 | "month": "Current Month", 97 | "week": "Current Week", 98 | "pastThreeDays": "Past Three Days", 99 | "futureThreeDays": "Future Three Days", 100 | "today": "Today" 101 | } 102 | }, 103 | "dockCalendar": { 104 | "todoTaskNumPopover": "Todo Task Count: ", 105 | "doneTaskNumPopover": "Done Task Count: " 106 | }, 107 | "addHandleDate": "Add Task Handle Time", 108 | "hideSingleTask": "Hide Task", 109 | "hideSelfTask": "Hide Tasks in This Node", 110 | "hideChildTask": "Hide Tasks in This Node and Children", 111 | "cancel": "Cancel", 112 | "confirm": "Confirm", 113 | "top": "Pin to Top", 114 | "aiRoast": { 115 | "title": "AI Summary", 116 | "authCode": "Authentication Code", 117 | "authCodePlaceholder": "Click the button on the right to enter authentication code", 118 | "getAuthCode": "Get Authentication Code?", 119 | "totalTimes": "Total Times", 120 | "remainingTimes": "Remaining Times", 121 | "save": "Save", 122 | "edit": "Edit", 123 | "show": "Show", 124 | "hide": "Hide", 125 | "refresh": "Refresh", 126 | "callMe": "How would you like me to address you?", 127 | "aiGenerate": "AI Generate", 128 | "shareAs": "Share as", 129 | "img": "Image", 130 | "current": "Current", 131 | "history": "History", 132 | "close": "Close", 133 | "footer": { 134 | "by": "By", 135 | "siYuanNote": "SiYuan Note", 136 | "plugin": "Plugin", 137 | "taskList": "Task List", 138 | "generatedByAI": "Generated by AI" 139 | }, 140 | "all": "All", 141 | "empty": "No data available, please click AI Generate button", 142 | "content": { 143 | "profile": "Character Profile" 144 | }, 145 | "toast": { 146 | "downloadSuccess": "Image Download Success", 147 | "downloadError": "Image Download Failed", 148 | "fileNamePrefix": "SiYuan Task-List AI", 149 | "netWorkError": "Network Connection Lost, Please Check Your Network Settings" 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/i18n/zh_CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "简体中文", 3 | "pluginTitle": "任务列表", 4 | "taskStatus": { 5 | "todo": "未完成", 6 | "done": "已完成", 7 | "all": "全部" 8 | }, 9 | "options": { 10 | "ai": "AI", 11 | "refresh": "刷新", 12 | "expand": "展开", 13 | "collapse": "收起", 14 | "switch": "切换状态", 15 | "filter": "筛选", 16 | "search": "搜索" 17 | }, 18 | "searchPlaceholder": "请输入要搜索的任务", 19 | "range": { 20 | "doc": "文档", 21 | "box": "笔记本", 22 | "workspace": "工作空间" 23 | }, 24 | "docTreeEmptyText": "您没有创建任何任务", 25 | "emptyText": { 26 | "allNotebooksHidden": "您设置了任务的隐藏范围:所有笔记本", 27 | "currentNotebook": "您设置了任务的隐藏范围:当前笔记本", 28 | "currentDoc": "您设置了任务的隐藏范围:当前文档或者文档及其子文档", 29 | "dateFilter": "您对任务进行了过滤:当前日期下无任务", 30 | "statusFilter": "您对任务进行了过滤:当前状态下无任务", 31 | "rangeStatus": "您对任务进行了过滤:当前维度下无任务", 32 | "reallyNoTask": "太棒了,所有任务都完成了~" 33 | }, 34 | "setting": { 35 | "title": "设置", 36 | "cancel": "取消", 37 | "confirm": "确认", 38 | "taskCountLimit": "任务数量控制", 39 | "infoCardConfigDivider": "信息卡片配置", 40 | "taskListConfigDivider": "任务列表配置", 41 | "docTaskConfigDivider": "文档编辑区域信息展示配置", 42 | "hideTaskTreeDesc": "您所勾选的节点,其中的任务会将被隐藏", 43 | "onlySelf": "仅自身", 44 | "selfAndChildren": "自身及子节点", 45 | "singleTaskHiddenDesc": "以下是被隐藏的任务,您可以在这里重新显示它们", 46 | "showTask": "显示任务", 47 | "noHiddenTask": "没有隐藏的任务", 48 | "taskListDisplayLabel": "任务列表的展示方式", 49 | "boxDocTask": "笔记本 & 文档 & 任务", 50 | "boxTask": "笔记本 & 任务", 51 | "onlyTask": "仅任务", 52 | "sortItem": { 53 | "sortBy": "任务排序方式", 54 | "placeholder": "请选择排序方式", 55 | "createdAsc": "创建时间升序", 56 | "createdDesc": "创建时间降序", 57 | "updatedAsc": "更新时间升序", 58 | "updatedDesc": "更新时间降序", 59 | "finishedAsc": "完成时间升序", 60 | "finishedDesc": "完成时间降序" 61 | }, 62 | "infoCardConfig": { 63 | "dateTimeDisplayMode": "日期时间展示方式", 64 | "dateFormat": "yyyy-mm-dd", 65 | "dateTimeFormat": "yyyy-mm-dd hh:mm", 66 | "fieldsForHidden": "隐藏所选字段", 67 | "fieldsForHiddenPlaceholder": "请选择想要隐藏的字段" 68 | }, 69 | "docTaskConfig": { 70 | "isShowExtraInfoOnDocTask": "是否在任务附近显示附加信息" 71 | } 72 | }, 73 | "infoCard": { 74 | "taskName": "任务名称", 75 | "created": "创建时间", 76 | "handleAt": "处理时间", 77 | "updated": "更新时间", 78 | "finished": "完成时间", 79 | "box": "笔记本", 80 | "docPath": "文档路径" 81 | }, 82 | "filterConfig": { 83 | "title": "任务筛选配置", 84 | "taskFilterWay": "任务筛选方式", 85 | "monthRange": "月视图范围筛选", 86 | "dockCalendar": "Dock栏日期筛选", 87 | "dockCalendarDisplayMode": "日历显示模式", 88 | "dockWeekMode": "周视图", 89 | "dockMonthMode": "月视图", 90 | "dateRangeFormat": "日期范围筛选形式", 91 | "static": "静态日期筛选", 92 | "dynamic": "动态日期筛选", 93 | "dateRangeValue": "请选择日期范围", 94 | "clearBtn": "清空", 95 | "dynamicDateRangeList": { 96 | "month": "本月", 97 | "week": "本周", 98 | "pastThreeDays": "过去三天", 99 | "futureThreeDays": "未来三天", 100 | "today": "今天" 101 | } 102 | }, 103 | "dockCalendar": { 104 | "todoTaskNumPopover": "待处理 任务数量:", 105 | "doneTaskNumPopover": "已完成 任务数量:" 106 | }, 107 | "addHandleDate": "添加任务处理时间", 108 | "hideSingleTask": "隐藏任务", 109 | "hideSelfTask": "隐藏节点自身中的任务", 110 | "hideChildTask": "隐藏节点自身及子节点中的任务", 111 | "cancel": "取消", 112 | "confirm": "确认", 113 | "top": "置顶", 114 | "aiRoast": { 115 | "title": "AI 总结", 116 | "authCode": "认证码", 117 | "authCodePlaceholder": "点击右侧按钮输入认证码", 118 | "getAuthCode": "获取认证码?", 119 | "totalTimes": "总次数", 120 | "remainingTimes": "剩余次数", 121 | "save": "保存", 122 | "edit": "编辑", 123 | "show": "显示", 124 | "hide": "隐藏", 125 | "refresh": "刷新", 126 | "callMe": "想让我如何称呼您?", 127 | "aiGenerate": "AI 生成", 128 | "shareAs": "分享为", 129 | "img": "图片", 130 | "current": "当前", 131 | "history": "历史", 132 | "close": "关闭", 133 | "footer": { 134 | "by": "由", 135 | "siYuanNote": "思源笔记", 136 | "plugin": "插件", 137 | "taskList": "任务列表", 138 | "generatedByAI": "通过 AI 生成" 139 | }, 140 | "all": "全部", 141 | "empty": "暂无数据,请点击 AI 生成 按钮", 142 | "content": { 143 | "profile": "人物概述" 144 | }, 145 | "toast": { 146 | "downloadSuccess": "图片下载成功", 147 | "downloadError": "图片下载失败", 148 | "fileNamePrefix": "思源插件任务列表 AI", 149 | "netWorkError": "网络连接已断开,请检查您的网络设置" 150 | } 151 | } 152 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from 'siyuan' 2 | import * as utils from '@/utils/common' 3 | import * as addBtn from '@/utils/addButton' 4 | import '@/styles/index.scss' 5 | import * as task from '@/utils/handleTaskNode' 6 | import '@/utils/compatible' 7 | import { initLocalStorageWhenFirstUsePlugin } from '@/utils/initLocalStorage' 8 | import { addHandleDateToTaskNode } from '@/utils/addInfoToHtmlNode' 9 | import { useGlobalStore } from '@/store/index' 10 | const globalStore = useGlobalStore() 11 | 12 | export default class TaskListPlugin extends Plugin { 13 | async onload() { 14 | console.log( 15 | `%c任务列表插件 siyuan-plugin-task-list 加载完成`, 16 | 'color: #429077;font-size:22px' 17 | ) 18 | this.init() 19 | await initLocalStorageWhenFirstUsePlugin() 20 | await addBtn.addDock() 21 | this.eventBus.on('loaded-protyle-static', (e: any) => { 22 | utils.setCurrentDocId(e.detail.protyle.block.rootID) 23 | }) 24 | this.eventBus.on('switch-protyle', async (e: any) => { 25 | utils.setCurrentDocId(e.detail.protyle.block.rootID) 26 | 27 | globalStore.setCurrentDocInfo(e.detail.protyle.block.rootID) 28 | 29 | utils.setCurrentBoxId(e.detail.protyle.notebookId) 30 | globalStore.setCurrentBoxInfo(e.detail.protyle.notebookId) 31 | // utils.addOperationForTaskNode(e) 32 | 33 | await utils.sleep(350); 34 | addHandleDateToTaskNode() 35 | }) 36 | 37 | // 编辑事件 & 其他消息事件 38 | this.eventBus.on('ws-main', (e: any) => { 39 | task.taskNodeFinishedSetAttrs(e) 40 | }) 41 | 42 | // 块菜单打开事件 43 | this.eventBus.on('click-blockicon', (e: any) => { 44 | addBtn.addBlockMenuForTaskNode(e) 45 | }) 46 | } 47 | 48 | onLayoutReady(): void { 49 | globalStore.setCurrentWorkSpaceName() 50 | globalStore.setCurrentThemeMode() 51 | // console.log('当前APP对象:', this.app) 52 | } 53 | 54 | onunload() { 55 | console.log( 56 | `%c任务列表插件 siyuan-plugin-task-list 卸载完成`, 57 | 'color: #e25f8a;font-size:22px' 58 | ) 59 | } 60 | 61 | /** 62 | * 获取i18n和插件类实例 63 | */ 64 | init() { 65 | utils.setI18n(this.i18n) 66 | utils.setPlugin(this) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import * as API from '@/api/index' 3 | 4 | export const useGlobalStore = defineStore('global', { 5 | state: () => ({ 6 | currentDocInfo: { id: '', rootID: '', name: '' }, 7 | currentBoxInfo: { box: '', name: '' }, 8 | currentWorkSpaceName: '', 9 | currentThemeMode: 'light', 10 | }), 11 | 12 | actions: { 13 | async setCurrentDocInfo(id: string) { 14 | const docInfo: any = await API.getDocInfo({ id }) 15 | this.currentDocInfo = docInfo.data 16 | }, 17 | 18 | async setCurrentBoxInfo(notebook: string) { 19 | const boxInfo: any = await API.getNotebookConf({ notebook }) 20 | this.currentBoxInfo = boxInfo.data 21 | }, 22 | 23 | setCurrentWorkSpaceName() { 24 | const workSpaceName: string = document 25 | .getElementById('toolbar') 26 | .querySelector('#barWorkspace .toolbar__text').innerHTML 27 | 28 | this.currentWorkSpaceName = workSpaceName 29 | }, 30 | 31 | setCurrentThemeMode() { 32 | this.currentThemeMode = 33 | document.documentElement.getAttribute('data-theme-mode') 34 | }, 35 | }, 36 | }) 37 | -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import '@/styles/vc-calendar.scss'; 2 | 3 | // 高亮选中的taskNode节点 4 | .plugin-task-list__hightlight-node { 5 | background-color: var(--tl-color-active-bg); 6 | } 7 | 8 | .icon { 9 | width: 1em; 10 | height: 1em; 11 | vertical-align: -0.15em; 12 | fill: currentColor; 13 | overflow: hidden; 14 | } 15 | 16 | // 图标和描述的通用样式 17 | .icon-label-wrap { 18 | display: flex; 19 | justify-content: flex-start; 20 | align-items: center; 21 | color: var(--tl-color-text); 22 | svg.icon { 23 | font-size: 18px; 24 | &.icon-box { 25 | color: var(--tl-color-notebook-icon); 26 | } 27 | &.icon-doc { 28 | color: var(--tl-color-doc-icon); 29 | } 30 | &.icon-todo { 31 | color: var(--tl-color-todo-icon); 32 | } 33 | &.icon-done { 34 | color: var(--tl-color-done-icon); 35 | } 36 | } 37 | > span { 38 | padding-left: 10px; 39 | width: 100%; 40 | overflow: hidden; 41 | text-overflow: ellipsis; 42 | white-space: nowrap; 43 | } 44 | } 45 | 46 | .el-dialog { 47 | background: var(--tl-color-surface-bg); 48 | header { 49 | .el-dialog__title { 50 | color: var(--tl-color-text); 51 | } 52 | } 53 | } 54 | 55 | @mixin text-ellipsis { 56 | overflow: hidden; 57 | text-overflow: ellipsis; 58 | white-space: nowrap; 59 | } 60 | 61 | .siyuan-plugin-task-date-info, 62 | .siyuan-plugin-task-date-info-wrapper { 63 | pointer-events: none !important; 64 | user-select: none !important; 65 | -webkit-user-select: none !important; 66 | -moz-user-select: none !important; 67 | -ms-user-select: none !important; 68 | contenteditable: false !important; 69 | 70 | &[data-type="siyuan-plugin-custom-date-info"], 71 | &[data-type="siyuan-plugin-custom-wrapper"] { 72 | pointer-events: none !important; 73 | user-select: none !important; 74 | } 75 | } 76 | 77 | // [data-type="NodeHTMLBlock"] { 78 | // .siyuan-plugin-task-date-info, 79 | // .siyuan-plugin-task-date-info-wrapper { 80 | // display: none !important; 81 | // } 82 | // } 83 | -------------------------------------------------------------------------------- /src/styles/themes.scss: -------------------------------------------------------------------------------- 1 | // 定义基础色值 2 | :root { 3 | /** 公共主题 */ 4 | --tl-color-surface-bg: var(--b3-theme-background); 5 | --tl-color-surface-deep-bg: var(--b3-theme-surface); 6 | --tl-color-text: var(--b3-theme-on-background); 7 | --tl-color-border: var(--b3-border-color); 8 | --tl-color-btn-bg: var(--b3-toolbar-hover); 9 | --tl-color-btn-hover-bg: var(--b3-scroll-color-hover); 10 | --tl-color-btn-active-bg: var(--b3-toolbar-color); 11 | 12 | // --tl-color-todo-icon: #f4538a; 13 | 14 | /** light 主题 */ 15 | --tl-color-notebook-icon-light: #0077c6; 16 | --tl-color-doc-icon-light: #6a097d; 17 | --tl-color-todo-icon-light: #d74b76; 18 | --tl-color-done-icon-light: #58a399; 19 | --tl-color-active-bg-light: #e3e2e2; 20 | --tl-color-hover-bg-light: #f3f3f3; 21 | --tl-color-tabs-border-light: #d3d1cc; 22 | 23 | /** dark 主题 */ 24 | --tl-color-notebook-icon-dark: #faa300; 25 | --tl-color-doc-icon-dark: #8b93ff; 26 | --tl-color-todo-icon-dark: #f4538a; 27 | --tl-color-done-icon-dark: #59d5e0; 28 | --tl-color-active-bg-dark: #27374d; 29 | --tl-color-hover-bg-dark: #2c2f32; 30 | --tl-color-tabs-border-dark: #000000; 31 | } 32 | 33 | // Light 默认主题色 34 | html[data-theme-mode='light'] { 35 | --tl-color-notebook-icon: var(--tl-color-notebook-icon-light); 36 | --tl-color-doc-icon: var(--tl-color-doc-icon-light); 37 | --tl-color-todo-icon: var(--tl-color-todo-icon-light); 38 | --tl-color-done-icon: var(--tl-color-done-icon-light); 39 | --tl-color-active-bg: var(--tl-color-active-bg-light); 40 | --tl-color-hover-bg: var(--tl-color-hover-bg-light); 41 | --tl-color-tabs-border: var(--tl-color-tabs-border-light); 42 | } 43 | 44 | // Dark 默认主题色 45 | html[data-theme-mode='dark'] { 46 | --tl-color-notebook-icon: var(--tl-color-notebook-icon-dark); 47 | --tl-color-doc-icon: var(--tl-color-doc-icon-dark); 48 | --tl-color-todo-icon: var(--tl-color-todo-icon-dark); 49 | --tl-color-done-icon: var(--tl-color-done-icon-dark); 50 | --tl-color-active-bg: var(--tl-color-active-bg-dark); 51 | --tl-color-hover-bg: var(--tl-color-hover-bg-dark); 52 | --tl-color-tabs-border: var(--tl-color-tabs-border-dark); 53 | } 54 | -------------------------------------------------------------------------------- /src/styles/vc-calendar.scss: -------------------------------------------------------------------------------- 1 | @import '@/styles/themes.scss'; 2 | 3 | .vc-container { 4 | .vc-highlight-content-solid { 5 | // background-color: var(--tl-color-todo-icon) !important; 6 | background-color: #ffb4cb !important; 7 | } 8 | color: var(--tl-color-text); 9 | &.vc-bordered { 10 | border-color: var(--tl-color-border); 11 | } 12 | button { 13 | background-color: var(--tl-color-btn-bg) !important; 14 | } 15 | } 16 | 17 | .vc-popover-content-wrapper { 18 | color: var(--tl-color-text); 19 | .vc-popover-content { 20 | background-color: var(--tl-color-surface-deep-bg); 21 | button { 22 | background-color: var(--tl-color-btn-bg) !important; 23 | } 24 | button.is-active { 25 | background-color: var(--tl-color-btn-active-bg) !important; 26 | } 27 | button.is-current { 28 | background-color: var(--tl-color-btn-hover-bg) !important; 29 | color: var(--tl-color-text); 30 | } 31 | } 32 | } 33 | 34 | /** 每日任务情况popover浮窗 */ 35 | .vc-popover-content-wrapper { 36 | .vc-day-popover-container { 37 | .vc-day-popover-header { 38 | color: var(--b3-theme-on-background); 39 | } 40 | color: var(--b3-theme-on-background); 41 | } 42 | } 43 | 44 | /** dock栏中的日历样式 */ 45 | .plugin-task-list-wrap { 46 | .vc-container { 47 | border-bottom: none; 48 | border-radius: 0; 49 | } 50 | 51 | .vc-container .vc-pane-container { 52 | .vc-pane-header-wrapper { 53 | .vc-header { 54 | margin-top: 0px; 55 | button.vc-arrow { 56 | scale: 0.7; 57 | color: var(--tl-color-text) !important; 58 | } 59 | } 60 | } 61 | .vc-pane-layout { 62 | .vc-header { 63 | margin-top: 0px; 64 | .vc-title-wrapper { 65 | scale: 0.8; 66 | .vc-title { 67 | color: var(--tl-color-text) !important; 68 | } 69 | } 70 | } 71 | 72 | .vc-weeks { 73 | padding-top: 0px; 74 | padding-bottom: 12px; 75 | // 周一到周日那一行的样式 76 | .vc-weekdays { 77 | height: 20px; 78 | .vc-weekday { 79 | scale: 0.9; 80 | color: var(--tl-color-text) !important; 81 | } 82 | } 83 | 84 | // 下面的日期 85 | .vc-week { 86 | height: 20px; 87 | margin-top: 6px; 88 | .vc-day { 89 | scale: 0.9; 90 | } 91 | } 92 | } 93 | } 94 | } 95 | 96 | // 月视图模式下隐藏最后一行没有日期的元素 97 | .vc-container.vc-monthly { 98 | .vc-pane-container { 99 | .vc-pane-layout 100 | .vc-weeks 101 | .vc-week:last-child:has(.vc-day:first-child.is-not-in-month) { 102 | display: none; 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/types/index.d.ts: -------------------------------------------------------------------------------- 1 | export type IRange = 'doc' | 'box' | 'workspace' 2 | 3 | export type TResponse = { 4 | code: number 5 | data: T 6 | msg: string 7 | } 8 | 9 | /** sql接口的返回值类型 */ 10 | export type TSqlResItem = { 11 | alias: string 12 | box: string 13 | content: string 14 | created: string 15 | fcontent: string 16 | hash: string 17 | hapth: string 18 | ial: string 19 | id: string 20 | length: number 21 | markdown: string 22 | memo: string 23 | name: string 24 | parent_id: string 25 | path: string 26 | root_id: string 27 | sort: number 28 | subtype: string 29 | tag: string 30 | type: string 31 | updated: string 32 | } 33 | -------------------------------------------------------------------------------- /src/types/settings.d.ts: -------------------------------------------------------------------------------- 1 | export type SortOptionValue = 2 | | "createdAsc" 3 | | "createdDesc" 4 | | "updatedAsc" 5 | | "updatedDesc" 6 | | "finishedAsc" 7 | | "finishedDesc"; 8 | 9 | export type LocalSettings = { 10 | /** 信息卡片的配置 */ 11 | infoCardConfig: { 12 | dateTimeDisplayMode: "date" | "dateTime"; 13 | fieldsForHidden: string[]; 14 | }; 15 | /** 需要隐藏任务的节点,包括笔记本节点或者是文档节点 */ 16 | nodeListForHideTask: any[]; 17 | /** 任务列表树的显示模式 */ 18 | taskTreeDisplayMode: "box-doc-task" | "box-task" | "only-task"; 19 | /** 任务节点的排序方式 */ 20 | taskSortBy: SortOptionValue; 21 | }; 22 | -------------------------------------------------------------------------------- /src/utils/addButton.ts: -------------------------------------------------------------------------------- 1 | import { plugin, i18n } from '@/utils/common' 2 | import App from '@/App.vue' 3 | import { createApp } from 'vue' 4 | import eventBus from '@/utils/eventBus' 5 | import { addIcons } from '@/utils/addIcon' 6 | import { createPinia } from 'pinia' 7 | 8 | import ElementPlus from 'element-plus' 9 | import 'element-plus/dist/index.css' 10 | 11 | const app = createApp(App) 12 | 13 | const pinia = createPinia() 14 | app.use(pinia) 15 | 16 | // size 用于设置表单组件的默认尺寸,zIndex 用于设置弹出组件的层级,zIndex 的默认值为 2000。 17 | app.use(ElementPlus, { size: 'small', zIndex: 3000 }) 18 | 19 | /** 20 | * 添加右下角 dock 按钮 21 | */ 22 | export async function addDock() { 23 | addIcons() 24 | plugin.addDock({ 25 | config: { 26 | position: 'RightTop', 27 | size: { width: 200, height: 0 }, 28 | icon: `tl-task`, 29 | hotkey: '⇧⌘T', 30 | title: i18n.pluginTitle, 31 | show: false, 32 | }, 33 | data: { 34 | text: i18n.pluginTitle, 35 | }, 36 | type: 'dock_tab', 37 | async init() { 38 | // 添加id 39 | this.element.id = 'siyuan-plugin-task-list' 40 | this.element.style.height = '100%' 41 | // 由于本插件依赖于当前编辑区中的文档,所以等到编辑区加载完毕后再初始化组件 42 | setTimeout(() => { 43 | app.mount(this.element) 44 | }, 100) 45 | }, 46 | destroy() { 47 | // console.log('destroy dock: dock_tab') 48 | }, 49 | }) 50 | } 51 | 52 | /** 53 | * 为任务节点添加额外的菜单项 54 | * 事件 click-blockicon 的监听函数 55 | * @param e 56 | */ 57 | export function addBlockMenuForTaskNode(e: CustomEvent): void { 58 | const taskId: string = isClickedTaskNodeIcon(e.detail) 59 | if (taskId) { 60 | e.detail.menu.addItem({ 61 | icon: 'tl-greenTask', 62 | label: i18n.addHandleDate, 63 | click: () => { 64 | eventBus.emit('add-handle-date-for-task-node', taskId) 65 | }, 66 | }) 67 | } 68 | } 69 | 70 | /** 71 | * 判断点击的是不是task节点的图标,并返回task节点ID 72 | * @param detail 73 | * @returns {string} 任务节点ID 74 | */ 75 | function isClickedTaskNodeIcon(detail: any): string { 76 | const blockEle: HTMLElement = detail.blockElements[0] 77 | 78 | const dataType: string = 79 | blockEle.attributes.getNamedItem('data-type')?.nodeValue 80 | 81 | const dataSubType: string = 82 | blockEle.attributes.getNamedItem('data-subtype')?.nodeValue 83 | 84 | const dataNodeId: string = 85 | blockEle.attributes.getNamedItem('data-node-id')?.nodeValue 86 | 87 | // 点击的是任务列表子节点才会展示设置 88 | if (dataSubType === 't') { 89 | if (dataType === 'NodeListItem') { 90 | return dataNodeId 91 | } else if (dataType === 'NodeList') { 92 | const realTaskBlock: HTMLElement = blockEle.querySelector( 93 | 'div[data-subtype="t"][data-subtype="t"][data-type="NodeListItem"]' 94 | ) 95 | return realTaskBlock.attributes.getNamedItem('data-node-id').nodeValue 96 | } 97 | } else { 98 | return '' 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/utils/addIcon.ts: -------------------------------------------------------------------------------- 1 | import { plugin } from "@/utils/common"; 2 | 3 | const icons = [ 4 | { 5 | id: "tl-ai", 6 | viewBox: "0 0 24 24", 7 | pid: "1234", 8 | fill: "", 9 | path: "M10.6144 17.7956C10.277 18.5682 9.20776 18.5682 8.8704 17.7956L7.99275 15.7854C7.21171 13.9966 5.80589 12.5726 4.0523 11.7942L1.63658 10.7219C.868536 10.381.868537 9.26368 1.63658 8.92276L3.97685 7.88394C5.77553 7.08552 7.20657 5.60881 7.97427 3.75892L8.8633 1.61673C9.19319.821767 10.2916.821765 10.6215 1.61673L11.5105 3.75894C12.2782 5.60881 13.7092 7.08552 15.5079 7.88394L17.8482 8.92276C18.6162 9.26368 18.6162 10.381 17.8482 10.7219L15.4325 11.7942C13.6789 12.5726 12.2731 13.9966 11.492 15.7854L10.6144 17.7956ZM4.53956 9.82234C6.8254 10.837 8.68402 12.5048 9.74238 14.7996 10.8008 12.5048 12.6594 10.837 14.9452 9.82234 12.6321 8.79557 10.7676 7.04647 9.74239 4.71088 8.71719 7.04648 6.85267 8.79557 4.53956 9.82234ZM19.4014 22.6899 19.6482 22.1242C20.0882 21.1156 20.8807 20.3125 21.8695 19.8732L22.6299 19.5353C23.0412 19.3526 23.0412 18.7549 22.6299 18.5722L21.9121 18.2532C20.8978 17.8026 20.0911 16.9698 19.6586 15.9269L19.4052 15.3156C19.2285 14.8896 18.6395 14.8896 18.4628 15.3156L18.2094 15.9269C17.777 16.9698 16.9703 17.8026 15.956 18.2532L15.2381 18.5722C14.8269 18.7549 14.8269 19.3526 15.2381 19.5353L15.9985 19.8732C16.9874 20.3125 17.7798 21.1156 18.2198 22.1242L18.4667 22.6899C18.6473 23.104 19.2207 23.104 19.4014 22.6899ZM18.3745 19.0469 18.937 18.4883 19.4878 19.0469 18.937 19.5898 18.3745 19.0469Z", 10 | }, 11 | { 12 | id: "tl-sync", 13 | viewBox: "0 0 1024 1024", 14 | pid: "1311", 15 | fill: "", 16 | path: "M168 504.2c1-43.7 10-86.1 26.9-126 17.3-41 42.1-77.7 73.7-109.4S337 212.3 378 195c42.4-17.9 87.4-27 133.9-27s91.5 9.1 133.8 27c40.9 17.3 77.7 42.1 109.3 73.8 9.9 9.9 19.2 20.4 27.8 31.4l-60.2 47c-5.3 4.1-3.5 12.5 3 14.1l175.7 43c5 1.2 9.9-2.6 9.9-7.7l0.8-180.9c0-6.7-7.7-10.5-12.9-6.3l-56.4 44.1C765.8 155.1 646.2 92 511.8 92 282.7 92 96.3 275.6 92 503.8c-0.1 4.5 3.5 8.2 8 8.2h60c4.4 0 7.9-3.5 8-7.8zM924 512h-60c-4.4 0-7.9 3.5-8 7.8-1 43.7-10 86.1-26.9 126-17.3 41-42.1 77.8-73.7 109.4S687 811.7 646 829c-42.4 17.9-87.4 27-133.9 27s-91.5-9.1-133.9-27c-40.9-17.3-77.7-42.1-109.3-73.8-9.9-9.9-19.2-20.4-27.8-31.4l60.2-47c5.3-4.1 3.5-12.5-3-14.1l-175.7-43c-5-1.2-9.9 2.6-9.9 7.7l-0.7 181c0 6.7 7.7 10.5 12.9 6.3l56.4-44.1C258.2 868.9 377.8 932 512.2 932c229.2 0 415.5-183.7 419.8-411.8 0.1-4.5-3.5-8.2-8-8.2z", 17 | }, 18 | { 19 | id: "tl-repeat", 20 | viewBox: "0 0 1024 1024", 21 | pid: "1787", 22 | fill: "", 23 | path: "M810.666667 533.333333V725.333333a85.333333 85.333333 0 0 1-85.333334 85.333334H298.666667v85.333333a21.333333 21.333333 0 0 1-36.266667 15.36l-128-128a21.76 21.76 0 0 1 0-30.293333l128-128A21.333333 21.333333 0 0 1 298.666667 640v85.333333h426.666666v-192a21.333333 21.333333 0 0 1 21.333334-21.333333h42.666666a21.333333 21.333333 0 0 1 21.333334 21.333333z m79.36-292.693333l-128-128A21.333333 21.333333 0 0 0 725.333333 128v85.333333H298.666667a85.333333 85.333333 0 0 0-85.333334 85.333334v192a21.333333 21.333333 0 0 0 21.333334 21.333333h42.666666a21.333333 21.333333 0 0 0 21.333334-21.333333V298.666667h426.666666v85.333333a21.333333 21.333333 0 0 0 36.266667 15.36l128-128a21.76 21.76 0 0 0 0.426667-30.72z", 24 | }, 25 | { 26 | id: "tl-verticalAlign", 27 | viewBox: "0 0 1024 1024", 28 | pid: "1948", 29 | fill: "", 30 | path: "M859.9 474H164.1c-4.5 0-8.1 3.6-8.1 8v60c0 4.4 3.6 8 8.1 8h695.8c4.5 0 8.1-3.6 8.1-8v-60c0-4.4-3.6-8-8.1-8zM506.3 399.3c2.9 3.7 8.5 3.7 11.3 0l100.8-127.5c3.7-4.7 0.4-11.7-5.7-11.7H550V104c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v156h-62.8c-6 0-9.4 7-5.7 11.7l100.8 127.6zM517.7 624.7c-2.9-3.7-8.5-3.7-11.3 0L405.6 752.3c-3.7 4.7-0.4 11.7 5.7 11.7H474v156c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V764h62.8c6 0 9.4-7 5.7-11.7L517.7 624.7z", 31 | }, 32 | { 33 | id: "tl-columnHeight", 34 | viewBox: "0 0 1024 1024", 35 | pid: "2109", 36 | fill: "", 37 | path: "M840 836H184c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h656c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8zM840 112H184c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h656c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8zM610.8 378c6 0 9.4-7 5.7-11.7L515.7 238.7c-2.9-3.7-8.5-3.7-11.3 0L403.6 366.3c-3.7 4.7-0.4 11.7 5.7 11.7H476v268h-62.8c-6 0-9.4 7-5.7 11.7l100.8 127.5c2.9 3.7 8.5 3.7 11.3 0l100.8-127.5c3.7-4.7 0.4-11.7-5.7-11.7H548V378h62.8z", 38 | }, 39 | { 40 | id: "tl-checkCircleFill", 41 | viewBox: "0 0 1024 1024", 42 | pid: "2270", 43 | fill: "", 44 | path: "M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64z m193.5 301.7l-210.6 292c-12.7 17.7-39 17.7-51.7 0L318.5 484.9c-3.8-5.3 0-12.7 6.5-12.7h46.9c10.2 0 19.9 4.9 25.9 13.3l71.2 98.8 157.2-218c6-8.3 15.6-13.3 25.9-13.3H699c6.5 0 10.3 7.4 6.5 12.7z", 45 | }, 46 | { 47 | id: "tl-timeCircleFill", 48 | viewBox: "0 0 1024 1024", 49 | pid: "2431", 50 | fill: "", 51 | path: "M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64z m176.5 585.7l-28.6 39c-2.6 3.6-7.6 4.3-11.2 1.7L483.3 569.8c-2.1-1.5-3.3-3.9-3.3-6.5V288c0-4.4 3.6-8 8-8h48.1c4.4 0 8 3.6 8 8v247.5l142.6 103.1c3.6 2.5 4.4 7.5 1.8 11.1z", 52 | }, 53 | { 54 | id: "tl-document", 55 | viewBox: "0 0 1024 1024", 56 | pid: "2592", 57 | fill: "", 58 | path: "M768 85.333333H256a85.333333 85.333333 0 0 0-85.333333 85.333334v682.666666a85.333333 85.333333 0 0 0 85.333333 85.333334h305.92a85.333333 85.333333 0 0 0 60.586667-25.173334l205.653333-205.653333a85.333333 85.333333 0 0 0 25.173333-60.586667V170.666667a85.333333 85.333333 0 0 0-85.333333-85.333334zM298.666667 277.333333a21.333333 21.333333 0 0 1 21.333333-21.333333h384a21.333333 21.333333 0 0 1 21.333333 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333333 21.333333h-384a21.333333 21.333333 0 0 1-21.333333-21.333333z m234.666666 234.666667h-213.333333a21.333333 21.333333 0 0 1-21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333333-21.333333h213.333333a21.333333 21.333333 0 0 1 21.333334 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333334 21.333333z m21.333334 349.013333V682.666667a42.666667 42.666667 0 0 1 42.666666-42.666667h178.346667z", 59 | }, 60 | { 61 | id: "tl-notebook", 62 | viewBox: "0 0 1024 1024", 63 | pid: "2753", 64 | fill: "", 65 | path: "M213.333333 256v213.333333H149.333333a21.333333 21.333333 0 0 0-21.333333 21.333334v42.666666a21.333333 21.333333 0 0 0 21.333333 21.333334H213.333333v213.333333H149.333333a21.333333 21.333333 0 0 0-21.333333 21.333333v42.666667a21.333333 21.333333 0 0 0 21.333333 21.333333H213.333333a85.333333 85.333333 0 0 0 85.333334 85.333334h512a85.333333 85.333333 0 0 0 85.333333-85.333334V170.666667a85.333333 85.333333 0 0 0-85.333333-85.333334H298.666667a85.333333 85.333333 0 0 0-85.333334 85.333334H149.333333a21.333333 21.333333 0 0 0-21.333333 21.333333v42.666667a21.333333 21.333333 0 0 0 21.333333 21.333333z m341.333334-64a21.333333 21.333333 0 0 1 21.333333-21.333333h213.333333a21.333333 21.333333 0 0 1 21.333334 21.333333v298.666667a21.333333 21.333333 0 0 1-33.28 17.92l-82.773334-55.466667a22.186667 22.186667 0 0 0-23.893333 0l-82.773333 55.466667a21.333333 21.333333 0 0 1-33.28-17.92z", 66 | }, 67 | { 68 | id: "tl-search", 69 | viewBox: "0 0 1024 1024", 70 | pid: "2914", 71 | fill: "", 72 | path: "M966.4 903.428571L669.6 606.628571C715.657143 547.085714 740.571429 474.285714 740.571429 397.714286c0-91.657143-35.771429-177.6-100.457143-242.4-64.685714-64.8-150.857143-100.457143-242.4-100.457143s-177.714286 35.771429-242.4 100.457143C90.514286 220 54.857143 306.057143 54.857143 397.714286c0 91.542857 35.771429 177.714286 100.457143 242.4C220 704.914286 306.057143 740.571429 397.714286 740.571429c76.571429 0 149.257143-24.914286 208.8-70.857143l296.8 296.685714a9.371429 9.371429 0 0 0 13.257143 0l49.828571-49.714286a9.371429 9.371429 0 0 0 0-13.257143zM578.742857 578.742857C530.285714 627.085714 466.057143 653.714286 397.714286 653.714286s-132.571429-26.628571-181.028572-74.971429C168.342857 530.285714 141.714286 466.057143 141.714286 397.714286s26.628571-132.685714 74.971428-181.028572C265.142857 168.342857 329.371429 141.714286 397.714286 141.714286s132.685714 26.514286 181.028571 74.971428S653.714286 329.371429 653.714286 397.714286s-26.628571 132.685714-74.971429 181.028571z", 73 | }, 74 | { 75 | id: "tl-setting", 76 | viewBox: "0 0 1024 1024", 77 | pid: "3075", 78 | fill: "", 79 | path: "M924.8 625.7l-65.5-56c3.1-19 4.7-38.4 4.7-57.8s-1.6-38.8-4.7-57.8l65.5-56c10.1-8.6 13.8-22.6 9.3-35.2l-0.9-2.6c-18.1-50.5-44.9-96.9-79.7-137.9l-1.8-2.1c-8.6-10.1-22.5-13.9-35.1-9.5l-81.3 28.9c-30-24.6-63.5-44-99.7-57.6l-15.7-85c-2.4-13.1-12.7-23.3-25.8-25.7l-2.7-0.5c-52.1-9.4-106.9-9.4-159 0l-2.7 0.5c-13.1 2.4-23.4 12.6-25.8 25.7l-15.8 85.4c-35.9 13.6-69.2 32.9-99 57.4l-81.9-29.1c-12.5-4.4-26.5-0.7-35.1 9.5l-1.8 2.1c-34.8 41.1-61.6 87.5-79.7 137.9l-0.9 2.6c-4.5 12.5-0.8 26.5 9.3 35.2l66.3 56.6c-3.1 18.8-4.6 38-4.6 57.1 0 19.2 1.5 38.4 4.6 57.1L99 625.5c-10.1 8.6-13.8 22.6-9.3 35.2l0.9 2.6c18.1 50.4 44.9 96.9 79.7 137.9l1.8 2.1c8.6 10.1 22.5 13.9 35.1 9.5l81.9-29.1c29.8 24.5 63.1 43.9 99 57.4l15.8 85.4c2.4 13.1 12.7 23.3 25.8 25.7l2.7 0.5c26.1 4.7 52.8 7.1 79.5 7.1 26.7 0 53.5-2.4 79.5-7.1l2.7-0.5c13.1-2.4 23.4-12.6 25.8-25.7l15.7-85c36.2-13.6 69.7-32.9 99.7-57.6l81.3 28.9c12.5 4.4 26.5 0.7 35.1-9.5l1.8-2.1c34.8-41.1 61.6-87.5 79.7-137.9l0.9-2.6c4.5-12.3 0.8-26.3-9.3-35zM788.3 465.9c2.5 15.1 3.8 30.6 3.8 46.1s-1.3 31-3.8 46.1l-6.6 40.1 74.7 63.9c-11.3 26.1-25.6 50.7-42.6 73.6L721 702.8l-31.4 25.8c-23.9 19.6-50.5 35-79.3 45.8l-38.1 14.3-17.9 97c-28.1 3.2-56.8 3.2-85 0l-17.9-97.2-37.8-14.5c-28.5-10.8-55-26.2-78.7-45.7l-31.4-25.9-93.4 33.2c-17-22.9-31.2-47.6-42.6-73.6l75.5-64.5-6.5-40c-2.4-14.9-3.7-30.3-3.7-45.5 0-15.3 1.2-30.6 3.7-45.5l6.5-40-75.5-64.5c11.3-26.1 25.6-50.7 42.6-73.6l93.4 33.2 31.4-25.9c23.7-19.5 50.2-34.9 78.7-45.7l37.9-14.3 17.9-97.2c28.1-3.2 56.8-3.2 85 0l17.9 97 38.1 14.3c28.7 10.8 55.4 26.2 79.3 45.8l31.4 25.8 92.8-32.9c17 22.9 31.2 47.6 42.6 73.6L781.8 426l6.5 39.9z M512 326c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176z m79.2 255.2C570 602.3 541.9 614 512 614c-29.9 0-58-11.7-79.2-32.8C411.7 560 400 531.9 400 502c0-29.9 11.7-58 32.8-79.2C454 401.6 482.1 390 512 390c29.9 0 58 11.6 79.2 32.8C612.3 444 624 472.1 624 502c0 29.9-11.7 58-32.8 79.2z", 80 | }, 81 | { 82 | id: "tl-filter", 83 | viewBox: "0 0 1024 1024", 84 | pid: "3237", 85 | fill: "", 86 | path: "M844.653714 121.904762C889.831619 121.904762 926.47619 159.061333 926.47619 204.897524c0 19.139048-6.534095 37.692952-18.480761 52.516571L654.409143 572.001524V926.47619l-69.485714-31.987809V527.043048L844.653714 204.897524H179.346286l255.951238 328.289524V825.539048l-74.044953-34.084572V571.977143L115.175619 256.365714a83.772952 83.772952 0 0 1 13.409524-116.565333A81.115429 81.115429 0 0 1 179.370667 121.904762h665.307428z", 87 | }, 88 | { 89 | id: "tl-share", 90 | viewBox: "0 0 1024 1024", 91 | pid: "3398", 92 | fill: "#606265", 93 | path: "M922.282667 448.128l-243.797334-260.309333a53.845333 53.845333 0 0 0-28.245333-16.085334 51.2 51.2 0 0 0-31.701333 3.584 56.32 56.32 0 0 0-24.576 21.973334c-5.973333 9.813333-9.216 21.333333-9.216 33.066666v130.986667c-110.08 9.258667-215.381333 52.266667-303.701334 124.032-88.32 71.765333-155.989333 169.301333-195.072 281.173333a12.885333 12.885333 0 0 0-0.213333 7.253334 12.032 12.032 0 0 0 3.712 6.101333 10.325333 10.325333 0 0 0 12.757333 0.768c145.792-96.426667 311.850667-151.168 482.517334-159.018667v129.322667c0 11.776 3.2 23.253333 9.216 33.066667 6.016 9.813333 14.592 17.450667 24.576 21.973333 10.026667 4.565333 21.077333 5.802667 31.701333 3.584 10.666667-2.218667 20.48-7.808 28.245333-16.085333l243.797334-260.266667c5.205333-5.589333 9.301333-12.202667 12.117333-19.541333a64.256 64.256 0 0 0 0-46.08 60.373333 60.373333 0 0 0-12.117333-19.498667z", 94 | }, 95 | { 96 | id: "tl-greenTask", 97 | viewBox: "0 0 1024 1024", 98 | pid: "3559", 99 | fill: "#339078", 100 | path: "M128 0h64v128c0 35.328 28.672 64 64 64h512a64 64 0 0 0 64-64V0h64C966.656 0 1024 57.344 1024 128v768A128 128 0 0 1 896 1024H128A128 128 0 0 1 0 896V128C0 57.344 57.344 0 128 0z m641.170286 355.766857L465.188571 659.748571 289.353143 454.509714l-72.923429 62.464 243.346286 283.940572 377.197714-377.197715-67.803428-67.949714zM768 0v64a64 64 0 0 1-64 64h-384A64 64 0 0 1 256 64V0h512z", 101 | }, 102 | { 103 | id: "tl-task", 104 | viewBox: "0 0 1024 1024", 105 | pid: "3716", 106 | fill: "", 107 | path: "M128 0h64v128c0 35.328 28.672 64 64 64h512a64 64 0 0 0 64-64V0h64C966.656 0 1024 57.344 1024 128v768A128 128 0 0 1 896 1024H128A128 128 0 0 1 0 896V128C0 57.344 57.344 0 128 0z m641.170286 355.766857L465.188571 659.748571 289.353143 454.509714l-72.923429 62.464 243.346286 283.940572 377.197714-377.197715-67.803428-67.949714zM768 0v64a64 64 0 0 1-64 64h-384A64 64 0 0 1 256 64V0h512z", 108 | }, 109 | { 110 | id: "tl-edit", 111 | viewBox: "0 0 24 24", 112 | pid: "", 113 | fill: "", 114 | path: "M9.24264 18.9967H21V20.9967H3V16.754L12.8995 6.85453L17.1421 11.0972L9.24264 18.9967ZM14.3137 5.44032L16.435 3.319C16.8256 2.92848 17.4587 2.92848 17.8492 3.319L20.6777 6.14743C21.0682 6.53795 21.0682 7.17112 20.6777 7.56164L18.5563 9.68296L14.3137 5.44032Z", 115 | }, 116 | { 117 | id: "tl-doubleCheck", 118 | viewBox: "0 0 24 24", 119 | pid: "", 120 | fill: "", 121 | path: "M11.602 13.7599L13.014 15.1719L21.4795 6.7063L22.8938 8.12051L13.014 18.0003L6.65 11.6363L8.06421 10.2221L10.189 12.3469L11.6025 13.7594L11.602 13.7599ZM11.6037 10.9322L16.5563 5.97949L17.9666 7.38977L13.014 12.3424L11.6037 10.9322ZM8.77698 16.5873L7.36396 18.0003L1 11.6363L2.41421 10.2221L3.82723 11.6352L3.82604 11.6363L8.77698 16.5873Z", 122 | }, 123 | { 124 | id: "tl-eye", 125 | viewBox: "0 0 24 24", 126 | pid: "", 127 | fill: "", 128 | path: "M1.18164 12C2.12215 6.87976 6.60812 3 12.0003 3C17.3924 3 21.8784 6.87976 22.8189 12C21.8784 17.1202 17.3924 21 12.0003 21C6.60812 21 2.12215 17.1202 1.18164 12ZM12.0003 17C14.7617 17 17.0003 14.7614 17.0003 12C17.0003 9.23858 14.7617 7 12.0003 7C9.23884 7 7.00026 9.23858 7.00026 12C7.00026 14.7614 9.23884 17 12.0003 17ZM12.0003 15C10.3434 15 9.00026 13.6569 9.00026 12C9.00026 10.3431 10.3434 9 12.0003 9C13.6571 9 15.0003 10.3431 15.0003 12C15.0003 13.6569 13.6571 15 12.0003 15Z", 129 | }, 130 | { 131 | id: "tl-eyeClose", 132 | viewBox: "0 0 24 24", 133 | pid: "", 134 | fill: "", 135 | path: "M4.52047 5.93457L1.39366 2.80777L2.80788 1.39355L22.6069 21.1925L21.1927 22.6068L17.8827 19.2968C16.1814 20.3755 14.1638 21.0002 12.0003 21.0002C6.60812 21.0002 2.12215 17.1204 1.18164 12.0002C1.61832 9.62282 2.81932 7.5129 4.52047 5.93457ZM14.7577 16.1718L13.2937 14.7078C12.902 14.8952 12.4634 15.0002 12.0003 15.0002C10.3434 15.0002 9.00026 13.657 9.00026 12.0002C9.00026 11.537 9.10522 11.0984 9.29263 10.7067L7.82866 9.24277C7.30514 10.0332 7.00026 10.9811 7.00026 12.0002C7.00026 14.7616 9.23884 17.0002 12.0003 17.0002C13.0193 17.0002 13.9672 16.6953 14.7577 16.1718ZM7.97446 3.76015C9.22127 3.26959 10.5793 3.00016 12.0003 3.00016C17.3924 3.00016 21.8784 6.87992 22.8189 12.0002C22.5067 13.6998 21.8038 15.2628 20.8068 16.5925L16.947 12.7327C16.9821 12.4936 17.0003 12.249 17.0003 12.0002C17.0003 9.23873 14.7617 7.00016 12.0003 7.00016C11.7514 7.00016 11.5068 7.01833 11.2677 7.05343L7.97446 3.76015Z", 136 | }, 137 | { 138 | id: "tl-closeCancel", 139 | viewBox: "0 0 24 24", 140 | pid: "", 141 | fill: "", 142 | path: "M11.9997 10.5865L16.9495 5.63672L18.3637 7.05093L13.4139 12.0007L18.3637 16.9504L16.9495 18.3646L11.9997 13.4149L7.04996 18.3646L5.63574 16.9504L10.5855 12.0007L5.63574 7.05093L7.04996 5.63672L11.9997 10.5865Z", 143 | }, 144 | { 145 | id: "tl-checkDone", 146 | viewBox: "0 0 24 24", 147 | pid: "", 148 | fill: "", 149 | path: "M9.9997 15.1709L19.1921 5.97852L20.6063 7.39273L9.9997 17.9993L3.63574 11.6354L5.04996 10.2212L9.9997 15.1709Z", 150 | }, 151 | { 152 | id: "tl-toTop", 153 | viewBox: "0 0 1024 1024", 154 | pid: "19088", 155 | fill: "#2c2c2c", 156 | path: "M698.176 490.666667H554.666667v384a42.666667 42.666667 0 1 1-85.333334 0V490.666667h-143.509333c-18.56 0-28.266667-22.058667-15.722667-35.754667l186.176-203.093333a21.333333 21.333333 0 0 1 31.445334 0l186.176 203.093333c12.544 13.696 2.837333 35.754667-15.722667 35.754667zM874.666667 192a42.666667 42.666667 0 1 0 0-85.333333H149.333333a42.666667 42.666667 0 1 0 0 85.333333h725.333334z", 157 | }, 158 | { 159 | id: "tl-handleDate", 160 | viewBox: "0 0 1024 1024", 161 | pid: "21493", 162 | fill: "#2c2c2c", 163 | path: "M42.589091 178.501818A128 128 0 0 1 162.769455 50.734545l7.819636-0.232727h550.167273A128 128 0 0 1 848.523636 170.356364l0.232728 7.773091 0.698181 217.367272a34.909091 34.909091 0 0 1-69.492363 4.980364l-0.325818-4.747636-0.698182-217.367273a58.181818 58.181818 0 0 0-52.224-57.716364l-5.957818-0.325818H170.589091A58.181818 58.181818 0 0 0 112.64 172.544l-0.279273 5.957818V743.796364c0 30.114909 22.900364 54.923636 52.224 57.902545l5.957818 0.279273h139.636364a34.909091 34.909091 0 0 1 4.747636 69.492363l-4.747636 0.325819h-139.636364A128 128 0 0 1 42.821818 751.569455l-0.232727-7.819637V178.501818z M546.769455 278.946909a34.909091 34.909091 0 0 1 54.690909 43.147636l-3.258182 4.049455-230.4 251.345455a34.909091 34.909091 0 0 1-48.872727 2.466909l-3.630546-3.723637-104.727273-125.672727a34.909091 34.909091 0 0 1 50.036364-48.407273l3.584 3.723637 79.173818 94.952727 203.403637-221.882182z M698.181818 477.090909a244.363636 244.363636 0 1 0 0 488.727273 244.363636 244.363636 0 0 0 0-488.727273z m0 69.818182a174.545455 174.545455 0 1 1 0 349.090909 174.545455 174.545455 0 0 1 0-349.090909z M684.218182 616.727273a34.909091 34.909091 0 0 1 34.583273 30.161454l0.325818 4.747637-0.046546 71.493818 73.821091 36.910545a34.909091 34.909091 0 0 1 17.454546 42.449455l-1.861819 4.375273a34.909091 34.909091 0 0 1-42.402909 17.454545l-4.375272-1.861818-93.090909-46.545455a34.909091 34.909091 0 0 1-18.990546-26.298182L649.309091 744.727273v-93.090909a34.909091 34.909091 0 0 1 34.909091-34.909091z", 164 | }, 165 | ]; 166 | 167 | export function addIcons() { 168 | icons.forEach((icon) => { 169 | plugin.addIcons(` 170 | 171 | 174 | 175 | `); 176 | }); 177 | } 178 | -------------------------------------------------------------------------------- /src/utils/addInfoToHtmlNode.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 为任务节点添加日期信息 3 | * 当任务节点被勾选以及取消勾选时,添加日期信息 4 | * 当切换任务节点时,添加日期信息 5 | */ 6 | import { formatDateTime } from "@/utils/date"; 7 | import { i18n } from "@/utils/common"; 8 | import * as API from "@/api"; 9 | 10 | const judgeIsShowExtraInfoOnDocTask = async () => { 11 | const { data: storage } = await API.getLocalStorage(); 12 | if (!storage["plugin-task-list-settings"]) return; 13 | const { docTaskConfig } = storage["plugin-task-list-settings"]; 14 | let isShow: boolean = false; 15 | if (typeof docTaskConfig?.isShowExtraInfoOnDocTask === "boolean") { 16 | isShow = docTaskConfig.isShowExtraInfoOnDocTask; 17 | } else { 18 | // 默认显示 19 | isShow = true; 20 | } 21 | return isShow; 22 | }; 23 | 24 | export async function addHandleDateToTaskNode() { 25 | 26 | // 获取编辑器容器 27 | const editor = document.querySelector(".protyle-wysiwyg"); 28 | if (!editor) return; 29 | 30 | // 清除所有已存在的日期信息元素和包装器 31 | const existingWrappers = document.querySelectorAll( 32 | ".siyuan-plugin-task-date-info-wrapper" 33 | ); 34 | existingWrappers.forEach((el) => el.remove()); 35 | 36 | // 清除可能存在的 NodeHTMLBlock 37 | const htmlBlocks = document.querySelectorAll( 38 | 'div[data-type="NodeHTMLBlock"]' 39 | ); 40 | htmlBlocks.forEach((block) => { 41 | if (block.innerHTML.includes("siyuan-plugin-task-date-info")) { 42 | block.remove(); 43 | } 44 | }); 45 | 46 | const isShow = await judgeIsShowExtraInfoOnDocTask(); 47 | if (!isShow) return; 48 | 49 | // 获取所有任务节点 50 | const taskNodes = document.querySelectorAll( 51 | 'div[data-marker="*"][data-type="NodeListItem"][data-subtype="t"]' 52 | ); 53 | 54 | taskNodes.forEach((node) => { 55 | const handleDate = node.getAttribute("custom-plugin-task-list-handleat"); 56 | const finishedDate = node.getAttribute("custom-plugin-task-list-finished"); 57 | 58 | if (handleDate || finishedDate) { 59 | const dateInfo = document.createElement("div"); 60 | dateInfo.className = "siyuan-plugin-task-date-info"; 61 | 62 | // 设置样式和属性 63 | dateInfo.style.cssText = ` 64 | position: absolute; 65 | font-size: 10px; 66 | color: #888; 67 | pointer-events: none !important; 68 | text-align: right; 69 | right: 20px; 70 | top: -12px; 71 | user-select: none !important; 72 | -webkit-user-select: none !important; 73 | `; 74 | 75 | // 使用多重保护确保元素不可编辑 76 | dateInfo.setAttribute("contenteditable", "false"); 77 | dateInfo.setAttribute("data-editable", "false"); 78 | dateInfo.setAttribute("spellcheck", "false"); 79 | dateInfo.setAttribute("data-type", "siyuan-plugin-custom-date-info"); 80 | 81 | // 设置日期文本 82 | let dateText = []; 83 | if (handleDate) { 84 | dateText.push( 85 | `${i18n.infoCard.handleAt}: ${formatDateTime(handleDate, "date")}` 86 | ); 87 | } 88 | if (finishedDate) { 89 | dateText.push( 90 | `${i18n.infoCard.finished}: ${formatDateTime(finishedDate, "date")}` 91 | ); 92 | } 93 | dateInfo.textContent = dateText.join(" "); 94 | 95 | // 确保任务节点是相对定位 96 | (node as HTMLElement).style.position = "relative"; 97 | 98 | // 创建一个包装器来进一步隔离编辑状态 99 | const wrapper = document.createElement("div"); 100 | wrapper.className = "siyuan-plugin-task-date-info-wrapper"; 101 | wrapper.style.cssText = 102 | "pointer-events: none !important; position: absolute; top: 0; right: 0; left: 0;"; 103 | wrapper.setAttribute("contenteditable", "false"); 104 | wrapper.setAttribute("data-type", "siyuan-plugin-custom-wrapper"); 105 | 106 | wrapper.appendChild(dateInfo); 107 | node.appendChild(wrapper); 108 | } 109 | }); 110 | } 111 | -------------------------------------------------------------------------------- /src/utils/ai.ts: -------------------------------------------------------------------------------- 1 | import * as date from "@/utils/date"; 2 | import { ElMessage } from "element-plus"; 3 | import html2canvas from "html2canvas"; 4 | import { i18n } from "@/utils/common"; 5 | 6 | export let taskListForAI: any[] = []; 7 | 8 | export function handleTaskListForAI(taskList: any[]) { 9 | taskListForAI = taskList.map((item) => ({ 10 | boxName: item.boxName, 11 | hpath: item.hpath, 12 | label: item.label, 13 | status: item.status, 14 | created: date.formatDateTime(item.created, "dateTime"), 15 | // updated: date.formatDateTime(item.updated, 'dateTime'), 16 | handleAt: date.formatDateTime(item.handleAt, "dateTime"), 17 | finished: date.formatDateTime(item.finished, "dateTime"), 18 | })); 19 | } 20 | 21 | const imgWidthMap = { 22 | pc: { 23 | whole: 1200, 24 | single: 800, 25 | }, 26 | mobile: { 27 | whole: 800, 28 | single: 350, 29 | }, 30 | }; 31 | 32 | /** 33 | * 下载为图片 34 | * @param elementId 元素ID 35 | * @param fileName 文件名【如果fileName以“全部”开头,则单独处理图片宽度】 36 | * @param pcOrMobilePic pc或mobile 37 | */ 38 | export async function downloadAsImage({ 39 | elementId, 40 | fileName, 41 | pcOrMobilePic, 42 | isAll = false, 43 | }: { 44 | elementId: string; 45 | fileName: string; 46 | pcOrMobilePic: string; 47 | isAll?: boolean; 48 | }) { 49 | try { 50 | const originalElement = document.getElementById(elementId); 51 | if (!originalElement) { 52 | ElMessage.error("未找到指定元素"); 53 | return; 54 | } 55 | 56 | let wrapperWidth: number = 0; 57 | if (isAll) { 58 | wrapperWidth = imgWidthMap[pcOrMobilePic].whole; 59 | } else { 60 | wrapperWidth = imgWidthMap[pcOrMobilePic].single; 61 | } 62 | // 创建包装元素 63 | const wrapper = document.createElement("div"); 64 | wrapper.style.padding = "20px"; 65 | wrapper.style.width = `${wrapperWidth}px`; 66 | // wrapper.style.height = `${wrapperWidth * 1.414}px`; 67 | wrapper.style.background = 68 | "linear-gradient(-45deg, #fc00ff 0%, #00dbde 100%)"; 69 | // wrapper.style.borderRadius = "16px"; 70 | wrapper.style.boxShadow = 71 | "0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08)"; 72 | wrapper.style.display = "inline-block"; 73 | 74 | // 克隆原始元素的内容 75 | const clone = originalElement.cloneNode(true) as HTMLElement; 76 | // clone.style.background = "transparent"; 77 | clone.style.borderRadius = "16px"; 78 | clone.style.padding = "20px"; 79 | clone.style.wordBreak = "break-word"; 80 | clone.style.whiteSpace = "pre-wrap"; 81 | clone.style.overflow = "hidden"; 82 | clone.style.fontSize = "16px"; 83 | clone.style.lineHeight = "1.5"; 84 | clone.style.letterSpacing = "0.5px"; // 防止文字粘连 85 | clone.style.maxWidth = "100%"; 86 | clone.style.width = "100%"; 87 | 88 | wrapper.appendChild(clone); 89 | 90 | // 处理SVG图标 91 | const svgIcons = wrapper.querySelectorAll("svg"); 92 | await Promise.all( 93 | Array.from(svgIcons).map(async (svg) => { 94 | const use = svg.querySelector("use"); 95 | if (use) { 96 | const iconId = use.getAttribute("xlink:href"); 97 | if (iconId) { 98 | // 尝试获取原始SVG内容 99 | const originalIcon = document.querySelector(iconId); 100 | if (originalIcon) { 101 | // 如果找到原始SVG,直接复制其内容 102 | svg.innerHTML = originalIcon.innerHTML; 103 | svg.setAttribute( 104 | "viewBox", 105 | originalIcon.getAttribute("viewBox") || "" 106 | ); 107 | } else { 108 | // 如果找不到原始SVG,尝试加载外部SVG 109 | try { 110 | const response = await fetch(iconId); 111 | const svgContent = await response.text(); 112 | const parser = new DOMParser(); 113 | const svgDoc = parser.parseFromString( 114 | svgContent, 115 | "image/svg+xml" 116 | ); 117 | const svgElement = svgDoc.documentElement; 118 | svg.innerHTML = svgElement.innerHTML; 119 | svg.setAttribute( 120 | "viewBox", 121 | svgElement.getAttribute("viewBox") || "" 122 | ); 123 | } catch (error) { 124 | console.error("无法加载SVG图标:", error); 125 | } 126 | } 127 | } 128 | } 129 | // 确保SVG有合适的尺寸 130 | if (!svg.getAttribute("width")) svg.setAttribute("width", "1em"); 131 | if (!svg.getAttribute("height")) svg.setAttribute("height", "1em"); 132 | }) 133 | ); 134 | 135 | // 临时将包装器添加到body,定位到页面外面 136 | wrapper.style.position = "fixed"; 137 | wrapper.style.top = "-10000px"; 138 | wrapper.style.left = "-10000px"; 139 | document.body.appendChild(wrapper); 140 | 141 | // 使用html2canvas捕获图像 142 | const canvas = await html2canvas(wrapper, { 143 | scale: 2, 144 | useCORS: true, 145 | logging: false, 146 | // allowTaint: true, 147 | // foreignObjectRendering: true, 148 | }); 149 | 150 | // 移除临时元素 151 | document.body.removeChild(wrapper); 152 | 153 | // 将画布转换为图像并下载 154 | const image = canvas.toDataURL("image/png"); 155 | const link = document.createElement("a"); 156 | link.href = image; 157 | link.download = `${i18n.aiRoast.toast.fileNamePrefix}—${fileName}-${pcOrMobilePic}.png`; 158 | document.body.appendChild(link); 159 | link.click(); 160 | document.body.removeChild(link); 161 | 162 | ElMessage.success(i18n.aiRoast.toast.downloadSuccess); 163 | } catch (error) { 164 | console.error(i18n.aiRoast.toast.downloadError + ": ", error); 165 | ElMessage.error(i18n.aiRoast.toast.downloadError); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/utils/compatible.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 兼容旧版本数据:迁移旧版本数据到新版本 3 | */ 4 | import * as API from '@/api/index' 5 | import * as utils from '@/utils/common' 6 | 7 | import * as sySDK from '@siyuan-community/siyuan-sdk' 8 | 9 | /* 初始化客户端 (默认使用 Axios 发起 XHR 请求) */ 10 | export const client = new sySDK.Client() 11 | 12 | /** 13 | * 迁移旧版本数据到新版本 14 | * { 'plugin-task-list-nodeListForHideTask': [] } ==> { 'plugin-task-list-settings': { nodeListForHideTask: [] } } 15 | */ 16 | export async function makeLocalStorageNew() { 17 | const { data: storage } = await API.getLocalStorage() 18 | if (storage.hasOwnProperty('plugin-task-list-nodeListForHideTask')) { 19 | const nodeListForHideTask = storage['plugin-task-list-nodeListForHideTask'] 20 | delete storage['plugin-task-list-nodeListForHideTask'] 21 | await client.setLocalStorage({ 22 | app: utils.plugin.app.appId, 23 | val: { 24 | ...storage, 25 | 'plugin-task-list-settings': { 26 | nodeListForHideTask: nodeListForHideTask, 27 | }, 28 | }, 29 | }) 30 | } 31 | } 32 | 33 | makeLocalStorageNew() 34 | -------------------------------------------------------------------------------- /src/utils/date.ts: -------------------------------------------------------------------------------- 1 | function padZero(num: number) { 2 | return num.toString().padStart(2, "0"); 3 | } 4 | 5 | /** 6 | * 获取此时此刻的日期时间 7 | * @returns 8 | */ 9 | export function getCurrentDateTime() { 10 | let now = new Date(); 11 | 12 | let year = now.getFullYear(); 13 | let month = padZero(now.getMonth() + 1); 14 | let day = padZero(now.getDate()); 15 | let hours = padZero(now.getHours()); 16 | let minutes = padZero(now.getMinutes()); 17 | let seconds = padZero(now.getSeconds()); 18 | 19 | return year + month + day + hours + minutes + seconds; 20 | } 21 | 22 | /** 23 | * 格式化时间 24 | */ 25 | export function formatDateTime(dateTime: string, type?: "date" | "dateTime") { 26 | if (!dateTime) return ""; 27 | 28 | let year = dateTime.slice(0, 4); 29 | let month = dateTime.slice(4, 6); 30 | let day = dateTime.slice(6, 8); 31 | let hours = dateTime.slice(8, 10); 32 | let minutes = dateTime.slice(10, 12); 33 | // let seconds = dateTime.slice(12, 14) 34 | 35 | let res: string = ""; 36 | if (type === "dateTime") { 37 | res = `${year}-${month}-${day} ${hours}:${minutes}`; 38 | } else { 39 | res = `${year}-${month}-${day}`; 40 | } 41 | return res; 42 | } 43 | 44 | /** 45 | * 将形如 2024-05-08 的日期转换为形如 20240508000000 的格式 46 | * @param dateParam 形如 2024-05-08 或者 Date格式 的日期格式 47 | * @returns {string} 形如 20240508000000 的日期格式 48 | */ 49 | export function formatHandleDateToStorage({ 50 | dateParam, 51 | isDateRangeStart = false, 52 | isDateInRangeEnd = false, 53 | }: { 54 | dateParam: string | Date; 55 | isDateRangeStart?: boolean; 56 | isDateInRangeEnd?: boolean; 57 | }): string { 58 | let date: Date = null; 59 | if (typeof dateParam === "string") { 60 | // 将日期字符串转换为Date对象 61 | date = new Date(dateParam); 62 | } else { 63 | date = dateParam; 64 | } 65 | 66 | // 获取年、月、日等信息 67 | let year = date.getFullYear(); 68 | let month = padZero(date.getMonth() + 1); 69 | let day = padZero(date.getDate()); 70 | let hours = padZero(date.getHours()); 71 | let minutes = padZero(date.getMinutes()); 72 | let seconds = padZero(date.getSeconds()); 73 | 74 | // 拼接字符串 75 | let formattedDate = year + month + day + hours + minutes + seconds; 76 | if (isDateRangeStart) { 77 | formattedDate = year + month + day + "000000"; 78 | } 79 | if (isDateInRangeEnd) { 80 | formattedDate = year + month + day + "235959"; 81 | } 82 | 83 | return formattedDate; 84 | } 85 | 86 | export function isDateInRange( 87 | date: string, 88 | startDate: string, 89 | endDate: string 90 | ) { 91 | return date >= startDate && date <= endDate; 92 | } 93 | 94 | /** 95 | * 将Date格式的日期转换为形如 20240508000000 的格式 96 | * @param date Date格式的日期 97 | * @returns 98 | */ 99 | export function formatDate(date: Date) { 100 | let year = date.getFullYear(); 101 | let month = padZero(date.getMonth() + 1); // getMonth() 返回的月份是从0开始的 102 | let day = padZero(date.getDate()); 103 | let hour = padZero(date.getHours()); 104 | let minute = padZero(date.getMinutes()); 105 | let second = padZero(date.getSeconds()); 106 | return `${year}${month}${day}${hour}${minute}${second}`; 107 | } 108 | 109 | /** 110 | * 根据枚举值和今天的日期获取日期范围 111 | * @param enumValue 日期范围枚举值 112 | * @returns 113 | */ 114 | export function getDateRangeByEnumValue(enumValue: string) { 115 | let today = new Date(); 116 | let start: Date = null; 117 | let end: Date = null; 118 | 119 | switch (enumValue) { 120 | case "currentMonth": 121 | // 本月的第一天和最后一天 122 | start = new Date(today.getFullYear(), today.getMonth(), 1); 123 | end = new Date(today.getFullYear(), today.getMonth() + 1, 0); 124 | break; 125 | case "currentWeek": 126 | // 本周的第一天(周日)和最后一天(周六) 127 | let dayOfWeek = today.getDay(); 128 | let diff = today.getDay() - 1; 129 | if (dayOfWeek === 0) diff--; // 如果今天是周日,那么上一周的周日是本周的第一天 130 | start = new Date(today); 131 | start.setDate(today.getDate() - diff); 132 | end = new Date(start); 133 | end.setDate(start.getDate() + 6); 134 | break; 135 | case "pastThreeDays": 136 | // 今天的日期减去2天 137 | start = new Date(today.getTime() - 2 * 24 * 60 * 60 * 1000); 138 | end = today; 139 | break; 140 | case "today": 141 | // 今天的开始和结束 142 | start = new Date( 143 | today.getFullYear(), 144 | today.getMonth(), 145 | today.getDate(), 146 | 0, 147 | 0, 148 | 0 149 | ); 150 | end = new Date( 151 | today.getFullYear(), 152 | today.getMonth(), 153 | today.getDate(), 154 | 23, 155 | 59, 156 | 59 157 | ); 158 | break; 159 | case "futureThreeDays": 160 | // 今天的日期加上2天 161 | start = today; 162 | end = new Date(today.getTime() + 2 * 24 * 60 * 60 * 1000); 163 | break; 164 | default: 165 | throw new Error("Invalid enum value"); 166 | } 167 | 168 | // 格式化日期 169 | return { 170 | start: formatDate(start), 171 | end: formatDate(end), 172 | }; 173 | } 174 | 175 | /** 176 | * 将Date格式的时间转换为形如 2024-05-08 12:00:00 的格式 177 | * @param date Date格式的时间 178 | * @returns 形如 2024-05-08 12:00:00 的格式 179 | */ 180 | export function formatDateToLocaleString(date: Date) { 181 | return ( 182 | date.getFullYear() + 183 | "-" + 184 | (date.getMonth() + 1).toString().padStart(2, "0") + 185 | "-" + 186 | date.getDate().toString().padStart(2, "0") + 187 | " " + 188 | date.getHours().toString().padStart(2, "0") + 189 | ":" + 190 | date.getMinutes().toString().padStart(2, "0") + 191 | ":" + 192 | date.getSeconds().toString().padStart(2, "0") 193 | ); 194 | } 195 | -------------------------------------------------------------------------------- /src/utils/eventBus.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 事件类——发布者、订阅者模式 3 | */ 4 | class EventBus { 5 | private events: {} 6 | constructor() { 7 | this.events = {} 8 | } 9 | 10 | //发布 11 | on(event: any, listener: any) { 12 | if (!this.events[event]) { 13 | this.events[event] = [] 14 | } 15 | this.events[event].push(listener) 16 | } 17 | 18 | //关闭 19 | off(event: any, listener: any) { 20 | if (!this.events[event]) { 21 | return 22 | } 23 | const index = this.events[event].indexOf(listener) 24 | if (index >= 0) { 25 | this.events[event].splice(index, 1) 26 | } 27 | } 28 | 29 | //订阅 30 | emit(event: any, ...args: any[]) { 31 | if (!this.events[event]) { 32 | return 33 | } 34 | this.events[event].forEach((listener: any) => { 35 | listener.apply(this, args) 36 | }) 37 | } 38 | 39 | //订阅一次 40 | once(event: any, listener: any) { 41 | function callback(...args: any[]) { 42 | listener.apply(this, args) 43 | this.off(event, callback) 44 | } 45 | this.on(event, callback) 46 | } 47 | } 48 | 49 | export default new EventBus() 50 | -------------------------------------------------------------------------------- /src/utils/func.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 解析字符串为键值对 3 | * 解析节点的自定义属性 ial 4 | * @param ial 5 | * @returns 6 | */ 7 | export function parseStringToKeyValuePairs(ial: string) { 8 | // 使用正则表达式匹配键值对 9 | const pattern = /(\w[\w-]*)="([^"]*)"/g 10 | let match: any = {} 11 | const keyValuePairs = {} 12 | 13 | while ((match = pattern.exec(ial)) !== null) { 14 | // 将匹配到的键和值添加到对象中 15 | keyValuePairs[match[1]] = match[2] 16 | } 17 | 18 | return keyValuePairs 19 | } 20 | 21 | /** 22 | * 判断节点的真实完成状态 23 | * 不要通过完成时间判断,不然使用插件之前完成的任务就统计不出来了 24 | * @param params 25 | * @returns 'todo' | 'done' 26 | */ 27 | // export function findTaskNodeRealStatus(params: { 28 | // markdown: string 29 | // }): 'todo' | 'done' { 30 | // return params.markdown.substring(0, 5) === '* [ ]' ? 'todo' : 'done' 31 | // } 32 | -------------------------------------------------------------------------------- /src/utils/globalStroage.ts: -------------------------------------------------------------------------------- 1 | /** 设置抽屉中的各项配置 */ 2 | export let settings: any 3 | export function setStorage(storage: any) { 4 | settings = storage['plugin-task-list-settings'] 5 | } 6 | -------------------------------------------------------------------------------- /src/utils/handleTaskNode.ts: -------------------------------------------------------------------------------- 1 | import * as API from '@/api' 2 | import * as date from '@/utils/date' 3 | import { addHandleDateToTaskNode } from '@/utils/addInfoToHtmlNode' 4 | 5 | /** 6 | * 为任务节点设置自定义属性:完成时间 7 | * @param id 8 | * @param isFinish 9 | */ 10 | const setTaskBlockAttrs = async (id: string, isFinish: boolean) => { 11 | await API.setBlockAttrs({ 12 | id, 13 | attrs: { 14 | /** 15 | * 这里必须加上 custom- 前缀 16 | * 关联思源issue:https://github.com/siyuan-note/siyuan/issues/10928 17 | */ 18 | 'custom-plugin-task-list-finished': isFinish 19 | ? date.getCurrentDateTime() 20 | : '', 21 | }, 22 | }) 23 | } 24 | 25 | /** 26 | * 27 | * @param e ws-main的事件参数 28 | * @returns 29 | */ 30 | export const taskNodeFinishedSetAttrs = (e: any): void => { 31 | const detailData = e.detail.data 32 | if (!detailData) return 33 | const divStr = detailData[0]?.doOperations[0]?.data 34 | 35 | if (!divStr || typeof divStr !== 'string') return 36 | 37 | //
节点内容
38 | 39 | // 创建DOMParser对象 40 | let parser = new DOMParser() 41 | // 将字符串解析为DOM元素 42 | let doc = parser.parseFromString(divStr, 'text/html') 43 | // 获取最外层的div 44 | let outerDiv = doc.querySelector('div') 45 | 46 | if (!outerDiv) { 47 | console.log("siyuan-plugin-task-list: can't find outerDiv") 48 | return 49 | } 50 | 51 | // 获取div的class属性 52 | let divClass = outerDiv?.getAttribute('class') 53 | // 获取div的data-marker属性 54 | let divDataMarker = outerDiv?.getAttribute('data-marker') 55 | // 获取div的data-subtype属性 56 | let divDataSubtype = outerDiv?.getAttribute('data-subtype') 57 | // 获取div的data-node-id属性 58 | let divNodeId = outerDiv?.getAttribute('data-node-id') 59 | // 获取div的data-type属性 60 | let divDataType = outerDiv?.getAttribute('data-type') 61 | 62 | if ( 63 | divDataMarker === '*' && 64 | divDataSubtype === 't' && 65 | divDataType === 'NodeListItem' 66 | ) { 67 | // 勾选任务节点 68 | if (divClass.includes('protyle-task--done')) { 69 | setTaskBlockAttrs(divNodeId, true) 70 | } 71 | // 取消勾选任务节点 72 | else { 73 | setTaskBlockAttrs(divNodeId, false) 74 | } 75 | setTimeout(() => { 76 | addHandleDateToTaskNode() 77 | }, 100) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/utils/handleTreeData.ts: -------------------------------------------------------------------------------- 1 | import * as API from '@/api' 2 | 3 | /** 4 | * 【未使用】 5 | * 根据配置中的列表对 treeData 进行过滤,删掉存在于 配置 中的 doc 节点,需要递归 6 | * @param e 7 | * @param treeData 8 | */ 9 | export const refreshTaskAfterHideDocChecked = async ( 10 | treeData: any 11 | ): Promise> => { 12 | const { data: storage } = await API.getLocalStorage() 13 | let checkedNodeList = 14 | storage['plugin-task-list-settings']?.['nodeListForHideTask'] 15 | // 递归删除选中的doc节点 16 | function removeDocNode(node: any) { 17 | let findedNodeInChecked = checkedNodeList.find( 18 | (item: any) => item.key === node.key 19 | ) 20 | // 如果找到了需要删除任务的节点 21 | if (findedNodeInChecked) { 22 | if (findedNodeInChecked.hideTaskInNodeStatus === 1) { 23 | // 删除children中type===task的孩子 24 | node.children = node.children?.filter((item: any) => { 25 | if (item.type === 'task') { 26 | return false 27 | } 28 | if (item.children) { 29 | removeDocNode(item) 30 | } 31 | return true 32 | }) 33 | } else if (findedNodeInChecked.hideTaskInNodeStatus === 2) { 34 | // #syh-fix-me 如何删除节点本身 35 | node.children = [] 36 | } 37 | } 38 | // 如果没找到,进行递归处理 39 | else { 40 | node.children = node.children?.map((item: any) => { 41 | if (item.children) { 42 | removeDocNode(item) 43 | } 44 | return item 45 | }) 46 | } 47 | 48 | return node 49 | } 50 | return treeData?.map((item: any) => { 51 | return removeDocNode(item) 52 | }) 53 | } 54 | 55 | /** 56 | * 根据localStorage中的数据设置树节点的勾选状态 57 | */ 58 | export const handleCheckStatusForTreeData = async ( 59 | treeData: any 60 | ): Promise> => { 61 | const { data: storage } = await API.getLocalStorage() 62 | 63 | let nodeListForHideTask = 64 | storage['plugin-task-list-settings']?.['nodeListForHideTask'] 65 | function setCheckStatus(node: any) { 66 | if (nodeListForHideTask) { 67 | nodeListForHideTask.forEach((item: any) => { 68 | if (item.key === node.key) { 69 | node.hideTaskInNodeStatus = item.hideTaskInNodeStatus 70 | } 71 | }) 72 | } 73 | if (node.children?.length) { 74 | node.children.forEach((item: any) => { 75 | setCheckStatus(item) 76 | }) 77 | } 78 | return node 79 | } 80 | return treeData.map((item: any) => { 81 | return setCheckStatus(item) 82 | }) 83 | } 84 | 85 | /** 86 | * 找到树数据中被勾选的用于隐藏任务的节点列表 87 | * @returns 树数据中被勾选的用于隐藏任务的节点列表 88 | */ 89 | export const findHiddenTaskNodesInTreeData = (treeData: any): Array => { 90 | let hiddenTaskNodes: any[] = [] 91 | function findHiddenTaskNodes(node: any) { 92 | // 勾选了仅自身 93 | if (node.hideTaskInNodeStatus === 1) { 94 | hiddenTaskNodes.push({ 95 | ...node, 96 | children: null, 97 | }) 98 | } 99 | // 勾选了包含子节点则直接忽略子节点 100 | else if (node.hideTaskInNodeStatus === 2) { 101 | node.children = [] 102 | hiddenTaskNodes.push({ 103 | ...node, 104 | children: null, 105 | }) 106 | } 107 | if (node.children?.length) { 108 | node.children.forEach((item: any) => { 109 | findHiddenTaskNodes(item) 110 | }) 111 | } 112 | } 113 | treeData.forEach((item: any) => { 114 | findHiddenTaskNodes(item) 115 | }) 116 | return hiddenTaskNodes 117 | } 118 | 119 | /** 120 | * 将树数据中的任务节点过滤掉 121 | */ 122 | export const handleTreeDataWithoutTaskNode = ( 123 | treeData: Array 124 | ): Array => { 125 | function removeTaskNode(node: any) { 126 | node.hideTaskInNodeStatus = 0 127 | node.children = node.children.filter((item: any) => { 128 | if (item.type === 'task') { 129 | return false 130 | } 131 | if (item.children) { 132 | removeTaskNode(item) 133 | } 134 | return true 135 | }) 136 | return node 137 | } 138 | treeData.forEach((item: any) => { 139 | item = removeTaskNode(item) 140 | }) 141 | return treeData 142 | } 143 | 144 | /** 145 | * 额外的置顶排序 146 | * @param a 147 | * @param b 148 | * @param sortBy 149 | * @param isDesc 150 | * @returns 151 | */ 152 | const sortByTopNum = (a: any, b: any, sortBy: string, isDesc: boolean) => { 153 | if (a.topNum > 0 && b.topNum > 0) { 154 | return b.topNum - a.topNum // topNum 大的置顶 155 | } else if (a.topNum > 0) { 156 | return -1 // a 置顶 157 | } else if (b.topNum > 0) { 158 | return 1 // b 置顶 159 | } else { 160 | if (isDesc) { 161 | return b[sortBy] - a[sortBy] 162 | } else { 163 | return a[sortBy] - b[sortBy] 164 | } 165 | } 166 | } 167 | 168 | /** 节点的排序方法 */ 169 | const sortNodeMethod = (a: any, b: any, sortBy: string) => { 170 | // const taskTopSorted: number = a.topNum - b.topNum 171 | let sortRes: number = 0 172 | // 根据sortBy进行排序,有创建时间、更新时间、完成时间等 173 | if (['createdDesc', 'createdAsc'].includes(sortBy)) { 174 | if (sortBy === 'createdDesc') { 175 | sortRes = sortByTopNum(a, b, 'created', true) 176 | } else { 177 | sortRes = sortByTopNum(a, b, 'created', false) 178 | } 179 | } else if (['updatedDesc', 'updatedAsc'].includes(sortBy)) { 180 | if (sortBy === 'updatedDesc') { 181 | sortRes = sortByTopNum(a, b, 'updated', true) 182 | } else { 183 | sortRes = sortByTopNum(a, b, 'updated', false) 184 | } 185 | } else if (['finishedDesc', 'finishedAsc'].includes(sortBy)) { 186 | if (sortBy === 'finishedDesc') { 187 | sortRes = sortByTopNum(a, b, 'finished', true) 188 | } else { 189 | sortRes = sortByTopNum(a, b, 'finished', false) 190 | } 191 | } 192 | return sortRes 193 | } 194 | 195 | /** 判断某个节点下是否有任务节点 */ 196 | // const isNodeHasChildrenTask = (node: any): boolean => { 197 | // if (node.children?.length) { 198 | // return node.children.some((item: any) => item.type === 'task') 199 | // } else { 200 | // return false 201 | // } 202 | // } 203 | 204 | /** 205 | * 任务节点的排序方法 206 | * @param treeData 207 | * @param sortBy 208 | * @returns treeData 排序后的树形数据 209 | */ 210 | export const sortTaskTreeData = (treeData: any, sortBy: string): Array => { 211 | function sortTaskNode(node: any): any { 212 | if (node.children) { 213 | node.children.sort((a: any, b: any) => { 214 | return sortNodeMethod(a, b, sortBy) 215 | }) 216 | node.children.forEach((item: any) => { 217 | sortTaskNode(item) 218 | }) 219 | } 220 | return node 221 | } 222 | 223 | if (treeData.length === 0) return treeData 224 | 225 | // 如果显示的任务节点没有层级,即只有一层 226 | if (treeData[0].type === 'task') { 227 | treeData.sort((a: any, b: any) => { 228 | return sortNodeMethod(a, b, sortBy) 229 | }) 230 | } else { 231 | treeData = treeData?.map((item: any) => { 232 | return sortTaskNode(item) 233 | }) 234 | } 235 | return treeData 236 | } 237 | 238 | /** 239 | * 统计各个维度的任务数量或者统计某个笔记本下的任务数量 240 | * @param treeData 树形数据 241 | * @param boxId 笔记本ID,统计某个笔记本中的任务数量 242 | * @param docId 文档ID,统计某个文档中的任务数量 243 | * @returns 任务数量 244 | */ 245 | // export const getTaskNodeCountsInTree = ( 246 | // treeData: any = [], 247 | // boxId?: string, 248 | // docId?: string 249 | // ): number => { 250 | // let count = 0 251 | // function getTaskNodeCounts(node: any) { 252 | // if (node.type === 'doc' && docId && node.key === docId) { 253 | // count = node.filter((item: any) => item.type === 'task').length 254 | // return 255 | // } 256 | 257 | // if (node.type === 'task') { 258 | // count++ 259 | // } 260 | // if (node.children?.length) { 261 | // node.children.forEach((item: any) => { 262 | // getTaskNodeCounts(item) 263 | // }) 264 | // } 265 | // } 266 | // for (let i = 0; i < treeData.length; i++) { 267 | // if (boxId && treeData[i].key === boxId) { 268 | // count = 0 269 | // getTaskNodeCounts(treeData[i]) 270 | // break 271 | // } 272 | // getTaskNodeCounts(treeData[i]) 273 | // } 274 | // return count 275 | // } 276 | -------------------------------------------------------------------------------- /src/utils/initLocalStorage.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 如果是第一次使用插件,就初始化需要 持久化的数据 3 | */ 4 | import * as API from '@/api/index' 5 | 6 | export async function initLocalStorageWhenFirstUsePlugin() { 7 | try { 8 | const { data: storage } = await API.getLocalStorage() 9 | 10 | const tasks = [] 11 | 12 | const createTask = (key: string, val: any) => { 13 | return API.setLocalStorageVal({ key, val }).catch((error: any) => { 14 | console.error(`Failed to set ${key}:`, error) 15 | }) 16 | } 17 | 18 | const range = 'plugin-task-list-taskRangeTabClicked' 19 | if (!storage.hasOwnProperty(range)) { 20 | tasks.push(createTask(range, 'doc')) 21 | } 22 | 23 | const filters = 'plugin-task-list-filters' 24 | if (!storage.hasOwnProperty(filters)) { 25 | tasks.push( 26 | createTask(filters, { 27 | /** 任务筛选方式:月视图范围筛选 OR Dock栏中日历视图日期筛选 */ 28 | taskFilterWay: 'monthRange', 29 | /** Dock栏中日历视图日期筛选形式:周视图、月视图 */ 30 | dockCalendarDisplayMode: 'weekly', 31 | /** 日期范围筛选格式:动态 OR 静态 */ 32 | dateRangeFormat: 'static', 33 | /** 动态日期范围:下拉框选择的值 */ 34 | dynamicDateRange: '', 35 | /** 静态日期范围:日历视图选择的日期 */ 36 | staticDateRange: [], 37 | }) 38 | ) 39 | } 40 | 41 | const settings = 'plugin-task-list-settings' 42 | if (!storage.hasOwnProperty(settings)) { 43 | tasks.push( 44 | createTask(settings, { 45 | /** 信息卡片的配置 */ 46 | infoCardConfig: { 47 | dateTimeDisplayMode: 'date', 48 | fieldsForHidden: [], 49 | }, 50 | /** 需要隐藏任务的节点,包括笔记本节点或者是文档节点 */ 51 | nodeListForHideTask: [], 52 | /** 任务列表树的显示模式 */ 53 | taskTreeDisplayMode: 'box-doc-task', 54 | /** 任务节点的排序方式 */ 55 | taskSortBy: 'createdAsc', 56 | }) 57 | ) 58 | } 59 | 60 | // 等待所有任务完成,即使某些任务失败 61 | const results = await Promise.allSettled(tasks) 62 | 63 | // 可以在这里处理结果,例如记录哪些操作成功,哪些失败 64 | results.forEach((result, index) => { 65 | if (result.status === 'rejected') { 66 | console.error(`Task ${index} failed:`, result.reason) 67 | } 68 | }) 69 | } catch (error) { 70 | console.error('Failed to initialize local storage:', error) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/utils/request.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance, AxiosResponse } from "axios"; 2 | import { ElMessage } from "element-plus"; 3 | import { i18n } from "@/utils/common"; 4 | interface ApiResponse { 5 | code: number; 6 | data: T; 7 | msg: string; 8 | } 9 | 10 | const baseURL = "https://siyuan-plugin-task-list.sylwair.com"; 11 | 12 | // const baseURL = "http://localhost:8787"; 13 | // console.log("当前环境", import.meta.env.MODE); 14 | 15 | const instance: AxiosInstance = axios.create({ 16 | baseURL, 17 | maxBodyLength: Infinity, 18 | maxContentLength: Infinity, 19 | timeout: 3 * 60 * 1000, 20 | }); 21 | 22 | // Add response interceptor 23 | instance.interceptors.response.use( 24 | (response) => response, 25 | (error) => { 26 | if (error.code === "ERR_NETWORK") { 27 | ElMessage({ 28 | message: i18n.aiRoast.toast.netWorkError, 29 | grouping: true, 30 | type: "error", 31 | }); 32 | } 33 | return Promise.reject(error); 34 | } 35 | ); 36 | 37 | export const request = { 38 | get: async (url: string, params?: any): Promise> => { 39 | try { 40 | const response: AxiosResponse> = await instance.get(url, { 41 | params, 42 | }); 43 | return response.data; 44 | } catch (error) { 45 | console.error("Error in GET request:", error); 46 | throw error; 47 | } 48 | }, 49 | 50 | post: async (url: string, data?: any): Promise> => { 51 | try { 52 | const response: AxiosResponse> = await instance.post( 53 | url, 54 | data 55 | ); 56 | return response.data; 57 | } catch (error) { 58 | console.error("Error in POST request:", error); 59 | throw error; 60 | } 61 | }, 62 | }; 63 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": [ 7 | "ES2020", 8 | "DOM", 9 | "DOM.Iterable" 10 | ], 11 | "skipLibCheck": true, 12 | /* Bundler mode */ 13 | "moduleResolution": "Node", 14 | "allowImportingTsExtensions": true, 15 | "allowSyntheticDefaultImports": true, 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "noEmit": true, 19 | "jsx": "preserve", 20 | /* Linting */ 21 | "strict": false, 22 | "noUnusedLocals": true, 23 | "noUnusedParameters": true, 24 | "noFallthroughCasesInSwitch": true, 25 | "allowJs": true, 26 | "checkJs": true, 27 | "types": [ 28 | "node", 29 | "vite/client", 30 | "vue" 31 | ], 32 | /* Path alias */ 33 | "baseUrl": ".", 34 | "paths": { 35 | "@/*": ["src/*"] 36 | } 37 | }, 38 | "include": [ 39 | "tools/**/*.ts", 40 | "src/**/*.ts", 41 | "src/**/*.d.ts", 42 | "src/**/*.tsx", 43 | "src/**/*.vue" 44 | ], 45 | "references": [ 46 | { 47 | "path": "./tsconfig.node.json" 48 | } 49 | ], 50 | "root": "." 51 | } 52 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "Node", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": [ 10 | "vite.config.ts" 11 | ] 12 | } -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path' 2 | import { defineConfig, loadEnv } from 'vite' 3 | import minimist from 'minimist' 4 | import { viteStaticCopy } from 'vite-plugin-static-copy' 5 | import livereload from 'rollup-plugin-livereload' 6 | import zipPack from 'vite-plugin-zip-pack' 7 | import fg from 'fast-glob' 8 | 9 | // vite.config.js 10 | import vue from '@vitejs/plugin-vue' 11 | 12 | import AutoImport from 'unplugin-auto-import/vite' 13 | import Components from 'unplugin-vue-components/vite' 14 | import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' 15 | 16 | // 解析命令行参数 17 | const args = minimist(process.argv.slice(2)) 18 | // 判断是否为开发模式 19 | const isWatch = args.watch || args.w || false 20 | // 设置开发和生产环境的输出目录 21 | const devDistDir = './dev' 22 | const distDir = isWatch ? devDistDir : './dist' 23 | 24 | // console.log('isWatch=>', isWatch) 25 | // console.log('distDir=>', distDir) 26 | 27 | export default defineConfig({ 28 | // 配置路径别名 29 | resolve: { 30 | alias: { 31 | '@': resolve(__dirname, 'src'), 32 | }, 33 | }, 34 | // 插件列表 35 | plugins: [ 36 | // 自动导入插件 37 | AutoImport({ 38 | resolvers: [ElementPlusResolver()], 39 | }), 40 | // 自动注册组件插件 41 | Components({ 42 | resolvers: [ElementPlusResolver()], 43 | }), 44 | // Vue插件 45 | vue(), 46 | 47 | // 静态文件复制插件 48 | viteStaticCopy({ 49 | targets: [ 50 | { 51 | src: './README*.md', 52 | dest: './', 53 | }, 54 | { 55 | src: './icon.png', 56 | dest: './', 57 | }, 58 | { 59 | src: './preview.png', 60 | dest: './', 61 | }, 62 | { 63 | src: './plugin.json', 64 | dest: './', 65 | }, 66 | { 67 | src: './src/i18n/**', 68 | dest: './i18n/', 69 | }, 70 | // { 71 | // src: './src/assets/**', 72 | // dest: './assets/', 73 | // }, 74 | ], 75 | }), 76 | ], 77 | 78 | // https://github.com/vitejs/vite/issues/1930 79 | // https://vitejs.dev/guide/env-and-mode.html#env-files 80 | // https://github.com/vitejs/vite/discussions/3058#discussioncomment-2115319 81 | // 在这里自定义变量 82 | define: { 83 | 'process.env.DEV_MODE': `"${isWatch}"`, 84 | 'process.env': {}, 85 | }, 86 | 87 | // 构建配置 88 | build: { 89 | // 输出路径 90 | outDir: distDir, 91 | emptyOutDir: false, 92 | 93 | // 构建后是否���成 source map 文件 94 | sourcemap: false, 95 | 96 | // 设置为 false 可以禁用最小化混淆 97 | // 或是用来指定是应用哪种混淆器 98 | // boolean | 'terser' | 'esbuild' 99 | // 不压缩,用于调试 100 | minify: !isWatch, 101 | 102 | // 库模式配置 103 | lib: { 104 | // Could also be a dictionary or array of multiple entry points 105 | entry: resolve(__dirname, 'src/index.ts'), 106 | // the proper extensions will be added 107 | fileName: 'index', 108 | formats: ['cjs'], 109 | }, 110 | // Rollup 配置 111 | rollupOptions: { 112 | plugins: [ 113 | ...(isWatch 114 | ? [ 115 | // 热更新插件 116 | livereload(devDistDir), 117 | { 118 | //监听静态资源文件 119 | name: 'watch-external', 120 | async buildStart() { 121 | const files = await fg([ 122 | 'src/i18n/*.json', 123 | './README*.md', 124 | './plugin.json', 125 | ]) 126 | for (let file of files) { 127 | this.addWatchFile(file) 128 | } 129 | }, 130 | }, 131 | ] 132 | : [ 133 | // 打包插件 134 | zipPack({ 135 | inDir: './dist', 136 | outDir: './', 137 | outFileName: 'package.zip', 138 | }), 139 | ]), 140 | ], 141 | 142 | // make sure to externalize deps that shouldn't be bundled 143 | // into your library 144 | external: ['siyuan', 'process'], 145 | 146 | output: { 147 | entryFileNames: '[name].js', 148 | assetFileNames: (assetInfo) => { 149 | if (assetInfo.name === 'style.css') { 150 | return 'index.css' 151 | } 152 | return assetInfo.name 153 | }, 154 | }, 155 | }, 156 | }, 157 | }) --------------------------------------------------------------------------------