mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-29 00:20:04 +01:00
feat: add file & image upload
This commit is contained in:
@@ -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 : (
|
||||
<>
|
||||
<View
|
||||
style={{
|
||||
flexGrow: 1,
|
||||
backgroundColor: 'transparent',
|
||||
flex: 1
|
||||
}}
|
||||
>
|
||||
<WebView
|
||||
testID={notesnook.editor.id}
|
||||
ref={editor.ref}
|
||||
onLoad={editor.onLoad}
|
||||
onRenderProcessGone={onError}
|
||||
onError={onError}
|
||||
injectedJavaScript={`globalThis.sessionId="${editor.sessionId}";`}
|
||||
javaScriptEnabled={true}
|
||||
focusable={true}
|
||||
setSupportMultipleWindows={false}
|
||||
overScrollMode="never"
|
||||
keyboardDisplayRequiresUserAction={false}
|
||||
onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
|
||||
cacheMode="LOAD_DEFAULT"
|
||||
cacheEnabled={true}
|
||||
domStorageEnabled={true}
|
||||
bounces={false}
|
||||
setBuiltInZoomControls={false}
|
||||
setDisplayZoomControls={false}
|
||||
allowFileAccess={true}
|
||||
scalesPageToFit={true}
|
||||
renderLoading={() => <View />}
|
||||
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}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
return editor.loading ? null : (
|
||||
<WebView
|
||||
testID={notesnook.editor.id}
|
||||
ref={editor.ref}
|
||||
onLoad={editor.onLoad}
|
||||
onRenderProcessGone={onError}
|
||||
onError={onError}
|
||||
injectedJavaScript={`globalThis.sessionId="${editor.sessionId}";`}
|
||||
javaScriptEnabled={true}
|
||||
focusable={true}
|
||||
setSupportMultipleWindows={false}
|
||||
overScrollMode="never"
|
||||
keyboardDisplayRequiresUserAction={false}
|
||||
onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
|
||||
cacheMode="LOAD_DEFAULT"
|
||||
cacheEnabled={true}
|
||||
domStorageEnabled={true}
|
||||
bounces={false}
|
||||
setBuiltInZoomControls={false}
|
||||
setDisplayZoomControls={false}
|
||||
allowFileAccess={true}
|
||||
scalesPageToFit={true}
|
||||
hideKeyboardAccessoryView={true}
|
||||
allowingReadAccessToURL={Platform.OS === 'android' ? true : null}
|
||||
allowFileAccessFromFileURLs={true}
|
||||
allowUniversalAccessFromFileURLs={true}
|
||||
originWhitelist={['*']}
|
||||
source={{
|
||||
uri: 'http://192.168.10.5:3000'
|
||||
}}
|
||||
style={style}
|
||||
autoManageStatusBarEnabled={false}
|
||||
onMessage={onMessage}
|
||||
/>
|
||||
);
|
||||
},
|
||||
() => true
|
||||
|
||||
@@ -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<Settings>) => {
|
||||
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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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<AttachmentStore>((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 }),
|
||||
|
||||
Reference in New Issue
Block a user