/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see
${text .replace(/[\n]+/g, "\n") .replace(/(?:\r\n|\r|\n)/g, "
")}
`; } let defaultNote = { title: null, id: null, content: { type: "tiptap", 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 ShareView = () => { const { colors } = useThemeColors(); const appendNoteId = useShareStore((state) => state.appendNote); const [note, setNote] = useState({ ...defaultNote }); const noteContent = useRef(""); const noteTitle = useRef(""); const [loading, setLoading] = useState(false); const [loadingExtension, setLoadingExtension] = useState(true); const [rawData, setRawData] = useState({ type: null, value: null }); const inputRef = useRef(null); const [mode, setMode] = useState(1); const keyboardHeight = useRef(0); const { width, height } = useWindowDimensions(); const [loadingPage, setLoadingPage] = useState(false); const editorRef = useRef(); const [searchMode, setSearchMode] = useState(null); const [rawFiles, setRawFiles] = useState([]); const [kh, setKh] = useState(0); const [compress, setCompress] = useState(true); globalThis["IS_SHARE_EXTENSION"] = true; const onKeyboardDidShow = (event) => { let height = Dimensions.get("screen").height - event.endCoordinates.screenY; keyboardHeight.current = height; setKh(height); }; const onKeyboardDidHide = () => { keyboardHeight.current = 0; setKh(0); }; useEffect(() => { let keyboardDidShow = Keyboard.addListener( "keyboardDidShow", onKeyboardDidShow ); let keyboardDidHide = Keyboard.addListener( "keyboardDidHide", onKeyboardDidHide ); return () => { keyboardDidShow?.remove(); keyboardDidHide?.remove(); }; }, []); const showLinkPreview = async (note, link) => { let _note = note; _note.content.data = makeHtmlFromUrl(link); try { let preview = await getLinkPreview(link); _note.title = preview.title; } catch (e) { console.log(e); } return note; }; const loadData = useCallback( async (isEditor) => { try { if (noteContent.current) { onLoad(); return; } defaultNote.content.data = null; setNote({ ...defaultNote }); const data = await ShareExtension.data(); if (!data || data.length === 0) { setRawData({ value: "" }); if (isEditor) { setTimeout(() => { editorRef.current?.focus(); }, 300); } return; } let note = { ...defaultNote }; for (let item of data) { if (item.type === "text") { setRawData(item); if (isURL(item.value)) { note = await showLinkPreview(note, item.value); } else { note.content.data = makeHtmlFromPlainText(item.value); } noteContent.current = note.content.data; } else if (item.type === "extras") { for (const key in item) { if (!key) continue; if (key.includes("TITLE") || key.includes("SUBJECT")) { note.title = item[key]; noteTitle.current = note.title; inputRef.current?.setNativeProps?.({ text: noteTitle.current }); } if (key.includes("TEXT") && !note.content.data) { note.content.data = item[key]; noteContent.current = item[key]; } } } else { const user = await db.user.getUser(); if (user && user.subscription.type !== 0) { if ( (isImage(item.type) && item.size > IMAGE_SIZE_LIMIT) || (!isImage(item.type) && item.size > FILE_SIZE_LIMIT) ) { continue; } setRawFiles((files) => { const index = files.findIndex( (file) => file.name === item.name ); if (index === -1) { files.push(item); return [...files]; } else { return files; } }); } } } onLoad(); setNote({ ...note }); } catch (e) { console.error(e); } }, [onLoad] ); const onLoad = useCallback(() => { console.log(noteContent.current, "current..."); eSendEvent(eOnLoadNote + "shareEditor", { id: null, content: { type: "tiptap", data: noteContent.current }, forced: true }); }, []); useEffect(() => { (async () => { try { await initDatabase(); setLoadingExtension(false); loadData(); useShareStore.getState().restore(); } catch (e) { DatabaseLogger.error(e); } })(); }, [loadData]); const close = async () => { setNote({ ...defaultNote }); setLoadingExtension(true); ShareExtension.close(); }; const onPress = async () => { setLoading(true); if (!noteContent.current && rawFiles.length === 0 && !noteTitle.current) { setLoading(false); return; } let noteData; if (appendNoteId) { if (!(await db.notes.exists(appendNoteId))) { useShareStore.getState().setAppendNote(null); Alert.alert("The note you are trying to append to has been deleted."); setLoading(false); return; } const note = await db.notes.note(appendNoteId); let rawContent = await db.content.get(note.contentId); noteData = { content: { data: (rawContent?.data || "") + noteContent.current, type: "tiptap" }, id: note.id, sessionId: Date.now() }; } else { noteData = { ...note }; noteData.content.data = noteContent.current; noteData.sessionId = Date.now(); noteData.title = noteTitle.current; } try { await NoteBundle.createNotes({ files: rawFiles, note: noteData, notebooks: useShareStore.getState().selectedNotebooks, tags: useShareStore.getState().selectedTags, compress }); if (!globalThis["IS_MAIN_APP_RUNNING"]) { await db.sync({ type: "send", force: false }); } else { console.log("main app running, skipping sync"); } } catch (e) { DatabaseLogger.error(e, "Error adding notes from share extension"); } await Storage.write("notesAddedFromIntent", "added"); close(); setLoading(false); }; const changeMode = async (m) => { setMode(m); setLoading(true); try { if (m === 2) { setLoadingPage(true); setTimeout(async () => { let html = await sanitizeHtml(rawData.value); noteContent.current = html; setLoadingPage(false); onLoad(); setNote((note) => { note.content.data = html; return { ...note }; }); }, 300); } else { setLoadingPage(false); let html = isURL(rawData.value) ? makeHtmlFromUrl(rawData.value) : makeHtmlFromPlainText(rawData.value); setNote((note) => { note.content.data = html; noteContent.current = html; onLoad(); return { ...note }; }); } } catch (e) { console.error(e); } finally { setLoading(false); } }; const onLoadEditor = useCallback(() => { Storage.write("shareExtensionOpened", "opened"); loadData(true); }, [loadData]); const onRemoveFile = (item) => { const index = rawFiles.findIndex((file) => file.name === item.name); if (index > -1) { setRawFiles((state) => { const files = [...state]; files.splice(index); return files; }); RNFetchBlob.fs.unlink(item.value).catch(console.log); } }; const WrapperView = Platform.OS === "android" ? View : ScrollView; return loadingExtension ? null : (