import { DialogWebViewApi, DialogMainProcessApi, WebViewPostMessageCallback, DialogSetOnMessageListenerCallback } from '../types'; import reportUnhandledErrors from './utils/reportUnhandledErrors'; import wrapConsoleLog from './utils/wrapConsoleLog'; import WebViewToRNMessenger from '../../../utils/ipc/WebViewToRNMessenger'; import getFormData from './utils/getFormData'; interface ExtendedWindow extends Window { webviewApi: { postMessage: WebViewPostMessageCallback; onMessage: DialogSetOnMessageListenerCallback; }; exports: Record; } declare const window: ExtendedWindow; let themeCssElement: HTMLStyleElement|null = null; const initializeDialogWebView = (messageChannelId: string) => { const loadedPaths: Set = new Set(); type ScriptType = 'js'|'css'; const includeScriptsOrStyles = (type: ScriptType, paths: string[]) => { for (const path of paths) { if (loadedPaths.has(path)) { continue; } loadedPaths.add(path); if (type === 'css') { const stylesheetLink = document.createElement('link'); stylesheetLink.rel = 'stylesheet'; stylesheetLink.href = path; document.head.appendChild(stylesheetLink); } else { const script = document.createElement('script'); script.src = path; document.head.appendChild(script); } } }; const localApi: DialogWebViewApi = { includeCssFiles: async (paths: string[]) => { return includeScriptsOrStyles('css', paths); }, includeJsFiles: async (paths: string[]) => { return includeScriptsOrStyles('js', paths); }, runScript: async (key: string, scriptData: string) => { if (loadedPaths.has(key)) { return; } loadedPaths.add(key); if (key.endsWith('.css')) { const stylesheetLink = document.createElement('style'); stylesheetLink.appendChild(document.createTextNode(scriptData)); document.head.appendChild(stylesheetLink); } else { const script = document.createElement('script'); script.appendChild(document.createTextNode(scriptData)); document.head.appendChild(script); } }, getFormData: async () => { return getFormData(); }, setThemeCss: async (css: string) => { themeCssElement?.remove?.(); const styleElement = document.createElement('style'); styleElement.appendChild(document.createTextNode(css)); document.body.appendChild(styleElement); themeCssElement = styleElement; }, getContentSize: async () => { // To convert to React Native pixel units from browser pixel units, // we need to multiply by the devicePixelRatio: const dpr = window.devicePixelRatio ?? 1; const element = document.getElementById('joplin-plugin-content') ?? document.body; return { width: element.clientWidth * dpr, height: element.clientHeight * dpr, }; }, }; const messenger = new WebViewToRNMessenger(messageChannelId, localApi); window.webviewApi = { postMessage: messenger.remoteApi.postMessage, onMessage: messenger.remoteApi.onMessage, }; reportUnhandledErrors(messenger.remoteApi.onError); wrapConsoleLog(messenger.remoteApi.onLog); // If dialog content scripts were bundled with Webpack for NodeJS, // they may expect a global "exports" to be present. window.exports ??= {}; }; export default initializeDialogWebView;