mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-29 10:09:26 +02:00
Compare commits
3 Commits
editor/com
...
fix-image-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b07a2f048d | ||
|
|
e961b19e0f | ||
|
|
a71ba86d85 |
@@ -22,7 +22,7 @@ import NetInfo from "@react-native-community/netinfo";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import { ToastManager } from "../../services/event-manager";
|
||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { db } from "../database";
|
||||
import { DatabaseLogger, db } from "../database";
|
||||
import { cacheDir, fileCheck } from "./utils";
|
||||
import { createCacheDir, exists } from "./io";
|
||||
|
||||
@@ -100,18 +100,17 @@ export async function getUploadedFileSize(hash) {
|
||||
try {
|
||||
const url = `${hosts.API_HOST}/s3?name=${hash}`;
|
||||
const token = await db.tokenManager.getAccessToken();
|
||||
|
||||
const attachmentInfo = await fetch(url, {
|
||||
method: "HEAD",
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
});
|
||||
|
||||
const contentLength = parseInt(
|
||||
attachmentInfo.headers?.get("content-length")
|
||||
);
|
||||
return isNaN(contentLength) ? 0 : contentLength;
|
||||
} catch (e) {
|
||||
return 0;
|
||||
DatabaseLogger.error(e);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +124,9 @@ export async function checkAttachment(hash) {
|
||||
|
||||
try {
|
||||
const size = await getUploadedFileSize(hash);
|
||||
if (size <= 0) return { failed: "File length is 0." };
|
||||
if (size === -1) return { success: true };
|
||||
|
||||
if (size === 0) return { failed: "File length is 0." };
|
||||
} catch (e) {
|
||||
return { failed: e?.message };
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import { isImage, isDocument } from "@notesnook/core/dist/utils/filename";
|
||||
import { Platform } from "react-native";
|
||||
import { IOS_APPGROUPID } from "../../utils/constants";
|
||||
import { createCacheDir } from "./io";
|
||||
import { getUploadedFileSize } from "./download";
|
||||
|
||||
export async function uploadFile(filename, data, cancelToken) {
|
||||
if (!data) return false;
|
||||
@@ -33,6 +34,18 @@ export async function uploadFile(filename, data, cancelToken) {
|
||||
DatabaseLogger.info(`Preparing to upload file: ${filename}`);
|
||||
|
||||
try {
|
||||
const uploadedFileSize = await getUploadedFileSize(filename);
|
||||
if (uploadedFileSize === -1) {
|
||||
DatabaseLogger.log("Upload verification failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const isUploaded = uploadedFileSize !== 0;
|
||||
if (isUploaded) {
|
||||
DatabaseLogger.log(`File ${filename} is already uploaded.`);
|
||||
return true;
|
||||
}
|
||||
|
||||
let res = await fetch(url, {
|
||||
method: "PUT",
|
||||
headers
|
||||
@@ -50,7 +63,15 @@ export async function uploadFile(filename, data, cancelToken) {
|
||||
let exists = await RNFetchBlob.fs.exists(uploadFilePath);
|
||||
if (!exists && Platform.OS === "ios") {
|
||||
uploadFilePath = appGroupPath;
|
||||
exists = await RNFetchBlob.fs.exists(uploadFilePath);
|
||||
}
|
||||
|
||||
if (!exists) {
|
||||
throw new Error(
|
||||
`Trying to upload file at path ${uploadFilePath} that doest not exist.`
|
||||
);
|
||||
}
|
||||
|
||||
DatabaseLogger.info(`Starting upload: ${filename}`);
|
||||
|
||||
let request = RNFetchBlob.config({
|
||||
|
||||
@@ -56,6 +56,7 @@ import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { useTabStore } from "../../screens/editor/tiptap/use-tab-store";
|
||||
import { editorController } from "../../screens/editor/tiptap/utils";
|
||||
import SheetProvider from "../sheet-provider";
|
||||
|
||||
const Actions = ({
|
||||
attachment,
|
||||
@@ -218,6 +219,7 @@ const Actions = ({
|
||||
}}
|
||||
>
|
||||
<Dialog context={contextId} />
|
||||
<SheetProvider context={contextId} />
|
||||
<View
|
||||
style={{
|
||||
borderBottomWidth: 1,
|
||||
|
||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import React, { useState } from "react";
|
||||
import { Image, ScrollView, TouchableOpacity, View } from "react-native";
|
||||
import { ImagePickerResponse } from "react-native-image-picker";
|
||||
import { Image as ImageType } from "react-native-image-crop-picker";
|
||||
import { useThemeColors } from "../../../../../../packages/theme/dist";
|
||||
import { presentSheet } from "../../../services/event-manager";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
@@ -32,7 +32,7 @@ export default function AttachImage({
|
||||
onAttach,
|
||||
close
|
||||
}: {
|
||||
response: ImagePickerResponse;
|
||||
response: ImageType[];
|
||||
onAttach: ({ compress }: { compress: boolean }) => void;
|
||||
close: ((ctx?: string | undefined) => void) | undefined;
|
||||
}) {
|
||||
@@ -58,14 +58,14 @@ export default function AttachImage({
|
||||
}}
|
||||
>
|
||||
<Paragraph style={{ color: colors.primary.paragraph, marginBottom: 6 }}>
|
||||
Attaching {response.assets?.length} image(s):
|
||||
Attaching {response?.length} image(s):
|
||||
</Paragraph>
|
||||
<ScrollView horizontal>
|
||||
{response.assets?.map((item) => (
|
||||
<TouchableOpacity key={item.fileName} activeOpacity={0.9}>
|
||||
{response?.map((item) => (
|
||||
<TouchableOpacity key={item.filename} activeOpacity={0.9}>
|
||||
<Image
|
||||
source={{
|
||||
uri: item.uri
|
||||
uri: item.sourceURL || item.path
|
||||
}}
|
||||
style={{
|
||||
width: 100,
|
||||
@@ -142,7 +142,7 @@ export default function AttachImage({
|
||||
|
||||
<Button
|
||||
title={`${
|
||||
(response.assets?.length || 0) > 1 ? "Attach Images" : "Attach Image"
|
||||
(response?.length || 0) > 1 ? "Attach Images" : "Attach Image"
|
||||
}`}
|
||||
type="accent"
|
||||
width="100%"
|
||||
@@ -156,10 +156,16 @@ export default function AttachImage({
|
||||
);
|
||||
}
|
||||
|
||||
AttachImage.present = (response: ImagePickerResponse) => {
|
||||
return new Promise((resolve) => {
|
||||
AttachImage.present = (response: ImageType[], context?: string) => {
|
||||
return new Promise<
|
||||
| {
|
||||
compress: boolean;
|
||||
}
|
||||
| undefined
|
||||
>((resolve) => {
|
||||
let resolved = false;
|
||||
presentSheet({
|
||||
context: context,
|
||||
component: (ref, close, update) => (
|
||||
<AttachImage
|
||||
response={response}
|
||||
|
||||
@@ -19,10 +19,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import Sodium from "@ammarahmed/react-native-sodium";
|
||||
import dataurl from "@notesnook/core/dist/utils/dataurl";
|
||||
import type { ImageAttributes } from "@notesnook/editor/dist/extensions/image/index";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Platform, View } from "react-native";
|
||||
import ImageViewer from "react-native-image-zoom-viewer";
|
||||
import { db } from "../../common/database";
|
||||
import downloadAttachment from "../../common/filesystem/download-attachment";
|
||||
import { cacheDir } from "../../common/filesystem/utils";
|
||||
import {
|
||||
@@ -32,13 +34,13 @@ import {
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import { ProgressBarComponent } from "../ui/svg/lazy";
|
||||
import type { ImageAttributes } from "@notesnook/editor/dist/extensions/image/index";
|
||||
const ImagePreview = () => {
|
||||
const { colors } = useThemeColors("dialog");
|
||||
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [image, setImage] = useState<string>();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const imageRef = useRef<ImageAttributes>();
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent("ImagePreview", open);
|
||||
@@ -48,6 +50,7 @@ const ImagePreview = () => {
|
||||
}, []);
|
||||
|
||||
const open = async (image: ImageAttributes) => {
|
||||
imageRef.current = image;
|
||||
setVisible(true);
|
||||
setLoading(true);
|
||||
setTimeout(async () => {
|
||||
@@ -60,6 +63,9 @@ const ImagePreview = () => {
|
||||
type: "base64",
|
||||
uri: ""
|
||||
});
|
||||
if (imageRef.current) {
|
||||
imageRef.current.hash = hash;
|
||||
}
|
||||
}
|
||||
if (!hash) return;
|
||||
//@ts-ignore // FIX ME
|
||||
@@ -80,7 +86,13 @@ const ImagePreview = () => {
|
||||
|
||||
return (
|
||||
visible && (
|
||||
<BaseDialog animation="slide" visible={true} onRequestClose={close}>
|
||||
<BaseDialog
|
||||
background="black"
|
||||
animation="slide"
|
||||
visible={true}
|
||||
onRequestClose={close}
|
||||
transparent
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
@@ -101,6 +113,21 @@ const ImagePreview = () => {
|
||||
color={colors.primary.accent}
|
||||
borderColor="transparent"
|
||||
/>
|
||||
<IconButton
|
||||
onPress={() => {
|
||||
if (imageRef.current?.hash) {
|
||||
db.fs().cancel(imageRef.current?.hash);
|
||||
}
|
||||
close();
|
||||
}}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: Platform.OS === "android" ? 35 : 0,
|
||||
right: 12
|
||||
}}
|
||||
color={colors.static.white}
|
||||
name="close"
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<ImageViewer
|
||||
@@ -117,13 +144,12 @@ const ImagePreview = () => {
|
||||
width: "100%",
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
height: 80,
|
||||
marginTop: 0,
|
||||
paddingHorizontal: 12,
|
||||
height: 50,
|
||||
paddingHorizontal: 24,
|
||||
position: "absolute",
|
||||
zIndex: 999,
|
||||
backgroundColor: "rgba(0,0,0,0.3)",
|
||||
paddingTop: Platform.OS === "android" ? 30 : 0
|
||||
marginTop: Platform.OS === "android" ? 30 : 0
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
@@ -137,7 +163,7 @@ const ImagePreview = () => {
|
||||
)}
|
||||
imageUrls={[
|
||||
{
|
||||
url: image
|
||||
url: image as string
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -21,12 +21,11 @@ import Sodium from "@ammarahmed/react-native-sodium";
|
||||
import { isImage } from "@notesnook/core/dist/utils/filename";
|
||||
import { Platform } from "react-native";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import DocumentPicker from "react-native-document-picker";
|
||||
import {
|
||||
ImagePickerResponse,
|
||||
launchCamera,
|
||||
launchImageLibrary
|
||||
} from "react-native-image-picker";
|
||||
import DocumentPicker, {
|
||||
DocumentPickerOptions,
|
||||
DocumentPickerResponse
|
||||
} from "react-native-document-picker";
|
||||
import { Image, openCamera, openPicker } from "react-native-image-crop-picker";
|
||||
import { DatabaseLogger, db } from "../../../common/database";
|
||||
import filesystem from "../../../common/filesystem";
|
||||
import { compressToFile } from "../../../common/filesystem/compress";
|
||||
@@ -43,7 +42,7 @@ import { eCloseSheet } from "../../../utils/events";
|
||||
import { useTabStore } from "./use-tab-store";
|
||||
import { editorController, editorState } from "./utils";
|
||||
|
||||
const showEncryptionSheet = (file) => {
|
||||
const showEncryptionSheet = (file: DocumentPickerResponse) => {
|
||||
presentSheet({
|
||||
title: "Encrypting attachment",
|
||||
paragraph: `Please wait while we encrypt ${file.name} file for upload`,
|
||||
@@ -51,24 +50,25 @@ const showEncryptionSheet = (file) => {
|
||||
});
|
||||
};
|
||||
|
||||
const santizeUri = (uri) => {
|
||||
const santizeUri = (uri: string) => {
|
||||
uri = decodeURI(uri);
|
||||
uri = Platform.OS === "ios" ? uri.replace("file:///", "/") : uri;
|
||||
return uri;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* noteId: string,
|
||||
* tabId: string,
|
||||
* type: "image" | "camera" | "file"
|
||||
* reupload: boolean
|
||||
* hash?: string
|
||||
* }} fileOptions
|
||||
*/
|
||||
const file = async (fileOptions) => {
|
||||
type PickerOptions = {
|
||||
noteId?: string;
|
||||
tabId?: number;
|
||||
type: "image" | "camera" | "file";
|
||||
reupload: boolean;
|
||||
hash?: string;
|
||||
context?: string;
|
||||
outputType?: "base64" | "url" | "cache";
|
||||
};
|
||||
|
||||
const file = async (fileOptions: PickerOptions) => {
|
||||
try {
|
||||
const options = {
|
||||
const options: DocumentPickerOptions<"ios"> = {
|
||||
mode: "import",
|
||||
allowMultiSelection: false
|
||||
};
|
||||
@@ -87,11 +87,11 @@ const file = async (fileOptions) => {
|
||||
|
||||
file = file[0];
|
||||
|
||||
let uri = Platform.OS === "ios" ? file.fileCopyUri : file.uri;
|
||||
let uri = Platform.OS === "ios" ? file.fileCopyUri || file.uri : file.uri;
|
||||
|
||||
if (file.size > FILE_SIZE_LIMIT) {
|
||||
if ((file.size || 0) > FILE_SIZE_LIMIT) {
|
||||
ToastManager.show({
|
||||
title: "File too large",
|
||||
heading: "File too large",
|
||||
message: "The maximum allowed size per file is 500 MB",
|
||||
type: "error"
|
||||
});
|
||||
@@ -115,22 +115,34 @@ const file = async (fileOptions) => {
|
||||
uri: uri,
|
||||
type: "url"
|
||||
});
|
||||
if (!(await attachFile(uri, hash, file.type, file.name, fileOptions)))
|
||||
if (
|
||||
!(await attachFile(
|
||||
uri,
|
||||
hash,
|
||||
file.type || "application/octet-stream",
|
||||
file.name,
|
||||
fileOptions
|
||||
))
|
||||
)
|
||||
return;
|
||||
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
|
||||
|
||||
if (!fileOptions.tabId) return;
|
||||
if (
|
||||
useTabStore.getState().getNoteIdForTab(options.tabId) === options.noteId
|
||||
fileOptions.tabId &&
|
||||
fileOptions.noteId &&
|
||||
useTabStore.getState().getNoteIdForTab(fileOptions.tabId) ===
|
||||
fileOptions.noteId
|
||||
) {
|
||||
if (isImage(file.type)) {
|
||||
if (isImage(file.type || "application/octet-stream")) {
|
||||
editorController.current?.commands.insertImage(
|
||||
{
|
||||
hash: hash,
|
||||
filename: file.name,
|
||||
mime: file.type,
|
||||
size: file.size,
|
||||
dataurl: await db.attachments.read(hash, "base64"),
|
||||
title: file.name
|
||||
mime: file.type || "application/octet-stream",
|
||||
size: file.size || 0,
|
||||
dataurl: (await db.attachments.read(hash, "base64")) as string,
|
||||
type: "image"
|
||||
},
|
||||
fileOptions.tabId
|
||||
);
|
||||
@@ -139,8 +151,9 @@ const file = async (fileOptions) => {
|
||||
{
|
||||
hash: hash,
|
||||
filename: file.name,
|
||||
mime: file.type,
|
||||
size: file.size
|
||||
mime: file.type || "application/octet-stream",
|
||||
size: file.size || 0,
|
||||
type: "file"
|
||||
},
|
||||
fileOptions.tabId
|
||||
);
|
||||
@@ -152,7 +165,7 @@ const file = async (fileOptions) => {
|
||||
}, 1000);
|
||||
} catch (e) {
|
||||
ToastManager.show({
|
||||
heading: e.message,
|
||||
heading: (e as Error).message,
|
||||
message: "You need internet access to attach a file",
|
||||
type: "error",
|
||||
context: "global"
|
||||
@@ -161,29 +174,51 @@ const file = async (fileOptions) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* noteId: string,
|
||||
* tabId: string,
|
||||
* type: "image" | "camera" | "file"
|
||||
* reupload: boolean
|
||||
* hash?: string
|
||||
* }} options
|
||||
*/
|
||||
const camera = async (options) => {
|
||||
const camera = async (options: PickerOptions) => {
|
||||
try {
|
||||
await db.attachments.generateKey();
|
||||
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
|
||||
launchCamera(
|
||||
{
|
||||
includeBase64: true,
|
||||
mediaType: "photo"
|
||||
},
|
||||
(response) => handleImageResponse(response, options)
|
||||
);
|
||||
openCamera({
|
||||
mediaType: "photo",
|
||||
includeBase64: true,
|
||||
cropping: false,
|
||||
multiple: true,
|
||||
maxFiles: 10,
|
||||
writeTempFile: true
|
||||
})
|
||||
.then((response) => handleImageResponse(response, options))
|
||||
.catch((e) => {
|
||||
console.log("camera error: ", e);
|
||||
});
|
||||
} catch (e) {
|
||||
ToastManager.show({
|
||||
heading: e.message,
|
||||
heading: (e as Error).message,
|
||||
type: "error",
|
||||
context: "global"
|
||||
});
|
||||
console.log("attachment error:", e);
|
||||
}
|
||||
};
|
||||
|
||||
const gallery = async (options: PickerOptions) => {
|
||||
try {
|
||||
await db.attachments.generateKey();
|
||||
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
|
||||
openPicker({
|
||||
includeBase64: true,
|
||||
mediaType: "photo",
|
||||
maxFiles: 10,
|
||||
cropping: false,
|
||||
multiple: true
|
||||
})
|
||||
.then((response) => handleImageResponse(response, options))
|
||||
.catch((e) => {
|
||||
console.log("gallery error: ", e);
|
||||
});
|
||||
} catch (e) {
|
||||
useSettingStore.getState().setAppDidEnterBackgroundForAction(false);
|
||||
ToastManager.show({
|
||||
heading: (e as Error).message,
|
||||
message: "You need internet access to attach a file",
|
||||
type: "error",
|
||||
context: "global"
|
||||
@@ -192,53 +227,9 @@ const camera = async (options) => {
|
||||
}
|
||||
};
|
||||
|
||||
const gallery = async (options) => {
|
||||
try {
|
||||
await db.attachments.generateKey();
|
||||
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
|
||||
launchImageLibrary(
|
||||
{
|
||||
includeBase64: true,
|
||||
mediaType: "photo",
|
||||
selectionLimit: 10
|
||||
},
|
||||
(response) => handleImageResponse(response, options)
|
||||
);
|
||||
} catch (e) {
|
||||
ToastManager.show({
|
||||
heading: e.message,
|
||||
message: "You need internet access to attach a file",
|
||||
type: "error",
|
||||
context: "global"
|
||||
});
|
||||
console.log("attachment error:", e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @typedef {{
|
||||
* noteId?: string,
|
||||
* tabId?: string,
|
||||
* type: "image" | "camera" | "file"
|
||||
* reupload: boolean
|
||||
* hash?: string
|
||||
* context?: string
|
||||
* }} ImagePickerOptions
|
||||
*
|
||||
* @param {{
|
||||
* noteId?: string,
|
||||
* tabId?: string,
|
||||
* type: "image" | "camera" | "file"
|
||||
* reupload: boolean
|
||||
* hash?: string
|
||||
* context?: string
|
||||
* }} options
|
||||
* @returns
|
||||
*/
|
||||
const pick = async (options) => {
|
||||
const pick = async (options: PickerOptions) => {
|
||||
if (!PremiumService.get()) {
|
||||
let user = await db.user.getUser();
|
||||
const user = await db.user.getUser();
|
||||
if (editorState().isFocused) {
|
||||
editorState().isFocused = true;
|
||||
}
|
||||
@@ -259,58 +250,49 @@ const pick = async (options) => {
|
||||
file(options);
|
||||
}
|
||||
};
|
||||
/**
|
||||
*
|
||||
* @param {ImagePickerResponse} response
|
||||
* @param {ImagePickerOptions} options
|
||||
* @returns
|
||||
*/
|
||||
const handleImageResponse = async (response, options) => {
|
||||
if (
|
||||
response.didCancel ||
|
||||
response.errorMessage ||
|
||||
!response.assets ||
|
||||
response.assets?.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await AttachImage.present(response);
|
||||
|
||||
const handleImageResponse = async (
|
||||
response: Image[],
|
||||
options: PickerOptions
|
||||
) => {
|
||||
const result = await AttachImage.present(response, options.context);
|
||||
if (!result) return;
|
||||
const compress = result.compress;
|
||||
|
||||
for (let image of response.assets) {
|
||||
const isPng = /(png)/g.test(image.type);
|
||||
const isJpeg = /(jpeg|jpg)/g.test(image.type);
|
||||
for (const image of response) {
|
||||
const isPng = /(png)/g.test(image.mime);
|
||||
const isJpeg = /(jpeg|jpg)/g.test(image.mime);
|
||||
|
||||
if (compress && (isPng || isJpeg)) {
|
||||
image.uri = await compressToFile(
|
||||
Platform.OS === "ios" ? "file://" + image.uri : image.uri,
|
||||
image.path = await compressToFile(
|
||||
Platform.OS === "ios" ? "file://" + image.path : image.path,
|
||||
isPng ? "PNG" : "JPEG"
|
||||
);
|
||||
const stat = await RNFetchBlob.fs.stat(image.uri.replace("file://", ""));
|
||||
image.fileSize = stat.size;
|
||||
|
||||
const stat = await RNFetchBlob.fs.stat(image.path.replace("file://", ""));
|
||||
image.size = stat.size;
|
||||
image.path =
|
||||
Platform.OS === "ios" ? image.path.replace("file://", "") : image.path;
|
||||
}
|
||||
|
||||
if (image.fileSize > IMAGE_SIZE_LIMIT) {
|
||||
if (image.size > IMAGE_SIZE_LIMIT) {
|
||||
ToastManager.show({
|
||||
title: "File too large",
|
||||
heading: "File too large",
|
||||
message: "The maximum allowed size per image is 50 MB",
|
||||
type: "error"
|
||||
});
|
||||
return;
|
||||
}
|
||||
let b64 = `data:${image.type};base64, ` + image.base64;
|
||||
const uri = decodeURI(image.uri);
|
||||
const b64 = `data:${image.mime};base64, ` + image.data;
|
||||
const uri = decodeURI(image.path);
|
||||
const hash = await Sodium.hashFile({
|
||||
uri: uri,
|
||||
type: "url"
|
||||
});
|
||||
|
||||
let fileName = image.originalFileName || image.fileName;
|
||||
const fileName = image.filename || "image";
|
||||
console.log("attaching file...");
|
||||
if (!(await attachFile(uri, hash, image.type, fileName, options))) return;
|
||||
if (!(await attachFile(uri, hash, image.mime, fileName, options))) return;
|
||||
|
||||
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
|
||||
console.log("attaching image to note...");
|
||||
@@ -322,11 +304,13 @@ const handleImageResponse = async (response, options) => {
|
||||
editorController.current?.commands.insertImage(
|
||||
{
|
||||
hash: hash,
|
||||
mime: image.type,
|
||||
title: fileName,
|
||||
mime: image.mime,
|
||||
type: "image",
|
||||
dataurl: b64,
|
||||
size: image.fileSize,
|
||||
filename: fileName
|
||||
size: image.size,
|
||||
filename: fileName as string,
|
||||
width: image.width,
|
||||
height: image.height
|
||||
},
|
||||
options.tabId
|
||||
);
|
||||
@@ -343,10 +327,16 @@ const handleImageResponse = async (response, options) => {
|
||||
* @param {ImagePickerOptions} options
|
||||
* @returns
|
||||
*/
|
||||
export async function attachFile(uri, hash, type, filename, options) {
|
||||
export async function attachFile(
|
||||
uri: string,
|
||||
hash: string,
|
||||
type: string,
|
||||
filename: string,
|
||||
options: PickerOptions
|
||||
) {
|
||||
try {
|
||||
let exists = await db.attachments.exists(hash);
|
||||
let encryptionInfo;
|
||||
const exists = await db.attachments.exists(hash);
|
||||
let encryptionInfo: any;
|
||||
if (options?.hash && options.hash !== hash) {
|
||||
ToastManager.show({
|
||||
heading: "Please select the same file for reuploading",
|
||||
@@ -358,26 +348,36 @@ export async function attachFile(uri, hash, type, filename, options) {
|
||||
}
|
||||
|
||||
if (!options.reupload && exists) {
|
||||
options.reupload = (await filesystem.getUploadedFileSize(hash)) <= 0;
|
||||
options.reupload = (await filesystem.getUploadedFileSize(hash)) === 0;
|
||||
}
|
||||
|
||||
if (options.reupload) {
|
||||
DatabaseLogger.log(`Deleting file before reupload. ${hash}`);
|
||||
const deleted = await db.fs().deleteFile(hash, false);
|
||||
if (!deleted)
|
||||
throw new Error(`Failed to delete file before reupload. ${hash}`);
|
||||
}
|
||||
|
||||
if (!exists || options?.reupload) {
|
||||
let key = await db.attachments.generateKey();
|
||||
const key = await db.attachments.generateKey();
|
||||
encryptionInfo = await Sodium.encryptFile(key, {
|
||||
uri: uri,
|
||||
type: options.type || "url",
|
||||
type: options.outputType || "url",
|
||||
hash: hash
|
||||
});
|
||||
} as any);
|
||||
encryptionInfo.mimeType = type;
|
||||
encryptionInfo.filename = filename;
|
||||
encryptionInfo.alg = "xcha-stream";
|
||||
encryptionInfo.size = encryptionInfo.length;
|
||||
encryptionInfo.key = key;
|
||||
if (options?.reupload && exists) await db.attachments.reset(hash);
|
||||
if (options?.reupload && exists) {
|
||||
const attachment = await db.attachments.attachment(hash);
|
||||
if (attachment) await db.attachments.reset(attachment?.id);
|
||||
}
|
||||
} else {
|
||||
encryptionInfo = { hash: hash };
|
||||
}
|
||||
await db.attachments.add(encryptionInfo, options.noteId);
|
||||
await db.attachments.add(encryptionInfo);
|
||||
return true;
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e);
|
||||
@@ -77,6 +77,7 @@ import { EditorMessage, EditorProps, useEditorType } from "./types";
|
||||
import { useTabStore } from "./use-tab-store";
|
||||
import { EditorEvents, editorState, openInternalLink } from "./utils";
|
||||
|
||||
|
||||
const publishNote = async () => {
|
||||
const user = useUserStore.getState().user;
|
||||
if (!user) {
|
||||
@@ -449,7 +450,7 @@ export const useEditorEvents = (
|
||||
break;
|
||||
case EventTypes.filepicker:
|
||||
editorState().isAwaitingResult = true;
|
||||
const { pick } = require("./picker.js").default;
|
||||
const { pick } = require("./picker").default;
|
||||
pick({
|
||||
type: editorMessage.value,
|
||||
noteId: noteId,
|
||||
|
||||
@@ -164,12 +164,6 @@ export const LICENSES = [
|
||||
author: "dooboolab",
|
||||
link: "https://github.com/dooboolab/react-native-iap"
|
||||
},
|
||||
{
|
||||
name: "react-native-image-picker",
|
||||
licenseType: "MIT",
|
||||
author: "Johan du Toit (Johan-dutoit)",
|
||||
link: "https://github.com/react-native-image-picker/react-native-image-picker"
|
||||
},
|
||||
{
|
||||
name: "react-native-keychain",
|
||||
licenseType: "MIT",
|
||||
|
||||
@@ -319,8 +319,6 @@ PODS:
|
||||
- React-Core
|
||||
- react-native-html-to-pdf-lite (0.9.1):
|
||||
- React
|
||||
- react-native-image-picker (4.1.2):
|
||||
- React-Core
|
||||
- react-native-image-resizer (3.0.5):
|
||||
- React-Core
|
||||
- react-native-in-app-review (4.3.3):
|
||||
@@ -597,7 +595,6 @@ DEPENDENCIES:
|
||||
- react-native-get-random-values (from `../../node_modules/react-native-get-random-values`)
|
||||
- react-native-gzip (from `../../node_modules/react-native-gzip`)
|
||||
- react-native-html-to-pdf-lite (from `../../node_modules/react-native-html-to-pdf-lite`)
|
||||
- react-native-image-picker (from `../../node_modules/react-native-image-picker`)
|
||||
- "react-native-image-resizer (from `../../node_modules/@bam.tech/react-native-image-resizer`)"
|
||||
- react-native-in-app-review (from `../../node_modules/react-native-in-app-review`)
|
||||
- "react-native-keep-awake (from `../../node_modules/@sayem314/react-native-keep-awake`)"
|
||||
@@ -736,8 +733,6 @@ EXTERNAL SOURCES:
|
||||
:path: "../../node_modules/react-native-gzip"
|
||||
react-native-html-to-pdf-lite:
|
||||
:path: "../../node_modules/react-native-html-to-pdf-lite"
|
||||
react-native-image-picker:
|
||||
:path: "../../node_modules/react-native-image-picker"
|
||||
react-native-image-resizer:
|
||||
:path: "../../node_modules/@bam.tech/react-native-image-resizer"
|
||||
react-native-in-app-review:
|
||||
@@ -899,7 +894,6 @@ SPEC CHECKSUMS:
|
||||
react-native-get-random-values: dee677497c6a740b71e5612e8dbd83e7539ed5bb
|
||||
react-native-gzip: c5e87ee9e359f02350e3a2ee52eb35eddc398868
|
||||
react-native-html-to-pdf-lite: 21bfb169bf4cbcd7bec9f736975ee1b3f5292d4a
|
||||
react-native-image-picker: 9c8a2687b69300ad9e95cec5d38f35ab9d32467d
|
||||
react-native-image-resizer: 00ceb0e05586c7aadf061eea676957a6c2ec60fa
|
||||
react-native-in-app-review: db8bb167a5f238e7ceca5c242d6b36ce8c4404a4
|
||||
react-native-keep-awake: caee3ff89eaa21dfe29010f0d143566874a04441
|
||||
@@ -964,4 +958,4 @@ SPEC CHECKSUMS:
|
||||
|
||||
PODFILE CHECKSUM: 2b8b28a341b202bf3ca5f231b75bb05893486ed8
|
||||
|
||||
COCOAPODS: 1.12.1
|
||||
COCOAPODS: 1.14.2
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
"react-native-gzip": "1.1.0",
|
||||
"react-native-html-to-pdf-lite": "^0.9.1",
|
||||
"react-native-iap": "12.11.0",
|
||||
"react-native-image-picker": "4.1.2",
|
||||
"react-native-in-app-review": "4.3.3",
|
||||
"react-native-keychain": "4.0.5",
|
||||
"react-native-mmkv-storage": "^0.10.0-alpha.12",
|
||||
|
||||
9
apps/mobile/package-lock.json
generated
9
apps/mobile/package-lock.json
generated
@@ -28473,7 +28473,6 @@
|
||||
"react-native-html-to-pdf-lite": "^0.9.1",
|
||||
"react-native-iap": "12.11.0",
|
||||
"react-native-image-crop-picker": "^0.40.2",
|
||||
"react-native-image-picker": "4.1.2",
|
||||
"react-native-in-app-review": "4.3.3",
|
||||
"react-native-keychain": "4.0.5",
|
||||
"react-native-mmkv-storage": "^0.10.0-alpha.12",
|
||||
@@ -45061,14 +45060,6 @@
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-image-picker": {
|
||||
"version": "4.1.2",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-image-zoom-viewer": {
|
||||
"version": "3.0.1",
|
||||
"license": "MIT",
|
||||
|
||||
Reference in New Issue
Block a user