mirror of
https://github.com/siyuan-note/plugin-sample-vite-svelte.git
synced 2025-06-07 18:46:01 +00:00
merge new changes
This commit is contained in:
commit
4eaa160ab7
25 changed files with 1136 additions and 356 deletions
|
@ -1,2 +0,0 @@
|
|||
node_modules
|
||||
dist
|
|
@ -1,38 +0,0 @@
|
|||
module.exports = {
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:svelte/recommended",
|
||||
"turbo",
|
||||
"prettier",
|
||||
],
|
||||
|
||||
parser: "@typescript-eslint/parser",
|
||||
|
||||
overrides: [
|
||||
{
|
||||
files: ["*.svelte"],
|
||||
parser: "svelte-eslint-parser",
|
||||
// Parse the script in `.svelte` as TypeScript by adding the following configuration.
|
||||
parserOptions: {
|
||||
parser: "@typescript-eslint/parser",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
plugins: ["@typescript-eslint", "prettier"],
|
||||
|
||||
rules: {
|
||||
// Note: you must disable the base rule as it can report incorrect errors
|
||||
semi: "off",
|
||||
quotes: "off",
|
||||
"no-undef": "off",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"@typescript-eslint/no-this-alias": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"turbo/no-undeclared-env-vars": "off",
|
||||
"prettier/prettier": "error",
|
||||
},
|
||||
}
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,6 +2,7 @@
|
|||
.vscode
|
||||
.DS_Store
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
package.zip
|
||||
node_modules
|
||||
dev
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
# Changelog
|
||||
|
||||
## 0.3.5 2024-03
|
||||
## v0.3.5 2024-04-30
|
||||
|
||||
* [Add `direction` to plugin method `Setting.addItem`](https://github.com/siyuan-note/siyuan/issues/11183)
|
||||
|
||||
|
||||
## 0.3.4 2024-02-20
|
||||
|
||||
|
|
48
README.md
48
README.md
|
@ -3,7 +3,7 @@
|
|||
|
||||
[中文版](./README_zh_CN.md)
|
||||
|
||||
> Consistent with [siyuan/plugin-sample](https://github.com/siyuan-note/plugin-sample) [v0.3.4](https://github.com/siyuan-note/plugin-sample/tree/v0.3.4)
|
||||
> 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)
|
||||
|
||||
|
||||
|
||||
|
@ -11,7 +11,9 @@
|
|||
2. Use symbolic linking instead of putting the project into the plugins directory program development
|
||||
3. Built-in support for the svelte framework
|
||||
|
||||
> If don't want svelte, turn to this template: [frostime/plugin-sample-vite](https://github.com/frostime/plugin-sample-vite)
|
||||
> **If don't want svelte, turn to this template**: [frostime/plugin-sample-vite](https://github.com/frostime/plugin-sample-vite)
|
||||
>
|
||||
> **We also provide with a vite+solidjs template**: [frostime/plugin-sample-vite-solidjs](https://github.com/frostime/plugin-sample-vite-solidjs)
|
||||
|
||||
4. Provides a github action template to automatically generate package.zip and upload to new release
|
||||
|
||||
|
@ -65,8 +67,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
|
||||
* 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.
|
||||
|
@ -246,43 +251,12 @@ Related APIs can be found at: `/api/file/*` (e.g., `/api/file/getFile`).
|
|||
|
||||
### 2. Daily Note Attribute Specifications
|
||||
|
||||
When creating a diary in SiYuan, a custom-dailynote-yyyymmdd attribute will be automatically added to the document to distinguish it from regular documents.
|
||||
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 diary, 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 diary), please manually add this attribute to the document.
|
||||
* 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
|
||||
|
||||
Here is a reference code:
|
||||
|
||||
```ts
|
||||
/*
|
||||
* Copyright (c) 2023 by frostime. All Rights Reserved.
|
||||
* @Author : frostime
|
||||
* @Url : https://github.com/frostime/siyuan-dailynote-today/blob/v1.3.0/src/func/dailynote/dn-attr.ts
|
||||
*/
|
||||
|
||||
export function formatDate(date?: Date, sep=''): string {
|
||||
date = date === undefined ? new Date() : date;
|
||||
let year = date.getFullYear();
|
||||
let month = date.getMonth() + 1;
|
||||
let day = date.getDate();
|
||||
return `${year}${sep}${month < 10 ? '0' + month : month}${sep}${day < 10 ? '0' + day : day}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set custom attribute: `custom-dailynote-yyyyMMdd`: yyyyMMdd
|
||||
* https://github.com/siyuan-note/siyuan/issues/9807
|
||||
* @param doc_id Id of daily note
|
||||
*/
|
||||
export function setCustomDNAttr(doc_id: string, date?: Date) {
|
||||
let td = formatDate(date);
|
||||
let attr = `custom-dailynote-${td}`;
|
||||
// 构建 attr: td
|
||||
let attrs: { [key: string]: string } = {};
|
||||
attrs[attr] = td;
|
||||
serverApi.setBlockAttrs(doc_id, attrs);
|
||||
}
|
||||
```
|
||||
|
|
|
@ -4,13 +4,15 @@
|
|||
[English](./README.md)
|
||||
|
||||
|
||||
> 本例同 [siyuan/plugin-sample](https://github.com/siyuan-note/plugin-sample) [v0.3.4](https://github.com/siyuan-note/plugin-sample/tree/v0.3.4)
|
||||
> 本例同 [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)
|
||||
> **如果不想要 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并上传到新版本中
|
||||
|
||||
|
@ -62,9 +64,12 @@
|
|||
* 插件自身的元信息,比如插件描述和自述文件
|
||||
* plugin.json 中的 `description` 和 `readme` 字段,以及对应的 README*.md 文件
|
||||
* 插件中使用的文本,比如按钮文字和提示信息
|
||||
* src/i18n/*.json 语言配置文件
|
||||
* public/i18n/*.json 语言配置文件
|
||||
* 代码中使用 `this.i18.key` 获取文本
|
||||
* 最后在 plugin.json 中的 `i18n` 字段中声明该插件支持的语言
|
||||
* yaml 支持
|
||||
* 本模板特别支持基于 Yaml 语法的 I18n,见 `public/i18n/zh_CN.yaml`
|
||||
* 编译时,会自动把定义的 yaml 文件翻译成 json 文件放到 dist 或 dev 目录下
|
||||
|
||||
建议插件至少支持英文和简体中文,这样可以方便更多人使用。
|
||||
|
||||
|
@ -234,47 +239,15 @@ PR 社区集市仓库。
|
|||
|
||||
插件或者外部扩展如果有直接读取或者写入 data 下文件的需求,请通过调用内核 API 来实现,**不要自行调用 `fs` 或者其他 electron、nodejs API**,否则可能会导致数据同步时分块丢失,造成云端数据损坏。
|
||||
|
||||
相关 API 见: `/api/file/*`(例如 `/api/file/getFile` 等)。
|
||||
相关 API 见 `/api/file/*`(例如 `/api/file/getFile` 等)。
|
||||
|
||||
### 2. Daily Note 属性规范
|
||||
|
||||
思源在创建日记的时候会自动为文档添加 custom-dailynote-yyyymmdd 属性, 以方便将日记文档同普通文档区分。
|
||||
思源在创建日记的时候会自动为文档添加 custom-dailynote-yyyymmdd 属性,以方便将日记文档同普通文档区分。
|
||||
|
||||
> 详情请见 [Github Issue #9807](https://github.com/siyuan-note/siyuan/issues/9807)。
|
||||
|
||||
开发者在开发手动创建 Daily Note 的功能时请注意:
|
||||
|
||||
- 如果调用了 `/api/filetree/createDailyNote` 创建日记,那么文档会自动添加这个属性,无需开发者特别处理。
|
||||
- 如果是开发者代码手动创建文档(例如使用 `createDocWithMd` API 创建日记),请手动为文档添加该属性。
|
||||
|
||||
参考代码:
|
||||
|
||||
```ts
|
||||
/*
|
||||
* Copyright (c) 2023 by frostime. All Rights Reserved.
|
||||
* @Author : frostime
|
||||
* @Url : https://github.com/frostime/siyuan-dailynote-today/blob/v1.3.0/src/func/dailynote/dn-attr.ts
|
||||
*/
|
||||
|
||||
export function formatDate(date?: Date, sep=''): string {
|
||||
date = date === undefined ? new Date() : date;
|
||||
let year = date.getFullYear();
|
||||
let month = date.getMonth() + 1;
|
||||
let day = date.getDate();
|
||||
return `${year}${sep}${month < 10 ? '0' + month : month}${sep}${day < 10 ? '0' + day : day}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set custom attribute: `custom-dailynote-yyyyMMdd`: yyyyMMdd
|
||||
* https://github.com/siyuan-note/siyuan/issues/9807
|
||||
* @param doc_id Id of daily note
|
||||
*/
|
||||
export function setCustomDNAttr(doc_id: string, date?: Date) {
|
||||
let td = formatDate(date);
|
||||
let attr = `custom-dailynote-${td}`;
|
||||
// 构建 attr: td
|
||||
let attrs: { [key: string]: string } = {};
|
||||
attrs[attr] = td;
|
||||
serverApi.setBlockAttrs(doc_id, attrs);
|
||||
}
|
||||
```
|
||||
* 如果调用了 `/api/filetree/createDailyNote` 创建日记,那么文档会自动添加这个属性,无需开发者特别处理
|
||||
* 如果是开发者代码手动创建文档(例如使用 `createDocWithMd` API 创建日记),请手动为文档添加该属性
|
||||
|
|
19
package.json
19
package.json
|
@ -1,33 +1,34 @@
|
|||
{
|
||||
"name": "plugin-sample-vite-svelte",
|
||||
"version": "0.3.2",
|
||||
"version": "0.3.5",
|
||||
"type": "module",
|
||||
"description": "This is a sample plugin based on vite and svelte for Siyuan (https://b3log.org/siyuan)",
|
||||
"repository": "",
|
||||
"homepage": "",
|
||||
"author": "",
|
||||
"author": "frostime",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"make-link": "node --no-warnings ./scripts/make_dev_link.js",
|
||||
"dev": "vite build --watch",
|
||||
"build": "vite build"
|
||||
"build": "vite build",
|
||||
"make-install": "vite build && node --no-warnings ./scripts/make_install.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^2.4.1",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||
"@tsconfig/svelte": "^4.0.1",
|
||||
"@types/node": "^20.3.0",
|
||||
"eslint": "^8.42.0",
|
||||
"fast-glob": "^3.2.12",
|
||||
"glob": "^7.2.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"minimist": "^1.2.8",
|
||||
"rollup-plugin-livereload": "^2.0.5",
|
||||
"sass": "^1.63.3",
|
||||
"siyuan": "0.9.4",
|
||||
"svelte": "^3.59.1",
|
||||
"siyuan": "0.9.9",
|
||||
"svelte": "^4.2.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.1.3",
|
||||
"vite": "^4.5.2",
|
||||
"vite-plugin-static-copy": "^0.15.0",
|
||||
"vite": "^5.0.0",
|
||||
"vite-plugin-static-copy": "^1.0.2",
|
||||
"vite-plugin-zip-pack": "^1.0.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
"name": "plugin-sample-vite-svelte",
|
||||
"author": "frostime",
|
||||
"url": "https://github.com/siyuan-note/plugin-sample-vite-svelte",
|
||||
"version": "0.3.4",
|
||||
"minAppVersion": "3.0.0",
|
||||
"version": "0.3.5",
|
||||
"minAppVersion": "3.0.12",
|
||||
"backends": [
|
||||
"windows",
|
||||
"linux",
|
||||
|
@ -32,7 +32,7 @@
|
|||
},
|
||||
"funding": {
|
||||
"custom": [
|
||||
"https://afdian.net/a/frostime"
|
||||
""
|
||||
]
|
||||
},
|
||||
"keywords": [
|
||||
|
|
21
public/i18n/zh_CN.yaml
Normal file
21
public/i18n/zh_CN.yaml
Normal file
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
addTopBarIcon: 使用插件添加一个顶栏按钮
|
||||
cancel: 取消
|
||||
save: 保存
|
||||
byeMenu: 再见,菜单!
|
||||
helloPlugin: 你好,插件!
|
||||
byePlugin: 再见,插件!
|
||||
showDialog: 弹出一个对话框
|
||||
removedData: 数据已删除
|
||||
confirmRemove: 确认删除 ${name} 中的数据?
|
||||
insertEmoji: 插入表情
|
||||
removeSpace: 移除空格
|
||||
getTab: 在日志中打印出已打开的所有自定义页签
|
||||
name: 思源
|
||||
hello:
|
||||
makesure: 使用这个模板之前,请阅读<a href="https://github.com/siyuan-note/plugin-sample">官方教程</a>,
|
||||
确保自己已经理解了插件的基本开发流程。
|
||||
hintTitle: 关于
|
||||
hintDesc: "<a href='https://github.com/siyuan-note/plugin-sample-vite-svelte'>\U0001F517
|
||||
plugin-sample-vite-svelte</a><br>\U0001F4BB @frostime<br>\U0001F4BB @88250<br>\U0001F4BB
|
||||
@zxkmm"
|
191
scripts/make_install.js
Normal file
191
scripts/make_install.js
Normal file
|
@ -0,0 +1,191 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import http from 'node:http';
|
||||
import readline from 'node:readline';
|
||||
|
||||
|
||||
//************************************ Write you dir here ************************************
|
||||
|
||||
let targetDir = ''; // the target directory of the plugin, '*/data/plugin'
|
||||
//********************************************************************************************
|
||||
|
||||
const log = (info) => console.log(`\x1B[36m%s\x1B[0m`, info);
|
||||
const error = (info) => console.log(`\x1B[31m%s\x1B[0m`, info);
|
||||
|
||||
let POST_HEADER = {
|
||||
// "Authorization": `Token ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
async function myfetch(url, options) {
|
||||
//使用 http 模块,从而兼容那些不支持 fetch 的 nodejs 版本
|
||||
return new Promise((resolve, reject) => {
|
||||
let req = http.request(url, options, (res) => {
|
||||
let data = '';
|
||||
res.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
res.on('end', () => {
|
||||
resolve({
|
||||
ok: true,
|
||||
status: res.statusCode,
|
||||
json: () => JSON.parse(data)
|
||||
});
|
||||
});
|
||||
});
|
||||
req.on('error', (e) => {
|
||||
reject(e);
|
||||
});
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
async function getSiYuanDir() {
|
||||
let url = 'http://127.0.0.1:6806/api/system/getWorkspaces';
|
||||
let conf = {};
|
||||
try {
|
||||
let response = await myfetch(url, {
|
||||
method: 'POST',
|
||||
headers: POST_HEADER
|
||||
});
|
||||
if (response.ok) {
|
||||
conf = await response.json();
|
||||
} else {
|
||||
error(`\tHTTP-Error: ${response.status}`);
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
error(`\tError: ${e}`);
|
||||
error("\tPlease make sure SiYuan is running!!!");
|
||||
return null;
|
||||
}
|
||||
return conf.data;
|
||||
}
|
||||
|
||||
async function chooseTarget(workspaces) {
|
||||
let count = workspaces.length;
|
||||
log(`>>> Got ${count} SiYuan ${count > 1 ? 'workspaces' : 'workspace'}`)
|
||||
for (let i = 0; i < workspaces.length; i++) {
|
||||
log(`\t[${i}] ${workspaces[i].path}`);
|
||||
}
|
||||
|
||||
if (count == 1) {
|
||||
return `${workspaces[0].path}/data/plugins`;
|
||||
} else {
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
let index = await new Promise((resolve, reject) => {
|
||||
rl.question(`\tPlease select a workspace[0-${count-1}]: `, (answer) => {
|
||||
resolve(answer);
|
||||
});
|
||||
});
|
||||
rl.close();
|
||||
return `${workspaces[index].path}/data/plugins`;
|
||||
}
|
||||
}
|
||||
|
||||
log('>>> Try to visit constant "targetDir" in make_install.js...')
|
||||
|
||||
if (targetDir === '') {
|
||||
log('>>> Constant "targetDir" is empty, try to get SiYuan directory automatically....')
|
||||
let res = await getSiYuanDir();
|
||||
|
||||
if (res === null || res === undefined || res.length === 0) {
|
||||
error('>>> Can not get SiYuan directory automatically');
|
||||
|
||||
process.exit(1);
|
||||
} else {
|
||||
targetDir = await chooseTarget(res);
|
||||
}
|
||||
|
||||
log(`>>> Successfully got target directory: ${targetDir}`);
|
||||
}
|
||||
|
||||
//Check
|
||||
if (!fs.existsSync(targetDir)) {
|
||||
error(`Failed! plugin directory not exists: "${targetDir}"`);
|
||||
error(`Please set the plugin directory in scripts/make_install.js`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
//check if plugin.json exists
|
||||
if (!fs.existsSync('./plugin.json')) {
|
||||
//change dir to parent
|
||||
process.chdir('../');
|
||||
if (!fs.existsSync('./plugin.json')) {
|
||||
error('Failed! plugin.json not found');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
//load plugin.json
|
||||
const plugin = JSON.parse(fs.readFileSync('./plugin.json', 'utf8'));
|
||||
const name = plugin?.name;
|
||||
if (!name || name === '') {
|
||||
error('Failed! Please set plugin name in plugin.json');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const distDir = `${process.cwd()}/dist`;
|
||||
//mkdir if not exists
|
||||
if (!fs.existsSync(distDir)) {
|
||||
fs.mkdirSync(distDir);
|
||||
}
|
||||
|
||||
function cmpPath(path1, path2) {
|
||||
path1 = path1.replace(/\\/g, '/');
|
||||
path2 = path2.replace(/\\/g, '/');
|
||||
// sepertor at tail
|
||||
if (path1[path1.length - 1] !== '/') {
|
||||
path1 += '/';
|
||||
}
|
||||
if (path2[path2.length - 1] !== '/') {
|
||||
path2 += '/';
|
||||
}
|
||||
return path1 === path2;
|
||||
}
|
||||
|
||||
const targetPath = `${targetDir}/${name}`;
|
||||
|
||||
|
||||
function copyDirectory(srcDir, dstDir) {
|
||||
if (!fs.existsSync(dstDir)) {
|
||||
fs.mkdirSync(dstDir);
|
||||
log(`Created directory ${dstDir}`);
|
||||
}
|
||||
//将 distDir 下的所有文件复制到 targetPath
|
||||
fs.readdir(srcDir, { withFileTypes: true }, (err, files) => {
|
||||
if (err) {
|
||||
error('Error reading source directory:', err);
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历源目录中的所有文件和子目录
|
||||
files.forEach((file) => {
|
||||
const src = path.join(srcDir, file.name);
|
||||
const dst = path.join(dstDir, file.name);
|
||||
|
||||
// 判断当前项是文件还是目录
|
||||
if (file.isDirectory()) {
|
||||
// 如果是目录,则递归调用复制函数复制子目录
|
||||
copyDirectory(src, dst);
|
||||
} else {
|
||||
// 如果是文件,则复制文件到目标目录
|
||||
fs.copyFile(src, dst, (err) => {
|
||||
if (err) {
|
||||
error('Error copying file:' + err);
|
||||
} else {
|
||||
log(`Copied file: ${src} --> ${dst}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
log(`Copied ${distDir} to ${targetPath}`);
|
||||
});
|
||||
}
|
||||
copyDirectory(distDir, targetPath);
|
||||
|
||||
|
13
src/api.ts
13
src/api.ts
|
@ -6,7 +6,7 @@
|
|||
* API 文档见 [API_zh_CN.md](https://github.com/siyuan-note/siyuan/blob/master/API_zh_CN.md)
|
||||
*/
|
||||
|
||||
import { fetchSyncPost, IWebSocketData } from "siyuan";
|
||||
import { fetchPost, fetchSyncPost, IWebSocketData } from "siyuan";
|
||||
|
||||
|
||||
export async function request(url: string, data: any) {
|
||||
|
@ -328,12 +328,11 @@ export async function getFile(path: string): Promise<any> {
|
|||
path: path
|
||||
}
|
||||
let url = '/api/file/getFile';
|
||||
try {
|
||||
let file = await fetchSyncPost(url, data);
|
||||
return file;
|
||||
} catch (error_msg) {
|
||||
return null;
|
||||
}
|
||||
return new Promise((resolve, _) => {
|
||||
fetchPost(url, data, (content: any) => {
|
||||
resolve(content)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function putFile(path: string, isDir: boolean, file: any) {
|
||||
|
|
88
src/index.ts
88
src/index.ts
|
@ -31,7 +31,7 @@ const DOCK_TYPE = "dock_tab";
|
|||
|
||||
export default class PluginSample extends Plugin {
|
||||
|
||||
private customTab: () => IModel;
|
||||
customTab: () => IModel;
|
||||
private isMobile: boolean;
|
||||
private blockIconEventBindThis = this.blockIconEvent.bind(this);
|
||||
private settingUtils: SettingUtils;
|
||||
|
@ -163,20 +163,23 @@ export default class PluginSample extends Plugin {
|
|||
}
|
||||
});
|
||||
|
||||
this.settingUtils = new SettingUtils(this, STORAGE_NAME);
|
||||
|
||||
try {
|
||||
this.settingUtils.load();
|
||||
} catch (error) {
|
||||
console.error("Error loading settings storage, probably empty config json:", error);
|
||||
}
|
||||
|
||||
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",
|
||||
|
@ -184,6 +187,14 @@ export default class PluginSample extends Plugin {
|
|||
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",
|
||||
|
@ -191,16 +202,31 @@ export default class PluginSample extends Plugin {
|
|||
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: "Readonly text",
|
||||
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({
|
||||
|
@ -209,10 +235,18 @@ export default class PluginSample extends Plugin {
|
|||
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({
|
||||
|
@ -228,6 +262,28 @@ export default class PluginSample extends Plugin {
|
|||
}
|
||||
}
|
||||
});
|
||||
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: "onlyEnableListedDevices",
|
||||
value: false,
|
||||
|
@ -276,6 +332,13 @@ export default class PluginSample extends Plugin {
|
|||
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>`,
|
||||
|
@ -379,7 +442,6 @@ export default class PluginSample extends Plugin {
|
|||
|
||||
async onunload() {
|
||||
console.log(this.i18n.byePlugin);
|
||||
await this.settingUtils.save();
|
||||
showMessage("Goodbye SiYuan Plugin");
|
||||
console.log("onunload");
|
||||
}
|
||||
|
@ -408,7 +470,7 @@ export default class PluginSample extends Plugin {
|
|||
let dialog = new Dialog({
|
||||
title: "SettingPannel",
|
||||
content: `<div id="SettingPanel" style="height: 100%;"></div>`,
|
||||
width: "600px",
|
||||
width: "800px",
|
||||
destroyCallback: (options) => {
|
||||
console.log("destroyCallback", options);
|
||||
//You'd better destroy the component when the dialog is closed
|
||||
|
@ -460,7 +522,7 @@ export default class PluginSample extends Plugin {
|
|||
title: `SiYuan ${Constants.SIYUAN_VERSION}`,
|
||||
content: `<div id="helloPanel" class="b3-dialog__content"></div>`,
|
||||
width: this.isMobile ? "92vw" : "720px",
|
||||
destroyCallback(options) {
|
||||
destroyCallback() {
|
||||
// hello.$destroy();
|
||||
},
|
||||
});
|
||||
|
|
99
src/libs/const.ts
Normal file
99
src/libs/const.ts
Normal file
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Copyright (c) 2024 by frostime. All Rights Reserved.
|
||||
* @Author : frostime
|
||||
* @Date : 2024-06-08 20:36:30
|
||||
* @FilePath : /src/libs/const.ts
|
||||
* @LastEditTime : 2024-06-08 20:48:06
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
|
||||
export const BlockType2NodeType: {[key in BlockType]: string} = {
|
||||
d: 'NodeDocument',
|
||||
p: 'NodeParagraph',
|
||||
query_embed: 'NodeBlockQueryEmbed',
|
||||
l: 'NodeList',
|
||||
i: 'NodeListItem',
|
||||
h: 'NodeHeading',
|
||||
iframe: 'NodeIFrame',
|
||||
tb: 'NodeThematicBreak',
|
||||
b: 'NodeBlockquote',
|
||||
s: 'NodeSuperBlock',
|
||||
c: 'NodeCodeBlock',
|
||||
widget: 'NodeWidget',
|
||||
t: 'NodeTable',
|
||||
html: 'NodeHTMLBlock',
|
||||
m: 'NodeMathBlock',
|
||||
av: 'NodeAttributeView',
|
||||
audio: 'NodeAudio'
|
||||
}
|
||||
|
||||
|
||||
export const NodeIcons = {
|
||||
NodeAttributeView: {
|
||||
icon: "iconDatabase"
|
||||
},
|
||||
NodeAudio: {
|
||||
icon: "iconRecord"
|
||||
},
|
||||
NodeBlockQueryEmbed: {
|
||||
icon: "iconSQL"
|
||||
},
|
||||
NodeBlockquote: {
|
||||
icon: "iconQuote"
|
||||
},
|
||||
NodeCodeBlock: {
|
||||
icon: "iconCode"
|
||||
},
|
||||
NodeDocument: {
|
||||
icon: "iconFile"
|
||||
},
|
||||
NodeHTMLBlock: {
|
||||
icon: "iconHTML5"
|
||||
},
|
||||
NodeHeading: {
|
||||
icon: "iconHeadings",
|
||||
subtypes: {
|
||||
h1: { icon: "iconH1" },
|
||||
h2: { icon: "iconH2" },
|
||||
h3: { icon: "iconH3" },
|
||||
h4: { icon: "iconH4" },
|
||||
h5: { icon: "iconH5" },
|
||||
h6: { icon: "iconH6" }
|
||||
}
|
||||
},
|
||||
NodeIFrame: {
|
||||
icon: "iconLanguage"
|
||||
},
|
||||
NodeList: {
|
||||
subtypes: {
|
||||
o: { icon: "iconOrderedList" },
|
||||
t: { icon: "iconCheck" },
|
||||
u: { icon: "iconList" }
|
||||
}
|
||||
},
|
||||
NodeListItem: {
|
||||
icon: "iconListItem"
|
||||
},
|
||||
NodeMathBlock: {
|
||||
icon: "iconMath"
|
||||
},
|
||||
NodeParagraph: {
|
||||
icon: "iconParagraph"
|
||||
},
|
||||
NodeSuperBlock: {
|
||||
icon: "iconSuper"
|
||||
},
|
||||
NodeTable: {
|
||||
icon: "iconTable"
|
||||
},
|
||||
NodeThematicBreak: {
|
||||
icon: "iconLine"
|
||||
},
|
||||
NodeVideo: {
|
||||
icon: "iconVideo"
|
||||
},
|
||||
NodeWidget: {
|
||||
icon: "iconBoth"
|
||||
}
|
||||
};
|
121
src/libs/dialog.ts
Normal file
121
src/libs/dialog.ts
Normal file
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright (c) 2024 by frostime. All Rights Reserved.
|
||||
* @Author : frostime
|
||||
* @Date : 2024-03-23 21:37:33
|
||||
* @FilePath : /src/libs/dialog.ts
|
||||
* @LastEditTime : 2024-06-01 16:28:30
|
||||
* @Description : Kits about dialogs
|
||||
*/
|
||||
import { Dialog } from "siyuan";
|
||||
|
||||
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);
|
||||
});
|
||||
};
|
34
src/libs/index.d.ts
vendored
34
src/libs/index.d.ts
vendored
|
@ -1,10 +1,17 @@
|
|||
type TSettingItemType = "checkbox" | "select" | "textinput" | "textarea" | "number" | "slider" | "button" | "hint";
|
||||
interface ISettingItem {
|
||||
/*
|
||||
* 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;
|
||||
type: TSettingItemType;
|
||||
title: string;
|
||||
description?: string;
|
||||
placeholder?: string;
|
||||
slider?: {
|
||||
min: number;
|
||||
|
@ -17,3 +24,20 @@ interface ISettingItem {
|
|||
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;
|
||||
}
|
||||
|
|
108
src/libs/item-input.svelte
Normal file
108
src/libs/item-input.svelte
Normal file
|
@ -0,0 +1,108 @@
|
|||
<!--
|
||||
Copyright (c) 2024 by frostime. All Rights Reserved.
|
||||
Author : frostime
|
||||
Date : 2024-06-07 18:49:52
|
||||
FilePath : /src/libs/components/input-item.svelte
|
||||
LastEditTime : 2024-06-07 20:07:58
|
||||
Description :
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
export let type: string; // Setting Type
|
||||
export let key: string;
|
||||
export let value: any;
|
||||
|
||||
//Optional
|
||||
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: () => {} };
|
||||
|
||||
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}
|
||||
/>
|
||||
{:else if type === "textinput"}
|
||||
<!-- Text Input -->
|
||||
<input
|
||||
class="b3-text-field fn__flex-center fn__size200"
|
||||
id={key}
|
||||
{placeholder}
|
||||
bind:value={value}
|
||||
on:change={changed}
|
||||
/>
|
||||
{:else if type === "textarea"}
|
||||
<textarea
|
||||
class="b3-text-field fn__block"
|
||||
style="resize: vertical; height: 10em; white-space: nowrap;"
|
||||
bind:value={value}
|
||||
on:change={changed}
|
||||
/>
|
||||
{:else if type === "number"}
|
||||
<input
|
||||
class="b3-text-field fn__flex-center fn__size200"
|
||||
id={key}
|
||||
type="number"
|
||||
bind:value={value}
|
||||
on:change={changed}
|
||||
/>
|
||||
{:else if type === "button"}
|
||||
<!-- Button Input -->
|
||||
<button
|
||||
class="b3-button b3-button--outline fn__flex-center fn__size200"
|
||||
id={key}
|
||||
on:click={click}
|
||||
>
|
||||
{button.label}
|
||||
</button>
|
||||
{:else if type === "select"}
|
||||
<!-- Dropdown select -->
|
||||
<select
|
||||
class="b3-select fn__flex-center fn__size200"
|
||||
id="iconPosition"
|
||||
bind:value={value}
|
||||
on:change={changed}
|
||||
>
|
||||
{#each Object.entries(options) as [value, text]}
|
||||
<option {value}>{text}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{:else if type == "slider"}
|
||||
<!-- Slider -->
|
||||
<div class="b3-tooltips b3-tooltips__n" aria-label={value}>
|
||||
<input
|
||||
class="b3-slider fn__size200"
|
||||
id="fontSize"
|
||||
min={slider.min}
|
||||
max={slider.max}
|
||||
step={slider.step}
|
||||
type="range"
|
||||
bind:value={value}
|
||||
on:change={changed}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
51
src/libs/item-wrap.svelte
Normal file
51
src/libs/item-wrap.svelte
Normal file
|
@ -0,0 +1,51 @@
|
|||
<!--
|
||||
Copyright (c) 2024 by frostime. All Rights Reserved.
|
||||
Author : frostime
|
||||
Date : 2024-06-01 20:03:50
|
||||
FilePath : /src/libs/setting-item-wrap.svelte
|
||||
LastEditTime : 2024-06-07 19:14:28
|
||||
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>
|
||||
<slot />
|
||||
</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>
|
|
@ -1,100 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
export let type: string; // Setting Type
|
||||
export let title: string; // Displayint Setting Title
|
||||
export let description: string; // Displaying Setting Text
|
||||
export let settingKey: string;
|
||||
export let settingValue: any;
|
||||
|
||||
//Optional
|
||||
export let placeholder: string = ""; // Use it if type is input
|
||||
export let options: { [key: string | number]: string } = {}; // Use it if type is select
|
||||
export let slider: {
|
||||
min: number;
|
||||
max: number;
|
||||
step: number;
|
||||
} = { min: 0, max: 100, step: 1 }; // Use it if type is slider
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
function clicked() {
|
||||
dispatch("clicked");
|
||||
}
|
||||
|
||||
function changed() {
|
||||
dispatch("changed", { key: settingKey, value: settingValue });
|
||||
}
|
||||
</script>
|
||||
|
||||
<label class="fn__flex b3-label">
|
||||
<div class="fn__flex-1">
|
||||
{title}
|
||||
<div class="b3-label__text">
|
||||
{@html description}
|
||||
</div>
|
||||
</div>
|
||||
<span class="fn__space" />
|
||||
<!-- <slot /> -->
|
||||
{#if type === "checkbox"}
|
||||
<!-- Checkbox -->
|
||||
<input
|
||||
class="b3-switch fn__flex-center"
|
||||
id={settingKey}
|
||||
type="checkbox"
|
||||
bind:checked={settingValue}
|
||||
on:change={changed}
|
||||
/>
|
||||
{:else if type === "textinput"}
|
||||
<!-- Text Input -->
|
||||
<input
|
||||
class="b3-text-field fn__flex-center fn__size200"
|
||||
id={settingKey}
|
||||
{placeholder}
|
||||
bind:value={settingValue}
|
||||
on:change={changed}
|
||||
/>
|
||||
{:else if type === "number"}
|
||||
<input
|
||||
class="b3-text-field fn__flex-center fn__size200"
|
||||
id={settingKey}
|
||||
type="number"
|
||||
bind:value={settingValue}
|
||||
on:change={changed}
|
||||
/>
|
||||
{:else if type === "button"}
|
||||
<!-- Button Input -->
|
||||
<button
|
||||
class="b3-button b3-button--outline fn__flex-center fn__size200"
|
||||
id={settingKey}
|
||||
on:click={clicked}
|
||||
>
|
||||
{settingValue}
|
||||
</button>
|
||||
{:else if type === "select"}
|
||||
<!-- Dropdown select -->
|
||||
<select
|
||||
class="b3-select fn__flex-center fn__size200"
|
||||
id="iconPosition"
|
||||
bind:value={settingValue}
|
||||
on:change={changed}
|
||||
>
|
||||
{#each Object.entries(options) as [value, text]}
|
||||
<option {value}>{text}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{:else if type == "slider"}
|
||||
<!-- Slider -->
|
||||
<div class="b3-tooltips b3-tooltips__n" aria-label={settingValue}>
|
||||
<input
|
||||
class="b3-slider fn__size200"
|
||||
id="fontSize"
|
||||
min={slider.min}
|
||||
max={slider.max}
|
||||
step={slider.step}
|
||||
type="range"
|
||||
bind:value={settingValue}
|
||||
on:change={changed}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</label>
|
|
@ -3,12 +3,13 @@
|
|||
Author : frostime
|
||||
Date : 2023-07-01 19:23:50
|
||||
FilePath : /src/libs/setting-panel.svelte
|
||||
LastEditTime : 2023-11-28 21:45:10
|
||||
LastEditTime : 2024-06-08 18:25:34
|
||||
Description :
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import SettingItem from "./setting-item.svelte";
|
||||
import ItemWrap from "./item-wrap.svelte";
|
||||
import InputItem from "./item-input.svelte";
|
||||
|
||||
export let group: string;
|
||||
export let settingItems: ISettingItem[];
|
||||
|
@ -30,17 +31,22 @@
|
|||
<div class="config__tab-container {fn__none}" data-name={group}>
|
||||
<slot />
|
||||
{#each settingItems as item (item.key)}
|
||||
<SettingItem
|
||||
type={item.type}
|
||||
<ItemWrap
|
||||
title={item.title}
|
||||
description={item.description}
|
||||
settingKey={item.key}
|
||||
settingValue={item.value}
|
||||
direction={item?.direction}
|
||||
>
|
||||
<InputItem
|
||||
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}
|
||||
/>
|
||||
</ItemWrap>
|
||||
{/each}
|
||||
</div>
|
|
@ -3,41 +3,112 @@
|
|||
* @Author : frostime
|
||||
* @Date : 2023-12-17 18:28:19
|
||||
* @FilePath : /src/libs/setting-utils.ts
|
||||
* @LastEditTime : 2024-01-15 20:45:16
|
||||
* @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, ISettingItem> = new Map();
|
||||
settings: Map<string, ISettingUtilsItem> = new Map();
|
||||
elements: Map<string, HTMLElement> = new Map();
|
||||
|
||||
constructor(plugin: Plugin, name?: string, callback?: (data: any) => void, width?: string, height?: string) {
|
||||
this.name = name ?? 'settings';
|
||||
this.plugin = plugin;
|
||||
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: width,
|
||||
height: height,
|
||||
width: args.width,
|
||||
height: args.height,
|
||||
confirmCallback: () => {
|
||||
for (let key of this.settings.keys()) {
|
||||
this.updateValueFromElement(key);
|
||||
}
|
||||
let data = this.dump();
|
||||
if (callback !== undefined) {
|
||||
callback(data);
|
||||
} else {
|
||||
this.plugin.data[this.name] = data;
|
||||
this.save();
|
||||
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);
|
||||
}
|
||||
|
@ -57,15 +128,15 @@ export class SettingUtils {
|
|||
return data;
|
||||
}
|
||||
|
||||
async save() {
|
||||
let data = this.dump();
|
||||
async save(data?: any) {
|
||||
data = data ?? this.dump();
|
||||
await this.plugin.saveData(this.file, this.dump());
|
||||
console.debug('Save config:', data);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get setting item value
|
||||
* read the data after saving
|
||||
* @param key key name
|
||||
* @returns setting item value
|
||||
*/
|
||||
|
@ -73,6 +144,12 @@ export class SettingUtils {
|
|||
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) {
|
||||
|
@ -89,6 +166,74 @@ export class SettingUtils {
|
|||
window.location.reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -102,9 +247,59 @@ export class SettingUtils {
|
|||
return data;
|
||||
}
|
||||
|
||||
addItem(item: ISettingItem) {
|
||||
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');
|
||||
|
@ -112,6 +307,7 @@ export class SettingUtils {
|
|||
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');
|
||||
|
@ -125,6 +321,7 @@ export class SettingUtils {
|
|||
selectElement.appendChild(optionElement);
|
||||
}
|
||||
selectElement.value = item.value;
|
||||
selectElement.onchange = item.action?.callback ?? (() => { });
|
||||
itemElement = selectElement;
|
||||
break;
|
||||
case 'slider':
|
||||
|
@ -138,6 +335,7 @@ export class SettingUtils {
|
|||
sliderElement.value = item.value;
|
||||
sliderElement.onchange = () => {
|
||||
sliderElement.ariaLabel = sliderElement.value;
|
||||
item.action?.callback();
|
||||
}
|
||||
itemElement = sliderElement;
|
||||
break;
|
||||
|
@ -145,12 +343,15 @@ export class SettingUtils {
|
|||
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':
|
||||
|
@ -159,12 +360,13 @@ export class SettingUtils {
|
|||
numberElement.className = 'b3-text-field fn__flex-center fn__size200';
|
||||
numberElement.value = item.value;
|
||||
itemElement = numberElement;
|
||||
numberElement.addEventListener('keydown', preventEnterConfirm);
|
||||
break;
|
||||
case 'button':
|
||||
let buttonElement: HTMLButtonElement = document.createElement('button');
|
||||
buttonElement.className = "b3-button b3-button--outline fn__flex-center fn__size200";
|
||||
buttonElement.innerText = item.button?.label ?? 'Button';
|
||||
buttonElement.onclick = item.button?.callback ?? (() => {});
|
||||
buttonElement.onclick = item.button?.callback ?? (() => { });
|
||||
itemElement = buttonElement;
|
||||
break;
|
||||
case 'hint':
|
||||
|
@ -173,84 +375,31 @@ export class SettingUtils {
|
|||
itemElement = hintElement;
|
||||
break;
|
||||
}
|
||||
this.elements.set(item.key, itemElement);
|
||||
this.plugin.setting.addItem({
|
||||
title: item.title,
|
||||
description: item?.description,
|
||||
createActionElement: () => {
|
||||
let element = this.getElement(item.key);
|
||||
return element;
|
||||
}
|
||||
})
|
||||
return itemElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* return the setting element
|
||||
* @param key key name
|
||||
* @returns element
|
||||
*/
|
||||
getElement(key: string) {
|
||||
let item = this.settings.get(key);
|
||||
// let item = this.settings.get(key);
|
||||
let element = this.elements.get(key) as any;
|
||||
switch (item.type) {
|
||||
case 'checkbox':
|
||||
element.checked = item.value;
|
||||
break;
|
||||
case 'select':
|
||||
element.value = item.value;
|
||||
break;
|
||||
case 'slider':
|
||||
element.value = item.value;
|
||||
element.ariaLabel = item.value;
|
||||
break;
|
||||
case 'textinput':
|
||||
element.value = item.value;
|
||||
break;
|
||||
case 'textarea':
|
||||
element.value = item.value;
|
||||
break;
|
||||
}
|
||||
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;
|
||||
// console.debug(element, element?.value);
|
||||
switch (item.type) {
|
||||
case 'checkbox':
|
||||
item.value = element.checked;
|
||||
break;
|
||||
case 'select':
|
||||
item.value = element.value;
|
||||
break;
|
||||
case 'slider':
|
||||
item.value = element.value;
|
||||
break;
|
||||
case 'textinput':
|
||||
item.value = element.value;
|
||||
break;
|
||||
case 'textarea':
|
||||
item.value = element.value;
|
||||
break;
|
||||
}
|
||||
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;
|
||||
switch (item.type) {
|
||||
case 'checkbox':
|
||||
element.checked = item.value;
|
||||
break;
|
||||
case 'select':
|
||||
element.value = item.value;
|
||||
break;
|
||||
case 'slider':
|
||||
element.value = item.value;
|
||||
break;
|
||||
case 'textinput':
|
||||
element.value = item.value;
|
||||
break;
|
||||
case 'textarea':
|
||||
element.value = item.value;
|
||||
break;
|
||||
item.setEleVal(element, item.value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { showMessage } from "siyuan";
|
||||
import SettingPanel from "./libs/setting-panel.svelte";
|
||||
|
||||
let groups: string[] = ["🌈 Default"];
|
||||
|
@ -20,6 +21,15 @@
|
|||
value: 'This is a text',
|
||||
placeholder: 'placeholder'
|
||||
},
|
||||
{
|
||||
type: 'textarea',
|
||||
title: 'textarea',
|
||||
description: 'This is a textarea',
|
||||
key: 'b2',
|
||||
value: 'This is a textarea',
|
||||
placeholder: 'placeholder',
|
||||
direction: 'row'
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
title: 'select',
|
||||
|
@ -43,6 +53,19 @@
|
|||
max: 100,
|
||||
step: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
title: 'button',
|
||||
description: 'This is a button',
|
||||
key: 'e',
|
||||
value: 'Click Button',
|
||||
button: {
|
||||
label: 'Click Me',
|
||||
callback: () => {
|
||||
showMessage('Hello, world!');
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -56,6 +79,8 @@
|
|||
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>
|
||||
|
@ -63,6 +88,7 @@
|
|||
<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}
|
||||
|
@ -82,6 +108,7 @@
|
|||
settingItems={SettingItems}
|
||||
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.
|
||||
|
|
36
src/types/index.d.ts
vendored
36
src/types/index.d.ts
vendored
|
@ -1,10 +1,13 @@
|
|||
/**
|
||||
* Copyright (c) 2023 frostime. All rights reserved.
|
||||
/*
|
||||
* Copyright (c) 2024 by frostime. All Rights Reserved.
|
||||
* @Author : frostime
|
||||
* @Date : 2023-08-15 10:28:10
|
||||
* @FilePath : /src/types/index.d.ts
|
||||
* @LastEditTime : 2024-06-08 20:50:53
|
||||
* @Description : Frequently used data structures in SiYuan
|
||||
*/
|
||||
|
||||
/**
|
||||
* Frequently used data structures in SiYuan
|
||||
*/
|
||||
|
||||
type DocumentId = string;
|
||||
type BlockId = string;
|
||||
type NotebookId = string;
|
||||
|
@ -28,7 +31,25 @@ type NotebookConf = {
|
|||
dailyNoteTemplatePath: string;
|
||||
}
|
||||
|
||||
type BlockType = "d" | "s" | "h" | "t" | "i" | "p" | "f" | "audio" | "video" | "other";
|
||||
type BlockType =
|
||||
| 'd'
|
||||
| 'p'
|
||||
| 'query_embed'
|
||||
| 'l'
|
||||
| 'i'
|
||||
| 'h'
|
||||
| 'iframe'
|
||||
| 'tb'
|
||||
| 'b'
|
||||
| 's'
|
||||
| 'c'
|
||||
| 'widget'
|
||||
| 't'
|
||||
| 'html'
|
||||
| 'm'
|
||||
| 'av'
|
||||
| 'audio';
|
||||
|
||||
|
||||
type BlockSubType = "d1" | "d2" | "s1" | "s2" | "s3" | "t1" | "t2" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "table" | "task" | "toggle" | "latex" | "quote" | "html" | "code" | "footnote" | "cite" | "collection" | "bookmark" | "attachment" | "comment" | "mindmap" | "spreadsheet" | "calendar" | "image" | "audio" | "video" | "other";
|
||||
|
||||
|
@ -70,6 +91,7 @@ type doOperation = {
|
|||
|
||||
interface Window {
|
||||
siyuan: {
|
||||
config: any;
|
||||
notebooks: any;
|
||||
menus: any;
|
||||
dialogs: any;
|
||||
|
@ -78,5 +100,7 @@ interface Window {
|
|||
user: any;
|
||||
ws: any;
|
||||
languages: any;
|
||||
emojis: any;
|
||||
};
|
||||
Lute: any;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,10 +7,12 @@ import { svelte } from "@sveltejs/vite-plugin-svelte"
|
|||
import zipPack from "vite-plugin-zip-pack";
|
||||
import fg from 'fast-glob';
|
||||
|
||||
import vitePluginYamlI18n from './yaml-plugin';
|
||||
|
||||
const args = minimist(process.argv.slice(2))
|
||||
const isWatch = args.watch || args.w || false
|
||||
const devDistDir = "./dev"
|
||||
const distDir = isWatch ? devDistDir : "./dist"
|
||||
const devDistDir = "dev"
|
||||
const distDir = isWatch ? devDistDir : "dist"
|
||||
|
||||
console.log("isWatch=>", isWatch)
|
||||
console.log("distDir=>", distDir)
|
||||
|
@ -25,6 +27,11 @@ export default defineConfig({
|
|||
plugins: [
|
||||
svelte(),
|
||||
|
||||
vitePluginYamlI18n({
|
||||
inDir: 'public/i18n',
|
||||
outDir: `${distDir}/i18n`
|
||||
}),
|
||||
|
||||
viteStaticCopy({
|
||||
targets: [
|
||||
{
|
||||
|
|
60
yaml-plugin.js
Normal file
60
yaml-plugin.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) 2024 by frostime. All Rights Reserved.
|
||||
* @Author : frostime
|
||||
* @Date : 2024-04-05 21:27:55
|
||||
* @FilePath : /yaml-plugin.js
|
||||
* @LastEditTime : 2024-04-05 22:53:34
|
||||
* @Description : 去妮玛的 json 格式,我就是要用 yaml 写 i18n
|
||||
*/
|
||||
// plugins/vite-plugin-parse-yaml.js
|
||||
import fs from 'fs';
|
||||
import yaml from 'js-yaml';
|
||||
import { resolve } from 'path';
|
||||
|
||||
export default function vitePluginYamlI18n(options = {}) {
|
||||
// Default options with a fallback
|
||||
const DefaultOptions = {
|
||||
inDir: 'src/i18n',
|
||||
outDir: 'dist/i18n',
|
||||
};
|
||||
|
||||
const finalOptions = { ...DefaultOptions, ...options };
|
||||
|
||||
return {
|
||||
name: 'vite-plugin-yaml-i18n',
|
||||
buildStart() {
|
||||
console.log('🌈 Parse I18n: YAML to JSON..');
|
||||
const inDir = finalOptions.inDir;
|
||||
const outDir = finalOptions.outDir
|
||||
|
||||
if (!fs.existsSync(outDir)) {
|
||||
fs.mkdirSync(outDir, { recursive: true });
|
||||
}
|
||||
|
||||
//Parse yaml file, output to json
|
||||
const files = fs.readdirSync(inDir);
|
||||
for (const file of files) {
|
||||
if (file.endsWith('.yaml') || file.endsWith('.yml')) {
|
||||
console.log(`-- Parsing ${file}`)
|
||||
//检查是否有同名的json文件
|
||||
const jsonFile = file.replace(/\.(yaml|yml)$/, '.json');
|
||||
if (files.includes(jsonFile)) {
|
||||
console.log(`---- File ${jsonFile} already exists, skipping...`);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const filePath = resolve(inDir, file);
|
||||
const fileContents = fs.readFileSync(filePath, 'utf8');
|
||||
const parsed = yaml.load(fileContents);
|
||||
const jsonContent = JSON.stringify(parsed, null, 2);
|
||||
const outputFilePath = resolve(outDir, file.replace(/\.(yaml|yml)$/, '.json'));
|
||||
console.log(`---- Writing to ${outputFilePath}`);
|
||||
fs.writeFileSync(outputFilePath, jsonContent);
|
||||
} catch (error) {
|
||||
this.error(`---- Error parsing YAML file ${file}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue