2023-05-06 11:29:46 +08:00
|
|
|
import {App} from "../index";
|
2023-05-07 17:31:50 +08:00
|
|
|
import {EventBus} from "./EventBus";
|
2023-05-08 21:00:41 +08:00
|
|
|
import {fetchPost} from "../util/fetch";
|
2023-05-09 11:36:11 +08:00
|
|
|
import {isMobile, isWindow} from "../util/functions";
|
2023-05-10 16:11:02 +08:00
|
|
|
/// #if !MOBILE
|
2023-05-10 00:01:47 +08:00
|
|
|
import {Custom} from "../layout/dock/Custom";
|
2023-05-10 16:11:02 +08:00
|
|
|
/// #endif
|
2023-05-10 00:01:47 +08:00
|
|
|
import {Tab} from "../layout/Tab";
|
2023-05-12 17:11:43 +08:00
|
|
|
import {getDockByType, setPanelFocus} from "../layout/util";
|
|
|
|
|
import {hasClosestByAttribute} from "../protyle/util/hasClosest";
|
2023-05-19 20:27:14 +08:00
|
|
|
import {BlockPanel} from "../block/Panel";
|
2023-05-27 18:34:39 +08:00
|
|
|
import {genUUID} from "../util/genID";
|
2023-06-02 21:49:22 +08:00
|
|
|
import {Setting} from "./Setting";
|
2023-05-04 18:28:57 +08:00
|
|
|
|
2023-05-06 11:29:46 +08:00
|
|
|
export class Plugin {
|
2023-05-18 19:27:21 +08:00
|
|
|
private app: App;
|
2023-05-06 11:29:46 +08:00
|
|
|
public i18n: IObject;
|
2023-05-07 17:31:50 +08:00
|
|
|
public eventBus: EventBus;
|
2023-05-16 22:18:33 +08:00
|
|
|
public data: any = {};
|
2023-05-08 21:00:41 +08:00
|
|
|
public name: string;
|
2023-05-16 00:04:25 +08:00
|
|
|
public topBarIcons: Element[] = [];
|
2023-06-02 21:49:22 +08:00
|
|
|
public setting: Setting;
|
2023-05-31 15:39:42 +08:00
|
|
|
public statusBarIcons: Element[] = [];
|
2023-05-30 00:27:47 +08:00
|
|
|
public commands: ICommand[] = [];
|
2023-05-10 16:11:02 +08:00
|
|
|
public models: {
|
|
|
|
|
/// #if !MOBILE
|
|
|
|
|
[key: string]: (options: { tab: Tab, data: any }) => Custom
|
|
|
|
|
/// #endif
|
|
|
|
|
} = {};
|
2023-05-12 17:11:43 +08:00
|
|
|
public docks: {
|
|
|
|
|
/// #if !MOBILE
|
|
|
|
|
[key: string]: {
|
|
|
|
|
config: IPluginDockTab,
|
|
|
|
|
model: (options: { tab: Tab }) => Custom
|
|
|
|
|
}
|
|
|
|
|
/// #endif
|
|
|
|
|
} = {};
|
2023-05-04 18:28:57 +08:00
|
|
|
|
2023-05-06 11:29:46 +08:00
|
|
|
constructor(options: {
|
|
|
|
|
app: App,
|
2023-05-07 17:31:50 +08:00
|
|
|
name: string,
|
2023-05-06 11:29:46 +08:00
|
|
|
i18n: IObject
|
|
|
|
|
}) {
|
2023-05-18 19:27:21 +08:00
|
|
|
this.app = options.app;
|
2023-05-06 11:29:46 +08:00
|
|
|
this.i18n = options.i18n;
|
2023-05-08 21:00:41 +08:00
|
|
|
this.name = options.name;
|
2023-05-07 17:31:50 +08:00
|
|
|
this.eventBus = new EventBus(options.name);
|
2023-05-04 18:28:57 +08:00
|
|
|
}
|
|
|
|
|
|
2023-05-08 21:00:41 +08:00
|
|
|
public onload() {
|
2023-05-09 19:27:47 +08:00
|
|
|
// 加载
|
2023-05-08 21:00:41 +08:00
|
|
|
}
|
|
|
|
|
|
2023-05-17 15:36:21 +08:00
|
|
|
public onunload() {
|
|
|
|
|
// 禁用/卸载
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-17 21:06:37 +08:00
|
|
|
public onLayoutReady() {
|
|
|
|
|
// 布局加载完成
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-30 00:27:47 +08:00
|
|
|
public addCommand(command: ICommand) {
|
|
|
|
|
this.commands.push(command);
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-25 23:18:38 +08:00
|
|
|
public addIcons(svg: string) {
|
2023-05-31 09:37:09 +08:00
|
|
|
document.body.insertAdjacentHTML("afterbegin", `<svg data-name="${this.name}" style="position: absolute; width: 0; height: 0; overflow: hidden;" xmlns="http://www.w3.org/2000/svg">
|
2023-05-25 23:18:38 +08:00
|
|
|
<defs>${svg}</defs></svg>`);
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-08 10:56:52 +08:00
|
|
|
public addTopBar(options: {
|
|
|
|
|
icon: string,
|
|
|
|
|
title: string,
|
2023-05-31 15:39:42 +08:00
|
|
|
position?: "right" | "left",
|
2023-05-08 10:56:52 +08:00
|
|
|
callback: (evt: MouseEvent) => void
|
|
|
|
|
}) {
|
|
|
|
|
const iconElement = document.createElement("div");
|
2023-05-27 18:34:39 +08:00
|
|
|
iconElement.setAttribute("data-menu", "true");
|
|
|
|
|
iconElement.addEventListener("click", options.callback);
|
|
|
|
|
iconElement.id = "plugin" + genUUID();
|
2023-05-09 11:36:11 +08:00
|
|
|
if (isMobile()) {
|
|
|
|
|
iconElement.className = "b3-menu__item";
|
|
|
|
|
iconElement.innerHTML = (options.icon.startsWith("icon") ? `<svg class="b3-menu__icon"><use xlink:href="#${options.icon}"></use></svg>` : options.icon) +
|
|
|
|
|
`<span class="b3-menu__label">${options.title}</span>`;
|
|
|
|
|
} else if (!isWindow()) {
|
|
|
|
|
iconElement.className = "toolbar__item b3-tooltips b3-tooltips__sw";
|
|
|
|
|
iconElement.setAttribute("aria-label", options.title);
|
|
|
|
|
iconElement.innerHTML = options.icon.startsWith("icon") ? `<svg><use xlink:href="#${options.icon}"></use></svg>` : options.icon;
|
|
|
|
|
iconElement.addEventListener("click", options.callback);
|
2023-05-31 15:39:42 +08:00
|
|
|
iconElement.setAttribute("data-position", options.position || "right");
|
2023-05-09 11:36:11 +08:00
|
|
|
}
|
2023-05-16 00:04:25 +08:00
|
|
|
this.topBarIcons.push(iconElement);
|
2023-05-08 10:56:52 +08:00
|
|
|
return iconElement;
|
2023-05-04 18:28:57 +08:00
|
|
|
}
|
|
|
|
|
|
2023-05-31 15:39:42 +08:00
|
|
|
public addStatusBar(options: {
|
|
|
|
|
element: HTMLElement,
|
|
|
|
|
position?: "right" | "left",
|
|
|
|
|
}) {
|
|
|
|
|
/// #if !MOBILE
|
|
|
|
|
options.element.setAttribute("data-position", options.position || "right");
|
|
|
|
|
this.statusBarIcons.push(options.element);
|
|
|
|
|
return options.element;
|
|
|
|
|
/// #endif
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-08 21:00:41 +08:00
|
|
|
public openSetting() {
|
2023-06-02 21:49:22 +08:00
|
|
|
if (!this.setting) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this.setting.open(this.name);
|
2023-05-08 21:00:41 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public loadData(storageName: string) {
|
|
|
|
|
if (typeof this.data[storageName] === "undefined") {
|
|
|
|
|
this.data[storageName] = "";
|
|
|
|
|
}
|
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
|
fetchPost("/api/file/getFile", {path: `/data/storage/petal/${this.name}/${storageName}`}, (response) => {
|
2023-05-16 22:18:33 +08:00
|
|
|
if (response.code !== 404) {
|
2023-05-08 21:00:41 +08:00
|
|
|
this.data[storageName] = response;
|
|
|
|
|
}
|
|
|
|
|
resolve(this.data[storageName]);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public saveData(storageName: string, data: any) {
|
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
|
const pathString = `/data/storage/petal/${this.name}/${storageName}`;
|
2023-05-16 22:18:33 +08:00
|
|
|
let file: File;
|
|
|
|
|
if (typeof data === "object") {
|
|
|
|
|
file = new File([new Blob([JSON.stringify(data)], {
|
|
|
|
|
type: "application/json"
|
|
|
|
|
})], pathString.split("/").pop());
|
|
|
|
|
} else {
|
|
|
|
|
file = new File([new Blob([data])], pathString.split("/").pop());
|
|
|
|
|
}
|
2023-05-08 21:00:41 +08:00
|
|
|
const formData = new FormData();
|
2023-05-09 19:27:47 +08:00
|
|
|
formData.append("path", pathString);
|
|
|
|
|
formData.append("file", file);
|
|
|
|
|
formData.append("isDir", "false");
|
2023-05-08 21:00:41 +08:00
|
|
|
fetchPost("/api/file/putFile", formData, (response) => {
|
2023-05-09 19:17:16 +08:00
|
|
|
this.data[storageName] = data;
|
2023-05-08 21:00:41 +08:00
|
|
|
resolve(response);
|
|
|
|
|
});
|
|
|
|
|
});
|
2023-05-04 18:28:57 +08:00
|
|
|
}
|
2023-05-10 00:01:47 +08:00
|
|
|
|
2023-05-15 22:33:36 +08:00
|
|
|
public removeData(storageName: string) {
|
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
|
if (!this.data) {
|
|
|
|
|
this.data = {};
|
|
|
|
|
}
|
|
|
|
|
fetchPost("/api/file/removeFile", {path: `/data/storage/petal/${this.name}/${storageName}`}, (response) => {
|
|
|
|
|
delete this.data[storageName];
|
|
|
|
|
resolve(response);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-12 17:11:43 +08:00
|
|
|
public addTab(options: {
|
2023-05-10 00:01:47 +08:00
|
|
|
type: string,
|
|
|
|
|
destroy?: () => void,
|
|
|
|
|
resize?: () => void,
|
|
|
|
|
update?: () => void,
|
|
|
|
|
init: () => void
|
|
|
|
|
}) {
|
2023-05-10 16:11:02 +08:00
|
|
|
/// #if !MOBILE
|
2023-05-10 00:01:47 +08:00
|
|
|
const type2 = this.name + options.type;
|
2023-05-12 17:11:43 +08:00
|
|
|
this.models[type2] = (arg: { data: any, tab: Tab }) => {
|
|
|
|
|
const customObj = new Custom({
|
2023-05-18 19:27:21 +08:00
|
|
|
app: this.app,
|
2023-05-12 17:11:43 +08:00
|
|
|
tab: arg.tab,
|
|
|
|
|
type: type2,
|
|
|
|
|
data: arg.data,
|
|
|
|
|
init: options.init,
|
|
|
|
|
destroy: options.destroy,
|
|
|
|
|
resize: options.resize,
|
|
|
|
|
update: options.update,
|
|
|
|
|
});
|
|
|
|
|
customObj.element.addEventListener("click", () => {
|
|
|
|
|
setPanelFocus(customObj.element.parentElement.parentElement);
|
|
|
|
|
});
|
|
|
|
|
return customObj;
|
|
|
|
|
};
|
2023-05-10 00:01:47 +08:00
|
|
|
return this.models[type2];
|
2023-05-10 16:11:02 +08:00
|
|
|
/// #endif
|
2023-05-10 00:01:47 +08:00
|
|
|
}
|
2023-05-12 17:11:43 +08:00
|
|
|
|
|
|
|
|
public addDock(options: {
|
|
|
|
|
config: IPluginDockTab,
|
|
|
|
|
data: any,
|
|
|
|
|
type: string,
|
|
|
|
|
destroy?: () => void,
|
|
|
|
|
resize?: () => void,
|
|
|
|
|
update?: () => void,
|
|
|
|
|
init: () => void
|
|
|
|
|
}) {
|
|
|
|
|
/// #if !MOBILE
|
|
|
|
|
const type2 = this.name + options.type;
|
2023-05-24 10:47:53 +08:00
|
|
|
if (typeof options.config.index === "undefined") {
|
|
|
|
|
options.config.index = 1000;
|
|
|
|
|
}
|
2023-05-12 17:11:43 +08:00
|
|
|
this.docks[type2] = {
|
|
|
|
|
config: options.config,
|
|
|
|
|
model: (arg: { tab: Tab }) => {
|
|
|
|
|
const customObj = new Custom({
|
2023-05-18 19:27:21 +08:00
|
|
|
app: this.app,
|
2023-05-12 17:11:43 +08:00
|
|
|
tab: arg.tab,
|
|
|
|
|
type: type2,
|
|
|
|
|
data: options.data,
|
|
|
|
|
init: options.init,
|
|
|
|
|
destroy: options.destroy,
|
|
|
|
|
resize: options.resize,
|
|
|
|
|
update: options.update,
|
2023-05-12 17:13:11 +08:00
|
|
|
});
|
2023-05-12 17:11:43 +08:00
|
|
|
customObj.element.addEventListener("click", (event: MouseEvent) => {
|
|
|
|
|
setPanelFocus(customObj.element);
|
|
|
|
|
if (hasClosestByAttribute(event.target as HTMLElement, "data-type", "min")) {
|
|
|
|
|
getDockByType(type2).toggleModel(type2);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
customObj.element.classList.add("sy__" + type2);
|
2023-05-12 17:13:11 +08:00
|
|
|
return customObj;
|
2023-05-12 17:11:43 +08:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
return this.docks[type2];
|
|
|
|
|
/// #endif
|
|
|
|
|
}
|
2023-05-19 20:27:14 +08:00
|
|
|
|
|
|
|
|
public addFloatLayer = (options: {
|
|
|
|
|
ids: string[],
|
|
|
|
|
defIds?: string[],
|
|
|
|
|
x?: number,
|
|
|
|
|
y?: number,
|
|
|
|
|
targetElement?: HTMLElement,
|
|
|
|
|
isBacklink: boolean,
|
|
|
|
|
}) => {
|
|
|
|
|
window.siyuan.blockPanels.push(new BlockPanel({
|
|
|
|
|
app: this.app,
|
|
|
|
|
targetElement: options.targetElement,
|
|
|
|
|
isBacklink: options.isBacklink,
|
|
|
|
|
x: options.x,
|
|
|
|
|
y: options.y,
|
|
|
|
|
nodeIds: options.ids,
|
|
|
|
|
defIds: options.defIds,
|
|
|
|
|
}));
|
2023-05-21 23:14:05 +08:00
|
|
|
};
|
2023-05-04 18:28:57 +08:00
|
|
|
}
|