mirror of
https://github.com/siyuan-note/plugin-sample-vite-svelte.git
synced 2025-06-08 02:46:02 +00:00
Compare commits
127 commits
Author | SHA1 | Date | |
---|---|---|---|
|
777f31761c | ||
|
38b19fdb88 | ||
|
591cf2e95e | ||
|
4aee720da5 | ||
|
19cae2eabd | ||
|
63ee8d92dc | ||
|
63cc3ac0c2 | ||
|
e345e18613 | ||
|
da4c4ced8f | ||
|
1ac9cf8ff5 | ||
|
4c004d73b5 | ||
|
b739c5ad54 | ||
|
0d0167b85b | ||
|
6361001b8a | ||
|
46e48452c0 | ||
|
059744903d | ||
|
504de7d821 | ||
|
9e2a64b471 | ||
|
0a8ab5a227 | ||
|
5ff929e456 | ||
|
ba57ba3f23 | ||
|
6d75a46a56 | ||
|
49eb06330d | ||
|
9c7572b820 | ||
|
81d5bee160 | ||
|
67d6024a04 | ||
|
03ab34f552 | ||
|
9f54e7046c | ||
|
3d565424ae | ||
|
29c494a3f4 | ||
|
59daefa941 | ||
|
7159755c70 | ||
|
4a9d0f3953 | ||
|
0df9ec29ea | ||
|
57cc62f7b5 | ||
|
56be185458 | ||
|
8daa01aedb | ||
|
2c45caf80f | ||
|
7a46168ed2 | ||
|
587d5f2112 | ||
|
8f27804c83 | ||
|
0645799101 | ||
|
786cc5e933 | ||
|
3b47436806 | ||
|
1a1c267b46 | ||
|
adf7d86dcd | ||
|
4286a49a0b | ||
|
cd38b61aba | ||
|
0e372e9068 | ||
|
f06c82085e | ||
|
44d61785cf | ||
|
762ae2197b | ||
|
4310b00dca | ||
|
a50c61efaf | ||
|
91deda2a72 | ||
|
f9ea1bec3f | ||
|
6700d611b9 | ||
|
c58c836b7c | ||
|
ec00075b4e | ||
|
9fcb5bb481 | ||
|
4a7bd848a9 | ||
|
0ff9fde560 | ||
|
e48a9abbc9 | ||
|
c33a63aa8f | ||
|
90b2805d9e | ||
|
881ff608b0 | ||
|
65b9505ae4 | ||
|
3ad078d548 | ||
|
953d15c661 | ||
|
bf21514b99 | ||
|
e6a7277f8a | ||
|
6f8ae4dcd3 | ||
|
2ab75b2bf3 | ||
|
f84497f6da | ||
|
1735ec4bce | ||
|
74d62ac1aa | ||
|
a2a615aae2 | ||
|
c179519f95 | ||
|
50a2286d43 | ||
|
2a08298060 | ||
|
2051a2a7d6 | ||
|
b0d28e2513 | ||
|
f1fcf77500 | ||
|
ec8d768d2d | ||
|
7f57c40db5 | ||
|
b31102d12f | ||
|
962f879fa2 | ||
|
8e841a4a90 | ||
|
3de8bc6134 | ||
|
5f59ffefe4 | ||
|
7d95423d24 | ||
|
3343eb7e5c | ||
|
c580fb7a00 | ||
|
e2ed03218d | ||
|
bc635356dc | ||
|
8be20e023c | ||
|
c2d3812e19 | ||
|
05c9f269e2 | ||
|
1a0b1018c5 | ||
|
b1a061cb0c | ||
|
cdb5085f35 | ||
|
deb065668f | ||
|
06f598b18b | ||
|
2fa17e4a7b | ||
|
57e50e1ca8 | ||
|
f114305718 | ||
|
cfa959c1a8 | ||
|
1f199a6dcf | ||
|
548f80a46f | ||
|
b7f633598f | ||
|
811519bfd8 | ||
|
4fc8ef7afc | ||
|
014f906263 | ||
|
d64c5b1c38 | ||
|
e6f5580c26 | ||
|
d5bf3cbc66 | ||
|
38b8677c15 | ||
|
77e337b9f5 | ||
|
519bece35b | ||
|
f0ef27efab | ||
|
ddca3e0e24 | ||
|
34e3646add | ||
|
b1660caa30 | ||
|
73070a56fd | ||
|
22c27c0542 | ||
|
837f5ae1f5 | ||
|
4dde9da239 |
40 changed files with 3003 additions and 760 deletions
|
@ -1,2 +0,0 @@
|
||||||
node_modules
|
|
||||||
dist
|
|
|
@ -1,38 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
extends: [
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:svelte/recommended",
|
|
||||||
"turbo",
|
|
||||||
"prettier",
|
|
||||||
],
|
|
||||||
|
|
||||||
parser: "@typescript-eslint/parser",
|
|
||||||
|
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
files: ["*.svelte"],
|
|
||||||
parser: "svelte-eslint-parser",
|
|
||||||
// Parse the script in `.svelte` as TypeScript by adding the following configuration.
|
|
||||||
parserOptions: {
|
|
||||||
parser: "@typescript-eslint/parser",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
plugins: ["@typescript-eslint", "prettier"],
|
|
||||||
|
|
||||||
rules: {
|
|
||||||
// Note: you must disable the base rule as it can report incorrect errors
|
|
||||||
semi: "off",
|
|
||||||
quotes: "off",
|
|
||||||
"no-undef": "off",
|
|
||||||
"@typescript-eslint/no-var-requires": "off",
|
|
||||||
"@typescript-eslint/no-this-alias": "off",
|
|
||||||
"@typescript-eslint/no-non-null-assertion": "off",
|
|
||||||
"@typescript-eslint/no-unused-vars": "off",
|
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
|
||||||
"turbo/no-undeclared-env-vars": "off",
|
|
||||||
"prettier/prettier": "error",
|
|
||||||
},
|
|
||||||
}
|
|
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
|
@ -17,12 +17,12 @@ jobs:
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 20
|
||||||
registry-url: "https://registry.npmjs.org"
|
registry-url: "https://registry.npmjs.org"
|
||||||
|
|
||||||
# Install pnpm
|
# Install pnpm
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v4
|
||||||
id: pnpm-install
|
id: pnpm-install
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 8
|
||||||
|
@ -59,4 +59,4 @@ jobs:
|
||||||
artifactErrorsFailBuild: true
|
artifactErrorsFailBuild: true
|
||||||
artifacts: "package.zip"
|
artifacts: "package.zip"
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
prerelease: true
|
prerelease: false
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,6 +2,7 @@
|
||||||
.vscode
|
.vscode
|
||||||
.DS_Store
|
.DS_Store
|
||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
|
package-lock.json
|
||||||
package.zip
|
package.zip
|
||||||
node_modules
|
node_modules
|
||||||
dev
|
dev
|
||||||
|
|
112
CHANGELOG.md
112
CHANGELOG.md
|
@ -0,0 +1,112 @@
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
## v0.3.5 2024-04-30
|
||||||
|
|
||||||
|
* [Add `direction` to plugin method `Setting.addItem`](https://github.com/siyuan-note/siyuan/issues/11183)
|
||||||
|
|
||||||
|
|
||||||
|
## 0.3.4 2024-02-20
|
||||||
|
|
||||||
|
* [Add plugin event bus `click-flashcard-action`](https://github.com/siyuan-note/siyuan/issues/10318)
|
||||||
|
|
||||||
|
## 0.3.3 2024-01-24
|
||||||
|
|
||||||
|
* Update dock icon class
|
||||||
|
|
||||||
|
## 0.3.2 2024-01-09
|
||||||
|
|
||||||
|
* [Add plugin `protyleOptions`](https://github.com/siyuan-note/siyuan/issues/10090)
|
||||||
|
* [Add plugin api `uninstall`](https://github.com/siyuan-note/siyuan/issues/10063)
|
||||||
|
* [Add plugin method `updateCards`](https://github.com/siyuan-note/siyuan/issues/10065)
|
||||||
|
* [Add plugin function `lockScreen`](https://github.com/siyuan-note/siyuan/issues/10063)
|
||||||
|
* [Add plugin event bus `lock-screen`](https://github.com/siyuan-note/siyuan/pull/9967)
|
||||||
|
* [Add plugin event bus `open-menu-inbox`](https://github.com/siyuan-note/siyuan/pull/9967)
|
||||||
|
|
||||||
|
|
||||||
|
## 0.3.1 2023-12-06
|
||||||
|
|
||||||
|
* [Support `Dock Plugin` and `Command Palette` on mobile](https://github.com/siyuan-note/siyuan/issues/9926)
|
||||||
|
|
||||||
|
## 0.3.0 2023-12-05
|
||||||
|
|
||||||
|
* Upgrade Siyuan to 0.9.0
|
||||||
|
* Support more platforms
|
||||||
|
|
||||||
|
## 0.2.9 2023-11-28
|
||||||
|
|
||||||
|
* [Add plugin method `openMobileFileById`](https://github.com/siyuan-note/siyuan/issues/9738)
|
||||||
|
|
||||||
|
|
||||||
|
## 0.2.8 2023-11-15
|
||||||
|
|
||||||
|
* [`resize` cannot be triggered after dragging to unpin the dock](https://github.com/siyuan-note/siyuan/issues/9640)
|
||||||
|
|
||||||
|
## 0.2.7 2023-10-31
|
||||||
|
|
||||||
|
* [Export `Constants` to plugin](https://github.com/siyuan-note/siyuan/issues/9555)
|
||||||
|
* [Add plugin `app.appId`](https://github.com/siyuan-note/siyuan/issues/9538)
|
||||||
|
* [Add plugin event bus `switch-protyle`](https://github.com/siyuan-note/siyuan/issues/9454)
|
||||||
|
|
||||||
|
## 0.2.6 2023-10-24
|
||||||
|
|
||||||
|
* [Deprecated `loaded-protyle` use `loaded-protyle-static` instead](https://github.com/siyuan-note/siyuan/issues/9468)
|
||||||
|
|
||||||
|
## 0.2.5 2023-10-10
|
||||||
|
|
||||||
|
* [Add plugin event bus `open-menu-doctree`](https://github.com/siyuan-note/siyuan/issues/9351)
|
||||||
|
|
||||||
|
## 0.2.4 2023-09-19
|
||||||
|
|
||||||
|
* Supports use in windows
|
||||||
|
* [Add plugin function `transaction`](https://github.com/siyuan-note/siyuan/issues/9172)
|
||||||
|
|
||||||
|
## 0.2.3 2023-09-05
|
||||||
|
|
||||||
|
* [Add plugin function `transaction`](https://github.com/siyuan-note/siyuan/issues/9172)
|
||||||
|
* [Plugin API add openWindow and command.globalCallback](https://github.com/siyuan-note/siyuan/issues/9032)
|
||||||
|
|
||||||
|
## 0.2.2 2023-08-29
|
||||||
|
|
||||||
|
* [Add plugin event bus `destroy-protyle`](https://github.com/siyuan-note/siyuan/issues/9033)
|
||||||
|
* [Add plugin event bus `loaded-protyle-dynamic`](https://github.com/siyuan-note/siyuan/issues/9021)
|
||||||
|
|
||||||
|
## 0.2.1 2023-08-21
|
||||||
|
|
||||||
|
* [Plugin API add getOpenedTab method](https://github.com/siyuan-note/siyuan/issues/9002)
|
||||||
|
* [Plugin API custom.fn => custom.id in openTab](https://github.com/siyuan-note/siyuan/issues/8944)
|
||||||
|
|
||||||
|
## 0.2.0 2023-08-15
|
||||||
|
|
||||||
|
* [Add plugin event bus `open-siyuan-url-plugin` and `open-siyuan-url-block`](https://github.com/siyuan-note/siyuan/pull/8927)
|
||||||
|
|
||||||
|
|
||||||
|
## 0.1.12 2023-08-01
|
||||||
|
|
||||||
|
* Upgrade siyuan to 0.7.9
|
||||||
|
|
||||||
|
## 0.1.11
|
||||||
|
|
||||||
|
* [Add `input-search` event bus to plugins](https://github.com/siyuan-note/siyuan/issues/8725)
|
||||||
|
|
||||||
|
|
||||||
|
## 0.1.10
|
||||||
|
|
||||||
|
* [Add `bind this` example for eventBus in plugins](https://github.com/siyuan-note/siyuan/issues/8668)
|
||||||
|
* [Add `open-menu-breadcrumbmore` event bus to plugins](https://github.com/siyuan-note/siyuan/issues/8666)
|
||||||
|
|
||||||
|
## 0.1.9
|
||||||
|
|
||||||
|
* [Add `open-menu-xxx` event bus for plugins ](https://github.com/siyuan-note/siyuan/issues/8617)
|
||||||
|
|
||||||
|
## 0.1.8
|
||||||
|
|
||||||
|
* [Add protyleSlash to the plugin](https://github.com/siyuan-note/siyuan/issues/8599)
|
||||||
|
* [Add plugin API protyle](https://github.com/siyuan-note/siyuan/issues/8445)
|
||||||
|
|
||||||
|
## 0.1.7
|
||||||
|
|
||||||
|
* [Support build js and json](https://github.com/siyuan-note/plugin-sample/pull/8)
|
||||||
|
|
||||||
|
## 0.1.6
|
||||||
|
|
||||||
|
* add `fetchPost` example
|
97
README.md
97
README.md
|
@ -3,27 +3,46 @@
|
||||||
|
|
||||||
[中文版](./README_zh_CN.md)
|
[中文版](./README_zh_CN.md)
|
||||||
|
|
||||||
> Consistent with [siyuan/plugin-sample](https://github.com/siyuan-note/plugin-sample) [v0.1.5](https://github.com/siyuan-note/plugin-sample/tree/v0.1.5).
|
> Consistent with [siyuan/plugin-sample](https://github.com/siyuan-note/plugin-sample) [v0.3.5](https://github.com/siyuan-note/plugin-sample/tree/v0.3.5)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
1. Using vite for packaging
|
1. Using vite for packaging
|
||||||
2. Use symbolic linking instead of putting the project into the plugins directory program development
|
2. Use symbolic linking instead of putting the project into the plugins directory program development
|
||||||
3. Built-in support for the svelte framework
|
3. Built-in support for the svelte framework
|
||||||
|
|
||||||
|
> **If don't want svelte, turn to this template**: [frostime/plugin-sample-vite](https://github.com/frostime/plugin-sample-vite)
|
||||||
|
>
|
||||||
|
> **We also provide with a vite+solidjs template**: [frostime/plugin-sample-vite-solidjs](https://github.com/frostime/plugin-sample-vite-solidjs)
|
||||||
|
|
||||||
4. Provides a github action template to automatically generate package.zip and upload to new release
|
4. Provides a github action template to automatically generate package.zip and upload to new release
|
||||||
|
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> You can also use our maintained [siyuan-plugin-cli](https://www.npmjs.com/package/siyuan-plugin-cli) command-line tool to directly build plugins in your local terminal.
|
||||||
|
>
|
||||||
|
> Additionally, for the `make-link` related commands mentioned in this plugin, all future updates will be made in [siyuan-plugin-cli](https://www.npmjs.com/package/siyuan-plugin-cli).
|
||||||
|
>
|
||||||
|
> The built-in `make-link` scripts may also be removed in a future version, in favor of using the `siyuan-plugin-cli` tool, aiming to simplify the workload of maintaining multiple plugin templates.
|
||||||
|
|
||||||
|
|
||||||
## Get started
|
## Get started
|
||||||
|
|
||||||
1. Make a copy of this repo as a template with the `Use this template` button, please note that the repo name must be the same as the plugin name, the default branch must be `main`
|
1. Use the <kbd>Use this template</kbd> button to make a copy of this repo as a template. Note that the repository name should match the plugin name, and the default branch must be `main`.
|
||||||
|
2. Clone your repository to the local development folder.
|
||||||
|
* Note: Unlike `plugin-sample`, this example does not recommend directly downloading the code to `{workspace}/data/plugins/`.
|
||||||
|
3. Install [NodeJS](https://nodejs.org/en/download) and [pnpm](https://pnpm.io/installation), then run `pnpm i` in the development folder to install the required dependencies.
|
||||||
|
4. Run the `pnpm run make-link` command to create a symbolic link (Windows developers, please refer to the "make-link on Windows" section below).
|
||||||
|
5. Execute `pnpm run dev` for real-time compilation.
|
||||||
|
6. Open the marketplace in SiYuan and enable the plugin in the download tab.
|
||||||
|
|
||||||
2. Clone your repo to a local development folder at any place
|
### Setting the Target Directory for the make-link Command
|
||||||
- Notice: we **don't recommand** you to place the folder under your `{workspace}/data/plugins/` folder.
|
|
||||||
|
|
||||||
3. Install NodeJS and pnpm, then run pnpm i in the command line under your repo folder
|
The `make-link` command creates a symbolic link that binds your `dev` directory to the SiYuan plugin directory. You can configure the target SiYuan workspace and create the symbolic link in three ways:
|
||||||
4. **Auto create development symbolic links**
|
|
||||||
- Make sure that SiYuan is running
|
1. **Select Workspace**
|
||||||
- Run `pnpm run make-link`, the script will detect all the siyuan workspace, please select the targe workspace and the script will automatically create the symbolic link under the `{workspace}/data/plugins/` folder
|
- Open SiYuan, ensure the SiYuan kernel is running.
|
||||||
|
- Run `pnpm run make-link`, the script will automatically detect all SiYuan workspaces, please manually enter the number to select the workspace.
|
||||||
```bash
|
```bash
|
||||||
>>> pnpm run make-link
|
>>> pnpm run make-link
|
||||||
> plugin-sample-vite-svelte@0.0.3 make-link H:\SrcCode\开源项目\plugin-sample-vite-svelte
|
> plugin-sample-vite-svelte@0.0.3 make-link H:\SrcCode\开源项目\plugin-sample-vite-svelte
|
||||||
|
@ -37,21 +56,24 @@
|
||||||
Got target directory: H:\Media\SiYuan/data/plugins
|
Got target directory: H:\Media\SiYuan/data/plugins
|
||||||
Done! Created symlink H:\Media\SiYuan/data/plugins/plugin-sample-vite-svelte
|
Done! Created symlink H:\Media\SiYuan/data/plugins/plugin-sample-vite-svelte
|
||||||
```
|
```
|
||||||
4. **Manually create development symbolic links**
|
2. **Manually Configure Target Directory**
|
||||||
- Open `./scripts/make_dev_link.js` file, set `targetDir` to your SiYuan plugin directory `<siyuan workspace>/data/plugins`
|
- Open the `./scripts/make_dev_link.js` file, change `targetDir` to the SiYuan plugin directory `<siyuan workspace>/data/plugins`.
|
||||||
- Run `pnpm run make-link`, succeed if following message is shown:
|
- Run the `pnpm run make-link` command. If you see a message similar to the one below, it indicates successful creation:
|
||||||
```bash
|
|
||||||
>>> pnpm run make-link
|
|
||||||
> plugin-sample-vite-svelte@0.0.1 make-link H:\SrcCode\plugin-sample-vite-svelte
|
|
||||||
> node ./scripts/make_dev_link.js
|
|
||||||
|
|
||||||
Done! Created symlink H:/SiYuanDevSpace/data/plugins/plugin-sample-vite-svelte
|
3. **Set Environment Variable to Create Symbolic Link**
|
||||||
```
|
- Set the system environment variable `SIYUAN_PLUGIN_DIR` to the path `workspace/data/plugins`.
|
||||||
|
|
||||||
5. Execute pnpm run dev for real-time compilation
|
### make-link on Windows
|
||||||
6. Open SiYuan marketplace and enable plugin in downloaded tab
|
|
||||||
|
|
||||||
> Notice: as the `make-link` script rely on the `fetch` function, please **ensure that at least version v18 of nodejs is installed** if you want to use make-link script.
|
Due to SiYuan upgrading to Go 1.23, the old version of junction links cannot be recognized normally on Windows, so it has been changed to create `dir` symbolic links.
|
||||||
|
|
||||||
|
> https://github.com/siyuan-note/siyuan/issues/12399
|
||||||
|
|
||||||
|
However, creating directory symbolic links on Windows using NodeJs may require administrator privileges. You have the following options:
|
||||||
|
|
||||||
|
1. Run `pnpm run make-link` in a command line with administrator privileges.
|
||||||
|
2. Configure Windows settings, enable developer mode in [System Settings - Update & Security - Developer Mode] then run `pnpm run make-link`.
|
||||||
|
3. Run `pnpm run make-link-win`, this command will use a PowerShell script to request administrator privileges, requiring the system to enable PowerShell script execution permissions.
|
||||||
|
|
||||||
## I18n
|
## I18n
|
||||||
|
|
||||||
|
@ -61,8 +83,11 @@ complete the following tasks:
|
||||||
* Meta information about the plugin itself, such as plugin description and readme
|
* Meta information about the plugin itself, such as plugin description and readme
|
||||||
* `description` and `readme` fields in plugin.json, and the corresponding README*.md file
|
* `description` and `readme` fields in plugin.json, and the corresponding README*.md file
|
||||||
* Text used in the plugin, such as button text and tooltips
|
* Text used in the plugin, such as button text and tooltips
|
||||||
* src/i18n/*.json language configuration files
|
* public/i18n/*.json language configuration files
|
||||||
* Use `this.i18.key` to get the text in the code
|
* Use `this.i18.key` to get the text in the code
|
||||||
|
* YAML Support
|
||||||
|
* This template specifically supports I18n based on YAML syntax, see `public/i18n/zh_CN.yaml`
|
||||||
|
* During compilation, the defined YAML files will be automatically translated into JSON files and placed in the dist or dev directory.
|
||||||
|
|
||||||
It is recommended that the plugin supports at least English and Simplified Chinese, so that more people can use it more
|
It is recommended that the plugin supports at least English and Simplified Chinese, so that more people can use it more
|
||||||
conveniently.
|
conveniently.
|
||||||
|
@ -97,7 +122,10 @@ conveniently.
|
||||||
"custom": [
|
"custom": [
|
||||||
"https://ld246.com/sponsor"
|
"https://ld246.com/sponsor"
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
"keywords": [
|
||||||
|
"sample", "示例"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -136,6 +164,7 @@ conveniently.
|
||||||
* `patreon`: Patreon name
|
* `patreon`: Patreon name
|
||||||
* `github`: GitHub login name
|
* `github`: GitHub login name
|
||||||
* `custom`: Custom sponsorship link list
|
* `custom`: Custom sponsorship link list
|
||||||
|
* `keywords`: Search keyword list, used for marketplace search function
|
||||||
|
|
||||||
## Package
|
## Package
|
||||||
|
|
||||||
|
@ -203,6 +232,8 @@ The github action is included in this sample, you can use it to publish your new
|
||||||
|
|
||||||
## How to remove svelte dependencies
|
## How to remove svelte dependencies
|
||||||
|
|
||||||
|
> Pure vite without svelte: https://github.com/frostime/plugin-sample-vite
|
||||||
|
|
||||||
This plugin is packaged in vite and provides a dependency on the svelte framework. However, in practice some developers may not want to use svelte and only want to use the vite package.
|
This plugin is packaged in vite and provides a dependency on the svelte framework. However, in practice some developers may not want to use svelte and only want to use the vite package.
|
||||||
|
|
||||||
In fact you can use this template without using svelte without any modifications at all. The compilation-related parts of the svelte compilation are loaded into the vite workflow as plugins, so even if you don't have svelte in your project, it won't matter much.
|
In fact you can use this template without using svelte without any modifications at all. The compilation-related parts of the svelte compilation are loaded into the vite workflow as plugins, so even if you don't have svelte in your project, it won't matter much.
|
||||||
|
@ -223,3 +254,25 @@ If you insist on removing all svelte dependencies so that they do not pollute yo
|
||||||
- Line 20: `svelte(),`
|
- Line 20: `svelte(),`
|
||||||
4. delete line 37 of `tsconfig.json` from `"svelte"` 5.
|
4. delete line 37 of `tsconfig.json` from `"svelte"` 5.
|
||||||
5. re-run `pnpm i`
|
5. re-run `pnpm i`
|
||||||
|
|
||||||
|
## Developer's Guide
|
||||||
|
|
||||||
|
Developers of SiYuan need to pay attention to the following specifications.
|
||||||
|
|
||||||
|
### 1. File Reading and Writing Specifications
|
||||||
|
|
||||||
|
If plugins or external extensions require direct reading or writing of files under the `data` directory, please use the kernel API to achieve this. **Do not call `fs` or other electron or nodejs APIs directly**, as it may result in data loss during synchronization and cause damage to cloud data.
|
||||||
|
|
||||||
|
Related APIs can be found at: `/api/file/*` (e.g., `/api/file/getFile`).
|
||||||
|
|
||||||
|
### 2. Daily Note Attribute Specifications
|
||||||
|
|
||||||
|
When creating a daily note in SiYuan, a custom-dailynote-yyyymmdd attribute will be automatically added to the document to distinguish it from regular documents.
|
||||||
|
|
||||||
|
> For more details, please refer to [Github Issue #9807](https://github.com/siyuan-note/siyuan/issues/9807).
|
||||||
|
|
||||||
|
Developers should pay attention to the following when developing the functionality to manually create Daily Notes:
|
||||||
|
|
||||||
|
* If `/api/filetree/createDailyNote` is called to create a daily note, the attribute will be automatically added to the document, and developers do not need to handle it separately
|
||||||
|
* If a document is created manually by developer's code (e.g., using the `createDocWithMd` API to create a daily note), please manually add this attribute to the document
|
||||||
|
|
||||||
|
|
|
@ -4,20 +4,40 @@
|
||||||
[English](./README.md)
|
[English](./README.md)
|
||||||
|
|
||||||
|
|
||||||
> 本例和 [siyuan/plugin-sample](https://github.com/siyuan-note/plugin-sample) [v0.1.5](https://github.com/siyuan-note/plugin-sample/tree/v0.1.5)
|
> 本例同 [siyuan/plugin-sample](https://github.com/siyuan-note/plugin-sample) [v0.3.5](https://github.com/siyuan-note/plugin-sample/tree/v0.3.5)
|
||||||
|
|
||||||
1. 使用 vite 打包
|
1. 使用 vite 打包
|
||||||
2. 使用符号链接、而不是把项目放到插件目录下的模式进行开发
|
2. 使用符号链接、而不是把项目放到插件目录下的模式进行开发
|
||||||
3. 内置对 svelte 框架的支持
|
3. 内置对 svelte 框架的支持
|
||||||
|
|
||||||
|
> **如果不想要 svelte,请移步这个模板:** [frostime/plugin-sample-vite](https://github.com/frostime/plugin-sample-vite)
|
||||||
|
>
|
||||||
|
> **这里还提供了一个 vite+solidjs 的模板**: [frostime/plugin-sample-vite-solidjs](https://github.com/frostime/plugin-sample-vite-solidjs)
|
||||||
|
|
||||||
4. 提供一个github action 模板,能自动生成package.zip并上传到新版本中
|
4. 提供一个github action 模板,能自动生成package.zip并上传到新版本中
|
||||||
|
|
||||||
## 开始
|
## 开始
|
||||||
|
|
||||||
1. 通过 <kbd>Use this template</kbd> 按钮将该库文件复制到你自己的库中,请注意库名必须和插件名称一致,默认分支必须为 `main`
|
1. 通过 <kbd>Use this template</kbd> 按钮将该库文件复制到你自己的库中,请注意库名和插件名称一致,默认分支必须为 `main`
|
||||||
2. 将你的库克隆到本地开发文件夹中
|
2. 将你的库克隆到本地开发文件夹中
|
||||||
* 注意: 同 `plugin-sample` 不同, 本样例并不推荐直接把代码下载到 `{workspace}/data/plugins/`
|
* 注意: 同 `plugin-sample` 不同, 本样例并不推荐直接把代码下载到 `{workspace}/data/plugins/`
|
||||||
3. 安装 [NodeJS](https://nodejs.org/en/download) 和 [pnpm](https://pnpm.io/installation),然后在开发文件夹下执行 `pnpm i` 安装所需要的依赖
|
3. 安装 [NodeJS](https://nodejs.org/en/download) 和 [pnpm](https://pnpm.io/installation),然后在开发文件夹下执行 `pnpm i` 安装所需要的依赖
|
||||||
3. **自动创建符号链接**
|
4. 运行 `pnpm run make-link` 命令创建符号链接 (Windows 下的开发者请参阅下方「Windows 下的 make-link」小节)
|
||||||
|
5. 执行 `pnpm run dev` 进行实时编译
|
||||||
|
6. 在思源中打开集市并在下载选项卡中启用插件
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> 你也可以使用我们维护的 [siyuan-plugin-cli](https://www.npmjs.com/package/siyuan-plugin-cli) 命令行工具,在本地终端中直接构建插件。
|
||||||
|
>
|
||||||
|
> 此外,对于本插件以下提及到的 `make-link` 相关的命令,后续所有更新将在 [siyuan-plugin-cli](https://www.npmjs.com/package/siyuan-plugin-cli) 中进行。
|
||||||
|
>
|
||||||
|
> 模板内置的 `make-link` 脚本也可能会在未来某个版本中移除,转而使用 `siyuan-plugin-cli` 工具,意在简化同时维护多个插件模板的工作量。
|
||||||
|
|
||||||
|
### 设置 make-link 命令的目标目录
|
||||||
|
|
||||||
|
make-link 命令会创建符号链接将你的 `dev` 目录绑定到思源的插件目录下。你可以有三种方式来配置目标的思源工作空间并创建符号链接:
|
||||||
|
|
||||||
|
1. **选择工作空间**
|
||||||
- 打开思源笔记, 确保思源内核正在运行
|
- 打开思源笔记, 确保思源内核正在运行
|
||||||
- 运行 `pnpm run make-link`, 脚本会自动检测所有思源的工作空间, 请在命令行中手动输入序号以选择工作空间
|
- 运行 `pnpm run make-link`, 脚本会自动检测所有思源的工作空间, 请在命令行中手动输入序号以选择工作空间
|
||||||
```bash
|
```bash
|
||||||
|
@ -33,21 +53,26 @@
|
||||||
Got target directory: H:\Media\SiYuan/data/plugins
|
Got target directory: H:\Media\SiYuan/data/plugins
|
||||||
Done! Created symlink H:\Media\SiYuan/data/plugins/plugin-sample-vite-svelte
|
Done! Created symlink H:\Media\SiYuan/data/plugins/plugin-sample-vite-svelte
|
||||||
```
|
```
|
||||||
4. **手动创建符号链接**
|
2. **手动配置目标目录**
|
||||||
- 打开 `./scripts/make_dev_link.js` 文件,更改 `targetDir` 为思源的插件目录 `<siyuan workspace>/data/plugins`
|
- 打开 `./scripts/make_dev_link.js` 文件,更改 `targetDir` 为思源的插件目录 `<siyuan workspace>/data/plugins`
|
||||||
- 运行 `pnpm run make-link` 命令, 如果看到类似以下的消息,说明创建成功:
|
- 运行 `pnpm run make-link` 命令, 如果看到类似以下的消息,说明创建成功:
|
||||||
```bash
|
|
||||||
❯❯❯ pnpm run make-link
|
|
||||||
> plugin-sample-vite-svelte@0.0.1 make-link H:\SrcCode\plugin-sample-vite-svelte
|
|
||||||
> node ./scripts/make_dev_link.js
|
|
||||||
|
|
||||||
Done! Created symlink H:/SiYuanDevSpace/data/plugins/plugin-sample-vite-svelte
|
3. **设置环境变量创建符号链接**
|
||||||
```
|
- 设置系统的环境变量 `SIYUAN_PLUGIN_DIR` 为 `工作空间/data/plugins` 的路径
|
||||||
5. 执行 `pnpm run dev` 进行实时编译
|
|
||||||
6. 在思源中打开集市并在下载选项卡中启用插件
|
|
||||||
|
|
||||||
|
|
||||||
> 注意由于使用的 make-link 脚本依赖于 `fetch`,所以如果想要使用 make-link **请保证至少安装 v18 版本的 nodejs**
|
### Windows 下的 make-link
|
||||||
|
|
||||||
|
由于思源升级了 Go 1.23,旧版创建的 junction link 在 windows 下无法被正常识别,故而改为创建 `dir` 符号链接。
|
||||||
|
|
||||||
|
> https://github.com/siyuan-note/siyuan/issues/12399
|
||||||
|
|
||||||
|
|
||||||
|
不过 Windows 下使用 NodeJs 创建目录符号链接可能需要管理员权限,你可以有如下几种选择:
|
||||||
|
|
||||||
|
1. 在具有管理员权限的命令行中运行 `pnpm run make-link`
|
||||||
|
2. 配置 Windows 设置,在 [系统设置-更新与安全-开发者模式] 中启用开发者模式,然后再运行 `pnpm run make-link`
|
||||||
|
3. 运行 `pnpm run make-link-win`,该命令会使用一个 powershell 脚本来寻求管理员权限,需要在系统中开启 PowerShell 脚本执行权限
|
||||||
|
|
||||||
|
|
||||||
## 国际化
|
## 国际化
|
||||||
|
@ -57,9 +82,12 @@
|
||||||
* 插件自身的元信息,比如插件描述和自述文件
|
* 插件自身的元信息,比如插件描述和自述文件
|
||||||
* plugin.json 中的 `description` 和 `readme` 字段,以及对应的 README*.md 文件
|
* plugin.json 中的 `description` 和 `readme` 字段,以及对应的 README*.md 文件
|
||||||
* 插件中使用的文本,比如按钮文字和提示信息
|
* 插件中使用的文本,比如按钮文字和提示信息
|
||||||
* src/i18n/*.json 语言配置文件
|
* public/i18n/*.json 语言配置文件
|
||||||
* 代码中使用 `this.i18.key` 获取文本
|
* 代码中使用 `this.i18.key` 获取文本
|
||||||
* 最后在 plugin.json 中的 `i18n` 字段中声明该插件支持的语言
|
* 最后在 plugin.json 中的 `i18n` 字段中声明该插件支持的语言
|
||||||
|
* yaml 支持
|
||||||
|
* 本模板特别支持基于 Yaml 语法的 I18n,见 `public/i18n/zh_CN.yaml`
|
||||||
|
* 编译时,会自动把定义的 yaml 文件翻译成 json 文件放到 dist 或 dev 目录下
|
||||||
|
|
||||||
建议插件至少支持英文和简体中文,这样可以方便更多人使用。
|
建议插件至少支持英文和简体中文,这样可以方便更多人使用。
|
||||||
|
|
||||||
|
@ -93,7 +121,10 @@
|
||||||
"custom": [
|
"custom": [
|
||||||
"https://ld246.com/sponsor"
|
"https://ld246.com/sponsor"
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
"keywords": [
|
||||||
|
"sample", "示例"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -131,6 +162,7 @@
|
||||||
* `patreon`:Patreon 名称
|
* `patreon`:Patreon 名称
|
||||||
* `github`:GitHub 登录名
|
* `github`:GitHub 登录名
|
||||||
* `custom`:自定义赞助链接列表
|
* `custom`:自定义赞助链接列表
|
||||||
|
* `keywords`:搜索关键字列表,用于集市搜索功能
|
||||||
|
|
||||||
## 打包
|
## 打包
|
||||||
|
|
||||||
|
@ -193,6 +225,8 @@ PR 社区集市仓库。
|
||||||
|
|
||||||
## 如何去掉 svelte 依赖
|
## 如何去掉 svelte 依赖
|
||||||
|
|
||||||
|
> 无 Svelte 依赖版: https://github.com/frostime/plugin-sample-vite
|
||||||
|
|
||||||
本插件使用 vite 打包,并提供了 svelte 框架依赖。不过实际情况下可能有些开发者并不想要 svelte,只希望使用 vite 打包。
|
本插件使用 vite 打包,并提供了 svelte 框架依赖。不过实际情况下可能有些开发者并不想要 svelte,只希望使用 vite 打包。
|
||||||
|
|
||||||
实际上你可以完全不做任何修改,就可以在不使用 svelte 的前提下使用这个模板。与 svelte 编译的编译相关的部分是以插件的形式载入到 vite 的工作流中,所以即使你的项目里面没有 svelte,也不会有太大的影响。
|
实际上你可以完全不做任何修改,就可以在不使用 svelte 的前提下使用这个模板。与 svelte 编译的编译相关的部分是以插件的形式载入到 vite 的工作流中,所以即使你的项目里面没有 svelte,也不会有太大的影响。
|
||||||
|
@ -213,3 +247,25 @@ PR 社区集市仓库。
|
||||||
- 第二十行: `svelte(),`
|
- 第二十行: `svelte(),`
|
||||||
4. 删掉 `tsconfig.json` 中 37 行 `"svelte"`
|
4. 删掉 `tsconfig.json` 中 37 行 `"svelte"`
|
||||||
5. 重新执行 `pnpm i`
|
5. 重新执行 `pnpm i`
|
||||||
|
|
||||||
|
|
||||||
|
## 开发者须知
|
||||||
|
|
||||||
|
思源开发者需注意以下规范。
|
||||||
|
|
||||||
|
### 1. 读写文件规范
|
||||||
|
|
||||||
|
插件或者外部扩展如果有直接读取或者写入 data 下文件的需求,请通过调用内核 API 来实现,**不要自行调用 `fs` 或者其他 electron、nodejs API**,否则可能会导致数据同步时分块丢失,造成云端数据损坏。
|
||||||
|
|
||||||
|
相关 API 见 `/api/file/*`(例如 `/api/file/getFile` 等)。
|
||||||
|
|
||||||
|
### 2. Daily Note 属性规范
|
||||||
|
|
||||||
|
思源在创建日记的时候会自动为文档添加 custom-dailynote-yyyymmdd 属性,以方便将日记文档同普通文档区分。
|
||||||
|
|
||||||
|
> 详情请见 [Github Issue #9807](https://github.com/siyuan-note/siyuan/issues/9807)。
|
||||||
|
|
||||||
|
开发者在开发手动创建 Daily Note 的功能时请注意:
|
||||||
|
|
||||||
|
* 如果调用了 `/api/filetree/createDailyNote` 创建日记,那么文档会自动添加这个属性,无需开发者特别处理
|
||||||
|
* 如果是开发者代码手动创建文档(例如使用 `createDocWithMd` API 创建日记),请手动为文档添加该属性
|
||||||
|
|
38
package.json
38
package.json
|
@ -1,33 +1,37 @@
|
||||||
{
|
{
|
||||||
"name": "plugin-sample-vite-svelte",
|
"name": "plugin-sample-vite-svelte",
|
||||||
"version": "0.1.5",
|
"version": "0.3.6",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "",
|
"description": "This is a sample plugin based on vite and svelte for Siyuan (https://b3log.org/siyuan)",
|
||||||
"repository": "",
|
"repository": "",
|
||||||
"homepage": "",
|
"homepage": "",
|
||||||
"author": "",
|
"author": "frostime",
|
||||||
"license": "GPL-3.0",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"make-link": "node --no-warnings ./scripts/make_dev_link.js",
|
"dev": "cross-env NODE_ENV=development VITE_SOURCEMAP=inline vite build --watch",
|
||||||
"dev": "vite build --watch",
|
"build": "cross-env NODE_ENV=production vite build",
|
||||||
"build": "vite build"
|
"make-link": "node --no-warnings ./scripts/make_dev_link.js",
|
||||||
|
"make-link-win": "powershell.exe -NoProfile -ExecutionPolicy Bypass -File ./scripts/elevate.ps1 -scriptPath ./scripts/make_dev_link.js",
|
||||||
|
"update-version": "node --no-warnings ./scripts/update_version.js",
|
||||||
|
"make-install": "vite build && node --no-warnings ./scripts/make_install.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/vite-plugin-svelte": "^2.0.3",
|
"@sveltejs/vite-plugin-svelte": "^3.1.0",
|
||||||
"@tsconfig/svelte": "^4.0.1",
|
"@tsconfig/svelte": "^4.0.1",
|
||||||
"@types/node": "^20.2.0",
|
"@types/node": "^20.3.0",
|
||||||
"eslint": "^8.42.0",
|
"cross-env": "^7.0.3",
|
||||||
"fast-glob": "^3.2.12",
|
"fast-glob": "^3.2.12",
|
||||||
"glob": "^7.2.3",
|
"glob": "^10.0.0",
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
"minimist": "^1.2.8",
|
"minimist": "^1.2.8",
|
||||||
"rollup-plugin-livereload": "^2.0.5",
|
"rollup-plugin-livereload": "^2.0.5",
|
||||||
"sass": "^1.62.1",
|
"sass": "^1.63.3",
|
||||||
"siyuan": "0.7.4",
|
"siyuan": "1.0.4",
|
||||||
"svelte": "^3.57.0",
|
"svelte": "^4.2.19",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "^5.0.4",
|
"typescript": "^5.1.3",
|
||||||
"vite": "^4.3.7",
|
"vite": "^5.2.9",
|
||||||
"vite-plugin-static-copy": "^0.15.0",
|
"vite-plugin-static-copy": "^1.0.2",
|
||||||
"vite-plugin-zip-pack": "^1.0.5"
|
"vite-plugin-zip-pack": "^1.0.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
31
plugin.json
31
plugin.json
|
@ -2,10 +2,24 @@
|
||||||
"name": "plugin-sample-vite-svelte",
|
"name": "plugin-sample-vite-svelte",
|
||||||
"author": "frostime",
|
"author": "frostime",
|
||||||
"url": "https://github.com/siyuan-note/plugin-sample-vite-svelte",
|
"url": "https://github.com/siyuan-note/plugin-sample-vite-svelte",
|
||||||
"version": "0.1.5",
|
"version": "0.3.6",
|
||||||
"minAppVersion": "2.9.0",
|
"minAppVersion": "3.0.12",
|
||||||
"backends": ["all"],
|
"backends": [
|
||||||
"frontends": ["all"],
|
"windows",
|
||||||
|
"linux",
|
||||||
|
"darwin",
|
||||||
|
"ios",
|
||||||
|
"android",
|
||||||
|
"harmony",
|
||||||
|
"docker"
|
||||||
|
],
|
||||||
|
"frontends": [
|
||||||
|
"desktop",
|
||||||
|
"mobile",
|
||||||
|
"browser-desktop",
|
||||||
|
"browser-mobile",
|
||||||
|
"desktop-window"
|
||||||
|
],
|
||||||
"displayName": {
|
"displayName": {
|
||||||
"en_US": "Plugin sample with vite and svelte",
|
"en_US": "Plugin sample with vite and svelte",
|
||||||
"zh_CN": "插件样例 vite + svelte 版"
|
"zh_CN": "插件样例 vite + svelte 版"
|
||||||
|
@ -20,7 +34,12 @@
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"custom": [
|
"custom": [
|
||||||
"https://afdian.net/a/frostime"
|
""
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
"keywords": [
|
||||||
|
"plugin",
|
||||||
|
"sample",
|
||||||
|
"插件样例"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
12
public/i18n/README.md
Normal file
12
public/i18n/README.md
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
思源支持的 i18n 文件范围,可以在控制台 `siyuan.config.langs` 中查看。以下是目前(2024-10-24)支持的语言方案:
|
||||||
|
|
||||||
|
The range of i18n files supported by SiYuan can be viewed in the console under `siyuan.config.langs`. Below are the language schemes currently supported as of now (October 24, 2024) :
|
||||||
|
|
||||||
|
```js
|
||||||
|
>>> siyuan.config.langs.map( lang => lang.name)
|
||||||
|
['de_DE', 'en_US', 'es_ES', 'fr_FR', 'he_IL', 'it_IT', 'ja_JP', 'pl_PL', 'ru_RU', 'zh_CHT', 'zh_CN']
|
||||||
|
```
|
||||||
|
|
||||||
|
在插件开发中,默认使用 JSON 格式作为国际化(i18n)的载体文件。如果您更喜欢使用 YAML 语法,可以将 JSON 文件替换为 YAML 文件(例如 `en_US.yaml`),并在其中编写 i18n 文本。本模板提供了相关的 Vite 插件,可以在编译时自动将 YAML 文件转换为 JSON 文件(请参见 `/yaml-plugin.js`)。本 MD 文件 和 YAML 文件会在 `npm run build` 时自动从 `dist` 目录下删除,仅保留必要的 JSON 文件共插件系统使用。
|
||||||
|
|
||||||
|
In plugin development, JSON format is used by default as the carrier file for internationalization (i18n). If you prefer to use YAML syntax, you can replace the JSON file with a YAML file (e.g., `en_US.yaml`) and write the i18n text within it. This template provides a related Vite plugin that can automatically convert YAML files to JSON files during the compilation process (see `/yaml-plugin.js`). This markdown file and YAML files will be automatically removed from the `dist` directory during `npm run build`, leaving only the necessary JSON files for plugin system to use.
|
|
@ -8,8 +8,13 @@
|
||||||
"showDialog": "Show dialog",
|
"showDialog": "Show dialog",
|
||||||
"removedData": "Data deleted",
|
"removedData": "Data deleted",
|
||||||
"confirmRemove": "Confirm to delete the data in ${name}?",
|
"confirmRemove": "Confirm to delete the data in ${name}?",
|
||||||
|
"insertEmoji": "Insert Emoji",
|
||||||
|
"removeSpace": "Remove Space",
|
||||||
|
"getTab": "Print out all opened custom tabs in the debugger",
|
||||||
"name": "SiYuan",
|
"name": "SiYuan",
|
||||||
"hello": {
|
"hello": {
|
||||||
"makesure": "Before using this template, please read the <a href=\"https://github.com/siyuan-note/plugin-sample\">offical sample</a>, make sure that you've known about the pipeline for plugin developing."
|
"makesure": "Before using this template, please read the <a href=\"https://github.com/siyuan-note/plugin-sample\">offical sample</a>, make sure that you've known about the pipeline for plugin developing."
|
||||||
}
|
},
|
||||||
|
"hintTitle":"About",
|
||||||
|
"hintDesc":"<a href='https://github.com/siyuan-note/plugin-sample-vite-svelte'>plugin-sample-vite-svelte</a><br>@frostime<br>@88250<br>@zxkmm"
|
||||||
}
|
}
|
|
@ -8,8 +8,13 @@
|
||||||
"showDialog": "弹出一个对话框",
|
"showDialog": "弹出一个对话框",
|
||||||
"removedData": "数据已删除",
|
"removedData": "数据已删除",
|
||||||
"confirmRemove": "确认删除 ${name} 中的数据?",
|
"confirmRemove": "确认删除 ${name} 中的数据?",
|
||||||
|
"insertEmoji": "插入表情",
|
||||||
|
"removeSpace": "移除空格",
|
||||||
|
"getTab": "在日志中打印出已打开的所有自定义页签",
|
||||||
"name": "思源",
|
"name": "思源",
|
||||||
"hello": {
|
"hello": {
|
||||||
"makesure": "使用这个模板之前,请阅读<a href=\"https://github.com/siyuan-note/plugin-sample\">官方教程</a>, 确保自己已经理解了插件的基本开发流程。"
|
"makesure": "使用这个模板之前,请阅读<a href=\"https://github.com/siyuan-note/plugin-sample\">官方教程</a>, 确保自己已经理解了插件的基本开发流程。"
|
||||||
}
|
},
|
||||||
|
"hintTitle": "关于",
|
||||||
|
"hintDesc": "<a href='https://github.com/siyuan-note/plugin-sample-vite-svelte'>🔗 plugin-sample-vite-svelte</a><br>💻 @frostime<br>💻 @88250<br>💻 @zxkmm"
|
||||||
}
|
}
|
24
scripts/elevate.ps1
Normal file
24
scripts/elevate.ps1
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# Copyright (c) 2024 by frostime. All Rights Reserved.
|
||||||
|
# @Author : frostime
|
||||||
|
# @Date : 2024-09-06 19:15:53
|
||||||
|
# @FilePath : /scripts/elevate.ps1
|
||||||
|
# @LastEditTime : 2024-09-06 19:39:13
|
||||||
|
# @Description : Force to elevate the script to admin privilege.
|
||||||
|
|
||||||
|
param (
|
||||||
|
[string]$scriptPath
|
||||||
|
)
|
||||||
|
|
||||||
|
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
$projectDir = Split-Path -Parent $scriptDir
|
||||||
|
|
||||||
|
if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
|
||||||
|
$args = "-NoProfile -ExecutionPolicy Bypass -File `"" + $MyInvocation.MyCommand.Path + "`" -scriptPath `"" + $scriptPath + "`""
|
||||||
|
Start-Process powershell.exe -Verb RunAs -ArgumentList $args -WorkingDirectory $projectDir
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
Set-Location -Path $projectDir
|
||||||
|
& node $scriptPath
|
||||||
|
|
||||||
|
pause
|
|
@ -1,173 +1,66 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 by frostime. All Rights Reserved.
|
||||||
|
* @Author : frostime
|
||||||
|
* @Date : 2023-07-15 15:31:31
|
||||||
|
* @FilePath : /scripts/make_dev_link.js
|
||||||
|
* @LastEditTime : 2024-09-06 18:13:53
|
||||||
|
* @Description :
|
||||||
|
*/
|
||||||
|
// make_dev_link.js
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import http from 'node:http';
|
import { log, error, getSiYuanDir, chooseTarget, getThisPluginName, makeSymbolicLink } from './utils.js';
|
||||||
import readline from 'node:readline';
|
|
||||||
|
|
||||||
|
|
||||||
//************************************ Write you dir here ************************************
|
|
||||||
|
|
||||||
//Please write the "workspace/data/plugins" directory here
|
|
||||||
//请在这里填写你的 "workspace/data/plugins" 目录
|
|
||||||
let targetDir = '';
|
let targetDir = '';
|
||||||
//Like this
|
|
||||||
// let targetDir = `H:\\SiYuanDevSpace\\data\\plugins`;
|
|
||||||
//********************************************************************************************
|
|
||||||
|
|
||||||
const log = (info) => console.log(`\x1B[36m%s\x1B[0m`, info);
|
|
||||||
const error = (info) => console.log(`\x1B[31m%s\x1B[0m`, info);
|
|
||||||
|
|
||||||
let POST_HEADER = {
|
|
||||||
// "Authorization": `Token ${token}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
}
|
|
||||||
|
|
||||||
async function myfetch(url, options) {
|
|
||||||
//使用 http 模块,从而兼容那些不支持 fetch 的 nodejs 版本
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let req = http.request(url, options, (res) => {
|
|
||||||
let data = '';
|
|
||||||
res.on('data', (chunk) => {
|
|
||||||
data += chunk;
|
|
||||||
});
|
|
||||||
res.on('end', () => {
|
|
||||||
resolve({
|
|
||||||
ok: true,
|
|
||||||
status: res.statusCode,
|
|
||||||
json: () => JSON.parse(data)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
req.on('error', (e) => {
|
|
||||||
reject(e);
|
|
||||||
});
|
|
||||||
req.end();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getSiYuanDir() {
|
|
||||||
let url = 'http://127.0.0.1:6806/api/system/getWorkspaces';
|
|
||||||
let conf = {};
|
|
||||||
try {
|
|
||||||
let response = await myfetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: POST_HEADER
|
|
||||||
});
|
|
||||||
if (response.ok) {
|
|
||||||
conf = await response.json();
|
|
||||||
} else {
|
|
||||||
error(`HTTP-Error: ${response.status}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
error("Error:", e);
|
|
||||||
error("Please make sure SiYuan is running!!!");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return conf.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function chooseTarget(workspaces) {
|
|
||||||
let count = workspaces.length;
|
|
||||||
log(`Got ${count} SiYuan ${count > 1 ? 'workspaces' : 'workspace'}`)
|
|
||||||
for (let i = 0; i < workspaces.length; i++) {
|
|
||||||
log(`[${i}] ${workspaces[i].path}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count == 1) {
|
|
||||||
return `${workspaces[0].path}/data/plugins`;
|
|
||||||
} else {
|
|
||||||
const rl = readline.createInterface({
|
|
||||||
input: process.stdin,
|
|
||||||
output: process.stdout
|
|
||||||
});
|
|
||||||
let index = await new Promise((resolve, reject) => {
|
|
||||||
rl.question(`Please select a workspace[0-${count-1}]: `, (answer) => {
|
|
||||||
resolve(answer);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
rl.close();
|
|
||||||
return `${workspaces[index].path}/data/plugins`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Get the parent directory to install the plugin
|
||||||
|
*/
|
||||||
|
log('>>> Try to visit constant "targetDir" in make_dev_link.js...');
|
||||||
if (targetDir === '') {
|
if (targetDir === '') {
|
||||||
log('"targetDir" is empty, try to get SiYuan directory automatically....')
|
log('>>> Constant "targetDir" is empty, try to get SiYuan directory automatically....');
|
||||||
let res = await getSiYuanDir();
|
let res = await getSiYuanDir();
|
||||||
|
|
||||||
if (res === null) {
|
if (!res || res.length === 0) {
|
||||||
log('Failed! You can set the plugin directory in scripts/make_dev_link.js and try again');
|
log('>>> Can not get SiYuan directory automatically, try to visit environment variable "SIYUAN_PLUGIN_DIR"....');
|
||||||
process.exit(1);
|
let env = process.env?.SIYUAN_PLUGIN_DIR;
|
||||||
|
if (env) {
|
||||||
|
targetDir = env;
|
||||||
|
log(`\tGot target directory from environment variable "SIYUAN_PLUGIN_DIR": ${targetDir}`);
|
||||||
|
} else {
|
||||||
|
error('\tCan not get SiYuan directory from environment variable "SIYUAN_PLUGIN_DIR", failed!');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
targetDir = await chooseTarget(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
targetDir = await chooseTarget(res);
|
log(`>>> Successfully got target directory: ${targetDir}`);
|
||||||
log(`Got target directory: ${targetDir}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check
|
|
||||||
if (!fs.existsSync(targetDir)) {
|
if (!fs.existsSync(targetDir)) {
|
||||||
error(`Failed! plugin directory not exists: "${targetDir}"`);
|
error(`Failed! Plugin directory not exists: "${targetDir}"`);
|
||||||
error(`Please set the plugin directory in scripts/make_dev_link.js`);
|
error('Please set the plugin directory in scripts/make_dev_link.js');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
//check if plugin.json exists
|
* 2. The dev directory, which contains the compiled plugin code
|
||||||
if (!fs.existsSync('./plugin.json')) {
|
*/
|
||||||
//change dir to parent
|
|
||||||
process.chdir('../');
|
|
||||||
if (!fs.existsSync('./plugin.json')) {
|
|
||||||
error('Failed! plugin.json not found');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//load plugin.json
|
|
||||||
const plugin = JSON.parse(fs.readFileSync('./plugin.json', 'utf8'));
|
|
||||||
const name = plugin?.name;
|
|
||||||
if (!name || name === '') {
|
|
||||||
error('Failed! Please set plugin name in plugin.json');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
//dev directory
|
|
||||||
const devDir = `${process.cwd()}/dev`;
|
const devDir = `${process.cwd()}/dev`;
|
||||||
//mkdir if not exists
|
|
||||||
if (!fs.existsSync(devDir)) {
|
if (!fs.existsSync(devDir)) {
|
||||||
fs.mkdirSync(devDir);
|
fs.mkdirSync(devDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
function cmpPath(path1, path2) {
|
|
||||||
path1 = path1.replace(/\\/g, '/');
|
|
||||||
path2 = path2.replace(/\\/g, '/');
|
|
||||||
// sepertor at tail
|
|
||||||
if (path1[path1.length - 1] !== '/') {
|
|
||||||
path1 += '/';
|
|
||||||
}
|
|
||||||
if (path2[path2.length - 1] !== '/') {
|
|
||||||
path2 += '/';
|
|
||||||
}
|
|
||||||
return path1 === path2;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3. The target directory to make symbolic link to dev directory
|
||||||
|
*/
|
||||||
|
const name = getThisPluginName();
|
||||||
|
if (name === null) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
const targetPath = `${targetDir}/${name}`;
|
const targetPath = `${targetDir}/${name}`;
|
||||||
//如果已经存在,就退出
|
|
||||||
if (fs.existsSync(targetPath)) {
|
|
||||||
let isSymbol = fs.lstatSync(targetPath).isSymbolicLink();
|
|
||||||
|
|
||||||
if (isSymbol) {
|
|
||||||
let srcPath = fs.readlinkSync(targetPath);
|
|
||||||
|
|
||||||
if (cmpPath(srcPath, devDir)) {
|
|
||||||
log(`Good! ${targetPath} is already linked to ${devDir}`);
|
|
||||||
} else {
|
|
||||||
error(`Error! Already exists symbolic link ${targetPath}\nBut it links to ${srcPath}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
error(`Failed! ${targetPath} already exists and is not a symbolic link`);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
//创建软链接
|
|
||||||
fs.symlinkSync(devDir, targetPath, 'junction');
|
|
||||||
log(`Done! Created symlink ${targetPath}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 4. Make symbolic link
|
||||||
|
*/
|
||||||
|
makeSymbolicLink(devDir, targetPath);
|
||||||
|
|
57
scripts/make_install.js
Normal file
57
scripts/make_install.js
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 by frostime. All Rights Reserved.
|
||||||
|
* @Author : frostime
|
||||||
|
* @Date : 2024-03-28 20:03:59
|
||||||
|
* @FilePath : /scripts/make_install.js
|
||||||
|
* @LastEditTime : 2024-09-06 18:08:19
|
||||||
|
* @Description :
|
||||||
|
*/
|
||||||
|
// make_install.js
|
||||||
|
import fs from 'fs';
|
||||||
|
import { log, error, getSiYuanDir, chooseTarget, copyDirectory, getThisPluginName } from './utils.js';
|
||||||
|
|
||||||
|
let targetDir = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Get the parent directory to install the plugin
|
||||||
|
*/
|
||||||
|
log('>>> Try to visit constant "targetDir" in make_install.js...');
|
||||||
|
if (targetDir === '') {
|
||||||
|
log('>>> Constant "targetDir" is empty, try to get SiYuan directory automatically....');
|
||||||
|
let res = await getSiYuanDir();
|
||||||
|
|
||||||
|
if (res === null || res === undefined || res.length === 0) {
|
||||||
|
error('>>> Can not get SiYuan directory automatically');
|
||||||
|
process.exit(1);
|
||||||
|
} else {
|
||||||
|
targetDir = await chooseTarget(res);
|
||||||
|
}
|
||||||
|
log(`>>> Successfully got target directory: ${targetDir}`);
|
||||||
|
}
|
||||||
|
if (!fs.existsSync(targetDir)) {
|
||||||
|
error(`Failed! Plugin directory not exists: "${targetDir}"`);
|
||||||
|
error('Please set the plugin directory in scripts/make_install.js');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 2. The dist directory, which contains the compiled plugin code
|
||||||
|
*/
|
||||||
|
const distDir = `${process.cwd()}/dist`;
|
||||||
|
if (!fs.existsSync(distDir)) {
|
||||||
|
fs.mkdirSync(distDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3. The target directory to install the plugin
|
||||||
|
*/
|
||||||
|
const name = getThisPluginName();
|
||||||
|
if (name === null) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
const targetPath = `${targetDir}/${name}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 4. Copy the compiled plugin code to the target directory
|
||||||
|
*/
|
||||||
|
copyDirectory(distDir, targetPath);
|
141
scripts/update_version.js
Normal file
141
scripts/update_version.js
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
// const fs = require('fs');
|
||||||
|
// const path = require('path');
|
||||||
|
// const readline = require('readline');
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import readline from 'node:readline';
|
||||||
|
|
||||||
|
// Utility to read JSON file
|
||||||
|
function readJsonFile(filePath) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fs.readFile(filePath, 'utf8', (err, data) => {
|
||||||
|
if (err) return reject(err);
|
||||||
|
try {
|
||||||
|
const jsonData = JSON.parse(data);
|
||||||
|
resolve(jsonData);
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility to write JSON file
|
||||||
|
function writeJsonFile(filePath, jsonData) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fs.writeFile(filePath, JSON.stringify(jsonData, null, 2), 'utf8', (err) => {
|
||||||
|
if (err) return reject(err);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility to prompt the user for input
|
||||||
|
function promptUser(query) {
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout
|
||||||
|
});
|
||||||
|
return new Promise((resolve) => rl.question(query, (answer) => {
|
||||||
|
rl.close();
|
||||||
|
resolve(answer);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to parse the version string
|
||||||
|
function parseVersion(version) {
|
||||||
|
const [major, minor, patch] = version.split('.').map(Number);
|
||||||
|
return { major, minor, patch };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to auto-increment version parts
|
||||||
|
function incrementVersion(version, type) {
|
||||||
|
let { major, minor, patch } = parseVersion(version);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'major':
|
||||||
|
major++;
|
||||||
|
minor = 0;
|
||||||
|
patch = 0;
|
||||||
|
break;
|
||||||
|
case 'minor':
|
||||||
|
minor++;
|
||||||
|
patch = 0;
|
||||||
|
break;
|
||||||
|
case 'patch':
|
||||||
|
patch++;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${major}.${minor}.${patch}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main script
|
||||||
|
(async function () {
|
||||||
|
try {
|
||||||
|
const pluginJsonPath = path.join(process.cwd(), 'plugin.json');
|
||||||
|
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
||||||
|
|
||||||
|
// Read both JSON files
|
||||||
|
const pluginData = await readJsonFile(pluginJsonPath);
|
||||||
|
const packageData = await readJsonFile(packageJsonPath);
|
||||||
|
|
||||||
|
// Get the current version from both files (assuming both have the same version)
|
||||||
|
const currentVersion = pluginData.version || packageData.version;
|
||||||
|
console.log(`\n🌟 Current version: \x1b[36m${currentVersion}\x1b[0m\n`);
|
||||||
|
|
||||||
|
// Calculate potential new versions for auto-update
|
||||||
|
const newPatchVersion = incrementVersion(currentVersion, 'patch');
|
||||||
|
const newMinorVersion = incrementVersion(currentVersion, 'minor');
|
||||||
|
const newMajorVersion = incrementVersion(currentVersion, 'major');
|
||||||
|
|
||||||
|
// Prompt the user with formatted options
|
||||||
|
console.log('🔄 How would you like to update the version?\n');
|
||||||
|
console.log(` 1️⃣ Auto update \x1b[33mpatch\x1b[0m version (new version: \x1b[32m${newPatchVersion}\x1b[0m)`);
|
||||||
|
console.log(` 2️⃣ Auto update \x1b[33mminor\x1b[0m version (new version: \x1b[32m${newMinorVersion}\x1b[0m)`);
|
||||||
|
console.log(` 3️⃣ Auto update \x1b[33mmajor\x1b[0m version (new version: \x1b[32m${newMajorVersion}\x1b[0m)`);
|
||||||
|
console.log(` 4️⃣ Input version \x1b[33mmanually\x1b[0m`);
|
||||||
|
// Press 0 to skip version update
|
||||||
|
console.log(' 0️⃣ Quit without updating\n');
|
||||||
|
|
||||||
|
const updateChoice = await promptUser('👉 Please choose (1/2/3/4): ');
|
||||||
|
|
||||||
|
let newVersion;
|
||||||
|
|
||||||
|
switch (updateChoice.trim()) {
|
||||||
|
case '1':
|
||||||
|
newVersion = newPatchVersion;
|
||||||
|
break;
|
||||||
|
case '2':
|
||||||
|
newVersion = newMinorVersion;
|
||||||
|
break;
|
||||||
|
case '3':
|
||||||
|
newVersion = newMajorVersion;
|
||||||
|
break;
|
||||||
|
case '4':
|
||||||
|
newVersion = await promptUser('✍️ Please enter the new version (in a.b.c format): ');
|
||||||
|
break;
|
||||||
|
case '0':
|
||||||
|
console.log('\n🛑 Skipping version update.');
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
console.log('\n❌ Invalid option, no version update.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the version in both plugin.json and package.json
|
||||||
|
pluginData.version = newVersion;
|
||||||
|
packageData.version = newVersion;
|
||||||
|
|
||||||
|
// Write the updated JSON back to files
|
||||||
|
await writeJsonFile(pluginJsonPath, pluginData);
|
||||||
|
await writeJsonFile(packageJsonPath, packageData);
|
||||||
|
|
||||||
|
console.log(`\n✅ Version successfully updated to: \x1b[32m${newVersion}\x1b[0m\n`);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error:', error);
|
||||||
|
}
|
||||||
|
})();
|
182
scripts/utils.js
Normal file
182
scripts/utils.js
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 by frostime. All Rights Reserved.
|
||||||
|
* @Author : frostime
|
||||||
|
* @Date : 2024-09-06 17:42:57
|
||||||
|
* @FilePath : /scripts/utils.js
|
||||||
|
* @LastEditTime : 2024-09-06 19:23:12
|
||||||
|
* @Description :
|
||||||
|
*/
|
||||||
|
// common.js
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import http from 'node:http';
|
||||||
|
import readline from 'node:readline';
|
||||||
|
|
||||||
|
// Logging functions
|
||||||
|
export const log = (info) => console.log(`\x1B[36m%s\x1B[0m`, info);
|
||||||
|
export const error = (info) => console.log(`\x1B[31m%s\x1B[0m`, info);
|
||||||
|
|
||||||
|
// HTTP POST headers
|
||||||
|
export const POST_HEADER = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fetch function compatible with older Node.js versions
|
||||||
|
export async function myfetch(url, options) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let req = http.request(url, options, (res) => {
|
||||||
|
let data = '';
|
||||||
|
res.on('data', (chunk) => {
|
||||||
|
data += chunk;
|
||||||
|
});
|
||||||
|
res.on('end', () => {
|
||||||
|
resolve({
|
||||||
|
ok: true,
|
||||||
|
status: res.statusCode,
|
||||||
|
json: () => JSON.parse(data)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
req.on('error', (e) => {
|
||||||
|
reject(e);
|
||||||
|
});
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch SiYuan workspaces from port 6806
|
||||||
|
* @returns {Promise<Object | null>}
|
||||||
|
*/
|
||||||
|
export async function getSiYuanDir() {
|
||||||
|
let url = 'http://127.0.0.1:6806/api/system/getWorkspaces';
|
||||||
|
let conf = {};
|
||||||
|
try {
|
||||||
|
let response = await myfetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: POST_HEADER
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
conf = await response.json();
|
||||||
|
} else {
|
||||||
|
error(`\tHTTP-Error: ${response.status}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
error(`\tError: ${e}`);
|
||||||
|
error("\tPlease make sure SiYuan is running!!!");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return conf?.data; // 保持原始返回值
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Choose target workspace
|
||||||
|
* @param {{path: string}[]} workspaces
|
||||||
|
* @returns {string} The path of the selected workspace
|
||||||
|
*/
|
||||||
|
export async function chooseTarget(workspaces) {
|
||||||
|
let count = workspaces.length;
|
||||||
|
log(`>>> Got ${count} SiYuan ${count > 1 ? 'workspaces' : 'workspace'}`);
|
||||||
|
workspaces.forEach((workspace, i) => {
|
||||||
|
log(`\t[${i}] ${workspace.path}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (count === 1) {
|
||||||
|
return `${workspaces[0].path}/data/plugins`;
|
||||||
|
} else {
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout
|
||||||
|
});
|
||||||
|
let index = await new Promise((resolve) => {
|
||||||
|
rl.question(`\tPlease select a workspace[0-${count - 1}]: `, (answer) => {
|
||||||
|
resolve(answer);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
rl.close();
|
||||||
|
return `${workspaces[index].path}/data/plugins`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if two paths are the same
|
||||||
|
* @param {string} path1
|
||||||
|
* @param {string} path2
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function cmpPath(path1, path2) {
|
||||||
|
path1 = path1.replace(/\\/g, '/');
|
||||||
|
path2 = path2.replace(/\\/g, '/');
|
||||||
|
if (path1[path1.length - 1] !== '/') {
|
||||||
|
path1 += '/';
|
||||||
|
}
|
||||||
|
if (path2[path2.length - 1] !== '/') {
|
||||||
|
path2 += '/';
|
||||||
|
}
|
||||||
|
return path1 === path2;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getThisPluginName() {
|
||||||
|
if (!fs.existsSync('./plugin.json')) {
|
||||||
|
process.chdir('../');
|
||||||
|
if (!fs.existsSync('./plugin.json')) {
|
||||||
|
error('Failed! plugin.json not found');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const plugin = JSON.parse(fs.readFileSync('./plugin.json', 'utf8'));
|
||||||
|
const name = plugin?.name;
|
||||||
|
if (!name) {
|
||||||
|
error('Failed! Please set plugin name in plugin.json');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function copyDirectory(srcDir, dstDir) {
|
||||||
|
if (!fs.existsSync(dstDir)) {
|
||||||
|
fs.mkdirSync(dstDir);
|
||||||
|
log(`Created directory ${dstDir}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.readdirSync(srcDir, { withFileTypes: true }).forEach((file) => {
|
||||||
|
const src = path.join(srcDir, file.name);
|
||||||
|
const dst = path.join(dstDir, file.name);
|
||||||
|
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
copyDirectory(src, dst);
|
||||||
|
} else {
|
||||||
|
fs.copyFileSync(src, dst);
|
||||||
|
log(`Copied file: ${src} --> ${dst}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
log(`All files copied!`);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function makeSymbolicLink(srcPath, targetPath) {
|
||||||
|
if (!fs.existsSync(targetPath)) {
|
||||||
|
// fs.symlinkSync(srcPath, targetPath, 'junction');
|
||||||
|
//Go 1.23 no longer supports junctions as symlinks
|
||||||
|
//Please refer to https://github.com/siyuan-note/siyuan/issues/12399
|
||||||
|
fs.symlinkSync(srcPath, targetPath, 'dir');
|
||||||
|
log(`Done! Created symlink ${targetPath}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check the existed target path
|
||||||
|
let isSymbol = fs.lstatSync(targetPath).isSymbolicLink();
|
||||||
|
if (!isSymbol) {
|
||||||
|
error(`Failed! ${targetPath} already exists and is not a symbolic link`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let existedPath = fs.readlinkSync(targetPath);
|
||||||
|
if (cmpPath(existedPath, srcPath)) {
|
||||||
|
log(`Good! ${targetPath} is already linked to ${srcPath}`);
|
||||||
|
} else {
|
||||||
|
error(`Error! Already exists symbolic link ${targetPath}\nBut it links to ${existedPath}`);
|
||||||
|
}
|
||||||
|
}
|
218
src/api.ts
218
src/api.ts
|
@ -6,10 +6,10 @@
|
||||||
* API 文档见 [API_zh_CN.md](https://github.com/siyuan-note/siyuan/blob/master/API_zh_CN.md)
|
* API 文档见 [API_zh_CN.md](https://github.com/siyuan-note/siyuan/blob/master/API_zh_CN.md)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { fetchSyncPost, IWebSocketData } from "siyuan";
|
import { fetchPost, fetchSyncPost, IWebSocketData } from "siyuan";
|
||||||
|
|
||||||
|
|
||||||
async function request(url: string, data: any) {
|
export async function request(url: string, data: any) {
|
||||||
let response: IWebSocketData = await fetchSyncPost(url, data);
|
let response: IWebSocketData = await fetchSyncPost(url, data);
|
||||||
let res = response.code === 0 ? response.data : null;
|
let res = response.code === 0 ? response.data : null;
|
||||||
return res;
|
return res;
|
||||||
|
@ -18,11 +18,8 @@ async function request(url: string, data: any) {
|
||||||
|
|
||||||
// **************************************** Noteboook ****************************************
|
// **************************************** Noteboook ****************************************
|
||||||
|
|
||||||
export type ReslsNotebooks = {
|
|
||||||
notebooks: Notebook[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function lsNotebooks(): Promise<ReslsNotebooks> {
|
export async function lsNotebooks(): Promise<IReslsNotebooks> {
|
||||||
let url = '/api/notebook/lsNotebooks';
|
let url = '/api/notebook/lsNotebooks';
|
||||||
return request(url, '');
|
return request(url, '');
|
||||||
}
|
}
|
||||||
|
@ -57,14 +54,8 @@ export async function removeNotebook(notebook: NotebookId) {
|
||||||
return request(url, { notebook: notebook });
|
return request(url, { notebook: notebook });
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ResGetNotebookConf = {
|
|
||||||
box: string;
|
|
||||||
conf: NotebookConf;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
export async function getNotebookConf(notebook: NotebookId): Promise<IResGetNotebookConf> {
|
||||||
export async function getNotebookConf(notebook: NotebookId): Promise<ResGetNotebookConf> {
|
|
||||||
let data = { notebook: notebook };
|
let data = { notebook: notebook };
|
||||||
let url = '/api/notebook/getNotebookConf';
|
let url = '/api/notebook/getNotebookConf';
|
||||||
return request(url, data);
|
return request(url, data);
|
||||||
|
@ -78,7 +69,7 @@ export async function setNotebookConf(notebook: NotebookId, conf: NotebookConf):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// **************************************** Document ****************************************
|
// **************************************** File Tree ****************************************
|
||||||
export async function createDocWithMd(notebook: NotebookId, path: string, markdown: string): Promise<DocumentId> {
|
export async function createDocWithMd(notebook: NotebookId, path: string, markdown: string): Promise<DocumentId> {
|
||||||
let data = {
|
let data = {
|
||||||
notebook: notebook,
|
notebook: notebook,
|
||||||
|
@ -140,13 +131,19 @@ export async function getHPathByID(id: BlockId): Promise<string> {
|
||||||
return request(url, data);
|
return request(url, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// **************************************** Asset Files ****************************************
|
|
||||||
export type ResUpload = {
|
export async function getIDsByHPath(notebook: NotebookId, path: string): Promise<BlockId[]> {
|
||||||
errFiles: string[];
|
let data = {
|
||||||
succMap: { [key: string]: string };
|
notebook: notebook,
|
||||||
|
path: path
|
||||||
|
};
|
||||||
|
let url = '/api/filetree/getIDsByHPath';
|
||||||
|
return request(url, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function upload(assetsDirPath: string, files: any[]): Promise<ResUpload> {
|
// **************************************** Asset Files ****************************************
|
||||||
|
|
||||||
|
export async function upload(assetsDirPath: string, files: any[]): Promise<IResUpload> {
|
||||||
let form = new FormData();
|
let form = new FormData();
|
||||||
form.append('assetsDirPath', assetsDirPath);
|
form.append('assetsDirPath', assetsDirPath);
|
||||||
for (let file of files) {
|
for (let file of files) {
|
||||||
|
@ -157,45 +154,57 @@ export async function upload(assetsDirPath: string, files: any[]): Promise<ResUp
|
||||||
}
|
}
|
||||||
|
|
||||||
// **************************************** Block ****************************************
|
// **************************************** Block ****************************************
|
||||||
export type ResdoOperations = {
|
|
||||||
doOperations: doOperation[];
|
|
||||||
undoOperations: doOperation[] | null;
|
|
||||||
}
|
|
||||||
type DataType = "markdown" | "dom";
|
type DataType = "markdown" | "dom";
|
||||||
export async function insertBlock(dataType: DataType, data: string, previousID: BlockId): Promise<ResdoOperations> {
|
export async function insertBlock(
|
||||||
let data1 = {
|
dataType: DataType, data: string,
|
||||||
|
nextID?: BlockId, previousID?: BlockId, parentID?: BlockId
|
||||||
|
): Promise<IResdoOperations[]> {
|
||||||
|
let payload = {
|
||||||
dataType: dataType,
|
dataType: dataType,
|
||||||
data: data,
|
data: data,
|
||||||
previousID: previousID
|
nextID: nextID,
|
||||||
|
previousID: previousID,
|
||||||
|
parentID: parentID
|
||||||
}
|
}
|
||||||
let url = '/api/block/insertBlock';
|
let url = '/api/block/insertBlock';
|
||||||
return request(url, data1);
|
return request(url, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function appendBlock(dataType: DataType, data: string, parentID: BlockId | DocumentId): Promise<ResdoOperations> {
|
export async function prependBlock(dataType: DataType, data: string, parentID: BlockId | DocumentId): Promise<IResdoOperations[]> {
|
||||||
let data1 = {
|
let payload = {
|
||||||
|
dataType: dataType,
|
||||||
|
data: data,
|
||||||
|
parentID: parentID
|
||||||
|
}
|
||||||
|
let url = '/api/block/prependBlock';
|
||||||
|
return request(url, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function appendBlock(dataType: DataType, data: string, parentID: BlockId | DocumentId): Promise<IResdoOperations[]> {
|
||||||
|
let payload = {
|
||||||
dataType: dataType,
|
dataType: dataType,
|
||||||
data: data,
|
data: data,
|
||||||
parentID: parentID
|
parentID: parentID
|
||||||
}
|
}
|
||||||
let url = '/api/block/appendBlock';
|
let url = '/api/block/appendBlock';
|
||||||
return request(url, data1);
|
return request(url, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function updateBlock(dataType: DataType, data: string, id: BlockId): Promise<ResdoOperations> {
|
export async function updateBlock(dataType: DataType, data: string, id: BlockId): Promise<IResdoOperations[]> {
|
||||||
let data1 = {
|
let payload = {
|
||||||
dataType: dataType,
|
dataType: dataType,
|
||||||
data: data,
|
data: data,
|
||||||
id: id
|
id: id
|
||||||
}
|
}
|
||||||
let url = '/api/block/updateBlock';
|
let url = '/api/block/updateBlock';
|
||||||
return request(url, data1);
|
return request(url, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function deleteBlock(id: BlockId): Promise<ResdoOperations> {
|
export async function deleteBlock(id: BlockId): Promise<IResdoOperations[]> {
|
||||||
let data = {
|
let data = {
|
||||||
id: id
|
id: id
|
||||||
}
|
}
|
||||||
|
@ -204,7 +213,7 @@ export async function deleteBlock(id: BlockId): Promise<ResdoOperations> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function moveBlock(id: BlockId, previousID: PreviousID | null = null, parentID: ParentID | null = null): Promise<ResdoOperations> {
|
export async function moveBlock(id: BlockId, previousID?: PreviousID, parentID?: ParentID): Promise<IResdoOperations[]> {
|
||||||
let data = {
|
let data = {
|
||||||
id: id,
|
id: id,
|
||||||
previousID: previousID,
|
previousID: previousID,
|
||||||
|
@ -215,12 +224,25 @@ export async function moveBlock(id: BlockId, previousID: PreviousID | null = nul
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export type ResGetBlockKramdown = {
|
export async function foldBlock(id: BlockId) {
|
||||||
id: BlockId;
|
let data = {
|
||||||
kramdown: string;
|
id: id
|
||||||
|
}
|
||||||
|
let url = '/api/block/foldBlock';
|
||||||
|
return request(url, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getBlockKramdown(id: BlockId): Promise<ResGetBlockKramdown> {
|
|
||||||
|
export async function unfoldBlock(id: BlockId) {
|
||||||
|
let data = {
|
||||||
|
id: id
|
||||||
|
}
|
||||||
|
let url = '/api/block/unfoldBlock';
|
||||||
|
return request(url, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function getBlockKramdown(id: BlockId): Promise<IResGetBlockKramdown> {
|
||||||
let data = {
|
let data = {
|
||||||
id: id
|
id: id
|
||||||
}
|
}
|
||||||
|
@ -228,12 +250,8 @@ export async function getBlockKramdown(id: BlockId): Promise<ResGetBlockKramdown
|
||||||
return request(url, data);
|
return request(url, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ChildBlock = {
|
|
||||||
id: BlockId;
|
export async function getChildBlocks(id: BlockId): Promise<IResGetChildBlock[]> {
|
||||||
type: BlockType;
|
|
||||||
subtype?: BlockSubType;
|
|
||||||
}
|
|
||||||
export async function getChildBlocks(id: BlockId): Promise<ChildBlock[]> {
|
|
||||||
let data = {
|
let data = {
|
||||||
id: id
|
id: id
|
||||||
}
|
}
|
||||||
|
@ -288,11 +306,7 @@ export async function getBlockByID(blockId: string): Promise<Block> {
|
||||||
|
|
||||||
// **************************************** Template ****************************************
|
// **************************************** Template ****************************************
|
||||||
|
|
||||||
export type ResGetTemplates = {
|
export async function render(id: DocumentId, path: string): Promise<IResGetTemplates> {
|
||||||
content: string;
|
|
||||||
path: string;
|
|
||||||
}
|
|
||||||
export async function render(id: DocumentId, path: string): Promise<ResGetTemplates> {
|
|
||||||
let data = {
|
let data = {
|
||||||
id: id,
|
id: id,
|
||||||
path: path
|
path: path
|
||||||
|
@ -314,14 +328,35 @@ export async function getFile(path: string): Promise<any> {
|
||||||
path: path
|
path: path
|
||||||
}
|
}
|
||||||
let url = '/api/file/getFile';
|
let url = '/api/file/getFile';
|
||||||
try {
|
return new Promise((resolve, _) => {
|
||||||
let file = await fetchSyncPost(url, data);
|
fetchPost(url, data, (content: any) => {
|
||||||
return file;
|
resolve(content)
|
||||||
} catch (error_msg) {
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fetchPost will secretly convert data into json, this func merely return Blob
|
||||||
|
* @param endpoint
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getFileBlob = async (path: string): Promise<Blob | null> => {
|
||||||
|
const endpoint = '/api/file/getFile'
|
||||||
|
let response = await fetch(endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
path: path
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
let data = await response.blob();
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function putFile(path: string, isDir: boolean, file: any) {
|
export async function putFile(path: string, isDir: boolean, file: any) {
|
||||||
let form = new FormData();
|
let form = new FormData();
|
||||||
form.append('path', path);
|
form.append('path', path);
|
||||||
|
@ -343,11 +378,8 @@ export async function removeFile(path: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export type ResReadDir = {
|
|
||||||
isDir: boolean;
|
export async function readDir(path: string): Promise<IResReadDir> {
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
export async function readDir(path: string): Promise<ResReadDir> {
|
|
||||||
let data = {
|
let data = {
|
||||||
path: path
|
path: path
|
||||||
}
|
}
|
||||||
|
@ -356,11 +388,9 @@ export async function readDir(path: string): Promise<ResReadDir> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export type ResExportMdContent = {
|
// **************************************** Export ****************************************
|
||||||
hPath: string;
|
|
||||||
content: string;
|
export async function exportMdContent(id: DocumentId): Promise<IResExportMdContent> {
|
||||||
}
|
|
||||||
export async function exportMdContent(id: DocumentId): Promise<ResExportMdContent> {
|
|
||||||
let data = {
|
let data = {
|
||||||
id: id
|
id: id
|
||||||
}
|
}
|
||||||
|
@ -368,6 +398,17 @@ export async function exportMdContent(id: DocumentId): Promise<ResExportMdConten
|
||||||
return request(url, data);
|
return request(url, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function exportResources(paths: string[], name: string): Promise<IResExportResources> {
|
||||||
|
let data = {
|
||||||
|
paths: paths,
|
||||||
|
name: name
|
||||||
|
}
|
||||||
|
let url = '/api/export/exportResources';
|
||||||
|
return request(url, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// **************************************** Convert ****************************************
|
||||||
|
|
||||||
export type PandocArgs = string;
|
export type PandocArgs = string;
|
||||||
export async function pandoc(args: PandocArgs[]) {
|
export async function pandoc(args: PandocArgs[]) {
|
||||||
let data = {
|
let data = {
|
||||||
|
@ -377,13 +418,52 @@ export async function pandoc(args: PandocArgs[]) {
|
||||||
return request(url, data);
|
return request(url, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// **************************************** Notification ****************************************
|
||||||
|
|
||||||
|
// /api/notification/pushMsg
|
||||||
|
// {
|
||||||
|
// "msg": "test",
|
||||||
|
// "timeout": 7000
|
||||||
|
// }
|
||||||
|
export async function pushMsg(msg: string, timeout: number = 7000) {
|
||||||
|
let payload = {
|
||||||
|
msg: msg,
|
||||||
|
timeout: timeout
|
||||||
|
};
|
||||||
|
let url = "/api/notification/pushMsg";
|
||||||
|
return request(url, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function pushErrMsg(msg: string, timeout: number = 7000) {
|
||||||
|
let payload = {
|
||||||
|
msg: msg,
|
||||||
|
timeout: timeout
|
||||||
|
};
|
||||||
|
let url = "/api/notification/pushErrMsg";
|
||||||
|
return request(url, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
// **************************************** Network ****************************************
|
||||||
|
export async function forwardProxy(
|
||||||
|
url: string, method: string = 'GET', payload: any = {},
|
||||||
|
headers: any[] = [], timeout: number = 7000, contentType: string = "text/html"
|
||||||
|
): Promise<IResForwardProxy> {
|
||||||
|
let data = {
|
||||||
|
url: url,
|
||||||
|
method: method,
|
||||||
|
timeout: timeout,
|
||||||
|
contentType: contentType,
|
||||||
|
headers: headers,
|
||||||
|
payload: payload
|
||||||
|
}
|
||||||
|
let url1 = '/api/network/forwardProxy';
|
||||||
|
return request(url1, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// **************************************** System ****************************************
|
// **************************************** System ****************************************
|
||||||
export type ResBootProgress = {
|
|
||||||
progress: number;
|
export async function bootProgress(): Promise<IResBootProgress> {
|
||||||
details: string;
|
|
||||||
}
|
|
||||||
export async function bootProgress(): Promise<ResBootProgress> {
|
|
||||||
return request('/api/system/bootProgress', {});
|
return request('/api/system/bootProgress', {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,61 +1,63 @@
|
||||||
|
<!--
|
||||||
|
Copyright (c) 2024 by frostime. All Rights Reserved.
|
||||||
|
Author : frostime
|
||||||
|
Date : 2023-11-19 12:30:45
|
||||||
|
FilePath : /src/hello.svelte
|
||||||
|
LastEditTime : 2024-10-16 14:37:50
|
||||||
|
Description :
|
||||||
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onDestroy, onMount } from "svelte";
|
import { onDestroy, onMount } from "svelte";
|
||||||
import { version } from "@/api";
|
import { version, sql as query } from "@/api";
|
||||||
import { showMessage } from "siyuan";
|
import { showMessage, fetchPost, Protyle } from "siyuan";
|
||||||
import Typo from "@/libs/b3-typography.svelte";
|
|
||||||
|
|
||||||
export let name: string;
|
export let app;
|
||||||
export let i18n: any;
|
|
||||||
|
|
||||||
let time;
|
let time: string = "";
|
||||||
let ver;
|
let ver: string = "";
|
||||||
|
|
||||||
let intv1 = setInterval(async () => {
|
let divProtyle: HTMLDivElement;
|
||||||
time = new Date();
|
let protyle: any;
|
||||||
}, 1000);
|
let blockID: string = '';
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
time = new Date();
|
|
||||||
ver = await version();
|
ver = await version();
|
||||||
|
fetchPost("/api/system/currentTime", {}, (response) => {
|
||||||
|
time = new Date(response.data).toString();
|
||||||
|
});
|
||||||
|
protyle = await initProtyle();
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* You must call this function when the component is destroyed.
|
|
||||||
*/
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
showMessage("Hello panel closed");
|
showMessage("Hello panel closed");
|
||||||
clearInterval(intv1);
|
protyle.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
$: time_str = new Date(time).toLocaleTimeString();
|
async function initProtyle() {
|
||||||
|
let sql = "SELECT * FROM blocks ORDER BY RANDOM () LIMIT 1;";
|
||||||
|
let blocks: Block[] = await query(sql);
|
||||||
|
blockID = blocks[0].id;
|
||||||
|
return new Protyle(app, divProtyle, {
|
||||||
|
blockId: blockID
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div id="hello">
|
<div class="b3-dialog__content">
|
||||||
<div class="fn__flex">
|
<div>appId:</div>
|
||||||
<div class="fn__flex-1">
|
<div class="fn__hr"></div>
|
||||||
<h2>Hello {name} v{ver}</h2>
|
<div class="plugin-sample__time">${app?.appId}</div>
|
||||||
</div>
|
<div class="fn__hr"></div>
|
||||||
<div class="fn__flex-1 b3-label__text __text-right">
|
<div class="fn__hr"></div>
|
||||||
{time_str}
|
<div>API demo:</div>
|
||||||
</div>
|
<div class="fn__hr" />
|
||||||
|
<div class="plugin-sample__time">
|
||||||
|
System current time: <span id="time">{time}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="fn__hr" />
|
||||||
<Typo>
|
<div class="fn__hr" />
|
||||||
<h2>Wellcome to plugin sample with vite & svelte</h2>
|
<div>Protyle demo: id = {blockID}</div>
|
||||||
<p>{@html i18n.makesure}</p>
|
<div class="fn__hr" />
|
||||||
</Typo>
|
<div id="protyle" style="height: 360px;" bind:this={divProtyle}/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
#hello {
|
|
||||||
margin: 20px;
|
|
||||||
div {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.__text-right {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
#helloPanel {
|
|
||||||
border: 1px rgb(189, 119, 119) dashed;
|
|
||||||
}
|
|
728
src/index.ts
728
src/index.ts
|
@ -9,12 +9,22 @@ import {
|
||||||
getFrontend,
|
getFrontend,
|
||||||
getBackend,
|
getBackend,
|
||||||
IModel,
|
IModel,
|
||||||
Setting
|
Protyle,
|
||||||
|
openWindow,
|
||||||
|
IOperation,
|
||||||
|
Constants,
|
||||||
|
openMobileFileById,
|
||||||
|
lockScreen,
|
||||||
|
ICard,
|
||||||
|
ICardData
|
||||||
} from "siyuan";
|
} from "siyuan";
|
||||||
import "@/index.scss";
|
import "@/index.scss";
|
||||||
|
|
||||||
import HelloExample from "@/hello.svelte";
|
import HelloExample from "@/hello.svelte";
|
||||||
import SettingPannel from "@/libs/setting-panel.svelte";
|
import SettingExample from "@/setting-example.svelte";
|
||||||
|
|
||||||
|
import { SettingUtils } from "./libs/setting-utils";
|
||||||
|
import { svelteDialog } from "./libs/dialog";
|
||||||
|
|
||||||
const STORAGE_NAME = "menu-config";
|
const STORAGE_NAME = "menu-config";
|
||||||
const TAB_TYPE = "custom_tab";
|
const TAB_TYPE = "custom_tab";
|
||||||
|
@ -22,11 +32,13 @@ const DOCK_TYPE = "dock_tab";
|
||||||
|
|
||||||
export default class PluginSample extends Plugin {
|
export default class PluginSample extends Plugin {
|
||||||
|
|
||||||
private customTab: () => IModel;
|
customTab: () => IModel;
|
||||||
private isMobile: boolean;
|
private isMobile: boolean;
|
||||||
|
private blockIconEventBindThis = this.blockIconEvent.bind(this);
|
||||||
|
private settingUtils: SettingUtils;
|
||||||
|
|
||||||
async onload() {
|
async onload() {
|
||||||
this.data[STORAGE_NAME] = {readonlyText: "Readonly"};
|
this.data[STORAGE_NAME] = { readonlyText: "Readonly" };
|
||||||
|
|
||||||
console.log("loading plugin-sample", this.i18n);
|
console.log("loading plugin-sample", this.i18n);
|
||||||
|
|
||||||
|
@ -62,7 +74,7 @@ export default class PluginSample extends Plugin {
|
||||||
});
|
});
|
||||||
|
|
||||||
const statusIconTemp = document.createElement("template");
|
const statusIconTemp = document.createElement("template");
|
||||||
statusIconTemp.innerHTML = `<div class="toolbar__item b3-tooltips b3-tooltips__w" aria-label="Remove plugin-sample Data">
|
statusIconTemp.innerHTML = `<div class="toolbar__item ariaLabel" aria-label="Remove plugin-sample Data">
|
||||||
<svg>
|
<svg>
|
||||||
<use xlink:href="#iconTrashcan"></use>
|
<use xlink:href="#iconTrashcan"></use>
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -70,7 +82,7 @@ export default class PluginSample extends Plugin {
|
||||||
statusIconTemp.content.firstElementChild.addEventListener("click", () => {
|
statusIconTemp.content.firstElementChild.addEventListener("click", () => {
|
||||||
confirm("⚠️", this.i18n.confirmRemove.replace("${name}", this.name), () => {
|
confirm("⚠️", this.i18n.confirmRemove.replace("${name}", this.name), () => {
|
||||||
this.removeData(STORAGE_NAME).then(() => {
|
this.removeData(STORAGE_NAME).then(() => {
|
||||||
this.data[STORAGE_NAME] = {readonlyText: "Readonly"};
|
this.data[STORAGE_NAME] = { readonlyText: "Readonly" };
|
||||||
showMessage(`[${this.name}]: ${this.i18n.removedData}`);
|
showMessage(`[${this.name}]: ${this.i18n.removedData}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -79,12 +91,276 @@ export default class PluginSample extends Plugin {
|
||||||
element: statusIconTemp.content.firstElementChild as HTMLElement,
|
element: statusIconTemp.content.firstElementChild as HTMLElement,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.addCommand({
|
||||||
|
langKey: "showDialog",
|
||||||
|
hotkey: "⇧⌘O",
|
||||||
|
callback: () => {
|
||||||
|
this.showDialog();
|
||||||
|
},
|
||||||
|
fileTreeCallback: (file: any) => {
|
||||||
|
console.log(file, "fileTreeCallback");
|
||||||
|
},
|
||||||
|
editorCallback: (protyle: any) => {
|
||||||
|
console.log(protyle, "editorCallback");
|
||||||
|
},
|
||||||
|
dockCallback: (element: HTMLElement) => {
|
||||||
|
console.log(element, "dockCallback");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.addCommand({
|
||||||
|
langKey: "getTab",
|
||||||
|
hotkey: "⇧⌘M",
|
||||||
|
globalCallback: () => {
|
||||||
|
console.log(this.getOpenedTab());
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addDock({
|
||||||
|
config: {
|
||||||
|
position: "LeftBottom",
|
||||||
|
size: { width: 200, height: 0 },
|
||||||
|
icon: "iconSaving",
|
||||||
|
title: "Custom Dock",
|
||||||
|
hotkey: "⌥⌘W",
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
text: "This is my custom dock"
|
||||||
|
},
|
||||||
|
type: DOCK_TYPE,
|
||||||
|
resize() {
|
||||||
|
console.log(DOCK_TYPE + " resize");
|
||||||
|
},
|
||||||
|
update() {
|
||||||
|
console.log(DOCK_TYPE + " update");
|
||||||
|
},
|
||||||
|
init: (dock) => {
|
||||||
|
if (this.isMobile) {
|
||||||
|
dock.element.innerHTML = `<div class="toolbar toolbar--border toolbar--dark">
|
||||||
|
<svg class="toolbar__icon"><use xlink:href="#iconEmoji"></use></svg>
|
||||||
|
<div class="toolbar__text">Custom Dock</div>
|
||||||
|
</div>
|
||||||
|
<div class="fn__flex-1 plugin-sample__custom-dock">
|
||||||
|
${dock.data.text}
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
} else {
|
||||||
|
dock.element.innerHTML = `<div class="fn__flex-1 fn__flex-column">
|
||||||
|
<div class="block__icons">
|
||||||
|
<div class="block__logo">
|
||||||
|
<svg class="block__logoicon"><use xlink:href="#iconEmoji"></use></svg>
|
||||||
|
Custom Dock
|
||||||
|
</div>
|
||||||
|
<span class="fn__flex-1 fn__space"></span>
|
||||||
|
<span data-type="min" class="block__icon b3-tooltips b3-tooltips__sw" aria-label="Min ${adaptHotkey("⌘W")}"><svg class="block__logoicon"><use xlink:href="#iconMin"></use></svg></span>
|
||||||
|
</div>
|
||||||
|
<div class="fn__flex-1 plugin-sample__custom-dock">
|
||||||
|
${dock.data.text}
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
console.log("destroy dock:", DOCK_TYPE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.settingUtils = new SettingUtils({
|
||||||
|
plugin: this, name: STORAGE_NAME
|
||||||
|
});
|
||||||
|
this.settingUtils.addItem({
|
||||||
|
key: "Input",
|
||||||
|
value: "",
|
||||||
|
type: "textinput",
|
||||||
|
title: "Readonly text",
|
||||||
|
description: "Input description",
|
||||||
|
action: {
|
||||||
|
// Called when focus is lost and content changes
|
||||||
|
callback: () => {
|
||||||
|
// Return data and save it in real time
|
||||||
|
let value = this.settingUtils.takeAndSave("Input");
|
||||||
|
console.log(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.settingUtils.addItem({
|
||||||
|
key: "InputArea",
|
||||||
|
value: "",
|
||||||
|
type: "textarea",
|
||||||
|
title: "Readonly text",
|
||||||
|
description: "Input description",
|
||||||
|
// Called when focus is lost and content changes
|
||||||
|
action: {
|
||||||
|
callback: () => {
|
||||||
|
// Read data in real time
|
||||||
|
let value = this.settingUtils.take("InputArea");
|
||||||
|
console.log(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.settingUtils.addItem({
|
||||||
|
key: "Check",
|
||||||
|
value: true,
|
||||||
|
type: "checkbox",
|
||||||
|
title: "Checkbox text",
|
||||||
|
description: "Check description",
|
||||||
|
action: {
|
||||||
|
callback: () => {
|
||||||
|
// Return data and save it in real time
|
||||||
|
let value = !this.settingUtils.get("Check");
|
||||||
|
this.settingUtils.set("Check", value);
|
||||||
|
console.log(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.settingUtils.addItem({
|
||||||
|
key: "Select",
|
||||||
|
value: 1,
|
||||||
|
type: "select",
|
||||||
|
title: "Select",
|
||||||
|
description: "Select description",
|
||||||
|
options: {
|
||||||
|
1: "Option 1",
|
||||||
|
2: "Option 2"
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
callback: () => {
|
||||||
|
// Read data in real time
|
||||||
|
let value = this.settingUtils.take("Select");
|
||||||
|
console.log(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.settingUtils.addItem({
|
||||||
|
key: "Slider",
|
||||||
|
value: 50,
|
||||||
|
type: "slider",
|
||||||
|
title: "Slider text",
|
||||||
|
description: "Slider description",
|
||||||
|
direction: "column",
|
||||||
|
slider: {
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
step: 1,
|
||||||
|
},
|
||||||
|
action:{
|
||||||
|
callback: () => {
|
||||||
|
// Read data in real time
|
||||||
|
let value = this.settingUtils.take("Slider");
|
||||||
|
console.log(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.settingUtils.addItem({
|
||||||
|
key: "Btn",
|
||||||
|
value: "",
|
||||||
|
type: "button",
|
||||||
|
title: "Button",
|
||||||
|
description: "Button description",
|
||||||
|
button: {
|
||||||
|
label: "Button",
|
||||||
|
callback: () => {
|
||||||
|
showMessage("Button clicked");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.settingUtils.addItem({
|
||||||
|
key: "Custom Element",
|
||||||
|
value: "",
|
||||||
|
type: "custom",
|
||||||
|
direction: "row",
|
||||||
|
title: "Custom Element",
|
||||||
|
description: "Custom Element description",
|
||||||
|
//Any custom element must offer the following methods
|
||||||
|
createElement: (currentVal: any) => {
|
||||||
|
let div = document.createElement('div');
|
||||||
|
div.style.border = "1px solid var(--b3-theme-primary)";
|
||||||
|
div.contentEditable = "true";
|
||||||
|
div.textContent = currentVal;
|
||||||
|
return div;
|
||||||
|
},
|
||||||
|
getEleVal: (ele: HTMLElement) => {
|
||||||
|
return ele.textContent;
|
||||||
|
},
|
||||||
|
setEleVal: (ele: HTMLElement, val: any) => {
|
||||||
|
ele.textContent = val;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.settingUtils.addItem({
|
||||||
|
key: "Hint",
|
||||||
|
value: "",
|
||||||
|
type: "hint",
|
||||||
|
title: this.i18n.hintTitle,
|
||||||
|
description: this.i18n.hintDesc,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.settingUtils.load();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading settings storage, probably empty config json:", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.protyleSlash = [{
|
||||||
|
filter: ["insert emoji 😊", "插入表情 😊", "crbqwx"],
|
||||||
|
html: `<div class="b3-list-item__first"><span class="b3-list-item__text">${this.i18n.insertEmoji}</span><span class="b3-list-item__meta">😊</span></div>`,
|
||||||
|
id: "insertEmoji",
|
||||||
|
callback(protyle: Protyle) {
|
||||||
|
protyle.insert("😊");
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
|
||||||
|
this.protyleOptions = {
|
||||||
|
toolbar: ["block-ref",
|
||||||
|
"a",
|
||||||
|
"|",
|
||||||
|
"text",
|
||||||
|
"strong",
|
||||||
|
"em",
|
||||||
|
"u",
|
||||||
|
"s",
|
||||||
|
"mark",
|
||||||
|
"sup",
|
||||||
|
"sub",
|
||||||
|
"clear",
|
||||||
|
"|",
|
||||||
|
"code",
|
||||||
|
"kbd",
|
||||||
|
"tag",
|
||||||
|
"inline-math",
|
||||||
|
"inline-memo",
|
||||||
|
"|",
|
||||||
|
{
|
||||||
|
name: "insert-smail-emoji",
|
||||||
|
icon: "iconEmoji",
|
||||||
|
hotkey: "⇧⌘I",
|
||||||
|
tipPosition: "n",
|
||||||
|
tip: this.i18n.insertEmoji,
|
||||||
|
click(protyle: Protyle) {
|
||||||
|
protyle.insert("😊");
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(this.i18n.helloPlugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
onLayoutReady() {
|
||||||
|
// this.loadData(STORAGE_NAME);
|
||||||
|
this.settingUtils.load();
|
||||||
|
console.log(`frontend: ${getFrontend()}; backend: ${getBackend()}`);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"Official settings value calling example:\n" +
|
||||||
|
this.settingUtils.get("InputArea") + "\n" +
|
||||||
|
this.settingUtils.get("Slider") + "\n" +
|
||||||
|
this.settingUtils.get("Select") + "\n"
|
||||||
|
);
|
||||||
|
|
||||||
let tabDiv = document.createElement("div");
|
let tabDiv = document.createElement("div");
|
||||||
new HelloExample({
|
new HelloExample({
|
||||||
target: tabDiv,
|
target: tabDiv,
|
||||||
props: {
|
props: {
|
||||||
name: this.i18n.name,
|
app: this.app,
|
||||||
i18n: this.i18n.hello
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.customTab = this.addTab({
|
this.customTab = this.addTab({
|
||||||
|
@ -100,136 +376,110 @@ export default class PluginSample extends Plugin {
|
||||||
console.log("destroy tab:", TAB_TYPE);
|
console.log("destroy tab:", TAB_TYPE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addCommand({
|
|
||||||
langKey: "showDialog",
|
|
||||||
hotkey: "⇧⌘M",
|
|
||||||
callback: () => {
|
|
||||||
this.showDialog();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.addDock({
|
|
||||||
config: {
|
|
||||||
position: "LeftBottom",
|
|
||||||
size: {width: 200, height: 0},
|
|
||||||
icon: "iconSaving",
|
|
||||||
title: "Custom Dock",
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
text: "This is my custom dock"
|
|
||||||
},
|
|
||||||
type: DOCK_TYPE,
|
|
||||||
init() {
|
|
||||||
this.element.innerHTML = `<div class="fn__flex-1 fn__flex-column">
|
|
||||||
<div class="block__icons">
|
|
||||||
<div class="block__logo">
|
|
||||||
<svg><use xlink:href="#iconEmoji"></use></svg>
|
|
||||||
Custom Dock
|
|
||||||
</div>
|
|
||||||
<span class="fn__flex-1 fn__space"></span>
|
|
||||||
<span data-type="min" class="block__icon b3-tooltips b3-tooltips__sw" aria-label="Min ${adaptHotkey("⌘W")}"><svg><use xlink:href="#iconMin"></use></svg></span>
|
|
||||||
</div>
|
|
||||||
<div class="fn__flex-1 plugin-sample__custom-dock">
|
|
||||||
${this.data.text}
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
},
|
|
||||||
destroy() {
|
|
||||||
console.log("destroy dock:", DOCK_TYPE);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const textareaElement = document.createElement("textarea");
|
|
||||||
this.setting = new Setting({
|
|
||||||
confirmCallback: () => {
|
|
||||||
this.saveData(STORAGE_NAME, {readonlyText: textareaElement.value});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.setting.addItem({
|
|
||||||
title: "Readonly text",
|
|
||||||
createActionElement: () => {
|
|
||||||
textareaElement.className = "b3-text-field fn__block";
|
|
||||||
textareaElement.placeholder = "Readonly text in the menu";
|
|
||||||
textareaElement.value = this.data[STORAGE_NAME].readonlyText;
|
|
||||||
return textareaElement;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const btnaElement = document.createElement("button");
|
|
||||||
btnaElement.className = "b3-button b3-button--outline fn__flex-center fn__size200";
|
|
||||||
btnaElement.textContent = "Open";
|
|
||||||
btnaElement.addEventListener("click", () => {
|
|
||||||
window.open("https://github.com/siyuan-note/plugin-sample-vite-svelte");
|
|
||||||
});
|
|
||||||
this.setting.addItem({
|
|
||||||
title: "Open plugin url",
|
|
||||||
description: "Open plugin url in browser",
|
|
||||||
actionElement: btnaElement,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(this.i18n.helloPlugin);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onLayoutReady() {
|
async onunload() {
|
||||||
this.loadData(STORAGE_NAME);
|
|
||||||
console.log(`frontend: ${getFrontend()}; backend: ${getBackend()}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
onunload() {
|
|
||||||
console.log(this.i18n.byePlugin);
|
console.log(this.i18n.byePlugin);
|
||||||
showMessage("Goodbye SiYuan Plugin");
|
showMessage("Goodbye SiYuan Plugin");
|
||||||
console.log("onunload");
|
console.log("onunload");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uninstall() {
|
||||||
|
console.log("uninstall");
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateCards(options: ICardData) {
|
||||||
|
options.cards.sort((a: ICard, b: ICard) => {
|
||||||
|
if (a.blockID < b.blockID) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (a.blockID > b.blockID) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A custom setting pannel provided by svelte
|
* A custom setting pannel provided by svelte
|
||||||
*/
|
*/
|
||||||
openDIYSetting(): void {
|
openDIYSetting(): void {
|
||||||
let dialog = new Dialog({
|
let dialog = new Dialog({
|
||||||
title: "SettingPannel",
|
title: "SettingPannel",
|
||||||
content: `<div id="SettingPanel"></div>`,
|
content: `<div id="SettingPanel" style="height: 100%;"></div>`,
|
||||||
width: "600px",
|
width: "800px",
|
||||||
destroyCallback: (options) => {
|
destroyCallback: (options) => {
|
||||||
console.log("destroyCallback", options);
|
console.log("destroyCallback", options);
|
||||||
//You'd better destroy the component when the dialog is closed
|
//You'd better destroy the component when the dialog is closed
|
||||||
pannel.$destroy();
|
pannel.$destroy();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let pannel = new SettingPannel({
|
let pannel = new SettingExample({
|
||||||
target: dialog.element.querySelector("#SettingPanel"),
|
target: dialog.element.querySelector("#SettingPanel"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private eventBusLog({detail}: any) {
|
private eventBusPaste(event: any) {
|
||||||
|
// 如果需异步处理请调用 preventDefault, 否则会进行默认处理
|
||||||
|
event.preventDefault();
|
||||||
|
// 如果使用了 preventDefault,必须调用 resolve,否则程序会卡死
|
||||||
|
event.detail.resolve({
|
||||||
|
textPlain: event.detail.textPlain.trim(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private eventBusLog({ detail }: any) {
|
||||||
console.log(detail);
|
console.log(detail);
|
||||||
}
|
}
|
||||||
|
|
||||||
private blockIconEvent({detail}: any) {
|
private blockIconEvent({ detail }: any) {
|
||||||
const ids: string[] = [];
|
|
||||||
detail.blockElements.forEach((item: HTMLElement) => {
|
|
||||||
ids.push(item.getAttribute("data-node-id"));
|
|
||||||
});
|
|
||||||
detail.menu.addItem({
|
detail.menu.addItem({
|
||||||
iconHTML: "",
|
iconHTML: "",
|
||||||
type: "readonly",
|
label: this.i18n.removeSpace,
|
||||||
label: "IDs<br>" + ids.join("<br>"),
|
click: () => {
|
||||||
|
const doOperations: IOperation[] = [];
|
||||||
|
detail.blockElements.forEach((item: HTMLElement) => {
|
||||||
|
const editElement = item.querySelector('[contenteditable="true"]');
|
||||||
|
if (editElement) {
|
||||||
|
editElement.textContent = editElement.textContent.replace(/ /g, "");
|
||||||
|
doOperations.push({
|
||||||
|
id: item.dataset.nodeId,
|
||||||
|
data: item.outerHTML,
|
||||||
|
action: "update"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
detail.protyle.getInstance().transaction(doOperations);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private showDialog() {
|
private showDialog() {
|
||||||
let dialog = new Dialog({
|
// let dialog = new Dialog({
|
||||||
title: "Hello World",
|
// title: `SiYuan ${Constants.SIYUAN_VERSION}`,
|
||||||
content: `<div id="helloPanel" class="b3-dialog__content"></div>`,
|
// content: `<div id="helloPanel" class="b3-dialog__content"></div>`,
|
||||||
|
// width: this.isMobile ? "92vw" : "720px",
|
||||||
|
// destroyCallback() {
|
||||||
|
// // hello.$destroy();
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// new HelloExample({
|
||||||
|
// target: dialog.element.querySelector("#helloPanel"),
|
||||||
|
// props: {
|
||||||
|
// app: this.app,
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
svelteDialog({
|
||||||
|
title: `SiYuan ${Constants.SIYUAN_VERSION}`,
|
||||||
width: this.isMobile ? "92vw" : "720px",
|
width: this.isMobile ? "92vw" : "720px",
|
||||||
destroyCallback(options) {
|
constructor: (container: HTMLElement) => {
|
||||||
// hello.$destroy();
|
return new HelloExample({
|
||||||
},
|
target: container,
|
||||||
});
|
props: {
|
||||||
let hello = new HelloExample({
|
app: this.app,
|
||||||
target: dialog.element.querySelector("#helloPanel"),
|
}
|
||||||
props: {
|
});
|
||||||
name: this.i18n.name,
|
|
||||||
i18n: this.i18n.hello
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -240,15 +490,15 @@ export default class PluginSample extends Plugin {
|
||||||
});
|
});
|
||||||
menu.addItem({
|
menu.addItem({
|
||||||
icon: "iconInfo",
|
icon: "iconInfo",
|
||||||
label: "Dialog",
|
label: "Dialog(open help first)",
|
||||||
accelerator: this.commands[0].customHotkey,
|
accelerator: this.commands[0].customHotkey,
|
||||||
click: () => {
|
click: () => {
|
||||||
this.showDialog()
|
this.showDialog();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!this.isMobile) {
|
if (!this.isMobile) {
|
||||||
menu.addItem({
|
menu.addItem({
|
||||||
icon: "iconLayoutBottom",
|
icon: "iconFace",
|
||||||
label: "Open Custom Tab",
|
label: "Open Custom Tab",
|
||||||
click: () => {
|
click: () => {
|
||||||
const tab = openTab({
|
const tab = openTab({
|
||||||
|
@ -259,14 +509,14 @@ export default class PluginSample extends Plugin {
|
||||||
data: {
|
data: {
|
||||||
text: "This is my custom tab",
|
text: "This is my custom tab",
|
||||||
},
|
},
|
||||||
fn: this.customTab
|
id: this.name + TAB_TYPE
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
console.log(tab);
|
console.log(tab);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
menu.addItem({
|
menu.addItem({
|
||||||
icon: "iconLayoutBottom",
|
icon: "iconImage",
|
||||||
label: "Open Asset Tab(open help first)",
|
label: "Open Asset Tab(open help first)",
|
||||||
click: () => {
|
click: () => {
|
||||||
const tab = openTab({
|
const tab = openTab({
|
||||||
|
@ -279,7 +529,7 @@ export default class PluginSample extends Plugin {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
menu.addItem({
|
menu.addItem({
|
||||||
icon: "iconLayoutBottom",
|
icon: "iconFile",
|
||||||
label: "Open Doc Tab(open help first)",
|
label: "Open Doc Tab(open help first)",
|
||||||
click: async () => {
|
click: async () => {
|
||||||
const tab = await openTab({
|
const tab = await openTab({
|
||||||
|
@ -292,7 +542,7 @@ export default class PluginSample extends Plugin {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
menu.addItem({
|
menu.addItem({
|
||||||
icon: "iconLayoutBottom",
|
icon: "iconSearch",
|
||||||
label: "Open Search Tab",
|
label: "Open Search Tab",
|
||||||
click: () => {
|
click: () => {
|
||||||
const tab = openTab({
|
const tab = openTab({
|
||||||
|
@ -305,7 +555,7 @@ export default class PluginSample extends Plugin {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
menu.addItem({
|
menu.addItem({
|
||||||
icon: "iconLayoutBottom",
|
icon: "iconRiffCard",
|
||||||
label: "Open Card Tab",
|
label: "Open Card Tab",
|
||||||
click: () => {
|
click: () => {
|
||||||
const tab = openTab({
|
const tab = openTab({
|
||||||
|
@ -329,7 +579,31 @@ export default class PluginSample extends Plugin {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
menu.addItem({
|
||||||
|
icon: "iconOpenWindow",
|
||||||
|
label: "Open Doc Window(open help first)",
|
||||||
|
click: () => {
|
||||||
|
openWindow({
|
||||||
|
doc: {id: "20200812220555-lj3enxa"}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
menu.addItem({
|
||||||
|
icon: "iconFile",
|
||||||
|
label: "Open Doc(open help first)",
|
||||||
|
click: () => {
|
||||||
|
openMobileFileById(this.app, "20200812220555-lj3enxa");
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
menu.addItem({
|
||||||
|
icon: "iconLock",
|
||||||
|
label: "Lockscreen",
|
||||||
|
click: () => {
|
||||||
|
lockScreen(this.app);
|
||||||
|
}
|
||||||
|
});
|
||||||
menu.addItem({
|
menu.addItem({
|
||||||
icon: "iconScrollHoriz",
|
icon: "iconScrollHoriz",
|
||||||
label: "Event Bus",
|
label: "Event Bus",
|
||||||
|
@ -350,13 +624,13 @@ export default class PluginSample extends Plugin {
|
||||||
icon: "iconSelect",
|
icon: "iconSelect",
|
||||||
label: "On click-blockicon",
|
label: "On click-blockicon",
|
||||||
click: () => {
|
click: () => {
|
||||||
this.eventBus.on("click-blockicon", this.blockIconEvent);
|
this.eventBus.on("click-blockicon", this.blockIconEventBindThis);
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
icon: "iconClose",
|
icon: "iconClose",
|
||||||
label: "Off click-blockicon",
|
label: "Off click-blockicon",
|
||||||
click: () => {
|
click: () => {
|
||||||
this.eventBus.off("click-blockicon", this.blockIconEvent);
|
this.eventBus.off("click-blockicon", this.blockIconEventBindThis);
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
icon: "iconSelect",
|
icon: "iconSelect",
|
||||||
|
@ -394,6 +668,18 @@ export default class PluginSample extends Plugin {
|
||||||
click: () => {
|
click: () => {
|
||||||
this.eventBus.off("click-editortitleicon", this.eventBusLog);
|
this.eventBus.off("click-editortitleicon", this.eventBusLog);
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconSelect",
|
||||||
|
label: "On click-flashcard-action",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.on("click-flashcard-action", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconClose",
|
||||||
|
label: "Off click-flashcard-action",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.off("click-flashcard-action", this.eventBusLog);
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
icon: "iconSelect",
|
icon: "iconSelect",
|
||||||
label: "On open-noneditableblock",
|
label: "On open-noneditableblock",
|
||||||
|
@ -408,15 +694,219 @@ export default class PluginSample extends Plugin {
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
icon: "iconSelect",
|
icon: "iconSelect",
|
||||||
label: "On loaded-protyle",
|
label: "On loaded-protyle-static",
|
||||||
click: () => {
|
click: () => {
|
||||||
this.eventBus.on("loaded-protyle", this.eventBusLog);
|
this.eventBus.on("loaded-protyle-static", this.eventBusLog);
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
icon: "iconClose",
|
icon: "iconClose",
|
||||||
label: "Off loaded-protyle",
|
label: "Off loaded-protyle-static",
|
||||||
click: () => {
|
click: () => {
|
||||||
this.eventBus.off("loaded-protyle", this.eventBusLog);
|
this.eventBus.off("loaded-protyle-static", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconSelect",
|
||||||
|
label: "On loaded-protyle-dynamic",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.on("loaded-protyle-dynamic", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconClose",
|
||||||
|
label: "Off loaded-protyle-dynamic",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.off("loaded-protyle-dynamic", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconSelect",
|
||||||
|
label: "On switch-protyle",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.on("switch-protyle", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconClose",
|
||||||
|
label: "Off switch-protyle",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.off("switch-protyle", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconSelect",
|
||||||
|
label: "On destroy-protyle",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.on("destroy-protyle", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconClose",
|
||||||
|
label: "Off destroy-protyle",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.off("destroy-protyle", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconSelect",
|
||||||
|
label: "On open-menu-doctree",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.on("open-menu-doctree", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconClose",
|
||||||
|
label: "Off open-menu-doctree",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.off("open-menu-doctree", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconSelect",
|
||||||
|
label: "On open-menu-blockref",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.on("open-menu-blockref", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconClose",
|
||||||
|
label: "Off open-menu-blockref",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.off("open-menu-blockref", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconSelect",
|
||||||
|
label: "On open-menu-fileannotationref",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.on("open-menu-fileannotationref", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconClose",
|
||||||
|
label: "Off open-menu-fileannotationref",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.off("open-menu-fileannotationref", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconSelect",
|
||||||
|
label: "On open-menu-tag",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.on("open-menu-tag", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconClose",
|
||||||
|
label: "Off open-menu-tag",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.off("open-menu-tag", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconSelect",
|
||||||
|
label: "On open-menu-link",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.on("open-menu-link", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconClose",
|
||||||
|
label: "Off open-menu-link",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.off("open-menu-link", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconSelect",
|
||||||
|
label: "On open-menu-image",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.on("open-menu-image", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconClose",
|
||||||
|
label: "Off open-menu-image",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.off("open-menu-image", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconSelect",
|
||||||
|
label: "On open-menu-av",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.on("open-menu-av", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconClose",
|
||||||
|
label: "Off open-menu-av",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.off("open-menu-av", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconSelect",
|
||||||
|
label: "On open-menu-content",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.on("open-menu-content", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconClose",
|
||||||
|
label: "Off open-menu-content",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.off("open-menu-content", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconSelect",
|
||||||
|
label: "On open-menu-breadcrumbmore",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.on("open-menu-breadcrumbmore", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconClose",
|
||||||
|
label: "Off open-menu-breadcrumbmore",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.off("open-menu-breadcrumbmore", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconSelect",
|
||||||
|
label: "On open-menu-inbox",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.on("open-menu-inbox", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconClose",
|
||||||
|
label: "Off open-menu-inbox",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.off("open-menu-inbox", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconSelect",
|
||||||
|
label: "On input-search",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.on("input-search", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconClose",
|
||||||
|
label: "Off input-search",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.off("input-search", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconSelect",
|
||||||
|
label: "On paste",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.on("paste", this.eventBusPaste);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconClose",
|
||||||
|
label: "Off paste",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.off("paste", this.eventBusPaste);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconSelect",
|
||||||
|
label: "On open-siyuan-url-plugin",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.on("open-siyuan-url-plugin", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconClose",
|
||||||
|
label: "Off open-siyuan-url-plugin",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.off("open-siyuan-url-plugin", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconSelect",
|
||||||
|
label: "On open-siyuan-url-block",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.on("open-siyuan-url-block", this.eventBusLog);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
icon: "iconClose",
|
||||||
|
label: "Off open-siyuan-url-block",
|
||||||
|
click: () => {
|
||||||
|
this.eventBus.off("open-siyuan-url-block", this.eventBusLog);
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
|
|
118
src/libs/components/Form/form-input.svelte
Normal file
118
src/libs/components/Form/form-input.svelte
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { createEventDispatcher } from "svelte";
|
||||||
|
export let type: string; // Setting Type
|
||||||
|
export let key: string;
|
||||||
|
export let value: any;
|
||||||
|
|
||||||
|
// Optional parameters
|
||||||
|
export let placeholder: string = "";
|
||||||
|
export let options: { [key: string | number]: string } = {};
|
||||||
|
export let slider: {
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
step: number;
|
||||||
|
} = { min: 0, max: 100, step: 1 };
|
||||||
|
export let button: {
|
||||||
|
label: string;
|
||||||
|
callback?: () => void;
|
||||||
|
} = { label: value, callback: () => {} };
|
||||||
|
export let fnSize: boolean = true; // If the form input is used within setting panel context, it is usually given a fixed width by a class named "fn__size200".
|
||||||
|
export let style: string = ""; // Custom style
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
function click() {
|
||||||
|
button?.callback();
|
||||||
|
dispatch("click", { key: key });
|
||||||
|
}
|
||||||
|
|
||||||
|
function changed() {
|
||||||
|
dispatch("changed", { key: key, value: value });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if type === "checkbox"}
|
||||||
|
<!-- Checkbox -->
|
||||||
|
<input
|
||||||
|
class="b3-switch fn__flex-center"
|
||||||
|
id={key}
|
||||||
|
type="checkbox"
|
||||||
|
bind:checked={value}
|
||||||
|
on:change={changed}
|
||||||
|
style={style}
|
||||||
|
/>
|
||||||
|
{:else if type === "textinput"}
|
||||||
|
<!-- Text Input -->
|
||||||
|
<input
|
||||||
|
class:b3-text-field={true}
|
||||||
|
class:fn__flex-center={true}
|
||||||
|
class:fn__size200={fnSize}
|
||||||
|
id={key}
|
||||||
|
{placeholder}
|
||||||
|
bind:value={value}
|
||||||
|
on:change={changed}
|
||||||
|
style={style}
|
||||||
|
/>
|
||||||
|
{:else if type === "textarea"}
|
||||||
|
<textarea
|
||||||
|
class="b3-text-field fn__block"
|
||||||
|
style={`resize: vertical; height: 10em; white-space: nowrap; ${style}`}
|
||||||
|
bind:value={value}
|
||||||
|
on:change={changed}
|
||||||
|
/>
|
||||||
|
{:else if type === "number"}
|
||||||
|
<input
|
||||||
|
class:b3-text-field={true}
|
||||||
|
class:fn__flex-center={true}
|
||||||
|
class:fn__size200={fnSize}
|
||||||
|
id={key}
|
||||||
|
type="number"
|
||||||
|
bind:value={value}
|
||||||
|
on:change={changed}
|
||||||
|
style={style}
|
||||||
|
/>
|
||||||
|
{:else if type === "button"}
|
||||||
|
<!-- Button Input -->
|
||||||
|
<button
|
||||||
|
class:b3-button={true}
|
||||||
|
class:b3-button--outline={true}
|
||||||
|
class:fn__flex-center={true}
|
||||||
|
class:fn__size200={fnSize}
|
||||||
|
id={key}
|
||||||
|
on:click={click}
|
||||||
|
style={style}
|
||||||
|
>
|
||||||
|
{button.label}
|
||||||
|
</button>
|
||||||
|
{:else if type === "select"}
|
||||||
|
<!-- Dropdown select -->
|
||||||
|
<select
|
||||||
|
class:b3-select={true}
|
||||||
|
class:fn__flex-center={true}
|
||||||
|
class:fn__size200={fnSize}
|
||||||
|
id="iconPosition"
|
||||||
|
bind:value={value}
|
||||||
|
on:change={changed}
|
||||||
|
style={style}
|
||||||
|
>
|
||||||
|
{#each Object.entries(options) as [value, text]}
|
||||||
|
<option {value}>{text}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
{:else if type == "slider"}
|
||||||
|
<!-- Slider -->
|
||||||
|
<div class="b3-tooltips b3-tooltips__n" aria-label={value}>
|
||||||
|
<input
|
||||||
|
class:b3-slider={true}
|
||||||
|
class:fn__size200={fnSize}
|
||||||
|
id="fontSize"
|
||||||
|
min={slider.min}
|
||||||
|
max={slider.max}
|
||||||
|
step={slider.step}
|
||||||
|
type="range"
|
||||||
|
bind:value={value}
|
||||||
|
on:change={changed}
|
||||||
|
style={style}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
53
src/libs/components/Form/form-wrap.svelte
Normal file
53
src/libs/components/Form/form-wrap.svelte
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
<!--
|
||||||
|
Copyright (c) 2024 by frostime. All Rights Reserved.
|
||||||
|
Author : frostime
|
||||||
|
Date : 2024-06-01 20:03:50
|
||||||
|
FilePath : /src/libs/components/item-wrap.svelte
|
||||||
|
LastEditTime : 2024-07-19 15:28:57
|
||||||
|
Description : The setting item container
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
export let title: string; // Displayint Setting Title
|
||||||
|
export let description: string; // Displaying Setting Text
|
||||||
|
export let direction: 'row' | 'column' = 'column';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if direction === "row"}
|
||||||
|
<div class="item-wrap b3-label" data-key="CustomCSS">
|
||||||
|
<div class="fn__block">
|
||||||
|
<span class="title">{title}</span>
|
||||||
|
<div class="b3-label__text">{@html description}</div>
|
||||||
|
<div class="fn__hr"></div>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 5px; position: relative;">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="item-wrap fn__flex b3-label config__item">
|
||||||
|
<div class="fn__flex-1">
|
||||||
|
<span class="title">{title}</span>
|
||||||
|
<div class="b3-label__text">
|
||||||
|
{@html description}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="fn__space" />
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
span.title {
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--b3-theme-primary)
|
||||||
|
}
|
||||||
|
.item-wrap.b3-label {
|
||||||
|
box-shadow: none !important;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-wrap.b3-label:not(:last-child) {
|
||||||
|
border-bottom: 1px solid var(--b3-border-color);
|
||||||
|
}
|
||||||
|
</style>
|
6
src/libs/components/Form/index.ts
Normal file
6
src/libs/components/Form/index.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import FormInput from './form-input.svelte';
|
||||||
|
import FormWrap from './form-wrap.svelte';
|
||||||
|
|
||||||
|
const Form = { Wrap: FormWrap, Input: FormInput };
|
||||||
|
export default Form;
|
||||||
|
export { FormInput, FormWrap };
|
51
src/libs/components/setting-panel.svelte
Normal file
51
src/libs/components/setting-panel.svelte
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
<!--
|
||||||
|
Copyright (c) 2023 by frostime All Rights Reserved.
|
||||||
|
Author : frostime
|
||||||
|
Date : 2023-07-01 19:23:50
|
||||||
|
FilePath : /src/libs/components/setting-panel.svelte
|
||||||
|
LastEditTime : 2024-08-09 21:41:07
|
||||||
|
Description :
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import { createEventDispatcher } from "svelte";
|
||||||
|
import Form from './Form';
|
||||||
|
|
||||||
|
export let group: string;
|
||||||
|
export let settingItems: ISettingItem[];
|
||||||
|
export let display: boolean = true;
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
function onClick( {detail}) {
|
||||||
|
dispatch("click", { key: detail.key });
|
||||||
|
}
|
||||||
|
function onChanged( {detail}) {
|
||||||
|
dispatch("changed", {group: group, ...detail});
|
||||||
|
}
|
||||||
|
|
||||||
|
$: fn__none = display ? "" : "fn__none";
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="config__tab-container {fn__none}" data-name={group}>
|
||||||
|
<slot />
|
||||||
|
{#each settingItems as item (item.key)}
|
||||||
|
<Form.Wrap
|
||||||
|
title={item.title}
|
||||||
|
description={item.description}
|
||||||
|
direction={item?.direction}
|
||||||
|
>
|
||||||
|
<Form.Input
|
||||||
|
type={item.type}
|
||||||
|
key={item.key}
|
||||||
|
bind:value={item.value}
|
||||||
|
placeholder={item?.placeholder}
|
||||||
|
options={item?.options}
|
||||||
|
slider={item?.slider}
|
||||||
|
button={item?.button}
|
||||||
|
on:click={onClick}
|
||||||
|
on:changed={onChanged}
|
||||||
|
/>
|
||||||
|
</Form.Wrap>
|
||||||
|
{/each}
|
||||||
|
</div>
|
99
src/libs/const.ts
Normal file
99
src/libs/const.ts
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 by frostime. All Rights Reserved.
|
||||||
|
* @Author : frostime
|
||||||
|
* @Date : 2024-06-08 20:36:30
|
||||||
|
* @FilePath : /src/libs/const.ts
|
||||||
|
* @LastEditTime : 2024-06-08 20:48:06
|
||||||
|
* @Description :
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
export const BlockType2NodeType: {[key in BlockType]: string} = {
|
||||||
|
d: 'NodeDocument',
|
||||||
|
p: 'NodeParagraph',
|
||||||
|
query_embed: 'NodeBlockQueryEmbed',
|
||||||
|
l: 'NodeList',
|
||||||
|
i: 'NodeListItem',
|
||||||
|
h: 'NodeHeading',
|
||||||
|
iframe: 'NodeIFrame',
|
||||||
|
tb: 'NodeThematicBreak',
|
||||||
|
b: 'NodeBlockquote',
|
||||||
|
s: 'NodeSuperBlock',
|
||||||
|
c: 'NodeCodeBlock',
|
||||||
|
widget: 'NodeWidget',
|
||||||
|
t: 'NodeTable',
|
||||||
|
html: 'NodeHTMLBlock',
|
||||||
|
m: 'NodeMathBlock',
|
||||||
|
av: 'NodeAttributeView',
|
||||||
|
audio: 'NodeAudio'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const NodeIcons = {
|
||||||
|
NodeAttributeView: {
|
||||||
|
icon: "iconDatabase"
|
||||||
|
},
|
||||||
|
NodeAudio: {
|
||||||
|
icon: "iconRecord"
|
||||||
|
},
|
||||||
|
NodeBlockQueryEmbed: {
|
||||||
|
icon: "iconSQL"
|
||||||
|
},
|
||||||
|
NodeBlockquote: {
|
||||||
|
icon: "iconQuote"
|
||||||
|
},
|
||||||
|
NodeCodeBlock: {
|
||||||
|
icon: "iconCode"
|
||||||
|
},
|
||||||
|
NodeDocument: {
|
||||||
|
icon: "iconFile"
|
||||||
|
},
|
||||||
|
NodeHTMLBlock: {
|
||||||
|
icon: "iconHTML5"
|
||||||
|
},
|
||||||
|
NodeHeading: {
|
||||||
|
icon: "iconHeadings",
|
||||||
|
subtypes: {
|
||||||
|
h1: { icon: "iconH1" },
|
||||||
|
h2: { icon: "iconH2" },
|
||||||
|
h3: { icon: "iconH3" },
|
||||||
|
h4: { icon: "iconH4" },
|
||||||
|
h5: { icon: "iconH5" },
|
||||||
|
h6: { icon: "iconH6" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NodeIFrame: {
|
||||||
|
icon: "iconLanguage"
|
||||||
|
},
|
||||||
|
NodeList: {
|
||||||
|
subtypes: {
|
||||||
|
o: { icon: "iconOrderedList" },
|
||||||
|
t: { icon: "iconCheck" },
|
||||||
|
u: { icon: "iconList" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NodeListItem: {
|
||||||
|
icon: "iconListItem"
|
||||||
|
},
|
||||||
|
NodeMathBlock: {
|
||||||
|
icon: "iconMath"
|
||||||
|
},
|
||||||
|
NodeParagraph: {
|
||||||
|
icon: "iconParagraph"
|
||||||
|
},
|
||||||
|
NodeSuperBlock: {
|
||||||
|
icon: "iconSuper"
|
||||||
|
},
|
||||||
|
NodeTable: {
|
||||||
|
icon: "iconTable"
|
||||||
|
},
|
||||||
|
NodeThematicBreak: {
|
||||||
|
icon: "iconLine"
|
||||||
|
},
|
||||||
|
NodeVideo: {
|
||||||
|
icon: "iconVideo"
|
||||||
|
},
|
||||||
|
NodeWidget: {
|
||||||
|
icon: "iconBoth"
|
||||||
|
}
|
||||||
|
};
|
164
src/libs/dialog.ts
Normal file
164
src/libs/dialog.ts
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 by frostime. All Rights Reserved.
|
||||||
|
* @Author : frostime
|
||||||
|
* @Date : 2024-03-23 21:37:33
|
||||||
|
* @FilePath : /src/libs/dialog.ts
|
||||||
|
* @LastEditTime : 2024-10-16 14:31:04
|
||||||
|
* @Description : Kits about dialogs
|
||||||
|
*/
|
||||||
|
import { Dialog } from "siyuan";
|
||||||
|
import { type SvelteComponent } from "svelte";
|
||||||
|
|
||||||
|
export const inputDialog = (args: {
|
||||||
|
title: string, placeholder?: string, defaultText?: string,
|
||||||
|
confirm?: (text: string) => void, cancel?: () => void,
|
||||||
|
width?: string, height?: string
|
||||||
|
}) => {
|
||||||
|
const dialog = new Dialog({
|
||||||
|
title: args.title,
|
||||||
|
content: `<div class="b3-dialog__content">
|
||||||
|
<div class="ft__breakword"><textarea class="b3-text-field fn__block" style="height: 100%;" placeholder=${args?.placeholder ?? ''}>${args?.defaultText ?? ''}</textarea></div>
|
||||||
|
</div>
|
||||||
|
<div class="b3-dialog__action">
|
||||||
|
<button class="b3-button b3-button--cancel">${window.siyuan.languages.cancel}</button><div class="fn__space"></div>
|
||||||
|
<button class="b3-button b3-button--text" id="confirmDialogConfirmBtn">${window.siyuan.languages.confirm}</button>
|
||||||
|
</div>`,
|
||||||
|
width: args.width ?? "520px",
|
||||||
|
height: args.height
|
||||||
|
});
|
||||||
|
const target: HTMLTextAreaElement = dialog.element.querySelector(".b3-dialog__content>div.ft__breakword>textarea");
|
||||||
|
const btnsElement = dialog.element.querySelectorAll(".b3-button");
|
||||||
|
btnsElement[0].addEventListener("click", () => {
|
||||||
|
if (args?.cancel) {
|
||||||
|
args.cancel();
|
||||||
|
}
|
||||||
|
dialog.destroy();
|
||||||
|
});
|
||||||
|
btnsElement[1].addEventListener("click", () => {
|
||||||
|
if (args?.confirm) {
|
||||||
|
args.confirm(target.value);
|
||||||
|
}
|
||||||
|
dialog.destroy();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const inputDialogSync = async (args: {
|
||||||
|
title: string, placeholder?: string, defaultText?: string,
|
||||||
|
width?: string, height?: string
|
||||||
|
}) => {
|
||||||
|
return new Promise<string>((resolve) => {
|
||||||
|
let newargs = {
|
||||||
|
...args, confirm: (text) => {
|
||||||
|
resolve(text);
|
||||||
|
}, cancel: () => {
|
||||||
|
resolve(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
inputDialog(newargs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface IConfirmDialogArgs {
|
||||||
|
title: string;
|
||||||
|
content: string | HTMLElement;
|
||||||
|
confirm?: (ele?: HTMLElement) => void;
|
||||||
|
cancel?: (ele?: HTMLElement) => void;
|
||||||
|
width?: string;
|
||||||
|
height?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const confirmDialog = (args: IConfirmDialogArgs) => {
|
||||||
|
const { title, content, confirm, cancel, width, height } = args;
|
||||||
|
|
||||||
|
const dialog = new Dialog({
|
||||||
|
title,
|
||||||
|
content: `<div class="b3-dialog__content">
|
||||||
|
<div class="ft__breakword">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="b3-dialog__action">
|
||||||
|
<button class="b3-button b3-button--cancel">${window.siyuan.languages.cancel}</button><div class="fn__space"></div>
|
||||||
|
<button class="b3-button b3-button--text" id="confirmDialogConfirmBtn">${window.siyuan.languages.confirm}</button>
|
||||||
|
</div>`,
|
||||||
|
width: width,
|
||||||
|
height: height
|
||||||
|
});
|
||||||
|
|
||||||
|
const target: HTMLElement = dialog.element.querySelector(".b3-dialog__content>div.ft__breakword");
|
||||||
|
if (typeof content === "string") {
|
||||||
|
target.innerHTML = content;
|
||||||
|
} else {
|
||||||
|
target.appendChild(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
const btnsElement = dialog.element.querySelectorAll(".b3-button");
|
||||||
|
btnsElement[0].addEventListener("click", () => {
|
||||||
|
if (cancel) {
|
||||||
|
cancel(target);
|
||||||
|
}
|
||||||
|
dialog.destroy();
|
||||||
|
});
|
||||||
|
btnsElement[1].addEventListener("click", () => {
|
||||||
|
if (confirm) {
|
||||||
|
confirm(target);
|
||||||
|
}
|
||||||
|
dialog.destroy();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const confirmDialogSync = async (args: IConfirmDialogArgs) => {
|
||||||
|
return new Promise<HTMLElement>((resolve) => {
|
||||||
|
let newargs = {
|
||||||
|
...args, confirm: (ele: HTMLElement) => {
|
||||||
|
resolve(ele);
|
||||||
|
}, cancel: (ele: HTMLElement) => {
|
||||||
|
resolve(ele);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
confirmDialog(newargs);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const simpleDialog = (args: {
|
||||||
|
title: string, ele: HTMLElement | DocumentFragment,
|
||||||
|
width?: string, height?: string,
|
||||||
|
callback?: () => void;
|
||||||
|
}) => {
|
||||||
|
const dialog = new Dialog({
|
||||||
|
title: args.title,
|
||||||
|
content: `<div class="dialog-content" style="display: flex; height: 100%;"/>`,
|
||||||
|
width: args.width,
|
||||||
|
height: args.height,
|
||||||
|
destroyCallback: args.callback
|
||||||
|
});
|
||||||
|
dialog.element.querySelector(".dialog-content").appendChild(args.ele);
|
||||||
|
return {
|
||||||
|
dialog,
|
||||||
|
close: dialog.destroy.bind(dialog)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const svelteDialog = (args: {
|
||||||
|
title: string, constructor: (container: HTMLElement) => SvelteComponent,
|
||||||
|
width?: string, height?: string,
|
||||||
|
callback?: () => void;
|
||||||
|
}) => {
|
||||||
|
let container = document.createElement('div')
|
||||||
|
container.style.display = 'contents';
|
||||||
|
let component = args.constructor(container);
|
||||||
|
const { dialog, close } = simpleDialog({
|
||||||
|
...args, ele: container, callback: () => {
|
||||||
|
component.$destroy();
|
||||||
|
if (args.callback) args.callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
component,
|
||||||
|
dialog,
|
||||||
|
close
|
||||||
|
}
|
||||||
|
}
|
43
src/libs/index.d.ts
vendored
Normal file
43
src/libs/index.d.ts
vendored
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 by frostime. All Rights Reserved.
|
||||||
|
* @Author : frostime
|
||||||
|
* @Date : 2024-04-19 18:30:12
|
||||||
|
* @FilePath : /src/libs/index.d.ts
|
||||||
|
* @LastEditTime : 2024-04-30 16:39:54
|
||||||
|
* @Description :
|
||||||
|
*/
|
||||||
|
type TSettingItemType = "checkbox" | "select" | "textinput" | "textarea" | "number" | "slider" | "button" | "hint" | "custom";
|
||||||
|
|
||||||
|
interface ISettingItemCore {
|
||||||
|
type: TSettingItemType;
|
||||||
|
key: string;
|
||||||
|
value: any;
|
||||||
|
placeholder?: string;
|
||||||
|
slider?: {
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
step: number;
|
||||||
|
};
|
||||||
|
options?: { [key: string | number]: string };
|
||||||
|
button?: {
|
||||||
|
label: string;
|
||||||
|
callback: () => void;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ISettingItem extends ISettingItemCore {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
direction?: "row" | "column";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Interface for setting-utils
|
||||||
|
interface ISettingUtilsItem extends ISettingItem {
|
||||||
|
action?: {
|
||||||
|
callback: () => void;
|
||||||
|
}
|
||||||
|
createElement?: (currentVal: any) => HTMLElement;
|
||||||
|
getEleVal?: (ele: HTMLElement) => any;
|
||||||
|
setEleVal?: (ele: HTMLElement, val: any) => void;
|
||||||
|
}
|
48
src/libs/promise-pool.ts
Normal file
48
src/libs/promise-pool.ts
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
export default class PromiseLimitPool<T> {
|
||||||
|
private maxConcurrent: number;
|
||||||
|
private currentRunning = 0;
|
||||||
|
private queue: (() => void)[] = [];
|
||||||
|
private promises: Promise<T>[] = [];
|
||||||
|
|
||||||
|
constructor(maxConcurrent: number) {
|
||||||
|
this.maxConcurrent = maxConcurrent;
|
||||||
|
}
|
||||||
|
|
||||||
|
add(fn: () => Promise<T>): void {
|
||||||
|
const promise = new Promise<T>((resolve, reject) => {
|
||||||
|
const run = async () => {
|
||||||
|
try {
|
||||||
|
this.currentRunning++;
|
||||||
|
const result = await fn();
|
||||||
|
resolve(result);
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
} finally {
|
||||||
|
this.currentRunning--;
|
||||||
|
this.next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.currentRunning < this.maxConcurrent) {
|
||||||
|
run();
|
||||||
|
} else {
|
||||||
|
this.queue.push(run);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.promises.push(promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
async awaitAll(): Promise<T[]> {
|
||||||
|
return Promise.all(this.promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the next task in the queue.
|
||||||
|
*/
|
||||||
|
private next(): void {
|
||||||
|
if (this.queue.length > 0 && this.currentRunning < this.maxConcurrent) {
|
||||||
|
const nextRun = this.queue.shift()!;
|
||||||
|
nextRun();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,90 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { createEventDispatcher } from "svelte";
|
|
||||||
export let type: string; // Setting Type
|
|
||||||
export let title: string; // Displayint Setting Title
|
|
||||||
export let text: string; // Displaying Setting Text
|
|
||||||
export let settingKey: string;
|
|
||||||
export let settingValue: any;
|
|
||||||
|
|
||||||
//Optional
|
|
||||||
export let placeholder: string = ""; // Use it if type is input
|
|
||||||
export let options: { [key: string]: string } = {}; // Use it if type is select
|
|
||||||
export let slider: {
|
|
||||||
min: number;
|
|
||||||
max: number;
|
|
||||||
step: number;
|
|
||||||
} = {min: 0, max: 100, step: 1}; // Use it if type is slider
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
|
|
||||||
function clicked() {
|
|
||||||
dispatch("clicked");
|
|
||||||
}
|
|
||||||
|
|
||||||
function changed() {
|
|
||||||
dispatch("changed", { key: settingKey, value: settingValue });
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<label class="fn__flex b3-label">
|
|
||||||
<div class="fn__flex-1">
|
|
||||||
{title}
|
|
||||||
<div class="b3-label__text">
|
|
||||||
{text}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span class="fn__space" />
|
|
||||||
<!-- <slot /> -->
|
|
||||||
{#if type === "checkbox"}
|
|
||||||
<!-- Checkbox -->
|
|
||||||
<input
|
|
||||||
class="b3-switch fn__flex-center"
|
|
||||||
id={settingKey}
|
|
||||||
type="checkbox"
|
|
||||||
bind:checked={settingValue}
|
|
||||||
on:change={changed}
|
|
||||||
/>
|
|
||||||
{:else if type === "input"}
|
|
||||||
<!-- Text Input -->
|
|
||||||
<input
|
|
||||||
class="b3-text-field fn__flex-center fn__size200"
|
|
||||||
id={settingKey}
|
|
||||||
{placeholder}
|
|
||||||
bind:value={settingValue}
|
|
||||||
on:change={changed}
|
|
||||||
/>
|
|
||||||
{:else if type === "button"}
|
|
||||||
<!-- Button Input -->
|
|
||||||
<button
|
|
||||||
class="b3-button b3-button--outline fn__flex-center fn__size200"
|
|
||||||
id={settingKey}
|
|
||||||
on:click={clicked}
|
|
||||||
>
|
|
||||||
{settingValue}
|
|
||||||
</button>
|
|
||||||
{:else if type === "select"}
|
|
||||||
<!-- Dropdown select -->
|
|
||||||
<select
|
|
||||||
class="b3-select fn__flex-center fn__size200"
|
|
||||||
id="iconPosition"
|
|
||||||
bind:value={settingValue}
|
|
||||||
on:change={changed}
|
|
||||||
>
|
|
||||||
{#each Object.entries(options) as [value, text]}
|
|
||||||
<option {value}>{text}</option>
|
|
||||||
{/each}
|
|
||||||
</select>
|
|
||||||
{:else if type == "slider"}
|
|
||||||
<!-- Slider -->
|
|
||||||
<input
|
|
||||||
class="b3-slider fn__size200"
|
|
||||||
id="fontSize"
|
|
||||||
min="{slider.min}"
|
|
||||||
max="{slider.max}"
|
|
||||||
step="{slider.step}"
|
|
||||||
type="range"
|
|
||||||
bind:value={settingValue}
|
|
||||||
on:change={changed}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</label>
|
|
|
@ -1,99 +0,0 @@
|
||||||
<script>
|
|
||||||
import SettingItem from "./setting-item.svelte";
|
|
||||||
import { showMessage } from "siyuan";
|
|
||||||
import { onMount, onDestroy } from 'svelte';
|
|
||||||
onMount(() => {
|
|
||||||
showMessage("Setting panel opened");
|
|
||||||
});
|
|
||||||
onDestroy(() => {
|
|
||||||
showMessage("Setting panel closed");
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
You can use this template to quickly create a setting panel,
|
|
||||||
with the same UI style in SiYuan
|
|
||||||
-->
|
|
||||||
|
|
||||||
<div class="config__tab-container">
|
|
||||||
<div data-type="Header" class="fn__flex b3-label">
|
|
||||||
<div class="fn_flex-1">
|
|
||||||
<h4>This setting panel is provided by a svelte component</h4>
|
|
||||||
<div class="b3-label__text">
|
|
||||||
<span class="fn__flex-1">
|
|
||||||
See:
|
|
||||||
<pre style="display: inline">/lib/setting-pannel.svelte</pre>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<SettingItem
|
|
||||||
type="checkbox"
|
|
||||||
title="Checkbox"
|
|
||||||
text="This is a checkbox"
|
|
||||||
settingKey="Checkbox"
|
|
||||||
settingValue={true}
|
|
||||||
on:changed={(event) => {
|
|
||||||
showMessage(
|
|
||||||
`Checkbox changed: ${event.detail.key} = ${event.detail.value}`
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<SettingItem
|
|
||||||
type="input"
|
|
||||||
title="Input"
|
|
||||||
text="This is an input"
|
|
||||||
settingKey="Input"
|
|
||||||
settingValue=""
|
|
||||||
placeholder="Input something"
|
|
||||||
on:changed={(event) => {
|
|
||||||
showMessage(
|
|
||||||
`Input changed: ${event.detail.key} = ${event.detail.value}`
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<SettingItem
|
|
||||||
type="button"
|
|
||||||
title="Button"
|
|
||||||
text="This is a button"
|
|
||||||
settingKey="Button"
|
|
||||||
settingValue="Click me"
|
|
||||||
on:clicked={() => {
|
|
||||||
showMessage("Button clicked");
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<SettingItem
|
|
||||||
type="select"
|
|
||||||
title="Select"
|
|
||||||
text="This is a select"
|
|
||||||
settingKey="Select"
|
|
||||||
settingValue="left"
|
|
||||||
options={{
|
|
||||||
left: "Left",
|
|
||||||
center: "Center",
|
|
||||||
right: "Right",
|
|
||||||
}}
|
|
||||||
on:changed={(event) => {
|
|
||||||
showMessage(
|
|
||||||
`Select changed: ${event.detail.key} = ${event.detail.value}`
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<SettingItem
|
|
||||||
type="slider"
|
|
||||||
title="Slide"
|
|
||||||
text="This is a slide"
|
|
||||||
settingKey="Slide"
|
|
||||||
settingValue={50}
|
|
||||||
slider={{
|
|
||||||
min: 0,
|
|
||||||
max: 100,
|
|
||||||
step: 1,
|
|
||||||
}}
|
|
||||||
on:changed={(event) => {
|
|
||||||
showMessage(
|
|
||||||
`Slide changed: ${event.detail.key} = ${event.detail.value}`
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
397
src/libs/setting-utils.ts
Normal file
397
src/libs/setting-utils.ts
Normal file
|
@ -0,0 +1,397 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 by frostime. All Rights Reserved.
|
||||||
|
* @Author : frostime
|
||||||
|
* @Date : 2023-12-17 18:28:19
|
||||||
|
* @FilePath : /src/libs/setting-utils.ts
|
||||||
|
* @LastEditTime : 2024-05-01 17:44:16
|
||||||
|
* @Description :
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Plugin, Setting } from 'siyuan';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default function to get the value of the element
|
||||||
|
* @param type
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const createDefaultGetter = (type: TSettingItemType) => {
|
||||||
|
let getter: (ele: HTMLElement) => any;
|
||||||
|
switch (type) {
|
||||||
|
case 'checkbox':
|
||||||
|
getter = (ele: HTMLInputElement) => {
|
||||||
|
return ele.checked;
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'select':
|
||||||
|
case 'slider':
|
||||||
|
case 'textinput':
|
||||||
|
case 'textarea':
|
||||||
|
getter = (ele: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement) => {
|
||||||
|
return ele.value;
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'number':
|
||||||
|
getter = (ele: HTMLInputElement) => {
|
||||||
|
return parseInt(ele.value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
getter = () => null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return getter;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default function to set the value of the element
|
||||||
|
* @param type
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const createDefaultSetter = (type: TSettingItemType) => {
|
||||||
|
let setter: (ele: HTMLElement, value: any) => void;
|
||||||
|
switch (type) {
|
||||||
|
case 'checkbox':
|
||||||
|
setter = (ele: HTMLInputElement, value: any) => {
|
||||||
|
ele.checked = value;
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'select':
|
||||||
|
case 'slider':
|
||||||
|
case 'textinput':
|
||||||
|
case 'textarea':
|
||||||
|
case 'number':
|
||||||
|
setter = (ele: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement, value: any) => {
|
||||||
|
ele.value = value;
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
setter = () => {};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return setter;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class SettingUtils {
|
||||||
|
plugin: Plugin;
|
||||||
|
name: string;
|
||||||
|
file: string;
|
||||||
|
|
||||||
|
settings: Map<string, ISettingUtilsItem> = new Map();
|
||||||
|
elements: Map<string, HTMLElement> = new Map();
|
||||||
|
|
||||||
|
constructor(args: {
|
||||||
|
plugin: Plugin,
|
||||||
|
name?: string,
|
||||||
|
callback?: (data: any) => void,
|
||||||
|
width?: string,
|
||||||
|
height?: string
|
||||||
|
}) {
|
||||||
|
this.name = args.name ?? 'settings';
|
||||||
|
this.plugin = args.plugin;
|
||||||
|
this.file = this.name.endsWith('.json') ? this.name : `${this.name}.json`;
|
||||||
|
this.plugin.setting = new Setting({
|
||||||
|
width: args.width,
|
||||||
|
height: args.height,
|
||||||
|
confirmCallback: () => {
|
||||||
|
for (let key of this.settings.keys()) {
|
||||||
|
this.updateValueFromElement(key);
|
||||||
|
}
|
||||||
|
let data = this.dump();
|
||||||
|
if (args.callback !== undefined) {
|
||||||
|
args.callback(data);
|
||||||
|
}
|
||||||
|
this.plugin.data[this.name] = data;
|
||||||
|
this.save(data);
|
||||||
|
},
|
||||||
|
destroyCallback: () => {
|
||||||
|
//Restore the original value
|
||||||
|
for (let key of this.settings.keys()) {
|
||||||
|
this.updateElementFromValue(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
let data = await this.plugin.loadData(this.file);
|
||||||
|
console.debug('Load config:', data);
|
||||||
|
if (data) {
|
||||||
|
for (let [key, item] of this.settings) {
|
||||||
|
item.value = data?.[key] ?? item.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.plugin.data[this.name] = this.dump();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async save(data?: any) {
|
||||||
|
data = data ?? this.dump();
|
||||||
|
await this.plugin.saveData(this.file, this.dump());
|
||||||
|
console.debug('Save config:', data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* read the data after saving
|
||||||
|
* @param key key name
|
||||||
|
* @returns setting item value
|
||||||
|
*/
|
||||||
|
get(key: string) {
|
||||||
|
return this.settings.get(key)?.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set data to this.settings,
|
||||||
|
* but do not save it to the configuration file
|
||||||
|
* @param key key name
|
||||||
|
* @param value value
|
||||||
|
*/
|
||||||
|
set(key: string, value: any) {
|
||||||
|
let item = this.settings.get(key);
|
||||||
|
if (item) {
|
||||||
|
item.value = value;
|
||||||
|
this.updateElementFromValue(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set and save setting item value
|
||||||
|
* If you want to set and save immediately you can use this method
|
||||||
|
* @param key key name
|
||||||
|
* @param value value
|
||||||
|
*/
|
||||||
|
async setAndSave(key: string, value: any) {
|
||||||
|
let item = this.settings.get(key);
|
||||||
|
if (item) {
|
||||||
|
item.value = value;
|
||||||
|
this.updateElementFromValue(key);
|
||||||
|
await this.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read in the value of element instead of setting obj in real time
|
||||||
|
* @param key key name
|
||||||
|
* @param apply whether to apply the value to the setting object
|
||||||
|
* if true, the value will be applied to the setting object
|
||||||
|
* @returns value in html
|
||||||
|
*/
|
||||||
|
take(key: string, apply: boolean = false) {
|
||||||
|
let item = this.settings.get(key);
|
||||||
|
let element = this.elements.get(key) as any;
|
||||||
|
if (!element) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (apply) {
|
||||||
|
this.updateValueFromElement(key);
|
||||||
|
}
|
||||||
|
return item.getEleVal(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read data from html and save it
|
||||||
|
* @param key key name
|
||||||
|
* @param value value
|
||||||
|
* @return value in html
|
||||||
|
*/
|
||||||
|
async takeAndSave(key: string) {
|
||||||
|
let value = this.take(key, true);
|
||||||
|
await this.save();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable setting item
|
||||||
|
* @param key key name
|
||||||
|
*/
|
||||||
|
disable(key: string) {
|
||||||
|
let element = this.elements.get(key) as any;
|
||||||
|
if (element) {
|
||||||
|
element.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable setting item
|
||||||
|
* @param key key name
|
||||||
|
*/
|
||||||
|
enable(key: string) {
|
||||||
|
let element = this.elements.get(key) as any;
|
||||||
|
if (element) {
|
||||||
|
element.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将设置项目导出为 JSON 对象
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
dump(): Object {
|
||||||
|
let data: any = {};
|
||||||
|
for (let [key, item] of this.settings) {
|
||||||
|
if (item.type === 'button') continue;
|
||||||
|
data[key] = item.value;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
addItem(item: ISettingUtilsItem) {
|
||||||
|
this.settings.set(item.key, item);
|
||||||
|
const IsCustom = item.type === 'custom';
|
||||||
|
let error = IsCustom && (item.createElement === undefined || item.getEleVal === undefined || item.setEleVal === undefined);
|
||||||
|
if (error) {
|
||||||
|
console.error('The custom setting item must have createElement, getEleVal and setEleVal methods');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.getEleVal === undefined) {
|
||||||
|
item.getEleVal = createDefaultGetter(item.type);
|
||||||
|
}
|
||||||
|
if (item.setEleVal === undefined) {
|
||||||
|
item.setEleVal = createDefaultSetter(item.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.createElement === undefined) {
|
||||||
|
let itemElement = this.createDefaultElement(item);
|
||||||
|
this.elements.set(item.key, itemElement);
|
||||||
|
this.plugin.setting.addItem({
|
||||||
|
title: item.title,
|
||||||
|
description: item?.description,
|
||||||
|
direction: item?.direction,
|
||||||
|
createActionElement: () => {
|
||||||
|
this.updateElementFromValue(item.key);
|
||||||
|
let element = this.getElement(item.key);
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.plugin.setting.addItem({
|
||||||
|
title: item.title,
|
||||||
|
description: item?.description,
|
||||||
|
direction: item?.direction,
|
||||||
|
createActionElement: () => {
|
||||||
|
let val = this.get(item.key);
|
||||||
|
let element = item.createElement(val);
|
||||||
|
this.elements.set(item.key, element);
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createDefaultElement(item: ISettingUtilsItem) {
|
||||||
|
let itemElement: HTMLElement;
|
||||||
|
//阻止思源内置的回车键确认
|
||||||
|
const preventEnterConfirm = (e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch (item.type) {
|
||||||
|
case 'checkbox':
|
||||||
|
let element: HTMLInputElement = document.createElement('input');
|
||||||
|
element.type = 'checkbox';
|
||||||
|
element.checked = item.value;
|
||||||
|
element.className = "b3-switch fn__flex-center";
|
||||||
|
itemElement = element;
|
||||||
|
element.onchange = item.action?.callback ?? (() => { });
|
||||||
|
break;
|
||||||
|
case 'select':
|
||||||
|
let selectElement: HTMLSelectElement = document.createElement('select');
|
||||||
|
selectElement.className = "b3-select fn__flex-center fn__size200";
|
||||||
|
let options = item?.options ?? {};
|
||||||
|
for (let val in options) {
|
||||||
|
let optionElement = document.createElement('option');
|
||||||
|
let text = options[val];
|
||||||
|
optionElement.value = val;
|
||||||
|
optionElement.text = text;
|
||||||
|
selectElement.appendChild(optionElement);
|
||||||
|
}
|
||||||
|
selectElement.value = item.value;
|
||||||
|
selectElement.onchange = item.action?.callback ?? (() => { });
|
||||||
|
itemElement = selectElement;
|
||||||
|
break;
|
||||||
|
case 'slider':
|
||||||
|
let sliderElement: HTMLInputElement = document.createElement('input');
|
||||||
|
sliderElement.type = 'range';
|
||||||
|
sliderElement.className = 'b3-slider fn__size200 b3-tooltips b3-tooltips__n';
|
||||||
|
sliderElement.ariaLabel = item.value;
|
||||||
|
sliderElement.min = item.slider?.min.toString() ?? '0';
|
||||||
|
sliderElement.max = item.slider?.max.toString() ?? '100';
|
||||||
|
sliderElement.step = item.slider?.step.toString() ?? '1';
|
||||||
|
sliderElement.value = item.value;
|
||||||
|
sliderElement.onchange = () => {
|
||||||
|
sliderElement.ariaLabel = sliderElement.value;
|
||||||
|
item.action?.callback();
|
||||||
|
}
|
||||||
|
itemElement = sliderElement;
|
||||||
|
break;
|
||||||
|
case 'textinput':
|
||||||
|
let textInputElement: HTMLInputElement = document.createElement('input');
|
||||||
|
textInputElement.className = 'b3-text-field fn__flex-center fn__size200';
|
||||||
|
textInputElement.value = item.value;
|
||||||
|
textInputElement.onchange = item.action?.callback ?? (() => { });
|
||||||
|
itemElement = textInputElement;
|
||||||
|
textInputElement.addEventListener('keydown', preventEnterConfirm);
|
||||||
|
break;
|
||||||
|
case 'textarea':
|
||||||
|
let textareaElement: HTMLTextAreaElement = document.createElement('textarea');
|
||||||
|
textareaElement.className = "b3-text-field fn__block";
|
||||||
|
textareaElement.value = item.value;
|
||||||
|
textareaElement.onchange = item.action?.callback ?? (() => { });
|
||||||
|
itemElement = textareaElement;
|
||||||
|
break;
|
||||||
|
case 'number':
|
||||||
|
let numberElement: HTMLInputElement = document.createElement('input');
|
||||||
|
numberElement.type = 'number';
|
||||||
|
numberElement.className = 'b3-text-field fn__flex-center fn__size200';
|
||||||
|
numberElement.value = item.value;
|
||||||
|
itemElement = numberElement;
|
||||||
|
numberElement.addEventListener('keydown', preventEnterConfirm);
|
||||||
|
break;
|
||||||
|
case 'button':
|
||||||
|
let buttonElement: HTMLButtonElement = document.createElement('button');
|
||||||
|
buttonElement.className = "b3-button b3-button--outline fn__flex-center fn__size200";
|
||||||
|
buttonElement.innerText = item.button?.label ?? 'Button';
|
||||||
|
buttonElement.onclick = item.button?.callback ?? (() => { });
|
||||||
|
itemElement = buttonElement;
|
||||||
|
break;
|
||||||
|
case 'hint':
|
||||||
|
let hintElement: HTMLElement = document.createElement('div');
|
||||||
|
hintElement.className = 'b3-label fn__flex-center';
|
||||||
|
itemElement = hintElement;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return itemElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return the setting element
|
||||||
|
* @param key key name
|
||||||
|
* @returns element
|
||||||
|
*/
|
||||||
|
getElement(key: string) {
|
||||||
|
// let item = this.settings.get(key);
|
||||||
|
let element = this.elements.get(key) as any;
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateValueFromElement(key: string) {
|
||||||
|
let item = this.settings.get(key);
|
||||||
|
if (item.type === 'button') return;
|
||||||
|
let element = this.elements.get(key) as any;
|
||||||
|
item.value = item.getEleVal(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateElementFromValue(key: string) {
|
||||||
|
let item = this.settings.get(key);
|
||||||
|
if (item.type === 'button') return;
|
||||||
|
let element = this.elements.get(key) as any;
|
||||||
|
item.setEleVal(element, item.value);
|
||||||
|
}
|
||||||
|
}
|
139
src/setting-example.svelte
Normal file
139
src/setting-example.svelte
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { showMessage } from "siyuan";
|
||||||
|
import SettingPanel from "./libs/components/setting-panel.svelte";
|
||||||
|
|
||||||
|
let groups: string[] = ["🌈 Group 1", "✨ Group 2"];
|
||||||
|
let focusGroup = groups[0];
|
||||||
|
|
||||||
|
const group1Items: ISettingItem[] = [
|
||||||
|
{
|
||||||
|
type: 'checkbox',
|
||||||
|
title: 'checkbox',
|
||||||
|
description: 'checkbox',
|
||||||
|
key: 'a',
|
||||||
|
value: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'textinput',
|
||||||
|
title: 'text',
|
||||||
|
description: 'This is a text',
|
||||||
|
key: 'b',
|
||||||
|
value: 'This is a text',
|
||||||
|
placeholder: 'placeholder'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'textarea',
|
||||||
|
title: 'textarea',
|
||||||
|
description: 'This is a textarea',
|
||||||
|
key: 'b2',
|
||||||
|
value: 'This is a textarea',
|
||||||
|
placeholder: 'placeholder',
|
||||||
|
direction: 'row'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
title: 'select',
|
||||||
|
description: 'select',
|
||||||
|
key: 'c',
|
||||||
|
value: 'x',
|
||||||
|
options: {
|
||||||
|
x: 'x',
|
||||||
|
y: 'y',
|
||||||
|
z: 'z'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const group2Items: ISettingItem[] = [
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
title: 'button',
|
||||||
|
description: 'This is a button',
|
||||||
|
key: 'e',
|
||||||
|
value: 'Click Button',
|
||||||
|
button: {
|
||||||
|
label: 'Click Me',
|
||||||
|
callback: () => {
|
||||||
|
showMessage('Hello, world!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'slider',
|
||||||
|
title: 'slider',
|
||||||
|
description: 'slider',
|
||||||
|
key: 'd',
|
||||||
|
value: 50,
|
||||||
|
slider: {
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
step: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
/********** Events **********/
|
||||||
|
interface ChangeEvent {
|
||||||
|
group: string;
|
||||||
|
key: string;
|
||||||
|
value: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChanged = ({ detail }: CustomEvent<ChangeEvent>) => {
|
||||||
|
if (detail.group === groups[0]) {
|
||||||
|
// setting.set(detail.key, detail.value);
|
||||||
|
//Please add your code here
|
||||||
|
//Udpate the plugins setting data, don't forget to call plugin.save() for data persistence
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="fn__flex-1 fn__flex config__panel">
|
||||||
|
<ul class="b3-tab-bar b3-list b3-list--background">
|
||||||
|
{#each groups as group}
|
||||||
|
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||||
|
<li
|
||||||
|
data-name="editor"
|
||||||
|
class:b3-list-item--focus={group === focusGroup}
|
||||||
|
class="b3-list-item"
|
||||||
|
on:click={() => {
|
||||||
|
focusGroup = group;
|
||||||
|
}}
|
||||||
|
on:keydown={() => {}}
|
||||||
|
>
|
||||||
|
<span class="b3-list-item__text">{group}</span>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
<div class="config__tab-wrap">
|
||||||
|
<SettingPanel
|
||||||
|
group={groups[0]}
|
||||||
|
settingItems={group1Items}
|
||||||
|
display={focusGroup === groups[0]}
|
||||||
|
on:changed={onChanged}
|
||||||
|
on:click={({ detail }) => { console.debug("Click:", detail.key); }}
|
||||||
|
>
|
||||||
|
<div class="fn__flex b3-label">
|
||||||
|
💡 This is our default settings.
|
||||||
|
</div>
|
||||||
|
</SettingPanel>
|
||||||
|
<SettingPanel
|
||||||
|
group={groups[1]}
|
||||||
|
settingItems={group2Items}
|
||||||
|
display={focusGroup === groups[1]}
|
||||||
|
on:changed={onChanged}
|
||||||
|
on:click={({ detail }) => { console.debug("Click:", detail.key); }}
|
||||||
|
>
|
||||||
|
</SettingPanel>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.config__panel {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.config__panel > ul > li {
|
||||||
|
padding-left: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
65
src/types/api.d.ts
vendored
Normal file
65
src/types/api.d.ts
vendored
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
interface IResGetNotebookConf {
|
||||||
|
box: string;
|
||||||
|
conf: NotebookConf;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IReslsNotebooks {
|
||||||
|
notebooks: Notebook[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IResUpload {
|
||||||
|
errFiles: string[];
|
||||||
|
succMap: { [key: string]: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IResdoOperations {
|
||||||
|
doOperations: doOperation[];
|
||||||
|
undoOperations: doOperation[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IResGetBlockKramdown {
|
||||||
|
id: BlockId;
|
||||||
|
kramdown: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IResGetChildBlock {
|
||||||
|
id: BlockId;
|
||||||
|
type: BlockType;
|
||||||
|
subtype?: BlockSubType;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IResGetTemplates {
|
||||||
|
content: string;
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IResReadDir {
|
||||||
|
isDir: boolean;
|
||||||
|
isSymlink: boolean;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IResExportMdContent {
|
||||||
|
hPath: string;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IResBootProgress {
|
||||||
|
progress: number;
|
||||||
|
details: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IResForwardProxy {
|
||||||
|
body: string;
|
||||||
|
contentType: string;
|
||||||
|
elapsed: number;
|
||||||
|
headers: { [key: string]: string };
|
||||||
|
status: number;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IResExportResources {
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
54
src/types/index.d.ts
vendored
54
src/types/index.d.ts
vendored
|
@ -1,10 +1,13 @@
|
||||||
/**
|
/*
|
||||||
* Copyright (c) 2023 frostime. All rights reserved.
|
* Copyright (c) 2024 by frostime. All Rights Reserved.
|
||||||
|
* @Author : frostime
|
||||||
|
* @Date : 2023-08-15 10:28:10
|
||||||
|
* @FilePath : /src/types/index.d.ts
|
||||||
|
* @LastEditTime : 2024-06-08 20:50:53
|
||||||
|
* @Description : Frequently used data structures in SiYuan
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* Frequently used data structures in SiYuan
|
|
||||||
*/
|
|
||||||
type DocumentId = string;
|
type DocumentId = string;
|
||||||
type BlockId = string;
|
type BlockId = string;
|
||||||
type NotebookId = string;
|
type NotebookId = string;
|
||||||
|
@ -28,7 +31,25 @@ type NotebookConf = {
|
||||||
dailyNoteTemplatePath: string;
|
dailyNoteTemplatePath: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type BlockType = "d" | "s" | "h" | "t" | "i" | "p" | "f" | "audio" | "video" | "other";
|
type BlockType =
|
||||||
|
| 'd'
|
||||||
|
| 'p'
|
||||||
|
| 'query_embed'
|
||||||
|
| 'l'
|
||||||
|
| 'i'
|
||||||
|
| 'h'
|
||||||
|
| 'iframe'
|
||||||
|
| 'tb'
|
||||||
|
| 'b'
|
||||||
|
| 's'
|
||||||
|
| 'c'
|
||||||
|
| 'widget'
|
||||||
|
| 't'
|
||||||
|
| 'html'
|
||||||
|
| 'm'
|
||||||
|
| 'av'
|
||||||
|
| 'audio';
|
||||||
|
|
||||||
|
|
||||||
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";
|
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";
|
||||||
|
|
||||||
|
@ -50,7 +71,10 @@ type Block = {
|
||||||
length: number;
|
length: number;
|
||||||
type: BlockType;
|
type: BlockType;
|
||||||
subtype: BlockSubType;
|
subtype: BlockSubType;
|
||||||
ial?: { [key: string]: string };
|
/** string of { [key: string]: string }
|
||||||
|
* For instance: "{: custom-type=\"query-code\" id=\"20230613234017-zkw3pr0\" updated=\"20230613234509\"}"
|
||||||
|
*/
|
||||||
|
ial?: string;
|
||||||
sort: number;
|
sort: number;
|
||||||
created: string;
|
created: string;
|
||||||
updated: string;
|
updated: string;
|
||||||
|
@ -64,3 +88,19 @@ type doOperation = {
|
||||||
previousID: BlockId;
|
previousID: BlockId;
|
||||||
retData: null;
|
retData: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Window {
|
||||||
|
siyuan: {
|
||||||
|
config: any;
|
||||||
|
notebooks: any;
|
||||||
|
menus: any;
|
||||||
|
dialogs: any;
|
||||||
|
blockPanels: any;
|
||||||
|
storage: any;
|
||||||
|
user: any;
|
||||||
|
ws: any;
|
||||||
|
languages: any;
|
||||||
|
emojis: any;
|
||||||
|
};
|
||||||
|
Lute: any;
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 by frostime. All Rights Reserved.
|
||||||
|
* @Author : frostime
|
||||||
|
* @Date : 2023-05-19 19:49:13
|
||||||
|
* @FilePath : /svelte.config.js
|
||||||
|
* @LastEditTime : 2024-04-19 19:01:55
|
||||||
|
* @Description :
|
||||||
|
*/
|
||||||
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"
|
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"
|
||||||
|
|
||||||
|
const NoWarns = new Set([
|
||||||
|
"a11y-click-events-have-key-events",
|
||||||
|
"a11y-no-static-element-interactions",
|
||||||
|
"a11y-no-noninteractive-element-interactions"
|
||||||
|
]);
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
|
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
|
||||||
// for more information about preprocessors
|
// for more information about preprocessors
|
||||||
preprocess: vitePreprocess(),
|
preprocess: vitePreprocess(),
|
||||||
|
onwarn: (warning, handler) => {
|
||||||
|
// suppress warnings on `vite dev` and `vite build`; but even without this, things still work
|
||||||
|
if (NoWarns.has(warning.code)) return;
|
||||||
|
handler(warning);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,8 @@
|
||||||
"src/**/*.ts",
|
"src/**/*.ts",
|
||||||
"src/**/*.d.ts",
|
"src/**/*.d.ts",
|
||||||
"src/**/*.tsx",
|
"src/**/*.tsx",
|
||||||
"src/**/*.vue"
|
"src/**/*.vue",
|
||||||
|
"src/**/*.svelte"
|
||||||
],
|
],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
|
178
vite.config.ts
178
vite.config.ts
|
@ -1,19 +1,22 @@
|
||||||
import { resolve } from "path"
|
import { resolve } from "path"
|
||||||
import { defineConfig, loadEnv } from "vite"
|
import { defineConfig, loadEnv } from "vite"
|
||||||
import minimist from "minimist"
|
|
||||||
import { viteStaticCopy } from "vite-plugin-static-copy"
|
import { viteStaticCopy } from "vite-plugin-static-copy"
|
||||||
import livereload from "rollup-plugin-livereload"
|
import livereload from "rollup-plugin-livereload"
|
||||||
import { svelte } from "@sveltejs/vite-plugin-svelte"
|
import { svelte } from "@sveltejs/vite-plugin-svelte"
|
||||||
import zipPack from "vite-plugin-zip-pack";
|
import zipPack from "vite-plugin-zip-pack";
|
||||||
import fg from 'fast-glob';
|
import fg from 'fast-glob';
|
||||||
|
|
||||||
const args = minimist(process.argv.slice(2))
|
import vitePluginYamlI18n from './yaml-plugin';
|
||||||
const isWatch = args.watch || args.w || false
|
|
||||||
const devDistDir = "./dev"
|
|
||||||
const distDir = isWatch ? devDistDir : "./dist"
|
|
||||||
|
|
||||||
console.log("isWatch=>", isWatch)
|
const env = process.env;
|
||||||
console.log("distDir=>", distDir)
|
const isSrcmap = env.VITE_SOURCEMAP === 'inline';
|
||||||
|
const isDev = env.NODE_ENV === 'development';
|
||||||
|
|
||||||
|
const outputDir = isDev ? "dev" : "dist";
|
||||||
|
|
||||||
|
console.log("isDev=>", isDev);
|
||||||
|
console.log("isSrcmap=>", isSrcmap);
|
||||||
|
console.log("outputDir=>", outputDir);
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
resolve: {
|
resolve: {
|
||||||
|
@ -25,92 +28,69 @@ export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
svelte(),
|
svelte(),
|
||||||
|
|
||||||
|
vitePluginYamlI18n({
|
||||||
|
inDir: 'public/i18n',
|
||||||
|
outDir: `${outputDir}/i18n`
|
||||||
|
}),
|
||||||
|
|
||||||
viteStaticCopy({
|
viteStaticCopy({
|
||||||
targets: [
|
targets: [
|
||||||
{
|
{ src: "./README*.md", dest: "./" },
|
||||||
src: "./README*.md",
|
{ src: "./plugin.json", dest: "./" },
|
||||||
dest: "./",
|
{ src: "./preview.png", dest: "./" },
|
||||||
},
|
{ src: "./icon.png", dest: "./" }
|
||||||
{
|
|
||||||
src: "./icon.png",
|
|
||||||
dest: "./",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: "./preview.png",
|
|
||||||
dest: "./",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: "./plugin.json",
|
|
||||||
dest: "./",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: "./src/i18n/**",
|
|
||||||
dest: "./i18n/",
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
||||||
// https://github.com/vitejs/vite/issues/1930
|
|
||||||
// https://vitejs.dev/guide/env-and-mode.html#env-files
|
|
||||||
// https://github.com/vitejs/vite/discussions/3058#discussioncomment-2115319
|
|
||||||
// 在这里自定义变量
|
|
||||||
define: {
|
define: {
|
||||||
"process.env.DEV_MODE": `"${isWatch}"`,
|
"process.env.DEV_MODE": JSON.stringify(isDev),
|
||||||
|
"process.env.NODE_ENV": JSON.stringify(env.NODE_ENV)
|
||||||
},
|
},
|
||||||
|
|
||||||
build: {
|
build: {
|
||||||
// 输出路径
|
outDir: outputDir,
|
||||||
outDir: distDir,
|
|
||||||
emptyOutDir: false,
|
emptyOutDir: false,
|
||||||
|
minify: true,
|
||||||
// 构建后是否生成 source map 文件
|
sourcemap: isSrcmap ? 'inline' : false,
|
||||||
sourcemap: false,
|
|
||||||
|
|
||||||
// 设置为 false 可以禁用最小化混淆
|
|
||||||
// 或是用来指定是应用哪种混淆器
|
|
||||||
// boolean | 'terser' | 'esbuild'
|
|
||||||
// 不压缩,用于调试
|
|
||||||
minify: !isWatch,
|
|
||||||
|
|
||||||
lib: {
|
lib: {
|
||||||
// Could also be a dictionary or array of multiple entry points
|
|
||||||
entry: resolve(__dirname, "src/index.ts"),
|
entry: resolve(__dirname, "src/index.ts"),
|
||||||
// the proper extensions will be added
|
|
||||||
fileName: "index",
|
fileName: "index",
|
||||||
formats: ["cjs"],
|
formats: ["cjs"],
|
||||||
},
|
},
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
plugins: [
|
plugins: [
|
||||||
...(
|
...(isDev ? [
|
||||||
isWatch ? [
|
livereload(outputDir),
|
||||||
livereload(devDistDir),
|
{
|
||||||
{
|
name: 'watch-external',
|
||||||
//监听静态资源文件
|
async buildStart() {
|
||||||
name: 'watch-external',
|
const files = await fg([
|
||||||
async buildStart() {
|
'public/i18n/**',
|
||||||
const files = await fg([
|
'./README*.md',
|
||||||
'src/i18n/*.json',
|
'./plugin.json'
|
||||||
'./README*.md',
|
]);
|
||||||
'./plugin.json'
|
for (let file of files) {
|
||||||
]);
|
this.addWatchFile(file);
|
||||||
for (let file of files) {
|
|
||||||
this.addWatchFile(file);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
] : [
|
}
|
||||||
zipPack({
|
] : [
|
||||||
inDir: './dist',
|
// Clean up unnecessary files under dist dir
|
||||||
outDir: './',
|
cleanupDistFiles({
|
||||||
outFileName: 'package.zip'
|
patterns: ['i18n/*.yaml', 'i18n/*.md'],
|
||||||
})
|
distDir: outputDir
|
||||||
]
|
}),
|
||||||
)
|
zipPack({
|
||||||
|
inDir: './dist',
|
||||||
|
outDir: './',
|
||||||
|
outFileName: 'package.zip'
|
||||||
|
})
|
||||||
|
])
|
||||||
],
|
],
|
||||||
|
|
||||||
// make sure to externalize deps that shouldn't be bundled
|
|
||||||
// into your library
|
|
||||||
external: ["siyuan", "process"],
|
external: ["siyuan", "process"],
|
||||||
|
|
||||||
output: {
|
output: {
|
||||||
|
@ -124,4 +104,60 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up some dist files after compiled
|
||||||
|
* @author frostime
|
||||||
|
* @param options:
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function cleanupDistFiles(options: { patterns: string[], distDir: string }) {
|
||||||
|
const {
|
||||||
|
patterns,
|
||||||
|
distDir
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'rollup-plugin-cleanup',
|
||||||
|
enforce: 'post',
|
||||||
|
writeBundle: {
|
||||||
|
sequential: true,
|
||||||
|
order: 'post' as 'post',
|
||||||
|
async handler() {
|
||||||
|
const fg = await import('fast-glob');
|
||||||
|
const fs = await import('fs');
|
||||||
|
// const path = await import('path');
|
||||||
|
|
||||||
|
// 使用 glob 语法,确保能匹配到文件
|
||||||
|
const distPatterns = patterns.map(pat => `${distDir}/${pat}`);
|
||||||
|
console.debug('Cleanup searching patterns:', distPatterns);
|
||||||
|
|
||||||
|
const files = await fg.default(distPatterns, {
|
||||||
|
dot: true,
|
||||||
|
absolute: true,
|
||||||
|
onlyFiles: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// console.info('Files to be cleaned up:', files);
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
try {
|
||||||
|
if (fs.default.existsSync(file)) {
|
||||||
|
const stat = fs.default.statSync(file);
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
fs.default.rmSync(file, { recursive: true });
|
||||||
|
} else {
|
||||||
|
fs.default.unlinkSync(file);
|
||||||
|
}
|
||||||
|
console.log(`Cleaned up: ${file}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to clean up ${file}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
60
yaml-plugin.js
Normal file
60
yaml-plugin.js
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 by frostime. All Rights Reserved.
|
||||||
|
* @Author : frostime
|
||||||
|
* @Date : 2024-04-05 21:27:55
|
||||||
|
* @FilePath : /yaml-plugin.js
|
||||||
|
* @LastEditTime : 2024-04-05 22:53:34
|
||||||
|
* @Description : 去妮玛的 json 格式,我就是要用 yaml 写 i18n
|
||||||
|
*/
|
||||||
|
// plugins/vite-plugin-parse-yaml.js
|
||||||
|
import fs from 'fs';
|
||||||
|
import yaml from 'js-yaml';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
|
||||||
|
export default function vitePluginYamlI18n(options = {}) {
|
||||||
|
// Default options with a fallback
|
||||||
|
const DefaultOptions = {
|
||||||
|
inDir: 'src/i18n',
|
||||||
|
outDir: 'dist/i18n',
|
||||||
|
};
|
||||||
|
|
||||||
|
const finalOptions = { ...DefaultOptions, ...options };
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'vite-plugin-yaml-i18n',
|
||||||
|
buildStart() {
|
||||||
|
console.log('🌈 Parse I18n: YAML to JSON..');
|
||||||
|
const inDir = finalOptions.inDir;
|
||||||
|
const outDir = finalOptions.outDir
|
||||||
|
|
||||||
|
if (!fs.existsSync(outDir)) {
|
||||||
|
fs.mkdirSync(outDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
//Parse yaml file, output to json
|
||||||
|
const files = fs.readdirSync(inDir);
|
||||||
|
for (const file of files) {
|
||||||
|
if (file.endsWith('.yaml') || file.endsWith('.yml')) {
|
||||||
|
console.log(`-- Parsing ${file}`)
|
||||||
|
//检查是否有同名的json文件
|
||||||
|
const jsonFile = file.replace(/\.(yaml|yml)$/, '.json');
|
||||||
|
if (files.includes(jsonFile)) {
|
||||||
|
console.log(`---- File ${jsonFile} already exists, skipping...`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const filePath = resolve(inDir, file);
|
||||||
|
const fileContents = fs.readFileSync(filePath, 'utf8');
|
||||||
|
const parsed = yaml.load(fileContents);
|
||||||
|
const jsonContent = JSON.stringify(parsed, null, 2);
|
||||||
|
const outputFilePath = resolve(outDir, file.replace(/\.(yaml|yml)$/, '.json'));
|
||||||
|
console.log(`---- Writing to ${outputFilePath}`);
|
||||||
|
fs.writeFileSync(outputFilePath, jsonContent);
|
||||||
|
} catch (error) {
|
||||||
|
this.error(`---- Error parsing YAML file ${file}: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue