mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-31 19:19:34 +02:00
Compare commits
4 Commits
2.5.0-andr
...
fix/editor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee359770ce | ||
|
|
9d2b445078 | ||
|
|
43421c5478 | ||
|
|
4b60e8bb9e |
@@ -20,7 +20,6 @@ const SCOPES = [
|
||||
"logger",
|
||||
"theme",
|
||||
"core",
|
||||
"fs",
|
||||
"clipper",
|
||||
"config",
|
||||
"ci",
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
))}
|
||||
</>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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} />
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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 += "*";
|
||||
}
|
||||
|
||||
@@ -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 />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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={{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -108,7 +108,7 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
|
||||
backgroundColor: "transparent",
|
||||
paddingHorizontal: 0
|
||||
}}
|
||||
fontSize={SIZE.xs}
|
||||
fontSize={SIZE.xs + 1}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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];
|
||||
};
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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={{
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
}));
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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!
|
||||
@@ -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;
|
||||
|
||||
@@ -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)";
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,11 +22,6 @@ if (isGithubRelease) {
|
||||
android:null
|
||||
}
|
||||
}
|
||||
config.dependencies["react-native-in-app-review"] = {
|
||||
platforms: {
|
||||
android: null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = config;
|
||||
|
||||
@@ -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!
|
||||
@@ -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/,
|
||||
|
||||
29237
apps/mobile/package-lock.json
generated
29237
apps/mobile/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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": "*"
|
||||
}
|
||||
}
|
||||
}
|
||||
89
apps/mobile/patches/leac+0.6.0.patch
Normal file
89
apps/mobile/patches/leac+0.6.0.patch
Normal 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 };
|
||||
+ };
|
||||
+};
|
||||
103
apps/mobile/patches/rn-fetch-blob+0.12.0.patch
Normal file
103
apps/mobile/patches/rn-fetch-blob+0.12.0.patch
Normal 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')
|
||||
-}
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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;
|
||||
@@ -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"
|
||||
]);
|
||||
});
|
||||
4
apps/web/desktop/package-lock.json
generated
4
apps/web/desktop/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
19227
apps/web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
},
|
||||
|
||||
22
apps/web/public/pdf.worker.min.js
vendored
22
apps/web/public/pdf.worker.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -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" });
|
||||
};
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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;
|
||||
@@ -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
Reference in New Issue
Block a user