Merge pull request #11 from siyuan-note/dev

Update to v0.1.3
This commit is contained in:
Frostime 2023-06-03 16:56:54 +08:00 committed by GitHub
commit e849287222
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 825 additions and 559 deletions

View file

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

1
.gitignore vendored
View file

@ -7,3 +7,4 @@ node_modules
dev dev
dist dist
build build
tmp

View file

@ -3,7 +3,8 @@
[中文版](./README_zh_CN.md) [中文版](./README_zh_CN.md)
> Consistent with [siyuan/plugin-sample](https://github.com/siyuan-note/plugin-sample) [v0.0.6](https://github.com/siyuan-note/plugin-sample/tree/v0.0.6). > Consistent with [siyuan/plugin-sample](https://github.com/siyuan-note/plugin-sample) [v0.1.3](https://github.com/siyuan-note/plugin-sample/tree/v0.1.3).
1. Using vite for packaging 1. Using vite for packaging
@ -62,7 +63,6 @@ complete the following tasks:
* Text used in the plugin, such as button text and tooltips * Text used in the plugin, such as button text and tooltips
* src/i18n/*.json language configuration files * src/i18n/*.json language configuration files
* Use `this.i18.key` to get the text in the code * Use `this.i18.key` to get the text in the code
* Finally, declare the language supported by the plugin in the `i18n` field in plugin.json
It is recommended that the plugin supports at least English and Simplified Chinese, so that more people can use it more It is recommended that the plugin supports at least English and Simplified Chinese, so that more people can use it more
conveniently. conveniently.
@ -74,8 +74,10 @@ conveniently.
"name": "plugin-sample-vite-svelte", "name": "plugin-sample-vite-svelte",
"author": "frostime", "author": "frostime",
"url": "https://github.com/siyuan-note/plugin-sample-vite-svelte", "url": "https://github.com/siyuan-note/plugin-sample-vite-svelte",
"version": "0.0.1", "version": "0.1.3",
"minAppVersion": "2.9.0", "minAppVersion": "2.8.8",
"backends": ["windows", "linux", "darwin"],
"frontends": ["desktop"],
"displayName": { "displayName": {
"en_US": "Plugin sample with vite and svelte", "en_US": "Plugin sample with vite and svelte",
"zh_CN": "插件样例 vite + svelte 版" "zh_CN": "插件样例 vite + svelte 版"
@ -89,8 +91,11 @@ conveniently.
"zh_CN": "README.md" "zh_CN": "README.md"
}, },
"funding": { "funding": {
"openCollective": "",
"patreon": "",
"github": "",
"custom": [ "custom": [
"" "https://ld246.com/sponsor"
] ]
} }
} }
@ -102,6 +107,21 @@ conveniently.
* `url`: Plugin repo URL * `url`: Plugin repo URL
* `version`: Plugin version number, it is recommended to follow the [semver](https://semver.org/) specification * `version`: Plugin version number, it is recommended to follow the [semver](https://semver.org/) specification
* `minAppVersion`: Minimum version number of SiYuan required to use this plugin * `minAppVersion`: Minimum version number of SiYuan required to use this plugin
* `backends`: Backend environment required by the plugin, optional values are `windows`, `linux`, `darwin`, `docker`, `android`, `ios` and `all`
* `windows`: Windows desktop
* `linux`: Linux desktop
* `darwin`: macOS desktop
* `docker`: Docker
* `android`: Android APP
* `ios`: iOS APP
* `all`: All environments
* `frontends`: Frontend environment required by the plugin, optional values are `desktop`, `desktop-window`, `mobile`, `browser-desktop`, `browser-mobile` and `all`
* `desktop`: Desktop
* `desktop-window`: Desktop window converted from tab
* `mobile`: Mobile APP
* `browser-desktop`: Desktop browser
* `browser-mobile`: Mobile browser
* `all`: All environments
* `displayName`: Template display name, mainly used for display in the marketplace list, supports multiple languages * `displayName`: Template display name, mainly used for display in the marketplace list, supports multiple languages
* `default`: Default language, must exist * `default`: Default language, must exist
* `zh_CN`, `en_US` and other languages: optional, it is recommended to provide at least Chinese and English * `zh_CN`, `en_US` and other languages: optional, it is recommended to provide at least Chinese and English
@ -122,13 +142,13 @@ conveniently.
No matter which method is used to compile and package, we finally need to generate a package.zip, which contains at No matter which method is used to compile and package, we finally need to generate a package.zip, which contains at
least the following files: least the following files:
* icon.png * i18n/*
* icon.png (160*160)
* index.css
* index.js * index.js
* plugin.json * plugin.json
* preview.png * preview.png (1024*768)
* README*.md * README*.md
* index.css (optional)
* i18n/* (optional)
## List on the marketplace ## List on the marketplace

View file

@ -4,7 +4,7 @@
[English](./README.md) [English](./README.md)
> 本例和 [siyuan/plugin-sample](https://github.com/siyuan-note/plugin-sample) [v0.0.6](https://github.com/siyuan-note/plugin-sample/tree/v0.0.6) 基本保持一致。 > 本例和 [siyuan/plugin-sample](https://github.com/siyuan-note/plugin-sample) [v0.1.3](https://github.com/siyuan-note/plugin-sample/tree/v0.1.3) 基本保持一致。
1. 使用 vite 打包 1. 使用 vite 打包
2. 使用符号链接、而不是把项目放到插件目录下的模式进行开发 2. 使用符号链接、而不是把项目放到插件目录下的模式进行开发
@ -59,7 +59,6 @@
* 插件中使用的文本,比如按钮文字和提示信息 * 插件中使用的文本,比如按钮文字和提示信息
* src/i18n/*.json 语言配置文件 * src/i18n/*.json 语言配置文件
* 代码中使用 `this.i18.key` 获取文本 * 代码中使用 `this.i18.key` 获取文本
* 最后在 plugin.json 中的 `i18n` 字段中声明该插件支持的语言
建议插件至少支持英文和简体中文,这样可以方便更多人使用。 建议插件至少支持英文和简体中文,这样可以方便更多人使用。
@ -70,8 +69,10 @@
"name": "plugin-sample-vite-svelte", "name": "plugin-sample-vite-svelte",
"author": "frostime", "author": "frostime",
"url": "https://github.com/siyuan-note/plugin-sample-vite-svelte", "url": "https://github.com/siyuan-note/plugin-sample-vite-svelte",
"version": "0.0.1", "version": "0.1.3",
"minAppVersion": "2.9.0", "minAppVersion": "2.8.8",
"backends": ["windows", "linux", "darwin"],
"frontends": ["desktop"],
"displayName": { "displayName": {
"en_US": "Plugin sample with vite and svelte", "en_US": "Plugin sample with vite and svelte",
"zh_CN": "插件样例 vite + svelte 版" "zh_CN": "插件样例 vite + svelte 版"
@ -85,8 +86,11 @@
"zh_CN": "README.md" "zh_CN": "README.md"
}, },
"funding": { "funding": {
"openCollective": "",
"patreon": "",
"github": "",
"custom": [ "custom": [
"" "https://ld246.com/sponsor"
] ]
} }
} }
@ -97,32 +101,47 @@
* `url`:插件仓库地址 * `url`:插件仓库地址
* `version`:插件版本号,建议遵循 [semver](https://semver.org/lang/zh-CN/) 规范 * `version`:插件版本号,建议遵循 [semver](https://semver.org/lang/zh-CN/) 规范
* `minAppVersion`:插件支持的最低思源笔记版本号 * `minAppVersion`:插件支持的最低思源笔记版本号
* `backends`:插件需要的后端环境,可选值为 `windows`, `linux`, `darwin`, `docker`, `android`, `ios` and `all`
* `windows`Windows 桌面端
* `linux`Linux 桌面端
* `darwin`macOS 桌面端
* `docker`Docker 端
* `android`Android 端
* `ios`iOS 端
* `all`:所有环境
* `frontends`:插件需要的前端环境,可选值为 `desktop`, `desktop-window`, `mobile`, `browser-desktop`, `browser-mobile` and `all`
* `desktop`:桌面端
* `desktop-window`:桌面端页签转换的独立窗口
* `mobile`:移动端
* `browser-desktop`:桌面端浏览器
* `browser-mobile`:移动端浏览器
* `all`:所有环境
* `displayName`:模板显示名称,主要用于模板集市列表中显示,支持多语言 * `displayName`:模板显示名称,主要用于模板集市列表中显示,支持多语言
* `default`:默认语言,必须存在 * `default`:默认语言,必须存在
* `zh_CN``en_US` 等其他语言:可选,建议至少提供中文和英文 * `zh_CN``en_US` 等其他语言:可选,建议至少提供中文和英文
* `description`:插件描述,主要用于插件集市列表中显示,支持多语言 * `description`:插件描述,主要用于插件集市列表中显示,支持多语言
* `default`:默认语言,必须存在 * `default`:默认语言,必须存在
* `zh_CN``en_US` 等其他语言:可选,建议至少提供中文和英文 * `zh_CN``en_US` 等其他语言:可选,建议至少提供中文和英文
* `readme`:自述文件名,主要用于插件集市详情页中显示,支持多语言 * `readme`:自述文件名,主要用于插件集市详情页中显示,支持多语言
* `default`:默认语言,必须存在 * `default`:默认语言,必须存在
* `zh_CN``en_US` 等其他语言:可选,建议至少提供中文和英文 * `zh_CN``en_US` 等其他语言:可选,建议至少提供中文和英文
* `funding`:插件赞助信息 * `funding`:插件赞助信息
* `openCollective`Open Collective 名称 * `openCollective`Open Collective 名称
* `patreon`Patreon 名称 * `patreon`Patreon 名称
* `github`GitHub 登录名 * `github`GitHub 登录名
* `custom`:自定义赞助链接列表 * `custom`:自定义赞助链接列表
## 打包 ## 打包
无论使用何种方式编译打包,我们最终需要生成一个 package.zip它至少包含如下文件 无论使用何种方式编译打包,我们最终需要生成一个 package.zip它至少包含如下文件
* icon.png * i18n/*
* icon.png (160*160)
* index.css
* index.js * index.js
* plugin.json * plugin.json
* preview.png * preview.png (1024*768)
* README*.md * README*.md
* index.css (optional)
* i18n/* (optional)
## 上架集市 ## 上架集市

View file

@ -21,7 +21,7 @@
"minimist": "^1.2.8", "minimist": "^1.2.8",
"rollup-plugin-livereload": "^2.0.5", "rollup-plugin-livereload": "^2.0.5",
"sass": "^1.62.1", "sass": "^1.62.1",
"siyuan": "^0.7.1", "siyuan": "^0.7.2",
"svelte": "^3.57.0", "svelte": "^3.57.0",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "^5.0.4", "typescript": "^5.0.4",

View file

@ -2,8 +2,10 @@
"name": "plugin-sample-vite-svelte", "name": "plugin-sample-vite-svelte",
"author": "frostime", "author": "frostime",
"url": "https://github.com/siyuan-note/plugin-sample-vite-svelte", "url": "https://github.com/siyuan-note/plugin-sample-vite-svelte",
"version": "0.0.6", "version": "0.1.3",
"minAppVersion": "2.9.0", "minAppVersion": "2.9.0",
"backends": ["windows", "linux", "darwin"],
"frontends": ["desktop"],
"displayName": { "displayName": {
"en_US": "Plugin sample with vite and svelte", "en_US": "Plugin sample with vite and svelte",
"zh_CN": "插件样例 vite + svelte 版" "zh_CN": "插件样例 vite + svelte 版"

View file

@ -5,6 +5,9 @@
"byeMenu": "Bye, Menu!", "byeMenu": "Bye, Menu!",
"helloPlugin": "Hello, Plugin!", "helloPlugin": "Hello, Plugin!",
"byePlugin": "Bye, Plugin!", "byePlugin": "Bye, Plugin!",
"showDialog": "Show dialog",
"removedData": "Data deleted",
"confirmRemove": "Confirm to delete the data in ${name}?",
"name": "SiYuan", "name": "SiYuan",
"hello": { "hello": {
"makesure": "Before using this template, please read the <a href=\"https://github.com/siyuan-note/plugin-sample\">offical sample</a>, make sure that you've known about the pipeline for plugin developing." "makesure": "Before using this template, please read the <a href=\"https://github.com/siyuan-note/plugin-sample\">offical sample</a>, make sure that you've known about the pipeline for plugin developing."

View file

@ -5,6 +5,9 @@
"byeMenu": "再见,菜单!", "byeMenu": "再见,菜单!",
"helloPlugin": "你好,插件!", "helloPlugin": "你好,插件!",
"byePlugin": "再见,插件!", "byePlugin": "再见,插件!",
"showDialog": "弹出一个对话框",
"removedData": "数据已删除",
"confirmRemove": "确认删除 ${name} 中的数据?",
"name": "思源", "name": "思源",
"hello": { "hello": {
"makesure": "使用这个模板之前,请阅读<a href=\"https://github.com/siyuan-note/plugin-sample\">官方教程</a>, 确保自己已经理解了插件的基本开发流程。" "makesure": "使用这个模板之前,请阅读<a href=\"https://github.com/siyuan-note/plugin-sample\">官方教程</a>, 确保自己已经理解了插件的基本开发流程。"

View file

@ -1,4 +1,15 @@
import { Plugin, showMessage, confirm, Dialog, Menu, isMobile, openTab, adaptHotkey } from "siyuan"; import {
Plugin,
showMessage,
confirm,
Dialog,
Menu,
openTab,
adaptHotkey,
getFrontend,
getBackend,
IModel
} from "siyuan";
import "./index.scss"; import "./index.scss";
import HelloExample from "./hello.svelte"; import HelloExample from "./hello.svelte";
@ -8,26 +19,61 @@ const STORAGE_NAME = "menu-config";
const TAB_TYPE = "custom_tab"; const TAB_TYPE = "custom_tab";
const DOCK_TYPE = "dock_tab"; const DOCK_TYPE = "dock_tab";
export default class SamplePlugin extends Plugin { export default class PluginSample extends Plugin {
private customTab: () => any; private customTab: () => IModel;
private isMobile: boolean;
async onload() { async onload() {
showMessage("Hello SiYuan Plugin");
this.data[STORAGE_NAME] = {readonlyText: "Readonly"}; this.data[STORAGE_NAME] = {readonlyText: "Readonly"};
console.log("loading plugin-sample", this.i18n);
const frontEnd = getFrontend();
this.isMobile = frontEnd === "mobile" || frontEnd === "browser-mobile";
// 图标的制作参见帮助文档
this.addIcons(`<symbol id="iconFace" viewBox="0 0 32 32">
<path d="M13.667 17.333c0 0.92-0.747 1.667-1.667 1.667s-1.667-0.747-1.667-1.667 0.747-1.667 1.667-1.667 1.667 0.747 1.667 1.667zM20 15.667c-0.92 0-1.667 0.747-1.667 1.667s0.747 1.667 1.667 1.667 1.667-0.747 1.667-1.667-0.747-1.667-1.667-1.667zM29.333 16c0 7.36-5.973 13.333-13.333 13.333s-13.333-5.973-13.333-13.333 5.973-13.333 13.333-13.333 13.333 5.973 13.333 13.333zM14.213 5.493c1.867 3.093 5.253 5.173 9.12 5.173 0.613 0 1.213-0.067 1.787-0.16-1.867-3.093-5.253-5.173-9.12-5.173-0.613 0-1.213 0.067-1.787 0.16zM5.893 12.627c2.28-1.293 4.040-3.4 4.88-5.92-2.28 1.293-4.040 3.4-4.88 5.92zM26.667 16c0-1.040-0.16-2.040-0.44-2.987-0.933 0.2-1.893 0.32-2.893 0.32-4.173 0-7.893-1.92-10.347-4.92-1.4 3.413-4.187 6.093-7.653 7.4 0.013 0.053 0 0.12 0 0.187 0 5.88 4.787 10.667 10.667 10.667s10.667-4.787 10.667-10.667z"></path>
</symbol>
<symbol id="iconSaving" viewBox="0 0 32 32">
<path d="M20 13.333c0-0.733 0.6-1.333 1.333-1.333s1.333 0.6 1.333 1.333c0 0.733-0.6 1.333-1.333 1.333s-1.333-0.6-1.333-1.333zM10.667 12h6.667v-2.667h-6.667v2.667zM29.333 10v9.293l-3.76 1.253-2.24 7.453h-7.333v-2.667h-2.667v2.667h-7.333c0 0-3.333-11.28-3.333-15.333s3.28-7.333 7.333-7.333h6.667c1.213-1.613 3.147-2.667 5.333-2.667 1.107 0 2 0.893 2 2 0 0.28-0.053 0.533-0.16 0.773-0.187 0.453-0.347 0.973-0.427 1.533l3.027 3.027h2.893zM26.667 12.667h-1.333l-4.667-4.667c0-0.867 0.12-1.72 0.347-2.547-1.293 0.333-2.347 1.293-2.787 2.547h-8.227c-2.573 0-4.667 2.093-4.667 4.667 0 2.507 1.627 8.867 2.68 12.667h2.653v-2.667h8v2.667h2.68l2.067-6.867 3.253-1.093v-4.707z"></path>
</symbol>`);
const topBarElement = this.addTopBar({ const topBarElement = this.addTopBar({
icon: "iconEmoji", icon: "iconFace",
title: this.i18n.addTopBarIcon, title: this.i18n.addTopBarIcon,
position: "left", position: "right",
callback: () => { callback: () => {
this.addMenu(topBarElement.getBoundingClientRect()); let rect = topBarElement.getBoundingClientRect();
// 如果被隐藏,则使用更多按钮
if (rect.width === 0) {
rect = document.querySelector("#barMore").getBoundingClientRect();
}
this.addMenu(rect);
} }
}); });
let div = document.createElement("div"); const statusIconTemp = document.createElement("template");
statusIconTemp.innerHTML = `<div class="toolbar__item b3-tooltips b3-tooltips__w" aria-label="Remove plugin-sample Data">
<svg>
<use xlink:href="#iconTrashcan"></use>
</svg>
</div>`;
statusIconTemp.content.firstElementChild.addEventListener("click", () => {
confirm("⚠️", this.i18n.confirmRemove.replace("${name}", this.name), () => {
this.removeData(STORAGE_NAME).then(() => {
this.data[STORAGE_NAME] = {readonlyText: "Readonly"};
showMessage(`[${this.name}]: ${this.i18n.removedData}`);
});
});
});
this.addStatusBar({
element: statusIconTemp.content.firstElementChild as HTMLElement,
});
let tabDiv = document.createElement("div");
new HelloExample({ new HelloExample({
target: div, target: tabDiv,
props: { props: {
name: this.i18n.name, name: this.i18n.name,
i18n: this.i18n.hello i18n: this.i18n.hello
@ -36,7 +82,7 @@ export default class SamplePlugin extends Plugin {
this.customTab = this.addTab({ this.customTab = this.addTab({
type: TAB_TYPE, type: TAB_TYPE,
init() { init() {
this.element.appendChild(div); this.element.appendChild(tabDiv);
console.log(this.element); console.log(this.element);
}, },
destroy() { destroy() {
@ -44,11 +90,19 @@ export default class SamplePlugin extends Plugin {
} }
}); });
this.addCommand({
langKey: "showDialog",
hotkey: "⇧⌘M",
callback: () => {
this.showDialog();
}
});
this.addDock({ this.addDock({
config: { config: {
position: "LeftBottom", position: "LeftBottom",
size: {width: 200, height: 0}, size: {width: 200, height: 0},
icon: "iconEmoji", icon: "iconSaving",
title: "Custom Dock", title: "Custom Dock",
}, },
data: { data: {
@ -75,10 +129,12 @@ export default class SamplePlugin extends Plugin {
} }
}); });
console.log(this.i18n.helloPlugin);
} }
onLayoutReady() { onLayoutReady() {
this.loadData(STORAGE_NAME); this.loadData(STORAGE_NAME);
console.log(`frontend: ${getFrontend()}; backend: ${getBackend()}`);
} }
onunload() { onunload() {
@ -87,160 +143,6 @@ export default class SamplePlugin extends Plugin {
console.log("onunload"); console.log("onunload");
} }
private wsEvent({ detail }: any) {
console.log(detail);
}
private blockIconEvent({detail}: any) {
console.log(detail);
detail.menu.addSeparator(0);
const ids: string[] = [];
detail.blockElements.forEach((item: HTMLElement) => {
ids.push(item.getAttribute("data-node-id"));
});
detail.menu.addItem({
index: 1,
iconHTML: "",
type: "readonly",
label: "IDs<br>" + ids.join("<br>"),
});
}
private async addMenu(rect: DOMRect) {
const menu = new Menu("topBarSample", () => {
console.log(this.i18n.byeMenu);
});
menu.addItem({
icon: "iconHelp",
label: "Confirm",
click() {
confirm("Confirm", "Is this a confirm?", () => {
showMessage("confirm");
}, () => {
showMessage("cancel");
});
}
});
menu.addItem({
icon: "iconFeedback",
label: "Message",
click: () => {
showMessage(this.i18n.helloPlugin);
}
});
menu.addItem({
icon: "iconInfo",
label: "Dialog",
click: () => this.openHelloInDialog()
});
menu.addItem({
icon: "iconLayoutBottom",
label: "Open Tab",
click: () => {
openTab({
custom: {
icon: "iconEmoji",
title: "Custom Tab",
data: {
text: "This is my custom tab",
},
fn: this.customTab
},
});
}
});
menu.addItem({
icon: "iconLayout",
label: "Open Float Layer(open help)",
click: () => {
this.addFloatLayer({
ids: ["20230523173319-xj1l3qu", "20230523173321-55o0w2n"],
defIds: ["20230523173323-imgm9tp", "20230523173324-cxu98t3"],
x: window.innerWidth - 768 - 120,
y: 32
});
}
});
menu.addItem({
icon: "iconTrashcan",
label: "Remove Data",
click: () => {
this.removeData(STORAGE_NAME).then(() => {
this.data[STORAGE_NAME] = {readonlyText: "Readonly"};
});
}
});
menu.addItem({
icon: "iconScrollHoriz",
label: "Event Bus",
type: "submenu",
submenu: [{
icon: "iconSelect",
label: "On ws-main",
click: () => {
this.eventBus.on("ws-main", this.wsEvent);
}
}, {
icon: "iconClose",
label: "Off ws-main",
click: () => {
this.eventBus.off("ws-main", this.wsEvent);
}
}, {
icon: "iconSelect",
label: "On click-blockicon",
click: () => {
this.eventBus.on("click-blockicon", this.blockIconEvent);
}
}, {
icon: "iconClose",
label: "Off click-blockicon",
click: () => {
this.eventBus.off("click-blockicon", this.blockIconEvent);
}
}, {
icon: "iconSelect",
label: "On click-pdf",
click: () => {
this.eventBus.on("click-pdf", this.wsEvent);
}
}, {
icon: "iconClose",
label: "Off click-pdf",
click: () => {
this.eventBus.off("click-pdf", this.wsEvent);
}
}, {
icon: "iconSelect",
label: "On click-editorcontent",
click: () => {
this.eventBus.on("click-editorcontent", this.wsEvent);
}
}, {
icon: "iconClose",
label: "Off click-editorcontent",
click: () => {
this.eventBus.off("click-editorcontent", this.wsEvent);
}
}]
});
menu.addSeparator();
menu.addItem({
icon: "iconSparkles",
label: this.data[STORAGE_NAME] || "Readonly",
type: "readonly",
});
if (isMobile()) {
menu.fullscreen();
} else {
menu.open({
x: rect.right,
y: rect.bottom,
isLeft: true,
});
}
}
openSetting(): void { openSetting(): void {
let dialog = new Dialog({ let dialog = new Dialog({
title: "SettingPannel", title: "SettingPannel",
@ -257,13 +159,30 @@ export default class SamplePlugin extends Plugin {
}); });
} }
private openHelloInDialog() { private eventBusLog({detail}: any) {
console.log(detail);
}
private blockIconEvent({detail}: any) {
const ids: string[] = [];
detail.blockElements.forEach((item: HTMLElement) => {
ids.push(item.getAttribute("data-node-id"));
});
detail.menu.addItem({
iconHTML: "",
type: "readonly",
label: "IDs<br>" + ids.join("<br>"),
});
}
private showDialog() {
let dialog = new Dialog({ let dialog = new Dialog({
title: "Hello World", title: "Hello World",
content: `<div id="helloPanel"></div>`, content: `<div id="helloPanel" class="b3-dialog__content"></div>`,
width: this.isMobile ? "92vw" : "720px",
destroyCallback(options) { destroyCallback(options) {
//You must destroy the component when the dialog is closed //Destroy the component when the dialog is closed
hello.$destroy(); // hello.$destroy();
}, },
}); });
let hello = new HelloExample({ let hello = new HelloExample({
@ -274,4 +193,193 @@ export default class SamplePlugin extends Plugin {
} }
}); });
} }
private addMenu(rect: DOMRect) {
const menu = new Menu("topBarSample", () => {
console.log(this.i18n.byeMenu);
});
menu.addItem({
icon: "iconInfo",
label: "Dialog",
accelerator: this.commands[0].customHotkey,
click: () => this.showDialog()
});
if (!this.isMobile) {
menu.addItem({
icon: "iconLayoutBottom",
label: "Open Custom Tab",
click: () => {
const tab = openTab({
app: this.app,
custom: {
icon: "iconFace",
title: "Custom Tab",
data: {
text: "This is my custom tab",
},
fn: this.customTab
},
});
console.log(tab)
}
});
menu.addItem({
icon: "iconLayoutBottom",
label: "Open Asset Tab(open help first)",
click: () => {
const tab = openTab({
app: this.app,
asset: {
path: "assets/paragraph-20210512165953-ag1nib4.svg"
}
});
console.log(tab)
}
});
menu.addItem({
icon: "iconLayoutBottom",
label: "Open Doc Tab(open help first)",
click: async () => {
const tab = await openTab({
app: this.app,
doc: {
id: "20200812220555-lj3enxa",
}
});
console.log(tab)
}
});
menu.addItem({
icon: "iconLayoutBottom",
label: "Open Search Tab",
click: () => {
const tab = openTab({
app: this.app,
search: {
k: "SiYuan"
}
});
console.log(tab)
}
});
menu.addItem({
icon: "iconLayoutBottom",
label: "Open Card Tab",
click: () => {
const tab = openTab({
app: this.app,
card: {
type: "all"
}
});
console.log(tab)
}
});
menu.addItem({
icon: "iconLayout",
label: "Open Float Layer(open help first)",
click: () => {
this.addFloatLayer({
ids: ["20210428212840-8rqwn5o", "20201225220955-l154bn4"],
defIds: ["20230415111858-vgohvf3", "20200813131152-0wk5akh"],
x: window.innerWidth - 768 - 120,
y: 32
});
}
});
}
menu.addItem({
icon: "iconScrollHoriz",
label: "Event Bus",
type: "submenu",
submenu: [{
icon: "iconSelect",
label: "On ws-main",
click: () => {
this.eventBus.on("ws-main", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off ws-main",
click: () => {
this.eventBus.off("ws-main", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On click-blockicon",
click: () => {
this.eventBus.on("click-blockicon", this.blockIconEvent);
}
}, {
icon: "iconClose",
label: "Off click-blockicon",
click: () => {
this.eventBus.off("click-blockicon", this.blockIconEvent);
}
}, {
icon: "iconSelect",
label: "On click-pdf",
click: () => {
this.eventBus.on("click-pdf", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off click-pdf",
click: () => {
this.eventBus.off("click-pdf", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On click-editorcontent",
click: () => {
this.eventBus.on("click-editorcontent", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off click-editorcontent",
click: () => {
this.eventBus.off("click-editorcontent", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On click-editortitleicon",
click: () => {
this.eventBus.on("click-editortitleicon", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off click-editortitleicon",
click: () => {
this.eventBus.off("click-editortitleicon", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On open-noneditableblock",
click: () => {
this.eventBus.on("open-noneditableblock", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off open-noneditableblock",
click: () => {
this.eventBus.off("open-noneditableblock", this.eventBusLog);
}
}]
});
menu.addSeparator();
menu.addItem({
icon: "iconSparkles",
label: this.data[STORAGE_NAME].readonlyText || "Readonly",
type: "readonly",
});
if (this.isMobile) {
menu.fullscreen();
} else {
menu.open({
x: rect.right,
y: rect.bottom,
isLeft: true,
});
}
}
} }

730
src/siyuan.d.ts vendored
View file

@ -1,314 +1,424 @@
declare module "siyuan" { type TEventBus = "ws-main" | "click-blockicon" | "click-editorcontent" | "click-pdf" |
type TEventBus = "ws-main" | "click-blockicon" | "click-editorcontent" | "click-pdf" | "click-editortitleicon" "click-editortitleicon" | "open-noneditableblock"
declare global { type TCardType = "doc" | "notebook" | "all"
interface Window {
Lute: Lute
}
}
declare global {
interface IObject { interface Window {
[key: string]: string; Lute: Lute
}
interface ILuteNode {
TokensStr: () => string;
__internal_object__: {
Parent: {
Type: number,
},
HeadingLevel: string,
};
}
interface IWebSocketData {
cmd: string
callback?: string
data: any
msg: string
code: number
sid: string
}
interface IPluginDockTab {
position: "LeftTop" | "LeftBottom" | "RightTop" | "RightBottom" | "BottomLeft" | "BottomRight",
size: { width: number, height: number },
icon: string,
hotkey?: string,
title: string,
}
interface IMenuItemOption {
label?: string,
click?: (element: HTMLElement) => void,
type?: "separator" | "submenu" | "readonly",
accelerator?: string,
action?: string,
id?: string,
submenu?: IMenuItemOption[]
disabled?: boolean
icon?: string
iconHTML?: string
current?: boolean
bind?: (element: HTMLElement) => void
}
export function fetchPost(url: string, data?: any, cb?: (response: IWebSocketData) => void, headers?: IObject): void;
export function fetchSyncPost(url: string, data?: any): Promise<IWebSocketData>;
export function fetchGet(url: string, cb: (response: IWebSocketData) => void): void;
export function openTab(options: {
custom?: {
title: string,
icon: string,
data?: any
fn?: () => any,
} // card 和自定义页签 必填
position?: "right" | "bottom",
keepCursor?: boolean // 是否跳转到新 tab 上
removeCurrentTab?: boolean // 在当前页签打开时需移除原有页签
afterOpen?: () => void // 打开后回调
}): void
export function isMobile(): boolean;
export function adaptHotkey(hotkey: string): string;
export function confirm(title: string, text: string, confirmCB?: () => void, cancelCB?: () => void): void;
/**
* @param timeout - ms. 0: manual close-1: always show; 6000: default
* @param {string} [type=info]
*/
export function showMessage(text: string, timeout?: number, type?: "info" | "error", id?: string): void;
export class App {
plugins: Plugin[];
}
export abstract class Plugin {
eventBus: EventBus;
i18n: IObject;
data: any;
name: string;
constructor(options: {
app: App,
id: string,
name: string,
i18n: IObject
})
onload(): void;
onunload(): void;
onLayoutReady(): void;
/*
* @param {string} [options.position=right]
*/
addTopBar(options: {
icon: string,
title: string,
callback: (evt: MouseEvent) => void
position?: "right" | "left"
}): HTMLDivElement;
openSetting(): void
// registerCommand(command: IPluginCommand): void;
// registerSettingRender(settingRender: SettingRender): void;
loadData(storageName: string): Promise<any>;
saveData(storageName: string, content: any): Promise<void>;
removeData(storageName: string): Promise<any>;
addTab(options: {
type: string,
destroy?: () => void,
resize?: () => void,
update?: () => void,
init: () => void
}): () => any
addDock(options: {
config: IPluginDockTab,
data: any,
type: string,
destroy?: () => void,
resize?: () => void,
update?: () => void,
init: () => void
}): any
addFloatLayer(options: {
ids: string[],
defIds?: string[],
x?: number,
y?: number,
targetElement?: HTMLElement
}): void
}
export class EventBus {
on(type: TEventBus, listener: (event: CustomEvent<any>) => void): void;
once(type: TEventBus, listener: (event: CustomEvent<any>) => void): void;
off(type: TEventBus, listener: (event: CustomEvent<any>) => void): void;
emit(type: TEventBus, detail?: any): boolean;
}
export class Dialog {
element: HTMLElement;
constructor(options: {
title?: string,
transparent?: boolean,
content: string,
width?: string
height?: string,
destroyCallback?: (options?: IObject) => void
disableClose?: boolean
disableAnimation?: boolean
});
destroy(options?: IObject): void;
bindInput(inputElement: HTMLInputElement | HTMLTextAreaElement, enterEvent?: () => void): void;
}
export class Menu {
constructor(id?: string, closeCB?: () => void);
showSubMenu(subMenuElement: HTMLElement): void;
addItem(options: IMenuItemOption): HTMLElement;
addSeparator(): void;
open(options: { x: number, y: number, h?: number, w?: number, isLeft?: boolean }): void;
/*
* @param {string} [position=all]
*/
fullscreen(position?: "bottom" | "all"): void;
close(): void;
}
declare class Lute {
public static WalkStop: number;
public static WalkSkipChildren: number;
public static WalkContinue: number;
public static Version: string;
public static Caret: string;
public static New(): Lute;
public static EChartsMindmapStr(text: string): string;
public static NewNodeID(): string;
public static Sanitize(html: string): string;
public static EscapeHTMLStr(str: string): string;
public static UnEscapeHTMLStr(str: string): string;
public static GetHeadingID(node: ILuteNode): string;
public static BlockDOM2Content(html: string): string;
private constructor();
public BlockDOM2Content(text: string): string;
public BlockDOM2EscapeMarkerContent(text: string): string;
public SetTextMark(enable: boolean): void;
public SetHeadingID(enable: boolean): void;
public SetProtyleMarkNetImg(enable: boolean): void;
public SetSpellcheck(enable: boolean): void;
public SetFileAnnotationRef(enable: boolean): void;
public SetSetext(enable: boolean): void;
public SetYamlFrontMatter(enable: boolean): void;
public SetChineseParagraphBeginningSpace(enable: boolean): void;
public SetRenderListStyle(enable: boolean): void;
public SetImgPathAllowSpace(enable: boolean): void;
public SetKramdownIAL(enable: boolean): void;
public BlockDOM2Md(html: string): string;
public BlockDOM2StdMd(html: string): string;
public SetGitConflict(enable: boolean): void;
public SetSuperBlock(enable: boolean): void;
public SetTag(enable: boolean): void;
public SetMark(enable: boolean): void;
public SetSub(enable: boolean): void;
public SetSup(enable: boolean): void;
public SetBlockRef(enable: boolean): void;
public SetSanitize(enable: boolean): void;
public SetHeadingAnchor(enable: boolean): void;
public SetImageLazyLoading(imagePath: string): void;
public SetInlineMathAllowDigitAfterOpenMarker(enable: boolean): void;
public SetToC(enable: boolean): void;
public SetIndentCodeBlock(enable: boolean): void;
public SetParagraphBeginningSpace(enable: boolean): void;
public SetFootnotes(enable: boolean): void;
public SetLinkRef(enalbe: boolean): void;
public SetEmojiSite(emojiSite: string): void;
public PutEmojis(emojis: IObject): void;
public SpinBlockDOM(html: string): string;
public Md2BlockDOM(html: string): string;
public SetProtyleWYSIWYG(wysiwyg: boolean): void;
public MarkdownStr(name: string, md: string): string;
public IsValidLinkDest(text: string): boolean;
public BlockDOM2InlineBlockDOM(html: string): string;
public BlockDOM2HTML(html: string): string;
} }
} }
interface ITab {
id: string;
headElement: HTMLElement;
panelElement: HTMLElement;
model: IModel;
title: string;
icon: string;
docIcon: string;
updateTitle: (title: string) => void;
pin: () => void;
unpin: () => void;
setDocIcon: (icon: string) => void;
close: () => void;
}
interface IModel {
element: Element;
tab: ITab;
data: any;
type: string;
}
interface IObject {
[key: string]: string;
}
interface ILuteNode {
TokensStr: () => string;
__internal_object__: {
Parent: {
Type: number,
},
HeadingLevel: string,
};
}
interface ISearchOption {
page?: number
group?: number, // 0不分组1按文档分组
hasReplace?: boolean,
method?: number // 0文本1查询语法2SQL3正则表达式
hPath?: string
idPath?: string[]
k: string
r?: string
types?: {
mathBlock: boolean
table: boolean
blockquote: boolean
superBlock: boolean
paragraph: boolean
document: boolean
heading: boolean
list: boolean
listItem: boolean
codeBlock: boolean
htmlBlock: boolean
embedBlock: boolean
}
}
interface IWebSocketData {
cmd: string
callback?: string
data: any
msg: string
code: number
sid: string
}
declare interface IPluginDockTab {
position: "LeftTop" | "LeftBottom" | "RightTop" | "RightBottom" | "BottomLeft" | "BottomRight",
size: { width: number, height: number },
icon: string,
hotkey?: string,
title: string,
index?: number,
show?: boolean
}
interface IMenuItemOption {
label?: string,
click?: (element: HTMLElement) => void,
type?: "separator" | "submenu" | "readonly",
accelerator?: string,
action?: string,
id?: string,
submenu?: IMenuItemOption[]
disabled?: boolean
icon?: string
iconHTML?: string
current?: boolean
bind?: (element: HTMLElement) => void
index?: number
element?: HTMLElement
}
interface ICommandOption {
langKey: string, // 多语言 key
/**
* 使 MacOS A
* "Ctrl": "⌘",
* "Shift": "⇧",
* "Alt": "⌥",
* "Tab": "⇥",
* "Backspace": "⌫",
* "Delete": "⌦",
* "Enter": "↩",
*/
hotkey: string,
customHotkey?: string,
callback?: () => void
fileTreeCallback?: (file: any) => void
editorCallback?: (protyle: any) => void
dockCallback?: (element: HTMLElement) => void
}
export function fetchPost(url: string, data?: any, callback?: (response: IWebSocketData) => void, headers?: IObject): void;
export function fetchSyncPost(url: string, data?: any): Promise<IWebSocketData>;
export function fetchGet(url: string, callback: (response: IWebSocketData) => void): void;
export function openTab(options: {
app: App,
doc?: {
id: string, // 块 id
action?: string [] // cb-get-all获取所有内容cb-get-focus打开后光标定位在 id 所在的块cb-get-hl: 打开后 id 块高亮
zoomIn?: boolean // 是否缩放
},
pdf?: {
path: string,
page?: number, // pdf 页码
id?: string, // File Annotation id
},
asset?: {
path: string,
},
search?: ISearchOption
card?: {
type: TCardType,
id?: string, // cardType 为 all 时不传,否则传文档或笔记本 id
title?: string // cardType 为 all 时不传,否则传文档或笔记本名称
},
custom?: {
title: string,
icon: string,
data?: any
fn?: () => IModel,
}
position?: "right" | "bottom",
keepCursor?: boolean // 是否跳转到新 tab 上
removeCurrentTab?: boolean // 在当前页签打开时需移除原有页签
afterOpen?: () => void // 打开后回调
}): ITab
export function getFrontend(): "desktop" | "desktop-window" | "mobile" | "browser-desktop" | "browser-mobile";
export function getBackend(): "windows" | "linux" | "darwin" | "docker" | "android" | "ios"
export function adaptHotkey(hotkey: string): string;
export function confirm(title: string, text: string, confirmCallback?: () => void, cancelCallback?: () => void): void;
/**
* @param timeout - ms. 0: manual close-1: always show; 6000: default
* @param {string} [type=info]
*/
export function showMessage(text: string, timeout?: number, type?: "info" | "error", id?: string): void;
export class App {
plugins: Plugin[];
}
export abstract class Plugin {
eventBus: EventBus;
i18n: IObject;
data: any;
name: string;
app: App;
commands: ICommandOption[];
constructor(options: {
app: App,
name: string,
i18n: IObject
})
onload(): void;
onunload(): void;
onLayoutReady(): void;
/**
* Must be executed before the synchronous function.
* @param {string} [options.position=right]
*/
addTopBar(options: {
icon: string,
title: string,
callback: (event: MouseEvent) => void
position?: "right" | "left"
}): HTMLElement;
/**
* Must be executed before the synchronous function.
* @param {string} [options.position=right]
*/
addStatusBar(options: {
element: HTMLElement,
position?: "right" | "left"
}): HTMLElement
openSetting(): void
loadData(storageName: string): Promise<any>;
saveData(storageName: string, content: any): Promise<void>;
removeData(storageName: string): Promise<any>;
addIcons(svg: string): void;
/**
* Must be executed before the synchronous function.
*/
addTab(options: {
type: string,
destroy?: () => void,
resize?: () => void,
update?: () => void,
init: () => void
}): () => IModel
/**
* Must be executed before the synchronous function.
*/
addDock(options: {
config: IPluginDockTab,
data: any,
type: string,
destroy?: () => void,
resize?: () => void,
update?: () => void,
init: () => void
}): { config: IPluginDockTab, model: IModel }
addCommand(options: ICommandOption): void
addFloatLayer(options: {
ids: string[],
defIds?: string[],
x?: number,
y?: number,
targetElement?: HTMLElement
}): void
}
export class EventBus {
on(type: TEventBus, listener: (event: CustomEvent<any>) => void): void;
once(type: TEventBus, listener: (event: CustomEvent<any>) => void): void;
off(type: TEventBus, listener: (event: CustomEvent<any>) => void): void;
emit(type: TEventBus, detail?: any): boolean;
}
export class Dialog {
element: HTMLElement;
constructor(options: {
title?: string,
transparent?: boolean,
content: string,
width?: string
height?: string,
destroyCallback?: (options?: IObject) => void
disableClose?: boolean
disableAnimation?: boolean
});
destroy(options?: IObject): void;
bindInput(inputElement: HTMLInputElement | HTMLTextAreaElement, enterEvent?: () => void): void;
}
export class Menu {
constructor(id?: string, closeCallback?: () => void);
showSubMenu(subMenuElement: HTMLElement): void;
addItem(options: IMenuItemOption): HTMLElement;
addSeparator(index?: number): void;
open(options: { x: number, y: number, h?: number, w?: number, isLeft?: boolean }): void;
/**
* @param {string} [position=all]
*/
fullscreen(position?: "bottom" | "all"): void;
close(): void;
}
declare class Lute {
public static WalkStop: number;
public static WalkSkipChildren: number;
public static WalkContinue: number;
public static Version: string;
public static Caret: string;
public static New(): Lute;
public static EChartsMindmapStr(text: string): string;
public static NewNodeID(): string;
public static Sanitize(html: string): string;
public static EscapeHTMLStr(str: string): string;
public static UnEscapeHTMLStr(str: string): string;
public static GetHeadingID(node: ILuteNode): string;
public static BlockDOM2Content(html: string): string;
private constructor();
public BlockDOM2Content(text: string): string;
public BlockDOM2EscapeMarkerContent(text: string): string;
public SetTextMark(enable: boolean): void;
public SetHeadingID(enable: boolean): void;
public SetProtyleMarkNetImg(enable: boolean): void;
public SetSpellcheck(enable: boolean): void;
public SetFileAnnotationRef(enable: boolean): void;
public SetSetext(enable: boolean): void;
public SetYamlFrontMatter(enable: boolean): void;
public SetChineseParagraphBeginningSpace(enable: boolean): void;
public SetRenderListStyle(enable: boolean): void;
public SetImgPathAllowSpace(enable: boolean): void;
public SetKramdownIAL(enable: boolean): void;
public BlockDOM2Md(html: string): string;
public BlockDOM2StdMd(html: string): string;
public SetGitConflict(enable: boolean): void;
public SetSuperBlock(enable: boolean): void;
public SetTag(enable: boolean): void;
public SetMark(enable: boolean): void;
public SetSub(enable: boolean): void;
public SetSup(enable: boolean): void;
public SetBlockRef(enable: boolean): void;
public SetSanitize(enable: boolean): void;
public SetHeadingAnchor(enable: boolean): void;
public SetImageLazyLoading(imagePath: string): void;
public SetInlineMathAllowDigitAfterOpenMarker(enable: boolean): void;
public SetToC(enable: boolean): void;
public SetIndentCodeBlock(enable: boolean): void;
public SetParagraphBeginningSpace(enable: boolean): void;
public SetFootnotes(enable: boolean): void;
public SetLinkRef(enalbe: boolean): void;
public SetEmojiSite(emojiSite: string): void;
public PutEmojis(emojis: IObject): void;
public SpinBlockDOM(html: string): string;
public Md2BlockDOM(html: string): string;
public SetProtyleWYSIWYG(wysiwyg: boolean): void;
public MarkdownStr(name: string, md: string): string;
public IsValidLinkDest(text: string): boolean;
public BlockDOM2InlineBlockDOM(html: string): string;
public BlockDOM2HTML(html: string): string;
}