diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 49834e5..7bd6c4e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,12 +17,12 @@ jobs: - name: Install Node.js uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 18 registry-url: "https://registry.npmjs.org" # Install pnpm - name: Install pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v2 id: pnpm-install with: version: 8 @@ -59,4 +59,4 @@ jobs: artifactErrorsFailBuild: true artifacts: "package.zip" token: ${{ secrets.GITHUB_TOKEN }} - prerelease: false + prerelease: true diff --git a/CHANGELOG.md b/CHANGELOG.md index af04807..ce36ec8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,6 @@ # Changelog -## v0.3.5 2024-04-30 - -* [Add `direction` to plugin method `Setting.addItem`](https://github.com/siyuan-note/siyuan/issues/11183) - +## 0.3.5 2024-03 ## 0.3.4 2024-02-20 diff --git a/README.md b/README.md index 648dcb7..09c8cdd 100644 --- a/README.md +++ b/README.md @@ -11,38 +11,22 @@ 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) - > - > **We also provide with a vite+solidjs template**: [frostime/plugin-sample-vite-solidjs](https://github.com/frostime/plugin-sample-vite-solidjs) + > If don't want svelte, turn to this template: [frostime/plugin-sample-vite](https://github.com/frostime/plugin-sample-vite) 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. Use the Use this template 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. +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` -### Setting the Target Directory for the make-link Command +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. -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. +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 ```bash >>> pnpm run make-link > plugin-sample-vite-svelte@0.0.3 make-link H:\SrcCode\开源项目\plugin-sample-vite-svelte @@ -56,24 +40,22 @@ The `make-link` command creates a symbolic link that binds your `dev` directory Got target directory: H:\Media\SiYuan/data/plugins Done! Created symlink H:\Media\SiYuan/data/plugins/plugin-sample-vite-svelte ``` -2. **Manually Configure Target Directory** - - Open the `./scripts/make_dev_link.js` file, change `targetDir` to the SiYuan plugin directory `/data/plugins`. - - Run the `pnpm run make-link` command. If you see a message similar to the one below, it indicates successful creation: +4. **Manually create development symbolic links** + - Open `./scripts/make_dev_link.js` file, set `targetDir` to your SiYuan plugin directory `/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 -3. **Set Environment Variable to Create Symbolic Link** - - Set the system environment variable `SIYUAN_PLUGIN_DIR` to the path `workspace/data/plugins`. + 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 -### 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. +> 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. ## I18n diff --git a/README_zh_CN.md b/README_zh_CN.md index 0ac9fca..ca35f7c 100644 --- a/README_zh_CN.md +++ b/README_zh_CN.md @@ -10,34 +10,17 @@ 2. 使用符号链接、而不是把项目放到插件目录下的模式进行开发 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) + > 如果不想要 svelte,请移步 [frostime/plugin-sample-vite](https://github.com/frostime/plugin-sample-vite) 4. 提供一个github action 模板,能自动生成package.zip并上传到新版本中 ## 开始 -1. 通过 Use this template 按钮将该库文件复制到你自己的库中,请注意库名和插件名称一致,默认分支必须为 `main` +1. 通过 Use this template 按钮将该库文件复制到你自己的库中,请注意库名必须和插件名称一致,默认分支必须为 `main` 2. 将你的库克隆到本地开发文件夹中 * 注意: 同 `plugin-sample` 不同, 本样例并不推荐直接把代码下载到 `{workspace}/data/plugins/` 3. 安装 [NodeJS](https://nodejs.org/en/download) 和 [pnpm](https://pnpm.io/installation),然后在开发文件夹下执行 `pnpm i` 安装所需要的依赖 -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. **选择工作空间** +3. **自动创建符号链接** - 打开思源笔记, 确保思源内核正在运行 - 运行 `pnpm run make-link`, 脚本会自动检测所有思源的工作空间, 请在命令行中手动输入序号以选择工作空间 ```bash @@ -53,26 +36,23 @@ make-link 命令会创建符号链接将你的 `dev` 目录绑定到思源的插 Got target directory: H:\Media\SiYuan/data/plugins Done! Created symlink H:\Media\SiYuan/data/plugins/plugin-sample-vite-svelte ``` -2. **手动配置目标目录** +4. **手动创建符号链接** - 打开 `./scripts/make_dev_link.js` 文件,更改 `targetDir` 为思源的插件目录 `/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 -3. **设置环境变量创建符号链接** - - 设置系统的环境变量 `SIYUAN_PLUGIN_DIR` 为 `工作空间/data/plugins` 的路径 + Done! Created symlink H:/SiYuanDevSpace/data/plugins/plugin-sample-vite-svelte + ``` +5. **设置环境变量创建符号链接** + - 你也可以设置系统的环境变量 `SIYUAN_PLUGIN_DIR` 为 `/data/plugins` 的路径 +6. 执行 `pnpm run dev` 进行实时编译 +7. 在思源中打开集市并在下载选项卡中启用插件 -### 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 脚本执行权限 +> 注意由于使用的 make-link 脚本依赖于 `fetch`,所以如果想要使用 make-link **请保证至少安装 v18 版本的 nodejs** ## 国际化 diff --git a/package.json b/package.json index 2b7cdec..44cdd78 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "plugin-sample-vite-svelte", - "version": "0.3.6", + "version": "0.3.5", "type": "module", "description": "This is a sample plugin based on vite and svelte for Siyuan (https://b3log.org/siyuan)", "repository": "", @@ -8,30 +8,27 @@ "author": "frostime", "license": "MIT", "scripts": { - "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-link": "node --no-warnings ./scripts/make_dev_link.js", + "dev": "vite build --watch", + "build": "vite build", "make-install": "vite build && node --no-warnings ./scripts/make_install.js" }, "devDependencies": { - "@sveltejs/vite-plugin-svelte": "^3.1.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0", "@tsconfig/svelte": "^4.0.1", "@types/node": "^20.3.0", - "cross-env": "^7.0.3", "fast-glob": "^3.2.12", - "glob": "^10.0.0", + "glob": "^7.2.3", "js-yaml": "^4.1.0", "minimist": "^1.2.8", "rollup-plugin-livereload": "^2.0.5", "sass": "^1.63.3", - "siyuan": "1.0.4", - "svelte": "^4.2.19", + "siyuan": "0.9.8", + "svelte": "^4.2.0", "ts-node": "^10.9.1", "typescript": "^5.1.3", - "vite": "^5.2.9", + "vite": "^5.0.0", "vite-plugin-static-copy": "^1.0.2", "vite-plugin-zip-pack": "^1.0.5" } -} \ No newline at end of file +} diff --git a/plugin.json b/plugin.json index fd63a3c..071b2a3 100644 --- a/plugin.json +++ b/plugin.json @@ -2,16 +2,14 @@ "name": "plugin-sample-vite-svelte", "author": "frostime", "url": "https://github.com/siyuan-note/plugin-sample-vite-svelte", - "version": "0.3.6", + "version": "0.3.5", "minAppVersion": "3.0.12", "backends": [ "windows", "linux", "darwin", "ios", - "android", - "harmony", - "docker" + "android" ], "frontends": [ "desktop", @@ -34,12 +32,10 @@ }, "funding": { "custom": [ - "" + "https://afdian.net/a/frostime" ] }, "keywords": [ - "plugin", - "sample", - "插件样例" + "plugin", "sample", "插件样例" ] } diff --git a/public/i18n/README.md b/public/i18n/README.md deleted file mode 100644 index af8de98..0000000 --- a/public/i18n/README.md +++ /dev/null @@ -1,12 +0,0 @@ -思源支持的 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. diff --git a/public/i18n/zh_CN.json b/public/i18n/zh_CN.json deleted file mode 100644 index 6600f6a..0000000 --- a/public/i18n/zh_CN.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "addTopBarIcon": "使用插件添加一个顶栏按钮", - "cancel": "取消", - "save": "保存", - "byeMenu": "再见,菜单!", - "helloPlugin": "你好,插件!", - "byePlugin": "再见,插件!", - "showDialog": "弹出一个对话框", - "removedData": "数据已删除", - "confirmRemove": "确认删除 ${name} 中的数据?", - "insertEmoji": "插入表情", - "removeSpace": "移除空格", - "getTab": "在日志中打印出已打开的所有自定义页签", - "name": "思源", - "hello": { - "makesure": "使用这个模板之前,请阅读官方教程, 确保自己已经理解了插件的基本开发流程。" - }, - "hintTitle": "关于", - "hintDesc": "🔗 plugin-sample-vite-svelte
💻 @frostime
💻 @88250
💻 @zxkmm" -} \ No newline at end of file diff --git a/public/i18n/zh_CN.yaml b/public/i18n/zh_CN.yaml new file mode 100644 index 0000000..16099a3 --- /dev/null +++ b/public/i18n/zh_CN.yaml @@ -0,0 +1,21 @@ +--- +addTopBarIcon: 使用插件添加一个顶栏按钮 +cancel: 取消 +save: 保存 +byeMenu: 再见,菜单! +helloPlugin: 你好,插件! +byePlugin: 再见,插件! +showDialog: 弹出一个对话框 +removedData: 数据已删除 +confirmRemove: 确认删除 ${name} 中的数据? +insertEmoji: 插入表情 +removeSpace: 移除空格 +getTab: 在日志中打印出已打开的所有自定义页签 +name: 思源 +hello: + makesure: 使用这个模板之前,请阅读官方教程, + 确保自己已经理解了插件的基本开发流程。 +hintTitle: 关于 +hintDesc: "\U0001F517 + plugin-sample-vite-svelte
\U0001F4BB @frostime
\U0001F4BB @88250
\U0001F4BB + @zxkmm" diff --git a/scripts/elevate.ps1 b/scripts/elevate.ps1 deleted file mode 100644 index 151b8ba..0000000 --- a/scripts/elevate.ps1 +++ /dev/null @@ -1,24 +0,0 @@ -# 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 diff --git a/scripts/make_dev_link.js b/scripts/make_dev_link.js index 2be3d2b..845d298 100644 --- a/scripts/make_dev_link.js +++ b/scripts/make_dev_link.js @@ -1,29 +1,106 @@ -/* - * 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 { log, error, getSiYuanDir, chooseTarget, getThisPluginName, makeSymbolicLink } from './utils.js'; +import http from 'node:http'; +import readline from 'node:readline'; + +//************************************ 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 || res.length === 0) { + + 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) let env = process.env?.SIYUAN_PLUGIN_DIR; - if (env) { + if (env !== undefined && env !== null && env !== '') { targetDir = env; log(`\tGot target directory from environment variable "SIYUAN_PLUGIN_DIR": ${targetDir}`); } else { @@ -34,33 +111,76 @@ 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); } -/** - * 2. The dev directory, which contains the compiled plugin code - */ + +//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 const devDir = `${process.cwd()}/dev`; +//mkdir if not exists if (!fs.existsSync(devDir)) { fs.mkdirSync(devDir); } - -/** - * 3. The target directory to make symbolic link to dev directory - */ -const name = getThisPluginName(); -if (name === null) { - process.exit(1); +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; +} + +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}`); } -const targetPath = `${targetDir}/${name}`; -/** - * 4. Make symbolic link - */ -makeSymbolicLink(devDir, targetPath); diff --git a/scripts/make_install.js b/scripts/make_install.js index cb2a4ac..9c2cd9b 100644 --- a/scripts/make_install.js +++ b/scripts/make_install.js @@ -1,57 +1,191 @@ -/* - * 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'; +import path from 'path'; +import http from 'node:http'; +import readline from 'node:readline'; -let targetDir = ''; -/** - * 1. Get the parent directory to install the plugin - */ -log('>>> Try to visit constant "targetDir" in make_install.js...'); +//************************************ 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...') + 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'); + 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'); 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); } -/** - * 3. The target directory to install the plugin - */ -const name = getThisPluginName(); -if (name === null) { - process.exit(1); +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; } + const targetPath = `${targetDir}/${name}`; -/** - * 4. Copy the compiled plugin code to the target directory - */ + +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}`); + }); +} copyDirectory(distDir, targetPath); + + diff --git a/scripts/update_version.js b/scripts/update_version.js deleted file mode 100644 index 775c98a..0000000 --- a/scripts/update_version.js +++ /dev/null @@ -1,141 +0,0 @@ -// 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); - } -})(); diff --git a/scripts/utils.js b/scripts/utils.js deleted file mode 100644 index 210b6b1..0000000 --- a/scripts/utils.js +++ /dev/null @@ -1,182 +0,0 @@ -/* - * 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} - */ -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}`); - } -} diff --git a/src/api.ts b/src/api.ts index 0595371..c227518 100644 --- a/src/api.ts +++ b/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 { fetchPost, fetchSyncPost, IWebSocketData } from "siyuan"; +import { fetchSyncPost, IWebSocketData } from "siyuan"; export async function request(url: string, data: any) { @@ -328,35 +328,14 @@ export async function getFile(path: string): Promise { path: path } let url = '/api/file/getFile'; - 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 => { - const endpoint = '/api/file/getFile' - let response = await fetch(endpoint, { - method: 'POST', - body: JSON.stringify({ - path: path - }) - }); - if (!response.ok) { + try { + let file = await fetchSyncPost(url, data); + return file; + } catch (error_msg) { 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); diff --git a/src/hello.svelte b/src/hello.svelte index 967c7f6..cc9f0dd 100644 --- a/src/hello.svelte +++ b/src/hello.svelte @@ -1,11 +1,3 @@ - - -{#if type === "checkbox"} - - -{:else if type === "textinput"} - - -{:else if type === "textarea"} - - -
-
- -
`, - 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((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: `
-
-
-
-
-
- -
`, - 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((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: `
`, - 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 - } -} diff --git a/src/libs/index.d.ts b/src/libs/index.d.ts index 27a27ed..ab2f6c3 100644 --- a/src/libs/index.d.ts +++ b/src/libs/index.d.ts @@ -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; } diff --git a/src/libs/promise-pool.ts b/src/libs/promise-pool.ts deleted file mode 100644 index e20b0b8..0000000 --- a/src/libs/promise-pool.ts +++ /dev/null @@ -1,48 +0,0 @@ -export default class PromiseLimitPool { - private maxConcurrent: number; - private currentRunning = 0; - private queue: (() => void)[] = []; - private promises: Promise[] = []; - - constructor(maxConcurrent: number) { - this.maxConcurrent = maxConcurrent; - } - - add(fn: () => Promise): void { - const promise = new Promise((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 { - 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(); - } - } -} diff --git a/src/libs/setting-item.svelte b/src/libs/setting-item.svelte new file mode 100644 index 0000000..ef1a964 --- /dev/null +++ b/src/libs/setting-item.svelte @@ -0,0 +1,105 @@ + + + diff --git a/src/libs/components/setting-panel.svelte b/src/libs/setting-panel.svelte similarity index 58% rename from src/libs/components/setting-panel.svelte rename to src/libs/setting-panel.svelte index 783e2b6..038d890 100644 --- a/src/libs/components/setting-panel.svelte +++ b/src/libs/setting-panel.svelte @@ -2,13 +2,13 @@ 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 + FilePath : /src/libs/setting-panel.svelte + LastEditTime : 2024-04-27 16:46:49 Description : -->