Compare commits

...

126 Commits

Author SHA1 Message Date
ammarahm-ed
93fd6fce94 mobile: hide note headline in share ext 2023-06-06 18:17:28 +05:00
ammarahm-ed
2be801ba3b mobile: fix oboarding ui on iphone 2023-06-06 12:35:49 +05:00
Abdullah Atta
f37cd3fe63 core: add new date formats 2023-06-06 11:58:59 +05:00
ammarahm-ed
1224e48bc8 core: fix comma added in default title 2023-06-06 10:42:26 +05:00
Abdullah Atta
780bebe141 web: fix crash on webkit (safari) 2023-06-05 20:55:41 +05:00
ammarahm-ed
f707b43efb mobile: release v2.5.1 2023-06-05 20:40:34 +05:00
Abdullah Atta
92e9cd740f web: fix crash on opening notebook context menu 2023-06-05 19:06:53 +05:00
Abdullah Atta
20bef5e70c web: fix crash when opening notebook context menu 2023-06-05 19:05:39 +05:00
ammarahm-ed
c9b45875a0 mobile: remove patch 2023-06-05 18:54:36 +05:00
Abdullah Atta
f180b79a3e web: bump version to 2.5.1 2023-06-05 18:40:56 +05:00
ammarahm-ed
438a3c2686 mobile: update deps 2023-06-05 18:33:52 +05:00
ammarahm-ed
64d23d3de0 mobile: show loading when updating marketing consent 2023-06-05 18:27:51 +05:00
ammarahm-ed
e251c6f71c mobile: add marketing consent settings 2023-06-05 18:21:07 +05:00
Abdullah Atta
40cb6ae8bf web: allow opting out of marketing emails 2023-06-05 18:21:07 +05:00
Abdullah Atta
06aaa7e2c2 web: clarify marketing consent on sign up screen 2023-06-05 18:21:07 +05:00
Abdullah Atta
ab29a01649 core: allow changing marketing emails consent settings 2023-06-05 18:21:07 +05:00
Abdullah Atta
49e7bdf202 editor: allow starting lists with arbitrary number 2023-06-05 18:04:13 +05:00
ammarahm-ed
2e0f9d8524 mobile: do not move login dialog on keyboard change 2023-06-05 16:40:33 +05:00
Ammar Ahmed
092b26be57 mobile: add support for image & file sharing (#2652)
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2023-06-05 16:26:39 +05:00
Abdullah Atta
3525feffcf web: fix color not setting on icons 2023-06-05 16:21:40 +05:00
Abdullah Atta
6cb5a5e473 web: fix incorrect loading state of icons 2023-06-05 16:20:40 +05:00
Abdullah Atta
0992d4eeab editor: make image selectable in readonly mode 2023-06-05 16:02:50 +05:00
Abdullah Atta
828cd6be35 editor: disable selection persist in readonly mode 2023-06-05 16:02:50 +05:00
Abdullah Atta
1b1a962ec2 editor: minor refactor 2023-06-05 16:02:50 +05:00
Abdullah Atta
0a88502e59 editor: hide replace button in readonly mode 2023-06-05 16:02:50 +05:00
Muhammad Ali
50ff39845a editor: changed absolute to relative 2023-06-05 16:02:50 +05:00
Muhammad Ali
1cb34d0783 editor: added image-tools for readonly-mode 2023-06-05 16:02:50 +05:00
Abdullah Atta
bbb43bc8b4 web: replace date-time in test snapshot with xxx 2023-06-05 15:29:10 +05:00
ammarahm-ed
1521a7d120 mobile: formatted dates in editor 2023-06-05 15:29:10 +05:00
Abdullah Atta
3b6a2cb9de web: use editor date/time format from settings 2023-06-05 15:29:10 +05:00
Abdullah Atta
0deaed4ccd editor: make date-time format configurable 2023-06-05 15:29:10 +05:00
Abdullah Atta
8ebb58b70c core: fix reminder date time formatting 2023-06-05 15:29:10 +05:00
ammarahm-ed
f691d113d2 mobile: update wording 2023-06-05 15:29:10 +05:00
ammarahm-ed
a57eaf9be6 mobile: sync with core changes 2023-06-05 15:29:10 +05:00
ammarahm-ed
c5a264270c core: use only first 10 words of headline in title 2023-06-05 15:29:10 +05:00
Abdullah Atta
e75c62e6e1 core: use 2 digits for 12 hour time format 2023-06-05 15:29:10 +05:00
Abdullah Atta
6c3583c9e5 web: add support for setting default notebook/topic 2023-06-05 15:29:10 +05:00
Abdullah Atta
58ba453f57 web: use default note title from settings 2023-06-05 15:29:10 +05:00
Abdullah Atta
7f5d5fcb82 core: simplify default notebook object 2023-06-05 15:29:10 +05:00
Abdullah Atta
e218194402 web: add settings for default date, time & title formats 2023-06-05 15:29:10 +05:00
Abdullah Atta
c36f532bbb web: use date & time formats from settings 2023-06-05 15:29:10 +05:00
ammarahm-ed
b3b7bd44ef core: fix note count incorrect 2023-06-05 15:29:10 +05:00
ammarahm-ed
c85242a610 core: format time correctly 2023-06-05 15:29:10 +05:00
ammarahm-ed
58f7a206d9 mobile: add settings 2023-06-05 15:29:10 +05:00
Ammar Ahmed
25bf4fee82 mobile: redesign onboarding (#2633) 2023-06-05 15:13:19 +05:00
Abdullah Atta
c2ab5ded47 desktop: fix start as minimized on windows 2023-06-05 12:14:33 +05:00
Ammar Ahmed
ad6a0ebfab mobie: enable search in readonly mode (#2659) 2023-06-05 11:16:18 +05:00
ammarahm-ed
cb9e6578c1 mobile: downgrade react-native-bootsplash version 2023-06-01 21:16:17 +05:00
Abdullah Atta
17a1475ded editor: do not create 2 toolbar groups for static tools on mobile 2023-06-01 21:15:17 +05:00
Abdullah Atta
2cee5a4d89 editor: set preview attachment icon to eye 2023-06-01 21:15:17 +05:00
Abdullah Atta
337f88c373 editor: fix link popup font family 2023-06-01 21:15:17 +05:00
Abdullah Atta
53d5073690 editor: double click to preview image 2023-06-01 21:15:17 +05:00
Ramiro Pruis
9cc9660b48 editor: allow download & preview of attachments in readonly mode (#2621)
Signed-off-by: Ramiro Pruis<ramapruis@gmail.com>
2023-06-01 20:39:10 +05:00
ammarahm-ed
e6162b2580 mobile: fix crash on download all attachement 2023-05-31 11:31:51 +05:00
ammarahm-ed
abc14d2d1f mobile: set fdroid build to false 2023-05-30 00:17:39 +05:00
ammarahm-ed
fa11b5fabb mobile: update packages 2023-05-29 23:52:43 +05:00
ammarahm-ed
d904916863 mobile: release v2.5.0 2023-05-29 23:01:53 +05:00
Abdullah Atta
0cfc2e1609 web: bump version to 2.5.0 2023-05-29 19:14:53 +05:00
Abdullah Atta
3742e923c9 editor: fix crash due to attachment.mime being undefined 2023-05-29 13:12:28 +05:00
Abdullah Atta
792a254513 web: fix split pane sash styling 2023-05-29 12:09:50 +05:00
Abdullah Atta
17928ba182 web: disable loading announcements during tests 2023-05-27 16:50:24 +05:00
Abdullah Atta
2da0f164c6 web: fix list crash on shift+click when there's no selection 2023-05-27 16:47:54 +05:00
Abdullah Atta
4eabe79e7e web: fix attachment caching issues 2023-05-27 16:14:12 +05:00
Abdullah Atta
89dd8f5b6c web: minor ui fixes for pdf attachment preview 2023-05-27 16:14:12 +05:00
Abdullah Atta
b4d3662897 web: never delete image & pdf attachments 2023-05-27 16:14:12 +05:00
ammarahm-ed
675e1e2c6f mobile: do not cleanup documents from cache after download 2023-05-27 16:14:12 +05:00
ammarahm-ed
0f7f3bae7e mobile: save pdf snapshot 2023-05-27 16:14:12 +05:00
Abdullah Atta
c917d95a7b web: remove unnecessary css import 2023-05-27 16:14:12 +05:00
Abdullah Atta
3ae9001504 mobile: remove rn-fetch-blob patch 2023-05-27 16:14:12 +05:00
Abdullah Atta
cc97b8beed global: update package-lock.json 2023-05-27 16:14:12 +05:00
Abdullah Atta
954bdebcef web: remove @react-pdf-viewer/default-layout 2023-05-27 16:14:12 +05:00
Abdullah Atta
04b62f4e53 misc: update package-lock.json 2023-05-27 16:14:12 +05:00
Abdullah Atta
a2dcb67b36 web: add support for previewing pdf attachments 2023-05-27 16:14:12 +05:00
Abdullah Atta
cc9141c981 editor: enable preview attachment button on desktop 2023-05-27 16:14:12 +05:00
Abdullah Atta
85484b1762 fs: add support for getting actual file size 2023-05-27 16:14:12 +05:00
Abdullah Atta
1a0c94896a config: add new "fs" scope for commits 2023-05-27 16:14:12 +05:00
ammarahm-ed
068a99463d mobile: fix loading 2023-05-27 16:14:12 +05:00
ammarahm-ed
b413315777 mobile: add pdf attachment previews 2023-05-27 16:14:12 +05:00
ammarahm-ed
7227513783 editor: enable attachment previews for files 2023-05-27 16:14:12 +05:00
Abdullah Atta
083e4e1150 web: add basic support for attachment previews 2023-05-27 16:14:12 +05:00
Abdullah Atta
09098410a5 editor: enable previewing attachments on desktop/web 2023-05-27 16:14:12 +05:00
Abdullah Atta
2c9f5df530 web: fix font family in unlock note view 2023-05-25 16:15:01 +05:00
Abdullah Atta
3ad346362b web: fix notebooks in move note dialog not getting checked sometimes 2023-05-25 16:15:01 +05:00
ammarahm-ed
e176b1bf76 mobile: reset premium status 2023-05-25 15:55:14 +05:00
ammarahm-ed
b368232598 mobile: fix app hangs on adding >3 notebooks on iOS 2023-05-25 15:54:54 +05:00
Abdullah Atta
bcd27e10c1 web: add support for search inside notebook 2023-05-25 15:52:58 +05:00
Abdullah Atta
af9b9b7083 web: fix notebook item date font family 2023-05-25 15:44:09 +05:00
ammarahm-ed
ea1565048a mobile: tag multiple notes from manage tags sheet 2023-05-25 15:41:43 +05:00
Muhammad Ali
59b081704b web: add support for setting tags in bulk (#2328)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-05-25 14:10:32 +05:00
Muhammad Ali
2761222b36 web: add support for printing notes (#2551)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-05-25 12:19:21 +05:00
Muhammad Ali
571f15b556 web: close deleted note on sync (#2580)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-05-25 12:14:49 +05:00
Muhammad Ali
283efa6e41 editor: prevent table column size reset in readonly mode (#2586) 2023-05-25 12:11:57 +05:00
Abdullah Atta
b3b1b7470e web: do not show save file dialog if download is canceled 2023-05-25 12:02:50 +05:00
Abdullah Atta
a9e59b17dc web: use fixed size columns in attachment manager 2023-05-25 12:02:50 +05:00
Abdullah Atta
22d946b677 web: fix app lag when downloading attachment 2023-05-25 12:02:50 +05:00
ammarahm-ed
6a748c3f98 mobile: fix download all attachments progress 2023-05-25 10:18:21 +05:00
Abdullah Atta
520296106a editor: bump version to 1.5.0 2023-05-25 09:48:22 +05:00
Abdullah Atta
3b727e6f64 editor: update tiptap packages to v2.0.3 2023-05-25 09:47:20 +05:00
ammarahm-ed
b77711a53d mobile: use isImage check from core 2023-05-23 12:52:50 +05:00
ammarahm-ed
aebb2d414b mobile: allow attaching images from file manager 2023-05-23 12:52:50 +05:00
ammarahm-ed
e0e505371c mobile: update download attachments 2023-05-23 12:52:50 +05:00
ammarahm-ed
b9040a9358 mobile: show error on file check failed 2023-05-23 12:52:50 +05:00
ammarahm-ed
68f10ce308 core: add isAudio function 2023-05-23 12:52:50 +05:00
Abdullah Atta
8a475f654f web: improve attachments manager ui/ux 2023-05-23 12:52:50 +05:00
ammarahm-ed
1043b7df55 mobile: improved attachments manager 2023-05-23 12:52:50 +05:00
ammarahm-ed
8383e09243 mobile: show currently downloading file 2023-05-23 12:52:50 +05:00
ammarahm-ed
df58a6cfc4 mobile: fix recheck attachments 2023-05-23 12:52:50 +05:00
ammarahm-ed
a9df577478 mobile: update deps 2023-05-23 12:52:50 +05:00
ammarahm-ed
e021d93893 mobile: download mutliple attachments 2023-05-23 12:52:50 +05:00
Abdullah Atta
24e13f488e web: fully move to using web streams everywhere 2023-05-23 12:52:50 +05:00
Muhammad Ali
6f88b70937 web: add support for downloading all attachments as zip 2023-05-23 12:52:50 +05:00
ammarahm-ed
a90f957aa4 mobile: always check editor is ready on enter foreground 2023-05-23 08:08:10 +05:00
ammarahm-ed
748953b3eb mobile: don't lock app on showing app review 2023-05-23 08:07:27 +05:00
Muhammad Ali
874c892986 editor: make cell properties work with header cells (#2506) 2023-05-23 07:17:29 +05:00
Muhammad Ali
ab678fa3a9 core: fix duplication of notes that are directly inside a notebook (#2552)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-05-23 06:40:07 +05:00
Muhammad Ali
c2351ab2d3 editor: add support for link markdown syntax (#2576) 2023-05-23 06:36:34 +05:00
Muhammad Ali
3fd3e02256 web: fix wrong date font in notebook item (#2560)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-05-23 06:20:12 +05:00
Muhammad Ali
d2fd26abd5 editor: fix position of outline list bullet/arrow (#2555) 2023-05-20 08:03:39 +05:00
ammarahm-ed
dd84b9ca2f mobile: add fdroid changelog 2023-05-18 20:06:37 +05:00
Abdullah Atta
269f083dce clipper: bump version to 0.2.1 2023-05-18 15:14:25 +05:00
Abdullah Atta
e18867d3f1 clipper: request scripting permission 2023-05-18 15:13:44 +05:00
Abdullah Atta
e221882f8e clipper: change version to 0.2.0 2023-05-17 11:35:52 +05:00
Abdullah Atta
153ee4bce3 clipper: load content scripts on demand to reduce host permissions 2023-05-17 11:35:52 +05:00
ammarahm-ed
a2ecdab61c mobile: release 2.4.17 2023-05-17 11:24:29 +05:00
Ammar Ahmed
eb089e6211 Merge pull request #2543 from streetwriters/feat/in-app-review
mobile: add in-app-review flow
2023-05-17 11:18:22 +05:00
ammarahm-ed
1981c3a2f8 mobile: add in-app-review flow 2023-05-17 11:14:49 +05:00
247 changed files with 20643 additions and 88223 deletions

View File

@@ -20,6 +20,7 @@ const SCOPES = [
"logger",
"theme",
"core",
"fs",
"clipper",
"config",
"ci",

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

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Platform } from "react-native";
import RNFetchBlob from "rn-fetch-blob";
import RNFetchBlob from "react-native-blob-util";
import {
decrypt,
deriveCryptoKey,

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Dimensions, Platform } from "react-native";
import ImageResizer from "@bam.tech/react-native-image-resizer";
import RNFetchBlob from "rn-fetch-blob";
import RNFetchBlob from "react-native-blob-util";
/**
* Scale down & compress images to screen width
* for loading in editor.

View File

@@ -17,25 +17,169 @@ 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 {
getFileNameWithExtension,
isImage,
isDocument
} from "@notesnook/core/utils/filename";
import React from "react";
import { Platform } from "react-native";
import * as ScopedStorage from "react-native-scoped-storage";
import Sodium from "@ammarahmed/react-native-sodium";
import RNFetchBlob from "rn-fetch-blob";
import { subscribe, zip } from "react-native-zip-archive";
import RNFetchBlob from "react-native-blob-util";
import { ShareComponent } from "../../components/sheets/export-notes/share";
import { presentSheet, ToastEvent } from "../../services/event-manager";
import { ToastEvent, presentSheet } from "../../services/event-manager";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { db } from "../database";
import Storage from "../database/storage";
import { cacheDir } from "./utils";
import { getFileNameWithExtension } from "@notesnook/core/utils/filename";
import { cacheDir, copyFileAsync, releasePermissions } from "./utils";
export const FileDownloadStatus = {
Success: 1,
Fail: 0
};
/**
* Download all user's attachments
* @returns
*/
export function downloadAllAttachments() {
const attachments = db.attachments.all;
return downloadAttachments(attachments);
}
/**
* Downloads provided attachments to a .zip file
* on user's device.
* @param attachments
* @param onProgress
* @returns
*/
export async function downloadAttachments(
attachments,
onProgress,
canceled,
groupId
) {
if (!attachments || !attachments.length) return;
const result = new Map();
let outputFolder;
if (Platform.OS === "android") {
// Ask the user to select a directory to store the file
let file = await ScopedStorage.openDocumentTree(true);
outputFolder = file.uri;
if (!outputFolder) return;
} else {
outputFolder = await Storage.checkAndCreateDir("/downloads/");
}
// Create the folder to zip;
const zipSourceFolder = `${cacheDir}/notesnook-attachments`;
const zipOutputFile =
Platform.OS === "ios"
? `${outputFolder}/notesnook-attachments-${Date.now()}.zip`
: `${cacheDir}/notesnook-attachments.zip`;
if (await RNFetchBlob.fs.exists(zipSourceFolder))
await RNFetchBlob.fs.unlink(zipSourceFolder);
await RNFetchBlob.fs.mkdir(zipSourceFolder);
for (let i = 0; i < attachments.length; i++) {
let attachment = attachments[i];
const hash = attachment.metadata.hash;
try {
if (canceled.current) {
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
return;
}
onProgress?.(
i + 1 / attachments.length,
`Downloading attachments (${i + 1}/${
attachments.length
})... Please wait`
);
// Download to cache
let uri = await downloadAttachment(hash, false, {
silent: true,
cache: true,
groupId: groupId
});
if (canceled.current) {
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
return;
}
if (!uri) throw new Error("Failed to download file");
// Move file to the source folder we will zip eventually and rename the file to it's actual name.
const filePath = `${zipSourceFolder}/${attachment.metadata.filename}`;
await RNFetchBlob.fs.mv(`${cacheDir}/${uri}`, filePath);
result.set(hash, {
filename: attachment.metadata.filename,
status: FileDownloadStatus.Success
});
} catch (e) {
result.set(hash, {
filename: attachment.metadata.filename,
status: FileDownloadStatus.Fail,
reason: e
});
console.log("Error downloading attachment", e);
}
}
if (canceled.current) {
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
return;
}
if (result?.size) {
let sub;
try {
onProgress?.(0, `Zipping... Please wait`);
// If all goes well, zip the notesnook-attachments folder in cache.
sub = subscribe(({ progress }) => {
onProgress(
progress,
`Saving zip file (${(progress * 100).toFixed(1)}%)... Please wait`
);
});
await zip(zipSourceFolder, zipOutputFile);
sub?.remove();
onProgress(1, `Saving zip file... Please wait`);
if (Platform.OS === "android") {
// Move the zip to user selected directory.
const file = await ScopedStorage.createFile(
outputFolder,
`notesnook-attachments-${Date.now()}.zip`,
"application/zip"
);
await copyFileAsync(`file://${zipOutputFile}`, file.uri);
}
onProgress?.(1, `Done`);
releasePermissions(outputFolder);
} catch (e) {
releasePermissions(outputFolder);
sub?.remove();
console.log("Error zipping attachments", e);
}
// Remove source & zip file from cache.
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
if (Platform.OS === "android") {
RNFetchBlob.fs.unlink(zipOutputFile).catch(console.log);
}
}
return result;
}
export default async function downloadAttachment(
hash,
global = true,
options = {
silent: false,
cache: false
cache: false,
throwError: false,
groupId: undefined
}
) {
let attachment = db.attachments.attachment(hash);
@@ -55,8 +199,13 @@ export default async function downloadAttachment(
}
try {
await db.fs.downloadFile(
console.log(
"starting download attachment",
attachment.metadata.hash,
options.groupId
);
await db.fs.downloadFile(
options.groupId || attachment.metadata.hash,
attachment.metadata.hash
);
if (
@@ -99,7 +248,8 @@ export default async function downloadAttachment(
if (
attachment.dateUploaded &&
!attachment.metadata?.type?.startsWith("image")
!isImage(attachment.metadata?.type) &&
!isDocument(attachment.metadata?.type)
) {
RNFetchBlob.fs
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.metadata.hash}`)
@@ -131,5 +281,8 @@ export default async function downloadAttachment(
.catch(console.log);
}
useAttachmentStore.getState().remove(attachment.metadata.hash);
if (options.throwError) {
throw e;
}
}
}

View File

@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import hosts from "@notesnook/core/utils/constants";
import NetInfo from "@react-native-community/netinfo";
import RNFetchBlob from "rn-fetch-blob";
import RNFetchBlob from "react-native-blob-util";
import { ToastEvent } from "../../services/event-manager";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { db } from "../database";

View File

@@ -19,18 +19,29 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { Platform } from "react-native";
import Sodium from "@ammarahmed/react-native-sodium";
import RNFetchBlob from "rn-fetch-blob";
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

@@ -17,10 +17,13 @@ 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 RNFetchBlob from "rn-fetch-blob";
import RNFetchBlob from "react-native-blob-util";
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;
@@ -35,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",
@@ -45,7 +58,7 @@ export async function uploadFile(filename, data, cancelToken) {
{
"content-type": ""
},
RNFetchBlob.wrap(`${cacheDir}/${filename}`)
RNFetchBlob.wrap(uploadFilePath)
)
.uploadProgress((sent, total) => {
useAttachmentStore
@@ -63,7 +76,10 @@ export async function uploadFile(filename, data, cancelToken) {
if (result) {
let attachment = db.attachments.attachment(filename);
if (!attachment) return result;
if (!attachment.metadata.type.startsWith("image/")) {
if (
!isImage(attachment.metadata.type) &&
!isDocument(attachment.metadata?.type)
) {
RNFetchBlob.fs.unlink(`${cacheDir}/${filename}`).catch(console.log);
}
}

View File

@@ -17,7 +17,9 @@ 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 RNFetchBlob from "rn-fetch-blob";
import * as ScopedStorage from "react-native-scoped-storage";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
export const cacheDir = RNFetchBlob.fs.dirs.CacheDir;
@@ -62,3 +64,22 @@ export function cancelable(operation) {
};
};
}
export function copyFileAsync(source, dest) {
return new Promise((resolve) => {
ScopedStorage.copyFile(source, dest, (e, r) => {
console.log(e, r);
resolve();
});
});
}
export async function releasePermissions(path) {
if (Platform.OS === "ios") return;
const uris = await ScopedStorage.getPersistedUriPermissions();
for (let uri of uris) {
if (path.startsWith(uri)) {
await ScopedStorage.releasePersistableUriPermission(uri);
}
}
}

View File

@@ -102,15 +102,21 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
if (res.failed) {
db.attachments.markAsFailed(attachment.id, res.failed);
setFailed(res.failed);
ToastEvent.show({
heading: "File check failed with error: " + res.failed,
type: "error",
context: "local"
});
} else {
setFailed(null);
db.attachments.markAsFailed(attachment.id, null);
ToastEvent.show({
heading: "File check passed",
type: "success",
context: "local"
});
}
ToastEvent.show({
heading: "File check passed",
type: "success",
context: "local"
});
setAttachments([...db.attachments.all]);
setLoading({
name: null

View File

@@ -35,12 +35,14 @@ function getFileExtension(filename) {
var ext = /^.+\.([^.]+)$/.exec(filename);
return ext == null ? "" : ext[1];
}
/**
*
* @param {any} param0
* @returns
*/
export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
export const AttachmentItem = ({
attachment,
encryption,
setAttachments,
pressable = true,
hideWhenNotDownloading
}) => {
const colors = useThemeStore((state) => state.colors);
const [currentProgress, setCurrentProgress] = useAttachmentProgress(
attachment,
@@ -48,9 +50,12 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
);
const onPress = () => {
if (!pressable) return;
Actions.present(attachment, setAttachments, attachment.metadata.hash);
};
return (
return hideWhenNotDownloading &&
(!currentProgress || !currentProgress.value) ? null : (
<TouchableOpacity
activeOpacity={0.9}
onPress={onPress}
@@ -113,12 +118,14 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
{attachment.metadata.filename}
</Paragraph>
<Paragraph color={colors.icon} size={SIZE.xs}>
{formatBytes(attachment.length)}{" "}
{currentProgress?.type
? "(" + currentProgress.type + "ing - tap to cancel)"
: ""}
</Paragraph>
{!hideWhenNotDownloading ? (
<Paragraph color={colors.icon} size={SIZE.xs}>
{formatBytes(attachment.length)}{" "}
{currentProgress?.type
? "(" + currentProgress.type + "ing - tap to cancel)"
: ""}
</Paragraph>
) : null}
</View>
</View>
@@ -126,7 +133,7 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
<TouchableOpacity
activeOpacity={0.9}
onPress={() => {
if (encryption) return;
if (encryption || !pressable) return;
db.fs.cancel(attachment.metadata.hash);
setCurrentProgress(null);
}}

View File

@@ -0,0 +1,273 @@
/*
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 React, { useRef, useState } from "react";
import { Platform, View } from "react-native";
import { db } from "../../common/database";
import { downloadAttachments } from "../../common/filesystem/download-attachment";
import { presentSheet } from "../../services/event-manager";
import { Button } from "../ui/button";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { ProgressBarComponent } from "../ui/svg/lazy";
import { useThemeStore } from "../../stores/use-theme-store";
import { FlatList } from "react-native-actions-sheet";
import { AttachmentItem } from "./attachment-item";
const DownloadAttachments = ({ close, attachments, isNote, update }) => {
const colors = useThemeStore((state) => state.colors);
const [downloading, setDownloading] = useState(false);
const [progress, setProgress] = useState({
value: 0,
statusText: "Download started... Please wait"
});
const [result, setResult] = useState(new Map());
const canceled = useRef(false);
const groupId = useRef();
const onDownload = async () => {
update({
disableClosing: true
});
setDownloading(true);
canceled.current = false;
groupId.current = Date.now().toString();
const result = await downloadAttachments(
attachments,
(progress, statusText) => setProgress({ value: progress, statusText }),
canceled,
groupId.current
);
if (canceled.current) return;
setResult(result || new Map());
setDownloading(false);
update({
disableClosing: false
});
};
const cancel = async () => {
update({
disableClosing: false
});
canceled.current = true;
console.log(groupId.current, "canceling groupId downloads");
await db.fs.cancel(groupId.current);
setDownloading(false);
groupId.current = null;
};
const successResults = () => {
const results = [];
for (let [key, value] of result.entries()) {
if (value.status === 1) results.push(db.attachments.attachment(key));
}
return results;
};
const failedResults = () => {
const results = [];
for (let [key, value] of result.entries()) {
if (value.status === 0) results.push(db.attachments.attachment(key));
}
return results;
};
function getResultText() {
const downloadedAttachmentsCount =
attachments.length - failedResults().length;
if (downloadedAttachmentsCount === 0)
return "Failed to download all attachments";
return `Successfully downloaded ${downloadedAttachmentsCount}/${
attachments.length
} attachments as a zip file at ${
Platform.OS === "android" ? "the selected folder" : "Notesnook/downloads"
}`;
}
return (
<View
style={{
alignItems: "center",
width: "100%",
paddingVertical: 12,
paddingHorizontal: 12
}}
>
<Heading>
{downloading
? "Downloading attachments"
: result?.size
? "Downloaded attachments"
: "Download attachments"}
</Heading>
{downloading ? (
<Paragraph
style={{
textAlign: "center"
}}
>
{progress.statusText}
</Paragraph>
) : result?.size ? (
<Paragraph
style={{
textAlign: "center"
}}
>
{getResultText()}
</Paragraph>
) : (
<Paragraph
style={{
textAlign: "center"
}}
>
Are you sure you want to download all attachments
{isNote ? " of this note?" : "?"}
</Paragraph>
)}
{downloading ? (
<View
style={{
width: 200,
marginTop: 10
}}
>
<ProgressBarComponent
height={5}
width={null}
animated={true}
useNativeDriver
progress={progress.value ? progress.value / attachments.length : 0}
unfilledColor={colors.nav}
color={colors.accent}
borderWidth={0}
/>
</View>
) : null}
<FlatList
style={{
maxHeight: 300,
width: "100%",
minHeight: 60,
backgroundColor: colors.nav,
borderRadius: 5,
marginVertical: 12
}}
data={downloading ? attachments : []}
ListEmptyComponent={
<View
style={{
width: "100%",
justifyContent: "center",
alignItems: "center",
height: 60
}}
>
<Paragraph color={colors.icon}>No downloads in progress.</Paragraph>
</View>
}
keyExtractor={(item) => item.id}
renderItem={({ item }) => {
return (
<AttachmentItem
attachment={item}
setAttachments={() => {}}
pressable={false}
hideWhenNotDownloading={true}
/>
);
}}
/>
{result?.size ? (
<Button
style={{
width: 250,
borderRadius: 100,
marginTop: 20
}}
onPress={close}
type="accent"
title="Done"
/>
) : !downloading ? (
<View
style={{
flexDirection: "row",
width: "100%",
marginTop: 20
}}
>
<Button
style={{
flex: 1,
borderRadius: 100,
marginRight: 5
}}
onPress={close}
type="grayBg"
title="No"
/>
<Button
style={{
flex: 1,
borderRadius: 100,
marginLeft: 5
}}
onPress={onDownload}
type="accent"
title="Yes"
/>
</View>
) : (
<Button
style={{
width: 250,
borderRadius: 100,
marginTop: 20
}}
onPress={cancel}
type="error"
title="Cancel"
/>
)}
</View>
);
};
DownloadAttachments.present = (context, attachments, isNote) => {
presentSheet({
context: context,
component: (ref, close, update) => (
<DownloadAttachments
close={close}
attachments={attachments}
isNote={isNote}
update={update}
/>
)
});
};
export default DownloadAttachments;

View File

@@ -18,44 +18,60 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useRef, useState } from "react";
import { View } from "react-native";
import { ActivityIndicator, ScrollView, View } from "react-native";
import { FlatList } from "react-native-actions-sheet";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import filesystem from "../../common/filesystem";
import { presentSheet } from "../../services/event-manager";
import { useThemeStore } from "../../stores/use-theme-store";
import { SIZE } from "../../utils/size";
import DialogHeader from "../dialog/dialog-header";
import SheetProvider from "../sheet-provider";
import { IconButton } from "../ui/icon-button";
import Input from "../ui/input";
import Seperator from "../ui/seperator";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { AttachmentItem } from "./attachment-item";
import { FlatList } from "react-native-actions-sheet";
import DownloadAttachments from "./download-attachments";
import { Button } from "../ui/button";
import {
isAudio,
isDocument,
isImage,
isVideo
} from "@notesnook/core/utils/filename";
export const AttachmentDialog = ({ data }) => {
export const AttachmentDialog = ({ note }) => {
const colors = useThemeStore((state) => state.colors);
const [note, setNote] = useState(data);
const [attachments, setAttachments] = useState(
data
? db.attachments.ofNote(data.id, "all")
note
? db.attachments.ofNote(note.id, "all")
: [...(db.attachments.all || [])]
);
const attachmentSearchValue = useRef();
const searchTimer = useRef();
const [loading, setLoading] = useState(false);
const [currentFilter, setCurrentFilter] = useState("all");
const onChangeText = (text) => {
const attachments = note
? db.attachments.ofNote(note.id, "all")
: [...(db.attachments.all || [])];
attachmentSearchValue.current = text;
if (
!attachmentSearchValue.current ||
attachmentSearchValue.current === ""
) {
setAttachments([...db.attachments.all]);
setAttachments([...attachments]);
}
clearTimeout(searchTimer.current);
searchTimer.current = setTimeout(() => {
let results = db.lookup.attachments(
db.attachments.all,
attachments,
attachmentSearchValue.current
);
if (results.length === 0) return;
@@ -67,6 +83,77 @@ export const AttachmentDialog = ({ data }) => {
<AttachmentItem setAttachments={setAttachments} attachment={item} />
);
const onCheck = async () => {
setLoading(true);
const checkedAttachments = [];
for (let attachment of attachments) {
let result = await filesystem.checkAttachment(attachment.metadata.hash);
if (result.failed) {
await db.attachments.markAsFailed(
attachment.metadata.hash,
result.failed
);
} else {
await db.attachments.markAsFailed(attachment.id, null);
}
checkedAttachments.push(
db.attachments.attachment(attachment.metadata.hash)
);
setAttachments([...checkedAttachments]);
}
setLoading(false);
};
const attachmentTypes = [
{
title: "All",
filterBy: "all"
},
{
title: "Images",
filterBy: "images"
},
{
title: "Documents",
filterBy: "documents"
},
{
title: "Video",
filterBy: "video"
},
{
title: "Audio",
filterBy: "audio"
}
];
const filterAttachments = (type) => {
const attachments = note
? db.attachments.ofNote(note.id, "all")
: [...(db.attachments.all || [])];
isDocument;
switch (type) {
case "all":
return attachments;
case "images":
return attachments.filter((attachment) =>
isImage(attachment.metadata.type)
);
case "video":
return attachments.filter((attachment) =>
isVideo(attachment.metadata.type)
);
case "audio":
return attachments.filter((attachment) =>
isAudio(attachment.metadata.type)
);
case "documents":
return attachments.filter((attachment) =>
isDocument(attachment.metadata.type)
);
}
};
return (
<View
style={{
@@ -75,43 +162,71 @@ export const AttachmentDialog = ({ data }) => {
paddingHorizontal: 12
}}
>
<DialogHeader
title={note ? "Attachments" : "Manage attachments"}
paragraph="Tap on an attachment to view properties"
button={{
title: "Check all",
type: "grayAccent",
loading: loading,
onPress: async () => {
setLoading(true);
for (let attachment of attachments) {
let result = await filesystem.checkAttachment(
attachment.metadata.hash
<SheetProvider context="attachments-list" />
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center"
}}
>
<Heading>Attachments</Heading>
<View
style={{
flexDirection: "row"
}}
>
{loading ? (
<ActivityIndicator
style={{
height: 40,
width: 40,
marginRight: 10
}}
size={SIZE.lg}
/>
) : (
<IconButton
name="check-all"
customStyle={{
height: 40,
width: 40,
marginRight: 10
}}
color={colors.pri}
size={SIZE.lg}
onPress={onCheck}
/>
)}
<IconButton
name="download"
customStyle={{
height: 40,
width: 40
}}
color={colors.pri}
onPress={() => {
DownloadAttachments.present(
"attachments-list",
attachments,
!!note
);
if (result.failed) {
db.attachments.markAsFailed(
attachment.metadata.hash,
result.failed
);
} else {
db.attachments.markAsFailed(attachment.id, null);
}
setAttachments([...db.attachments.all]);
}
setLoading(false);
}
}}
size={SIZE.lg}
/>
</View>
</View>
<Seperator />
<Input
placeholder="Filter attachments by filename, type or hash"
onChangeText={onChangeText}
onSubmit={() => {
onChangeText(attachmentSearchValue.current);
}}
/>
<Seperator />
{!note ? (
<Input
placeholder="Filter attachments by filename, type or hash"
onChangeText={onChangeText}
onSubmit={() => {
onChangeText(attachmentSearchValue.current);
}}
/>
) : null}
<FlatList
keyboardDismissMode="none"
@@ -119,6 +234,45 @@ export const AttachmentDialog = ({ data }) => {
maxToRenderPerBatch={10}
initialNumToRender={10}
windowSize={5}
stickyHeaderIndices={[0]}
ListHeaderComponent={
<ScrollView
style={{
width: "100%",
height: 50,
flexDirection: "row",
backgroundColor: colors.bg
}}
contentContainerStyle={{
minWidth: "100%"
}}
horizontal
>
{attachmentTypes.map((item) => (
<Button
type={currentFilter === item.filterBy ? "grayAccent" : "gray"}
key={item.title}
title={
item.title +
` (${filterAttachments(item.filterBy)?.length || 0})`
}
style={{
borderRadius: 0,
borderBottomWidth: 1,
flexGrow: 1,
borderBottomColor:
currentFilter !== item.filterBy
? "transparent"
: colors.accent
}}
onPress={() => {
setCurrentFilter(item.filterBy);
setAttachments(filterAttachments(item.filterBy));
}}
/>
))}
</ScrollView>
}
ListEmptyComponent={
<View
style={{
@@ -162,6 +316,6 @@ export const AttachmentDialog = ({ data }) => {
AttachmentDialog.present = (note) => {
presentSheet({
component: () => <AttachmentDialog data={note} />
component: () => <AttachmentDialog note={note} />
});
};

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useEffect, useRef, useState } from "react";
import { Platform, View } from "react-native";
import { View } from "react-native";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import {
eSubscribeEvent,
@@ -75,7 +75,7 @@ const AuthModal = () => {
return !visible ? null : (
<BaseDialog
overlayOpacity={0}
statusBarTranslucent={false}
statusBarTranslucent={true}
onRequestClose={currentAuthMode !== AuthMode.welcomeSignup && close}
visible={true}
onClose={close}
@@ -101,7 +101,7 @@ const AuthModal = () => {
<View
style={{
position: "absolute",
top: Platform.OS === "ios" ? insets.top : 0,
top: insets.top,
zIndex: 999,
flexDirection: "row",
alignItems: "center",
@@ -126,11 +126,11 @@ const AuthModal = () => {
{initialAuthMode.current !== AuthMode.welcomeSignup ? null : (
<Button
title="Skip for now"
title="Skip"
onPress={() => {
hideAuth();
}}
iconSize={20}
iconSize={16}
type="gray"
iconPosition="right"
icon="chevron-right"

View File

@@ -18,9 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useEffect, useState } from "react";
import { View } from "react-native";
import { TouchableOpacity, View } from "react-native";
import { SheetManager } from "react-native-actions-sheet";
import Animated, { FadeInDown, FadeOutUp } from "react-native-reanimated";
import { DDS } from "../../services/device-detection";
import { eSendEvent } from "../../services/event-manager";
import { useThemeStore } from "../../stores/use-theme-store";
@@ -30,13 +29,12 @@ import SheetProvider from "../sheet-provider";
import { Progress } from "../sheets/progress";
import { Button } from "../ui/button";
import Input from "../ui/input";
import { SvgView } from "../ui/svg";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { SVG } from "./background";
import { hideAuth } from "./common";
import { ForgotPassword } from "./forgot-password";
import { useLogin } from "./use-login";
import { useSettingStore } from "../../stores/use-setting-store";
const LoginSteps = {
emailAuth: 1,
@@ -64,6 +62,7 @@ export const Login = ({ changeMode }) => {
await sleep(500);
Progress.present();
});
const deviceMode = useSettingStore((state) => state.deviceMode);
useEffect(() => {
async () => {
@@ -82,62 +81,70 @@ export const Login = ({ changeMode }) => {
<>
<ForgotPassword />
<SheetProvider context="two_factor_verify" />
<Animated.View
entering={FadeInDown}
exiting={FadeOutUp}
<View
style={{
borderRadius: DDS.isTab ? 5 : 0,
backgroundColor: colors.bg,
zIndex: 10,
width: "100%",
alignSelf: "center",
height: "100%",
minHeight: "100%"
}}
>
<View
style={{
height: 250,
overflow: "hidden"
}}
>
<SvgView
src={SVG(colors.night ? colors.icon : "black")}
height={700}
/>
</View>
<View
style={{
width: "100%",
justifyContent: "center",
alignSelf: "center",
paddingHorizontal: 12,
marginBottom: 30,
marginTop: 15
flexGrow: 0.8,
justifyContent: "flex-end",
paddingHorizontal: 20,
backgroundColor: colors.nav,
borderBottomWidth: 1,
marginBottom: 12,
borderBottomColor: colors.border,
alignSelf: deviceMode !== "mobile" ? "center" : undefined,
borderWidth: deviceMode !== "mobile" ? 1 : null,
borderColor: deviceMode !== "mobile" ? colors.border : null,
borderRadius: deviceMode !== "mobile" ? 20 : null,
marginTop: deviceMode !== "mobile" ? 50 : null,
width: deviceMode === "mobile" ? null : "50%"
}}
>
<View
style={{
flexDirection: "row"
}}
>
<View
style={{
width: 100,
height: 5,
backgroundColor: colors.accent,
borderRadius: 2,
marginRight: 7
}}
/>
<View
style={{
width: 20,
height: 5,
backgroundColor: colors.nav,
borderRadius: 2
}}
/>
</View>
<Heading
style={{
textAlign: "center"
marginBottom: 25,
marginTop: 10
}}
size={30}
color={colors.heading}
extraBold
size={SIZE.xxl}
>
Welcome back!
Login to your {"\n"}account
</Heading>
<Paragraph
style={{
textDecorationLine: "underline",
textAlign: "center",
marginTop: 5
}}
onPress={() => {
if (loading) return;
changeMode(1);
}}
size={SIZE.md}
>
{"Don't have an account? Sign up"}
</Paragraph>
</View>
<View
style={{
width: DDS.isTab
@@ -147,10 +154,10 @@ export const Login = ({ changeMode }) => {
: focused
? "100%"
: "99.9%",
padding: 12,
backgroundColor: colors.bg,
flexGrow: 1,
alignSelf: "center"
alignSelf: "center",
paddingHorizontal: 20,
flexGrow: 1
}}
>
<Input
@@ -217,28 +224,22 @@ export const Login = ({ changeMode }) => {
<View
style={{
marginTop: 25,
alignSelf: "center"
marginTop: 25
}}
>
<Button
style={{
width: 250,
borderRadius: 100
}}
loading={loading}
onPress={() => {
if (loading) return;
login();
}}
style={{
width: 250,
borderRadius: 100
}}
fontSize={SIZE.md}
type="accent"
title={
loading
? null
: step === LoginSteps.emailAuth
? "Login"
: "Continue"
}
title={!loading ? "Continue" : null}
/>
{step === LoginSteps.passwordAuth && (
@@ -261,9 +262,34 @@ export const Login = ({ changeMode }) => {
type="errorShade"
/>
)}
{!loading ? (
<TouchableOpacity
onPress={() => {
if (loading) return;
changeMode(1);
}}
activeOpacity={0.8}
style={{
alignSelf: "center",
marginTop: 12,
paddingVertical: 12
}}
>
<Paragraph size={SIZE.xs + 1} color={colors.icon}>
Don't have an account?{" "}
<Paragraph
size={SIZE.xs + 1}
style={{ color: colors.accent }}
>
Sign up
</Paragraph>
</Paragraph>
</TouchableOpacity>
) : null}
</View>
</View>
</Animated.View>
</View>
</>
);
};

View File

@@ -18,8 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useRef, useState } from "react";
import { Dimensions, View } from "react-native";
import Animated, { FadeInDown, FadeOutUp } from "react-native-reanimated";
import { TouchableOpacity, View } from "react-native";
import { db } from "../../common/database";
import { DDS } from "../../services/device-detection";
import { ToastEvent } from "../../services/event-manager";
@@ -32,11 +31,10 @@ import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import { Button } from "../ui/button";
import Input from "../ui/input";
import { SvgView } from "../ui/svg";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { SVG } from "./background";
import { hideAuth } from "./common";
import { useSettingStore } from "../../stores/use-setting-store";
export const Signup = ({ changeMode, trial }) => {
const colors = useThemeStore((state) => state.colors);
@@ -50,6 +48,7 @@ export const Signup = ({ changeMode, trial }) => {
const [loading, setLoading] = useState(false);
const setUser = useUserStore((state) => state.setUser);
const setLastSynced = useUserStore((state) => state.setLastSynced);
const deviceMode = useSettingStore((state) => state.deviceMode);
const validateInfo = () => {
if (!password.current || !email.current || !confirmPassword.current) {
@@ -96,69 +95,77 @@ export const Signup = ({ changeMode, trial }) => {
return (
<>
<Animated.View
entering={FadeInDown}
exiting={FadeOutUp}
<View
style={{
borderRadius: DDS.isTab ? 5 : 0,
backgroundColor: colors.bg,
zIndex: 10,
width: "100%",
alignSelf: "center",
height: "100%",
minHeight: "100%"
}}
>
<View
style={{
height: 250,
overflow: "hidden"
flexGrow: 1,
justifyContent: "flex-end",
paddingHorizontal: 20,
backgroundColor: colors.nav,
marginBottom: 20,
borderBottomWidth: 1,
borderBottomColor: colors.border,
alignSelf: deviceMode !== "mobile" ? "center" : undefined,
borderWidth: deviceMode !== "mobile" ? 1 : null,
borderColor: deviceMode !== "mobile" ? colors.border : null,
borderRadius: deviceMode !== "mobile" ? 20 : null,
marginTop: deviceMode !== "mobile" ? 50 : null,
width: deviceMode === "mobile" ? null : "50%"
}}
>
<SvgView
src={SVG(colors.night ? colors.icon : "black")}
height={700}
/>
<View
style={{
flexDirection: "row"
}}
>
<View
style={{
width: 100,
height: 5,
backgroundColor: colors.accent,
borderRadius: 2,
marginRight: 7
}}
/>
<View
style={{
width: 20,
height: 5,
backgroundColor: colors.nav,
borderRadius: 2
}}
/>
</View>
<Heading
extraBold
style={{
marginBottom: 25,
marginTop: 10
}}
size={SIZE.xxl}
>
Create your {"\n"}account
</Heading>
</View>
<View
style={{
width: "100%",
justifyContent: "center",
alignSelf: "center",
paddingHorizontal: 12,
marginBottom: 30,
marginTop: Dimensions.get("window").height < 700 ? -75 : 15
}}
>
<Heading
style={{
textAlign: "center"
}}
size={30}
color={colors.heading}
>
Create your account
</Heading>
<Paragraph
style={{
textDecorationLine: "underline",
textAlign: "center"
}}
onPress={() => {
if (loading) return;
changeMode(0);
}}
size={SIZE.md}
>
Already have an account? Log in
</Paragraph>
</View>
<View
style={{
width: DDS.isTab ? "50%" : "100%",
padding: 12,
paddingHorizontal: 20,
backgroundColor: colors.bg,
flexGrow: 1,
alignSelf: "center"
alignSelf: "center",
flexGrow: 0.5
}}
>
<Input
@@ -217,37 +224,13 @@ export const Signup = ({ changeMode, trial }) => {
validationType="confirmPassword"
customValidator={() => password.current}
placeholder="Confirm password"
marginBottom={5}
marginBottom={12}
onSubmit={signup}
/>
<View
style={{
marginTop: 25,
alignSelf: "center"
}}
>
<Button
style={{
width: 250,
borderRadius: 100
}}
loading={loading}
onPress={() => {
if (loading) return;
signup();
}}
type="accent"
title={loading ? null : "Agree and continue"}
/>
</View>
<Paragraph
style={{
textAlign: "center",
position: "absolute",
bottom: 0,
alignSelf: "center",
marginBottom: 20
marginBottom: 25
}}
size={SIZE.xs}
color={colors.icon}
@@ -263,7 +246,7 @@ export const Signup = ({ changeMode, trial }) => {
}}
color={colors.accent}
>
terms of service{" "}
Terms of Service{" "}
</Paragraph>
and{" "}
<Paragraph
@@ -276,11 +259,46 @@ export const Signup = ({ changeMode, trial }) => {
}}
color={colors.accent}
>
privacy policy.
</Paragraph>
Privacy Policy.
</Paragraph>{" "}
You also agree to recieve marketing emails from us which you can
opt-out of from app settings.
</Paragraph>
<Button
title={!loading ? "Continue" : null}
type="accent"
loading={loading}
onPress={signup}
fontSize={SIZE.md}
style={{
marginRight: 12,
width: 250,
borderRadius: 100
}}
/>
<TouchableOpacity
onPress={() => {
if (loading) return;
changeMode(0);
}}
activeOpacity={0.8}
style={{
alignSelf: "center",
marginTop: 12,
paddingVertical: 12
}}
>
<Paragraph size={SIZE.xs + 1} color={colors.icon}>
Already have an account?{" "}
<Paragraph size={SIZE.xs + 1} style={{ color: colors.accent }}>
Login
</Paragraph>
</Paragraph>
</TouchableOpacity>
</View>
</Animated.View>
</View>
</>
);
};

View File

@@ -36,6 +36,7 @@ import SheetProvider from "../sheet-provider";
import RateAppSheet from "../sheets/rate-app";
import RecoveryKeySheet from "../sheets/recovery-key";
import RestoreDataSheet from "../sheets/restore-data";
import PDFPreview from "../dialogs/pdf-preview";
const DialogProvider = () => {
const colors = useThemeStore((state) => state.colors);
@@ -60,6 +61,7 @@ const DialogProvider = () => {
{loading ? null : <Expiring />}
<AnnouncementDialog />
<SessionExpired />
<PDFPreview />
</>
);
};

View File

@@ -0,0 +1,324 @@
/*
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 React, { useEffect, useRef, useState } from "react";
import { Dimensions, TextInput, View } from "react-native";
import {
addOrientationListener,
removeOrientationListener
} from "react-native-orientation";
import Pdf from "react-native-pdf";
import Animated, { FadeIn, FadeOut } from "react-native-reanimated";
import downloadAttachment from "../../../common/filesystem/download-attachment";
import { cacheDir } from "../../../common/filesystem/utils";
import { useAttachmentProgress } from "../../../hooks/use-attachment-progress";
import useGlobalSafeAreaInsets from "../../../hooks/use-global-safe-area-insets";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../../services/event-manager";
import { useThemeStore } from "../../../stores/use-theme-store";
import { SIZE } from "../../../utils/size";
import { Dialog } from "../../dialog";
import BaseDialog from "../../dialog/base-dialog";
import { presentDialog } from "../../dialog/functions";
import SheetProvider from "../../sheet-provider";
import { IconButton } from "../../ui/icon-button";
import { ProgressBarComponent } from "../../ui/svg/lazy";
import Paragraph from "../../ui/typography/paragraph";
import { sleep } from "../../../utils/time";
import { MMKV } from "../../../common/database/mmkv";
const WIN_WIDTH = Dimensions.get("window").width;
const WIN_HEIGHT = Dimensions.get("window").height;
const attachmentSnapshotsKey = "___attachmentsnapshots";
const usePDFSnapshot = (attachment) => {
const snapshots = useRef(MMKV.getMap(attachmentSnapshotsKey) || {});
const snapshot = useRef(snapshots[attachment?.id]);
function saveSnapshot(ss) {
if (!attachment) return;
snapshots.current[attachment.id] = ss;
MMKV.setMap(attachmentSnapshotsKey, snapshots.current);
snapshot.current = snapshots.current[attachment.id];
}
return [snapshot, saveSnapshot];
};
const PDFPreview = () => {
const colors = useThemeStore((state) => state.colors);
const [visible, setVisible] = useState(false);
const [pdfSource, setPDFSource] = useState();
const [loading, setLoading] = useState(false);
const [width, setWidth] = useState(WIN_WIDTH);
const insets = useGlobalSafeAreaInsets();
const [numPages, setNumPages] = useState(0);
const [currentPage, setCurrentPage] = useState(1);
const inputRef = useRef();
const pdfRef = useRef();
const [attachment, setAttachment] = useState(null);
const [password, setPassword] = useState("");
const [progress] = useAttachmentProgress(attachment);
const [snapshot, saveSnapshot] = usePDFSnapshot(attachment);
const snapshotValue = useRef(snapshot.current);
useEffect(() => {
eSubscribeEvent("PDFPreview", open);
return () => {
eUnSubscribeEvent("PDFPreview", open);
};
}, []);
const onOrientationChange = (o) => {
if (o.includes("LANDSCAPE")) {
setWidth(WIN_HEIGHT);
} else {
setWidth(WIN_WIDTH);
}
};
useEffect(() => {
addOrientationListener(onOrientationChange);
return () => {
removeOrientationListener(onOrientationChange);
};
}, []);
const open = async (attachment) => {
setVisible(true);
setLoading(true);
setTimeout(async () => {
setAttachment(attachment);
let hash = attachment.metadata.hash;
if (!hash) return;
const uri = await downloadAttachment(hash, false, {
silent: true,
cache: true
});
const path = `${cacheDir}/${uri}`;
snapshotValue.current = snapshot.current;
setPDFSource("file://" + path);
setLoading(false);
}, 100);
};
const close = () => {
setPDFSource(null);
setVisible(false);
setPassword("");
};
const onError = async (error) => {
if (error?.message === "Password required or incorrect password.") {
await sleep(300);
presentDialog({
context: attachment?.metadata?.hash,
input: true,
inputPlaceholder: "Enter password",
positiveText: "Unlock",
title: "Decrypt",
paragraph: "Please input password to view pdf.",
positivePress: (value) => {
setTimeout(() => {
setPassword(value);
});
},
onClose: () => {
close();
}
});
}
};
return (
visible && (
<BaseDialog animation="fade" visible={true} onRequestClose={close}>
<SheetProvider context={attachment?.metadata?.hash} />
<Dialog context={attachment?.metadata?.hash} />
<View
style={{
width: "100%",
height: "100%",
backgroundColor: "black"
}}
>
{loading ? (
<Animated.View
exiting={FadeOut}
style={{
flex: 1,
justifyContent: "center",
alignItems: "center"
}}
>
<ProgressBarComponent
indeterminate={!progress}
color={colors.accent}
borderColor="transparent"
progress={parseInt(progress?.value || "100") / 100}
/>
<Paragraph
style={{
marginTop: 10
}}
color={colors.light}
>
Loading {`${progress?.percent ? `(${progress?.percent})` : ""}`}
... Please wait
</Paragraph>
</Animated.View>
) : (
<>
<View
style={{
width: "100%",
height: 50,
marginTop: insets.top,
flexDirection: "row",
justifyContent: "space-between",
paddingHorizontal: 12,
paddingLeft: 6
}}
>
<View
style={{
flexDirection: "row"
}}
>
<IconButton
color={colors.light}
name="arrow-left"
onPress={close}
customStyle={{
marginRight: 12
}}
size={SIZE.xxl}
/>
</View>
<View
style={{
flexDirection: "row",
alignItems: "center",
marginRight: 12
}}
>
<TextInput
ref={inputRef}
defaultValue={currentPage + ""}
style={{
color: colors.pri,
padding: 0,
paddingTop: 0,
paddingBottom: 0,
marginTop: 0,
marginBottom: 0,
paddingVertical: 0,
height: 25,
backgroundColor: colors.nav,
width: 40,
textAlign: "center",
marginRight: 4,
borderRadius: 3,
fontFamily: "OpenSans-Regular"
}}
selectTextOnFocus
keyboardType="decimal-pad"
onSubmitEditing={(event) => {
setCurrentPage(event.nativeEvent.text);
pdfRef.current?.setPage(parseInt(event.nativeEvent.text));
}}
blurOnSubmit
/>
<Paragraph color={colors.light}>/{numPages}</Paragraph>
</View>
<View
style={{
flexDirection: "row"
}}
>
<IconButton
color={colors.light}
name="download"
onPress={() => {
downloadAttachment(attachment.metadata.hash, false);
}}
/>
</View>
</View>
{pdfSource ? (
<Animated.View
style={{
flex: 1
}}
entering={FadeIn}
>
<Pdf
source={{
uri: pdfSource
}}
ref={pdfRef}
onLoadComplete={(numberOfPages) => {
setNumPages(numberOfPages);
}}
onPageChanged={(page) => {
setCurrentPage(page);
inputRef.current?.setNativeProps({
text: page + ""
});
saveSnapshot({
currentPage: page,
scale: snapshot?.current?.scale
});
}}
// scale={snapshotValue.current?.scale}
// onScaleChanged={(scale) => {
// saveSnapshot({
// currentPage: snapshot?.current?.currentPage,
// scale: scale
// });
// }}
page={snapshotValue?.current?.currentPage}
password={password}
maxScale={6}
onError={onError}
onPressLink={(uri) => {
console.log(`Link pressed: ${uri}`);
}}
style={{
flex: 1,
width: width,
height: Dimensions.get("window").height
}}
/>
</Animated.View>
) : null}
</>
)}
</View>
</BaseDialog>
)
);
};
export default PDFPreview;

File diff suppressed because one or more lines are too long

View File

@@ -42,7 +42,6 @@ import { eOpenAnnouncementDialog } from "../../utils/events";
import { getGithubVersion } from "../../utils/github-version";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import { SVG } from "../auth/background";
import Migrate from "../sheets/migrate";
import NewFeature from "../sheets/new-feature/index";
import { Update } from "../sheets/update";
@@ -50,7 +49,6 @@ import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
import Input from "../ui/input";
import Seperator from "../ui/seperator";
import { SvgView } from "../ui/svg";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { Walkthrough } from "../walkthroughs";
@@ -89,7 +87,7 @@ const Launcher = React.memo(
const init = useCallback(async () => {
if (!db.isInitialized) {
await RNBootSplash.hide({ fade: true });
RNBootSplash.hide({ fade: true });
DatabaseLogger.info("Initializing database");
await db.init();
}
@@ -132,7 +130,6 @@ const Launcher = React.memo(
if (NewFeature.present()) return;
if (await checkAppUpdateAvailable()) return;
if (await checkForRateAppRequest()) return;
if (await checkNeedsBackup()) return;
if (await PremiumService.getRemainingTrialDaysStatus()) return;
if (introCompleted) {
@@ -175,23 +172,6 @@ const Launcher = React.memo(
return false;
};
const checkNeedsBackup = async () => {
return false;
// let { nextBackupRequestTime, reminder } = SettingsService.get();
// if (reminder === 'off' || !reminder) {
// if (nextBackupRequestTime < Date.now()) {
// presentSheet({
// title: 'Backup & restore',
// paragraph: 'Please enable automatic backups to keep your data safe',
// component: <SettingsBackupAndRestore isSheet={true} />
// });
// return true;
// }
// }
// return false;
};
const onUnlockBiometrics = useCallback(async () => {
if (!(await BiometricService.isBiometryAvailable())) return;
if (Platform.OS === "android") {
@@ -218,7 +198,8 @@ const Launcher = React.memo(
}, [init, verifyUser]);
useEffect(() => {
if (verifying.current) return;
if (verifying.current || useUserStore.getState().shouldBlockVerifyUser)
return;
if (verifyUser && appState === "active") {
verifying.current = true;
onUnlockBiometrics();
@@ -246,18 +227,10 @@ const Launcher = React.memo(
width: "100%",
height: "100%",
position: "absolute",
zIndex: 999
zIndex: 999,
justifyContent: "center"
}}
>
<View
style={{
height: 250,
overflow: "hidden"
}}
>
<SvgView src={SVG(colors.night ? "white" : "black")} height={700} />
</View>
<View
style={{
flex: 1,
@@ -293,14 +266,13 @@ const Launcher = React.memo(
textAlign: "center"
}}
>
Unlock to access your notes
Unlock your notes
</Heading>
<Paragraph
style={{
alignSelf: "center",
textAlign: "center",
fontSize: SIZE.md,
maxWidth: "90%"
}}
>
@@ -311,8 +283,7 @@ const Launcher = React.memo(
style={{
width: "100%",
padding: 12,
backgroundColor: colors.bg,
flexGrow: 1
backgroundColor: colors.bg
}}
>
{user ? (
@@ -352,14 +323,9 @@ const Launcher = React.memo(
<Button
title="Unlock with Biometrics"
width={250}
height={45}
style={{
borderRadius: 100
}}
onPress={onUnlockBiometrics}
icon={"fingerprint"}
type={user ? "grayAccent" : "accent"}
fontSize={SIZE.md}
/>
</View>
</View>

View File

@@ -31,6 +31,7 @@ import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { getFormattedDate } from "../../../utils/time";
const showActionSheet = (item) => {
Properties.present(item);
@@ -163,7 +164,7 @@ export const NotebookItem = ({
marginRight: 6
}}
>
{new Date(item[dateBy]).toDateString().substring(4)}
{getFormattedDate(item[dateBy], "date")}
</Paragraph>
)}
<Paragraph

View File

@@ -37,7 +37,7 @@ import { useThemeStore } from "../../stores/use-theme-store";
import { dHeight } from "../../utils";
import { eOnLoadNote, eShowMergeDialog } from "../../utils/events";
import { SIZE } from "../../utils/size";
import { sleep, timeConverter } from "../../utils/time";
import { getFormattedDate, sleep } from "../../utils/time";
import BaseDialog from "../dialog/base-dialog";
import DialogButtons from "../dialog/dialog-buttons";
import DialogContainer from "../dialog/dialog-container";
@@ -166,7 +166,7 @@ const MergeConflicts = () => {
{isCurrent ? "(This Device)" : "(Incoming)"}
</Text>
{"\n"}
{timeConverter(contentToKeep?.dateEdited)}
{getFormattedDate(contentToKeep?.dateEdited)}
</Paragraph>
</View>

View File

@@ -26,7 +26,7 @@ import { presentSheet } from "../../services/event-manager";
import { useThemeStore } from "../../stores/use-theme-store";
import { openLinkInBrowser } from "../../utils/functions";
import { SIZE } from "../../utils/size";
import { timeConverter, timeSince } from "../../utils/time";
import { getFormattedDate, timeSince } from "../../utils/time";
import DialogHeader from "../dialog/dialog-header";
import SheetProvider from "../sheet-provider";
import { PressableButton } from "../ui/pressable";
@@ -63,15 +63,15 @@ export default function NoteHistory({ note, fwdRef }) {
}, []);
const getDate = (start, end) => {
let _start = timeConverter(start);
let _end = timeConverter(end + 60000);
if (_start === _end) return _start;
let final = _end.lastIndexOf(",");
let part = _end.slice(0, final + 1);
if (_start.includes(part)) {
return _start + " —" + _end.replace(part, "");
}
return _start + " — " + _end;
let _start_date = getFormattedDate(start, "date");
let _end_date = getFormattedDate(end + 60000, "date");
let _start_time = getFormattedDate(start, "time");
let _end_time = getFormattedDate(end + 60000, "time");
return `${_start_date} ${_start_time} - ${
_end_date === _start_date ? " " : _end_date + " "
}${_end_time}`;
};
const renderItem = useCallback(

View File

@@ -18,13 +18,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { ScrollView } from "react-native";
import { FeatureBlock } from "./feature";
import { ScrollView } from "react-native-actions-sheet";
export const CompactFeatures = ({
vertical,
features = [],
maxHeight = 500,
maxHeight = 600,
scrollRef
}) => {
let data = vertical
@@ -80,14 +80,11 @@ export const CompactFeatures = ({
return (
<ScrollView
horizontal={!vertical}
nestedScrollEnabled
onMomentumScrollEnd={() => {
scrollRef?.current?.handleChildScrollEnd();
}}
showsHorizontalScrollIndicator={false}
style={{
width: "100%",
maxHeight: maxHeight
maxHeight: maxHeight,
paddingHorizontal: 12
}}
>
{data.map((item) => (

View File

@@ -41,18 +41,19 @@ export const FeatureBlock = ({
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 12,
marginBottom: 10
marginBottom: 10,
backgroundColor: colors.nav,
borderRadius: 10,
paddingVertical: 12
}}
>
<Icon color={colors.accent} name="check" size={SIZE.lg} />
<Paragraph
style={{
flexWrap: "wrap",
marginLeft: 5,
flexShrink: 1
}}
size={SIZE.md}
size={SIZE.sm}
>
{content}
</Paragraph>

View File

@@ -232,8 +232,7 @@ export const PricingPlans = ({
width={250}
style={{
paddingHorizontal: 12,
marginBottom: 15,
borderRadius: 100
marginBottom: 15
}}
/>
</>
@@ -398,8 +397,7 @@ export const PricingPlans = ({
style={{
paddingHorizontal: 12,
marginTop: 30,
marginBottom: 10,
borderRadius: 100
marginBottom: 10
}}
/>
{Platform.OS !== "ios" &&

View File

@@ -21,7 +21,7 @@ import React from "react";
import { View } from "react-native";
import { useThemeStore } from "../../stores/use-theme-store";
import { SIZE } from "../../utils/size";
import { timeConverter } from "../../utils/time";
import { getFormattedDate } from "../../utils/time";
import Paragraph from "../ui/typography/paragraph";
export const DateMeta = ({ item }) => {
const colors = useThemeStore((state) => state.colors);
@@ -66,7 +66,7 @@ export const DateMeta = ({ item }) => {
{getNameFromKey(key)}
</Paragraph>
<Paragraph size={SIZE.xs} color={colors.icon}>
{timeConverter(item[key])}
{getFormattedDate(item[key], "date-time")}
</Paragraph>
</View>
);

View File

@@ -191,13 +191,25 @@ Properties.present = (item, buttons = [], isSheet) => {
break;
case "notebook":
props[0] = db.notebooks.notebook(item.id).data;
props.push(["edit-notebook", "pin", "add-shortcut", "trash"]);
props.push([
"edit-notebook",
"pin",
"add-shortcut",
"trash",
"default-notebook"
]);
break;
case "topic":
props[0] = db.notebooks
.notebook(item.notebookId)
.topics.topic(item.id)._topic;
props.push(["move-notes", "edit-topic", "add-shortcut", "trash"]);
props.push([
"move-notes",
"edit-topic",
"add-shortcut",
"trash",
"default-topic"
]);
break;
case "tag":
props[0] = db.tags.tag(item.id);

View File

@@ -47,7 +47,7 @@ export const Tags = ({ item, close }) => {
>
<Button
onPress={async () => {
ManageTagsSheet.present(item);
ManageTagsSheet.present([item]);
}}
buttonType={{
text: colors.accent

View File

@@ -36,6 +36,7 @@ import MoveNoteSheet from "../sheets/add-to";
import ExportNotesSheet from "../sheets/export-notes";
import { IconButton } from "../ui/icon-button";
import Heading from "../ui/typography/heading";
import ManageTagsSheet from "../sheets/manage-tags";
export const SelectionHeader = React.memo(() => {
const colors = useThemeStore((state) => state.colors);
@@ -209,6 +210,20 @@ export const SelectionHeader = React.memo(() => {
screen === "Notebook" ||
screen === "Reminders" ? null : (
<>
<IconButton
onPress={async () => {
await sleep(100);
ManageTagsSheet.present(selectedItemsList);
}}
customStyle={{
marginLeft: 10
}}
color={colors.pri}
tooltipText="Manage tags"
tooltipPosition={4}
name="pound"
size={SIZE.xl}
/>
<IconButton
onPress={async () => {
//setSelectionMode(false);
@@ -224,6 +239,7 @@ export const SelectionHeader = React.memo(() => {
name="plus"
size={SIZE.xl}
/>
<IconButton
onPress={async () => {
ExportNotesSheet.present(selectedItemsList);

View File

@@ -196,12 +196,11 @@ const SheetProvider = ({ context = "global" }) => {
accentColor={data.iconColor || "accent"}
accentText="light"
type="accent"
height={45}
height={40}
width={250}
style={{
borderRadius: 100
marginBottom: 25
}}
fontSize={SIZE.md}
/>
) : null}

View File

@@ -309,7 +309,6 @@ export class AddNotebookSheet extends React.Component {
borderRadius: 100,
paddingHorizontal: 24
}}
fontSize={SIZE.md}
onPress={this.addNewNotebook}
/>
</View>

View File

@@ -42,6 +42,7 @@ import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { eSendEvent } from "../../../services/event-manager";
import { eCloseSheet } from "../../../utils/events";
import { requestInAppReview } from "../../../services/app-review";
const ExportNotesSheet = ({ notes, update }) => {
const colors = useThemeStore((state) => state.colors);
@@ -72,6 +73,7 @@ const ExportNotesSheet = ({ notes, update }) => {
update({ disableClosing: false });
setComplete(true);
setExporting(false);
requestInAppReview();
};
const actions = [

View File

@@ -31,9 +31,10 @@ import Input from "../../ui/input";
import { PressableButton } from "../../ui/pressable";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { IconButton } from "../../ui/icon-button";
const ManageTagsSheet = (props) => {
const colors = useThemeStore((state) => state.colors);
const [note, setNote] = useState(props.note);
const [notes, setNotes] = useState(props.notes || []);
const allTags = useTagStore((state) => state.tags);
const [tags, setTags] = useState([]);
const [query, setQuery] = useState(null);
@@ -42,7 +43,7 @@ const ManageTagsSheet = (props) => {
useEffect(() => {
sortTags();
}, [allTags, note, query, sortTags]);
}, [allTags, notes, query, sortTags]);
const sortTags = useCallback(() => {
let _tags = [...allTags];
@@ -51,13 +52,14 @@ const ManageTagsSheet = (props) => {
if (query) {
_tags = _tags.filter((t) => t.title.startsWith(query));
}
const tagsMerged = [...notes.map((note) => note.tags || []).flat()];
if (!note || !note.tags) {
if (!tagsMerged || !tagsMerged.length) {
setTags(_tags);
return;
}
let noteTags = [];
for (let tag of note.tags) {
for (let tag of tagsMerged) {
let index = _tags.findIndex((t) => t.title === tag);
if (index !== -1) {
noteTags.push(_tags[index]);
@@ -67,7 +69,7 @@ const ManageTagsSheet = (props) => {
noteTags = noteTags.sort((a, b) => a.title.localeCompare(b.title));
let combinedTags = [...noteTags, ..._tags];
setTags(combinedTags);
}, [allTags, note, query]);
}, [allTags, notes, query]);
useEffect(() => {
useTagStore.getState().setTags();
@@ -85,15 +87,23 @@ const ManageTagsSheet = (props) => {
}
let tag = _query;
setNote({ ...note, tags: note.tags ? [...note.tags, tag] : [tag] });
setNotes(
notes.map((note) => ({
...note,
tags: note.tags ? [...note.tags, tag] : [tag]
}))
);
setQuery(null);
inputRef.current?.setNativeProps({
text: ""
});
try {
await db.notes.note(note.id).tag(tag);
for (let note of notes) {
await db.notes.note(note.id).tag(tag);
}
useTagStore.getState().setTags();
setNote(db.notes.note(note.id).data);
setNotes(notes.map((note) => db.notes.note(note.id).data));
} catch (e) {
ToastEvent.show({
heading: "Cannot add tag",
@@ -180,41 +190,52 @@ const ManageTagsSheet = (props) => {
) : null}
{tags.map((item) => (
<TagItem key={item.title} tag={item} note={note} setNote={setNote} />
<TagItem
key={item.title}
tag={item}
notes={notes}
setNotes={setNotes}
/>
))}
</ScrollView>
</View>
);
};
ManageTagsSheet.present = (note) => {
ManageTagsSheet.present = (notes) => {
presentSheet({
component: (ref) => {
return <ManageTagsSheet actionSheetRef={ref} note={note} />;
return <ManageTagsSheet actionSheetRef={ref} notes={notes} />;
}
});
};
export default ManageTagsSheet;
const TagItem = ({ tag, note, setNote }) => {
const TagItem = ({ tag, notes, setNotes }) => {
const colors = useThemeStore((state) => state.colors);
const someNotesTagged = notes.some(
(note) => note.tags?.indexOf(tag.title) !== -1
);
const allNotesTagged = notes.every(
(note) => note.tags?.indexOf(tag.title) !== -1
);
const onPress = async () => {
let prevNote = { ...note };
try {
if (prevNote.tags.indexOf(tag.title) !== -1) {
await db.notes
.note(note.id)
.untag(prevNote.tags[prevNote.tags.indexOf(tag.title)]);
} else {
await db.notes.note(note.id).tag(tag.title);
for (let note of notes) {
try {
if (someNotesTagged) {
await db.notes
.note(note.id)
.untag(note.tags[note.tags.indexOf(tag.title)]);
} else {
await db.notes.note(note.id).tag(tag.title);
}
} catch (e) {
console.error(e);
}
useTagStore.getState().setTags();
setNote(db.notes.note(note.id).data);
} catch (e) {
console.error(e);
}
useTagStore.getState().setTags();
setNotes(notes.map((note) => db.notes.note(note.id).data));
setTimeout(() => {
Navigation.queueRoutesForUpdate();
}, 1);
@@ -225,39 +246,36 @@ const TagItem = ({ tag, note, setNote }) => {
customStyle={{
flexDirection: "row",
marginVertical: 5,
justifyContent: "space-between",
padding: 12
justifyContent: "flex-start",
height: 40
}}
onPress={onPress}
type={
note && note.tags.findIndex((t) => t === tag.title) !== -1
? "shade"
: "grayBg"
}
type="gray"
>
<Heading
size={SIZE.sm}
color={
note && note?.tags.findIndex((t) => t === tag.title) !== -1
? colors.accent
: colors.pri
<IconButton
size={22}
customStyle={{
marginRight: 5,
width: 23,
height: 23
}}
color={someNotesTagged || allNotesTagged ? colors.accent : colors.icon}
testID={
allNotesTagged
? "check-circle-outline"
: someNotesTagged
? "minus-circle-outline"
: "checkbox-blank-circle-outline"
}
>
{"#" + tag.title}
</Heading>
<Icon
name={
note && note?.tags.findIndex((t) => t === tag.title) !== -1
? "minus"
: "plus"
allNotesTagged
? "check-circle-outline"
: someNotesTagged
? "minus-circle-outline"
: "checkbox-blank-circle-outline"
}
color={
note && note?.tags.findIndex((t) => t === tag.title) !== -1
? colors.accent
: colors.accent
}
size={SIZE.lg}
/>
<Paragraph size={SIZE.sm}>{"#" + tag.title}</Paragraph>
</PressableButton>
);
};

View File

@@ -35,6 +35,7 @@ import Input from "../../ui/input";
import Seperator from "../../ui/seperator";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { requestInAppReview } from "../../../services/app-review";
const PublishNoteSheet = ({ note: item, update }) => {
const colors = useThemeStore((state) => state.colors);
@@ -66,6 +67,7 @@ const PublishNoteSheet = ({ note: item, update }) => {
Navigation.queueRoutesForUpdate();
setPublishLoading(false);
}
requestInAppReview();
} catch (e) {
ToastEvent.show({
heading: "Could not publish note",

View File

@@ -43,7 +43,7 @@ import Seperator from "../../ui/seperator";
import SheetWrapper from "../../ui/sheet";
import { QRCode } from "../../ui/svg/lazy";
import Paragraph from "../../ui/typography/paragraph";
import RNFetchBlob from "rn-fetch-blob";
import RNFetchBlob from "react-native-blob-util";
class RecoveryKeySheet extends React.Component {
constructor(props) {

View File

@@ -243,7 +243,6 @@ export default function ReminderSheet({
borderRadius: 100,
paddingHorizontal: 24
}}
fontSize={SIZE.md}
onPress={saveReminder}
/>
</View>

View File

@@ -21,21 +21,22 @@ import { EVENTS } from "@notesnook/core/common";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { ActivityIndicator, Platform, View } from "react-native";
import { FlatList } from "react-native-actions-sheet";
import RNFetchBlob from "react-native-blob-util";
import DocumentPicker from "react-native-document-picker";
import * as ScopedStorage from "react-native-scoped-storage";
import { db } from "../../../common/database";
import storage from "../../../common/database/storage";
import {
ToastEvent,
eSubscribeEvent,
eUnSubscribeEvent,
ToastEvent
eUnSubscribeEvent
} from "../../../services/event-manager";
import SettingsService from "../../../services/settings";
import { initialize } from "../../../stores";
import { useThemeStore } from "../../../stores/use-theme-store";
import { eCloseRestoreDialog, eOpenRestoreDialog } from "../../../utils/events";
import { SIZE } from "../../../utils/size";
import { timeConverter } from "../../../utils/time";
import { getFormattedDate } from "../../../utils/time";
import { Dialog } from "../../dialog";
import DialogHeader from "../../dialog/dialog-header";
import { presentDialog } from "../../dialog/functions";
@@ -44,7 +45,6 @@ import { Button } from "../../ui/button";
import Seperator from "../../ui/seperator";
import SheetWrapper from "../../ui/sheet";
import Paragraph from "../../ui/typography/paragraph";
import RNFetchBlob from "rn-fetch-blob";
const RestoreDataSheet = () => {
const [visible, setVisible] = useState(false);
@@ -251,7 +251,7 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
}}
>
<Paragraph size={SIZE.sm} style={{ width: "100%", maxWidth: "100%" }}>
{timeConverter(item?.lastModified * 1)}
{getFormattedDate(item?.lastModified * 1)}
</Paragraph>
<Paragraph size={SIZE.xs}>
{(item.filename || item.name).replace(".nnbackup", "")}

View File

@@ -65,7 +65,6 @@ import { deleteItems } from "../../../utils/functions";
import { presentDialog } from "../../dialog/functions";
import { Properties } from "../../properties";
import Sort from "../sort";
import Heading from "../../ui/typography/heading";
type ConfigItem = { id: string; type: string };
class TopicSheetConfig {

View File

@@ -302,6 +302,7 @@ export const FluidTabs = forwardRef<TabsRef, TabProps>(function FluidTabs(
translateX.value = value;
})
.onEnd((event) => {
if (locked.value || forcedLock.value) return;
if (currentTab.value === 2 && Platform.OS === "android") return;
const velocityX =
event.velocityX < 0 ? event.velocityX * -1 : event.velocityX;

View File

@@ -119,7 +119,7 @@ const Input = ({
? colors.red
: focus
? customColor || colors.accent
: colors.nav;
: colors.border;
const validate = async (value: string) => {
if (!validationType) return;

View File

@@ -16,17 +16,15 @@ 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 { isReminderActive } from "@notesnook/core/collections/reminders";
import React from "react";
import {
formatReminderTime,
isReminderActive
} from "@notesnook/core/collections/reminders";
import { ViewStyle } from "react-native";
import { Reminder } from "../../../services/notifications";
import { Button, ButtonProps } from "../button";
import { useThemeStore } from "../../../stores/use-theme-store";
import { SIZE } from "../../../utils/size";
import { getFormattedReminderTime } from "../../../utils/time";
import { Button, ButtonProps } from "../button";
export const ReminderTime = ({
checkIsActive = true,
@@ -42,7 +40,7 @@ export const ReminderTime = ({
} & ButtonProps) => {
const colors = useThemeStore((state) => state.colors);
const reminder = props.reminder;
const time = !reminder ? undefined : formatReminderTime(reminder);
const time = !reminder ? undefined : getFormattedReminderTime(reminder);
const isTodayOrTomorrow =
(time?.includes("Today") || time?.includes("Tomorrow")) &&
!time?.includes("Last");

View File

@@ -105,6 +105,9 @@ const SheetWrapper = ({
defaultOverlayOpacity={overlayOpacity}
overlayColor={pitchBlack ? "#585858" : "#2b2b2b"}
keyboardShouldPersistTaps="always"
openAnimationConfig={{
friction: 9
}}
ExtraOverlayComponent={
<>
{overlay}

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useMemo } from "react";
import { Platform, Text, TextProps } from "react-native";
import { Platform, Text, TextProps, ViewStyle } from "react-native";
import Animated, {
ComplexAnimationBuilder,
Layout
@@ -30,15 +30,26 @@ interface HeadingProps extends TextProps {
size?: number;
layout?: ComplexAnimationBuilder;
animated?: boolean;
extraBold?: boolean;
}
const AnimatedText = Animated.createAnimatedComponent(Text);
const extraBoldStyle = {
fontFamily: Platform.OS === "android" ? "OpenSans-Bold" : undefined,
fontWeight: Platform.OS === "ios" ? "800" : undefined
};
const boldStyle = {
fontFamily: Platform.OS === "android" ? "OpenSans-SemiBold" : undefined,
fontWeight: Platform.OS === "ios" ? "600" : undefined
};
const Heading = ({
color,
size = SIZE.xl,
style,
animated,
extraBold,
...restProps
}: HeadingProps) => {
const colors = useThemeStore((state) => state.colors);
@@ -53,11 +64,9 @@ const Heading = ({
style={[
{
fontSize: size || SIZE.xl,
color: color || colors.heading,
fontFamily:
Platform.OS === "android" ? "OpenSans-SemiBold" : undefined,
fontWeight: Platform.OS === "ios" ? "600" : undefined
color: color || colors.heading
},
extraBold ? (extraBoldStyle as ViewStyle) : (boldStyle as ViewStyle),
style
]}
></Component>

View File

@@ -19,4 +19,21 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { FeatureType } from "./components/sheets/new-feature";
export const features: FeatureType[] = [];
export const features: FeatureType[] = [
{
title: "File & image sharing",
body: "You can now share images and files from other apps to Notesnook and sync them instantly."
},
{
title: "Configurable date/time formats",
body: "You can now change date and time format across the app from Settings > Behaviour"
},
{
title: "Change default note title",
body: "You can now change default note title for new notes using different templates like $date$, $time$ and $headline$. Go to Settings > Editor to try"
},
{
title: "Default notebook/topic",
body: "You can now set a default notebook or topic to add new notes to automatically. You can make a notebook default from properties."
}
];

View File

@@ -71,6 +71,9 @@ export const useActions = ({ close = () => null, item }) => {
const user = useUserStore((state) => state.user);
const [notifPinned, setNotifPinned] = useState(null);
const alias = item.alias || item.title;
const [defaultNotebook, setDefaultNotebook] = useState(
db.settings.getDefaultNotebook()
);
const isPublished =
item.type === "note" && db.monographs.isPublished(item.id);
@@ -773,6 +776,53 @@ export const useActions = ({ close = () => null, item }) => {
func: openHistory
},
{
id: "default-notebook",
title:
defaultNotebook?.id === item.id
? "Remove as default"
: "Set as default",
hidden: item.type !== "notebook",
icon: "notebook",
func: async () => {
if (defaultNotebook?.id === item.id) {
await db.settings.setDefaultNotebook();
setDefaultNotebook();
} else {
const notebook = {
id: item.id
};
await db.settings.setDefaultNotebook(notebook);
setDefaultNotebook(notebook);
}
close();
},
on: defaultNotebook?.topic ? false : defaultNotebook?.id === item.id
},
{
id: "default-topic",
title:
defaultNotebook?.id === item.id
? "Remove as default"
: "Set as default",
hidden: item.type !== "topic",
icon: "bookmark",
func: async () => {
if (defaultNotebook?.topic === item.id) {
await db.settings.setDefaultNotebook();
setDefaultNotebook();
} else {
const notebook = {
id: item.notebookId,
topic: item.id
};
await db.settings.setDefaultNotebook(notebook);
setDefaultNotebook(notebook);
}
close();
},
on: defaultNotebook?.topic === item.id
},
{
id: "disable-reminder",
title: !item.disabled ? "Turn off reminder" : "Turn on reminder",

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 = () => {
@@ -489,7 +491,8 @@ export const useAppEvents = () => {
if (
SettingsService.get().appLockMode === "background" &&
!useSettingStore.getState().requestBiometrics &&
!useUserStore.getState().verifyUser
!useUserStore.getState().verifyUser &&
!useUserStore.getState().shouldBlockVerifyUser
) {
useUserStore.getState().setVerifyUser(true);
if (Platform.OS === "ios") {
@@ -561,7 +564,6 @@ export const useAppEvents = () => {
await db.initCollections();
await db.notes.init();
}
useNoteStore.getState().setNotes();
eSendEvent(refreshNotesPage);
MMKV.removeItem("notesAddedFromIntent");
initAfterSync();

View File

@@ -42,7 +42,7 @@ export const useAttachmentProgress = (
);
useEffect(() => {
const attachmentProgress = progress?.[attachment.metadata.hash];
const attachmentProgress = progress?.[attachment?.metadata?.hash];
if (attachmentProgress) {
const type = attachmentProgress.type;
const loaded =
@@ -56,9 +56,11 @@ export const useAttachmentProgress = (
type: type
});
} else {
setCurrentProgress(undefined);
setTimeout(() => {
setCurrentProgress(undefined);
}, 300);
}
}, [attachment.metadata.hash, progress]);
}, [attachment, progress]);
return [currentProgress, setCurrentProgress];
};

View File

@@ -50,6 +50,7 @@ import { useSettingStore } from "../stores/use-setting-store";
import { useThemeStore } from "../stores/use-theme-store";
import { history } from "../utils";
import { rootNavigatorRef } from "../utils/global-refs";
import Auth from "../components/auth";
const NativeStack = createNativeStackNavigator();
const IntroStack = createNativeStackNavigator();
@@ -64,6 +65,7 @@ const IntroStack = createNativeStackNavigator();
const IntroStackNavigator = () => {
const colors = useThemeStore((state) => state.colors);
const height = useSettingStore((state) => state.dimensions.height);
return (
<IntroStack.Navigator
screenOptions={{
@@ -71,12 +73,14 @@ const IntroStackNavigator = () => {
lazy: false,
animation: "none",
contentStyle: {
backgroundColor: colors.bg
backgroundColor: colors.bg,
minHeight: height
}
}}
initialRouteName={"Intro"}
>
<NativeStack.Screen name="Intro" component={Intro} />
<NativeStack.Screen name="Auth" component={Auth} />
<NativeStack.Screen name="AppLock" component={AppLock} />
</IntroStack.Navigator>
);
@@ -120,7 +124,7 @@ const _Tabs = () => {
animation: "none",
contentStyle: {
backgroundColor: colors.bg,
height: !introCompleted ? undefined : screenHeight
minHeight: !introCompleted ? undefined : screenHeight
}
}}
>

View File

@@ -271,13 +271,13 @@ const _TabsHolder = () => {
setTimeout(() => {
switch (current) {
case "tablet":
introCompleted && tabBarRef.current?.goToIndex(0, false);
tabBarRef.current?.goToIndex(0, false);
break;
case "smallTablet":
if (!fullscreen) {
introCompleted && tabBarRef.current?.closeDrawer(false);
tabBarRef.current?.closeDrawer(false);
} else {
introCompleted && tabBarRef.current?.openDrawer(false);
tabBarRef.current?.openDrawer(false);
}
break;
case "mobile":
@@ -416,64 +416,66 @@ const _TabsHolder = () => {
backgroundColor="transparent"
/>
{deviceMode && widths[deviceMode] ? (
<FluidTabs
ref={tabBarRef}
dimensions={dimensions}
widths={!introCompleted ? widths["mobile"] : widths[deviceMode]}
enabled={deviceMode !== "tablet" && !fullscreen}
onScroll={onScroll}
onChangeTab={onChangeTab}
onDrawerStateChange={() => true}
>
<View
key="1"
style={{
height: "100%",
width: fullscreen
? 0
: widths[!introCompleted ? "mobile" : deviceMode]?.a
}}
>
<SideMenu />
</View>
<View
key="2"
style={{
height: "100%",
width: fullscreen
? 0
: widths[!introCompleted ? "mobile" : deviceMode]?.b
}}
>
{deviceMode === "mobile" ? (
<Animated.View
onTouchEnd={() => {
tabBarRef.current?.closeDrawer();
animatedOpacity.value = withTiming(0);
animatedTranslateY.value = withTiming(-9999);
{!introCompleted ? (
<NavigationStack />
) : (
<>
{deviceMode && widths[deviceMode] ? (
<FluidTabs
ref={tabBarRef}
dimensions={dimensions}
widths={widths[deviceMode]}
enabled={deviceMode !== "tablet" && !fullscreen}
onScroll={onScroll}
onChangeTab={onChangeTab}
onDrawerStateChange={() => true}
>
<View
key="1"
style={{
height: "100%",
width: fullscreen ? 0 : widths[deviceMode]?.a
}}
style={[
{
position: "absolute",
width: "100%",
height: "100%",
zIndex: 999,
backgroundColor: "rgba(0,0,0,0.2)"
},
animatedStyle
]}
ref={overlayRef}
/>
) : null}
>
<SideMenu />
</View>
<NavigationStack />
</View>
<View
key="2"
style={{
height: "100%",
width: fullscreen ? 0 : widths[deviceMode]?.b
}}
>
{deviceMode === "mobile" ? (
<Animated.View
onTouchEnd={() => {
tabBarRef.current?.closeDrawer();
animatedOpacity.value = withTiming(0);
animatedTranslateY.value = withTiming(-9999);
}}
style={[
{
position: "absolute",
width: "100%",
height: "100%",
zIndex: 999,
backgroundColor: "rgba(0,0,0,0.2)"
},
animatedStyle
]}
ref={overlayRef}
/>
) : null}
<EditorWrapper key="3" width={widths} dimensions={dimensions} />
</FluidTabs>
) : null}
<NavigationStack />
</View>
<EditorWrapper key="3" width={widths} dimensions={dimensions} />
</FluidTabs>
) : null}
</>
)}
</View>
);
};

View File

@@ -27,7 +27,9 @@
"validator": "^13.5.2",
"zustand": "^3.6.0",
"fflate": "^0.7.3",
"timeago.js": "4.0.2"
"timeago.js": "4.0.2",
"react-native-blob-util": "0.17.3",
"react-native-swiper-flatlist": "3.2.2"
},
"sideEffects": false
}

View File

@@ -46,6 +46,7 @@ import { useEditor } from "./tiptap/use-editor";
import { useEditorEvents } from "./tiptap/use-editor-events";
import { editorController } from "./tiptap/utils";
import { useLayoutEffect } from "react";
import useKeyboard from "../../hooks/use-keyboard";
const style: ViewStyle = {
height: "100%",
@@ -232,6 +233,8 @@ const AppSection = ({
const ReadonlyButton = ({ editor }: { editor: useEditorType }) => {
const readonly = useEditorStore((state) => state.readonly);
const keyboard = useKeyboard();
const onPress = async () => {
if (editor.note.current) {
await db.notes?.note(editor.note.current.id).readonly();
@@ -241,7 +244,7 @@ const ReadonlyButton = ({ editor }: { editor: useEditorType }) => {
}
};
return readonly && IconButton ? (
return readonly && IconButton && !keyboard.keyboardShown ? (
<IconButton
name="pencil-lock"
type="grayBg"
@@ -249,7 +252,7 @@ const ReadonlyButton = ({ editor }: { editor: useEditorType }) => {
color="accent"
customStyle={{
position: "absolute",
bottom: 20,
bottom: 60,
width: 60,
height: 60,
right: 12,

View File

@@ -217,7 +217,7 @@ typeof globalThis.statusBar !== "undefined" && statusBar.current.set({date:"",sa
);
};
updateWebclip = async ({ src, hash }: ImageAttributes) => {
updateWebclip = async ({ src, hash }: Partial<ImageAttributes>) => {
await this.doAsync(
`editor && editor.commands.updateWebClip(${JSON.stringify({
hash
@@ -225,7 +225,7 @@ typeof globalThis.statusBar !== "undefined" && statusBar.current.set({date:"",sa
);
};
updateImage = async ({ src, hash }: ImageAttributes) => {
updateImage = async ({ src, hash }: Partial<ImageAttributes>) => {
await this.doAsync(
`editor && editor.commands.updateImage(${JSON.stringify({
hash

View File

@@ -22,7 +22,7 @@ import React from "react";
import { Platform, View } from "react-native";
import DocumentPicker from "react-native-document-picker";
import { launchCamera, launchImageLibrary } from "react-native-image-picker";
import RNFetchBlob from "rn-fetch-blob";
import RNFetchBlob from "react-native-blob-util";
import { db } from "../../../common/database";
import { compressToBase64 } from "../../../common/filesystem/compress";
import { AttachmentItem } from "../../../components/attachments/attachment-item";
@@ -34,8 +34,8 @@ import {
import PremiumService from "../../../services/premium";
import { eCloseSheet } from "../../../utils/events";
import { editorController, editorState } from "./utils";
const FILE_SIZE_LIMIT = 500 * 1024 * 1024;
const IMAGE_SIZE_LIMIT = 50 * 1024 * 1024;
import { isImage } from "@notesnook/core/utils/filename";
import { FILE_SIZE_LIMIT, IMAGE_SIZE_LIMIT } from "../../../utils/constants";
const showEncryptionSheet = (file) => {
presentSheet({
@@ -74,7 +74,7 @@ const file = async (fileOptions) => {
mode: "import",
allowMultiSelection: false
};
if (Platform.OS == "ios") {
if (Platform.OS === "ios") {
options.copyTo = "cachesDirectory";
}
await db.attachments.generateKey();
@@ -87,14 +87,9 @@ const file = async (fileOptions) => {
}
file = file[0];
if (file.type.startsWith("image")) {
ToastEvent.show({
title: "Type not supported",
message: "Please add images from gallery or camera picker.",
type: "error"
});
return;
}
let uri = Platform.OS === "ios" ? file.fileCopyUri : file.uri;
if (file.size > FILE_SIZE_LIMIT) {
ToastEvent.show({
title: "File too large",
@@ -114,7 +109,6 @@ const file = async (fileOptions) => {
return;
}
let uri = Platform.OS === "ios" ? file.fileCopyUri : file.uri;
console.log("file uri: ", uri);
uri = Platform.OS === "ios" ? santizeUri(uri) : uri;
showEncryptionSheet(file);
@@ -125,12 +119,24 @@ const file = async (fileOptions) => {
if (!(await attachFile(uri, hash, file.type, file.name, fileOptions)))
return;
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
editorController.current?.commands.insertAttachment({
hash: hash,
filename: file.name,
type: file.type,
size: file.size
});
if (isImage(file.type)) {
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
});
} else {
editorController.current?.commands.insertAttachment({
hash: hash,
filename: file.name,
mime: file.type,
size: file.size
});
}
setTimeout(() => {
eSendEvent(eCloseSheet);
}, 1000);
@@ -255,7 +261,7 @@ const handleImageResponse = async (response, options) => {
editorController.current?.commands.insertImage({
hash: hash,
type: image.type,
mime: image.type,
title: fileName,
dataurl: b64,
size: image.fileSize,
@@ -263,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;
@@ -282,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

@@ -50,6 +50,8 @@ export type Settings = {
corsProxy: string;
fontSize: string;
fontFamily: string;
dateFormat: string;
timeFormat: string;
};
export type EditorProps = {

View File

@@ -130,6 +130,11 @@ export const useEditorEvents = (
const deviceMode = useSettingStore((state) => state.deviceMode);
const fullscreen = useSettingStore((state) => state.fullscreen);
const corsProxy = useSettingStore((state) => state.settings.corsProxy);
const [dateFormat, timeFormat] = useSettingStore((state) => [
state.dateFormat,
state.timeFormat
]);
const handleBack = useRef<NativeEventSubscription>();
const readonly = useEditorStore((state) => state.readonly);
const isPremium = useUserStore((state) => state.premium);
@@ -173,7 +178,9 @@ export const useEditorEvents = (
doubleSpacedLines: doubleSpacedLines,
corsProxy: corsProxy,
fontSize: defaultFontSize,
fontFamily: defaultFontFamily
fontFamily: defaultFontFamily,
dateFormat: db.settings?.getDateFormat(),
timeFormat: db.settings?.getTimeFormat()
});
}, [
fullscreen,
@@ -190,7 +197,9 @@ export const useEditorEvents = (
noToolbar,
corsProxy,
defaultFontSize,
defaultFontFamily
defaultFontFamily,
dateFormat,
timeFormat
]);
const onBackPress = useCallback(async () => {
@@ -349,7 +358,7 @@ export const useEditorEvents = (
});
return;
}
ManageTagsSheet.present(editor.note.current);
ManageTagsSheet.present([editor.note.current]);
break;
case EventTypes.tag:
if (editorMessage.value) {
@@ -398,9 +407,18 @@ export const useEditorEvents = (
openLinkInBrowser(editorMessage.value as string);
break;
case EventTypes.previewAttachment:
eSendEvent("ImagePreview", editorMessage.value);
case EventTypes.previewAttachment: {
const hash = (editorMessage.value as Attachment)?.hash;
const attachment = db.attachments?.attachment(hash);
if (attachment.metadata.type.startsWith("image/")) {
eSendEvent("ImagePreview", editorMessage.value);
} else {
eSendEvent("PDFPreview", attachment);
}
break;
}
default:
break;
}

View File

@@ -38,8 +38,9 @@ import { useTagStore } from "../../../stores/use-tag-store";
import { ThemeStore, useThemeStore } from "../../../stores/use-theme-store";
import { eClearEditor, eOnLoadNote } from "../../../utils/events";
import { tabBarRef } from "../../../utils/global-refs";
import { timeConverter } from "../../../utils/time";
import { getFormattedDate } from "../../../utils/time";
import { NoteType } from "../../../utils/types";
import { onNoteCreated } from "../../notes/common";
import Commands from "./commands";
import { Content, EditorState, Note, SavePayload } from "./types";
import {
@@ -198,10 +199,7 @@ export const useEditor = (
sessionId: isContentInvalid(data) ? null : currentSessionHistoryId
};
if (title) {
noteData.title = title;
}
noteData.title = title;
if (data) {
noteData.content = {
data: data,
@@ -213,12 +211,19 @@ export const useEditor = (
id = await db.notes?.add(noteData);
if (!note && id) {
currentNote.current = db.notes?.note(id).data as NoteType;
state.current?.onNoteCreated && state.current.onNoteCreated(id);
const defaultNotebook = db.settings?.getDefaultNotebook();
if (!state.current.onNoteCreated && defaultNotebook) {
onNoteCreated(id, {
type: defaultNotebook.topic ? "topic" : "notebook",
id: defaultNotebook.id,
notebook: defaultNotebook.topic
});
} else {
state.current?.onNoteCreated && state.current.onNoteCreated(id);
}
if (!noteData.title) {
postMessage(
EditorEvents.titleplaceholder,
currentNote.current.title
);
postMessage(EditorEvents.title, currentNote.current.title);
}
}
@@ -243,7 +248,7 @@ export const useEditor = (
}
if (id && sessionIdRef.current === currentSessionId) {
note = db.notes?.note(id)?.data as Note;
await commands.setStatus(timeConverter(note.dateEdited), "Saved");
await commands.setStatus(getFormattedDate(note.dateEdited), "Saved");
lastContentChangeTime.current = note.dateEdited;
@@ -373,7 +378,7 @@ export const useEditor = (
commands.setSessionId(nextSessionId);
sessionIdRef.current = nextSessionId;
currentNote.current = item as NoteType;
await commands.setStatus(timeConverter(item.dateEdited), "Saved");
await commands.setStatus(getFormattedDate(item.dateEdited), "Saved");
await postMessage(EditorEvents.title, item.title);
await postMessage(
EditorEvents.html,
@@ -460,7 +465,7 @@ export const useEditor = (
if (note.tags !== currentNote.current.tags) {
await commands.setTags(note);
}
await commands.setStatus(timeConverter(note.dateEdited), "Saved");
await commands.setStatus(getFormattedDate(note.dateEdited), "Saved");
}
lock.current = false;

View File

@@ -35,7 +35,7 @@ import { useSettingStore } from "../../stores/use-setting-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { editorRef } from "../../utils/global-refs";
import { ProgressBar } from "./progress";
import { editorController, editorState, textInput } from "./tiptap/utils";
import { editorController, textInput } from "./tiptap/utils";
export const EditorWrapper = ({ width }) => {
const colors = useThemeStore((state) => state.colors);
const deviceMode = useSettingStore((state) => state.deviceMode);
@@ -48,7 +48,6 @@ export const EditorWrapper = ({ width }) => {
const keyboard = useKeyboard();
const onAppStateChanged = async (state) => {
if (editorState().movedAway) return;
if (state === "active") {
editorController.current.onReady();
editorController.current.overlay(false);

View File

@@ -86,7 +86,7 @@ export const setOnFirstSave = (
}, 0);
};
async function onNoteCreated(id: string, params: FirstSaveData) {
export async function onNoteCreated(id: string, params: FirstSaveData) {
if (!params) return;
switch (params.type) {
case "notebook": {

View File

@@ -28,7 +28,7 @@ import React, {
import { ActivityIndicator, Linking, Platform, View } from "react-native";
import { FlatList } from "react-native-gesture-handler";
import * as ScopedStorage from "react-native-scoped-storage";
import RNFetchBlob from "rn-fetch-blob";
import RNFetchBlob from "react-native-blob-util";
import { db } from "../../common/database";
import Storage from "../../common/database/storage";
import DialogHeader from "../../components/dialog/dialog-header";

View File

@@ -17,33 +17,36 @@ 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 React, { useState } from "react";
import { Dimensions, LayoutAnimation, Platform, View } from "react-native";
import React from "react";
import { Platform, View } from "react-native";
import Animated, { FadeInDown, FadeOutUp } from "react-native-reanimated";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { SVG_Z } from "../../components/intro";
import { WelcomeNotice } from "../../components/intro/welcome";
import { AuthMode } from "../../components/auth";
import { Button } from "../../components/ui/button";
import { PressableButton } from "../../components/ui/pressable";
import Seperator from "../../components/ui/seperator";
import { SvgView } from "../../components/ui/svg";
import { BouncingView } from "../../components/ui/transitions/bouncing-view";
import Heading from "../../components/ui/typography/heading";
import Paragraph from "../../components/ui/typography/paragraph";
import BiometicService from "../../services/biometrics";
import { DDS } from "../../services/device-detection";
import { presentSheet, ToastEvent } from "../../services/event-manager";
import {
ToastEvent,
eSendEvent,
presentSheet
} from "../../services/event-manager";
import Navigation from "../../services/navigation";
import SettingsService from "../../services/settings";
import { useSettingStore } from "../../stores/use-setting-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { useUserStore } from "../../stores/use-user-store";
import { getElevation } from "../../utils";
import { eOpenLoginDialog } from "../../utils/events";
import { SIZE } from "../../utils/size";
const AppLock = ({ route }) => {
const colors = useThemeStore((state) => state.colors);
const appLockMode = useSettingStore((state) => state.settings.appLockMode);
const [step, setStep] = useState(0);
const welcome = route?.params?.welcome;
const deviceMode = useSettingStore((state) => state.deviceMode);
const modes = [
{
@@ -76,13 +79,12 @@ const AppLock = ({ route }) => {
exiting={!welcome ? undefined : FadeOutUp}
entering={!welcome ? undefined : FadeInDown}
style={{
justifyContent: !welcome ? undefined : "center",
height: !welcome ? undefined : "100%",
width: !welcome ? undefined : "100%"
}}
>
{step === 0 ? (
<>
<>
{!welcome ? (
<View
style={{
flexDirection: "row",
@@ -125,121 +127,163 @@ const AppLock = ({ route }) => {
</Paragraph>
</View>
</View>
<Seperator />
) : (
<View
style={{
paddingHorizontal: 12,
width: DDS.isTab && welcome ? "50%" : "100%",
alignSelf: "center"
flexGrow: 1,
justifyContent: "flex-end",
paddingHorizontal: 20,
backgroundColor: colors.nav,
borderBottomWidth: 1,
borderBottomColor: colors.border,
alignSelf: deviceMode !== "mobile" ? "center" : undefined,
borderWidth: deviceMode !== "mobile" ? 1 : null,
borderColor: deviceMode !== "mobile" ? colors.border : null,
borderRadius: deviceMode !== "mobile" ? 20 : null,
marginTop: deviceMode !== "mobile" ? 50 : null,
width: deviceMode === "mobile" ? null : "50%"
}}
>
{modes.map((item) => (
<PressableButton
key={item.title}
type={appLockMode === item.value ? "grayBg" : "transparent"}
onPress={async () => {
if (
!(await BiometicService.isBiometryAvailable()) &&
!useUserStore.getState().user
) {
ToastEvent.show({
heading: "Biometrics not enrolled",
type: "error",
message:
"To use app lock, you must enable biometrics such as Fingerprint lock or Face ID on your phone or create an account."
});
return;
}
SettingsService.set({ appLockMode: item.value });
}}
customStyle={{
justifyContent: "flex-start",
alignItems: "flex-start",
paddingHorizontal: 12,
paddingVertical: 12,
marginTop: 0,
marginBottom: 12,
borderWidth: 1,
borderColor:
appLockMode === item.value ? item.activeColor : colors.nav
}}
<View
style={{
flexDirection: "row"
}}
>
<View
style={{
marginBottom: 10
width: 100,
height: 5,
backgroundColor: colors.accent,
borderRadius: 2,
marginRight: 7
}}
>
<Heading
color={
appLockMode === item.value ? item.activeColor : colors.pri
}
style={{ maxWidth: "95%" }}
size={SIZE.md}
>
{item.title}
</Heading>
<Paragraph
color={
appLockMode === item.value
? item.activeColor
: colors.icon
}
style={{ maxWidth: "95%" }}
size={SIZE.sm}
>
{item.desc}
</Paragraph>
</PressableButton>
))}
{welcome && (
<Button
fontSize={SIZE.md}
height={45}
width={250}
onPress={async () => {
LayoutAnimation.configureNext({
...LayoutAnimation.Presets.linear,
delete: {
duration: 50,
property: "opacity",
type: "linear"
}
});
setStep(1);
}}
style={{
paddingHorizontal: 24,
alignSelf: "center",
borderRadius: 100,
...getElevation(5),
marginTop: 30
}}
type="accent"
title="Next"
/>
)}
</View>
</>
) : (
<WelcomeNotice />
)}
{welcome && !colors.night ? (
<BouncingView
<View
style={{
width: 20,
height: 5,
backgroundColor: colors.nav,
borderRadius: 2
}}
/>
</View>
<Heading
style={{
marginTop: 10
}}
extraBold
size={SIZE.xxl}
>
Protect your notes
</Heading>
<Paragraph
style={{
marginBottom: 25
}}
>
Choose how you want to secure your notes locally.
</Paragraph>
</View>
)}
<Seperator />
<View
style={{
position: "absolute",
bottom: DDS.isTab ? -300 : -130,
zIndex: -1
paddingHorizontal: 12,
width: DDS.isTab && welcome ? "50%" : "100%",
alignSelf: "center",
flexGrow: 1
}}
animated={false}
duration={3000}
>
<SvgView
width={Dimensions.get("window").width}
height={Dimensions.get("window").width}
src={SVG_Z}
/>
</BouncingView>
) : null}
{modes.map((item) => (
<PressableButton
key={item.title}
type={appLockMode === item.value ? "grayBg" : "transparent"}
onPress={async () => {
if (
!(await BiometicService.isBiometryAvailable()) &&
!useUserStore.getState().user
) {
ToastEvent.show({
heading: "Biometrics not enrolled",
type: "error",
message:
"To use app lock, you must enable biometrics such as Fingerprint lock or Face ID on your phone or create an account."
});
return;
}
SettingsService.set({ appLockMode: item.value });
}}
customStyle={{
justifyContent: "flex-start",
alignItems: "flex-start",
paddingHorizontal: 12,
paddingVertical: 12,
marginTop: 0,
marginBottom: 12,
borderWidth: 1,
borderColor:
appLockMode === item.value ? item.activeColor : colors.nav
}}
style={{
marginBottom: 10
}}
>
<Heading
color={
appLockMode === item.value ? item.activeColor : colors.pri
}
style={{ maxWidth: "95%" }}
size={SIZE.md}
>
{item.title}
</Heading>
<Paragraph
color={
appLockMode === item.value ? item.activeColor : colors.icon
}
style={{ maxWidth: "95%" }}
size={SIZE.sm}
>
{item.desc}
</Paragraph>
</PressableButton>
))}
{welcome && (
<Button
fontSize={SIZE.md}
width={250}
onPress={async () => {
eSendEvent(eOpenLoginDialog, AuthMode.welcomeSignup);
setTimeout(() => {
SettingsService.set({
introCompleted: true
});
Navigation.replace(
{
name: "Notes"
},
{
canGoBack: false
}
);
}, 1000);
}}
style={{
paddingHorizontal: 24,
alignSelf: "center",
...getElevation(5),
marginTop: 30,
borderRadius: 100
}}
type="accent"
title="Next"
/>
)}
</View>
</>
</Animated.View>
</>
);

View File

@@ -27,6 +27,8 @@ import SoundPicker from "./sound-picker";
import { Subscription } from "./subscription";
import { TrashIntervalSelector } from "./trash-interval-selector";
import { FontSelector } from "./font-selector";
import { TitleFormat } from "./title-format";
import { DateFormatSelector, TimeFormatSelector } from "./date-format";
export const components: { [name: string]: ReactElement } = {
colorpicker: <AccentColorPicker />,
homeselector: <HomagePageSelector />,
@@ -37,5 +39,8 @@ export const components: { [name: string]: ReactElement } = {
"sound-picker": <SoundPicker />,
licenses: <Licenses />,
"trash-interval-selector": <TrashIntervalSelector />,
"font-selector": <FontSelector />
"font-selector": <FontSelector />,
"title-format": <TitleFormat />,
"date-format-selector": <DateFormatSelector />,
"time-format-selector": <TimeFormatSelector />
};

View File

@@ -0,0 +1,198 @@
/*
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 dayjs from "dayjs";
import React, { useRef, useState } from "react";
import { View } from "react-native";
import Menu, { MenuItem } from "react-native-reanimated-material-menu";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import { PressableButton } from "../../components/ui/pressable";
import Paragraph from "../../components/ui/typography/paragraph";
import { useThemeStore } from "../../stores/use-theme-store";
import { SIZE } from "../../utils/size";
import { DATE_FORMATS, TIME_FORMATS } from "@notesnook/core/common";
import { useSettingStore } from "../../stores/use-setting-store";
export const DateFormatSelector = () => {
const colors = useThemeStore((state) => state.colors);
const menuRef = useRef();
const [width, setWidth] = useState(0);
const [dateFormat, setDateFormat] = useState(db.settings.getDateFormat());
const onChange = (item) => {
menuRef.current?.hide();
db.settings.setDateFormat(item);
setDateFormat(item);
useSettingStore.setState({
dateFormat: item
});
};
return (
<View
onLayout={(event) => {
setWidth(event.nativeEvent.layout.width);
}}
style={{
width: "100%"
}}
>
<Menu
ref={menuRef}
animationDuration={200}
style={{
borderRadius: 5,
backgroundColor: colors.bg,
width: width,
marginTop: 60
}}
onRequestClose={() => {
menuRef.current?.hide();
}}
anchor={
<PressableButton
onPress={async () => {
menuRef.current?.show();
}}
type="grayBg"
customStyle={{
flexDirection: "row",
alignItems: "center",
marginTop: 10,
width: "100%",
justifyContent: "space-between",
padding: 12
}}
>
<Paragraph>
{dateFormat} ({dayjs().format(dateFormat)})
</Paragraph>
<Icon color={colors.icon} name="menu-down" size={SIZE.md} />
</PressableButton>
}
>
{DATE_FORMATS.map((item) => (
<MenuItem
key={item.id}
onPress={async () => {
onChange(item);
}}
style={{
backgroundColor: dateFormat === item ? colors.nav : "transparent",
width: "100%",
maxWidth: width
}}
textStyle={{
fontSize: SIZE.md,
color: dateFormat === item ? colors.accent : colors.pri
}}
>
{item} ({dayjs().format(item)})
</MenuItem>
))}
</Menu>
</View>
);
};
export const TimeFormatSelector = () => {
const colors = useThemeStore((state) => state.colors);
const menuRef = useRef();
const [width, setWidth] = useState(0);
const [timeFormat, setTimeFormat] = useState(db.settings.getTimeFormat());
const onChange = (item) => {
menuRef.current?.hide();
db.settings.setTimeFormat(item);
setTimeFormat(item);
useSettingStore.setState({
timeFormat: item
});
};
const TimeFormats = {
"12-hour": "hh:mm A",
"24-hour": "HH:mm"
};
return (
<View
onLayout={(event) => {
setWidth(event.nativeEvent.layout.width);
}}
style={{
width: "100%"
}}
>
<Menu
ref={menuRef}
animationDuration={200}
style={{
borderRadius: 5,
backgroundColor: colors.bg,
width: width,
marginTop: 60
}}
onRequestClose={() => {
menuRef.current?.hide();
}}
anchor={
<PressableButton
onPress={async () => {
menuRef.current?.show();
}}
type="grayBg"
customStyle={{
flexDirection: "row",
alignItems: "center",
marginTop: 10,
width: "100%",
justifyContent: "space-between",
padding: 12
}}
>
<Paragraph>
{timeFormat} ({dayjs().format(TimeFormats[timeFormat])})
</Paragraph>
<Icon color={colors.icon} name="menu-down" size={SIZE.md} />
</PressableButton>
}
>
{TIME_FORMATS.map((item) => (
<MenuItem
key={item.id}
onPress={async () => {
onChange(item);
}}
style={{
backgroundColor: timeFormat === item ? colors.nav : "transparent",
width: "100%",
maxWidth: width
}}
textStyle={{
fontSize: SIZE.md,
color: timeFormat === item ? colors.accent : colors.pri
}}
>
{item} ({dayjs().format(TimeFormats[item])})
</MenuItem>
))}
</Menu>
</View>
);
};

View File

@@ -18,12 +18,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Clipboard from "@react-native-clipboard/clipboard";
import { LogMessage } from "@streetwriters/logger";
import { LogMessage } from "@notesnook/logger";
import { format, LogLevel, logManager } from "@notesnook/core/logger";
import React, { useEffect, useState } from "react";
import { FlatList, Platform, TouchableOpacity, View } from "react-native";
import * as ScopedStorage from "react-native-scoped-storage";
import RNFetchBlob from "rn-fetch-blob";
import RNFetchBlob from "react-native-blob-util";
import Storage from "../../common/database/storage";
import { presentDialog } from "../../components/dialog/functions";
import { IconButton } from "../../components/ui/icon-button";

View File

@@ -268,10 +268,10 @@ export const LICENSES = [
link: "https://github.com/RocketChat/rn-extensions-share"
},
{
name: "rn-fetch-blob",
name: "react-native-blob-util",
licenseType: "MIT",
author: "Joltup",
link: "https://github.com/joltup/rn-fetch-blob"
author: "RonRadtke",
link: "https://github.com/RonRadtke/react-native-blob-util"
},
{
name: "react-native-gzip",

View File

@@ -22,8 +22,8 @@ import {
StackActions,
useNavigation
} from "@react-navigation/native";
import React, { useRef } from "react";
import { View, TextInput } from "react-native";
import React, { useRef, useState } from "react";
import { View, TextInput, ActivityIndicator } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import ToggleSwitch from "toggle-switch-react-native";
import Input from "../../components/ui/input";
@@ -46,10 +46,14 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
const current = item.useHook && item.useHook(item);
const isHidden = item.hidden && item.hidden(item.property || current);
const inputRef = useRef<TextInput>(null);
const [loading, setLoading] = useState(false);
const onChangeSettings = () => {
const onChangeSettings = async () => {
if (loading) return;
if (item.modifer) {
item.modifer(item.property || current);
setLoading(true);
await item.modifer(item.property || current);
setLoading(false);
return;
}
if (!item.property) return;
@@ -289,12 +293,12 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
</View>
</View>
{item.type === "switch" && item.property && (
{item.type === "switch" && !loading && (
<ToggleSwitch
isOn={
item.getter
? item.getter(item.property || current)
: settings[item.property]
: settings[item?.property as never]
}
onColor={colors.accent}
offColor={colors.icon}
@@ -303,6 +307,10 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
onToggle={onChangeSettings}
/>
)}
{loading ? (
<ActivityIndicator size={SIZE.xxl} color={colors.accent} />
) : null}
</PressableButton>
);
};

View File

@@ -524,6 +524,20 @@ export const settingsGroups: SettingSection[] = [
description: "Default screen to open on app startup",
component: "homeselector"
},
{
id: "date-format",
name: "Date format",
description: "Set the format for date used across the app",
type: "component",
component: "date-format-selector"
},
{
id: "time-format",
name: "Time format",
description: "Set the format for time used across the app",
type: "component",
component: "time-format-selector"
},
{
id: "clear-trash-interval",
type: "component",
@@ -531,6 +545,15 @@ export const settingsGroups: SettingSection[] = [
description:
"Select the duration after which trash items will be cleared",
component: "trash-interval-selector"
},
{
id: "default-notebook",
name: "Clear default notebook/topic",
description: "Clear the default notebook/topic for new notes",
modifer: () => {
db.settings?.setDefaultNotebook(undefined);
},
hidden: () => !db.settings?.getDefaultNotebook()
}
]
},
@@ -588,6 +611,13 @@ export const settingsGroups: SettingSection[] = [
icon: "format-font",
property: "defaultFontFamily",
component: "font-selector"
},
{
id: "title-format",
name: "Title format",
component: "title-format",
description: "Customize the formatting for new note title",
type: "component"
}
]
}
@@ -606,6 +636,26 @@ export const settingsGroups: SettingSection[] = [
"Contribute towards a better Notesnook. All tracking information is anonymous.",
property: "telemetry"
},
{
id: "marketing-emails",
type: "switch",
name: "Marketing emails",
description:
"We will send you occasional promotional offers & product updates on your email (sent once every month).",
modifer: async () => {
try {
await db.user?.changeMarketingConsent(
!useUserStore.getState().user?.marketingConsent
);
useUserStore.getState().setUser(await db.user?.fetchUser());
} catch (e) {
ToastEvent.error(e as Error);
}
},
getter: (current: any) => current?.marketingConsent,
useHook: () => useUserStore((state) => state.user),
hidden: (current) => !current
},
{
id: "cors-bypass",
type: "input",

View File

@@ -0,0 +1,61 @@
/*
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 { useRef, useState } from "react";
import { db } from "../../common/database";
import Input from "../../components/ui/input";
import React from "react";
import { TextInput } from "react-native";
import Paragraph from "../../components/ui/typography/paragraph";
import { useThemeStore } from "../../stores/use-theme-store";
import { SIZE } from "../../utils/size";
export const TitleFormat = () => {
const [titleFormat] = useState(db.settings?.getTitleFormat());
const inputRef = useRef<TextInput>();
const colors = useThemeStore((state) => state.colors);
return (
<>
<Input
onSubmit={(e) => {
db.settings?.setTitleFormat(e.nativeEvent.text);
}}
onChangeText={(text) => {
db.settings?.setTitleFormat(text);
}}
containerStyle={{ marginTop: 6 }}
onLayout={() => {
inputRef?.current?.setNativeProps({
text: titleFormat
});
}}
defaultValue={titleFormat}
/>
<Paragraph style={{ marginTop: 2 }} color={colors.icon} size={SIZE.xs}>
Use the following key to format the title:{"\n"}
{"\n"}
$date$: Current date{"\n"}
$time$: Current time{"\n"}
$count$: Number of notes + 1{"\n"}
$headline$: Use starting line of the note as title{"\n"}
</Paragraph>
</>
);
};

View File

@@ -0,0 +1,47 @@
/*
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 InAppReview from "react-native-in-app-review";
import { DatabaseLogger } from "../common/database";
import { MMKV } from "../common/database/mmkv";
import Config from "react-native-config";
import { useUserStore } from "../stores/use-user-store";
const day_ms = 86400000;
export function requestInAppReview() {
if (Config.GITHUB_RELEASE === "true") return;
const time = MMKV.getMap<{ timestamp: number }>("requestInAppReview");
if (time?.timestamp && time?.timestamp + day_ms * 7 > Date.now()) {
return;
}
if (InAppReview.isAvailable()) {
useUserStore.getState().setShouldBlockVerifyUser(true);
InAppReview.RequestInAppReview()
.then(() => {})
.catch((error) => {
DatabaseLogger.error(error);
});
MMKV.setMap("requestInAppReview", { timestamp: Date.now() });
} else {
DatabaseLogger.error(new Error("In App Review not available"));
}
}

View File

@@ -21,7 +21,7 @@ import { Platform } from "react-native";
import FileViewer from "react-native-file-viewer";
import * as ScopedStorage from "react-native-scoped-storage";
import Share from "react-native-share";
import RNFetchBlob from "rn-fetch-blob";
import RNFetchBlob from "react-native-blob-util";
import { presentDialog } from "../components/dialog/functions";
import { DatabaseLogger, db } from "../common/database";
import storage from "../common/database/storage";

View File

@@ -22,7 +22,7 @@ import { zipSync } from "fflate";
import { Platform } from "react-native";
import RNHTMLtoPDF from "react-native-html-to-pdf-lite";
import * as ScopedStorage from "react-native-scoped-storage";
import RNFetchBlob from "rn-fetch-blob";
import RNFetchBlob from "react-native-blob-util";
import { DatabaseLogger, db } from "../common/database/index";
import Storage from "../common/database/storage";
import { toTXT } from "../utils";

View File

@@ -154,9 +154,11 @@ const onUserStatusCheck = async (type) => {
};
break;
case CHECK_IDS.notebookAdd:
setTimeout(() => {
eSendEvent(eOpenPremiumDialog);
}, 500);
message = {
context: "sheet",
title: "Get Notesnook Pro",
desc: "With Notesnook Pro you can create unlimited notebooks and do so much more! Get it now."
};
break;
case CHECK_IDS.vaultAdd:
message = {
@@ -177,7 +179,6 @@ const onUserStatusCheck = async (type) => {
const showVerifyEmailDialog = () => {
presentSheet({
title: "Confirm your email",
icon: "email",
paragraph:
"We have sent you an email confirmation link. Please check your email inbox. If you cannot find the email, check your spam folder.",
action: async () => {
@@ -367,7 +368,7 @@ const features_list = [
"Rich note editing experience with markdown, tables, checklists and more"
},
{
content: "Export your notes in Pdf, markdown and html formats"
content: "Export your notes in PDF, markdown and html formats"
}
];
@@ -386,7 +387,7 @@ const sheet = (context, promo, trial) => {
<Seperator />
<CompactFeatures
scrollRef={ref}
maxHeight={300}
maxHeight={400}
features={features_list}
vertical
/>

View File

@@ -106,6 +106,8 @@ export interface SettingStore extends State {
setRequestBiometrics: (requestBiometrics: boolean) => void;
insets: Insets;
setInsets: (insets: Insets) => void;
timeFormat: string;
dateFormat: string;
}
const { width, height } = Dimensions.get("window");
@@ -170,6 +172,8 @@ export const useSettingStore = create<SettingStore>((set) => ({
requestBiometrics: false,
setRequestBiometrics: (requestBiometrics) => set({ requestBiometrics }),
setInsets: (insets) => set({ insets }),
timeFormat: "12-hour",
dateFormat: "DD-MM-YYYY",
insets: initialWindowMetrics?.insets
? initialWindowMetrics.insets
: { top: 0, right: 0, left: 0, bottom: 0 }

View File

@@ -37,6 +37,8 @@ export interface UserStore extends State {
setLastSynced: (lastSynced: string) => void;
verifyUser: boolean;
setVerifyUser: (verified: boolean) => void;
shouldBlockVerifyUser: boolean;
setShouldBlockVerifyUser: (shouldBlockVerifyUser: boolean) => void;
}
export const useUserStore = create<UserStore>((set) => ({
@@ -52,5 +54,12 @@ export const useUserStore = create<UserStore>((set) => ({
},
setLastSynced: (lastSynced) => set({ lastSynced: lastSynced }),
setVerifyUser: (verified) => set({ verifyUser: verified }),
lastSyncStatus: SyncStatus.Never
lastSyncStatus: SyncStatus.Never,
shouldBlockVerifyUser: false,
setShouldBlockVerifyUser: (shouldBlockVerifyUser) => {
set({ shouldBlockVerifyUser });
setTimeout(() => {
set({ shouldBlockVerifyUser: false });
}, 1000);
}
}));

View File

@@ -18,7 +18,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"
@@ -139,6 +142,7 @@ export const MenuItemsList = [
icon: "text-box-multiple-outline",
close: true,
func: () => {
const Monographs = require("../screens/notes/monographs").Monographs;
Monographs.navigate();
}
},

View File

@@ -0,0 +1,122 @@
/*
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
});
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

@@ -17,6 +17,10 @@ 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 { formatDate } from "@notesnook/core/utils/date";
import { db } from "../../common/database";
import { formatReminderTime } from "@notesnook/core/collections/reminders";
export const sleep = (duration: number) =>
new Promise((resolve) => setTimeout(() => resolve(true), duration));
@@ -107,3 +111,21 @@ export const timeConverter = (timestamp: number | undefined | null) => {
return time;
};
export function getFormattedDate(
date: any,
type: "time" | "date-time" | "date" = "date-time"
) {
return formatDate(date, {
dateFormat: db.settings?.getDateFormat() as string,
timeFormat: db.settings?.getTimeFormat() as string,
type: type
});
}
export function getFormattedReminderTime(reminder: any, short = false) {
return formatReminderTime(reminder, short, {
dateFormat: db.settings?.getDateFormat() as string,
timeFormat: db.settings?.getTimeFormat() as string
});
}

View File

@@ -164,7 +164,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled true
versionCode 2047
versionCode 2050
versionName getNpmVersion()
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
@@ -276,6 +276,13 @@ android {
}
}
packagingOptions {
pickFirst 'lib/x86/libc++_shared.so'
pickFirst 'lib/x86_64/libc++_shared.so'
pickFirst 'lib/armeabi-v7a/libc++_shared.so'
pickFirst 'lib/arm64-v8a/libc++_shared.so'
}
// applicationVariants are e.g. debug, release
applicationVariants.all { variant ->
variant.outputs.each { output ->

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

@@ -73,4 +73,6 @@ allprojects {
// url("${project(':react-native-background-fetch').projectDir}/libs")
// }
}
}

View File

@@ -1,4 +1,6 @@
- Fix note not saved from share widget when offline
- Do not allow insecure http requests from app
- You can now share images and files from other apps to Notesnook and sync them instantly.
- Change date and time format across the app
- Change default note title for new notes using different templates
- Set a default notebook or topic to add new notes to automatically
Thank you for using Notesnook!

View File

@@ -33,10 +33,16 @@
<dict>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsFileWithMaxCount</key>
<integer>5</integer>
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
<integer>5</integer>
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
<integer>5</integer>
<key>NSExtensionActivationSupportsText</key>
<true/>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>10</integer>
<integer>1</integer>
</dict>
</dict>
<key>NSExtensionMainStoryboard</key>
@@ -46,6 +52,7 @@
</dict>
<key>UIAppFonts</key>
<array>
<string>OpenSans-Bold.ttf</string>
<string>OpenSans-SemiBold.ttf</string>
<string>OpenSans-Regular.ttf</string>
<string>MaterialCommunityIcons.ttf</string>
@@ -57,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

@@ -23,6 +23,8 @@
6515C42F2580AA3000E83E39 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6515C42E2580AA2F00E83E39 /* StoreKit.framework */; };
6529A13E279BC4C70048D4A8 /* BootSplash.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6529A13D279BC4C70048D4A8 /* BootSplash.storyboard */; };
6593E4A3281C345400492C50 /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6593E4A2281C345400492C50 /* AppDelegate.mm */; };
659670B22A2754FD00C5D2AF /* OpenSans-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 659670B12A2754FD00C5D2AF /* OpenSans-Bold.ttf */; };
659670B32A2754FD00C5D2AF /* OpenSans-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 659670B12A2754FD00C5D2AF /* OpenSans-Bold.ttf */; };
659BE46725E11A5100E05671 /* notesnook-text.png in Resources */ = {isa = PBXBuildFile; fileRef = 659BE46625E11A5100E05671 /* notesnook-text.png */; };
65AA857925E6DDEC00772A01 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65AA857825E6DDEC00772A01 /* WidgetKit.framework */; };
65AA857B25E6DDEC00772A01 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65AA857A25E6DDEC00772A01 /* SwiftUI.framework */; };
@@ -116,6 +118,7 @@
6552012E27019F6E00A43C51 /* OpenSans-SemiBold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "OpenSans-SemiBold.ttf"; path = "../android/app/src/main/assets/fonts/OpenSans-SemiBold.ttf"; sourceTree = "<group>"; };
6552012F27019F7700A43C51 /* OpenSans-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "OpenSans-Regular.ttf"; path = "../android/app/src/main/assets/fonts/OpenSans-Regular.ttf"; sourceTree = "<group>"; };
6593E4A2281C345400492C50 /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = Notesnook/AppDelegate.mm; sourceTree = "<group>"; };
659670B12A2754FD00C5D2AF /* OpenSans-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "OpenSans-Bold.ttf"; path = "../android/app/src/main/assets/fonts/OpenSans-Bold.ttf"; sourceTree = "<group>"; };
659BE46625E11A5100E05671 /* notesnook-text.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "notesnook-text.png"; path = "Notesnook/Images.xcassets/notesnook-text.png"; sourceTree = "<group>"; };
65A7F34F255689AD00699170 /* Notesnook.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Notesnook.entitlements; path = Notesnook/Notesnook.entitlements; sourceTree = "<group>"; };
65AA857725E6DDEC00772A01 /* NotesWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotesWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -294,6 +297,7 @@
6510E6D62877215700DACAA9 /* build.bundle */,
65EC5B71272A7EE200FB3748 /* NotesWidgetExtensionDebug.entitlements */,
6552012F27019F7700A43C51 /* OpenSans-Regular.ttf */,
659670B12A2754FD00C5D2AF /* OpenSans-Bold.ttf */,
6552012E27019F6E00A43C51 /* OpenSans-SemiBold.ttf */,
65D145C429DC30470056FE7D /* MaterialCommunityIcons.ttf */,
13B07FAE1A68108700A75B9A /* Notesnook */,
@@ -538,6 +542,7 @@
files = (
6510626F27042891009661C3 /* OpenSans-Regular.ttf in Resources */,
65D145C529DC30470056FE7D /* MaterialCommunityIcons.ttf in Resources */,
659670B22A2754FD00C5D2AF /* OpenSans-Bold.ttf in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
6529A13E279BC4C70048D4A8 /* BootSplash.storyboard in Resources */,
6510E6D72877215700DACAA9 /* build.bundle in Resources */,
@@ -575,6 +580,7 @@
files = (
6510E6D82877215700DACAA9 /* build.bundle in Resources */,
65D145C629DC30470056FE7D /* MaterialCommunityIcons.ttf in Resources */,
659670B32A2754FD00C5D2AF /* OpenSans-Bold.ttf in Resources */,
6510627727042896009661C3 /* OpenSans-SemiBold.ttf in Resources */,
6510627527042893009661C3 /* OpenSans-Regular.ttf in Resources */,
65B5014725A672B200E2D264 /* MainInterface.storyboard in Resources */,
@@ -993,7 +999,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2039;
CURRENT_PROJECT_VERSION = 2042;
DEVELOPMENT_TEAM = 53CWBG3QUC;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1067,7 +1073,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.4.16;
MARKETING_VERSION = 2.5.1;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -1097,7 +1103,7 @@
CODE_SIGN_ENTITLEMENTS = Notesnook/Notesnook.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2039;
CURRENT_PROJECT_VERSION = 2042;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
@@ -1170,7 +1176,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.4.16;
MARKETING_VERSION = 2.5.1;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = (
"$(inherited)",
@@ -1328,7 +1334,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2039;
CURRENT_PROJECT_VERSION = 2042;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1340,7 +1346,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.4.16;
MARKETING_VERSION = 2.5.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
@@ -1370,7 +1376,7 @@
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2039;
CURRENT_PROJECT_VERSION = 2042;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1382,7 +1388,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.4.16;
MARKETING_VERSION = 2.5.1;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1411,7 +1417,7 @@
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2039;
CURRENT_PROJECT_VERSION = 2042;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1485,7 +1491,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.4.16;
MARKETING_VERSION = 2.5.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
@@ -1515,7 +1521,7 @@
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2039;
CURRENT_PROJECT_VERSION = 2042;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1589,7 +1595,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.4.16;
MARKETING_VERSION = 2.5.1;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
PRODUCT_NAME = "$(TARGET_NAME)";

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

@@ -1,8 +1,11 @@
PODS:
- Base64 (1.1.2)
- BEMCheckBox (1.4.1)
- boost (1.76.0)
- callstack-repack (3.0.0):
- callstack-repack (3.2.0):
- JWTDecode (~> 3.0)
- React-Core
- SwiftyRSA
- DoubleConversion (1.1.6)
- FBLazyVector (0.69.7)
- FBReactNativeSpec (0.69.7):
@@ -15,6 +18,7 @@ PODS:
- fmt (6.2.1)
- glog (0.3.5)
- GZIP (1.3.0)
- JWTDecode (3.0.1)
- MMKV (1.2.13):
- MMKVCore (~> 1.2.13)
- MMKVCore (1.2.13)
@@ -234,9 +238,11 @@ PODS:
- React-Core
- react-native-begin-background-task (0.1.0):
- React
- react-native-config (1.4.11):
- react-native-config/App (= 1.4.11)
- react-native-config/App (1.4.11):
- react-native-blob-util (0.17.3):
- React-Core
- react-native-config (1.5.1):
- react-native-config/App (= 1.5.1)
- react-native-config/App (1.5.1):
- React-Core
- react-native-date-picker (4.2.6):
- React-Core
@@ -244,7 +250,7 @@ PODS:
- React-Core
- react-native-fingerprint-scanner (5.0.0):
- React-Core
- react-native-get-random-values (1.8.0):
- react-native-get-random-values (1.9.0):
- React-Core
- react-native-gzip (1.0.0):
- Base64
@@ -256,26 +262,32 @@ PODS:
- React-Core
- react-native-image-resizer (3.0.5):
- React-Core
- react-native-in-app-review (4.3.3):
- React-Core
- react-native-keep-awake (1.1.0):
- React-Core
- react-native-mmkv-storage (0.8.0):
- MMKV (= 1.2.13)
- React-Core
- react-native-netinfo (9.3.7):
- react-native-netinfo (9.3.10):
- React-Core
- react-native-notification-sounds (0.5.5):
- React
- react-native-orientation (3.1.3):
- React
- react-native-safe-area-context (4.4.1):
- react-native-pdf (6.6.2):
- React-Core
- react-native-safe-area-context (4.5.3):
- RCT-Folly
- RCTRequired
- RCTTypeSafety
- React-Core
- ReactCommon/turbomodule/core
- react-native-sodium (1.3.0):
- react-native-share-extension (2.5.5):
- React
- react-native-webview (11.23.1):
- react-native-sodium (1.4.1):
- React
- react-native-webview (11.26.1):
- React-Core
- React-perflogger (0.69.7)
- React-RCTActionSheet (0.69.7):
@@ -343,17 +355,14 @@ PODS:
- React-jsi (= 0.69.7)
- React-logger (= 0.69.7)
- React-perflogger (= 0.69.7)
- rn-extensions-share (2.4.0):
- RNBootSplash (4.1.4):
- React-Core
- rn-fetch-blob (0.12.0):
- RNCCheckbox (0.5.15):
- BEMCheckBox (~> 1.4)
- React-Core
- RNBootSplash (4.3.2):
- RNCClipboard (1.11.2):
- React-Core
- RNCCheckbox (0.5.12):
- React-Core
- RNCClipboard (1.11.1):
- React-Core
- RNCMaskedView (0.2.8):
- RNCMaskedView (0.2.9):
- React-Core
- RNDateTimePicker (6.6.0):
- React-Core
@@ -365,7 +374,7 @@ PODS:
- React-Core
- RNFlashList (1.4.0):
- React-Core
- RNGestureHandler (2.7.1):
- RNGestureHandler (2.10.1):
- React-Core
- RNIap (7.5.6):
- React-Core
@@ -405,21 +414,32 @@ PODS:
- React-RCTText
- ReactCommon/turbomodule/core
- Yoga
- RNScreens (3.18.2):
- RNScreens (3.20.0):
- React-Core
- React-RCTImage
- RNSecureRandom (1.0.1):
- React
- RNShare (7.9.1):
- React-Core
- RNSVG (12.4.4):
- RNSVG (12.5.1):
- React-Core
- RNTooltips (1.0.3):
- pop (~> 1.0)
- React
- SexyTooltip
- RNZipArchive (6.0.9):
- React-Core
- RNZipArchive/Core (= 6.0.9)
- SSZipArchive (~> 2.2)
- RNZipArchive/Core (6.0.9):
- React-Core
- SSZipArchive (~> 2.2)
- SexyTooltip (1.2.5):
- pop (~> 1.0)
- SSZipArchive (2.4.3)
- SwiftyRSA (1.7.0):
- SwiftyRSA/ObjC (= 1.7.0)
- SwiftyRSA/ObjC (1.7.0)
- toolbar-android (0.2.1):
- React
- Yoga (1.14.0)
@@ -451,6 +471,7 @@ DEPENDENCIES:
- react-native-actions-shortcuts (from `../../node_modules/react-native-actions-shortcuts`)
- react-native-background-actions (from `../../node_modules/react-native-background-actions`)
- react-native-begin-background-task (from `../../node_modules/react-native-begin-background-task`)
- react-native-blob-util (from `../../node_modules/react-native-blob-util`)
- react-native-config (from `../../node_modules/react-native-config`)
- react-native-date-picker (from `../../node_modules/react-native-date-picker`)
- react-native-document-picker (from `../../node_modules/react-native-document-picker`)
@@ -460,12 +481,15 @@ DEPENDENCIES:
- 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`)"
- react-native-mmkv-storage (from `../../node_modules/react-native-mmkv-storage`)
- "react-native-netinfo (from `../../node_modules/@react-native-community/netinfo`)"
- react-native-notification-sounds (from `../../node_modules/react-native-notification-sounds`)
- 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`)
@@ -480,8 +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`)
- rn-fetch-blob (from `../../node_modules/rn-fetch-blob`)
- RNBootSplash (from `../../node_modules/react-native-bootsplash`)
- "RNCCheckbox (from `../../node_modules/@react-native-community/checkbox`)"
- "RNCClipboard (from `../../node_modules/@react-native-clipboard/clipboard`)"
@@ -502,6 +524,7 @@ DEPENDENCIES:
- RNShare (from `../../node_modules/react-native-share`)
- RNSVG (from `../../node_modules/react-native-svg`)
- RNTooltips (from `../../node_modules/react-native-tooltips`)
- RNZipArchive (from `../../node_modules/react-native-zip-archive`)
- SexyTooltip (from `https://github.com/ammarahm-ed/SexyTooltip.git`)
- "toolbar-android (from `../../node_modules/@react-native-community/toolbar-android`)"
- Yoga (from `../../node_modules/react-native/ReactCommon/yoga`)
@@ -509,11 +532,15 @@ DEPENDENCIES:
SPEC REPOS:
trunk:
- Base64
- BEMCheckBox
- fmt
- GZIP
- JWTDecode
- MMKV
- MMKVCore
- pop
- SSZipArchive
- SwiftyRSA
EXTERNAL SOURCES:
boost:
@@ -562,6 +589,8 @@ EXTERNAL SOURCES:
:path: "../../node_modules/react-native-background-actions"
react-native-begin-background-task:
:path: "../../node_modules/react-native-begin-background-task"
react-native-blob-util:
:path: "../../node_modules/react-native-blob-util"
react-native-config:
:path: "../../node_modules/react-native-config"
react-native-date-picker:
@@ -580,6 +609,8 @@ EXTERNAL SOURCES:
: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:
:path: "../../node_modules/react-native-in-app-review"
react-native-keep-awake:
:path: "../../node_modules/@sayem314/react-native-keep-awake"
react-native-mmkv-storage:
@@ -590,8 +621,12 @@ EXTERNAL SOURCES:
:path: "../../node_modules/react-native-notification-sounds"
react-native-orientation:
:path: "../../node_modules/react-native-orientation"
react-native-pdf:
: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:
@@ -620,10 +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"
rn-fetch-blob:
:path: "../../node_modules/rn-fetch-blob"
RNBootSplash:
:path: "../../node_modules/react-native-bootsplash"
RNCCheckbox:
@@ -664,6 +695,8 @@ EXTERNAL SOURCES:
:path: "../../node_modules/react-native-svg"
RNTooltips:
:path: "../../node_modules/react-native-tooltips"
RNZipArchive:
:path: "../../node_modules/react-native-zip-archive"
SexyTooltip:
:git: https://github.com/ammarahm-ed/SexyTooltip.git
toolbar-android:
@@ -678,14 +711,16 @@ CHECKOUT OPTIONS:
SPEC CHECKSUMS:
Base64: cecfb41a004124895a7bcee567a89bae5a89d49b
BEMCheckBox: 5ba6e37ade3d3657b36caecc35c8b75c6c2b1a4e
boost: a7c83b31436843459a1961bfd74b96033dc77234
callstack-repack: 9e5425dfffeda7ea87b71729c4097141c7d8ce1c
callstack-repack: 3e48a96824e0e0411ae1f48749a2ab103aa62a3a
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
FBLazyVector: 6b7f5692909b4300d50e7359cdefbcd09dd30faa
FBReactNativeSpec: f53cf57758c70c6bfba5230b739cd1071e7a6824
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: 3d02b25ca00c2d456734d0bcff864cbc62f6ae1a
GZIP: 416858efbe66b41b206895ac6dfd5493200d95b3
JWTDecode: 2eed97c2fa46ccaf3049a787004eedf0be474a87
MMKV: aac95d817a100479445633f2b3ed8961b4ac5043
MMKVCore: 3388952ded307e41b3ed8a05892736a236ed1b8e
pop: d582054913807fd11fd50bfe6a539d91c7e1a55a
@@ -706,23 +741,27 @@ SPEC CHECKSUMS:
react-native-actions-shortcuts: 5d9cf0c9c308333dfcc1e05c3f9afa8c428e2533
react-native-background-actions: 2c251c986f23347f9c1722f05fd296938f60edb1
react-native-begin-background-task: 3b889e07458afc5822a7277cf9cbc7cd077e39ee
react-native-config: bcafda5b4c51491ee1b0e1d0c4e3905bc7b56c1b
react-native-blob-util: 99f4d79189252f597fe0d810c57a3733b1b1dea6
react-native-config: 86038147314e2e6d10ea9972022aa171e6b1d4d8
react-native-date-picker: 93e43b3084cea595b4d68b1405d6d99849663bd6
react-native-document-picker: ec07866a30707f23660c0f3ae591d669d3e89096
react-native-fingerprint-scanner: be63e626b31fb951780a5fac5328b065a61a3d6e
react-native-get-random-values: a6ea6a8a65dc93e96e24a11105b1a9c8cfe1d72a
react-native-get-random-values: dee677497c6a740b71e5612e8dbd83e7539ed5bb
react-native-gzip: 02f9968afa759e189f0414d41f8f4a951a86b4f1
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: acbee258db16483744910f0da3ace39eb9ab47fd
react-native-mmkv-storage: 8ba3c0216a6df283ece11205b442a3e435aec4e5
react-native-netinfo: 2517ad504b3d303e90d7a431b0fcaef76d207983
react-native-netinfo: ccbe1085dffd16592791d550189772e13bf479e2
react-native-notification-sounds: da78c828fe1bcbb92d8b505d5261890ed315ff39
react-native-orientation: f1caf84d65f1a4fd4511a18f2b924e634ad7a628
react-native-safe-area-context: 99b24a0c5acd0d5dcac2b1a7f18c49ea317be99a
react-native-sodium: 1681828855ec18fa952f4557cd595bf048cf5c32
react-native-webview: d33e2db8925d090871ffeb232dfa50cb3a727581
react-native-pdf: 33c622cbdf776a649929e8b9d1ce2d313347c4fa
react-native-safe-area-context: b8979f5eda6ed5903d4dbc885be3846ea3daa753
react-native-share-extension: 8fa247810cc533c9d3bab6913f62ce7aec9bcf4a
react-native-sodium: f4e3986ddcb73482f8679e534b448a0675d0cf13
react-native-webview: 9f111dfbcfc826084d6c507f569e5e03342ee1c1
React-perflogger: 8e832d4e21fdfa613033c76d58d7e617341e804b
React-RCTActionSheet: 9ca778182a9523991bff6381045885b6e808bb73
React-RCTAnimation: 9ced26ad20b96e532ac791a8ab92a7b1ce2266b8
@@ -735,29 +774,30 @@ SPEC CHECKSUMS:
React-RCTVibration: 600a9f8b3537db360563d50fab3d040c262567d4
React-runtimeexecutor: 65cd2782a57e1d59a68aa5d504edf94278578e41
ReactCommon: 1e783348b9aa73ae68236271df972ba898560a95
rn-extensions-share: 3f0ecce20dfbca1f0358deb4ebfb9ee121a6d92a
rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba
RNBootSplash: 5f346163977573d6b2aeba1b25df9d2245c0d73c
RNCCheckbox: ed1b4ca295475b41e7251ebae046360a703b6eb5
RNCClipboard: 2834e1c4af68697089cdd455ee4a4cdd198fa7dd
RNCMaskedView: bc0170f389056201c82a55e242e5d90070e18e5a
RNBootSplash: de2c568373a9c79a66e9918b8929eb6c9a35246f
RNCCheckbox: 43bcc6493611468af0e19f19f029dab3da8561c4
RNCClipboard: 3f0451a8100393908bea5c5c5b16f96d45f30bfc
RNCMaskedView: 949696f25ec596bfc697fc88e6f95cf0c79669b6
RNDateTimePicker: 818672afa85519722533d017b832ed09539d9ddb
RNDeviceInfo: aad3c663b25752a52bf8fce93f2354001dd185aa
RNExitApp: c4e052df2568b43bec8a37c7cd61194d4cfee2c3
RNFileViewer: ce7ca3ac370e18554d35d6355cffd7c30437c592
RNFlashList: 399bf6a0db68f594ad2c86aaff3ea39564f39f8a
RNGestureHandler: b7a872907ee289ada902127f2554fa1d2c076122
RNGestureHandler: 42ec7c28dd02d540ed6c9159c57a98ff016492dc
RNIap: d248609d1b8937e63bd904e865c318e9b1457eff
RNKeychain: 840f8e6f13be0576202aefcdffd26a4f54bfe7b5
RNNotifee: 2ae3c18196e6f307fa62ae5c8e5305dea03ff147
RNPrivacySnapshot: 8eaf571478a353f2e5184f5c803164f22428b023
RNReanimated: f1b109fb8341505ace9d7d2eedd150da1686716b
RNScreens: 34cc502acf1b916c582c60003dc3089fa01dc66d
RNScreens: 218801c16a2782546d30bd2026bb625c0302d70f
RNSecureRandom: 07efbdf2cd99efe13497433668e54acd7df49fef
RNShare: a5dc3b9c53ddc73e155b8cd9a94c70c91913c43c
RNSVG: ecd661f380a07ba690c9c5929c475a44f432d674
RNSVG: d7d7bc8229af3842c9cfc3a723c815a52cdd1105
RNTooltips: 5424d4bf0b3d441104127943b1115cc7f0616b1f
RNZipArchive: 68a0c6db4b1c103f846f1559622050df254a3ade
SexyTooltip: 5c9b4dec52bfb317938cb0488efd9da3717bb6fd
SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef
SwiftyRSA: 8c6dd1ea7db1b8dc4fb517a202f88bb1354bc2c6
toolbar-android: 2a73856e98b750d7e71ce4644d3f41cc98211719
Yoga: 0b84a956f7393ef1f37f3bb213c516184e4a689d

View File

@@ -5,7 +5,7 @@
"main": "index.js",
"license": "GPL-3.0-or-later",
"dependencies": {
"@callstack/repack": "^3.0.0",
"@callstack/repack": "^3.2.0",
"@react-native-clipboard/clipboard": "^1.9.0",
"@react-native-community/checkbox": "^0.5.8",
"@react-native-community/netinfo": "^9.3.7",
@@ -20,7 +20,7 @@
"react-native": "0.69.7",
"react-native-background-actions": "^2.6.6",
"react-native-begin-background-task": "https://github.com/blockfirm/react-native-begin-background-task.git",
"react-native-bootsplash": "^4.1.4",
"react-native-bootsplash": "4.1.4",
"react-native-config": "^1.4.6",
"react-native-device-info": "^8.4.1",
"react-native-document-picker": "^7.1.1",
@@ -43,13 +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-vector-icons": "^9.0.0",
"react-native-webview": "^11.14.1",
"rn-extensions-share": "^2.4.0",
"rn-fetch-blob": "^0.12.0",
"react-native-gzip":"1.0.0",
"@shopify/flash-list":"1.4.0",
"@ammarahmed/notifee-react-native": "7.4.4",
@@ -59,7 +56,13 @@
"react-native-notification-sounds": "0.5.5",
"@bam.tech/react-native-image-resizer": "3.0.5",
"react-native-navigation-bar-color": "2.0.2",
"react-native-actions-shortcuts": "^1.0.1"
"react-native-actions-shortcuts": "^1.0.1",
"react-native-zip-archive": "6.0.9",
"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.5",
"react-native-in-app-review": "4.3.3"
},
"devDependencies": {
"@babel/core": "^7.12.9",
@@ -98,6 +101,7 @@
"react-test-renderer": "18.0.0",
"terser-webpack-plugin": "^5.3.5",
"ts-jest": "^28.0.7",
"webpack": "^5.74.0"
"webpack": "^5.74.0",
"react-refresh": "0.14.0"
}
}

View File

@@ -22,6 +22,11 @@ if (isGithubRelease) {
android:null
}
}
config.dependencies["react-native-in-app-review"] = {
platforms: {
android: null
}
}
}
module.exports = config;

View File

@@ -1,4 +1,6 @@
- Fix note not saved from share widget when offline
- Do not allow insecure http requests from app
- Change date and time format across the app
- Change default note title for new notes using different templates
- Set a default notebook or topic to add new notes to automatically
- You can now share images and files from other apps to Notesnook and sync them instantly.
Thank you for using Notesnook!

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@notesnook/mobile",
"version": "2.4.16",
"version": "2.5.1",
"private": true,
"license": "GPL-3.0-or-later",
"workspaces": [
@@ -25,13 +25,16 @@
"devDependencies": {
"patch-package": "^6.4.7",
"typescript": "^4.8.2",
"otplib": "12.0.1"
"otplib": "12.0.1",
"react-refresh": "0.14.0"
},
"dependencies": {
"react": "18.0.0",
"react-native": "0.69.7",
"@notesnook/core": "*",
"@notesnook/editor": "*",
"@notesnook/editor-mobile": "*"
"@notesnook/editor-mobile": "*",
"react-native-swiper-flatlist": "3.2.2",
"@notesnook/logger": "*"
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,103 +0,0 @@
diff --git a/node_modules/rn-fetch-blob/android/build.gradle b/node_modules/rn-fetch-blob/android/build.gradle
index a4ca7a4..4fd3cfa 100644
--- a/node_modules/rn-fetch-blob/android/build.gradle
+++ b/node_modules/rn-fetch-blob/android/build.gradle
@@ -41,6 +41,7 @@ android {
dependencies {
implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}"
- //compile 'com.squareup.okhttp3:okhttp:+'
+ implementation 'com.squareup.okhttp3:okhttp:3.4.1'
+
//{RNFetchBlob_PRE_0.28_DEPDENDENCY}
}
diff --git a/node_modules/rn-fetch-blob/react-native.config.js b/node_modules/rn-fetch-blob/react-native.config.js
deleted file mode 100644
index 03c61b6..0000000
--- a/node_modules/rn-fetch-blob/react-native.config.js
+++ /dev/null
@@ -1,7 +0,0 @@
-module.exports = {
- dependency: {
- hooks: {
- prelink: 'node ./node_modules/rn-fetch-blob/scripts/prelink.js',
- },
- },
-};
diff --git a/node_modules/rn-fetch-blob/scripts/prelink.js b/node_modules/rn-fetch-blob/scripts/prelink.js
deleted file mode 100644
index e2c3ac4..0000000
--- a/node_modules/rn-fetch-blob/scripts/prelink.js
+++ /dev/null
@@ -1,71 +0,0 @@
-try {
- var fs = require('fs');
- var glob = require('glob');
- var addAndroidPermissions = process.env.RNFB_ANDROID_PERMISSIONS == 'true';
- var MANIFEST_PATH = glob.sync(process.cwd() + '/android/app/src/main/**/AndroidManifest.xml')[0];
- var PACKAGE_JSON = process.cwd() + '/package.json';
- var package = JSON.parse(fs.readFileSync(PACKAGE_JSON));
- var APP_NAME = package.name;
- var PACKAGE_GRADLE = process.cwd() + '/node_modules/rn-fetch-blob/android/build.gradle'
- var VERSION = checkVersion();
-
- console.log('RNFetchBlob detected app version => ' + VERSION);
-
- if(VERSION < 0.28) {
- console.log('You project version is '+ VERSION + ' which may not compatible to rn-fetch-blob 7.0+, please consider upgrade your application template to react-native 0.27+.')
- // add OkHttp3 dependency fo pre 0.28 project
- var main = fs.readFileSync(PACKAGE_GRADLE);
- console.log('adding OkHttp3 dependency to pre 0.28 project .. ')
- main = String(main).replace('//{RNFetchBlob_PRE_0.28_DEPDENDENCY}', "compile 'com.squareup.okhttp3:okhttp:3.4.1'");
- fs.writeFileSync(PACKAGE_GRADLE, main);
- console.log('adding OkHttp3 dependency to pre 0.28 project .. ok')
- }
-
- console.log('Add Android permissions => ' + (addAndroidPermissions == "true"))
-
- if(addAndroidPermissions) {
-
- // set file access permission for Android < 6.0
- fs.readFile(MANIFEST_PATH, function(err, data) {
-
- if(err)
- console.log('failed to locate AndroidManifest.xml file, you may have to add file access permission manually.');
- else {
-
- console.log('RNFetchBlob patching AndroidManifest.xml .. ');
- // append fs permission
- data = String(data).replace(
- '<uses-permission android:name="android.permission.INTERNET" />',
- '<uses-permission android:name="android.permission.INTERNET" />\n <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> '
- )
- // append DOWNLOAD_COMPLETE intent permission
- data = String(data).replace(
- '<category android:name="android.intent.category.LAUNCHER" />',
- '<category android:name="android.intent.category.LAUNCHER" />\n <action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>'
- )
- fs.writeFileSync(MANIFEST_PATH, data);
- console.log('RNFetchBlob patching AndroidManifest.xml .. ok');
-
- }
-
- })
- }
- else {
- console.log(
- '\033[95mrn-fetch-blob \033[97mwill not automatically add Android permissions after \033[92m0.9.4 '+
- '\033[97mplease run the following command if you want to add default permissions :\n\n' +
- '\033[96m\tRNFB_ANDROID_PERMISSIONS=true react-native link \n')
- }
-
- function checkVersion() {
- console.log('RNFetchBlob checking app version ..');
- return parseFloat(/\d\.\d+(?=\.)/.exec(package.dependencies['react-native']));
- }
-
-} catch(err) {
- console.log(
- '\033[95mrn-fetch-blob\033[97m link \033[91mFAILED \033[97m\nCould not automatically link package :'+
- err.stack +
- 'please follow the instructions to manually link the library : ' +
- '\033[4mhttps://github.com/joltup/rn-fetch-blob/wiki/Manually-Link-Package\n')
-}

View File

@@ -142,19 +142,6 @@ const ListItem = ({ item, mode, close }) => {
{item.type === "tag" ? "#" : ""}
{item.alias || item.title}
</Text>
{item.type === "note" ? (
<Text
numberOfLines={1}
style={{
color: colors.icon,
fontSize: 12,
fontFamily: "OpenSans-Regular"
}}
>
{item.headline}
</Text>
) : null}
</View>
</View>

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,

View File

@@ -256,6 +256,23 @@ test("select notes using Shift+Click upwards", async ({ page }, info) => {
expect(await notesList[0].isFocused()).toBeTruthy();
});
test("using Shift+Click when no notes are selected should not crash the app", async ({
page
}, info) => {
info.setTimeout(60 * 1000);
const { notes } = await populateList(page, 5);
await page.reload();
const note = await notes.findNote({ title: "Test note 3" });
await page.keyboard.down("Shift");
await note?.click();
await page.keyboard.up("Shift");
expect(await notes.isEmpty()).toBeFalsy();
});
test("Ctrl+Click to select/unselect notes", async ({ page }, info) => {
info.setTimeout(60 * 1000);
const { notesList, notes } = await populateList(page, 10);

View File

@@ -234,18 +234,17 @@ export class NoteContextMenuModel extends BaseProperties {
await this.open();
await this.menu.clickOnItem("export");
// we need to override date time so
// date created & date edited remain fixed.
await this.noteLocator.evaluate(() => {
// eslint-disable-next-line no-extend-native
Date.prototype.toLocaleString = () => "xxx";
});
return await downloadAndReadFile(
const content = await downloadAndReadFile(
this.noteLocator.page(),
this.menu.getItem(format),
"utf-8"
);
if (format === "html") {
return content
.replace(/(name="created-on" content=")(.+?)"/, '$1xxx"')
.replace(/(name="last-edited-on" content=")(.+?)"/, '$1xxx"');
}
return content;
}
async addToNotebook(notebook: Notebook) {

View File

@@ -0,0 +1,46 @@
/*
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 "./bootstrap";
import { test } from "vitest";
import { Base64DecoderStream } from "../src/utils/streams/base64-decoder-stream";
import { consumeReadableStream } from "../src/utils/stream";
import { createReadStream, readFileSync } from "fs";
import { Readable } from "stream";
import path from "path";
test("streamed base64 decoder should output same as non-streamed", async (t) => {
const expected = readFileSync(
path.join(__dirname, "..", "__e2e__", "data", "importer-data.zip"),
"base64"
);
const fileStream = Readable.toWeb(
createReadStream(
path.join(__dirname, "..", "__e2e__", "data", "importer-data.zip")
)
) as ReadableStream<Uint8Array>;
t.expect(
(
await consumeReadableStream(
fileStream.pipeThrough(new Base64DecoderStream("base64"))
)
).join("")
).toBe(expected);
});

Some files were not shown because too many files have changed in this diff Show More