Compare commits

..

190 commits
v0.0.1 ... 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
frostime
e345e18613 🔨 refactor: 创建 dir 符号链接;并编写 ps 脚本用于获取 windows 管理员权限
https://github.com/siyuan-note/siyuan/issues/12399
2024-09-06 19:50:50 +08:00
frostime
da4c4ced8f feat: 更新 Form 组件 2024-08-09 21:49:29 +08:00
frostime
1ac9cf8ff5 feat(api): getFileBlob 2024-08-01 13:34:25 +08:00
frostime
4c004d73b5 🎨 misc 2024-07-19 15:51:59 +08:00
frostime
b739c5ad54 ️ perf: 优化各个组件 2024-07-19 15:40:50 +08:00
frostime
0d0167b85b Merge branch 'main' of github.com:siyuan-note/plugin-sample-vite-svelte 2024-07-19 15:18:08 +08:00
frostime
6361001b8a 🚚 mv:将libs目录下的组件移至components子目录
为了更好地组织项目结构,将libs目录下的组件文件移动到新的libs/components子目录中。
同时,更新了setting-example.svelte组件中SettingPanel的导入路径,以反映新的文件位置。
2024-07-19 15:17:49 +08:00
Frostime
46e48452c0
Update plugin.json 2024-07-11 00:41:46 +08:00
frostime
059744903d 🔧 ci: update pnpm action
ref to: https://github.com/pnpm/action-setup/issues/136
2024-07-04 21:27:46 +08:00
frostime
504de7d821 🐛 fix: build 是不生成 sourcemap 2024-07-01 13:44:54 +08:00
frostime
9e2a64b471 🔧 ci(vite): 生成 inline source map,方便调试 2024-06-30 12:45:18 +08:00
frostime
0a8ab5a227 📝 docs 2024-06-14 22:36:29 +08:00
frostime
5ff929e456 📝 doc: another template 2024-06-14 22:35:19 +08:00
frostime
ba57ba3f23 feat: add const.ts 2024-06-08 20:51:13 +08:00
frostime
6d75a46a56 🔨 refactor: 重构基于 svelte 的设置面板功能 2024-06-08 18:30:53 +08:00
Frostime
49eb06330d
🔨 Remove the default fund in plugin.json 2024-06-06 22:31:20 +08:00
frostime
9c7572b820 🐛 getFile API 2024-06-04 15:04:07 +08:00
frostime
81d5bee160 📝docs 2024-06-01 16:35:02 +08:00
frostime
67d6024a04 feat: add kits about dialogs 2024-06-01 16:29:07 +08:00
frostime
03ab34f552 禁用思源内部的 enter confirm 特性 2024-05-01 17:45:05 +08:00
frostime
9f54e7046c misc 2024-04-30 22:15:50 +08:00
frostime
3d565424ae 🎨 feat: Add "custom setting-items" example 2024-04-30 16:48:10 +08:00
frostime
29c494a3f4 feat(setting-utils): getter and setting 2024-04-30 16:32:26 +08:00
frostime
59daefa941 ⬆️ update siyuan: add direction parameter 2024-04-30 16:15:58 +08:00
frostime
7159755c70 🔨 调整 setting 的 interface 定义 2024-04-30 16:00:03 +08:00
frostime
4a9d0f3953 🔥 remove eslint 2024-04-30 15:57:43 +08:00
frostime
0df9ec29ea feat(setting-utils): 增加自定义元素功能 2024-04-29 17:43:02 +08:00
frostime
57cc62f7b5 🎨 refactor: setting-utils args 2024-04-28 17:50:52 +08:00
frostime
56be185458 misc(setting-utils) 2024-04-28 17:48:49 +08:00
frostime
8daa01aedb 🐛 fix: 模板提供的 button 的 click 事件不生效; close #31 2024-04-27 16:55:46 +08:00
Frostime
2c45caf80f
Merge pull request #30 from tengfei-xy/main
修改take()方法的获取方式
2024-04-25 21:31:11 +08:00
浔阳陌客
7a46168ed2 修改take()的获取值方法 2024-04-25 21:16:28 +08:00
浔阳陌客
587d5f2112 修复刷新页面后,数据已加载,但没有显示到设置页面的问题 2024-04-24 14:46:41 +08:00
浔阳陌客
8f27804c83 完善修改 2024-04-24 14:23:51 +08:00
小小飞同学
0645799101
Merge branch 'siyuan-note:main' into main 2024-04-24 12:19:10 +08:00
浔阳陌客
786cc5e933 修改getElement()方法和方法说明 2024-04-24 12:17:56 +08:00
frostime
3b47436806 📝 readme 2024-04-19 19:17:49 +08:00
frostime
1a1c267b46 Merge branch 'upgrade-svelte' 2024-04-19 19:09:30 +08:00
frostime
adf7d86dcd 🔧 config svelte 2024-04-19 19:09:15 +08:00
frostime
4286a49a0b ⬆️ update svelte deps 2024-04-19 18:52:39 +08:00
Frostime
cd38b61aba
Merge pull request #29 from tengfei-xy/main
实时读取和实时保存
2024-04-06 16:33:26 +08:00
浔阳陌客
0e372e9068 修改textinput和textarea的回调函数 2024-04-06 14:48:25 +08:00
浔阳陌客
f06c82085e 为经典设置窗口添加实时读取和实时保存的能力 2024-04-06 13:24:00 +08:00
浔阳陌客
44d61785cf 添加setAndSave方法作为设置参数并保存 2024-04-06 01:44:54 +08:00
frostime
762ae2197b 🔧 build: yaml-plugin 2024-04-05 22:54:18 +08:00
Frostime
4310b00dca
Merge pull request #28 from tengfei-xy/main
为表单提供禁用和启用的方法
2024-04-05 22:44:33 +08:00
frostime
a50c61efaf 🔥 rm: en_US.yaml 2024-04-05 22:42:00 +08:00
frostime
91deda2a72 🔧 build: 使用 yaml i18n 2024-04-05 22:41:07 +08:00
浔阳陌客
f9ea1bec3f 为表单提供禁用和启用的方法 2024-04-05 21:16:33 +08:00
Frostime
6700d611b9
Merge pull request #27 from tengfei-xy/main
为checkout添加切换状态时添加回调函数
2024-04-05 17:14:06 +08:00
浔阳陌客
c58c836b7c 为checkout添加切换状态时添加回调函数 2024-04-05 16:37:25 +08:00
frostime
ec00075b4e remove save at onunload 2024-04-04 17:50:01 +08:00
frostime
9fcb5bb481 feat: make install script 2024-03-28 20:06:59 +08:00
frostime
4a7bd848a9 📝 doc: add developer guide document 2024-03-16 13:43:27 +08:00
frostime
0ff9fde560 📝 doc: update readme 2024-03-03 17:41:28 +08:00
frostime
e48a9abbc9 🚀 update 2024-03-03 17:37:02 +08:00
frostime
c33a63aa8f 🐛 fix: 2.12.4 block icon incompatible 2024-01-24 12:55:11 +08:00
frostime
90b2805d9e 🐛 fix: vite.config.ts 2024-01-23 22:03:34 +08:00
frostime
881ff608b0 🚚 move: png back to root 2024-01-23 21:59:02 +08:00
frostime
65b9505ae4 ⬆️ ci: update vite 2024-01-22 00:36:26 +08:00
frostime
3ad078d548 ⬆️ ci: update vite 2024-01-21 13:14:34 +08:00
frostime
953d15c661 feat: Update setting utils 2024-01-15 20:45:34 +08:00
frostime
bf21514b99 🔨 Add plugin.json back to top 2024-01-15 20:21:53 +08:00
Seven Chord
e6a7277f8a 使用public目录替代复制静态资源的配置 2024-01-14 14:52:04 +08:00
frostime
6f8ae4dcd3 Merge branch 'zxkmm-add_hint_type_to_settingUtil_and_fix_official_setting_calling' into dev 2024-01-13 22:39:49 +08:00
zxkmm
2ab75b2bf3 clean up 2024-01-13 22:17:09 +08:00
zxkmm
f84497f6da add hint type to settingUtil 2024-01-13 22:10:54 +08:00
frostime
1735ec4bce 🚀 update to v0.3.2 2024-01-13 21:40:22 +08:00
frostime
74d62ac1aa feat: add foldBlock api 2023-12-28 22:42:58 +08:00
frostime
a2a615aae2 ⬆️ update with siyuan petal 2023-12-28 22:37:37 +08:00
frostime
c179519f95 feat: Add input number element in setting-utils 2023-12-28 18:11:21 +08:00
Frostime
50a2286d43
Merge pull request #23 from zxkmm/fix_wrong_type_def_to_match_header
fix wrong type def in lib, edited to match header
2023-12-28 18:08:21 +08:00
zxkmm
2a08298060 fix wrong type def in lib to match header 2023-12-28 17:09:08 +08:00
frostime
2051a2a7d6 test: setting panel 2023-11-28 21:49:25 +08:00
frostime
b0d28e2513 🔨 refactor(setting): 重构了Svelte设置的模板
- 将 SettingPanel 作为基本模板
- 更改了 settingitem 的类型定义
2023-11-28 21:24:19 +08:00
frostime
f1fcf77500 Merge branch 'main' of github.com:siyuan-note/plugin-sample-vite-svelte 2023-11-27 09:28:53 +08:00
frostime
ec8d768d2d 👷 ci: 优化 vite 配置 2023-11-27 09:28:36 +08:00
Frostime
7f57c40db5
doc: 宣传一下无 svelte 版本的模板 2023-11-21 19:55:28 +08:00
frostime
b31102d12f Merge branch 'dev' 2023-11-19 12:30:45 +08:00
frostime
962f879fa2 feat(api): 更新后端API getIDsByHPath 2023-11-19 12:30:11 +08:00
frostime
8e841a4a90 feat(update): 更新插件模板前端部分 2023-11-19 12:17:42 +08:00
frostime
3de8bc6134 🐳 chore(update): 更新模板的依赖、文档等部分 2023-11-19 11:49:25 +08:00
frostime
5f59ffefe4 update keywords 2023-10-28 18:22:56 +08:00
frostime
7d95423d24 update 2023-10-28 16:52:03 +08:00
frostime
3343eb7e5c update 2023-10-27 22:12:43 +08:00
frostime
c580fb7a00 update setting utils 2023-10-27 22:11:43 +08:00
frostime
e2ed03218d Merge branch 'dev' 2023-10-27 21:04:59 +08:00
frostime
bc635356dc fix protyle bug 2023-10-27 21:04:12 +08:00
frostime
8be20e023c update typescript 2023-10-27 20:52:25 +08:00
frostime
c2d3812e19 misc 2023-10-27 20:41:40 +08:00
frostime
05c9f269e2 update doc 2023-09-16 18:37:00 +08:00
frostime
1a0b1018c5 优化 setting-utils 2023-09-16 18:17:41 +08:00
Frostime
b1a061cb0c
Merge pull request #22 from siyuan-note:setting-utils
setting-utils
2023-09-16 18:04:22 +08:00
frostime
cdb5085f35 setting-utils 2023-09-16 18:03:59 +08:00
Frostime
deb065668f
Merge pull request #21 from siyuan-note/dev
Update
2023-09-16 16:25:54 +08:00
frostime
06f598b18b doc 2023-09-16 16:22:25 +08:00
frostime
2fa17e4a7b update index.ts 2023-09-16 16:21:00 +08:00
frostime
57e50e1ca8 udpate version 2023-09-16 16:16:56 +08:00
frostime
f114305718 update 2023-08-27 16:47:42 +08:00
Frostime
cfa959c1a8
Update api.ts 2023-08-17 00:10:25 +08:00
frostime
1f199a6dcf udpate api.ts 2023-08-15 23:29:23 +08:00
frostime
548f80a46f update api 2023-08-15 11:06:33 +08:00
frostime
b7f633598f feat: 单独抽出 api.d.ts 2023-08-15 10:52:47 +08:00
frostime
811519bfd8 update 2023-08-15 10:43:52 +08:00
frostime
4fc8ef7afc update to v0.1.12 2023-08-15 10:38:13 +08:00
Frostime
014f906263
Update index.d.ts 2023-07-29 20:16:58 +08:00
frostime
d64c5b1c38 update make-link 2023-07-23 14:27:58 +08:00
Frostime
e6f5580c26
Merge pull request #19 from siyuan-note/dev
Update
2023-07-15 15:31:11 +08:00
frostime
d5bf3cbc66 update make-link 2023-07-15 15:28:30 +08:00
frostime
38b8677c15 upgrade to v0.1.10 2023-07-15 15:21:40 +08:00
Frostime
77e337b9f5
Merge pull request #18 from siyuan-note/frostime-patch-3
Update index.d.ts, fix ial type
2023-07-08 15:47:17 +08:00
Frostime
519bece35b
Update index.d.ts, fix ial type 2023-07-08 15:45:26 +08:00
Frostime
f0ef27efab
Merge pull request #17 from siyuan-note/dev
Update
2023-06-28 21:29:16 +08:00
frostime
ddca3e0e24 fix: protyle 2023-06-28 21:28:38 +08:00
frostime
34e3646add doc 2023-06-28 21:18:04 +08:00
frostime
b1660caa30 slider tooltips 2023-06-28 21:17:34 +08:00
frostime
73070a56fd dialog use protyle 2023-06-28 21:11:35 +08:00
frostime
22c27c0542 change 2023-06-28 20:43:58 +08:00
frostime
837f5ae1f5 i18n 2023-06-28 20:35:12 +08:00
frostime
4dde9da239 chore 2023-06-28 20:34:16 +08:00
frostime
d4f9ffa687 update to v0.1.5 2023-06-17 14:17:44 +08:00
Frostime
881a479241
Merge pull request #16 from siyuan-note/frostime-patch-1
Update plugin.json
2023-06-14 16:40:42 +08:00
Frostime
0ed1b06315
Merge pull request #15 from siyuan-note/frostime-patch-2
Update plugin.json
2023-06-14 16:01:32 +08:00
Frostime
ca28eb77df
Update plugin.json 2023-06-14 16:01:23 +08:00
Frostime
37e885d63a
Update plugin.json 2023-06-14 16:00:59 +08:00
Frostime
cb7dd43525
Update api.ts, add transferBlockRef API 2023-06-14 15:19:34 +08:00
frostime
6a6d7e2f03 update 2023-06-11 20:34:40 +08:00
frostime
8e8973852b doc 2023-06-11 15:54:12 +08:00
frostime
38f3abde71 setting 2023-06-11 15:53:02 +08:00
frostime
5cfd0da04d update 2023-06-11 15:43:43 +08:00
frostime
6b903d5a14 feat: Add @ alias to ./src 2023-06-06 15:44:42 +08:00
Frostime
14479d3d14
Merge pull request #14 from siyuan-note/dev
add @types
2023-06-04 13:53:02 +08:00
frostime
57e008870e add @types 2023-06-04 13:49:30 +08:00
frostime
9b46057ab0 README 2023-06-03 18:27:26 +08:00
Frostime
4341cb4a4d
Merge pull request #13 from siyuan-note/dev
fix #9
2023-06-03 18:26:03 +08:00
frostime
a4c5c962c2 fix #9 2023-06-03 18:25:18 +08:00
Frostime
7ff5334444
Merge pull request #12 from siyuan-note/dev
Update make-link
2023-06-03 18:20:26 +08:00
frostime
3f160a5b1f targetDir 2023-06-03 18:18:25 +08:00
frostime
d365b88d09 header 2023-06-03 18:11:57 +08:00
frostime
e3304bbade 检查已经存在的文件 2023-06-03 18:09:57 +08:00
frostime
ef0f739a98 log + error 2023-06-03 17:55:00 +08:00
Frostime
e849287222
Merge pull request #11 from siyuan-note/dev
Update to v0.1.3
2023-06-03 16:56:54 +08:00
frostime
5d9805a665 readme 2023-06-03 16:55:40 +08:00
frostime
8b11d429da 基本功能保持一致 2023-06-03 16:51:40 +08:00
frostime
9f8f1fb69e onload done 2023-06-03 16:34:30 +08:00
frostime
4dbc215a0e i18n 2023-06-03 16:32:43 +08:00
frostime
2759bb4c13 plugin.json 2023-06-03 15:51:29 +08:00
frostime
fbfc7467b5 copy from plugin-sample:index.ts 2023-06-03 15:50:28 +08:00
frostime
c00d5c9373 chore siyuan version 2023-06-03 14:42:40 +08:00
frostime
bfb5887dd3 chore: update siyuan to npm package latest 2023-06-03 14:41:48 +08:00
frostime
78d576a19e chore: update release.yml 2023-06-03 14:40:39 +08:00
frostime
439c2134b6 更新 README,指明 node 版本在 18 以上 2023-05-30 21:39:25 +08:00
frostime
876ee9c62f readme 2023-05-26 17:48:39 +08:00
frostime
3cf80f7d2d readme 2023-05-26 17:47:09 +08:00
frostime
44e0010f6a remove dock.svelte 2023-05-26 17:44:48 +08:00
frostime
ead99605fd come up with 0.0.6 2023-05-26 17:43:08 +08:00
frostime
d33a7b494d update version 2023-05-23 17:37:42 +08:00
Frostime
c8497611ce
Merge pull request #7 from siyuan-note/dev
Up to date with `plugin-sample` v0.0.5
2023-05-23 17:35:46 +08:00
frostime
e8bf750037 test 2023-05-23 17:34:51 +08:00
frostime
4992959b24 copy from plugin-sample v0.0.5 2023-05-23 17:27:30 +08:00
frostime
22b90d85b8 how to delete svelte 2023-05-23 17:06:44 +08:00
frostime
df1363e410 update siyuan.d.ts 2023-05-23 15:43:13 +08:00
frostime
d536f9b25d remove copyrigth 2023-05-23 15:09:41 +08:00
frostime
b42d86b17a add b3-typography to render plain text content 2023-05-23 15:02:56 +08:00
Frostime
8b19031d08
Update README.md 2023-05-23 00:20:42 +08:00
Frostime
2dc290df03
Merge pull request #6 from siyuan-note/dev-auto-link
Dev auto link
2023-05-22 23:26:30 +08:00
frostime
3853269e82 log 2023-05-22 22:53:25 +08:00
frostime
b3088200b1 update readme 2023-05-22 22:50:03 +08:00
frostime
c99faadc2a auto detect workspace 2023-05-22 22:39:14 +08:00
frostime
9007a64178 可以读取 workspace 了 2023-05-22 22:20:40 +08:00
frostime
c29ea97240 commit 2023-05-22 21:34:17 +08:00
frostime
831ba76c7c dev 2023-05-22 21:34:17 +08:00
frostime
aa19c76235 fix getFile with err 2023-05-22 21:33:58 +08:00
frostime
94469c4d5e fix: api.getFile 2023-05-22 21:22:32 +08:00
Frostime
14f3cf2f69
Update siyuan.d.ts 2023-05-21 00:33:31 +08:00
frostime
df40349bd0 remove pnpm-lock, fix #4 2023-05-20 20:26:33 +08:00
frostime
c963fc202e Use npm to create symlink remove python dependency 2023-05-20 19:36:42 +08:00
frostime
aed38809be Update README 2023-05-20 19:29:00 +08:00
frostime
afc54cfb8d fix 2023-05-20 18:47:04 +08:00
frostime
b4e58d5650 Change the way to create symbolic link 2023-05-20 18:43:10 +08:00
Frostime
0f049cc461
Dev (#2)
* feat: svelte example for setting pannel

* Must destroy svelte

* onDestroy

* Make hello easy

* 拷贝官方的代码功能

* Use hello both in tab and dialog

* fix: destroy dock
2023-05-20 17:40:15 +08:00
frostime
18fc66f0f6 改为 v0.0.1 2023-05-19 23:29:28 +08:00
frostime
accd976d27 提供 exe 下载地址 2023-05-19 23:27:56 +08:00
45 changed files with 3666 additions and 1727 deletions

View file

@ -1,2 +0,0 @@
node_modules
dist

View file

@ -1,38 +0,0 @@
module.exports = {
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:svelte/recommended",
"turbo",
"prettier",
],
parser: "@typescript-eslint/parser",
overrides: [
{
files: ["*.svelte"],
parser: "svelte-eslint-parser",
// Parse the script in `.svelte` as TypeScript by adding the following configuration.
parserOptions: {
parser: "@typescript-eslint/parser",
},
},
],
plugins: ["@typescript-eslint", "prettier"],
rules: {
// Note: you must disable the base rule as it can report incorrect errors
semi: "off",
quotes: "off",
"no-undef": "off",
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/no-this-alias": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-explicit-any": "off",
"turbo/no-undeclared-env-vars": "off",
"prettier/prettier": "error",
},
}

