diff --git a/apps/mobile/src/screens/editor/index.js b/apps/mobile/src/screens/editor/index.js index dfa5ecc82..3b185b1c3 100755 --- a/apps/mobile/src/screens/editor/index.js +++ b/apps/mobile/src/screens/editor/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import { Linking, Platform, View } from 'react-native'; +import { Linking, Platform } from 'react-native'; import WebView from 'react-native-webview'; import { notesnook } from '../../../e2e/test.ids'; import { useUserStore } from '../../stores/use-user-store'; @@ -8,7 +8,6 @@ import { useEditorEvents } from './tiptap/use-editor-events'; import { editorController } from './tiptap/utils'; const sourceUri = ''; - const source = { uri: sourceUri + 'index.html' }; const style = { @@ -18,7 +17,11 @@ const style = { alignSelf: 'center', backgroundColor: 'transparent' }; -const off = true; +const onShouldStartLoadWithRequest = request => { + Linking.openURL(request.url); + return false; +}; + const Editor = React.memo( () => { const premiumUser = useUserStore(state => state.premium); @@ -27,62 +30,44 @@ const Editor = React.memo( editorController.current = editor; const onError = () => { - console.log('onError'); editorController.current?.setLoading(true); setTimeout(() => editorController.current?.setLoading(false), 10); }; - const onShouldStartLoadWithRequest = request => { - Linking.openURL(request.url); - return false; - }; - - return editor.loading || off ? null : ( - <> - - } - startInLoadingState - hideKeyboardAccessoryView={true} - allowingReadAccessToURL={Platform.OS === 'android' ? true : null} - allowFileAccessFromFileURLs={true} - allowUniversalAccessFromFileURLs={true} - originWhitelist={['*']} - source={{ - uri: 'http://192.168.10.7:3000' - }} - style={style} - autoManageStatusBarEnabled={false} - onMessage={onMessage} - /> - - + return editor.loading ? null : ( + ); }, () => true diff --git a/apps/mobile/src/screens/editor/tiptap/commands.ts b/apps/mobile/src/screens/editor/tiptap/commands.ts index 5c8574aa5..b1906bede 100644 --- a/apps/mobile/src/screens/editor/tiptap/commands.ts +++ b/apps/mobile/src/screens/editor/tiptap/commands.ts @@ -6,6 +6,8 @@ import { db } from '../../../utils/database'; import { sleep } from '../../../utils/time'; import { Note } from './types'; import { getResponse, randId, textInput } from './utils'; +import { Attachment, AttachmentProgress } from 'notesnook-editor/dist/extensions/attachment/index'; +import { ImageAttributes } from 'notesnook-editor/dist/extensions/image/index'; type Action = { job: string; id: string }; @@ -49,6 +51,11 @@ class Commands { this.ref = ref; } + async doAsync(job: string) { + if (!this.ref) return false; + return await call(this.ref, fn(job)); + } + focus = async () => { if (!this.ref) return; if (Platform.OS === 'android') { @@ -57,70 +64,57 @@ class Commands { if (!this.ref) return; textInput.current?.focus(); this.ref?.current?.requestFocus(); - await call(this.ref, fn(`editor.commands.focus()`)); + await this.doAsync(`editor.commands.focus()`); }, 1); } else { await sleep(200); - await call(this.ref, fn(`editor.commands.focus()`)); + await this.doAsync(`editor.commands.focus()`); } }; - blur = async () => await call(this.ref, fn(`editor.commands.blur();editorTitle.current?.blur()`)); + blur = async () => await this.doAsync(`editor.commands.blur();editorTitle.current?.blur()`); clearContent = async () => { - await call( - this.ref, - fn( - ` + await this.doAsync( + ` editor.commands.blur(); editorTitle.current?.blur(); editor?.commands.clearContent(false); editorController.setTitle(null); statusBar.current.set({date:"",saved:""}); ` - ) ); }; - setSessionId = async (id: string | null) => - await call(this.ref, fn(`globalThis.sessionId = "${id}"`)); + setSessionId = async (id: string | null) => await this.doAsync(`globalThis.sessionId = "${id}"`); setStatus = async (date: string | undefined, saved: string) => - await call(this.ref, fn(`statusBar.current.set({date:"${date}",saved:"${saved}"})`)); + await this.doAsync(`statusBar.current.set({date:"${date}",saved:"${saved}"})`); setPlaceholder = async (placeholder: string) => { - await call( - this.ref, - fn(` + await this.doAsync(` const element = document.querySelector(".is-editor-empty"); if (element) { element.setAttribute("data-placeholder","${placeholder}"); } - `) - ); + `); }; setInsets = async (insets: EdgeInsets) => { logger.info('setInsets', insets); - await call( - this.ref, - fn(` + await this.doAsync(` if (typeof safeAreaController !== "undefined") { safeAreaController.update(${JSON.stringify(insets)}) } - `) - ); + `); }; setSettings = async (settings: Partial) => { - await call( - this.ref, - fn(` + await this.doAsync(` if (typeof globalThis.settingsController !== "undefined") { globalThis.settingsController.update(${JSON.stringify(settings)}) } - `) - ); + `); }; setTags = async (note: Note | null | undefined) => { @@ -130,15 +124,32 @@ statusBar.current.set({date:"",saved:""}); db.tags?.tag(t) ? { title: db.tags.tag(t).title, alias: db.tags.tag(t).alias } : null ) .filter((t: any) => t !== null); - await call( - this.ref, - fn(` - if (typeof editorTags !== "undefined" && editorTags.current) { - editorTags.current.setTags(${JSON.stringify(tags)}); - } - `) + await this.doAsync(` + if (typeof editorTags !== "undefined" && editorTags.current) { + editorTags.current.setTags(${JSON.stringify(tags)}); + } + `); + }; + + insertAttachment = async (attachment: Attachment) => { + this.doAsync(`editor && editor.commands.insertAttachment(${JSON.stringify(attachment)})`); + }; + + setAttachmentProgress = async (attachmentProgress: AttachmentProgress) => { + this.doAsync( + `editor && editor.commands.setAttachmentProgress(${JSON.stringify(attachmentProgress)})` ); }; + + insertImage = async (image: ImageAttributes) => { + console.log('image data', image); + this.doAsync(`editor && editor.commands.insertImage(${JSON.stringify(image)})`); + }; + + updateImage = async (image: ImageAttributes) => { + this.doAsync(`editor && editor.commands.updateImage(${JSON.stringify(image)})`); + }; + //todo add replace image function } export default Commands; diff --git a/apps/mobile/src/screens/editor/tiptap/picker.js b/apps/mobile/src/screens/editor/tiptap/picker.js index dd48e29f5..ed2455018 100644 --- a/apps/mobile/src/screens/editor/tiptap/picker.js +++ b/apps/mobile/src/screens/editor/tiptap/picker.js @@ -48,7 +48,7 @@ const santizeUri = uri => { const file = async fileOptions => { try { - let options = { + const options = { mode: 'import', allowMultiSelection: false }; @@ -56,7 +56,7 @@ const file = async fileOptions => { options.copyTo = 'cachesDirectory'; } console.log('generate key for attachment'); - let key = await db.attachments.generateKey(); + const key = await db.attachments.generateKey(); console.log('generated key for attachments: ', key); let file; try { @@ -97,38 +97,20 @@ const file = async fileOptions => { console.log('file uri: ', uri); uri = Platform.OS === 'ios' ? santizeUri(uri) : uri; showEncryptionSheet(file); - let hash = await Sodium.hashFile({ + const hash = await Sodium.hashFile({ uri: uri, type: 'url' }); - console.log('decoded uri: ', uri); - let result = await attachFile(uri, hash, file.type, file.name, fileOptions); - console.log('attach file: ', result); - + if (!(await attachFile(uri, hash, file.type, file.name, fileOptions))) return; + editorController.current?.commands.insertAttachment({ + hash: hash, + filename: file.name, + type: file.type, + size: file.size + }); setTimeout(() => { eSendEvent(eCloseProgressDialog); }, 1000); - if (!result) return; - // tiny.call( - // EditorWebView, - // ` - // (function() { - // let file = ${JSON.stringify({ - // hash: hash, - // filename: file.name, - // type: file.type, - // size: file.size - // })} - // editor.undoManager.transact(function() { - // tinymce.activeEditor.execCommand('mceAttachFile',file); - // setTimeout(function() { - // tinymce.activeEditor.fire("input",{data:""}) - // },100) - // }); - - // })(); - // ` - // ); } catch (e) { ToastEvent.show({ heading: e.message, @@ -207,45 +189,29 @@ const pick = async options => { return; } - if (options?.reupload) { - if (options?.type.startsWith('image')) { + if (options?.type.startsWith('image')) { + if (options?.reupload) { gallery(options); } else { - file(options); + presentSheet({ + context: options?.context, + actionsArray: [ + { + action: () => camera(options), + actionText: 'Open camera', + icon: 'camera' + }, + { + action: () => gallery(options), + actionText: 'Select image from gallery', + icon: 'image-multiple' + } + ] + }); } - return; + } else { + file(options); } - - if (editorState().isFocused) { - editorState().isFocused = true; - } - - presentSheet({ - context: options?.context, - actionsArray: [ - { - action: async () => { - eSendEvent(eCloseProgressDialog); - await sleep(400); - await file(options); - }, - actionText: 'Attach a file', - icon: 'file' - }, - { - action: () => camera(options), - actionText: 'Open camera', - icon: 'camera' - }, - { - action: () => gallery(options), - actionText: 'Select image from gallery', - icon: 'image-multiple' - } - ] - }); - - return; }; const handleImageResponse = async (response, options) => { @@ -267,38 +233,24 @@ const handleImageResponse = async (response, options) => { }); return; } - let b64 = `data:${image.type};base64, ` + image.base64; - let uri = image.uri; - uri = decodeURI(uri); - let hash = await Sodium.hashFile({ + const b64 = `data:${image.type};base64, ` + image.base64; + const uri = decodeURI(image.uri); + const hash = await Sodium.hashFile({ uri: uri, type: 'url' }); let fileName = image.originalFileName || image.fileName; - let result = await attachFile(uri, hash, image.type, fileName, options); - if (!result) return; - // tiny.call( - // EditorWebView, - // ` - // (function(){ - // let image = ${JSON.stringify({ - // hash: hash, - // type: image.type, - // filename: fileName, - // dataurl: b64, - // size: image.fileSize - // })} + if (!(await attachFile(uri, hash, image.type, fileName, options))) return; - // editor.undoManager.transact(function() { - // tinymce.activeEditor.execCommand('mceAttachImage',image); - // setTimeout(function() { - // tinymce.activeEditor.fire("input",{data:""}) - // },100) - // }); - // })(); - // ` - // ); + editorController.current?.commands.insertImage({ + hash: hash, + type: image.type, + title: fileName, + src: b64, + size: image.fileSize, + filename: fileName + }); }; async function attachFile(uri, hash, type, filename, options) { diff --git a/apps/mobile/src/screens/editor/tiptap/useEditor.ts b/apps/mobile/src/screens/editor/tiptap/useEditor.ts index 56d25d8f5..aa19486de 100644 --- a/apps/mobile/src/screens/editor/tiptap/useEditor.ts +++ b/apps/mobile/src/screens/editor/tiptap/useEditor.ts @@ -92,6 +92,7 @@ export const useEditor = () => { }, []); const reset = useCallback(async (resetState = true) => { + currentNote.current?.id && db.fs.cancel(currentNote.current.id); currentNote.current = null; currentContent.current = null; sessionHistoryId.current = undefined; @@ -224,6 +225,7 @@ export const useEditor = () => { await commands.setStatus(timeConverter(item.dateEdited), 'Saved'); await postMessage(EditorEvents.title, item.title); await postMessage(EditorEvents.html, currentContent.current?.data); + loadImages(); await commands.setTags(currentNote.current); overlay(false); } @@ -231,6 +233,14 @@ export const useEditor = () => { [setSessionId] ); + const loadImages = () => { + if (!currentNote.current?.id) return; + const images = db.attachments?.ofNote(currentNote.current?.id, 'images'); + if (images && images.length > 0) { + db.attachments?.downloadImages(currentNote.current.id); + } + }; + useEffect(() => { eSubscribeEvent(eOnLoadNote, loadNote); return () => { diff --git a/apps/mobile/src/screens/editor/tiptap/useEditorEvents.ts b/apps/mobile/src/screens/editor/tiptap/useEditorEvents.ts index 14f34bd50..a61dc3a75 100644 --- a/apps/mobile/src/screens/editor/tiptap/useEditorEvents.ts +++ b/apps/mobile/src/screens/editor/tiptap/useEditorEvents.ts @@ -127,7 +127,7 @@ export const useEditorEvents = (editor: useEditorType) => { premium: isPremium, readonly: readonly }); - }, [currentEditingNote, fullscreen, isPremium, readonly]); + }, [currentEditingNote, fullscreen, isPremium, readonly, editor.sessionId, editor.loading]); const onBackPress = useCallback(async () => { setTimeout(async () => { @@ -272,9 +272,10 @@ export const useEditorEvents = (editor: useEditorType) => { } break; case EventTypes.filepicker: - picker.pick(); + picker.pick({ type: editorMessage.value }); break; case EventTypes.download: + console.log('download attachment request', editorMessage.value); filesystem.downloadAttachment(editorMessage.value?.hash, true); break; case EventTypes.pro: diff --git a/apps/mobile/src/stores/useAttachmentStore.ts b/apps/mobile/src/stores/useAttachmentStore.ts index 7d2dcae99..c7dce52f5 100644 --- a/apps/mobile/src/stores/useAttachmentStore.ts +++ b/apps/mobile/src/stores/useAttachmentStore.ts @@ -1,5 +1,6 @@ //@ts-ignore import create from 'zustand'; +import { editorController } from '../screens/editor/tiptap/utils'; interface AttachmentStore { progress?: { @@ -28,37 +29,27 @@ interface AttachmentStore { export const useAttachmentStore = create((set, get) => ({ progress: {}, remove: hash => { - let _p = get().progress; - if (!_p) return; - _p[hash] = null; - // tiny.call( - // EditorWebView, - // ` - // (function() { - // let progress = ${JSON.stringify({ - // loaded: 1, - // total: 1, - // hash - // })} - // tinymce.activeEditor._updateAttachmentProgress(progress); - // })()` - // ); - set({ progress: { ..._p } }); + let progress = get().progress; + if (!progress) return; + editorController.current?.commands.setAttachmentProgress({ + hash: hash, + progress: 100, + type: progress[hash]?.type || 'download' + }); + progress[hash] = null; + set({ progress: { ...progress } }); }, setProgress: (sent, total, hash, recieved, type) => { - let _p = get().progress; - if (!_p) return; - _p[hash] = { sent, total, hash, recieved, type }; - let progress = { total, hash, loaded: type === 'download' ? recieved : sent }; - // tiny.call( - // EditorWebView, - // ` - // (function() { - // let progress = ${JSON.stringify(progress)} - // tinymce.activeEditor._updateAttachmentProgress(progress); - // })()` - // ); - set({ progress: { ..._p } }); + let progress = get().progress; + if (!progress) return; + progress[hash] = { sent, total, hash, recieved, type }; + const progressPercentage = type === 'upload' ? sent / total : recieved / total; + editorController.current?.commands.setAttachmentProgress({ + hash: hash, + progress: Math.round(Math.max(progressPercentage * 100, 0)), + type: type + }); + set({ progress: { ...progress } }); }, encryptionProgress: 0, setEncryptionProgress: encryptionProgress => set({ encryptionProgress: encryptionProgress }),