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,185 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import {
|
||||
forwardRef, Ref, useCallback, useEffect, useImperativeHandle, useRef, useState,
|
||||
} from 'react';
|
||||
import { Props, WebViewControl } from './types';
|
||||
|
||||
import { View, ViewStyle } from 'react-native';
|
||||
import makeSandboxedIframe from '@joplin/lib/utils/dom/makeSandboxedIframe';
|
||||
import Logger from '@joplin/utils/Logger';
|
||||
import useCss from './utils/useCss';
|
||||
|
||||
const logger = Logger.create('ExtendedWebView');
|
||||
|
||||
// At present, react-native-webview doesn't support web. As such, ExtendedWebView.web.tsx
|
||||
// uses an iframe when running on web.
|
||||
|
||||
const iframeContainerStyles = { height: '100%', width: '100%' };
|
||||
const wrapperStyle: ViewStyle = { height: '100%', width: '100%', flex: 1 };
|
||||
|
||||
const ExtendedWebView = (props: Props, ref: Ref<WebViewControl>) => {
|
||||
const iframeRef = useRef<HTMLIFrameElement|null>(null);
|
||||
|
||||
const injectJs = useCallback((js: string) => {
|
||||
if (!iframeRef.current) {
|
||||
logger.warn(`WebView(${props.webviewInstanceId}): Tried to inject JavaScript after the iframe has unloaded.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// react-native-webview doesn't seem to show a warning in the case where JavaScript
|
||||
// is injected before the first page loads.
|
||||
if (!iframeRef.current.contentWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
iframeRef.current.contentWindow.postMessage({
|
||||
injectJs: js,
|
||||
}, '*');
|
||||
}, [props.webviewInstanceId]);
|
||||
|
||||
useImperativeHandle(ref, (): WebViewControl => {
|
||||
return {
|
||||
injectJS: injectJs,
|
||||
postMessage(message: unknown) {
|
||||
if (!iframeRef.current || !iframeRef.current.contentWindow) {
|
||||
logger.warn(`WebView(${props.webviewInstanceId}): Tried to post a message to an unloaded iframe.`);
|
||||
return;
|
||||
}
|
||||
|
||||
iframeRef.current.contentWindow.postMessage({
|
||||
postMessage: message,
|
||||
}, '*');
|
||||
},
|
||||
};
|
||||
}, [props.webviewInstanceId, injectJs]);
|
||||
|
||||
const [containerElement, setContainerElement] = useState<HTMLDivElement>();
|
||||
const containerRef = useRef(containerElement);
|
||||
containerRef.current = containerElement;
|
||||
|
||||
const onMessageRef = useRef(props.onMessage);
|
||||
onMessageRef.current = props.onMessage;
|
||||
const onLoadEndRef = useRef(props.onLoadEnd);
|
||||
onLoadEndRef.current = props.onLoadEnd;
|
||||
const onLoadStartRef = useRef(props.onLoadStart);
|
||||
onLoadStartRef.current = props.onLoadStart;
|
||||
|
||||
const { injectedJs: cssInjectedJs } = useCss(
|
||||
iframeRef.current ? injectJs : null,
|
||||
props.css,
|
||||
);
|
||||
const injectedJavaScript = props.injectedJavaScript + cssInjectedJs;
|
||||
|
||||
// Don't re-load when injected JS changes. This should match the behavior of the native webview.
|
||||
const injectedJavaScriptRef = useRef(injectedJavaScript);
|
||||
injectedJavaScriptRef.current = injectedJavaScript;
|
||||
|
||||
useEffect(() => {
|
||||
const headHtml = `
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
|
||||
<meta charset="utf-8"/>
|
||||
<!-- Open links in a new window by default -->
|
||||
<base target="_blank"/>
|
||||
`;
|
||||
|
||||
const scripts = [
|
||||
`
|
||||
window.ReactNativeWebView = {
|
||||
postMessage: (message) => {
|
||||
parent.postMessage(message, '*');
|
||||
},
|
||||
supportsNonStringMessages: true,
|
||||
};
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event.source !== parent || event.origin === 'react-native') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.data.postMessage) {
|
||||
window.dispatchEvent(
|
||||
new MessageEvent(
|
||||
'message',
|
||||
{
|
||||
data: event.data.postMessage,
|
||||
origin: 'react-native'
|
||||
},
|
||||
),
|
||||
);
|
||||
} else if (event.data.injectJs) {
|
||||
eval('(() => { ' + event.data.injectJs + ' })()');
|
||||
}
|
||||
});
|
||||
`,
|
||||
injectedJavaScriptRef.current,
|
||||
];
|
||||
|
||||
const { iframe } = makeSandboxedIframe({
|
||||
bodyHtml: props.html,
|
||||
headHtml: headHtml,
|
||||
scripts,
|
||||
|
||||
// allow-popups-to-escape-sandbox: Allows PDF previews to work on target="_blank" links.
|
||||
// allow-popups: Allows links to open in a new tab.
|
||||
permissions: 'allow-scripts allow-modals allow-popups allow-popups-to-escape-sandbox',
|
||||
allow: 'clipboard-write; clipboard-read; fullscreen \'self\'; autoplay \'self\'; local-fonts \'self\'; encrypted-media \'self\'',
|
||||
});
|
||||
|
||||
if (containerRef.current) {
|
||||
containerRef.current.replaceChildren(iframe);
|
||||
}
|
||||
|
||||
iframeRef.current = iframe;
|
||||
|
||||
iframe.style.height = '100%';
|
||||
iframe.style.width = '100%';
|
||||
iframe.style.border = 'none';
|
||||
|
||||
const messageListener = (event: MessageEvent) => {
|
||||
if (event.source !== iframe.contentWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
onMessageRef.current?.({ nativeEvent: { data: event.data } });
|
||||
};
|
||||
window.addEventListener('message', messageListener);
|
||||
if (!iframe.loading) {
|
||||
onLoadStartRef.current?.();
|
||||
onLoadEndRef.current?.();
|
||||
} else {
|
||||
iframe.onload = () => onLoadEndRef.current?.();
|
||||
iframe.onloadstart = () => onLoadStartRef.current?.();
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('message', messageListener);
|
||||
|
||||
if (iframeRef.current.parentElement) {
|
||||
iframeRef.current.remove();
|
||||
}
|
||||
iframeRef.current = null;
|
||||
};
|
||||
}, [props.html]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!iframeRef.current || !containerElement) return;
|
||||
if (iframeRef.current.parentElement) {
|
||||
iframeRef.current.remove();
|
||||
}
|
||||
|
||||
containerElement.replaceChildren(iframeRef.current);
|
||||
}, [containerElement]);
|
||||
|
||||
return (
|
||||
<View style={[wrapperStyle, props.style]}>
|
||||
<div
|
||||
ref={setContainerElement}
|
||||
className='iframe-container'
|
||||
style={iframeContainerStyles}
|
||||
></div>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default forwardRef(ExtendedWebView);
|
||||
在新工单中引用
屏蔽一个用户