├── .eslintignore ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ └── chinese.yml └── workflows │ └── release.yml ├── scripts ├── .gitignore └── make_dev_link.js ├── icon.png ├── preview.png ├── asset └── action.png ├── src ├── libs │ ├── b3-typography.svelte │ ├── index.d.ts │ ├── setting-panel.svelte │ ├── setting-item.svelte │ └── setting-utils.ts ├── index.scss ├── types │ ├── api.d.ts │ └── index.d.ts ├── i18n │ ├── zh_CN.json │ └── en_US.json ├── hello.svelte ├── setting-example.svelte ├── index.ts └── api.ts ├── tools └── IMG_1665.jpeg ├── CHANGELOG.md ├── .gitignore ├── svelte.config.js ├── tsconfig.node.json ├── plugin.json ├── package.json ├── LICENSE ├── .eslintrc.cjs ├── README_zh_CN.md ├── tsconfig.json ├── README.md └── vite.config.ts /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /scripts/.gitignore: -------------------------------------------------------------------------------- 1 | .venv 2 | build 3 | dist 4 | *.exe 5 | *.spec 6 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxkmm/siyuan_leave_to_lock/HEAD/icon.png -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxkmm/siyuan_leave_to_lock/HEAD/preview.png -------------------------------------------------------------------------------- /asset/action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxkmm/siyuan_leave_to_lock/HEAD/asset/action.png -------------------------------------------------------------------------------- /src/libs/b3-typography.svelte: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /tools/IMG_1665.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxkmm/siyuan_leave_to_lock/HEAD/tools/IMG_1665.jpeg -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.0 2 | - add capability to select which devices to enable 3 | 4 | # 0.2.0 5 | - add api support -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | .DS_Store 4 | pnpm-lock.yaml 5 | package.zip 6 | node_modules 7 | dev 8 | dist 9 | build 10 | tmp 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /src/libs/index.d.ts: -------------------------------------------------------------------------------- 1 | type TSettingItemType = "checkbox" | "select" | "textinput" | "textarea" | "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/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 | } -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "siyuan_leave_to_lock", 3 | "author": "zxkmm", 4 | "url": "https://github.com/siyuan-note/plugin-sample-vite-svelte", 5 | "version": "0.2.3", 6 | "minAppVersion": "2.12.1", 7 | "backends": [ 8 | "windows", 9 | "linux", 10 | "darwin" 11 | ], 12 | "frontends": [ 13 | "desktop", 14 | "desktop-window" 15 | ], 16 | "displayName": { 17 | "en_US": "SiYuan Leave To Lock", 18 | "zh_CN": "离开即锁" 19 | }, 20 | "description": { 21 | "en_US": "Lock SiYuan when you leave", 22 | "zh_CN": "当你离开思源笔记时锁定思源。可设置多种监听模式和自定义延迟。" 23 | }, 24 | "readme": { 25 | "en_US": "README.md", 26 | "zh_CN": "README_zh_CN.md" 27 | }, 28 | "funding": { 29 | "custom": [ 30 | "https://github.com/zxkmm/zxkmm" 31 | ] 32 | }, 33 | "keywords": [ 34 | "锁屏", 35 | "离开", 36 | "锁定", 37 | "Lock", 38 | "lock", 39 | "Leave", 40 | "leave", 41 | "siyuan_leave_to_lock", 42 | "zxkmm" 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "siyuan_leave_to_lock", 3 | "version": "0.2.3", 4 | "type": "module", 5 | "description": "Lock SiYuan when you leave", 6 | "repository": "https://github.com/zxkmm/siyuan_leave_to_lock", 7 | "homepage": "https://github.com/zxkmm/zxkmm", 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.5.3", 17 | "@tsconfig/svelte": "^4.0.1", 18 | "@types/node": "^20.10.7", 19 | "eslint": "^8.56.0", 20 | "fast-glob": "^3.3.2", 21 | "glob": "^7.2.3", 22 | "minimist": "^1.2.8", 23 | "rollup-plugin-livereload": "^2.0.5", 24 | "sass": "^1.69.7", 25 | "siyuan": "0.9.2", 26 | "svelte": "^3.59.2", 27 | "ts-node": "^10.9.2", 28 | "typescript": "^5.3.3", 29 | "vite": "^4.5.1", 30 | "vite-plugin-static-copy": "^0.15.0", 31 | "vite-plugin-zip-pack": "^1.0.7" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /README_zh_CN.md: -------------------------------------------------------------------------------- 1 | # 思源离开即锁 2 | ## 恳请: 3 | ## 如果您有空的话,恳求您给我的GitHub仓库点一个**免费**的Star(星星)。 4 | ## 本插件是完全开源免费的,我相信任何经营模式都有可取之处,本插件的开源免费经营模式,需要的就是您的鼓励。 5 | ## 我是毕业大学生,在艰难的就业市场中正在寻找工作。 6 | ## 您给我点的免费Star将很大程度帮助我解决温饱问题,再次感谢! 7 | ## 链接:https://github.com/zxkmm/siyuan_leave_to_lock 8 | ## 9 | 当你离开思源笔记时锁定思源。可设置多种监听模式和自定义延迟。 10 | 11 | # changelog 12 | ## 0.2.1 13 | - seperate the two monitor methods' delay 14 | - decrease minimum delay to a better value 15 | 16 | # 注意事项 17 | 为了避免由于系统不兼容或者Electron出现bug导致循环锁屏无法解开,请先在新的工作空间尝试。确保您的系统支持该插件。 18 | 如果还是不幸被锁,请到工作空间删除整个``siyuan_leave_to_lock``文件夹,同时,请避免在iOS和Docker等无法或难以编辑工作空间文件的操作系统或环境中使用。 19 | # 备注 20 | 如果您喜欢这个插件,请给我的 GitHub 仓库点亮免费的星星⭐(Star)。 21 | 链接:[https://github.com/zxkmm/siyuan_leave_to_lock](https://github.com/zxkmm/siyuan_leave_to_lock) 22 | 23 | # 鸣谢 24 | [Frostime](https://github.com/frostime) ([b3log](https://ld246.com/member/Frostime)) 25 | [Zuoqiu-Yingyi](https://github.com/Zuoqiu-Yingyi) ([b3log](https://ld246.com/member/shuoying)) 26 | 27 | # 对MIT许可证的额外附加 28 | 29 | 您可以自由使用/分发此存储库中的代码,无论您打算闭源还是开源,或者无论您打算用在付费或免费软件的一部分,您都可以自由使用。然而,我已将这些额外的请求纳入了此存储库的许可证。如果您使用了来自此存储库的代码、设计、文本、算法或任何其他东西,您必须在以下三个地方包含我的用户名 "zxkmm" 和此存储库的链接: 30 | 31 | 1. 在代码注释中。 32 | 2. 在与我的代码相关的设置界面中。 33 | 3. 在您的软件/网站的 '关于' 页面以及或任何其他形式的计算机产品中。 34 | -------------------------------------------------------------------------------- /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/i18n/zh_CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "mainSwitch": "主开关", 3 | "monitorVisibility": "监听思源窗口可见性(是否最小化)", 4 | "monitorMouse": "监听鼠标是否在思源窗口内", 5 | "timeout": "监控鼠标在思源窗口内的锁屏延时时间", 6 | "visibilityDelay": "监控窗口可见性(最小化)的锁屏延时时间", 7 | "timeUnit": "分钟", 8 | "onlyEnableListedDevices": "只在以下设备上启用", 9 | "onlyEnableListedDevicesDesc": "当打开开关时,只在表中列出的设备上启用该插件功能。当关闭开关时,在所有设备上启用该插件功能。", 10 | "enableDeviceList": "启用的设备的列表", 11 | "enableDeviceListDesc": "表中列出的设备会在开关打开时启用该插件功能。请尽量避免手动编辑此表", 12 | "addCurrentDeviceIntoList": "添加当前设备", 13 | "addCurrentDeviceIntoListDesc": "把当前设备添加到表中", 14 | "addCurrentDeviceIntoListLabel": "添加", 15 | "removeCurrentDeviceFromList": "移除当前设备", 16 | "removeCurrentDeviceFromListDesc": "把当前设备从表中移除", 17 | "removeCurrentDeviceFromListLabel": "移除", 18 | "lockImplementation": "锁屏实现", 19 | "lockImplementationDesc": "选择锁屏实现方式(若版本支持,推荐使用API)", 20 | "simulateClick": "模拟点击", 21 | "simulateClickText": "模拟点击的按钮文字", 22 | "simulateClickTextDesc": "仅当锁屏实现选项选择模拟点击时有效", 23 | "dangerousModeDesc": "缩短锁屏最小时间。此选项可能会导致您永远被锁在外面,请千万确保您知道如何操作工作空间文件解锁", 24 | "dangerousMode": "危险模式", 25 | "hintTitle": "关于", 26 | "hintDesc": "" 27 | } -------------------------------------------------------------------------------- /src/libs/setting-panel.svelte: -------------------------------------------------------------------------------- 1 | 9 | 29 | 30 |
31 | 32 | {#each settingItems as item (item.key)} 33 | 45 | {/each} 46 |
-------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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: 8 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Siyuan Locks When You Leave 2 | Automatically lock SiYuan when you leave the Siyuan Note. Multiple monitoring modes and custom delays can be configured. 3 | 4 | # changelog 5 | ## 0.2.1 6 | - seperate the two monitor methods' delay 7 | - decrease minimum delay to a better value 8 | 9 | # Precautions 10 | To avoid the possibility of being unable to unlock the loop screen due to system incompatibility or Electron bugs, please try in a new workspace first. Ensure that your system supports this plugin. 11 | If you unfortunately remain locked, go to the workspace and delete the entire `siyuan_leave_to_lock` folder. Also, refrain from using it in operating systems or environments like iOS and Docker, where editing workspace files is difficult or impossible. 12 | 13 | # Note 14 | Please star⭐ my GitHub repository if you like this plugin. [https://github.com/zxkmm/siyuan_leave_to_lock](https://github.com/zxkmm/siyuan_leave_to_lock) 15 | 16 | # Credits 17 | [Frostime](https://github.com/frostime) ([b3log](https://ld246.com/member/Frostime)) 18 | [Zuoqiu-Yingyi](https://github.com/Zuoqiu-Yingyi) ([b3log](https://ld246.com/member/shuoying)) 19 | 20 | 21 | # Additional Attachment to MIT License 22 | 23 | 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: 24 | 25 | 1. In the code comments. 26 | 2. In the settings interface related to my code. 27 | 3. On the 'About' page of your software/website/and or any other format of computer production. 28 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /src/i18n/en_US.json: -------------------------------------------------------------------------------- 1 | { 2 | "mainSwitch": "Main Switch", 3 | "monitorVisibility": "Monitor SiYuan Window Visibility (Minimized)", 4 | "monitorMouse": "Monitor Mouse Inside SiYuan Window", 5 | "timeout": "Locking delay of mouse over monitor", 6 | "visibilityDelay": "Locking delay of Visibility monitor", 7 | "timeUnit": "Minutes", 8 | "onlyEnableListedDevices": "Enable Only on Listed Devices", 9 | "onlyEnableListedDevicesDesc": "When the switch is on, this plugin enabled only on the devices listed in the list. When the switch is off, this plugin enabled on all devices.", 10 | "enableDeviceList": "List of Enabled Devices", 11 | "enableDeviceListDesc": "Devices listed in the table will enable this plugin function when the switch is on. Please avoid manual editing of this table as much as possible.", 12 | "addCurrentDeviceIntoList": "Add Current Device to List", 13 | "addCurrentDeviceIntoListDesc": "Add the current device to the table.", 14 | "addCurrentDeviceIntoListLabel": "Add", 15 | "removeCurrentDeviceFromList": "Remove Current Device from List", 16 | "removeCurrentDeviceFromListDesc": "Remove the current device from the table.", 17 | "removeCurrentDeviceFromListLabel": "Remove", 18 | "lockImplementation": "Lock Screen Implementation", 19 | "lockImplementationDesc": "If app version supports, API is recommended. If not, use simulate click.", 20 | "simulateClick": "Simulate Click", 21 | "simulateClickText": "Simulate Click Button Text", 22 | "simulateClickTextDesc": "Only valid when you use simulate click option above.", 23 | "dangerousModeDesc" : "Allow shorter delay.
This mode is dangerous. Please use it with caution.", 24 | "dangerousMode": "Dangerous Mode", 25 | "hintTitle": "About", 26 | "hintDesc": "" 27 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/libs/setting-item.svelte: -------------------------------------------------------------------------------- 1 | 28 | 29 | 101 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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-11-28 21:46:29 7 | * @Description : A utility for siyuan plugin settings 8 | */ 9 | 10 | import { it } from 'node:test'; 11 | import { Plugin, Setting } from 'siyuan'; 12 | 13 | export class SettingUtils { 14 | plugin: Plugin; 15 | name: string; 16 | file: string; 17 | 18 | settings: Map = new Map(); 19 | elements: Map = new Map(); 20 | 21 | constructor(plugin: Plugin, name?: string, callback?: (data: any) => void, width?: string, height?: string) { 22 | this.name = name ?? 'settings'; 23 | this.plugin = plugin; 24 | this.file = this.name.endsWith('.json') ? this.name : `${this.name}.json`; 25 | this.plugin.setting = new Setting({ 26 | width: width, 27 | height: height, 28 | confirmCallback: () => { 29 | for (let key of this.settings.keys()) { 30 | this.updateValue(key); 31 | } 32 | let data = this.dump(); 33 | if (callback !== undefined) { 34 | callback(data); 35 | } else { 36 | this.plugin.data[this.name] = data; 37 | this.save(); 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 | 93 | addItem(item: ISettingItem) { 94 | this.settings.set(item.key, item); 95 | let itemElement: HTMLElement; 96 | switch (item.type) { 97 | case 'checkbox': 98 | let element: HTMLInputElement = document.createElement('input'); 99 | element.type = 'checkbox'; 100 | element.checked = item.value; 101 | element.className = "b3-switch fn__flex-center"; 102 | itemElement = element; 103 | break; 104 | case 'select': 105 | let selectElement: HTMLSelectElement = document.createElement('select'); 106 | selectElement.className = "b3-select fn__flex-center fn__size200"; 107 | let options = item?.options ?? {}; 108 | for (let val in options) { 109 | let optionElement = document.createElement('option'); 110 | let text = options[val]; 111 | optionElement.value = val; 112 | optionElement.text = text; 113 | selectElement.appendChild(optionElement); 114 | } 115 | selectElement.value = item.value; 116 | itemElement = selectElement; 117 | break; 118 | case 'slider': 119 | let sliderElement: HTMLInputElement = document.createElement('input'); 120 | sliderElement.type = 'range'; 121 | sliderElement.className = 'b3-slider fn__size200 b3-tooltips b3-tooltips__n'; 122 | sliderElement.ariaLabel = item.value; 123 | sliderElement.min = item.slider?.min.toString() ?? '0'; 124 | sliderElement.max = item.slider?.max.toString() ?? '100'; 125 | sliderElement.step = item.slider?.step.toString() ?? '1'; 126 | sliderElement.value = item.value; 127 | sliderElement.onchange = () => { 128 | sliderElement.ariaLabel = sliderElement.value; 129 | } 130 | itemElement = sliderElement; 131 | break; 132 | case 'textinput': 133 | let textInputElement: HTMLInputElement = document.createElement('input'); 134 | textInputElement.className = 'b3-text-field fn__flex-center fn__size200'; 135 | textInputElement.value = item.value; 136 | itemElement = textInputElement; 137 | break; 138 | case 'textarea': 139 | let textareaElement: HTMLTextAreaElement = document.createElement('textarea'); 140 | textareaElement.className = "b3-text-field fn__block"; 141 | textareaElement.value = item.value; 142 | itemElement = textareaElement; 143 | break; 144 | case 'button': 145 | let buttonElement: HTMLButtonElement = document.createElement('button'); 146 | buttonElement.className = "b3-button b3-button--outline fn__flex-center fn__size200"; 147 | buttonElement.innerText = item.button?.label ?? 'Button'; 148 | buttonElement.onclick = item.button?.callback ?? (() => { }); 149 | itemElement = buttonElement; 150 | break; 151 | case 'hint': 152 | let hintElement: HTMLElement = document.createElement('div'); 153 | hintElement.className = 'b3-label fn__flex-center'; 154 | itemElement = hintElement; 155 | break; 156 | } 157 | this.elements.set(item.key, itemElement); 158 | this.plugin.setting.addItem({ 159 | title: item.title, 160 | description: item?.description, 161 | createActionElement: () => { 162 | let element = this.getElement(item.key); 163 | return element; 164 | } 165 | }) 166 | } 167 | 168 | private getElement(key: string) { 169 | let item = this.settings.get(key); 170 | let element = this.elements.get(key) as any; 171 | switch (item.type) { 172 | case 'checkbox': 173 | element.checked = item.value; 174 | break; 175 | case 'select': 176 | element.value = item.value; 177 | break; 178 | case 'slider': 179 | element.value = item.value; 180 | element.ariaLabel = item.value; 181 | break; 182 | case 'textinput': 183 | element.value = item.value; 184 | break; 185 | case 'textarea': 186 | element.value = item.value; 187 | break; 188 | } 189 | return element; 190 | } 191 | 192 | private updateValue(key: string) { 193 | let item = this.settings.get(key); 194 | let element = this.elements.get(key) as any; 195 | // console.debug(element, element?.value); 196 | switch (item.type) { 197 | case 'checkbox': 198 | item.value = element.checked; 199 | break; 200 | case 'select': 201 | item.value = element.value; 202 | break; 203 | case 'slider': 204 | item.value = element.value; 205 | break; 206 | case 'textinput': 207 | item.value = element.value; 208 | break; 209 | case 'textarea': 210 | item.value = element.value; 211 | break; 212 | } 213 | } 214 | 215 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Plugin, 3 | lockScreen 4 | } from "siyuan"; 5 | import "@/index.scss"; 6 | 7 | 8 | 9 | import { SettingUtils } from "./libs/setting-utils"; 10 | 11 | const STORAGE_NAME = "menu-config"; 12 | var minLockDelay = 0.5; // minutes 13 | 14 | export default class siyuan_leave_to_lock extends Plugin { 15 | 16 | private settingUtils: SettingUtils; 17 | 18 | async onload() { 19 | this.data[STORAGE_NAME] = { readonlyText: "Readonly" }; 20 | 21 | this.settingUtils = new SettingUtils(this, STORAGE_NAME); 22 | 23 | this.settingUtils.load(); 24 | 25 | this.settingUtils.addItem({ 26 | key: "mainSwitch", 27 | value: false, 28 | type: "checkbox", 29 | title: this.i18n.mainSwitch, 30 | description: "", 31 | }); 32 | 33 | this.settingUtils.addItem({ 34 | key: "monitorVisibility", 35 | value: true, 36 | type: "checkbox", 37 | title: this.i18n.monitorVisibility, 38 | description: "", 39 | }); 40 | 41 | this.settingUtils.addItem({ 42 | key: "monitorMouse", 43 | value: true, 44 | type: "checkbox", 45 | title: this.i18n.monitorMouse, 46 | description: "", 47 | }); 48 | 49 | this.settingUtils.addItem({ 50 | key: "Slider", 51 | value: 50, 52 | type: "slider", 53 | title: this.i18n.timeout, 54 | description: this.i18n.timeUnit, 55 | slider: { 56 | min: 0.5, 57 | max: 120, 58 | step: 0.5, 59 | } 60 | }); 61 | 62 | this.settingUtils.addItem({ 63 | key: "visibilityDelay", 64 | value: 50, 65 | type: "slider", 66 | title: this.i18n.visibilityDelay, 67 | description: this.i18n.timeUnit, 68 | slider: { 69 | min: 0, 70 | max: 120, 71 | step: 0.1, 72 | } 73 | }); 74 | 75 | this.settingUtils.addItem({ 76 | key: "lockImplementation", 77 | value: 1, 78 | type: "select", 79 | title: this.i18n.lockImplementation, 80 | description: this.i18n.lockImplementationDesc, 81 | options: { 82 | 1: "API", 83 | 2: this.i18n.simulateClick, 84 | } 85 | }); 86 | 87 | this.settingUtils.addItem({ 88 | key: "simulateClickText", 89 | value: "锁屏", 90 | type: "textinput", 91 | title: this.i18n.simulateClickText, 92 | description: this.i18n.simulateClickTextDesc, 93 | }); 94 | 95 | this.settingUtils.addItem({ 96 | key: "onlyEnableListedDevices", 97 | value: false, 98 | type: "checkbox", 99 | title: this.i18n.onlyEnableListedDevices, 100 | description: this.i18n.onlyEnableListedDevicesDesc, 101 | }); 102 | 103 | this.settingUtils.addItem({ 104 | key: "enableDeviceList", 105 | value: "", 106 | type: "textarea", 107 | title: this.i18n.enableDeviceList, 108 | description: this.i18n.enableDeviceListDesc, 109 | }); 110 | 111 | this.settingUtils.addItem({ 112 | key: "addCurrentDeviceIntoList", 113 | value: "", 114 | type: "button", 115 | title: this.i18n.addCurrentDeviceIntoList, 116 | description: this.i18n.addCurrentDeviceIntoListDesc, 117 | button: { 118 | label: this.i18n.addCurrentDeviceIntoListLabel, 119 | callback: () => { 120 | this.appendCurrentDeviceIntoList(); 121 | } 122 | } 123 | }); 124 | 125 | this.settingUtils.addItem({ 126 | key: "removeCurrentDeviceFromList", 127 | value: "", 128 | type: "button", 129 | title: this.i18n.removeCurrentDeviceFromList, 130 | description: this.i18n.removeCurrentDeviceFromListDesc, 131 | button: { 132 | label: this.i18n.removeCurrentDeviceFromListLabel, 133 | callback: () => { 134 | this.removeCurrentDeviceFromList(); 135 | } 136 | } 137 | }); 138 | 139 | this.settingUtils.addItem({ 140 | key: "hint", 141 | value: "", 142 | type: "hint", 143 | title: this.i18n.hintTitle, 144 | description: this.i18n.hintDesc, 145 | }); 146 | 147 | 148 | } 149 | 150 | 151 | 152 | onLayoutReady() { 153 | this.loadData(STORAGE_NAME); 154 | this.settingUtils.load(); 155 | 156 | const layoutReadyAsyncHandler = async () => { 157 | 158 | 159 | /*条件列表: 160 | 当前设备真, 仅允许开关开,后半段为假 :真||假: 执行 161 | 当前设备真, 仅允许开关关,后半段为真 :真||真: 执行 162 | 当前设备假, 仅允许开关开,后半段为假 :假||假: 不执行 163 | 当前设备假, 仅允许开关关,后半段为真 :假||真: 执行 164 | */ 165 | 166 | 167 | try { 168 | 169 | const _visibilityDelay_ = this.settingUtils.get("visibilityDelay") * 1000 * 60; 170 | const _mouseOverDelay_ = this.settingUtils.get("Slider") * 1000 * 60; 171 | if ((await this.currentDeviceInList() || !this.settingUtils.get("onlyEnableListedDevices")) && this.settingUtils.get("mainSwitch")) { 172 | // console.log("siyuan_leave_to_lock: device ifEnable condition entered"); //DBG 173 | 174 | let timer; 175 | 176 | document.addEventListener("visibilitychange", () => { 177 | if (document.hidden) { 178 | timer = setTimeout(() => { 179 | if (this.settingUtils.get("mainSwitch") && this.settingUtils.get("monitorVisibility")) { 180 | 181 | 182 | if (this.settingUtils.get("lockImplementation") == 1) { 183 | console.log("condition,1,1"); //DBG 184 | this.lock_screen_with_api(); 185 | } else if (this.settingUtils.get("lockImplementation") == 2) { 186 | console.log("condition,1,2"); //DBG 187 | this.lock_screen_with_simulate_click(); 188 | } else { 189 | console.log("condition,1,3"); //DBG 190 | this.lock_screen_with_api(); 191 | } 192 | 193 | this.sleep(1000); 194 | } 195 | }, _visibilityDelay_); 196 | } else { 197 | clearTimeout(timer); 198 | } 199 | }); 200 | 201 | document.addEventListener("mouseout", () => { 202 | timer = setTimeout(() => { 203 | if (this.settingUtils.get("mainSwitch") && this.settingUtils.get("monitorMouse")) { 204 | if (this.settingUtils.get("lockImplementation") == 1) { 205 | this.lock_screen_with_api(); 206 | // console.log("condition,2,1"); //DBG 207 | } else if (this.settingUtils.get("lockImplementation") == 2) { 208 | // console.log("condition,2,2"); //DBG 209 | this.lock_screen_with_simulate_click(); 210 | } else { 211 | this.lock_screen_with_api(); 212 | // console.log("condition,2,3"); //DBG 213 | } 214 | this.sleep(1000); 215 | } 216 | }, _mouseOverDelay_); 217 | }); 218 | 219 | document.addEventListener("mouseover", () => { 220 | clearTimeout(timer); 221 | }); 222 | } 223 | } catch (error) { 224 | console.error("sy_leave_to_lock: failed loading device ifEnable condition", error); 225 | } 226 | }; 227 | 228 | layoutReadyAsyncHandler(); 229 | } 230 | 231 | 232 | lock_screen_with_api() { 233 | lockScreen(this.app); 234 | } 235 | 236 | 237 | 238 | async lock_screen_with_simulate_click() { 239 | var user_defined_simulate_click_text = this.settingUtils.get("simulateClickText"); 240 | // console.log("try to lock"); //DBG 241 | var mainMenuButton = document.getElementById("barWorkspace"); 242 | 243 | // main menu 244 | if (mainMenuButton) { 245 | mainMenuButton.click(); 246 | await this.sleep(300); 247 | } else { 248 | console.log("siyuan_leave_to_lock: cant find the main menu button"); 249 | return; 250 | } 251 | 252 | await this.sleep(100); 253 | 254 | 255 | function findTargetButton(elements) { 256 | var targetButton = null; 257 | elements.forEach(function (button) { 258 | var labelElement = button.querySelector('.b3-menu__label'); 259 | if (labelElement && labelElement.textContent.trim() == user_defined_simulate_click_text) { 260 | targetButton = button; 261 | } else { 262 | var submenu = button.querySelector('.b3-menu__submenu'); 263 | if (submenu) { 264 | // submenu exists 递归 265 | targetButton = findTargetButton(submenu.querySelectorAll('.b3-menu__item')); 266 | } 267 | } 268 | }); 269 | return targetButton; 270 | } 271 | 272 | var targetButton = findTargetButton(document.querySelectorAll('.b3-menu__item')); 273 | 274 | if (targetButton) { 275 | targetButton.click(); 276 | } else { 277 | console.error('siyuan_leave_to_lock: cant find the text you defined'); 278 | } 279 | } 280 | 281 | 282 | 283 | 284 | sleep(ms) { 285 | return new Promise(resolve => setTimeout(resolve, ms)); 286 | } 287 | 288 | 289 | 290 | async onunload() { 291 | await this.settingUtils.save(); 292 | // window.location.reload(); 293 | } 294 | 295 | 296 | async currentDeviceInList() { 297 | try { 298 | var current_device_info = await this.fetchCurrentDeviceInfo(); 299 | 300 | var enableDeviceList = await this.settingUtils.get("enableDeviceList"); 301 | var enableDeviceListArray = enableDeviceList.split("\n"); 302 | 303 | return enableDeviceListArray.includes(current_device_info); 304 | } catch (error) { 305 | console.error("Error checking if current device is enabled:", error); 306 | } 307 | } 308 | 309 | 310 | fetchCurrentDeviceInfo(): Promise { 311 | var current_device_uuid = window.siyuan.config.system.id; 312 | var current_device_name = window.siyuan.config.system.name; 313 | var current_device_info = current_device_uuid + " " + current_device_name; 314 | 315 | return Promise.resolve(current_device_info.toString()); 316 | } 317 | 318 | 319 | async appendCurrentDeviceIntoList() { 320 | try { 321 | // await!!!!! 322 | var current_device_info = await this.fetchCurrentDeviceInfo(); 323 | 324 | var enableDeviceList = this.settingUtils.get("enableDeviceList"); 325 | var enableDeviceListArray = enableDeviceList.split("\n"); 326 | var enableDeviceListArrayLength = enableDeviceListArray.length; 327 | var enableDeviceListArrayLast = enableDeviceListArray[enableDeviceListArrayLength - 1]; 328 | 329 | // remove empty line 330 | if (enableDeviceListArrayLast === "") { 331 | enableDeviceListArray.pop(); 332 | } 333 | 334 | enableDeviceListArray.push(current_device_info); 335 | 336 | var enableDeviceListArrayString = enableDeviceListArray.join("\n"); 337 | 338 | this.settingUtils.assignValue("enableDeviceList", enableDeviceListArrayString); 339 | this.settingUtils.save(); 340 | } catch (error) { 341 | console.error("Error appending current device into list:", error); 342 | } 343 | } 344 | 345 | 346 | 347 | async removeCurrentDeviceFromList() { 348 | 349 | try { 350 | 351 | var current_device_info = await this.fetchCurrentDeviceInfo(); 352 | 353 | var enableDeviceList = this.settingUtils.get("enableDeviceList"); 354 | var enableDeviceListArray = enableDeviceList.split("\n"); 355 | 356 | // make sure visited the entire list 357 | for (var i = enableDeviceListArray.length - 1; i >= 0; i--) { 358 | var deviceInfo = enableDeviceListArray[i]; 359 | 360 | if (deviceInfo === current_device_info) { 361 | enableDeviceListArray.splice(i, 1); 362 | } 363 | } 364 | 365 | // reassemble list 366 | var enableDeviceListArrayString = enableDeviceListArray.join("\n"); 367 | 368 | this.settingUtils.assignValue("enableDeviceList", enableDeviceListArrayString); 369 | this.settingUtils.save(); 370 | } catch (error) { 371 | console.error("Error removing current device from list:", error); 372 | } 373 | 374 | } 375 | 376 | 377 | 378 | 379 | 380 | } 381 | -------------------------------------------------------------------------------- /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 | 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 getBlockKramdown(id: BlockId): Promise { 228 | let data = { 229 | id: id 230 | } 231 | let url = '/api/block/getBlockKramdown'; 232 | return request(url, data); 233 | } 234 | 235 | 236 | export async function getChildBlocks(id: BlockId): Promise { 237 | let data = { 238 | id: id 239 | } 240 | let url = '/api/block/getChildBlocks'; 241 | return request(url, data); 242 | } 243 | 244 | export async function transferBlockRef(fromID: BlockId, toID: BlockId, refIDs: BlockId[]) { 245 | let data = { 246 | fromID: fromID, 247 | toID: toID, 248 | refIDs: refIDs 249 | } 250 | let url = '/api/block/transferBlockRef'; 251 | return request(url, data); 252 | } 253 | 254 | // **************************************** Attributes **************************************** 255 | export async function setBlockAttrs(id: BlockId, attrs: { [key: string]: string }) { 256 | let data = { 257 | id: id, 258 | attrs: attrs 259 | } 260 | let url = '/api/attr/setBlockAttrs'; 261 | return request(url, data); 262 | } 263 | 264 | 265 | export async function getBlockAttrs(id: BlockId): Promise<{ [key: string]: string }> { 266 | let data = { 267 | id: id 268 | } 269 | let url = '/api/attr/getBlockAttrs'; 270 | return request(url, data); 271 | } 272 | 273 | // **************************************** SQL **************************************** 274 | 275 | export async function sql(sql: string): Promise { 276 | let sqldata = { 277 | stmt: sql, 278 | }; 279 | let url = '/api/query/sql'; 280 | return request(url, sqldata); 281 | } 282 | 283 | export async function getBlockByID(blockId: string): Promise { 284 | let sqlScript = `select * from blocks where id ='${blockId}'`; 285 | let data = await sql(sqlScript); 286 | return data[0]; 287 | } 288 | 289 | // **************************************** Template **************************************** 290 | 291 | export async function render(id: DocumentId, path: string): Promise { 292 | let data = { 293 | id: id, 294 | path: path 295 | } 296 | let url = '/api/template/render'; 297 | return request(url, data); 298 | } 299 | 300 | 301 | export async function renderSprig(template: string): Promise { 302 | let url = '/api/template/renderSprig'; 303 | return request(url, { template: template }); 304 | } 305 | 306 | // **************************************** File **************************************** 307 | 308 | export async function getFile(path: string): Promise { 309 | let data = { 310 | path: path 311 | } 312 | let url = '/api/file/getFile'; 313 | try { 314 | let file = await fetchSyncPost(url, data); 315 | return file; 316 | } catch (error_msg) { 317 | return null; 318 | } 319 | } 320 | 321 | export async function putFile(path: string, isDir: boolean, file: any) { 322 | let form = new FormData(); 323 | form.append('path', path); 324 | form.append('isDir', isDir.toString()); 325 | // Copyright (c) 2023, terwer. 326 | // https://github.com/terwer/siyuan-plugin-importer/blob/v1.4.1/src/api/kernel-api.ts 327 | form.append('modTime', Math.floor(Date.now() / 1000).toString()); 328 | form.append('file', file); 329 | let url = '/api/file/putFile'; 330 | return request(url, form); 331 | } 332 | 333 | export async function removeFile(path: string) { 334 | let data = { 335 | path: path 336 | } 337 | let url = '/api/file/removeFile'; 338 | return request(url, data); 339 | } 340 | 341 | 342 | 343 | export async function readDir(path: string): Promise { 344 | let data = { 345 | path: path 346 | } 347 | let url = '/api/file/readDir'; 348 | return request(url, data); 349 | } 350 | 351 | 352 | // **************************************** Export **************************************** 353 | 354 | export async function exportMdContent(id: DocumentId): Promise { 355 | let data = { 356 | id: id 357 | } 358 | let url = '/api/export/exportMdContent'; 359 | return request(url, data); 360 | } 361 | 362 | export async function exportResources(paths: string[], name: string): Promise { 363 | let data = { 364 | paths: paths, 365 | name: name 366 | } 367 | let url = '/api/export/exportResources'; 368 | return request(url, data); 369 | } 370 | 371 | // **************************************** Convert **************************************** 372 | 373 | export type PandocArgs = string; 374 | export async function pandoc(args: PandocArgs[]) { 375 | let data = { 376 | args: args 377 | } 378 | let url = '/api/convert/pandoc'; 379 | return request(url, data); 380 | } 381 | 382 | // **************************************** Notification **************************************** 383 | 384 | // /api/notification/pushMsg 385 | // { 386 | // "msg": "test", 387 | // "timeout": 7000 388 | // } 389 | export async function pushMsg(msg: string, timeout: number = 7000) { 390 | let payload = { 391 | msg: msg, 392 | timeout: timeout 393 | }; 394 | let url = "/api/notification/pushMsg"; 395 | return request(url, payload); 396 | } 397 | 398 | export async function pushErrMsg(msg: string, timeout: number = 7000) { 399 | let payload = { 400 | msg: msg, 401 | timeout: timeout 402 | }; 403 | let url = "/api/notification/pushErrMsg"; 404 | return request(url, payload); 405 | } 406 | 407 | // **************************************** Network **************************************** 408 | export async function forwardProxy( 409 | url: string, method: string = 'GET', payload: any = {}, 410 | headers: any[] = [], timeout: number = 7000, contentType: string = "text/html" 411 | ): Promise { 412 | let data = { 413 | url: url, 414 | method: method, 415 | timeout: timeout, 416 | contentType: contentType, 417 | headers: headers, 418 | payload: payload 419 | } 420 | let url1 = '/api/network/forwardProxy'; 421 | return request(url1, data); 422 | } 423 | 424 | 425 | // **************************************** System **************************************** 426 | 427 | export async function bootProgress(): Promise { 428 | return request('/api/system/bootProgress', {}); 429 | } 430 | 431 | 432 | export async function version(): Promise { 433 | return request('/api/system/version', {}); 434 | } 435 | 436 | 437 | export async function currentTime(): Promise { 438 | return request('/api/system/currentTime', {}); 439 | } 440 | --------------------------------------------------------------------------------