├── .eslintignore ├── .eslintrc.cjs ├── .github ├── ISSUE_TEMPLATE │ ├── chinese.yml │ └── config.yml └── workflows │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── README_zh_CN.md ├── asset └── action.png ├── icon.png ├── package.json ├── plugin.json ├── pnpm-lock.yaml ├── preview.png ├── scripts ├── .gitignore └── make_dev_link.js ├── src ├── api.ts ├── device_specific_helpers.ts ├── hello.svelte ├── i18n │ ├── en_US.json │ └── zh_CN.json ├── index.scss ├── index.ts ├── libs │ ├── b3-typography.svelte │ ├── index.d.ts │ ├── setting-item.svelte │ ├── setting-panel.svelte │ └── setting-utils.ts ├── setting-example.svelte ├── style_injection.ts └── types │ ├── api.d.ts │ └── index.d.ts ├── svelte.config.js ├── tools └── archived │ ├── hello.svelte │ ├── index.scss │ ├── index.ts │ └── setting-example.svelte ├── 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 | "plugin:svelte/recommended", 6 | "turbo", 7 | "prettier", 8 | ], 9 | 10 | parser: "@typescript-eslint/parser", 11 | 12 | overrides: [ 13 | { 14 | files: ["*.svelte"], 15 | parser: "svelte-eslint-parser", 16 | // Parse the script in `.svelte` as TypeScript by adding the following configuration. 17 | parserOptions: { 18 | parser: "@typescript-eslint/parser", 19 | }, 20 | }, 21 | ], 22 | 23 | plugins: ["@typescript-eslint", "prettier"], 24 | 25 | rules: { 26 | // Note: you must disable the base rule as it can report incorrect errors 27 | semi: "off", 28 | quotes: "off", 29 | "no-undef": "off", 30 | "@typescript-eslint/no-var-requires": "off", 31 | "@typescript-eslint/no-this-alias": "off", 32 | "@typescript-eslint/no-non-null-assertion": "off", 33 | "@typescript-eslint/no-unused-vars": "off", 34 | "@typescript-eslint/no-explicit-any": "off", 35 | "turbo/no-undeclared-env-vars": "off", 36 | "prettier/prettier": "error", 37 | }, 38 | } 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/chinese.yml: -------------------------------------------------------------------------------- 1 | name: Issue 2 | description: Add a new issue 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | > [!IMPORTANT] 8 | > A Message from the Developer: 9 | > Hello! Thank you for using my plugin. Unlike many other Obsidian plugins, my plugin is 100% free and 100% open source, which should give you more confidence in using it. 10 | > Also, because this plugin is 100% open source and free, the only thing I can gain from maintaining and writing this plugin is your encouragement. 11 | > Therefore, before you continue to submit issues, I hope you could give this repository a free ⭐ star. ([How to star?](https://docs.github.com/zh/get-started/exploring-projects-on-github/saving-repositories-with-stars#starring-a-repository)) 12 | > 13 | > 14 | > 来自开发者的话: 15 | > 您好!感谢使用我的插件。不像很多其他思源插件,我的插件是100%免费且100%开源的,这会让您更放心的使用我的插件。 16 | > 同时,由于该插件的100%开源免费,我从维护和编写这个插件中,唯一能得到的只有您的鼓励。 17 | > 所以,在您继续提交issue之前,希望您能给这个仓库点击一个免费的⭐星星(star)。([怎么点?](https://docs.github.com/zh/get-started/exploring-projects-on-github/saving-repositories-with-stars#starring-a-repository)) 18 | - type: textarea 19 | id: description 20 | attributes: 21 | label: Feature request | 功能请求 22 | description: "Fill the blank below if you are submitting a featire request | 如果是(添加)功能请求,请在下面填写" 23 | placeholder: | 24 | Please Start mt repo before submitting an issue ticket. 25 | 在提交之前请给我的GitHub仓库点一个免费的星星 26 | validations: 27 | required: false 28 | 29 | - type: textarea 30 | id: repro 31 | attributes: 32 | label: Report bug | 汇报Bug 33 | description: "Please carefully describe how to reproduce the bug. attach log and the document you are having bug would be better | 请仔细描述bug的复现步骤,最好同时上传你发现问题的文档和思源的日志" 34 | placeholder: | 35 | Please Start mt repo before submitting an issue ticket. 36 | 在提交之前请给我的GitHub仓库点一个免费的星星 37 | validations: 38 | required: false 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create Release on Tag Push 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | # Checkout 13 | - name: Checkout 14 | uses: actions/checkout@v3 15 | 16 | # Install Node.js 17 | - name: Install Node.js 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: 18 21 | registry-url: "https://registry.npmjs.org" 22 | 23 | # Install pnpm 24 | - name: Install pnpm 25 | uses: pnpm/action-setup@v4 26 | id: pnpm-install 27 | with: 28 | version: 9 29 | run_install: false 30 | 31 | # Get pnpm store directory 32 | - name: Get pnpm store directory 33 | id: pnpm-cache 34 | shell: bash 35 | run: | 36 | echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT 37 | 38 | # Setup pnpm cache 39 | - name: Setup pnpm cache 40 | uses: actions/cache@v3 41 | with: 42 | path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} 43 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 44 | restore-keys: | 45 | ${{ runner.os }}-pnpm-store- 46 | 47 | # Install dependencies 48 | - name: Install dependencies 49 | run: pnpm install 50 | 51 | # Build for production, 这一步会生成一个 package.zip 52 | - name: Build for production 53 | run: pnpm build 54 | 55 | - name: Release 56 | uses: ncipollo/release-action@v1 57 | with: 58 | allowUpdates: true 59 | artifactErrorsFailBuild: true 60 | artifacts: "package.zip" 61 | token: ${{ secrets.GITHUB_TOKEN }} 62 | prerelease: true 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | .DS_Store 4 | package.zip 5 | node_modules 6 | dev 7 | dist 8 | build 9 | tmp 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.0.1 2 | - init 3 | # 0.0.2 4 | - make observer observing smaller range 5 | - (performance improve) 6 | # 0.0.3 7 | - support remove doc icon in doctree 8 | # 0.0.4 9 | - fix logic err 10 | 11 | # 0.1.0 12 | - add many many new features 13 | 14 | ## 0.1.2 15 | - add support for mobile serving mode 16 | - add support for mouse hoving font size overload 17 | - add support for unclamp when mouse hoving 18 | - fix influenced setting panel 19 | 20 | ## 0.2.0 21 | - add per device selection 22 | 23 | ## 0.3.0 24 | - add vscode style doctree front line 25 | - add hide doctree icon label 26 | - add hide doctree item content 27 | - add doctree item seperator 28 | - add disable icon click but keep icon 29 | - add line height setting 30 | 31 | ## 0.3.3 32 | - add notebook outline by @TCOTC 33 | - fix deadly bug 34 | - textual/ interface change 35 | 36 | ## 0.3.4 37 | - notebook outline support tight mode 38 | - notebook outline support dark/ night theme 39 | - fix fetching null in setting dialog 40 | - fix unexpected console print 41 | 42 | ## 0.3.5 43 | - support multiple color for outline 44 | 45 | ## 0.3.9 46 | - Support Docker 47 | 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 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 | # Document Tree Compression 2 | ## Features 3 | - Custom indentation of (sub)document items in the document tree 4 | - Customization of whether to hide document item icons in the document tree 5 | - Customization of the font size of document items in the document tree 6 | - Fluid layout of document tree items: Temporarily remove indentation when the mouse hovers over, to avoid difficulty in seeing or clicking on titles in multi-level subdirectories 7 | 8 | ### What is Flow Layout 9 | 10 | - When browsing a document tree with multiple levels of subdocuments, you may encounter situations where you cannot click on them due to excessive indentation, as shown in the image below. 11 | 12 | ![disabled_ldbj](https://tvax3.sinaimg.cn/large/0089YRx6gy1hlvq5zs9xzj30cy0fctay.jpg) 13 | 14 | This makes it difficult or even impossible to click on multi-level subdirectories. 15 | - Flow layout allows the temporary reduction of indentation to the left position when hovering over a subdirectory item, making it easier for you to view titles and click into documents. After you move your mouse away, the indentation will be restored, so it does not affect your ability to differentiate subdirectories. As shown in the image below: 16 | 17 | ![enabled_ldbj](https://tvax4.sinaimg.cn/large/0089YRx6gy1hlvqad31rwg309e08mh65.gif) 18 | 19 | - Furthermore, since flow layout keeps the contents of each column fixed at the same position, you can even perform blind operations once you get used to it, without having to align and click on icons, as shown in the image below: 20 | 21 | ![static_area](https://tvax3.sinaimg.cn/large/0089YRx6gy1hlvq6uhx16j30z00oik89.jpg) 22 | 23 | ## Links 24 | - Repo/Source Code: https://github.com/zxkmm/siyuan_doctree_compress 25 | - Download: https://github.com/zxkmm/siyuan_doctree_compress/releases or search `siyuan_doctree_compress` in SiYuan market 26 | - Reporting Bugs / Submitting Feature Requests: https://github.com/zxkmm/siyuan_doctree_compress/issues 27 | - Forum / Thread (Chinese): https://ld246.com/article/1705326690352 https://ld246.com/article/1705227991331 28 | 29 | # Credit 30 | ## Donator 31 | - Mr. or Mrs. 林 32 | ## Contributors 33 | - [@TCOTC](https://github.com/TCOTC) ([in b3log](https://ld246.com/member/a2930610542)) 34 | - [@TinkMingKing](https://github.com/TinkMingKing) 35 | 36 | 37 | 38 | # Declaration 39 | - This plugin is fully open source under the MIT license, does not have internet access, and does not have the ability to fetch and inject malicious code from a remote source. 40 | 41 | # Additional Attachment to MIT License 42 | 43 | You are free to use the code in this repository, regardless of whether it's closed source or not, or whether it's part of paid software or not. However, I have incorporated these additional requests into the license of this repository. If you use the code, design, text, algorithms, or anything else from this repository, you must include my username "zxkmm" and the link to this repository in three places: 44 | 45 | 1. In the code comments. 46 | 2. In the settings interface related to my code. 47 | 3. On the 'About' page of your software/website/and or any other format of computer production. 48 | 49 | -------------------------------------------------------------------------------- /README_zh_CN.md: -------------------------------------------------------------------------------- 1 | # 文档树自定义 2 | 3 | ## 乞讨 4 | 这款插件是开源免费的,因此我需要您的鼓励。 5 | 如果您喜欢这款插件为您带来的功能,您可以考虑捐款给我,帮助我解决温饱问题:[捐款](https://pipe.b3log.org/blogs/zxkmm/articles/2025/02/08/1738993480704)。(捐款不会解锁更多功能) 6 | 如果您经济不宽裕,您可以考虑给[我的GitHub仓库](https://github.com/zxkmm/siyuan_doctree_compress)点一下免费的星星鼓励我。 7 | 如果您认为您或者您认识的人需要我的技能,欢迎随时雇用我。 8 | 9 | ## 功能 10 | - 自定义文档树中(子)文档项目的缩进 11 | - 自定义文档树中文档项目图标的隐藏与否 12 | - 自定义文档树的文档项目字体大小 13 | - 文档树项目流动布局:在鼠标悬停时,暂时将缩进取消,避免多级子目录难以看到标题或难以点击 14 | - 鼠标悬浮独立功能(字体大小/ 图标) 15 | - 隐藏气泡 16 | - 按设备启用 17 | 18 | ### 什么是流动布局 19 | - 您在浏览多层子文档的文档树时,会遇到因为有多级缩进而点不到的情况,如下图。 20 | 21 | ![disabled_ldbj](https://tvax3.sinaimg.cn/large/0089YRx6gy1hlvq5zs9xzj30cy0fctay.jpg) 22 | 23 | 24 | 这导致难以或者根本不能点击多级子目录。 25 | - 流动布局在鼠标悬浮到子目录项目时,可以临时将子目录的缩进缩短到左侧位置,方便您轻松地查看标题和点击进入文档,在您的鼠标离开后,缩进将恢复,所以完全不影响您区分子目录。如下图: 26 | 27 | ![enabled_ldbj](https://tvax4.sinaimg.cn/large/0089YRx6gy1hlvqad31rwg309e08mh65.gif) 28 | 29 | - 更多地,由于流动布局将每个栏目的内容都固定在列的相同位置,您习惯后甚至可以实现盲操作,而不用去对准图标点击,如下图 30 | 31 | ![static_area](https://tvax3.sinaimg.cn/large/0089YRx6gy1hlvq6uhx16j30z00oik89.jpg) 32 | 33 | ## 链接 34 | - repo/Source Code:https://github.com/zxkmm/siyuan_doctree_compress 35 | 36 | - Download:https://github.com/zxkmm/siyuan_doctree_compress /releases or search `siyuan_doctree_compress` in SiYuan market 37 | 38 | - Reporting Bugs / Submitting Feqture request:https://github.com/zxkmm/siyuan_doctree_compress /issues 39 | 40 | - Forum / thread (Chinese):https://ld246.com/article/1705326690352 https://ld246.com/article/1705227991331 41 | 42 | # 鸣谢 43 | ## 捐助者 44 | - 林 先生/女士 45 | 46 | ## 代码贡献者 47 | - [@TCOTC](https://github.com/TCOTC) ([in b3log](https://ld246.com/member/a2930610542)) 48 | - [@TinkMingKing](https://github.com/TinkMingKing) 49 | 50 | 51 | ## 申明 52 | - 本插件在MIT协议下完全开源,没有联网行为,也没有从远端fetch并植入恶意代码的能力 53 | 54 | # 对MIT许可证的额外附加 55 | 56 | 您可以自由使用/分发此存储库中的代码,无论您打算闭源还是开源,或者无论您打算用在付费或免费软件的一部分,您都可以自由使用。然而,我已将这些额外的请求纳入了此存储库的许可证。如果您使用了来自此存储库的代码、设计、文本、算法或任何其他东西,您必须在以下三个地方包含我的用户名 "zxkmm" 和此存储库的链接: 57 | 58 | 1. 在代码注释中。 59 | 2. 在与我的代码相关的设置界面中。 60 | 3. 在您的软件/网站的 '关于' 页面以及或任何其他形式的计算机产品的中。 61 | -------------------------------------------------------------------------------- /asset/action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxkmm/siyuan_doctree_compress/7d9fca218aa87ebe5d3d44f59811e8a263ffde64/asset/action.png -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxkmm/siyuan_doctree_compress/7d9fca218aa87ebe5d3d44f59811e8a263ffde64/icon.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "siyuan_doctree_compress", 3 | "version": "0.4.2", 4 | "type": "module", 5 | "description": "fully customs your doctree", 6 | "repository": "https://github.com/zxkmm/siyuan_doctree_compress", 7 | "homepage": "https://github.com/zxkmm/siyuan_doctree_compress", 8 | "author": "zxkmm", 9 | "license": "MIT", 10 | "scripts": { 11 | "make-link": "node --no-warnings ./scripts/make_dev_link.js", 12 | "dev": "vite build --watch", 13 | "build": "vite build" 14 | }, 15 | "devDependencies": { 16 | "@sveltejs/vite-plugin-svelte": "^2.0.3", 17 | "@tsconfig/svelte": "^4.0.1", 18 | "@types/node": "^20.2.0", 19 | "eslint": "^8.42.0", 20 | "fast-glob": "^3.2.12", 21 | "glob": "^7.2.3", 22 | "minimist": "^1.2.8", 23 | "rollup-plugin-livereload": "^2.0.5", 24 | "sass": "^1.62.1", 25 | "siyuan": "0.9.2", 26 | "svelte": "^4.2.19", 27 | "ts-node": "^10.9.1", 28 | "typescript": "^5.0.4", 29 | "vite": "^4.3.7", 30 | "vite-plugin-static-copy": "^0.15.0", 31 | "vite-plugin-zip-pack": "^1.0.5" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "siyuan_doctree_compress", 3 | "author": "zxkmm", 4 | "url": "https://github.com/zxkmm/siyuan_doctree_compress", 5 | "version": "0.4.3", 6 | "minAppVersion": "2.12.1", 7 | "backends": [ 8 | "windows", 9 | "linux", 10 | "darwin", 11 | "android", 12 | "ios", 13 | "docker", 14 | "harmony" 15 | ], 16 | "frontends": [ 17 | "desktop", 18 | "browser-desktop", 19 | "desktop-window" 20 | ], 21 | "displayName": { 22 | "en_US": "doctree modify", 23 | "zh_CN": "文档树自定义" 24 | }, 25 | "description": { 26 | "en_US": "(doctree compress) provides up to 12 functions, including flow layout/ indentation adjustment/ hide bubble/ shield button/ font size/ mouse hover, etc., to completely customize your document tree", 27 | "zh_CN": "(文档树压缩)提供多达20余项功能,包括 流动布局/ 缩进调节/ 隐藏气泡/ 屏蔽按钮/ 字体大小/ 鼠标悬浮/ 层级竖线/ 分割线/ 等等,完全自定义您的文档树" 28 | }, 29 | "readme": { 30 | "en_US": "README.md", 31 | "zh_CN": "README_zh_CN.md" 32 | }, 33 | "funding": { 34 | "custom": [ 35 | "https://pipe.b3log.org/blogs/zxkmm/articles/2025/02/08/1738993480704" 36 | ] 37 | }, 38 | "keywords": [ 39 | "文档树", "压缩", "自定义", "compress", "doctree", "modify", "zxkmm", "文档树压缩", "文档树自定义", "siyuan_doctree_compress" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxkmm/siyuan_doctree_compress/7d9fca218aa87ebe5d3d44f59811e8a263ffde64/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 http from 'node:http'; 3 | import readline from 'node:readline'; 4 | 5 | 6 | //************************************ Write you dir here ************************************ 7 | 8 | //Please write the "workspace/data/plugins" directory here 9 | //请在这里填写你的 "workspace/data/plugins" 目录 10 | let targetDir = '/home/zxkmm/Documents/siyuan_dev/data/plugins'; 11 | //Like this 12 | // let targetDir = `H:\\SiYuanDevSpace\\data\\plugins`; 13 | //******************************************************************************************** 14 | 15 | const log = (info) => console.log(`\x1B[36m%s\x1B[0m`, info); 16 | const error = (info) => console.log(`\x1B[31m%s\x1B[0m`, info); 17 | 18 | let POST_HEADER = { 19 | // "Authorization": `Token ${token}`, 20 | "Content-Type": "application/json", 21 | } 22 | 23 | async function myfetch(url, options) { 24 | //使用 http 模块,从而兼容那些不支持 fetch 的 nodejs 版本 25 | return new Promise((resolve, reject) => { 26 | let req = http.request(url, options, (res) => { 27 | let data = ''; 28 | res.on('data', (chunk) => { 29 | data += chunk; 30 | }); 31 | res.on('end', () => { 32 | resolve({ 33 | ok: true, 34 | status: res.statusCode, 35 | json: () => JSON.parse(data) 36 | }); 37 | }); 38 | }); 39 | req.on('error', (e) => { 40 | reject(e); 41 | }); 42 | req.end(); 43 | }); 44 | } 45 | 46 | async function getSiYuanDir() { 47 | let url = 'http://127.0.0.1:6806/api/system/getWorkspaces'; 48 | let conf = {}; 49 | try { 50 | let response = await myfetch(url, { 51 | method: 'POST', 52 | headers: POST_HEADER 53 | }); 54 | if (response.ok) { 55 | conf = await response.json(); 56 | } else { 57 | error(`\tHTTP-Error: ${response.status}`); 58 | return null; 59 | } 60 | } catch (e) { 61 | error(`\tError: ${e}`); 62 | error("\tPlease make sure SiYuan is running!!!"); 63 | return null; 64 | } 65 | return conf.data; 66 | } 67 | 68 | async function chooseTarget(workspaces) { 69 | let count = workspaces.length; 70 | log(`>>> Got ${count} SiYuan ${count > 1 ? 'workspaces' : 'workspace'}`) 71 | for (let i = 0; i < workspaces.length; i++) { 72 | log(`\t[${i}] ${workspaces[i].path}`); 73 | } 74 | 75 | if (count == 1) { 76 | return `${workspaces[0].path}/data/plugins`; 77 | } else { 78 | const rl = readline.createInterface({ 79 | input: process.stdin, 80 | output: process.stdout 81 | }); 82 | let index = await new Promise((resolve, reject) => { 83 | rl.question(`\tPlease select a workspace[0-${count-1}]: `, (answer) => { 84 | resolve(answer); 85 | }); 86 | }); 87 | rl.close(); 88 | return `${workspaces[index].path}/data/plugins`; 89 | } 90 | } 91 | 92 | log('>>> Try to visit constant "targetDir" in make_dev_link.js...') 93 | 94 | if (targetDir === '') { 95 | log('>>> Constant "targetDir" is empty, try to get SiYuan directory automatically....') 96 | let res = await getSiYuanDir(); 97 | 98 | if (res === null || res === undefined || res.length === 0) { 99 | log('>>> Can not get SiYuan directory automatically, try to visit environment variable "SIYUAN_PLUGIN_DIR"....'); 100 | 101 | // console.log(process.env) 102 | let env = process.env?.SIYUAN_PLUGIN_DIR; 103 | if (env !== undefined && env !== null && env !== '') { 104 | targetDir = env; 105 | log(`\tGot target directory from environment variable "SIYUAN_PLUGIN_DIR": ${targetDir}`); 106 | } else { 107 | error('\tCan not get SiYuan directory from environment variable "SIYUAN_PLUGIN_DIR", failed!'); 108 | process.exit(1); 109 | } 110 | } else { 111 | targetDir = await chooseTarget(res); 112 | } 113 | 114 | 115 | log(`>>> Successfully got target directory: ${targetDir}`); 116 | } 117 | 118 | //Check 119 | if (!fs.existsSync(targetDir)) { 120 | error(`Failed! plugin directory not exists: "${targetDir}"`); 121 | error(`Please set the plugin directory in scripts/make_dev_link.js`); 122 | process.exit(1); 123 | } 124 | 125 | 126 | //check if plugin.json exists 127 | if (!fs.existsSync('./plugin.json')) { 128 | //change dir to parent 129 | process.chdir('../'); 130 | if (!fs.existsSync('./plugin.json')) { 131 | error('Failed! plugin.json not found'); 132 | process.exit(1); 133 | } 134 | } 135 | 136 | //load plugin.json 137 | const plugin = JSON.parse(fs.readFileSync('./plugin.json', 'utf8')); 138 | const name = plugin?.name; 139 | if (!name || name === '') { 140 | error('Failed! Please set plugin name in plugin.json'); 141 | process.exit(1); 142 | } 143 | 144 | //dev directory 145 | const devDir = `${process.cwd()}/dev`; 146 | //mkdir if not exists 147 | if (!fs.existsSync(devDir)) { 148 | fs.mkdirSync(devDir); 149 | } 150 | 151 | function cmpPath(path1, path2) { 152 | path1 = path1.replace(/\\/g, '/'); 153 | path2 = path2.replace(/\\/g, '/'); 154 | // sepertor at tail 155 | if (path1[path1.length - 1] !== '/') { 156 | path1 += '/'; 157 | } 158 | if (path2[path2.length - 1] !== '/') { 159 | path2 += '/'; 160 | } 161 | return path1 === path2; 162 | } 163 | 164 | const targetPath = `${targetDir}/${name}`; 165 | //如果已经存在,就退出 166 | if (fs.existsSync(targetPath)) { 167 | let isSymbol = fs.lstatSync(targetPath).isSymbolicLink(); 168 | 169 | if (isSymbol) { 170 | let srcPath = fs.readlinkSync(targetPath); 171 | 172 | if (cmpPath(srcPath, devDir)) { 173 | log(`Good! ${targetPath} is already linked to ${devDir}`); 174 | } else { 175 | error(`Error! Already exists symbolic link ${targetPath}\nBut it links to ${srcPath}`); 176 | } 177 | } else { 178 | error(`Failed! ${targetPath} already exists and is not a symbolic link`); 179 | } 180 | 181 | } else { 182 | //创建软链接 183 | fs.symlinkSync(devDir, targetPath, 'junction'); 184 | log(`Done! Created symlink ${targetPath}`); 185 | } 186 | 187 | -------------------------------------------------------------------------------- /src/api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2023 frostime. All rights reserved. 3 | * https://github.com/frostime/sy-plugin-template-vite 4 | * 5 | * See API Document in [API.md](https://github.com/siyuan-note/siyuan/blob/master/API.md) 6 | * API 文档见 [API_zh_CN.md](https://github.com/siyuan-note/siyuan/blob/master/API_zh_CN.md) 7 | */ 8 | 9 | import { fetchSyncPost, IWebSocketData } from "siyuan"; 10 | 11 | 12 | export async function request(url: string, data: any) { 13 | let response: IWebSocketData = await fetchSyncPost(url, data); 14 | let res = response.code === 0 ? response.data : null; 15 | return res; 16 | } 17 | 18 | 19 | // **************************************** Noteboook **************************************** 20 | 21 | 22 | export async function lsNotebooks(): Promise { 23 | let url = '/api/notebook/lsNotebooks'; 24 | return request(url, ''); 25 | } 26 | 27 | 28 | export async function openNotebook(notebook: NotebookId) { 29 | let url = '/api/notebook/openNotebook'; 30 | return request(url, { notebook: notebook }); 31 | } 32 | 33 | 34 | export async function closeNotebook(notebook: NotebookId) { 35 | let url = '/api/notebook/closeNotebook'; 36 | return request(url, { notebook: notebook }); 37 | } 38 | 39 | 40 | export async function renameNotebook(notebook: NotebookId, name: string) { 41 | let url = '/api/notebook/renameNotebook'; 42 | return request(url, { notebook: notebook, name: name }); 43 | } 44 | 45 | 46 | export async function createNotebook(name: string): Promise { 47 | let url = '/api/notebook/createNotebook'; 48 | return request(url, { name: name }); 49 | } 50 | 51 | 52 | export async function removeNotebook(notebook: NotebookId) { 53 | let url = '/api/notebook/removeNotebook'; 54 | return request(url, { notebook: notebook }); 55 | } 56 | 57 | 58 | export async function getNotebookConf(notebook: NotebookId): Promise { 59 | let data = { notebook: notebook }; 60 | let url = '/api/notebook/getNotebookConf'; 61 | return request(url, data); 62 | } 63 | 64 | 65 | export async function setNotebookConf(notebook: NotebookId, conf: NotebookConf): Promise { 66 | let data = { notebook: notebook, conf: conf }; 67 | let url = '/api/notebook/setNotebookConf'; 68 | return request(url, data); 69 | } 70 | 71 | 72 | // **************************************** File Tree **************************************** 73 | export async function createDocWithMd(notebook: NotebookId, path: string, markdown: string): Promise { 74 | let data = { 75 | notebook: notebook, 76 | path: path, 77 | markdown: markdown, 78 | }; 79 | let url = '/api/filetree/createDocWithMd'; 80 | return request(url, data); 81 | } 82 | 83 | 84 | export async function renameDoc(notebook: NotebookId, path: string, title: string): Promise { 85 | let data = { 86 | doc: notebook, 87 | path: path, 88 | title: title 89 | }; 90 | let url = '/api/filetree/renameDoc'; 91 | return request(url, data); 92 | } 93 | 94 | 95 | export async function removeDoc(notebook: NotebookId, path: string) { 96 | let data = { 97 | notebook: notebook, 98 | path: path, 99 | }; 100 | let url = '/api/filetree/removeDoc'; 101 | return request(url, data); 102 | } 103 | 104 | 105 | export async function moveDocs(fromPaths: string[], toNotebook: NotebookId, toPath: string) { 106 | let data = { 107 | fromPaths: fromPaths, 108 | toNotebook: toNotebook, 109 | toPath: toPath 110 | }; 111 | let url = '/api/filetree/moveDocs'; 112 | return request(url, data); 113 | } 114 | 115 | 116 | export async function getHPathByPath(notebook: NotebookId, path: string): Promise { 117 | let data = { 118 | notebook: notebook, 119 | path: path 120 | }; 121 | let url = '/api/filetree/getHPathByPath'; 122 | return request(url, data); 123 | } 124 | 125 | 126 | export async function getHPathByID(id: BlockId): Promise { 127 | let data = { 128 | id: id 129 | }; 130 | let url = '/api/filetree/getHPathByID'; 131 | return request(url, data); 132 | } 133 | 134 | 135 | export async function getIDsByHPath(notebook: NotebookId, path: string): Promise { 136 | let data = { 137 | notebook: notebook, 138 | path: path 139 | }; 140 | let url = '/api/filetree/getIDsByHPath'; 141 | return request(url, data); 142 | } 143 | 144 | // **************************************** Asset Files **************************************** 145 | 146 | export async function upload(assetsDirPath: string, files: any[]): Promise { 147 | let form = new FormData(); 148 | form.append('assetsDirPath', assetsDirPath); 149 | for (let file of files) { 150 | form.append('file[]', file); 151 | } 152 | let url = '/api/asset/upload'; 153 | return request(url, form); 154 | } 155 | 156 | // **************************************** Block **************************************** 157 | type DataType = "markdown" | "dom"; 158 | export async function insertBlock( 159 | dataType: DataType, data: string, 160 | nextID?: BlockId, previousID?: BlockId, parentID?: BlockId 161 | ): Promise { 162 | let payload = { 163 | dataType: dataType, 164 | data: data, 165 | nextID: nextID, 166 | previousID: previousID, 167 | parentID: parentID 168 | } 169 | let url = '/api/block/insertBlock'; 170 | return request(url, payload); 171 | } 172 | 173 | 174 | export async function prependBlock(dataType: DataType, data: string, parentID: BlockId | DocumentId): Promise { 175 | let payload = { 176 | dataType: dataType, 177 | data: data, 178 | parentID: parentID 179 | } 180 | let url = '/api/block/prependBlock'; 181 | return request(url, payload); 182 | } 183 | 184 | 185 | export async function appendBlock(dataType: DataType, data: string, parentID: BlockId | DocumentId): Promise { 186 | let payload = { 187 | dataType: dataType, 188 | data: data, 189 | parentID: parentID 190 | } 191 | let url = '/api/block/appendBlock'; 192 | return request(url, payload); 193 | } 194 | 195 | 196 | export async function updateBlock(dataType: DataType, data: string, id: BlockId): Promise { 197 | let payload = { 198 | dataType: dataType, 199 | data: data, 200 | id: id 201 | } 202 | let url = '/api/block/updateBlock'; 203 | return request(url, payload); 204 | } 205 | 206 | 207 | export async function deleteBlock(id: BlockId): Promise { 208 | let data = { 209 | id: id 210 | } 211 | let url = '/api/block/deleteBlock'; 212 | return request(url, data); 213 | } 214 | 215 | 216 | export async function moveBlock(id: BlockId, previousID?: PreviousID, parentID?: ParentID): Promise { 217 | let data = { 218 | id: id, 219 | previousID: previousID, 220 | parentID: parentID 221 | } 222 | let url = '/api/block/moveBlock'; 223 | return request(url, data); 224 | } 225 | 226 | 227 | export async function foldBlock(id: BlockId) { 228 | let data = { 229 | id: id 230 | } 231 | let url = '/api/block/foldBlock'; 232 | return request(url, data); 233 | } 234 | 235 | 236 | export async function unfoldBlock(id: BlockId) { 237 | let data = { 238 | id: id 239 | } 240 | let url = '/api/block/unfoldBlock'; 241 | return request(url, data); 242 | } 243 | 244 | 245 | export async function getBlockKramdown(id: BlockId): Promise { 246 | let data = { 247 | id: id 248 | } 249 | let url = '/api/block/getBlockKramdown'; 250 | return request(url, data); 251 | } 252 | 253 | 254 | export async function getChildBlocks(id: BlockId): Promise { 255 | let data = { 256 | id: id 257 | } 258 | let url = '/api/block/getChildBlocks'; 259 | return request(url, data); 260 | } 261 | 262 | export async function transferBlockRef(fromID: BlockId, toID: BlockId, refIDs: BlockId[]) { 263 | let data = { 264 | fromID: fromID, 265 | toID: toID, 266 | refIDs: refIDs 267 | } 268 | let url = '/api/block/transferBlockRef'; 269 | return request(url, data); 270 | } 271 | 272 | // **************************************** Attributes **************************************** 273 | export async function setBlockAttrs(id: BlockId, attrs: { [key: string]: string }) { 274 | let data = { 275 | id: id, 276 | attrs: attrs 277 | } 278 | let url = '/api/attr/setBlockAttrs'; 279 | return request(url, data); 280 | } 281 | 282 | 283 | export async function getBlockAttrs(id: BlockId): Promise<{ [key: string]: string }> { 284 | let data = { 285 | id: id 286 | } 287 | let url = '/api/attr/getBlockAttrs'; 288 | return request(url, data); 289 | } 290 | 291 | // **************************************** SQL **************************************** 292 | 293 | export async function sql(sql: string): Promise { 294 | let sqldata = { 295 | stmt: sql, 296 | }; 297 | let url = '/api/query/sql'; 298 | return request(url, sqldata); 299 | } 300 | 301 | export async function getBlockByID(blockId: string): Promise { 302 | let sqlScript = `select * from blocks where id ='${blockId}'`; 303 | let data = await sql(sqlScript); 304 | return data[0]; 305 | } 306 | 307 | // **************************************** Template **************************************** 308 | 309 | export async function render(id: DocumentId, path: string): Promise { 310 | let data = { 311 | id: id, 312 | path: path 313 | } 314 | let url = '/api/template/render'; 315 | return request(url, data); 316 | } 317 | 318 | 319 | export async function renderSprig(template: string): Promise { 320 | let url = '/api/template/renderSprig'; 321 | return request(url, { template: template }); 322 | } 323 | 324 | // **************************************** File **************************************** 325 | 326 | export async function getFile(path: string): Promise { 327 | let data = { 328 | path: path 329 | } 330 | let url = '/api/file/getFile'; 331 | try { 332 | let file = await fetchSyncPost(url, data); 333 | return file; 334 | } catch (error_msg) { 335 | return null; 336 | } 337 | } 338 | 339 | export async function putFile(path: string, isDir: boolean, file: any) { 340 | let form = new FormData(); 341 | form.append('path', path); 342 | form.append('isDir', isDir.toString()); 343 | // Copyright (c) 2023, terwer. 344 | // https://github.com/terwer/siyuan-plugin-importer/blob/v1.4.1/src/api/kernel-api.ts 345 | form.append('modTime', Math.floor(Date.now() / 1000).toString()); 346 | form.append('file', file); 347 | let url = '/api/file/putFile'; 348 | return request(url, form); 349 | } 350 | 351 | export async function removeFile(path: string) { 352 | let data = { 353 | path: path 354 | } 355 | let url = '/api/file/removeFile'; 356 | return request(url, data); 357 | } 358 | 359 | 360 | 361 | export async function readDir(path: string): Promise { 362 | let data = { 363 | path: path 364 | } 365 | let url = '/api/file/readDir'; 366 | return request(url, data); 367 | } 368 | 369 | 370 | // **************************************** Export **************************************** 371 | 372 | export async function exportMdContent(id: DocumentId): Promise { 373 | let data = { 374 | id: id 375 | } 376 | let url = '/api/export/exportMdContent'; 377 | return request(url, data); 378 | } 379 | 380 | export async function exportResources(paths: string[], name: string): Promise { 381 | let data = { 382 | paths: paths, 383 | name: name 384 | } 385 | let url = '/api/export/exportResources'; 386 | return request(url, data); 387 | } 388 | 389 | // **************************************** Convert **************************************** 390 | 391 | export type PandocArgs = string; 392 | export async function pandoc(args: PandocArgs[]) { 393 | let data = { 394 | args: args 395 | } 396 | let url = '/api/convert/pandoc'; 397 | return request(url, data); 398 | } 399 | 400 | // **************************************** Notification **************************************** 401 | 402 | // /api/notification/pushMsg 403 | // { 404 | // "msg": "test", 405 | // "timeout": 7000 406 | // } 407 | export async function pushMsg(msg: string, timeout: number = 7000) { 408 | let payload = { 409 | msg: msg, 410 | timeout: timeout 411 | }; 412 | let url = "/api/notification/pushMsg"; 413 | return request(url, payload); 414 | } 415 | 416 | export async function pushErrMsg(msg: string, timeout: number = 7000) { 417 | let payload = { 418 | msg: msg, 419 | timeout: timeout 420 | }; 421 | let url = "/api/notification/pushErrMsg"; 422 | return request(url, payload); 423 | } 424 | 425 | // **************************************** Network **************************************** 426 | export async function forwardProxy( 427 | url: string, method: string = 'GET', payload: any = {}, 428 | headers: any[] = [], timeout: number = 7000, contentType: string = "text/html" 429 | ): Promise { 430 | let data = { 431 | url: url, 432 | method: method, 433 | timeout: timeout, 434 | contentType: contentType, 435 | headers: headers, 436 | payload: payload 437 | } 438 | let url1 = '/api/network/forwardProxy'; 439 | return request(url1, data); 440 | } 441 | 442 | 443 | // **************************************** System **************************************** 444 | 445 | export async function bootProgress(): Promise { 446 | return request('/api/system/bootProgress', {}); 447 | } 448 | 449 | 450 | export async function version(): Promise { 451 | return request('/api/system/version', {}); 452 | } 453 | 454 | 455 | export async function currentTime(): Promise { 456 | return request('/api/system/currentTime', {}); 457 | } 458 | -------------------------------------------------------------------------------- /src/device_specific_helpers.ts: -------------------------------------------------------------------------------- 1 | export async function appendCurrentDeviceIntoList(settingUtils) { 2 | try { 3 | // await!!!!! 4 | var current_device_info = await fetchCurrentDeviceInfo(); 5 | 6 | var enableDeviceList = settingUtils.get("enableDeviceList"); 7 | var enableDeviceListArray = enableDeviceList.split("\n"); 8 | var enableDeviceListArrayLength = enableDeviceListArray.length; 9 | var enableDeviceListArrayLast = 10 | enableDeviceListArray[enableDeviceListArrayLength - 1]; 11 | 12 | // remove empty line 13 | if (enableDeviceListArrayLast === "") { 14 | enableDeviceListArray.pop(); 15 | } 16 | 17 | enableDeviceListArray.push(current_device_info); 18 | 19 | var enableDeviceListArrayString = enableDeviceListArray.join("\n"); 20 | 21 | settingUtils.assignValue("enableDeviceList", enableDeviceListArrayString); 22 | settingUtils.save(); 23 | } catch (error) { 24 | console.error("Error appending current device into list:", error); 25 | } 26 | } 27 | 28 | export async function removeCurrentDeviceFromList(settingUtils) { 29 | try { 30 | var current_device_info = await fetchCurrentDeviceInfo(); 31 | 32 | var enableDeviceList = settingUtils.get("enableDeviceList"); 33 | var enableDeviceListArray = enableDeviceList.split("\n"); 34 | 35 | // make sure visited the entire list 36 | for (var i = enableDeviceListArray.length - 1; i >= 0; i--) { 37 | var deviceInfo = enableDeviceListArray[i]; 38 | 39 | if (deviceInfo === current_device_info) { 40 | enableDeviceListArray.splice(i, 1); 41 | } 42 | } 43 | 44 | // reassemble list 45 | var enableDeviceListArrayString = enableDeviceListArray.join("\n"); 46 | 47 | settingUtils.assignValue("enableDeviceList", enableDeviceListArrayString); 48 | settingUtils.save(); 49 | } catch (error) { 50 | console.error("Error removing current device from list:", error); 51 | } 52 | } 53 | 54 | export async function fetchCurrentDeviceInfo(): Promise { 55 | var current_device_uuid = window.siyuan.config.system.id; 56 | var current_device_name = window.siyuan.config.system.name; 57 | var current_device_info = current_device_uuid + " " + current_device_name; 58 | 59 | return Promise.resolve(current_device_info.toString()); 60 | } 61 | 62 | export async function currentDeviceInList(settingUtils) { 63 | try { 64 | var current_device_info = await fetchCurrentDeviceInfo(); 65 | 66 | var enableDeviceList = await settingUtils.get("enableDeviceList"); 67 | var enableDeviceListArray = enableDeviceList.split("\n"); 68 | 69 | return enableDeviceListArray.includes(current_device_info); 70 | } catch (error) { 71 | console.error("Error checking if current device is enabled:", error); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/hello.svelte: -------------------------------------------------------------------------------- 1 | 37 | 38 |
39 |
appId:
40 |
41 |
${app?.appId}
42 |
43 |
44 |
API demo:
45 |
46 |
47 | System current time: {time} 48 |
49 |
50 |
51 |
Protyle demo: id = {blockID}
52 |
53 |
54 |
55 | 56 | -------------------------------------------------------------------------------- /src/i18n/en_US.json: -------------------------------------------------------------------------------- 1 | { 2 | "mainSwitch": "Main Switch", 3 | 4 | "enableAdjustStaticDoctreePadding": "Enable Compress Doctree Padding", 5 | "enableAdjustStaticDoctreePaddingDesc": "Enable Compress Doctree Padding", 6 | "compressPercent": "Compression Percentage", 7 | "compressPercentDesc": "Document tree indentation compression percentage %", 8 | 9 | "hideIcon": "Hide Document Icons in the Tree", 10 | "hideIconDesc": "", 11 | "hideIconForce": "Force Hide Icon Mode", 12 | "hideIconDescForce": "Enable when the above switch is not effective, and only effective when the previous switch is on", 13 | 14 | "hintTitle": "About
", 15 | "hintDesc": "
  • ● Made by zxkmm, open source under the MIT license.
  • ● If you like this plugin, please light up the free star⭐ (Star) for my GitHub repository.
  • ● Link: https://github.com/zxkmm/siyuan_doctree_compress
  • ", 16 | 17 | "experimentFeatureHintDesc": "(Maybe have compatible issue with 3rd party themes)", 18 | "experimentFeatureHintTitle": "
    ● Dynamic Doctree layout Options", 19 | 20 | "hintDangerousZoneTitle": "
    ● Static Doctree Layout Options", 21 | "hintDangerousZoneDesc": "(Maybe have compatible issue with 3rd party themes)", 22 | 23 | "overloadFontSizeSwitch": "Overload Document Tree Font Size", 24 | "overloadFontSizeSwitchDesc": "Once enabled, you can use the following settings to adjust the font size of the document tree", 25 | "overloadFontSizeForceSwitch": "Force Overload Font Mode", 26 | "overloadFontSizeForceSwitchDesc": "Enable when the above switch is not effective, and only effective when the previous switch is on", 27 | "overloadFontSizePx": "Document Tree Font Size", 28 | "overloadFontSizePxDesc": "Must check the above switch to take effect", 29 | 30 | "mouseHoverZeroPadding": "Flow Layout of Document Tree Items when Mouse Hovering", 31 | "mouseHoverZeroPaddingDesc": "Convenient for clicking and viewing titles.
    Read more details here\n", 32 | "mouseHoverZeroPaddingForce": "Force Flow Layout Mode", 33 | "mouseHoverZeroPaddingForceDesc": "Enable when the above switch is not effective, and only effective when the previous switch is on", 34 | "mouseHoverZeroPaddingStyle": "Flow layout style", 35 | "mouseHoverZeroPaddingStyledesc": "", 36 | "mouseHoverZeroPaddingStylePaddingToggle":"Flow layout all element", 37 | "mouseHoverZeroPaddingStylePaddingIcon":"Don't flow layout toggle", 38 | "mouseHoverZeroPaddingStylePaddingText":"Only flow layout text", 39 | "mouseHoverZeroPaddingStylePaddingIconButMoveLR":"Don't flow layout toggle, method 2", 40 | "mouseHoverZeroPaddingPx": "Flow Layout Padding", 41 | "mouseHoverZeroPaddingPxDesc": "default value is 4 which is the default padding of the first level notbooks", 42 | 43 | "mouseOverLineUnclampDesc": "Convenient for clicking and viewing titles", 44 | "mouseOverLineUnclampTitle": "Unclamp lines in doctree, convenient for clicking and viewing titles", 45 | "mouseOverLineUnclampForceDesc": "Enable when the above switch is not effective, and only effective when the previous switch is on", 46 | "mouseOverLineUnclampForceTitle": "Force Unclamp lines mode", 47 | 48 | "mouseOverReduceFontSizeDesc": "Convenient for clicking and viewing titles", 49 | "mouseOverReduceFontSizeTitle": "Reload font size when mouse hovering", 50 | "mouseOverReduceFontSizeForceDesc": "Enable when the above switch is not effective, and only effective when the previous switch is on", 51 | "mouseOverReduceFontSizeForceTitle": "Force reload font zise mode", 52 | "mouseHoverReduceFontSizePx": "Font size when mouse over", 53 | "mouseHoverReduceFontSizePxDesc": "default:14 unit: px", 54 | 55 | "overloadLineHeight": "Overload Line Height", 56 | "overloadLineHeightDesc": "Overload Document Tree Line Height", 57 | "overloadLineHeightForce": "Force Overload Height Mode", 58 | "overloadLineHeightForceDesc": "Enable when the above switch is not effective, and only effective when the previous switch is on", 59 | "overloadLineHeightPx": "Document Tree Line Height", 60 | "overloadLineHeightPxDesc": "Must check the above switch to take effect, default: 28 unit: px", 61 | 62 | "addCurrentDeviceIntoListLabel":"Add", 63 | "addCurrentDeviceIntoListDesc":"When you click the button, the interface will be reloaded, and unsaved configurations will be lost, so please save other configuration items before clicking the button", 64 | "addCurrentDeviceIntoList":"Add current device into the list", 65 | "enableDeviceListDesc":"This list is used to control which device should enable this plugin on, please try not to modify it manually", 66 | "enableDeviceList":"Enable Device List", 67 | "onlyEnableListedDevicesDesc":"If checked, this plugin will only enable on the devices in the list", 68 | "hintDeviceSpecificSettingsDesc":"These options letting you control which device should enable this plugin on", 69 | "hintDeviceSpecificSettingsTitle":"
    ● Devices Specific Options", 70 | "onlyEnableListedDevices":"Only Enable on Listed Devices", 71 | "removeCurrentDeviceFromList":"Remove current device from the list", 72 | "removeCurrentDeviceFromListDesc":"When you click the button, the interface will be reloaded, and unsaved configurations will be lost, so please save other configuration items before clicking the button", 73 | "removeCurrentDeviceFromListLabel":"Remove", 74 | 75 | "hideContextualLabel": "Hide Contextual Label", 76 | "hideContextualLabelDesc": "Hide the hovering label of the current document in the document tree", 77 | 78 | "displayIconButDisableIconClickDesc":"Display icons but disable icon click", 79 | "displayIconButDisableIconClick":"Disable icon click", 80 | 81 | "disableDocumentButtonsPopup":"Disable document buttons popup", 82 | "disableDocumentButtonsPopupDesc":"Disable document buttons popup when mouse hovering", 83 | 84 | "enableDoctreeFrontLine": "Enable Doctree Front Line", 85 | "enableDoctreeFrontLineDesc": "Enable Doctree Front Line, you must turn off custom padding and flow layout to take effect", 86 | "doctreeFrontLinePosition": "Doctree Front Line Position", 87 | "doctreeFrontLinePositionDesc": "Define doctree Front Line Position, default is 20, unit: px", 88 | "doctreeFrontLinePadding": "Doctree Front Line Padding", 89 | "doctreeFrontLinePaddingDesc": "Define doctree Front Line Padding, default is 20, unit: px", 90 | "doctreeFrontLineBorder": "Doctree Front Line Border", 91 | "doctreeFrontLineBorderDesc": "Define doctree Front Line Border, default is 2, unit: px", 92 | "docTreeFrontLineImplememtation": "Doctree front line style", 93 | "docTreeFrontLineImplememtationDesc":"Define the doctree front line style", 94 | "enableDoctreeSeperateLine": "Enable Doctree Seperate Line", 95 | "enableDoctreeSeperateLineDesc": "Enable Doctree Seperate Line", 96 | "doctreeSeperateLineBorder": "Doctree Seperate Line Border", 97 | "doctreeSeperateLineBorderDesc": "Define doctree Seperate Line Border, default is 2, unit: px", 98 | "addNotebookOutline": "Notebook Outline", 99 | "addNotebookOutlineDesc": "Add notebook outline to the document tree.
    by https://github.com/TCOTC aka @Jeffrey Chen", 100 | "notebookOutlineMode": "Notebook Outline Mode", 101 | "notebookOutlineModeDesc": "", 102 | 103 | 104 | "beggingTitle": "🙏 Begging 🙏", 105 | "beggingDesc": "This plugin is open source and free, so I need your encouragement.
    If you like the features this plugin brings you, you can consider donating to help me with basic living expenses 🍚: Donate. (Donations won't unlock additional features)
    If you're not financially comfortable, you can encourage me by giving a free ⭐ star to my GitHub repository.
    If you or someone you know needs my skills, feel free to hire me anytime." 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/i18n/zh_CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "mainSwitch": "主开关", 3 | 4 | "enableAdjustStaticDoctreePadding": "启用文档树缩进压缩", 5 | "enableAdjustStaticDoctreePaddingDesc": "", 6 | "compressPercent": "压缩百分比", 7 | "compressPercentDesc": "文档树缩进压缩百分比 %", 8 | "hideIcon": "隐藏文档树中文档的图标", 9 | "hideIconDesc": "", 10 | "hideIconForce": "强制隐藏图标模式", 11 | "hideIconDescForce": "上面开关没有作用时再启用,且仅在上一个开关启用时有效", 12 | "hintTitle": "关于", 13 | "hintDesc": "
    • ● 由zxkmm制作, MIT 协议开源。
    • ● 如果您喜欢这个插件,请给我的 GitHub 仓库点亮免费的星星⭐(Star)。
    • ● 链接:https://github.com/zxkmm/siyuan_doctree_compress
    • ", 14 | "experimentFeatureHintDesc": "设置文档树的动态外观(可能和第三方主题不兼容)", 15 | "experimentFeatureHintTitle": "
      ● 动态文档树布局设置", 16 | "hintDangerousZoneTitle": "
      ● 静态文档树布局设置", 17 | "hintDangerousZoneDesc": "设置文档树的静态外观(可能和第三方主题不兼容)", 18 | "overloadFontSizeSwitch": "重载文档树字体大小", 19 | "overloadFontSizeSwitchDesc": "启用后可以用下面的设置来调节文档树字体大小", 20 | "overloadFontSizeForceSwitch": "强制重载字体模式", 21 | "overloadFontSizeForceSwitchDesc": "上面开关没有作用时再启用,且仅在上一个开关启用时有效", 22 | "overloadFontSizePx": "文档树字体大小", 23 | "overloadFontSizePxDesc": "需勾选上面的开关才能生效", 24 | "mouseHoverZeroPadding": "鼠标悬停时流动布局文档树项目", 25 | "mouseHoverZeroPaddingDesc": "方便点击和查看标题,不建议和本栏目其他功能一起启用
      详细阅读教程\n
      详细教程国内站点\n", 26 | "mouseHoverZeroPaddingForce": "强制流动布局模式", 27 | "mouseHoverZeroPaddingForceDesc": "默认启用,若主题冲突可关闭;且仅在上一个开关启用时有效", 28 | "mouseHoverZeroPaddingStyle": "流动布局风格", 29 | "mouseHoverZeroPaddingStyledesc": "", 30 | "mouseHoverZeroPaddingStylePaddingToggle":"流动布局所有元素", 31 | "mouseHoverZeroPaddingStylePaddingIcon":"不流动布局子文档展开按钮", 32 | "mouseHoverZeroPaddingStylePaddingText":"只流动布局文字", 33 | "mouseHoverZeroPaddingStylePaddingIconButMoveLR":"不流动布局子文档展开按钮,风格2", 34 | "mouseHoverZeroPaddingPx": "流动布局缩进值", 35 | "mouseHoverZeroPaddingPxDesc": "默认值是4,也是一级笔记本条目的默认缩进值,单位:像素", 36 | "mouseOverLineUnclampDesc": "方便点击和查看标题,不建议和本栏目其他功能一起启用", 37 | "mouseOverLineUnclampTitle": "鼠标悬停时自动换行文档树项目", 38 | "mouseOverLineUnclampForceDesc": "上面开关没有作用时再启用,且仅在上一个开关启用时有效", 39 | "mouseOverLineUnclampForceTitle": "强制自动换行模式", 40 | "mouseOverReduceFontSizeDesc": "方便查看标题,不建议和本栏目其他功能一起启用", 41 | "mouseOverReduceFontSizeTitle": "鼠标悬停时重载字体大小", 42 | "mouseOverReduceFontSizeForceDesc": "上面开关没有作用时再启用,且仅在上一个开关启用时有效", 43 | "mouseOverReduceFontSizeForceTitle": "强制重载字体大小模式", 44 | "mouseHoverReduceFontSizePx": "鼠标悬停时重载字体大小值", 45 | "mouseHoverReduceFontSizePxDesc": "默认值:14 单位: 像素", 46 | 47 | "overloadLineHeight": "重载行高", 48 | "overloadLineHeightDesc": "重载文档树项目行高", 49 | "overloadLineHeightForce": "强制重载行高模式", 50 | "overloadLineHeightForceDesc": "当上述开关无效时启用,且只有在前一个开关打开时才有效", 51 | "overloadLineHeightPx": "文档树行高", 52 | "overloadLineHeightPxDesc": "必须勾选上面的开关才能生效,默认值:28 单位:像素", 53 | 54 | "addCurrentDeviceIntoListLabel": "添加", 55 | "addCurrentDeviceIntoListDesc": "点击按钮时将重新加载界面,没有保存的配置将丢失,所以请在保存好其他配置项之后再点击按钮", 56 | "addCurrentDeviceIntoList": "将当前设备添加到列表中", 57 | "enableDeviceListDesc": "此列表用于控制该插件在哪些设备上启用,请尽量不要手动修改", 58 | "enableDeviceList": "启用的设备列表", 59 | "onlyEnableListedDevicesDesc": "如果选中,此插件只会在列表中的设备上启用", 60 | "hintDeviceSpecificSettingsDesc": "这些选项可让您控制在哪些设备上启用此插件", 61 | "hintDeviceSpecificSettingsTitle": "
      ● 设备特定选项", 62 | "onlyEnableListedDevices": "仅在列表中的设备上启用", 63 | "removeCurrentDeviceFromList": "从列表中移除当前设备", 64 | "removeCurrentDeviceFromListDesc": "点击按钮时将重新加载界面,没有保存的配置将丢失,所以请在保存好其他配置项之后再点击按钮", 65 | "removeCurrentDeviceFromListLabel": "移除", 66 | "hideContextualLabel": "隐藏文档详情标签", 67 | "hideContextualLabelDesc": "隐藏文档树中当前文档的悬停标签(创建时间等弹出式标签)", 68 | "displayIconButDisableIconClickDesc": "显示文档图标但禁用图标点击", 69 | "displayIconButDisableIconClick": "禁用文档图标点击", 70 | "disableDocumentButtonsPopup": "禁用文档按钮气泡", 71 | "disableDocumentButtonsPopupDesc": "禁用鼠标悬停时文档按钮弹出的气泡", 72 | 73 | "enableDoctreeFrontLine": "文档树层级指示竖线", 74 | "enableDoctreeFrontLineDesc": "启用文档树前线,必须关闭自定义缩进和流动布局才能生效", 75 | "doctreeFrontLinePosition": "文档树层级竖线位置", 76 | "doctreeFrontLinePositionDesc": "定义文档树层级竖线位置,默认为20,单位:像素", 77 | "doctreeFrontLinePadding": "文档树层级竖线缩进", 78 | "doctreeFrontLinePaddingDesc": "定义文档树层级竖线缩进,默认为20,单位:像素", 79 | "doctreeFrontLineBorder": "文档树层级指示竖线厚度", 80 | "doctreeFrontLineBorderDesc": "定义文档树层级指示竖线厚度,默认为2,单位:像素", 81 | "docTreeFrontLineImplememtation": "文档树层级指示竖线风格", 82 | "docTreeFrontLineImplememtationDesc":"定义文档属层级指示线的风格", 83 | "enableDoctreeSeperateLine": "文档项目树分隔线", 84 | "enableDoctreeSeperateLineDesc": "启用项目间分割线", 85 | "doctreeSeperateLineBorder": "文档树分隔线厚度", 86 | "doctreeSeperateLineBorderDesc": "定义文档树分隔线厚度,默认为2,单位:像素", 87 | "addNotebookOutline": "笔记本外框", 88 | "addNotebookOutlineDesc": "在笔记本和其覆盖范围内增加外框,方便区分笔记本
      此功能由 https://github.com/TCOTC 也就是 @Jeffrey Chen 设计", 89 | "notebookOutlineMode": "笔记本外框样式", 90 | "notebookOutlineModeDesc": "", 91 | 92 | "beggingTitle": "🙏乞讨🙏", 93 | "beggingDesc": "这款插件是开源免费的,因此我需要您的鼓励.
      如果您喜欢这款插件为您带来的功能,您可以考虑捐款给我,帮助我解决温饱🍚问题:捐款。(捐款不会解锁更多功能)
      如果您经济不宽裕,您可以考虑给我的GitHub仓库点一下免费的⭐星星鼓励我。
      如果您认为您或者您认识的人需要我的技能,欢迎随时雇用我。" 94 | } 95 | -------------------------------------------------------------------------------- /src/index.scss: -------------------------------------------------------------------------------- 1 | #helloPanel { 2 | border: 1px rgb(189, 119, 119) dashed; 3 | } 4 | 5 | .plugin-sample { 6 | &__custom-tab { 7 | background-color: var(--b3-theme-background); 8 | height: 100%; 9 | width: 100%; 10 | display: flex; 11 | justify-content: center; 12 | align-items: center; 13 | } 14 | 15 | &__custom-dock { 16 | display: flex; 17 | justify-content: center; 18 | align-items: center; 19 | } 20 | 21 | &__time { 22 | background: var(--b3-card-info-background); 23 | border-radius: 4px; 24 | padding: 2px 8px; 25 | } 26 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from "siyuan"; 2 | import "@/index.scss"; 3 | 4 | /* 5 | zxkmm naming style: 6 | _inFuncMember_ 7 | _funcArgument_ 8 | funcName 9 | privateClassMember_ 10 | _publicClassMember 11 | */ 12 | 13 | import { SettingUtils } from "./libs/setting-utils"; 14 | 15 | import { 16 | addFrontLine, 17 | mouseOverZeroPadding, 18 | overloadDoctreeFontSize, 19 | rmvDoctreeIcons, 20 | addNotebookOutline, 21 | addSeperateLine, 22 | overloadLineHeight, 23 | hideContextualLabel, 24 | mouseOverLineUnclamp, 25 | mouseOverReduceFontSize, 26 | disableDocumentButtonsPopup, 27 | displayIconButDisableIconClick, 28 | } from "./style_injection"; 29 | 30 | import { 31 | currentDeviceInList, 32 | removeCurrentDeviceFromList, 33 | appendCurrentDeviceIntoList, 34 | } from "./device_specific_helpers"; 35 | 36 | const STORAGE_NAME = "menu-config"; 37 | 38 | export default class SiyuanDoctreeCompress extends Plugin { 39 | private settingUtils: SettingUtils; 40 | 41 | async onload() { 42 | this.data[STORAGE_NAME] = { readonlyText: "Readonly" }; 43 | 44 | this.settingUtils = new SettingUtils(this, STORAGE_NAME); 45 | 46 | this.settingUtils.load(); 47 | 48 | this.settingUtils.addItem({ 49 | key: "begging", 50 | value: "", 51 | type: "hint", 52 | title: this.i18n.beggingTitle, 53 | description: this.i18n.beggingDesc, 54 | }); 55 | 56 | this.settingUtils.addItem({ 57 | key: "mainSwitch", 58 | value: false, 59 | type: "checkbox", 60 | title: this.i18n.mainSwitch, 61 | description: "", 62 | }); 63 | 64 | this.settingUtils.addItem({ 65 | //dynamic options 66 | key: "highPerformanceZoneHint", 67 | value: "", 68 | type: "hint", 69 | title: this.i18n.experimentFeatureHintTitle, 70 | description: this.i18n.experimentFeatureHintDesc, 71 | }); 72 | 73 | this.settingUtils.addItem({ 74 | key: "mouseHoverZeroPadding", 75 | value: false, 76 | type: "checkbox", 77 | title: "🌊 " + this.i18n.mouseHoverZeroPadding, 78 | description: this.i18n.mouseHoverZeroPaddingDesc, 79 | }); 80 | 81 | this.settingUtils.addItem({ 82 | key: "mouseHoverZeroPaddingForce", 83 | value: true, 84 | type: "checkbox", 85 | title: "🌊 " + this.i18n.mouseHoverZeroPaddingForce, 86 | description: this.i18n.mouseHoverZeroPaddingForceDesc, 87 | }); 88 | 89 | this.settingUtils.addItem({ 90 | key: "mouseHoverZeroPaddingStyle", 91 | value: 1, 92 | type: "select", 93 | title: "🌊 " + this.i18n.mouseHoverZeroPaddingStyle, 94 | description: this.i18n.mouseHoverZeroPaddingStyledesc, 95 | options: { 96 | 1: this.i18n.mouseHoverZeroPaddingStylePaddingToggle, 97 | 2: this.i18n.mouseHoverZeroPaddingStylePaddingIcon, 98 | 3: this.i18n.mouseHoverZeroPaddingStylePaddingIconButMoveLR, 99 | 4: this.i18n.mouseHoverZeroPaddingStylePaddingText, 100 | }, 101 | }); 102 | 103 | this.settingUtils.addItem({ 104 | key: "mouseHoverZeroPaddingPx", 105 | value: 4, 106 | type: "slider", 107 | title: "🌊 " + this.i18n.mouseHoverZeroPaddingPx, 108 | description: this.i18n.mouseHoverZeroPaddingPxDesc, 109 | slider: { 110 | min: 0, 111 | max: 10, 112 | step: 1, 113 | }, 114 | }); 115 | 116 | this.settingUtils.addItem({ 117 | key: "mouseOverLineUnclamp", 118 | value: false, 119 | type: "checkbox", 120 | title: "🟰 " + this.i18n.mouseOverLineUnclampTitle, 121 | description: this.i18n.mouseOverLineUnclampDesc, 122 | }); 123 | 124 | this.settingUtils.addItem({ 125 | key: "mouseOverLineUnclampForce", 126 | value: false, 127 | type: "checkbox", 128 | title: "🟰 " + this.i18n.mouseOverLineUnclampForceTitle, 129 | description: this.i18n.mouseOverLineUnclampForceDesc, 130 | }); 131 | 132 | this.settingUtils.addItem({ 133 | key: "mouseOverReduceFontSize", 134 | value: false, 135 | type: "checkbox", 136 | title: "🔡 " + this.i18n.mouseOverReduceFontSizeTitle, 137 | description: this.i18n.mouseOverReduceFontSizeDesc, 138 | }); 139 | 140 | this.settingUtils.addItem({ 141 | key: "mouseOverReduceFontSizeForce", 142 | value: false, 143 | type: "checkbox", 144 | title: "🔡 " + this.i18n.mouseOverReduceFontSizeForceTitle, 145 | description: this.i18n.mouseOverReduceFontSizeForceDesc, 146 | }); 147 | 148 | this.settingUtils.addItem({ 149 | key: "mouseHoverReduceFontSizePx", 150 | value: 4, 151 | type: "slider", 152 | title: "🔡 " + this.i18n.mouseHoverReduceFontSizePx, 153 | description: this.i18n.mouseHoverReduceFontSizePxDesc, 154 | slider: { 155 | min: 1, 156 | max: 50, 157 | step: 1, 158 | }, 159 | }); 160 | 161 | this.settingUtils.addItem({ 162 | key: "disable document buttons popup", 163 | value: false, 164 | type: "checkbox", 165 | title: "💬 " + this.i18n.disableDocumentButtonsPopup, 166 | description: this.i18n.disableDocumentButtonsPopupDesc, 167 | }); 168 | 169 | this.settingUtils.addItem({ 170 | key: "hideContextualLabel", 171 | value: false, 172 | type: "checkbox", 173 | title: "🖃 " + this.i18n.hideContextualLabel, 174 | description: this.i18n.hideContextualLabelDesc, 175 | }); 176 | 177 | this.settingUtils.addItem({ 178 | //static options 179 | key: "hintDangerousZone", 180 | value: "", 181 | type: "hint", 182 | title: this.i18n.hintDangerousZoneTitle, 183 | description: this.i18n.hintDangerousZoneDesc, 184 | }); 185 | 186 | this.settingUtils.addItem({ 187 | key: "enableAdjustStaticDoctreePadding", 188 | value: false, 189 | type: "checkbox", 190 | title: "🗜️ " + this.i18n.enableAdjustStaticDoctreePadding, 191 | description: this.i18n.enableAdjustStaticDoctreePaddingDesc, 192 | }); 193 | 194 | this.settingUtils.addItem({ 195 | key: "Slider", 196 | value: 50, 197 | type: "slider", 198 | title: "🗜️ " + this.i18n.compressPercent, 199 | description: this.i18n.compressPercentDesc, 200 | slider: { 201 | min: 0, 202 | max: 100, 203 | step: 5, 204 | }, 205 | }); 206 | 207 | this.settingUtils.addItem({ 208 | key: "enableDoctreeFrontLine", 209 | value: false, 210 | type: "checkbox", 211 | title: "⛕ " + this.i18n.enableDoctreeFrontLine, 212 | description: this.i18n.enableDoctreeFrontLineDesc, 213 | }); 214 | 215 | this.settingUtils.addItem({ 216 | key: "doctreeFrontLinePosition", 217 | value: 20, 218 | type: "slider", 219 | title: "⛕ " + this.i18n.doctreeFrontLinePosition, 220 | description: this.i18n.doctreeFrontLinePositionDesc, 221 | slider: { 222 | min: 0, 223 | max: 60, 224 | step: 1, 225 | }, 226 | }); 227 | 228 | this.settingUtils.addItem({ 229 | key: "doctreeFrontLinePadding", 230 | value: 20, 231 | type: "slider", 232 | title: "⛕ " + this.i18n.doctreeFrontLinePadding, 233 | description: this.i18n.doctreeFrontLinePaddingDesc, 234 | slider: { 235 | min: 6, 236 | max: 60, 237 | step: 1, 238 | }, 239 | }); 240 | 241 | this.settingUtils.addItem({ 242 | key: "doctreeFrontLineBorder", 243 | value: 2, 244 | type: "slider", 245 | title: "⛕ " + this.i18n.doctreeFrontLineBorder, 246 | description: this.i18n.doctreeFrontLineBorderDesc, 247 | slider: { 248 | min: 1, 249 | max: 20, 250 | step: 1, 251 | }, 252 | }); 253 | 254 | this.settingUtils.addItem({ 255 | key: "docTreeFrontLineImplememtation", 256 | value: 1, 257 | type: "select", 258 | title: this.i18n.docTreeFrontLineImplememtation, 259 | description: this.i18n.docTreeFrontLineImplememtationDesc, 260 | options: { 261 | 1: "1", 262 | 2: "2", 263 | 3: "3", 264 | 4: "4", 265 | }, 266 | }); 267 | 268 | this.settingUtils.addItem({ 269 | key: "enableDoctreeSeperateLine", 270 | value: false, 271 | type: "checkbox", 272 | title: "➖ " + this.i18n.enableDoctreeSeperateLine, 273 | description: this.i18n.enableDoctreeSeperateLineDesc, 274 | }); 275 | 276 | this.settingUtils.addItem({ 277 | key: "doctreeSeperateLineBorder", 278 | value: 2, 279 | type: "slider", 280 | title: "➖ " + this.i18n.doctreeSeperateLineBorder, 281 | description: this.i18n.doctreeSeperateLineBorderDesc, 282 | slider: { 283 | min: 1, 284 | max: 20, 285 | step: 1, 286 | }, 287 | }); 288 | 289 | this.settingUtils.addItem({ 290 | key: "hideIcon", 291 | value: false, 292 | type: "checkbox", 293 | title: "🧩 " + this.i18n.hideIcon, 294 | description: this.i18n.hideIconDesc, 295 | }), 296 | this.settingUtils.addItem({ 297 | key: "hideIconForce", 298 | value: false, 299 | type: "checkbox", 300 | title: "🧩 " + this.i18n.hideIconForce, 301 | description: this.i18n.hideIconDescForce, 302 | }), 303 | this.settingUtils.addItem({ 304 | key: "displayIconButDisableIconClick", 305 | value: false, 306 | type: "checkbox", 307 | title: "🖱️ " + this.i18n.displayIconButDisableIconClick, 308 | description: this.i18n.displayIconButDisableIconClickDesc, 309 | }); 310 | 311 | this.settingUtils.addItem({ 312 | key: "overloadFontSizeSwitch", 313 | value: false, 314 | type: "checkbox", 315 | title: "🇦 " + this.i18n.overloadFontSizeSwitch, 316 | description: this.i18n.overloadFontSizeSwitchDesc, 317 | }), 318 | this.settingUtils.addItem({ 319 | key: "overloadFontSizeForceSwitch", 320 | value: false, 321 | type: "checkbox", 322 | title: "🇦 " + this.i18n.overloadFontSizeForceSwitch, 323 | description: this.i18n.overloadFontSizeForceSwitchDesc, 324 | }), 325 | this.settingUtils.addItem({ 326 | key: "overloadFontSizePx", 327 | value: 14, 328 | type: "slider", 329 | title: "🇦 " + this.i18n.overloadFontSizePx, 330 | description: this.i18n.overloadFontSizePxDesc, 331 | slider: { 332 | min: 5, 333 | max: 60, 334 | step: 1, 335 | }, 336 | }); 337 | 338 | this.settingUtils.addItem({ 339 | key: "overloadLineHeight", 340 | value: false, 341 | type: "checkbox", 342 | title: "🛅 " + this.i18n.overloadLineHeight, 343 | description: this.i18n.overloadLineHeightDesc, 344 | }); 345 | 346 | this.settingUtils.addItem({ 347 | key: "overloadLineHeightForce", 348 | value: false, 349 | type: "checkbox", 350 | title: "🛅 " + this.i18n.overloadLineHeightForce, 351 | description: this.i18n.overloadLineHeightForceDesc, 352 | }); 353 | 354 | this.settingUtils.addItem({ 355 | key: "overloadLineHeightPx", 356 | value: 28, 357 | type: "slider", 358 | title: "🛅 " + this.i18n.overloadLineHeightPx, 359 | description: this.i18n.overloadLineHeightPxDesc, 360 | slider: { 361 | min: 1, 362 | max: 100, 363 | step: 1, 364 | }, 365 | }); 366 | 367 | this.settingUtils.addItem({ 368 | //by https://github.com/TCOTC aka @Jeffrey Chen 369 | key: "addNotebookOutline", 370 | value: false, 371 | type: "checkbox", 372 | title: "🖼️ " + this.i18n.addNotebookOutline, 373 | description: this.i18n.addNotebookOutlineDesc, 374 | }); 375 | 376 | // this.settingUtils.addItem({ //moved to notebookOutlineMode 377 | // key: "notebookOutlineTightMode", 378 | // value: false, 379 | // type: "checkbox", 380 | // title: "🖼️ " + this.i18n.notebookOutlineTightMode, 381 | // description: this.i18n.notebookOutlineTightModeDesc, 382 | // }); 383 | 384 | this.settingUtils.addItem({ 385 | key: "notebookOutlineMode", 386 | value: 1, 387 | type: "select", 388 | title: "🖼️ " + this.i18n.notebookOutlineMode, 389 | description: this.i18n.notebookOutlineModeDesc, 390 | options: { 391 | 1: "normal", 392 | 2: "tight", 393 | 3: "high contrast AKA TCOTC style", 394 | }, 395 | }); 396 | 397 | this.settingUtils.addItem({ 398 | key: "hintDeviceSpecificSettings", 399 | value: "", 400 | type: "hint", 401 | title: this.i18n.hintDeviceSpecificSettingsTitle, 402 | description: this.i18n.hintDeviceSpecificSettingsDesc, 403 | }); 404 | 405 | this.settingUtils.addItem({ 406 | key: "onlyEnableListedDevices", 407 | value: false, 408 | type: "checkbox", 409 | title: this.i18n.onlyEnableListedDevices, 410 | description: this.i18n.onlyEnableListedDevicesDesc, 411 | }); 412 | 413 | this.settingUtils.addItem({ 414 | key: "enableDeviceList", 415 | value: "", 416 | type: "textarea", 417 | title: this.i18n.enableDeviceList, 418 | description: this.i18n.enableDeviceListDesc, 419 | }); 420 | 421 | this.settingUtils.addItem({ 422 | key: "addCurrentDeviceIntoList", 423 | value: "", 424 | type: "button", 425 | title: this.i18n.addCurrentDeviceIntoList, 426 | description: this.i18n.addCurrentDeviceIntoListDesc, 427 | button: { 428 | label: this.i18n.addCurrentDeviceIntoListLabel, 429 | callback: () => { 430 | appendCurrentDeviceIntoList(this.settingUtils); 431 | }, 432 | }, 433 | }); 434 | 435 | this.settingUtils.addItem({ 436 | key: "removeCurrentDeviceFromList", 437 | value: "", 438 | type: "button", 439 | title: this.i18n.removeCurrentDeviceFromList, 440 | description: this.i18n.removeCurrentDeviceFromListDesc, 441 | button: { 442 | label: this.i18n.removeCurrentDeviceFromListLabel, 443 | callback: () => { 444 | removeCurrentDeviceFromList(this.settingUtils); 445 | }, 446 | }, 447 | }); 448 | 449 | this.settingUtils.addItem({ 450 | key: "hint", 451 | value: "", 452 | type: "hint", 453 | title: this.i18n.hintTitle, 454 | description: this.i18n.hintDesc, 455 | }); 456 | } 457 | 458 | onLayoutReady() { 459 | this.loadData(STORAGE_NAME); 460 | this.settingUtils.load(); 461 | 462 | const layoutReadyAsyncHandler = async () => { 463 | //async!!!!!!! 464 | try { 465 | const _mouseoverZeroPadding_ = this.settingUtils.get( 466 | "mouseHoverZeroPadding" 467 | ); 468 | const _mainSwitchStat_ = this.settingUtils.get("mainSwitch"); 469 | const _hideIcon_ = this.settingUtils.get("hideIcon"); 470 | const _hideIconForceSwitch_ = this.settingUtils.get("hideIconForce"); 471 | const _enableAdjustStaticDoctreePadding_ = this.settingUtils.get( 472 | "enableAdjustStaticDoctreePadding" 473 | ); 474 | const _compressionPercentage_ = this.settingUtils.get("Slider"); 475 | const _overloadFontSizeSwitch_ = this.settingUtils.get( 476 | "overloadFontSizeSwitch" 477 | ); 478 | const _overloadFontSizeForceSwitch_ = this.settingUtils.get( 479 | "overloadFontSizeForceSwitch" 480 | ); 481 | const _overloadFontSizePx_ = 482 | this.settingUtils.get("overloadFontSizePx"); 483 | const _mouseHoverZeroPaddingForce_ = this.settingUtils.get( 484 | "mouseHoverZeroPaddingForce" 485 | ); 486 | const _mouseHoverZeroPaddingStyle_ = this.settingUtils.get( 487 | "mouseHoverZeroPaddingStyle" 488 | ); 489 | const _mouseHoverZeroPaddingPx_ = this.settingUtils.get( 490 | "mouseHoverZeroPaddingPx" 491 | ); 492 | const _mouseOverLineUnclamp_ = this.settingUtils.get( 493 | "mouseOverLineUnclamp" 494 | ); 495 | const _mouseOverLineUnclampForce_ = this.settingUtils.get( 496 | "mouseOverLineUnclampForce" 497 | ); 498 | const _mouseOverReduceFontSize_ = this.settingUtils.get( 499 | "mouseOverReduceFontSize" 500 | ); 501 | const _mouseOverReduceFontSizeForce_ = this.settingUtils.get( 502 | "mouseOverLineUnclampForce" 503 | ); 504 | const _mouseHoverReduceFontSizePx_ = this.settingUtils.get( 505 | "mouseHoverReduceFontSizePx" 506 | ); 507 | const _onlyEnableListedDevices_ = this.settingUtils.get( 508 | "onlyEnableListedDevices" 509 | ); 510 | const _currentDeviceInList_ = await currentDeviceInList( 511 | this.settingUtils 512 | ); 513 | const _hideContextualLabel_ = this.settingUtils.get( 514 | "hideContextualLabel" 515 | ); 516 | const _displayIconButDIsableIconClick_ = this.settingUtils.get( 517 | "displayIconButDisableIconClick" 518 | ); 519 | const _disableDocumentButtonsPopup_ = this.settingUtils.get( 520 | "disable document buttons popup" 521 | ); 522 | const _overloadLineHeight_ = 523 | this.settingUtils.get("overloadLineHeight"); 524 | const _overloadLineHeightForce_ = this.settingUtils.get( 525 | "overloadLineHeightForce" 526 | ); 527 | const _overloadLineHeightPx_ = this.settingUtils.get( 528 | "overloadLineHeightPx" 529 | ); 530 | const _enableDoctreeFrontLine_ = this.settingUtils.get( 531 | "enableDoctreeFrontLine" 532 | ); 533 | const _doctreeFrontLinePosition_ = this.settingUtils.get( 534 | "doctreeFrontLinePosition" 535 | ); 536 | const _doctreeFrontLinePadding_ = this.settingUtils.get( 537 | "doctreeFrontLinePadding" 538 | ); 539 | const _doctreeFrontLineBorder_ = this.settingUtils.get( 540 | "doctreeFrontLineBorder" 541 | ); 542 | const _doctreeFrontLineImplememtation_ = this.settingUtils.get( 543 | "docTreeFrontLineImplememtation" 544 | ); 545 | const _enableDoctreeSeperateLine_ = this.settingUtils.get( 546 | "enableDoctreeSeperateLine" 547 | ); 548 | const _doctreeSeperateLineBorder_ = this.settingUtils.get( 549 | "doctreeSeperateLineBorder" 550 | ); 551 | const _addNotebookOutline_ = 552 | this.settingUtils.get("addNotebookOutline"); 553 | // const _notebookOutlineTightMode_ = this.settingUtils.get("notebookOutlineTightMode"); //moved to notebookOutlineMode 554 | const _notebookOutlinemode_ = this.settingUtils.get( 555 | "notebookOutlineMode" 556 | ); 557 | 558 | // console.log({ 559 | // mouseoverZeroPadding: _mouseoverZeroPadding_, 560 | // mainSwitchStat: _mainSwitchStat_, 561 | // hideIcon: _hideIcon_, 562 | // compressionPercentage: _compressionPercentage_, 563 | // overloadFontSizeSwitch: _overloadFontSizeSwitch_, 564 | // mouseHoverZeroPaddingForce: _mouseHoverZeroPaddingForce_, 565 | // mouseHoverZeroPaddingPx: _mouseHoverZeroPaddingPx_, 566 | // mouseOverLineUnclamp: _mouseOverLineUnclamp_, 567 | // mouseOverLineUnclampForce: _mouseOverLineUnclampForce_, 568 | // mouseOverReduceFontSize: _mouseOverReduceFontSize_, 569 | // mouseOverReduceFontSizeForce: _mouseOverReduceFontSizeForce_, 570 | // mouseHoverReduceFontSizePx: _mouseHoverReduceFontSizePx_, 571 | // onlyEnableListedDevices: _onlyEnableListedDevices_, 572 | // currentDeviceInList: _currentDeviceInList_, 573 | // hideContextualLabel: _hideContextualLabel_, 574 | // displayIconButDIsableIconClick: _displayIconButDIsableIconClick_, 575 | // disableDocumentButtonsPopup: _disableDocumentButtonsPopup_, 576 | // overloadLineHeight: _overloadLineHeight_, 577 | // overloadLineHeightForce: _overloadLineHeightForce_, 578 | // overloadLineHeightPx: _overloadLineHeightPx_, 579 | // enableDoctreeFrontLine: _enableDoctreeFrontLine_, 580 | // doctreeFrontLinePosition: _doctreeFrontLinePosition_, 581 | // doctreeFrontLinePadding: _doctreeFrontLinePadding_, 582 | // doctreeFrontLineBorder: _doctreeFrontLineBorder_, 583 | // enableDoctreeSeperateLine: _enableDoctreeSeperateLine_, 584 | // doctreeSeperateLineBorder: _doctreeSeperateLineBorder_, 585 | // addNotebookOutline: _addNotebookOutline_, 586 | // notebookOutlineTightMode: _notebookOutlineTightMode_, 587 | // }); 588 | 589 | /*条件列表: 590 | 当前设备真, 仅允许开关开,后半段为假 :真||假: 执行 591 | 当前设备真, 仅允许开关关,后半段为真 :真||真: 执行 592 | 当前设备假, 仅允许开关开,后半段为假 :假||假: 不执行 593 | 当前设备假, 仅允许开关关,后半段为真 :假||真: 执行 594 | */ 595 | 596 | if ( 597 | (_currentDeviceInList_ || !_onlyEnableListedDevices_) && 598 | _mainSwitchStat_ 599 | ) { 600 | //main swtich and per deivce condition selecter 601 | 602 | if (_overloadLineHeight_) { 603 | //overload line height sel 604 | overloadLineHeight( 605 | _overloadLineHeightForce_, 606 | _overloadLineHeightPx_ 607 | ); 608 | } 609 | 610 | if (_hideIcon_) { 611 | //hide icon sel 612 | rmvDoctreeIcons(_hideIconForceSwitch_); 613 | } 614 | 615 | if (_hideContextualLabel_) { 616 | //hide contextual label sel 617 | hideContextualLabel(); 618 | } 619 | 620 | if (_mouseoverZeroPadding_) { 621 | //TODO: 希望能更优雅一些。。。 622 | 623 | mouseOverZeroPadding( 624 | _mouseHoverZeroPaddingForce_, 625 | _mouseHoverZeroPaddingPx_, 626 | _mouseHoverZeroPaddingStyle_ 627 | ); 628 | } 629 | 630 | if (_mouseOverLineUnclamp_) { 631 | mouseOverLineUnclamp(_mouseOverLineUnclampForce_); 632 | } 633 | 634 | if (_mouseOverReduceFontSize_) { 635 | //mouse hover reduce font size sel 636 | 637 | mouseOverReduceFontSize( 638 | _mouseOverReduceFontSizeForce_, 639 | _mouseHoverReduceFontSizePx_ 640 | ); 641 | } 642 | 643 | //static options 644 | 645 | if (_overloadFontSizeSwitch_) { 646 | //overload font size sel 647 | overloadDoctreeFontSize( 648 | _overloadFontSizeForceSwitch_, 649 | _overloadFontSizePx_ 650 | ); 651 | } 652 | 653 | if (_displayIconButDIsableIconClick_) { 654 | // display icon but disable icon click sel 655 | displayIconButDisableIconClick(); 656 | } 657 | 658 | if (_disableDocumentButtonsPopup_) { 659 | disableDocumentButtonsPopup(); 660 | } 661 | 662 | if ( 663 | _enableDoctreeFrontLine_ && 664 | !_mouseoverZeroPadding_ && 665 | !_enableAdjustStaticDoctreePadding_ 666 | ) { 667 | addFrontLine( 668 | _doctreeFrontLineImplememtation_, 669 | _doctreeFrontLinePosition_, 670 | _doctreeFrontLinePadding_, 671 | _doctreeFrontLineBorder_ 672 | ); 673 | } 674 | 675 | if (_enableDoctreeSeperateLine_) { 676 | addSeperateLine(_doctreeSeperateLineBorder_); 677 | } 678 | 679 | if (_addNotebookOutline_) { 680 | addNotebookOutline(_notebookOutlinemode_); 681 | } 682 | 683 | if (!_mouseoverZeroPadding_ && _enableAdjustStaticDoctreePadding_) { 684 | //主开关打开 && 鼠标悬停零缩进关闭 && 分别缩进开关启用 685 | 686 | const doctreeObserver = new MutationObserver((mutations) => { 687 | handleDomChanges(); 688 | }); 689 | 690 | const config = { attributes: true, childList: true, subtree: true }; 691 | 692 | // doctreeBbserver.observe(document, config); 693 | 694 | document.querySelectorAll(".fn__flex-column").forEach((element) => { 695 | doctreeObserver.observe(element, config); 696 | }); 697 | // 698 | 699 | function handleDomChanges() { 700 | const _elements_ = document.querySelectorAll(".b3-list-item"); 701 | 702 | _elements_.forEach((element) => { 703 | const _toggleElement_ = element.querySelector( 704 | ".b3-list-item__toggle" 705 | ); 706 | if (_toggleElement_) { 707 | // Check if the element exists 708 | const _isCompressed_ = 709 | _toggleElement_.getAttribute("data-compressed"); 710 | 711 | if (!_isCompressed_) { 712 | const _originalPadding_ = parseFloat( 713 | window.getComputedStyle(_toggleElement_).paddingLeft 714 | ); 715 | const _compressedPadding_ = 716 | _originalPadding_ * (1 - _compressionPercentage_ / 100); 717 | 718 | if ( 719 | element.getAttribute("data-type") != "navigation-root" 720 | ) { 721 | //prevent compress notebook 722 | _toggleElement_.style.paddingLeft = `${_compressedPadding_}px`; 723 | _toggleElement_.setAttribute("data-compressed", "true"); //mark as compressed prevent nested compression 724 | } 725 | } 726 | } 727 | }); 728 | } 729 | } 730 | } 731 | } catch (error) { 732 | console.error( 733 | "siyuan_doctree_compress: failed inject interface", 734 | error 735 | ); 736 | } 737 | }; 738 | 739 | layoutReadyAsyncHandler(); 740 | } 741 | 742 | async onunload() { 743 | // await this.settingUtils.save(); 744 | // window.location.reload(); 745 | } 746 | } 747 | -------------------------------------------------------------------------------- /src/libs/b3-typography.svelte: -------------------------------------------------------------------------------- 1 |
      2 | 3 |
      -------------------------------------------------------------------------------- /src/libs/index.d.ts: -------------------------------------------------------------------------------- 1 | type TSettingItemType = "checkbox" | "select" | "textinput" | "textarea" | "number" | "slider" | "button" | "hint"; 2 | interface ISettingItem { 3 | key: string; 4 | value: any; 5 | type: TSettingItemType; 6 | title: string; 7 | description?: string; 8 | placeholder?: string; 9 | slider?: { 10 | min: number; 11 | max: number; 12 | step: number; 13 | }; 14 | options?: { [key: string | number]: string }; 15 | button?: { 16 | label: string; 17 | callback: () => void; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/libs/setting-item.svelte: -------------------------------------------------------------------------------- 1 | 28 | 29 | 101 | -------------------------------------------------------------------------------- /src/libs/setting-panel.svelte: -------------------------------------------------------------------------------- 1 | 9 | 29 | 30 |
      31 | 32 | {#each settingItems as item (item.key)} 33 | 45 | {/each} 46 |
      -------------------------------------------------------------------------------- /src/libs/setting-utils.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 by frostime. All Rights Reserved. 3 | * @Author : frostime 4 | * @Date : 2023-09-16 18:05:00 5 | * @FilePath : /src/libs/setting-utils.ts 6 | * @LastEditTime : 2023-12-28 18:10:12 7 | * @Description : A utility for siyuan plugin settings 8 | */ 9 | 10 | import { Plugin, Setting } from 'siyuan'; 11 | 12 | export class SettingUtils { 13 | plugin: Plugin; 14 | name: string; 15 | file: string; 16 | 17 | settings: Map = new Map(); 18 | elements: Map = new Map(); 19 | 20 | constructor(plugin: Plugin, name?: string, callback?: (data: any) => void, width?: string, height?: string) { 21 | this.name = name ?? 'settings'; 22 | this.plugin = plugin; 23 | this.file = this.name.endsWith('.json') ? this.name : `${this.name}.json`; 24 | this.plugin.setting = new Setting({ 25 | width: width, 26 | height: height, 27 | confirmCallback: () => { 28 | for (let key of this.settings.keys()) { 29 | this.updateValue(key); 30 | } 31 | let data = this.dump(); 32 | if (callback !== undefined) { 33 | callback(data); 34 | } else { 35 | this.plugin.data[this.name] = data; 36 | this.save(); 37 | } 38 | window.location.reload(); 39 | 40 | } 41 | }); 42 | } 43 | 44 | async load() { 45 | let data = await this.plugin.loadData(this.file); 46 | console.debug('Load config:', data); 47 | if (data) { 48 | for (let [key, item] of this.settings) { 49 | item.value = data?.[key] ?? item.value; 50 | } 51 | } 52 | this.plugin.data[this.name] = this.dump(); 53 | return data; 54 | } 55 | 56 | async save() { 57 | let data = this.dump(); 58 | await this.plugin.saveData(this.file, this.dump()); 59 | return data; 60 | } 61 | 62 | /** 63 | * Get setting item value 64 | * @param key key name 65 | * @returns setting item value 66 | */ 67 | get(key: string) { 68 | return this.settings.get(key)?.value; 69 | } 70 | 71 | async assignValue(_key_: string, _value_: any) { 72 | let item = this.settings.get(_key_); 73 | item.value = _value_; 74 | this.plugin.data[this.name] = item.value; 75 | await this.save(); 76 | window.location.reload(); 77 | } 78 | 79 | /** 80 | * 将设置项目导出为 JSON 对象 81 | * @returns object 82 | */ 83 | dump(): Object { 84 | let data: any = {}; 85 | for (let [key, item] of this.settings) { 86 | if (item.type === 'button') continue; 87 | data[key] = item.value; 88 | } 89 | return data; 90 | } 91 | 92 | addItem(item: ISettingItem) { 93 | this.settings.set(item.key, item); 94 | let itemElement: HTMLElement; 95 | switch (item.type) { 96 | case 'checkbox': 97 | let element: HTMLInputElement = document.createElement('input'); 98 | element.type = 'checkbox'; 99 | element.checked = item.value; 100 | element.className = "b3-switch fn__flex-center"; 101 | itemElement = element; 102 | break; 103 | case 'select': 104 | let selectElement: HTMLSelectElement = document.createElement('select'); 105 | selectElement.className = "b3-select fn__flex-center fn__size200"; 106 | let options = item?.options ?? {}; 107 | for (let val in options) { 108 | let optionElement = document.createElement('option'); 109 | let text = options[val]; 110 | optionElement.value = val; 111 | optionElement.text = text; 112 | selectElement.appendChild(optionElement); 113 | } 114 | selectElement.value = item.value; 115 | itemElement = selectElement; 116 | break; 117 | case 'slider': 118 | let sliderElement: HTMLInputElement = document.createElement('input'); 119 | sliderElement.type = 'range'; 120 | sliderElement.className = 'b3-slider fn__size200 b3-tooltips b3-tooltips__n'; 121 | sliderElement.ariaLabel = item.value; 122 | sliderElement.min = item.slider?.min.toString() ?? '0'; 123 | sliderElement.max = item.slider?.max.toString() ?? '100'; 124 | sliderElement.step = item.slider?.step.toString() ?? '1'; 125 | sliderElement.value = item.value; 126 | sliderElement.onchange = () => { 127 | sliderElement.ariaLabel = sliderElement.value; 128 | } 129 | itemElement = sliderElement; 130 | break; 131 | case 'textinput': 132 | let textInputElement: HTMLInputElement = document.createElement('input'); 133 | textInputElement.className = 'b3-text-field fn__flex-center fn__size200'; 134 | textInputElement.value = item.value; 135 | itemElement = textInputElement; 136 | break; 137 | case 'textarea': 138 | let textareaElement: HTMLTextAreaElement = document.createElement('textarea'); 139 | textareaElement.className = "b3-text-field fn__block"; 140 | textareaElement.value = item.value; 141 | itemElement = textareaElement; 142 | break; 143 | case 'number': 144 | let numberElement: HTMLInputElement = document.createElement('input'); 145 | numberElement.type = 'number'; 146 | numberElement.className = 'b3-text-field fn__flex-center fn__size200'; 147 | numberElement.value = item.value; 148 | itemElement = numberElement; 149 | break; 150 | case 'button': 151 | let buttonElement: HTMLButtonElement = document.createElement('button'); 152 | buttonElement.className = "b3-button b3-button--outline fn__flex-center fn__size200"; 153 | buttonElement.innerText = item.button?.label ?? 'Button'; 154 | buttonElement.onclick = item.button?.callback ?? (() => {}); 155 | itemElement = buttonElement; 156 | break; 157 | case 'hint': 158 | let hintElement: HTMLElement = document.createElement('div'); 159 | hintElement.className = 'b3-label fn__flex-center'; 160 | itemElement = hintElement; 161 | break; 162 | } 163 | this.elements.set(item.key, itemElement); 164 | this.plugin.setting.addItem({ 165 | title: item.title, 166 | description: item?.description, 167 | createActionElement: () => { 168 | let element = this.getElement(item.key); 169 | return element; 170 | } 171 | }) 172 | } 173 | 174 | 175 | 176 | private getElement(key: string) { 177 | let item = this.settings.get(key); 178 | let element = this.elements.get(key) as any; 179 | switch (item.type) { 180 | case 'checkbox': 181 | element.checked = item.value; 182 | break; 183 | case 'select': 184 | element.value = item.value; 185 | break; 186 | case 'slider': 187 | element.value = item.value; 188 | element.ariaLabel = item.value; 189 | break; 190 | case 'textinput': 191 | element.value = item.value; 192 | break; 193 | case 'textarea': 194 | element.value = item.value; 195 | break; 196 | } 197 | return element; 198 | } 199 | 200 | private updateValue(key: string) { 201 | let item = this.settings.get(key); 202 | let element = this.elements.get(key) as any; 203 | // console.debug(element, element?.value); 204 | switch (item.type) { 205 | case 'checkbox': 206 | item.value = element.checked; 207 | break; 208 | case 'select': 209 | item.value = element.value; 210 | break; 211 | case 'slider': 212 | item.value = element.value; 213 | break; 214 | case 'textinput': 215 | item.value = element.value; 216 | break; 217 | case 'textarea': 218 | item.value = element.value; 219 | break; 220 | } 221 | } 222 | 223 | } -------------------------------------------------------------------------------- /src/setting-example.svelte: -------------------------------------------------------------------------------- 1 | 62 | 63 |
      64 |
        65 | {#each groups as group} 66 |
      • { 71 | focusGroup = group; 72 | }} 73 | on:keydown={() => {}} 74 | > 75 | {group} 76 |
      • 77 | {/each} 78 |
      79 |
      80 | 86 |
      87 | 💡 This is our default settings. 88 |
      89 |
      90 |
      91 |
      92 | 93 | 101 | 102 | -------------------------------------------------------------------------------- /src/style_injection.ts: -------------------------------------------------------------------------------- 1 | /******** simple css injections **********/ 2 | export function disableDocumentButtonsPopup() { 3 | const css = ` 4 | .b3-list-item__icon.b3-tooltips.b3-tooltips__n:hover::after, 5 | .b3-list-item__action.b3-tooltips.b3-tooltips__nw:hover::after, 6 | .popover__block.b3-tooltips.b3-tooltips__nw:hover::after { 7 | display: none; 8 | } 9 | `; 10 | applyStyles(css); 11 | } 12 | 13 | export function displayIconButDisableIconClick() { 14 | const css = ` 15 | .b3-list-item__icon.b3-tooltips.b3-tooltips__n[aria-label="修改图标"], 16 | .b3-list-item__icon.b3-tooltips.b3-tooltips__n[aria-label="Change icon"] { 17 | pointer-events: none; 18 | } 19 | `; 20 | applyStyles(css); 21 | } 22 | 23 | export function mouseOverReduceFontSize(_force_, _px_) { 24 | const css = _force_ 25 | ? ` 26 | .layout-tab-container .b3-list-item__text { 27 | transition: font-size 0.2s ease; 28 | } 29 | .layout-tab-container .b3-list-item:hover > .b3-list-item__text { 30 | font-size: ${_px_}px !important; 31 | } 32 | ` 33 | : ` 34 | .layout-tab-container .b3-list-item__text { 35 | transition: font-size 0.2s ease; 36 | } 37 | .layout-tab-container .b3-list-item:hover > .b3-list-item__text { 38 | font-size: ${_px_}px; 39 | }`; 40 | applyStyles(css); 41 | } 42 | 43 | export function mouseOverLineUnclamp(_force_) { 44 | const css = _force_ 45 | ? ` 46 | .layout-tab-container .b3-list-item:hover > .b3-list-item__text { 47 | overflow:visible !important; 48 | -webkit-line-clamp: unset; 49 | } 50 | ` 51 | : ` 52 | .layout-tab-container .b3-list-item:hover > .b3-list-item__text { 53 | overflow:visible; 54 | -webkit-line-clamp: unset; 55 | }`; 56 | 57 | applyStyles(css); 58 | } 59 | 60 | export function hideContextualLabel() { 61 | const css = ` 62 | .fn__flex-1.fn__flex-column.file-tree.sy__file .ariaLabel:hover { 63 | pointer-events: none; 64 | } 65 | `; 66 | 67 | applyStyles(css); 68 | } 69 | 70 | export function overloadLineHeight(_force_, _px_) { 71 | const css = _force_ 72 | ? ` 73 | .layout-tab-container .b3-list-item__text { 74 | line-height: ${_px_}px !important; 75 | } 76 | ` 77 | : ` 78 | .layout-tab-container .b3-list-item__text { 79 | line-height: ${_px_}px; 80 | }`; 81 | 82 | applyStyles(css); 83 | } 84 | 85 | export function addSeperateLine(_border_) { 86 | const css = ` 87 | .layout-tab-container .b3-list-item__text { 88 | border-top: ${_border_}px solid #eaecef; 89 | } 90 | `; 91 | applyStyles(css); 92 | } 93 | 94 | export function addNotebookOutline(_mode_) { 95 | //by https://github.com/TCOTC aka @Jeffrey Chen 96 | const css_tight_ = ` 97 | .sy__file ul.b3-list.b3-list--background { 98 | border-radius: 0.1em; 99 | margin: 7px 4px 7px 4px; 100 | outline: 1.9px solid var(--b3-theme-background-light); 101 | overflow: hidden; 102 | } 103 | `; 104 | const css_normal_ = ` 105 | .sy__file ul.b3-list.b3-list--background { 106 | border-radius: 0.3em; 107 | margin: 7px 10px 6px 10px; 108 | outline: 2px solid var(--b3-theme-background-light); 109 | overflow: hidden; 110 | } 111 | `; 112 | 113 | const css_high_contrast_ = ` 114 | .sy__file ul.b3-list.b3-list--background { 115 | border-radius: 0.3em; 116 | margin: 6px 10px 6px 12px; 117 | outline: 1.5px double #8e9ba3; 118 | overflow: hidden; 119 | } 120 | `; 121 | if (_mode_ == 1) { 122 | applyStyles(css_normal_); 123 | } else if (_mode_ == 2) { 124 | applyStyles(css_tight_); 125 | } else if (_mode_ == 3) { 126 | applyStyles(css_high_contrast_); 127 | } 128 | } 129 | 130 | export function rmvDoctreeIcons(_force_) { 131 | const css = _force_ 132 | ? ` 133 | .b3-list-item__icon { 134 | display: none !important; 135 | } 136 | ` 137 | : ` 138 | .b3-list-item__icon { 139 | display: none; 140 | } 141 | `; 142 | 143 | applyStyles(css); 144 | } 145 | 146 | export function overloadDoctreeFontSize(_force_, _px_) { 147 | const css = 148 | _force_ == true 149 | ? ` 150 | .layout-tab-container.fn__flex-1 { 151 | font-size: ${_px_}px !important; 152 | } 153 | ` 154 | : ` 155 | .layout-tab-container.fn__flex-1 { 156 | font-size: ${_px_}px; 157 | } 158 | `; 159 | applyStyles(css); 160 | } 161 | 162 | /********** has script and injections *********/ 163 | export function addFrontLine( 164 | _implementation_, 165 | _line_location_, 166 | _padding_, 167 | _border_ 168 | ) { 169 | console.log(_implementation_); 170 | 171 | if (Number(_padding_) >= Number(_line_location_)) { 172 | _padding_ = _line_location_; 173 | } 174 | 175 | var css; 176 | 177 | switch (_implementation_) { 178 | case "1": 179 | css = ` .b3-list ul { 180 | position: relative; 181 | } 182 | 183 | /* main */ 184 | .b3-list ul::before { 185 | content: ''; 186 | position: absolute; 187 | left: 20px; 188 | top: 0; 189 | height: 100%; 190 | border-left: ${_border_}px solid var(--b3-theme-background-light); 191 | z-index: 1; 192 | } 193 | 194 | .b3-list ul ul::before { 195 | left: 34px; 196 | } 197 | 198 | .b3-list ul ul ul::before { 199 | left: 52px; 200 | } 201 | 202 | .b3-list ul ul ul ul::before { 203 | left: 70px; 204 | } 205 | 206 | .b3-list ul ul ul ul ul::before { 207 | left: 88px; 208 | } 209 | 210 | .b3-list ul ul ul ul ul ul::before { 211 | left: 106px; 212 | } 213 | 214 | .b3-list ul ul ul ul ul ul ul::before { 215 | left: 124px; 216 | } 217 | 218 | .b3-list ul ul ul ul ul ul ul ul::before { 219 | left: 142px; 220 | } 221 | 222 | .b3-list ul ul ul ul ul ul ul ul ul::before { 223 | left: 160px; 224 | } 225 | 226 | .b3-list ul ul ul ul ul ul ul ul ul ul::before { 227 | left: 178px; 228 | } 229 | 230 | .b3-list ul ul ul ul ul ul ul ul ul ul ul::before { 231 | left: 196px; 232 | } 233 | 234 | .b3-list ul ul ul ul ul ul ul ul ul ul ul ul::before { 235 | left: 214px; 236 | } 237 | 238 | .b3-list ul ul ul ul ul ul ul ul ul ul ul ul ul::before { 239 | left: 232px; 240 | } 241 | 242 | .b3-list ul ul ul ul ul ul ul ul ul ul ul ul ul ul::before { 243 | left: 250px; 244 | } 245 | 246 | .b3-list ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul::before { 247 | left: 268px; 248 | } 249 | 250 | .b3-list ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul::before { 251 | left: 286px; 252 | } 253 | 254 | .b3-list ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul::before { 255 | left: 304px; 256 | } 257 | 258 | .b3-list ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul::before { 259 | left: 322px; 260 | } 261 | 262 | .b3-list ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul::before { 263 | left: 340px; 264 | } 265 | 266 | .b3-list ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul::before { 267 | left: 358px; 268 | } 269 | 270 | .b3-list ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul::before { 271 | left: 376px; 272 | } 273 | 274 | .b3-list ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul::before { 275 | left: 394px; 276 | } 277 | 278 | .b3-list ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul::before { 279 | left: 412px; 280 | } 281 | 282 | .b3-list ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul::before { 283 | left: 430px; 284 | } 285 | 286 | .b3-list ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul ul::before { 287 | left: 448px; 288 | } 289 | `; 290 | break; 291 | 292 | case "2": 293 | css = ` 294 | .layout-tab-container .b3-list-item > .b3-list-item__toggle { 295 | position: relative; 296 | padding-left: 4px !important; 297 | } 298 | 299 | .layout-tab-container ul ul::before { 300 | content: ""; 301 | position: absolute; 302 | top: 0; 303 | bottom: 0; 304 | left: ${_line_location_}px; 305 | border-left: ${_border_}px solid var(--b3-theme-background-light); 306 | } 307 | 308 | .layout-tab-container ul ul { 309 | position: relative; 310 | padding-left: ${_padding_}px; 311 | } 312 | 313 | .layout-tab-container ul ul::after { 314 | content: ""; 315 | position: absolute; 316 | left: ${_line_location_}px; 317 | border-bottom: var(--custom-block-list-guides-line-width) solid var(--b3-theme-on-surface) !important; 318 | width: 0px; //dunno what's this currently 319 | height: 0; 320 | } 321 | 322 | .layout-tab-container ul ul::before { 323 | content: ""; 324 | position: absolute; 325 | top: 0px; // make the line go down for x px. 326 | left: ${_line_location_}px; 327 | border-top: var(--custom-block-list-guides-line-width) solid var(--b3-theme-on-surface); 328 | } 329 | 330 | `; 331 | break; 332 | 333 | case "3": 334 | css = ` 335 | 336 | .layout-tab-container .b3-list-item > .b3-list-item__toggle { 337 | padding-left: 4px !important; 338 | } 339 | 340 | .layout-tab-container ul ul:before { 341 | content: ""; 342 | position: absolute; 343 | top: 0; 344 | bottom: 0; 345 | left: ${_line_location_}px; 346 | border-left: ${_border_}px solid var(--b3-theme-background-light); 347 | } 348 | 349 | .layout-tab-container ul ul { 350 | position: relative; 351 | padding-left: ${_padding_}px; 352 | } 353 | 354 | `; 355 | 356 | case "4": 357 | css = ` 358 | .layout-tab-container .b3-list-item > .b3-list-item__toggle { 359 | padding-left: 4px !important; 360 | } 361 | 362 | .layout-tab-container ul ul:before { 363 | content: ""; 364 | position: absolute; 365 | top: 0; 366 | bottom: 0; 367 | left: ${_line_location_}px; 368 | border-left: ${_border_}px solid var(--b3-theme-background-light); 369 | } 370 | 371 | .layout-tab-container ul ul { 372 | position: relative; 373 | padding-left: ${_padding_}px; 374 | } 375 | 376 | .layout-tab-container ul ul:hover:before { 377 | border-left-color: var(--b3-theme-on-primary); 378 | } 379 | `; 380 | break; 381 | } 382 | 383 | applyStyles(css); 384 | } 385 | 386 | export function mouseOverZeroPadding(_force_, _px_, _style_) { 387 | switch (_style_) { 388 | case "1": 389 | const css_padding_toggle = _force_ 390 | ? ` 391 | .layout-tab-container .b3-list-item__toggle { 392 | transition: padding-left 0.2s ease; 393 | } 394 | .layout-tab-container .b3-list-item:hover > .b3-list-item__toggle { 395 | padding-left: ${_px_}px !important; 396 | } 397 | ` 398 | : ` 399 | .layout-tab-container .b3-list-item__toggle { 400 | transition: padding-left 0.2s ease; 401 | } 402 | .layout-tab-container .b3-list-item:hover > .b3-list-item__toggle { 403 | padding-left: ${_px_}px; 404 | }`; 405 | applyStyles(css_padding_toggle); 406 | break; 407 | 408 | case "2": 409 | const css_padding_icon = _force_ 410 | ? ` 411 | .layout-tab-container .b3-list-item__icon { 412 | transition: padding-left 0.2s ease; 413 | } 414 | .layout-tab-container .b3-list-item:hover > .b3-list-item__icon { 415 | padding-left: ${_px_}px !important; 416 | } 417 | ` 418 | : ` 419 | .layout-tab-container .b3-list-item__icon { 420 | transition: padding-left 0.2s ease; 421 | } 422 | .layout-tab-container .b3-list-item:hover > .b3-list-item__icon { 423 | padding-left: ${_px_}px; 424 | }`; 425 | applyStyles(css_padding_icon); 426 | 427 | ///worker moving padding from toggle into icon 428 | function moving_padding_from_toggle_into_icon() { 429 | var toggles = document.getElementsByClassName("b3-list-item__toggle"); 430 | for (var i = 0; i < toggles.length; i++) { 431 | var paddingLeft = window 432 | .getComputedStyle(toggles[i], null) 433 | .getPropertyValue("padding-left"); 434 | var icon = 435 | toggles[i].parentNode.getElementsByClassName( 436 | "b3-list-item__icon" 437 | )[0]; 438 | if (icon && paddingLeft !== _px_ + "px") { 439 | icon.style.paddingLeft = paddingLeft; 440 | toggles[i].style.paddingLeft = _px_ + "px"; // 将 padding-left 设为 0 441 | } 442 | } 443 | } 444 | 445 | moving_padding_from_toggle_into_icon(); 446 | 447 | var observer = new MutationObserver(function (mutations) { 448 | moving_padding_from_toggle_into_icon(); 449 | }); 450 | 451 | var config = { childList: true, subtree: true }; 452 | 453 | observer.observe(document, config); 454 | 455 | break; 456 | 457 | case "3": 458 | ///worker moving left padding of toggle into right 459 | 460 | function moving_left_padding_into_right() { 461 | var toggles = document.getElementsByClassName("b3-list-item__toggle"); 462 | for (var i = 0; i < toggles.length; i++) { 463 | var paddingLeft = window 464 | .getComputedStyle(toggles[i], null) 465 | .getPropertyValue("padding-left"); 466 | if (paddingLeft !== _px_ + "px") { 467 | toggles[i].style.paddingRight = paddingLeft; 468 | toggles[i].style.paddingLeft = _px_ + "px"; 469 | } 470 | } 471 | } 472 | 473 | moving_left_padding_into_right(); 474 | 475 | var observer = new MutationObserver(function (mutations) { 476 | moving_left_padding_into_right(); 477 | }); 478 | 479 | var config = { childList: true, subtree: true }; 480 | 481 | observer.observe(document, config); 482 | 483 | const css_padding_icon_LR = _force_ 484 | ? ` 485 | .layout-tab-container .b3-list-item__toggle { 486 | transition: padding-right 0.2s ease; 487 | } 488 | .layout-tab-container .b3-list-item:hover > .b3-list-item__toggle { 489 | padding-right: ${_px_}px !important; 490 | } 491 | ` 492 | : ` 493 | .layout-tab-container .b3-list-item__toggle { 494 | transition: padding-right 0.2s ease; 495 | } 496 | .layout-tab-container .b3-list-item:hover > .b3-list-item__toggle { 497 | padding-right: ${_px_}px; 498 | }`; 499 | applyStyles(css_padding_icon_LR); 500 | 501 | break; 502 | 503 | case "4": 504 | const css_padding_text = _force_ 505 | ? ` 506 | .layout-tab-container .b3-list-item__text { 507 | transition: padding-left 0.2s ease; 508 | } 509 | .layout-tab-container .b3-list-item:hover > .b3-list-item__text { 510 | padding-left: ${_px_}px !important; 511 | } 512 | ` 513 | : ` 514 | .layout-tab-container .b3-list-item__text { 515 | transition: padding-left 0.2s ease; 516 | } 517 | .layout-tab-container .b3-list-item:hover > .b3-list-item__text { 518 | padding-left: ${_px_}px; 519 | }`; 520 | applyStyles(css_padding_text); 521 | 522 | /// worker padding text 523 | 524 | function moving_padding_from_toggle_into_text() { 525 | var toggles = document.getElementsByClassName("b3-list-item__toggle"); 526 | for (var i = 0; i < toggles.length; i++) { 527 | var paddingLeft = window 528 | .getComputedStyle(toggles[i], null) 529 | .getPropertyValue("padding-left"); 530 | var text = 531 | toggles[i].parentNode.getElementsByClassName( 532 | "b3-list-item__text" 533 | )[0]; 534 | if (text && paddingLeft !== _px_ + "px") { 535 | text.style.paddingLeft = paddingLeft; 536 | toggles[i].style.paddingLeft = _px_ + "px"; // 将 padding-left 设为 0 537 | } 538 | } 539 | } 540 | 541 | moving_padding_from_toggle_into_text(); 542 | 543 | var observer = new MutationObserver(function (mutations) { 544 | moving_padding_from_toggle_into_text(); 545 | }); 546 | 547 | var config = { childList: true, subtree: true }; 548 | 549 | observer.observe(document, config); 550 | } 551 | } 552 | 553 | /******** helpers ***********/ 554 | export function applyStyles(css) { 555 | const head = document.head || document.getElementsByTagName("head")[0]; 556 | const style = document.createElement("style"); 557 | head.appendChild(style); 558 | style.appendChild(document.createTextNode(css)); 559 | } 560 | -------------------------------------------------------------------------------- /src/types/api.d.ts: -------------------------------------------------------------------------------- 1 | interface IResGetNotebookConf { 2 | box: string; 3 | conf: NotebookConf; 4 | name: string; 5 | } 6 | 7 | interface IReslsNotebooks { 8 | notebooks: Notebook[]; 9 | } 10 | 11 | interface IResUpload { 12 | errFiles: string[]; 13 | succMap: { [key: string]: string }; 14 | } 15 | 16 | interface IResdoOperations { 17 | doOperations: doOperation[]; 18 | undoOperations: doOperation[] | null; 19 | } 20 | 21 | interface IResGetBlockKramdown { 22 | id: BlockId; 23 | kramdown: string; 24 | } 25 | 26 | interface IResGetChildBlock { 27 | id: BlockId; 28 | type: BlockType; 29 | subtype?: BlockSubType; 30 | } 31 | 32 | interface IResGetTemplates { 33 | content: string; 34 | path: string; 35 | } 36 | 37 | interface IResReadDir { 38 | isDir: boolean; 39 | isSymlink: boolean; 40 | name: string; 41 | } 42 | 43 | interface IResExportMdContent { 44 | hPath: string; 45 | content: string; 46 | } 47 | 48 | interface IResBootProgress { 49 | progress: number; 50 | details: string; 51 | } 52 | 53 | interface IResForwardProxy { 54 | body: string; 55 | contentType: string; 56 | elapsed: number; 57 | headers: { [key: string]: string }; 58 | status: number; 59 | url: string; 60 | } 61 | 62 | interface IResExportResources { 63 | path: string; 64 | } 65 | 66 | -------------------------------------------------------------------------------- /src/types/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2023 frostime. All rights reserved. 3 | */ 4 | 5 | /** 6 | * Frequently used data structures in SiYuan 7 | */ 8 | type DocumentId = string; 9 | type BlockId = string; 10 | type NotebookId = string; 11 | type PreviousID = BlockId; 12 | type ParentID = BlockId | DocumentId; 13 | 14 | type Notebook = { 15 | id: NotebookId; 16 | name: string; 17 | icon: string; 18 | sort: number; 19 | closed: boolean; 20 | } 21 | 22 | type NotebookConf = { 23 | name: string; 24 | closed: boolean; 25 | refCreateSavePath: string; 26 | createDocNameTemplate: string; 27 | dailyNoteSavePath: string; 28 | dailyNoteTemplatePath: string; 29 | } 30 | 31 | type BlockType = "d" | "s" | "h" | "t" | "i" | "p" | "f" | "audio" | "video" | "other"; 32 | 33 | type BlockSubType = "d1" | "d2" | "s1" | "s2" | "s3" | "t1" | "t2" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "table" | "task" | "toggle" | "latex" | "quote" | "html" | "code" | "footnote" | "cite" | "collection" | "bookmark" | "attachment" | "comment" | "mindmap" | "spreadsheet" | "calendar" | "image" | "audio" | "video" | "other"; 34 | 35 | type Block = { 36 | id: BlockId; 37 | parent_id?: BlockId; 38 | root_id: DocumentId; 39 | hash: string; 40 | box: string; 41 | path: string; 42 | hpath: string; 43 | name: string; 44 | alias: string; 45 | memo: string; 46 | tag: string; 47 | content: string; 48 | fcontent?: string; 49 | markdown: string; 50 | length: number; 51 | type: BlockType; 52 | subtype: BlockSubType; 53 | /** string of { [key: string]: string } 54 | * For instance: "{: custom-type=\"query-code\" id=\"20230613234017-zkw3pr0\" updated=\"20230613234509\"}" 55 | */ 56 | ial?: string; 57 | sort: number; 58 | created: string; 59 | updated: string; 60 | } 61 | 62 | type doOperation = { 63 | action: string; 64 | data: string; 65 | id: BlockId; 66 | parentID: BlockId | DocumentId; 67 | previousID: BlockId; 68 | retData: null; 69 | } 70 | 71 | interface Window { 72 | siyuan: { 73 | notebooks: any; 74 | menus: any; 75 | dialogs: any; 76 | blockPanels: any; 77 | storage: any; 78 | user: any; 79 | ws: any; 80 | languages: any; 81 | }; 82 | } 83 | -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from "@sveltejs/vite-plugin-svelte" 2 | 3 | export default { 4 | // Consult https://svelte.dev/docs#compile-time-svelte-preprocess 5 | // for more information about preprocessors 6 | preprocess: vitePreprocess(), 7 | } 8 | -------------------------------------------------------------------------------- /tools/archived/hello.svelte: -------------------------------------------------------------------------------- 1 | 37 | 38 |
      39 |
      appId:
      40 |
      41 |
      ${app?.appId}
      42 |
      43 |
      44 |
      API demo:
      45 |
      46 |
      47 | System current time: {time} 48 |
      49 |
      50 |
      51 |
      Protyle demo: id = {blockID}
      52 |
      53 |
      54 |
      55 | 56 | -------------------------------------------------------------------------------- /tools/archived/index.scss: -------------------------------------------------------------------------------- 1 | #helloPanel { 2 | border: 1px rgb(189, 119, 119) dashed; 3 | } 4 | 5 | .plugin-sample { 6 | &__custom-tab { 7 | background-color: var(--b3-theme-background); 8 | height: 100%; 9 | width: 100%; 10 | display: flex; 11 | justify-content: center; 12 | align-items: center; 13 | } 14 | 15 | &__custom-dock { 16 | display: flex; 17 | justify-content: center; 18 | align-items: center; 19 | } 20 | 21 | &__time { 22 | background: var(--b3-card-info-background); 23 | border-radius: 4px; 24 | padding: 2px 8px; 25 | } 26 | } -------------------------------------------------------------------------------- /tools/archived/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Plugin 3 | } from "siyuan"; 4 | import "@/index.scss"; 5 | 6 | /* 7 | zxkmm naming style: 8 | _inFuncMember_ 9 | _funcArgument_ 10 | funcName 11 | _privateClassMember 12 | */ 13 | 14 | 15 | import { SettingUtils } from "./libs/setting-utils"; 16 | 17 | const STORAGE_NAME = "menu-config"; 18 | 19 | export default class siyuan_doctree_compress extends Plugin { 20 | 21 | private settingUtils: SettingUtils; 22 | 23 | 24 | rmvdoctreeIcons(_elementType_) { 25 | 26 | const _hideIconForceSwitch_ = this.settingUtils.get("hideIconForce"); 27 | 28 | const _styleElement_ = document.createElement('style'); 29 | _styleElement_.textContent = _hideIconForceSwitch_ == true ? ` 30 | .${_elementType_} { 31 | display: none !important; 32 | } 33 | ` : ` 34 | .${_elementType_} { 35 | display: none; 36 | } 37 | ` 38 | ; 39 | 40 | document.head.appendChild(_styleElement_); 41 | } 42 | 43 | 44 | overloadDoctreeFontSize() { 45 | 46 | const _overloadFontSizeForceSwitch_ = this.settingUtils.get("overloadFontSizeForceSwitch"); 47 | const _overloadFontSizePx_ = this.settingUtils.get("overloadFontSizePx"); 48 | 49 | const _styleElement_ = document.createElement('style'); 50 | _styleElement_.textContent = _overloadFontSizeForceSwitch_ == true ? ` 51 | .layout-tab-container.fn__flex-1 { 52 | font-size: ${_overloadFontSizePx_}px; 53 | } 54 | ` : ` 55 | .layout-tab-container.fn__flex-1 { 56 | font-size: ${_overloadFontSizePx_}px !important; 57 | } 58 | ` 59 | ; 60 | 61 | document.head.appendChild(_styleElement_); 62 | } 63 | 64 | async appendCurrentDeviceIntoList() { 65 | try { 66 | // await!!!!! 67 | var current_device_info = await this.fetchCurrentDeviceInfo(); 68 | 69 | var enableDeviceList = this.settingUtils.get("enableDeviceList"); 70 | var enableDeviceListArray = enableDeviceList.split("\n"); 71 | var enableDeviceListArrayLength = enableDeviceListArray.length; 72 | var enableDeviceListArrayLast = enableDeviceListArray[enableDeviceListArrayLength - 1]; 73 | 74 | // remove empty line 75 | if (enableDeviceListArrayLast === "") { 76 | enableDeviceListArray.pop(); 77 | } 78 | 79 | enableDeviceListArray.push(current_device_info); 80 | 81 | var enableDeviceListArrayString = enableDeviceListArray.join("\n"); 82 | 83 | this.settingUtils.assignValue("enableDeviceList", enableDeviceListArrayString); 84 | this.settingUtils.save(); 85 | } catch (error) { 86 | console.error("Error appending current device into list:", error); 87 | } 88 | } 89 | 90 | async removeCurrentDeviceFromList() { 91 | 92 | try { 93 | 94 | var current_device_info = await this.fetchCurrentDeviceInfo(); 95 | 96 | var enableDeviceList = this.settingUtils.get("enableDeviceList"); 97 | var enableDeviceListArray = enableDeviceList.split("\n"); 98 | 99 | // make sure visited the entire list 100 | for (var i = enableDeviceListArray.length - 1; i >= 0; i--) { 101 | var deviceInfo = enableDeviceListArray[i]; 102 | 103 | if (deviceInfo === current_device_info) { 104 | enableDeviceListArray.splice(i, 1); 105 | } 106 | } 107 | 108 | // reassemble list 109 | var enableDeviceListArrayString = enableDeviceListArray.join("\n"); 110 | 111 | this.settingUtils.assignValue("enableDeviceList", enableDeviceListArrayString); 112 | this.settingUtils.save(); 113 | } catch (error) { 114 | console.error("Error removing current device from list:", error); 115 | } 116 | 117 | } 118 | 119 | fetchCurrentDeviceInfo(): Promise { 120 | var current_device_uuid = window.siyuan.config.system.id; 121 | var current_device_name = window.siyuan.config.system.name; 122 | var current_device_info = current_device_uuid + " " + current_device_name; 123 | 124 | return Promise.resolve(current_device_info.toString()); 125 | } 126 | 127 | async currentDeviceInList() { 128 | try { 129 | var current_device_info = await this.fetchCurrentDeviceInfo(); 130 | 131 | var enableDeviceList = await this.settingUtils.get("enableDeviceList"); 132 | var enableDeviceListArray = enableDeviceList.split("\n"); 133 | 134 | return enableDeviceListArray.includes(current_device_info); 135 | } catch (error) { 136 | console.error("Error checking if current device is enabled:", error); 137 | } 138 | } 139 | 140 | async onload() { 141 | 142 | this.data[STORAGE_NAME] = { readonlyText: "Readonly" }; 143 | 144 | this.settingUtils = new SettingUtils(this, STORAGE_NAME); 145 | 146 | this.settingUtils.load(); 147 | 148 | 149 | 150 | this.settingUtils.addItem({ 151 | key: "mainSwitch", 152 | value: false, 153 | type: "checkbox", 154 | title: this.i18n.mainSwitch, 155 | description: "", 156 | }); 157 | 158 | 159 | this.settingUtils.addItem({ //dynamic options 160 | key: "highPerformanceZoneHint", 161 | value: "", 162 | type: "hint", 163 | title: this.i18n.experimentFeatureHintTitle, 164 | description: this.i18n.experimentFeatureHintDesc, 165 | }); 166 | 167 | this.settingUtils.addItem({ 168 | key: "mouseHoverZeroPadding", 169 | value: false, 170 | type: "checkbox", 171 | title: this.i18n.mouseHoverZeroPadding, 172 | description: this.i18n.mouseHoverZeroPaddingDesc, 173 | }); 174 | 175 | this.settingUtils.addItem({ 176 | key: "mouseHoverZeroPaddingForce", 177 | value: true, 178 | type: "checkbox", 179 | title: this.i18n.mouseHoverZeroPaddingForce, 180 | description: this.i18n.mouseHoverZeroPaddingForceDesc, 181 | }); 182 | 183 | this.settingUtils.addItem({ 184 | key: "mouseHoverZeroPaddingPx", 185 | value: 4, 186 | type: "slider", 187 | title: this.i18n.mouseHoverZeroPaddingPx, 188 | description: this.i18n.mouseHoverZeroPaddingPxDesc, 189 | slider: { 190 | min: 0, 191 | max: 10, 192 | step: 1, 193 | } 194 | }); 195 | 196 | this.settingUtils.addItem({ 197 | key: "mouseOverLineUnclamp", 198 | value: false, 199 | type: "checkbox", 200 | title: this.i18n.mouseOverLineUnclampTitle, 201 | description: this.i18n.mouseOverLineUnclampDesc, 202 | 203 | }); 204 | 205 | this.settingUtils.addItem({ 206 | key: "mouseOverLineUnclampForce", 207 | value: false, 208 | type: "checkbox", 209 | title: this.i18n.mouseOverLineUnclampForceTitle, 210 | description: this.i18n.mouseOverLineUnclampForceDesc, 211 | 212 | }); 213 | 214 | this.settingUtils.addItem({ 215 | key: "mouseOverReduceFontSize", 216 | value: false, 217 | type: "checkbox", 218 | title: this.i18n.mouseOverReduceFontSizeTitle, 219 | description: this.i18n.mouseOverReduceFontSizeDesc, 220 | 221 | }); 222 | 223 | this.settingUtils.addItem({ 224 | key: "mouseOverReduceFontSizeForce", 225 | value: false, 226 | type: "checkbox", 227 | title: this.i18n.mouseOverReduceFontSizeForceTitle, 228 | description: this.i18n.mouseOverReduceFontSizeForceDesc, 229 | 230 | }); 231 | 232 | this.settingUtils.addItem({ 233 | key: "mouseHoverReduceFontSizePx", 234 | value: 4, 235 | type: "slider", 236 | title: this.i18n.mouseHoverReduceFontSizePx, 237 | description: this.i18n.mouseHoverReduceFontSizePxDesc, 238 | slider: { 239 | min: 1, 240 | max: 50, 241 | step: 1, 242 | } 243 | }); 244 | 245 | 246 | this.settingUtils.addItem({ 247 | key: "disable document buttons popup", 248 | value: false, 249 | type: "checkbox", 250 | title: this.i18n.disableDocumentButtonsPopup, 251 | description: this.i18n.disableDocumentButtonsPopupDesc, 252 | }); 253 | 254 | this.settingUtils.addItem({ 255 | key: "hideContextualLabel", 256 | value: false, 257 | type: "checkbox", 258 | title: this.i18n.hideContextualLabel, 259 | description: this.i18n.hideContextualLabelDesc, 260 | }); 261 | 262 | this.settingUtils.addItem({ //static options 263 | key: "hintDangerousZone", 264 | value: "", 265 | type: "hint", 266 | title: this.i18n.hintDangerousZoneTitle, 267 | description: this.i18n.hintDangerousZoneDesc, 268 | }); 269 | 270 | this.settingUtils.addItem({ 271 | key: "enableAdjustStaticDoctreePadding", 272 | value: false, 273 | type: "checkbox", 274 | title: this.i18n.enableAdjustStaticDoctreePadding, 275 | description: this.i18n.enableAdjustStaticDoctreePaddingDesc, 276 | }); 277 | 278 | this.settingUtils.addItem({ 279 | key: "Slider", 280 | value: 50, 281 | type: "slider", 282 | title: this.i18n.compressPercent, 283 | description: this.i18n.compressPercentDesc, 284 | slider: { 285 | min: 0, 286 | max: 100, 287 | step: 5, 288 | } 289 | }); 290 | 291 | this.settingUtils.addItem({ 292 | key: "hideIcon", 293 | value: false, 294 | type: "checkbox", 295 | title: this.i18n.hideIcon, 296 | description: this.i18n.hideIconDesc, 297 | }), 298 | 299 | this.settingUtils.addItem({ 300 | key: "hideIconForce", 301 | value: false, 302 | type: "checkbox", 303 | title: this.i18n.hideIconForce, 304 | description: this.i18n.hideIconDescForce, 305 | }), 306 | 307 | this.settingUtils.addItem({ 308 | key: "displayIconButDisableIconClick", 309 | value: false, 310 | type: "checkbox", 311 | title: this.i18n.displayIconButDisableIconClick, 312 | description: this.i18n.displayIconButDisableIconClickDesc, 313 | }); 314 | 315 | this.settingUtils.addItem({ 316 | key: "overloadFontSizeSwitch", 317 | value: false, 318 | type: "checkbox", 319 | title: this.i18n.overloadFontSizeSwitch, 320 | description: this.i18n.overloadFontSizeSwitchDesc, 321 | }), 322 | 323 | 324 | this.settingUtils.addItem({ 325 | key: "overloadFontSizeForceSwitch", 326 | value: false, 327 | type: "checkbox", 328 | title: this.i18n.overloadFontSizeForceSwitch, 329 | description: this.i18n.overloadFontSizeForceSwitchDesc, 330 | }), 331 | 332 | this.settingUtils.addItem({ 333 | key: "overloadFontSizePx", 334 | value: 14, 335 | type: "slider", 336 | title: this.i18n.overloadFontSizePx, 337 | description: this.i18n.overloadFontSizePxDesc, 338 | slider: { 339 | min: 5, 340 | max: 60, 341 | step: 1, 342 | } 343 | }); 344 | 345 | this.settingUtils.addItem({ 346 | key: "overloadLineHeight", 347 | value: false, 348 | type: "checkbox", 349 | title: this.i18n.overloadLineHeight, 350 | description: this.i18n.overloadLineHeightDesc, 351 | }); 352 | 353 | this.settingUtils.addItem({ 354 | key: "overloadLineHeightForce", 355 | value: false, 356 | type: "checkbox", 357 | title: this.i18n.overloadLineHeightForce, 358 | description: this.i18n.overloadLineHeightForceDesc, 359 | }); 360 | 361 | this.settingUtils.addItem({ 362 | key: "overloadLineHeightPx", 363 | value: 28, 364 | type: "slider", 365 | title: this.i18n.overloadLineHeightPx, 366 | description: this.i18n.overloadLineHeightPxDesc, 367 | slider: { 368 | min: 1, 369 | max: 100, 370 | step: 1, 371 | } 372 | }); 373 | 374 | this.settingUtils.addItem({ 375 | key: "hintDeviceSpecificSettings", 376 | value: "", 377 | type: "hint", 378 | title: this.i18n.hintDeviceSpecificSettingsTitle, 379 | description: this.i18n.hintDeviceSpecificSettingsDesc, 380 | }); 381 | 382 | this.settingUtils.addItem({ 383 | key: "onlyEnableListedDevices", 384 | value: false, 385 | type: "checkbox", 386 | title: this.i18n.onlyEnableListedDevices, 387 | description: this.i18n.onlyEnableListedDevicesDesc, 388 | }); 389 | 390 | this.settingUtils.addItem({ 391 | key: "enableDeviceList", 392 | value: "", 393 | type: "textarea", 394 | title: this.i18n.enableDeviceList, 395 | description: this.i18n.enableDeviceListDesc, 396 | }); 397 | 398 | this.settingUtils.addItem({ 399 | key: "addCurrentDeviceIntoList", 400 | value: "", 401 | type: "button", 402 | title: this.i18n.addCurrentDeviceIntoList, 403 | description: this.i18n.addCurrentDeviceIntoListDesc, 404 | button: { 405 | label: this.i18n.addCurrentDeviceIntoListLabel, 406 | callback: () => { 407 | this.appendCurrentDeviceIntoList(); 408 | } 409 | } 410 | }); 411 | 412 | this.settingUtils.addItem({ 413 | key: "removeCurrentDeviceFromList", 414 | value: "", 415 | type: "button", 416 | title: this.i18n.removeCurrentDeviceFromList, 417 | description: this.i18n.removeCurrentDeviceFromListDesc, 418 | button: { 419 | label: this.i18n.removeCurrentDeviceFromListLabel, 420 | callback: () => { 421 | this.removeCurrentDeviceFromList(); 422 | } 423 | } 424 | }); 425 | 426 | this.settingUtils.addItem({ 427 | key: "hint", 428 | value: "", 429 | type: "hint", 430 | title: this.i18n.hintTitle, 431 | description: this.i18n.hintDesc, 432 | }); 433 | 434 | 435 | } 436 | 437 | 438 | 439 | onLayoutReady() { 440 | 441 | this.loadData(STORAGE_NAME); 442 | this.settingUtils.load(); 443 | 444 | 445 | 446 | 447 | 448 | const layoutReadyAsyncHandler = async () => { 449 | 450 | 451 | 452 | 453 | 454 | 455 | //async!!!!!!! 456 | try { 457 | 458 | const _mouseoverZeroPadding_ = this.settingUtils.get("mouseHoverZeroPadding"); 459 | const _mainSwitchStat_ = this.settingUtils.get("mainSwitch"); 460 | const _hideIcon_ = this.settingUtils.get("hideIcon"); 461 | const _enableAdjustStaticDoctreePadding_ = this.settingUtils.get("enableAdjustStaticDoctreePadding"); 462 | const _compressionPercentage_ = this.settingUtils.get("Slider"); 463 | const _overloadFontSizeSwitch_ = this.settingUtils.get("overloadFontSizeSwitch"); 464 | const _mouseHoverZeroPaddingForce_ = this.settingUtils.get("mouseHoverZeroPaddingForce"); 465 | const _mouseHoverZeroPaddingPx_ = this.settingUtils.get("mouseHoverZeroPaddingPx"); 466 | const _mouseOverLineUnclamp_ = this.settingUtils.get("mouseOverLineUnclamp"); 467 | const _mouseOverLineUnclampForce_ = this.settingUtils.get("mouseOverLineUnclampForce"); 468 | const _mouseOverReduceFontSize_ = this.settingUtils.get("mouseOverReduceFontSize"); 469 | const _mouseOverReduceFontSizeForce_ = this.settingUtils.get("mouseOverLineUnclampForce"); 470 | const _mouseHoverReduceFontSizePx_ = this.settingUtils.get("mouseHoverReduceFontSizePx"); 471 | const _onlyEnableListedDevices_ = this.settingUtils.get("onlyEnableListedDevices"); 472 | const _currentDeviceInList_ = await this.currentDeviceInList(); 473 | const _hideContextualLabel_ = this.settingUtils.get("hideContextualLabel"); 474 | const _displayIconButDIsableIconClick_ = this.settingUtils.get("displayIconButDisableIconClick"); 475 | const _disableDocumentButtonsPopup_ = this.settingUtils.get("disable document buttons popup"); 476 | const _overloadLineHeight_ = this.settingUtils.get("overloadLineHeight"); 477 | const _overloadLineHeightForce_ = this.settingUtils.get("overloadLineHeightForce"); 478 | const _overloadLineHeightPx_ = this.settingUtils.get("overloadLineHeightPx"); 479 | 480 | // console.log({ 481 | // mouseoverZeroPadding: _mouseoverZeroPadding_, 482 | // mainSwitchStat: _mainSwitchStat_, 483 | // hideIcon: _hideIcon_, 484 | // compressionPercentage: _compressionPercentage_, 485 | // overloadFontSizeSwitch: _overloadFontSizeSwitch_, 486 | // mouseHoverZeroPaddingForce: _mouseHoverZeroPaddingForce_, 487 | // mouseHoverZeroPaddingPx: _mouseHoverZeroPaddingPx_, 488 | // mouseOverLineUnclamp: _mouseOverLineUnclamp_, 489 | // mouseOverLineUnclampForce: _mouseOverLineUnclampForce_, 490 | // mouseOverReduceFontSize: _mouseOverReduceFontSize_, 491 | // mouseOverReduceFontSizeForce: _mouseOverReduceFontSizeForce_, 492 | // mouseHoverReduceFontSizePx: _mouseHoverReduceFontSizePx_, 493 | // onlyEnableListedDevices: _onlyEnableListedDevices_, 494 | // currentDeviceInList: _currentDeviceInList_ 495 | // }); 496 | 497 | 498 | /*条件列表: 499 | 当前设备真, 仅允许开关开,后半段为假 :真||假: 执行 500 | 当前设备真, 仅允许开关关,后半段为真 :真||真: 执行 501 | 当前设备假, 仅允许开关开,后半段为假 :假||假: 不执行 502 | 当前设备假, 仅允许开关关,后半段为真 :假||真: 执行 503 | */ 504 | 505 | // if ((_currentDeviceInList_ || !_onlyEnableListedDevices_) && _mainSwitchStat_) { 506 | // console.log("进入条件"); 507 | // } else { 508 | // console.log("不进入条件"); 509 | // } 510 | 511 | if ((_currentDeviceInList_ || !_onlyEnableListedDevices_) && _mainSwitchStat_ && _overloadLineHeight_) { //overload line height sel 512 | function overloadLineHeight(css) { 513 | const head = document.head || document.getElementsByTagName('head')[0]; 514 | const style = document.createElement('style'); 515 | head.appendChild(style); 516 | style.appendChild(document.createTextNode(css)); 517 | } 518 | 519 | const css = _overloadLineHeightForce_ ? ` 520 | .layout-tab-container .b3-list-item__text { 521 | line-height: ${_overloadLineHeightPx_}px !important; 522 | } 523 | ` : ` 524 | .layout-tab-container .b3-list-item__text { 525 | line-height: ${_overloadLineHeightPx_}px; 526 | }` 527 | 528 | overloadLineHeight(css); 529 | } 530 | 531 | 532 | if ((_currentDeviceInList_ || !_onlyEnableListedDevices_) && _mainSwitchStat_ && _hideIcon_) { //hide icon sel 533 | this.rmvdoctreeIcons('b3-list-item__icon'); 534 | } 535 | 536 | 537 | 538 | if (_hideContextualLabel_) { //hide contextual label sel 539 | function hideContextualLabel(css) { 540 | const head = document.head || document.getElementsByTagName('head')[0]; 541 | const style = document.createElement('style'); 542 | head.appendChild(style); 543 | style.appendChild(document.createTextNode(css)); 544 | } 545 | 546 | const css = ` 547 | .fn__flex-1.fn__flex-column.file-tree.sy__file .ariaLabel:hover { 548 | pointer-events: none; 549 | } 550 | ` 551 | hideContextualLabel(css); 552 | } 553 | 554 | 555 | if ((_currentDeviceInList_ || !_onlyEnableListedDevices_) && _mainSwitchStat_ && _mouseoverZeroPadding_) { //TODO: 希望能更优雅一些。。。 556 | 557 | function addTempPaddingCss(css) { 558 | const head = document.head || document.getElementsByTagName('head')[0]; 559 | const style = document.createElement('style'); 560 | head.appendChild(style); 561 | style.appendChild(document.createTextNode(css)); 562 | } 563 | 564 | const css = _mouseHoverZeroPaddingForce_ ? ` 565 | .layout-tab-container .b3-list-item:hover > .b3-list-item__toggle { 566 | padding-left: ${_mouseHoverZeroPaddingPx_}px !important; 567 | } 568 | ` : ` 569 | .layout-tab-container .b3-list-item:hover > .b3-list-item__toggle { 570 | padding-left: ${_mouseHoverZeroPaddingPx_}px; 571 | }` 572 | 573 | addTempPaddingCss(css); 574 | } 575 | 576 | if ((_currentDeviceInList_ || !_onlyEnableListedDevices_) && _mainSwitchStat_ && _mouseOverLineUnclamp_) { 577 | 578 | function addReduceLineClampCss(css) { 579 | const head = document.head || document.getElementsByTagName('head')[0]; 580 | const style = document.createElement('style'); 581 | head.appendChild(style); 582 | style.appendChild(document.createTextNode(css)); 583 | } 584 | 585 | const css = _mouseOverLineUnclampForce_ ? ` 586 | .layout-tab-container .b3-list-item:hover > .b3-list-item__text { 587 | overflow:visible !important; 588 | -webkit-line-clamp: unset; 589 | } 590 | ` : ` 591 | .layout-tab-container .b3-list-item:hover > .b3-list-item__text { 592 | overflow:visible; 593 | -webkit-line-clamp: unset; 594 | }` 595 | 596 | addReduceLineClampCss(css); 597 | } 598 | 599 | if ((_currentDeviceInList_ || !_onlyEnableListedDevices_) && _mainSwitchStat_ && _mouseOverReduceFontSize_) { //mouse hover reduce font size sel 600 | 601 | function addTempPaddingCss(css) { 602 | const head = document.head || document.getElementsByTagName('head')[0]; 603 | const style = document.createElement('style'); 604 | head.appendChild(style); 605 | style.appendChild(document.createTextNode(css)); 606 | } 607 | 608 | const css = _mouseOverReduceFontSizeForce_ ? ` 609 | .layout-tab-container .b3-list-item:hover > .b3-list-item__text { 610 | font-size: ${_mouseHoverReduceFontSizePx_}px !important; 611 | } 612 | ` : ` 613 | .layout-tab-container .b3-list-item:hover > .b3-list-item__text { 614 | font-size: ${_mouseHoverReduceFontSizePx_}px; 615 | }` 616 | 617 | addTempPaddingCss(css); 618 | } 619 | 620 | //static options 621 | 622 | 623 | if ((_currentDeviceInList_ || !_onlyEnableListedDevices_) && _mainSwitchStat_ && _overloadFontSizeSwitch_) { //overload font size sel 624 | this.overloadDoctreeFontSize(); 625 | } 626 | 627 | if ((_currentDeviceInList_ || !_onlyEnableListedDevices_) && _mainSwitchStat_ && _displayIconButDIsableIconClick_) {// display icon but disable icon click sel 628 | function hideIconPopup(css) { 629 | const head = document.head || document.getElementsByTagName('head')[0]; 630 | const style = document.createElement('style'); 631 | head.appendChild(style); 632 | style.appendChild(document.createTextNode(css)); 633 | } 634 | 635 | const css = ` 636 | .b3-list-item__icon.b3-tooltips.b3-tooltips__n[aria-label="修改图标"], 637 | .b3-list-item__icon.b3-tooltips.b3-tooltips__n[aria-label="Change icon"] { 638 | pointer-events: none; 639 | } 640 | ` 641 | hideIconPopup(css); 642 | } 643 | 644 | if ((_currentDeviceInList_ || !_onlyEnableListedDevices_) && _mainSwitchStat_ && _disableDocumentButtonsPopup_) { 645 | function makeIconPopupAnArrow(css) { 646 | const head = document.head || document.getElementsByTagName('head')[0]; 647 | const style = document.createElement('style'); 648 | head.appendChild(style); 649 | style.appendChild(document.createTextNode(css)); 650 | } 651 | 652 | const css = ` 653 | .b3-list-item__icon.b3-tooltips.b3-tooltips__n:hover::after, 654 | .b3-list-item__action.b3-tooltips.b3-tooltips__nw:hover::after, 655 | .popover__block.b3-tooltips.b3-tooltips__nw:hover::after { 656 | display: none; 657 | } 658 | ` 659 | makeIconPopupAnArrow(css); 660 | } 661 | 662 | 663 | 664 | if ((_currentDeviceInList_ || !_onlyEnableListedDevices_) && _mainSwitchStat_) { //main sel 665 | 666 | 667 | if (!_mouseoverZeroPadding_ && _enableAdjustStaticDoctreePadding_) { //主开关打开 && 鼠标悬停零缩进关闭 && 分别缩进开关启用 668 | 669 | // console.log("主开关打开 && 鼠标悬停零缩进关闭"); 670 | 671 | 672 | const doctreeObserver = new MutationObserver(mutations => { 673 | handleDomChanges(); 674 | }); 675 | 676 | const config = { attributes: true, childList: true, subtree: true }; 677 | 678 | // doctreeBbserver.observe(document, config); 679 | // 680 | document.querySelectorAll('.fn__flex-column').forEach(element => { 681 | doctreeObserver.observe(element, config); 682 | }); 683 | // 684 | 685 | function handleDomChanges() { 686 | 687 | const elements = document.querySelectorAll('.b3-list-item'); 688 | 689 | elements.forEach(element => { 690 | const isCompressed = element.querySelector('.b3-list-item__toggle').getAttribute('data-compressed'); 691 | 692 | if (!isCompressed) { 693 | const originalPadding = parseFloat(window.getComputedStyle(element.querySelector('.b3-list-item__toggle')).paddingLeft); 694 | 695 | const compressedPadding = originalPadding * (1 - _compressionPercentage_ / 100); 696 | 697 | if (element.getAttribute('data-type') != 'navigation-root') { //prevent compress notebook 698 | 699 | console.dir(element.getAttribute('data-type')); 700 | 701 | element.querySelector('.b3-list-item__toggle').style.paddingLeft = `${compressedPadding}px`; 702 | 703 | element.querySelector('.b3-list-item__toggle').setAttribute('data-compressed', 'true'); //mark as compressed prevent nested compression 704 | } 705 | } 706 | }); 707 | } 708 | 709 | 710 | } 711 | // 712 | 713 | // if (_mouseoverZeroPadding_) { //主开关打开 && 鼠标悬停零缩进打开 //旧方案,暂时保留!!! 714 | 715 | 716 | // console.log("主开关打开 && 鼠标悬停零缩进打开"); 717 | // function handleDomChanges() { 718 | // const elements = document.querySelectorAll('.b3-list-item:not(.event-added)'); 719 | 720 | // elements.forEach(element => { 721 | // const toggleElement = element.querySelector('.b3-list-item__toggle'); 722 | // if (toggleElement) { 723 | // const originalPadding = window.getComputedStyle(toggleElement).paddingLeft; 724 | // toggleElement.setAttribute('data-original-padding', originalPadding); 725 | 726 | // element.classList.add('event-added'); 727 | // } 728 | // }); 729 | // } 730 | 731 | // const doctreeObserver = new MutationObserver(mutations => { 732 | // mutations.forEach(mutation => { 733 | // if (mutation.type === 'attributes' && mutation.attributeName === 'style') { 734 | // const element = mutation.target; 735 | // const toggleElement = element.querySelector('.b3-list-item__toggle'); 736 | // if (toggleElement) { 737 | // const originalPadding = toggleElement.getAttribute('data-original-padding'); 738 | // if (originalPadding) { 739 | // toggleElement.style.paddingLeft = originalPadding; 740 | // } 741 | // } 742 | // } 743 | // }); 744 | 745 | // handleDomChanges(); 746 | // }); 747 | 748 | // const config = { attributes: true, childList: true, subtree: true }; 749 | 750 | // document.querySelectorAll('.fn__flex-1').forEach(element => { 751 | // doctreeObserver.observe(element, config); 752 | // }); 753 | 754 | // handleDomChanges(); 755 | 756 | // } 757 | 758 | // // 759 | 760 | 761 | 762 | 763 | } 764 | } catch (error) { 765 | console.error("siyuan_doctree_compress: failed inject interface", error); 766 | } 767 | }; 768 | 769 | layoutReadyAsyncHandler(); 770 | } 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | async onunload() { 779 | await this.settingUtils.save(); 780 | window.location.reload(); 781 | } 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | } -------------------------------------------------------------------------------- /tools/archived/setting-example.svelte: -------------------------------------------------------------------------------- 1 | 62 | 63 |
      64 |
        65 | {#each groups as group} 66 |
      • { 71 | focusGroup = group; 72 | }} 73 | on:keydown={() => {}} 74 | > 75 | {group} 76 |
      • 77 | {/each} 78 |
      79 |
      80 | 86 |
      87 | 💡 This is our default settings. 88 |
      89 |
      90 |
      91 |
      92 | 93 | 101 | 102 | -------------------------------------------------------------------------------- /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 | /* Svelte */ 26 | /** 27 | * Typecheck JS in `.svelte` and `.js` files by default. 28 | * Disable checkJs if you'd like to use dynamic types in JS. 29 | * Note that setting allowJs false does not prevent the use 30 | * of JS in `.svelte` files. 31 | */ 32 | "allowJs": true, 33 | "checkJs": true, 34 | "types": [ 35 | "node", 36 | "vite/client", 37 | "svelte" 38 | ], 39 | // "baseUrl": "./src", 40 | "paths": { 41 | "@/*": ["./src/*"], 42 | "@/libs/*": ["./src/libs/*"], 43 | } 44 | }, 45 | "include": [ 46 | "tools/**/*.ts", 47 | "src/**/*.ts", 48 | "src/**/*.d.ts", 49 | "src/**/*.tsx", 50 | "src/**/*.vue" 51 | ], 52 | "references": [ 53 | { 54 | "path": "./tsconfig.node.json" 55 | } 56 | ], 57 | "root": "." 58 | } -------------------------------------------------------------------------------- /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 { svelte } from "@sveltejs/vite-plugin-svelte" 7 | import zipPack from "vite-plugin-zip-pack"; 8 | import fg from 'fast-glob'; 9 | 10 | const args = minimist(process.argv.slice(2)) 11 | const isWatch = args.watch || args.w || false 12 | const devDistDir = "./dev" 13 | const distDir = isWatch ? devDistDir : "./dist" 14 | 15 | console.log("isWatch=>", isWatch) 16 | console.log("distDir=>", distDir) 17 | 18 | export default defineConfig({ 19 | resolve: { 20 | alias: { 21 | "@": resolve(__dirname, "src"), 22 | } 23 | }, 24 | 25 | plugins: [ 26 | svelte(), 27 | 28 | viteStaticCopy({ 29 | targets: [ 30 | { 31 | src: "./README*.md", 32 | dest: "./", 33 | }, 34 | { 35 | src: "./icon.png", 36 | dest: "./", 37 | }, 38 | { 39 | src: "./preview.png", 40 | dest: "./", 41 | }, 42 | { 43 | src: "./plugin.json", 44 | dest: "./", 45 | }, 46 | { 47 | src: "./src/i18n/**", 48 | dest: "./i18n/", 49 | }, 50 | ], 51 | }), 52 | ], 53 | 54 | // https://github.com/vitejs/vite/issues/1930 55 | // https://vitejs.dev/guide/env-and-mode.html#env-files 56 | // https://github.com/vitejs/vite/discussions/3058#discussioncomment-2115319 57 | // 在这里自定义变量 58 | define: { 59 | "process.env.DEV_MODE": `"${isWatch}"`, 60 | "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV) 61 | }, 62 | 63 | build: { 64 | // 输出路径 65 | outDir: distDir, 66 | emptyOutDir: false, 67 | 68 | // 构建后是否生成 source map 文件 69 | sourcemap: false, 70 | 71 | // 设置为 false 可以禁用最小化混淆 72 | // 或是用来指定是应用哪种混淆器 73 | // boolean | 'terser' | 'esbuild' 74 | // 不压缩,用于调试 75 | minify: !isWatch, 76 | 77 | lib: { 78 | // Could also be a dictionary or array of multiple entry points 79 | entry: resolve(__dirname, "src/index.ts"), 80 | // the proper extensions will be added 81 | fileName: "index", 82 | formats: ["cjs"], 83 | }, 84 | rollupOptions: { 85 | plugins: [ 86 | ...( 87 | isWatch ? [ 88 | livereload(devDistDir), 89 | { 90 | //监听静态资源文件 91 | name: 'watch-external', 92 | async buildStart() { 93 | const files = await fg([ 94 | 'src/i18n/*.json', 95 | './README*.md', 96 | './plugin.json' 97 | ]); 98 | for (let file of files) { 99 | this.addWatchFile(file); 100 | } 101 | } 102 | } 103 | ] : [ 104 | zipPack({ 105 | inDir: './dist', 106 | outDir: './', 107 | outFileName: 'package.zip' 108 | }) 109 | ] 110 | ) 111 | ], 112 | 113 | // make sure to externalize deps that shouldn't be bundled 114 | // into your library 115 | external: ["siyuan", "process"], 116 | 117 | output: { 118 | entryFileNames: "[name].js", 119 | assetFileNames: (assetInfo) => { 120 | if (assetInfo.name === "style.css") { 121 | return "index.css" 122 | } 123 | return assetInfo.name 124 | }, 125 | }, 126 | }, 127 | } 128 | }) 129 | --------------------------------------------------------------------------------