View file

@ -1,62 +1,62 @@
name: Create Release on Tag Push
on:
push:
tags:
- "v*"
push:
tags:
- "v*"
jobs:
build:
runs-on: ubuntu-latest
steps:
# Checkout
- name: Checkout
uses: actions/checkout@v3
build:
runs-on: ubuntu-latest
steps:
# Checkout
- name: Checkout
uses: actions/checkout@v3
# Install Node.js
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 18
registry-url: "https://registry.npmjs.org"
# Install Node.js
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 20
registry-url: "https://registry.npmjs.org"
# Install pnpm
- name: Install pnpm
uses: pnpm/action-setup@v2
id: pnpm-install
with:
version: 8
run_install: false
# Install pnpm
- name: Install pnpm
uses: pnpm/action-setup@v4
id: pnpm-install
with:
version: 8
run_install: false
# Get pnpm store directory
- name: Get pnpm store directory
id: pnpm-cache
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
# Get pnpm store directory
- name: Get pnpm store directory
id: pnpm-cache
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
# Setup pnpm cache
- name: Setup pnpm cache
uses: actions/cache@v3
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
# Setup pnpm cache
- name: Setup pnpm cache
uses: actions/cache@v3
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
# Install dependencies
- name: Install dependencies
run: pnpm install
# Install dependencies
- name: Install dependencies
run: pnpm install
# Build for production, 这一步会生成一个 package.zip
- name: Build for production
run: pnpm build
# Build for production, 这一步会生成一个 package.zip
- name: Build for production
run: pnpm build
- name: Release
uses: ncipollo/release-action@v1
with:
allowUpdates: true
artifactErrorsFailBuild: true
artifacts: 'package.zip'
token: ${{ secrets.GITHUB_TOKEN }}
prerelease: true
- name: Release
uses: ncipollo/release-action@v1
with:
allowUpdates: true
artifactErrorsFailBuild: true
artifacts: "package.zip"
token: ${{ secrets.GITHUB_TOKEN }}
prerelease: false

3
.gitignore vendored
View file

@ -1,8 +1,11 @@
.idea
.vscode
.DS_Store
pnpm-lock.yaml
package-lock.json
package.zip
node_modules
dev
dist
build
tmp

View file

