import * as React from 'react'; import useOnMessage, { HandleMessageCallback, OnMarkForDownloadCallback } from './hooks/useOnMessage'; import { useRef, useCallback, useState, useMemo } from 'react'; import { View, ViewStyle } from 'react-native'; import ExtendedWebView from '../ExtendedWebView'; import { WebViewControl } from '../ExtendedWebView/types'; import useOnResourceLongPress from './hooks/useOnResourceLongPress'; import useRenderer from './hooks/useRenderer'; import { OnWebViewMessageHandler } from './types'; import useRerenderHandler, { ResourceInfo } from './hooks/useRerenderHandler'; import useSource from './hooks/useSource'; import Setting from '@joplin/lib/models/Setting'; import uuid from '@joplin/lib/uuid'; import { PluginStates } from '@joplin/lib/services/plugins/reducer'; import useContentScripts from './hooks/useContentScripts'; import { MarkupLanguage } from '@joplin/renderer'; import shim from '@joplin/lib/shim'; import CommandService from '@joplin/lib/services/CommandService'; import { AppState } from '../../utils/types'; import { connect } from 'react-redux'; interface Props { themeId: number; style: ViewStyle; fontSize: number; noteBody: string; noteMarkupLanguage: MarkupLanguage; highlightedKeywords: string[]; noteResources: Record; paddingBottom: number; initialScroll: number|null; noteHash: string; onCheckboxChange?: HandleMessageCallback; onRequestEditResource?: HandleMessageCallback; onMarkForDownload?: OnMarkForDownloadCallback; onScroll: (scrollTop: number)=> void; onLoadEnd?: ()=> void; pluginStates: PluginStates; } const onJoplinLinkClick = async (message: string) => { try { await CommandService.instance().execute('openItem', message); } catch (error) { await shim.showErrorDialog(error.message); } }; function NoteBodyViewer(props: Props) { const webviewRef = useRef(null); const onScroll = useCallback(async (scrollTop: number) => { props.onScroll(scrollTop); }, [props.onScroll]); const onResourceLongPress = useOnResourceLongPress( { onJoplinLinkClick, onRequestEditResource: props.onRequestEditResource, }, ); const onPostMessage = useOnMessage(props.noteBody, { onMarkForDownload: props.onMarkForDownload, onJoplinLinkClick, onRequestEditResource: props.onRequestEditResource, onCheckboxChange: props.onCheckboxChange, onResourceLongPress, }); const [webViewLoaded, setWebViewLoaded] = useState(false); const [onWebViewMessage, setOnWebViewMessage] = useState(()=>()=>{}); // The renderer can write to whichever temporary directory we choose. As such, // we use a subdirectory of the main temporary directory for security reasons. const tempDir = useMemo(() => { return `${Setting.value('tempDir')}/${uuid.createNano()}`; }, []); const renderer = useRenderer({ webViewLoaded, onScroll, webviewRef, onPostMessage, setOnWebViewMessage, tempDir, }); const contentScripts = useContentScripts(props.pluginStates); useRerenderHandler({ renderer, fontSize: props.fontSize, noteBody: props.noteBody, noteMarkupLanguage: props.noteMarkupLanguage, themeId: props.themeId, highlightedKeywords: props.highlightedKeywords, noteResources: props.noteResources, noteHash: props.noteHash, initialScroll: props.initialScroll, paddingBottom: props.paddingBottom, contentScripts, }); const onLoadEnd = useCallback(() => { setWebViewLoaded(true); if (props.onLoadEnd) props.onLoadEnd(); }, [props.onLoadEnd]); const { html, injectedJs } = useSource(tempDir, props.themeId); return ( ); } export default connect((state: AppState) => ({ themeId: state.settings.theme, fontSize: state.settings['style.viewer.fontSize'], pluginStates: state.pluginService.plugins, }))(NoteBodyViewer);