mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-30 10:39:07 +02:00
Compare commits
94 Commits
3.0.0-andr
...
fix-editor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b47383aff7 | ||
|
|
b1ace27e67 | ||
|
|
ef192f3d22 | ||
|
|
c9a8134265 | ||
|
|
cd7a5d2ec0 | ||
|
|
b92bc03fa9 | ||
|
|
5f5e472800 | ||
|
|
0951906cac | ||
|
|
6f0fe03760 | ||
|
|
159ce97e42 | ||
|
|
8a9848634c | ||
|
|
282705a683 | ||
|
|
c91b706ed0 | ||
|
|
16afc07b2f | ||
|
|
04f8be5a62 | ||
|
|
0e8a5ca1fa | ||
|
|
1491bf771e | ||
|
|
0f5ce465c3 | ||
|
|
647ff7ae0e | ||
|
|
45a552adef | ||
|
|
8e207d787d | ||
|
|
00754593ac | ||
|
|
b22770f1ae | ||
|
|
523da4e91f | ||
|
|
de6c8154a3 | ||
|
|
9ef32e8081 | ||
|
|
5f53593a26 | ||
|
|
1f404944d9 | ||
|
|
23dca36475 | ||
|
|
3dd8e20e9a | ||
|
|
8f412f9487 | ||
|
|
bf790219db | ||
|
|
c6af310b2d | ||
|
|
8d89b5dca9 | ||
|
|
fa35244607 | ||
|
|
d96815fa36 | ||
|
|
4a9f5b1d8b | ||
|
|
43fac79011 | ||
|
|
f2c62c8d8b | ||
|
|
70c9dd0083 | ||
|
|
ae6d8c5f2b | ||
|
|
38e2250fd1 | ||
|
|
17e2c5a8a2 | ||
|
|
2e226f0f72 | ||
|
|
167c54578b | ||
|
|
fb656af5ca | ||
|
|
ddfab272d2 | ||
|
|
28958311c1 | ||
|
|
f1b8275b4b | ||
|
|
c8c75d40dd | ||
|
|
bace90808d | ||
|
|
fe7c7e8db4 | ||
|
|
519a569366 | ||
|
|
89d602a40f | ||
|
|
a8db65b5af | ||
|
|
716239abfd | ||
|
|
62dd5a5416 | ||
|
|
96558c5df5 | ||
|
|
45997dd2e4 | ||
|
|
bde190f01c | ||
|
|
042c97688f | ||
|
|
95b5114070 | ||
|
|
20742e2a1c | ||
|
|
1e3149284e | ||
|
|
d77c09f1fd | ||
|
|
aa602ea8e9 | ||
|
|
8ab6b6dc31 | ||
|
|
404d136a73 | ||
|
|
cf9aeecd7e | ||
|
|
1adda96ce2 | ||
|
|
6dd6d818c9 | ||
|
|
c7e83961d4 | ||
|
|
03428e5848 | ||
|
|
d0fceed56d | ||
|
|
b8c68b5163 | ||
|
|
2496004896 | ||
|
|
16ec35de25 | ||
|
|
d248b8e2b8 | ||
|
|
641e7484e3 | ||
|
|
a46409ff44 | ||
|
|
5dbabc2706 | ||
|
|
2d4d656dd9 | ||
|
|
ab51b22e4a | ||
|
|
781d09daca | ||
|
|
f21533e5e4 | ||
|
|
2ea9b04863 | ||
|
|
675d517cbc | ||
|
|
c336c57bda | ||
|
|
7b5fa09c71 | ||
|
|
d85974ed7a | ||
|
|
9598ea7542 | ||
|
|
42048ad53a | ||
|
|
1fbd2efb92 | ||
|
|
fe91ef79a0 |
69
.github/workflows/web.beta.publish.yml
vendored
69
.github/workflows/web.beta.publish.yml
vendored
@@ -1,69 +0,0 @@
|
||||
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' }}
|
||||
32
.github/workflows/web.v3-beta.publish.yml
vendored
32
.github/workflows/web.v3-beta.publish.yml
vendored
@@ -1,32 +0,0 @@
|
||||
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.
4
apps/desktop/package-lock.json
generated
4
apps/desktop/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "3.0.0",
|
||||
"version": "3.0.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "3.0.0",
|
||||
"version": "3.0.2",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@notesnook/desktop",
|
||||
"productName": "Notesnook",
|
||||
"description": "Your private note taking space",
|
||||
"version": "3.0.0",
|
||||
"version": "3.0.2",
|
||||
"appAppleId": "1544027013",
|
||||
"private": true,
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@@ -32,6 +32,7 @@ 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();
|
||||
|
||||
@@ -55,6 +56,15 @@ 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())
|
||||
|
||||
@@ -35,6 +35,7 @@ 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()) {
|
||||
@@ -151,14 +152,8 @@ async function createWindow() {
|
||||
app.once("ready", async () => {
|
||||
console.info("App ready. Opening window.");
|
||||
|
||||
app.configureHostResolver({
|
||||
secureDnsServers: [
|
||||
"https://mozilla.cloudflare-dns.com/dns-query",
|
||||
"https://dns.quad9.net/dns-query"
|
||||
],
|
||||
enableBuiltInResolver: true,
|
||||
secureDnsMode: "automatic"
|
||||
});
|
||||
if (config.customDns) enableCustomDns();
|
||||
else disableCustomDns();
|
||||
|
||||
if (!isDevelopment()) registerProtocol();
|
||||
await createWindow();
|
||||
|
||||
@@ -43,6 +43,7 @@ export const config = {
|
||||
theme: nativeTheme.themeSource,
|
||||
automaticUpdates: true,
|
||||
proxyRules: "",
|
||||
customDns: true,
|
||||
|
||||
backgroundColor: nativeTheme.themeSource === "dark" ? "#0f0f0f" : "#ffffff",
|
||||
windowControlsIconColor:
|
||||
|
||||
37
apps/desktop/src/utils/custom-dns.ts
Normal file
37
apps/desktop/src/utils/custom-dns.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
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
|
||||
});
|
||||
}
|
||||
@@ -218,12 +218,7 @@ export default async function downloadAttachment(
|
||||
}
|
||||
|
||||
if (options.base64 || options.text) {
|
||||
console.log(
|
||||
"starting decrypt base64 file...",
|
||||
options.base64,
|
||||
options.text,
|
||||
attachment.hash
|
||||
);
|
||||
DatabaseLogger.log(`Starting to decrypt... hash: ${attachment.hash}`);
|
||||
return await db.attachments.read(
|
||||
attachment.hash,
|
||||
options.base64 ? "base64" : "text"
|
||||
@@ -292,7 +287,7 @@ export default async function downloadAttachment(
|
||||
|
||||
return fileUri;
|
||||
} catch (e) {
|
||||
console.log("download attachment error: ", e);
|
||||
DatabaseLogger.error(e);
|
||||
if (attachment.dateUploaded) {
|
||||
RNFetchBlob.fs
|
||||
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}`)
|
||||
|
||||
@@ -22,21 +22,24 @@ 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 { db } from "../database";
|
||||
import { DatabaseLogger, db } from "../database";
|
||||
import { cacheDir, fileCheck } from "./utils";
|
||||
import { createCacheDir, exists } from "./io";
|
||||
|
||||
export async function downloadFile(filename, data, cancelToken) {
|
||||
if (!data) return false;
|
||||
if (!data) {
|
||||
DatabaseLogger.log(`Error downloading file: ${filename}, reason: No data`);
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log("Downloading", filename);
|
||||
DatabaseLogger.log(`Downloading ${filename}`);
|
||||
await createCacheDir();
|
||||
let { url, headers } = data;
|
||||
let path = `${cacheDir}/${filename}`;
|
||||
|
||||
try {
|
||||
if (await exists(filename)) {
|
||||
console.log("Exists already", filename);
|
||||
DatabaseLogger.log(`File Exists already: ${filename}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -44,13 +47,24 @@ export async function downloadFile(filename, data, cancelToken) {
|
||||
method: "GET",
|
||||
headers
|
||||
});
|
||||
if (!res.ok)
|
||||
|
||||
if (!res.ok) {
|
||||
DatabaseLogger.log(
|
||||
`Error downloading file: ${filename}, ${res.status}, ${res.statusText}, reason: Unable to resolve download url`
|
||||
);
|
||||
throw new Error(`${res.status}: Unable to resolve download url`);
|
||||
}
|
||||
|
||||
const downloadUrl = await res.text();
|
||||
|
||||
if (!downloadUrl) throw new Error("Unable to resolve download url");
|
||||
if (!downloadUrl) {
|
||||
DatabaseLogger.log(
|
||||
`Error downloading file: ${filename}, reason: Unable to resolve download url`
|
||||
);
|
||||
throw new Error("Unable to resolve download url");
|
||||
}
|
||||
let totalSize = 0;
|
||||
console.log("Download starting");
|
||||
DatabaseLogger.log(`Download starting: ${filename}`);
|
||||
let request = RNFetchBlob.config({
|
||||
path: path,
|
||||
IOSBackgroundTask: true
|
||||
@@ -61,13 +75,15 @@ export async function downloadFile(filename, data, cancelToken) {
|
||||
.getState()
|
||||
.setProgress(0, total, filename, recieved, "download");
|
||||
totalSize = total;
|
||||
console.log("downloading: ", recieved, total);
|
||||
DatabaseLogger.log(`Downloading: ${filename}, ${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;
|
||||
@@ -91,7 +107,10 @@ export async function downloadFile(filename, data, cancelToken) {
|
||||
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
RNFetchBlob.fs.unlink(path).catch(console.log);
|
||||
console.log("Download file error:", e, url, headers);
|
||||
DatabaseLogger.error(e, {
|
||||
url,
|
||||
headers
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -100,18 +119,17 @@ 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) {
|
||||
return 0;
|
||||
DatabaseLogger.error(e);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +143,9 @@ export async function checkAttachment(hash) {
|
||||
|
||||
try {
|
||||
const size = await getUploadedFileSize(hash);
|
||||
if (size <= 0) return { failed: "File length is 0." };
|
||||
if (size === -1) return { success: true };
|
||||
|
||||
if (size === 0) return { failed: "File length is 0." };
|
||||
} catch (e) {
|
||||
return { failed: e?.message };
|
||||
}
|
||||
|
||||
@@ -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 { db } from "../database";
|
||||
import { DatabaseLogger, db } from "../database";
|
||||
import { cacheDir, cacheDirOld, getRandomId } from "./utils";
|
||||
|
||||
export async function readEncrypted(filename, key, cipherData) {
|
||||
await migrateFilesFromCache();
|
||||
console.log("Read encrypted file...");
|
||||
DatabaseLogger.log("Read encrypted file...");
|
||||
let path = `${cacheDir}/${filename}`;
|
||||
|
||||
try {
|
||||
@@ -34,8 +34,6 @@ 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,
|
||||
{
|
||||
@@ -45,12 +43,13 @@ export async function readEncrypted(filename, key, cipherData) {
|
||||
},
|
||||
cipherData.outputType === "base64" ? "base64" : "text"
|
||||
);
|
||||
console.log("file decrypted...", attachment?.mimeType);
|
||||
|
||||
DatabaseLogger.log("File decrypted...");
|
||||
|
||||
return output;
|
||||
} catch (e) {
|
||||
RNFetchBlob.fs.unlink(path).catch(console.log);
|
||||
console.log("readEncrypted", e);
|
||||
DatabaseLogger.error(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -128,7 +127,7 @@ export async function clearFileStorage() {
|
||||
export async function createCacheDir() {
|
||||
if (!(await RNFetchBlob.fs.exists(cacheDir))) {
|
||||
await RNFetchBlob.fs.mkdir(cacheDir);
|
||||
console.log("Cache directory created");
|
||||
DatabaseLogger.log("Cache directory created");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,6 +187,9 @@ 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);
|
||||
|
||||
@@ -25,6 +25,7 @@ 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;
|
||||
@@ -33,6 +34,18 @@ 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
|
||||
@@ -50,7 +63,15 @@ 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({
|
||||
|
||||
@@ -56,6 +56,7 @@ 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,
|
||||
@@ -218,6 +219,7 @@ const Actions = ({
|
||||
}}
|
||||
>
|
||||
<Dialog context={contextId} />
|
||||
<SheetProvider context={contextId} />
|
||||
<View
|
||||
style={{
|
||||
borderBottomWidth: 1,
|
||||
@@ -238,36 +240,20 @@ const Actions = ({
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
marginBottom: 10,
|
||||
paddingHorizontal: 12
|
||||
paddingHorizontal: 12,
|
||||
marginTop: 6,
|
||||
gap: 10
|
||||
}}
|
||||
>
|
||||
<Paragraph
|
||||
size={SIZE.xs}
|
||||
style={{
|
||||
marginRight: 10
|
||||
}}
|
||||
color={colors.secondary.paragraph}
|
||||
>
|
||||
{attachment.type}
|
||||
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
|
||||
{attachment.mimeType}
|
||||
</Paragraph>
|
||||
<Paragraph
|
||||
style={{
|
||||
marginRight: 10
|
||||
}}
|
||||
size={SIZE.xs}
|
||||
color={colors.secondary.paragraph}
|
||||
>
|
||||
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
|
||||
{formatBytes(attachment.size)}
|
||||
</Paragraph>
|
||||
|
||||
{notes.length ? (
|
||||
<Paragraph
|
||||
style={{
|
||||
marginRight: 10
|
||||
}}
|
||||
size={SIZE.xs}
|
||||
color={colors.secondary.paragraph}
|
||||
>
|
||||
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
|
||||
{notes.length} note
|
||||
{notes.length > 1 ? "s" : ""}
|
||||
</Paragraph>
|
||||
@@ -366,7 +352,7 @@ const Actions = ({
|
||||
{failed ? (
|
||||
<Notice
|
||||
type="alert"
|
||||
text={`File check failed with error: ${attachment.failed} Try reuploading the file to fix the issue.`}
|
||||
text={`File check failed: ${failed} Try reuploading the file to fix the issue.`}
|
||||
size="small"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -177,7 +177,7 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
|
||||
case "audio":
|
||||
items = note
|
||||
? db.attachments.ofNote(note.id, "audio")
|
||||
: db.attachments.all;
|
||||
: db.attachments.audios;
|
||||
break;
|
||||
case "documents":
|
||||
items = note
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
setAppLockVerificationCipher,
|
||||
validateAppLockPassword
|
||||
} from "../../../common/database/encryption";
|
||||
import BiometicService from "../../../services/biometrics";
|
||||
import BiometricService from "../../../services/biometrics";
|
||||
import { DDS } from "../../../services/device-detection";
|
||||
import {
|
||||
ToastManager,
|
||||
@@ -323,7 +323,7 @@ export const AppLockPassword = () => {
|
||||
SettingsService.setProperty("appLockHasPasswordSecurity", false);
|
||||
|
||||
if (
|
||||
!(await BiometicService.isBiometryAvailable()) ||
|
||||
!(await BiometricService.isBiometryAvailable()) ||
|
||||
SettingsService.getProperty("biometricsAuthEnabled") === false
|
||||
) {
|
||||
SettingsService.setProperty("appLockEnabled", false);
|
||||
|
||||
@@ -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 { ImagePickerResponse } from "react-native-image-picker";
|
||||
import { Image as ImageType } from "react-native-image-crop-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: ImagePickerResponse;
|
||||
response: ImageType[];
|
||||
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.assets?.length} image(s):
|
||||
Attaching {response?.length} image(s):
|
||||
</Paragraph>
|
||||
<ScrollView horizontal>
|
||||
{response.assets?.map((item) => (
|
||||
<TouchableOpacity key={item.fileName} activeOpacity={0.9}>
|
||||
{response?.map((item) => (
|
||||
<TouchableOpacity key={item.filename} activeOpacity={0.9}>
|
||||
<Image
|
||||
source={{
|
||||
uri: item.uri
|
||||
uri: item.sourceURL || item.path
|
||||
}}
|
||||
style={{
|
||||
width: 100,
|
||||
@@ -142,7 +142,7 @@ export default function AttachImage({
|
||||
|
||||
<Button
|
||||
title={`${
|
||||
(response.assets?.length || 0) > 1 ? "Attach Images" : "Attach Image"
|
||||
(response?.length || 0) > 1 ? "Attach Images" : "Attach Image"
|
||||
}`}
|
||||
type="accent"
|
||||
width="100%"
|
||||
@@ -156,10 +156,16 @@ export default function AttachImage({
|
||||
);
|
||||
}
|
||||
|
||||
AttachImage.present = (response: ImagePickerResponse) => {
|
||||
return new Promise((resolve) => {
|
||||
AttachImage.present = (response: ImageType[], context?: string) => {
|
||||
return new Promise<
|
||||
| {
|
||||
compress: boolean;
|
||||
}
|
||||
| undefined
|
||||
>((resolve) => {
|
||||
let resolved = false;
|
||||
presentSheet({
|
||||
context: context,
|
||||
component: (ref, close, update) => (
|
||||
<AttachImage
|
||||
response={response}
|
||||
|
||||
@@ -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. If biometrics are not working, you can enter device pin to unlock vault."
|
||||
? "Unlock note to open it in editor."
|
||||
: "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.");
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ 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;
|
||||
@@ -66,6 +67,8 @@ export const Header = ({
|
||||
hasSearch?: boolean;
|
||||
onSearch?: () => void;
|
||||
}) => {
|
||||
const navigation = useNavigation();
|
||||
|
||||
const { colors } = useThemeColors();
|
||||
const insets = useGlobalSafeAreaInsets();
|
||||
const [borderHidden, setBorderHidden] = useState(true);
|
||||
|
||||
@@ -19,10 +19,12 @@ 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, useState } from "react";
|
||||
import React, { useEffect, useRef, 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 {
|
||||
@@ -32,13 +34,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);
|
||||
@@ -48,6 +50,7 @@ const ImagePreview = () => {
|
||||
}, []);
|
||||
|
||||
const open = async (image: ImageAttributes) => {
|
||||
imageRef.current = image;
|
||||
setVisible(true);
|
||||
setLoading(true);
|
||||
setTimeout(async () => {
|
||||
@@ -60,6 +63,9 @@ const ImagePreview = () => {
|
||||
type: "base64",
|
||||
uri: ""
|
||||
});
|
||||
if (imageRef.current) {
|
||||
imageRef.current.hash = hash;
|
||||
}
|
||||
}
|
||||
if (!hash) return;
|
||||
//@ts-ignore // FIX ME
|
||||
@@ -80,7 +86,13 @@ const ImagePreview = () => {
|
||||
|
||||
return (
|
||||
visible && (
|
||||
<BaseDialog animation="slide" visible={true} onRequestClose={close}>
|
||||
<BaseDialog
|
||||
background="black"
|
||||
animation="slide"
|
||||
visible={true}
|
||||
onRequestClose={close}
|
||||
transparent
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
@@ -101,6 +113,21 @@ 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
|
||||
@@ -117,13 +144,12 @@ const ImagePreview = () => {
|
||||
width: "100%",
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
height: 80,
|
||||
marginTop: 0,
|
||||
paddingHorizontal: 12,
|
||||
height: 50,
|
||||
paddingHorizontal: 24,
|
||||
position: "absolute",
|
||||
zIndex: 999,
|
||||
backgroundColor: "rgba(0,0,0,0.3)",
|
||||
paddingTop: Platform.OS === "android" ? 30 : 0
|
||||
marginTop: Platform.OS === "android" ? 30 : 0
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
@@ -137,7 +163,7 @@ const ImagePreview = () => {
|
||||
)}
|
||||
imageUrls={[
|
||||
{
|
||||
url: image
|
||||
url: image as string
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -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 = () => {
|
||||
return <View style={{ height: 150 }} />;
|
||||
export const Footer = ({ height = 150 }) => {
|
||||
return <View style={{ height: height }} />;
|
||||
};
|
||||
|
||||
@@ -95,7 +95,7 @@ export const NotebookHeader = ({
|
||||
style={{
|
||||
flexShrink: 1
|
||||
}}
|
||||
size={SIZE.xxl}
|
||||
size={SIZE.lg}
|
||||
>
|
||||
{notebook.title}
|
||||
</Heading>
|
||||
@@ -114,7 +114,7 @@ export const NotebookHeader = ({
|
||||
width: 40,
|
||||
height: 40
|
||||
}}
|
||||
type={isPinnedToMenu ? "secondary" : "secondary"}
|
||||
type="transparent"
|
||||
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="secondary"
|
||||
type="transparent"
|
||||
color={colors.primary.icon}
|
||||
style={{
|
||||
width: 40,
|
||||
@@ -134,7 +134,7 @@ export const NotebookHeader = ({
|
||||
</View>
|
||||
|
||||
{notebook.description ? (
|
||||
<Paragraph size={SIZE.md} color={colors.primary.paragraph}>
|
||||
<Paragraph size={SIZE.sm} color={colors.primary.paragraph}>
|
||||
{notebook.description}
|
||||
</Paragraph>
|
||||
) : null}
|
||||
|
||||
@@ -28,13 +28,12 @@ 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 from "../selection-wrapper";
|
||||
import SelectionWrapper, { selectItem } from "../selection-wrapper";
|
||||
|
||||
export const openNote = async (
|
||||
item: Note,
|
||||
@@ -47,21 +46,8 @@ 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 (selectedItemsList.length > 0 && selectionMode === item.type) {
|
||||
setSelectedItem(note.id);
|
||||
} else {
|
||||
clearSelection();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (selectItem(item)) return;
|
||||
|
||||
if (note.conflicted) {
|
||||
eSendEvent(eShowMergeDialog, note);
|
||||
|
||||
@@ -27,18 +27,12 @@ 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 from "../selection-wrapper";
|
||||
import SelectionWrapper, { selectItem } from "../selection-wrapper";
|
||||
|
||||
export const openNotebook = (item: Notebook | BaseTrashItem<Notebook>) => {
|
||||
const isTrash = item.type === "trash";
|
||||
const { selectedItemsList, setSelectedItem, selectionMode, clearSelection } =
|
||||
useSelectionStore.getState();
|
||||
if (selectedItemsList.length > 0 && selectionMode === item.type) {
|
||||
setSelectedItem(item.id);
|
||||
return;
|
||||
} else {
|
||||
clearSelection();
|
||||
}
|
||||
|
||||
if (selectItem(item)) return;
|
||||
|
||||
if (isTrash) {
|
||||
presentDialog({
|
||||
|
||||
@@ -22,7 +22,6 @@ 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";
|
||||
@@ -30,7 +29,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 from "../selection-wrapper";
|
||||
import SelectionWrapper, { selectItem } from "../selection-wrapper";
|
||||
|
||||
const ReminderItem = React.memo(
|
||||
({
|
||||
@@ -44,18 +43,7 @@ const ReminderItem = React.memo(
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
const openReminder = () => {
|
||||
const {
|
||||
selectedItemsList,
|
||||
setSelectedItem,
|
||||
selectionMode,
|
||||
clearSelection
|
||||
} = useSelectionStore.getState();
|
||||
if (selectedItemsList.length > 0 && selectionMode === item.type) {
|
||||
setSelectedItem(item.id);
|
||||
return;
|
||||
} else {
|
||||
clearSelection();
|
||||
}
|
||||
if (selectItem(item)) return;
|
||||
|
||||
ReminderSheet.present(item, undefined, isSheet);
|
||||
};
|
||||
|
||||
@@ -26,6 +26,23 @@ 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;
|
||||
|
||||
@@ -23,13 +23,12 @@ 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 from "../selection-wrapper";
|
||||
import SelectionWrapper, { selectItem } from "../selection-wrapper";
|
||||
|
||||
const TagItem = React.memo(
|
||||
({
|
||||
@@ -43,18 +42,7 @@ const TagItem = React.memo(
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
const onPress = () => {
|
||||
const {
|
||||
selectedItemsList,
|
||||
setSelectedItem,
|
||||
selectionMode,
|
||||
clearSelection
|
||||
} = useSelectionStore.getState();
|
||||
if (selectedItemsList.length > 0 && selectionMode === item.type) {
|
||||
setSelectedItem(item.id);
|
||||
return;
|
||||
} else {
|
||||
clearSelection();
|
||||
}
|
||||
if (selectItem(item)) return;
|
||||
|
||||
TaggedNotes.navigate(item, true);
|
||||
};
|
||||
|
||||
@@ -192,7 +192,9 @@ export default function List(props: ListProps) {
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
ListFooterComponent={<Footer />}
|
||||
ListFooterComponent={
|
||||
<Footer height={props.renderedInRoute === "Notebook" ? 300 : 150} />
|
||||
}
|
||||
ListHeaderComponent={
|
||||
<>
|
||||
{props.CustomLisHeader ? (
|
||||
|
||||
@@ -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 } from "react";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import {
|
||||
BackHandler,
|
||||
NativeEventSubscription,
|
||||
Platform,
|
||||
View
|
||||
} from "react-native";
|
||||
import Animated, { FadeInUp } from "react-native-reanimated";
|
||||
import Menu from "react-native-reanimated-material-menu/src/Menu";
|
||||
import { db } from "../../common/database";
|
||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||
import { ToastManager } from "../../services/event-manager";
|
||||
@@ -42,9 +42,10 @@ 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(
|
||||
({
|
||||
@@ -58,6 +59,8 @@ 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(
|
||||
@@ -132,8 +135,7 @@ export const SelectionHeader = React.memo(
|
||||
}, [clearSelection, selectionMode]);
|
||||
|
||||
return selectionMode !== type || focusedRouteId !== id ? null : (
|
||||
<Animated.View
|
||||
entering={FadeInUp}
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
height: Platform.OS === "android" ? 50 + insets.top : 50,
|
||||
@@ -143,8 +145,7 @@ export const SelectionHeader = React.memo(
|
||||
alignItems: "center",
|
||||
flexDirection: "row",
|
||||
zIndex: 999,
|
||||
paddingHorizontal: 12,
|
||||
marginVertical: 10
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
>
|
||||
<View
|
||||
@@ -164,18 +165,15 @@ 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,
|
||||
@@ -184,11 +182,12 @@ export const SelectionHeader = React.memo(
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Heading size={SIZE.md} color={colors.primary.accent}>
|
||||
<Heading size={SIZE.lg} color={colors.primary.paragraph}>
|
||||
{selectedItemsList.length}
|
||||
</Heading>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
@@ -211,161 +210,142 @@ export const SelectionHeader = React.memo(
|
||||
allSelected ? colors.primary.accent : colors.primary.paragraph
|
||||
}
|
||||
name="select-all"
|
||||
size={SIZE.xl}
|
||||
/>
|
||||
|
||||
{renderedInRoute === "Notebooks" ? (
|
||||
<IconButton
|
||||
{selectedItemsList.length ? (
|
||||
<Menu
|
||||
ref={menuRef}
|
||||
animationDuration={200}
|
||||
style={{
|
||||
marginLeft: 10
|
||||
borderRadius: 5,
|
||||
backgroundColor: contextMenuColors.primary.background,
|
||||
marginTop: -20
|
||||
}}
|
||||
onPress={async () => {
|
||||
const ids = selectedItemsList;
|
||||
const notebooks = await db.notebooks.all.items(ids);
|
||||
MoveNotebookSheet.present(notebooks);
|
||||
onRequestClose={() => {
|
||||
menuRef.current?.hide();
|
||||
}}
|
||||
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();
|
||||
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"
|
||||
}
|
||||
}}
|
||||
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}
|
||||
/>
|
||||
</>
|
||||
].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>
|
||||
) : null}
|
||||
</View>
|
||||
</Animated.View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -45,14 +45,16 @@ export const AddNotebookSheet = ({
|
||||
notebook,
|
||||
parentNotebook,
|
||||
close,
|
||||
showMoveNotesOnComplete
|
||||
showMoveNotesOnComplete,
|
||||
defaultTitle
|
||||
}: {
|
||||
notebook?: Notebook;
|
||||
parentNotebook?: Notebook;
|
||||
close?: (didAddNotebook: boolean) => void;
|
||||
showMoveNotesOnComplete: boolean;
|
||||
defaultTitle?: string;
|
||||
}) => {
|
||||
const title = useRef(notebook?.title);
|
||||
const title = useRef(notebook?.title || defaultTitle);
|
||||
const description = useRef(notebook?.description);
|
||||
const titleInput = useRef<TextInput>(null);
|
||||
const descriptionInput = useRef<TextInput>(null);
|
||||
@@ -139,7 +141,7 @@ export const AddNotebookSheet = ({
|
||||
}}
|
||||
returnKeyLabel="Next"
|
||||
returnKeyType="next"
|
||||
defaultValue={notebook ? notebook.title : ""}
|
||||
defaultValue={notebook ? notebook.title : title.current}
|
||||
/>
|
||||
|
||||
<Input
|
||||
@@ -174,7 +176,8 @@ AddNotebookSheet.present = (
|
||||
parentNotebook?: Notebook,
|
||||
context?: string,
|
||||
onClose?: (didAddNotebook: boolean) => void,
|
||||
showMoveNotesOnComplete = true
|
||||
showMoveNotesOnComplete = true,
|
||||
defaultTitle?: string
|
||||
) => {
|
||||
presentSheet({
|
||||
context: context,
|
||||
@@ -187,6 +190,7 @@ AddNotebookSheet.present = (
|
||||
onClose?.(didAddNotebook);
|
||||
}}
|
||||
showMoveNotesOnComplete={showMoveNotesOnComplete || false}
|
||||
defaultTitle={defaultTitle}
|
||||
/>
|
||||
)
|
||||
});
|
||||
|
||||
@@ -19,7 +19,13 @@ 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 } from "react";
|
||||
import React, {
|
||||
RefObject,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState
|
||||
} from "react";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Keyboard,
|
||||
@@ -47,6 +53,8 @@ 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
|
||||
@@ -93,7 +101,11 @@ const MoveNoteSheet = ({
|
||||
actionSheetRef: RefObject<ActionSheetRef>;
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
const [notebooks, loading] = useNotebooks();
|
||||
const [rootNotebooks, loading] = useNotebooks();
|
||||
const searchQuery = useRef("");
|
||||
const searchTimer = useRef<NodeJS.Timeout>();
|
||||
const [notebooks, setNotebooks] = useState(rootNotebooks);
|
||||
|
||||
const dimensions = useSettingStore((state) => state.dimensions);
|
||||
const selectedItemsList = useSelectionStore(
|
||||
(state) => state.selectedItemsList
|
||||
@@ -103,6 +115,12 @@ const MoveNoteSheet = ({
|
||||
(state) => state.multiSelect
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
setNotebooks(rootNotebooks);
|
||||
}
|
||||
}, [loading, rootNotebooks]);
|
||||
|
||||
useEffect(() => {
|
||||
const items = note ? [note.id] : selectedItemsList;
|
||||
updateInitialSelectionState(items);
|
||||
@@ -199,7 +217,6 @@ const MoveNoteSheet = ({
|
||||
{hasSelected() ? (
|
||||
<IconButton
|
||||
name="restore"
|
||||
type="secondaryAccented"
|
||||
color={colors.primary.icon}
|
||||
onPress={() => {
|
||||
const items = note ? [note.id] : selectedItemsList;
|
||||
@@ -239,33 +256,48 @@ const MoveNoteSheet = ({
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
ListHeaderComponent={
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
width: "100%"
|
||||
width: "100%",
|
||||
paddingTop: 12
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
title="Add new notebook"
|
||||
style={{
|
||||
alignSelf: "flex-start",
|
||||
paddingHorizontal: 12,
|
||||
justifyContent: "space-between"
|
||||
<Input
|
||||
placeholder="Search notebooks...."
|
||||
button={{
|
||||
icon: "plus",
|
||||
onPress: () => {
|
||||
AddNotebookSheet.present(
|
||||
undefined,
|
||||
undefined,
|
||||
"link-notebooks",
|
||||
undefined,
|
||||
false,
|
||||
searchQuery.current
|
||||
);
|
||||
},
|
||||
color: colors.primary.icon
|
||||
}}
|
||||
onPress={() => {
|
||||
AddNotebookSheet.present(
|
||||
undefined,
|
||||
undefined,
|
||||
"link-notebooks",
|
||||
undefined,
|
||||
false
|
||||
);
|
||||
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);
|
||||
}}
|
||||
icon="plus"
|
||||
iconPosition="right"
|
||||
type="secondaryAccented"
|
||||
width="100%"
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ export default function LinkNote(props: {
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
minHeight: 400,
|
||||
minHeight: "100%",
|
||||
maxHeight: "100%"
|
||||
}}
|
||||
>
|
||||
@@ -311,6 +311,7 @@ export default function LinkNote(props: {
|
||||
style={{
|
||||
marginTop: 10
|
||||
}}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
windowSize={3}
|
||||
keyExtractor={(item) => item.id}
|
||||
data={nodes}
|
||||
@@ -324,6 +325,7 @@ export default function LinkNote(props: {
|
||||
onSelectNote={onSelectNote}
|
||||
/>
|
||||
)}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
style={{
|
||||
marginTop: 10
|
||||
}}
|
||||
|
||||
@@ -96,9 +96,12 @@ export default function Migrate() {
|
||||
});
|
||||
setLoading(true);
|
||||
await sleep(1);
|
||||
const { error } = await BackupService.run(false, "local");
|
||||
const { error, report } = await BackupService.run(false, "local");
|
||||
if (error) {
|
||||
ToastManager.error(error, "Backup failed");
|
||||
if (report) {
|
||||
reportError(error);
|
||||
}
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -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 } from "react";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { Text, View } from "react-native";
|
||||
import { FlatList } from "react-native-actions-sheet";
|
||||
import create from "zustand";
|
||||
@@ -72,6 +72,20 @@ 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 (
|
||||
@@ -93,6 +107,7 @@ 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);
|
||||
@@ -157,36 +172,38 @@ export const MoveNotebookSheet = ({
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
>
|
||||
<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);
|
||||
{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);
|
||||
}
|
||||
}
|
||||
}
|
||||
useNotebookStore.getState().refresh();
|
||||
close?.();
|
||||
}}
|
||||
/>
|
||||
useNotebookStore.getState().refresh();
|
||||
close?.();
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
}
|
||||
ListEmptyComponent={
|
||||
|
||||
@@ -118,7 +118,12 @@ NewFeature.present = () => {
|
||||
);
|
||||
if (_features.length === 0) return;
|
||||
presentSheet({
|
||||
component: <NewFeature features={features} version={version} />,
|
||||
component: (
|
||||
<NewFeature
|
||||
features={features}
|
||||
version={SettingsService.getProperty("version")}
|
||||
/>
|
||||
),
|
||||
disableClosing: true
|
||||
});
|
||||
return true;
|
||||
|
||||
@@ -19,13 +19,19 @@ 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 { RefreshControl, View, useWindowDimensions } from "react-native";
|
||||
import {
|
||||
Platform,
|
||||
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";
|
||||
@@ -43,9 +49,8 @@ import { IconButton } from "../../ui/icon-button";
|
||||
import { Pressable } from "../../ui/pressable";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { AddNotebookSheet } from "../add-notebook";
|
||||
import Sort from "../sort";
|
||||
import { MoveNotebookSheet } from "../move-notebook";
|
||||
import { db } from "../../../common/database";
|
||||
import Sort from "../sort";
|
||||
|
||||
const useItemSelectionStore = createItemSelectionStore(true, false);
|
||||
|
||||
@@ -63,7 +68,8 @@ class NotebookSheetConfig {
|
||||
}
|
||||
|
||||
static get(item: ConfigItem) {
|
||||
return MMKV.getInt(NotebookSheetConfig.makeId(item)) || 0;
|
||||
const value = MMKV.getInt(NotebookSheetConfig.makeId(item));
|
||||
return typeof value === "number" ? value : 0;
|
||||
}
|
||||
|
||||
static set(item: ConfigItem, index = 0) {
|
||||
@@ -126,11 +132,6 @@ 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: {}
|
||||
@@ -145,11 +146,8 @@ export const NotebookSheet = () => {
|
||||
if (ref.current?.isOpen()) {
|
||||
ref.current?.snapToIndex(snapPoint);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
ref.current?.show(snapPoint);
|
||||
}, 150);
|
||||
ref.current?.show(snapPoint);
|
||||
}
|
||||
console.log("NotebookSheet.useEffect.didShow", focusedRouteId);
|
||||
setRoot(nextRoot);
|
||||
onRequestUpdate();
|
||||
});
|
||||
@@ -162,7 +160,7 @@ export const NotebookSheet = () => {
|
||||
ref.current?.hide();
|
||||
}
|
||||
}
|
||||
}, [canShow, onRequestUpdate, focusedRouteId]);
|
||||
}, [canShow, focusedRouteId]);
|
||||
|
||||
return (
|
||||
<ActionSheet
|
||||
@@ -198,7 +196,11 @@ export const NotebookSheet = () => {
|
||||
backgroundColor: colors.secondary.background
|
||||
}}
|
||||
keyboardHandlerEnabled={false}
|
||||
snapPoints={Config.isTesting === "true" ? [100] : [20, 100]}
|
||||
snapPoints={
|
||||
Config.isTesting === "true"
|
||||
? [100]
|
||||
: [Platform.OS === "android" ? 15 : 10, 100]
|
||||
}
|
||||
initialSnapIndex={1}
|
||||
backgroundInteractionEnabled
|
||||
gestureEnabled
|
||||
@@ -237,7 +239,7 @@ export const NotebookSheet = () => {
|
||||
>
|
||||
<Icon
|
||||
name="notebook-plus"
|
||||
color={colors.primary.accent}
|
||||
color={colors.primary.icon}
|
||||
size={SIZE.xxl}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -18,7 +18,12 @@ 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 } from "react-native";
|
||||
import {
|
||||
Platform,
|
||||
TextInput,
|
||||
View,
|
||||
ScrollView as RNScrollView
|
||||
} from "react-native";
|
||||
import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
|
||||
import DateTimePickerModal from "react-native-modal-datetime-picker";
|
||||
import {
|
||||
@@ -364,7 +369,7 @@ export default function ReminderSheet({
|
||||
))}
|
||||
</View>
|
||||
|
||||
<ScrollView showsHorizontalScrollIndicator={false} horizontal>
|
||||
<RNScrollView showsHorizontalScrollIndicator={false} horizontal>
|
||||
{recurringMode === RecurringModes.Daily ||
|
||||
recurringMode === RecurringModes.Year
|
||||
? null
|
||||
@@ -425,7 +430,7 @@ export default function ReminderSheet({
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</ScrollView>
|
||||
</RNScrollView>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = "asc";
|
||||
_groupOptions.sortDirection = "desc";
|
||||
}
|
||||
|
||||
updateGroupOptions(_groupOptions);
|
||||
|
||||
@@ -21,15 +21,11 @@ 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";
|
||||
@@ -59,26 +55,6 @@ 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",
|
||||
@@ -121,7 +97,7 @@ export const SideMenu = React.memo(
|
||||
<PinnedSection />
|
||||
</>
|
||||
),
|
||||
[]
|
||||
[order, hiddensItems]
|
||||
);
|
||||
|
||||
return !isAppLoading && introCompleted ? (
|
||||
|
||||
@@ -20,29 +20,25 @@ 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,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from "react-native";
|
||||
import { ActivityIndicator, Image, Platform, 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 } = useThemeColors();
|
||||
const { colors, isDark } = useThemeColors();
|
||||
const user = useUserStore((state) => state.user);
|
||||
const syncing = useUserStore((state) => state.syncing);
|
||||
const lastSyncStatus = useUserStore((state) => state.lastSyncStatus);
|
||||
@@ -97,7 +93,16 @@ export const UserStatus = () => {
|
||||
borderRadius: 100
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
) : (
|
||||
<Icon
|
||||
name="cog-outline"
|
||||
size={SIZE.lg - 2}
|
||||
color={colors.secondary.icon}
|
||||
style={{
|
||||
paddingLeft: 8
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<View
|
||||
style={{
|
||||
@@ -110,14 +115,8 @@ export const UserStatus = () => {
|
||||
size={SIZE.sm}
|
||||
color={colors.primary.heading}
|
||||
>
|
||||
{!user
|
||||
? "Login to sync your notes."
|
||||
: lastSyncStatus === SyncStatus.Failed
|
||||
? "Sync failed, tap to retry"
|
||||
: syncing
|
||||
? `Syncing ${progress ? `(${progress.current})` : ""}`
|
||||
: !userProfile?.fullName
|
||||
? "Tap to sync"
|
||||
{!user || !userProfile?.fullName
|
||||
? "Settings"
|
||||
: userProfile.fullName}
|
||||
</Paragraph>
|
||||
|
||||
@@ -132,16 +131,20 @@ export const UserStatus = () => {
|
||||
"Not logged in"
|
||||
) : lastSynced && lastSynced !== "Never" ? (
|
||||
<>
|
||||
{lastSyncStatus === SyncStatus.Failed
|
||||
{syncing
|
||||
? `Syncing ${progress ? `(${progress.current})` : ""}`
|
||||
: lastSyncStatus === SyncStatus.Failed
|
||||
? "Sync failed"
|
||||
: "Synced"}{" "}
|
||||
<TimeSince
|
||||
style={{
|
||||
fontSize: SIZE.xs,
|
||||
color: colors.secondary.paragraph
|
||||
}}
|
||||
time={lastSynced}
|
||||
/>
|
||||
{!syncing ? (
|
||||
<TimeSince
|
||||
style={{
|
||||
fontSize: SIZE.xs,
|
||||
color: colors.secondary.paragraph
|
||||
}}
|
||||
time={lastSynced}
|
||||
/>
|
||||
) : null}
|
||||
{isOffline ? " (offline)" : ""}
|
||||
</>
|
||||
) : (
|
||||
@@ -149,7 +152,7 @@ export const UserStatus = () => {
|
||||
)}{" "}
|
||||
<Icon
|
||||
name="checkbox-blank-circle"
|
||||
size={11}
|
||||
size={9}
|
||||
allowFontScaling
|
||||
color={
|
||||
!user || lastSyncStatus === SyncStatus.Failed
|
||||
@@ -162,56 +165,80 @@ export const UserStatus = () => {
|
||||
</Paragraph>
|
||||
</View>
|
||||
|
||||
<Pressable
|
||||
<View
|
||||
style={{
|
||||
borderRadius: 100,
|
||||
width: 40,
|
||||
height: 40
|
||||
}}
|
||||
hitSlop={{
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
left: 20
|
||||
}}
|
||||
onPress={() => {
|
||||
if (user) {
|
||||
Sync.run();
|
||||
} else {
|
||||
tabBarRef.current?.closeDrawer();
|
||||
eSendEvent(eOpenLoginDialog);
|
||||
}
|
||||
flexDirection: "row",
|
||||
gap: 0
|
||||
}}
|
||||
>
|
||||
{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
|
||||
/>
|
||||
<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}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<Icon
|
||||
allowFontScaling
|
||||
color={colors.primary.accent}
|
||||
name="sync"
|
||||
size={SIZE.lg}
|
||||
name="login"
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<Icon
|
||||
allowFontScaling
|
||||
color={colors.primary.accent}
|
||||
size={SIZE.lg}
|
||||
name="login"
|
||||
/>
|
||||
)}
|
||||
</Pressable>
|
||||
)}
|
||||
</Pressable>
|
||||
</View>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -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
|
||||
}),
|
||||
|
||||
@@ -19,35 +19,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { FeatureType } from "./components/sheets/new-feature";
|
||||
|
||||
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."
|
||||
}
|
||||
];
|
||||
export const features: FeatureType[] = [];
|
||||
|
||||
@@ -85,7 +85,6 @@ import { SyncStatus, useUserStore } from "../stores/use-user-store";
|
||||
import { updateStatusBarColor } from "../utils/colors";
|
||||
import { BETA } from "../utils/constants";
|
||||
import {
|
||||
eClearEditor,
|
||||
eCloseSheet,
|
||||
eLoginSessionExpired,
|
||||
eOnLoadNote,
|
||||
@@ -532,12 +531,6 @@ 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() &&
|
||||
|
||||
@@ -71,12 +71,10 @@ 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]) {
|
||||
|
||||
@@ -36,7 +36,6 @@ export const useNotebook = (
|
||||
|
||||
const onRequestUpdate = React.useCallback(() => {
|
||||
if (!item?.id) return;
|
||||
console.log("useNotebook.onRequestUpdate", item?.id, Date.now());
|
||||
|
||||
const selector = db.relations.from(
|
||||
{
|
||||
@@ -59,7 +58,6 @@ export const useNotebook = (
|
||||
|
||||
useEffect(() => {
|
||||
if (nestedNotebooks) {
|
||||
console.log("useNotebook.useEffect.onRequestUpdate");
|
||||
onRequestUpdate();
|
||||
}
|
||||
}, [item?.id, onRequestUpdate, nestedNotebooks]);
|
||||
|
||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import BiometicService from "../services/biometrics";
|
||||
import BiometricService 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 BiometicService.isBiometryAvailable();
|
||||
const fingerprint = await BiometicService.hasInternetCredentials();
|
||||
const available = await BiometricService.isBiometryAvailable();
|
||||
const fingerprint = await BiometricService.hasInternetCredentials();
|
||||
if (
|
||||
VaultStatusCache.exists === exists &&
|
||||
VaultStatusCache.biometryEnrolled === fingerprint &&
|
||||
|
||||
@@ -65,6 +65,7 @@ import {
|
||||
eClearEditor,
|
||||
eCloseFullscreenEditor,
|
||||
eOnEnterEditor,
|
||||
eOnExitEditor,
|
||||
eOnLoadNote,
|
||||
eOpenFullscreenEditor,
|
||||
eUnlockNote
|
||||
@@ -511,6 +512,7 @@ const onChangeTab = async (event) => {
|
||||
editorState().movedAway = false;
|
||||
editorState().isFocused = true;
|
||||
activateKeepAwake();
|
||||
eSendEvent(eOnEnterEditor);
|
||||
|
||||
if (!useTabStore.getState().getCurrentNoteId()) {
|
||||
eSendEvent(eOnLoadNote, {
|
||||
@@ -522,14 +524,13 @@ const onChangeTab = async (event) => {
|
||||
) {
|
||||
eSendEvent(eUnlockNote);
|
||||
}
|
||||
eSendEvent(eOnEnterEditor);
|
||||
}
|
||||
} else {
|
||||
if (event.from === 2) {
|
||||
deactivateKeepAwake();
|
||||
editorState().movedAway = true;
|
||||
editorState().isFocused = false;
|
||||
eSendEvent(eClearEditor, "removeHandler");
|
||||
eSendEvent(eOnExitEditor);
|
||||
|
||||
// Lock all tabs with locked notes...
|
||||
for (const tab of useTabStore.getState().tabs) {
|
||||
|
||||
@@ -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 BiometicService from "../../services/biometrics";
|
||||
import BiometricService from "../../services/biometrics";
|
||||
import {
|
||||
ToastManager,
|
||||
eSendEvent,
|
||||
@@ -51,7 +51,11 @@ 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 } from "./tiptap/utils";
|
||||
import {
|
||||
editorController,
|
||||
editorState,
|
||||
openInternalLink
|
||||
} from "./tiptap/utils";
|
||||
|
||||
const style: ViewStyle = {
|
||||
height: "100%",
|
||||
@@ -61,7 +65,10 @@ const style: ViewStyle = {
|
||||
backgroundColor: "transparent"
|
||||
};
|
||||
const onShouldStartLoadWithRequest = (request: ShouldStartLoadRequest) => {
|
||||
if (request.url.includes("https")) {
|
||||
if (request.url.includes("nn://")) {
|
||||
openInternalLink(request.url);
|
||||
return false;
|
||||
} else if (request.url.includes("https")) {
|
||||
if (Platform.OS === "ios" && !request.isTopFrame) return true;
|
||||
openLinkInBrowser(request.url);
|
||||
return false;
|
||||
@@ -201,8 +208,8 @@ const useLockedNoteHandler = () => {
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const biometry = await BiometicService.isBiometryAvailable();
|
||||
const fingerprint = await BiometicService.hasInternetCredentials();
|
||||
const biometry = await BiometricService.isBiometryAvailable();
|
||||
const fingerprint = await BiometricService.hasInternetCredentials();
|
||||
useTabStore.setState({
|
||||
biometryAvailable: !!biometry,
|
||||
biometryEnrolled: !!fingerprint
|
||||
@@ -216,9 +223,9 @@ const useLockedNoteHandler = () => {
|
||||
try {
|
||||
if (!tabRef.current?.noteLocked || !tabRef.current) return;
|
||||
console.log("Trying to unlock with biometrics...");
|
||||
const credentials = await BiometicService.getCredentials(
|
||||
const credentials = await BiometricService.getCredentials(
|
||||
"Unlock note",
|
||||
"Unlock note to open it in editor. If biometrics are not working, you can enter device pin to unlock vault."
|
||||
"Unlock note to open it in editor."
|
||||
);
|
||||
|
||||
if (credentials && credentials?.password && tabRef.current.noteId) {
|
||||
@@ -226,6 +233,7 @@ const useLockedNoteHandler = () => {
|
||||
tabRef.current.noteId,
|
||||
credentials?.password
|
||||
);
|
||||
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: note
|
||||
});
|
||||
@@ -262,7 +270,7 @@ const useLockedNoteHandler = () => {
|
||||
try {
|
||||
const unlocked = await db.vault.unlock(password);
|
||||
if (!unlocked) throw new Error("Incorrect vault password");
|
||||
await BiometicService.storeCredentials(password);
|
||||
await BiometricService.storeCredentials(password);
|
||||
eSendEvent("vaultUpdated");
|
||||
ToastManager.show({
|
||||
heading: "Biometric unlocking enabled!",
|
||||
@@ -271,8 +279,8 @@ const useLockedNoteHandler = () => {
|
||||
context: "global"
|
||||
});
|
||||
|
||||
const biometry = await BiometicService.isBiometryAvailable();
|
||||
const fingerprint = await BiometicService.hasInternetCredentials();
|
||||
const biometry = await BiometricService.isBiometryAvailable();
|
||||
const fingerprint = await BiometricService.hasInternetCredentials();
|
||||
useTabStore.setState({
|
||||
biometryAvailable: !!biometry,
|
||||
biometryEnrolled: !!fingerprint
|
||||
|
||||
@@ -21,12 +21,11 @@ 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 from "react-native-document-picker";
|
||||
import {
|
||||
ImagePickerResponse,
|
||||
launchCamera,
|
||||
launchImageLibrary
|
||||
} from "react-native-image-picker";
|
||||
import DocumentPicker, {
|
||||
DocumentPickerOptions,
|
||||
DocumentPickerResponse
|
||||
} from "react-native-document-picker";
|
||||
import { Image, openCamera, openPicker } from "react-native-image-crop-picker";
|
||||
import { DatabaseLogger, db } from "../../../common/database";
|
||||
import filesystem from "../../../common/filesystem";
|
||||
import { compressToFile } from "../../../common/filesystem/compress";
|
||||
@@ -43,7 +42,7 @@ import { eCloseSheet } from "../../../utils/events";
|
||||
import { useTabStore } from "./use-tab-store";
|
||||
import { editorController, editorState } from "./utils";
|
||||
|
||||
const showEncryptionSheet = (file) => {
|
||||
const showEncryptionSheet = (file: DocumentPickerResponse) => {
|
||||
presentSheet({
|
||||
title: "Encrypting attachment",
|
||||
paragraph: `Please wait while we encrypt ${file.name} file for upload`,
|
||||
@@ -51,24 +50,25 @@ const showEncryptionSheet = (file) => {
|
||||
});
|
||||
};
|
||||
|
||||
const santizeUri = (uri) => {
|
||||
const santizeUri = (uri: string) => {
|
||||
uri = decodeURI(uri);
|
||||
uri = Platform.OS === "ios" ? uri.replace("file:///", "/") : uri;
|
||||
return uri;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* noteId: string,
|
||||
* tabId: string,
|
||||
* type: "image" | "camera" | "file"
|
||||
* reupload: boolean
|
||||
* hash?: string
|
||||
* }} fileOptions
|
||||
*/
|
||||
const file = async (fileOptions) => {
|
||||
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) => {
|
||||
try {
|
||||
const options = {
|
||||
const options: DocumentPickerOptions<"ios"> = {
|
||||
mode: "import",
|
||||
allowMultiSelection: false
|
||||
};
|
||||
@@ -87,11 +87,11 @@ const file = async (fileOptions) => {
|
||||
|
||||
file = file[0];
|
||||
|
||||
let uri = Platform.OS === "ios" ? file.fileCopyUri : file.uri;
|
||||
let uri = Platform.OS === "ios" ? file.fileCopyUri || file.uri : file.uri;
|
||||
|
||||
if (file.size > FILE_SIZE_LIMIT) {
|
||||
if ((file.size || 0) > FILE_SIZE_LIMIT) {
|
||||
ToastManager.show({
|
||||
title: "File too large",
|
||||
heading: "File too large",
|
||||
message: "The maximum allowed size per file is 500 MB",
|
||||
type: "error"
|
||||
});
|
||||
@@ -115,22 +115,34 @@ const file = async (fileOptions) => {
|
||||
uri: uri,
|
||||
type: "url"
|
||||
});
|
||||
if (!(await attachFile(uri, hash, file.type, file.name, fileOptions)))
|
||||
if (
|
||||
!(await attachFile(
|
||||
uri,
|
||||
hash,
|
||||
file.type || "application/octet-stream",
|
||||
file.name,
|
||||
fileOptions
|
||||
))
|
||||
)
|
||||
return;
|
||||
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
|
||||
|
||||
if (!fileOptions.tabId) return;
|
||||
if (
|
||||
useTabStore.getState().getNoteIdForTab(options.tabId) === options.noteId
|
||||
fileOptions.tabId &&
|
||||
fileOptions.noteId &&
|
||||
useTabStore.getState().getNoteIdForTab(fileOptions.tabId) ===
|
||||
fileOptions.noteId
|
||||
) {
|
||||
if (isImage(file.type)) {
|
||||
if (isImage(file.type || "application/octet-stream")) {
|
||||
editorController.current?.commands.insertImage(
|
||||
{
|
||||
hash: hash,
|
||||
filename: file.name,
|
||||
mime: file.type,
|
||||
size: file.size,
|
||||
dataurl: await db.attachments.read(hash, "base64"),
|
||||
title: file.name
|
||||
mime: file.type || "application/octet-stream",
|
||||
size: file.size || 0,
|
||||
dataurl: (await db.attachments.read(hash, "base64")) as string,
|
||||
type: "image"
|
||||
},
|
||||
fileOptions.tabId
|
||||
);
|
||||
@@ -139,8 +151,9 @@ const file = async (fileOptions) => {
|
||||
{
|
||||
hash: hash,
|
||||
filename: file.name,
|
||||
mime: file.type,
|
||||
size: file.size
|
||||
mime: file.type || "application/octet-stream",
|
||||
size: file.size || 0,
|
||||
type: "file"
|
||||
},
|
||||
fileOptions.tabId
|
||||
);
|
||||
@@ -152,7 +165,7 @@ const file = async (fileOptions) => {
|
||||
}, 1000);
|
||||
} catch (e) {
|
||||
ToastManager.show({
|
||||
heading: e.message,
|
||||
heading: (e as Error).message,
|
||||
message: "You need internet access to attach a file",
|
||||
type: "error",
|
||||
context: "global"
|
||||
@@ -161,29 +174,51 @@ const file = async (fileOptions) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* noteId: string,
|
||||
* tabId: string,
|
||||
* type: "image" | "camera" | "file"
|
||||
* reupload: boolean
|
||||
* hash?: string
|
||||
* }} options
|
||||
*/
|
||||
const camera = async (options) => {
|
||||
const camera = async (options: PickerOptions) => {
|
||||
try {
|
||||
await db.attachments.generateKey();
|
||||
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
|
||||
launchCamera(
|
||||
{
|
||||
includeBase64: true,
|
||||
mediaType: "photo"
|
||||
},
|
||||
(response) => handleImageResponse(response, options)
|
||||
);
|
||||
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);
|
||||
});
|
||||
} catch (e) {
|
||||
ToastManager.show({
|
||||
heading: e.message,
|
||||
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,
|
||||
message: "You need internet access to attach a file",
|
||||
type: "error",
|
||||
context: "global"
|
||||
@@ -192,53 +227,9 @@ const camera = async (options) => {
|
||||
}
|
||||
};
|
||||
|
||||
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) => {
|
||||
const pick = async (options: PickerOptions) => {
|
||||
if (!PremiumService.get()) {
|
||||
let user = await db.user.getUser();
|
||||
const user = await db.user.getUser();
|
||||
if (editorState().isFocused) {
|
||||
editorState().isFocused = true;
|
||||
}
|
||||
@@ -259,58 +250,49 @@ const pick = async (options) => {
|
||||
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 (let image of response.assets) {
|
||||
const isPng = /(png)/g.test(image.type);
|
||||
const isJpeg = /(jpeg|jpg)/g.test(image.type);
|
||||
for (const image of response) {
|
||||
const isPng = /(png)/g.test(image.mime);
|
||||
const isJpeg = /(jpeg|jpg)/g.test(image.mime);
|
||||
|
||||
if (compress && (isPng || isJpeg)) {
|
||||
image.uri = await compressToFile(
|
||||
Platform.OS === "ios" ? "file://" + image.uri : image.uri,
|
||||
image.path = await compressToFile(
|
||||
Platform.OS === "ios" ? "file://" + image.path : image.path,
|
||||
isPng ? "PNG" : "JPEG"
|
||||
);
|
||||
const stat = await RNFetchBlob.fs.stat(image.uri.replace("file://", ""));
|
||||
image.fileSize = stat.size;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (image.fileSize > IMAGE_SIZE_LIMIT) {
|
||||
if (image.size > IMAGE_SIZE_LIMIT) {
|
||||
ToastManager.show({
|
||||
title: "File too large",
|
||||
heading: "File too large",
|
||||
message: "The maximum allowed size per image is 50 MB",
|
||||
type: "error"
|
||||
});
|
||||
return;
|
||||
}
|
||||
let b64 = `data:${image.type};base64, ` + image.base64;
|
||||
const uri = decodeURI(image.uri);
|
||||
const b64 = `data:${image.mime};base64, ` + image.data;
|
||||
const uri = decodeURI(image.path);
|
||||
const hash = await Sodium.hashFile({
|
||||
uri: uri,
|
||||
type: "url"
|
||||
});
|
||||
|
||||
let fileName = image.originalFileName || image.fileName;
|
||||
const fileName = image.filename || "image";
|
||||
console.log("attaching file...");
|
||||
if (!(await attachFile(uri, hash, image.type, fileName, options))) return;
|
||||
if (!(await attachFile(uri, hash, image.mime, fileName, options))) return;
|
||||
|
||||
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
|
||||
console.log("attaching image to note...");
|
||||
@@ -322,11 +304,13 @@ const handleImageResponse = async (response, options) => {
|
||||
editorController.current?.commands.insertImage(
|
||||
{
|
||||
hash: hash,
|
||||
mime: image.type,
|
||||
title: fileName,
|
||||
mime: image.mime,
|
||||
type: "image",
|
||||
dataurl: b64,
|
||||
size: image.fileSize,
|
||||
filename: fileName
|
||||
size: image.size,
|
||||
filename: fileName as string,
|
||||
width: image.width,
|
||||
height: image.height
|
||||
},
|
||||
options.tabId
|
||||
);
|
||||
@@ -343,10 +327,16 @@ const handleImageResponse = async (response, options) => {
|
||||
* @param {ImagePickerOptions} options
|
||||
* @returns
|
||||
*/
|
||||
export async function attachFile(uri, hash, type, filename, options) {
|
||||
export async function attachFile(
|
||||
uri: string,
|
||||
hash: string,
|
||||
type: string,
|
||||
filename: string,
|
||||
options: PickerOptions
|
||||
) {
|
||||
try {
|
||||
let exists = await db.attachments.exists(hash);
|
||||
let encryptionInfo;
|
||||
const exists = await db.attachments.exists(hash);
|
||||
let encryptionInfo: any;
|
||||
if (options?.hash && options.hash !== hash) {
|
||||
ToastManager.show({
|
||||
heading: "Please select the same file for reuploading",
|
||||
@@ -358,26 +348,36 @@ export async function attachFile(uri, hash, type, filename, options) {
|
||||
}
|
||||
|
||||
if (!options.reupload && exists) {
|
||||
options.reupload = (await filesystem.getUploadedFileSize(hash)) <= 0;
|
||||
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}`);
|
||||
}
|
||||
|
||||
if (!exists || options?.reupload) {
|
||||
let key = await db.attachments.generateKey();
|
||||
const key = await db.attachments.generateKey();
|
||||
encryptionInfo = await Sodium.encryptFile(key, {
|
||||
uri: uri,
|
||||
type: options.type || "url",
|
||||
type: options.outputType || "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) await db.attachments.reset(hash);
|
||||
if (options?.reupload && exists) {
|
||||
const attachment = await db.attachments.attachment(hash);
|
||||
if (attachment) await db.attachments.reset(attachment?.id);
|
||||
}
|
||||
} else {
|
||||
encryptionInfo = { hash: hash };
|
||||
}
|
||||
await db.attachments.add(encryptionInfo, options.noteId);
|
||||
await db.attachments.add(encryptionInfo);
|
||||
return true;
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e);
|
||||
@@ -19,7 +19,6 @@ 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";
|
||||
@@ -27,7 +26,6 @@ import Clipboard from "@react-native-clipboard/clipboard";
|
||||
import React, { useCallback, useEffect, useRef } from "react";
|
||||
import {
|
||||
BackHandler,
|
||||
InteractionManager,
|
||||
Keyboard,
|
||||
KeyboardEventListener,
|
||||
NativeEventSubscription,
|
||||
@@ -37,6 +35,7 @@ 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";
|
||||
@@ -61,6 +60,7 @@ 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 } from "./utils";
|
||||
import { Issue } from "../../../components/sheets/github/issue";
|
||||
import { EditorEvents, editorState, openInternalLink } from "./utils";
|
||||
|
||||
|
||||
const publishNote = async () => {
|
||||
const user = useUserStore.getState().user;
|
||||
@@ -270,21 +270,20 @@ export const useEditorEvents = (
|
||||
}, [editor, deviceMode, fullscreen]);
|
||||
|
||||
const onHardwareBackPress = useCallback(() => {
|
||||
if (tabBarRef.current?.page === 2) {
|
||||
console.log(tabBarRef.current?.page());
|
||||
if (tabBarRef.current?.page() === 2) {
|
||||
onBackPress();
|
||||
return true;
|
||||
}
|
||||
}, [onBackPress]);
|
||||
|
||||
const onEnterEditor = useCallback(async () => {
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
if (!DDS.isTab) {
|
||||
handleBack.current = BackHandler.addEventListener(
|
||||
"hardwareBackPress",
|
||||
onHardwareBackPress
|
||||
);
|
||||
}
|
||||
});
|
||||
if (!DDS.isTab) {
|
||||
handleBack.current = BackHandler.addEventListener(
|
||||
"hardwareBackPress",
|
||||
onHardwareBackPress
|
||||
);
|
||||
}
|
||||
}, [onHardwareBackPress]);
|
||||
|
||||
const onClearEditorSessionRequest = useCallback(
|
||||
@@ -331,7 +330,14 @@ 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
|
||||
@@ -339,6 +345,7 @@ export const useEditorEvents = (
|
||||
return () => {
|
||||
eUnSubscribeEvent(eClearEditor, onClearEditorSessionRequest);
|
||||
eUnSubscribeEvent(eOnEnterEditor, onEnterEditor);
|
||||
eUnSubscribeEvent(eOnExitEditor, onExitEditor);
|
||||
};
|
||||
}, [editor.editorId, onClearEditorSessionRequest, onEnterEditor]);
|
||||
|
||||
@@ -443,7 +450,7 @@ export const useEditorEvents = (
|
||||
break;
|
||||
case EventTypes.filepicker:
|
||||
editorState().isAwaitingResult = true;
|
||||
const { pick } = require("./picker.js").default;
|
||||
const { pick } = require("./picker").default;
|
||||
pick({
|
||||
type: editorMessage.value,
|
||||
noteId: noteId,
|
||||
@@ -462,12 +469,10 @@ export const useEditorEvents = (
|
||||
|
||||
case EventTypes.getAttachmentData: {
|
||||
const attachment = (editorMessage.value as any)
|
||||
.attachment as Attachment;
|
||||
?.attachment as Attachment;
|
||||
|
||||
console.log(
|
||||
"Getting attachment data:",
|
||||
attachment.hash,
|
||||
attachment.type
|
||||
DatabaseLogger.log(
|
||||
`Getting attachment data: ${attachment?.hash} ${attachment?.type}`
|
||||
);
|
||||
downloadAttachment(attachment.hash, true, {
|
||||
base64: attachment.type === "image",
|
||||
@@ -487,8 +492,8 @@ export const useEditorEvents = (
|
||||
data
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
console.log("Error downloading attachment data");
|
||||
.catch((e) => {
|
||||
DatabaseLogger.error(e);
|
||||
editor.postMessage(EditorEvents.attachmentData, {
|
||||
resolverId: (editorMessage.value as any).resolverId,
|
||||
data: undefined
|
||||
@@ -519,27 +524,7 @@ export const useEditorEvents = (
|
||||
break;
|
||||
case EventTypes.link:
|
||||
if (editorMessage.value.startsWith("nn://")) {
|
||||
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
|
||||
});
|
||||
openInternalLink(editorMessage.value);
|
||||
console.log(
|
||||
"Opening note from internal link:",
|
||||
editorMessage.value
|
||||
@@ -662,10 +647,8 @@ export const useEditorEvents = (
|
||||
.updateTab(useTabStore.getState().currentTab, {
|
||||
readonly: false
|
||||
});
|
||||
Navigation.queueRoutesForUpdate();
|
||||
ToastManager.show({
|
||||
heading: "Readonly mode disabled.",
|
||||
type: "success"
|
||||
setTimeout(() => {
|
||||
Navigation.queueRoutesForUpdate();
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,10 +22,15 @@ 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>;
|
||||
@@ -176,3 +181,27 @@ 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
|
||||
});
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import { openEditor } from "../notes/common";
|
||||
|
||||
export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
|
||||
const [notes, loading] = useNotes();
|
||||
|
||||
const isFocused = useNavigationFocus(navigation, {
|
||||
onFocus: (prev) => {
|
||||
Navigation.routeNeedsUpdate(
|
||||
|
||||
@@ -16,40 +16,37 @@ 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,
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import { eSendEvent, eSubscribeEvent } 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 { 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";
|
||||
import { openEditor, setOnFirstSave } from "../notes/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;
|
||||
@@ -59,11 +56,17 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
|
||||
useNavigationFocus(navigation, {
|
||||
onFocus: () => {
|
||||
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
|
||||
if (updateOnFocus.current) {
|
||||
onRequestUpdate();
|
||||
updateOnFocus.current = false;
|
||||
} else {
|
||||
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
|
||||
}
|
||||
syncWithNavigation();
|
||||
return false;
|
||||
},
|
||||
onBlur: () => {
|
||||
updateOnFocus.current = false;
|
||||
setOnFirstSave(null);
|
||||
return false;
|
||||
}
|
||||
@@ -79,6 +82,15 @@ 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(
|
||||
@@ -104,7 +116,7 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
|
||||
if (notebook) {
|
||||
const breadcrumbs = await db.notebooks.breadcrumbs(notebook.id);
|
||||
setBreadcrumbs(breadcrumbs);
|
||||
setBreadcrumbs(breadcrumbs.slice(0, breadcrumbs.length - 1));
|
||||
params.current.item = notebook;
|
||||
const notes = await db.relations
|
||||
.from(notebook, "note")
|
||||
@@ -122,10 +134,10 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
onRequestUpdate();
|
||||
eSubscribeEvent(eUpdateNotebookRoute, onRequestUpdate);
|
||||
onRequestUpdate(params.current);
|
||||
const sub = eSubscribeEvent(eUpdateNotebookRoute, onRequestUpdate);
|
||||
return () => {
|
||||
eUnSubscribeEvent(eUpdateNotebookRoute, onRequestUpdate);
|
||||
sub?.unsubscribe();
|
||||
};
|
||||
}, [onRequestUpdate]);
|
||||
|
||||
@@ -167,7 +179,7 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
onPressDefaultRightButton={openEditor}
|
||||
/>
|
||||
|
||||
{breadcrumbs ? (
|
||||
{breadcrumbs && breadcrumbs.length > 0 ? (
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
|
||||
@@ -81,16 +81,21 @@ const NotesPage = ({
|
||||
route.name === "ColoredNotes"
|
||||
? (params.current?.item as Color)?.colorCode
|
||||
: undefined;
|
||||
|
||||
const updateOnFocus = useRef(false);
|
||||
const isFocused = useNavigationFocus(navigation, {
|
||||
onFocus: (prev) => {
|
||||
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
|
||||
if (updateOnFocus.current) {
|
||||
onRequestUpdate();
|
||||
updateOnFocus.current = false;
|
||||
} else {
|
||||
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
|
||||
}
|
||||
syncWithNavigation();
|
||||
|
||||
if (focusControl) return !prev.current;
|
||||
return false;
|
||||
},
|
||||
onBlur: () => {
|
||||
updateOnFocus.current = false;
|
||||
setOnFirstSave(null);
|
||||
return false;
|
||||
},
|
||||
@@ -99,7 +104,6 @@ const NotesPage = ({
|
||||
|
||||
const syncWithNavigation = React.useCallback(() => {
|
||||
const { item } = params.current;
|
||||
|
||||
useNavigationStore
|
||||
.getState()
|
||||
.setFocusedRouteId(params?.current?.item?.id || route.name);
|
||||
@@ -113,6 +117,14 @@ 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;
|
||||
|
||||
|
||||
@@ -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 { View } from "react-native";
|
||||
import { Platform, 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: 50 + insets.top,
|
||||
paddingTop: insets.top,
|
||||
height: Platform.OS === "android" ? 50 + insets.top : 50,
|
||||
paddingTop: Platform.OS === "ios" ? 0 : insets.top,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
flexShrink: 1,
|
||||
|
||||
@@ -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 BiometicService from "../../services/biometrics";
|
||||
import BiometricService 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 BiometicService.isBiometryAvailable()) &&
|
||||
!(await BiometricService.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 BiometicService.validateUser(
|
||||
const verified = await BiometricService.validateUser(
|
||||
"Verify it's you"
|
||||
);
|
||||
if (verified) {
|
||||
|
||||
@@ -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 BiometicService from "../../services/biometrics";
|
||||
import BiometricService 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 {
|
||||
BiometicService.isBiometryAvailable().then((available) => {
|
||||
BiometricService.isBiometryAvailable().then((available) => {
|
||||
if (available) {
|
||||
BiometicService.validateUser("Verify it's you").then((verified) => {
|
||||
BiometricService.validateUser("Verify it's you").then((verified) => {
|
||||
resolve(verified);
|
||||
});
|
||||
} else if (useUserStore.getState().user) {
|
||||
|
||||
@@ -164,12 +164,6 @@ 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",
|
||||
|
||||
@@ -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 BiometicService from "../../services/biometrics";
|
||||
import BiometricService from "../../services/biometrics";
|
||||
import {
|
||||
ToastManager,
|
||||
eSendEvent,
|
||||
@@ -349,7 +349,7 @@ export const settingsGroups: SettingSection[] = [
|
||||
await db.user?.logout();
|
||||
setLoginMessage();
|
||||
await PremiumService.setPremiumStatus();
|
||||
await BiometicService.resetCredentials();
|
||||
await BiometricService.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 BiometicService.resetCredentials();
|
||||
await BiometricService.resetCredentials();
|
||||
SettingsService.set({
|
||||
introCompleted: true
|
||||
});
|
||||
@@ -789,7 +789,7 @@ export const settingsGroups: SettingSection[] = [
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "biometic-unlock",
|
||||
id: "biometric-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 BiometicService.isBiometryAvailable())
|
||||
(await BiometricService.isBiometryAvailable())
|
||||
) {
|
||||
SettingsService.setProperty("biometricsAuthEnabled", true);
|
||||
}
|
||||
|
||||
if (
|
||||
!(await BiometicService.isBiometryAvailable()) &&
|
||||
!(await BiometricService.isBiometryAvailable()) &&
|
||||
!SettingsService.getProperty("appLockHasPasswordSecurity")
|
||||
) {
|
||||
ToastManager.show({
|
||||
|
||||
@@ -41,7 +41,7 @@ import {
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from "react-native";
|
||||
import { db } from "../../common/database";
|
||||
import { DatabaseLogger, db } from "../../common/database";
|
||||
import SheetProvider from "../../components/sheet-provider";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import Input from "../../components/ui/input";
|
||||
@@ -326,11 +326,10 @@ function ThemeSelector() {
|
||||
return page.themes;
|
||||
})
|
||||
.flat()
|
||||
.filter(
|
||||
(theme) =>
|
||||
(!searchQuery || searchQuery === "") &&
|
||||
darkTheme.id !== theme.id &&
|
||||
lightTheme.id !== theme.id
|
||||
.filter((theme) =>
|
||||
searchQuery && searchQuery !== ""
|
||||
? true
|
||||
: darkTheme.id !== theme.id && lightTheme.id !== theme.id
|
||||
) || []
|
||||
);
|
||||
}
|
||||
@@ -546,7 +545,7 @@ const ThemeSetter = ({
|
||||
context: "global"
|
||||
});
|
||||
} catch (e) {
|
||||
console.log("Error", e);
|
||||
DatabaseLogger.error(e);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -151,13 +151,14 @@ async function updateNextBackupTime() {
|
||||
/**
|
||||
* @param {boolean=} progress
|
||||
* @param {string=} context
|
||||
* @returns {Promise<{path?: string, error?: Error}}>
|
||||
* @returns {Promise<{path?: string, error?: Error, report?: boolean}}>
|
||||
*/
|
||||
async function run(progress = false, context) {
|
||||
let androidBackupDirectory = await checkBackupDirExists(false, context);
|
||||
if (!androidBackupDirectory)
|
||||
return {
|
||||
error: new Error("Backup directory not selected")
|
||||
error: new Error("Backup directory not selected"),
|
||||
report: false
|
||||
};
|
||||
|
||||
if (progress) {
|
||||
@@ -191,9 +192,10 @@ 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
|
||||
SettingsService.get().encryptedBackup && user
|
||||
)) {
|
||||
console.log("Writing backup chunk of size...", file?.data?.length);
|
||||
await RNFetchBlob.fs.writeFile(
|
||||
@@ -252,16 +254,17 @@ async function run(progress = false, context) {
|
||||
} catch (e) {
|
||||
ToastManager.error(e, "Backup failed", context || "global");
|
||||
|
||||
if (e.message.includes("android.net.Uri") && androidBackupDirectory) {
|
||||
if (e?.message?.includes("android.net.Uri") && androidBackupDirectory) {
|
||||
SettingsService.setProperty("backupDirectoryAndroid", null);
|
||||
return run(progress, context);
|
||||
}
|
||||
|
||||
DatabaseLogger.error(e, "Backup failed");
|
||||
DatabaseLogger.error(e);
|
||||
await sleep(300);
|
||||
progress && eSendEvent(eCloseSheet);
|
||||
return {
|
||||
error: e
|
||||
error: e,
|
||||
report: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ async function validateUser(title: string, description?: string) {
|
||||
}
|
||||
}
|
||||
|
||||
const BiometicService = {
|
||||
const BiometricService = {
|
||||
isBiometryAvailable,
|
||||
enableFingerprintAuth,
|
||||
isFingerprintAuthEnabled,
|
||||
@@ -190,4 +190,4 @@ const BiometicService = {
|
||||
validateUser
|
||||
};
|
||||
|
||||
export default BiometicService;
|
||||
export default BiometricService;
|
||||
|
||||
@@ -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 BiometicService from "./biometrics";
|
||||
import BiometricService 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 BiometicService.isBiometryAvailable();
|
||||
const fingerprint = await BiometicService.hasInternetCredentials();
|
||||
const biometry = await BiometricService.isBiometryAvailable();
|
||||
const fingerprint = await BiometricService.hasInternetCredentials();
|
||||
if (biometry && fingerprint) {
|
||||
const credentials = await BiometicService.getCredentials(
|
||||
const credentials = await BiometricService.getCredentials(
|
||||
"Unlock vault",
|
||||
"Unlock vault to export locked notes"
|
||||
);
|
||||
|
||||
@@ -49,6 +49,8 @@ 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[] = [];
|
||||
|
||||
@@ -139,6 +141,7 @@ const onEvent = async ({ type, detail }: Event) => {
|
||||
}
|
||||
editorState().movedAway = false;
|
||||
const noteId = notification?.id;
|
||||
console.log("NOTE ID", noteId);
|
||||
loadNote(noteId as string, true);
|
||||
}
|
||||
|
||||
@@ -407,18 +410,31 @@ async function loadNote(id: string, jump: boolean) {
|
||||
if (!DDS.isTab && jump) {
|
||||
tabBarRef.current?.goToPage(1);
|
||||
}
|
||||
eSendEvent("loadingNote", note);
|
||||
setTimeout(
|
||||
() => {
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: note
|
||||
});
|
||||
if (!jump && !DDS.isTab) {
|
||||
tabBarRef.current?.goToPage(1);
|
||||
}
|
||||
},
|
||||
tabBarRef?.current ? 0 : 2000
|
||||
|
||||
MMKV.setString(
|
||||
"appState",
|
||||
JSON.stringify({
|
||||
editing: true,
|
||||
movedAway: false,
|
||||
timestamp: Date.now()
|
||||
})
|
||||
);
|
||||
|
||||
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") {
|
||||
@@ -476,7 +492,15 @@ async function displayNotification({
|
||||
reply_button_text?: string;
|
||||
id?: string;
|
||||
}) {
|
||||
if (!(await checkAndRequestPermissions())) return;
|
||||
useUserStore.setState({
|
||||
disableAppLockRequests: true
|
||||
});
|
||||
const permission = await checkAndRequestPermissions();
|
||||
useUserStore.setState({
|
||||
disableAppLockRequests: false
|
||||
});
|
||||
|
||||
if (!permission) return;
|
||||
|
||||
try {
|
||||
await notifee.displayNotification({
|
||||
@@ -846,7 +870,16 @@ async function remove(id: string) {
|
||||
}
|
||||
|
||||
async function pinQuickNote(launch: boolean) {
|
||||
if (!(await checkAndRequestPermissions())) return;
|
||||
useUserStore.setState({
|
||||
disableAppLockRequests: true
|
||||
});
|
||||
const permission = await checkAndRequestPermissions();
|
||||
useUserStore.setState({
|
||||
disableAppLockRequests: false
|
||||
});
|
||||
if (!permission) {
|
||||
return;
|
||||
}
|
||||
get().then((items) => {
|
||||
const notification = items.filter((n) => n.id === "notesnook_note_input");
|
||||
if (notification && launch) {
|
||||
|
||||
@@ -72,7 +72,7 @@ export const useMenuStore = create<MenuStore>((set, get) => ({
|
||||
section as SideBarSection
|
||||
);
|
||||
hiddenItems[section as SideBarHideableSection] =
|
||||
db.settings.getSideBarHiddenItems("colors");
|
||||
db.settings.getSideBarHiddenItems(section as SideBarHideableSection);
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -80,6 +80,7 @@ export const useMenuStore = create<MenuStore>((set, get) => ({
|
||||
JSON.stringify(get().hiddenItems || {}) !==
|
||||
JSON.stringify(hiddenItems || {})
|
||||
) {
|
||||
console.log(order, hiddenItems);
|
||||
set({
|
||||
order: order,
|
||||
hiddenItems: hiddenItems
|
||||
|
||||
@@ -173,3 +173,4 @@ export const eUnlockWithBiometrics = "618";
|
||||
export const eUnlockWithPassword = "619";
|
||||
export const eUpdateNoteInEditor = "620";
|
||||
export const eOnEnterEditor = "621";
|
||||
export const eOnExitEditor = "622";
|
||||
|
||||
@@ -111,7 +111,7 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
multiDexEnabled true
|
||||
versionCode 3008
|
||||
versionCode 3010
|
||||
versionName getNpmVersion()
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
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.
|
||||
- Bug fixes and performance improvements
|
||||
|
||||
Check out our blogpost to learn about what's new at https://blog.notesnook.com/introducing-notesnook-v3.
|
||||
Thank you for using Notesnook!
|
||||
@@ -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 /* (null) in Resources */ = {isa = PBXBuildFile; };
|
||||
656DD2AC2B1891DF00A362EA /* (null) in Resources */ = {isa = PBXBuildFile; };
|
||||
656DD2AD2B1891DF00A362EA /* (null) in Resources */ = {isa = PBXBuildFile; };
|
||||
656DD2AB2B1891DF00A362EA /* BuildFile in Resources */ = {isa = PBXBuildFile; };
|
||||
656DD2AC2B1891DF00A362EA /* BuildFile in Resources */ = {isa = PBXBuildFile; };
|
||||
656DD2AD2B1891DF00A362EA /* BuildFile 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 /* (null) in Resources */,
|
||||
656DD2AC2B1891DF00A362EA /* (null) in Resources */,
|
||||
656DD2AD2B1891DF00A362EA /* (null) in Resources */,
|
||||
656DD2AB2B1891DF00A362EA /* BuildFile in Resources */,
|
||||
656DD2AC2B1891DF00A362EA /* BuildFile in Resources */,
|
||||
656DD2AD2B1891DF00A362EA /* BuildFile 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 = 2097;
|
||||
CURRENT_PROJECT_VERSION = 2099;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
ENABLE_BITCODE = NO;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1089,7 +1089,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.0;
|
||||
MARKETING_VERSION = 3.0.2;
|
||||
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 = 2097;
|
||||
CURRENT_PROJECT_VERSION = 2099;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1194,7 +1194,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.0;
|
||||
MARKETING_VERSION = 3.0.2;
|
||||
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 = 2097;
|
||||
CURRENT_PROJECT_VERSION = 2099;
|
||||
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.0;
|
||||
MARKETING_VERSION = 3.0.2;
|
||||
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 = 2097;
|
||||
CURRENT_PROJECT_VERSION = 2099;
|
||||
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.0;
|
||||
MARKETING_VERSION = 3.0.2;
|
||||
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 = 2097;
|
||||
CURRENT_PROJECT_VERSION = 2099;
|
||||
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.0;
|
||||
MARKETING_VERSION = 3.0.2;
|
||||
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 = 2097;
|
||||
CURRENT_PROJECT_VERSION = 2099;
|
||||
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.0;
|
||||
MARKETING_VERSION = 3.0.2;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
||||
@@ -319,8 +319,6 @@ 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):
|
||||
@@ -345,7 +343,7 @@ PODS:
|
||||
- React-Core
|
||||
- react-native-safe-area-context (4.9.0):
|
||||
- React-Core
|
||||
- react-native-share-extension (2.5.6):
|
||||
- react-native-share-extension (3.0.0):
|
||||
- React
|
||||
- react-native-sodium (1.5.4):
|
||||
- React
|
||||
@@ -597,7 +595,6 @@ 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`)"
|
||||
@@ -736,8 +733,6 @@ 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:
|
||||
@@ -899,7 +894,6 @@ 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
|
||||
@@ -910,7 +904,7 @@ SPEC CHECKSUMS:
|
||||
react-native-pdf: 33c622cbdf776a649929e8b9d1ce2d313347c4fa
|
||||
react-native-quick-sqlite: e0e23b749382a85e4b57146f753de737a6c3a9e1
|
||||
react-native-safe-area-context: b97eb6f9e3b7f437806c2ce5983f479f8eb5de4b
|
||||
react-native-share-extension: df66a2ee48a62277d79898375e2142bde0782063
|
||||
react-native-share-extension: faed334b1ddf165f1e576fcabd3dc1c9e748bfa9
|
||||
react-native-sodium: 955bb0dc3ea05f8ea06d5e96cb89d1be7b5d7681
|
||||
react-native-theme-switch-animation: 220f883f7be290e79f2ab022093ed1a7a5929e6d
|
||||
react-native-webview: 9f111dfbcfc826084d6c507f569e5e03342ee1c1
|
||||
@@ -964,4 +958,4 @@ SPEC CHECKSUMS:
|
||||
|
||||
PODFILE CHECKSUM: 2b8b28a341b202bf3ca5f231b75bb05893486ed8
|
||||
|
||||
COCOAPODS: 1.12.1
|
||||
COCOAPODS: 1.14.2
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -6,7 +6,7 @@
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@ammarahmed/notifee-react-native": "7.4.7",
|
||||
"@ammarahmed/react-native-share-extension": "^2.5.9",
|
||||
"@ammarahmed/react-native-share-extension": "^3.0.0",
|
||||
"@ammarahmed/react-native-sodium": "1.5.4",
|
||||
"@bam.tech/react-native-image-resizer": "3.0.5",
|
||||
"@callstack/repack": "^3.2.0",
|
||||
@@ -41,7 +41,6 @@
|
||||
"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",
|
||||
|
||||
115
apps/mobile/package-lock.json
generated
115
apps/mobile/package-lock.json
generated
@@ -7162,7 +7162,7 @@
|
||||
},
|
||||
"../../packages/editor-mobile/node_modules/@types/prop-types": {
|
||||
"version": "15.7.11",
|
||||
"dev": true,
|
||||
"devOptional": 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",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
@@ -7213,7 +7213,7 @@
|
||||
},
|
||||
"../../packages/editor-mobile/node_modules/@types/scheduler": {
|
||||
"version": "0.16.8",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"../../packages/editor-mobile/node_modules/@types/semver": {
|
||||
@@ -12071,7 +12071,7 @@
|
||||
},
|
||||
"../../packages/editor-mobile/node_modules/immer": {
|
||||
"version": "9.0.21",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@@ -22460,7 +22460,6 @@
|
||||
},
|
||||
"../../packages/editor/node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"../../packages/editor/node_modules/json-parse-even-better-errors": {
|
||||
@@ -22516,7 +22515,6 @@
|
||||
},
|
||||
"../../packages/editor/node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
@@ -23038,7 +23036,6 @@
|
||||
},
|
||||
"../../packages/editor/node_modules/react": {
|
||||
"version": "18.2.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
@@ -23057,7 +23054,6 @@
|
||||
},
|
||||
"../../packages/editor/node_modules/react-dom": {
|
||||
"version": "18.2.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
@@ -23177,7 +23173,6 @@
|
||||
},
|
||||
"../../packages/editor/node_modules/scheduler": {
|
||||
"version": "0.23.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
@@ -28443,7 +28438,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": "^2.5.9",
|
||||
"@ammarahmed/react-native-share-extension": "^3.0.0",
|
||||
"@ammarahmed/react-native-sodium": "1.5.4",
|
||||
"@bam.tech/react-native-image-resizer": "3.0.5",
|
||||
"@callstack/repack": "^3.2.0",
|
||||
@@ -28478,7 +28473,6 @@
|
||||
"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",
|
||||
@@ -28596,9 +28590,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@ammarahmed/react-native-share-extension": {
|
||||
"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==",
|
||||
"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==",
|
||||
"dependencies": {
|
||||
"react-native": "^0.63.1"
|
||||
}
|
||||
@@ -30896,7 +30890,6 @@
|
||||
},
|
||||
"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"
|
||||
@@ -30910,7 +30903,6 @@
|
||||
},
|
||||
"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",
|
||||
@@ -31057,7 +31049,6 @@
|
||||
},
|
||||
"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"
|
||||
@@ -31068,7 +31059,6 @@
|
||||
},
|
||||
"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",
|
||||
@@ -31114,7 +31104,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-syntax-class-static-block": {
|
||||
"version": "7.14.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.14.5"
|
||||
@@ -31151,7 +31140,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-syntax-export-namespace-from": {
|
||||
"version": "7.8.3",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.8.3"
|
||||
@@ -31175,7 +31163,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-syntax-import-assertions": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
@@ -31189,7 +31176,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-syntax-import-attributes": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
@@ -31203,7 +31189,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-syntax-import-meta": {
|
||||
"version": "7.10.4",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.10.4"
|
||||
@@ -31214,7 +31199,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-syntax-json-strings": {
|
||||
"version": "7.8.3",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.8.0"
|
||||
@@ -31238,7 +31222,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-syntax-logical-assignment-operators": {
|
||||
"version": "7.10.4",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.10.4"
|
||||
@@ -31299,7 +31282,6 @@
|
||||
},
|
||||
"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"
|
||||
@@ -31313,7 +31295,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-syntax-top-level-await": {
|
||||
"version": "7.14.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.14.5"
|
||||
@@ -31340,7 +31321,6 @@
|
||||
},
|
||||
"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",
|
||||
@@ -31368,7 +31348,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-async-generator-functions": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-environment-visitor": "^7.22.5",
|
||||
@@ -31426,7 +31405,6 @@
|
||||
},
|
||||
"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",
|
||||
@@ -31441,7 +31419,6 @@
|
||||
},
|
||||
"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",
|
||||
@@ -31505,7 +31482,6 @@
|
||||
},
|
||||
"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",
|
||||
@@ -31520,7 +31496,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-duplicate-keys": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
@@ -31534,7 +31509,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-dynamic-import": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
@@ -31563,7 +31537,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-export-namespace-from": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
@@ -31620,7 +31593,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-json-strings": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
@@ -31648,7 +31620,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-logical-assignment-operators": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
@@ -31676,7 +31647,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-modules-amd": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-module-transforms": "^7.22.5",
|
||||
@@ -31706,7 +31676,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-modules-systemjs": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-hoist-variables": "^7.22.5",
|
||||
@@ -31723,7 +31692,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-modules-umd": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-module-transforms": "^7.22.5",
|
||||
@@ -31752,7 +31720,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-new-target": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
@@ -31766,7 +31733,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-nullish-coalescing-operator": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
@@ -31781,7 +31747,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-numeric-separator": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
@@ -31809,7 +31774,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-object-rest-spread": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/compat-data": "^7.22.5",
|
||||
@@ -31841,7 +31805,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-optional-catch-binding": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
@@ -31856,7 +31819,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-optional-chaining": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
@@ -31885,7 +31847,6 @@
|
||||
},
|
||||
"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",
|
||||
@@ -31900,7 +31861,6 @@
|
||||
},
|
||||
"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",
|
||||
@@ -32000,7 +31960,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-reserved-words": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
@@ -32092,7 +32051,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-typeof-symbol": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
@@ -32122,7 +32080,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-unicode-escapes": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
@@ -32136,7 +32093,6 @@
|
||||
},
|
||||
"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",
|
||||
@@ -32165,7 +32121,6 @@
|
||||
},
|
||||
"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",
|
||||
@@ -32180,7 +32135,6 @@
|
||||
},
|
||||
"node_modules/@babel/preset-env": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/compat-data": "^7.22.5",
|
||||
@@ -32273,7 +32227,6 @@
|
||||
},
|
||||
"node_modules/@babel/preset-env/node_modules/semver": {
|
||||
"version": "6.3.0",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
@@ -32296,7 +32249,6 @@
|
||||
},
|
||||
"node_modules/@babel/preset-modules": {
|
||||
"version": "0.1.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.0.0",
|
||||
@@ -34946,7 +34898,6 @@
|
||||
},
|
||||
"node_modules/@types/eslint": {
|
||||
"version": "8.40.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "*",
|
||||
@@ -34955,7 +34906,6 @@
|
||||
},
|
||||
"node_modules/@types/eslint-scope": {
|
||||
"version": "3.7.4",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/eslint": "*",
|
||||
@@ -34964,7 +34914,6 @@
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.1",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/graceful-fs": {
|
||||
@@ -35413,7 +35362,6 @@
|
||||
},
|
||||
"node_modules/@webassemblyjs/ast": {
|
||||
"version": "1.11.6",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/helper-numbers": "1.11.6",
|
||||
@@ -35422,22 +35370,18 @@
|
||||
},
|
||||
"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",
|
||||
@@ -35447,12 +35391,10 @@
|
||||
},
|
||||
"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",
|
||||
@@ -35463,7 +35405,6 @@
|
||||
},
|
||||
"node_modules/@webassemblyjs/ieee754": {
|
||||
"version": "1.11.6",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@xtuc/ieee754": "^1.2.0"
|
||||
@@ -35471,7 +35412,6 @@
|
||||
},
|
||||
"node_modules/@webassemblyjs/leb128": {
|
||||
"version": "1.11.6",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@xtuc/long": "4.2.2"
|
||||
@@ -35479,12 +35419,10 @@
|
||||
},
|
||||
"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",
|
||||
@@ -35499,7 +35437,6 @@
|
||||
},
|
||||
"node_modules/@webassemblyjs/wasm-gen": {
|
||||
"version": "1.11.6",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.11.6",
|
||||
@@ -35511,7 +35448,6 @@
|
||||
},
|
||||
"node_modules/@webassemblyjs/wasm-opt": {
|
||||
"version": "1.11.6",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.11.6",
|
||||
@@ -35522,7 +35458,6 @@
|
||||
},
|
||||
"node_modules/@webassemblyjs/wasm-parser": {
|
||||
"version": "1.11.6",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.11.6",
|
||||
@@ -35535,7 +35470,6 @@
|
||||
},
|
||||
"node_modules/@webassemblyjs/wast-printer": {
|
||||
"version": "1.11.6",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.11.6",
|
||||
@@ -35593,12 +35527,10 @@
|
||||
},
|
||||
"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": {
|
||||
@@ -35654,7 +35586,6 @@
|
||||
},
|
||||
"node_modules/acorn-import-assertions": {
|
||||
"version": "1.9.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"acorn": "^8"
|
||||
@@ -36759,7 +36690,6 @@
|
||||
},
|
||||
"node_modules/chrome-trace-event": {
|
||||
"version": "1.0.3",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
@@ -37860,7 +37790,6 @@
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.15.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.4",
|
||||
@@ -37973,7 +37902,6 @@
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "1.3.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
@@ -38304,7 +38232,6 @@
|
||||
},
|
||||
"node_modules/eslint-scope": {
|
||||
"version": "5.1.1",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"esrecurse": "^4.3.0",
|
||||
@@ -38316,7 +38243,6 @@
|
||||
},
|
||||
"node_modules/eslint-scope/node_modules/estraverse": {
|
||||
"version": "4.3.0",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
@@ -38502,7 +38428,6 @@
|
||||
},
|
||||
"node_modules/esrecurse": {
|
||||
"version": "4.3.0",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"estraverse": "^5.2.0"
|
||||
@@ -38513,7 +38438,6 @@
|
||||
},
|
||||
"node_modules/estraverse": {
|
||||
"version": "5.3.0",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
@@ -38521,7 +38445,6 @@
|
||||
},
|
||||
"node_modules/esutils": {
|
||||
"version": "2.0.3",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -39578,7 +39501,6 @@
|
||||
},
|
||||
"node_modules/glob-to-regexp": {
|
||||
"version": "0.4.1",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/global": {
|
||||
@@ -41822,7 +41744,6 @@
|
||||
},
|
||||
"node_modules/json-parse-even-better-errors": {
|
||||
"version": "2.3.1",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
@@ -42090,7 +42011,6 @@
|
||||
},
|
||||
"node_modules/loader-runner": {
|
||||
"version": "4.3.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.11.5"
|
||||
@@ -44742,7 +44662,6 @@
|
||||
},
|
||||
"node_modules/randombytes": {
|
||||
"version": "2.1.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.1.0"
|
||||
@@ -45141,14 +45060,6 @@
|
||||
"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",
|
||||
@@ -45521,7 +45432,6 @@
|
||||
},
|
||||
"node_modules/react-test-renderer": {
|
||||
"version": "18.2.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-is": "^18.2.0",
|
||||
@@ -45534,12 +45444,10 @@
|
||||
},
|
||||
"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"
|
||||
@@ -46375,7 +46283,6 @@
|
||||
},
|
||||
"node_modules/serialize-javascript": {
|
||||
"version": "6.0.1",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"randombytes": "^2.1.0"
|
||||
@@ -47431,7 +47338,6 @@
|
||||
},
|
||||
"node_modules/terser-webpack-plugin": {
|
||||
"version": "5.3.9",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "^0.3.17",
|
||||
@@ -47464,7 +47370,6 @@
|
||||
},
|
||||
"node_modules/terser-webpack-plugin/node_modules/jest-worker": {
|
||||
"version": "27.5.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
@@ -47477,7 +47382,6 @@
|
||||
},
|
||||
"node_modules/terser-webpack-plugin/node_modules/supports-color": {
|
||||
"version": "8.1.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
@@ -48222,7 +48126,6 @@
|
||||
},
|
||||
"node_modules/watchpack": {
|
||||
"version": "2.4.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"glob-to-regexp": "^0.4.1",
|
||||
@@ -48245,7 +48148,6 @@
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
"version": "5.88.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/eslint-scope": "^3.7.3",
|
||||
@@ -48360,7 +48262,6 @@
|
||||
},
|
||||
"node_modules/webpack-sources": {
|
||||
"version": "3.2.3",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.0.0",
|
||||
"version": "3.0.2",
|
||||
"private": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"workspaces": [
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
useWindowDimensions
|
||||
@@ -115,12 +116,14 @@ 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();
|
||||
@@ -192,6 +195,7 @@ const ShareView = () => {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let note = { ...defaultNote };
|
||||
for (let item of data) {
|
||||
if (item.type === "text") {
|
||||
@@ -207,7 +211,14 @@ const ShareView = () => {
|
||||
if (!key) continue;
|
||||
if (key.includes("TITLE") || key.includes("SUBJECT")) {
|
||||
note.title = item[key];
|
||||
console.log("Note title will be", note.title);
|
||||
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];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -216,8 +227,9 @@ 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(
|
||||
@@ -244,6 +256,7 @@ const ShareView = () => {
|
||||
);
|
||||
|
||||
const onLoad = useCallback(() => {
|
||||
console.log(noteContent.current, "current...");
|
||||
eSendEvent(eOnLoadNote + "shareEditor", {
|
||||
id: null,
|
||||
content: {
|
||||
@@ -275,7 +288,7 @@ const ShareView = () => {
|
||||
|
||||
const onPress = async () => {
|
||||
setLoading(true);
|
||||
if (!noteContent.current && rawFiles.length === 0) {
|
||||
if (!noteContent.current && rawFiles.length === 0 && !noteTitle.current) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
@@ -298,12 +311,14 @@ const ShareView = () => {
|
||||
type: "tiptap"
|
||||
},
|
||||
id: note.id,
|
||||
sessionId: Date.now()
|
||||
sessionId: Date.now(),
|
||||
title: noteTitle.current
|
||||
};
|
||||
} else {
|
||||
noteData = { ...note };
|
||||
noteData.content.data = noteContent.current;
|
||||
noteData.sessionId = Date.now();
|
||||
noteData.title = noteTitle.current;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -468,10 +483,29 @@ const ShareView = () => {
|
||||
borderBottomWidth: 1,
|
||||
paddingBottom: 12,
|
||||
borderBottomColor: colors.secondary.background,
|
||||
paddingHorizontal: 12
|
||||
paddingHorizontal: 12,
|
||||
gap: 10
|
||||
}}
|
||||
>
|
||||
<Heading size={SIZE.lg}>Save to Notesnook</Heading>
|
||||
<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();
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
title="Done"
|
||||
type="accent"
|
||||
@@ -626,13 +660,15 @@ 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 ? (
|
||||
@@ -646,7 +682,19 @@ const ShareView = () => {
|
||||
</SafeAreaProvider>
|
||||
</View>
|
||||
|
||||
{appendNoteId ? <AppendNote id={appendNoteId} /> : null}
|
||||
{appendNoteId ? (
|
||||
<AppendNote
|
||||
id={appendNoteId}
|
||||
onLoad={(title) => {
|
||||
if (!noteTitle.current) {
|
||||
noteTitle.current = title;
|
||||
inputRef.current?.setNativeProps?.({
|
||||
text: noteTitle.current
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<View
|
||||
style={{
|
||||
@@ -766,10 +814,16 @@ const ShareView = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const AppendNote = ({ id }) => {
|
||||
const AppendNote = ({ id, onLoad }) => {
|
||||
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}
|
||||
|
||||
4
apps/web/package-lock.json
generated
4
apps/web/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/web",
|
||||
"version": "3.0.0",
|
||||
"version": "3.0.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/web",
|
||||
"version": "3.0.0",
|
||||
"version": "3.0.2",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@notesnook/web",
|
||||
"description": "Your private note taking space",
|
||||
"version": "3.0.0",
|
||||
"version": "3.0.2",
|
||||
"private": true,
|
||||
"main": "./src/app.js",
|
||||
"homepage": "https://notesnook.com/",
|
||||
|
||||
@@ -142,8 +142,13 @@ function DesktopAppContents({
|
||||
}, [show]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isFocusMode) navPane.current?.collapse();
|
||||
else navPane.current?.expand();
|
||||
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();
|
||||
}, [isFocusMode]);
|
||||
|
||||
return (
|
||||
@@ -159,9 +164,10 @@ function DesktopAppContents({
|
||||
ref={navPane}
|
||||
className="nav-pane"
|
||||
defaultSize={10}
|
||||
minSize={3}
|
||||
onResize={(size) => setIsNarrow(size <= 3)}
|
||||
minSize={3.5}
|
||||
onResize={(size) => setIsNarrow(size <= 5)}
|
||||
collapsible
|
||||
collapsedSize={3.5}
|
||||
>
|
||||
<NavigationMenu
|
||||
toggleNavigationContainer={(state) => {
|
||||
@@ -192,7 +198,7 @@ function DesktopAppContents({
|
||||
</ScopedThemeProvider>
|
||||
</Panel>
|
||||
<PanelResizeHandle className="panel-resize-handle" />
|
||||
<Panel className="editor-pane">
|
||||
<Panel className="editor-pane" defaultSize={70}>
|
||||
<Flex
|
||||
sx={{
|
||||
display: "flex",
|
||||
|
||||
@@ -18,12 +18,16 @@ 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) return;
|
||||
if (!attachment) {
|
||||
logger.debug("could not find attachment for download", { hash, groupId });
|
||||
return;
|
||||
}
|
||||
const downloadResult = await db
|
||||
.fs()
|
||||
.downloadFile(
|
||||
@@ -40,19 +44,27 @@ async function download(hash: string, groupId?: string) {
|
||||
}
|
||||
|
||||
export async function saveAttachment(hash: string) {
|
||||
const response = await download(hash);
|
||||
if (!response) return;
|
||||
try {
|
||||
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
|
||||
})
|
||||
);
|
||||
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})`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type OutputTypeToReturnType = {
|
||||
@@ -68,6 +80,7 @@ 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;
|
||||
@@ -85,6 +98,7 @@ export async function downloadAttachment<
|
||||
isUploaded: !!attachment.dateUploaded
|
||||
})
|
||||
);
|
||||
logger.debug("Attachment decrypted", { hash });
|
||||
|
||||
if (!blob) return;
|
||||
return blob as TOutputType;
|
||||
@@ -106,9 +120,12 @@ export async function checkAttachment(hash: string) {
|
||||
import("../interfaces/fs"),
|
||||
({ getUploadedFileSize }) => getUploadedFileSize(hash)
|
||||
);
|
||||
if (size <= 0) return { failed: "File length is 0." };
|
||||
if (size === 0) throw new Error("File length is 0.");
|
||||
else if (size === -1) throw new Error("File verification check failed.");
|
||||
} catch (e) {
|
||||
return { failed: e instanceof Error ? e.message : "Unknown error." };
|
||||
const reason = e instanceof Error ? e.message : "Unknown error.";
|
||||
await db.attachments.markAsFailed(attachment.id, reason);
|
||||
return { failed: reason };
|
||||
}
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
@@ -77,18 +77,18 @@ export async function introduceFeatures() {
|
||||
|
||||
export const DEFAULT_CONTEXT = { colors: [], tags: [], notebook: {} };
|
||||
|
||||
export async function createBackup() {
|
||||
export async function createBackup(rescueMode = false) {
|
||||
const { isLoggedIn } = useUserStore.getState();
|
||||
const { encryptBackups, toggleEncryptBackups } = useSettingStore.getState();
|
||||
if (!isLoggedIn && encryptBackups) toggleEncryptBackups();
|
||||
|
||||
const verified = encryptBackups || (await verifyAccount());
|
||||
const verified = rescueMode || encryptBackups || (await verifyAccount());
|
||||
if (!verified) {
|
||||
showToast("error", "Could not create a backup: user verification failed.");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const encryptedBackups = isLoggedIn && encryptBackups;
|
||||
const encryptedBackups = !rescueMode && isLoggedIn && encryptBackups;
|
||||
|
||||
const filename = sanitizeFilename(
|
||||
`${formatDate(Date.now(), {
|
||||
@@ -139,7 +139,9 @@ export async function createBackup() {
|
||||
console.error(error);
|
||||
} else {
|
||||
showToast("success", `Backup saved at ${filePath}.`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function selectBackupFile() {
|
||||
@@ -193,7 +195,10 @@ export async function restoreBackupFile(backupFile: File) {
|
||||
}
|
||||
entries.push(entry);
|
||||
}
|
||||
if (!isValid) throw new Error("Invalid backup.");
|
||||
if (!isValid)
|
||||
console.warn(
|
||||
"The backup file does not contain the verification .nnbackup file."
|
||||
);
|
||||
|
||||
await db.transaction(async () => {
|
||||
for (const entry of entries) {
|
||||
|
||||
@@ -121,7 +121,7 @@ function Header(props: HeaderProps) {
|
||||
}}
|
||||
defaultItems={() =>
|
||||
db.tags.all.limit(10).items(undefined, {
|
||||
sortBy: "dateCreated",
|
||||
sortBy: "title",
|
||||
sortDirection: "desc"
|
||||
})
|
||||
}
|
||||
|
||||
@@ -488,12 +488,25 @@ export function Editor(props: EditorProps) {
|
||||
editor?.attachFile(attachment);
|
||||
}
|
||||
}}
|
||||
onGetAttachmentData={(attachment) => {
|
||||
return downloadAttachment(
|
||||
onGetAttachmentData={async (attachment) => {
|
||||
logger.debug("Getting attachment data", {
|
||||
hash: attachment.hash,
|
||||
type: attachment.type
|
||||
});
|
||||
|
||||
const result = await 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;
|
||||
|
||||
@@ -189,17 +189,21 @@ 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,
|
||||
|
||||
@@ -46,6 +46,7 @@ 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;
|
||||
@@ -100,7 +101,7 @@ function TableOfContents(props: TableOfContentsProps) {
|
||||
display: "flex",
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
top: 0,
|
||||
top: TITLE_BAR_HEIGHT,
|
||||
zIndex: 999,
|
||||
height: "100%",
|
||||
width: "300px",
|
||||
|
||||
@@ -24,7 +24,6 @@ import "@notesnook/editor/styles/fonts.css";
|
||||
import {
|
||||
Toolbar,
|
||||
useTiptap,
|
||||
PortalProvider,
|
||||
Editor,
|
||||
AttachmentType,
|
||||
usePermissionHandler,
|
||||
@@ -41,7 +40,6 @@ import {
|
||||
import { Box, Flex } from "@theme-ui/components";
|
||||
import {
|
||||
PropsWithChildren,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
@@ -61,7 +59,6 @@ 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,
|
||||
@@ -402,58 +399,56 @@ function TiptapWrapper(
|
||||
}, [theme]);
|
||||
|
||||
return (
|
||||
<PortalProvider>
|
||||
<Flex
|
||||
ref={containerRef}
|
||||
sx={{
|
||||
flex: 1,
|
||||
flexDirection: "column",
|
||||
".tiptap.ProseMirror": { pb: 150 }
|
||||
<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();
|
||||
}}
|
||||
>
|
||||
<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}
|
||||
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}
|
||||
/>
|
||||
{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>
|
||||
<Skeleton
|
||||
enableAnimation={false}
|
||||
height={22}
|
||||
width={25}
|
||||
style={{ marginTop: 16 }}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
export default TiptapWrapper;
|
||||
|
||||
@@ -29,6 +29,8 @@ 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;
|
||||
@@ -337,7 +339,12 @@ export class Lightbox extends React.Component<LightboxProps> {
|
||||
borderRadius: "0px 0px 0px 5px",
|
||||
overflow: "hidden",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end"
|
||||
justifyContent: "flex-end",
|
||||
height: IS_DESKTOP_APP ? TITLE_BAR_HEIGHT : "auto",
|
||||
pr:
|
||||
IS_DESKTOP_APP && getPlatform() !== "darwin"
|
||||
? "calc(100vw - env(titlebar-area-width))"
|
||||
: 0
|
||||
}}
|
||||
>
|
||||
{tools.map((tool) => (
|
||||
@@ -349,6 +356,7 @@ export class Lightbox extends React.Component<LightboxProps> {
|
||||
bg="transparent"
|
||||
title={tool.title}
|
||||
sx={{
|
||||
height: "100%",
|
||||
borderRadius: 0,
|
||||
display: [
|
||||
tool.hideOnMobile ? "none" : "flex",
|
||||
|
||||
@@ -106,7 +106,7 @@ function NavigationItem(
|
||||
<Button
|
||||
data-test-id={`navigation-item`}
|
||||
sx={{
|
||||
px: 2,
|
||||
px: isTablet ? 1 : 2,
|
||||
flex: 1,
|
||||
alignItems: "center",
|
||||
justifyContent: isTablet ? "center" : "flex-start",
|
||||
@@ -124,7 +124,10 @@ function NavigationItem(
|
||||
}}
|
||||
>
|
||||
{image ? (
|
||||
<Image src={image} sx={{ borderRadius: 50, size: 20 }} />
|
||||
<Image
|
||||
src={image}
|
||||
sx={{ borderRadius: 50, size: 20, minWidth: 20, flexShrink: 0 }}
|
||||
/>
|
||||
) : Icon ? (
|
||||
<Icon
|
||||
size={isTablet ? 16 : 15}
|
||||
|
||||
@@ -282,7 +282,8 @@ 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 === nextProps.attachments &&
|
||||
prevProps.attachments?.failed === nextProps.attachments?.failed &&
|
||||
prevProps.attachments?.total === nextProps.attachments?.total &&
|
||||
prevProps.locked === nextProps.locked
|
||||
);
|
||||
});
|
||||
|
||||
@@ -61,6 +61,7 @@ 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" },
|
||||
@@ -129,7 +130,7 @@ function EditorProperties(props: EditorPropertiesProps) {
|
||||
sx={{
|
||||
display: "flex",
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
top: TITLE_BAR_HEIGHT,
|
||||
right: 0,
|
||||
zIndex: 999,
|
||||
height: "100%",
|
||||
|
||||
@@ -56,6 +56,7 @@ function Toggle(props: ToggleProps) {
|
||||
<Switch
|
||||
sx={{ m: 0, bg: isOn ? "accent" : "icon-secondary" }}
|
||||
checked={isOn}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -29,6 +29,7 @@ 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();
|
||||
@@ -65,7 +66,7 @@ export function TitleBar() {
|
||||
scope="titleBar"
|
||||
sx={{
|
||||
background: "background",
|
||||
height: 37.8,
|
||||
height: TITLE_BAR_HEIGHT,
|
||||
display: "flex",
|
||||
borderBottom: "1px solid var(--border)",
|
||||
...(!isFullscreen && hasNativeWindowControls
|
||||
|
||||
@@ -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 } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { Button, Flex, Text } from "@theme-ui/components";
|
||||
import {
|
||||
Plus,
|
||||
@@ -46,7 +46,8 @@ import {
|
||||
TreeEnvironmentRef
|
||||
} from "react-complex-tree";
|
||||
import { FlexScrollContainer } from "../components/scroll-container";
|
||||
import { pluralize, usePromise } from "@notesnook/common";
|
||||
import { pluralize } from "@notesnook/common";
|
||||
import Field from "../components/field";
|
||||
|
||||
type MoveDialogProps = { onClose: Perform; noteIds: string[] };
|
||||
type NotebookReference = {
|
||||
@@ -73,32 +74,17 @@ 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 rootNotebooks = usePromise(() =>
|
||||
db.notebooks.roots.ids(db.settings.getGroupOptions("notebooks"))
|
||||
);
|
||||
const [notebooks, setNotebooks] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
// 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
|
||||
// // });
|
||||
// // }
|
||||
// // }
|
||||
// }
|
||||
db.notebooks.roots
|
||||
.ids(db.settings.getGroupOptions("notebooks"))
|
||||
.then((ids) => setNotebooks(ids));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
(async function () {
|
||||
const selected: NotebookReference[] = useSelectionStore
|
||||
.getState()
|
||||
@@ -123,20 +109,6 @@ 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(
|
||||
@@ -155,7 +127,7 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
|
||||
title={"Select notebooks"}
|
||||
description={`Use ${
|
||||
isMac() ? "cmd" : "ctrl"
|
||||
}+click to select multiple topics`}
|
||||
}+click to select multiple notebooks`}
|
||||
onClose={() => _onClose(false)}
|
||||
width={450}
|
||||
positiveButton={{
|
||||
@@ -194,6 +166,22 @@ 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"
|
||||
@@ -210,8 +198,7 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
|
||||
Reset selection
|
||||
</Button>
|
||||
)}
|
||||
{rootNotebooks.status === "fulfilled" &&
|
||||
rootNotebooks.value.length > 0 ? (
|
||||
{notebooks.length > 0 ? (
|
||||
<FlexScrollContainer>
|
||||
<UncontrolledTreeEnvironment
|
||||
ref={treeRef}
|
||||
@@ -232,7 +219,7 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
|
||||
isFolder: true,
|
||||
canMove: false,
|
||||
canRename: false,
|
||||
children: rootNotebooks.value
|
||||
children: notebooks
|
||||
};
|
||||
}
|
||||
|
||||
@@ -268,7 +255,7 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
|
||||
isFolder: true,
|
||||
canMove: false,
|
||||
canRename: false,
|
||||
children: rootNotebooks.value
|
||||
children: notebooks
|
||||
};
|
||||
}
|
||||
|
||||
@@ -332,9 +319,9 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
|
||||
sx={{ mt: 2 }}
|
||||
onClick={() =>
|
||||
showAddNotebookDialog().then(() =>
|
||||
rootNotebooks.status === "fulfilled"
|
||||
? rootNotebooks.refresh()
|
||||
: null
|
||||
db.notebooks.roots
|
||||
.ids(db.settings.getGroupOptions("notebooks"))
|
||||
.then((ids) => setNotebooks(ids))
|
||||
)
|
||||
}
|
||||
>
|
||||
|
||||
@@ -185,9 +185,16 @@ export default function NoteLinkingDialog(props: NoteLinkingDialogProps) {
|
||||
autoFocus
|
||||
placeholder="Search for a note to link to..."
|
||||
sx={{ mx: 0 }}
|
||||
onChange={async (e) =>
|
||||
setNotes(await db.lookup.notes(e.target.value).sorted())
|
||||
}
|
||||
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")
|
||||
)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{notes && (
|
||||
<ScrollContainer>
|
||||
|
||||
@@ -44,7 +44,8 @@ export const AppearanceSettings: SettingsGroup[] = [
|
||||
type: "input",
|
||||
inputType: "number",
|
||||
min: 0.5,
|
||||
max: 2.0,
|
||||
max: 3.0,
|
||||
step: 0.1,
|
||||
defaultValue: () => useSettingStore.getState().zoomFactor,
|
||||
onChange: (value) => useSettingStore.getState().setZoomFactor(value)
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export const AuthenticationSettings: SettingsGroup[] = [
|
||||
title: "Change password",
|
||||
variant: "secondary",
|
||||
action: async () => {
|
||||
await createBackup();
|
||||
if (!(await createBackup())) return;
|
||||
const result = await showPasswordDialog({
|
||||
title: "Change account password",
|
||||
message: `All your data will be re-encrypted and synced with the new password.
|
||||
|
||||
@@ -508,6 +508,7 @@ 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) => {
|
||||
|
||||
@@ -111,6 +111,26 @@ 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",
|
||||
|
||||
@@ -64,7 +64,6 @@ 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
Reference in New Issue
Block a user