Compare commits

..

4 Commits

Author SHA1 Message Date
ammarahm-ed
ee359770ce mobile: always load notes on init 2023-04-26 08:53:27 +05:00
ammarahm-ed
9d2b445078 mobile: ensure that overlay gets hidden when entering foreground 2023-04-26 08:21:10 +05:00
ammarahm-ed
43421c5478 mobile: only show app lock for main activity 2023-04-26 08:20:36 +05:00
ammarahm-ed
4b60e8bb9e mobile: reload editor if rendered view lost 2023-04-25 19:52:04 +05:00
235 changed files with 42044 additions and 17394 deletions

View File

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

View File

@@ -31,6 +31,14 @@ import { TipManager } from "./services/tip-manager";
import { useUserStore } from "./stores/use-user-store";
import { View } from "react-native";
import { useState } from "react";
import NetInfo from "@react-native-community/netinfo";
NetInfo.configure({
reachabilityUrl: "https://notesnook.com",
reachabilityTest: (response) => {
if (!response) return false;
return response?.status >= 200 && response?.status < 300;
}
});
SettingsService.init();
SettingsService.checkOrientation();

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 "react-native-blob-util";
import RNFetchBlob from "rn-fetch-blob";
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 "react-native-blob-util";
import RNFetchBlob from "rn-fetch-blob";
/**
* Scale down & compress images to screen width
* for loading in editor.

View File

@@ -17,169 +17,25 @@ 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 { subscribe, zip } from "react-native-zip-archive";
import RNFetchBlob from "react-native-blob-util";
import Sodium from "@ammarahmed/react-native-sodium";
import RNFetchBlob from "rn-fetch-blob";
import { ShareComponent } from "../../components/sheets/export-notes/share";
import { ToastEvent, presentSheet } from "../../services/event-manager";
import { presentSheet, ToastEvent } from "../../services/event-manager";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { db } from "../database";
import Storage from "../database/storage";
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;
}
import { cacheDir } from "./utils";
import { getFileNameWithExtension } from "@notesnook/core/utils/filename";
export default async function downloadAttachment(
hash,
global = true,
options = {
silent: false,
cache: false,
throwError: false,
groupId: undefined
cache: false
}
) {
let attachment = db.attachments.attachment(hash);
@@ -199,13 +55,8 @@ export default async function downloadAttachment(
}
try {
console.log(
"starting download attachment",
attachment.metadata.hash,
options.groupId
);
await db.fs.downloadFile(
options.groupId || attachment.metadata.hash,
attachment.metadata.hash,
attachment.metadata.hash
);
if (
@@ -248,8 +99,7 @@ export default async function downloadAttachment(
if (
attachment.dateUploaded &&
!isImage(attachment.metadata?.type) &&
!isDocument(attachment.metadata?.type)
!attachment.metadata?.type?.startsWith("image")
) {
RNFetchBlob.fs
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.metadata.hash}`)
@@ -281,8 +131,5 @@ 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 "react-native-blob-util";
import RNFetchBlob from "rn-fetch-blob";
import { ToastEvent } from "../../services/event-manager";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { db } from "../database";

View File

@@ -19,7 +19,7 @@ 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 "react-native-blob-util";
import RNFetchBlob from "rn-fetch-blob";
import { cacheDir, getRandomId } from "./utils";
import { db } from "../database";
import { compressToBase64 } from "./compress";

View File

@@ -17,11 +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 RNFetchBlob from "react-native-blob-util";
import RNFetchBlob from "rn-fetch-blob";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { db } from "../database";
import { cacheDir } from "./utils";
import { isImage, isDocument } from "@notesnook/core/utils/filename";
export async function uploadFile(filename, data, cancelToken) {
if (!data) return false;
@@ -64,10 +63,7 @@ export async function uploadFile(filename, data, cancelToken) {
if (result) {
let attachment = db.attachments.attachment(filename);
if (!attachment) return result;
if (
!isImage(attachment.metadata.type) &&
!isDocument(attachment.metadata?.type)
) {
if (!attachment.metadata.type.startsWith("image/")) {
RNFetchBlob.fs.unlink(`${cacheDir}/${filename}`).catch(console.log);
}
}

View File

@@ -17,9 +17,7 @@ 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 * as ScopedStorage from "react-native-scoped-storage";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import RNFetchBlob from "rn-fetch-blob";
export const cacheDir = RNFetchBlob.fs.dirs.CacheDir;
@@ -64,22 +62,3 @@ 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

@@ -148,7 +148,7 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
<Button
key={item.title}
title={item.title}
fontSize={SIZE.xs}
fontSize={SIZE.xs + 1}
type="gray"
onPress={() => onPress(item)}
width={null}

View File

@@ -67,7 +67,7 @@ export const Title = ({ text, style = {}, inline }) => {
right: 0
}}
iconSize={24}
fontSize={SIZE.xs}
fontSize={SIZE.xs + 1}
style={{
borderRadius: 100,
paddingVertical: 0,

View File

@@ -102,21 +102,15 @@ 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
@@ -215,7 +209,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
}}
>
<Paragraph
size={SIZE.xs}
size={SIZE.xs + 1}
style={{
marginRight: 10
}}
@@ -227,7 +221,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
style={{
marginRight: 10
}}
size={SIZE.xs}
size={SIZE.xs + 1}
color={colors.icon}
>
{formatBytes(attachment.length)}
@@ -238,7 +232,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
style={{
marginRight: 10
}}
size={SIZE.xs}
size={SIZE.xs + 1}
color={colors.icon}
>
{attachment.noteIds.length} note
@@ -254,7 +248,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
context: "local"
});
}}
size={SIZE.xs}
size={SIZE.xs + 1}
color={colors.icon}
>
{attachment.metadata.hash}
@@ -310,7 +304,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
}}
key={item.id}
>
<Paragraph size={SIZE.xs}>{item.title}</Paragraph>
<Paragraph size={SIZE.xs + 1}>{item.title}</Paragraph>
</PressableButton>
))}
</>

View File

@@ -22,6 +22,7 @@ import { TouchableOpacity, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import { useAttachmentProgress } from "../../hooks/use-attachment-progress";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { formatBytes } from "../../utils";
import { SIZE } from "../../utils/size";
@@ -35,27 +36,24 @@ function getFileExtension(filename) {
var ext = /^.+\.([^.]+)$/.exec(filename);
return ext == null ? "" : ext[1];
}
export const AttachmentItem = ({
attachment,
encryption,
setAttachments,
pressable = true,
hideWhenNotDownloading
}) => {
/**
*
* @param {any} param0
* @returns
*/
export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
const colors = useThemeStore((state) => state.colors);
const [currentProgress, setCurrentProgress] = useAttachmentProgress(
attachment,
encryption
);
const encryptionProgress = useAttachmentStore(
(state) => state.encryptionProgress
);
const onPress = () => {
if (!pressable) return;
Actions.present(attachment, setAttachments, attachment.metadata.hash);
};
return hideWhenNotDownloading &&
(!currentProgress || !currentProgress.value) ? null : (
return (
<TouchableOpacity
activeOpacity={0.9}
onPress={onPress}
@@ -118,22 +116,22 @@ export const AttachmentItem = ({
{attachment.metadata.filename}
</Paragraph>
{!hideWhenNotDownloading ? (
<Paragraph color={colors.icon} size={SIZE.xs}>
{formatBytes(attachment.length)}{" "}
{currentProgress?.type
? "(" + currentProgress.type + "ing - tap to cancel)"
: ""}
</Paragraph>
) : null}
<Paragraph color={colors.icon} size={SIZE.xs}>
{formatBytes(attachment.length)}{" "}
{currentProgress?.type
? "(" + currentProgress.type + "ing - tap to cancel)"
: ""}
</Paragraph>
</View>
</View>
{currentProgress ? (
{currentProgress ||
(encryptionProgress && encryptionProgress !== "0.00") ||
encryption ? (
<TouchableOpacity
activeOpacity={0.9}
onPress={() => {
if (encryption || !pressable) return;
if (encryption) return;
db.fs.cancel(attachment.metadata.hash);
setCurrentProgress(null);
}}
@@ -146,7 +144,13 @@ export const AttachmentItem = ({
>
<ProgressCircleComponent
size={SIZE.xxl}
progress={currentProgress?.value ? currentProgress?.value / 100 : 0}
progress={
encryptionProgress
? encryptionProgress
: currentProgress?.value
? currentProgress?.value / 100
: 0
}
showsText
textStyle={{
fontSize: 10

View File

@@ -1,273 +0,0 @@
/*
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);
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,60 +18,44 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useRef, useState } from "react";
import { ActivityIndicator, ScrollView, View } from "react-native";
import { FlatList } from "react-native-actions-sheet";
import { View } from "react-native";
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 SheetProvider from "../sheet-provider";
import { IconButton } from "../ui/icon-button";
import DialogHeader from "../dialog/dialog-header";
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 DownloadAttachments from "./download-attachments";
import { Button } from "../ui/button";
import {
isAudio,
isDocument,
isImage,
isVideo
} from "@notesnook/core/utils/filename";
import { FlatList } from "react-native-actions-sheet";
export const AttachmentDialog = ({ note }) => {
export const AttachmentDialog = ({ data }) => {
const colors = useThemeStore((state) => state.colors);
const [note, setNote] = useState(data);
const [attachments, setAttachments] = useState(
note
? db.attachments.ofNote(note.id, "all")
data
? db.attachments.ofNote(data.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([...attachments]);
setAttachments([...db.attachments.all]);
}
clearTimeout(searchTimer.current);
searchTimer.current = setTimeout(() => {
let results = db.lookup.attachments(
attachments,
db.attachments.all,
attachmentSearchValue.current
);
if (results.length === 0) return;
@@ -83,77 +67,6 @@ export const AttachmentDialog = ({ note }) => {
<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={{
@@ -162,71 +75,43 @@ export const AttachmentDialog = ({ note }) => {
paddingHorizontal: 12
}}
>
<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
<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
);
}}
size={SIZE.lg}
/>
</View>
</View>
<Seperator />
<Input
placeholder="Filter attachments by filename, type or hash"
onChangeText={onChangeText}
onSubmit={() => {
onChangeText(attachmentSearchValue.current);
if (result.failed) {
db.attachments.markAsFailed(
attachment.metadata.hash,
result.failed
);
} else {
db.attachments.markAsFailed(attachment.id, null);
}
setAttachments([...db.attachments.all]);
}
setLoading(false);
}
}}
/>
<Seperator />
{!note ? (
<Input
placeholder="Filter attachments by filename, type or hash"
onChangeText={onChangeText}
onSubmit={() => {
onChangeText(attachmentSearchValue.current);
}}
/>
) : null}
<FlatList
keyboardDismissMode="none"
@@ -234,45 +119,6 @@ export const AttachmentDialog = ({ note }) => {
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={{
@@ -316,6 +162,6 @@ export const AttachmentDialog = ({ note }) => {
AttachmentDialog.present = (note) => {
presentSheet({
component: () => <AttachmentDialog note={note} />
component: () => <AttachmentDialog data={note} />
});
};

View File

@@ -44,10 +44,8 @@ import Paragraph from "../ui/typography/paragraph";
import { LoginSteps, useLogin } from "./use-login";
function getObfuscatedEmail(email) {
if (!email) return "";
const [username, provider] = email.split("@");
if (username.length === 1) return `****@${provider}`;
return email.replace(/(.{1})(.*)(?=@)/, function (gp1, gp2, gp3) {
if (!email) return null;
return email.replace(/(.{2})(.*)(?=@)/, function (gp1, gp2, gp3) {
for (let i = 0; i < gp3.length; i++) {
gp2 += "*";
}

View File

@@ -36,7 +36,6 @@ 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);
@@ -61,7 +60,6 @@ const DialogProvider = () => {
{loading ? null : <Expiring />}
<AnnouncementDialog />
<SessionExpired />
<PDFPreview />
</>
);
};

View File

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

View File

@@ -86,7 +86,7 @@ export const ProFeatures = ({ count = 6 }) => {
await sleep(300);
eSendEvent(eOpenPremiumDialog);
}}
size={SIZE.xs}
size={SIZE.xs + 1}
style={{
textDecorationLine: "underline",
color: colors.icon

View File

@@ -70,7 +70,6 @@ const Launcher = React.memo(
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
const verifying = useRef(false);
const loadNotes = useCallback(async () => {
if (verifyUser) {
@@ -207,9 +206,6 @@ const Launcher = React.memo(
setVerifyUser(false);
enabled(false);
password.current = null;
setTimeout(() => {
verifying.current = false;
}, 1);
}
}, [setVerifyUser]);
@@ -218,10 +214,7 @@ const Launcher = React.memo(
}, [init, verifyUser]);
useEffect(() => {
if (verifying.current || useUserStore.getState().shouldBlockVerifyUser)
return;
if (verifyUser && appState === "active") {
verifying.current = true;
onUnlockBiometrics();
}
}, [appState, onUnlockBiometrics, verifyUser]);

View File

@@ -108,7 +108,7 @@ const ReminderItem = React.memo(
color={colors.errorText}
/>
<Paragraph
size={SIZE.xs}
size={SIZE.xs + 1}
color={colors.icon}
style={{ marginLeft: 5 }}
>
@@ -133,7 +133,7 @@ const ReminderItem = React.memo(
>
<Icon name="reload" size={SIZE.md} color={colors.accent} />
<Paragraph
size={SIZE.xs}
size={SIZE.xs + 1}
color={colors.icon}
style={{ marginLeft: 5 }}
>
@@ -147,7 +147,7 @@ const ReminderItem = React.memo(
<ReminderTime
reminder={item}
checkIsActive={false}
fontSize={SIZE.xs}
fontSize={SIZE.xs + 1}
style={{
justifyContent: "flex-start",
borderWidth: 0,

View File

@@ -143,7 +143,7 @@ export const Expiring = () => {
await sleep(300);
eSendEvent(eOpenPremiumDialog, promo);
}}
size={SIZE.xs}
size={SIZE.xs + 1}
style={{
textDecorationLine: "underline",
color: colors.icon,

View File

@@ -17,10 +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 React, { useCallback, useEffect, useRef, useState } from "react";
import React, { useEffect, useRef, useState } from "react";
import { View } from "react-native";
import Animated, { FadeInUp, FadeOutUp } from "react-native-reanimated";
import useKeyboard from "../../hooks/use-keyboard";
import { editorState } from "../../screens/editor/tiptap/utils";
import { DDS } from "../../services/device-detection";
import {
eSendEvent,
@@ -31,7 +31,6 @@ import { useThemeStore } from "../../stores/use-theme-store";
import { getElevation } from "../../utils";
import {
eCloseActionSheet,
eCloseSheet,
eOpenPremiumDialog,
eShowGetPremium
} from "../../utils/events";
@@ -40,12 +39,12 @@ import { sleep } from "../../utils/time";
import { Button } from "../ui/button";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { useCallback } from "react";
export const PremiumToast = ({ context = "global", offset = 0 }) => {
const colors = useThemeStore((state) => state.colors);
const [msg, setMsg] = useState(null);
const timer = useRef();
const keyboard = useKeyboard();
const open = useCallback(
(event) => {
@@ -73,6 +72,7 @@ export const PremiumToast = ({ context = "global", offset = 0 }) => {
useEffect(() => {
eSubscribeEvent(eShowGetPremium, open);
return () => {
clearTimeout(timer.current);
eUnSubscribeEvent(eShowGetPremium, open);
};
}, [open]);
@@ -80,7 +80,9 @@ export const PremiumToast = ({ context = "global", offset = 0 }) => {
const onPress = async () => {
open(null);
eSendEvent(eCloseActionSheet);
eSendEvent(eCloseSheet);
if (editorState().isFocused) {
//tiny.call(EditorWebView, tiny.blur);
}
await sleep(300);
eSendEvent(eOpenPremiumDialog);
};
@@ -100,13 +102,9 @@ export const PremiumToast = ({ context = "global", offset = 0 }) => {
flexDirection: "row",
alignSelf: "center",
justifyContent: "space-between",
top: offset + keyboard.keyboardHeight,
top: offset,
maxWidth: DDS.isLargeTablet() ? 400 : "98%"
}}
onTouchEnd={() => {
setMsg(null);
clearTimeout(timer.current);
}}
>
<View
style={{

View File

@@ -62,10 +62,10 @@ export const DateMeta = ({ item }) => {
paddingVertical: 3
}}
>
<Paragraph size={SIZE.xs} color={colors.icon}>
<Paragraph size={SIZE.xs + 1} color={colors.icon}>
{getNameFromKey(key)}
</Paragraph>
<Paragraph size={SIZE.xs} color={colors.icon}>
<Paragraph size={SIZE.xs + 1} color={colors.icon}>
{timeConverter(item[key])}
</Paragraph>
</View>

View File

@@ -108,7 +108,7 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
backgroundColor: "transparent",
paddingHorizontal: 0
}}
fontSize={SIZE.xs}
fontSize={SIZE.xs + 1}
/>
) : null}
</View>

View File

@@ -115,12 +115,12 @@ export const Items = ({ item, buttons, close }) => {
onPress={item.func}
key={item.id}
testID={"icon-" + item.id}
activeOpacity={1}
customStyle={{
alignSelf: "flex-start",
alignItems: "center",
width: topBarItemWidth,
marginBottom: 10,
marginRight: isLast ? 0 : 10,
paddingHorizontal: 0,
width: topBarItemWidth
backgroundColor: "transparent"
}}
>
<PressableButton
@@ -150,7 +150,11 @@ export const Items = ({ item, buttons, close }) => {
/>
</PressableButton>
<Paragraph size={SIZE.xxs + 1} style={{ textAlign: "center" }}>
<Paragraph
size={SIZE.xxs + 1}
style={{ textAlign: "center" }}
textBreakStrategy="simple"
>
{item.title}
</Paragraph>
</PressableButton>
@@ -183,7 +187,7 @@ export const Items = ({ item, buttons, close }) => {
horizontal
style={{
paddingHorizontal: 12,
paddingVertical: 12
paddingTop: 12
}}
>
{topBarItems.map(renderTopBarItem)}

View File

@@ -23,18 +23,13 @@ import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import Notebook from "../../screens/notebook";
import { TopicNotes } from "../../screens/notes/topic-notes";
import {
eSendEvent,
presentSheet,
ToastEvent
} from "../../services/event-manager";
import { presentSheet, ToastEvent } from "../../services/event-manager";
import Navigation from "../../services/navigation";
import { useNotebookStore } from "../../stores/use-notebook-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
import Heading from "../ui/typography/heading";
import { eClearEditor } from "../../utils/events";
export default function Notebooks({ note, close, full }) {
const colors = useThemeStore((state) => state.colors);
@@ -117,7 +112,6 @@ export default function Notebooks({ note, close, full }) {
size={SIZE.sm}
onPress={() => {
navigateNotebook(item.id);
eSendEvent(eClearEditor);
close();
}}
>
@@ -140,7 +134,6 @@ export default function Notebooks({ note, close, full }) {
key={topic.id}
onPress={() => {
navigateTopic(topic.id, item.id);
eSendEvent(eClearEditor);
close();
}}
onLongPress={async () => {
@@ -162,7 +155,7 @@ export default function Notebooks({ note, close, full }) {
title={topic.title}
type="gray"
height={30}
fontSize={SIZE.xs}
fontSize={SIZE.xs + 1}
icon="bookmark-outline"
style={{
marginRight: 5,
@@ -191,7 +184,7 @@ export default function Notebooks({ note, close, full }) {
{noteNotebooks.length > 1 && !full ? (
<Button
title={`See all linked notebooks`}
fontSize={SIZE.xs}
fontSize={SIZE.xs + 1}
style={{
alignSelf: "flex-end",
marginRight: 12,

View File

@@ -91,7 +91,7 @@ export const Synced = ({ item, close }) => {
console.error(e);
}
}}
fontSize={SIZE.xs}
fontSize={SIZE.xs + 1}
title="Learn more"
height={30}
type="grayAccent"

View File

@@ -47,7 +47,7 @@ export const Tags = ({ item, close }) => {
>
<Button
onPress={async () => {
ManageTagsSheet.present([item]);
ManageTagsSheet.present(item);
}}
buttonType={{
text: colors.accent
@@ -57,7 +57,7 @@ export const Tags = ({ item, close }) => {
icon="plus"
iconPosition="right"
height={30}
fontSize={SIZE.xs}
fontSize={SIZE.xs + 1}
style={{
marginRight: 5,
borderRadius: 100,
@@ -107,7 +107,7 @@ const TagItem = ({ tag, close }) => {
title={"#" + tag}
type="grayBg"
height={20}
fontSize={SIZE.xs}
fontSize={SIZE.xs + 1}
style={style}
textStyle={{
textDecorationLine: "underline"

View File

@@ -39,7 +39,7 @@ export const Topics = ({ item, close }) => {
height={30}
onPress={() => open(topic)}
icon="bookmark-outline"
fontSize={SIZE.xs}
fontSize={SIZE.xs + 1}
style={{
marginRight: 5,
paddingHorizontal: 8,

View File

@@ -36,7 +36,6 @@ 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);
@@ -210,20 +209,6 @@ 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);
@@ -239,7 +224,6 @@ export const SelectionHeader = React.memo(() => {
name="plus"
size={SIZE.xl}
/>
<IconButton
onPress={async () => {
ExportNotesSheet.present(selectedItemsList);

View File

@@ -42,7 +42,6 @@ import { SelectionProvider } from "./context";
import { FilteredList } from "./filtered-list";
import { ListItem } from "./list-item";
import { useItemSelectionStore } from "./store";
import { useRelationStore } from "../../../stores/use-relation-store";
const MoveNoteSheet = ({ note, actionSheetRef }) => {
const colors = useThemeStore((state) => state.colors);
@@ -264,7 +263,7 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
if (itemState[id] === "selected") {
if (item.type === "notebook") {
for (let noteId of noteIds) {
await db.relations.add(item, { id: noteId, type: "note" });
db.relations.add(item, { id: noteId, type: "note" });
}
} else {
await db.notes.addToNotebook(
@@ -279,7 +278,7 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
} else if (itemState[id] === "deselected") {
if (item.type === "notebook") {
for (let noteId of noteIds) {
await db.relations.unlink(item, { id: noteId, type: "note" });
db.relations.unlink(item, { id: noteId, type: "note" });
}
} else {
await db.notes.removeFromNotebook(
@@ -297,7 +296,6 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
setNotebooks();
eSendEvent(eOnTopicSheetUpdate);
SearchService.updateAndSearch();
useRelationStore.getState().update();
actionSheetRef.current?.hide();
};

View File

@@ -42,7 +42,6 @@ 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);
@@ -73,7 +72,6 @@ const ExportNotesSheet = ({ notes, update }) => {
update({ disableClosing: false });
setComplete(true);
setExporting(false);
requestInAppReview();
};
const actions = [

View File

@@ -22,7 +22,7 @@ import { View } from "react-native";
import { ScrollView } from "react-native-actions-sheet";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../../common/database";
import { ToastEvent, presentSheet } from "../../../services/event-manager";
import { presentSheet, ToastEvent } from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { useTagStore } from "../../../stores/use-tag-store";
import { useThemeStore } from "../../../stores/use-theme-store";
@@ -31,10 +31,9 @@ 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 [notes, setNotes] = useState(props.notes || []);
const [note, setNote] = useState(props.note);
const allTags = useTagStore((state) => state.tags);
const [tags, setTags] = useState([]);
const [query, setQuery] = useState(null);
@@ -43,7 +42,7 @@ const ManageTagsSheet = (props) => {
useEffect(() => {
sortTags();
}, [allTags, notes, query, sortTags]);
}, [allTags, note, query, sortTags]);
const sortTags = useCallback(() => {
let _tags = [...allTags];
@@ -52,14 +51,13 @@ const ManageTagsSheet = (props) => {
if (query) {
_tags = _tags.filter((t) => t.title.startsWith(query));
}
const tagsMerged = [...notes.map((note) => note.tags || []).flat()];
if (!tagsMerged || !tagsMerged.length) {
if (!note || !note.tags) {
setTags(_tags);
return;
}
let noteTags = [];
for (let tag of tagsMerged) {
for (let tag of note.tags) {
let index = _tags.findIndex((t) => t.title === tag);
if (index !== -1) {
noteTags.push(_tags[index]);
@@ -69,7 +67,7 @@ const ManageTagsSheet = (props) => {
noteTags = noteTags.sort((a, b) => a.title.localeCompare(b.title));
let combinedTags = [...noteTags, ..._tags];
setTags(combinedTags);
}, [allTags, notes, query]);
}, [allTags, note, query]);
useEffect(() => {
useTagStore.getState().setTags();
@@ -87,23 +85,15 @@ const ManageTagsSheet = (props) => {
}
let tag = _query;
setNotes(
notes.map((note) => ({
...note,
tags: note.tags ? [...note.tags, tag] : [tag]
}))
);
setNote({ ...note, tags: note.tags ? [...note.tags, tag] : [tag] });
setQuery(null);
inputRef.current?.setNativeProps({
text: ""
});
try {
for (let note of notes) {
await db.notes.note(note.id).tag(tag);
}
await db.notes.note(note.id).tag(tag);
useTagStore.getState().setTags();
setNotes(notes.map((note) => db.notes.note(note.id).data));
setNote(db.notes.note(note.id).data);
} catch (e) {
ToastEvent.show({
heading: "Cannot add tag",
@@ -190,52 +180,41 @@ const ManageTagsSheet = (props) => {
) : null}
{tags.map((item) => (
<TagItem
key={item.title}
tag={item}
notes={notes}
setNotes={setNotes}
/>
<TagItem key={item.title} tag={item} note={note} setNote={setNote} />
))}
</ScrollView>
</View>
);
};
ManageTagsSheet.present = (notes) => {
ManageTagsSheet.present = (note) => {
presentSheet({
component: (ref) => {
return <ManageTagsSheet actionSheetRef={ref} notes={notes} />;
return <ManageTagsSheet actionSheetRef={ref} note={note} />;
}
});
};
export default ManageTagsSheet;
const TagItem = ({ tag, notes, setNotes }) => {
const TagItem = ({ tag, note, setNote }) => {
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 () => {
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);
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);
}
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);
@@ -246,36 +225,39 @@ const TagItem = ({ tag, notes, setNotes }) => {
customStyle={{
flexDirection: "row",
marginVertical: 5,
justifyContent: "flex-start",
height: 40
justifyContent: "space-between",
padding: 12
}}
onPress={onPress}
type="gray"
type={
note && note.tags.findIndex((t) => t === tag.title) !== -1
? "shade"
: "grayBg"
}
>
<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"
<Heading
size={SIZE.sm}
color={
note && note?.tags.findIndex((t) => t === tag.title) !== -1
? colors.accent
: colors.pri
}
>
{"#" + tag.title}
</Heading>
<Icon
name={
allNotesTagged
? "check-circle-outline"
: someNotesTagged
? "minus-circle-outline"
: "checkbox-blank-circle-outline"
note && note?.tags.findIndex((t) => t === tag.title) !== -1
? "minus"
: "plus"
}
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

@@ -158,7 +158,11 @@ export const MoveNotes = ({
{item.title}
</Paragraph>
{item.type == "note" && item.headline ? (
<Paragraph numberOfLines={1} color={colors.icon} size={SIZE.xs}>
<Paragraph
numberOfLines={1}
color={colors.icon}
size={SIZE.xs + 1}
>
{item.headline}
</Paragraph>
) : null}

View File

@@ -35,7 +35,6 @@ 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);
@@ -67,7 +66,6 @@ const PublishNoteSheet = ({ note: item, update }) => {
Navigation.queueRoutesForUpdate();
setPublishLoading(false);
}
requestInAppReview();
} catch (e) {
ToastEvent.show({
heading: "Could not publish note",

View File

@@ -43,7 +43,8 @@ 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 "react-native-blob-util";
let RNFetchBlob;
class RecoveryKeySheet extends React.Component {
constructor(props) {
@@ -114,6 +115,7 @@ class RecoveryKeySheet extends React.Component {
this.svg.current?.toDataURL(async (data) => {
try {
let path;
RNFetchBlob = (await import("rn-fetch-blob")).default;
let fileName = "nn_" + this.user.email + "_recovery_key_qrcode";
fileName = sanitizeFilename(fileName, { replacement: "_" });
fileName = fileName + ".png";
@@ -149,6 +151,7 @@ class RecoveryKeySheet extends React.Component {
fileName = sanitizeFilename(fileName, { replacement: "_" });
fileName = fileName + ".txt";
RNFetchBlob = (await import("rn-fetch-blob")).default;
if (Platform.OS === "android") {
let file = await ScopedStorage.createDocument(
fileName,

View File

@@ -119,7 +119,7 @@ export default function ReminderNotify({
marginTop: 10
}}
>
<Paragraph size={SIZE.xs}>Remind me in:</Paragraph>
<Paragraph size={SIZE.xs + 1}>Remind me in:</Paragraph>
{QuickActions.map((item) => {
return (
<Button
@@ -127,7 +127,7 @@ export default function ReminderNotify({
key={item.title}
title={item.title}
height={30}
fontSize={SIZE.xs}
fontSize={SIZE.xs + 1}
style={{ marginLeft: 10, borderRadius: 100 }}
onPress={() => onSnooze(item.time)}
/>
@@ -150,7 +150,7 @@ export default function ReminderNotify({
<Paragraph
style={{
color: colors.icon,
fontSize: SIZE.xs,
fontSize: SIZE.xs + 1,
marginBottom: 10
}}
>

View File

@@ -550,7 +550,7 @@ export default function ReminderSheet({
}}
>
<>
<Paragraph size={SIZE.xs} color={colors.icon}>
<Paragraph size={SIZE.xs + 1} color={colors.icon}>
{recurringMode === RecurringModes.Daily
? "Repeats daily " + `at ${dayjs(date).format("hh:mm A")}.`
: selectedDays.length === 7 &&

View File

@@ -44,8 +44,7 @@ import { Button } from "../../ui/button";
import Seperator from "../../ui/seperator";
import SheetWrapper from "../../ui/sheet";
import Paragraph from "../../ui/typography/paragraph";
import RNFetchBlob from "react-native-blob-util";
let RNFetchBlob;
const RestoreDataSheet = () => {
const [visible, setVisible] = useState(false);
const [restoring, setRestoring] = useState(false);
@@ -214,6 +213,7 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
return;
}
} else {
RNFetchBlob = (await import("rn-fetch-blob")).default;
let path = await storage.checkAndCreateDir("/backups/");
files = await RNFetchBlob.fs.lstat(path);
}

View File

@@ -25,6 +25,7 @@ import Navigation from "../../../services/navigation";
import { useThemeStore } from "../../../stores/use-theme-store";
import { GROUP, SORT } from "../../../utils/constants";
import { refreshNotesPage } from "../../../utils/events";
import layoutmanager from "../../../utils/layout-manager";
import { SIZE } from "../../../utils/size";
import { Button } from "../../ui/button";
import Seperator from "../../ui/seperator";
@@ -38,6 +39,7 @@ const Sort = ({ type, screen }) => {
const updateGroupOptions = async (_groupOptions) => {
await db.settings.setGroupOptions(type, _groupOptions);
layoutmanager.withSpringAnimation(600);
setGroupOptions(_groupOptions);
setTimeout(() => {
if (screen !== "TopicSheet") Navigation.queueRoutesForUpdate(screen);

View File

@@ -40,8 +40,6 @@ import { getElevation } from "../../../utils";
import { SIZE } from "../../../utils/size";
import { IconButton } from "../icon-button";
import Paragraph from "../typography/paragraph";
import phone from "phone";
interface InputProps extends TextInputProps {
fwdRef?: RefObject<TextInput>;
validationType?:
@@ -151,6 +149,7 @@ const Input = ({
isError = customValidator && value === customValidator();
break;
case "phonenumber": {
const { default: phone } = await import("phone");
const result = phone(value, {
strictDetection: true,
validateMobilePrefix: true

View File

@@ -65,7 +65,7 @@ export const Notice = ({
flexShrink: 1
}}
selectable={selectable}
size={isSmall ? SIZE.xs : SIZE.sm}
size={isSmall ? SIZE.xs + 1 : SIZE.sm}
>
{text}
</Paragraph>

View File

@@ -21,15 +21,7 @@ import { FeatureType } from "./components/sheets/new-feature";
export const features: FeatureType[] = [
{
title: "Attachments preview",
body: "You can now preview PDFs & Images directly inside Notesnook."
},
{
title: "New attachments manager",
body: "The new attachments manager makes is much easier to view & interact with your attachments. It also allows you to download all (or some) of your attachments."
},
{
title: "Assign tags to multiple notes",
body: "You can now easily tag multiple notes without first opening them."
title: "Default font size & font family",
body: "Now you can set default font size and font family in editor that will be used across all your new and old notes."
}
];

View File

@@ -520,7 +520,7 @@ export const useActions = ({ close = () => null, item }) => {
}
async function showAttachments() {
AttachmentDialog.present(item);
AttachmentDialog.present();
}
async function exportNote() {
@@ -623,7 +623,7 @@ export const useActions = ({ close = () => null, item }) => {
},
{
id: "favorite",
title: item.favorite ? "Unfavorite" : "Favorite",
title: !item.favorite ? "Favorite" : "Unfavorite",
icon: item.favorite ? "star-off" : "star-outline",
func: addToFavorites,
close: false,
@@ -701,7 +701,7 @@ export const useActions = ({ close = () => null, item }) => {
},
{
id: "read-only",
title: "Readonly",
title: "Read only",
icon: "pencil-lock",
func: toggleReadyOnlyMode,
on: item.readonly

View File

@@ -489,8 +489,7 @@ export const useAppEvents = () => {
if (
SettingsService.get().appLockMode === "background" &&
!useSettingStore.getState().requestBiometrics &&
!useUserStore.getState().verifyUser &&
!useUserStore.getState().shouldBlockVerifyUser
!useUserStore.getState().verifyUser
) {
useUserStore.getState().setVerifyUser(true);
if (Platform.OS === "ios") {

View File

@@ -17,7 +17,7 @@ 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 { useEffect, useState } from "react";
import { useState } from "react";
import { useAttachmentStore } from "../stores/use-attachment-store";
type AttachmentProgress = {
@@ -41,26 +41,22 @@ export const useAttachmentProgress = (
: undefined
);
useEffect(() => {
const attachmentProgress = progress?.[attachment?.metadata?.hash];
if (attachmentProgress) {
const type = attachmentProgress.type;
const loaded =
attachmentProgress.type === "download"
? attachmentProgress.recieved
: attachmentProgress.sent;
const value = loaded / attachmentProgress.total;
setCurrentProgress({
value: value * 100,
percent: (value * 100).toFixed(0) + "%",
type: type
});
} else {
setTimeout(() => {
setCurrentProgress(undefined);
}, 300);
}
}, [attachment, progress]);
const attachmentProgress = progress?.[attachment.metadata.hash];
if (attachmentProgress) {
const type = attachmentProgress.type;
const loaded =
attachmentProgress.type === "download"
? attachmentProgress.recieved
: attachmentProgress.sent;
const value = loaded / attachmentProgress.total;
setCurrentProgress({
value: value * 100,
percent: (value * 100).toFixed(0) + "%",
type: type
});
} else {
setCurrentProgress(undefined);
}
return [currentProgress, setCurrentProgress];
};

View File

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

View File

@@ -128,6 +128,7 @@ const Editor = React.memo(
renderKey.current =
renderKey.current === `editor-0` ? `editor-1` : `editor-0`;
editor.setLoading(true);
setTimeout(() => editor.setLoading(false), 10);
}, [editor]);
useEffect(() => {
@@ -166,7 +167,6 @@ const Editor = React.memo(
injectedJavaScript={`globalThis.sessionId="${editor.sessionId}";`}
javaScriptEnabled={true}
focusable={true}
onContentProcessDidTerminate={onError}
setSupportMultipleWindows={false}
overScrollMode="never"
scrollEnabled={false}

View File

@@ -101,9 +101,8 @@ const EditorOverlay = ({ editorId = "", editor }) => {
setTimeout(() => {
if (!loadingState.current.startTime) {
translateValue.value = 6000;
opacity.value = 0;
}
}, 3000);
}, 1000);
eSubscribeEvent("loadingNote" + editorId, load);
return () => {
clearTimers();

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

View File

@@ -349,7 +349,7 @@ export const useEditorEvents = (
});
return;
}
ManageTagsSheet.present([editor.note.current]);
ManageTagsSheet.present(editor.note.current);
break;
case EventTypes.tag:
if (editorMessage.value) {
@@ -398,18 +398,9 @@ export const useEditorEvents = (
openLinkInBrowser(editorMessage.value as string);
break;
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);
}
case EventTypes.previewAttachment:
eSendEvent("ImagePreview", editorMessage.value);
break;
}
default:
break;
}

View File

@@ -80,8 +80,8 @@ export const useEditor = (
const lockedSessionId = useRef<string>();
const postMessage = useCallback(
async <T>(type: string, data: T, waitFor = 300) =>
await post(editorRef, sessionIdRef.current, type, data, waitFor),
async <T>(type: string, data: T) =>
await post(editorRef, sessionIdRef.current, type, data),
[sessionIdRef]
);
@@ -120,13 +120,9 @@ export const useEditor = (
[editorId]
);
useEffect(() => {
if (loading) {
setLoading(false);
} else {
state.current.ready = false;
}
}, [loading]);
if (loading) {
setLoading(false);
}
const withTimer = useCallback(
(id: string, fn: () => void, duration: number) => {
@@ -340,6 +336,7 @@ export const useEditor = (
) => {
state.current.currentlyEditing = true;
const editorState = useEditorStore.getState();
if (item && item.type === "new") {
currentNote.current && (await reset());
const nextSessionId = makeSessionId(item as NoteType);
@@ -347,7 +344,7 @@ export const useEditor = (
sessionIdRef.current = nextSessionId;
sessionHistoryId.current = Date.now();
await commands.setSessionId(nextSessionId);
if (state.current?.ready) await commands.focus();
await commands.focus();
lastContentChangeTime.current = 0;
useEditorStore.getState().setReadonly(false);
} else {
@@ -361,7 +358,7 @@ export const useEditor = (
!currentContent.current?.data ||
currentContent.current?.data.length < 50000
) {
if (state.current.ready) overlay(false);
overlay(false);
} else {
overlay(true);
}
@@ -375,11 +372,7 @@ export const useEditor = (
currentNote.current = item as NoteType;
await commands.setStatus(timeConverter(item.dateEdited), "Saved");
await postMessage(EditorEvents.title, item.title);
await postMessage(
EditorEvents.html,
currentContent.current?.data,
10000
);
await postMessage(EditorEvents.html, currentContent.current?.data);
useEditorStore.getState().setReadonly(item.readonly);
await commands.setTags(currentNote.current);
commands.setSettings();
@@ -585,10 +578,6 @@ export const useEditor = (
lastContentChangeTime.current = Date.now();
};
useEffect(() => {
state.current.saveCount = 0;
}, [sessionId, loading]);
const onReady = useCallback(async () => {
if (!(await isEditorLoaded(editorRef, sessionIdRef.current))) {
eSendEvent("webview_reset");
@@ -597,24 +586,30 @@ export const useEditor = (
}
}, [isDefaultEditor, restoreEditorState]);
const onLoad = useCallback(async () => {
if (currentNote.current) overlay(true);
clearTimeout(timers.current["editor:loaded"]);
timers.current["editor:loaded"] = setTimeout(async () => {
postMessage(EditorEvents.theme, theme || useThemeStore.getState().colors);
commands.setInsets(
isDefaultEditor ? insets : { top: 0, left: 0, right: 0, bottom: 0 }
);
useEffect(() => {
state.current.saveCount = 0;
(async () => {
await commands.setSessionId(sessionIdRef.current);
await onReady();
await commands.setSettings();
if (currentNote.current) {
loadNote({ ...currentNote.current, forced: true });
} else {
await commands.setPlaceholder(placeholderTip.current);
if (sessionIdRef.current) {
if (!state.current?.ready) return;
await onReady();
}
state.current.ready = true;
}, 300);
})();
}, [sessionId, loading, commands, onReady]);
const onLoad = useCallback(async () => {
state.current.ready = true;
onReady();
postMessage(EditorEvents.theme, theme || useThemeStore.getState().colors);
commands.setInsets(
isDefaultEditor ? insets : { top: 0, left: 0, right: 0, bottom: 0 }
);
if (currentNote.current) {
loadNote({ ...currentNote.current, forced: true });
} else {
await commands.setPlaceholder(placeholderTip.current);
}
commands.setSettings();
}, [
onReady,
postMessage,
@@ -622,8 +617,7 @@ export const useEditor = (
commands,
isDefaultEditor,
insets,
loadNote,
overlay
loadNote
]);
return {

View File

@@ -73,8 +73,7 @@ export async function post<T>(
ref: RefObject<WebView>,
sessionId: string,
type: string,
value: T | null = null,
waitFor = 300
value: T | null = null
) {
if (!sessionId) {
console.warn("post called without sessionId of type:", type);
@@ -86,7 +85,7 @@ export async function post<T>(
sessionId: sessionId
};
setImmediate(() => ref.current?.postMessage(JSON.stringify(message)));
const response = await getResponse(type, waitFor);
const response = await getResponse(type);
return response;
}
@@ -98,8 +97,7 @@ type WebviewResponseData = {
};
export const getResponse = async (
type: string,
waitFor = 300
type: string
): Promise<WebviewResponseData | false> => {
return new Promise((resolve) => {
const callback = (data: WebviewResponseData) => {
@@ -109,7 +107,7 @@ export const getResponse = async (
eSubscribeEvent(type, callback);
setTimeout(() => {
resolve(false);
}, waitFor);
}, 5000);
});
};

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, textInput } from "./tiptap/utils";
import { editorController, editorState, textInput } from "./tiptap/utils";
export const EditorWrapper = ({ width }) => {
const colors = useThemeStore((state) => state.colors);
const deviceMode = useSettingStore((state) => state.deviceMode);
@@ -48,6 +48,7 @@ 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

@@ -118,7 +118,6 @@ const Notebook = ({ route, navigation }: NavigationProps<"Notebook">) => {
get: () => {
const notebook = db.notebooks?.notebook(params?.current?.item?.id)
?.data as NotebookType;
if (!notebook) return [];
return db.relations?.from(notebook, "note");
}
});

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 "react-native-blob-util";
import RNFetchBlob from "rn-fetch-blob";
import { db } from "../../common/database";
import Storage from "../../common/database/storage";
import DialogHeader from "../../components/dialog/dialog-header";

View File

@@ -39,7 +39,6 @@ import { useThemeStore } from "../../stores/use-theme-store";
import { useUserStore } from "../../stores/use-user-store";
import { getElevation } from "../../utils";
import { SIZE } from "../../utils/size";
import { requestInAppReview } from "../../services/app-review";
const AppLock = ({ route }) => {
const colors = useThemeStore((state) => state.colors);
const appLockMode = useSettingStore((state) => state.settings.appLockMode);
@@ -152,9 +151,6 @@ const AppLock = ({ route }) => {
return;
}
SettingsService.set({ appLockMode: item.value });
if (!welcome) {
requestInAppReview();
}
}}
customStyle={{
justifyContent: "flex-start",

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 "@notesnook/logger";
import { LogMessage } from "@streetwriters/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 "react-native-blob-util";
import RNFetchBlob from "rn-fetch-blob";
import Storage from "../../common/database/storage";
import { presentDialog } from "../../components/dialog/functions";
import { IconButton } from "../../components/ui/icon-button";

View File

@@ -20,6 +20,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { ToolId } from "@notesnook/editor/dist/toolbar";
import React, { RefObject } from "react";
import { View } from "react-native";
import ActionSheet from "react-native-actions-sheet";
import { ScrollView } from "react-native-gesture-handler";
import { PressableButton } from "../../../components/ui/pressable";
import { SvgView } from "../../../components/ui/svg";
import Paragraph from "../../../components/ui/typography/paragraph";
@@ -32,14 +34,13 @@ import {
getToolIcon,
getUngroupedTools
} from "./toolbar-definition";
import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
export default function ToolSheet({
group,
fwdRef
}: {
group: DraggableItem;
fwdRef: RefObject<ActionSheetRef>;
fwdRef: RefObject<ActionSheet>;
}) {
const colors = useThemeStore((state) => state.colors);
const data = useDragState((state) => state.data);
@@ -49,7 +50,6 @@ export default function ToolSheet({
(item: ToolId) => {
const tool = findToolById(item);
const iconSvgString = tool ? getToolIcon(tool.icon as ToolId) : null;
if (item === "none") return;
return (
<PressableButton
key={item}
@@ -109,7 +109,12 @@ export default function ToolSheet({
padding: 12
}}
>
<ScrollView nestedScrollEnabled={true}>
<ScrollView
onMomentumScrollEnd={() => {
fwdRef.current?.handleChildScrollEnd();
}}
nestedScrollEnabled={true}
>
{!ungrouped || ungrouped.length === 0 ? (
<Paragraph
style={{

View File

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

View File

@@ -109,7 +109,7 @@ const SettingsUserSection = ({ item }) => {
flexGrow: 1
}}
>
<Heading color={colors.accent} size={SIZE.xs}>
<Heading color={colors.accent} size={SIZE.xs + 1}>
{SUBSCRIPTION_STATUS_STRINGS[
user.subscription?.type
]?.toUpperCase() || "Basic"}

View File

@@ -1,47 +0,0 @@
/*
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 "react-native-blob-util";
import RNFetchBlob from "rn-fetch-blob";
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 "react-native-blob-util";
import RNFetchBlob from "rn-fetch-blob";
import { DatabaseLogger, db } from "../common/database/index";
import Storage from "../common/database/storage";
import { toTXT } from "../utils";

View File

@@ -45,7 +45,6 @@ import { sleep } from "../utils/time";
import { useRelationStore } from "../stores/use-relation-store";
import { useReminderStore } from "../stores/use-reminder-store";
import { presentDialog } from "../components/dialog/functions";
import NetInfo from "@react-native-community/netinfo";
export type Reminder = {
id: string;
@@ -194,7 +193,7 @@ const onEvent = async ({ type, detail }: Event) => {
case "Hide":
unpinQuickNote();
break;
case "ReplyInput": {
case "ReplyInput":
displayNotification({
title: "Quick note",
message: 'Tap on "Take note" to add a note.',
@@ -204,25 +203,16 @@ const onEvent = async ({ type, detail }: Event) => {
reply_button_text: "Take note",
reply_placeholder_text: "Write something..."
});
if (!db.isInitialized) await db.init();
await db.notes?.init();
await initDatabase(false);
await db.notes?.add({
content: {
type: "tiptap",
data: `<p>${input} </p>`
}
});
const status = await NetInfo.fetch();
if (status.isInternetReachable) {
try {
await db.sync(false, false);
} catch (e) {
console.log(e, (e as Error).stack);
}
}
await db.notes?.init();
useNoteStore.getState().setNotes();
break;
}
}
}
};

View File

@@ -98,7 +98,7 @@ async function getProducts() {
}
function get() {
if (__DEV__ || Config.isTesting === "true") return true;
if (__DEV__ || Config.isTesting) return true;
return SUBSCRIPTION_STATUS.BASIC !== premiumStatus;
}
@@ -154,11 +154,9 @@ const onUserStatusCheck = async (type) => {
};
break;
case CHECK_IDS.notebookAdd:
message = {
context: "sheet",
title: "Get Notesnook Pro",
desc: "With Notesnook Pro you can create unlimited notebooks and do so much more! Get it now."
};
setTimeout(() => {
eSendEvent(eOpenPremiumDialog);
}, 500);
break;
case CHECK_IDS.vaultAdd:
message = {

View File

@@ -37,8 +37,6 @@ 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) => ({
@@ -54,12 +52,5 @@ export const useUserStore = create<UserStore>((set) => ({
},
setLastSynced: (lastSynced) => set({ lastSynced: lastSynced }),
setVerifyUser: (verified) => set({ verifyUser: verified }),
lastSyncStatus: SyncStatus.Never,
shouldBlockVerifyUser: false,
setShouldBlockVerifyUser: (shouldBlockVerifyUser) => {
set({ shouldBlockVerifyUser });
setTimeout(() => {
set({ shouldBlockVerifyUser: false });
}, 1000);
}
lastSyncStatus: SyncStatus.Never
}));

View File

@@ -164,7 +164,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled true
versionCode 2049
versionCode 2044
versionName getNpmVersion()
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
@@ -276,13 +276,6 @@ 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

@@ -51,7 +51,8 @@
android:theme="@style/BootTheme"
android:largeHeap="true"
android:supportsRtl="false"
tools:replace="android:supportsRtl">
tools:replace="android:supportsRtl"
android:usesCleartextTraffic="true">
<receiver android:exported="false" android:name=".NoteWidget">
<intent-filter>

View File

@@ -1,6 +1,10 @@
- Preview PDFs & Images directly inside Notesnook."
- Improved attachments manager with support for downloading all attachments
- Assign tags to multiple notes
- Added default font size & font family settings
- Redesign reminder/notebook sheets to improve UX
- Fixed an issue causing sync to fail
- Fix tag suggestions in share extension
- Fix app font sizes too small in some places
- Improved mobile editor performance on low-end devices
- Fix pin to notifications not working
- Bug fixes and performance improvements
Thank you for using Notesnook!

View File

@@ -7,15 +7,6 @@ import Config from 'react-native-config';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import appJson from './app.json';
import Notifications from "../app/services/notifications";
import NetInfo from "@react-native-community/netinfo";
NetInfo.configure({
reachabilityUrl: "https://notesnook.com",
reachabilityTest: (response) => {
if (!response) return false;
return response?.status >= 200 && response?.status < 300;
}
});
Notifications.init();
const appName = appJson.name;

View File

@@ -993,7 +993,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2041;
CURRENT_PROJECT_VERSION = 2036;
DEVELOPMENT_TEAM = 53CWBG3QUC;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1067,7 +1067,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.5.0;
MARKETING_VERSION = 2.4.13;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -1097,7 +1097,7 @@
CODE_SIGN_ENTITLEMENTS = Notesnook/Notesnook.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2041;
CURRENT_PROJECT_VERSION = 2036;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
@@ -1170,7 +1170,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.5.0;
MARKETING_VERSION = 2.4.13;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = (
"$(inherited)",
@@ -1328,7 +1328,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2041;
CURRENT_PROJECT_VERSION = 2036;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1340,7 +1340,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.5.0;
MARKETING_VERSION = 2.4.13;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
@@ -1370,7 +1370,7 @@
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2041;
CURRENT_PROJECT_VERSION = 2036;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1382,7 +1382,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.5.0;
MARKETING_VERSION = 2.4.13;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1411,7 +1411,7 @@
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2041;
CURRENT_PROJECT_VERSION = 2036;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1485,7 +1485,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.5.0;
MARKETING_VERSION = 2.4.13;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
@@ -1515,7 +1515,7 @@
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2041;
CURRENT_PROJECT_VERSION = 2036;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1589,7 +1589,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.5.0;
MARKETING_VERSION = 2.4.13;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -1,11 +1,8 @@
PODS:
- Base64 (1.1.2)
- BEMCheckBox (1.4.1)
- boost (1.76.0)
- callstack-repack (3.2.0):
- JWTDecode (~> 3.0)
- callstack-repack (3.0.0):
- React-Core
- SwiftyRSA
- DoubleConversion (1.1.6)
- FBLazyVector (0.69.7)
- FBReactNativeSpec (0.69.7):
@@ -18,7 +15,6 @@ 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)
@@ -238,11 +234,9 @@ PODS:
- React-Core
- react-native-begin-background-task (0.1.0):
- React
- 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-native-config (1.4.11):
- react-native-config/App (= 1.4.11)
- react-native-config/App (1.4.11):
- React-Core
- react-native-date-picker (4.2.6):
- React-Core
@@ -250,7 +244,7 @@ PODS:
- React-Core
- react-native-fingerprint-scanner (5.0.0):
- React-Core
- react-native-get-random-values (1.9.0):
- react-native-get-random-values (1.8.0):
- React-Core
- react-native-gzip (1.0.0):
- Base64
@@ -262,22 +256,18 @@ 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.10):
- react-native-netinfo (9.3.7):
- React-Core
- react-native-notification-sounds (0.5.5):
- React
- react-native-orientation (3.1.3):
- React
- react-native-pdf (6.6.2):
- React-Core
- react-native-safe-area-context (4.5.3):
- react-native-safe-area-context (4.4.1):
- RCT-Folly
- RCTRequired
- RCTTypeSafety
@@ -285,7 +275,7 @@ PODS:
- ReactCommon/turbomodule/core
- react-native-sodium (1.3.0):
- React
- react-native-webview (11.26.1):
- react-native-webview (11.23.1):
- React-Core
- React-perflogger (0.69.7)
- React-RCTActionSheet (0.69.7):
@@ -355,14 +345,15 @@ PODS:
- React-perflogger (= 0.69.7)
- rn-extensions-share (2.4.0):
- React-Core
- RNBootSplash (4.7.1):
- rn-fetch-blob (0.12.0):
- React-Core
- RNCCheckbox (0.5.15):
- BEMCheckBox (~> 1.4)
- RNBootSplash (4.3.2):
- React-Core
- RNCClipboard (1.11.2):
- RNCCheckbox (0.5.12):
- React-Core
- RNCMaskedView (0.2.9):
- RNCClipboard (1.11.1):
- React-Core
- RNCMaskedView (0.2.8):
- React-Core
- RNDateTimePicker (6.6.0):
- React-Core
@@ -374,7 +365,7 @@ PODS:
- React-Core
- RNFlashList (1.4.0):
- React-Core
- RNGestureHandler (2.10.1):
- RNGestureHandler (2.7.1):
- React-Core
- RNIap (7.5.6):
- React-Core
@@ -414,32 +405,21 @@ PODS:
- React-RCTText
- ReactCommon/turbomodule/core
- Yoga
- RNScreens (3.20.0):
- RNScreens (3.18.2):
- React-Core
- React-RCTImage
- RNSecureRandom (1.0.1):
- React
- RNShare (7.9.1):
- React-Core
- RNSVG (12.5.1):
- RNSVG (12.4.4):
- 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)
@@ -471,7 +451,6 @@ 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`)
@@ -481,13 +460,11 @@ 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-sodium (from `../../node_modules/@ammarahmed/react-native-sodium`)"
- react-native-webview (from `../../node_modules/react-native-webview`)
@@ -504,6 +481,7 @@ DEPENDENCIES:
- 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`)"
@@ -524,7 +502,6 @@ 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`)
@@ -532,15 +509,11 @@ DEPENDENCIES:
SPEC REPOS:
trunk:
- Base64
- BEMCheckBox
- fmt
- GZIP
- JWTDecode
- MMKV
- MMKVCore
- pop
- SSZipArchive
- SwiftyRSA
EXTERNAL SOURCES:
boost:
@@ -589,8 +562,6 @@ 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:
@@ -609,8 +580,6 @@ 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:
@@ -621,8 +590,6 @@ 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-sodium:
@@ -655,6 +622,8 @@ EXTERNAL SOURCES:
: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:
@@ -695,8 +664,6 @@ 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:
@@ -711,16 +678,14 @@ CHECKOUT OPTIONS:
SPEC CHECKSUMS:
Base64: cecfb41a004124895a7bcee567a89bae5a89d49b
BEMCheckBox: 5ba6e37ade3d3657b36caecc35c8b75c6c2b1a4e
boost: a7c83b31436843459a1961bfd74b96033dc77234
callstack-repack: 3e48a96824e0e0411ae1f48749a2ab103aa62a3a
callstack-repack: 9e5425dfffeda7ea87b71729c4097141c7d8ce1c
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
FBLazyVector: 6b7f5692909b4300d50e7359cdefbcd09dd30faa
FBReactNativeSpec: f53cf57758c70c6bfba5230b739cd1071e7a6824
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: 3d02b25ca00c2d456734d0bcff864cbc62f6ae1a
GZIP: 416858efbe66b41b206895ac6dfd5493200d95b3
JWTDecode: 2eed97c2fa46ccaf3049a787004eedf0be474a87
MMKV: aac95d817a100479445633f2b3ed8961b4ac5043
MMKVCore: 3388952ded307e41b3ed8a05892736a236ed1b8e
pop: d582054913807fd11fd50bfe6a539d91c7e1a55a
@@ -741,26 +706,23 @@ SPEC CHECKSUMS:
react-native-actions-shortcuts: 5d9cf0c9c308333dfcc1e05c3f9afa8c428e2533
react-native-background-actions: 2c251c986f23347f9c1722f05fd296938f60edb1
react-native-begin-background-task: 3b889e07458afc5822a7277cf9cbc7cd077e39ee
react-native-blob-util: 99f4d79189252f597fe0d810c57a3733b1b1dea6
react-native-config: 86038147314e2e6d10ea9972022aa171e6b1d4d8
react-native-config: bcafda5b4c51491ee1b0e1d0c4e3905bc7b56c1b
react-native-date-picker: 93e43b3084cea595b4d68b1405d6d99849663bd6
react-native-document-picker: ec07866a30707f23660c0f3ae591d669d3e89096
react-native-fingerprint-scanner: be63e626b31fb951780a5fac5328b065a61a3d6e
react-native-get-random-values: dee677497c6a740b71e5612e8dbd83e7539ed5bb
react-native-get-random-values: a6ea6a8a65dc93e96e24a11105b1a9c8cfe1d72a
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: ccbe1085dffd16592791d550189772e13bf479e2
react-native-netinfo: 2517ad504b3d303e90d7a431b0fcaef76d207983
react-native-notification-sounds: da78c828fe1bcbb92d8b505d5261890ed315ff39
react-native-orientation: f1caf84d65f1a4fd4511a18f2b924e634ad7a628
react-native-pdf: 33c622cbdf776a649929e8b9d1ce2d313347c4fa
react-native-safe-area-context: b8979f5eda6ed5903d4dbc885be3846ea3daa753
react-native-safe-area-context: 99b24a0c5acd0d5dcac2b1a7f18c49ea317be99a
react-native-sodium: 1681828855ec18fa952f4557cd595bf048cf5c32
react-native-webview: 9f111dfbcfc826084d6c507f569e5e03342ee1c1
react-native-webview: d33e2db8925d090871ffeb232dfa50cb3a727581
React-perflogger: 8e832d4e21fdfa613033c76d58d7e617341e804b
React-RCTActionSheet: 9ca778182a9523991bff6381045885b6e808bb73
React-RCTAnimation: 9ced26ad20b96e532ac791a8ab92a7b1ce2266b8
@@ -774,30 +736,28 @@ SPEC CHECKSUMS:
React-runtimeexecutor: 65cd2782a57e1d59a68aa5d504edf94278578e41
ReactCommon: 1e783348b9aa73ae68236271df972ba898560a95
rn-extensions-share: 3f0ecce20dfbca1f0358deb4ebfb9ee121a6d92a
RNBootSplash: 3f3f7f82efe2addbfe7ddeda20877ff4d579cd81
RNCCheckbox: 43bcc6493611468af0e19f19f029dab3da8561c4
RNCClipboard: 3f0451a8100393908bea5c5c5b16f96d45f30bfc
RNCMaskedView: 949696f25ec596bfc697fc88e6f95cf0c79669b6
rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba
RNBootSplash: 5f346163977573d6b2aeba1b25df9d2245c0d73c
RNCCheckbox: ed1b4ca295475b41e7251ebae046360a703b6eb5
RNCClipboard: 2834e1c4af68697089cdd455ee4a4cdd198fa7dd
RNCMaskedView: bc0170f389056201c82a55e242e5d90070e18e5a
RNDateTimePicker: 818672afa85519722533d017b832ed09539d9ddb
RNDeviceInfo: aad3c663b25752a52bf8fce93f2354001dd185aa
RNExitApp: c4e052df2568b43bec8a37c7cd61194d4cfee2c3
RNFileViewer: ce7ca3ac370e18554d35d6355cffd7c30437c592
RNFlashList: 399bf6a0db68f594ad2c86aaff3ea39564f39f8a
RNGestureHandler: 42ec7c28dd02d540ed6c9159c57a98ff016492dc
RNGestureHandler: b7a872907ee289ada902127f2554fa1d2c076122
RNIap: d248609d1b8937e63bd904e865c318e9b1457eff
RNKeychain: 840f8e6f13be0576202aefcdffd26a4f54bfe7b5
RNNotifee: 2ae3c18196e6f307fa62ae5c8e5305dea03ff147
RNPrivacySnapshot: 8eaf571478a353f2e5184f5c803164f22428b023
RNReanimated: f1b109fb8341505ace9d7d2eedd150da1686716b
RNScreens: 218801c16a2782546d30bd2026bb625c0302d70f
RNScreens: 34cc502acf1b916c582c60003dc3089fa01dc66d
RNSecureRandom: 07efbdf2cd99efe13497433668e54acd7df49fef
RNShare: a5dc3b9c53ddc73e155b8cd9a94c70c91913c43c
RNSVG: d7d7bc8229af3842c9cfc3a723c815a52cdd1105
RNSVG: ecd661f380a07ba690c9c5929c475a44f432d674
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.2.0",
"@callstack/repack": "^3.0.0",
"@react-native-clipboard/clipboard": "^1.9.0",
"@react-native-community/checkbox": "^0.5.8",
"@react-native-community/netinfo": "^9.3.7",
@@ -46,8 +46,10 @@
"@ammarahmed/react-native-sodium": "1.3.0",
"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",
@@ -57,12 +59,7 @@
"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-in-app-review": "4.3.3",
"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"
"react-native-actions-shortcuts": "^1.0.1"
},
"devDependencies": {
"@babel/core": "^7.12.9",
@@ -101,7 +98,6 @@
"react-test-renderer": "18.0.0",
"terser-webpack-plugin": "^5.3.5",
"ts-jest": "^28.0.7",
"webpack": "^5.74.0",
"react-refresh": "0.14.0"
"webpack": "^5.74.0"
}
}

View File

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

View File

@@ -1,6 +1,10 @@
- Preview PDFs & Images directly inside Notesnook."
- Improved attachments manager with support for downloading all attachments
- Assign tags to multiple notes
- Added default font size & font family settings
- Redesign reminder/notebook sheets to improve UX
- Fixed an issue causing sync to fail
- Fix tag suggestions in share extension
- Fix app font sizes too small in some places
- Improved mobile editor performance on low-end devices
- Fix pin to notifications not working
- Bug fixes and performance improvements
Thank you for using Notesnook!

View File

@@ -168,7 +168,6 @@ module.exports = (env) => {
/node_modules(.*[/\\])+@microsoft/,
/node_modules(.*[/\\])+@msgpack/,
/node_modules(.*[/\\])+liqe/,
/node_modules(.*[/\\])+leac/,
/node_modules(.*[/\\])+selderee/,
/node_modules(.*[/\\])+html-to-text/,
/node_modules(.*[/\\])+buffer/,

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,89 @@
diff --git a/node_modules/leac/lib/leac.cjs b/node_modules/leac/lib/leac.cjs
index 13123da..9d490e2 100644
--- a/node_modules/leac/lib/leac.cjs
+++ b/node_modules/leac/lib/leac.cjs
@@ -1 +1,83 @@
-"use strict";Object.defineProperty(exports,"__esModule",{value:!0});const e=/\n/g;function t(t){const o=[...t.matchAll(e)].map((e=>e.index||0));o.unshift(-1);const s=n(o,0,o.length);return e=>r(s,e)}function n(e,t,r){if(r-t==1)return{offset:e[t],index:t+1};const o=Math.ceil((t+r)/2),s=n(e,t,o),l=n(e,o,r);return{offset:s.offset,low:s,high:l}}function r(e,t){return function(e){return Object.prototype.hasOwnProperty.call(e,"index")}(e)?{line:e.index,column:t-e.offset}:r(e.high.offset<t?e.high:e.low,t)}function o(e,t){return{...e,regex:s(e,t)}}function s(e,t){if(0===e.name.length)throw new Error(`Rule #${t} has empty name, which is not allowed.`);if(function(e){return Object.prototype.hasOwnProperty.call(e,"regex")}(e))return function(e){if(e.global)throw new Error(`Regular expression /${e.source}/${e.flags} contains the global flag, which is not allowed.`);return e.sticky?e:new RegExp(e.source,e.flags+"y")}(e.regex);if(function(e){return Object.prototype.hasOwnProperty.call(e,"str")}(e)){if(0===e.str.length)throw new Error(`Rule #${t} ("${e.name}") has empty "str" property, which is not allowed.`);return new RegExp(l(e.str),"y")}return new RegExp(l(e.name),"y")}function l(e){return e.replace(/[-[\]{}()*+!<=:?./\\^$|#\s,]/g,"\\$&")}exports.createLexer=function(e,n="",r={}){const s="string"!=typeof n?n:r,l="string"==typeof n?n:"",c=e.map(o),i=!!s.lineNumbers;return function(e,n=0){const r=i?t(e):()=>({line:0,column:0});let o=n;const s=[];e:for(;o<e.length;){let t=!1;for(const n of c){n.regex.lastIndex=o;const c=n.regex.exec(e);if(c&&c[0].length>0){if(!n.discard){const e=r(o),t="string"==typeof n.replace?c[0].replace(new RegExp(n.regex.source,n.regex.flags),n.replace):c[0];s.push({state:l,name:n.name,text:t,offset:o,len:c[0].length,line:e.line,column:e.column})}if(o=n.regex.lastIndex,t=!0,n.push){const t=n.push(e,o);s.push(...t.tokens),o=t.offset}if(n.pop)break e;break}}if(!t)break}return{tokens:s,offset:o,complete:e.length<=o}}};
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: !0 });
+const e = /\n/g;
+function t(t) {
+ const o = [...t.matchAll(e)].map((e) => e.index || 0);
+ o.unshift(-1);
+ const s = n(o, 0, o.length);
+ return (e) => r(s, e);
+}
+function n(e, t, r) {
+ if (r - t == 1) return { offset: e[t], index: t + 1 };
+ const o = Math.ceil((t + r) / 2),
+ s = n(e, t, o),
+ l = n(e, o, r);
+ return { offset: s.offset, low: s, high: l };
+}
+function r(e, t) {
+ return (function (e) {
+ return Object.prototype.hasOwnProperty.call(e, "index");
+ })(e)
+ ? { line: e.index, column: t - e.offset }
+ : r(e.high.offset < t ? e.high : e.low, t);
+}
+function _o(e, t) {
+ return { ...e, regex: s(e, t) };
+}
+function s(e, t) {
+ if (0 === e.name.length) throw new Error(`Rule #${t} has empty name, which is not allowed.`);
+ if (
+ (function (e) {
+ return Object.prototype.hasOwnProperty.call(e, "regex");
+ })(e)
+ )
+ return (function (e) {
+ if (e.global) throw new Error(`Regular expression /${e.source}/${e.flags} contains the global flag, which is not allowed.`);
+ return e.sticky ? e : new RegExp(e.source, e.flags + "y");
+ })(e.regex);
+ if (
+ (function (e) {
+ return Object.prototype.hasOwnProperty.call(e, "str");
+ })(e)
+ ) {
+ if (0 === e.str.length) throw new Error(`Rule #${t} ("${e.name}") has empty "str" property, which is not allowed.`);
+ return new RegExp(l(e.str), "y");
+ }
+ return new RegExp(l(e.name), "y");
+}
+function l(e) {
+ return e.replace(/[-[\]{}()*+!<=:?./\\^$|#\s,]/g, "\\$&");
+}
+exports.createLexer = function (_e, n = "", r = {}) {
+ const s = "string" != typeof n ? n : r,
+ l = "string" == typeof n ? n : "",
+ i = !!s.lineNumbers;
+ return function (e, n = 0) {
+ const r = i ? t(e) : () => ({ line: 0, column: 0 });
+ let o = n;
+ const s = [];
+ e: for (; o < e.length; ) {
+ let t = !1;
+ let z = _e.map(_o);
+ for (const n of z) {
+ n.regex.lastIndex = o;
+ const c = n.regex.exec(e);
+ if (c && c[0].length > 0) {
+ if (!n.discard) {
+ const e = r(o),
+ t = "string" == typeof n.replace ? c[0].replace(new RegExp(n.regex.source, n.regex.flags), n.replace) : c[0];
+ s.push({ state: l, name: n.name, text: t, offset: o, len: c[0].length, line: e.line, column: e.column });
+ }
+ if (((o = n.regex.lastIndex), (t = !0), n.push)) {
+ const t = n.push(e, o);
+ s.push(...t.tokens), (o = t.offset);
+ }
+ if (n.pop) break e;
+ break;
+ }
+ }
+ if (!t) break;
+ }
+ return { tokens: s, offset: o, complete: true };
+ };
+};

View File

@@ -0,0 +1,103 @@
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

@@ -26,12 +26,12 @@ import {
Keyboard,
Platform,
SafeAreaView,
ScrollView,
StatusBar,
Text,
TouchableOpacity,
useWindowDimensions,
View,
useWindowDimensions
ScrollView
} from "react-native";
import {
SafeAreaProvider,
@@ -45,10 +45,10 @@ import Storage from "../app/common/database/storage";
import { eSendEvent } from "../app/services/event-manager";
import { getElevation } from "../app/utils";
import { eOnLoadNote } from "../app/utils/events";
import { Editor } from "./editor";
import { sleep } from "../app/utils/time";
import { Search } from "./search";
import { initDatabase, useShareStore } from "./store";
import NetInfo from "@react-native-community/netinfo";
import { Editor } from "./editor";
const getLinkPreview = (url) => {
return getPreviewData(url, 5000);
};
@@ -63,15 +63,16 @@ async function sanitizeHtml(site) {
}
function makeHtmlFromUrl(url) {
return `<a href='${url}' target='_blank'>${url}</a>`;
return `<a style="overflow-wrap:anywhere;white-space:pre-wrap" href='${url}' target='_blank'>${url}</a>`;
}
function makeHtmlFromPlainText(text) {
if (!text) return "";
return `<p>${text
.replace(/[\n]+/g, "\n")
.replace(/(?:\r\n|\r|\n)/g, "</p><p>")}</p>`;
return `<p style="overflow-wrap:anywhere;white-space:pre-wrap" >${text.replace(
/(?:\r\n|\r|\n)/g,
"<br>"
)}</p>`;
}
function getBaseUrl(site) {
@@ -314,6 +315,7 @@ const ShareView = ({ quicknote = false }) => {
const onPress = async () => {
setLoading(true);
await initDatabase();
await sleep(1500);
if (!noteContent.current) return;
if (appendNote && !db.notes.note(appendNote.id)) {
useShareStore.getState().setAppendNote(null);
@@ -356,14 +358,6 @@ const ShareView = ({ quicknote = false }) => {
}
}
}
const status = await NetInfo.fetch();
if (status.isInternetReachable) {
try {
await db.sync(false, false);
} catch (e) {
console.log(e, e.stack);
}
}
await Storage.write("notesAddedFromIntent", "added");
close();
setLoading(false);

View File

@@ -29,7 +29,8 @@ import { db } from "../app/common/database";
export async function initDatabase() {
if (!db.isInitialized) {
await db.init();
// Only load collections in database.
await db.initCollections();
}
await db.notes.init();
}

View File

@@ -256,23 +256,6 @@ 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

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

View File

@@ -1,34 +0,0 @@
/*
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 {
TransformStream,
ReadableStream,
WritableStream
} from "node:stream/web";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
globalThis.TransformStream = TransformStream;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
globalThis.ReadableStream = ReadableStream;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
globalThis.WritableStream = WritableStream;

View File

@@ -1,45 +0,0 @@
/*
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 { ChunkedStream } from "../src/utils/streams/chunked-stream";
import { Readable } from "stream";
import { createReadStream } from "fs";
import { consumeReadableStream } from "../src/utils/stream";
import { xxhash64 } from "hash-wasm";
import path from "path";
const CHUNK_SIZE = 512 * 1024;
test("chunked stream should create equal sized chunks", async (t) => {
const chunks = await consumeReadableStream(
(
Readable.toWeb(
createReadStream(
path.join(__dirname, "..", "__e2e__", "data", "importer-data.zip")
)
) as ReadableStream<Uint8Array>
).pipeThrough(new ChunkedStream(CHUNK_SIZE))
);
t.expect(await Promise.all(chunks.map((a) => xxhash64(a)))).toMatchObject([
"6234b76401d9eb97",
"338834da3f6500b2"
]);
});

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/desktop",
"version": "2.5.0",
"version": "2.4.8",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@notesnook/desktop",
"version": "2.5.0",
"version": "2.4.8",
"dependencies": {
"diary": "^0.3.1",
"electron-updater": "^5.3.0",

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "2.5.0",
"version": "2.4.8",
"appAppleId": "1544027013",
"private": true,
"main": "./build/electron.js",

View File

@@ -22,8 +22,8 @@ import { isDevelopment, getPath } from "./utils";
import { createReadStream } from "fs";
import { extname, normalize } from "path";
import { logger } from "./logger";
import { URL } from "url";
import { Blob } from "buffer";
import { URL } from "url";
const FILE_NOT_FOUND = -6;
const BASE_PATH = isDevelopment() ? "../public" : "";
@@ -75,10 +75,9 @@ function registerProtocol() {
...request,
body,
headers: {
...request.headers
// origin: `${PROTOCOL}://${HOSTNAME}/`
...request.headers,
origin: `${PROTOCOL}://${HOSTNAME}/`
},
referrer: request.referrer,
redirect: "manual"
});
} catch (e) {
@@ -104,7 +103,7 @@ function registerProtocol() {
);
}
const bypassedRoutes = [];
const bypassedRoutes = ["/notes/index_v14.json", "/notes/welcome-web"];
function shouldInterceptRequest(url) {
let shouldIntercept = url.hostname === HOSTNAME;
return shouldIntercept && !bypassedRoutes.includes(url.pathname);
@@ -118,14 +117,14 @@ async function getBody(request) {
/**
* @type {Electron.Session}
*/
const session = globalThis?.window?.webContents?.session;
const session = globalThis.window.webContents.session;
const blobParts = [];
if (!request.uploadData || !request.uploadData.length) return null;
for (let data of request.uploadData) {
if (data.type === "rawData") {
blobParts.push(new Uint8Array(data.bytes));
} else if (session && data.type === "blob") {
} else if (data.type === "blob") {
const buffer = await session.getBlobData(data.blobUUID);
blobParts.push(new Uint8Array(buffer));
}

19227
apps/web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "@notesnook/web",
"description": "Your private note taking space",
"version": "2.5.0",
"version": "2.4.8",
"private": true,
"main": "./src/app.js",
"homepage": "https://notesnook.com/",
@@ -27,14 +27,12 @@
"@notesnook/streamable-fs": "*",
"@notesnook/theme": "*",
"@notesnook/web-clipper": "*",
"@react-pdf-viewer/core": "^3.12.0",
"@react-pdf-viewer/toolbar": "^3.12.0",
"@tanstack/react-virtual": "^3.0.0-beta.18",
"@theme-ui/components": "^0.14.7",
"@theme-ui/core": "^0.14.7",
"allotment": "^1.12.1",
"async-mutex": "^0.3.2",
"axios": "^1.3.4",
"axios": "^0.21.4",
"clipboard-polyfill": "^3.0.3",
"comlink": "^4.3.1",
"cronosjs": "^1.7.1",
@@ -50,7 +48,6 @@
"localforage-driver-memory": "^1.0.5",
"mac-scrollbar": "^0.10.3",
"marked": "^4.1.0",
"pdfjs-dist": "^3.6.172",
"phone": "^3.1.14",
"platform": "^1.3.6",
"print-js": "^1.6.0",
@@ -88,7 +85,6 @@
"env-cmd": "^10.1.0",
"file-loader": "^6.2.0",
"find-process": "^1.4.4",
"happy-dom": "^8.9.0",
"ip": "^1.1.8",
"lorem-ipsum": "^2.0.4",
"otplib": "^12.0.1",
@@ -98,7 +94,6 @@
"react-dom": "17.0.2",
"source-map-explorer": "^2.5.2",
"typescript": "^4.8.2",
"vitest": "^0.29.8",
"webpack-bundle-analyzer": "^4.8.0",
"worker-loader": "^3.0.8"
},

File diff suppressed because one or more lines are too long

View File

@@ -1,154 +0,0 @@
/*
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/>.
*/
/* eslint-disable no-restricted-globals */
self.addEventListener("install", () => {
self.skipWaiting();
});
self.addEventListener("activate", (event) => {
event.waitUntil(self.clients.claim());
});
const map = new Map();
// This should be called once per download
// Each event has a dataChannel that the data will be piped through
self.onmessage = (event) => {
// We send a heartbeat every x second to keep the
// service worker alive if a transferable stream is not sent
if (event.data === "ping") {
return;
}
const data = event.data;
const downloadUrl =
data.url ||
self.registration.scope +
Math.random() +
"/" +
(typeof data === "string" ? data : data.filename);
const port = event.ports[0];
const metadata = new Array(3); // [stream, data, port]
metadata[1] = data;
metadata[2] = port;
if (event.data.transferringReadable) {
port.onmessage = (evt) => {
port.onmessage = null;
metadata[0] = evt.data.readableStream;
};
} else {
metadata[0] = createStream(port);
}
map.set(downloadUrl, metadata);
port.postMessage({ download: downloadUrl });
};
function createStream(port) {
// ReadableStream is only supported by chrome 52
return new ReadableStream({
start(controller) {
// When we receive data on the messageChannel, we write
port.onmessage = ({ data }) => {
if (data === "end") {
return controller.close();
}
if (data === "abort") {
controller.error("Aborted the download");
return;
}
controller.enqueue(data);
};
},
cancel(reason) {
console.log("user aborted", reason);
port.postMessage({ abort: true });
}
});
}
self.onfetch = (event) => {
const url = event.request.url;
// this only works for Firefox
if (url.endsWith("/ping")) {
return event.respondWith(new Response("pong"));
}
const metadata = map.get(url);
if (!metadata) return null;
const [stream, data, port] = metadata;
map.delete(url);
// Not comfortable letting any user control all headers
// so we only copy over the length & disposition
const responseHeaders = new Headers({
"Content-Type": "application/octet-stream; charset=utf-8",
// To be on the safe side, The link can be opened in a iframe.
// but octet-stream should stop it.
"Content-Security-Policy": "default-src 'none'",
"X-Content-Security-Policy": "default-src 'none'",
"X-WebKit-CSP": "default-src 'none'",
"X-XSS-Protection": "1; mode=block"
});
let headers = new Headers(data.headers || {});
if (headers.has("Content-Length")) {
responseHeaders.set("Content-Length", headers.get("Content-Length"));
}
if (headers.has("Content-Disposition")) {
responseHeaders.set(
"Content-Disposition",
headers.get("Content-Disposition")
);
}
// data, data.filename and size should not be used anymore
if (data.size) {
console.warn("Depricated");
responseHeaders.set("Content-Length", data.size);
}
let fileName = typeof data === "string" ? data : data.filename;
if (fileName) {
console.warn("Depricated");
// Make filename RFC5987 compatible
fileName = encodeURIComponent(fileName)
.replace(/['()]/g, escape)
.replace(/\*/g, "%2A");
responseHeaders.set(
"Content-Disposition",
"attachment; filename*=UTF-8''" + fileName
);
}
event.respondWith(new Response(stream, { headers: responseHeaders }));
port.postMessage({ debug: "Download started" });
};

View File

@@ -45,8 +45,11 @@ import { isTesting } from "./utils/platform";
import { updateStatus, removeStatus, getStatus } from "./hooks/use-status";
import { showToast } from "./utils/toast";
import { interruptedOnboarding } from "./components/dialogs/onboarding-dialog";
import { WebExtensionRelay } from "./utils/web-extension-relay";
import { hashNavigate } from "./navigation";
const relay = new WebExtensionRelay();
export default function AppEffects({ setShow }) {
const refreshNavItems = useStore((store) => store.refreshNavItems);
const updateLastSynced = useStore((store) => store.updateLastSynced);
@@ -100,6 +103,7 @@ export default function AppEffects({ setShow }) {
await showOnboardingDialog(interruptedOnboarding());
await showFeatureDialog("highlights");
await scheduleBackups();
relay.connect();
})();
return () => {

View File

@@ -1,10 +1,3 @@
:root {
--focus-border: var(--primary);
--separator-border: var(--border);
--sash-size: 10px;
--sash-hover-size: 4px;
}
/* open-sans-regular - vietnamese_latin-ext_latin_hebrew_greek-ext_greek_cyrillic-ext_cyrillic */
@font-face {
font-family: "Open Sans";
@@ -102,8 +95,6 @@
format("truetype");
}
.rpv-core__text-layer,
.rpv-core__text-layer *,
.selectable,
.selectable *,
input,
@@ -129,17 +120,6 @@ textarea,
-webkit-tap-highlight-color: transparent;
}
.rpv-core__text-layer-text::selection {
background-color: var(--dimPrimary) !important;
color: transparent;
}
.rpv-core__text-layer-text::-moz-selection {
/* Code for Firefox */
background-color: var(--dimPrimary) !important;
color: transparent;
}
*::-moz-focus-inner {
border: 0;
}
@@ -185,6 +165,13 @@ textarea,
width: 1px !important;
}
:root {
--focus-border: var(--primary);
--separator-border: var(--border);
--sash-size: 10px;
--sash-hover-size: 4px;
}
.route#settings,
#mainRouteContainer {
overflow: hidden;

View File

@@ -25,6 +25,7 @@ import useTablet from "./hooks/use-tablet";
import { LazyMotion, domAnimation } from "framer-motion";
import useDatabase from "./hooks/use-database";
import { Allotment, LayoutPriority } from "allotment";
import "allotment/dist/style.css";
import Config from "./utils/config";
import { useStore } from "./stores/app-store";
import { Toaster } from "react-hot-toast";
@@ -34,9 +35,6 @@ import StatusBar from "./components/status-bar";
import { EditorLoader } from "./components/loaders/editor-loader";
import { FlexScrollContainer } from "./components/scroll-container";
import CachedRouter from "./components/cached-router";
import { WebExtensionRelay } from "./utils/web-extension-relay";
new WebExtensionRelay();
const GlobalMenuWrapper = React.lazy(() =>
import("./components/global-menu-wrapper")

View File

@@ -20,8 +20,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import FS from "../interfaces/fs";
import { db } from "./db";
async function download(hash: string) {
const attachment = db.attachments?.attachment(hash);
export async function downloadAttachment(hash) {
const attachment = db.attachments.attachment(hash);
if (!attachment) return;
const downloadResult = await db.fs.downloadFile(
attachment.metadata.hash,
@@ -31,17 +31,9 @@ async function download(hash: string) {
);
if (!downloadResult) throw new Error("Failed to download file.");
const key = await db.attachments?.decryptKey(attachment.key);
const key = await db.attachments.decryptKey(attachment.key);
if (!key) throw new Error("Invalid key for attachment.");
return { key, attachment };
}
export async function saveAttachment(hash: string) {
const response = await download(hash);
if (!response) return;
const { attachment, key } = response;
await FS.saveFile(attachment.metadata.hash, {
key,
iv: attachment.iv,
@@ -51,51 +43,23 @@ export async function saveAttachment(hash: string) {
});
}
type OutputTypeToReturnType = {
blob: Blob;
base64: string;
text: string;
};
export async function downloadAttachment<
TType extends "blob" | "base64" | "text",
TOutputType = OutputTypeToReturnType[TType]
>(hash: string, type: TType): Promise<TOutputType | undefined> {
const response = await download(hash);
if (!response) return;
const { attachment, key } = response;
if (type === "base64" || type === "text")
return (await db.attachments?.read(hash, type)) as TOutputType;
const blob = await FS.decryptFile(attachment.metadata.hash, {
key,
iv: attachment.iv,
name: attachment.metadata.filename,
type: attachment.metadata.type,
isUploaded: !!attachment.dateUploaded
});
if (!blob) return;
return blob as TOutputType;
}
export async function checkAttachment(hash: string) {
const attachment = db.attachments?.attachment(hash);
export async function checkAttachment(hash) {
const attachment = db.attachments.attachment(hash);
if (!attachment) return { failed: "Attachment not found." };
try {
const size = await FS.getUploadedFileSize(hash);
if (size <= 0) return { failed: "File length is 0." };
} catch (e) {
return { failed: e instanceof Error ? e.message : "Unknown error." };
return { failed: e.message };
}
return { success: true };
}
const ABYTES = 17;
export function getTotalSize(attachments: any[]) {
export function getTotalSize(attachments) {
let size = 0;
for (const attachment of attachments) {
for (let attachment of attachments) {
size += attachment.length + ABYTES;
}
return size;

View File

@@ -88,12 +88,6 @@ export function closeOpenedDialog() {
dialogs.forEach((elem) => elem.remove());
}
export function showAddTagsDialog(noteIds: string[]) {
return showDialog("AddTagsDialog", (Dialog, perform) => (
<Dialog onClose={(res) => perform(res)} noteIds={noteIds} />
));
}
export function showAddNotebookDialog() {
return showDialog("AddNotebookDialog", (Dialog, perform) => (
<Dialog
@@ -217,9 +211,8 @@ export function showError(title: string, message: string) {
export function showMultiDeleteConfirmation(length: number) {
return confirm({
title: `Delete ${length} items?`,
message: `These items will be **kept in your Trash for ${
db.settings?.getTrashCleanupInterval() || 7
} days** after which they will be permanently deleted.`,
message: `These items will be **kept in your Trash for ${db.settings?.getTrashCleanupInterval() || 7
} days** after which they will be permanently deleted.`,
positiveButtonText: "Yes",
negativeButtonText: "No"
});
@@ -338,12 +331,6 @@ export function showMoveNoteDialog(noteIds: string[]) {
));
}
export function showBillingHistoryDialog() {
return showDialog("BillingHistoryDialog", (Dialog, perform) => (
<Dialog onClose={(res: boolean) => perform(res)} />
));
}
function getDialogData(type: string) {
switch (type) {
case "create_vault":

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