Something went wrong. Try again.
A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491import {App} from "../index";import {EventBus} from "./EventBus";import {fetchPost} from "../util/fetch";import {isMobile, isWindow} from "../util/functions";/// #if !MOBILEimport {Custom} from "../layout/dock/Custom";import {getAllEditor, getAllModels} from "../layout/getAll";import {Tab} from "../layout/Tab";import {resizeTopBar, setPanelFocus} from "../layout/util";import {getDockByType} from "../layout/tabUtil";///#elseimport {MobileCustom} from "../mobile/dock/MobileCustom";/// #endifimport {hasClosestByAttribute} from "../protyle/util/hasClosest";import {BlockPanel} from "../block/Panel";import {Setting} from "./Setting";import {clearOBG} from "../layout/dock/util";import {Constants} from "../constants";import {uninstall} from "./uninstall";import {afterLoadPlugin, loadPlugins} from "./loader";import {normalizeStoragePath} from "../util/pathName";
export class Plugin { private app: App; public i18n: IObject; public eventBus: EventBus; public data: any = {}; public displayName: string; public readonly name: string; public protyleSlash: { filter: string[], html: string, id: string, callback: (protyle: import("../protyle").Protyle, nodeElement: HTMLElement) => void }[] = []; // TODO public customBlockRenders: { [key: string]: { icon: string, action: "edit" | "more"[], genCursor: boolean, render: (options: { app: App, element: Element }) => void } } = {}; public topBarIcons: Element[] = []; public setting: Setting; public statusBarIcons: Element[] = []; public commands: ICommand[] = []; public models: { /// #if !MOBILE [key: string]: (options: { tab: Tab, data: any }) => Custom /// #endif } = {}; public docks: { [key: string]: { config: IPluginDockTab, /// #if !MOBILE model: (options: { tab: Tab }) => Custom /// #else mobileModel: (element: Element) => MobileCustom /// #endif } } = {}; private protyleOptionsValue: IProtyleOptions;
constructor(options: { app: App, name: string, displayName: string, i18n: IObject }) { this.app = options.app; this.i18n = options.i18n; this.displayName = options.displayName; this.eventBus = new EventBus(options.name);
// https://github.com/siyuan-note/siyuan/issues/9943 Object.defineProperty(this, "name", { value: options.name, writable: false, });
this.updateProtyleToolbar([]).forEach(toolbarItem => { if (typeof toolbarItem === "string" || Constants.INLINE_TYPE.concat("|").includes(toolbarItem.name)) { return; } if (typeof toolbarItem.hotkey !== "string") { toolbarItem.hotkey = ""; } if (!window.siyuan.config.keymap.plugin) { window.siyuan.config.keymap.plugin = {}; } if (!window.siyuan.config.keymap.plugin[options.name]) { window.siyuan.config.keymap.plugin[options.name] = { [toolbarItem.name]: { default: toolbarItem.hotkey, custom: toolbarItem.hotkey, } }; } if (!window.siyuan.config.keymap.plugin[options.name][toolbarItem.name]) { window.siyuan.config.keymap.plugin[options.name][toolbarItem.name] = { default: toolbarItem.hotkey, custom: toolbarItem.hotkey, }; } else { window.siyuan.config.keymap.plugin[options.name][toolbarItem.name].default = toolbarItem.hotkey; } }); }
public onload(): Promise<void> | void { // 加载 }
public onunload() { // 禁用/关闭 }
public uninstall() { // 卸载 }
public onDataChanged() { // 存储数据变更 // 兼容 3.4.1 以前同步数据使用重载插件的问题 uninstall(this.app, this.name, true); loadPlugins(this.app, [this.name], false).then(() => { afterLoadPlugin(this); getAllEditor().forEach(editor => { editor.protyle.toolbar.update(editor.protyle); }); }); }
public async updateCards(options: ICardData) { return options; }
public onLayoutReady() { // 布局加载完成 }
public addCommand(command: ICommand) { if (typeof command.hotkey !== "string") { command.hotkey = ""; } if (!window.siyuan.config.keymap.plugin) { window.siyuan.config.keymap.plugin = {}; } if (!window.siyuan.config.keymap.plugin[this.name]) { command.customHotkey = command.hotkey; window.siyuan.config.keymap.plugin[this.name] = { [command.langKey]: { default: command.hotkey, custom: command.hotkey, } }; } else if (!window.siyuan.config.keymap.plugin[this.name][command.langKey]) { command.customHotkey = command.hotkey; window.siyuan.config.keymap.plugin[this.name][command.langKey] = { default: command.hotkey, custom: command.hotkey, }; } else if (window.siyuan.config.keymap.plugin[this.name][command.langKey]) { if (typeof window.siyuan.config.keymap.plugin[this.name][command.langKey].custom === "string") { command.customHotkey = window.siyuan.config.keymap.plugin[this.name][command.langKey].custom; } else { command.customHotkey = command.hotkey; } window.siyuan.config.keymap.plugin[this.name][command.langKey]["default"] = command.hotkey; } if (typeof command.customHotkey !== "string") { console.error(`${this.name} - commands data is error and has been removed.`); } else { this.commands.push(command); } }
public addIcons(svg: string) { const svgElement = document.querySelector(`svg[data-name="${this.name}"] defs`); if (svgElement) { svgElement.insertAdjacentHTML("afterbegin", svg); } else { const lastSvgElement = document.querySelector("body > svg:last-of-type"); if (lastSvgElement) { lastSvgElement.insertAdjacentHTML("afterend", `<svg data-name="${this.name}" style="position: absolute; width: 0; height: 0; overflow: hidden;" xmlns="http://www.w3.org/2000/svg"><defs>${svg}</defs></svg>`); } else { 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"><defs>${svg}</defs></svg>`); } } }
public addTopBar(options: { icon: string, title: string, position?: "south" | "left", callback: (evt: MouseEvent) => void }) { if (!options.icon.startsWith("icon") && !options.icon.startsWith("<svg")) { console.error(`plugin ${this.name} addTopBar error: icon must be svg id or svg tag`); return; } const iconElement = document.createElement("div"); iconElement.setAttribute("data-menu", "true"); iconElement.addEventListener("click", options.callback); iconElement.id = `plugin_${this.name}_${this.topBarIcons.length}`; 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 ariaLabel"; 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); iconElement.setAttribute("data-location", options.position || "right"); resizeTopBar(); } if (isMobile() && window.siyuan.storage) { if (!window.siyuan.storage[Constants.LOCAL_PLUGINTOPUNPIN].includes(iconElement.id)) { document.querySelector("#menuAbout")?.after(iconElement); } } else if (!isWindow() && window.siyuan.storage) { if (window.siyuan.storage[Constants.LOCAL_PLUGINTOPUNPIN].includes(iconElement.id)) { iconElement.classList.add("fn__none"); } document.querySelector("#" + (iconElement.getAttribute("data-location") === "right" ? "barPlugins" : "drag"))?.before(iconElement); } this.topBarIcons.push(iconElement); return iconElement; }
public addStatusBar(options: { element: HTMLElement, position?: "right" | "left", }) { /// #if !MOBILE options.element.setAttribute("data-location", options.position || "right"); this.statusBarIcons.push(options.element); const statusElement = document.getElementById("status"); if (statusElement) { if (options.element.getAttribute("data-location") === "right") { statusElement.insertAdjacentElement("beforeend", options.element); } else { statusElement.insertAdjacentElement("afterbegin", options.element); } } return options.element; /// #endif }
public openSetting() { if (!this.setting) { return; } this.setting.open(this.displayName || this.name); }
public loadData(storageName: string): Promise<any> { if (typeof this.data[storageName] === "undefined") { this.data[storageName] = ""; } return new Promise((resolve) => { fetchPost("/api/file/getFile", { path: `/data/storage/petal/${this.name}/${normalizeStoragePath(storageName)}` }, (response) => { this.data[storageName] = response; resolve(this.data[storageName]); }, null, () => { resolve(this.data[storageName]); }); }); }
public saveData(storageName: string, data: any): Promise<any | IWebSocketData> { if (window.siyuan.config.readonly || window.siyuan.isPublish) { return Promise.reject({ code: 403, msg: "Readonly mode or publish mode", data: null }); } return new Promise((resolve, reject) => { const pathString = `/data/storage/petal/${this.name}/${normalizeStoragePath(storageName)}`; let file: File; try { const fileName = pathString.split("/").pop(); if (typeof data === "object") { file = new File([new Blob([JSON.stringify(data)], { type: "application/json" })], fileName); } else { file = new File([new Blob([data])], fileName); } } catch (e) { reject({ code: 400, msg: e instanceof Error ? e.message : String(e), data: null }); return; } const formData = new FormData(); formData.append("path", pathString); formData.append("file", file); formData.append("isDir", "false"); fetchPost("/api/file/putFile", formData, (response) => { this.data[storageName] = data; resolve(response); }); }); }
public removeData(storageName: string): Promise<IWebSocketData> { if (window.siyuan.config.readonly || window.siyuan.isPublish) { return Promise.reject({ code: 403, msg: "Readonly mode or publish mode", data: null } as IWebSocketData); } return new Promise((resolve) => { if (!this.data) { this.data = {}; } fetchPost("/api/file/removeFile", {path: `/data/storage/petal/${this.name}/${normalizeStoragePath(storageName)}`}, (response) => { delete this.data[storageName]; resolve(response); }); }); }
public getOpenedTab() { const tabs: { [key: string]: Custom[] } = {}; const modelKeys = Object.keys(this.models); modelKeys.forEach(item => { tabs[item.replace(this.name, "")] = []; }); /// #if !MOBILE getAllModels().custom.find(item => { if (modelKeys.includes(item.type)) { tabs[item.type.replace(this.name, "")].push(item); } }); /// #endif return tabs; }
public addTab(options: { type: string, destroy?: () => void, beforeDestroy?: () => void, resize?: () => void, update?: () => void, init: () => void }) { /// #if !MOBILE const type2 = this.name + options.type; this.models[type2] = (arg: { data: any, tab: Tab }) => { const customObj = new Custom({ app: this.app, tab: arg.tab, type: type2, data: arg.data, init: options.init, beforeDestroy: options.beforeDestroy, destroy: options.destroy, resize: options.resize, update: options.update, }); customObj.element.addEventListener("click", () => { clearOBG(); setPanelFocus(customObj.element.parentElement.parentElement); }); return customObj; }; return this.models[type2]; /// #endif }
public addDock(options: { config: IPluginDockTab, data: any, type: string, destroy?: () => void, resize?: () => void, update?: () => void, init: () => void }) { const type2 = this.name + options.type; if (typeof options.config.index === "undefined") { options.config.index = 1000; } this.docks[type2] = { config: options.config, /// #if MOBILE mobileModel: (element) => { const customObj = new MobileCustom({ element, type: type2, data: options.data, init: options.init, update: options.update, destroy: options.destroy, }); return customObj; }, /// #else model: (arg: { tab: Tab }) => { const customObj = new Custom({ app: this.app, tab: arg.tab, type: type2, data: options.data, init: options.init, destroy: options.destroy, resize: options.resize, update: options.update, }); 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, "dockPanel"); return customObj; } /// #endif }; if (!window.siyuan.config.keymap.plugin) { window.siyuan.config.keymap.plugin = {}; } if (options.config.hotkey) { if (!window.siyuan.config.keymap.plugin[this.name]) { window.siyuan.config.keymap.plugin[this.name] = { [type2]: { default: options.config.hotkey, custom: options.config.hotkey, } }; } else if (!window.siyuan.config.keymap.plugin[this.name][type2]) { window.siyuan.config.keymap.plugin[this.name][type2] = { default: options.config.hotkey, custom: options.config.hotkey, }; } else if (window.siyuan.config.keymap.plugin[this.name][type2]) { if (typeof window.siyuan.config.keymap.plugin[this.name][type2].custom !== "string") { window.siyuan.config.keymap.plugin[this.name][type2].custom = options.config.hotkey; } window.siyuan.config.keymap.plugin[this.name][type2]["default"] = options.config.hotkey; } } return this.docks[type2]; }
public addFloatLayer = (options: { refDefs: IRefDefs[], x?: number, y?: number, targetElement?: HTMLElement, originalRefBlockIDs?: IObject, isBacklink: boolean, }) => { window.siyuan.blockPanels.push(new BlockPanel({ app: this.app, originalRefBlockIDs: options.originalRefBlockIDs, targetElement: options.targetElement, isBacklink: options.isBacklink, x: options.x, y: options.y, refDefs: options.refDefs, })); };
public updateProtyleToolbar(toolbar: Array<string | IMenuItem>) { return toolbar; }
set protyleOptions(options: IProtyleOptions) { this.protyleOptionsValue = options; }
get protyleOptions() { return this.protyleOptionsValue; }}