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/plugin.json b/plugin.json
index 8558256..37acc2b 100644
--- a/plugin.json
+++ b/plugin.json
@@ -2,7 +2,7 @@
"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",
"displayName": {
"en_US": "Plugin sample with vite and svelte",
diff --git a/src/index.ts b/src/index.ts
index e2f27cd..7c129b5 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";
@@ -11,20 +22,53 @@ const DOCK_TYPE = "dock_tab";
export default class SamplePlugin extends Plugin {
private customTab: () => any;
+ private isMobile: boolean;
async onload() {
- showMessage("Hello SiYuan Plugin");
- this.data[STORAGE_NAME] = {readonlyText: "Readonly"};
+ this.data[STORAGE_NAME] = { readonlyText: "Readonly" };
+
+ 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);
}
});
+ 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 div = document.createElement("div");
new HelloExample({
target: div,
@@ -44,10 +88,18 @@ 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},
+ size: { width: 200, height: 0 },
icon: "iconEmoji",
title: "Custom Dock",
},
@@ -79,6 +131,7 @@ export default class SamplePlugin extends Plugin {
onLayoutReady() {
this.loadData(STORAGE_NAME);
+ console.log(`frontend: ${getFrontend()}; backend: ${getBackend()}`);
}
onunload() {
@@ -87,11 +140,27 @@ export default class SamplePlugin extends Plugin {
console.log("onunload");
}
- private wsEvent({ detail }: any) {
+ openSetting(): void {
+ let dialog = new Dialog({
+ title: "SettingPannel",
+ content: ``,
+ width: "600px",
+ destroyCallback: (options) => {
+ console.log("destroyCallback", options);
+ //You must destroy the component when the dialog is closed
+ pannel.$destroy();
+ }
+ });
+ let pannel = new SettingPannel({
+ target: dialog.element.querySelector("#SettingPanel"),
+ });
+ }
+
+ private eventBusLog({ detail }: any) {
console.log(detail);
}
- private blockIconEvent({detail}: any) {
+ private blockIconEvent({ detail }: any) {
console.log(detail);
detail.menu.addSeparator(0);
const ids: string[] = [];
@@ -106,70 +175,108 @@ export default class SamplePlugin extends Plugin {
});
}
- private async addMenu(rect: DOMRect) {
+ private showDialog() {
+ new Dialog({
+ title: "Info",
+ content: 'This is a dialog
',
+ width: this.isMobile ? "92vw" : "520px",
+ });
+ }
+
+ private 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()
+ accelerator: this.commands[0].customHotkey,
+ click: this.showDialog
});
- menu.addItem({
- icon: "iconLayoutBottom",
- label: "Open Tab",
- click: () => {
- openTab({
- custom: {
- icon: "iconEmoji",
- title: "Custom Tab",
- data: {
- text: "This is my custom tab",
+ 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
},
- 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"};
- });
- }
- });
+ });
+ 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",
@@ -178,13 +285,13 @@ export default class SamplePlugin extends Plugin {
icon: "iconSelect",
label: "On ws-main",
click: () => {
- this.eventBus.on("ws-main", this.wsEvent);
+ this.eventBus.on("ws-main", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off ws-main",
click: () => {
- this.eventBus.off("ws-main", this.wsEvent);
+ this.eventBus.off("ws-main", this.eventBusLog);
}
}, {
icon: "iconSelect",
@@ -202,35 +309,59 @@ export default class SamplePlugin extends Plugin {
icon: "iconSelect",
label: "On click-pdf",
click: () => {
- this.eventBus.on("click-pdf", this.wsEvent);
+ this.eventBus.on("click-pdf", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off click-pdf",
click: () => {
- this.eventBus.off("click-pdf", this.wsEvent);
+ this.eventBus.off("click-pdf", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On click-editorcontent",
click: () => {
- this.eventBus.on("click-editorcontent", this.wsEvent);
+ this.eventBus.on("click-editorcontent", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off click-editorcontent",
click: () => {
- this.eventBus.off("click-editorcontent", this.wsEvent);
+ 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] || "Readonly",
+ label: this.data[STORAGE_NAME].readonlyText || "Readonly",
type: "readonly",
});
- if (isMobile()) {
+ if (this.isMobile) {
menu.fullscreen();
} else {
menu.open({
@@ -241,22 +372,6 @@ export default class SamplePlugin extends Plugin {
}
}
- openSetting(): void {
- let dialog = new Dialog({
- title: "SettingPannel",
- content: ``,
- width: "600px",
- destroyCallback: (options) => {
- console.log("destroyCallback", options);
- //You must destroy the component when the dialog is closed
- pannel.$destroy();
- }
- });
- let pannel = new SettingPannel({
- target: dialog.element.querySelector("#SettingPanel"),
- });
- }
-
private openHelloInDialog() {
let dialog = new Dialog({
title: "Hello World",
diff --git a/src/siyuan.d.ts b/src/siyuan.d.ts
new file mode 100644
index 0000000..4c74dc5
--- /dev/null
+++ b/src/siyuan.d.ts
@@ -0,0 +1,424 @@
+type TEventBus = "ws-main" | "click-blockicon" | "click-editorcontent" | "click-pdf" |
+ "click-editortitleicon" | "open-noneditableblock"
+
+type TCardType = "doc" | "notebook" | "all"
+
+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