文件
mynode/packages/app-desktop/services/plugins/UserWebviewIndex.js
chen 301194963b
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
feat: Initial clean release of CHM
2026-11-04 11:29:39 +08:00

254 行
6.6 KiB
JavaScript

// This is the API that JS files loaded from the webview can see
(function() {
const webviewApiPromises_ = {};
let viewMessageHandler_ = () => {};
const postMessage = (message) => {
parent.postMessage(message, '*');
};
window.webviewApi = {
postMessage: function(message) {
const messageId = `userWebview_${Date.now()}${Math.random()}`;
const promise = new Promise((resolve, reject) => {
webviewApiPromises_[messageId] = { resolve, reject };
});
postMessage({
target: 'postMessageService.message',
message: {
from: 'userWebview',
to: 'plugin',
id: messageId,
content: message,
},
});
return promise;
},
onMessage: function(viewMessageHandler) {
viewMessageHandler_ = viewMessageHandler;
postMessage({
target: 'postMessageService.registerViewMessageHandler',
});
},
menuPopupFromTemplate: (args) => {
postMessage({
target: 'webviewApi.menuPopupFromTemplate',
args,
});
},
};
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];
}
function serializeForm(form) {
const output = {};
const formData = new FormData(form);
for (const key of formData.keys()) {
output[key] = formData.get(key);
}
return output;
}
function serializeForms(document) {
const forms = document.getElementsByTagName('form');
const output = {};
let untitledIndex = 0;
for (const form of forms) {
const name = `${form.getAttribute('name')}` || (`form${untitledIndex++}`);
output[name] = serializeForm(form);
}
return output;
}
function watchElementSize(element, onChange) {
const emitSizeChange = () => {
onChange(element.getBoundingClientRect());
};
const observer = new ResizeObserver(emitSizeChange);
observer.observe(element);
// Initial size
requestAnimationFrame(emitSizeChange);
}
docReady(() => {
const rootElement = document.createElement('div');
rootElement.setAttribute('id', 'joplin-plugin-content-root');
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(() => {
// eslint-disable-next-line no-console
console.debug('UserWebviewIndex: setting html callback', args.hash);
postMessage({ target: 'UserWebview', message: 'htmlIsSet', hash: args.hash });
});
},
setScript: (args) => {
const { script, key } = args;
const scriptPath = `joplin-content://plugin-webview/${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 = `joplin-content://plugin-webview/${scripts[i]}`;
if (addedScripts[scriptPath]) continue;
addedScripts[scriptPath] = true;
addScript(scriptPath);
}
},
serializeForms: () => {
postMessage({
target: 'UserWebview',
message: 'serializedForms',
formData: serializeForms(document),
});
},
'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 {
// eslint-disable-next-line no-console
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(() => {
// eslint-disable-next-line no-console
console.debug('UserWebViewIndex: calling isReady');
postMessage({ target: 'UserWebview', message: 'ready' });
});
const sendFormSubmit = () => {
postMessage({ target: 'UserWebview', message: 'form-submit' });
};
const sendDismiss = () => {
postMessage({ target: 'UserWebview', message: 'dismiss' });
};
document.addEventListener('submit', () => {
sendFormSubmit();
});
document.addEventListener('keydown', event => {
if (event.key === 'Enter' && event.target.tagName === 'INPUT' && event.target.type === 'text') {
sendFormSubmit();
} else if (event.key === 'Escape') {
sendDismiss();
}
});
watchElementSize(document.getElementById('joplin-plugin-content'), size => {
postMessage({
target: 'UserWebview',
message: 'updateContentSize',
size,
});
});
});
})();