import { useEffect , useState , useMemo , useRef } from 'react' ; import shim from '@joplin/lib/shim' ; import Setting from '@joplin/lib/models/Setting' ; const { themeStyle } = require ( '../../global-style.js' ) ; import markupLanguageUtils from '@joplin/lib/markupLanguageUtils' ; const { assetsToHeaders } = require ( '@joplin/renderer' ) ; interface Source { uri : string ; baseUrl : string ; } interface UseSourceResult { source : Source ; injectedJs : string [ ] ; } function usePrevious ( value , initialValue = null ) { const ref = useRef ( initialValue ) ; useEffect ( ( ) => { ref . current = value ; } ) ; return ref . current ; } export default function useSource ( noteBody , noteMarkupLanguage , themeId , highlightedKeywords , noteResources , paddingBottom , noteHash ) { const [ source , setSource ] = useState < Source > ( undefined ) ; const [ injectedJs , setInjectedJs ] = useState < string [ ] > ( [ ] ) ; const [ resourceLoadedTime , setResourceLoadedTime ] = useState ( 0 ) ; const [ isFirstRender , setIsFirstRender ] = useState ( true ) ; const rendererTheme = useMemo ( ( ) => { return { bodyPaddingTop : '.8em' , bodyPaddingBottom : paddingBottom , ... themeStyle ( themeId ) , } ; } , [ themeId , paddingBottom ] ) ; const markupToHtml = useMemo ( ( ) => { return markupLanguageUtils . newMarkupToHtml ( ) ; } , [ isFirstRender ] ) ; const effectDependencies = [ noteBody , resourceLoadedTime , noteMarkupLanguage , themeId , rendererTheme , highlightedKeywords , noteResources , noteHash , isFirstRender , markupToHtml ] ; const previousDeps = usePrevious ( effectDependencies , [ ] ) ; const changedDeps = effectDependencies . reduce ( ( accum , dependency , index ) => { if ( dependency !== previousDeps [ index ] ) { return { ... accum , [ index ] : true } ; } return accum ; } , { } ) ; const onlyNoteBodyHasChanged = Object . keys ( changedDeps ) . length === 1 && changedDeps [ 0 ] ; useEffect ( ( ) => { if ( onlyNoteBodyHasChanged ) return ( ) => { } ; let cancelled = false ; async function renderNote ( ) { const theme = themeStyle ( themeId ) ; const bodyToRender = noteBody || '' ; const mdOptions = { onResourceLoaded : ( ) => { setResourceLoadedTime ( Date . now ( ) ) ; } , highlightedKeywords : highlightedKeywords , resources : noteResources , codeTheme : theme . codeThemeCss , postMessageSyntax : 'window.joplinPostMessage_' , enableLongPress : shim . mobilePlatform ( ) === 'android' , } ; markupToHtml . clearCache ( noteMarkupLanguage ) ; const result = await markupToHtml . render ( noteMarkupLanguage , bodyToRender , rendererTheme , mdOptions ) ; if ( cancelled ) return ; let html = result . html ; const resourceDownloadMode = Setting . value ( 'sync.resourceDownloadMode' ) ; const js = [ ] ; js . push ( 'try {' ) ; js . push ( shim . injectedJs ( 'webviewLib' ) ) ; js . push ( 'window.joplinPostMessage_ = (msg, args) => { return window.ReactNativeWebView.postMessage(msg); };' ) ; js . push ( 'webviewLib.initialize({ postMessage: msg => { return window.ReactNativeWebView.postMessage(msg); } });' ) ; js . push ( `
				const readyStateCheckInterval = setInterval(function() {
					if (document.readyState === "complete") {
						clearInterval(readyStateCheckInterval);
						if ("${ resourceDownloadMode }" === "manual") webviewLib.setupResourceManualDownload();
						const hash = "${ noteHash }";
						// Gives it a bit of time before scrolling to the anchor
						// so that images are loaded.
						if (hash) {
							setTimeout(() => { 
								const e = document.getElementById(hash);
								if (!e) {
									console.warn('Cannot find hash', hash);
									return;
								}
								e.scrollIntoView();
							}, 500);
						}
					}
				}, 10);
			` ) ; js . push ( '} catch (e) {' ) ; js . push ( '	window.ReactNativeWebView.postMessage("error:" + e.message + ": " + JSON.stringify(e))' ) ; js . push ( '	true;' ) ; js . push ( '}' ) ; js . push ( 'true;' ) ; html = `
				<!DOCTYPE html>
				<html>
					<head>
						<meta charset="UTF-8">
						<meta name="viewport" content="width=device-width, initial-scale=1">
						${ assetsToHeaders ( result . pluginAssets , { asHtml : true } ) }
					</head>
					<body>
						${ html }
					</body>
				</html>
			` ; const tempFile = `${ Setting . value ( 'resourceDir' ) }/NoteBodyViewer.html` ; await shim . fsDriver ( ) . writeFile ( tempFile , html , 'utf8' ) ; if ( cancelled ) return ; setSource ( { uri : `file://${ tempFile }?r=${ Math . round ( Math . random ( ) * 100000000 ) }` , baseUrl : `file://${ Setting . value ( 'resourceDir' ) }/` , } ) ; setInjectedJs ( js ) ; } if ( isFirstRender ) { setIsFirstRender ( false ) ; setSource ( undefined ) ; setInjectedJs ( [ ] ) ; } else { void renderNote ( ) ; } return ( ) => { cancelled = true ; } ; } , effectDependencies ) ; return { source , injectedJs } ; }