siyuan/app/src/plugin/loader.ts

268 lines
10 KiB
TypeScript
Raw Normal View History

2023-05-24 20:07:18 +08:00
import {fetchSyncPost} from "../util/fetch";
import {App} from "../index";
import {Plugin} from "./index";
2023-05-16 18:49:12 +08:00
/// #if !MOBILE
import {resizeTopBar, saveLayout} from "../layout/util";
2023-05-16 18:49:12 +08:00
/// #endif
import {API} from "./API";
import {getFrontend, isMobile, isWindow} from "../util/functions";
import {Constants} from "../constants";
import {uninstall} from "./uninstall";
import {setStorageVal} from "../protyle/util/compatibility";
import {getAllEditor} from "../layout/getAll";
const requireFunc = (key: string) => {
2023-12-04 16:23:38 +08:00
const modules = {
siyuan: API
};
// @ts-ignore
return modules[key]
?? window.require?.(key);
};
if (window.require instanceof Function) {
requireFunc.__proto__ = window.require;
}
const runCode = (code: string, sourceURL: string) => {
return window.eval("(function anonymous(require, module, exports){".concat(code, "\n})\n//# sourceURL=").concat(sourceURL, "\n"));
2023-05-05 14:04:43 +08:00
};
export const loadPlugins = async (app: App, names?: string[]) => {
const response = await fetchSyncPost("/api/petal/loadPetals", {frontend: getFrontend()});
const pluginsStyle = getPluginsStyle();
const pluginsToLoad = !names
? response.data
: response.data.filter((item: IPluginData) => names.includes(item.name));
pluginsToLoad.forEach((item: IPluginData) => {
// 为加快启动速度,不进行 await
loadPluginJS(app, item);
insertPluginCSS(item, pluginsStyle);
2023-05-05 14:04:43 +08:00
});
};
2023-05-16 10:31:01 +08:00
const loadPluginJS = async (app: App, item: IPluginData) => {
2023-05-16 10:31:01 +08:00
const exportsObj: { [key: string]: any } = {};
const moduleObj = {exports: exportsObj};
try {
runCode(item.js, "plugin:" + encodeURIComponent(item.name))(requireFunc, moduleObj, exportsObj);
2023-05-16 10:31:01 +08:00
} catch (e) {
console.error(`plugin ${item.name} run error:`, e);
2023-05-16 10:31:01 +08:00
return;
}
const pluginClass = (moduleObj.exports || exportsObj).default || moduleObj.exports;
if (typeof pluginClass !== "function") {
console.error(`plugin ${item.name} has no export`);
return;
}
if (!(pluginClass.prototype instanceof Plugin)) {
console.error(`plugin ${item.name} does not extends Plugin`);
return;
}
const plugin = new pluginClass({
app,
displayName: item.displayName,
2023-05-16 10:31:01 +08:00
name: item.name,
i18n: item.i18n
}) as Plugin;
2023-05-16 10:31:01 +08:00
app.plugins.push(plugin);
try {
await plugin.onload();
2023-05-16 10:31:01 +08:00
} catch (e) {
console.error(`plugin ${item.name} onload error:`, e);
2023-05-16 10:31:01 +08:00
}
return plugin;
2023-05-16 18:51:13 +08:00
};
2023-05-16 10:31:01 +08:00
const getPluginsStyle = () => {
let pluginsStyle = document.getElementById("pluginsStyle");
if (!pluginsStyle) {
pluginsStyle = document.createElement("style");
pluginsStyle.id = "pluginsStyle"; // 用于将内联样式插入到插件样式前的标识
document.head.append(pluginsStyle);
}
return pluginsStyle;
};
const insertPluginCSS = (item: IPluginData, pluginsStyle: HTMLElement) => {
if (!item.css) {
return;
}
const styleElement = document.createElement("style");
styleElement.id = "pluginsStyle" + item.name;
styleElement.textContent = item.css;
pluginsStyle.insertAdjacentElement("afterend", styleElement);
};
// 启用插件
export const loadPlugin = async (app: App, item: IPluginData) => {
const plugin = await loadPluginJS(app, item);
insertPluginCSS(item, getPluginsStyle());
afterLoadPlugin(plugin);
saveLayout();
getAllEditor().forEach(editor => {
editor.protyle.toolbar.update(editor.protyle);
});
return plugin;
};
const updateDock = (dockItem: Config.IUILayoutDockTab[], index: number, plugin: Plugin, type: string) => {
2023-05-24 20:06:32 +08:00
const dockKeys = Object.keys(plugin.docks);
dockItem.forEach((tabItem: Config.IUILayoutDockTab, tabIndex: number) => {
if (dockKeys.includes(tabItem.type)) {
if (type === "Left") {
plugin.docks[tabItem.type].config.position = index === 0 ? "LeftTop" : "LeftBottom";
} else if (type === "Right") {
plugin.docks[tabItem.type].config.position = index === 0 ? "RightTop" : "RightBottom";
} else if (type === "Bottom") {
plugin.docks[tabItem.type].config.position = index === 0 ? "BottomLeft" : "BottomRight";
}
plugin.docks[tabItem.type].config.index = tabIndex;
plugin.docks[tabItem.type].config.show = tabItem.show;
plugin.docks[tabItem.type].config.size = tabItem.size;
if (!window.siyuan.storage[Constants.LOCAL_PLUGIN_DOCKS][plugin.name]) {
2024-10-21 22:41:07 +08:00
window.siyuan.storage[Constants.LOCAL_PLUGIN_DOCKS][plugin.name] = {};
}
window.siyuan.storage[Constants.LOCAL_PLUGIN_DOCKS][plugin.name][tabItem.type] = plugin.docks[tabItem.type].config;
setStorageVal(Constants.LOCAL_PLUGIN_DOCKS, window.siyuan.storage[Constants.LOCAL_PLUGIN_DOCKS]);
}
2023-05-24 20:06:32 +08:00
});
};
export const afterLoadPlugin = (plugin: Plugin) => {
try {
plugin.onLayoutReady();
} catch (e) {
console.error(`plugin ${plugin.name} onLayoutReady error:`, e);
}
if (!isWindow() || isMobile()) {
plugin.topBarIcons.forEach(element => {
if (document.contains(element)) {
return;
}
if (isMobile()) {
if (!window.siyuan.storage[Constants.LOCAL_PLUGINTOPUNPIN].includes(element.id)) {
document.querySelector("#menuAbout").after(element);
}
} else if (!isWindow()) {
if (window.siyuan.storage[Constants.LOCAL_PLUGINTOPUNPIN].includes(element.id)) {
element.classList.add("fn__none");
}
document.querySelector("#" + (element.getAttribute("data-location") === "right" ? "barPlugins" : "drag")).before(element);
}
});
}
/// #if !MOBILE
resizeTopBar();
plugin.statusBarIcons.forEach(element => {
if (document.contains(element)) {
return;
}
2023-06-01 20:50:49 +08:00
const statusElement = document.getElementById("status");
if (element.getAttribute("data-location") === "right") {
statusElement.insertAdjacentElement("beforeend", element);
} else {
statusElement.insertAdjacentElement("afterbegin", element);
}
});
/// #endif
if (isWindow()) {
return;
}
/// #if !MOBILE
window.siyuan.config.uiLayout.left.data.forEach((dockItem: Config.IUILayoutDockTab[], index: number) => {
2023-05-24 20:06:32 +08:00
updateDock(dockItem, index, plugin, "Left");
});
window.siyuan.config.uiLayout.right.data.forEach((dockItem: Config.IUILayoutDockTab[], index: number) => {
2023-05-24 20:06:32 +08:00
updateDock(dockItem, index, plugin, "Right");
});
window.siyuan.config.uiLayout.bottom.data.forEach((dockItem: Config.IUILayoutDockTab[], index: number) => {
2023-05-24 20:06:32 +08:00
updateDock(dockItem, index, plugin, "Bottom");
});
2023-05-16 10:31:01 +08:00
Object.keys(plugin.docks).forEach(key => {
if (window.siyuan.storage[Constants.LOCAL_PLUGIN_DOCKS][plugin.name] && window.siyuan.storage[Constants.LOCAL_PLUGIN_DOCKS][plugin.name][key]) {
plugin.docks[key].config = window.siyuan.storage[Constants.LOCAL_PLUGIN_DOCKS][plugin.name][key];
}
2023-05-16 10:31:01 +08:00
const dock = plugin.docks[key];
2024-03-22 09:45:26 +08:00
const hotkey = window.siyuan.config.keymap.plugin[plugin.name] ? window.siyuan.config.keymap.plugin[plugin.name][key]?.custom : undefined;
2023-05-16 10:31:01 +08:00
if (dock.config.position.startsWith("Left")) {
window.siyuan.layout.leftDock.genButton([{
type: key,
size: dock.config.size,
show: dock.config.show,
2023-05-16 10:31:01 +08:00
icon: dock.config.icon,
title: dock.config.title,
hotkey
}], dock.config.position === "LeftBottom" ? 1 : 0, dock.config.index);
2023-05-16 10:31:01 +08:00
} else if (dock.config.position.startsWith("Bottom")) {
window.siyuan.layout.bottomDock.genButton([{
type: key,
size: dock.config.size,
show: dock.config.show,
2023-05-16 10:31:01 +08:00
icon: dock.config.icon,
title: dock.config.title,
hotkey
}], dock.config.position === "BottomRight" ? 1 : 0, dock.config.index);
2023-05-16 10:31:01 +08:00
} else if (dock.config.position.startsWith("Right")) {
window.siyuan.layout.rightDock.genButton([{
type: key,
size: dock.config.size,
show: dock.config.show,
2023-05-16 10:31:01 +08:00
icon: dock.config.icon,
title: dock.config.title,
hotkey
}], dock.config.position === "RightBottom" ? 1 : 0, dock.config.index);
2023-05-16 10:31:01 +08:00
}
});
/// #endif
2023-05-24 20:06:32 +08:00
};
export const reloadPlugin = async (app: App, data: {
upsertCodePlugins?: string[],
upsertDataPlugins?: string[],
removePlugins?: string[]
} = {}) => {
const {upsertCodePlugins = [], upsertDataPlugins = [], removePlugins = []} = data;
removePlugins.forEach((item) => {
uninstall(app, item, true);
});
upsertCodePlugins.forEach((item) => {
uninstall(app, item, false);
});
loadPlugins(app, upsertCodePlugins).then(() => {
app.plugins.forEach(item => {
if (upsertCodePlugins.includes(item.name)) {
afterLoadPlugin(item);
}
});
});
// 先收集需要处理的插件,避免在遍历过程中修改数组导致重复执行
const dataChangedPlugins = app.plugins.filter(item => upsertDataPlugins.includes(item.name));
dataChangedPlugins.forEach(item => {
try {
item.onDataChanged();
} catch (e) {
console.error(`plugin ${item.name} onDataChanged error:`, e);
}
});
/// #if !MOBILE
saveLayout();
/// #endif
};
// 重启插件
export const restartPlugin = async (app: App, plugin: Plugin) => {
uninstall(app, plugin.name, false, true);
app.plugins.push(plugin);
try {
await plugin.onload();
} catch (e) {
console.error(`plugin ${plugin.name} onload error:`, e);
}
afterLoadPlugin(plugin);
getAllEditor().forEach(editor => {
editor.protyle.toolbar.update(editor.protyle);
});
};