merge new changes

This commit is contained in:
zxkmm 2024-06-23 11:01:17 +08:00
commit 4eaa160ab7
25 changed files with 1136 additions and 356 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",
},
}

1
.gitignore vendored
View file

@ -2,6 +2,7 @@
.vscode
.DS_Store
pnpm-lock.yaml
package-lock.json
package.zip
node_modules
dev

View file

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

View file

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

View file

@ -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 创建日记),请手动为文档添加该属性

View file

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

View file

@ -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
View 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
View 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);

View file

@ -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) {

View file

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

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

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

View file

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

View file

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

View file

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

View file

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

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

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

@ -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
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}`);
}
}
}
},
};
}