Compare commits

...

8 Commits

Author SHA1 Message Date
ammarahm-ed
8472154abb mobile: allow removing attachable files 2023-06-05 16:25:40 +05:00
ammarahm-ed
d90dd5b48f mobile: fix file upload from share extension 2023-06-05 15:59:58 +05:00
Ammar Ahmed
3ce5b7929e Merge branch 'master' into feat/file-and-image-sharing
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2023-06-05 15:14:30 +05:00
ammarahm-ed
27b3575fcf mobile: guard ios functions 2023-06-05 10:38:57 +05:00
ammarahm-ed
aef6a1b540 mobile: guard ios only functions 2023-06-05 10:31:42 +05:00
ammarahm-ed
e78ac3c925 mobile: update deps 2023-06-03 23:33:08 +05:00
ammarahm-ed
f30c066305 mobile: add file size limits 2023-06-03 23:21:10 +05:00
ammarahm-ed
ac39ca8a11 mobile: add support for image & file sharing 2023-06-03 22:56:47 +05:00
17 changed files with 3099 additions and 102 deletions

View File

@@ -22,10 +22,17 @@ import "react-native-get-random-values";
import * as Keychain from "react-native-keychain";
import { generateSecureRandom } from "react-native-securerandom";
import Sodium from "@ammarahmed/react-native-sodium";
import { MMKV } from "./mmkv";
const IOS_KEYCHAIN_ACCESS_GROUP = "group.org.streetwriters.notesnook";
const IOS_KEYCHAIN_SERVICE_NAME = "org.streetwriters.notesnook";
const IOS_KEYCHAIN_UPGRAGE_KEY = "keychain-ios:upgraded";
const KEYSTORE_CONFIG = Platform.select({
ios: {
accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY
accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
accessGroup: IOS_KEYCHAIN_ACCESS_GROUP,
service: IOS_KEYCHAIN_SERVICE_NAME
},
android: {}
});
@@ -39,12 +46,27 @@ export async function deriveCryptoKey(name, data) {
credentials.key,
KEYSTORE_CONFIG
);
MMKV.setBool(IOS_KEYCHAIN_UPGRAGE_KEY, true);
return credentials.key;
} catch (e) {
console.error(e);
}
}
async function upgradeIOSKeychain(username, password) {
if (Platform.OS !== "ios") return;
if (!MMKV.getBool(IOS_KEYCHAIN_UPGRAGE_KEY)) {
await Keychain.setInternetCredentials(
"notesnook",
username,
password,
KEYSTORE_CONFIG
);
console.log("IOS KEYCHAIN MIGRATION COMPLETED!");
MMKV.setBool(IOS_KEYCHAIN_UPGRAGE_KEY, true);
}
}
export async function getCryptoKey(_name) {
try {
if (await Keychain.hasInternetCredentials("notesnook")) {
@@ -52,6 +74,9 @@ export async function getCryptoKey(_name) {
"notesnook",
KEYSTORE_CONFIG
);
// upgrades ios keychain to use accessGroups
// so we have access to keychain in share extension.
await upgradeIOSKeychain(credentials.username, credentials.password);
return credentials.password;
} else {
return null;

View File

@@ -23,14 +23,25 @@ import RNFetchBlob from "react-native-blob-util";
import { cacheDir, getRandomId } from "./utils";
import { db } from "../database";
import { compressToBase64 } from "./compress";
import { IOS_APPGROUPID } from "../../utils/constants";
export async function readEncrypted(filename, key, cipherData) {
let path = `${cacheDir}/${filename}`;
try {
let exists = await RNFetchBlob.fs.exists(path);
const iosAppGroup =
Platform.OS === "ios"
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
: null;
const appGroupPath = `${iosAppGroup}/${filename}`;
let exists =
(await RNFetchBlob.fs.exists(path)) ||
(Platform.OS === "ios" && (await RNFetchBlob.fs.exists(appGroupPath)));
if (!exists) {
return false;
}
const attachment = db.attachments.attachment(filename);
const isPng = /(png)/g.test(attachment?.metadata.type);
const isJpeg = /(jpeg|jpg)/g.test(attachment?.metadata.type);
@@ -39,7 +50,8 @@ export async function readEncrypted(filename, key, cipherData) {
key,
{
...cipherData,
hash: filename
hash: filename,
appGroupId: IOS_APPGROUPID
},
cipherData.outputType === "base64"
? isPng || isJpeg

View File

@@ -22,6 +22,8 @@ import { useAttachmentStore } from "../../stores/use-attachment-store";
import { db } from "../database";
import { cacheDir } from "./utils";
import { isImage, isDocument } from "@notesnook/core/utils/filename";
import { Platform } from "react-native";
import { IOS_APPGROUPID } from "../../utils/constants";
export async function uploadFile(filename, data, cancelToken) {
if (!data) return false;
@@ -36,9 +38,19 @@ export async function uploadFile(filename, data, cancelToken) {
if (!res.ok) throw new Error(`${res.status}: Unable to resolve upload url`);
const uploadUrl = await res.text();
if (!uploadUrl) throw new Error("Unable to resolve upload url");
let uploadFilePath = `${cacheDir}/${filename}`;
const iosAppGroup =
Platform.OS === "ios"
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
: null;
const appGroupPath = `${iosAppGroup}/${filename}`;
let exists = await RNFetchBlob.fs.exists(uploadFilePath);
if (!exists && Platform.OS === "ios") {
uploadFilePath = appGroupPath;
}
let request = RNFetchBlob.config({
IOSBackgroundTask: true
IOSBackgroundTask: !globalThis["IS_SHARE_EXTENSION"]
})
.fetch(
"PUT",
@@ -46,7 +58,7 @@ export async function uploadFile(filename, data, cancelToken) {
{
"content-type": ""
},
RNFetchBlob.wrap(`${cacheDir}/${filename}`)
RNFetchBlob.wrap(uploadFilePath)
)
.uploadProgress((sent, total) => {
useAttachmentStore

View File

@@ -17,22 +17,37 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import NetInfo from "@react-native-community/netinfo";
import { EV, EVENTS, SYNC_CHECK_IDS } from "@notesnook/core/common";
import { useEffect, useRef } from "react";
import notifee from "@notifee/react-native";
import NetInfo from "@react-native-community/netinfo";
import { useCallback, useEffect, useRef } from "react";
import {
Appearance,
AppState,
Appearance,
Keyboard,
Linking,
NativeEventEmitter,
NativeModules,
Platform,
Keyboard
Platform
} from "react-native";
import * as RNIap from "react-native-iap";
import { enabled } from "react-native-privacy-snapshot";
import { DatabaseLogger, db } from "../common/database";
import { MMKV } from "../common/database/mmkv";
import { Walkthrough } from "../components/walkthroughs";
import { editorController, editorState } from "../screens/editor/tiptap/utils";
import {
clearAppState,
editorController,
editorState
} from "../screens/editor/tiptap/utils";
import { useDragState } from "../screens/settings/editor/state";
import BackupService from "../services/backup";
import {
ToastEvent,
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent
} from "../services/event-manager";
import {
clearMessage,
setEmailVerifyMessage,
@@ -41,36 +56,23 @@ import {
} from "../services/message";
import PremiumService from "../services/premium";
import SettingsService from "../services/settings";
import Sync from "../services/sync";
import { initAfterSync } from "../stores";
import { useAttachmentStore } from "../stores/use-attachment-store";
import { useEditorStore } from "../stores/use-editor-store";
import { useMessageStore } from "../stores/use-message-store";
import { useNoteStore } from "../stores/use-notes-store";
import { useSettingStore } from "../stores/use-setting-store";
import { SyncStatus, useUserStore } from "../stores/use-user-store";
import { updateStatusBarColor } from "../utils/color-scheme";
import { DatabaseLogger, db } from "../common/database";
import { MMKV } from "../common/database/mmkv";
import {
eClearEditor,
eCloseSheet,
eOnLoadNote,
refreshNotesPage
} from "../utils/events";
import Sync from "../services/sync";
import { initAfterSync } from "../stores";
import { SyncStatus, useUserStore } from "../stores/use-user-store";
import { useMessageStore } from "../stores/use-message-store";
import { useSettingStore } from "../stores/use-setting-store";
import { useAttachmentStore } from "../stores/use-attachment-store";
import { useNoteStore } from "../stores/use-notes-store";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent,
ToastEvent
} from "../services/event-manager";
import { useEditorStore } from "../stores/use-editor-store";
import { useDragState } from "../screens/settings/editor/state";
import { useCallback } from "react";
import { clearAppState } from "../screens/editor/tiptap/utils";
import { tabBarRef } from "../utils/global-refs";
import BackupService from "../services/backup";
import { sleep } from "../utils/time";
import notifee from "@notifee/react-native";
const SodiumEventEmitter = new NativeEventEmitter(NativeModules.Sodium);
export const useAppEvents = () => {
@@ -562,7 +564,6 @@ export const useAppEvents = () => {
await db.initCollections();
await db.notes.init();
}
useNoteStore.getState().setNotes();
eSendEvent(refreshNotesPage);
MMKV.removeItem("notesAddedFromIntent");
initAfterSync();

View File

@@ -35,8 +35,7 @@ import PremiumService from "../../../services/premium";
import { eCloseSheet } from "../../../utils/events";
import { editorController, editorState } from "./utils";
import { isImage } from "@notesnook/core/utils/filename";
const FILE_SIZE_LIMIT = 500 * 1024 * 1024;
const IMAGE_SIZE_LIMIT = 50 * 1024 * 1024;
import { FILE_SIZE_LIMIT, IMAGE_SIZE_LIMIT } from "../../../utils/constants";
const showEncryptionSheet = (file) => {
presentSheet({
@@ -270,7 +269,7 @@ const handleImageResponse = async (response, options) => {
});
};
async function attachFile(uri, hash, type, filename, options) {
export async function attachFile(uri, hash, type, filename, options) {
try {
let exists = db.attachments.exists(hash);
let encryptionInfo;
@@ -289,7 +288,7 @@ async function attachFile(uri, hash, type, filename, options) {
let key = await db.attachments.generateKey();
encryptionInfo = await Sodium.encryptFile(key, {
uri: uri,
type: "url",
type: options.type || "url",
hash: hash
});
encryptionInfo.type = type;

View File

@@ -20,6 +20,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { Platform } from "react-native";
import { Monographs } from "../screens/notes/monographs";
export const IOS_APPGROUPID = "group.org.streetwriters.notesnook";
export const FILE_SIZE_LIMIT = 500 * 1024 * 1024;
export const IMAGE_SIZE_LIMIT = 50 * 1024 * 1024;
export const STORE_LINK =
Platform.OS === "ios"
? "https://apps.apple.com/us/app/notesnook/id1544027013"

View File

@@ -0,0 +1,123 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
import Sodium from "@ammarahmed/react-native-sodium";
import { isImage } from "@notesnook/core/utils/filename";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import { db } from "../common/database";
import { MMKV } from "../common/database/mmkv";
import { IOS_APPGROUPID } from "./constants";
export async function attachFile(uri, hash, type, filename, options) {
try {
let exists = db.attachments.exists(hash);
let encryptionInfo;
if (options?.hash && options.hash !== hash) return false;
if (!exists || options?.reupload) {
let key = await db.attachments.generateKey();
encryptionInfo = await Sodium.encryptFile(key, {
uri: uri,
type: options.type || "url",
hash: hash,
appGroupId: options.appGroupId
});
encryptionInfo.type = type;
encryptionInfo.filename = filename;
encryptionInfo.alg = "xcha-stream";
encryptionInfo.size = encryptionInfo.length;
encryptionInfo.key = key;
if (options?.reupload && exists) await db.attachments.reset(hash);
} else {
encryptionInfo = { hash: hash };
}
await db.attachments.add(encryptionInfo, options?.id);
return true;
} catch (e) {
if (Platform.OS === "ios") RNFetchBlob.fs.unlink(uri).catch(console.log);
console.log("attach file error: ", e);
return false;
}
}
async function createNotes() {
const bundles = MMKV.getArray("shared:noteBundles") || [];
console.log("creating notes from note bundles", bundles);
for (let i = 0; i < bundles.length; i++) {
const bundle = bundles[i];
const id = await db.notes.add(bundle.note);
for (const item of bundle.notebooks) {
if (item.type === "notebook") {
db.relations.add(item, { id, type: "note" });
} else {
db.notes.addToNotebook(
{
id: item.notebookId,
topic: item.id
},
id
);
}
}
for (const file of bundle.files) {
const uri =
Platform.OS === "ios"
? `${file.value.replace("file://", "")}`
: `${file.value}`;
const hash = await Sodium.hashFile({
uri: uri,
type: "cache"
});
await attachFile(uri, hash, file.type, file.name, {
type: "cache",
id: id,
appGroupId: IOS_APPGROUPID,
reupload: true
});
let content = ``;
if (isImage(file.type)) {
content = `<img data-hash="${hash}" data-mime="${file.type}" data-filename="${file.name}" />`;
} else {
content = `<p><span data-hash="${hash}" data-mime="${file.type}" data-filename="${file.name}" data-size="${file.size}" /></p>`;
}
const rawContent = await db.content.raw(
db.notes.note(id).data?.contentId
);
await db.notes.add({
id: id,
content: {
type: "tiptap",
data: rawContent?.data ? rawContent?.data + content : content
},
sessionId: Date.now()
});
}
const _bundles = [...bundles];
_bundles.splice(i, 1);
MMKV.setArray("shared:noteBundles", _bundles);
}
}
export const NoteBundle = {
createNotes
};

View File

@@ -28,6 +28,11 @@
<!-- If you don't know the MIME type in advance, set "mimeType" to "*/*". -->
<data android:mimeType="text/*" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<!-- If you don't know the MIME type in advance, set "mimeType" to "*/*". -->
<data android:mimeType="image/*" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<!-- If you don't know the MIME type in advance, set "mimeType" to "*/*". -->
@@ -108,13 +113,17 @@
android:excludeFromRecents="true"
android:windowSoftInputMode="adjustResize"
android:theme="@style/AppThemeB">
<intent-filter android:label="Make Note">
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/*" />
<data android:mimeType="image/*" />
<data android:mimeType="video/*" />
<data android:mimeType="image/*" />
<data android:mimeType="application/*" />
</intent-filter>
<intent-filter android:label="Make Note">
<action android:name="android.intent.action.PROCESS_TEXT" />
<category android:name="android.intent.category.DEFAULT" />

View File

@@ -33,10 +33,16 @@
<dict>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsFileWithMaxCount</key>
<string>5</string>
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
<integer>5</integer>
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
<string>5</string>
<key>NSExtensionActivationSupportsText</key>
<true/>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>10</integer>
<integer>1</integer>
</dict>
</dict>
<key>NSExtensionMainStoryboard</key>
@@ -58,5 +64,7 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>appGroupId</key>
<string>group.org.streetwriters.notesnook</string>
</dict>
</plist>

View File

@@ -6,5 +6,9 @@
<array>
<string>group.org.streetwriters.notesnook</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)group.org.streetwriters.notesnook</string>
</array>
</dict>
</plist>

View File

@@ -151,5 +151,7 @@
</dict>
</dict>
</array>
<key>appGroupId</key>
<string>group.org.streetwriters.notesnook</string>
</dict>
</plist>

View File

@@ -10,5 +10,9 @@
<array>
<string>group.org.streetwriters.notesnook</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)group.org.streetwriters.notesnook</string>
</array>
</dict>
</plist>

View File

@@ -11,5 +11,9 @@
<array>
<string>group.org.streetwriters.notesnook</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)group.org.streetwriters.notesnook</string>
</array>
</dict>
</plist>

View File

@@ -283,7 +283,9 @@ PODS:
- RCTTypeSafety
- React-Core
- ReactCommon/turbomodule/core
- react-native-sodium (1.3.0):
- react-native-share-extension (2.5.2):
- React
- react-native-sodium (1.4.1):
- React
- react-native-webview (11.26.1):
- React-Core
@@ -353,9 +355,7 @@ PODS:
- React-jsi (= 0.69.7)
- React-logger (= 0.69.7)
- React-perflogger (= 0.69.7)
- rn-extensions-share (2.4.0):
- React-Core
- RNBootSplash (4.7.1):
- RNBootSplash (4.1.4):
- React-Core
- RNCCheckbox (0.5.15):
- BEMCheckBox (~> 1.4)
@@ -489,6 +489,7 @@ DEPENDENCIES:
- react-native-orientation (from `../../node_modules/react-native-orientation`)
- react-native-pdf (from `../../node_modules/react-native-pdf`)
- react-native-safe-area-context (from `../../node_modules/react-native-safe-area-context`)
- "react-native-share-extension (from `../../node_modules/@ammarahmed/react-native-share-extension`)"
- "react-native-sodium (from `../../node_modules/@ammarahmed/react-native-sodium`)"
- react-native-webview (from `../../node_modules/react-native-webview`)
- React-perflogger (from `../../node_modules/react-native/ReactCommon/reactperflogger`)
@@ -503,7 +504,6 @@ DEPENDENCIES:
- React-RCTVibration (from `../../node_modules/react-native/Libraries/Vibration`)
- React-runtimeexecutor (from `../../node_modules/react-native/ReactCommon/runtimeexecutor`)
- ReactCommon/turbomodule/core (from `../../node_modules/react-native/ReactCommon`)
- rn-extensions-share (from `../../node_modules/rn-extensions-share`)
- RNBootSplash (from `../../node_modules/react-native-bootsplash`)
- "RNCCheckbox (from `../../node_modules/@react-native-community/checkbox`)"
- "RNCClipboard (from `../../node_modules/@react-native-clipboard/clipboard`)"
@@ -625,6 +625,8 @@ EXTERNAL SOURCES:
:path: "../../node_modules/react-native-pdf"
react-native-safe-area-context:
:path: "../../node_modules/react-native-safe-area-context"
react-native-share-extension:
:path: "../../node_modules/@ammarahmed/react-native-share-extension"
react-native-sodium:
:path: "../../node_modules/@ammarahmed/react-native-sodium"
react-native-webview:
@@ -653,8 +655,6 @@ EXTERNAL SOURCES:
:path: "../../node_modules/react-native/ReactCommon/runtimeexecutor"
ReactCommon:
:path: "../../node_modules/react-native/ReactCommon"
rn-extensions-share:
:path: "../../node_modules/rn-extensions-share"
RNBootSplash:
:path: "../../node_modules/react-native-bootsplash"
RNCCheckbox:
@@ -759,7 +759,8 @@ SPEC CHECKSUMS:
react-native-orientation: f1caf84d65f1a4fd4511a18f2b924e634ad7a628
react-native-pdf: 33c622cbdf776a649929e8b9d1ce2d313347c4fa
react-native-safe-area-context: b8979f5eda6ed5903d4dbc885be3846ea3daa753
react-native-sodium: 1681828855ec18fa952f4557cd595bf048cf5c32
react-native-share-extension: 828641041123f5489fcb820758fb8bb743d15e34
react-native-sodium: f4e3986ddcb73482f8679e534b448a0675d0cf13
react-native-webview: 9f111dfbcfc826084d6c507f569e5e03342ee1c1
React-perflogger: 8e832d4e21fdfa613033c76d58d7e617341e804b
React-RCTActionSheet: 9ca778182a9523991bff6381045885b6e808bb73
@@ -773,8 +774,7 @@ SPEC CHECKSUMS:
React-RCTVibration: 600a9f8b3537db360563d50fab3d040c262567d4
React-runtimeexecutor: 65cd2782a57e1d59a68aa5d504edf94278578e41
ReactCommon: 1e783348b9aa73ae68236271df972ba898560a95
rn-extensions-share: 3f0ecce20dfbca1f0358deb4ebfb9ee121a6d92a
RNBootSplash: 3f3f7f82efe2addbfe7ddeda20877ff4d579cd81
RNBootSplash: de2c568373a9c79a66e9918b8929eb6c9a35246f
RNCCheckbox: 43bcc6493611468af0e19f19f029dab3da8561c4
RNCClipboard: 3f0451a8100393908bea5c5c5b16f96d45f30bfc
RNCMaskedView: 949696f25ec596bfc697fc88e6f95cf0c79669b6

View File

@@ -43,11 +43,10 @@
"react-native-screens": "^3.13.1",
"react-native-securerandom": "^1.0.1",
"react-native-share": "^7.2.0",
"@ammarahmed/react-native-sodium": "1.3.0",
"@ammarahmed/react-native-sodium": "1.4.1",
"react-native-svg": "^12.3.0",
"react-native-tooltips": "^1.0.3",
"react-native-webview": "^11.14.1",
"rn-extensions-share": "^2.4.0",
"react-native-gzip":"1.0.0",
"@shopify/flash-list":"1.4.0",
"@ammarahmed/notifee-react-native": "7.4.4",
@@ -62,6 +61,7 @@
"react-native-vector-icons": "9.2.0",
"react-native-pdf": "6.6.2",
"react-native-blob-util": "0.17.3",
"@ammarahmed/react-native-share-extension": "^2.5.2",
"react-native-in-app-review": "4.3.3"
},
"devDependencies": {

File diff suppressed because it is too large Load Diff

View File

@@ -23,6 +23,7 @@ import React, { useCallback, useEffect, useRef, useState } from "react";
import {
ActivityIndicator,
Alert,
Image,
Keyboard,
Platform,
SafeAreaView,
@@ -38,17 +39,22 @@ import {
useSafeAreaInsets
} from "react-native-safe-area-context";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import ShareExtension from "rn-extensions-share";
import ShareExtension from "@ammarahmed/react-native-share-extension";
import isURL from "validator/lib/isURL";
import { db } from "../app/common/database";
import { MMKV } from "../app/common/database/mmkv";
import Storage from "../app/common/database/storage";
import { eSendEvent } from "../app/services/event-manager";
import { getElevation } from "../app/utils";
import { formatBytes, getElevation } from "../app/utils";
import { eOnLoadNote } from "../app/utils/events";
import { Editor } from "./editor";
import { Search } from "./search";
import { initDatabase, useShareStore } from "./store";
import NetInfo from "@react-native-community/netinfo";
import { isImage } from "@notesnook/core/utils/filename";
import { NoteBundle } from "../app/utils/note-bundle";
import { FILE_SIZE_LIMIT, IMAGE_SIZE_LIMIT } from "../app/utils/constants";
import RNFetchBlob from "react-native-blob-util";
const getLinkPreview = (url) => {
return getPreviewData(url, 5000);
};
@@ -193,7 +199,7 @@ const ShareView = ({ quicknote = false }) => {
const accent = useShareStore((state) => state.accent);
const appendNote = useShareStore((state) => state.appendNote);
const [note, setNote] = useState({ ...defaultNote });
const noteContent = useRef();
const noteContent = useRef("");
const [loading, setLoading] = useState(false);
const [loadingExtension, setLoadingExtension] = useState(true);
const [rawData, setRawData] = useState({
@@ -209,9 +215,10 @@ const ShareView = ({ quicknote = false }) => {
: // eslint-disable-next-line react-hooks/rules-of-hooks
useSafeAreaInsets();
const [searchMode, setSearchMode] = useState(null);
const [rawFiles, setRawFiles] = useState([]);
const [kh, setKh] = useState(0);
globalThis["IS_SHARE_EXTENSION"] = true;
const onKeyboardDidShow = (event) => {
let kHeight = event.endCoordinates.height;
keyboardHeight.current = kHeight;
@@ -272,6 +279,25 @@ const ShareView = ({ quicknote = false }) => {
note.content.data = makeHtmlFromPlainText(item.value);
}
noteContent.current = note.content.data;
} 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;
}
});
}
}
}
setNote({ ...note });
@@ -294,7 +320,7 @@ const ShareView = ({ quicknote = false }) => {
useEffect(() => {
(async () => {
//await loadDatabase();
await initDatabase();
setLoadingExtension(false);
loadData();
useShareStore.getState().restore();
@@ -313,8 +339,10 @@ const ShareView = ({ quicknote = false }) => {
const onPress = async () => {
setLoading(true);
await initDatabase();
if (!noteContent.current) return;
if (!noteContent.current && rawFiles.length === 0) {
setLoading(false);
return;
}
if (appendNote && !db.notes.note(appendNote.id)) {
useShareStore.getState().setAppendNote(null);
Alert.alert("The note you are trying to append to has been deleted.");
@@ -340,30 +368,22 @@ const ShareView = ({ quicknote = false }) => {
_note.sessionId = Date.now();
}
let id = await db.notes.add(_note);
if (!appendNote) {
for (const item of useShareStore.getState().selectedNotebooks) {
if (item.type === "notebook") {
db.relations.add(item, { id, type: "note" });
} else {
db.notes.addToNotebook(
{
id: item.notebookId,
topic: item.id
},
id
);
}
}
}
const status = await NetInfo.fetch();
if (status.isInternetReachable) {
try {
await db.sync(false, false);
} catch (e) {
console.log(e, e.stack);
}
const noteBundle = {
files: rawFiles,
note: _note,
notebooks: useShareStore.getState().selectedNotebooks
};
const bundles = MMKV.getArray("shared:noteBundles") || [];
bundles.push(noteBundle);
MMKV.setArray("shared:noteBundles", bundles);
await NoteBundle.createNotes();
try {
await db.sync(false, false);
} catch (e) {
console.log(e, e.stack);
}
await Storage.write("notesAddedFromIntent", "added");
close();
setLoading(false);
@@ -409,6 +429,18 @@ const ShareView = ({ quicknote = false }) => {
loadData();
}, [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 : (
@@ -580,10 +612,96 @@ const ShareView = ({ quicknote = false }) => {
}}
/>
</View>
{rawFiles?.length > 0 ? (
<View
style={{
paddingHorizontal: 12,
paddingVertical: 12,
backgroundColor: colors.nav
}}
>
<Text style={{ color: colors.pri, marginBottom: 6 }}>
Attaching {rawFiles.length} file(s):
</Text>
<ScrollView horizontal>
{rawFiles.map((item) =>
isImage(item.type) ? (
<TouchableOpacity
onPress={() => onRemoveFile(item)}
key={item.name}
activeOpacity={0.9}
>
<Image
source={{
uri:
Platform.OS === "android"
? `file://${item.value}`
: item.value
}}
style={{
width: 100,
height: 100,
borderRadius: 5,
backgroundColor: "black",
marginRight: 6
}}
resizeMode="cover"
/>
</TouchableOpacity>
) : (
<TouchableOpacity
activeOpacity={0.9}
key={item.name}
source={{
uri: `file://${item.value}`
}}
onPress={() => onRemoveFile(item)}
style={{
borderRadius: 5,
backgroundColor: colors.nav,
flexDirection: "row",
borderWidth: 1,
borderColor: colors.border,
alignItems: "center",
paddingVertical: 5,
paddingHorizontal: 8,
marginRight: 6
}}
resizeMode="cover"
>
<Icon color={colors.pri} size={15} name="file" />
<Text
style={{
marginLeft: 4,
color: colors.pri,
paddingRight: 8,
fontSize: 12
}}
>
{item.name} ({formatBytes(item.size)})
</Text>
</TouchableOpacity>
)
)}
</ScrollView>
<Text
style={{
color: colors.icon,
marginTop: 6,
fontSize: 11
}}
>
Tap to remove an attachment.
</Text>
</View>
) : null}
<View
style={{
width: "100%",
height: 200,
height: rawFiles.length > 0 ? 100 : 200,
paddingBottom: 15,
marginBottom: 10,
borderBottomColor: colors.nav,