import Clipboard from '@react-native-clipboard/clipboard'; import absolutify from 'absolutify'; import { getLinkPreview } from 'link-preview-js'; import React, { useEffect, useRef, useState } from 'react'; import { ActivityIndicator, Appearance, Keyboard, KeyboardAvoidingView, Platform, SafeAreaView, Text, TouchableOpacity, useWindowDimensions, View } from 'react-native'; import Animated, { Easing, timing, useValue } from 'react-native-reanimated'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import WebView from 'react-native-webview'; import ShareExtension from 'rn-extensions-share'; import validator from 'validator'; import { eSendEvent, eSubscribeEvent, eUnSubscribeEvent } from '../src/services/EventManager'; import { getElevation } from '../src/utils'; import { COLOR_SCHEME_DARK, COLOR_SCHEME_LIGHT } from '../src/utils/Colors'; import { db } from '../src/utils/database'; import Storage from '../src/utils/storage'; import { sleep } from '../src/utils/TimeUtils'; const AnimatedKAV = Animated.createAnimatedComponent(KeyboardAvoidingView); const AnimatedSAV = Animated.createAnimatedComponent(SafeAreaView); async function sanitizeHtml(site) { try { let html = await fetch(site); html = await html.text(); let siteHtml = html.replace( /(?:<(script|button|input|textarea|style|link)(?:\s[^>]*)?>)\s*((?:(?!<\1)[\s\S])*)\s*(?:<\/\1>)/g, '' ); return absolutify(siteHtml, site); } catch (e) { return ''; } } function makeHtmlFromUrl(url) { return `${url}`; } function makeHtmlFromPlainText(text) { if (!text) return ''; return `
${text}
`; } function getBaseUrl(site) { var url = site.split('/').slice(0, 3).join('/'); return url; } async function absolutifyImgs(html, site) { let parser = global.HTMLParser; global.HTMLParser.body.innerHTML = html; let images = parser.querySelectorAll('img'); for (var i = 0; i < images.length; i++) { let img = images[i]; let url = getBaseUrl(site); if (!img.src.startsWith('http')) { if (img.src.startsWith('//')) { img.src = img.src.replace('//', 'https://'); } else { img.src = url + img.src; } } } return parser.body.innerHTML; } let defaultNote = { title: null, id: null, content: { type: 'tiny', data: null } }; const modes = { 1: { type: 'text', title: 'Plain text', icon: 'card-text-outline' }, 2: { type: 'clip', title: 'Web clip', icon: 'web' }, 3: { type: 'link', title: 'Link', icon: 'link' } }; const NotesnookShare = () => { const [colors, setColors] = useState( Appearance.getColorScheme() === 'dark' ? COLOR_SCHEME_DARK : COLOR_SCHEME_LIGHT ); const [note, setNote] = useState(defaultNote); const [loadingIntent, setLoadingIntent] = useState(true); const [loading, setLoading] = useState(false); const [floating, setFloating] = useState(false); const [rawData, setRawData] = useState({ type: null, value: null }); const {width, height} = useWindowDimensions(); const webviewRef = useRef(); const opacity = useValue(0); const translate = useValue(1000); const insets = { top: Platform.OS === 'ios' ? 30 : 0 }; const prevAnimation = useRef(null); const [mode, setMode] = useState(1); const animate = (opacityV, translateV) => { prevAnimation.current = translateV; if (Platform.OS === 'ios') return; timing(opacity, { toValue: opacityV, duration: 300, easing: Easing.in(Easing.ease) }).start(); timing(translate, { toValue: translateV, duration: 300, easing: Easing.in(Easing.ease) }).start(); }; const onKeyboardDidShow = event => { let kHeight = event.endCoordinates.height; //translate.setValue(-150); }; const onKeyboardDidHide = () => { translate.setValue(0); }; useEffect(() => { let keyboardWillChangeFrame = Keyboard.addListener( 'keyboardWillChangeFrame', onKeyboardWillChangeFrame ); let keyboardDidShow = Keyboard.addListener( 'keyboardDidShow', onKeyboardDidShow ); let keyboardDidHide = Keyboard.addListener( 'keyboardDidHide', onKeyboardDidHide ); return () => { keyboardWillChangeFrame?.remove(); keyboardDidShow?.remove(); keyboardDidHide?.remove(); }; }, []); const onKeyboardWillChangeFrame = event => { setFloating(event.endCoordinates.width !== width); }; const showLinkPreview = async (note, link) => { let _note = note; _note.content.data = makeHtmlFromUrl(link); try { let preview = await getLinkPreview(link); _note.title = preview.siteName || preview.title; } catch (e) { console.log(e); } return note; }; const loadData = async () => { try { const data = await ShareExtension.data(); if (!data || data.length === 0) { setRawData({ value: '' }); setNote({...defaultNote}); setLoadingIntent(false); return; } let note = defaultNote; for (item of data) { if (item.type === 'text') { setRawData(item); if (validator.isURL(item.value)) { note = await showLinkPreview(note, item.value); } else { note.content.data = makeHtmlFromPlainText(item.value); } } } setNote({...note}); } catch (e) {} setLoadingIntent(false); }; useEffect(() => { loadData(); sleep(50).then(() => { animate(1, 0); }); }, []); const close = async () => { animate(0, 1000); await sleep(300); setNote(defaultNote); setLoadingIntent(true); ShareExtension.close(); }; const onLoad = () => { postMessage(webviewRef, 'htmldiff', note.content?.data); let theme = {...colors}; theme.factor = 1; postMessage(webviewRef, 'theme', JSON.stringify(theme)); }; function postMessage(webview, type, value = null) { let message = { type: type, value }; webview.current?.postMessage(JSON.stringify(message)); } const onPress = async () => { content = await getContent(); if (!content || content === '') { return; } setLoading(true); let add = async () => { let _note = { ...note, content: { data: content, type: 'tiny' } }; await db.notes.add(_note); }; if (db && db.notes) { await add(); } else { await db.init(); await add(); } await Storage.write('notesAddedFromIntent', 'added'); setLoading(false); await sleep(300); close(); }; const sourceUri = 'Plain.bundle/site/plaineditor.html'; const onShouldStartLoadWithRequest = request => { if (request.url.includes('/site/plaineditor.html')) { return true; } else { return false; } }; const getContent = () => { return new Promise(resolve => { let oncontent = value => { eUnSubscribeEvent('share_content_event', oncontent); resolve(value); }; eSubscribeEvent('share_content_event', oncontent); webviewRef.current?.injectJavaScript(`(function() { let html = document.querySelector(".htmldiff_div").innerHTML; if (!html) { html = ''; } reactNativeEventHandler('tiny', html); })();`); }); }; const onMessage = event => { if (!event) return; let data = JSON.parse(event.nativeEvent.data); if (data.type === 'tiny') { eSendEvent('share_content_event', data.value); } }; useEffect(() => { onLoad(); }, [note]); return (