Compare commits

..

1 Commits

Author SHA1 Message Date
Ammar Ahmed
0be9a03feb mobile: fix crash during backup 2024-04-30 00:16:30 +05:00
138 changed files with 1519 additions and 1846 deletions

69
.github/workflows/web.beta.publish.yml vendored Normal file
View File

@@ -0,0 +1,69 @@
name: Publish @notesnook/web Beta
on:
workflow_dispatch:
push:
branches:
- master
- "!v3-beta"
paths-ignore:
- "apps/mobile/**"
- "packages/editor-mobile/**"
- "apps/desktop/**"
- "apps/vericrypt/**"
- "servers/**"
- "packages/clipper/**"
- "extensions/web-clipper/**"
pull_request:
types:
- closed
- ready_for_review
- reopened
- opened
- synchronize
branches:
- master
- "!v3-beta"
paths-ignore:
- "apps/mobile/**"
- "packages/editor-mobile/**"
- "apps/desktop/**"
- "apps/vericrypt/**"
- "servers/**"
- "packages/clipper/**"
- "extensions/web-clipper/**"
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Install packages
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=web
- name: Setup environment
run: |
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV
- name: Generate beta build
run: npm run build:beta:web
- name: Publish to Cloudflare Pages
uses: unlike-ltd/github-actions-cloudflare-pages@v0.1.1
id: pages
with:
cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
cloudflare-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
cloudflare-project-name: notesnook-beta-app
directory: ./apps/web/build
github-token: ${{ secrets.GITHUB_TOKEN }}
github-environment: ${{ (github.ref == 'refs/heads/master' && 'beta') || 'preview' }}

View File

@@ -0,0 +1,32 @@
name: Publish @notesnook/web v3 Beta
on:
workflow_dispatch:
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Install packages
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=web
- name: Setup environment
run: |
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV
- name: Build
run: npm run build:web
- name: Publish to Cloudflare Pages
run: npx --yes wrangler pages deploy --project-name=notesnook-v3-beta ./apps/web/build/

Binary file not shown.

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/desktop",
"version": "3.0.2",
"version": "3.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/desktop",
"version": "3.0.2",
"version": "3.0.0",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "3.0.2",
"version": "3.0.0",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/index.js",

View File

@@ -32,7 +32,6 @@ import { AssetManager } from "../utils/asset-manager";
import { isFlatpak } from "../utils";
import { setupDesktopIntegration } from "../utils/desktop-integration";
import { rm } from "fs/promises";
import { disableCustomDns, enableCustomDns } from "../utils/custom-dns";
const t = initTRPC.create();
@@ -56,15 +55,6 @@ export const osIntegrationRouter = t.router({
config.zoomFactor = factor;
}),
customDns: t.procedure.query(() => config.customDns),
setCustomDns: t.procedure
.input(z.boolean())
.mutation(({ input: customDns }) => {
if (customDns) enableCustomDns();
else disableCustomDns();
config.customDns = customDns;
}),
proxyRules: t.procedure.query(() => config.proxyRules),
setProxyRules: t.procedure
.input(z.string().optional())

View File

