diff --git a/apps/mobile/src/views/Editor/tiny/toolbar/commands.js b/apps/mobile/src/views/Editor/tiny/toolbar/commands.js
index fecd572c6..e3f7fb80c 100644
--- a/apps/mobile/src/views/Editor/tiny/toolbar/commands.js
+++ b/apps/mobile/src/views/Editor/tiny/toolbar/commands.js
@@ -1,23 +1,4 @@
-import React from 'react';
-import {Platform, View} from 'react-native';
-import DocumentPicker from 'react-native-document-picker';
-import Sodium from 'react-native-sodium';
-import {launchCamera, launchImageLibrary} from 'react-native-image-picker';
-import RNFetchBlob from 'rn-fetch-blob';
-import {Attachment} from '../../../../components/AttachmentDialog';
-import {eSendEvent, ToastEvent} from '../../../../services/EventManager';
-import {editing} from '../../../../utils';
-import {db} from '../../../../utils/database';
-import {
- eCloseProgressDialog,
- eOpenProgressDialog
-} from '../../../../utils/Events';
-import {sleep} from '../../../../utils/TimeUtils';
-import {EditorWebView, getNote} from '../../Functions';
-import tiny, {safeKeyboardDismiss} from '../tiny';
-
-const FILE_SIZE_LIMIT = 500 * 1024 * 1024;
-const IMAGE_SIZE_LIMIT = 50 * 1024 * 1024;
+import picker from './picker';
export const execCommands = {
bold: `tinymce.activeEditor.execCommand('Bold');`,
@@ -89,185 +70,8 @@ export const execCommands = {
})();`,
cl: `tinymce.activeEditor.execCommand('insertCheckList')`,
- filepicker: async () => {
- try {
- let options = {
- mode: 'import',
- allowMultiSelection: false
- };
- if (Platform.OS == 'ios') {
- options.copyTo = 'cachesDirectory';
- }
-
- let key = await db.attachments.generateKey();
-
- console.log('generated key for attachments: ', key);
-
- let file = await DocumentPicker.pick(options);
- file = file[0];
- if (file.size > FILE_SIZE_LIMIT) {
- ToastEvent.show({
- title: 'File too large',
- message: 'The maximum allowed size per file is 512 MB',
- type:'error'
- });
- return;
- }
-
- if (file.copyError) {
- ToastEvent.show({
- heading: 'Failed to open file',
- message: file.copyError,
- type: 'error',
- context: 'global'
- });
- return;
- }
-
- let uri =
- Platform.OS === 'ios'
- ? file.fileCopyUri.replace('file:///', '/')
- : file.uri;
-
- eSendEvent(eOpenProgressDialog, {
- title: 'Encrypting attachment',
- paragraph: 'Please wait while we encrypt file for upload',
- nowarn: true,
- icon: 'attachment',
- component: (
-
-
-
- )
- });
-
- let hash = await Sodium.hashFile({
- uri: uri,
- type: 'url'
- });
- console.log(hash);
- let result = await attachFile(uri, hash, file.type, file.name);
- console.log('attach file: ', result);
- if (Platform.OS === 'ios') {
- await RNFetchBlob.fs.unlink(uri);
- }
- 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
- })}
- tinymce.activeEditor.execCommand('mceAttachFile',file);
- setTimeout(function() {
- tinymce.activeEditor.nodeChanged({selectionChange:true})
- },100)
- })();
- `
- );
- } catch (e) {
- ToastEvent.show({
- heading: e.message,
- message: 'You need internet access to attach a file',
- type: 'error',
- context: 'global'
- });
- console.log('attachment error: ', e);
- }
- },
- image: async () => {
- if (editing.isFocused) {
- safeKeyboardDismiss();
- await sleep(500);
- editing.isFocused = true;
- }
- eSendEvent(eOpenProgressDialog, {
- noProgress: true,
- noIcon: true,
- actionsArray: [
- {
- action: async () => {
- try {
- let key = await db.attachments.generateKey();
- eSendEvent(eCloseProgressDialog);
- await sleep(400);
- launchCamera(
- {
- includeBase64: true,
- maxWidth: 4000,
- maxHeight: 4000,
- quality: 0.8,
- mediaType: 'photo'
- },
- handleImageResponse
- );
- } catch (e) {
- ToastEvent.show({
- heading: e.message,
- message: 'You need internet access to attach a file',
- type: 'error',
- context: 'global'
- });
- console.log('attachment error:', e);
- }
- },
- actionText: 'Take photo',
- icon: 'camera'
- },
- {
- action: async () => {
- try {
- let key = await db.attachments.generateKey();
-
- eSendEvent(eCloseProgressDialog);
- await sleep(400);
- launchImageLibrary(
- {
- includeBase64: true,
- maxWidth: 4000,
- maxHeight: 4000,
- quality: 0.8,
- mediaType: 'photo',
- selectionLimit: 1
- },
- handleImageResponse
- );
- } catch (e) {
- ToastEvent.show({
- heading: e.message,
- message: 'You need internet access to attach a file',
- type: 'error',
- context: 'global'
- });
- console.log('attachment error:', e);
- }
- },
- actionText: 'Select from gallery',
- icon: 'image-multiple'
- }
- ]
- });
-
- return;
- },
+ filepicker: picker.file,
+ image: picker.image,
video: `tinymce.activeEditor.execCommand('mceMedia')`,
pre: `
tinymce.activeEditor.execCommand('mceInsertCodeBlock')
@@ -331,103 +135,6 @@ export const execCommands = {
});`
};
-const handleImageResponse = async response => {
- if (
- response.didCancel ||
- response.errorMessage ||
- !response.assets ||
- response.assets?.length === 0
- ) {
- return;
- }
-
- let image = response.assets[0];
- if (image.fileSize > IMAGE_SIZE_LIMIT) {
- ToastEvent.show({
- title: 'File too large',
- message: 'The maximum allowed size per image is 50 MB',
- type:'error'
- });
- return;
- }
- let b64 = `data:${image.type};base64, ` + image.base64;
- let options;
- if (Platform.OS === 'android') {
- options = {
- uri: image.uri,
- type: 'url'
- };
- } else {
- options = {
- data: image.base64,
- type: 'base64'
- };
- }
-
- let hash = await Sodium.hashFile(options);
- console.log(hash);
- tiny.call(
- EditorWebView,
- `
- (function(){
- let image = ${JSON.stringify({
- hash: hash,
- type: image.type,
- filename: image.fileName,
- dataurl: b64,
- size: image.fileSize
- })}
- tinymce.activeEditor.execCommand('mceAttachImage',image);
- setTimeout(function() {
- tinymce.activeEditor.nodeChanged({selectionChange:true})
- },100)
- })();
- `
- );
- attachFile(
- image.uri,
- hash,
- image.type,
- image.fileName,
- Platform.OS === 'ios' ? image.base64 : null
- );
-};
-
-async function attachFile(uri, hash, type, filename, b64) {
- try {
- let exists = db.attachments.exists(hash);
- let encryptionInfo;
- if (!exists) {
- let key = await db.attachments.generateKey();
- let options = {
- hash: hash
- };
- if (Platform.OS === 'ios' && b64) {
- options.data = b64;
- options.type = 'base64';
- } else {
- options.uri = uri;
- options.type = 'url';
- }
-
- encryptionInfo = await Sodium.encryptFile(key, options);
- encryptionInfo.type = type;
- encryptionInfo.filename = filename;
- encryptionInfo.alg = `xcha-argon2i13-s`;
- encryptionInfo.size = encryptionInfo.length;
- encryptionInfo.key = key;
- } else {
- encryptionInfo = {hash: hash};
- }
- console.log(encryptionInfo);
- await db.attachments.add(encryptionInfo, getNote()?.id);
- return true;
- } catch (e) {
- console.log('attach file error: ', e);
- return false;
- }
-}
-
const setFloat = float => `(function () {
let node = tinymce.activeEditor.selection.getNode();
if (node.tagName === 'IMG') {
diff --git a/apps/mobile/src/views/Editor/tiny/toolbar/picker.js b/apps/mobile/src/views/Editor/tiny/toolbar/picker.js
new file mode 100644
index 000000000..91d3d63ab
--- /dev/null
+++ b/apps/mobile/src/views/Editor/tiny/toolbar/picker.js
@@ -0,0 +1,304 @@
+import React from 'react';
+import {Platform, View} from 'react-native';
+import DocumentPicker from 'react-native-document-picker';
+import Sodium from 'react-native-sodium';
+import {launchCamera, launchImageLibrary} from 'react-native-image-picker';
+import RNFetchBlob from 'rn-fetch-blob';
+import {Attachment} from '../../../../components/AttachmentDialog';
+import {eSendEvent, ToastEvent} from '../../../../services/EventManager';
+import {editing} from '../../../../utils';
+import {db} from '../../../../utils/database';
+import {
+ eCloseProgressDialog,
+ eOpenProgressDialog
+} from '../../../../utils/Events';
+import {sleep} from '../../../../utils/TimeUtils';
+import {EditorWebView, getNote} from '../../Functions';
+import tiny, {safeKeyboardDismiss} from '../tiny';
+
+const FILE_SIZE_LIMIT = 500 * 1024 * 1024;
+const IMAGE_SIZE_LIMIT = 50 * 1024 * 1024;
+
+const showEncryptionSheet = () => {
+ eSendEvent(eOpenProgressDialog, {
+ title: 'Encrypting attachment',
+ paragraph: 'Please wait while we encrypt file for upload',
+ nowarn: true,
+ icon: 'attachment',
+ component: (
+
+
+
+ )
+ });
+};
+
+const santizeUri = uri => {
+ uri = decodeURI(uri);
+ uri = Platform.OS === 'ios' ? uri.replace('file:///', '/') : uri;
+ return uri;
+};
+
+const file = async () => {
+ try {
+ let options = {
+ mode: 'import',
+ allowMultiSelection: false
+ };
+ if (Platform.OS == 'ios') {
+ options.copyTo = 'cachesDirectory';
+ }
+
+ let key = await db.attachments.generateKey();
+
+ console.log('generated key for attachments: ', key);
+ let file;
+ try {
+ file = await DocumentPicker.pick(options);
+ } catch (e) {
+ return;
+ }
+
+ file = file[0];
+ if (file.type === 'image/') {
+ ToastEvent.show({
+ title: 'Type not supported',
+ message: 'Please add images from gallery or camera picker.',
+ type: 'error',
+ });
+ return;
+ }
+ if (file.size > FILE_SIZE_LIMIT) {
+ ToastEvent.show({
+ title: 'File too large',
+ message: 'The maximum allowed size per file is 500 MB',
+ type: 'error'
+ });
+ return;
+ }
+
+ if (file.copyError) {
+ ToastEvent.show({
+ heading: 'Failed to open file',
+ message: file.copyError,
+ type: 'error',
+ context: 'global'
+ });
+ return;
+ }
+
+ let uri = Platform.OS === 'ios' ? file.fileCopyUri : file.uri;
+ uri = santizeUri(uri);
+ showEncryptionSheet();
+ let hash = await Sodium.hashFile({
+ uri: uri,
+ type: 'url'
+ });
+ console.log(uri);
+ let result = await attachFile(uri, hash, file.type, file.name, 'file');
+ console.log('attach file: ', result);
+
+ 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
+ })}
+ tinymce.activeEditor.execCommand('mceAttachFile',file);
+ setTimeout(function() {
+ tinymce.activeEditor.nodeChanged({selectionChange:true})
+ },100)
+ })();
+ `
+ );
+ } catch (e) {
+ ToastEvent.show({
+ heading: e.message,
+ message: 'You need internet access to attach a file',
+ type: 'error',
+ context: 'global'
+ });
+ console.log('attachment error: ', e);
+ }
+};
+
+const image = async () => {
+ if (editing.isFocused) {
+ safeKeyboardDismiss();
+ await sleep(500);
+ editing.isFocused = true;
+ }
+ eSendEvent(eOpenProgressDialog, {
+ noProgress: true,
+ noIcon: true,
+ actionsArray: [
+ {
+ action: async () => {
+ try {
+ await db.attachments.generateKey();
+ eSendEvent(eCloseProgressDialog);
+ await sleep(400);
+ launchCamera(
+ {
+ includeBase64: true,
+ maxWidth: 4000,
+ maxHeight: 4000,
+ quality: 0.8,
+ mediaType: 'photo'
+ },
+ handleImageResponse
+ );
+ } catch (e) {
+ ToastEvent.show({
+ heading: e.message,
+ message: 'You need internet access to attach a file',
+ type: 'error',
+ context: 'global'
+ });
+ console.log('attachment error:', e);
+ }
+ },
+ actionText: 'Take photo',
+ icon: 'camera'
+ },
+ {
+ action: async () => {
+ try {
+ await db.attachments.generateKey();
+ eSendEvent(eCloseProgressDialog);
+ await sleep(400);
+ launchImageLibrary(
+ {
+ includeBase64: true,
+ maxWidth: 4000,
+ maxHeight: 4000,
+ quality: 0.8,
+ mediaType: 'photo',
+ selectionLimit: 1
+ },
+ handleImageResponse
+ );
+ } catch (e) {
+ ToastEvent.show({
+ heading: e.message,
+ message: 'You need internet access to attach a file',
+ type: 'error',
+ context: 'global'
+ });
+ console.log('attachment error:', e);
+ }
+ },
+ actionText: 'Select from gallery',
+ icon: 'image-multiple'
+ }
+ ]
+ });
+
+ return;
+};
+
+const handleImageResponse = async response => {
+ if (
+ response.didCancel ||
+ response.errorMessage ||
+ !response.assets ||
+ response.assets?.length === 0
+ ) {
+ return;
+ }
+
+ let image = response.assets[0];
+ if (image.fileSize > IMAGE_SIZE_LIMIT) {
+ ToastEvent.show({
+ title: 'File too large',
+ message: 'The maximum allowed size per image is 50 MB',
+ type: 'error'
+ });
+ return;
+ }
+ let b64 = `data:${image.type};base64, ` + image.base64;
+ let uri = image.uri;
+ uri = decodeURI(uri);
+ console.log(uri);
+ let hash = await Sodium.hashFile({
+ uri: uri,
+ type: 'url'
+ });
+ tiny.call(
+ EditorWebView,
+ `
+ (function(){
+ let image = ${JSON.stringify({
+ hash: hash,
+ type: image.type,
+ filename: image.fileName,
+ dataurl: b64,
+ size: image.fileSize
+ })}
+ tinymce.activeEditor.execCommand('mceAttachImage',image);
+ setTimeout(function() {
+ tinymce.activeEditor.nodeChanged({selectionChange:true})
+ },100)
+ })();
+ `
+ );
+ attachFile(uri, hash, image.type, image.fileName, 'image');
+};
+
+async function attachFile(uri, hash, type, filename) {
+ try {
+ let exists = db.attachments.exists(hash);
+ let encryptionInfo;
+ if (!exists) {
+ let key = await db.attachments.generateKey();
+ encryptionInfo = await Sodium.encryptFile(key, {
+ uri: uri,
+ type: 'url',
+ hash: hash
+ });
+ encryptionInfo.type = type;
+ encryptionInfo.filename = filename;
+ encryptionInfo.alg = `xcha-stream`;
+ encryptionInfo.size = encryptionInfo.length;
+ encryptionInfo.key = key;
+ } else {
+ encryptionInfo = {hash: hash};
+ }
+
+ console.log(encryptionInfo);
+ await db.attachments.add(encryptionInfo, getNote()?.id);
+ if (Platform.OS === 'ios') await RNFetchBlob.fs.unlink(uri);
+
+ return true;
+ } catch (e) {
+ console.log('attach file error: ', e);
+ if (Platform.OS === 'ios') {
+ await RNFetchBlob.fs.unlink(uri);
+ }
+ return false;
+ }
+}
+
+export default {
+ file,
+ image
+};