@ -0,0 +1,112 @@
# Changelog
## v0.3.5 2024-04-30
* [Add `direction` to plugin method `Setting.addItem`](https://github.com/siyuan-note/siyuan/issues/11183)
## 0.3.4 2024-02-20
* [Add plugin event bus `click-flashcard-action`](https://github.com/siyuan-note/siyuan/issues/10318)
## 0.3.3 2024-01-24
* Update dock icon class
## 0.3.2 2024-01-09
* [Add plugin `protyleOptions`](https://github.com/siyuan-note/siyuan/issues/10090)
* [Add plugin api `uninstall`](https://github.com/siyuan-note/siyuan/issues/10063)
* [Add plugin method `updateCards`](https://github.com/siyuan-note/siyuan/issues/10065)
* [Add plugin function `lockScreen`](https://github.com/siyuan-note/siyuan/issues/10063)
* [Add plugin event bus `lock-screen`](https://github.com/siyuan-note/siyuan/pull/9967)
* [Add plugin event bus `open-menu-inbox`](https://github.com/siyuan-note/siyuan/pull/9967)
## 0.3.1 2023-12-06
* [Support `Dock Plugin` and `Command Palette` on mobile](https://github.com/siyuan-note/siyuan/issues/9926)
## 0.3.0 2023-12-05
* Upgrade Siyuan to 0.9.0
* Support more platforms
## 0.2.9 2023-11-28
* [Add plugin method `openMobileFileById`](https://github.com/siyuan-note/siyuan/issues/9738)
## 0.2.8 2023-11-15
* [`resize` cannot be triggered after dragging to unpin the dock](https://github.com/siyuan-note/siyuan/issues/9640)
## 0.2.7 2023-10-31
* [Export `Constants` to plugin](https://github.com/siyuan-note/siyuan/issues/9555)
* [Add plugin `app.appId`](https://github.com/siyuan-note/siyuan/issues/9538)
* [Add plugin event bus `switch-protyle`](https://github.com/siyuan-note/siyuan/issues/9454)
## 0.2.6 2023-10-24
* [Deprecated `loaded-protyle` use `loaded-protyle-static` instead](https://github.com/siyuan-note/siyuan/issues/9468)
## 0.2.5 2023-10-10
* [Add plugin event bus `open-menu-doctree`](https://github.com/siyuan-note/siyuan/issues/9351)
## 0.2.4 2023-09-19
* Supports use in windows
* [Add plugin function `transaction`](https://github.com/siyuan-note/siyuan/issues/9172)
## 0.2.3 2023-09-05
* [Add plugin function `transaction`](https://github.com/siyuan-note/siyuan/issues/9172)
* [Plugin API add openWindow and command.globalCallback](https://github.com/siyuan-note/siyuan/issues/9032)
## 0.2.2 2023-08-29
* [Add plugin event bus `destroy-protyle`](https://github.com/siyuan-note/siyuan/issues/9033)
* [Add plugin event bus `loaded-protyle-dynamic`](https://github.com/siyuan-note/siyuan/issues/9021)
## 0.2.1 2023-08-21
* [Plugin API add getOpenedTab method](https://github.com/siyuan-note/siyuan/issues/9002)
* [Plugin API custom.fn => custom.id in openTab](https://github.com/siyuan-note/siyuan/issues/8944)
## 0.2.0 2023-08-15
* [Add plugin event bus `open-siyuan-url-plugin` and `open-siyuan-url-block`](https://github.com/siyuan-note/siyuan/pull/8927)
## 0.1.12 2023-08-01
* Upgrade siyuan to 0.7.9
## 0.1.11
* [Add `input-search` event bus to plugins](https://github.com/siyuan-note/siyuan/issues/8725)
## 0.1.10
* [Add `bind this` example for eventBus in plugins](https://github.com/siyuan-note/siyuan/issues/8668)
* [Add `open-menu-breadcrumbmore` event bus to plugins](https://github.com/siyuan-note/siyuan/issues/8666)
## 0.1.9
* [Add `open-menu-xxx` event bus for plugins ](https://github.com/siyuan-note/siyuan/issues/8617)
## 0.1.8
* [Add protyleSlash to the plugin](https://github.com/siyuan-note/siyuan/issues/8599)
* [Add plugin API protyle](https://github.com/siyuan-note/siyuan/issues/8445)
## 0.1.7
* [Support build js and json](https://github.com/siyuan-note/plugin-sample/pull/8)
## 0.1.6
* add `fetchPost` example

182
README.md
View file

@ -3,47 +3,77 @@
[中文版](./README_zh_CN.md)
> Consistent with [siyuan/plugin-sample](https://github.com/siyuan-note/plugin-sample) [v0.3.5](https://github.com/siyuan-note/plugin-sample/tree/v0.3.5)
1. Using vite for packaging
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)
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. Create development symbolic links
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:
- It is recommended to create a symbolic link between your development directory and the plugins directory.
- If you have python environment in you device, run the command `python scripts/make_dev_link.py`, input the `<plugin_dir>` i.e. the absolute path to the plugins directory, e.g.
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
> node --no-warnings ./scripts/make_dev_link.js
```powershell
>>> python make_dev_link.py
Please input the directory of siyuan/data/plugins: H:\SiYuanDevSpace\data\plugins
Symlink created: H:\SiYuanDevSpace\data\plugins\plugin-sample
"targetDir" is empty, try to get SiYuan directory automatically....
Got 2 SiYuan workspaces
[0] H:\Media\SiYuan
[1] H:\临时文件夹\SiYuanDevSpace
Please select a workspace[0-1]: 0
Got target directory: H:\Media\SiYuan/data/plugins
Done! Created symlink H:\Media\SiYuan/data/plugins/plugin-sample-vite-svelte
```
- If you haven't intalled python, while you are an unix user, you can use `ln` command
```sh
ln -s ./dev "<plugin_dir>/<plugin_name>"
```
- Notice: make sure that the name of symbolic link is same as the name in your plugin.json
- If you haven't intalled python, while you are a windows user, you can download the `make_dev_link.exe` in release and run it in your workspace (as administrator)
- If you haven't intalled python, while you are a windows user, and you don't trust any other exe file, you can make the symlink by your self
1. Create a dev folder in your worksapce
2. Using use [mklink](https://learn.microsoft.com/windows-server/administration/windows-commands/mklink) command to create symlink
```cmd
mklink /d "<plugin_dir>\<plugin_name>" "<project_dir>\dev"
```
- You may need to run it as administration.
- As the generated softlink is the same as the plugin name, **do not put the project directory under plugins** (this is contrary to the webpack version)
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:
4. Install NodeJS and pnpm, then run pnpm i in the command line under your repo folder
5. Execute pnpm run dev for real-time compilation
6. 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`.
### 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
@ -53,9 +83,11 @@ complete the following tasks:
* Meta information about the plugin itself, such as plugin description and readme
* `description` and `readme` fields in plugin.json, and the corresponding README*.md file
* Text used in the plugin, such as button text and tooltips
* src/i18n/*.json language configuration files
* public/i18n/*.json language configuration files
* Use `this.i18.key` to get the text in the code
* Finally, declare the language supported by the plugin in the `i18n` field in plugin.json
* YAML Support
* This template specifically supports I18n based on YAML syntax, see `public/i18n/zh_CN.yaml`
* During compilation, the defined YAML files will be automatically translated into JSON files and placed in the dist or dev directory.
It is recommended that the plugin supports at least English and Simplified Chinese, so that more people can use it more
conveniently.
@ -67,7 +99,10 @@ conveniently.
"name": "plugin-sample-vite-svelte",
"author": "frostime",
"url": "https://github.com/siyuan-note/plugin-sample-vite-svelte",
"version": "1.0.0",
"version": "0.1.3",
"minAppVersion": "2.8.8",
"backends": ["windows", "linux", "darwin"],
"frontends": ["desktop"],
"displayName": {
"en_US": "Plugin sample with vite and svelte",
"zh_CN": "插件样例 vite + svelte 版"
@ -80,15 +115,17 @@ conveniently.
"en_US": "README_en_US.md",
"zh_CN": "README.md"
},
"i18n": [
"en_US",
"zh_CN"
],
"funding": {
"openCollective": "",
"patreon": "",
"github": "",
"custom": [
"https://afdian.net/a/frostime"
"https://ld246.com/sponsor"
]
}
},
"keywords": [
"sample", "示例"
]
}
```
@ -97,6 +134,22 @@ conveniently.
* `author`: Plugin author name
* `url`: Plugin repo URL
* `version`: Plugin version number, it is recommended to follow the [semver](https://semver.org/) specification
* `minAppVersion`: Minimum version number of SiYuan required to use this plugin
* `backends`: Backend environment required by the plugin, optional values are `windows`, `linux`, `darwin`, `docker`, `android`, `ios` and `all`
* `windows`: Windows desktop
* `linux`: Linux desktop
* `darwin`: macOS desktop
* `docker`: Docker
* `android`: Android APP
* `ios`: iOS APP
* `all`: All environments
* `frontends`: Frontend environment required by the plugin, optional values are `desktop`, `desktop-window`, `mobile`, `browser-desktop`, `browser-mobile` and `all`
* `desktop`: Desktop
* `desktop-window`: Desktop window converted from tab
* `mobile`: Mobile APP
* `browser-desktop`: Desktop browser
* `browser-mobile`: Mobile browser
* `all`: All environments
* `displayName`: Template display name, mainly used for display in the marketplace list, supports multiple languages
* `default`: Default language, must exist
* `zh_CN`, `en_US` and other languages: optional, it is recommended to provide at least Chinese and English
@ -106,25 +159,25 @@ conveniently.
* `readme`: readme file name, mainly used to display in the marketplace details page, supports multiple languages
* `default`: Default language, must exist
* `zh_CN`, `en_US` and other languages: optional, it is recommended to provide at least Chinese and English
* `i18n`: Plugin supported language list
* `funding`: Plugin sponsorship information
* `openCollective`: Open Collective name
* `patreon`: Patreon name
* `github`: GitHub login name
* `custom`: Custom sponsorship link list
* `keywords`: Search keyword list, used for marketplace search function
## Package
No matter which method is used to compile and package, we finally need to generate a package.zip, which contains at
least the following files:
* icon.png
* i18n/*
* icon.png (160*160)
* index.css
* index.js
* plugin.json
* preview.png
* preview.png (1024*768)
* README*.md
* index.css (optional)
* i18n/* (optional)
## List on the marketplace
@ -176,3 +229,50 @@ The github action is included in this sample, you can use it to publish your new
prerelease: true # change this to false
```
## How to remove svelte dependencies
> Pure vite without svelte: https://github.com/frostime/plugin-sample-vite
This plugin is packaged in vite and provides a dependency on the svelte framework. However, in practice some developers may not want to use svelte and only want to use the vite package.
In fact you can use this template without using svelte without any modifications at all. The compilation-related parts of the svelte compilation are loaded into the vite workflow as plugins, so even if you don't have svelte in your project, it won't matter much.
If you insist on removing all svelte dependencies so that they do not pollute your workspace, you can perform the following steps. 1.
1. delete the
```json
{
"@sveltejs/vite-plugin-svelte": "^2.0.3",
"@tsconfig/svelte": "^4.0.1",
"svelte": "^3.57.0"
}
```
2. delete the `svelte.config.js` file
3. delete the following line from the `vite.config.js` file
- Line 6: `import { svelte } from "@sveltejs/vite-plugin-svelte"`
- Line 20: `svelte(),`
4. delete line 37 of `tsconfig.json` from `"svelte"` 5.
5. re-run `pnpm i`
## Developer's Guide
Developers of SiYuan need to pay attention to the following specifications.
### 1. File Reading and Writing Specifications
If plugins or external extensions require direct reading or writing of files under the `data` directory, please use the kernel API to achieve this. **Do not call `fs` or other electron or nodejs APIs directly**, as it may result in data loss during synchronization and cause damage to cloud data.
Related APIs can be found at: `/api/file/*` (e.g., `/api/file/getFile`).
### 2. Daily Note Attribute Specifications
When creating a daily note in SiYuan, a custom-dailynote-yyyymmdd attribute will be automatically added to the document to distinguish it from regular documents.
> For more details, please refer to [Github Issue #9807](https://github.com/siyuan-note/siyuan/issues/9807).
Developers should pay attention to the following when developing the functionality to manually create Daily Notes:
* If `/api/filetree/createDailyNote` is called to create a daily note, the attribute will be automatically added to the document, and developers do not need to handle it separately
* If a document is created manually by developer's code (e.g., using the `createDocWithMd` API to create a daily note), please manually add this attribute to the document

View file

@ -1,43 +1,78 @@
[English](./README.md)
# 使用 vite + svelte 的思源笔记插件示例
[English](./README.md)
> 本例同 [siyuan/plugin-sample](https://github.com/siyuan-note/plugin-sample) [v0.3.5](https://github.com/siyuan-note/plugin-sample/tree/v0.3.5)
1. 使用 vite 打包
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)
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. 创建开发需要的符号链接
- 推荐使用符号链接来链接你的工作目录和插件目录
- 如果你的设备安装了 python 环境,运行 `python scripts/make_dev_link.py` 命令,然后输入插件目录的绝对路径,示例:
```powershell
>>> python make_dev_link.py
Please input the directory of siyuan/data/plugins: H:\临时文件夹\SiYuanDevSpace\data\plugins
Symlink created: H:\临时文件夹\SiYuanDevSpace\data\plugins\plugin-sample-vite-svelte
```
- 如果您没有安装 python 环境但是是 unix 用户,可以直接使用 ln 命令创建符号链接
```sh
ln -s ./dev "<plugin_dir>/<plugin_name>"
```
- 注意:要确保符号链接的名称 `plugin_name` 和 plugin.json 中的 name 字段保持一致
- 如果您没有安装 python 环境,而且是 windows 用户, 您可以直接下载我们提供的 `make_dev_link.exe` 放在根目录下, 以管理员方式运行
- 如果您没有安装 python 环境,而且是 windows 用户, 而且对外来的 exe 不信任,你也可以手动创建符号链接
1. 首先手动在工作目录下创建 dev 目录
2. windows 用户请在**管理员** cmd 环境下 使用 [mklink](https://learn.microsoft.com/windows-server/administration/windows-commands/mklink) 命令, 注意要使用绝对路径
```cmd
mklink /d "<plugin_dir>\<plugin_name>" "<project_dir>\dev"
```
- 可能需要使用**管理员身份**来运行上面的命令
- 注意: 由于生成的符号链接和 plugin name 相同,所以不要把工程目录放在 plugins 下(这一点和 plugin-sample 模板相反)
4. 安装 [NodeJS](https://nodejs.org/en/download) 和 [pnpm](https://pnpm.io/installation),然后在开发文件夹下执行 `pnpm i`
3. 安装 [NodeJS](https://nodejs.org/en/download) 和 [pnpm](https://pnpm.io/installation),然后在开发文件夹下执行 `pnpm i` 安装所需要的依赖
4. 运行 `pnpm run make-link` 命令创建符号链接 (Windows 下的开发者请参阅下方「Windows 下的 make-link」小节)
5. 执行 `pnpm run dev` 进行实时编译
6. 在思源中打开集市并在下载选项卡中启用插件
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
>>> pnpm run make-link
> plugin-sample-vite-svelte@0.0.3 make-link H:\SrcCode\开源项目\plugin-sample-vite-svelte
> node --no-warnings ./scripts/make_dev_link.js
"targetDir" is empty, try to get SiYuan directory automatically....
Got 2 SiYuan workspaces
[0] H:\Media\SiYuan
[1] H:\临时文件夹\SiYuanDevSpace
Please select a workspace[0-1]: 0
Got target directory: H:\Media\SiYuan/data/plugins
Done! Created symlink H:\Media\SiYuan/data/plugins/plugin-sample-vite-svelte
```
2. **手动配置目标目录**
- 打开 `./scripts/make_dev_link.js` 文件,更改 `targetDir` 为思源的插件目录 `<siyuan workspace>/data/plugins`
- 运行 `pnpm run make-link` 命令, 如果看到类似以下的消息,说明创建成功:
3. **设置环境变量创建符号链接**
- 设置系统的环境变量 `SIYUAN_PLUGIN_DIR``工作空间/data/plugins` 的路径
### 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 脚本执行权限
## 国际化
@ -45,11 +80,14 @@
国际化方面我们主要考虑的是支持多语言,具体需要完成以下工作:
* 插件自身的元信息,比如插件描述和自述文件
* plugin.json 中的 `description` 和 `readme` 字段,以及对应的 README*.md 文件
* plugin.json 中的 `description` 和 `readme` 字段,以及对应的 README*.md 文件
* 插件中使用的文本,比如按钮文字和提示信息
* src/i18n/*.json 语言配置文件
* 代码中使用 `this.i18.key` 获取文本
* public/i18n/*.json 语言配置文件
* 代码中使用 `this.i18.key` 获取文本
* 最后在 plugin.json 中的 `i18n` 字段中声明该插件支持的语言
* yaml 支持
* 本模板特别支持基于 Yaml 语法的 I18n`public/i18n/zh_CN.yaml`
* 编译时,会自动把定义的 yaml 文件翻译成 json 文件放到 dist 或 dev 目录下
建议插件至少支持英文和简体中文,这样可以方便更多人使用。
@ -60,7 +98,10 @@
"name": "plugin-sample-vite-svelte",
"author": "frostime",
"url": "https://github.com/siyuan-note/plugin-sample-vite-svelte",
"version": "1.0.0",
"version": "0.1.3",
"minAppVersion": "2.8.8",
"backends": ["windows", "linux", "darwin"],
"frontends": ["desktop"],
"displayName": {
"en_US": "Plugin sample with vite and svelte",
"zh_CN": "插件样例 vite + svelte 版"
@ -73,15 +114,17 @@
"en_US": "README_en_US.md",
"zh_CN": "README.md"
},
"i18n": [
"en_US",
"zh_CN"
],
"funding": {
"openCollective": "",
"patreon": "",
"github": "",
"custom": [
"https://afdian.net/a/frostime"
"https://ld246.com/sponsor"
]
}
},
"keywords": [
"sample", "示例"
]
}
```
@ -89,33 +132,49 @@
* `author`:插件作者名
* `url`:插件仓库地址
* `version`:插件版本号,建议遵循 [semver](https://semver.org/lang/zh-CN/) 规范
* `minAppVersion`:插件支持的最低思源笔记版本号
* `backends`:插件需要的后端环境,可选值为 `windows`, `linux`, `darwin`, `docker`, `android`, `ios` and `all`
* `windows`Windows 桌面端
* `linux`Linux 桌面端
* `darwin`macOS 桌面端
* `docker`Docker 端
* `android`Android 端
* `ios`iOS 端
* `all`:所有环境
* `frontends`:插件需要的前端环境,可选值为 `desktop`, `desktop-window`, `mobile`, `browser-desktop`, `browser-mobile` and `all`
* `desktop`:桌面端
* `desktop-window`:桌面端页签转换的独立窗口
* `mobile`:移动端
* `browser-desktop`:桌面端浏览器
* `browser-mobile`:移动端浏览器
* `all`:所有环境
* `displayName`:模板显示名称,主要用于模板集市列表中显示,支持多语言
* `default`:默认语言,必须存在
* `zh_CN``en_US` 等其他语言:可选,建议至少提供中文和英文
* `default`:默认语言,必须存在
* `zh_CN``en_US` 等其他语言:可选,建议至少提供中文和英文
* `description`:插件描述,主要用于插件集市列表中显示,支持多语言
* `default`:默认语言,必须存在
* `zh_CN``en_US` 等其他语言:可选,建议至少提供中文和英文
* `default`:默认语言,必须存在
* `zh_CN``en_US` 等其他语言:可选,建议至少提供中文和英文
* `readme`:自述文件名,主要用于插件集市详情页中显示,支持多语言
* `default`:默认语言,必须存在
* `zh_CN``en_US` 等其他语言:可选,建议至少提供中文和英文
* `i18n`:插件支持的语言列表
* `default`:默认语言,必须存在
* `zh_CN``en_US` 等其他语言:可选,建议至少提供中文和英文
* `funding`:插件赞助信息
* `openCollective`Open Collective 名称
* `patreon`Patreon 名称
* `github`GitHub 登录名
* `custom`:自定义赞助链接列表
* `openCollective`Open Collective 名称
* `patreon`Patreon 名称
* `github`GitHub 登录名
* `custom`:自定义赞助链接列表
* `keywords`:搜索关键字列表,用于集市搜索功能
## 打包
无论使用何种方式编译打包,我们最终需要生成一个 package.zip它至少包含如下文件
* icon.png
* i18n/*
* icon.png (160*160)
* index.css
* index.js
* plugin.json
* preview.png
* preview.png (1024*768)
* README*.md
* index.css (optional)
* i18n/* (optional)
## 上架集市
@ -164,4 +223,49 @@ PR 社区集市仓库。
prerelease: true # 把这个改为 false
```
## 如何去掉 svelte 依赖
> 无 Svelte 依赖版: https://github.com/frostime/plugin-sample-vite
本插件使用 vite 打包,并提供了 svelte 框架依赖。不过实际情况下可能有些开发者并不想要 svelte只希望使用 vite 打包。
实际上你可以完全不做任何修改,就可以在不使用 svelte 的前提下使用这个模板。与 svelte 编译的编译相关的部分是以插件的形式载入到 vite 的工作流中,所以即使你的项目里面没有 svelte也不会有太大的影响。
如果你执意希望删除掉所有 svelte 依赖以免它们污染你的工作空间,可以执行一下步骤:
1. 删掉 package.json 中的
```json
{
"@sveltejs/vite-plugin-svelte": "^2.0.3",
"@tsconfig/svelte": "^4.0.1",
"svelte": "^3.57.0"
}
```
2. 删掉 `svelte.config.js` 文件
3. 删掉 `vite.config.js` 文件中的
- 第六行: `import { svelte } from "@sveltejs/vite-plugin-svelte"`
- 第二十行: `svelte(),`
4. 删掉 `tsconfig.json` 中 37 行 `"svelte"`
5. 重新执行 `pnpm i`
## 开发者须知
思源开发者需注意以下规范。
### 1. 读写文件规范
插件或者外部扩展如果有直接读取或者写入 data 下文件的需求,请通过调用内核 API 来实现,**不要自行调用 `fs` 或者其他 electron、nodejs API**,否则可能会导致数据同步时分块丢失,造成云端数据损坏。
相关 API 见 `/api/file/*`(例如 `/api/file/getFile` 等)。
### 2. Daily Note 属性规范
思源在创建日记的时候会自动为文档添加 custom-dailynote-yyyymmdd 属性,以方便将日记文档同普通文档区分。
> 详情请见 [Github Issue #9807](https://github.com/siyuan-note/siyuan/issues/9807)。
开发者在开发手动创建 Daily Note 的功能时请注意:
* 如果调用了 `/api/filetree/createDailyNote` 创建日记,那么文档会自动添加这个属性,无需开发者特别处理
* 如果是开发者代码手动创建文档(例如使用 `createDocWithMd` API 创建日记),请手动为文档添加该属性

View file

@ -1,32 +1,37 @@
{
"name": "sy-plugin-template-vite",
"version": "1.0.0",
"name": "plugin-sample-vite-svelte",
"version": "0.3.6",
"type": "module",
"description": "",
"description": "This is a sample plugin based on vite and svelte for Siyuan (https://b3log.org/siyuan)",
"repository": "",
"homepage": "",
"author": "",
"license": "GPL-3.0",
"author": "frostime",
"license": "MIT",
"scripts": {
"dev": "vite build --watch",
"build": "vite build",
"start": "vite preview"
"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": "^2.0.3",
"@sveltejs/vite-plugin-svelte": "^3.1.0",
"@tsconfig/svelte": "^4.0.1",
"@types/node": "^20.2.0",
"@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.62.1",
"siyuan": "^0.7.1",
"svelte": "^3.57.0",
"sass": "^1.63.3",
"siyuan": "1.0.4",
"svelte": "^4.2.19",
"ts-node": "^10.9.1",
"typescript": "^5.0.4",
"vite": "^4.3.7",
"vite-plugin-static-copy": "^0.15.0",
"typescript": "^5.1.3",
"vite": "^5.2.9",
"vite-plugin-static-copy": "^1.0.2",
"vite-plugin-zip-pack": "^1.0.5"
}
}

View file

@ -2,7 +2,24 @@
"name": "plugin-sample-vite-svelte",
"author": "frostime",
"url": "https://github.com/siyuan-note/plugin-sample-vite-svelte",
"version": "1.0.0",
"version": "0.3.6",
"minAppVersion": "3.0.12",
"backends": [
"windows",
"linux",
"darwin",
"ios",
"android",
"harmony",
"docker"
],
"frontends": [
"desktop",
"mobile",
"browser-desktop",
"browser-mobile",
"desktop-window"
],
"displayName": {
"en_US": "Plugin sample with vite and svelte",
"zh_CN": "插件样例 vite + svelte 版"
@ -15,13 +32,14 @@
"en_US": "README.md",
"zh_CN": "README_zh_CN.md"
},
"i18n": [
"en_US",
"zh_CN"
],
"funding": {
"custom": [
"https://afdian.net/a/frostime"
""
]
}
},
"keywords": [
"plugin",
"sample",
"插件样例"
]
}

955
pnpm-lock.yaml generated
View file

@ -1,955 +0,0 @@
lockfileVersion: '6.0'
devDependencies:
'@sveltejs/vite-plugin-svelte':
specifier: ^2.0.3
version: 2.0.3(svelte@3.59.1)(vite@4.3.7)
'@tsconfig/svelte':
specifier: ^4.0.1
version: 4.0.1
'@types/node':
specifier: ^20.2.0
version: 20.2.0
fast-glob:
specifier: ^3.2.12
version: 3.2.12
glob:
specifier: ^7.2.3
version: 7.2.3
minimist:
specifier: ^1.2.8
version: 1.2.8
rollup-plugin-livereload:
specifier: ^2.0.5
version: 2.0.5
sass:
specifier: ^1.62.1
version: 1.62.1
siyuan:
specifier: ^0.7.1
version: 0.7.1
svelte:
specifier: ^3.57.0
version: 3.59.1
ts-node:
specifier: ^10.9.1
version: 10.9.1(@types/node@20.2.0)(typescript@5.0.4)
typescript:
specifier: ^5.0.4
version: 5.0.4
vite:
specifier: ^4.3.7
version: 4.3.7(@types/node@20.2.0)(sass@1.62.1)
vite-plugin-static-copy:
specifier: ^0.15.0
version: 0.15.0(vite@4.3.7)
vite-plugin-zip-pack:
specifier: ^1.0.5
version: 1.0.5(vite@4.3.7)
packages:
/@cspotcode/source-map-support@0.8.1:
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
dependencies:
'@jridgewell/trace-mapping': 0.3.9
dev: true
/@esbuild/android-arm64@0.17.19:
resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==}
engines: {node: '>=12'}
cpu: [arm64]
os: [android]
requiresBuild: true
dev: true
optional: true
/@esbuild/android-arm@0.17.19:
resolution: {integrity: sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==}
engines: {node: '>=12'}
cpu: [arm]
os: [android]
requiresBuild: true
dev: true
optional: true
/@esbuild/android-x64@0.17.19:
resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==}
engines: {node: '>=12'}
cpu: [x64]
os: [android]
requiresBuild: true
dev: true
optional: true
/@esbuild/darwin-arm64@0.17.19:
resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==}
engines: {node: '>=12'}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@esbuild/darwin-x64@0.17.19:
resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==}
engines: {node: '>=12'}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@esbuild/freebsd-arm64@0.17.19:
resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==}
engines: {node: '>=12'}
cpu: [arm64]
os: [freebsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/freebsd-x64@0.17.19:
resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [freebsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-arm64@0.17.19:
resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==}
engines: {node: '>=12'}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-arm@0.17.19:
resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-ia32@0.17.19:
resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==}
engines: {node: '>=12'}
cpu: [ia32]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-loong64@0.17.19:
resolution: {integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==}
engines: {node: '>=12'}
cpu: [loong64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-mips64el@0.17.19:
resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==}
engines: {node: '>=12'}
cpu: [mips64el]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-ppc64@0.17.19:
resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-riscv64@0.17.19:
resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==}
engines: {node: '>=12'}
cpu: [riscv64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-s390x@0.17.19:
resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==}
engines: {node: '>=12'}
cpu: [s390x]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-x64@0.17.19:
resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==}
engines: {node: '>=12'}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/netbsd-x64@0.17.19:
resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==}
engines: {node: '>=12'}
cpu: [x64]
os: [netbsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/openbsd-x64@0.17.19:
resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==}
engines: {node: '>=12'}
cpu: [x64]
os: [openbsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/sunos-x64@0.17.19:
resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==}
engines: {node: '>=12'}
cpu: [x64]
os: [sunos]
requiresBuild: true
dev: true
optional: true
/@esbuild/win32-arm64@0.17.19:
resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==}
engines: {node: '>=12'}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@esbuild/win32-ia32@0.17.19:
resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==}
engines: {node: '>=12'}
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@esbuild/win32-x64@0.17.19:
resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==}
engines: {node: '>=12'}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@jridgewell/resolve-uri@3.1.1:
resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==}
engines: {node: '>=6.0.0'}
dev: true
/@jridgewell/sourcemap-codec@1.4.15:
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
dev: true
/@jridgewell/trace-mapping@0.3.9:
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
dependencies:
'@jridgewell/resolve-uri': 3.1.1
'@jridgewell/sourcemap-codec': 1.4.15
dev: true
/@nodelib/fs.scandir@2.1.5:
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
dependencies:
'@nodelib/fs.stat': 2.0.5
run-parallel: 1.2.0
dev: true
/@nodelib/fs.stat@2.0.5:
resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
engines: {node: '>= 8'}
dev: true
/@nodelib/fs.walk@1.2.8:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
dependencies:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.15.0
dev: true
/@sveltejs/vite-plugin-svelte@2.0.3(svelte@3.59.1)(vite@4.3.7):
resolution: {integrity: sha512-o+cguBFdwIGtRbNkYOyqTM7KvRUffxh5bfK4oJsWKG2obu+v/cbpT03tJrGl58C7tRXo/aEC0/axN5FVHBj0nA==}
engines: {node: ^14.18.0 || >= 16}
peerDependencies:
svelte: ^3.54.0
vite: ^4.0.0
dependencies:
debug: 4.3.4
deepmerge: 4.3.1
kleur: 4.1.5
magic-string: 0.29.0
svelte: 3.59.1
svelte-hmr: 0.15.1(svelte@3.59.1)
vite: 4.3.7(@types/node@20.2.0)(sass@1.62.1)
vitefu: 0.2.4(vite@4.3.7)
transitivePeerDependencies:
- supports-color
dev: true
/@tsconfig/node10@1.0.9:
resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==}
dev: true
/@tsconfig/node12@1.0.11:
resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
dev: true
/@tsconfig/node14@1.0.3:
resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
dev: true
/@tsconfig/node16@1.0.4:
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
dev: true
/@tsconfig/svelte@4.0.1:
resolution: {integrity: sha512-B+XlGpmuAQzJqDoBATNCvEPqQg0HkO7S8pM14QDI5NsmtymzRexQ1N+nX2H6RTtFbuFgaZD4I8AAi8voGg0GLg==}
dev: true
/@types/node@20.2.0:
resolution: {integrity: sha512-3iD2jaCCziTx04uudpJKwe39QxXgSUnpxXSvRQjRvHPxFQfmfP4NXIm/NURVeNlTCc+ru4WqjYGTmpXrW9uMlw==}
dev: true
/acorn-walk@8.2.0:
resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
engines: {node: '>=0.4.0'}
dev: true
/acorn@8.8.2:
resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==}
engines: {node: '>=0.4.0'}
hasBin: true
dev: true
/anymatch@3.1.3:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'}
dependencies:
normalize-path: 3.0.0
picomatch: 2.3.1
dev: true
/arg@4.1.3:
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
dev: true
/balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: true
/binary-extensions@2.2.0:
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
engines: {node: '>=8'}
dev: true
/brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
dev: true
/braces@3.0.2:
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
engines: {node: '>=8'}
dependencies:
fill-range: 7.0.1
dev: true
/chokidar@3.5.3:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
engines: {node: '>= 8.10.0'}
dependencies:
anymatch: 3.1.3
braces: 3.0.2
glob-parent: 5.1.2
is-binary-path: 2.1.0
is-glob: 4.0.3
normalize-path: 3.0.0
readdirp: 3.6.0
optionalDependencies:
fsevents: 2.3.2
dev: true
/concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
dev: true
/core-util-is@1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
dev: true
/create-require@1.1.1:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
dev: true
/debug@4.3.4:
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.1.2
dev: true
/deepmerge@4.3.1:
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
engines: {node: '>=0.10.0'}
dev: true
/diff@4.0.2:
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
engines: {node: '>=0.3.1'}
dev: true
/esbuild@0.17.19:
resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==}
engines: {node: '>=12'}
hasBin: true
requiresBuild: true
optionalDependencies:
'@esbuild/android-arm': 0.17.19
'@esbuild/android-arm64': 0.17.19
'@esbuild/android-x64': 0.17.19
'@esbuild/darwin-arm64': 0.17.19
'@esbuild/darwin-x64': 0.17.19
'@esbuild/freebsd-arm64': 0.17.19
'@esbuild/freebsd-x64': 0.17.19
'@esbuild/linux-arm': 0.17.19
'@esbuild/linux-arm64': 0.17.19
'@esbuild/linux-ia32': 0.17.19
'@esbuild/linux-loong64': 0.17.19
'@esbuild/linux-mips64el': 0.17.19
'@esbuild/linux-ppc64': 0.17.19
'@esbuild/linux-riscv64': 0.17.19
'@esbuild/linux-s390x': 0.17.19
'@esbuild/linux-x64': 0.17.19
'@esbuild/netbsd-x64': 0.17.19
'@esbuild/openbsd-x64': 0.17.19
'@esbuild/sunos-x64': 0.17.19
'@esbuild/win32-arm64': 0.17.19
'@esbuild/win32-ia32': 0.17.19
'@esbuild/win32-x64': 0.17.19
dev: true
/fast-glob@3.2.12:
resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==}
engines: {node: '>=8.6.0'}
dependencies:
'@nodelib/fs.stat': 2.0.5
'@nodelib/fs.walk': 1.2.8
glob-parent: 5.1.2
merge2: 1.4.1
micromatch: 4.0.5
dev: true
/fastq@1.15.0:
resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==}
dependencies:
reusify: 1.0.4
dev: true
/fill-range@7.0.1:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'}
dependencies:
to-regex-range: 5.0.1
dev: true
/fs-extra@11.1.1:
resolution: {integrity: sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==}
engines: {node: '>=14.14'}
dependencies:
graceful-fs: 4.2.11
jsonfile: 6.1.0
universalify: 2.0.0
dev: true
/fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
dev: true
/fsevents@2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
requiresBuild: true
dev: true
optional: true
/glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
dependencies:
is-glob: 4.0.3
dev: true
/glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 3.1.2
once: 1.4.0
path-is-absolute: 1.0.1
dev: true
/graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
dev: true
/immediate@3.0.6:
resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
dev: true
/immutable@4.3.0:
resolution: {integrity: sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==}
dev: true
/inflight@1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
dependencies:
once: 1.4.0
wrappy: 1.0.2
dev: true
/inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
dev: true
/is-binary-path@2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
dependencies:
binary-extensions: 2.2.0
dev: true
/is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
dev: true
/is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
dependencies:
is-extglob: 2.1.1
dev: true
/is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
dev: true
/isarray@1.0.0:
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
dev: true
/jsonfile@6.1.0:
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
dependencies:
universalify: 2.0.0
optionalDependencies:
graceful-fs: 4.2.11
dev: true
/jszip@3.10.1:
resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==}
dependencies:
lie: 3.3.0
pako: 1.0.11
readable-stream: 2.3.8
setimmediate: 1.0.5
dev: true
/kleur@4.1.5:
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
engines: {node: '>=6'}
dev: true
/lie@3.3.0:
resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==}
dependencies:
immediate: 3.0.6
dev: true
/livereload-js@3.4.1:
resolution: {integrity: sha512-5MP0uUeVCec89ZbNOT/i97Mc+q3SxXmiUGhRFOTmhrGPn//uWVQdCvcLJDy64MSBR5MidFdOR7B9viumoavy6g==}
dev: true
/livereload@0.9.3:
resolution: {integrity: sha512-q7Z71n3i4X0R9xthAryBdNGVGAO2R5X+/xXpmKeuPMrteg+W2U8VusTKV3YiJbXZwKsOlFlHe+go6uSNjfxrZw==}
engines: {node: '>=8.0.0'}
hasBin: true
dependencies:
chokidar: 3.5.3
livereload-js: 3.4.1
opts: 2.0.2
ws: 7.5.9
transitivePeerDependencies:
- bufferutil
- utf-8-validate
dev: true
/magic-string@0.29.0:
resolution: {integrity: sha512-WcfidHrDjMY+eLjlU+8OvwREqHwpgCeKVBUpQ3OhYYuvfaYCUgcbuBzappNzZvg/v8onU3oQj+BYpkOJe9Iw4Q==}
engines: {node: '>=12'}
dependencies:
'@jridgewell/sourcemap-codec': 1.4.15
dev: true
/make-error@1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
dev: true
/merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
dev: true
/micromatch@4.0.5:
resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
engines: {node: '>=8.6'}
dependencies:
braces: 3.0.2
picomatch: 2.3.1
dev: true
/minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
dependencies:
brace-expansion: 1.1.11
dev: true
/minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
dev: true
/ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
dev: true
/nanoid@3.3.6:
resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
dev: true
/normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
dev: true
/once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies:
wrappy: 1.0.2
dev: true
/opts@2.0.2:
resolution: {integrity: sha512-k41FwbcLnlgnFh69f4qdUfvDQ+5vaSDnVPFI/y5XuhKRq97EnVVneO9F1ESVCdiVu4fCS2L8usX3mU331hB7pg==}
dev: true
/pako@1.0.11:
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
dev: true
/path-is-absolute@1.0.1:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
engines: {node: '>=0.10.0'}
dev: true
/picocolors@1.0.0:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
dev: true
/picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
dev: true
/postcss@8.4.23:
resolution: {integrity: sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
nanoid: 3.3.6
picocolors: 1.0.0
source-map-js: 1.0.2
dev: true
/process-nextick-args@2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
dev: true
/queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
dev: true
/readable-stream@2.3.8:
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
dependencies:
core-util-is: 1.0.3
inherits: 2.0.4
isarray: 1.0.0
process-nextick-args: 2.0.1
safe-buffer: 5.1.2
string_decoder: 1.1.1
util-deprecate: 1.0.2
dev: true
/readdirp@3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
dependencies:
picomatch: 2.3.1
dev: true
/reusify@1.0.4:
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
dev: true
/rollup-plugin-livereload@2.0.5:
resolution: {integrity: sha512-vqQZ/UQowTW7VoiKEM5ouNW90wE5/GZLfdWuR0ELxyKOJUIaj+uismPZZaICU4DnWPVjnpCDDxEqwU7pcKY/PA==}
engines: {node: '>=8.3'}
dependencies:
livereload: 0.9.3
transitivePeerDependencies:
- bufferutil
- utf-8-validate
dev: true
/rollup@3.21.7:
resolution: {integrity: sha512-KXPaEuR8FfUoK2uHwNjxTmJ18ApyvD6zJpYv9FOJSqLStmt6xOY84l1IjK2dSolQmoXknrhEFRaPRgOPdqCT5w==}
engines: {node: '>=14.18.0', npm: '>=8.0.0'}
hasBin: true
optionalDependencies:
fsevents: 2.3.2
dev: true
/run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
dependencies:
queue-microtask: 1.2.3
dev: true
/safe-buffer@5.1.2:
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
dev: true
/sass@1.62.1:
resolution: {integrity: sha512-NHpxIzN29MXvWiuswfc1W3I0N8SXBd8UR26WntmDlRYf0bSADnwnOjsyMZ3lMezSlArD33Vs3YFhp7dWvL770A==}
engines: {node: '>=14.0.0'}
hasBin: true
dependencies:
chokidar: 3.5.3
immutable: 4.3.0
source-map-js: 1.0.2
dev: true
/setimmediate@1.0.5:
resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==}
dev: true
/siyuan@0.7.1:
resolution: {integrity: sha512-Q7OZqpJ8h+axLDkn6afoAdKD6mHPAona/jsoUpf8UgFqHuOscNS6ub8RudhTCPKppDnQzBy5o35xhlecxnrbjQ==}
dev: true
/source-map-js@1.0.2:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'}
dev: true
/string_decoder@1.1.1:
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
dependencies:
safe-buffer: 5.1.2
dev: true
/svelte-hmr@0.15.1(svelte@3.59.1):
resolution: {integrity: sha512-BiKB4RZ8YSwRKCNVdNxK/GfY+r4Kjgp9jCLEy0DuqAKfmQtpL38cQK3afdpjw4sqSs4PLi3jIPJIFp259NkZtA==}
engines: {node: ^12.20 || ^14.13.1 || >= 16}
peerDependencies:
svelte: '>=3.19.0'
dependencies:
svelte: 3.59.1
dev: true
/svelte@3.59.1:
resolution: {integrity: sha512-pKj8fEBmqf6mq3/NfrB9SLtcJcUvjYSWyePlfCqN9gujLB25RitWK8PvFzlwim6hD/We35KbPlRteuA6rnPGcQ==}
engines: {node: '>= 8'}
dev: true
/to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
dependencies:
is-number: 7.0.0
dev: true
/ts-node@10.9.1(@types/node@20.2.0)(typescript@5.0.4):
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
hasBin: true
peerDependencies:
'@swc/core': '>=1.2.50'
'@swc/wasm': '>=1.2.50'
'@types/node': '*'
typescript: '>=2.7'
peerDependenciesMeta:
'@swc/core':
optional: true
'@swc/wasm':
optional: true
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.9
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.4
'@types/node': 20.2.0
acorn: 8.8.2
acorn-walk: 8.2.0
arg: 4.1.3
create-require: 1.1.1
diff: 4.0.2
make-error: 1.3.6
typescript: 5.0.4
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
dev: true
/typescript@5.0.4:
resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==}
engines: {node: '>=12.20'}
hasBin: true
dev: true
/universalify@2.0.0:
resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==}
engines: {node: '>= 10.0.0'}
dev: true
/util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: true
/v8-compile-cache-lib@3.0.1:
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
dev: true
/vite-plugin-static-copy@0.15.0(vite@4.3.7):
resolution: {integrity: sha512-Ww+/Ug9guV45oIfIi/lA2z8v3K+lLHV9zCJqTVO4FTdqrJoZBj68VgGBSH1fi0N4q/EHW32RsL3ympi4Wlsq5w==}
engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies:
vite: ^3.0.0 || ^4.0.0
dependencies:
chokidar: 3.5.3
fast-glob: 3.2.12
fs-extra: 11.1.1
picocolors: 1.0.0
vite: 4.3.7(@types/node@20.2.0)(sass@1.62.1)
dev: true
/vite-plugin-zip-pack@1.0.5(vite@4.3.7):
resolution: {integrity: sha512-AY6F3GJL///Dc3d7fQhalJvWb08e0oIATVqHLwDAiK2/S1NCxbAFXfT4m2V+LtJRjHrRkHa0zbZvAI6/lb6vTQ==}
peerDependencies:
vite: '>=2.x'
dependencies:
jszip: 3.10.1
vite: 4.3.7(@types/node@20.2.0)(sass@1.62.1)
dev: true
/vite@4.3.7(@types/node@20.2.0)(sass@1.62.1):
resolution: {integrity: sha512-MTIFpbIm9v7Hh5b0wSBgkcWzSBz7SAa6K/cBTwS4kUiQJfQLFlZZRJRQgqunCVzhTPCk674tW+0Qaqh3Q00dBg==}
engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true
peerDependencies:
'@types/node': '>= 14'
less: '*'
sass: '*'
stylus: '*'
sugarss: '*'
terser: ^5.4.0
peerDependenciesMeta:
'@types/node':
optional: true
less:
optional: true
sass:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
dependencies:
'@types/node': 20.2.0
esbuild: 0.17.19
postcss: 8.4.23
rollup: 3.21.7
sass: 1.62.1
optionalDependencies:
fsevents: 2.3.2
dev: true
/vitefu@0.2.4(vite@4.3.7):
resolution: {integrity: sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==}
peerDependencies:
vite: ^3.0.0 || ^4.0.0
peerDependenciesMeta:
vite:
optional: true
dependencies:
vite: 4.3.7(@types/node@20.2.0)(sass@1.62.1)
dev: true
/wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
dev: true
/ws@7.5.9:
resolution: {integrity: sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==}
engines: {node: '>=8.3.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: ^5.0.2
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
dev: true
/yn@3.1.1:
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
engines: {node: '>=6'}
dev: true

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/en_US.json Normal file
View file

@ -0,0 +1,20 @@
{
"addTopBarIcon": "Add a top bar icon by plugin",
"cancel": "Cancel",
"save": "Save",
"byeMenu": "Bye, Menu!",
"helloPlugin": "Hello, Plugin!",
"byePlugin": "Bye, Plugin!",
"showDialog": "Show dialog",
"removedData": "Data deleted",
"confirmRemove": "Confirm to delete the data in ${name}?",
"insertEmoji": "Insert Emoji",
"removeSpace": "Remove Space",
"getTab": "Print out all opened custom tabs in the debugger",
"name": "SiYuan",
"hello": {
"makesure": "Before using this template, please read the <a href=\"https://github.com/siyuan-note/plugin-sample\">offical sample</a>, make sure that you've known about the pipeline for plugin developing."
},
"hintTitle":"About",
"hintDesc":"<a href='https://github.com/siyuan-note/plugin-sample-vite-svelte'>plugin-sample-vite-svelte</a><br>@frostime<br>@88250<br>@zxkmm"
}

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

24
scripts/elevate.ps1 Normal file
View 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

66
scripts/make_dev_link.js Normal file
View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2024 by frostime. All Rights Reserved.
* @Author : frostime
* @Date : 2023-07-15 15:31:31
* @FilePath : /scripts/make_dev_link.js
* @LastEditTime : 2024-09-06 18:13:53
* @Description :
*/
// make_dev_link.js
import fs from 'fs';
import { log, error, getSiYuanDir, chooseTarget, getThisPluginName, makeSymbolicLink } from './utils.js';
let targetDir = '';
/**
* 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....');
let res = await getSiYuanDir();
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) {
targetDir = env;
log(`\tGot target directory from environment variable "SIYUAN_PLUGIN_DIR": ${targetDir}`);
} else {
error('\tCan not get SiYuan directory from environment variable "SIYUAN_PLUGIN_DIR", failed!');
process.exit(1);
}
} else {
targetDir = await chooseTarget(res);
}
log(`>>> Successfully got target directory: ${targetDir}`);
}
if (!fs.existsSync(targetDir)) {
error(`Failed! Plugin directory not exists: "${targetDir}"`);
error('Please set the plugin directory in scripts/make_dev_link.js');
process.exit(1);
}
/**
* 2. The dev directory, which contains the compiled plugin code
*/
const devDir = `${process.cwd()}/dev`;
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);
}
const targetPath = `${targetDir}/${name}`;
/**
* 4. Make symbolic link
*/
makeSymbolicLink(devDir, targetPath);

View file

@ -1,49 +0,0 @@
import os
import sys
import json
def run():
# check path, must be in root folder
if not os.path.exists('plugin.json'):
os.chdir('..')
if not os.path.exists('plugin.json'):
print('plugin.json not found, exit')
return
# 1. Read plugin_dir
plugin_dir = ''
if len(sys.argv) > 1:
plugin_dir = sys.argv[1]
while not os.path.exists(plugin_dir):
plugin_dir = input('Please input the directory of siyuan/data/plugins: ')
if plugin_dir == 'exit' or plugin_dir == 'quit' or plugin_dir == 'q':
return
if not os.path.exists(plugin_dir):
print('plugin_dir not found!')
continue
# 2. Read name in plugin.json
with open('plugin.json', 'r', encoding='utf-8') as f:
content = json.load(f)
name = content.get('name')
# ...error if name not found
if not name or name == '':
print('"name" in plugin.json not found, exit')
return
dev_dir = os.path.abspath('dev')
if not os.path.exists(dev_dir):
os.mkdir(dev_dir)
# 3. Create symlink
if not os.path.exists(os.path.join(plugin_dir, name)):
link = os.path.join(plugin_dir, name)
os.symlink(dev_dir, link)
print('Symlink created:', link)
else:
print('Folder already exists, exit')
return
run()
os.system('pause')

57
scripts/make_install.js Normal file
View file

@ -0,0 +1,57 @@
/*
* Copyright (c) 2024 by frostime. All Rights Reserved.
* @Author : frostime
* @Date : 2024-03-28 20:03:59
* @FilePath : /scripts/make_install.js
* @LastEditTime : 2024-09-06 18:08:19
* @Description :
*/
// make_install.js
import fs from 'fs';
import { log, error, getSiYuanDir, chooseTarget, copyDirectory, getThisPluginName } from './utils.js';
let targetDir = '';
/**
* 1. Get the parent directory to install the plugin
*/
log('>>> Try to visit constant "targetDir" in make_install.js...');
if (targetDir === '') {
log('>>> Constant "targetDir" is empty, try to get SiYuan directory automatically....');
let res = await getSiYuanDir();
if (res === null || res === undefined || res.length === 0) {
error('>>> Can not get SiYuan directory automatically');
process.exit(1);
} else {
targetDir = await chooseTarget(res);
}
log(`>>> Successfully got target directory: ${targetDir}`);
}
if (!fs.existsSync(targetDir)) {
error(`Failed! Plugin directory not exists: "${targetDir}"`);
error('Please set the plugin directory in scripts/make_install.js');
process.exit(1);
}
/**
* 2. The dist directory, which contains the compiled plugin code
*/
const distDir = `${process.cwd()}/dist`;
if (!fs.existsSync(distDir)) {
fs.mkdirSync(distDir);
}
/**
* 3. The target directory to install the plugin
*/
const name = getThisPluginName();
if (name === null) {
process.exit(1);
}
const targetPath = `${targetDir}/${name}`;
/**
* 4. Copy the compiled plugin code to the target directory
*/
copyDirectory(distDir, targetPath);

View file

@ -1,4 +0,0 @@
.venv/Script/activate
pyinstaller --noconfirm --onefile --console "./make_dev_link.py"
rm ./make_dev_link.exe
mv ./dist/make_dev_link.exe .

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);
}
})();

182
scripts/utils.js Normal file
View 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}`);
}
}

View file

@ -6,14 +6,10 @@
* API [API_zh_CN.md](https://github.com/siyuan-note/siyuan/blob/master/API_zh_CN.md)
*/
import {
Block, Notebook, NotebookConf, NotebookId, DocumentId, BlockId,
doOperation, PreviousID, ParentID, BlockType, BlockSubType
} from "sy-dtype";
import { fetchSyncPost, IWebSocketData } from "siyuan";
import { fetchPost, fetchSyncPost, IWebSocketData } from "siyuan";
async function request(url: string, data: any) {
export async function request(url: string, data: any) {
let response: IWebSocketData = await fetchSyncPost(url, data);
let res = response.code === 0 ? response.data : null;
return res;
@ -22,11 +18,8 @@ async function request(url: string, data: any) {
// **************************************** Noteboook ****************************************
export type ReslsNotebooks = {
notebooks: Notebook[];
}
export async function lsNotebooks(): Promise<ReslsNotebooks> {
export async function lsNotebooks(): Promise<IReslsNotebooks> {
let url = '/api/notebook/lsNotebooks';
return request(url, '');
}
@ -61,14 +54,8 @@ export async function removeNotebook(notebook: NotebookId) {
return request(url, { notebook: notebook });
}
export type ResGetNotebookConf = {
box: string;
conf: NotebookConf;
name: string;
}
export async function getNotebookConf(notebook: NotebookId): Promise<ResGetNotebookConf> {
export async function getNotebookConf(notebook: NotebookId): Promise<IResGetNotebookConf> {
let data = { notebook: notebook };
let url = '/api/notebook/getNotebookConf';
return request(url, data);
@ -82,7 +69,7 @@ export async function setNotebookConf(notebook: NotebookId, conf: NotebookConf):
}
// **************************************** Document ****************************************
// **************************************** File Tree ****************************************
export async function createDocWithMd(notebook: NotebookId, path: string, markdown: string): Promise<DocumentId> {
let data = {
notebook: notebook,
@ -144,13 +131,19 @@ export async function getHPathByID(id: BlockId): Promise<string> {
return request(url, data);
}
// **************************************** Asset Files ****************************************
export type ResUpload = {
errFiles: string[];
succMap: { [key: string]: string };
export async function getIDsByHPath(notebook: NotebookId, path: string): Promise<BlockId[]> {
let data = {
notebook: notebook,
path: path
};
let url = '/api/filetree/getIDsByHPath';
return request(url, data);
}
export async function upload(assetsDirPath: string, files: any[]): Promise<ResUpload> {
// **************************************** Asset Files ****************************************
export async function upload(assetsDirPath: string, files: any[]): Promise<IResUpload> {
let form = new FormData();
form.append('assetsDirPath', assetsDirPath);
for (let file of files) {
@ -161,45 +154,57 @@ export async function upload(assetsDirPath: string, files: any[]): Promise<ResUp
}
// **************************************** Block ****************************************
export type ResdoOperations = {
doOperations: doOperation[];
undoOperations: doOperation[] | null;
}
type DataType = "markdown" | "dom";
export async function insertBlock(dataType: DataType, data: string, previousID: BlockId): Promise<ResdoOperations> {
let data1 = {
export async function insertBlock(
dataType: DataType, data: string,
nextID?: BlockId, previousID?: BlockId, parentID?: BlockId
): Promise<IResdoOperations[]> {
let payload = {
dataType: dataType,
data: data,
previousID: previousID
nextID: nextID,
previousID: previousID,
parentID: parentID
}
let url = '/api/block/insertBlock';
return request(url, data1);
return request(url, payload);
}
export async function appendBlock(dataType: DataType, data: string, parentID: BlockId | DocumentId): Promise<ResdoOperations> {
let data1 = {
export async function prependBlock(dataType: DataType, data: string, parentID: BlockId | DocumentId): Promise<IResdoOperations[]> {
let payload = {
dataType: dataType,
data: data,
parentID: parentID
}
let url = '/api/block/prependBlock';
return request(url, payload);
}
export async function appendBlock(dataType: DataType, data: string, parentID: BlockId | DocumentId): Promise<IResdoOperations[]> {
let payload = {
dataType: dataType,
data: data,
parentID: parentID
}
let url = '/api/block/appendBlock';
return request(url, data1);
return request(url, payload);
}
export async function updateBlock(dataType: DataType, data: string, id: BlockId): Promise<ResdoOperations> {
let data1 = {
export async function updateBlock(dataType: DataType, data: string, id: BlockId): Promise<IResdoOperations[]> {
let payload = {
dataType: dataType,
data: data,
id: id
}
let url = '/api/block/updateBlock';
return request(url, data1);
return request(url, payload);
}
export async function deleteBlock(id: BlockId): Promise<ResdoOperations> {
export async function deleteBlock(id: BlockId): Promise<IResdoOperations[]> {
let data = {
id: id
}
@ -208,7 +213,7 @@ export async function deleteBlock(id: BlockId): Promise<ResdoOperations> {
}
export async function moveBlock(id: BlockId, previousID: PreviousID | null = null, parentID: ParentID | null = null): Promise<ResdoOperations> {
export async function moveBlock(id: BlockId, previousID?: PreviousID, parentID?: ParentID): Promise<IResdoOperations[]> {
let data = {
id: id,
previousID: previousID,
@ -219,12 +224,25 @@ export async function moveBlock(id: BlockId, previousID: PreviousID | null = nul
}
export type ResGetBlockKramdown = {
id: BlockId;
kramdown: string;
export async function foldBlock(id: BlockId) {
let data = {
id: id
}
let url = '/api/block/foldBlock';
return request(url, data);
}
export async function getBlockKramdown(id: BlockId): Promise<ResGetBlockKramdown> {
export async function unfoldBlock(id: BlockId) {
let data = {
id: id
}
let url = '/api/block/unfoldBlock';
return request(url, data);
}
export async function getBlockKramdown(id: BlockId): Promise<IResGetBlockKramdown> {
let data = {
id: id
}
@ -232,12 +250,8 @@ export async function getBlockKramdown(id: BlockId): Promise<ResGetBlockKramdown
return request(url, data);
}
export type ChildBlock = {
id: BlockId;
type: BlockType;
subtype?: BlockSubType;
}
export async function getChildBlocks(id: BlockId): Promise<ChildBlock[]> {
export async function getChildBlocks(id: BlockId): Promise<IResGetChildBlock[]> {
let data = {
id: id
}
@ -245,6 +259,16 @@ export async function getChildBlocks(id: BlockId): Promise<ChildBlock[]> {
return request(url, data);
}
export async function transferBlockRef(fromID: BlockId, toID: BlockId, refIDs: BlockId[]) {
let data = {
fromID: fromID,
toID: toID,
refIDs: refIDs
}
let url = '/api/block/transferBlockRef';
return request(url, data);
}
// **************************************** Attributes ****************************************
export async function setBlockAttrs(id: BlockId, attrs: { [key: string]: string }) {
let data = {
@ -282,11 +306,7 @@ export async function getBlockByID(blockId: string): Promise<Block> {
// **************************************** Template ****************************************
export type ResGetTemplates = {
content: string;
path: string;
}
export async function render(id: DocumentId, path: string): Promise<ResGetTemplates> {
export async function render(id: DocumentId, path: string): Promise<IResGetTemplates> {
let data = {
id: id,
path: path
@ -308,9 +328,35 @@ export async function getFile(path: string): Promise<any> {
path: path
}
let url = '/api/file/getFile';
return request(url, data);
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);
@ -332,11 +378,8 @@ export async function removeFile(path: string) {
}
export type ResReadDir = {
isDir: boolean;
name: string;
}
export async function readDir(path: string): Promise<ResReadDir> {
export async function readDir(path: string): Promise<IResReadDir> {
let data = {
path: path
}
@ -345,11 +388,9 @@ export async function readDir(path: string): Promise<ResReadDir> {
}
export type ResExportMdContent = {
hPath: string;
content: string;
}
export async function exportMdContent(id: DocumentId): Promise<ResExportMdContent> {
// **************************************** Export ****************************************
export async function exportMdContent(id: DocumentId): Promise<IResExportMdContent> {
let data = {
id: id
}
@ -357,6 +398,17 @@ export async function exportMdContent(id: DocumentId): Promise<ResExportMdConten
return request(url, data);
}
export async function exportResources(paths: string[], name: string): Promise<IResExportResources> {
let data = {
paths: paths,
name: name
}
let url = '/api/export/exportResources';
return request(url, data);
}
// **************************************** Convert ****************************************
export type PandocArgs = string;
export async function pandoc(args: PandocArgs[]) {
let data = {
@ -366,13 +418,52 @@ export async function pandoc(args: PandocArgs[]) {
return request(url, data);
}
// **************************************** Notification ****************************************
// /api/notification/pushMsg
// {
// "msg": "test",
// "timeout": 7000
// }
export async function pushMsg(msg: string, timeout: number = 7000) {
let payload = {
msg: msg,
timeout: timeout
};
let url = "/api/notification/pushMsg";
return request(url, payload);
}
export async function pushErrMsg(msg: string, timeout: number = 7000) {
let payload = {
msg: msg,
timeout: timeout
};
let url = "/api/notification/pushErrMsg";
return request(url, payload);
}
// **************************************** Network ****************************************
export async function forwardProxy(
url: string, method: string = 'GET', payload: any = {},
headers: any[] = [], timeout: number = 7000, contentType: string = "text/html"
): Promise<IResForwardProxy> {
let data = {
url: url,
method: method,
timeout: timeout,
contentType: contentType,
headers: headers,
payload: payload
}
let url1 = '/api/network/forwardProxy';
return request(url1, data);
}
// **************************************** System ****************************************
export type ResBootProgress = {
progress: number;
details: string;
}
export async function bootProgress(): Promise<ResBootProgress> {
export async function bootProgress(): Promise<IResBootProgress> {
return request('/api/system/bootProgress', {});
}

View file

@ -1,70 +1,63 @@
<!--
* Copyright (c) 2023 frostime. All rights reserved.
* https://github.com/frostime/sy-plugin-template-vite
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 { onMount } from "svelte";
import { version } from "./api";
export let name: string;
import { onDestroy, onMount } from "svelte";
import { version, sql as query } from "@/api";
import { showMessage, fetchPost, Protyle } from "siyuan";
let time;
let ver;
export let app;
let time: string = "";
let ver: string = "";
let divProtyle: HTMLDivElement;
let protyle: any;
let blockID: string = '';
onMount(async () => {
time = new Date();
ver = await version();
fetchPost("/api/system/currentTime", {}, (response) => {
time = new Date(response.data).toString();
});
protyle = await initProtyle();
});
$: time_str = new Date(time).toLocaleTimeString();
setInterval(async () => {
time = new Date();
}, 1000);
onDestroy(() => {
showMessage("Hello panel closed");
protyle.destroy();
});
async function initProtyle() {
let sql = "SELECT * FROM blocks ORDER BY RANDOM () LIMIT 1;";
let blocks: Block[] = await query(sql);
blockID = blocks[0].id;
return new Protyle(app, divProtyle, {
blockId: blockID
});
}
</script>
<div id="hello">
<div class="row">
<div class="col left">
<h2>Hello {name} v{ver}</h2>
</div>
<div class="col right">
{time_str}
</div>
<div class="b3-dialog__content">
<div>appId:</div>
<div class="fn__hr"></div>
<div class="plugin-sample__time">${app?.appId}</div>
<div class="fn__hr"></div>
<div class="fn__hr"></div>
<div>API demo:</div>
<div class="fn__hr" />
<div class="plugin-sample__time">
System current time: <span id="time">{time}</span>
</div>
<br />
<p>
使用这个模板之前,请阅读<a
href="https://github.com/siyuan-note/plugin-sample">官方教程</a
>, 确保自己已经理解了插件的基本开发流程。
</p>
<p>
Before using this template, please read the <a
href="https://github.com/siyuan-note/plugin-sample">offical sample</a
>, make sure that you've known about the pipeline for plugin developing.
</p>
<div class="fn__hr" />
<div class="fn__hr" />
<div>Protyle demo: id = {blockID}</div>
<div class="fn__hr" />
<div id="protyle" style="height: 360px;" bind:this={divProtyle}/>
</div>
<style lang="scss">
#hello {
margin: 1rem;
text-align: left;
}
.row {
display: flex;
flex-direction: row;
.col {
flex: 1;
}
.left {
text-align: left;
}
.right {
text-align: right;
}
}
</style>

View file

@ -1,3 +0,0 @@
{
"name": "SiYuan"
}

View file

@ -1,3 +0,0 @@
{
"name": "思源"
}

View file

@ -1,3 +0,0 @@
#helloPanel {
border: 1px rgb(189, 119, 119) dashed;
}

View file

@ -1,38 +1,943 @@
/**
* Copyright (c) 2023 frostime. All rights reserved.
* https://github.com/frostime/sy-plugin-template-vite
*/
import { Plugin, showMessage, Dialog } from "siyuan"
import Hello from "./hello.svelte"
import "./index.scss"
import {
Plugin,
showMessage,
confirm,
Dialog,
Menu,
openTab,
adaptHotkey,
getFrontend,
getBackend,
IModel,
Protyle,
openWindow,
IOperation,
Constants,
openMobileFileById,
lockScreen,
ICard,
ICardData
} from "siyuan";
import "@/index.scss";
export default class SamplePlugin extends Plugin {
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";
const DOCK_TYPE = "dock_tab";
export default class PluginSample extends Plugin {
customTab: () => IModel;
private isMobile: boolean;
private blockIconEventBindThis = this.blockIconEvent.bind(this);
private settingUtils: SettingUtils;
async onload() {
console.log("onload");
showMessage("Hello World");
this.addTopBar(
{
icon: "iconEmoji",
"title": "Hello SiYuan",
"callback": () => {
let dialog = new Dialog({
title: "Hello World",
content: `<div id="helloPanel"></div>`,
});
new Hello({
target: dialog.element.querySelector("#helloPanel"),
props: {
name: this.i18n.name,
}
});
this.data[STORAGE_NAME] = { readonlyText: "Readonly" };
console.log("loading plugin-sample", this.i18n);
const frontEnd = getFrontend();
this.isMobile = frontEnd === "mobile" || frontEnd === "browser-mobile";
// 图标的制作参见帮助文档
this.addIcons(`<symbol id="iconFace" viewBox="0 0 32 32">
<path d="M13.667 17.333c0 0.92-0.747 1.667-1.667 1.667s-1.667-0.747-1.667-1.667 0.747-1.667 1.667-1.667 1.667 0.747 1.667 1.667zM20 15.667c-0.92 0-1.667 0.747-1.667 1.667s0.747 1.667 1.667 1.667 1.667-0.747 1.667-1.667-0.747-1.667-1.667-1.667zM29.333 16c0 7.36-5.973 13.333-13.333 13.333s-13.333-5.973-13.333-13.333 5.973-13.333 13.333-13.333 13.333 5.973 13.333 13.333zM14.213 5.493c1.867 3.093 5.253 5.173 9.12 5.173 0.613 0 1.213-0.067 1.787-0.16-1.867-3.093-5.253-5.173-9.12-5.173-0.613 0-1.213 0.067-1.787 0.16zM5.893 12.627c2.28-1.293 4.040-3.4 4.88-5.92-2.28 1.293-4.040 3.4-4.88 5.92zM26.667 16c0-1.040-0.16-2.040-0.44-2.987-0.933 0.2-1.893 0.32-2.893 0.32-4.173 0-7.893-1.92-10.347-4.92-1.4 3.413-4.187 6.093-7.653 7.4 0.013 0.053 0 0.12 0 0.187 0 5.88 4.787 10.667 10.667 10.667s10.667-4.787 10.667-10.667z"></path>
</symbol>
<symbol id="iconSaving" viewBox="0 0 32 32">
<path d="M20 13.333c0-0.733 0.6-1.333 1.333-1.333s1.333 0.6 1.333 1.333c0 0.733-0.6 1.333-1.333 1.333s-1.333-0.6-1.333-1.333zM10.667 12h6.667v-2.667h-6.667v2.667zM29.333 10v9.293l-3.76 1.253-2.24 7.453h-7.333v-2.667h-2.667v2.667h-7.333c0 0-3.333-11.28-3.333-15.333s3.28-7.333 7.333-7.333h6.667c1.213-1.613 3.147-2.667 5.333-2.667 1.107 0 2 0.893 2 2 0 0.28-0.053 0.533-0.16 0.773-0.187 0.453-0.347 0.973-0.427 1.533l3.027 3.027h2.893zM26.667 12.667h-1.333l-4.667-4.667c0-0.867 0.12-1.72 0.347-2.547-1.293 0.333-2.347 1.293-2.787 2.547h-8.227c-2.573 0-4.667 2.093-4.667 4.667 0 2.507 1.627 8.867 2.68 12.667h2.653v-2.667h8v2.667h2.68l2.067-6.867 3.253-1.093v-4.707z"></path>
</symbol>`);
const topBarElement = this.addTopBar({
icon: "iconFace",
title: this.i18n.addTopBarIcon,
position: "right",
callback: () => {
if (this.isMobile) {
this.addMenu();
} else {
let rect = topBarElement.getBoundingClientRect();
// 如果被隐藏,则使用更多按钮
if (rect.width === 0) {
rect = document.querySelector("#barMore").getBoundingClientRect();
}
if (rect.width === 0) {
rect = document.querySelector("#barPlugins").getBoundingClientRect();
}
this.addMenu(rect);
}
}
)
});
const statusIconTemp = document.createElement("template");
statusIconTemp.innerHTML = `<div class="toolbar__item ariaLabel" aria-label="Remove plugin-sample Data">
<svg>
<use xlink:href="#iconTrashcan"></use>
</svg>
</div>`;
statusIconTemp.content.firstElementChild.addEventListener("click", () => {
confirm("⚠️", this.i18n.confirmRemove.replace("${name}", this.name), () => {
this.removeData(STORAGE_NAME).then(() => {
this.data[STORAGE_NAME] = { readonlyText: "Readonly" };
showMessage(`[${this.name}]: ${this.i18n.removedData}`);
});
});
});
this.addStatusBar({
element: statusIconTemp.content.firstElementChild as HTMLElement,
});
this.addCommand({
langKey: "showDialog",
hotkey: "⇧⌘O",
callback: () => {
this.showDialog();
},
fileTreeCallback: (file: any) => {
console.log(file, "fileTreeCallback");
},
editorCallback: (protyle: any) => {
console.log(protyle, "editorCallback");
},
dockCallback: (element: HTMLElement) => {
console.log(element, "dockCallback");
},
});
this.addCommand({
langKey: "getTab",
hotkey: "⇧⌘M",
globalCallback: () => {
console.log(this.getOpenedTab());
},
});
this.addDock({
config: {
position: "LeftBottom",
size: { width: 200, height: 0 },
icon: "iconSaving",
title: "Custom Dock",
hotkey: "⌥⌘W",
},
data: {
text: "This is my custom dock"
},
type: DOCK_TYPE,
resize() {
console.log(DOCK_TYPE + " resize");
},
update() {
console.log(DOCK_TYPE + " update");
},
init: (dock) => {
if (this.isMobile) {
dock.element.innerHTML = `<div class="toolbar toolbar--border toolbar--dark">
<svg class="toolbar__icon"><use xlink:href="#iconEmoji"></use></svg>
<div class="toolbar__text">Custom Dock</div>
</div>
<div class="fn__flex-1 plugin-sample__custom-dock">
${dock.data.text}
</div>
</div>`;
} else {
dock.element.innerHTML = `<div class="fn__flex-1 fn__flex-column">
<div class="block__icons">
<div class="block__logo">
<svg class="block__logoicon"><use xlink:href="#iconEmoji"></use></svg>
Custom Dock
</div>
<span class="fn__flex-1 fn__space"></span>
<span data-type="min" class="block__icon b3-tooltips b3-tooltips__sw" aria-label="Min ${adaptHotkey("W")}"><svg class="block__logoicon"><use xlink:href="#iconMin"></use></svg></span>
</div>
<div class="fn__flex-1 plugin-sample__custom-dock">
${dock.data.text}
</div>
</div>`;
}
},
destroy() {
console.log("destroy dock:", DOCK_TYPE);
}
});
this.settingUtils = new SettingUtils({
plugin: this, name: STORAGE_NAME
});
this.settingUtils.addItem({
key: "Input",
value: "",
type: "textinput",
title: "Readonly text",
description: "Input description",
action: {
// Called when focus is lost and content changes
callback: () => {
// Return data and save it in real time
let value = this.settingUtils.takeAndSave("Input");
console.log(value);
}
}
});
this.settingUtils.addItem({
key: "InputArea",
value: "",
type: "textarea",
title: "Readonly text",
description: "Input description",
// Called when focus is lost and content changes
action: {
callback: () => {
// Read data in real time
let value = this.settingUtils.take("InputArea");
console.log(value);
}
}
});
this.settingUtils.addItem({
key: "Check",
value: true,
type: "checkbox",
title: "Checkbox text",
description: "Check description",
action: {
callback: () => {
// Return data and save it in real time
let value = !this.settingUtils.get("Check");
this.settingUtils.set("Check", value);
console.log(value);
}
}
});
this.settingUtils.addItem({
key: "Select",
value: 1,
type: "select",
title: "Select",
description: "Select description",
options: {
1: "Option 1",
2: "Option 2"
},
action: {
callback: () => {
// Read data in real time
let value = this.settingUtils.take("Select");
console.log(value);
}
}
});
this.settingUtils.addItem({
key: "Slider",
value: 50,
type: "slider",
title: "Slider text",
description: "Slider description",
direction: "column",
slider: {
min: 0,
max: 100,
step: 1,
},
action:{
callback: () => {
// Read data in real time
let value = this.settingUtils.take("Slider");
console.log(value);
}
}
});
this.settingUtils.addItem({
key: "Btn",
value: "",
type: "button",
title: "Button",
description: "Button description",
button: {
label: "Button",
callback: () => {
showMessage("Button clicked");
}
}
});
this.settingUtils.addItem({
key: "Custom Element",
value: "",
type: "custom",
direction: "row",
title: "Custom Element",
description: "Custom Element description",
//Any custom element must offer the following methods
createElement: (currentVal: any) => {
let div = document.createElement('div');
div.style.border = "1px solid var(--b3-theme-primary)";
div.contentEditable = "true";
div.textContent = currentVal;
return div;
},
getEleVal: (ele: HTMLElement) => {
return ele.textContent;
},
setEleVal: (ele: HTMLElement, val: any) => {
ele.textContent = val;
}
});
this.settingUtils.addItem({
key: "Hint",
value: "",
type: "hint",
title: this.i18n.hintTitle,
description: this.i18n.hintDesc,
});
try {
this.settingUtils.load();
} catch (error) {
console.error("Error loading settings storage, probably empty config json:", error);
}
this.protyleSlash = [{
filter: ["insert emoji 😊", "插入表情 😊", "crbqwx"],
html: `<div class="b3-list-item__first"><span class="b3-list-item__text">${this.i18n.insertEmoji}</span><span class="b3-list-item__meta">😊</span></div>`,
id: "insertEmoji",
callback(protyle: Protyle) {
protyle.insert("😊");
}
}];
this.protyleOptions = {
toolbar: ["block-ref",
"a",
"|",
"text",
"strong",
"em",
"u",
"s",
"mark",
"sup",
"sub",
"clear",
"|",
"code",
"kbd",
"tag",
"inline-math",
"inline-memo",
"|",
{
name: "insert-smail-emoji",
icon: "iconEmoji",
hotkey: "⇧⌘I",
tipPosition: "n",
tip: this.i18n.insertEmoji,
click(protyle: Protyle) {
protyle.insert("😊");
}
}],
};
console.log(this.i18n.helloPlugin);
}
onLayoutReady() {
// this.loadData(STORAGE_NAME);
this.settingUtils.load();
console.log(`frontend: ${getFrontend()}; backend: ${getBackend()}`);
console.log(
"Official settings value calling example:\n" +
this.settingUtils.get("InputArea") + "\n" +
this.settingUtils.get("Slider") + "\n" +
this.settingUtils.get("Select") + "\n"
);
let tabDiv = document.createElement("div");
new HelloExample({
target: tabDiv,
props: {
app: this.app,
}
});
this.customTab = this.addTab({
type: TAB_TYPE,
init() {
this.element.appendChild(tabDiv);
console.log(this.element);
},
beforeDestroy() {
console.log("before destroy tab:", TAB_TYPE);
},
destroy() {
console.log("destroy tab:", TAB_TYPE);
}
});
}
async onunload() {
showMessage("Goodbye World");
console.log(this.i18n.byePlugin);
showMessage("Goodbye SiYuan Plugin");
console.log("onunload");
}
uninstall() {
console.log("uninstall");
}
async updateCards(options: ICardData) {
options.cards.sort((a: ICard, b: ICard) => {
if (a.blockID < b.blockID) {
return -1;
}
if (a.blockID > b.blockID) {
return 1;
}
return 0;
});
return options;
}
/**
* A custom setting pannel provided by svelte
*/
openDIYSetting(): void {
let dialog = new Dialog({
title: "SettingPannel",
content: `<div id="SettingPanel" style="height: 100%;"></div>`,
width: "800px",
destroyCallback: (options) => {
console.log("destroyCallback", options);
//You'd better destroy the component when the dialog is closed
pannel.$destroy();
}
});
let pannel = new SettingExample({
target: dialog.element.querySelector("#SettingPanel"),
});
}
private eventBusPaste(event: any) {
// 如果需异步处理请调用 preventDefault 否则会进行默认处理
event.preventDefault();
// 如果使用了 preventDefault必须调用 resolve否则程序会卡死
event.detail.resolve({
textPlain: event.detail.textPlain.trim(),
});
}
private eventBusLog({ detail }: any) {
console.log(detail);
}
private blockIconEvent({ detail }: any) {
detail.menu.addItem({
iconHTML: "",
label: this.i18n.removeSpace,
click: () => {
const doOperations: IOperation[] = [];
detail.blockElements.forEach((item: HTMLElement) => {
const editElement = item.querySelector('[contenteditable="true"]');
if (editElement) {
editElement.textContent = editElement.textContent.replace(/ /g, "");
doOperations.push({
id: item.dataset.nodeId,
data: item.outerHTML,
action: "update"
});
}
});
detail.protyle.getInstance().transaction(doOperations);
}
});
}
private showDialog() {
// 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}`,
width: this.isMobile ? "92vw" : "720px",
constructor: (container: HTMLElement) => {
return new HelloExample({
target: container,
props: {
app: this.app,
}
});
}
});
}
private addMenu(rect?: DOMRect) {
const menu = new Menu("topBarSample", () => {
console.log(this.i18n.byeMenu);
});
menu.addItem({
icon: "iconInfo",
label: "Dialog(open help first)",
accelerator: this.commands[0].customHotkey,
click: () => {
this.showDialog();
}
});
if (!this.isMobile) {
menu.addItem({
icon: "iconFace",
label: "Open Custom Tab",
click: () => {
const tab = openTab({
app: this.app,
custom: {
icon: "iconFace",
title: "Custom Tab",
data: {
text: "This is my custom tab",
},
id: this.name + TAB_TYPE
},
});
console.log(tab);
}
});
menu.addItem({
icon: "iconImage",
label: "Open Asset Tab(open help first)",
click: () => {
const tab = openTab({
app: this.app,
asset: {
path: "assets/paragraph-20210512165953-ag1nib4.svg"
}
});
console.log(tab);
}
});
menu.addItem({
icon: "iconFile",
label: "Open Doc Tab(open help first)",
click: async () => {
const tab = await openTab({
app: this.app,
doc: {
id: "20200812220555-lj3enxa",
}
});
console.log(tab);
}
});
menu.addItem({
icon: "iconSearch",
label: "Open Search Tab",
click: () => {
const tab = openTab({
app: this.app,
search: {
k: "SiYuan"
}
});
console.log(tab);
}
});
menu.addItem({
icon: "iconRiffCard",
label: "Open Card Tab",
click: () => {
const tab = openTab({
app: this.app,
card: {
type: "all"
}
});
console.log(tab);
}
});
menu.addItem({
icon: "iconLayout",
label: "Open Float Layer(open help first)",
click: () => {
this.addFloatLayer({
ids: ["20210428212840-8rqwn5o", "20201225220955-l154bn4"],
defIds: ["20230415111858-vgohvf3", "20200813131152-0wk5akh"],
x: window.innerWidth - 768 - 120,
y: 32
});
}
});
menu.addItem({
icon: "iconOpenWindow",
label: "Open Doc Window(open help first)",
click: () => {
openWindow({
doc: {id: "20200812220555-lj3enxa"}
});
}
});
} else {
menu.addItem({
icon: "iconFile",
label: "Open Doc(open help first)",
click: () => {
openMobileFileById(this.app, "20200812220555-lj3enxa");
}
});
}
menu.addItem({
icon: "iconLock",
label: "Lockscreen",
click: () => {
lockScreen(this.app);
}
});
menu.addItem({
icon: "iconScrollHoriz",
label: "Event Bus",
type: "submenu",
submenu: [{
icon: "iconSelect",
label: "On ws-main",
click: () => {
this.eventBus.on("ws-main", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off ws-main",
click: () => {
this.eventBus.off("ws-main", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On click-blockicon",
click: () => {
this.eventBus.on("click-blockicon", this.blockIconEventBindThis);
}
}, {
icon: "iconClose",
label: "Off click-blockicon",
click: () => {
this.eventBus.off("click-blockicon", this.blockIconEventBindThis);
}
}, {
icon: "iconSelect",
label: "On click-pdf",
click: () => {
this.eventBus.on("click-pdf", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off click-pdf",
click: () => {
this.eventBus.off("click-pdf", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On click-editorcontent",
click: () => {
this.eventBus.on("click-editorcontent", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off click-editorcontent",
click: () => {
this.eventBus.off("click-editorcontent", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On click-editortitleicon",
click: () => {
this.eventBus.on("click-editortitleicon", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off click-editortitleicon",
click: () => {
this.eventBus.off("click-editortitleicon", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On click-flashcard-action",
click: () => {
this.eventBus.on("click-flashcard-action", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off click-flashcard-action",
click: () => {
this.eventBus.off("click-flashcard-action", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On open-noneditableblock",
click: () => {
this.eventBus.on("open-noneditableblock", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off open-noneditableblock",
click: () => {
this.eventBus.off("open-noneditableblock", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On loaded-protyle-static",
click: () => {
this.eventBus.on("loaded-protyle-static", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off loaded-protyle-static",
click: () => {
this.eventBus.off("loaded-protyle-static", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On loaded-protyle-dynamic",
click: () => {
this.eventBus.on("loaded-protyle-dynamic", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off loaded-protyle-dynamic",
click: () => {
this.eventBus.off("loaded-protyle-dynamic", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On switch-protyle",
click: () => {
this.eventBus.on("switch-protyle", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off switch-protyle",
click: () => {
this.eventBus.off("switch-protyle", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On destroy-protyle",
click: () => {
this.eventBus.on("destroy-protyle", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off destroy-protyle",
click: () => {
this.eventBus.off("destroy-protyle", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On open-menu-doctree",
click: () => {
this.eventBus.on("open-menu-doctree", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off open-menu-doctree",
click: () => {
this.eventBus.off("open-menu-doctree", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On open-menu-blockref",
click: () => {
this.eventBus.on("open-menu-blockref", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off open-menu-blockref",
click: () => {
this.eventBus.off("open-menu-blockref", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On open-menu-fileannotationref",
click: () => {
this.eventBus.on("open-menu-fileannotationref", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off open-menu-fileannotationref",
click: () => {
this.eventBus.off("open-menu-fileannotationref", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On open-menu-tag",
click: () => {
this.eventBus.on("open-menu-tag", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off open-menu-tag",
click: () => {
this.eventBus.off("open-menu-tag", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On open-menu-link",
click: () => {
this.eventBus.on("open-menu-link", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off open-menu-link",
click: () => {
this.eventBus.off("open-menu-link", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On open-menu-image",
click: () => {
this.eventBus.on("open-menu-image", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off open-menu-image",
click: () => {
this.eventBus.off("open-menu-image", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On open-menu-av",
click: () => {
this.eventBus.on("open-menu-av", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off open-menu-av",
click: () => {
this.eventBus.off("open-menu-av", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On open-menu-content",
click: () => {
this.eventBus.on("open-menu-content", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off open-menu-content",
click: () => {
this.eventBus.off("open-menu-content", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On open-menu-breadcrumbmore",
click: () => {
this.eventBus.on("open-menu-breadcrumbmore", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off open-menu-breadcrumbmore",
click: () => {
this.eventBus.off("open-menu-breadcrumbmore", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On open-menu-inbox",
click: () => {
this.eventBus.on("open-menu-inbox", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off open-menu-inbox",
click: () => {
this.eventBus.off("open-menu-inbox", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On input-search",
click: () => {
this.eventBus.on("input-search", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off input-search",
click: () => {
this.eventBus.off("input-search", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On paste",
click: () => {
this.eventBus.on("paste", this.eventBusPaste);
}
}, {
icon: "iconClose",
label: "Off paste",
click: () => {
this.eventBus.off("paste", this.eventBusPaste);
}
}, {
icon: "iconSelect",
label: "On open-siyuan-url-plugin",
click: () => {
this.eventBus.on("open-siyuan-url-plugin", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off open-siyuan-url-plugin",
click: () => {
this.eventBus.off("open-siyuan-url-plugin", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On open-siyuan-url-block",
click: () => {
this.eventBus.on("open-siyuan-url-block", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off open-siyuan-url-block",
click: () => {
this.eventBus.off("open-siyuan-url-block", this.eventBusLog);
}
}]
});
menu.addSeparator();
menu.addItem({
icon: "iconSettings",
label: "Official Setting Dialog",
click: () => {
this.openSetting();
}
});
menu.addItem({
icon: "iconSettings",
label: "A custom setting dialog (by svelte)",
click: () => {
this.openDIYSetting();
}
});
menu.addItem({
icon: "iconSparkles",
label: this.data[STORAGE_NAME].readonlyText || "Readonly",
type: "readonly",
});
if (this.isMobile) {
menu.fullscreen();
} else {
menu.open({
x: rect.right,
y: rect.bottom,
isLeft: true,
});
}
}
}

View 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}

View 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>

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

View file

@ -0,0 +1,3 @@
<div class="item__readme b3-typography">
<slot/>
</div>

View file

@ -0,0 +1,51 @@
<!--
Copyright (c) 2023 by frostime All Rights Reserved.
Author : frostime
Date : 2023-07-01 19:23:50
FilePath : /src/libs/components/setting-panel.svelte
LastEditTime : 2024-08-09 21:41:07
Description :
-->
<script lang="ts">
import { createEventDispatcher } from "svelte";
import Form from './Form';
export let group: string;
export let settingItems: ISettingItem[];
export let display: boolean = true;
const dispatch = createEventDispatcher();
function onClick( {detail}) {
dispatch("click", { key: detail.key });
}
function onChanged( {detail}) {
dispatch("changed", {group: group, ...detail});
}
$: fn__none = display ? "" : "fn__none";
</script>
<div class="config__tab-container {fn__none}" data-name={group}>
<slot />
{#each settingItems as item (item.key)}
<Form.Wrap
title={item.title}
description={item.description}
direction={item?.direction}
>
<Form.Input
type={item.type}
key={item.key}
bind:value={item.value}
placeholder={item?.placeholder}
options={item?.options}
slider={item?.slider}
button={item?.button}
on:click={onClick}
on:changed={onChanged}
/>
</Form.Wrap>
{/each}
</div>

99
src/libs/const.ts Normal file
View 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
View file

@ -0,0 +1,164 @@
/*
* Copyright (c) 2024 by frostime. All Rights Reserved.
* @Author : frostime
* @Date : 2024-03-23 21:37:33
* @FilePath : /src/libs/dialog.ts
* @LastEditTime : 2024-10-16 14:31:04
* @Description : Kits about dialogs
*/
import { Dialog } from "siyuan";
import { type SvelteComponent } from "svelte";
export const inputDialog = (args: {
title: string, placeholder?: string, defaultText?: string,
confirm?: (text: string) => void, cancel?: () => void,
width?: string, height?: string
}) => {
const dialog = new Dialog({
title: args.title,
content: `<div class="b3-dialog__content">
<div class="ft__breakword"><textarea class="b3-text-field fn__block" style="height: 100%;" placeholder=${args?.placeholder ?? ''}>${args?.defaultText ?? ''}</textarea></div>
</div>
<div class="b3-dialog__action">
<button class="b3-button b3-button--cancel">${window.siyuan.languages.cancel}</button><div class="fn__space"></div>
<button class="b3-button b3-button--text" id="confirmDialogConfirmBtn">${window.siyuan.languages.confirm}</button>
</div>`,
width: args.width ?? "520px",
height: args.height
});
const target: HTMLTextAreaElement = dialog.element.querySelector(".b3-dialog__content>div.ft__breakword>textarea");
const btnsElement = dialog.element.querySelectorAll(".b3-button");
btnsElement[0].addEventListener("click", () => {
if (args?.cancel) {
args.cancel();
}
dialog.destroy();
});
btnsElement[1].addEventListener("click", () => {
if (args?.confirm) {
args.confirm(target.value);
}
dialog.destroy();
});
};
export const inputDialogSync = async (args: {
title: string, placeholder?: string, defaultText?: string,
width?: string, height?: string
}) => {
return new Promise<string>((resolve) => {
let newargs = {
...args, confirm: (text) => {
resolve(text);
}, cancel: () => {
resolve(null);
}
};
inputDialog(newargs);
});
}
interface IConfirmDialogArgs {
title: string;
content: string | HTMLElement;
confirm?: (ele?: HTMLElement) => void;
cancel?: (ele?: HTMLElement) => void;
width?: string;
height?: string;
}
export const confirmDialog = (args: IConfirmDialogArgs) => {
const { title, content, confirm, cancel, width, height } = args;
const dialog = new Dialog({
title,
content: `<div class="b3-dialog__content">
<div class="ft__breakword">
</div>
</div>
<div class="b3-dialog__action">
<button class="b3-button b3-button--cancel">${window.siyuan.languages.cancel}</button><div class="fn__space"></div>
<button class="b3-button b3-button--text" id="confirmDialogConfirmBtn">${window.siyuan.languages.confirm}</button>
</div>`,
width: width,
height: height
});
const target: HTMLElement = dialog.element.querySelector(".b3-dialog__content>div.ft__breakword");
if (typeof content === "string") {
target.innerHTML = content;
} else {
target.appendChild(content);
}
const btnsElement = dialog.element.querySelectorAll(".b3-button");
btnsElement[0].addEventListener("click", () => {
if (cancel) {
cancel(target);
}
dialog.destroy();
});
btnsElement[1].addEventListener("click", () => {
if (confirm) {
confirm(target);
}
dialog.destroy();
});
};
export const confirmDialogSync = async (args: IConfirmDialogArgs) => {
return new Promise<HTMLElement>((resolve) => {
let newargs = {
...args, confirm: (ele: HTMLElement) => {
resolve(ele);
}, cancel: (ele: HTMLElement) => {
resolve(ele);
}
};
confirmDialog(newargs);
});
};
export const simpleDialog = (args: {
title: string, ele: HTMLElement | DocumentFragment,
width?: string, height?: string,
callback?: () => void;
}) => {
const dialog = new Dialog({
title: args.title,
content: `<div class="dialog-content" style="display: flex; height: 100%;"/>`,
width: args.width,
height: args.height,
destroyCallback: args.callback
});
dialog.element.querySelector(".dialog-content").appendChild(args.ele);
return {
dialog,
close: dialog.destroy.bind(dialog)
};
}
export const svelteDialog = (args: {
title: string, constructor: (container: HTMLElement) => SvelteComponent,
width?: string, height?: string,
callback?: () => void;
}) => {
let container = document.createElement('div')
container.style.display = 'contents';
let component = args.constructor(container);
const { dialog, close } = simpleDialog({
...args, ele: container, callback: () => {
component.$destroy();
if (args.callback) args.callback();
}
});
return {
component,
dialog,
close
}
}

43
src/libs/index.d.ts vendored Normal file
View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2024 by frostime. All Rights Reserved.
* @Author : frostime
* @Date : 2024-04-19 18:30:12
* @FilePath : /src/libs/index.d.ts
* @LastEditTime : 2024-04-30 16:39:54
* @Description :
*/
type TSettingItemType = "checkbox" | "select" | "textinput" | "textarea" | "number" | "slider" | "button" | "hint" | "custom";
interface ISettingItemCore {
type: TSettingItemType;
key: string;
value: any;
placeholder?: string;
slider?: {
min: number;
max: number;
step: number;
};
options?: { [key: string | number]: string };
button?: {
label: string;
callback: () => void;
}
}
interface ISettingItem extends ISettingItemCore {
title: string;
description: string;
direction?: "row" | "column";
}
//Interface for setting-utils
interface ISettingUtilsItem extends ISettingItem {
action?: {
callback: () => void;
}
createElement?: (currentVal: any) => HTMLElement;
getEleVal?: (ele: HTMLElement) => any;
setEleVal?: (ele: HTMLElement, val: any) => void;
}

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

397
src/libs/setting-utils.ts Normal file
View file

@ -0,0 +1,397 @@
/*
* Copyright (c) 2023 by frostime. All Rights Reserved.
* @Author : frostime
* @Date : 2023-12-17 18:28:19
* @FilePath : /src/libs/setting-utils.ts
* @LastEditTime : 2024-05-01 17:44:16
* @Description :
*/
import { Plugin, Setting } from 'siyuan';
/**
* The default function to get the value of the element
* @param type
* @returns
*/
const createDefaultGetter = (type: TSettingItemType) => {
let getter: (ele: HTMLElement) => any;
switch (type) {
case 'checkbox':
getter = (ele: HTMLInputElement) => {
return ele.checked;
};
break;
case 'select':
case 'slider':
case 'textinput':
case 'textarea':
getter = (ele: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement) => {
return ele.value;
};
break;
case 'number':
getter = (ele: HTMLInputElement) => {
return parseInt(ele.value);
}
break;
default:
getter = () => null;
break;
}
return getter;
}
/**
* The default function to set the value of the element
* @param type
* @returns
*/
const createDefaultSetter = (type: TSettingItemType) => {
let setter: (ele: HTMLElement, value: any) => void;
switch (type) {
case 'checkbox':
setter = (ele: HTMLInputElement, value: any) => {
ele.checked = value;
};
break;
case 'select':
case 'slider':
case 'textinput':
case 'textarea':
case 'number':
setter = (ele: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement, value: any) => {
ele.value = value;
};
break;
default:
setter = () => {};
break;
}
return setter;
}
export class SettingUtils {
plugin: Plugin;
name: string;
file: string;
settings: Map<string, ISettingUtilsItem> = new Map();
elements: Map<string, HTMLElement> = new Map();
constructor(args: {
plugin: Plugin,
name?: string,
callback?: (data: any) => void,
width?: string,
height?: string
}) {
this.name = args.name ?? 'settings';
this.plugin = args.plugin;
this.file = this.name.endsWith('.json') ? this.name : `${this.name}.json`;
this.plugin.setting = new Setting({
width: args.width,
height: args.height,
confirmCallback: () => {
for (let key of this.settings.keys()) {
this.updateValueFromElement(key);
}
let data = this.dump();
if (args.callback !== undefined) {
args.callback(data);
}
this.plugin.data[this.name] = data;
this.save(data);
},
destroyCallback: () => {
//Restore the original value
for (let key of this.settings.keys()) {
this.updateElementFromValue(key);
}
}
});
}
async load() {
let data = await this.plugin.loadData(this.file);
console.debug('Load config:', data);
if (data) {
for (let [key, item] of this.settings) {
item.value = data?.[key] ?? item.value;
}
}
this.plugin.data[this.name] = this.dump();
return data;
}
async save(data?: any) {
data = data ?? this.dump();
await this.plugin.saveData(this.file, this.dump());
console.debug('Save config:', data);
return data;
}
/**
* read the data after saving
* @param key key name
* @returns setting item value
*/
get(key: string) {
return this.settings.get(key)?.value;
}
/**
* Set data to this.settings,
* but do not save it to the configuration file
* @param key key name
* @param value value
*/
set(key: string, value: any) {
let item = this.settings.get(key);
if (item) {
item.value = value;
this.updateElementFromValue(key);
}
}
/**
* Set and save setting item value
* If you want to set and save immediately you can use this method
* @param key key name
* @param value value
*/
async setAndSave(key: string, value: any) {
let item = this.settings.get(key);
if (item) {
item.value = value;
this.updateElementFromValue(key);
await this.save();
}
}
/**
* Read in the value of element instead of setting obj in real time
* @param key key name
* @param apply whether to apply the value to the setting object
* if true, the value will be applied to the setting object
* @returns value in html
*/
take(key: string, apply: boolean = false) {
let item = this.settings.get(key);
let element = this.elements.get(key) as any;
if (!element) {
return
}
if (apply) {
this.updateValueFromElement(key);
}
return item.getEleVal(element);
}
/**
* Read data from html and save it
* @param key key name
* @param value value
* @return value in html
*/
async takeAndSave(key: string) {
let value = this.take(key, true);
await this.save();
return value;
}
/**
* Disable setting item
* @param key key name
*/
disable(key: string) {
let element = this.elements.get(key) as any;
if (element) {
element.disabled = true;
}
}
/**
* Enable setting item
* @param key key name
*/
enable(key: string) {
let element = this.elements.get(key) as any;
if (element) {
element.disabled = false;
}
}
/**
* JSON
* @returns object
*/
dump(): Object {
let data: any = {};
for (let [key, item] of this.settings) {
if (item.type === 'button') continue;
data[key] = item.value;
}
return data;
}
addItem(item: ISettingUtilsItem) {
this.settings.set(item.key, item);
const IsCustom = item.type === 'custom';
let error = IsCustom && (item.createElement === undefined || item.getEleVal === undefined || item.setEleVal === undefined);
if (error) {
console.error('The custom setting item must have createElement, getEleVal and setEleVal methods');
return;
}
if (item.getEleVal === undefined) {
item.getEleVal = createDefaultGetter(item.type);
}
if (item.setEleVal === undefined) {
item.setEleVal = createDefaultSetter(item.type);
}
if (item.createElement === undefined) {
let itemElement = this.createDefaultElement(item);
this.elements.set(item.key, itemElement);
this.plugin.setting.addItem({
title: item.title,
description: item?.description,
direction: item?.direction,
createActionElement: () => {
this.updateElementFromValue(item.key);
let element = this.getElement(item.key);
return element;
}
});
} else {
this.plugin.setting.addItem({
title: item.title,
description: item?.description,
direction: item?.direction,
createActionElement: () => {
let val = this.get(item.key);
let element = item.createElement(val);
this.elements.set(item.key, element);
return element;
}
});
}
}
createDefaultElement(item: ISettingUtilsItem) {
let itemElement: HTMLElement;
//阻止思源内置的回车键确认
const preventEnterConfirm = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
e.stopImmediatePropagation();
}
}
switch (item.type) {
case 'checkbox':
let element: HTMLInputElement = document.createElement('input');
element.type = 'checkbox';
element.checked = item.value;
element.className = "b3-switch fn__flex-center";
itemElement = element;
element.onchange = item.action?.callback ?? (() => { });
break;
case 'select':
let selectElement: HTMLSelectElement = document.createElement('select');
selectElement.className = "b3-select fn__flex-center fn__size200";
let options = item?.options ?? {};
for (let val in options) {
let optionElement = document.createElement('option');
let text = options[val];
optionElement.value = val;
optionElement.text = text;
selectElement.appendChild(optionElement);
}
selectElement.value = item.value;
selectElement.onchange = item.action?.callback ?? (() => { });
itemElement = selectElement;
break;
case 'slider':
let sliderElement: HTMLInputElement = document.createElement('input');
sliderElement.type = 'range';
sliderElement.className = 'b3-slider fn__size200 b3-tooltips b3-tooltips__n';
sliderElement.ariaLabel = item.value;
sliderElement.min = item.slider?.min.toString() ?? '0';
sliderElement.max = item.slider?.max.toString() ?? '100';
sliderElement.step = item.slider?.step.toString() ?? '1';
sliderElement.value = item.value;
sliderElement.onchange = () => {
sliderElement.ariaLabel = sliderElement.value;
item.action?.callback();
}
itemElement = sliderElement;
break;
case 'textinput':
let textInputElement: HTMLInputElement = document.createElement('input');
textInputElement.className = 'b3-text-field fn__flex-center fn__size200';
textInputElement.value = item.value;
textInputElement.onchange = item.action?.callback ?? (() => { });
itemElement = textInputElement;
textInputElement.addEventListener('keydown', preventEnterConfirm);
break;
case 'textarea':
let textareaElement: HTMLTextAreaElement = document.createElement('textarea');
textareaElement.className = "b3-text-field fn__block";
textareaElement.value = item.value;
textareaElement.onchange = item.action?.callback ?? (() => { });
itemElement = textareaElement;
break;
case 'number':
let numberElement: HTMLInputElement = document.createElement('input');
numberElement.type = 'number';
numberElement.className = 'b3-text-field fn__flex-center fn__size200';
numberElement.value = item.value;
itemElement = numberElement;
numberElement.addEventListener('keydown', preventEnterConfirm);
break;
case 'button':
let buttonElement: HTMLButtonElement = document.createElement('button');
buttonElement.className = "b3-button b3-button--outline fn__flex-center fn__size200";
buttonElement.innerText = item.button?.label ?? 'Button';
buttonElement.onclick = item.button?.callback ?? (() => { });
itemElement = buttonElement;
break;
case 'hint':
let hintElement: HTMLElement = document.createElement('div');
hintElement.className = 'b3-label fn__flex-center';
itemElement = hintElement;
break;
}
return itemElement;
}
/**
* return the setting element
* @param key key name
* @returns element
*/
getElement(key: string) {
// let item = this.settings.get(key);
let element = this.elements.get(key) as any;
return element;
}
private updateValueFromElement(key: string) {
let item = this.settings.get(key);
if (item.type === 'button') return;
let element = this.elements.get(key) as any;
item.value = item.getEleVal(element);
}
private updateElementFromValue(key: string) {
let item = this.settings.get(key);
if (item.type === 'button') return;
let element = this.elements.get(key) as any;
item.setEleVal(element, item.value);
}
}

139
src/setting-example.svelte Normal file
View file

@ -0,0 +1,139 @@
<script lang="ts">
import { showMessage } from "siyuan";
import SettingPanel from "./libs/components/setting-panel.svelte";
let groups: string[] = ["🌈 Group 1", "✨ Group 2"];
let focusGroup = groups[0];
const group1Items: ISettingItem[] = [
{
type: 'checkbox',
title: 'checkbox',
description: 'checkbox',
key: 'a',
value: true
},
{
type: 'textinput',
title: 'text',
description: 'This is a text',
key: 'b',
value: 'This is a text',
placeholder: 'placeholder'
},
{
type: 'textarea',
title: 'textarea',
description: 'This is a textarea',
key: 'b2',
value: 'This is a textarea',
placeholder: 'placeholder',
direction: 'row'
},
{
type: 'select',
title: 'select',
description: 'select',
key: 'c',
value: 'x',
options: {
x: 'x',
y: 'y',
z: 'z'
}
}
];
const group2Items: ISettingItem[] = [
{
type: 'button',
title: 'button',
description: 'This is a button',
key: 'e',
value: 'Click Button',
button: {
label: 'Click Me',
callback: () => {
showMessage('Hello, world!');
}
}
},
{
type: 'slider',
title: 'slider',
description: 'slider',
key: 'd',
value: 50,
slider: {
min: 0,
max: 100,
step: 1
}
}
];
/********** Events **********/
interface ChangeEvent {
group: string;
key: string;
value: any;
}
const onChanged = ({ detail }: CustomEvent<ChangeEvent>) => {
if (detail.group === groups[0]) {
// setting.set(detail.key, detail.value);
//Please add your code here
//Udpate the plugins setting data, don't forget to call plugin.save() for data persistence
}
};
</script>
<div class="fn__flex-1 fn__flex config__panel">
<ul class="b3-tab-bar b3-list b3-list--background">
{#each groups as group}
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
<li
data-name="editor"
class:b3-list-item--focus={group === focusGroup}
class="b3-list-item"
on:click={() => {
focusGroup = group;
}}
on:keydown={() => {}}
>
<span class="b3-list-item__text">{group}</span>
</li>
{/each}
</ul>
<div class="config__tab-wrap">
<SettingPanel
group={groups[0]}
settingItems={group1Items}
display={focusGroup === groups[0]}
on:changed={onChanged}
on:click={({ detail }) => { console.debug("Click:", detail.key); }}
>
<div class="fn__flex b3-label">
💡 This is our default settings.
</div>
</SettingPanel>
<SettingPanel
group={groups[1]}
settingItems={group2Items}
display={focusGroup === groups[1]}
on:changed={onChanged}
on:click={({ detail }) => { console.debug("Click:", detail.key); }}
>
</SettingPanel>
</div>
</div>
<style lang="scss">
.config__panel {
height: 100%;
}
.config__panel > ul > li {
padding-left: 1rem;
}
</style>

206
src/siyuan.d.ts vendored
View file

@ -1,206 +0,0 @@
/*
* Copyright (c) 2023, Terwer . All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Terwer designates this
* particular file as subject to the "Classpath" exception as provided
* by Terwer in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Terwer, Shenzhen, Guangdong, China, youweics@163.com
* or visit www.terwer.space if you need additional information or have any
* questions.
*/
declare module "siyuan" {
type TEventBus = "ws-main"
interface IObject {
[key: string]: string;
}
interface IWebSocketData {
cmd: string
callback?: string
data: any
msg: string
code: number
sid: string
}
declare interface IPluginDockTab {
position: "LeftTop" | "LeftBottom" | "RightTop" | "RightBottom" | "BottomLeft" | "BottomRight",
size: { width: number, height: number },
icon: string,
hotkey?: string,
title: string,
}
interface IMenuItemOption {
label?: string,
click?: (element: HTMLElement) => void,
type?: "separator" | "submenu" | "readonly",
accelerator?: string,
action?: string,
id?: string,
submenu?: IMenuItemOption[]
disabled?: boolean
icon?: string
iconHTML?: string
current?: boolean
bind?: (element: HTMLElement) => void
}
export function fetchPost(url: string, data?: any, cb?: (response: IWebSocketData) => void, headers?: IObject): void;
export function fetchSyncPost(url: string, data?: any): Promise<IWebSocketData>;
export function fetchGet(url: string, cb: (response: IWebSocketData) => void): void;
export function openTab(options: {
custom?: {
title: string,
icon: string,
data?: any
fn?: () => any,
} // card 和自定义页签 必填
position?: "right" | "bottom",
keepCursor?: boolean // 是否跳转到新 tab 上
removeCurrentTab?: boolean // 在当前页签打开时需移除原有页签
afterOpen?: () => void // 打开后回调
}): void
export function isMobile(): boolean;
export function adaptHotkey(hotkey: string): string;
export function confirm(title: string, text: string, confirmCB?: () => void, cancelCB?: () => void): void;
/**
* @param timeout - ms. 0: manual close-1: always show; 6000: default
* @param {string} [type=info]
*/
export function showMessage(text: string, timeout?: number, type?: "info" | "error", id?: string): void;
export class App {
plugins: Plugin[];
}
export abstract class Plugin {
eventBus: EventBus;
i18n: IObject;
data: any;
name: string;
constructor(options: {
app: App,
id: string,
name: string,
i18n: IObject
})
onload(): void;
onunload(): void;
/*
* @param {string} [options.position=right]
*/
addTopBar(options: {
icon: string,
title: string,
callback: (evt: MouseEvent) => void
position?: "right" | "left"
}): HTMLDivElement;
openSetting(): void
// registerCommand(command: IPluginCommand): void;
// registerSettingRender(settingRender: SettingRender): void;
loadData(storageName: string): Promise<any>;
saveData(storageName: string, content: any): Promise<void>;
removeData(storageName: string): Promise<any>;
addTab(options: {
type: string,
destroy?: () => void,
resize?: () => void,
update?: () => void,
init: () => void
}): () => any
addDock(options: {
config: IPluginDockTab,
data: any,
type: string,
destroy?: () => void,
resize?: () => void,
update?: () => void,
init: () => void
}): any
}
export class EventBus {
on(type: TEventBus, listener: (event: CustomEvent<any>) => void): void;
once(type: TEventBus, listener: (event: CustomEvent<any>) => void): void;
off(type: TEventBus, listener: (event: CustomEvent<any>) => void): void;
emit(type: TEventBus, detail?: any): boolean;
}
export class Dialog {
element: HTMLElement;
constructor(options: {
title?: string,
transparent?: boolean,
content: string,
width?: string
height?: string,
destroyCallback?: (options?: IObject) => void
disableClose?: boolean
disableAnimation?: boolean
});
destroy(options?: IObject): void;
bindInput(inputElement: HTMLInputElement | HTMLTextAreaElement, enterEvent?: () => void): void;
}
export class Menu {
constructor(id?: string, closeCB?: () => void);
showSubMenu(subMenuElement: HTMLElement): void;
addItem(options: IMenuItemOption): HTMLElement;
addSeparator(): void;
open(options: { x: number, y: number, h?: number, w?: number, isLeft?: boolean }): void;
/*
* @param {string} [position=all]
*/
fullscreen(position?: "bottom" | "all"): void;
close(): void;
}
}

70
src/sy-dtype.d.ts vendored
View file

@ -1,70 +0,0 @@
/**
* Copyright (c) 2023 frostime. All rights reserved.
* https://github.com/frostime/sy-plugin-template-vite
*/
/**
*
*/
declare module "sy-dtype" {
export type DocumentId = string;
export type BlockId = string;
export type NotebookId = string;
export type PreviousID = BlockId;
export type ParentID = BlockId | DocumentId;
export type Notebook = {
id: NotebookId;
name: string;
icon: string;
sort: number;
closed: boolean;
}
export type NotebookConf = {
name: string;
closed: boolean;
refCreateSavePath: string;
createDocNameTemplate: string;
dailyNoteSavePath: string;
dailyNoteTemplatePath: string;
}
export type BlockType = "d" | "s" | "h" | "t" | "i" | "p" | "f" | "audio" | "video" | "other";
export 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";
export type Block = {
id: BlockId;
parent_id?: BlockId;
root_id: DocumentId;
hash: string;
box: string;
path: string;
hpath: string;
name: string;
alias: string;
memo: string;
tag: string;
content: string;
fcontent?: string;
markdown: string;
length: number;
type: BlockType;
subtype: BlockSubType;
ial?: { [key: string]: string };
sort: number;
created: string;
updated: string;
}
export type doOperation = {
action: string;
data: string;
id: BlockId;
parentID: BlockId | DocumentId;
previousID: BlockId;
retData: null;
}
}

65
src/types/api.d.ts vendored Normal file
View file

@ -0,0 +1,65 @@
interface IResGetNotebookConf {
box: string;
conf: NotebookConf;
name: string;
}
interface IReslsNotebooks {
notebooks: Notebook[];
}
interface IResUpload {
errFiles: string[];
succMap: { [key: string]: string };
}
interface IResdoOperations {
doOperations: doOperation[];
undoOperations: doOperation[] | null;
}
interface IResGetBlockKramdown {
id: BlockId;
kramdown: string;
}
interface IResGetChildBlock {
id: BlockId;
type: BlockType;
subtype?: BlockSubType;
}
interface IResGetTemplates {
content: string;
path: string;
}
interface IResReadDir {
isDir: boolean;
isSymlink: boolean;
name: string;
}
interface IResExportMdContent {
hPath: string;
content: string;
}
interface IResBootProgress {
progress: number;
details: string;
}
interface IResForwardProxy {
body: string;
contentType: string;
elapsed: number;
headers: { [key: string]: string };
status: number;
url: string;
}
interface IResExportResources {
path: string;
}

106
src/types/index.d.ts vendored Normal file
View file

@ -0,0 +1,106 @@
/*
* 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
*/
type DocumentId = string;
type BlockId = string;
type NotebookId = string;
type PreviousID = BlockId;
type ParentID = BlockId | DocumentId;
type Notebook = {
id: NotebookId;
name: string;
icon: string;
sort: number;
closed: boolean;
}
type NotebookConf = {
name: string;
closed: boolean;
refCreateSavePath: string;
createDocNameTemplate: string;
dailyNoteSavePath: string;
dailyNoteTemplatePath: string;
}
type BlockType =
| 'd'
| 'p'
| 'query_embed'
| 'l'
| 'i'
| 'h'
| 'iframe'
| 'tb'
| 'b'
| 's'
| 'c'
| 'widget'
| 't'
| 'html'
| 'm'
| 'av'
| 'audio';
type BlockSubType = "d1" | "d2" | "s1" | "s2" | "s3" | "t1" | "t2" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "table" | "task" | "toggle" | "latex" | "quote" | "html" | "code" | "footnote" | "cite" | "collection" | "bookmark" | "attachment" | "comment" | "mindmap" | "spreadsheet" | "calendar" | "image" | "audio" | "video" | "other";
type Block = {
id: BlockId;
parent_id?: BlockId;
root_id: DocumentId;
hash: string;
box: string;
path: string;
hpath: string;
name: string;
alias: string;
memo: string;
tag: string;
content: string;
fcontent?: string;
markdown: string;
length: number;
type: BlockType;
subtype: BlockSubType;
/** string of { [key: string]: string }
* For instance: "{: custom-type=\"query-code\" id=\"20230613234017-zkw3pr0\" updated=\"20230613234509\"}"
*/
ial?: string;
sort: number;
created: string;
updated: string;
}
type doOperation = {
action: string;
data: string;
id: BlockId;
parentID: BlockId | DocumentId;
previousID: BlockId;
retData: null;
}
interface Window {
siyuan: {
config: any;
notebooks: any;
menus: any;
dialogs: any;
blockPanels: any;
storage: any;
user: any;
ws: any;
languages: any;
emojis: any;
};
Lute: any;
}

View file

@ -1,7 +1,26 @@
/*
* Copyright (c) 2024 by frostime. All Rights Reserved.
* @Author : frostime
* @Date : 2023-05-19 19:49:13
* @FilePath : /svelte.config.js
* @LastEditTime : 2024-04-19 19:01:55
* @Description :
*/
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"
const NoWarns = new Set([
"a11y-click-events-have-key-events",
"a11y-no-static-element-interactions",
"a11y-no-noninteractive-element-interactions"
]);
export default {
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
// for more information about preprocessors
preprocess: vitePreprocess(),
onwarn: (warning, handler) => {
// suppress warnings on `vite dev` and `vite build`; but even without this, things still work
if (NoWarns.has(warning.code)) return;
handler(warning);
}
}

View file

@ -35,14 +35,20 @@
"node",
"vite/client",
"svelte"
]
],
// "baseUrl": "./src",
"paths": {
"@/*": ["./src/*"],
"@/libs/*": ["./src/libs/*"],
}
},
"include": [
"tools/**/*.ts",
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue"
"src/**/*.vue",
"src/**/*.svelte"
],
"references": [
{

View file

@ -1,110 +1,96 @@
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"
import zipPack from "vite-plugin-zip-pack";
import fg from 'fast-glob';
const args = minimist(process.argv.slice(2))
const isWatch = args.watch || args.w || false
const devDistDir = "./dev"
const distDir = isWatch ? devDistDir : "./dist"
import vitePluginYamlI18n from './yaml-plugin';
console.log("isWatch=>", isWatch)
console.log("distDir=>", distDir)
const env = process.env;
const isSrcmap = env.VITE_SOURCEMAP === 'inline';
const isDev = env.NODE_ENV === 'development';
const outputDir = isDev ? "dev" : "dist";
console.log("isDev=>", isDev);
console.log("isSrcmap=>", isSrcmap);
console.log("outputDir=>", outputDir);
export default defineConfig({
resolve: {
alias: {
"@": resolve(__dirname, "src"),
}
},
plugins: [
svelte(),
vitePluginYamlI18n({
inDir: 'public/i18n',
outDir: `${outputDir}/i18n`
}),
viteStaticCopy({
targets: [
{
src: "./README*.md",
dest: "./",
},
{
src: "./icon.png",
dest: "./",
},
{
src: "./preview.png",
dest: "./",
},
{
src: "./plugin.json",
dest: "./",
},
{
src: "./src/i18n/**",
dest: "./i18n/",
},
{ 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.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([
'src/i18n/*.json',
'./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: {
@ -118,4 +104,60 @@ export default defineConfig({
},
},
}
})
});
/**
* Clean up some dist files after compiled
* @author frostime
* @param options:
* @returns
*/
function cleanupDistFiles(options: { patterns: string[], distDir: string }) {
const {
patterns,
distDir
} = options;
return {
name: 'rollup-plugin-cleanup',
enforce: 'post',
writeBundle: {
sequential: true,
order: 'post' as 'post',
async handler() {
const fg = await import('fast-glob');
const fs = await import('fs');
// const path = await import('path');
// 使用 glob 语法,确保能匹配到文件
const distPatterns = patterns.map(pat => `${distDir}/${pat}`);
console.debug('Cleanup searching patterns:', distPatterns);
const files = await fg.default(distPatterns, {
dot: true,
absolute: true,
onlyFiles: false
});
// console.info('Files to be cleaned up:', files);
for (const file of files) {
try {
if (fs.default.existsSync(file)) {
const stat = fs.default.statSync(file);
if (stat.isDirectory()) {
fs.default.rmSync(file, { recursive: true });
} else {
fs.default.unlinkSync(file);
}
console.log(`Cleaned up: ${file}`);
}
} catch (error) {
console.error(`Failed to clean up ${file}:`, error);
}
}
}
}
};
}

60
yaml-plugin.js Normal file
View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2024 by frostime. All Rights Reserved.
* @Author : frostime
* @Date : 2024-04-05 21:27:55
* @FilePath : /yaml-plugin.js
* @LastEditTime : 2024-04-05 22:53:34
* @Description : 去妮玛的 json 格式我就是要用 yaml i18n
*/
// plugins/vite-plugin-parse-yaml.js
import fs from 'fs';
import yaml from 'js-yaml';
import { resolve } from 'path';
export default function vitePluginYamlI18n(options = {}) {
// Default options with a fallback
const DefaultOptions = {
inDir: 'src/i18n',
outDir: 'dist/i18n',
};
const finalOptions = { ...DefaultOptions, ...options };
return {
name: 'vite-plugin-yaml-i18n',
buildStart() {
console.log('🌈 Parse I18n: YAML to JSON..');
const inDir = finalOptions.inDir;
const outDir = finalOptions.outDir
if (!fs.existsSync(outDir)) {
fs.mkdirSync(outDir, { recursive: true });
}
//Parse yaml file, output to json
const files = fs.readdirSync(inDir);
for (const file of files) {
if (file.endsWith('.yaml') || file.endsWith('.yml')) {
console.log(`-- Parsing ${file}`)
//检查是否有同名的json文件
const jsonFile = file.replace(/\.(yaml|yml)$/, '.json');
if (files.includes(jsonFile)) {
console.log(`---- File ${jsonFile} already exists, skipping...`);
continue;
}
try {
const filePath = resolve(inDir, file);
const fileContents = fs.readFileSync(filePath, 'utf8');
const parsed = yaml.load(fileContents);
const jsonContent = JSON.stringify(parsed, null, 2);
const outputFilePath = resolve(outDir, file.replace(/\.(yaml|yml)$/, '.json'));
console.log(`---- Writing to ${outputFilePath}`);
fs.writeFileSync(outputFilePath, jsonContent);
} catch (error) {
this.error(`---- Error parsing YAML file ${file}: ${error.message}`);
}
}
}
},
};
}