Compare commits

...

7 commits
v1.0.0 ... main

Author SHA1 Message Date
Frostime
777f31761c
Update plugin.json 2024-12-10 18:00:25 +08:00
frostime
38b19fdb88 🔧 ci: update vite script, auto remove trivial files under dist/ 2024-10-24 14:51:01 +08:00
frostime
591cf2e95e 🔧 ci: 更新 vite 打包脚本 2024-10-16 14:43:56 +08:00
frostime
4aee720da5 📝 doc: tip 2024-09-27 18:25:34 +08:00
frostime
19cae2eabd 📝 doc: introduce to siyuan-plugin-cli 2024-09-27 18:15:23 +08:00
frostime
63ee8d92dc 🔧 ci: tsconfig, include svelte 2024-09-10 10:55:59 +08:00
frostime
63cc3ac0c2 🔧 ci: add a script to update version 2024-09-08 14:05:54 +08:00
15 changed files with 382 additions and 138 deletions

View file

@ -59,4 +59,4 @@ jobs:
artifactErrorsFailBuild: true
artifacts: "package.zip"
token: ${{ secrets.GITHUB_TOKEN }}
prerelease: true
prerelease: false

View file

@ -18,6 +18,14 @@
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 <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`.

View file

@ -26,6 +26,13 @@
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` 目录绑定到思源的插件目录下。你可以有三种方式来配置目标的思源工作空间并创建符号链接:

View file

@ -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,28 +8,30 @@
"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",
"dev": "vite build --watch",
"build": "vite build",
"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.9",
"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"
}
}
}

View file

@ -2,15 +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",
"docker",
"ios",
"android"
"android",
"harmony",
"docker"
],
"frontends": [
"desktop",
@ -37,6 +38,8 @@
]
},
"keywords": [
"plugin", "sample", "插件样例"
"plugin",
"sample",
"插件样例"
]
}

12
public/i18n/README.md Normal file
View 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
View 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"
}

View file

@ -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"

141
scripts/update_version.js Normal file
View 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);
}
})();

View file

@ -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";

View file

@ -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;
}
}

View file

@ -3,7 +3,7 @@
* @Author : frostime
* @Date : 2024-03-23 21:37:33
* @FilePath : /src/libs/dialog.ts
* @LastEditTime : 2024-07-19 15:34:39
* @LastEditTime : 2024-10-16 14:31:04
* @Description : Kits about dialogs
*/
import { Dialog } from "siyuan";
@ -135,7 +135,10 @@ export const simpleDialog = (args: {
destroyCallback: args.callback
});
dialog.element.querySelector(".dialog-content").appendChild(args.ele);
return dialog;
return {
dialog,
close: dialog.destroy.bind(dialog)
};
}
@ -147,9 +150,15 @@ export const svelteDialog = (args: {
let container = document.createElement('div')
container.style.display = 'contents';
let component = args.constructor(container);
simpleDialog({...args, ele: container, callback: () => {
component.$destroy();
if (args.callback) args.callback();;
}});
return component;
const { dialog, close } = simpleDialog({
...args, ele: container, callback: () => {
component.$destroy();
if (args.callback) args.callback();
}
});
return {
component,
dialog,
close
}
}

48
src/libs/promise-pool.ts Normal file
View 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();
}
}
}

View file

@ -47,7 +47,8 @@
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue"
"src/**/*.vue",
"src/**/*.svelte"
],
"references": [
{

View file

@ -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: isWatch ? 'inline' : 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);
}
}
}
}
};
}