@@ -35,7 +35,6 @@ import path from "path";
import { bringToFront } from "./utils/bring-to-front";
import { bridge } from "./api/bridge";
import { setupDesktopIntegration } from "./utils/desktop-integration";
import { disableCustomDns, enableCustomDns } from "./utils/custom-dns";
// only run a single instance
if (!MAC_APP_STORE && !app.requestSingleInstanceLock()) {
@@ -152,8 +151,14 @@ async function createWindow() {
app.once("ready", async () => {
console.info("App ready. Opening window.");
if (config.customDns) enableCustomDns();
else disableCustomDns();
app.configureHostResolver({
secureDnsServers: [
"https://mozilla.cloudflare-dns.com/dns-query",
"https://dns.quad9.net/dns-query"
],
enableBuiltInResolver: true,
secureDnsMode: "automatic"
});
if (!isDevelopment()) registerProtocol();
await createWindow();

View File

@@ -43,7 +43,6 @@ export const config = {
theme: nativeTheme.themeSource,
automaticUpdates: true,
proxyRules: "",
customDns: true,
backgroundColor: nativeTheme.themeSource === "dark" ? "#0f0f0f" : "#ffffff",
windowControlsIconColor:

View File

@@ -1,37 +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 { app } from "electron";
export function enableCustomDns() {
app.configureHostResolver({
secureDnsServers: [
"https://mozilla.cloudflare-dns.com/dns-query",
"https://dns.quad9.net/dns-query"
],
enableBuiltInResolver: true
});
}
export function disableCustomDns() {
app.configureHostResolver({
secureDnsServers: [],
enableBuiltInResolver: true
});
}

View File

@@ -218,7 +218,12 @@ export default async function downloadAttachment(
}
if (options.base64 || options.text) {
DatabaseLogger.log(`Starting to decrypt... hash: ${attachment.hash}`);
console.log(
"starting decrypt base64 file...",
options.base64,
options.text,
attachment.hash
);
return await db.attachments.read(
attachment.hash,
options.base64 ? "base64" : "text"
@@ -287,7 +292,7 @@ export default async function downloadAttachment(
return fileUri;
} catch (e) {
DatabaseLogger.error(e);
console.log("download attachment error: ", e);
if (attachment.dateUploaded) {
RNFetchBlob.fs
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}`)

View File

@@ -22,24 +22,21 @@ import NetInfo from "@react-native-community/netinfo";
import RNFetchBlob from "react-native-blob-util";
import { ToastManager } from "../../services/event-manager";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { DatabaseLogger, db } from "../database";
import { db } from "../database";
import { cacheDir, fileCheck } from "./utils";
import { createCacheDir, exists } from "./io";
export async function downloadFile(filename, data, cancelToken) {
if (!data) {
DatabaseLogger.log(`Error downloading file: ${filename}, reason: No data`);
return false;
}
if (!data) return false;
DatabaseLogger.log(`Downloading ${filename}`);
console.log("Downloading", filename);
await createCacheDir();
let { url, headers } = data;
let path = `${cacheDir}/${filename}`;
try {
if (await exists(filename)) {
DatabaseLogger.log(`File Exists already: ${filename}`);
console.log("Exists already", filename);
return true;
}
@@ -47,24 +44,13 @@ export async function downloadFile(filename, data, cancelToken) {
method: "GET",
headers
});
if (!res.ok) {
DatabaseLogger.log(
`Error downloading file: ${filename}, ${res.status}, ${res.statusText}, reason: Unable to resolve download url`
);
if (!res.ok)
throw new Error(`${res.status}: Unable to resolve download url`);
}
const downloadUrl = await res.text();
if (!downloadUrl) {
DatabaseLogger.log(
`Error downloading file: ${filename}, reason: Unable to resolve download url`
);
throw new Error("Unable to resolve download url");
}
if (!downloadUrl) throw new Error("Unable to resolve download url");
let totalSize = 0;
DatabaseLogger.log(`Download starting: ${filename}`);
console.log("Download starting");
let request = RNFetchBlob.config({
path: path,
IOSBackgroundTask: true
@@ -75,15 +61,13 @@ export async function downloadFile(filename, data, cancelToken) {
.getState()
.setProgress(0, total, filename, recieved, "download");
totalSize = total;
DatabaseLogger.log(`Downloading: ${filename}, ${recieved}/${total}`);
console.log("downloading: ", recieved, total);
});
cancelToken.cancel = () => {
useAttachmentStore.getState().remove(filename);
request.cancel();
DatabaseLogger.log(`Download cancelled: ${filename}`);
};
let response = await request;
await fileCheck(response, totalSize);
let status = response.info().status;
@@ -107,10 +91,7 @@ export async function downloadFile(filename, data, cancelToken) {
useAttachmentStore.getState().remove(filename);
RNFetchBlob.fs.unlink(path).catch(console.log);
DatabaseLogger.error(e, {
url,
headers
});
console.log("Download file error:", e, url, headers);
return false;
}
}
@@ -119,17 +100,18 @@ export async function getUploadedFileSize(hash) {
try {
const url = `${hosts.API_HOST}/s3?name=${hash}`;
const token = await db.tokenManager.getAccessToken();
const attachmentInfo = await fetch(url, {
method: "HEAD",
headers: { Authorization: `Bearer ${token}` }
});
const contentLength = parseInt(
attachmentInfo.headers?.get("content-length")
);
return isNaN(contentLength) ? 0 : contentLength;
} catch (e) {
DatabaseLogger.error(e);
return -1;
return 0;
}
}
@@ -143,9 +125,7 @@ export async function checkAttachment(hash) {
try {
const size = await getUploadedFileSize(hash);
if (size === -1) return { success: true };
if (size === 0) return { failed: "File length is 0." };
if (size <= 0) return { failed: "File length is 0." };
} catch (e) {
return { failed: e?.message };
}

View File

@@ -21,12 +21,12 @@ import Sodium from "@ammarahmed/react-native-sodium";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import { IOS_APPGROUPID } from "../../utils/constants";
import { DatabaseLogger, db } from "../database";
import { db } from "../database";
import { cacheDir, cacheDirOld, getRandomId } from "./utils";
export async function readEncrypted(filename, key, cipherData) {
await migrateFilesFromCache();
DatabaseLogger.log("Read encrypted file...");
console.log("Read encrypted file...");
let path = `${cacheDir}/${filename}`;
try {
@@ -34,6 +34,8 @@ export async function readEncrypted(filename, key, cipherData) {
return false;
}
const attachment = await db.attachments.attachment(filename);
console.log("decrypting....");
let output = await Sodium.decryptFile(
key,
{
@@ -43,13 +45,12 @@ export async function readEncrypted(filename, key, cipherData) {
},
cipherData.outputType === "base64" ? "base64" : "text"
);
DatabaseLogger.log("File decrypted...");
console.log("file decrypted...", attachment?.mimeType);
return output;
} catch (e) {
RNFetchBlob.fs.unlink(path).catch(console.log);
DatabaseLogger.error(e);
console.log("readEncrypted", e);
return false;
}
}
@@ -127,7 +128,7 @@ export async function clearFileStorage() {
export async function createCacheDir() {
if (!(await RNFetchBlob.fs.exists(cacheDir))) {
await RNFetchBlob.fs.mkdir(cacheDir);
DatabaseLogger.log("Cache directory created");
console.log("Cache directory created");
}
}
@@ -187,9 +188,6 @@ export async function exists(filename) {
);
if (stat.size !== expectedFileSize) {
DatabaseLogger.log(
`File size mismatch: ${filename}, expected: ${expectedFileSize}, actual: ${stat.size}`
);
RNFetchBlob.fs
.unlink(existsInAppGroup ? appGroupPath : path)
.catch(console.log);

View File

@@ -25,7 +25,6 @@ import { isImage, isDocument } from "@notesnook/core/dist/utils/filename";
import { Platform } from "react-native";
import { IOS_APPGROUPID } from "../../utils/constants";
import { createCacheDir } from "./io";
import { getUploadedFileSize } from "./download";
export async function uploadFile(filename, data, cancelToken) {
if (!data) return false;
@@ -34,18 +33,6 @@ export async function uploadFile(filename, data, cancelToken) {
DatabaseLogger.info(`Preparing to upload file: ${filename}`);
try {
const uploadedFileSize = await getUploadedFileSize(filename);
if (uploadedFileSize === -1) {
DatabaseLogger.log("Upload verification failed.");
return false;
}
const isUploaded = uploadedFileSize !== 0;
if (isUploaded) {
DatabaseLogger.log(`File ${filename} is already uploaded.`);
return true;
}
let res = await fetch(url, {
method: "PUT",
headers
@@ -63,15 +50,7 @@ export async function uploadFile(filename, data, cancelToken) {
let exists = await RNFetchBlob.fs.exists(uploadFilePath);
if (!exists && Platform.OS === "ios") {
uploadFilePath = appGroupPath;
exists = await RNFetchBlob.fs.exists(uploadFilePath);
}
if (!exists) {
throw new Error(
`Trying to upload file at path ${uploadFilePath} that doest not exist.`
);
}
DatabaseLogger.info(`Starting upload: ${filename}`);
let request = RNFetchBlob.config({

View File

@@ -56,7 +56,6 @@ import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { useTabStore } from "../../screens/editor/tiptap/use-tab-store";
import { editorController } from "../../screens/editor/tiptap/utils";
import SheetProvider from "../sheet-provider";
const Actions = ({
attachment,
@@ -219,7 +218,6 @@ const Actions = ({
}}
>
<Dialog context={contextId} />
<SheetProvider context={contextId} />
<View
style={{
borderBottomWidth: 1,
@@ -240,20 +238,36 @@ const Actions = ({
style={{
flexDirection: "row",
marginBottom: 10,
paddingHorizontal: 12,
marginTop: 6,
gap: 10
paddingHorizontal: 12
}}
>
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
{attachment.mimeType}
<Paragraph
size={SIZE.xs}
style={{
marginRight: 10
}}
color={colors.secondary.paragraph}
>
{attachment.type}
</Paragraph>
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
<Paragraph
style={{
marginRight: 10
}}
size={SIZE.xs}
color={colors.secondary.paragraph}
>
{formatBytes(attachment.size)}
</Paragraph>
{notes.length ? (
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
<Paragraph
style={{
marginRight: 10
}}
size={SIZE.xs}
color={colors.secondary.paragraph}
>
{notes.length} note
{notes.length > 1 ? "s" : ""}
</Paragraph>
@@ -352,7 +366,7 @@ const Actions = ({
{failed ? (
<Notice
type="alert"
text={`File check failed: ${failed} Try reuploading the file to fix the issue.`}
text={`File check failed with error: ${attachment.failed} Try reuploading the file to fix the issue.`}
size="small"
/>
) : null}

View File

@@ -177,7 +177,7 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
case "audio":
items = note
? db.attachments.ofNote(note.id, "audio")
: db.attachments.audios;
: db.attachments.all;
break;
case "documents":
items = note

View File

@@ -24,7 +24,7 @@ import {
setAppLockVerificationCipher,
validateAppLockPassword
} from "../../../common/database/encryption";
import BiometricService from "../../../services/biometrics";
import BiometicService from "../../../services/biometrics";
import { DDS } from "../../../services/device-detection";
import {
ToastManager,
@@ -323,7 +323,7 @@ export const AppLockPassword = () => {
SettingsService.setProperty("appLockHasPasswordSecurity", false);
if (
!(await BiometricService.isBiometryAvailable()) ||
!(await BiometicService.isBiometryAvailable()) ||
SettingsService.getProperty("biometricsAuthEnabled") === false
) {
SettingsService.setProperty("appLockEnabled", false);

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useState } from "react";
import { Image, ScrollView, TouchableOpacity, View } from "react-native";
import { Image as ImageType } from "react-native-image-crop-picker";
import { ImagePickerResponse } from "react-native-image-picker";
import { useThemeColors } from "../../../../../../packages/theme/dist";
import { presentSheet } from "../../../services/event-manager";
import { SIZE } from "../../../utils/size";
@@ -32,7 +32,7 @@ export default function AttachImage({
onAttach,
close
}: {
response: ImageType[];
response: ImagePickerResponse;
onAttach: ({ compress }: { compress: boolean }) => void;
close: ((ctx?: string | undefined) => void) | undefined;
}) {
@@ -58,14 +58,14 @@ export default function AttachImage({
}}
>
<Paragraph style={{ color: colors.primary.paragraph, marginBottom: 6 }}>
Attaching {response?.length} image(s):
Attaching {response.assets?.length} image(s):
</Paragraph>
<ScrollView horizontal>
{response?.map((item) => (
<TouchableOpacity key={item.filename} activeOpacity={0.9}>
{response.assets?.map((item) => (
<TouchableOpacity key={item.fileName} activeOpacity={0.9}>
<Image
source={{
uri: item.sourceURL || item.path
uri: item.uri
}}
style={{
width: 100,
@@ -142,7 +142,7 @@ export default function AttachImage({
<Button
title={`${
(response?.length || 0) > 1 ? "Attach Images" : "Attach Image"
(response.assets?.length || 0) > 1 ? "Attach Images" : "Attach Image"
}`}
type="accent"
width="100%"
@@ -156,16 +156,10 @@ export default function AttachImage({
);
}
AttachImage.present = (response: ImageType[], context?: string) => {
return new Promise<
| {
compress: boolean;
}
| undefined
>((resolve) => {
AttachImage.present = (response: ImagePickerResponse) => {
return new Promise((resolve) => {
let resolved = false;
presentSheet({
context: context,
component: (ref, close, update) => (
<AttachImage
response={response}

View File

@@ -128,7 +128,7 @@ export class VaultDialog extends Component {
: this.state.copyNote
? "Unlock note to copy it. If biometrics are not working, you can enter device pin to unlock vault."
: this.state.goToEditor
? "Unlock note to open it in editor."
? "Unlock note to open it in editor. If biometrics are not working, you can enter device pin to unlock vault."
: "Enter vault password to unlock note. If biometrics are not working, you can enter device pin to unlock vault."
: "Enter vault password to lock note. If biometrics are not working, you can enter device pin to lock note.");
}

View File

@@ -33,7 +33,6 @@ import { eScrollEvent } from "../../utils/events";
import { LeftMenus } from "./left-menus";
import { RightMenus } from "./right-menus";
import { Title } from "./title";
import { useNavigation } from "@react-navigation/native";
type HeaderRightButton = {
title: string;
@@ -67,8 +66,6 @@ export const Header = ({
hasSearch?: boolean;
onSearch?: () => void;
}) => {
const navigation = useNavigation();
const { colors } = useThemeColors();
const insets = useGlobalSafeAreaInsets();
const [borderHidden, setBorderHidden] = useState(true);

View File

@@ -19,12 +19,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import Sodium from "@ammarahmed/react-native-sodium";
import dataurl from "@notesnook/core/dist/utils/dataurl";
import type { ImageAttributes } from "@notesnook/editor/dist/extensions/image/index";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef, useState } from "react";
import React, { useEffect, useState } from "react";
import { Platform, View } from "react-native";
import ImageViewer from "react-native-image-zoom-viewer";
import { db } from "../../common/database";
import downloadAttachment from "../../common/filesystem/download-attachment";
import { cacheDir } from "../../common/filesystem/utils";
import {
@@ -34,13 +32,13 @@ import {
import BaseDialog from "../dialog/base-dialog";
import { IconButton } from "../ui/icon-button";
import { ProgressBarComponent } from "../ui/svg/lazy";
import type { ImageAttributes } from "@notesnook/editor/dist/extensions/image/index";
const ImagePreview = () => {
const { colors } = useThemeColors("dialog");
const [visible, setVisible] = useState(false);
const [image, setImage] = useState<string>();
const [loading, setLoading] = useState(false);
const imageRef = useRef<ImageAttributes>();
useEffect(() => {
eSubscribeEvent("ImagePreview", open);
@@ -50,7 +48,6 @@ const ImagePreview = () => {
}, []);
const open = async (image: ImageAttributes) => {
imageRef.current = image;
setVisible(true);
setLoading(true);
setTimeout(async () => {
@@ -63,9 +60,6 @@ const ImagePreview = () => {
type: "base64",
uri: ""
});
if (imageRef.current) {
imageRef.current.hash = hash;
}
}
if (!hash) return;
//@ts-ignore // FIX ME
@@ -86,13 +80,7 @@ const ImagePreview = () => {
return (
visible && (
<BaseDialog
background="black"
animation="slide"
visible={true}
onRequestClose={close}
transparent
>
<BaseDialog animation="slide" visible={true} onRequestClose={close}>
<View
style={{
width: "100%",
@@ -113,21 +101,6 @@ const ImagePreview = () => {
color={colors.primary.accent}
borderColor="transparent"
/>
<IconButton
onPress={() => {
if (imageRef.current?.hash) {
db.fs().cancel(imageRef.current?.hash);
}
close();
}}
style={{
position: "absolute",
top: Platform.OS === "android" ? 35 : 0,
right: 12
}}
color={colors.static.white}
name="close"
/>
</View>
) : (
<ImageViewer
@@ -144,12 +117,13 @@ const ImagePreview = () => {
width: "100%",
justifyContent: "flex-end",
alignItems: "center",
height: 50,
paddingHorizontal: 24,
height: 80,
marginTop: 0,
paddingHorizontal: 12,
position: "absolute",
zIndex: 999,
backgroundColor: "rgba(0,0,0,0.3)",
marginTop: Platform.OS === "android" ? 30 : 0
paddingTop: Platform.OS === "android" ? 30 : 0
}}
>
<IconButton
@@ -163,7 +137,7 @@ const ImagePreview = () => {
)}
imageUrls={[
{
url: image as string
url: image
}
]}
/>

View File

@@ -19,6 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { View } from "react-native";
import React from "react";
export const Footer = ({ height = 150 }) => {
return <View style={{ height: height }} />;
export const Footer = () => {
return <View style={{ height: 150 }} />;
};

View File

@@ -95,7 +95,7 @@ export const NotebookHeader = ({
style={{
flexShrink: 1
}}
size={SIZE.lg}
size={SIZE.xxl}
>
{notebook.title}
</Heading>
@@ -114,7 +114,7 @@ export const NotebookHeader = ({
width: 40,
height: 40
}}
type="transparent"
type={isPinnedToMenu ? "secondary" : "secondary"}
color={isPinnedToMenu ? colors.primary.accent : colors.primary.icon}
size={SIZE.lg}
/>
@@ -123,7 +123,7 @@ export const NotebookHeader = ({
onPress={onEditNotebook}
tooltipText="Edit this notebook"
name="pencil"
type="transparent"
type="secondary"
color={colors.primary.icon}
style={{
width: 40,
@@ -134,7 +134,7 @@ export const NotebookHeader = ({
</View>
{notebook.description ? (
<Paragraph size={SIZE.sm} color={colors.primary.paragraph}>
<Paragraph size={SIZE.md} color={colors.primary.paragraph}>
{notebook.description}
</Paragraph>
) : null}

View File

@@ -28,12 +28,13 @@ import {
hideSheet,
presentSheet
} from "../../../services/event-manager";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { eOnLoadNote, eShowMergeDialog } from "../../../utils/events";
import { tabBarRef } from "../../../utils/global-refs";
import { NotebooksWithDateEdited, TagsWithDateEdited } from "@notesnook/common";
import NotePreview from "../../note-history/preview";
import SelectionWrapper, { selectItem } from "../selection-wrapper";
import SelectionWrapper from "../selection-wrapper";
export const openNote = async (
item: Note,
@@ -46,8 +47,21 @@ export const openNote = async (
if (!isTrash) {
note = (await db.notes.note(item.id)) as Note;
}
if (useSelectionStore.getState().selectionMode === item.type) {
const {
selectedItemsList,
selectionMode,
clearSelection,
setSelectedItem
} = useSelectionStore.getState();
if (selectItem(item)) return;
if (selectedItemsList.length > 0 && selectionMode === item.type) {
setSelectedItem(note.id);
} else {
clearSelection();
}
return;
}
if (note.conflicted) {
eSendEvent(eShowMergeDialog, note);

View File

@@ -27,12 +27,18 @@ import Navigation from "../../../services/navigation";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { useTrashStore } from "../../../stores/use-trash-store";
import { presentDialog } from "../../dialog/functions";
import SelectionWrapper, { selectItem } from "../selection-wrapper";
import SelectionWrapper from "../selection-wrapper";
export const openNotebook = (item: Notebook | BaseTrashItem<Notebook>) => {
const isTrash = item.type === "trash";
if (selectItem(item)) return;
const { selectedItemsList, setSelectedItem, selectionMode, clearSelection } =
useSelectionStore.getState();
if (selectedItemsList.length > 0 && selectionMode === item.type) {
setSelectedItem(item.id);
return;
} else {
clearSelection();
}
if (isTrash) {
presentDialog({

View File

@@ -22,6 +22,7 @@ import React from "react";
import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../../e2e/test.ids";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { SIZE } from "../../../utils/size";
import { Properties } from "../../properties";
import ReminderSheet from "../../sheets/reminder";
@@ -29,7 +30,7 @@ import { IconButton } from "../../ui/icon-button";
import { ReminderTime } from "../../ui/reminder-time";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import SelectionWrapper, { selectItem } from "../selection-wrapper";
import SelectionWrapper from "../selection-wrapper";
const ReminderItem = React.memo(
({
@@ -43,7 +44,18 @@ const ReminderItem = React.memo(
}) => {
const { colors } = useThemeColors();
const openReminder = () => {
if (selectItem(item)) return;
const {
selectedItemsList,
setSelectedItem,
selectionMode,
clearSelection
} = useSelectionStore.getState();
if (selectedItemsList.length > 0 && selectionMode === item.type) {
setSelectedItem(item.id);
return;
} else {
clearSelection();
}
ReminderSheet.present(item, undefined, isSheet);
};

View File

@@ -26,23 +26,6 @@ import { Filler } from "./back-fill";
import { SelectionIcon } from "./selection";
import { Item, TrashItem } from "@notesnook/core";
export function selectItem(item: Item) {
if (useSelectionStore.getState().selectionMode === item.type) {
const { selectionMode, clearSelection, setSelectedItem } =
useSelectionStore.getState();
if (selectionMode === item.type) {
setSelectedItem(item.id);
}
if (useSelectionStore.getState().selectedItemsList.length === 0) {
clearSelection();
}
return true;
}
return false;
}
type SelectionWrapperProps = PropsWithChildren<{
item: Item;
onPress: () => void;

View File

@@ -23,12 +23,13 @@ import React from "react";
import { View } from "react-native";
import { notesnook } from "../../../../e2e/test.ids";
import { TaggedNotes } from "../../../screens/notes/tagged";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { SIZE } from "../../../utils/size";
import { Properties } from "../../properties";
import { IconButton } from "../../ui/icon-button";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import SelectionWrapper, { selectItem } from "../selection-wrapper";
import SelectionWrapper from "../selection-wrapper";
const TagItem = React.memo(
({
@@ -42,7 +43,18 @@ const TagItem = React.memo(
}) => {
const { colors } = useThemeColors();
const onPress = () => {
if (selectItem(item)) return;
const {
selectedItemsList,
setSelectedItem,
selectionMode,
clearSelection
} = useSelectionStore.getState();
if (selectedItemsList.length > 0 && selectionMode === item.type) {
setSelectedItem(item.id);
return;
} else {
clearSelection();
}
TaggedNotes.navigate(item, true);
};

View File

@@ -192,9 +192,7 @@ export default function List(props: ListProps) {
/>
) : null
}
ListFooterComponent={
<Footer height={props.renderedInRoute === "Notebook" ? 300 : 150} />
}
ListFooterComponent={<Footer />}
ListHeaderComponent={
<>
{props.CustomLisHeader ? (

View File

@@ -19,14 +19,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { Item, ItemType, VirtualizedGrouping } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef } from "react";
import React, { useEffect } from "react";
import {
BackHandler,
NativeEventSubscription,
Platform,
View
} from "react-native";
import Menu from "react-native-reanimated-material-menu/src/Menu";
import Animated, { FadeInUp } from "react-native-reanimated";
import { db } from "../../common/database";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { ToastManager } from "../../services/event-manager";
@@ -42,10 +42,9 @@ import { presentDialog } from "../dialog/functions";
import MoveNoteSheet from "../sheets/add-to";
import ExportNotesSheet from "../sheets/export-notes";
import ManageTagsSheet from "../sheets/manage-tags";
import { MoveNotebookSheet } from "../sheets/move-notebook";
import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
import Heading from "../ui/typography/heading";
import { MoveNotebookSheet } from "../sheets/move-notebook";
export const SelectionHeader = React.memo(
({
@@ -59,8 +58,6 @@ export const SelectionHeader = React.memo(
type?: ItemType;
renderedInRoute?: string;
}) => {
const menuRef = useRef<Menu>(null);
const { colors: contextMenuColors } = useThemeColors("contextMenu");
const { colors } = useThemeColors();
const selectionMode = useSelectionStore((state) => state.selectionMode);
const selectedItemsList = useSelectionStore(
@@ -135,7 +132,8 @@ export const SelectionHeader = React.memo(
}, [clearSelection, selectionMode]);
return selectionMode !== type || focusedRouteId !== id ? null : (
<View
<Animated.View
entering={FadeInUp}
style={{
width: "100%",
height: Platform.OS === "android" ? 50 + insets.top : 50,
@@ -145,7 +143,8 @@ export const SelectionHeader = React.memo(
alignItems: "center",
flexDirection: "row",
zIndex: 999,
paddingHorizontal: 12
paddingHorizontal: 12,
marginVertical: 10
}}
>
<View
@@ -165,15 +164,18 @@ export const SelectionHeader = React.memo(
borderRadius: 100,
marginRight: 10
}}
type="secondary"
onPress={() => {
clearSelection();
}}
size={SIZE.xl}
color={colors.primary.icon}
name="close"
/>
<View
style={{
backgroundColor: colors.secondary.background,
height: 40,
borderRadius: 100,
paddingHorizontal: 16,
@@ -182,12 +184,11 @@ export const SelectionHeader = React.memo(
alignItems: "center"
}}
>
<Heading size={SIZE.lg} color={colors.primary.paragraph}>
<Heading size={SIZE.md} color={colors.primary.accent}>
{selectedItemsList.length}
</Heading>
</View>
</View>
<View
style={{
flexDirection: "row",
@@ -210,142 +211,161 @@ export const SelectionHeader = React.memo(
allSelected ? colors.primary.accent : colors.primary.paragraph
}
name="select-all"
size={SIZE.xl}
/>
{selectedItemsList.length ? (
<Menu
ref={menuRef}
animationDuration={200}
{renderedInRoute === "Notebooks" ? (
<IconButton
style={{
borderRadius: 5,
backgroundColor: contextMenuColors.primary.background,
marginTop: -20
marginLeft: 10
}}
onRequestClose={() => {
menuRef.current?.hide();
onPress={async () => {
const ids = selectedItemsList;
const notebooks = await db.notebooks.all.items(ids);
MoveNotebookSheet.present(notebooks);
}}
anchor={
<IconButton
onPress={() => {
menuRef.current?.show();
}}
name="dots-vertical"
color={colors.primary.paragraph}
/>
}
>
{[
{
title:
selectedItemsList.length > 1
? "Move notebooks"
: "Move notebook",
onPress: async () => {
const ids = selectedItemsList;
const notebooks = await db.notebooks.all.items(ids);
MoveNotebookSheet.present(notebooks);
},
visible: renderedInRoute === "Notebooks",
icon: "arrow-right-bold-box-outline"
},
{
title: "Manage tags",
onPress: async () => {
await sleep(100);
ManageTagsSheet.present(selectedItemsList);
},
visible: type === "note",
icon: "pound"
},
{
title: "Export",
onPress: async () => {
await sleep(100);
ExportNotesSheet.present(selectedItemsList);
},
visible: type === "note",
icon: "export"
},
{
title: "Link notebook",
onPress: async () => {
await sleep(100);
MoveNoteSheet.present();
},
visible: type === "note",
icon: "plus"
},
{
title: "Unlink notebook",
onPress: async () => {
if (!id) return;
await db.notes.removeFromNotebook(id, ...selectedItemsList);
updateNotebook(id);
Navigation.queueRoutesForUpdate();
clearSelection();
},
visible: renderedInRoute === "Notebook",
icon: "minus"
},
{
title: "Unfavorite",
onPress: addToFavorite,
visible: focusedRouteId === "Favorites",
icon: "star-off"
},
{
title: `Move to trash`,
onPress: async () => {
deleteItems(
undefined,
useSelectionStore.getState().selectionMode
).then(() => {
useSelectionStore.getState().clearSelection();
useSelectionStore.getState().setSelectionMode(undefined);
});
},
visible: type !== "trash",
icon: "delete"
},
{
title: `Restore`,
onPress: restoreItem,
visible: type === "trash",
icon: "delete-restore"
},
{
title: `Delete`,
onPress: deleteItem,
visible: type === "trash",
icon: "delete"
tooltipText="Move notebooks"
tooltipPosition={1}
name="arrow-right-bold-box-outline"
size={SIZE.xl}
/>
) : null}
{type !== "note" ? null : (
<>
<IconButton
onPress={async () => {
await sleep(100);
ManageTagsSheet.present(selectedItemsList);
}}
style={{
marginLeft: 10
}}
color={colors.primary.icon}
tooltipText="Manage tags"
tooltipPosition={4}
name="pound"
size={SIZE.xl}
/>
<IconButton
onPress={async () => {
ExportNotesSheet.present(selectedItemsList);
}}
tooltipText="Export"
tooltipPosition={4}
style={{
marginLeft: 10
}}
color={colors.primary.paragraph}
name="export"
size={SIZE.xl}
/>
<IconButton
onPress={async () => {
MoveNoteSheet.present();
}}
style={{
marginLeft: 10
}}
tooltipText="Add to notebooks"
tooltipPosition={4}
color={colors.primary.paragraph}
name="plus"
size={SIZE.xl}
/>
</>
)}
{renderedInRoute === "Notebook" ? (
<IconButton
onPress={async () => {
if (selectedItemsList.length > 0) {
if (!id) return;
await db.notes.removeFromNotebook(id, ...selectedItemsList);
updateNotebook(id);
Navigation.queueRoutesForUpdate();
clearSelection();
}
].map((item) =>
!item.visible ? null : (
<Button
style={{
width: 150,
justifyContent: "flex-start",
borderRadius: 0
}}
type="plain"
buttonType={{
text: contextMenuColors.primary.paragraph
}}
icon={item.icon}
key={item.title}
title={item.title}
onPress={async () => {
menuRef.current?.hide();
if (Platform.OS === "ios") await sleep(300);
item.onPress();
}}
/>
)
)}
</Menu>
}}
style={{
marginLeft: 10
}}
tooltipText={`Remove from Notebook`}
tooltipPosition={4}
testID="select-minus"
color={colors.primary.paragraph}
name="minus"
size={SIZE.xl}
/>
) : null}
{focusedRouteId === "Favorites" ? (
<IconButton
onPress={addToFavorite}
style={{
marginLeft: 10
}}
tooltipText="Remove from favorites"
tooltipPosition={4}
color={colors.primary.paragraph}
name="star-off"
size={SIZE.xl}
/>
) : null}
{type === "trash" ? null : (
<IconButton
style={{
marginLeft: 10
}}
onPress={() => {
deleteItems(
undefined,
useSelectionStore.getState().selectionMode
).then(() => {
useSelectionStore.getState().clearSelection();
useSelectionStore.getState().setSelectionMode(undefined);
});
}}
tooltipText="Move to trash"
tooltipPosition={1}
color={colors.primary.paragraph}
name="delete"
size={SIZE.xl}
/>
)}
{type === "trash" ? (
<>
<IconButton
style={{
marginLeft: 10
}}
color={colors.primary.paragraph}
onPress={restoreItem}
name="delete-restore"
tooltipText="Restore"
tooltipPosition={4}
size={SIZE.xl - 3}
/>
<IconButton
style={{
marginLeft: 10
}}
color={colors.primary.paragraph}
onPress={deleteItem}
tooltipText="Delete"
tooltipPosition={4}
name="delete"
size={SIZE.xl - 3}
/>
</>
) : null}
</View>
</View>
</Animated.View>
);
}
);

View File

@@ -45,16 +45,14 @@ export const AddNotebookSheet = ({
notebook,
parentNotebook,
close,
showMoveNotesOnComplete,
defaultTitle
showMoveNotesOnComplete
}: {
notebook?: Notebook;
parentNotebook?: Notebook;
close?: (didAddNotebook: boolean) => void;
showMoveNotesOnComplete: boolean;
defaultTitle?: string;
}) => {
const title = useRef(notebook?.title || defaultTitle);
const title = useRef(notebook?.title);
const description = useRef(notebook?.description);
const titleInput = useRef<TextInput>(null);
const descriptionInput = useRef<TextInput>(null);
@@ -141,7 +139,7 @@ export const AddNotebookSheet = ({
}}
returnKeyLabel="Next"
returnKeyType="next"
defaultValue={notebook ? notebook.title : title.current}
defaultValue={notebook ? notebook.title : ""}
/>
<Input
@@ -176,8 +174,7 @@ AddNotebookSheet.present = (
parentNotebook?: Notebook,
context?: string,
onClose?: (didAddNotebook: boolean) => void,
showMoveNotesOnComplete = true,
defaultTitle?: string
showMoveNotesOnComplete = true
) => {
presentSheet({
context: context,
@@ -190,7 +187,6 @@ AddNotebookSheet.present = (
onClose?.(didAddNotebook);
}}
showMoveNotesOnComplete={showMoveNotesOnComplete || false}
defaultTitle={defaultTitle}
/>
)
});

View File

@@ -19,13 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { Note } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, {
RefObject,
useCallback,
useEffect,
useRef,
useState
} from "react";
import React, { RefObject, useCallback, useEffect } from "react";
import {
ActivityIndicator,
Keyboard,
@@ -53,8 +47,6 @@ import Paragraph from "../../ui/typography/paragraph";
import { NotebookItem } from "./notebook-item";
import { useNotebookItemSelectionStore } from "./store";
import { AddNotebookSheet } from "../add-notebook";
import Input from "../../ui/input";
import { presentDialog } from "../../dialog/functions";
async function updateInitialSelectionState(items: string[]) {
const relations = await db.relations
@@ -101,11 +93,7 @@ const MoveNoteSheet = ({
actionSheetRef: RefObject<ActionSheetRef>;
}) => {
const { colors } = useThemeColors();
const [rootNotebooks, loading] = useNotebooks();
const searchQuery = useRef("");
const searchTimer = useRef<NodeJS.Timeout>();
const [notebooks, setNotebooks] = useState(rootNotebooks);
const [notebooks, loading] = useNotebooks();
const dimensions = useSettingStore((state) => state.dimensions);
const selectedItemsList = useSelectionStore(
(state) => state.selectedItemsList
@@ -115,12 +103,6 @@ const MoveNoteSheet = ({
(state) => state.multiSelect
);
useEffect(() => {
if (!loading) {
setNotebooks(rootNotebooks);
}
}, [loading, rootNotebooks]);
useEffect(() => {
const items = note ? [note.id] : selectedItemsList;
updateInitialSelectionState(items);
@@ -217,6 +199,7 @@ const MoveNoteSheet = ({
{hasSelected() ? (
<IconButton
name="restore"
type="secondaryAccented"
color={colors.primary.icon}
onPress={() => {
const items = note ? [note.id] : selectedItemsList;
@@ -256,48 +239,33 @@ const MoveNoteSheet = ({
style={{
width: "100%"
}}
keyboardShouldPersistTaps="handled"
ListHeaderComponent={
<View
style={{
paddingHorizontal: 12,
width: "100%",
paddingTop: 12
width: "100%"
}}
>
<Input
placeholder="Search notebooks...."
button={{
icon: "plus",
onPress: () => {
AddNotebookSheet.present(
undefined,
undefined,
"link-notebooks",
undefined,
false,
searchQuery.current
);
},
color: colors.primary.icon
<Button
title="Add new notebook"
style={{
alignSelf: "flex-start",
paddingHorizontal: 12,
justifyContent: "space-between"
}}
onChangeText={(value) => {
searchQuery.current = value;
if (!searchQuery.current) {
setNotebooks(rootNotebooks);
return;
}
searchTimer.current = setTimeout(() => {
db.lookup
.notebooks(searchQuery.current)
.sorted()
.then((result) => {
if (searchQuery.current === value) {
setNotebooks(result);
}
});
}, 300);
onPress={() => {
AddNotebookSheet.present(
undefined,
undefined,
"link-notebooks",
undefined,
false
);
}}
icon="plus"
iconPosition="right"
type="secondaryAccented"
width="100%"
/>
</View>
}

View File

@@ -219,7 +219,7 @@ export default function LinkNote(props: {
<View
style={{
paddingHorizontal: 12,
minHeight: "100%",
minHeight: 400,
maxHeight: "100%"
}}
>
@@ -311,7 +311,6 @@ export default function LinkNote(props: {
style={{
marginTop: 10
}}
keyboardShouldPersistTaps="handled"
windowSize={3}
keyExtractor={(item) => item.id}
data={nodes}
@@ -325,7 +324,6 @@ export default function LinkNote(props: {
onSelectNote={onSelectNote}
/>
)}
keyboardShouldPersistTaps="handled"
style={{
marginTop: 10
}}

View File

@@ -96,12 +96,9 @@ export default function Migrate() {
});
setLoading(true);
await sleep(1);
const { error, report } = await BackupService.run(false, "local");
const { error } = await BackupService.run(false, "local");
if (error) {
ToastManager.error(error, "Backup failed");
if (report) {
reportError(error);
}
setLoading(false);
return;
}

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Notebook, VirtualizedGrouping } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useState } from "react";
import React, { useCallback } from "react";
import { Text, View } from "react-native";
import { FlatList } from "react-native-actions-sheet";
import create from "zustand";
@@ -72,20 +72,6 @@ export const MoveNotebookSheet = ({
}) => {
const [notebooks] = useNotebooks();
const { colors } = useThemeColors();
const [moveToTop, setMoveToTop] = useState(false);
useEffect(() => {
(async () => {
for (const notebook of selectedNotebooks) {
const root = await findRootNotebookId(notebook.id);
if (root !== notebook.id) {
setMoveToTop(true);
return;
}
}
})();
}, [selectedNotebooks]);
const renderItem = useCallback(
({ index }: { index: number }) => {
return (
@@ -107,7 +93,6 @@ export const MoveNotebookSheet = ({
: selectedNotebooks[0].title
} to ${selectedNotebook.title}?`,
positiveText: "Move",
context: "move-notebook",
positivePress: async () => {
for (const notebook of selectedNotebooks) {
const parent = await getParentNotebookId(notebook.id);
@@ -172,38 +157,36 @@ export const MoveNotebookSheet = ({
paddingHorizontal: 12
}}
>
{moveToTop ? (
<Button
title="Move to top"
style={{
alignSelf: "flex-start",
width: "100%",
justifyContent: "space-between"
}}
icon="arrow-up-bold"
iconPosition="right"
type="secondaryAccented"
onPress={async () => {
for (const notebook of selectedNotebooks) {
const parent = await getParentNotebookId(notebook.id);
const root = await findRootNotebookId(notebook.id);
if (root !== notebook.id) {
await db.relations.unlink(
{
type: "notebook",
id: parent
},
notebook
);
eSendEvent(eOnNotebookUpdated, parent);
eSendEvent(eOnNotebookUpdated, notebook.id);
}
<Button
title="Move to top"
style={{
alignSelf: "flex-start",
width: "100%",
justifyContent: "space-between"
}}
icon="arrow-up-bold"
iconPosition="right"
type="secondaryAccented"
onPress={async () => {
for (const notebook of selectedNotebooks) {
const parent = await getParentNotebookId(notebook.id);
const root = await findRootNotebookId(notebook.id);
if (root !== notebook.id) {
await db.relations.unlink(
{
type: "notebook",
id: parent
},
notebook
);
eSendEvent(eOnNotebookUpdated, parent);
eSendEvent(eOnNotebookUpdated, notebook.id);
}
useNotebookStore.getState().refresh();
close?.();
}}
/>
) : null}
}
useNotebookStore.getState().refresh();
close?.();
}}
/>
</View>
}
ListEmptyComponent={

View File

@@ -118,12 +118,7 @@ NewFeature.present = () => {
);
if (_features.length === 0) return;
presentSheet({
component: (
<NewFeature
features={features}
version={SettingsService.getProperty("version")}
/>
),
component: <NewFeature features={features} version={version} />,
disableClosing: true
});
return true;

View File

@@ -19,19 +19,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { Notebook, VirtualizedGrouping } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef, useState } from "react";
import {
Platform,
RefreshControl,
View,
useWindowDimensions
} from "react-native";
import { RefreshControl, View, useWindowDimensions } from "react-native";
import ActionSheet, { ActionSheetRef } from "react-native-actions-sheet";
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
import Config from "react-native-config";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import create from "zustand";
import { notesnook } from "../../../../e2e/test.ids";
import { db } from "../../../common/database";
import { MMKV } from "../../../common/database/mmkv";
import { useNotebook } from "../../../hooks/use-notebook";
import NotebookScreen from "../../../screens/notebook";
@@ -49,8 +43,9 @@ import { IconButton } from "../../ui/icon-button";
import { Pressable } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph";
import { AddNotebookSheet } from "../add-notebook";
import { MoveNotebookSheet } from "../move-notebook";
import Sort from "../sort";
import { MoveNotebookSheet } from "../move-notebook";
import { db } from "../../../common/database";
const useItemSelectionStore = createItemSelectionStore(true, false);
@@ -68,8 +63,7 @@ class NotebookSheetConfig {
}
static get(item: ConfigItem) {
const value = MMKV.getInt(NotebookSheetConfig.makeId(item));
return typeof value === "number" ? value : 0;
return MMKV.getInt(NotebookSheetConfig.makeId(item)) || 0;
}
static set(item: ConfigItem, index = 0) {
@@ -132,6 +126,11 @@ export const NotebookSheet = () => {
if (!focusedRouteId) return;
const nextRoot = await findRootNotebookId(focusedRouteId);
if (nextRoot !== currentItem.current) {
console.log(
"NotebookSheet.useEffect.canShow",
"Root changed to",
nextRoot
);
useItemSelectionStore.setState({
enabled: false,
selection: {}
@@ -146,8 +145,11 @@ export const NotebookSheet = () => {
if (ref.current?.isOpen()) {
ref.current?.snapToIndex(snapPoint);
} else {
ref.current?.show(snapPoint);
setTimeout(() => {
ref.current?.show(snapPoint);
}, 150);
}
console.log("NotebookSheet.useEffect.didShow", focusedRouteId);
setRoot(nextRoot);
onRequestUpdate();
});
@@ -160,7 +162,7 @@ export const NotebookSheet = () => {
ref.current?.hide();
}
}
}, [canShow, focusedRouteId]);
}, [canShow, onRequestUpdate, focusedRouteId]);
return (
<ActionSheet
@@ -196,11 +198,7 @@ export const NotebookSheet = () => {
backgroundColor: colors.secondary.background
}}
keyboardHandlerEnabled={false}
snapPoints={
Config.isTesting === "true"
? [100]
: [Platform.OS === "android" ? 15 : 10, 100]
}
snapPoints={Config.isTesting === "true" ? [100] : [20, 100]}
initialSnapIndex={1}
backgroundInteractionEnabled
gestureEnabled
@@ -239,7 +237,7 @@ export const NotebookSheet = () => {
>
<Icon
name="notebook-plus"
color={colors.primary.icon}
color={colors.primary.accent}
size={SIZE.xxl}
/>
</View>

View File

@@ -18,12 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useThemeColors } from "@notesnook/theme";
import React, { RefObject, useRef, useState } from "react";
import {
Platform,
TextInput,
View,
ScrollView as RNScrollView
} from "react-native";
import { Platform, TextInput, View } from "react-native";
import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
import DateTimePickerModal from "react-native-modal-datetime-picker";
import {
@@ -369,7 +364,7 @@ export default function ReminderSheet({
))}
</View>
<RNScrollView showsHorizontalScrollIndicator={false} horizontal>
<ScrollView showsHorizontalScrollIndicator={false} horizontal>
{recurringMode === RecurringModes.Daily ||
recurringMode === RecurringModes.Year
? null
@@ -430,7 +425,7 @@ export default function ReminderSheet({
}}
/>
))}
</RNScrollView>
</ScrollView>
</View>
) : null}

View File

@@ -285,9 +285,9 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
const backupFiles = await RNFetchBlob.fs.ls(zipOutputFolder);
// if (backupFiles.findIndex((file) => file === ".nnbackup") === -1) {
// throw new Error("Backup file is invalid");
// }
if (backupFiles.findIndex((file) => file === ".nnbackup") === -1) {
throw new Error("Backup file is invalid");
}
await db.transaction(async () => {
let password;

View File

@@ -50,7 +50,7 @@ const Sort = ({ type, screen }) => {
const setOrderBy = async () => {
let _groupOptions = {
...groupOptions,
sortDirection: groupOptions?.sortDirection === "asc" ? "desc" : "asc"
sortDirection: groupOptions.sortDirection === "asc" ? "desc" : "asc"
};
if (type === "topics") {
_groupOptions.groupBy = "none";
@@ -85,7 +85,7 @@ const Sort = ({ type, screen }) => {
<Button
title={
groupOptions?.sortDirection === "asc"
groupOptions.sortDirection === "asc"
? groupOptions.groupBy === "abc" ||
groupOptions.sortBy === "title"
? "A - Z"
@@ -100,7 +100,7 @@ const Sort = ({ type, screen }) => {
: "New - Old"
}
icon={
groupOptions?.sortDirection === "asc"
groupOptions.sortDirection === "asc"
? "sort-ascending"
: "sort-descending"
}
@@ -229,7 +229,7 @@ const Sort = ({ type, screen }) => {
if (item === "abc") {
_groupOptions.sortBy = "title";
_groupOptions.sortDirection = "desc";
_groupOptions.sortDirection = "asc";
}
updateGroupOptions(_groupOptions);

View File

@@ -21,11 +21,15 @@ import { useThemeColors } from "@notesnook/theme";
import React, { useCallback } from "react";
import { View } from "react-native";
import { DraxProvider, DraxScrollView } from "react-native-drax";
import { notesnook } from "../../../e2e/test.ids";
import { db } from "../../common/database";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { DDS } from "../../services/device-detection";
import { eSendEvent } from "../../services/event-manager";
import Navigation from "../../services/navigation";
import { useMenuStore } from "../../stores/use-menu-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { useUserStore } from "../../stores/use-user-store";
import { SUBSCRIPTION_STATUS } from "../../utils/constants";
import { eOpenPremiumDialog } from "../../utils/events";
@@ -55,6 +59,26 @@ export const SideMenu = React.memo(
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
const BottomItemsList = [
{
name: isDark ? "Day" : "Night",
icon: "theme-light-dark",
func: () => {
useThemeStore.getState().setColorScheme();
},
switch: true,
on: !!isDark,
close: false
},
{
name: "Settings",
icon: "cog-outline",
close: true,
func: () => {
Navigation.navigate("Settings");
}
}
];
const pro = {
name: "Notesnook Pro",
@@ -97,7 +121,7 @@ export const SideMenu = React.memo(
<PinnedSection />
</>
),
[order, hiddensItems]
[]
);
return !isAppLoading && introCompleted ? (

View File

@@ -20,25 +20,29 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { useThemeColors } from "@notesnook/theme";
import { useNetInfo } from "@react-native-community/netinfo";
import React from "react";
import { ActivityIndicator, Image, Platform, View } from "react-native";
import {
ActivityIndicator,
Image,
Platform,
TouchableOpacity,
View
} from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import useSyncProgress from "../../hooks/use-sync-progress";
import { eSendEvent } from "../../services/event-manager";
import Navigation from "../../services/navigation";
import Sync from "../../services/sync";
import { useThemeStore } from "../../stores/use-theme-store";
import { SyncStatus, useUserStore } from "../../stores/use-user-store";
import { eOpenLoginDialog } from "../../utils/events";
import { tabBarRef } from "../../utils/global-refs";
import { SIZE } from "../../utils/size";
import { IconButton } from "../ui/icon-button";
import { Pressable } from "../ui/pressable";
import { TimeSince } from "../ui/time-since";
import Paragraph from "../ui/typography/paragraph";
import Navigation from "../../services/navigation";
export const UserStatus = () => {
const { colors, isDark } = useThemeColors();
const { colors } = useThemeColors();
const user = useUserStore((state) => state.user);
const syncing = useUserStore((state) => state.syncing);
const lastSyncStatus = useUserStore((state) => state.lastSyncStatus);
@@ -93,16 +97,7 @@ export const UserStatus = () => {
borderRadius: 100
}}
/>
) : (
<Icon
name="cog-outline"
size={SIZE.lg - 2}
color={colors.secondary.icon}
style={{
paddingLeft: 8
}}
/>
)}
) : null}
<View
style={{
@@ -115,8 +110,14 @@ export const UserStatus = () => {
size={SIZE.sm}
color={colors.primary.heading}
>
{!user || !userProfile?.fullName
? "Settings"
{!user
? "Login to sync your notes."
: lastSyncStatus === SyncStatus.Failed
? "Sync failed, tap to retry"
: syncing
? `Syncing ${progress ? `(${progress.current})` : ""}`
: !userProfile?.fullName
? "Tap to sync"
: userProfile.fullName}
</Paragraph>
@@ -131,20 +132,16 @@ export const UserStatus = () => {
"Not logged in"
) : lastSynced && lastSynced !== "Never" ? (
<>
{syncing
? `Syncing ${progress ? `(${progress.current})` : ""}`
: lastSyncStatus === SyncStatus.Failed
{lastSyncStatus === SyncStatus.Failed
? "Sync failed"
: "Synced"}{" "}
{!syncing ? (
<TimeSince
style={{
fontSize: SIZE.xs,
color: colors.secondary.paragraph
}}
time={lastSynced}
/>
) : null}
<TimeSince
style={{
fontSize: SIZE.xs,
color: colors.secondary.paragraph
}}
time={lastSynced}
/>
{isOffline ? " (offline)" : ""}
</>
) : (
@@ -152,7 +149,7 @@ export const UserStatus = () => {
)}{" "}
<Icon
name="checkbox-blank-circle"
size={9}
size={11}
allowFontScaling
color={
!user || lastSyncStatus === SyncStatus.Failed
@@ -165,80 +162,56 @@ export const UserStatus = () => {
</Paragraph>
</View>
<View
<Pressable
style={{
flexDirection: "row",
gap: 0
borderRadius: 100,
width: 40,
height: 40
}}
hitSlop={{
top: 10,
bottom: 10,
left: 20
}}
onPress={() => {
if (user) {
Sync.run();
} else {
tabBarRef.current?.closeDrawer();
eSendEvent(eOpenLoginDialog);
}
}}
>
<IconButton
hitSlop={{
top: 10,
bottom: 10
}}
onPress={() => {
useThemeStore.getState().setColorScheme();
}}
name="theme-light-dark"
color={isDark ? colors.primary.accent : colors.primary.icon}
size={SIZE.lg}
style={{
borderRadius: 100,
width: 40,
height: 40
}}
/>
<Pressable
style={{
borderRadius: 100,
width: 40,
height: 40
}}
hitSlop={{
top: 10,
bottom: 10
}}
onPress={() => {
if (user) {
Sync.run();
} else {
tabBarRef.current?.closeDrawer();
eSendEvent(eOpenLoginDialog);
}
}}
>
{user ? (
syncing ? (
<ActivityIndicator
color={colors.primary.accent}
size={SIZE.xl}
/>
) : lastSyncStatus === SyncStatus.Failed ? (
<Icon
color={colors.error.icon}
name="sync-alert"
size={SIZE.lg}
allowFontScaling
/>
) : (
<Icon
allowFontScaling
color={colors.primary.icon}
name="sync"
size={SIZE.lg}
/>
)
{user ? (
syncing ? (
<ActivityIndicator
color={colors.primary.accent}
size={SIZE.xl}
/>
) : lastSyncStatus === SyncStatus.Failed ? (
<Icon
color={colors.error.icon}
name="sync-alert"
size={SIZE.lg}
allowFontScaling
/>
) : (
<Icon
allowFontScaling
color={colors.primary.accent}
name="sync"
size={SIZE.lg}
name="login"
/>
)}
</Pressable>
</View>
)
) : (
<Icon
allowFontScaling
color={colors.primary.accent}
size={SIZE.lg}
name="login"
/>
)}
</Pressable>
</Pressable>
</View>
</View>

View File

@@ -59,7 +59,7 @@ export interface TabsRef {
lock: () => boolean;
openDrawer: (animated?: boolean) => void;
closeDrawer: (animated?: boolean) => void;
page: () => number;
page: number;
setScrollEnabled: () => true;
isDrawerOpen: () => boolean;
node: RefObject<Animated.View>;
@@ -239,7 +239,7 @@ export const FluidTabs = forwardRef<TabsRef, TabProps>(function FluidTabs(
onDrawerStateChange(false);
isDrawerOpen.value = false;
},
page: () => currentTab.value,
page: currentTab.value,
setScrollEnabled: () => true,
node: node
}),

View File

@@ -19,4 +19,35 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { FeatureType } from "./components/sheets/new-feature";
export const features: FeatureType[] = [];
export const features: FeatureType[] = [
{
title: "Bi-directional note linking",
body: "Now you can link notes to each other in both directions."
},
{
title: "Tabs",
body: "Tabs allow you to have multiple notes open at the same time. You can switch between them easily."
},
{
title: "Nested notebooks",
body: "You can now create notebooks inside other notebooks."
},
{
title: "At-rest encryption",
body: "Your notes are now encrypted when stored on the device."
},
{
title: "Material You themed icon",
body: "The app now has a Material You themed icon that changes color based on the wallpaper.",
platform: "android"
},
{
title: "New note quick settings tile",
body: "You can now add a new note tile to quickly create notes from quick settings panel.",
platform: "android"
},
{
title: "And so much more...",
body: "V3 packs so much that it's hard to put everything here, check out our blog at Notesnook V3 is here, and it's packed with new features and improvements such as bi-directional note linking, better syncing, at-rest encryption, nested notebooks, editor tabs, better app lock and so much more. https://blog.notesnook.com/introducing-notesnook-v3."
}
];

View File

@@ -85,6 +85,7 @@ import { SyncStatus, useUserStore } from "../stores/use-user-store";
import { updateStatusBarColor } from "../utils/colors";
import { BETA } from "../utils/constants";
import {
eClearEditor,
eCloseSheet,
eLoginSessionExpired,
eOnLoadNote,
@@ -531,6 +532,12 @@ export const useAppEvents = () => {
//@ts-ignore
globalThis["IS_SHARE_EXTENSION"] = false;
} else {
const id = useTabStore.getState().getCurrentNoteId();
const note = id ? await db.notes.note(id) : undefined;
const locked = note && (await db.vaults.itemExists(note));
if (locked && SettingsService.canLockAppInBackground()) {
eSendEvent(eClearEditor);
}
await saveEditorState();
if (
SettingsService.canLockAppInBackground() &&

View File

@@ -71,10 +71,12 @@ export const useDBItem = <T extends keyof ItemTypeKey>(
if (typeof itemId === "string" && itemId !== itemIdRef.current) return;
if (!isValidIdOrIndex(idOrIndex)) return;
console.log("useDBItem.onUpdateItem", idOrIndex, type);
if (items && typeof idOrIndex === "number") {
items.item(idOrIndex).then((item) => {
setItem(item.item);
itemIdRef.current = item.item?.id;
itemIdRef.current = item.item.id;
});
} else {
if (!(db as any)[type + "s"][type]) {

View File

@@ -36,6 +36,7 @@ export const useNotebook = (
const onRequestUpdate = React.useCallback(() => {
if (!item?.id) return;
console.log("useNotebook.onRequestUpdate", item?.id, Date.now());
const selector = db.relations.from(
{
@@ -58,6 +59,7 @@ export const useNotebook = (
useEffect(() => {
if (nestedNotebooks) {
console.log("useNotebook.useEffect.onRequestUpdate");
onRequestUpdate();
}
}, [item?.id, onRequestUpdate, nestedNotebooks]);

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useCallback, useEffect } from "react";
import BiometricService from "../services/biometrics";
import BiometicService from "../services/biometrics";
import { eSubscribeEvent, eUnSubscribeEvent } from "../services/event-manager";
import { db } from "../common/database";
@@ -39,8 +39,8 @@ export const useVaultStatus = () => {
const checkVaultStatus = useCallback(() => {
db.vault?.exists().then(async (exists) => {
const available = await BiometricService.isBiometryAvailable();
const fingerprint = await BiometricService.hasInternetCredentials();
const available = await BiometicService.isBiometryAvailable();
const fingerprint = await BiometicService.hasInternetCredentials();
if (
VaultStatusCache.exists === exists &&
VaultStatusCache.biometryEnrolled === fingerprint &&

View File

@@ -65,7 +65,6 @@ import {
eClearEditor,
eCloseFullscreenEditor,
eOnEnterEditor,
eOnExitEditor,
eOnLoadNote,
eOpenFullscreenEditor,
eUnlockNote
@@ -512,7 +511,6 @@ const onChangeTab = async (event) => {
editorState().movedAway = false;
editorState().isFocused = true;
activateKeepAwake();
eSendEvent(eOnEnterEditor);
if (!useTabStore.getState().getCurrentNoteId()) {
eSendEvent(eOnLoadNote, {
@@ -524,13 +522,14 @@ const onChangeTab = async (event) => {
) {
eSendEvent(eUnlockNote);
}
eSendEvent(eOnEnterEditor);
}
} else {
if (event.from === 2) {
deactivateKeepAwake();
editorState().movedAway = true;
editorState().isFocused = false;
eSendEvent(eOnExitEditor);
eSendEvent(eClearEditor, "removeHandler");
// Lock all tabs with locked notes...
for (const tab of useTabStore.getState().tabs) {

View File

@@ -32,7 +32,7 @@ import WebView from "react-native-webview";
import { ShouldStartLoadRequest } from "react-native-webview/lib/WebViewTypes";
import { notesnook } from "../../../e2e/test.ids";
import { db } from "../../common/database";
import BiometricService from "../../services/biometrics";
import BiometicService from "../../services/biometrics";
import {
ToastManager,
eSendEvent,
@@ -51,11 +51,7 @@ import { EditorProps, useEditorType } from "./tiptap/types";
import { useEditor } from "./tiptap/use-editor";
import { useEditorEvents } from "./tiptap/use-editor-events";
import { syncTabs, useTabStore } from "./tiptap/use-tab-store";
import {
editorController,
editorState,
openInternalLink
} from "./tiptap/utils";
import { editorController, editorState } from "./tiptap/utils";
const style: ViewStyle = {
height: "100%",
@@ -65,10 +61,7 @@ const style: ViewStyle = {
backgroundColor: "transparent"
};
const onShouldStartLoadWithRequest = (request: ShouldStartLoadRequest) => {
if (request.url.includes("nn://")) {
openInternalLink(request.url);
return false;
} else if (request.url.includes("https")) {
if (request.url.includes("https")) {
if (Platform.OS === "ios" && !request.isTopFrame) return true;
openLinkInBrowser(request.url);
return false;
@@ -208,8 +201,8 @@ const useLockedNoteHandler = () => {
useEffect(() => {
(async () => {
const biometry = await BiometricService.isBiometryAvailable();
const fingerprint = await BiometricService.hasInternetCredentials();
const biometry = await BiometicService.isBiometryAvailable();
const fingerprint = await BiometicService.hasInternetCredentials();
useTabStore.setState({
biometryAvailable: !!biometry,
biometryEnrolled: !!fingerprint
@@ -223,9 +216,9 @@ const useLockedNoteHandler = () => {
try {
if (!tabRef.current?.noteLocked || !tabRef.current) return;
console.log("Trying to unlock with biometrics...");
const credentials = await BiometricService.getCredentials(
const credentials = await BiometicService.getCredentials(
"Unlock note",
"Unlock note to open it in editor."
"Unlock note to open it in editor. If biometrics are not working, you can enter device pin to unlock vault."
);
if (credentials && credentials?.password && tabRef.current.noteId) {
@@ -233,7 +226,6 @@ const useLockedNoteHandler = () => {
tabRef.current.noteId,
credentials?.password
);
eSendEvent(eOnLoadNote, {
item: note
});
@@ -270,7 +262,7 @@ const useLockedNoteHandler = () => {
try {
const unlocked = await db.vault.unlock(password);
if (!unlocked) throw new Error("Incorrect vault password");
await BiometricService.storeCredentials(password);
await BiometicService.storeCredentials(password);
eSendEvent("vaultUpdated");
ToastManager.show({
heading: "Biometric unlocking enabled!",
@@ -279,8 +271,8 @@ const useLockedNoteHandler = () => {
context: "global"
});
const biometry = await BiometricService.isBiometryAvailable();
const fingerprint = await BiometricService.hasInternetCredentials();
const biometry = await BiometicService.isBiometryAvailable();
const fingerprint = await BiometicService.hasInternetCredentials();
useTabStore.setState({
biometryAvailable: !!biometry,
biometryEnrolled: !!fingerprint

View File

@@ -21,11 +21,12 @@ import Sodium from "@ammarahmed/react-native-sodium";
import { isImage } from "@notesnook/core/dist/utils/filename";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import DocumentPicker, {
DocumentPickerOptions,
DocumentPickerResponse
} from "react-native-document-picker";
import { Image, openCamera, openPicker } from "react-native-image-crop-picker";
import DocumentPicker from "react-native-document-picker";
import {
ImagePickerResponse,
launchCamera,
launchImageLibrary
} from "react-native-image-picker";
import { DatabaseLogger, db } from "../../../common/database";
import filesystem from "../../../common/filesystem";
import { compressToFile } from "../../../common/filesystem/compress";
@@ -42,7 +43,7 @@ import { eCloseSheet } from "../../../utils/events";
import { useTabStore } from "./use-tab-store";
import { editorController, editorState } from "./utils";
const showEncryptionSheet = (file: DocumentPickerResponse) => {
const showEncryptionSheet = (file) => {
presentSheet({
title: "Encrypting attachment",
paragraph: `Please wait while we encrypt ${file.name} file for upload`,
@@ -50,25 +51,24 @@ const showEncryptionSheet = (file: DocumentPickerResponse) => {
});
};
const santizeUri = (uri: string) => {
const santizeUri = (uri) => {
uri = decodeURI(uri);
uri = Platform.OS === "ios" ? uri.replace("file:///", "/") : uri;
return uri;
};
type PickerOptions = {
noteId?: string;
tabId?: number;
type: "image" | "camera" | "file";
reupload: boolean;
hash?: string;
context?: string;
outputType?: "base64" | "url" | "cache";
};
const file = async (fileOptions: PickerOptions) => {
/**
* @param {{
* noteId: string,
* tabId: string,
* type: "image" | "camera" | "file"
* reupload: boolean
* hash?: string
* }} fileOptions
*/
const file = async (fileOptions) => {
try {
const options: DocumentPickerOptions<"ios"> = {
const options = {
mode: "import",
allowMultiSelection: false
};
@@ -87,11 +87,11 @@ const file = async (fileOptions: PickerOptions) => {
file = file[0];
let uri = Platform.OS === "ios" ? file.fileCopyUri || file.uri : file.uri;
let uri = Platform.OS === "ios" ? file.fileCopyUri : file.uri;
if ((file.size || 0) > FILE_SIZE_LIMIT) {
if (file.size > FILE_SIZE_LIMIT) {
ToastManager.show({
heading: "File too large",
title: "File too large",
message: "The maximum allowed size per file is 500 MB",
type: "error"
});
@@ -115,34 +115,22 @@ const file = async (fileOptions: PickerOptions) => {
uri: uri,
type: "url"
});
if (
!(await attachFile(
uri,
hash,
file.type || "application/octet-stream",
file.name,
fileOptions
))
)
if (!(await attachFile(uri, hash, file.type, file.name, fileOptions)))
return;
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
if (!fileOptions.tabId) return;
if (
fileOptions.tabId &&
fileOptions.noteId &&
useTabStore.getState().getNoteIdForTab(fileOptions.tabId) ===
fileOptions.noteId
useTabStore.getState().getNoteIdForTab(options.tabId) === options.noteId
) {
if (isImage(file.type || "application/octet-stream")) {
if (isImage(file.type)) {
editorController.current?.commands.insertImage(
{
hash: hash,
filename: file.name,
mime: file.type || "application/octet-stream",
size: file.size || 0,
dataurl: (await db.attachments.read(hash, "base64")) as string,
type: "image"
mime: file.type,
size: file.size,
dataurl: await db.attachments.read(hash, "base64"),
title: file.name
},
fileOptions.tabId
);
@@ -151,9 +139,8 @@ const file = async (fileOptions: PickerOptions) => {
{
hash: hash,
filename: file.name,
mime: file.type || "application/octet-stream",
size: file.size || 0,
type: "file"
mime: file.type,
size: file.size
},
fileOptions.tabId
);
@@ -165,7 +152,7 @@ const file = async (fileOptions: PickerOptions) => {
}, 1000);
} catch (e) {
ToastManager.show({
heading: (e as Error).message,
heading: e.message,
message: "You need internet access to attach a file",
type: "error",
context: "global"
@@ -174,51 +161,29 @@ const file = async (fileOptions: PickerOptions) => {
}
};
const camera = async (options: PickerOptions) => {
/**
* @param {{
* noteId: string,
* tabId: string,
* type: "image" | "camera" | "file"
* reupload: boolean
* hash?: string
* }} options
*/
const camera = async (options) => {
try {
await db.attachments.generateKey();
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
openCamera({
mediaType: "photo",
includeBase64: true,
cropping: false,
multiple: true,
maxFiles: 10,
writeTempFile: true
})
.then((response) => handleImageResponse(response, options))
.catch((e) => {
console.log("camera error: ", e);
});
launchCamera(
{
includeBase64: true,
mediaType: "photo"
},
(response) => handleImageResponse(response, options)
);
} catch (e) {
ToastManager.show({
heading: (e as Error).message,
type: "error",
context: "global"
});
console.log("attachment error:", e);
}
};
const gallery = async (options: PickerOptions) => {
try {
await db.attachments.generateKey();
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
openPicker({
includeBase64: true,
mediaType: "photo",
maxFiles: 10,
cropping: false,
multiple: true
})
.then((response) => handleImageResponse(response, options))
.catch((e) => {
console.log("gallery error: ", e);
});
} catch (e) {
useSettingStore.getState().setAppDidEnterBackgroundForAction(false);
ToastManager.show({
heading: (e as Error).message,
heading: e.message,
message: "You need internet access to attach a file",
type: "error",
context: "global"
@@ -227,9 +192,53 @@ const gallery = async (options: PickerOptions) => {
}
};
const pick = async (options: PickerOptions) => {
const gallery = async (options) => {
try {
await db.attachments.generateKey();
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
launchImageLibrary(
{
includeBase64: true,
mediaType: "photo",
selectionLimit: 10
},
(response) => handleImageResponse(response, options)
);
} catch (e) {
ToastManager.show({
heading: e.message,
message: "You need internet access to attach a file",
type: "error",
context: "global"
});
console.log("attachment error:", e);
}
};
/**
*
* @typedef {{
* noteId?: string,
* tabId?: string,
* type: "image" | "camera" | "file"
* reupload: boolean
* hash?: string
* context?: string
* }} ImagePickerOptions
*
* @param {{
* noteId?: string,
* tabId?: string,
* type: "image" | "camera" | "file"
* reupload: boolean
* hash?: string
* context?: string
* }} options
* @returns
*/
const pick = async (options) => {
if (!PremiumService.get()) {
const user = await db.user.getUser();
let user = await db.user.getUser();
if (editorState().isFocused) {
editorState().isFocused = true;
}
@@ -250,49 +259,58 @@ const pick = async (options: PickerOptions) => {
file(options);
}
};
/**
*
* @param {ImagePickerResponse} response
* @param {ImagePickerOptions} options
* @returns
*/
const handleImageResponse = async (response, options) => {
if (
response.didCancel ||
response.errorMessage ||
!response.assets ||
response.assets?.length === 0
) {
return;
}
const result = await AttachImage.present(response);
const handleImageResponse = async (
response: Image[],
options: PickerOptions
) => {
const result = await AttachImage.present(response, options.context);
if (!result) return;
const compress = result.compress;
for (const image of response) {
const isPng = /(png)/g.test(image.mime);
const isJpeg = /(jpeg|jpg)/g.test(image.mime);
for (let image of response.assets) {
const isPng = /(png)/g.test(image.type);
const isJpeg = /(jpeg|jpg)/g.test(image.type);
if (compress && (isPng || isJpeg)) {
image.path = await compressToFile(
Platform.OS === "ios" ? "file://" + image.path : image.path,
image.uri = await compressToFile(
Platform.OS === "ios" ? "file://" + image.uri : image.uri,
isPng ? "PNG" : "JPEG"
);
const stat = await RNFetchBlob.fs.stat(image.path.replace("file://", ""));
image.size = stat.size;
image.path =
Platform.OS === "ios" ? image.path.replace("file://", "") : image.path;
const stat = await RNFetchBlob.fs.stat(image.uri.replace("file://", ""));
image.fileSize = stat.size;
}
if (image.size > IMAGE_SIZE_LIMIT) {
if (image.fileSize > IMAGE_SIZE_LIMIT) {
ToastManager.show({
heading: "File too large",
title: "File too large",
message: "The maximum allowed size per image is 50 MB",
type: "error"
});
return;
}
const b64 = `data:${image.mime};base64, ` + image.data;
const uri = decodeURI(image.path);
let b64 = `data:${image.type};base64, ` + image.base64;
const uri = decodeURI(image.uri);
const hash = await Sodium.hashFile({
uri: uri,
type: "url"
});
const fileName = image.filename || "image";
let fileName = image.originalFileName || image.fileName;
console.log("attaching file...");
if (!(await attachFile(uri, hash, image.mime, fileName, options))) return;
if (!(await attachFile(uri, hash, image.type, fileName, options))) return;
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
console.log("attaching image to note...");
@@ -304,13 +322,11 @@ const handleImageResponse = async (
editorController.current?.commands.insertImage(
{
hash: hash,
mime: image.mime,
type: "image",
mime: image.type,
title: fileName,
dataurl: b64,
size: image.size,
filename: fileName as string,
width: image.width,
height: image.height
size: image.fileSize,
filename: fileName
},
options.tabId
);
@@ -327,16 +343,10 @@ const handleImageResponse = async (
* @param {ImagePickerOptions} options
* @returns
*/
export async function attachFile(
uri: string,
hash: string,
type: string,
filename: string,
options: PickerOptions
) {
export async function attachFile(uri, hash, type, filename, options) {
try {
const exists = await db.attachments.exists(hash);
let encryptionInfo: any;
let exists = await db.attachments.exists(hash);
let encryptionInfo;
if (options?.hash && options.hash !== hash) {
ToastManager.show({
heading: "Please select the same file for reuploading",
@@ -348,36 +358,26 @@ export async function attachFile(
}
if (!options.reupload && exists) {
options.reupload = (await filesystem.getUploadedFileSize(hash)) === 0;
}
if (options.reupload) {
DatabaseLogger.log(`Deleting file before reupload. ${hash}`);
const deleted = await db.fs().deleteFile(hash, false);
if (!deleted)
throw new Error(`Failed to delete file before reupload. ${hash}`);
options.reupload = (await filesystem.getUploadedFileSize(hash)) <= 0;
}
if (!exists || options?.reupload) {
const key = await db.attachments.generateKey();
let key = await db.attachments.generateKey();
encryptionInfo = await Sodium.encryptFile(key, {
uri: uri,
type: options.outputType || "url",
type: options.type || "url",
hash: hash
} as any);
});
encryptionInfo.mimeType = type;
encryptionInfo.filename = filename;
encryptionInfo.alg = "xcha-stream";
encryptionInfo.size = encryptionInfo.length;
encryptionInfo.key = key;
if (options?.reupload && exists) {
const attachment = await db.attachments.attachment(hash);
if (attachment) await db.attachments.reset(attachment?.id);
}
if (options?.reupload && exists) await db.attachments.reset(hash);
} else {
encryptionInfo = { hash: hash };
}
await db.attachments.add(encryptionInfo);
await db.attachments.add(encryptionInfo, options.noteId);
return true;
} catch (e) {
DatabaseLogger.error(e);

View File

@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* eslint-disable no-case-declarations */
/* eslint-disable @typescript-eslint/no-var-requires */
import { parseInternalLink } from "@notesnook/core";
import { ItemReference } from "@notesnook/core/dist/types";
import type { Attachment } from "@notesnook/editor/dist/extensions/attachment/index";
import { getDefaultPresets } from "@notesnook/editor/dist/toolbar/tool-definitions";
@@ -26,6 +27,7 @@ import Clipboard from "@react-native-clipboard/clipboard";
import React, { useCallback, useEffect, useRef } from "react";
import {
BackHandler,
InteractionManager,
Keyboard,
KeyboardEventListener,
NativeEventSubscription,
@@ -35,7 +37,6 @@ import { WebViewMessageEvent } from "react-native-webview";
import { DatabaseLogger, db } from "../../../common/database";
import downloadAttachment from "../../../common/filesystem/download-attachment";
import EditorTabs from "../../../components/sheets/editor-tabs";
import { Issue } from "../../../components/sheets/github/issue";
import LinkNote from "../../../components/sheets/link-note";
import ManageTagsSheet from "../../../components/sheets/manage-tags";
import { RelationsList } from "../../../components/sheets/relations-list";
@@ -60,7 +61,6 @@ import {
eCloseFullscreenEditor,
eEditorTabFocused,
eOnEnterEditor,
eOnExitEditor,
eOnLoadNote,
eOpenFullscreenEditor,
eOpenLoginDialog,
@@ -75,8 +75,8 @@ import { useDragState } from "../../settings/editor/state";
import { EventTypes } from "./editor-events";
import { EditorMessage, EditorProps, useEditorType } from "./types";
import { useTabStore } from "./use-tab-store";
import { EditorEvents, editorState, openInternalLink } from "./utils";
import { EditorEvents, editorState } from "./utils";
import { Issue } from "../../../components/sheets/github/issue";
const publishNote = async () => {
const user = useUserStore.getState().user;
@@ -270,20 +270,21 @@ export const useEditorEvents = (
}, [editor, deviceMode, fullscreen]);
const onHardwareBackPress = useCallback(() => {
console.log(tabBarRef.current?.page());
if (tabBarRef.current?.page() === 2) {
if (tabBarRef.current?.page === 2) {
onBackPress();
return true;
}
}, [onBackPress]);
const onEnterEditor = useCallback(async () => {
if (!DDS.isTab) {
handleBack.current = BackHandler.addEventListener(
"hardwareBackPress",
onHardwareBackPress
);
}
InteractionManager.runAfterInteractions(() => {
if (!DDS.isTab) {
handleBack.current = BackHandler.addEventListener(
"hardwareBackPress",
onHardwareBackPress
);
}
});
}, [onHardwareBackPress]);
const onClearEditorSessionRequest = useCallback(
@@ -330,14 +331,7 @@ export const useEditorEvents = (
}, [fullscreen, onHardwareBackPress]);
useEffect(() => {
const onExitEditor = () => {
if (handleBack.current) {
handleBack.current.remove();
}
};
eSubscribeEvent(eOnEnterEditor, onEnterEditor);
eSubscribeEvent(eOnExitEditor, onExitEditor);
eSubscribeEvent(
eClearEditor + editor.editorId,
onClearEditorSessionRequest
@@ -345,7 +339,6 @@ export const useEditorEvents = (
return () => {
eUnSubscribeEvent(eClearEditor, onClearEditorSessionRequest);
eUnSubscribeEvent(eOnEnterEditor, onEnterEditor);
eUnSubscribeEvent(eOnExitEditor, onExitEditor);
};
}, [editor.editorId, onClearEditorSessionRequest, onEnterEditor]);
@@ -450,7 +443,7 @@ export const useEditorEvents = (
break;
case EventTypes.filepicker:
editorState().isAwaitingResult = true;
const { pick } = require("./picker").default;
const { pick } = require("./picker.js").default;
pick({
type: editorMessage.value,
noteId: noteId,
@@ -469,10 +462,12 @@ export const useEditorEvents = (
case EventTypes.getAttachmentData: {
const attachment = (editorMessage.value as any)
?.attachment as Attachment;
.attachment as Attachment;
DatabaseLogger.log(
`Getting attachment data: ${attachment?.hash} ${attachment?.type}`
console.log(
"Getting attachment data:",
attachment.hash,
attachment.type
);
downloadAttachment(attachment.hash, true, {
base64: attachment.type === "image",
@@ -492,8 +487,8 @@ export const useEditorEvents = (
data
});
})
.catch((e) => {
DatabaseLogger.error(e);
.catch(() => {
console.log("Error downloading attachment data");
editor.postMessage(EditorEvents.attachmentData, {
resolverId: (editorMessage.value as any).resolverId,
data: undefined
@@ -524,7 +519,27 @@ export const useEditorEvents = (
break;
case EventTypes.link:
if (editorMessage.value.startsWith("nn://")) {
openInternalLink(editorMessage.value);
const data = parseInternalLink(editorMessage.value);
if (!data?.id) break;
if (
data.id ===
useTabStore
.getState()
.getNoteIdForTab(useTabStore.getState().currentTab)
) {
if (data.params?.blockId) {
setTimeout(() => {
if (!data.params?.blockId) return;
editor.commands.scrollIntoViewById(data.params.blockId);
}, 150);
}
return;
}
eSendEvent(eOnLoadNote, {
item: await db.notes.note(data?.id),
blockId: data.params?.blockId
});
console.log(
"Opening note from internal link:",
editorMessage.value
@@ -647,8 +662,10 @@ export const useEditorEvents = (
.updateTab(useTabStore.getState().currentTab, {
readonly: false
});
setTimeout(() => {
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate();
ToastManager.show({
heading: "Readonly mode disabled.",
type: "success"
});
}
break;

View File

@@ -837,7 +837,7 @@ export const useEditor = (
if (!noteId) {
overlay(false);
loadNote({ newNote: true });
if (tabBarRef.current?.page() === 1) {
if (tabBarRef.current?.page === 1) {
state.current.currentlyEditing = false;
}
}

View File

@@ -22,15 +22,10 @@ import { TextInput } from "react-native";
import WebView from "react-native-webview";
import { MMKV } from "../../../common/database/mmkv";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent
} from "../../../services/event-manager";
import { AppState, EditorState, useEditorType } from "./types";
import { useTabStore } from "./use-tab-store";
import { parseInternalLink } from "@notesnook/core";
import { eOnLoadNote } from "../../../utils/events";
import { db } from "../../../common/database";
export const textInput = createRef<TextInput>();
export const editorController =
createRef<useEditorType>() as MutableRefObject<useEditorType>;
@@ -181,27 +176,3 @@ export function clearAppState() {
appState = undefined;
MMKV.removeItem("appState");
}
export async function openInternalLink(url: string) {
const data = parseInternalLink(url);
if (!data?.id) return false;
if (
data.id ===
useTabStore.getState().getNoteIdForTab(useTabStore.getState().currentTab)
) {
if (data.params?.blockId) {
setTimeout(() => {
if (!data.params?.blockId) return;
editorController.current.commands.scrollIntoViewById(
data.params.blockId
);
}, 150);
}
return;
}
eSendEvent(eOnLoadNote, {
item: await db.notes.note(data?.id),
blockId: data.params?.blockId
});
}

View File

@@ -32,7 +32,6 @@ import { openEditor } from "../notes/common";
export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
const [notes, loading] = useNotes();
const isFocused = useNavigationFocus(navigation, {
onFocus: (prev) => {
Navigation.routeNeedsUpdate(

View File

@@ -16,37 +16,40 @@ 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 { resolveItems } from "@notesnook/common";
import { VirtualizedGrouping } from "@notesnook/core";
import { Note, Notebook } from "@notesnook/core/dist/types";
import React, { useEffect, useRef, useState } from "react";
import { View } from "react-native";
import { db } from "../../common/database";
import DelayLayout from "../../components/delay-layout";
import { Header } from "../../components/header";
import List from "../../components/list";
import { NotebookHeader } from "../../components/list-items/headers/notebook-header";
import SelectionHeader from "../../components/selection-header";
import { AddNotebookSheet } from "../../components/sheets/add-notebook";
import { IconButton } from "../../components/ui/icon-button";
import { Pressable } from "../../components/ui/pressable";
import Paragraph from "../../components/ui/typography/paragraph";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import { eSendEvent, eSubscribeEvent } from "../../services/event-manager";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import Navigation, { NavigationProps } from "../../services/navigation";
import useNavigationStore, {
NotebookScreenParams
} from "../../stores/use-navigation-store";
import { eUpdateNotebookRoute } from "../../utils/events";
import { findRootNotebookId } from "../../utils/notebooks";
import { SIZE } from "../../utils/size";
import { openEditor, setOnFirstSave } from "../notes/common";
import SelectionHeader from "../../components/selection-header";
import Paragraph from "../../components/ui/typography/paragraph";
import { View } from "react-native";
import { SIZE } from "../../utils/size";
import { IconButton } from "../../components/ui/icon-button";
import { Pressable } from "../../components/ui/pressable";
import { resolveItems } from "@notesnook/common";
const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
const params = useRef<NotebookScreenParams>(route?.params);
const [loading, setLoading] = useState(true);
const updateOnFocus = useRef(false);
const [breadcrumbs, setBreadcrumbs] = useState<
{
id: string;
@@ -56,17 +59,11 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
useNavigationFocus(navigation, {
onFocus: () => {
if (updateOnFocus.current) {
onRequestUpdate();
updateOnFocus.current = false;
} else {
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
}
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
syncWithNavigation();
return false;
},
onBlur: () => {
updateOnFocus.current = false;
setOnFirstSave(null);
return false;
}
@@ -82,15 +79,6 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
const onRequestUpdate = React.useCallback(
async (data?: NotebookScreenParams) => {
if (
useNavigationStore.getState().focusedRouteId !==
params.current.item.id &&
!data
) {
updateOnFocus.current = true;
return;
}
if (data?.item?.id && params.current.item?.id !== data?.item?.id) {
const nextRootNotebookId = await findRootNotebookId(data?.item?.id);
const currentNotebookRoot = await findRootNotebookId(
@@ -116,7 +104,7 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
if (notebook) {
const breadcrumbs = await db.notebooks.breadcrumbs(notebook.id);
setBreadcrumbs(breadcrumbs.slice(0, breadcrumbs.length - 1));
setBreadcrumbs(breadcrumbs);
params.current.item = notebook;
const notes = await db.relations
.from(notebook, "note")
@@ -134,10 +122,10 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
);
useEffect(() => {
onRequestUpdate(params.current);
const sub = eSubscribeEvent(eUpdateNotebookRoute, onRequestUpdate);
onRequestUpdate();
eSubscribeEvent(eUpdateNotebookRoute, onRequestUpdate);
return () => {
sub?.unsubscribe();
eUnSubscribeEvent(eUpdateNotebookRoute, onRequestUpdate);
};
}, [onRequestUpdate]);
@@ -179,7 +167,7 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
onPressDefaultRightButton={openEditor}
/>
{breadcrumbs && breadcrumbs.length > 0 ? (
{breadcrumbs ? (
<View
style={{
width: "100%",

View File

@@ -81,21 +81,16 @@ const NotesPage = ({
route.name === "ColoredNotes"
? (params.current?.item as Color)?.colorCode
: undefined;
const updateOnFocus = useRef(false);
const isFocused = useNavigationFocus(navigation, {
onFocus: (prev) => {
if (updateOnFocus.current) {
onRequestUpdate();
updateOnFocus.current = false;
} else {
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
}
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
syncWithNavigation();
if (focusControl) return !prev.current;
return false;
},
onBlur: () => {
updateOnFocus.current = false;
setOnFirstSave(null);
return false;
},
@@ -104,6 +99,7 @@ const NotesPage = ({
const syncWithNavigation = React.useCallback(() => {
const { item } = params.current;
useNavigationStore
.getState()
.setFocusedRouteId(params?.current?.item?.id || route.name);
@@ -117,14 +113,6 @@ const NotesPage = ({
const onRequestUpdate = React.useCallback(
async (data?: NotesScreenParams) => {
if (
useNavigationStore.getState().focusedRouteId !==
params.current.item.id &&
!data
) {
updateOnFocus.current = false;
return;
}
const isNew = data && data?.item?.id !== params.current?.item?.id;
if (data) params.current = data;

View File

@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { useThemeColors } from "@notesnook/theme";
import React, { useRef } from "react";
import { Platform, View } from "react-native";
import { View } from "react-native";
import { TextInput } from "react-native-gesture-handler";
import { IconButton } from "../../components/ui/icon-button";
import Navigation from "../../services/navigation";
@@ -49,8 +49,8 @@ export const SearchBar = ({
return selectionMode && isFocused ? null : (
<View
style={{
height: Platform.OS === "android" ? 50 + insets.top : 50,
paddingTop: Platform.OS === "ios" ? 0 : insets.top,
height: 50 + insets.top,
paddingTop: insets.top,
flexDirection: "row",
alignItems: "center",
flexShrink: 1,

View File

@@ -26,7 +26,7 @@ import { Pressable } from "../../components/ui/pressable";
import Seperator from "../../components/ui/seperator";
import Heading from "../../components/ui/typography/heading";
import Paragraph from "../../components/ui/typography/paragraph";
import BiometricService from "../../services/biometrics";
import BiometicService from "../../services/biometrics";
import { ToastManager, presentSheet } from "../../services/event-manager";
import SettingsService from "../../services/settings";
import { useSettingStore } from "../../stores/use-setting-store";
@@ -119,7 +119,7 @@ const AppLock = () => {
type={appLockMode === item.value ? "secondary" : "transparent"}
onPress={async () => {
if (
!(await BiometricService.isBiometryAvailable()) &&
!(await BiometicService.isBiometryAvailable()) &&
!useUserStore.getState().user &&
item.value !== modes[0].value &&
!SettingsService.getProperty("appLockHasPasswordSecurity")
@@ -139,7 +139,7 @@ const AppLock = () => {
) &&
item.value !== modes[0].value
) {
const verified = await BiometricService.validateUser(
const verified = await BiometicService.validateUser(
"Verify it's you"
);
if (verified) {

View File

@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { db } from "../../common/database";
import { validateAppLockPassword } from "../../common/database/encryption";
import { presentDialog } from "../../components/dialog/functions";
import BiometricService from "../../services/biometrics";
import BiometicService from "../../services/biometrics";
import { ToastManager } from "../../services/event-manager";
import SettingsService from "../../services/settings";
import { useUserStore } from "../../stores/use-user-store";
@@ -100,9 +100,9 @@ export async function verifyUserWithApplock() {
}
});
} else {
BiometricService.isBiometryAvailable().then((available) => {
BiometicService.isBiometryAvailable().then((available) => {
if (available) {
BiometricService.validateUser("Verify it's you").then((verified) => {
BiometicService.validateUser("Verify it's you").then((verified) => {
resolve(verified);
});
} else if (useUserStore.getState().user) {

View File

@@ -164,6 +164,12 @@ export const LICENSES = [
author: "dooboolab",
link: "https://github.com/dooboolab/react-native-iap"
},
{
name: "react-native-image-picker",
licenseType: "MIT",
author: "Johan du Toit (Johan-dutoit)",
link: "https://github.com/react-native-image-picker/react-native-image-picker"
},
{
name: "react-native-keychain",
licenseType: "MIT",

View File

@@ -38,7 +38,7 @@ import { Update } from "../../components/sheets/update";
import { VaultStatusType, useVaultStatus } from "../../hooks/use-vault-status";
import { BackgroundSync } from "../../services/background-sync";
import BackupService from "../../services/backup";
import BiometricService from "../../services/biometrics";
import BiometicService from "../../services/biometrics";
import {
ToastManager,
eSendEvent,
@@ -349,7 +349,7 @@ export const settingsGroups: SettingSection[] = [
await db.user?.logout();
setLoginMessage();
await PremiumService.setPremiumStatus();
await BiometricService.resetCredentials();
await BiometicService.resetCredentials();
MMKV.clearStore();
clearAllStores();
refreshAllStores();
@@ -394,7 +394,7 @@ export const settingsGroups: SettingSection[] = [
if (verified) {
eSendEvent("settings-loading", true);
await db.user?.deleteUser(value);
await BiometricService.resetCredentials();
await BiometicService.resetCredentials();
SettingsService.set({
introCompleted: true
});
@@ -789,7 +789,7 @@ export const settingsGroups: SettingSection[] = [
}
},
{
id: "biometric-unlock",
id: "biometic-unlock",
type: "switch",
name: "Biometric unlocking",
icon: "fingerprint",
@@ -859,13 +859,13 @@ export const settingsGroups: SettingSection[] = [
if (!SettingsService.getProperty("appLockEnabled")) {
if (
!SettingsService.getProperty("appLockHasPasswordSecurity") &&
(await BiometricService.isBiometryAvailable())
(await BiometicService.isBiometryAvailable())
) {
SettingsService.setProperty("biometricsAuthEnabled", true);
}
if (
!(await BiometricService.isBiometryAvailable()) &&
!(await BiometicService.isBiometryAvailable()) &&
!SettingsService.getProperty("appLockHasPasswordSecurity")
) {
ToastManager.show({

View File

@@ -41,7 +41,7 @@ import {
TouchableOpacity,
View
} from "react-native";
import { DatabaseLogger, db } from "../../common/database";
import { db } from "../../common/database";
import SheetProvider from "../../components/sheet-provider";
import { Button } from "../../components/ui/button";
import Input from "../../components/ui/input";
@@ -326,10 +326,11 @@ function ThemeSelector() {
return page.themes;
})
.flat()
.filter((theme) =>
searchQuery && searchQuery !== ""
? true
: darkTheme.id !== theme.id && lightTheme.id !== theme.id
.filter(
(theme) =>
(!searchQuery || searchQuery === "") &&
darkTheme.id !== theme.id &&
lightTheme.id !== theme.id
) || []
);
}
@@ -545,7 +546,7 @@ const ThemeSetter = ({
context: "global"
});
} catch (e) {
DatabaseLogger.error(e);
console.log("Error", e);
}
setTimeout(() => {

View File

@@ -151,14 +151,13 @@ async function updateNextBackupTime() {
/**
* @param {boolean=} progress
* @param {string=} context
* @returns {Promise<{path?: string, error?: Error, report?: boolean}}>
* @returns {Promise<{path?: string, error?: Error}}>
*/
async function run(progress = false, context) {
let androidBackupDirectory = await checkBackupDirExists(false, context);
if (!androidBackupDirectory)
return {
error: new Error("Backup directory not selected"),
report: false
error: new Error("Backup directory not selected")
};
if (progress) {
@@ -192,10 +191,9 @@ async function run(progress = false, context) {
await RNFetchBlob.fs.mkdir(zipSourceFolder);
try {
const user = await db.user.getUser();
for await (const file of db.backup.export(
"mobile",
SettingsService.get().encryptedBackup && user
SettingsService.get().encryptedBackup
)) {
console.log("Writing backup chunk of size...", file?.data?.length);
await RNFetchBlob.fs.writeFile(
@@ -259,12 +257,11 @@ async function run(progress = false, context) {
return run(progress, context);
}
DatabaseLogger.error(e);
DatabaseLogger.error(e, "Backup failed");
await sleep(300);
progress && eSendEvent(eCloseSheet);
return {
error: e,
report: true
error: e
};
}
}

View File

@@ -179,7 +179,7 @@ async function validateUser(title: string, description?: string) {
}
}
const BiometricService = {
const BiometicService = {
isBiometryAvailable,
enableFingerprintAuth,
isFingerprintAuthEnabled,
@@ -190,4 +190,4 @@ const BiometricService = {
validateUser
};
export default BiometricService;
export default BiometicService;

View File

@@ -40,7 +40,7 @@ import { basename, dirname, join } from "pathe";
import downloadAttachment from "../common/filesystem/download-attachment";
import { presentDialog } from "../components/dialog/functions";
import { useSettingStore } from "../stores/use-setting-store";
import BiometricService from "./biometrics";
import BiometicService from "./biometrics";
import { ToastManager } from "./event-manager";
import { cacheDir } from "../common/filesystem/utils";
@@ -186,10 +186,10 @@ async function exportAs(
}
async function unlockVault() {
const biometry = await BiometricService.isBiometryAvailable();
const fingerprint = await BiometricService.hasInternetCredentials();
const biometry = await BiometicService.isBiometryAvailable();
const fingerprint = await BiometicService.hasInternetCredentials();
if (biometry && fingerprint) {
const credentials = await BiometricService.getCredentials(
const credentials = await BiometicService.getCredentials(
"Unlock vault",
"Unlock vault to export locked notes"
);

View File

@@ -49,8 +49,6 @@ import { DDS } from "./device-detection";
import { eSendEvent } from "./event-manager";
import Navigation from "./navigation";
import SettingsService from "./settings";
import { useUserStore } from "../stores/use-user-store";
import { useTabStore } from "../screens/editor/tiptap/use-tab-store";
let pinned: DisplayedNotification[] = [];
@@ -141,7 +139,6 @@ const onEvent = async ({ type, detail }: Event) => {
}
editorState().movedAway = false;
const noteId = notification?.id;
console.log("NOTE ID", noteId);
loadNote(noteId as string, true);
}
@@ -410,31 +407,18 @@ async function loadNote(id: string, jump: boolean) {
if (!DDS.isTab && jump) {
tabBarRef.current?.goToPage(1);
}
MMKV.setString(
"appState",
JSON.stringify({
editing: true,
movedAway: false,
timestamp: Date.now()
})
eSendEvent("loadingNote", note);
setTimeout(
() => {
eSendEvent(eOnLoadNote, {
item: note
});
if (!jump && !DDS.isTab) {
tabBarRef.current?.goToPage(1);
}
},
tabBarRef?.current ? 0 : 2000
);
const isLocked = await db.vaults.itemExists({
type: "note",
id: id
});
const tab = useTabStore.getState().getTabForNote(id);
if (tab !== undefined) {
useTabStore.getState().focusTab(tab);
} else {
useTabStore.getState().focusPreviewTab(id, {
noteId: id,
readonly: note.readonly,
noteLocked: isLocked
});
}
}
async function getChannelId(id: "silent" | "vibrate" | "urgent" | "default") {
@@ -492,15 +476,7 @@ async function displayNotification({
reply_button_text?: string;
id?: string;
}) {
useUserStore.setState({
disableAppLockRequests: true
});
const permission = await checkAndRequestPermissions();
useUserStore.setState({
disableAppLockRequests: false
});
if (!permission) return;
if (!(await checkAndRequestPermissions())) return;
try {
await notifee.displayNotification({
@@ -870,16 +846,7 @@ async function remove(id: string) {
}
async function pinQuickNote(launch: boolean) {
useUserStore.setState({
disableAppLockRequests: true
});
const permission = await checkAndRequestPermissions();
useUserStore.setState({
disableAppLockRequests: false
});
if (!permission) {
return;
}
if (!(await checkAndRequestPermissions())) return;
get().then((items) => {
const notification = items.filter((n) => n.id === "notesnook_note_input");
if (notification && launch) {

View File

@@ -72,7 +72,7 @@ export const useMenuStore = create<MenuStore>((set, get) => ({
section as SideBarSection
);
hiddenItems[section as SideBarHideableSection] =
db.settings.getSideBarHiddenItems(section as SideBarHideableSection);
db.settings.getSideBarHiddenItems("colors");
}
if (
@@ -80,7 +80,6 @@ export const useMenuStore = create<MenuStore>((set, get) => ({
JSON.stringify(get().hiddenItems || {}) !==
JSON.stringify(hiddenItems || {})
) {
console.log(order, hiddenItems);
set({
order: order,
hiddenItems: hiddenItems

View File

@@ -173,4 +173,3 @@ export const eUnlockWithBiometrics = "618";
export const eUnlockWithPassword = "619";
export const eUpdateNoteInEditor = "620";
export const eOnEnterEditor = "621";
export const eOnExitEditor = "622";

View File

@@ -111,7 +111,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled true
versionCode 3010
versionCode 3008
versionName getNpmVersion()
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'

View File

@@ -1,3 +1,3 @@
- Bug fixes and performance improvements
Notesnook V3 is here, and it's packed with new features and improvements such as bi-directional note linking, better syncing, at-rest encryption, nested notebooks, editor tabs, better app lock and so much more.
Thank you for using Notesnook!
Check out our blogpost to learn about what's new at https://blog.notesnook.com/introducing-notesnook-v3.

View File

@@ -21,9 +21,9 @@
6517B7C32B6838EB0079FF37 /* OpenSans-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6517B7C02B6838EB0079FF37 /* OpenSans-Bold.ttf */; };
6529A13E279BC4C70048D4A8 /* BootSplash.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6529A13D279BC4C70048D4A8 /* BootSplash.storyboard */; };
656835812BB29A9800144BAB /* OpenSans-Italic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 656835802BB29A8300144BAB /* OpenSans-Italic.ttf */; };
656DD2AB2B1891DF00A362EA /* BuildFile in Resources */ = {isa = PBXBuildFile; };
656DD2AC2B1891DF00A362EA /* BuildFile in Resources */ = {isa = PBXBuildFile; };
656DD2AD2B1891DF00A362EA /* BuildFile in Resources */ = {isa = PBXBuildFile; };
656DD2AB2B1891DF00A362EA /* (null) in Resources */ = {isa = PBXBuildFile; };
656DD2AC2B1891DF00A362EA /* (null) in Resources */ = {isa = PBXBuildFile; };
656DD2AD2B1891DF00A362EA /* (null) in Resources */ = {isa = PBXBuildFile; };
6593E4A3281C345400492C50 /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6593E4A2281C345400492C50 /* AppDelegate.mm */; };
659BE46725E11A5100E05671 /* notesnook-text.png in Resources */ = {isa = PBXBuildFile; fileRef = 659BE46625E11A5100E05671 /* notesnook-text.png */; };
65AA857925E6DDEC00772A01 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65AA857825E6DDEC00772A01 /* WidgetKit.framework */; };
@@ -586,9 +586,9 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
656DD2AB2B1891DF00A362EA /* BuildFile in Resources */,
656DD2AC2B1891DF00A362EA /* BuildFile in Resources */,
656DD2AD2B1891DF00A362EA /* BuildFile in Resources */,
656DD2AB2B1891DF00A362EA /* (null) in Resources */,
656DD2AC2B1891DF00A362EA /* (null) in Resources */,
656DD2AD2B1891DF00A362EA /* (null) in Resources */,
65C400DF2A80B6B600AA3DF5 /* MaterialCommunityIcons.ttf in Resources */,
65C149872A61151B005C40F1 /* extension.bundle in Resources */,
65B5014725A672B200E2D264 /* MainInterface.storyboard in Resources */,
@@ -1015,7 +1015,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2099;
CURRENT_PROJECT_VERSION = 2097;
DEVELOPMENT_TEAM = 53CWBG3QUC;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1089,7 +1089,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.0.2;
MARKETING_VERSION = 3.0.0;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -1120,7 +1120,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2099;
CURRENT_PROJECT_VERSION = 2097;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1194,7 +1194,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.0.2;
MARKETING_VERSION = 3.0.0;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = (
"$(inherited)",
@@ -1353,7 +1353,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2099;
CURRENT_PROJECT_VERSION = 2097;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1365,7 +1365,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.0.2;
MARKETING_VERSION = 3.0.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
@@ -1396,7 +1396,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2099;
CURRENT_PROJECT_VERSION = 2097;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1409,7 +1409,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.0.2;
MARKETING_VERSION = 3.0.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1439,7 +1439,7 @@
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2099;
CURRENT_PROJECT_VERSION = 2097;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1513,7 +1513,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.0.2;
MARKETING_VERSION = 3.0.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
@@ -1544,7 +1544,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2099;
CURRENT_PROJECT_VERSION = 2097;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1619,7 +1619,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.0.2;
MARKETING_VERSION = 3.0.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -319,6 +319,8 @@ PODS:
- React-Core
- react-native-html-to-pdf-lite (0.9.1):
- React
- react-native-image-picker (4.1.2):
- React-Core
- react-native-image-resizer (3.0.5):
- React-Core
- react-native-in-app-review (4.3.3):
@@ -343,7 +345,7 @@ PODS:
- React-Core
- react-native-safe-area-context (4.9.0):
- React-Core
- react-native-share-extension (3.0.0):
- react-native-share-extension (2.5.6):
- React
- react-native-sodium (1.5.4):
- React
@@ -595,6 +597,7 @@ DEPENDENCIES:
- react-native-get-random-values (from `../../node_modules/react-native-get-random-values`)
- react-native-gzip (from `../../node_modules/react-native-gzip`)
- react-native-html-to-pdf-lite (from `../../node_modules/react-native-html-to-pdf-lite`)
- react-native-image-picker (from `../../node_modules/react-native-image-picker`)
- "react-native-image-resizer (from `../../node_modules/@bam.tech/react-native-image-resizer`)"
- react-native-in-app-review (from `../../node_modules/react-native-in-app-review`)
- "react-native-keep-awake (from `../../node_modules/@sayem314/react-native-keep-awake`)"
@@ -733,6 +736,8 @@ EXTERNAL SOURCES:
:path: "../../node_modules/react-native-gzip"
react-native-html-to-pdf-lite:
:path: "../../node_modules/react-native-html-to-pdf-lite"
react-native-image-picker:
:path: "../../node_modules/react-native-image-picker"
react-native-image-resizer:
:path: "../../node_modules/@bam.tech/react-native-image-resizer"
react-native-in-app-review:
@@ -894,6 +899,7 @@ SPEC CHECKSUMS:
react-native-get-random-values: dee677497c6a740b71e5612e8dbd83e7539ed5bb
react-native-gzip: c5e87ee9e359f02350e3a2ee52eb35eddc398868
react-native-html-to-pdf-lite: 21bfb169bf4cbcd7bec9f736975ee1b3f5292d4a
react-native-image-picker: 9c8a2687b69300ad9e95cec5d38f35ab9d32467d
react-native-image-resizer: 00ceb0e05586c7aadf061eea676957a6c2ec60fa
react-native-in-app-review: db8bb167a5f238e7ceca5c242d6b36ce8c4404a4
react-native-keep-awake: caee3ff89eaa21dfe29010f0d143566874a04441
@@ -904,7 +910,7 @@ SPEC CHECKSUMS:
react-native-pdf: 33c622cbdf776a649929e8b9d1ce2d313347c4fa
react-native-quick-sqlite: e0e23b749382a85e4b57146f753de737a6c3a9e1
react-native-safe-area-context: b97eb6f9e3b7f437806c2ce5983f479f8eb5de4b
react-native-share-extension: faed334b1ddf165f1e576fcabd3dc1c9e748bfa9
react-native-share-extension: df66a2ee48a62277d79898375e2142bde0782063
react-native-sodium: 955bb0dc3ea05f8ea06d5e96cb89d1be7b5d7681
react-native-theme-switch-animation: 220f883f7be290e79f2ab022093ed1a7a5929e6d
react-native-webview: 9f111dfbcfc826084d6c507f569e5e03342ee1c1
@@ -958,4 +964,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 2b8b28a341b202bf3ca5f231b75bb05893486ed8
COCOAPODS: 1.14.2
COCOAPODS: 1.12.1

File diff suppressed because one or more lines are too long

View File

@@ -6,7 +6,7 @@
"license": "GPL-3.0-or-later",
"dependencies": {
"@ammarahmed/notifee-react-native": "7.4.7",
"@ammarahmed/react-native-share-extension": "^3.0.0",
"@ammarahmed/react-native-share-extension": "^2.5.9",
"@ammarahmed/react-native-sodium": "1.5.4",
"@bam.tech/react-native-image-resizer": "3.0.5",
"@callstack/repack": "^3.2.0",
@@ -41,6 +41,7 @@
"react-native-gzip": "1.1.0",
"react-native-html-to-pdf-lite": "^0.9.1",
"react-native-iap": "12.11.0",
"react-native-image-picker": "4.1.2",
"react-native-in-app-review": "4.3.3",
"react-native-keychain": "4.0.5",
"react-native-mmkv-storage": "^0.10.0-alpha.12",

View File

@@ -7162,7 +7162,7 @@
},
"../../packages/editor-mobile/node_modules/@types/prop-types": {
"version": "15.7.11",
"devOptional": true,
"dev": true,
"license": "MIT"
},
"../../packages/editor-mobile/node_modules/@types/q": {
@@ -7182,7 +7182,7 @@
},
"../../packages/editor-mobile/node_modules/@types/react": {
"version": "18.2.39",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
@@ -7213,7 +7213,7 @@
},
"../../packages/editor-mobile/node_modules/@types/scheduler": {
"version": "0.16.8",
"devOptional": true,
"dev": true,
"license": "MIT"
},
"../../packages/editor-mobile/node_modules/@types/semver": {
@@ -12071,7 +12071,7 @@
},
"../../packages/editor-mobile/node_modules/immer": {
"version": "9.0.21",
"devOptional": true,
"dev": true,
"license": "MIT",
"funding": {
"type": "opencollective",
@@ -22460,6 +22460,7 @@
},
"../../packages/editor/node_modules/js-tokens": {
"version": "4.0.0",
"dev": true,
"license": "MIT"
},
"../../packages/editor/node_modules/json-parse-even-better-errors": {
@@ -22515,6 +22516,7 @@
},
"../../packages/editor/node_modules/loose-envify": {
"version": "1.4.0",
"dev": true,
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
@@ -23036,6 +23038,7 @@
},
"../../packages/editor/node_modules/react": {
"version": "18.2.0",
"dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
@@ -23054,6 +23057,7 @@
},
"../../packages/editor/node_modules/react-dom": {
"version": "18.2.0",
"dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0",
@@ -23173,6 +23177,7 @@
},
"../../packages/editor/node_modules/scheduler": {
"version": "0.23.0",
"dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
@@ -28438,7 +28443,7 @@
"@ammarahmed/notifee-react-native": "7.4.7",
"@ammarahmed/react-native-background-fetch": "^4.2.2",
"@ammarahmed/react-native-eventsource": "1.1.0",
"@ammarahmed/react-native-share-extension": "^3.0.0",
"@ammarahmed/react-native-share-extension": "^2.5.9",
"@ammarahmed/react-native-sodium": "1.5.4",
"@bam.tech/react-native-image-resizer": "3.0.5",
"@callstack/repack": "^3.2.0",
@@ -28473,6 +28478,7 @@
"react-native-html-to-pdf-lite": "^0.9.1",
"react-native-iap": "12.11.0",
"react-native-image-crop-picker": "^0.40.2",
"react-native-image-picker": "4.1.2",
"react-native-in-app-review": "4.3.3",
"react-native-keychain": "4.0.5",
"react-native-mmkv-storage": "^0.10.0-alpha.12",
@@ -28590,9 +28596,9 @@
}
},
"node_modules/@ammarahmed/react-native-share-extension": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-share-extension/-/react-native-share-extension-3.0.0.tgz",
"integrity": "sha512-pXf/8Zt46yPfJZ4FMlhI+Bgh/yHllNPiOeb9rqTwGJLuWYA7E6QFNuZ4n4smK7rJZrmhOYV12gFUzFtodgC/8w==",
"version": "2.5.9",
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-share-extension/-/react-native-share-extension-2.5.9.tgz",
"integrity": "sha512-IEtlh/YCWggcse88yqSX5M+rTyEhu5xHTcje30QbVKtJwtWofbqjzL7UO4EIXKpyFh5u4/CDaY5Yy9kQS1yTjA==",
"dependencies": {
"react-native": "^0.63.1"
}
@@ -30890,6 +30896,7 @@
},
"node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
@@ -30903,6 +30910,7 @@
},
"node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -31049,6 +31057,7 @@
},
"node_modules/@babel/plugin-proposal-private-property-in-object": {
"version": "7.21.0-placeholder-for-preset-env.2",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -31059,6 +31068,7 @@
},
"node_modules/@babel/plugin-proposal-unicode-property-regex": {
"version": "7.18.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.18.6",
@@ -31104,6 +31114,7 @@
},
"node_modules/@babel/plugin-syntax-class-static-block": {
"version": "7.14.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.14.5"
@@ -31140,6 +31151,7 @@
},
"node_modules/@babel/plugin-syntax-export-namespace-from": {
"version": "7.8.3",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.8.3"
@@ -31163,6 +31175,7 @@
},
"node_modules/@babel/plugin-syntax-import-assertions": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
@@ -31176,6 +31189,7 @@
},
"node_modules/@babel/plugin-syntax-import-attributes": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
@@ -31189,6 +31203,7 @@
},
"node_modules/@babel/plugin-syntax-import-meta": {
"version": "7.10.4",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.10.4"
@@ -31199,6 +31214,7 @@
},
"node_modules/@babel/plugin-syntax-json-strings": {
"version": "7.8.3",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.8.0"
@@ -31222,6 +31238,7 @@
},
"node_modules/@babel/plugin-syntax-logical-assignment-operators": {
"version": "7.10.4",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.10.4"
@@ -31282,6 +31299,7 @@
},
"node_modules/@babel/plugin-syntax-private-property-in-object": {
"version": "7.14.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.14.5"
@@ -31295,6 +31313,7 @@
},
"node_modules/@babel/plugin-syntax-top-level-await": {
"version": "7.14.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.14.5"
@@ -31321,6 +31340,7 @@
},
"node_modules/@babel/plugin-syntax-unicode-sets-regex": {
"version": "7.18.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.18.6",
@@ -31348,6 +31368,7 @@
},
"node_modules/@babel/plugin-transform-async-generator-functions": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-environment-visitor": "^7.22.5",
@@ -31405,6 +31426,7 @@
},
"node_modules/@babel/plugin-transform-class-properties": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.22.5",
@@ -31419,6 +31441,7 @@
},
"node_modules/@babel/plugin-transform-class-static-block": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.22.5",
@@ -31482,6 +31505,7 @@
},
"node_modules/@babel/plugin-transform-dotall-regex": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.22.5",
@@ -31496,6 +31520,7 @@
},
"node_modules/@babel/plugin-transform-duplicate-keys": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
@@ -31509,6 +31534,7 @@
},
"node_modules/@babel/plugin-transform-dynamic-import": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -31537,6 +31563,7 @@
},
"node_modules/@babel/plugin-transform-export-namespace-from": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -31593,6 +31620,7 @@
},
"node_modules/@babel/plugin-transform-json-strings": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -31620,6 +31648,7 @@
},
"node_modules/@babel/plugin-transform-logical-assignment-operators": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -31647,6 +31676,7 @@
},
"node_modules/@babel/plugin-transform-modules-amd": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-module-transforms": "^7.22.5",
@@ -31676,6 +31706,7 @@
},
"node_modules/@babel/plugin-transform-modules-systemjs": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-hoist-variables": "^7.22.5",
@@ -31692,6 +31723,7 @@
},
"node_modules/@babel/plugin-transform-modules-umd": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-module-transforms": "^7.22.5",
@@ -31720,6 +31752,7 @@
},
"node_modules/@babel/plugin-transform-new-target": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
@@ -31733,6 +31766,7 @@
},
"node_modules/@babel/plugin-transform-nullish-coalescing-operator": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -31747,6 +31781,7 @@
},
"node_modules/@babel/plugin-transform-numeric-separator": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -31774,6 +31809,7 @@
},
"node_modules/@babel/plugin-transform-object-rest-spread": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^7.22.5",
@@ -31805,6 +31841,7 @@
},
"node_modules/@babel/plugin-transform-optional-catch-binding": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -31819,6 +31856,7 @@
},
"node_modules/@babel/plugin-transform-optional-chaining": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -31847,6 +31885,7 @@
},
"node_modules/@babel/plugin-transform-private-methods": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.22.5",
@@ -31861,6 +31900,7 @@
},
"node_modules/@babel/plugin-transform-private-property-in-object": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.22.5",
@@ -31960,6 +32000,7 @@
},
"node_modules/@babel/plugin-transform-reserved-words": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
@@ -32051,6 +32092,7 @@
},
"node_modules/@babel/plugin-transform-typeof-symbol": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
@@ -32080,6 +32122,7 @@
},
"node_modules/@babel/plugin-transform-unicode-escapes": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
@@ -32093,6 +32136,7 @@
},
"node_modules/@babel/plugin-transform-unicode-property-regex": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.22.5",
@@ -32121,6 +32165,7 @@
},
"node_modules/@babel/plugin-transform-unicode-sets-regex": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.22.5",
@@ -32135,6 +32180,7 @@
},
"node_modules/@babel/preset-env": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^7.22.5",
@@ -32227,6 +32273,7 @@
},
"node_modules/@babel/preset-env/node_modules/semver": {
"version": "6.3.0",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -32249,6 +32296,7 @@
},
"node_modules/@babel/preset-modules": {
"version": "0.1.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.0.0",
@@ -34898,6 +34946,7 @@
},
"node_modules/@types/eslint": {
"version": "8.40.2",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "*",
@@ -34906,6 +34955,7 @@
},
"node_modules/@types/eslint-scope": {
"version": "3.7.4",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/eslint": "*",
@@ -34914,6 +34964,7 @@
},
"node_modules/@types/estree": {
"version": "1.0.1",
"dev": true,
"license": "MIT"
},
"node_modules/@types/graceful-fs": {
@@ -35362,6 +35413,7 @@
},
"node_modules/@webassemblyjs/ast": {
"version": "1.11.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@webassemblyjs/helper-numbers": "1.11.6",
@@ -35370,18 +35422,22 @@
},
"node_modules/@webassemblyjs/floating-point-hex-parser": {
"version": "1.11.6",
"dev": true,
"license": "MIT"
},
"node_modules/@webassemblyjs/helper-api-error": {
"version": "1.11.6",
"dev": true,
"license": "MIT"
},
"node_modules/@webassemblyjs/helper-buffer": {
"version": "1.11.6",
"dev": true,
"license": "MIT"
},
"node_modules/@webassemblyjs/helper-numbers": {
"version": "1.11.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@webassemblyjs/floating-point-hex-parser": "1.11.6",
@@ -35391,10 +35447,12 @@
},
"node_modules/@webassemblyjs/helper-wasm-bytecode": {
"version": "1.11.6",
"dev": true,
"license": "MIT"
},
"node_modules/@webassemblyjs/helper-wasm-section": {
"version": "1.11.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
@@ -35405,6 +35463,7 @@
},
"node_modules/@webassemblyjs/ieee754": {
"version": "1.11.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@xtuc/ieee754": "^1.2.0"
@@ -35412,6 +35471,7 @@
},
"node_modules/@webassemblyjs/leb128": {
"version": "1.11.6",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@xtuc/long": "4.2.2"
@@ -35419,10 +35479,12 @@
},
"node_modules/@webassemblyjs/utf8": {
"version": "1.11.6",
"dev": true,
"license": "MIT"
},
"node_modules/@webassemblyjs/wasm-edit": {
"version": "1.11.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
@@ -35437,6 +35499,7 @@
},
"node_modules/@webassemblyjs/wasm-gen": {
"version": "1.11.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
@@ -35448,6 +35511,7 @@
},
"node_modules/@webassemblyjs/wasm-opt": {
"version": "1.11.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
@@ -35458,6 +35522,7 @@
},
"node_modules/@webassemblyjs/wasm-parser": {
"version": "1.11.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
@@ -35470,6 +35535,7 @@
},
"node_modules/@webassemblyjs/wast-printer": {
"version": "1.11.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
@@ -35527,10 +35593,12 @@
},
"node_modules/@xtuc/ieee754": {
"version": "1.2.0",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/@xtuc/long": {
"version": "4.2.2",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/@yarnpkg/lockfile": {
@@ -35586,6 +35654,7 @@
},
"node_modules/acorn-import-assertions": {
"version": "1.9.0",
"dev": true,
"license": "MIT",
"peerDependencies": {
"acorn": "^8"
@@ -36690,6 +36759,7 @@
},
"node_modules/chrome-trace-event": {
"version": "1.0.3",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0"
@@ -37790,6 +37860,7 @@
},
"node_modules/enhanced-resolve": {
"version": "5.15.0",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.4",
@@ -37902,6 +37973,7 @@
},
"node_modules/es-module-lexer": {
"version": "1.3.0",
"dev": true,
"license": "MIT"
},
"node_modules/es-set-tostringtag": {
@@ -38232,6 +38304,7 @@
},
"node_modules/eslint-scope": {
"version": "5.1.1",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"esrecurse": "^4.3.0",
@@ -38243,6 +38316,7 @@
},
"node_modules/eslint-scope/node_modules/estraverse": {
"version": "4.3.0",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=4.0"
@@ -38428,6 +38502,7 @@
},
"node_modules/esrecurse": {
"version": "4.3.0",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"estraverse": "^5.2.0"
@@ -38438,6 +38513,7 @@
},
"node_modules/estraverse": {
"version": "5.3.0",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=4.0"
@@ -38445,6 +38521,7 @@
},
"node_modules/esutils": {
"version": "2.0.3",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.10.0"
@@ -39501,6 +39578,7 @@
},
"node_modules/glob-to-regexp": {
"version": "0.4.1",
"dev": true,
"license": "BSD-2-Clause"
},
"node_modules/global": {
@@ -41744,6 +41822,7 @@
},
"node_modules/json-parse-even-better-errors": {
"version": "2.3.1",
"dev": true,
"license": "MIT"
},
"node_modules/json-schema-traverse": {
@@ -42011,6 +42090,7 @@
},
"node_modules/loader-runner": {
"version": "4.3.0",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.11.5"
@@ -44662,6 +44742,7 @@
},
"node_modules/randombytes": {
"version": "2.1.0",
"dev": true,
"license": "MIT",
"dependencies": {
"safe-buffer": "^5.1.0"
@@ -45060,6 +45141,14 @@
"react-native": "*"
}
},
"node_modules/react-native-image-picker": {
"version": "4.1.2",
"license": "MIT",
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-image-zoom-viewer": {
"version": "3.0.1",
"license": "MIT",
@@ -45432,6 +45521,7 @@
},
"node_modules/react-test-renderer": {
"version": "18.2.0",
"dev": true,
"license": "MIT",
"dependencies": {
"react-is": "^18.2.0",
@@ -45444,10 +45534,12 @@
},
"node_modules/react-test-renderer/node_modules/react-is": {
"version": "18.2.0",
"dev": true,
"license": "MIT"
},
"node_modules/react-test-renderer/node_modules/scheduler": {
"version": "0.23.0",
"dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
@@ -46283,6 +46375,7 @@
},
"node_modules/serialize-javascript": {
"version": "6.0.1",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"randombytes": "^2.1.0"
@@ -47338,6 +47431,7 @@
},
"node_modules/terser-webpack-plugin": {
"version": "5.3.9",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.17",
@@ -47370,6 +47464,7 @@
},
"node_modules/terser-webpack-plugin/node_modules/jest-worker": {
"version": "27.5.1",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*",
@@ -47382,6 +47477,7 @@
},
"node_modules/terser-webpack-plugin/node_modules/supports-color": {
"version": "8.1.1",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
@@ -48126,6 +48222,7 @@
},
"node_modules/watchpack": {
"version": "2.4.0",
"dev": true,
"license": "MIT",
"dependencies": {
"glob-to-regexp": "^0.4.1",
@@ -48148,6 +48245,7 @@
},
"node_modules/webpack": {
"version": "5.88.2",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/eslint-scope": "^3.7.3",
@@ -48262,6 +48360,7 @@
},
"node_modules/webpack-sources": {
"version": "3.2.3",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10.13.0"

View File

@@ -1,6 +1,6 @@
{
"name": "@notesnook/mobile",
"version": "3.0.2",
"version": "3.0.0",
"private": true,
"license": "GPL-3.0-or-later",
"workspaces": [

View File

@@ -33,7 +33,6 @@ import {
SafeAreaView,
ScrollView,
Text,
TextInput,
TouchableOpacity,
View,
useWindowDimensions
@@ -116,14 +115,12 @@ const ShareView = () => {
const appendNoteId = useShareStore((state) => state.appendNote);
const [note, setNote] = useState({ ...defaultNote });
const noteContent = useRef("");
const noteTitle = useRef("");
const [loading, setLoading] = useState(false);
const [loadingExtension, setLoadingExtension] = useState(true);
const [rawData, setRawData] = useState({
type: null,
value: null
});
const inputRef = useRef(null);
const [mode, setMode] = useState(1);
const keyboardHeight = useRef(0);
const { width, height } = useWindowDimensions();
@@ -195,7 +192,6 @@ const ShareView = () => {
}
return;
}
let note = { ...defaultNote };
for (let item of data) {
if (item.type === "text") {
@@ -211,14 +207,7 @@ const ShareView = () => {
if (!key) continue;
if (key.includes("TITLE") || key.includes("SUBJECT")) {
note.title = item[key];
noteTitle.current = note.title;
inputRef.current?.setNativeProps?.({
text: noteTitle.current
});
}
if (key.includes("TEXT") && !note.content.data) {
note.content.data = item[key];
noteContent.current = item[key];
console.log("Note title will be", note.title);
}
}
} else {
@@ -227,9 +216,8 @@ const ShareView = () => {
if (
(isImage(item.type) && item.size > IMAGE_SIZE_LIMIT) ||
(!isImage(item.type) && item.size > FILE_SIZE_LIMIT)
) {
)
continue;
}
setRawFiles((files) => {
const index = files.findIndex(
@@ -256,7 +244,6 @@ const ShareView = () => {
);
const onLoad = useCallback(() => {
console.log(noteContent.current, "current...");
eSendEvent(eOnLoadNote + "shareEditor", {
id: null,
content: {
@@ -288,7 +275,7 @@ const ShareView = () => {
const onPress = async () => {
setLoading(true);
if (!noteContent.current && rawFiles.length === 0 && !noteTitle.current) {
if (!noteContent.current && rawFiles.length === 0) {
setLoading(false);
return;
}
@@ -311,14 +298,12 @@ const ShareView = () => {
type: "tiptap"
},
id: note.id,
sessionId: Date.now(),
title: noteTitle.current
sessionId: Date.now()
};
} else {
noteData = { ...note };
noteData.content.data = noteContent.current;
noteData.sessionId = Date.now();
noteData.title = noteTitle.current;
}
try {
@@ -483,29 +468,10 @@ const ShareView = () => {
borderBottomWidth: 1,
paddingBottom: 12,
borderBottomColor: colors.secondary.background,
paddingHorizontal: 12,
gap: 10
paddingHorizontal: 12
}}
>
<TextInput
placeholder="Enter note title"
ref={inputRef}
style={{
flexShrink: 1,
flexGrow: 1,
fontFamily: "OpenSans-SemiBold",
fontSize: SIZE.lg,
paddingBottom: 0,
paddingTop: 0
}}
onChangeText={(value) => {
noteTitle.current = value;
}}
blurOnSubmit={false}
onSubmitEditing={() => {
editorRef.current.focus();
}}
/>
<Heading size={SIZE.lg}>Save to Notesnook</Heading>
<Button
title="Done"
type="accent"
@@ -660,15 +626,13 @@ const ShareView = () => {
}}
>
{!loadingExtension && !loadingPage ? (
<>
<Editor
editorRef={editorRef}
onLoad={onLoadEditor}
onChange={(html) => {
noteContent.current = html;
}}
/>
</>
<Editor
editorRef={editorRef}
onLoad={onLoadEditor}
onChange={(html) => {
noteContent.current = html;
}}
/>
) : (
<>
{loadingPage ? (
@@ -682,19 +646,7 @@ const ShareView = () => {
</SafeAreaProvider>
</View>
{appendNoteId ? (
<AppendNote
id={appendNoteId}
onLoad={(title) => {
if (!noteTitle.current) {
noteTitle.current = title;
inputRef.current?.setNativeProps?.({
text: noteTitle.current
});
}
}}
/>
) : null}
{appendNoteId ? <AppendNote id={appendNoteId} /> : null}
<View
style={{
@@ -814,16 +766,10 @@ const ShareView = () => {
);
};
const AppendNote = ({ id, onLoad }) => {
const AppendNote = ({ id }) => {
const { colors } = useThemeColors();
const [item] = useDBItem(id, "note");
useEffect(() => {
if (item?.title) {
onLoad?.(item.title);
}
}, [item?.title, onLoad]);
return !item ? null : (
<Paragraph
size={SIZE.xs}

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/web",
"version": "3.0.2",
"version": "3.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/web",
"version": "3.0.2",
"version": "3.0.0",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {

View File

@@ -1,7 +1,7 @@
{
"name": "@notesnook/web",
"description": "Your private note taking space",
"version": "3.0.2",
"version": "3.0.0",
"private": true,
"main": "./src/app.js",
"homepage": "https://notesnook.com/",

View File

@@ -142,13 +142,8 @@ function DesktopAppContents({
}, [show]);
useEffect(() => {
if (isFocusMode) {
const middlePaneSize = middlePane.current?.getSize() || 20;
navPane.current?.collapse();
// the middle pane has to be resized because collapsing the nav
// pane increases the middle pane's size every time.
middlePane.current?.resize(middlePaneSize);
} else navPane.current?.expand();
if (isFocusMode) navPane.current?.collapse();
else navPane.current?.expand();
}, [isFocusMode]);
return (
@@ -164,10 +159,9 @@ function DesktopAppContents({
ref={navPane}
className="nav-pane"
defaultSize={10}
minSize={3.5}
onResize={(size) => setIsNarrow(size <= 5)}
minSize={3}
onResize={(size) => setIsNarrow(size <= 3)}
collapsible
collapsedSize={3.5}
>
<NavigationMenu
toggleNavigationContainer={(state) => {
@@ -198,7 +192,7 @@ function DesktopAppContents({
</ScopedThemeProvider>
</Panel>
<PanelResizeHandle className="panel-resize-handle" />
<Panel className="editor-pane" defaultSize={70}>
<Panel className="editor-pane">
<Flex
sx={{
display: "flex",

View File

@@ -18,16 +18,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { lazify } from "../utils/lazify";
import { logger } from "../utils/logger";
import { showToast } from "../utils/toast";
import { db } from "./db";
async function download(hash: string, groupId?: string) {
const attachment = await db.attachments.attachment(hash);
if (!attachment) {
logger.debug("could not find attachment for download", { hash, groupId });
return;
}
if (!attachment) return;
const downloadResult = await db
.fs()
.downloadFile(
@@ -44,27 +40,19 @@ async function download(hash: string, groupId?: string) {
}
export async function saveAttachment(hash: string) {
try {
const response = await download(hash);
if (!response) return;
const response = await download(hash);
if (!response) return;
const { attachment, key } = response;
await lazify(import("../interfaces/fs"), ({ saveFile }) =>
saveFile(attachment.hash, {
key,
iv: attachment.iv,
name: attachment.filename,
type: attachment.mimeType,
isUploaded: !!attachment.dateUploaded
})
);
} catch (e) {
console.error(e);
showToast(
"error",
`Failed to download attachment: ${hash} (error: ${(e as Error).message})`
);
}
const { attachment, key } = response;
await lazify(import("../interfaces/fs"), ({ saveFile }) =>
saveFile(attachment.hash, {
key,
iv: attachment.iv,
name: attachment.filename,
type: attachment.mimeType,
isUploaded: !!attachment.dateUploaded
})
);
}
type OutputTypeToReturnType = {
@@ -80,7 +68,6 @@ export async function downloadAttachment<
type: TType,
groupId?: string
): Promise<TOutputType | undefined> {
logger.debug("downloading attachment", { hash, type, groupId });
try {
const response = await download(hash, groupId);
if (!response) return;
@@ -98,7 +85,6 @@ export async function downloadAttachment<
isUploaded: !!attachment.dateUploaded
})
);
logger.debug("Attachment decrypted", { hash });
if (!blob) return;
return blob as TOutputType;
@@ -120,12 +106,9 @@ export async function checkAttachment(hash: string) {
import("../interfaces/fs"),
({ getUploadedFileSize }) => getUploadedFileSize(hash)
);
if (size === 0) throw new Error("File length is 0.");
else if (size === -1) throw new Error("File verification check failed.");
if (size <= 0) return { failed: "File length is 0." };
} catch (e) {
const reason = e instanceof Error ? e.message : "Unknown error.";
await db.attachments.markAsFailed(attachment.id, reason);
return { failed: reason };
return { failed: e instanceof Error ? e.message : "Unknown error." };
}
return { success: true };
}

View File

@@ -77,18 +77,18 @@ export async function introduceFeatures() {
export const DEFAULT_CONTEXT = { colors: [], tags: [], notebook: {} };
export async function createBackup(rescueMode = false) {
export async function createBackup() {
const { isLoggedIn } = useUserStore.getState();
const { encryptBackups, toggleEncryptBackups } = useSettingStore.getState();
if (!isLoggedIn && encryptBackups) toggleEncryptBackups();
const verified = rescueMode || encryptBackups || (await verifyAccount());
const verified = encryptBackups || (await verifyAccount());
if (!verified) {
showToast("error", "Could not create a backup: user verification failed.");
return false;
return;
}
const encryptedBackups = !rescueMode && isLoggedIn && encryptBackups;
const encryptedBackups = isLoggedIn && encryptBackups;
const filename = sanitizeFilename(
`${formatDate(Date.now(), {
@@ -139,9 +139,7 @@ export async function createBackup(rescueMode = false) {
console.error(error);
} else {
showToast("success", `Backup saved at ${filePath}.`);
return true;
}
return false;
}
export async function selectBackupFile() {
@@ -195,10 +193,7 @@ export async function restoreBackupFile(backupFile: File) {
}
entries.push(entry);
}
if (!isValid)
console.warn(
"The backup file does not contain the verification .nnbackup file."
);
if (!isValid) throw new Error("Invalid backup.");
await db.transaction(async () => {
for (const entry of entries) {

View File

@@ -121,7 +121,7 @@ function Header(props: HeaderProps) {
}}
defaultItems={() =>
db.tags.all.limit(10).items(undefined, {
sortBy: "title",
sortBy: "dateCreated",
sortDirection: "desc"
})
}

View File

@@ -488,25 +488,12 @@ export function Editor(props: EditorProps) {
editor?.attachFile(attachment);
}
}}
onGetAttachmentData={async (attachment) => {
logger.debug("Getting attachment data", {
hash: attachment.hash,
type: attachment.type
});
const result = await downloadAttachment(
onGetAttachmentData={(attachment) => {
return downloadAttachment(
attachment.hash,
attachment.type === "web-clip" ? "text" : "base64",
id?.toString()
);
if (!result)
logger.debug("Got no result after downloading attachment", {
hash: attachment.hash,
type: attachment.type
});
return result;
}}
onAttachFiles={async (files) => {
const editor = useEditorManager.getState().getEditor(id)?.editor;

View File

@@ -189,21 +189,17 @@ async function addAttachment(
const exists = await db.attachments.attachment(hash);
if (!forceWrite && exists) {
forceWrite = (await getUploadedFileSize(hash)) === 0;
forceWrite = (await getUploadedFileSize(hash)) <= 0;
}
if (forceWrite || !exists) {
if (forceWrite && exists) {
if (!(await db.fs().deleteFile(hash, false)))
throw new Error("Failed to delete attachment from server.");
await db.attachments.reset(exists.id);
}
const key: SerializedKey = await getEncryptionKey();
const output = await writeEncryptedFile(file, key, hash);
if (!output) throw new Error("Could not encrypt file.");
if (forceWrite && exists) await db.attachments.reset(hash);
await db.attachments.add({
...output,
hash,

View File

@@ -46,7 +46,6 @@ import { Section } from "../properties";
import { scrollIntoViewById } from "@notesnook/editor";
import { Button, Flex, Text } from "@theme-ui/components";
import { useEditorManager } from "./manager";
import { TITLE_BAR_HEIGHT } from "../title-bar";
type TableOfContentsProps = {
sessionId: string;
@@ -101,7 +100,7 @@ function TableOfContents(props: TableOfContentsProps) {
display: "flex",
position: "absolute",
right: 0,
top: TITLE_BAR_HEIGHT,
top: 0,
zIndex: 999,
height: "100%",
width: "300px",

View File

@@ -24,6 +24,7 @@ import "@notesnook/editor/styles/fonts.css";
import {
Toolbar,
useTiptap,
PortalProvider,
Editor,
AttachmentType,
usePermissionHandler,
@@ -40,6 +41,7 @@ import {
import { Box, Flex } from "@theme-ui/components";
import {
PropsWithChildren,
useCallback,
useEffect,
useLayoutEffect,
useMemo,
@@ -59,6 +61,7 @@ import { writeToClipboard } from "../../utils/clipboard";
import { useEditorStore } from "../../stores/editor-store";
import { parseInternalLink } from "@notesnook/core";
import Skeleton from "react-loading-skeleton";
import { showToast } from "../../utils/toast";
export type OnChangeHandler = (
content: () => string,
@@ -399,56 +402,58 @@ function TiptapWrapper(
}, [theme]);
return (
<Flex
ref={containerRef}
sx={{
flex: 1,
flexDirection: "column",
".tiptap.ProseMirror": { pb: 150 }
}}
>
<TipTap
{...props}
onLoad={(editor) => {
props.onLoad?.(editor);
containerRef.current
?.querySelector(".editor-loading-container")
?.remove();
<PortalProvider>
<Flex
ref={containerRef}
sx={{
flex: 1,
flexDirection: "column",
".tiptap.ProseMirror": { pb: 150 }
}}
editorContainer={() => {
if (editorContainerRef.current) return editorContainerRef.current;
const editorContainer = document.createElement("div");
editorContainer.classList.add("selectable");
editorContainer.style.flex = "1";
editorContainer.style.cursor = "text";
editorContainer.style.color =
theme.scopes.editor?.primary?.paragraph ||
theme.scopes.base.primary.paragraph;
editorContainer.style.fontSize = `${editorConfig.fontSize}px`;
editorContainer.style.fontFamily =
getFontById(editorConfig.fontFamily)?.font || "sans-serif";
editorContainerRef.current = editorContainer;
return editorContainer;
}}
fontFamily={editorConfig.fontFamily}
fontSize={editorConfig.fontSize}
/>
{props.children}
<Box className="editor-loading-container">
<Skeleton
enableAnimation={false}
height={22}
style={{ marginTop: 16 }}
count={2}
>
<TipTap
{...props}
onLoad={(editor) => {
props.onLoad?.(editor);
containerRef.current
?.querySelector(".editor-loading-container")
?.remove();
}}
editorContainer={() => {
if (editorContainerRef.current) return editorContainerRef.current;
const editorContainer = document.createElement("div");
editorContainer.classList.add("selectable");
editorContainer.style.flex = "1";
editorContainer.style.cursor = "text";
editorContainer.style.color =
theme.scopes.editor?.primary?.paragraph ||
theme.scopes.base.primary.paragraph;
editorContainer.style.fontSize = `${editorConfig.fontSize}px`;
editorContainer.style.fontFamily =
getFontById(editorConfig.fontFamily)?.font || "sans-serif";
editorContainerRef.current = editorContainer;
return editorContainer;
}}
fontFamily={editorConfig.fontFamily}
fontSize={editorConfig.fontSize}
/>
<Skeleton
enableAnimation={false}
height={22}
width={25}
style={{ marginTop: 16 }}
/>
</Box>
</Flex>
{props.children}
<Box className="editor-loading-container">
<Skeleton
enableAnimation={false}
height={22}
style={{ marginTop: 16 }}
count={2}
/>
<Skeleton
enableAnimation={false}
height={22}
width={25}
style={{ marginTop: 16 }}
/>
</Box>
</Flex>
</PortalProvider>
);
}
export default TiptapWrapper;

View File

@@ -29,8 +29,6 @@ import {
ZoomIn,
ZoomOut
} from "../icons";
import { getPlatform } from "../../utils/platform";
import { TITLE_BAR_HEIGHT } from "../title-bar";
const DEFAULT_ZOOM_STEP = 0.3;
const DEFAULT_LARGE_ZOOM = 4;
@@ -339,12 +337,7 @@ export class Lightbox extends React.Component<LightboxProps> {
borderRadius: "0px 0px 0px 5px",
overflow: "hidden",
alignItems: "center",
justifyContent: "flex-end",
height: IS_DESKTOP_APP ? TITLE_BAR_HEIGHT : "auto",
pr:
IS_DESKTOP_APP && getPlatform() !== "darwin"
? "calc(100vw - env(titlebar-area-width))"
: 0
justifyContent: "flex-end"
}}
>
{tools.map((tool) => (
@@ -356,7 +349,6 @@ export class Lightbox extends React.Component<LightboxProps> {
bg="transparent"
title={tool.title}
sx={{
height: "100%",
borderRadius: 0,
display: [
tool.hideOnMobile ? "none" : "flex",

View File

@@ -106,7 +106,7 @@ function NavigationItem(
<Button
data-test-id={`navigation-item`}
sx={{
px: isTablet ? 1 : 2,
px: 2,
flex: 1,
alignItems: "center",
justifyContent: isTablet ? "center" : "flex-start",
@@ -124,10 +124,7 @@ function NavigationItem(
}}
>
{image ? (
<Image
src={image}
sx={{ borderRadius: 50, size: 20, minWidth: 20, flexShrink: 0 }}
/>
<Image src={image} sx={{ borderRadius: 50, size: 20 }} />
) : Icon ? (
<Icon
size={isTablet ? 16 : 15}

View File

@@ -282,8 +282,7 @@ export default React.memo(Note, function (prevProps, nextProps) {
prevProps.notebooks?.dateEdited === nextProps.notebooks?.dateEdited &&
prevProps.tags?.dateEdited === nextProps.tags?.dateEdited &&
prevProps.reminder?.dateModified === nextProps.reminder?.dateModified &&
prevProps.attachments?.failed === nextProps.attachments?.failed &&
prevProps.attachments?.total === nextProps.attachments?.total &&
prevProps.attachments === nextProps.attachments &&
prevProps.locked === nextProps.locked
);
});

View File

@@ -61,7 +61,6 @@ import {
} from "@notesnook/core";
import { VirtualizedTable } from "../virtualized-table";
import { TextSlice } from "@notesnook/core/dist/utils/content-block";
import { TITLE_BAR_HEIGHT } from "../title-bar";
const tools = [
{ key: "pin", property: "pinned", icon: Pin, label: "Pin" },
@@ -130,7 +129,7 @@ function EditorProperties(props: EditorPropertiesProps) {
sx={{
display: "flex",
position: "absolute",
top: TITLE_BAR_HEIGHT,
top: 0,
right: 0,
zIndex: 999,
height: "100%",

View File

@@ -56,7 +56,6 @@ function Toggle(props: ToggleProps) {
<Switch
sx={{ m: 0, bg: isOn ? "accent" : "icon-secondary" }}
checked={isOn}
onClick={(e) => e.stopPropagation()}
/>
</Flex>
);

View File

@@ -29,7 +29,6 @@ import {
} from "../icons";
import { BaseThemeProvider } from "../theme-provider";
export const TITLE_BAR_HEIGHT = IS_DESKTOP_APP ? 37.8 : 0;
export function TitleBar() {
const { isMaximized, isFullscreen, hasNativeWindowControls } =
useWindowControls();
@@ -66,7 +65,7 @@ export function TitleBar() {
scope="titleBar"
sx={{
background: "background",
height: TITLE_BAR_HEIGHT,
height: 37.8,
display: "flex",
borderBottom: "1px solid var(--border)",
...(!isFullscreen && hasNativeWindowControls

View File

@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useCallback, useEffect, useRef, useState } from "react";
import { useCallback, useEffect, useRef } from "react";
import { Button, Flex, Text } from "@theme-ui/components";
import {
Plus,
@@ -46,8 +46,7 @@ import {
TreeEnvironmentRef
} from "react-complex-tree";
import { FlexScrollContainer } from "../components/scroll-container";
import { pluralize } from "@notesnook/common";
import Field from "../components/field";
import { pluralize, usePromise } from "@notesnook/common";
type MoveDialogProps = { onClose: Perform; noteIds: string[] };
type NotebookReference = {
@@ -74,17 +73,32 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
const setIsMultiselect = useSelectionStore((store) => store.setIsMultiselect);
const isMultiselect = useSelectionStore((store) => store.isMultiselect);
const refreshNotebooks = useStore((store) => store.refresh);
// const notebooks = useStore((store) => store.notebooks);
const reloadItem = useRef<(changedItemIds: TreeItemIndex[]) => void>();
const treeRef = useRef<TreeEnvironmentRef>(null);
const [notebooks, setNotebooks] = useState<string[]>([]);
const rootNotebooks = usePromise(() =>
db.notebooks.roots.ids(db.settings.getGroupOptions("notebooks"))
);
useEffect(() => {
db.notebooks.roots
.ids(db.settings.getGroupOptions("notebooks"))
.then((ids) => setNotebooks(ids));
}, []);
// for (const notebook of notebooks.ids) {
// if (isGroupHeader(notebook)) continue;
// // for (const topic of notebook.topics) {
// // const isSelected =
// // selected.findIndex(
// // (item) => item.id === notebook.id && item.topic === topic.id
// // ) > -1;
// // if (!isSelected && topicHasNotes(topic, noteIds)) {
// // selected.push({
// // id: notebook.id,
// // topic: topic.id,
// // op: "add",
// // new: false
// // });
// // }
// // }
// }
useEffect(() => {
(async function () {
const selected: NotebookReference[] = useSelectionStore
.getState()
@@ -109,6 +123,20 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
setSelected(selected);
setIsMultiselect(selected.length > 1);
})();
// for (const notebook of noteIds
// .map((id) => db.relations.to({ id, type: "note" }, "notebook"))
// .flat()) {
// const isSelected =
// notebook && selected.findIndex((item) => item.id === notebook.id) > -1;
// if (!notebook || isSelected) continue;
// selected.push({
// id: notebook.id,
// op: "add",
// new: false
// });
// }
}, [noteIds, refreshNotebooks, setSelected, setIsMultiselect]);
const _onClose = useCallback(
@@ -127,7 +155,7 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
title={"Select notebooks"}
description={`Use ${
isMac() ? "cmd" : "ctrl"
}+click to select multiple notebooks`}
}+click to select multiple topics`}
onClose={() => _onClose(false)}
width={450}
positiveButton={{
@@ -166,22 +194,6 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
onClick: () => _onClose(false)
}}
>
<Field
autoFocus
sx={{ m: 0, mb: 2 }}
styles={{
input: { p: "7.5px" }
}}
placeholder={"Search notebooks"}
onChange={async (e) => {
const query = e.target.value.trim();
const ids = await (query
? db.lookup.notebooks(query).ids()
: db.notebooks.roots.ids(db.settings.getGroupOptions("notebooks")));
setNotebooks(ids);
reloadItem.current?.(["root"]);
}}
/>
{isMultiselect && (
<Button
variant="anchor"
@@ -198,7 +210,8 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
Reset selection
</Button>
)}
{notebooks.length > 0 ? (
{rootNotebooks.status === "fulfilled" &&
rootNotebooks.value.length > 0 ? (
<FlexScrollContainer>
<UncontrolledTreeEnvironment
ref={treeRef}
@@ -219,7 +232,7 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
isFolder: true,
canMove: false,
canRename: false,
children: notebooks
children: rootNotebooks.value
};
}
@@ -255,7 +268,7 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
isFolder: true,
canMove: false,
canRename: false,
children: notebooks
children: rootNotebooks.value
};
}
@@ -319,9 +332,9 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
sx={{ mt: 2 }}
onClick={() =>
showAddNotebookDialog().then(() =>
db.notebooks.roots
.ids(db.settings.getGroupOptions("notebooks"))
.then((ids) => setNotebooks(ids))
rootNotebooks.status === "fulfilled"
? rootNotebooks.refresh()
: null
)
}
>

View File

@@ -185,16 +185,9 @@ export default function NoteLinkingDialog(props: NoteLinkingDialogProps) {
autoFocus
placeholder="Search for a note to link to..."
sx={{ mx: 0 }}
onChange={async (e) => {
const query = e.target.value.trim();
setNotes(
query
? await db.lookup.notes(e.target.value).sorted()
: await db.notes.all.sorted(
db.settings.getGroupOptions("home")
)
);
}}
onChange={async (e) =>
setNotes(await db.lookup.notes(e.target.value).sorted())
}
/>
{notes && (
<ScrollContainer>

View File

@@ -44,8 +44,7 @@ export const AppearanceSettings: SettingsGroup[] = [
type: "input",
inputType: "number",
min: 0.5,
max: 3.0,
step: 0.1,
max: 2.0,
defaultValue: () => useSettingStore.getState().zoomFactor,
onChange: (value) => useSettingStore.getState().setZoomFactor(value)
}

View File

@@ -46,7 +46,7 @@ export const AuthenticationSettings: SettingsGroup[] = [
title: "Change password",
variant: "secondary",
action: async () => {
if (!(await createBackup())) return;
await createBackup();
const result = await showPasswordDialog({
title: "Change account password",
message: `All your data will be re-encrypted and synced with the new password.

View File

@@ -508,7 +508,6 @@ function SettingItem(props: { item: Setting }) {
type={"number"}
min={component.min}
max={component.max}
step={component.step}
defaultValue={component.defaultValue()}
sx={{ width: 80, mr: 1 }}
onChange={debounce((e) => {

View File

@@ -111,26 +111,6 @@ What data is collected & when?`,
section: "privacy",
header: "Advanced",
settings: [
{
key: "custom-dns",
title: "Use custom DNS",
description: `Notesnook uses the following DNS providers:
1. Cloudflare DNS
2. Quad9
This can sometimes bypass local ISP blockages on Notesnook traffic. Disable this if you want the app to use system's DNS settings.`,
onStateChange: (listener) =>
useSettingStore.subscribe((s) => s.customDns, listener),
isHidden: () => !IS_DESKTOP_APP,
components: [
{
type: "toggle",
isToggled: () => useSettingStore.getState().customDns,
toggle: () => useSettingStore.getState().toggleCustomDns()
}
]
},
{
key: "custom-cors",
title: "Custom CORS proxy",

View File

@@ -64,6 +64,7 @@ export const ProfileSettings: SettingsGroup[] = [
key: "manage-attachments",
title: "Attachments",
description: "Manage all your attachments in one place.",
isHidden: () => !useUserStore.getState().isLoggedIn,
components: [
{
type: "button",

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