99 lines
3.8 KiB
JavaScript
99 lines
3.8 KiB
JavaScript
export function runtimeBootstrapScript(basePath = "") {
|
|
return `(() => {
|
|
const runtime = {
|
|
basePath: ${JSON.stringify(basePath)},
|
|
hooks: {},
|
|
mounts: {},
|
|
registerMount(pluginId, hookName, mountId, mountFn) {
|
|
const key = pluginId + ':' + hookName + ':' + mountId;
|
|
this.mounts[key] = mountFn;
|
|
},
|
|
async loadScript(url) {
|
|
if (document.querySelector('script[data-runtime-src="' + url + '"]')) return;
|
|
await new Promise((resolve, reject) => {
|
|
const script = document.createElement('script');
|
|
script.src = url;
|
|
script.async = true;
|
|
script.dataset.runtimeSrc = url;
|
|
script.onload = () => resolve();
|
|
script.onerror = reject;
|
|
document.head.appendChild(script);
|
|
});
|
|
},
|
|
ensureCss(url) {
|
|
if (!url || document.querySelector('link[data-runtime-css="' + url + '"]')) return;
|
|
const link = document.createElement('link');
|
|
link.rel = 'stylesheet';
|
|
link.href = url;
|
|
link.dataset.runtimeCss = url;
|
|
document.head.appendChild(link);
|
|
},
|
|
async fetchHooks() {
|
|
const res = await fetch(this.basePath + '/api/runtime/hooks', { credentials: 'include' });
|
|
if (!res.ok) throw new Error('hook fetch failed: ' + res.status);
|
|
const data = await res.json();
|
|
this.hooks = data.hooks || {};
|
|
return data;
|
|
},
|
|
toolbarAnchor() {
|
|
return document.querySelector('[data-mailcow-plugin-hook="mail.compose.toolbar"]')
|
|
|| document.querySelector('.compose-toolbar')
|
|
|| document.querySelector('[role="toolbar"]')
|
|
|| document.body;
|
|
},
|
|
attachmentsAnchor() {
|
|
return document.querySelector('[data-mailcow-plugin-hook="mail.compose.attachments"]')
|
|
|| document.querySelector('.compose-attachments')
|
|
|| document.querySelector('[data-attachments]')
|
|
|| document.body;
|
|
},
|
|
async mountContribution(contribution) {
|
|
if (contribution.cssUrl) this.ensureCss(contribution.cssUrl);
|
|
if (contribution.mountUrl) await this.loadScript(contribution.mountUrl);
|
|
const key = contribution.pluginId + ':' + contribution.hook + ':' + contribution.id;
|
|
const mountFn = this.mounts[key];
|
|
if (!mountFn) return;
|
|
const parent = contribution.hook === 'mail.compose.attachments' ? this.attachmentsAnchor() : this.toolbarAnchor();
|
|
let target = parent.querySelector('[data-plugin-target="' + key + '"]');
|
|
if (!target) {
|
|
target = document.createElement('div');
|
|
target.dataset.pluginTarget = key;
|
|
target.className = 'mailcow-plugin-target';
|
|
parent.appendChild(target);
|
|
}
|
|
const context = {
|
|
basePath: this.basePath,
|
|
contribution,
|
|
hooks: this.hooks,
|
|
apiBase: contribution.apiBase,
|
|
uiBase: contribution.uiBase,
|
|
user: window.MAILCOW_USER || null,
|
|
};
|
|
await mountFn(target, context);
|
|
},
|
|
async renderHook(hookName) {
|
|
const list = this.hooks[hookName] || [];
|
|
for (const contribution of list) {
|
|
try {
|
|
await this.mountContribution(contribution);
|
|
} catch (error) {
|
|
console.error('Failed to mount contribution', contribution, error);
|
|
}
|
|
}
|
|
},
|
|
async init() {
|
|
window.MAILCOW_PLUGIN_RUNTIME = this;
|
|
await this.fetchHooks();
|
|
await this.renderHook('mail.compose.toolbar');
|
|
await this.renderHook('mail.compose.attachments');
|
|
}
|
|
};
|
|
const start = () => runtime.init().catch((err) => console.error('Mailcow plugin runtime bootstrap failed', err));
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', start, { once: true });
|
|
} else {
|
|
start();
|
|
}
|
|
})();`;
|
|
}
|