import React, { useEffect, useMemo, useState } from 'react';
import { Platform, StyleSheet, TextInput } from 'react-native';
import {
WebView,
type WebViewProps,
type WebViewMessageEvent,
} from 'react-native-webview';
import { editorHtml } from '../simpleWebEditor/build/editorHtml';
import { type EditorMessage } from '../types/Messaging';
import { useKeyboard } from '../utils';
import type { EditorBridge } from '../types';
import { getInjectedJS, getInjectedJSBeforeContentLoad } from './utils';
import { isFabric } from '../utils/misc';
import { CoreEditorActionType } from '../bridges/core';
interface RichTextProps extends WebViewProps {
editor: EditorBridge;
/** Makes it so that the onMessage method provided by Tentap does not fire if you have your own custom onMessage method.
* Introduced for backwards compatibility with previous versions of Tentap that had this behaviour by default.
* */
exclusivelyUseCustomOnMessage?: boolean;
}
const styles = StyleSheet.create({
hiddenInput: {
display: 'none',
width: 0,
height: 0,
position: 'absolute',
flex: 1,
top: 0,
left: 0,
},
});
const DEV_SERVER_URL = 'http://localhost:3000';
// TODO: make it a prop
const TOOLBAR_HEIGHT = 44;
export const RichText = ({
editor,
onMessage,
exclusivelyUseCustomOnMessage = true,
...props
}: RichTextProps) => {
const [editorHeight, setEditorHeight] = useState(0);
const [key, setKey] = useState('webview');
const [loaded, setLoaded] = useState(isFabric());
const { keyboardHeight, isKeyboardUp } = useKeyboard();
const source: WebViewProps['source'] = editor.DEV
? { uri: editor.DEV_SERVER_URL || DEV_SERVER_URL }
: {
html: editor.customSource || editorHtml,
baseUrl: editor.webviewBaseURL,
};
const onWebviewMessage = (event: WebViewMessageEvent) => {
onMessage && onMessage(event);
if (exclusivelyUseCustomOnMessage && onMessage) return;
const { data } = event.nativeEvent;
// on expo-web we sometimes get react-dev messages that come in as objects - so we ignore these
if (typeof data !== 'string') return;
// Parse the message sent from the editor
const { type, payload } = JSON.parse(data) as EditorMessage;
if (type === CoreEditorActionType.DocumentHeight) {
setEditorHeight(payload);
}
editor.bridgeExtensions?.forEach((e) => {
e.onEditorMessage && e.onEditorMessage({ type, payload }, editor);
});
};
useEffect(() => {
const setDocBottomPadding = (height: number) => {
if (editor.webviewRef.current) {
editor.webviewRef.current.injectJavaScript(`
doc = document.querySelector('.ProseMirror');
if(doc) doc.style.paddingBottom = '${height}px';
`);
}
};
if (editor.webviewRef.current && Platform.OS === 'android') {
// In case the keyboard is up we need to add padding to the bottom of the document
const paddingThreshold =
editor.avoidIosKeyboard && keyboardHeight && isKeyboardUp // avoidIosKeyboard should change to avoidKeyboard because used in android too (v1.0.0)
? TOOLBAR_HEIGHT
: 0;
setTimeout(() => {
setDocBottomPadding(paddingThreshold);
editor.updateScrollThresholdAndMargin(paddingThreshold);
}, 200);
}
// On iOS we want to control the scroll and not use the scrollview that comes with react-native-webview
// That's way we can get better exp on scroll and scroll to element when we need to
if (
editor.avoidIosKeyboard &&
editor.webviewRef.current &&
Platform.OS === 'ios'
) {
if (keyboardHeight) {
setDocBottomPadding(keyboardHeight + 10);
editor.updateScrollThresholdAndMargin(keyboardHeight + 10);
} else {
setDocBottomPadding(0);
editor.updateScrollThresholdAndMargin(0);
}
}
}, [editor.avoidIosKeyboard, editor, keyboardHeight, isKeyboardUp]);
const injectedJavaScript = useMemo(
() => getInjectedJS(editor.bridgeExtensions || []),
[editor.bridgeExtensions]
);
return (
<>
{editor.autofocus && Platform.OS === 'android' && (
)}
{
setLoaded(true);
// This is a workaround for iOS to make sure the webview is loaded
// See https://github.com/react-native-webview/react-native-webview/issues/3578
if (Platform.OS === 'ios' && key === 'webview') {
setKey('webview_reloaded');
}
props.onLoad && props.onLoad(e);
}}
/>
>
);
};
const RichTextStyles = StyleSheet.create({
fullScreen: {
flex: 1,
},
});