├── .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 |
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 |
30 |
31 | {title}
32 |
33 | {@html description}
34 |
35 |
36 |
37 |
38 | {#if type === "checkbox"}
39 |
40 |
47 | {:else if type === "textinput"}
48 |
49 |
56 | {:else if type === "number"}
57 |
64 | {:else if type === "button"}
65 |
66 |
71 | {settingValue}
72 |
73 | {:else if type === "select"}
74 |
75 |
81 | {#each Object.entries(options) as [value, text]}
82 | {text}
83 | {/each}
84 |
85 | {:else if type == "slider"}
86 |
87 |
88 |
98 |
99 | {/if}
100 |
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 |
--------------------------------------------------------------------------------