feat: Initial clean release of CHM
一些检查失败了
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
一些检查失败了
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
这个提交包含在:
@@ -0,0 +1,145 @@
|
||||
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<string, unknown>;
|
||||
}
|
||||
|
||||
declare const window: ExtendedWindow;
|
||||
|
||||
let themeCssElement: HTMLStyleElement|null = null;
|
||||
|
||||
const initializeDialogWebView = (messageChannelId: string) => {
|
||||
const loadedPaths: Set<string> = new Set();
|
||||
let contentResizeObserver: ResizeObserver|null = null;
|
||||
let observedElement: HTMLElement|null = null;
|
||||
let observedContentSize: { width: number; height: number }|null = null;
|
||||
let pageHideListenerRegistered = false;
|
||||
|
||||
const measureContent = (element: HTMLElement) => {
|
||||
const viewportWidth = document.documentElement.clientWidth;
|
||||
const scrollbarWidth = Math.max(0, window.innerWidth - viewportWidth);
|
||||
// Include the scrollbar gutter when the content fills the viewport so feeding this
|
||||
// measurement back into the iframe does not repeatedly shrink it.
|
||||
const width = element.clientWidth === viewportWidth ? element.clientWidth + scrollbarWidth : element.clientWidth;
|
||||
return { width, height: element.clientHeight };
|
||||
};
|
||||
const stopObservingContentSize = () => {
|
||||
contentResizeObserver?.disconnect();
|
||||
contentResizeObserver = null;
|
||||
observedElement = null;
|
||||
observedContentSize = null;
|
||||
if (pageHideListenerRegistered) {
|
||||
window.removeEventListener('pagehide', stopObservingContentSize);
|
||||
pageHideListenerRegistered = false;
|
||||
}
|
||||
};
|
||||
const observeContentSize = (element: HTMLElement) => {
|
||||
if (observedElement === element && contentResizeObserver) return;
|
||||
|
||||
stopObservingContentSize();
|
||||
observedElement = element;
|
||||
observedContentSize = measureContent(element);
|
||||
contentResizeObserver = new ResizeObserver(() => {
|
||||
if (observedElement === element) observedContentSize = measureContent(element);
|
||||
});
|
||||
contentResizeObserver.observe(element, { box: 'border-box' });
|
||||
window.addEventListener('pagehide', stopObservingContentSize);
|
||||
pageHideListenerRegistered = true;
|
||||
};
|
||||
|
||||
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 (watchForSizeChanges = false) => {
|
||||
// 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;
|
||||
if (watchForSizeChanges) {
|
||||
observeContentSize(element);
|
||||
} else {
|
||||
stopObservingContentSize();
|
||||
}
|
||||
const contentSize = observedContentSize ?? measureContent(element);
|
||||
return {
|
||||
width: contentSize.width * dpr,
|
||||
height: contentSize.height * dpr,
|
||||
};
|
||||
},
|
||||
};
|
||||
const messenger = new WebViewToRNMessenger<DialogWebViewApi, DialogMainProcessApi>(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;
|
||||
在新工单中引用
屏蔽一个用户