mirror of
https://github.com/siyuan-note/plugin-sample-vite-svelte.git
synced 2025-06-07 18:46:01 +00:00
Compare commits
28 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 |
33 changed files with 1294 additions and 654 deletions
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
|
@ -17,12 +17,12 @@ jobs:
|
|||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
# Install pnpm
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
uses: pnpm/action-setup@v4
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 8
|
||||
|
@ -59,4 +59,4 @@ jobs:
|
|||
artifactErrorsFailBuild: true
|
||||
artifacts: "package.zip"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
prerelease: true
|
||||
prerelease: false
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
# Changelog
|
||||
|
||||
## 0.3.5 2024-03
|
||||
## 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
|
||||
|
||||
|
|
62
README.md
62
README.md
|
@ -11,22 +11,38 @@
|
|||
2. Use symbolic linking instead of putting the project into the plugins directory program development
|
||||
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)
|
||||
> **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
|
||||
|
||||
|
||||
> [!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
|
||||
|
||||
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
|
||||
- Notice: we **don't recommand** you to place the folder under your `{workspace}/data/plugins/` folder.
|
||||
### Setting the Target Directory for the make-link Command
|
||||
|
||||
3. Install NodeJS and pnpm, then run pnpm i in the command line under your repo folder
|
||||
4. **Auto create development symbolic links**
|
||||
- Make sure that SiYuan is running
|
||||
- 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
|
||||
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:
|
||||
|
||||
1. **Select Workspace**
|
||||
- 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
|
||||
>>> pnpm run make-link
|
||||
> plugin-sample-vite-svelte@0.0.3 make-link H:\SrcCode\开源项目\plugin-sample-vite-svelte
|
||||
|
@ -40,22 +56,24 @@
|
|||
Got target directory: H:\Media\SiYuan/data/plugins
|
||||
Done! Created symlink H:\Media\SiYuan/data/plugins/plugin-sample-vite-svelte
|
||||
```
|
||||
4. **Manually create development symbolic links**
|
||||
- Open `./scripts/make_dev_link.js` file, set `targetDir` to your SiYuan plugin directory `<siyuan workspace>/data/plugins`
|
||||
- Run `pnpm run make-link`, succeed if following message is shown:
|
||||
```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
|
||||
2. **Manually Configure Target Directory**
|
||||
- Open the `./scripts/make_dev_link.js` file, change `targetDir` to the SiYuan plugin directory `<siyuan workspace>/data/plugins`.
|
||||
- Run the `pnpm run make-link` command. If you see a message similar to the one below, it indicates successful creation:
|
||||
|
||||
Done! Created symlink H:/SiYuanDevSpace/data/plugins/plugin-sample-vite-svelte
|
||||
```
|
||||
5. **Create development symbolic links by using environment variable**
|
||||
- You can set environment variable `SIYUAN_PLUGIN_DIR` as `/data/plugins`
|
||||
6. Execute pnpm run dev for real-time compilation
|
||||
7. Open SiYuan marketplace and enable plugin in downloaded tab
|
||||
3. **Set Environment Variable to Create Symbolic Link**
|
||||
- Set the system environment variable `SIYUAN_PLUGIN_DIR` to the path `workspace/data/plugins`.
|
||||
|
||||
> 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.
|
||||
### make-link on Windows
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -10,17 +10,34 @@
|
|||
2. 使用符号链接、而不是把项目放到插件目录下的模式进行开发
|
||||
3. 内置对 svelte 框架的支持
|
||||
|
||||
> 如果不想要 svelte,请移步 [frostime/plugin-sample-vite](https://github.com/frostime/plugin-sample-vite)
|
||||
> **如果不想要 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并上传到新版本中
|
||||
|
||||
## 开始
|
||||
|
||||
1. 通过 <kbd>Use this template</kbd> 按钮将该库文件复制到你自己的库中,请注意库名必须和插件名称一致,默认分支必须为 `main`
|
||||
1. 通过 <kbd>Use this template</kbd> 按钮将该库文件复制到你自己的库中,请注意库名和插件名称一致,默认分支必须为 `main`
|
||||
2. 将你的库克隆到本地开发文件夹中
|
||||
* 注意: 同 `plugin-sample` 不同, 本样例并不推荐直接把代码下载到 `{workspace}/data/plugins/`
|
||||
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`, 脚本会自动检测所有思源的工作空间, 请在命令行中手动输入序号以选择工作空间
|
||||
```bash
|
||||
|
@ -36,23 +53,26 @@
|
|||
Got target directory: H:\Media\SiYuan/data/plugins
|
||||
Done! Created symlink H:\Media\SiYuan/data/plugins/plugin-sample-vite-svelte
|
||||
```
|
||||
4. **手动创建符号链接**
|
||||
2. **手动配置目标目录**
|
||||
- 打开 `./scripts/make_dev_link.js` 文件,更改 `targetDir` 为思源的插件目录 `<siyuan workspace>/data/plugins`
|
||||
- 运行 `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
|
||||
```
|
||||
5. **设置环境变量创建符号链接**
|
||||
- 你也可以设置系统的环境变量 `SIYUAN_PLUGIN_DIR` 为 `/data/plugins` 的路径
|
||||
6. 执行 `pnpm run dev` 进行实时编译
|
||||
7. 在思源中打开集市并在下载选项卡中启用插件
|
||||
3. **设置环境变量创建符号链接**
|
||||
- 设置系统的环境变量 `SIYUAN_PLUGIN_DIR` 为 `工作空间/data/plugins` 的路径
|
||||
|
||||
|
||||
> 注意由于使用的 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 脚本执行权限
|
||||
|
||||
|
||||
## 国际化
|
||||
|
|
23
package.json
23
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "plugin-sample-vite-svelte",
|
||||
"version": "0.3.5",
|
||||
"version": "0.3.6",
|
||||
"type": "module",
|
||||
"description": "This is a sample plugin based on vite and svelte for Siyuan (https://b3log.org/siyuan)",
|
||||
"repository": "",
|
||||
|
@ -8,27 +8,30 @@
|
|||
"author": "frostime",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"make-link": "node --no-warnings ./scripts/make_dev_link.js",
|
||||
"dev": "vite build --watch",
|
||||
"build": "vite build",
|
||||
"dev": "cross-env NODE_ENV=development VITE_SOURCEMAP=inline vite build --watch",
|
||||
"build": "cross-env NODE_ENV=production 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": {
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.0",
|
||||
"@tsconfig/svelte": "^4.0.1",
|
||||
"@types/node": "^20.3.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"fast-glob": "^3.2.12",
|
||||
"glob": "^7.2.3",
|
||||
"glob": "^10.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"minimist": "^1.2.8",
|
||||
"rollup-plugin-livereload": "^2.0.5",
|
||||
"sass": "^1.63.3",
|
||||
"siyuan": "0.9.8",
|
||||
"svelte": "^4.2.0",
|
||||
"siyuan": "1.0.4",
|
||||
"svelte": "^4.2.19",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.1.3",
|
||||
"vite": "^5.0.0",
|
||||
"vite": "^5.2.9",
|
||||
"vite-plugin-static-copy": "^1.0.2",
|
||||
"vite-plugin-zip-pack": "^1.0.5"
|
||||
}
|
||||
}
|
||||
}
|
12
plugin.json
12
plugin.json
|
@ -2,14 +2,16 @@
|
|||
"name": "plugin-sample-vite-svelte",
|
||||
"author": "frostime",
|
||||
"url": "https://github.com/siyuan-note/plugin-sample-vite-svelte",
|
||||
"version": "0.3.5",
|
||||
"version": "0.3.6",
|
||||
"minAppVersion": "3.0.12",
|
||||
"backends": [
|
||||
"windows",
|
||||
"linux",
|
||||
"darwin",
|
||||
"ios",
|
||||
"android"
|
||||
"android",
|
||||
"harmony",
|
||||
"docker"
|
||||
],
|
||||
"frontends": [
|
||||
"desktop",
|
||||
|
@ -32,10 +34,12 @@
|
|||
},
|
||||
"funding": {
|
||||
"custom": [
|
||||
"https://afdian.net/a/frostime"
|
||||
""
|
||||
]
|
||||
},
|
||||
"keywords": [
|
||||
"plugin", "sample", "插件样例"
|
||||
"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.
|
20
public/i18n/zh_CN.json
Normal file
20
public/i18n/zh_CN.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"addTopBarIcon": "使用插件添加一个顶栏按钮",
|
||||
"cancel": "取消",
|
||||
"save": "保存",
|
||||
"byeMenu": "再见,菜单!",
|
||||
"helloPlugin": "你好,插件!",
|
||||
"byePlugin": "再见,插件!",
|
||||
"showDialog": "弹出一个对话框",
|
||||
"removedData": "数据已删除",
|
||||
"confirmRemove": "确认删除 ${name} 中的数据?",
|
||||
"insertEmoji": "插入表情",
|
||||
"removeSpace": "移除空格",
|
||||
"getTab": "在日志中打印出已打开的所有自定义页签",
|
||||
"name": "思源",
|
||||
"hello": {
|
||||
"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"
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
---
|
||||
addTopBarIcon: 使用插件添加一个顶栏按钮
|
||||
cancel: 取消
|
||||
save: 保存
|
||||
byeMenu: 再见,菜单!
|
||||
helloPlugin: 你好,插件!
|
||||
byePlugin: 再见,插件!
|
||||
showDialog: 弹出一个对话框
|
||||
removedData: 数据已删除
|
||||
confirmRemove: 确认删除 ${name} 中的数据?
|
||||
insertEmoji: 插入表情
|
||||
removeSpace: 移除空格
|
||||
getTab: 在日志中打印出已打开的所有自定义页签
|
||||
name: 思源
|
||||
hello:
|
||||
makesure: 使用这个模板之前,请阅读<a href="https://github.com/siyuan-note/plugin-sample">官方教程</a>,
|
||||
确保自己已经理解了插件的基本开发流程。
|
||||
hintTitle: 关于
|
||||
hintDesc: "<a href='https://github.com/siyuan-note/plugin-sample-vite-svelte'>\U0001F517
|
||||
plugin-sample-vite-svelte</a><br>\U0001F4BB @frostime<br>\U0001F4BB @88250<br>\U0001F4BB
|
||||
@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,106 +1,29 @@
|
|||
/*
|
||||
* 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 http from 'node:http';
|
||||
import readline from 'node:readline';
|
||||
import { log, error, getSiYuanDir, chooseTarget, getThisPluginName, makeSymbolicLink } from './utils.js';
|
||||
|
||||
|
||||
//************************************ Write you dir here ************************************
|
||||
|
||||
//Please write the "workspace/data/plugins" directory here
|
||||
//请在这里填写你的 "workspace/data/plugins" 目录
|
||||
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(`\tHTTP-Error: ${response.status}`);
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
error(`\tError: ${e}`);
|
||||
error("\tPlease 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(`\t[${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(`\tPlease select a workspace[0-${count-1}]: `, (answer) => {
|
||||
resolve(answer);
|
||||
});
|
||||
});
|
||||
rl.close();
|
||||
return `${workspaces[index].path}/data/plugins`;
|
||||
}
|
||||
}
|
||||
|
||||
log('>>> Try to visit constant "targetDir" in make_dev_link.js...')
|
||||
|
||||
/**
|
||||
* 1. Get the parent directory to install the plugin
|
||||
*/
|
||||
log('>>> Try to visit constant "targetDir" in make_dev_link.js...');
|
||||
if (targetDir === '') {
|
||||
log('>>> Constant "targetDir" is empty, try to get SiYuan directory automatically....')
|
||||
log('>>> Constant "targetDir" is empty, try to get SiYuan directory automatically....');
|
||||
let res = await getSiYuanDir();
|
||||
|
||||
if (res === null || res === undefined || res.length === 0) {
|
||||
log('>>> Can not get SiYuan directory automatically, try to visit environment variable "SIYUAN_PLUGIN_DIR"....');
|
||||
|
||||
// console.log(process.env)
|
||||
if (!res || res.length === 0) {
|
||||
log('>>> Can not get SiYuan directory automatically, try to visit environment variable "SIYUAN_PLUGIN_DIR"....');
|
||||
let env = process.env?.SIYUAN_PLUGIN_DIR;
|
||||
if (env !== undefined && env !== null && env !== '') {
|
||||
if (env) {
|
||||
targetDir = env;
|
||||
log(`\tGot target directory from environment variable "SIYUAN_PLUGIN_DIR": ${targetDir}`);
|
||||
} else {
|
||||
|
@ -111,76 +34,33 @@ if (targetDir === '') {
|
|||
targetDir = await chooseTarget(res);
|
||||
}
|
||||
|
||||
|
||||
log(`>>> Successfully got target directory: ${targetDir}`);
|
||||
}
|
||||
|
||||
//Check
|
||||
if (!fs.existsSync(targetDir)) {
|
||||
error(`Failed! plugin directory not exists: "${targetDir}"`);
|
||||
error(`Please set the plugin directory in scripts/make_dev_link.js`);
|
||||
error(`Failed! Plugin directory not exists: "${targetDir}"`);
|
||||
error('Please set the plugin directory in scripts/make_dev_link.js');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
//check if plugin.json exists
|
||||
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
|
||||
/**
|
||||
* 2. The dev directory, which contains the compiled plugin code
|
||||
*/
|
||||
const devDir = `${process.cwd()}/dev`;
|
||||
//mkdir if not exists
|
||||
if (!fs.existsSync(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}`;
|
||||
//如果已经存在,就退出
|
||||
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);
|
||||
|
|
|
@ -1,191 +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 path from 'path';
|
||||
import http from 'node:http';
|
||||
import readline from 'node:readline';
|
||||
import { log, error, getSiYuanDir, chooseTarget, copyDirectory, getThisPluginName } from './utils.js';
|
||||
|
||||
let targetDir = '';
|
||||
|
||||
//************************************ Write you dir here ************************************
|
||||
|
||||
let targetDir = ''; // the target directory of the plugin, '*/data/plugin'
|
||||
//********************************************************************************************
|
||||
|
||||
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(`\tHTTP-Error: ${response.status}`);
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
error(`\tError: ${e}`);
|
||||
error("\tPlease 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(`\t[${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(`\tPlease select a workspace[0-${count-1}]: `, (answer) => {
|
||||
resolve(answer);
|
||||
});
|
||||
});
|
||||
rl.close();
|
||||
return `${workspaces[index].path}/data/plugins`;
|
||||
}
|
||||
}
|
||||
|
||||
log('>>> Try to visit constant "targetDir" in make_install.js...')
|
||||
|
||||
/**
|
||||
* 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....')
|
||||
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}`);
|
||||
}
|
||||
|
||||
//Check
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
//check if plugin.json exists
|
||||
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');
|
||||
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`;
|
||||
//mkdir if not exists
|
||||
if (!fs.existsSync(distDir)) {
|
||||
fs.mkdirSync(distDir);
|
||||
}
|
||||
|
||||
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 install the plugin
|
||||
*/
|
||||
const name = getThisPluginName();
|
||||
if (name === null) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const targetPath = `${targetDir}/${name}`;
|
||||
|
||||
|
||||
function copyDirectory(srcDir, dstDir) {
|
||||
if (!fs.existsSync(dstDir)) {
|
||||
fs.mkdirSync(dstDir);
|
||||
log(`Created directory ${dstDir}`);
|
||||
}
|
||||
//将 distDir 下的所有文件复制到 targetPath
|
||||
fs.readdir(srcDir, { withFileTypes: true }, (err, files) => {
|
||||
if (err) {
|
||||
error('Error reading source directory:', err);
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历源目录中的所有文件和子目录
|
||||
files.forEach((file) => {
|
||||
const src = path.join(srcDir, file.name);
|
||||
const dst = path.join(dstDir, file.name);
|
||||
|
||||
// 判断当前项是文件还是目录
|
||||
if (file.isDirectory()) {
|
||||
// 如果是目录,则递归调用复制函数复制子目录
|
||||
copyDirectory(src, dst);
|
||||
} else {
|
||||
// 如果是文件,则复制文件到目标目录
|
||||
fs.copyFile(src, dst, (err) => {
|
||||
if (err) {
|
||||
error('Error copying file:' + err);
|
||||
} else {
|
||||
log(`Copied file: ${src} --> ${dst}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
log(`Copied ${distDir} to ${targetPath}`);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 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}`);
|
||||
}
|
||||
}
|
31
src/api.ts
31
src/api.ts
|
@ -6,7 +6,7 @@
|
|||
* 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";
|
||||
|
||||
|
||||
export async function request(url: string, data: any) {
|
||||
|
@ -328,14 +328,35 @@ export async function getFile(path: string): Promise<any> {
|
|||
path: path
|
||||
}
|
||||
let url = '/api/file/getFile';
|
||||
try {
|
||||
let file = await fetchSyncPost(url, data);
|
||||
return file;
|
||||
} catch (error_msg) {
|
||||
return new Promise((resolve, _) => {
|
||||
fetchPost(url, data, (content: any) => {
|
||||
resolve(content)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
let data = await response.blob();
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
export async function putFile(path: string, isDir: boolean, file: any) {
|
||||
let form = new FormData();
|
||||
form.append('path', path);
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
<!--
|
||||
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">
|
||||
import { onDestroy, onMount } from "svelte";
|
||||
import { version, sql as query } from "@/api";
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
#helloPanel {
|
||||
border: 1px rgb(189, 119, 119) dashed;
|
||||
}
|
||||
|
||||
.plugin-sample {
|
||||
&__custom-tab {
|
||||
background-color: var(--b3-theme-background);
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__custom-dock {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__time {
|
||||
background: var(--b3-card-info-background);
|
||||
border-radius: 4px;
|
||||
padding: 2px 8px;
|
||||
}
|
||||
}
|
33
src/index.ts
33
src/index.ts
|
@ -24,6 +24,7 @@ import HelloExample from "@/hello.svelte";
|
|||
import SettingExample from "@/setting-example.svelte";
|
||||
|
||||
import { SettingUtils } from "./libs/setting-utils";
|
||||
import { svelteDialog } from "./libs/dialog";
|
||||
|
||||
const STORAGE_NAME = "menu-config";
|
||||
const TAB_TYPE = "custom_tab";
|
||||
|
@ -455,18 +456,30 @@ export default class PluginSample extends Plugin {
|
|||
}
|
||||
|
||||
private showDialog() {
|
||||
let dialog = new Dialog({
|
||||
// let dialog = new Dialog({
|
||||
// title: `SiYuan ${Constants.SIYUAN_VERSION}`,
|
||||
// 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}`,
|
||||
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,
|
||||
constructor: (container: HTMLElement) => {
|
||||
return new HelloExample({
|
||||
target: container,
|
||||
props: {
|
||||
app: this.app,
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
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 };
|
|
@ -2,13 +2,13 @@
|
|||
Copyright (c) 2023 by frostime All Rights Reserved.
|
||||
Author : frostime
|
||||
Date : 2023-07-01 19:23:50
|
||||
FilePath : /src/libs/setting-panel.svelte
|
||||
LastEditTime : 2024-04-27 16:46:49
|
||||
FilePath : /src/libs/components/setting-panel.svelte
|
||||
LastEditTime : 2024-08-09 21:41:07
|
||||
Description :
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import SettingItem from "./setting-item.svelte";
|
||||
import Form from './Form';
|
||||
|
||||
export let group: string;
|
||||
export let settingItems: ISettingItem[];
|
||||
|
@ -30,18 +30,22 @@
|
|||
<div class="config__tab-container {fn__none}" data-name={group}>
|
||||
<slot />
|
||||
{#each settingItems as item (item.key)}
|
||||
<SettingItem
|
||||
type={item.type}
|
||||
<Form.Wrap
|
||||
title={item.title}
|
||||
description={item.description}
|
||||
settingKey={item.key}
|
||||
settingValue={item.value}
|
||||
placeholder={item?.placeholder}
|
||||
options={item?.options}
|
||||
slider={item?.slider}
|
||||
button={item?.button}
|
||||
on:click={onClick}
|
||||
on:changed={onChanged}
|
||||
/>
|
||||
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
|
||||
}
|
||||
}
|
2
src/libs/index.d.ts
vendored
2
src/libs/index.d.ts
vendored
|
@ -28,12 +28,12 @@ interface ISettingItemCore {
|
|||
interface ISettingItem extends ISettingItemCore {
|
||||
title: string;
|
||||
description: string;
|
||||
direction?: "row" | "column";
|
||||
}
|
||||
|
||||
|
||||
//Interface for setting-utils
|
||||
interface ISettingUtilsItem extends ISettingItem {
|
||||
direction?: "row" | "column";
|
||||
action?: {
|
||||
callback: () => 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,105 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
export let type: string; // Setting Type
|
||||
export let title: string; // Displayint Setting Title
|
||||
export let description: 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 | number]: 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
|
||||
export let button: {
|
||||
label: string;
|
||||
callback: () => void;
|
||||
} = { label: settingValue, callback: () => {} }; // Use it if type is button
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
function click() {
|
||||
button?.callback();
|
||||
dispatch("click", { key: settingKey });
|
||||
}
|
||||
|
||||
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">
|
||||
{@html description}
|
||||
</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 === "textinput"}
|
||||
<!-- Text Input -->
|
||||
<input
|
||||
class="b3-text-field fn__flex-center fn__size200"
|
||||
id={settingKey}
|
||||
{placeholder}
|
||||
bind:value={settingValue}
|
||||
on:change={changed}
|
||||
/>
|
||||
{:else if type === "number"}
|
||||
<input
|
||||
class="b3-text-field fn__flex-center fn__size200"
|
||||
id={settingKey}
|
||||
type="number"
|
||||
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={click}
|
||||
>
|
||||
{button.label}
|
||||
</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 -->
|
||||
<div class="b3-tooltips b3-tooltips__n" aria-label={settingValue}>
|
||||
<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}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</label>
|
|
@ -3,7 +3,7 @@
|
|||
* @Author : frostime
|
||||
* @Date : 2023-12-17 18:28:19
|
||||
* @FilePath : /src/libs/setting-utils.ts
|
||||
* @LastEditTime : 2024-04-30 16:42:23
|
||||
* @LastEditTime : 2024-05-01 17:44:16
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
|
@ -105,7 +105,7 @@ export class SettingUtils {
|
|||
args.callback(data);
|
||||
}
|
||||
this.plugin.data[this.name] = data;
|
||||
this.save();
|
||||
this.save(data);
|
||||
},
|
||||
destroyCallback: () => {
|
||||
//Restore the original value
|
||||
|
@ -128,8 +128,8 @@ export class SettingUtils {
|
|||
return data;
|
||||
}
|
||||
|
||||
async save() {
|
||||
let data = this.dump();
|
||||
async save(data?: any) {
|
||||
data = data ?? this.dump();
|
||||
await this.plugin.saveData(this.file, this.dump());
|
||||
console.debug('Save config:', data);
|
||||
return data;
|
||||
|
@ -285,6 +285,13 @@ export class SettingUtils {
|
|||
|
||||
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');
|
||||
|
@ -330,7 +337,7 @@ export class SettingUtils {
|
|||
textInputElement.value = item.value;
|
||||
textInputElement.onchange = item.action?.callback ?? (() => { });
|
||||
itemElement = textInputElement;
|
||||
|
||||
textInputElement.addEventListener('keydown', preventEnterConfirm);
|
||||
break;
|
||||
case 'textarea':
|
||||
let textareaElement: HTMLTextAreaElement = document.createElement('textarea');
|
||||
|
@ -345,6 +352,7 @@ export class SettingUtils {
|
|||
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');
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<script lang="ts">
|
||||
import { showMessage } from "siyuan";
|
||||
import SettingPanel from "./libs/setting-panel.svelte";
|
||||
import SettingPanel from "./libs/components/setting-panel.svelte";
|
||||
|
||||
let groups: string[] = ["🌈 Default"];
|
||||
let groups: string[] = ["🌈 Group 1", "✨ Group 2"];
|
||||
let focusGroup = groups[0];
|
||||
|
||||
const SettingItems: ISettingItem[] = [
|
||||
const group1Items: ISettingItem[] = [
|
||||
{
|
||||
type: 'checkbox',
|
||||
title: 'checkbox',
|
||||
|
@ -21,6 +21,15 @@
|
|||
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',
|
||||
|
@ -32,6 +41,22 @@
|
|||
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',
|
||||
|
@ -44,19 +69,6 @@
|
|||
max: 100,
|
||||
step: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
title: 'button',
|
||||
description: 'This is a button',
|
||||
key: 'e',
|
||||
value: 'Click Button',
|
||||
button: {
|
||||
label: 'Click Me',
|
||||
callback: () => {
|
||||
showMessage('Hello, world!');
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -96,7 +108,7 @@
|
|||
<div class="config__tab-wrap">
|
||||
<SettingPanel
|
||||
group={groups[0]}
|
||||
settingItems={SettingItems}
|
||||
settingItems={group1Items}
|
||||
display={focusGroup === groups[0]}
|
||||
on:changed={onChanged}
|
||||
on:click={({ detail }) => { console.debug("Click:", detail.key); }}
|
||||
|
@ -105,6 +117,14 @@
|
|||
💡 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>
|
||||
|
||||
|
|
36
src/types/index.d.ts
vendored
36
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 BlockId = string;
|
||||
type NotebookId = string;
|
||||
|
@ -28,7 +31,25 @@ type NotebookConf = {
|
|||
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";
|
||||
|
||||
|
@ -70,6 +91,7 @@ type doOperation = {
|
|||
|
||||
interface Window {
|
||||
siyuan: {
|
||||
config: any;
|
||||
notebooks: any;
|
||||
menus: any;
|
||||
dialogs: any;
|
||||
|
@ -78,5 +100,7 @@ interface Window {
|
|||
user: any;
|
||||
ws: any;
|
||||
languages: any;
|
||||
emojis: any;
|
||||
};
|
||||
Lute: any;
|
||||
}
|
||||
|
|
|
@ -47,7 +47,8 @@
|
|||
"src/**/*.ts",
|
||||
"src/**/*.d.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue"
|
||||
"src/**/*.vue",
|
||||
"src/**/*.svelte"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
|
|
170
vite.config.ts
170
vite.config.ts
|
@ -1,6 +1,5 @@
|
|||
import { resolve } from "path"
|
||||
import { defineConfig, loadEnv } from "vite"
|
||||
import minimist from "minimist"
|
||||
import { viteStaticCopy } from "vite-plugin-static-copy"
|
||||
import livereload from "rollup-plugin-livereload"
|
||||
import { svelte } from "@sveltejs/vite-plugin-svelte"
|
||||
|
@ -9,13 +8,15 @@ import fg from 'fast-glob';
|
|||
|
||||
import vitePluginYamlI18n from './yaml-plugin';
|
||||
|
||||
const args = minimist(process.argv.slice(2))
|
||||
const isWatch = args.watch || args.w || false
|
||||
const devDistDir = "dev"
|
||||
const distDir = isWatch ? devDistDir : "dist"
|
||||
const env = process.env;
|
||||
const isSrcmap = env.VITE_SOURCEMAP === 'inline';
|
||||
const isDev = env.NODE_ENV === 'development';
|
||||
|
||||
console.log("isWatch=>", isWatch)
|
||||
console.log("distDir=>", distDir)
|
||||
const outputDir = isDev ? "dev" : "dist";
|
||||
|
||||
console.log("isDev=>", isDev);
|
||||
console.log("isSrcmap=>", isSrcmap);
|
||||
console.log("outputDir=>", outputDir);
|
||||
|
||||
export default defineConfig({
|
||||
resolve: {
|
||||
|
@ -29,92 +30,67 @@ export default defineConfig({
|
|||
|
||||
vitePluginYamlI18n({
|
||||
inDir: 'public/i18n',
|
||||
outDir: `${distDir}/i18n`
|
||||
outDir: `${outputDir}/i18n`
|
||||
}),
|
||||
|
||||
viteStaticCopy({
|
||||
targets: [
|
||||
{
|
||||
src: "./README*.md",
|
||||
dest: "./",
|
||||
},
|
||||
{
|
||||
src: "./plugin.json",
|
||||
dest: "./",
|
||||
},
|
||||
{
|
||||
src: "./preview.png",
|
||||
dest: "./",
|
||||
},
|
||||
{
|
||||
src: "./icon.png",
|
||||
dest: "./",
|
||||
}
|
||||
{ src: "./README*.md", dest: "./" },
|
||||
{ src: "./plugin.json", dest: "./" },
|
||||
{ src: "./preview.png", dest: "./" },
|
||||
{ src: "./icon.png", dest: "./" }
|
||||
],
|
||||
}),
|
||||
|
||||
],
|
||||
|
||||
// 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: {
|
||||
"process.env.DEV_MODE": `"${isWatch}"`,
|
||||
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV)
|
||||
"process.env.DEV_MODE": JSON.stringify(isDev),
|
||||
"process.env.NODE_ENV": JSON.stringify(env.NODE_ENV)
|
||||
},
|
||||
|
||||
build: {
|
||||
// 输出路径
|
||||
outDir: distDir,
|
||||
outDir: outputDir,
|
||||
emptyOutDir: false,
|
||||
|
||||
// 构建后是否生成 source map 文件
|
||||
sourcemap: false,
|
||||
|
||||
// 设置为 false 可以禁用最小化混淆
|
||||
// 或是用来指定是应用哪种混淆器
|
||||
// boolean | 'terser' | 'esbuild'
|
||||
// 不压缩,用于调试
|
||||
minify: !isWatch,
|
||||
minify: true,
|
||||
sourcemap: isSrcmap ? 'inline' : false,
|
||||
|
||||
lib: {
|
||||
// Could also be a dictionary or array of multiple entry points
|
||||
entry: resolve(__dirname, "src/index.ts"),
|
||||
// the proper extensions will be added
|
||||
fileName: "index",
|
||||
formats: ["cjs"],
|
||||
},
|
||||
rollupOptions: {
|
||||
plugins: [
|
||||
...(
|
||||
isWatch ? [
|
||||
livereload(devDistDir),
|
||||
{
|
||||
//监听静态资源文件
|
||||
name: 'watch-external',
|
||||
async buildStart() {
|
||||
const files = await fg([
|
||||
'public/i18n/**',
|
||||
'./README*.md',
|
||||
'./plugin.json'
|
||||
]);
|
||||
for (let file of files) {
|
||||
this.addWatchFile(file);
|
||||
}
|
||||
...(isDev ? [
|
||||
livereload(outputDir),
|
||||
{
|
||||
name: 'watch-external',
|
||||
async buildStart() {
|
||||
const files = await fg([
|
||||
'public/i18n/**',
|
||||
'./README*.md',
|
||||
'./plugin.json'
|
||||
]);
|
||||
for (let file of files) {
|
||||
this.addWatchFile(file);
|
||||
}
|
||||
}
|
||||
] : [
|
||||
zipPack({
|
||||
inDir: './dist',
|
||||
outDir: './',
|
||||
outFileName: 'package.zip'
|
||||
})
|
||||
]
|
||||
)
|
||||
}
|
||||
] : [
|
||||
// Clean up unnecessary files under dist dir
|
||||
cleanupDistFiles({
|
||||
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"],
|
||||
|
||||
output: {
|
||||
|
@ -128,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue