diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a6b0329..7bd6c4e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,62 +1,62 @@ name: Create Release on Tag Push on: - push: - tags: - - "v*" + push: + tags: + - "v*" jobs: - build: - runs-on: ubuntu-latest - steps: - # Checkout - - name: Checkout - uses: actions/checkout@v3 + build: + runs-on: ubuntu-latest + steps: + # Checkout + - name: Checkout + uses: actions/checkout@v3 - # Install Node.js - - name: Install Node.js - uses: actions/setup-node@v3 - with: - node-version: 18 - registry-url: "https://registry.npmjs.org" + # Install Node.js + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + registry-url: "https://registry.npmjs.org" - # Install pnpm - - name: Install pnpm - uses: pnpm/action-setup@v2 - id: pnpm-install - with: - version: 8 - run_install: false + # Install pnpm + - name: Install pnpm + uses: pnpm/action-setup@v2 + id: pnpm-install + with: + version: 8 + run_install: false - # Get pnpm store directory - - name: Get pnpm store directory - id: pnpm-cache - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT + # Get pnpm store directory + - name: Get pnpm store directory + id: pnpm-cache + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT - # Setup pnpm cache - - name: Setup pnpm cache - uses: actions/cache@v3 - with: - path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- + # Setup pnpm cache + - name: Setup pnpm cache + uses: actions/cache@v3 + with: + path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- - # Install dependencies - - name: Install dependencies - run: pnpm install + # Install dependencies + - name: Install dependencies + run: pnpm install - # Build for production, 这一步会生成一个 package.zip - - name: Build for production - run: pnpm build + # Build for production, 这一步会生成一个 package.zip + - name: Build for production + run: pnpm build - - name: Release - uses: ncipollo/release-action@v1 - with: - allowUpdates: true - artifactErrorsFailBuild: true - artifacts: 'package.zip' - token: ${{ secrets.GITHUB_TOKEN }} - prerelease: true + - name: Release + uses: ncipollo/release-action@v1 + with: + allowUpdates: true + artifactErrorsFailBuild: true + artifacts: "package.zip" + token: ${{ secrets.GITHUB_TOKEN }} + prerelease: true diff --git a/.gitignore b/.gitignore index aa00657..764e2d4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ node_modules dev dist build +tmp diff --git a/README.md b/README.md index 352ada8..c0a6958 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@ [中文版](./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 @@ -62,7 +63,6 @@ complete the following tasks: * Text used in the plugin, such as button text and tooltips * src/i18n/*.json language configuration files * Use `this.i18.key` to get the text in the code -* Finally, declare the language supported by the plugin in the `i18n` field in plugin.json It is recommended that the plugin supports at least English and Simplified Chinese, so that more people can use it more conveniently. @@ -74,8 +74,10 @@ conveniently. "name": "plugin-sample-vite-svelte", "author": "frostime", "url": "https://github.com/siyuan-note/plugin-sample-vite-svelte", - "version": "0.0.1", - "minAppVersion": "2.9.0", + "version": "0.1.3", + "minAppVersion": "2.8.8", + "backends": ["windows", "linux", "darwin"], + "frontends": ["desktop"], "displayName": { "en_US": "Plugin sample with vite and svelte", "zh_CN": "插件样例 vite + svelte 版" @@ -89,8 +91,11 @@ conveniently. "zh_CN": "README.md" }, "funding": { + "openCollective": "", + "patreon": "", + "github": "", "custom": [ - "" + "https://ld246.com/sponsor" ] } } @@ -102,6 +107,21 @@ conveniently. * `url`: Plugin repo URL * `version`: Plugin version number, it is recommended to follow the [semver](https://semver.org/) specification * `minAppVersion`: Minimum version number of SiYuan required to use this plugin +* `backends`: Backend environment required by the plugin, optional values are `windows`, `linux`, `darwin`, `docker`, `android`, `ios` and `all` + * `windows`: Windows desktop + * `linux`: Linux desktop + * `darwin`: macOS desktop + * `docker`: Docker + * `android`: Android APP + * `ios`: iOS APP + * `all`: All environments +* `frontends`: Frontend environment required by the plugin, optional values are `desktop`, `desktop-window`, `mobile`, `browser-desktop`, `browser-mobile` and `all` + * `desktop`: Desktop + * `desktop-window`: Desktop window converted from tab + * `mobile`: Mobile APP + * `browser-desktop`: Desktop browser + * `browser-mobile`: Mobile browser + * `all`: All environments * `displayName`: Template display name, mainly used for display in the marketplace list, supports multiple languages * `default`: Default language, must exist * `zh_CN`, `en_US` and other languages: optional, it is recommended to provide at least Chinese and English @@ -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 least the following files: -* icon.png +* i18n/* +* icon.png (160*160) +* index.css * index.js * plugin.json -* preview.png +* preview.png (1024*768) * README*.md -* index.css (optional) -* i18n/* (optional) ## List on the marketplace diff --git a/README_zh_CN.md b/README_zh_CN.md index f1b02db..55ec0a7 100644 --- a/README_zh_CN.md +++ b/README_zh_CN.md @@ -4,7 +4,7 @@ [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 打包 2. 使用符号链接、而不是把项目放到插件目录下的模式进行开发 @@ -59,7 +59,6 @@ * 插件中使用的文本,比如按钮文字和提示信息 * src/i18n/*.json 语言配置文件 * 代码中使用 `this.i18.key` 获取文本 -* 最后在 plugin.json 中的 `i18n` 字段中声明该插件支持的语言 建议插件至少支持英文和简体中文,这样可以方便更多人使用。 @@ -70,8 +69,10 @@ "name": "plugin-sample-vite-svelte", "author": "frostime", "url": "https://github.com/siyuan-note/plugin-sample-vite-svelte", - "version": "0.0.1", - "minAppVersion": "2.9.0", + "version": "0.1.3", + "minAppVersion": "2.8.8", + "backends": ["windows", "linux", "darwin"], + "frontends": ["desktop"], "displayName": { "en_US": "Plugin sample with vite and svelte", "zh_CN": "插件样例 vite + svelte 版" @@ -85,8 +86,11 @@ "zh_CN": "README.md" }, "funding": { + "openCollective": "", + "patreon": "", + "github": "", "custom": [ - "" + "https://ld246.com/sponsor" ] } } @@ -97,32 +101,47 @@ * `url`:插件仓库地址 * `version`:插件版本号,建议遵循 [semver](https://semver.org/lang/zh-CN/) 规范 * `minAppVersion`:插件支持的最低思源笔记版本号 +* `backends`:插件需要的后端环境,可选值为 `windows`, `linux`, `darwin`, `docker`, `android`, `ios` and `all` + * `windows`:Windows 桌面端 + * `linux`:Linux 桌面端 + * `darwin`:macOS 桌面端 + * `docker`:Docker 端 + * `android`:Android 端 + * `ios`:iOS 端 + * `all`:所有环境 +* `frontends`:插件需要的前端环境,可选值为 `desktop`, `desktop-window`, `mobile`, `browser-desktop`, `browser-mobile` and `all` + * `desktop`:桌面端 + * `desktop-window`:桌面端页签转换的独立窗口 + * `mobile`:移动端 + * `browser-desktop`:桌面端浏览器 + * `browser-mobile`:移动端浏览器 + * `all`:所有环境 * `displayName`:模板显示名称,主要用于模板集市列表中显示,支持多语言 - * `default`:默认语言,必须存在 - * `zh_CN`、`en_US` 等其他语言:可选,建议至少提供中文和英文 + * `default`:默认语言,必须存在 + * `zh_CN`、`en_US` 等其他语言:可选,建议至少提供中文和英文 * `description`:插件描述,主要用于插件集市列表中显示,支持多语言 - * `default`:默认语言,必须存在 - * `zh_CN`、`en_US` 等其他语言:可选,建议至少提供中文和英文 + * `default`:默认语言,必须存在 + * `zh_CN`、`en_US` 等其他语言:可选,建议至少提供中文和英文 * `readme`:自述文件名,主要用于插件集市详情页中显示,支持多语言 - * `default`:默认语言,必须存在 - * `zh_CN`、`en_US` 等其他语言:可选,建议至少提供中文和英文 + * `default`:默认语言,必须存在 + * `zh_CN`、`en_US` 等其他语言:可选,建议至少提供中文和英文 * `funding`:插件赞助信息 - * `openCollective`:Open Collective 名称 - * `patreon`:Patreon 名称 - * `github`:GitHub 登录名 - * `custom`:自定义赞助链接列表 + * `openCollective`:Open Collective 名称 + * `patreon`:Patreon 名称 + * `github`:GitHub 登录名 + * `custom`:自定义赞助链接列表 ## 打包 无论使用何种方式编译打包,我们最终需要生成一个 package.zip,它至少包含如下文件: -* icon.png +* i18n/* +* icon.png (160*160) +* index.css * index.js * plugin.json -* preview.png +* preview.png (1024*768) * README*.md -* index.css (optional) -* i18n/* (optional) ## 上架集市 diff --git a/package.json b/package.json index 20ff17f..db69246 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "minimist": "^1.2.8", "rollup-plugin-livereload": "^2.0.5", "sass": "^1.62.1", - "siyuan": "^0.7.1", + "siyuan": "^0.7.2", "svelte": "^3.57.0", "ts-node": "^10.9.1", "typescript": "^5.0.4", diff --git a/plugin.json b/plugin.json index 8558256..311de7b 100644 --- a/plugin.json +++ b/plugin.json @@ -2,8 +2,10 @@ "name": "plugin-sample-vite-svelte", "author": "frostime", "url": "https://github.com/siyuan-note/plugin-sample-vite-svelte", - "version": "0.0.6", + "version": "0.1.3", "minAppVersion": "2.9.0", + "backends": ["windows", "linux", "darwin"], + "frontends": ["desktop"], "displayName": { "en_US": "Plugin sample with vite and svelte", "zh_CN": "插件样例 vite + svelte 版" diff --git a/src/i18n/en_US.json b/src/i18n/en_US.json index 7e9425a..c336270 100644 --- a/src/i18n/en_US.json +++ b/src/i18n/en_US.json @@ -5,6 +5,9 @@ "byeMenu": "Bye, Menu!", "helloPlugin": "Hello, Plugin!", "byePlugin": "Bye, Plugin!", + "showDialog": "Show dialog", + "removedData": "Data deleted", + "confirmRemove": "Confirm to delete the data in ${name}?", "name": "SiYuan", "hello": { "makesure": "Before using this template, please read the offical sample, make sure that you've known about the pipeline for plugin developing." diff --git a/src/i18n/zh_CN.json b/src/i18n/zh_CN.json index 7eaa3eb..568cccd 100644 --- a/src/i18n/zh_CN.json +++ b/src/i18n/zh_CN.json @@ -5,6 +5,9 @@ "byeMenu": "再见,菜单!", "helloPlugin": "你好,插件!", "byePlugin": "再见,插件!", + "showDialog": "弹出一个对话框", + "removedData": "数据已删除", + "confirmRemove": "确认删除 ${name} 中的数据?", "name": "思源", "hello": { "makesure": "使用这个模板之前,请阅读官方教程, 确保自己已经理解了插件的基本开发流程。" diff --git a/src/index.ts b/src/index.ts index e2f27cd..8947cf8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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 HelloExample from "./hello.svelte"; @@ -8,26 +19,61 @@ const STORAGE_NAME = "menu-config"; const TAB_TYPE = "custom_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() { - showMessage("Hello SiYuan Plugin"); 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(` + + + + +`); + const topBarElement = this.addTopBar({ - icon: "iconEmoji", + icon: "iconFace", title: this.i18n.addTopBarIcon, - position: "left", + position: "right", 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 = `
+ + + +
`; + 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({ - target: div, + target: tabDiv, props: { name: this.i18n.name, i18n: this.i18n.hello @@ -36,7 +82,7 @@ export default class SamplePlugin extends Plugin { this.customTab = this.addTab({ type: TAB_TYPE, init() { - this.element.appendChild(div); + this.element.appendChild(tabDiv); console.log(this.element); }, destroy() { @@ -44,11 +90,19 @@ export default class SamplePlugin extends Plugin { } }); + this.addCommand({ + langKey: "showDialog", + hotkey: "⇧⌘M", + callback: () => { + this.showDialog(); + } + }); + this.addDock({ config: { position: "LeftBottom", size: {width: 200, height: 0}, - icon: "iconEmoji", + icon: "iconSaving", title: "Custom Dock", }, data: { @@ -75,10 +129,12 @@ export default class SamplePlugin extends Plugin { } }); + console.log(this.i18n.helloPlugin); } onLayoutReady() { this.loadData(STORAGE_NAME); + console.log(`frontend: ${getFrontend()}; backend: ${getBackend()}`); } onunload() { @@ -87,160 +143,6 @@ export default class SamplePlugin extends Plugin { 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
" + ids.join("
"), - }); - } - - 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 { let dialog = new Dialog({ 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
" + ids.join("
"), + }); + } + + private showDialog() { let dialog = new Dialog({ title: "Hello World", - content: `
`, + content: `
`, + width: this.isMobile ? "92vw" : "720px", destroyCallback(options) { - //You must destroy the component when the dialog is closed - hello.$destroy(); + //Destroy the component when the dialog is closed + // hello.$destroy(); }, }); 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, + }); + } + } } diff --git a/src/siyuan.d.ts b/src/siyuan.d.ts index 1d5b0a9..4c74dc5 100644 --- a/src/siyuan.d.ts +++ b/src/siyuan.d.ts @@ -1,314 +1,424 @@ -declare module "siyuan" { - type TEventBus = "ws-main" | "click-blockicon" | "click-editorcontent" | "click-pdf" | "click-editortitleicon" +type TEventBus = "ws-main" | "click-blockicon" | "click-editorcontent" | "click-pdf" | + "click-editortitleicon" | "open-noneditableblock" - declare global { - interface Window { - Lute: Lute - } - } +type TCardType = "doc" | "notebook" | "all" - - interface IObject { - [key: string]: string; - } - - 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; - - 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; - - saveData(storageName: string, content: any): Promise; - - removeData(storageName: string): Promise; - - 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) => void): void; - - once(type: TEventBus, listener: (event: CustomEvent) => void): void; - - off(type: TEventBus, listener: (event: CustomEvent) => 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; +declare global { + interface Window { + Lute: Lute } } + +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:查询语法,2:SQL,3:正则表达式 + 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; + +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; + + saveData(storageName: string, content: any): Promise; + + removeData(storageName: string): Promise; + + 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) => void): void; + + once(type: TEventBus, listener: (event: CustomEvent) => void): void; + + off(type: TEventBus, listener: (event: CustomEvent) => 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; +} \ No newline at end of file