1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00
joplin/packages/app-desktop/services/plugins/UserWebviewIndex.js

174 lines
4.6 KiB
JavaScript

// This is the API that JS files loaded from the webview can see
const webviewApiPromises_ = {};
let viewMessageHandler_ = () => {};
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
const webviewApi = {
postMessage: function(message) {
const messageId = `userWebview_${Date.now()}${Math.random()}`;
const promise = new Promise((resolve, reject) => {
webviewApiPromises_[messageId] = { resolve, reject };
});
window.postMessage({
target: 'postMessageService.message',
message: {
from: 'userWebview',
to: 'plugin',
id: messageId,
content: message,
},
});
return promise;
},
onMessage: function(viewMessageHandler) {
viewMessageHandler_ = viewMessageHandler;
window.postMessage({
target: 'postMessageService.registerViewMessageHandler',
});
},
};
(function() {
function docReady(fn) {
if (document.readyState === 'complete' || document.readyState === 'interactive') {
setTimeout(fn, 1);
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
function fileExtension(path) {
if (!path) throw new Error('Path is empty');
const output = path.split('.');
if (output.length <= 1) return '';
return output[output.length - 1];
}
docReady(() => {
const rootElement = document.createElement('div');
document.getElementsByTagName('body')[0].appendChild(rootElement);
const contentElement = document.createElement('div');
contentElement.setAttribute('id', 'joplin-plugin-content');
rootElement.appendChild(contentElement);
const headElement = document.getElementsByTagName('head')[0];
const addedScripts = {};
function addScript(scriptPath, id = null) {
const ext = fileExtension(scriptPath).toLowerCase();
if (ext === 'js') {
const script = document.createElement('script');
script.src = scriptPath;
if (id) script.id = id;
headElement.appendChild(script);
} else if (ext === 'css') {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = scriptPath;
if (id) link.id = id;
headElement.appendChild(link);
} else {
throw new Error(`Unsupported script: ${scriptPath}`);
}
}
const ipc = {
setHtml: (args) => {
contentElement.innerHTML = args.html;
// console.debug('UserWebviewIndex: setting html to', args.html);
window.requestAnimationFrame(() => {
console.debug('UserWebviewIndex: setting html callback', args.hash);
window.postMessage({ target: 'UserWebview', message: 'htmlIsSet', hash: args.hash }, '*');
});
},
setScript: (args) => {
const { script, key } = args;
const scriptPath = `file://${script}`;
const elementId = `joplin-script-${key}`;
if (addedScripts[elementId]) {
document.getElementById(elementId).remove();
delete addedScripts[elementId];
}
addScript(scriptPath, elementId);
},
setScripts: (args) => {
const scripts = args.scripts;
if (!scripts) return;
for (let i = 0; i < scripts.length; i++) {
const scriptPath = `file://${scripts[i]}`;
if (addedScripts[scriptPath]) continue;
addedScripts[scriptPath] = true;
addScript(scriptPath);
}
},
'postMessageService.response': (event) => {
const message = event.message;
const promise = webviewApiPromises_[message.responseId];
if (!promise) {
console.warn('postMessageService.response: Could not find recorded promise to process message response', message);
return;
}
if (message.error) {
promise.reject(message.error);
} else {
promise.resolve(message.response);
}
},
'postMessageService.plugin_message': (message) => {
if (!viewMessageHandler_) {
console.warn('postMessageService.plugin_message: Could not process message because no onMessage handler was defined', message);
return;
}
viewMessageHandler_(message);
},
};
// respond to window.postMessage({})
window.addEventListener('message', ((event) => {
if (!event.data || event.data.target !== 'webview') return;
const callName = event.data.name;
const args = event.data.args;
if (!ipc[callName]) {
console.warn('Missing IPC function:', event.data);
} else {
console.debug('UserWebviewIndex: Got message', callName, args);
ipc[callName](args);
}
}));
// Send a message to the containing component to notify it that the
// view content is fully ready.
//
// Need to send it with a delay to make sure all listeners are
// ready when the message is sent.
window.requestAnimationFrame(() => {
console.debug('UserWebViewIndex: calling isReady');
window.postMessage({ target: 'UserWebview', message: 'ready' }, '*');
});
});
})();