├── .eslintignore ├── scripts ├── .gitignore └── make_dev_link.js ├── icon.png ├── preview.png ├── src ├── libs │ ├── b3-typography.svelte │ ├── index.d.ts │ ├── setting-panel.svelte │ ├── setting-item.svelte │ └── setting-utils.ts ├── helpers.ts ├── index.scss ├── types │ ├── api.d.ts │ └── index.d.ts ├── hello.svelte ├── setting-example.svelte ├── css_injection.ts ├── index.ts └── api.ts ├── asset └── action.png ├── .gitignore ├── svelte.config.js ├── tsconfig.node.json ├── plugin.json ├── LICENSE ├── README_zh_CN.md ├── package.json ├── .eslintrc.cjs ├── README.md ├── tsconfig.json ├── .github └── workflows │ └── release.yml ├── public └── i18n │ ├── zh_CN.json │ └── en_US.json ├── CHANGELOG.md └── vite.config.ts /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /scripts/.gitignore: -------------------------------------------------------------------------------- 1 | .venv 2 | build 3 | dist 4 | *.exe 5 | *.spec 6 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxkmm/siyuan_main_window_modification/HEAD/icon.png -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxkmm/siyuan_main_window_modification/HEAD/preview.png -------------------------------------------------------------------------------- /src/libs/b3-typography.svelte: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /asset/action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxkmm/siyuan_main_window_modification/HEAD/asset/action.png -------------------------------------------------------------------------------- /.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/helpers.ts: -------------------------------------------------------------------------------- 1 | export function convertStringToArray(userInput) { 2 | if (userInput) { 3 | var inputArray = userInput.split(/[,,]/); 4 | for (let i = 0; i < inputArray.length; i++) { 5 | inputArray[i] = inputArray[i].trim(); 6 | } 7 | return inputArray; 8 | } else { 9 | // 处理 undefined 10 | return []; 11 | } 12 | } -------------------------------------------------------------------------------- /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/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_main_window_modification", 3 | "author": "zxkmm", 4 | "url": "https://github.com/zxkmm/siyuan_main_window_modification", 5 | "version": "0.0.5", 6 | "minAppVersion": "3.0.0", 7 | "backends": [ 8 | "windows", 9 | "linux" 10 | ], 11 | "frontends": [ 12 | "desktop", 13 | "browser-desktop", 14 | "desktop-window" 15 | ], 16 | "displayName": { 17 | "en_US": "Main window fine tune", 18 | "zh_CN": "主窗口微调" 19 | }, 20 | "description": { 21 | "en_US": "Fine tune your SiYuan main window", 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" 31 | ] 32 | }, 33 | "keywords": [ 34 | "主窗口微调", "主窗口", "微调", "边框", "控制按钮" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /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_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_main_window_modification)点一下免费的星星鼓励我。 7 | 如果您认为您或者您认识的人需要我的技能,欢迎随时雇用我。 8 | 9 | # 下载 10 | 到release页面下载,或者到思源集市搜索“主窗口微调” 11 | 12 | 13 | # 链接 14 | repo/源码:https://github.com/zxkmm/siyuan_main_window_modification 15 | 16 | 下载:集市搜索 “主窗口微调” 或者 访问 https://github.com/zxkmm/siyuan_main_window_modification/releases 17 | 18 | 汇报 bug / 提交功能请求:https://github.com/zxkmm/siyuan_main_window_modification/issues 19 | 20 | 21 | 22 | # 备注 23 | 如果您喜欢这个插件,请给我的 GitHub 仓库点亮免费的星星⭐(Star)。 24 | 链接:[https://github.com/zxkmm/siyuan_main_window_modification](https://github.com/zxkmm/siyuan_main_window_modification) 25 | 26 | 27 | # 对MIT许可证的额外附加 28 | 29 | 您可以自由使用/ 分发此存储库中的代码,无论您打算闭源还是开源,无论您打算用在付费或免费软件的一部分,您都可以自由免费使用。然而,我已将这些额外的请求纳入了此存储库的许可证。如果您使用了来自此存储库的代码、设计、文本、算法或任何其他东西,您必须在以下三个地方包含我的用户名 "zxkmm" 和此存储库的链接: 30 | 31 | 1. 在代码注释中。 32 | 2. 在与我的代码相关的设置界面中。 33 | 3. 在您的软件/网站的 '关于' 页面以及或任何其他计算机产品的格式中。 34 | 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "siyuan_main_window_modification", 3 | "version": "0.0.5", 4 | "type": "module", 5 | "description": "This is a sample plugin based on vite and svelte for Siyuan (https://b3log.org/siyuan)", 6 | "repository": "https://github.com/zxkmm/siyuan_main_window_modification", 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.4.1", 17 | "@tsconfig/svelte": "^4.0.1", 18 | "@types/node": "^20.3.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.63.3", 25 | "siyuan": "0.9.4", 26 | "svelte": "^3.59.1", 27 | "ts-node": "^10.9.1", 28 | "typescript": "^5.1.3", 29 | "vite": "^4.5.2", 30 | "vite-plugin-static-copy": "^0.15.0", 31 | "vite-plugin-zip-pack": "^1.0.5" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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/libs/setting-panel.svelte: -------------------------------------------------------------------------------- 1 | 9 | 29 | 30 |
31 | 32 | {#each settingItems as item (item.key)} 33 | 45 | {/each} 46 |
-------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # siyuan_rmv_btn 2 | Fine tune your SiYuan main window. 3 | 4 | # Download 5 | go to release page or go to SiYuan's market and search `Main window fine tune` 6 | 7 | 8 | 9 | # Links 10 | repo/Source Code:"https://github.com/zxkmm/siyuan_main_window_modification", 11 | 12 | 13 | Download:"https://github.com/zxkmm/siyuan_main_window_modification", 14 | releases or search `Main window fine tune` in SiYuan market 15 | 16 | Reporting Bugs / Submitting Feqture request:https://github.com/zxkmm/siyuan_main_window_modification/issues 17 | 18 | 19 | # Note 20 | Please star⭐ my GitHub repository if you like this plugin. [https://github.com/zxkmm/siyuan_main_window_modification](https://github.com/zxkmm/siyuan_main_window_modification) 21 | 22 | 23 | # Credits 24 | [SiYuan](https://github.com/siyuan-note/siyuan) ([in b3log](https://b3log.org/siyuan/)) 25 | 26 | 27 | # Additional Attachment to MIT License 28 | 29 | 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: 30 | 31 | 1. In the code comments. 32 | 2. In the settings interface related to my code. 33 | 3. On the 'About' page of your software/website/and or any other format of computer production. 34 | 35 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/i18n/zh_CN.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | 4 | "beggingTitle": "🙏乞讨🙏", 5 | "beggingDesc": "这款插件是开源免费的,因此我需要您的鼓励.
如果您喜欢这款插件为您带来的功能,您可以考虑捐款给我,帮助我解决温饱🍚问题:捐款。(捐款不会解锁更多功能)
如果您经济不宽裕,您可以考虑给我的GitHub仓库点一下免费的⭐星星鼓励我。
如果您认为您或者您认识的人需要我的技能,欢迎随时雇用我。", 6 | "totalSwitch": "总开关", 7 | "enableWindowControlBtnsReload": "启用窗口控制按钮自定义", 8 | "enableWindowControlBtnsReloadDesc": "自定义窗口控制按钮的位置和布局", 9 | "windowControlBtnPosition": "窗口控制按钮位置", 10 | "windowControlBtnPositionDesc": "", 11 | "windowControlBtnsLayout": "窗口控制按钮布局", 12 | "windowControlBtnsLayoutDesc": "", 13 | "windowControlBtnApplyOs": "在哪些系统上自定义窗口控制按钮", 14 | "windowControlBtnApplyOsDesc": "由于 MacOS 的窗口控制按钮是由系统绘制的, 所以在 MacOS 上无法自定义", 15 | "addBodyBorder":"添加主窗口外框", 16 | "addBodyBorderDesc": "上外框和下外框可能存在挤兑关系, 左外框和右外框可能存在挤兑关系, 即,若您想让四边外框等宽,可能需要适度增加右外框和下外框的值", 17 | "bodyBorderTopWidth": "上外框宽度", 18 | "bodyBorderTopWidthDesc": "单位:像素. 可能挤兑下外框", 19 | "bodyBorderBottomWidth": "下外框宽度", 20 | "bodyBorderBottomWidthDesc": "单位:像素. 可能被上外框挤兑", 21 | "bodyBorderLeftWidth": "左外框宽度", 22 | "bodyBorderLeftWidthDesc": "单位:像素. 可能挤兑右外框", 23 | "bodyBorderRightWidth": "右外框宽度", 24 | "bodyBorderRightWidthDesc": "单位:像素. 可能被左外框挤兑", 25 | "bodyBorderColor": "外框颜色", 26 | "bodyBorderColorDesc": "主窗口外框的颜色", 27 | "addToolbarPadding": "添加顶栏内边距", 28 | "addToolbarPaddingDesc": "", 29 | "toolbarPaddingTopWidth": "顶栏上内边距宽度", 30 | "toolbarPaddingTopWidthDesc": "单位:像素.", 31 | "toolbarPaddingBottomWidth": "顶栏下内边距宽度", 32 | "toolbarPaddingBottomWidthDesc": "单位:像素.", 33 | "toolbarPaddingLeftWidth": "顶栏左内边距宽度", 34 | "toolbarPaddingLeftWidthDesc": "单位:像素.", 35 | "toolbarPaddingRightWidth": "顶栏右内边距宽度", 36 | "toolbarPaddingRightWidthDesc": "单位:像素.", 37 | "hintTitle": "关于", 38 | "hintDesc": "
  • ● 由zxkmm制作, MIT 协议开源。
  • ● 如果您喜欢这个插件,请给我的 GitHub 仓库点亮免费的星星⭐(Star)。
  • ● 链接:https://github.com/zxkmm/siyuan_main_window_modification
  • " 39 | } 40 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/i18n/en_US.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "beggingTitle": "🙏 Begging 🙏", 4 | "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.", 5 | 6 | "totalSwitch": "Total Switch", 7 | "enableWindowControlBtnsReload": "Enable Customization of Window Control Buttons", 8 | "enableWindowControlBtnsReloadDesc": "Customize the position and layout of window control buttons", 9 | "windowControlBtnPosition": "Window Control Button Position", 10 | "windowControlBtnPositionDesc": "", 11 | "windowControlBtnsLayout": "Window Control Buttons Layout", 12 | "windowControlBtnsLayoutDesc": "", 13 | "windowControlBtnApplyOs": "Customize Window Control Buttons on Which Systems", 14 | "windowControlBtnApplyOsDesc": "Since the window control buttons on macOS are drawn by the system, customization is not supported on macOS.", 15 | "addBodyBorder": "Add Main Window Border", 16 | "addBodyBorderDesc": "The top and bottom borders may have overlapping issues, and the left and right borders may have overlapping issues as well. If you want all four borders to be of equal width, you may need to increase the values of the right and bottom borders moderately.", 17 | "bodyBorderTopWidth": "Top Border Width", 18 | "bodyBorderTopWidthDesc": "Unit: pixels. May overlap with the bottom border.", 19 | "bodyBorderBottomWidth": "Bottom Border Width", 20 | "bodyBorderBottomWidthDesc": "Unit: pixels. May be overlapped by the top border.", 21 | "bodyBorderLeftWidth": "Left Border Width", 22 | "bodyBorderLeftWidthDesc": "Unit: pixels. May overlap with the right border.", 23 | "bodyBorderRightWidth": "Right Border Width", 24 | "bodyBorderRightWidthDesc": "Unit: pixels. May be overlapped by the left border.", 25 | "bodyBorderColor": "Border Color", 26 | "bodyBorderColorDesc": "The color of the main window border.", 27 | "addToolbarPadding": "Add Toolbar Padding", 28 | "addToolbarPaddingDesc": "", 29 | "toolbarPaddingTopWidth": "Top Padding Width", 30 | "toolbarPaddingTopWidthDesc": "Unit: pixels.", 31 | "toolbarPaddingBottomWidth": "Bottom Padding Width", 32 | "toolbarPaddingBottomWidthDesc": "Unit: pixels.", 33 | "toolbarPaddingLeftWidth": "Left Padding Width", 34 | "toolbarPaddingLeftWidthDesc": "Unit: pixels.", 35 | "toolbarPaddingRightWidth": "Right Padding Width", 36 | "toolbarPaddingRightWidthDesc": "Unit: pixels.", 37 | "hintTitle": "About", 38 | "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_main_window_modification
    • " 39 | } -------------------------------------------------------------------------------- /src/libs/setting-item.svelte: -------------------------------------------------------------------------------- 1 | 28 | 29 | 101 | -------------------------------------------------------------------------------- /src/css_injection.ts: -------------------------------------------------------------------------------- 1 | //TODO:namingspace is needed or not?? 2 | 3 | enum SiyuanColor { 4 | siyuan_highlight = "var(--b3-theme-background-light)", 5 | siyuan_background = "var(--b3-theme-background)", 6 | siyuan_surface = "var(--b3-theme-surface)", 7 | } 8 | 9 | export function applyStyles(css) { 10 | const head = document.head || document.getElementsByTagName("head")[0]; 11 | const style = document.createElement("style"); 12 | head.appendChild(style); 13 | style.appendChild(document.createTextNode(css)); 14 | } 15 | 16 | export function addBodyBorder( 17 | _upBorderWidth_, 18 | _downBorderWidth_, 19 | _leftBorderWidth_, 20 | _rightBorderWidth_, 21 | _color_ 22 | ) { 23 | var css = 24 | `body { 25 | border-top-width: ${_upBorderWidth_}px; 26 | border-bottom-width: ${_downBorderWidth_}px; 27 | border-left-width: ${_leftBorderWidth_}px; 28 | border-right-width: ${_rightBorderWidth_}px; 29 | border-style: solid;`; 30 | 31 | switch (_color_) { 32 | case "1": 33 | css += ` border-color: ${SiyuanColor.siyuan_highlight};`; 34 | break; 35 | case "2": 36 | css += ` border-color: ${SiyuanColor.siyuan_background};`; 37 | break; 38 | case "3": 39 | css += ` border-color: ${SiyuanColor.siyuan_surface};`; 40 | break; 41 | default: 42 | break; 43 | } 44 | 45 | css += `}`; 46 | 47 | console.log(css); 48 | 49 | applyStyles(css); 50 | } 51 | 52 | /* 53 | in full screen mode, after moved the windows ctl btn to the left side, 54 | need to padding the toolbar buttons to prevent they overlapped with each other 55 | */ 56 | export function leftOffsetWindowControlBtns() { 57 | const _css_ = 58 | ` 59 | body.body--win32 .fullscreen > .protyle-breadcrumb, 60 | body.body--win32 .fullscreen > .block__icons { 61 | padding-left: 120px; 62 | padding-right: 10px; 63 | } 64 | `; 65 | applyStyles(_css_); 66 | } 67 | 68 | export function adjustWindowControlBtnsLayout( 69 | _pos_, 70 | _layout_, 71 | _enabledSystem_ 72 | ) { 73 | //sys: 1: win 2: linux 3: win and linux 74 | 75 | const opration_system = navigator.platform.toLocaleLowerCase(); 76 | 77 | if ( 78 | (_enabledSystem_.includes("1") && opration_system.includes("win")) || 79 | (_enabledSystem_.includes("2") && opration_system.includes("linux")) || 80 | (_enabledSystem_.includes("3") && 81 | (opration_system.includes("win") || opration_system.includes("linux"))) 82 | ) { 83 | if (_pos_ == 2) { 84 | windowControls.style.order = "-1"; 85 | leftOffsetWindowControlBtns(); 86 | } 87 | 88 | if (_layout_ == 2) { 89 | closeWindow.style.order = "-1"; 90 | minWindow.style.order = "1"; 91 | maxWindow.style.order = "0"; 92 | } else if (_layout_ == 3) { 93 | closeWindow.style.order = "-1"; 94 | minWindow.style.order = "0"; 95 | maxWindow.style.order = "1"; 96 | } 97 | } 98 | } 99 | 100 | export function adjustToolbarPadding( 101 | _upPadding_, 102 | _downPadding_, 103 | _leftPadding_, 104 | _rightPadding_ 105 | ) { 106 | const css = 107 | `#toolbar.toolbar { 108 | 109 | --toolbar-padding-top: ${_upPadding_}px; 110 | --toolbar-padding-right: ${_rightPadding_}px; 111 | --toolbar-padding-bottom: ${_downPadding_}px; 112 | --toolbar-padding-left: ${_leftPadding_}px; 113 | 114 | padding: var(--toolbar-padding-top) 115 | var(--toolbar-padding-right) 116 | var(--toolbar-padding-bottom) 117 | var(--toolbar-padding-left); 118 | } 119 | 120 | `; 121 | 122 | console.log(css); 123 | 124 | applyStyles(css); 125 | } 126 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.3.5 2024-03 4 | 5 | ## 0.3.4 2024-02-20 6 | 7 | * [Add plugin event bus `click-flashcard-action`](https://github.com/siyuan-note/siyuan/issues/10318) 8 | 9 | ## 0.3.3 2024-01-24 10 | 11 | * Update dock icon class 12 | 13 | ## 0.3.2 2024-01-09 14 | 15 | * [Add plugin `protyleOptions`](https://github.com/siyuan-note/siyuan/issues/10090) 16 | * [Add plugin api `uninstall`](https://github.com/siyuan-note/siyuan/issues/10063) 17 | * [Add plugin method `updateCards`](https://github.com/siyuan-note/siyuan/issues/10065) 18 | * [Add plugin function `lockScreen`](https://github.com/siyuan-note/siyuan/issues/10063) 19 | * [Add plugin event bus `lock-screen`](https://github.com/siyuan-note/siyuan/pull/9967) 20 | * [Add plugin event bus `open-menu-inbox`](https://github.com/siyuan-note/siyuan/pull/9967) 21 | 22 | 23 | ## 0.3.1 2023-12-06 24 | 25 | * [Support `Dock Plugin` and `Command Palette` on mobile](https://github.com/siyuan-note/siyuan/issues/9926) 26 | 27 | ## 0.3.0 2023-12-05 28 | 29 | * Upgrade Siyuan to 0.9.0 30 | * Support more platforms 31 | 32 | ## 0.2.9 2023-11-28 33 | 34 | * [Add plugin method `openMobileFileById`](https://github.com/siyuan-note/siyuan/issues/9738) 35 | 36 | 37 | ## 0.2.8 2023-11-15 38 | 39 | * [`resize` cannot be triggered after dragging to unpin the dock](https://github.com/siyuan-note/siyuan/issues/9640) 40 | 41 | ## 0.2.7 2023-10-31 42 | 43 | * [Export `Constants` to plugin](https://github.com/siyuan-note/siyuan/issues/9555) 44 | * [Add plugin `app.appId`](https://github.com/siyuan-note/siyuan/issues/9538) 45 | * [Add plugin event bus `switch-protyle`](https://github.com/siyuan-note/siyuan/issues/9454) 46 | 47 | ## 0.2.6 2023-10-24 48 | 49 | * [Deprecated `loaded-protyle` use `loaded-protyle-static` instead](https://github.com/siyuan-note/siyuan/issues/9468) 50 | 51 | ## 0.2.5 2023-10-10 52 | 53 | * [Add plugin event bus `open-menu-doctree`](https://github.com/siyuan-note/siyuan/issues/9351) 54 | 55 | ## 0.2.4 2023-09-19 56 | 57 | * Supports use in windows 58 | * [Add plugin function `transaction`](https://github.com/siyuan-note/siyuan/issues/9172) 59 | 60 | ## 0.2.3 2023-09-05 61 | 62 | * [Add plugin function `transaction`](https://github.com/siyuan-note/siyuan/issues/9172) 63 | * [Plugin API add openWindow and command.globalCallback](https://github.com/siyuan-note/siyuan/issues/9032) 64 | 65 | ## 0.2.2 2023-08-29 66 | 67 | * [Add plugin event bus `destroy-protyle`](https://github.com/siyuan-note/siyuan/issues/9033) 68 | * [Add plugin event bus `loaded-protyle-dynamic`](https://github.com/siyuan-note/siyuan/issues/9021) 69 | 70 | ## 0.2.1 2023-08-21 71 | 72 | * [Plugin API add getOpenedTab method](https://github.com/siyuan-note/siyuan/issues/9002) 73 | * [Plugin API custom.fn => custom.id in openTab](https://github.com/siyuan-note/siyuan/issues/8944) 74 | 75 | ## 0.2.0 2023-08-15 76 | 77 | * [Add plugin event bus `open-siyuan-url-plugin` and `open-siyuan-url-block`](https://github.com/siyuan-note/siyuan/pull/8927) 78 | 79 | 80 | ## 0.1.12 2023-08-01 81 | 82 | * Upgrade siyuan to 0.7.9 83 | 84 | ## 0.1.11 85 | 86 | * [Add `input-search` event bus to plugins](https://github.com/siyuan-note/siyuan/issues/8725) 87 | 88 | 89 | ## 0.1.10 90 | 91 | * [Add `bind this` example for eventBus in plugins](https://github.com/siyuan-note/siyuan/issues/8668) 92 | * [Add `open-menu-breadcrumbmore` event bus to plugins](https://github.com/siyuan-note/siyuan/issues/8666) 93 | 94 | ## 0.1.9 95 | 96 | * [Add `open-menu-xxx` event bus for plugins ](https://github.com/siyuan-note/siyuan/issues/8617) 97 | 98 | ## 0.1.8 99 | 100 | * [Add protyleSlash to the plugin](https://github.com/siyuan-note/siyuan/issues/8599) 101 | * [Add plugin API protyle](https://github.com/siyuan-note/siyuan/issues/8445) 102 | 103 | ## 0.1.7 104 | 105 | * [Support build js and json](https://github.com/siyuan-note/plugin-sample/pull/8) 106 | 107 | ## 0.1.6 108 | 109 | * add `fetchPost` example 110 | -------------------------------------------------------------------------------- /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: "./plugin.json", 36 | dest: "./", 37 | }, 38 | { 39 | src: "./preview.png", 40 | dest: "./", 41 | }, 42 | { 43 | src: "./icon.png", 44 | dest: "./", 45 | } 46 | ], 47 | }), 48 | ], 49 | 50 | // https://github.com/vitejs/vite/issues/1930 51 | // https://vitejs.dev/guide/env-and-mode.html#env-files 52 | // https://github.com/vitejs/vite/discussions/3058#discussioncomment-2115319 53 | // 在这里自定义变量 54 | define: { 55 | "process.env.DEV_MODE": `"${isWatch}"`, 56 | "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV) 57 | }, 58 | 59 | build: { 60 | // 输出路径 61 | outDir: distDir, 62 | emptyOutDir: false, 63 | 64 | // 构建后是否生成 source map 文件 65 | sourcemap: false, 66 | 67 | // 设置为 false 可以禁用最小化混淆 68 | // 或是用来指定是应用哪种混淆器 69 | // boolean | 'terser' | 'esbuild' 70 | // 不压缩,用于调试 71 | minify: !isWatch, 72 | 73 | lib: { 74 | // Could also be a dictionary or array of multiple entry points 75 | entry: resolve(__dirname, "src/index.ts"), 76 | // the proper extensions will be added 77 | fileName: "index", 78 | formats: ["cjs"], 79 | }, 80 | rollupOptions: { 81 | plugins: [ 82 | ...( 83 | isWatch ? [ 84 | livereload(devDistDir), 85 | { 86 | //监听静态资源文件 87 | name: 'watch-external', 88 | async buildStart() { 89 | const files = await fg([ 90 | 'public/i18n/**', 91 | './README*.md', 92 | './plugin.json' 93 | ]); 94 | for (let file of files) { 95 | this.addWatchFile(file); 96 | } 97 | } 98 | } 99 | ] : [ 100 | zipPack({ 101 | inDir: './dist', 102 | outDir: './', 103 | outFileName: 'package.zip' 104 | }) 105 | ] 106 | ) 107 | ], 108 | 109 | // make sure to externalize deps that shouldn't be bundled 110 | // into your library 111 | external: ["siyuan", "process"], 112 | 113 | output: { 114 | entryFileNames: "[name].js", 115 | assetFileNames: (assetInfo) => { 116 | if (assetInfo.name === "style.css") { 117 | return "index.css" 118 | } 119 | return assetInfo.name 120 | }, 121 | }, 122 | }, 123 | } 124 | }) 125 | -------------------------------------------------------------------------------- /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-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 | async load() { 44 | let data = await this.plugin.loadData(this.file); 45 | console.debug('Load config:', data); 46 | if (data) { 47 | for (let [key, item] of this.settings) { 48 | item.value = data?.[key] ?? item.value; 49 | } 50 | } 51 | this.plugin.data[this.name] = this.dump(); 52 | return data; 53 | } 54 | 55 | async save() { 56 | let data = this.dump(); 57 | await this.plugin.saveData(this.file, this.dump()); 58 | return data; 59 | } 60 | 61 | /** 62 | * Get setting item value 63 | * @param key key name 64 | * @returns setting item value 65 | */ 66 | get(key: string) { 67 | return this.settings.get(key)?.value; 68 | } 69 | 70 | /** 71 | * 将设置项目导出为 JSON 对象 72 | * @returns object 73 | */ 74 | dump(): Object { 75 | let data: any = {}; 76 | for (let [key, item] of this.settings) { 77 | if (item.type === 'button') continue; 78 | data[key] = item.value; 79 | } 80 | return data; 81 | } 82 | 83 | addItem(item: ISettingItem) { 84 | this.settings.set(item.key, item); 85 | let itemElement: HTMLElement; 86 | switch (item.type) { 87 | case 'checkbox': 88 | let element: HTMLInputElement = document.createElement('input'); 89 | element.type = 'checkbox'; 90 | element.checked = item.value; 91 | element.className = "b3-switch fn__flex-center"; 92 | itemElement = element; 93 | break; 94 | case 'select': 95 | let selectElement: HTMLSelectElement = document.createElement('select'); 96 | selectElement.className = "b3-select fn__flex-center fn__size200"; 97 | let options = item?.options ?? {}; 98 | for (let val in options) { 99 | let optionElement = document.createElement('option'); 100 | let text = options[val]; 101 | optionElement.value = val; 102 | optionElement.text = text; 103 | selectElement.appendChild(optionElement); 104 | } 105 | selectElement.value = item.value; 106 | itemElement = selectElement; 107 | break; 108 | case 'slider': 109 | let sliderElement: HTMLInputElement = document.createElement('input'); 110 | sliderElement.type = 'range'; 111 | sliderElement.className = 'b3-slider fn__size200 b3-tooltips b3-tooltips__n'; 112 | sliderElement.ariaLabel = item.value; 113 | sliderElement.min = item.slider?.min.toString() ?? '0'; 114 | sliderElement.max = item.slider?.max.toString() ?? '100'; 115 | sliderElement.step = item.slider?.step.toString() ?? '1'; 116 | sliderElement.value = item.value; 117 | sliderElement.onchange = () => { 118 | sliderElement.ariaLabel = sliderElement.value; 119 | } 120 | itemElement = sliderElement; 121 | break; 122 | case 'textinput': 123 | let textInputElement: HTMLInputElement = document.createElement('input'); 124 | textInputElement.className = 'b3-text-field fn__flex-center fn__size200'; 125 | textInputElement.value = item.value; 126 | itemElement = textInputElement; 127 | break; 128 | case 'textarea': 129 | let textareaElement: HTMLTextAreaElement = document.createElement('textarea'); 130 | textareaElement.className = "b3-text-field fn__block"; 131 | textareaElement.value = item.value; 132 | itemElement = textareaElement; 133 | break; 134 | case 'number': 135 | let numberElement: HTMLInputElement = document.createElement('input'); 136 | numberElement.type = 'number'; 137 | numberElement.className = 'b3-text-field fn__flex-center fn__size200'; 138 | numberElement.value = item.value; 139 | itemElement = numberElement; 140 | break; 141 | case 'button': 142 | let buttonElement: HTMLButtonElement = document.createElement('button'); 143 | buttonElement.className = "b3-button b3-button--outline fn__flex-center fn__size200"; 144 | buttonElement.innerText = item.button?.label ?? 'Button'; 145 | buttonElement.onclick = item.button?.callback ?? (() => {}); 146 | itemElement = buttonElement; 147 | break; 148 | case 'hint': 149 | let hintElement: HTMLElement = document.createElement('div'); 150 | hintElement.className = 'b3-label fn__flex-center'; 151 | itemElement = hintElement; 152 | break; 153 | 154 | } 155 | this.elements.set(item.key, itemElement); 156 | this.plugin.setting.addItem({ 157 | title: item.title, 158 | description: item?.description, 159 | createActionElement: () => { 160 | let element = this.getElement(item.key); 161 | return element; 162 | } 163 | }) 164 | } 165 | 166 | private getElement(key: string) { 167 | let item = this.settings.get(key); 168 | let element = this.elements.get(key) as any; 169 | switch (item.type) { 170 | case 'checkbox': 171 | element.checked = item.value; 172 | break; 173 | case 'select': 174 | element.value = item.value; 175 | break; 176 | case 'slider': 177 | element.value = item.value; 178 | element.ariaLabel = item.value; 179 | break; 180 | case 'textinput': 181 | element.value = item.value; 182 | break; 183 | case 'textarea': 184 | element.value = item.value; 185 | break; 186 | } 187 | return element; 188 | } 189 | 190 | private updateValue(key: string) { 191 | let item = this.settings.get(key); 192 | let element = this.elements.get(key) as any; 193 | // console.debug(element, element?.value); 194 | switch (item.type) { 195 | case 'checkbox': 196 | item.value = element.checked; 197 | break; 198 | case 'select': 199 | item.value = element.value; 200 | break; 201 | case 'slider': 202 | item.value = element.value; 203 | break; 204 | case 'textinput': 205 | item.value = element.value; 206 | break; 207 | case 'textarea': 208 | item.value = element.value; 209 | break; 210 | } 211 | } 212 | 213 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | var windowControls: any; 3 | var closeWindow: any; 4 | var minWindow: any; 5 | var maxWindow: any; 6 | } 7 | 8 | import { Plugin, showMessage, getFrontend } from "siyuan"; 9 | import "@/index.scss"; 10 | 11 | import { SettingUtils } from "./libs/setting-utils"; 12 | 13 | import { convertStringToArray } from "./helpers"; 14 | 15 | import { 16 | addBodyBorder, 17 | adjustToolbarPadding, 18 | adjustWindowControlBtnsLayout, 19 | } from "./css_injection"; 20 | 21 | 22 | 23 | const STORAGE_NAME = "menu-config"; 24 | 25 | const frontEnd = getFrontend(); 26 | 27 | // TODO: use User-Agent Client Hints API to get platform 28 | 29 | // if (navigator.userAgentData) { 30 | // navigator.userAgentData.getHighEntropyValues(["platform"]) 31 | // .then(platform => { 32 | // console.log(platform); 33 | // }) 34 | // .catch(error => { 35 | // console.error(error); 36 | // }); 37 | // } else { 38 | // console.log('User-Agent Client Hints API not supported.'); 39 | // } 40 | 41 | 42 | const commonMenuNode = document.getElementById("commonMenu"); //it's the menu's id 43 | 44 | 45 | 46 | export default class SiyuanMainWindowModification extends Plugin { 47 | private settingUtils: SettingUtils; 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | reloadInterface() { 61 | // window.location.reload(); 62 | showMessage(this.i18n.reload_hint); 63 | } 64 | 65 | async onload() { 66 | this.settingUtils = new SettingUtils(this, STORAGE_NAME); 67 | this.settingUtils.load(); 68 | 69 | this.settingUtils.addItem({ 70 | key: "begging", 71 | value: "", 72 | type: "hint", 73 | title: this.i18n.beggingTitle, 74 | description: this.i18n.beggingDesc, 75 | }); 76 | 77 | this.settingUtils.addItem({ 78 | key: "totalSwitch", 79 | value: true, 80 | type: "checkbox", 81 | title: this.i18n.totalSwitch, 82 | description: this.i18n.totalSwitchDesc, 83 | }); 84 | 85 | this.settingUtils.addItem({ 86 | key: "enableWindowControlBtnsReload", 87 | value: false, 88 | type: "checkbox", 89 | title: this.i18n.enableWindowControlBtnsReload, 90 | description: this.i18n.enableWindowControlBtnsReloadDesc, 91 | }); 92 | this.settingUtils.addItem({ 93 | key: "windowControlBtnPosition", 94 | value: 1, 95 | type: "select", 96 | title: this.i18n.windowControlBtnPosition, 97 | description: this.i18n.windowControlBtnPositionDesc, 98 | options: { 99 | 1: "↗️", 100 | 2: "↖️", 101 | }, 102 | }); 103 | this.settingUtils.addItem({ 104 | key: "windowControlBtnsLayout", 105 | value: 1, 106 | type: "select", 107 | title: this.i18n.windowControlBtnsLayout, 108 | description: this.i18n.windowControlBtnsLayoutDesc, 109 | options: { 110 | 1: "➖🔲❌️ (Windows and KDE Style)", 111 | 2: "❌🔲➖", 112 | 3: "❌➖🔲 (Mac Style)", 113 | }, 114 | }); 115 | this.settingUtils.addItem({ 116 | key: "windowControlBtnApplyOs", 117 | value: 1, 118 | type: "select", 119 | title: this.i18n.windowControlBtnApplyOs, 120 | description: this.i18n.windowControlBtnApplyOsDesc, 121 | options: { 122 | 1: "Windows", 123 | 2: "Linux", 124 | 3: "Windows & Linux", 125 | }, 126 | }); 127 | 128 | this.settingUtils.addItem({ 129 | key: "addBodyBorder", 130 | value: false, 131 | type: "checkbox", 132 | title: this.i18n.addBodyBorder, 133 | description: this.i18n.addBodyBorderDesc, 134 | }); 135 | 136 | this.settingUtils.addItem({ 137 | key: "bodyBorderColor", 138 | value: 1, 139 | type: "select", 140 | title: this.i18n.bodyBorderColor, 141 | description: this.i18n.bodyBorderColor, 142 | options: { 143 | 1: "SiYuan Highlight", 144 | 2: "SiYuan Background", 145 | 3: "SiYuan Surface", 146 | }, 147 | }); 148 | 149 | this.settingUtils.addItem({ 150 | key: "bodyBorderTopWidth", 151 | value: 1, 152 | type: "slider", 153 | title: "⬆️" + this.i18n.bodyBorderTopWidth, 154 | description: this.i18n.bodyBorderTopWidthDesc, 155 | slider: { 156 | min: 0, 157 | max: 10, 158 | step: 1, 159 | }, 160 | }); 161 | 162 | this.settingUtils.addItem({ 163 | key: "bodyBorderBottomWidth", 164 | value: 1, 165 | type: "slider", 166 | title: "⬇️" + this.i18n.bodyBorderBottomWidth, 167 | description: this.i18n.bodyBorderBottomWidthDesc, 168 | slider: { 169 | min: 0, 170 | max: 10, 171 | step: 1, 172 | }, 173 | }); 174 | 175 | this.settingUtils.addItem({ 176 | key: "bodyBorderLeftWidth", 177 | value: 1, 178 | type: "slider", 179 | title: "⬅️" + this.i18n.bodyBorderLeftWidth, 180 | description: this.i18n.bodyBorderLeftWidthDesc, 181 | slider: { 182 | min: 0, 183 | max: 10, 184 | step: 1, 185 | }, 186 | }); 187 | 188 | this.settingUtils.addItem({ 189 | key: "bodyBorderRightWidth", 190 | value: 1, 191 | type: "slider", 192 | title: "➡️" + this.i18n.bodyBorderRightWidth, 193 | description: this.i18n.bodyBorderRightWidthDesc, 194 | slider: { 195 | min: 0, 196 | max: 10, 197 | step: 1, 198 | }, 199 | }); 200 | 201 | this.settingUtils.addItem({ 202 | key: "addToolbarPadding", 203 | value: false, 204 | type: "checkbox", 205 | title: this.i18n.addToolbarPadding, 206 | description: this.i18n.addToolbarPaddingDesc, 207 | }); 208 | 209 | this.settingUtils.addItem({ 210 | key: "toolbarPaddingTopWidth", 211 | value: 1, 212 | type: "slider", 213 | title: "⬆️" + this.i18n.toolbarPaddingTopWidth, 214 | description: this.i18n.toolbarPaddingTopWidthDesc, 215 | slider: { 216 | min: 0, 217 | max: 50, 218 | step: 1, 219 | }, 220 | }); 221 | 222 | this.settingUtils.addItem({ 223 | key: "toolbarPaddingBottomWidth", 224 | value: 1, 225 | type: "slider", 226 | title: "⬇️" + this.i18n.toolbarPaddingBottomWidth, 227 | description: this.i18n.toolbarPaddingBottomWidthDesc, 228 | slider: { 229 | min: 0, 230 | max: 50, 231 | step: 1, 232 | }, 233 | }); 234 | 235 | this.settingUtils.addItem({ 236 | key: "toolbarPaddingLeftWidth", 237 | value: 1, 238 | type: "slider", 239 | title: "⬅️" + this.i18n.toolbarPaddingLeftWidth, 240 | description: this.i18n.toolbarPaddingLeftWidthDesc, 241 | slider: { 242 | min: 0, 243 | max: 50, 244 | step: 1, 245 | }, 246 | }); 247 | 248 | this.settingUtils.addItem({ 249 | key: "toolbarPaddingRightWidth", 250 | value: 1, 251 | type: "slider", 252 | title: "➡️" + this.i18n.toolbarPaddingRightWidth, 253 | description: this.i18n.toolbarPaddingRightWidthDesc, 254 | slider: { 255 | min: 0, 256 | max: 50, 257 | step: 1, 258 | }, 259 | }); 260 | 261 | this.settingUtils.addItem({ 262 | key: "hint", 263 | value: "", 264 | type: "hint", 265 | title: this.i18n.hintTitle, 266 | description: this.i18n.hintDesc, 267 | }); 268 | } 269 | 270 | onLayoutReady() { 271 | // console.dir(commonMenuNode); 272 | 273 | this.loadData(STORAGE_NAME); 274 | this.settingUtils.load(); 275 | 276 | if (this.settingUtils.get("totalSwitch")) { 277 | if (this.settingUtils.get("enableWindowControlBtnsReload")) { 278 | adjustWindowControlBtnsLayout( 279 | this.settingUtils.get("windowControlBtnPosition"), 280 | this.settingUtils.get("windowControlBtnsLayout"), 281 | this.settingUtils.get("windowControlBtnApplyOs") 282 | ); 283 | } 284 | 285 | if (this.settingUtils.get("addBodyBorder")) { 286 | addBodyBorder( 287 | this.settingUtils.get("bodyBorderTopWidth"), 288 | this.settingUtils.get("bodyBorderBottomWidth"), 289 | this.settingUtils.get("bodyBorderLeftWidth"), 290 | this.settingUtils.get("bodyBorderRightWidth"), 291 | this.settingUtils.get("bodyBorderColor") 292 | ); 293 | } 294 | 295 | if (this.settingUtils.get("addToolbarPadding")) { 296 | adjustToolbarPadding( 297 | this.settingUtils.get("toolbarPaddingTopWidth"), 298 | this.settingUtils.get("toolbarPaddingBottomWidth"), 299 | this.settingUtils.get("toolbarPaddingLeftWidth"), 300 | this.settingUtils.get("toolbarPaddingRightWidth") 301 | ); 302 | } 303 | } 304 | } 305 | 306 | async onunload() { 307 | // await this.settingUtils.save(); 308 | // this.reloadInterface(); 309 | } 310 | 311 | uninstall() { 312 | this.removeData(STORAGE_NAME); 313 | showMessage(this.i18n.uninstall_hint); 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------