mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-29 10:09:26 +02:00
Compare commits
213 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
969611c9e6 | ||
|
|
06399fe3bd | ||
|
|
35c5a90b4e | ||
|
|
3c70cccd14 | ||
|
|
a3c24bc666 | ||
|
|
16b00d21a6 | ||
|
|
88f90c6240 | ||
|
|
a5c44dcb3b | ||
|
|
c0a2dd139a | ||
|
|
b354a26f0a | ||
|
|
b3de1205d9 | ||
|
|
f18e8ae421 | ||
|
|
a46b863802 | ||
|
|
2befa03cd2 | ||
|
|
763a108abb | ||
|
|
30a069c845 | ||
|
|
ffbf5cdc88 | ||
|
|
4318a65a99 | ||
|
|
bc433c56ae | ||
|
|
e43740c6bc | ||
|
|
6123bccf4b | ||
|
|
08106c20e8 | ||
|
|
eaaecbd5e5 | ||
|
|
29608099db | ||
|
|
7a773937c2 | ||
|
|
fa4a298bfa | ||
|
|
966bedbb49 | ||
|
|
159b6f3ca6 | ||
|
|
de1b0fc466 | ||
|
|
b6a3394dee | ||
|
|
f5ac51db13 | ||
|
|
1ec0b65fe8 | ||
|
|
d9a77ff658 | ||
|
|
c45118515c | ||
|
|
f9b8865351 | ||
|
|
1e92b3fc92 | ||
|
|
f34fb858ec | ||
|
|
1fdede5518 | ||
|
|
a454ec4944 | ||
|
|
5afbf103ac | ||
|
|
2ef1b76bfb | ||
|
|
f7d06d3b82 | ||
|
|
e405468471 | ||
|
|
9d2bacdc5f | ||
|
|
b44d4702be | ||
|
|
03803dbc23 | ||
|
|
9ea675ded1 | ||
|
|
c85d623e1c | ||
|
|
e6f1320b1d | ||
|
|
80f49e22e5 | ||
|
|
4884c27a9a | ||
|
|
4976a4dab4 | ||
|
|
5eb0b5faa7 | ||
|
|
5e33cb93c6 | ||
|
|
b4220d349f | ||
|
|
d4752685d0 | ||
|
|
0b06d2a4bd | ||
|
|
a37d9a193f | ||
|
|
b1d465e8bf | ||
|
|
31e17c305f | ||
|
|
72db8724ec | ||
|
|
b64cb22186 | ||
|
|
f5ca1220de | ||
|
|
fb54125ee6 | ||
|
|
be957d058f | ||
|
|
54a3e3c927 | ||
|
|
aabbf7c1c1 | ||
|
|
7773ba0804 | ||
|
|
9f812772fe | ||
|
|
75191afc3e | ||
|
|
1c25baccea | ||
|
|
b1d4ccc27e | ||
|
|
0d714847f3 | ||
|
|
760d4abbd0 | ||
|
|
7c1bb33ea9 | ||
|
|
5ba8ce549e | ||
|
|
a449c614ee | ||
|
|
12e6688f48 | ||
|
|
da47ef159b | ||
|
|
efc678d3cd | ||
|
|
d7a126ab85 | ||
|
|
bc9c976c95 | ||
|
|
26af23771d | ||
|
|
590f2d80d3 | ||
|
|
fdeb34ff0a | ||
|
|
ec76be776c | ||
|
|
4de5691428 | ||
|
|
ae5b339251 | ||
|
|
a6021943e1 | ||
|
|
765fcafc38 | ||
|
|
7a53881eb3 | ||
|
|
854f22f373 | ||
|
|
0566f98323 | ||
|
|
435a358c4c | ||
|
|
3f55422d45 | ||
|
|
9804c1fbb1 | ||
|
|
c3b8c4b630 | ||
|
|
8c4d8622d8 | ||
|
|
86770b32bb | ||
|
|
74c86c1e5f | ||
|
|
1538b74c63 | ||
|
|
5934c518e6 | ||
|
|
71dfd0cf9a | ||
|
|
984bf1f15f | ||
|
|
a14949b75a | ||
|
|
7bbe138d91 | ||
|
|
65dea70ae5 | ||
|
|
a07c8b9e11 | ||
|
|
69b82c61f8 | ||
|
|
fb8df74242 | ||
|
|
3674594832 | ||
|
|
1ee24cde07 | ||
|
|
93baed35f4 | ||
|
|
3e1420d496 | ||
|
|
ebc4324ef1 | ||
|
|
6ba04f4731 | ||
|
|
7359e23b65 | ||
|
|
01387bae94 | ||
|
|
df1d61ecdf | ||
|
|
cb644dbea6 | ||
|
|
fc5ea0ad51 | ||
|
|
8108dbae8a | ||
|
|
b14a9d6eca | ||
|
|
2f4a7f5410 | ||
|
|
dfa718f8ea | ||
|
|
f842700192 | ||
|
|
953d0295dc | ||
|
|
836f607aa0 | ||
|
|
2c6f205ea9 | ||
|
|
8154dd1830 | ||
|
|
686674c855 | ||
|
|
8e5d931be1 | ||
|
|
46509a04b0 | ||
|
|
d6e189134c | ||
|
|
0a05780e4d | ||
|
|
1671c99614 | ||
|
|
b5c8afe8f7 | ||
|
|
7e4ab8e14b | ||
|
|
35210ad02b | ||
|
|
9f853f0b36 | ||
|
|
559698c946 | ||
|
|
50c05efb48 | ||
|
|
94b9d3590d | ||
|
|
092016f942 | ||
|
|
24c899e709 | ||
|
|
e53cc648f0 | ||
|
|
4c541f3995 | ||
|
|
f56cbfc142 | ||
|
|
c21c4f1117 | ||
|
|
abf1f5c61c | ||
|
|
1d5b0793d0 | ||
|
|
5ba72fdbd4 | ||
|
|
c7d26d2444 | ||
|
|
22c146e350 | ||
|
|
1cb0ebd4f8 | ||
|
|
eff2e475ff | ||
|
|
aef475d8fc | ||
|
|
1ed2e55867 | ||
|
|
16ffec1f1e | ||
|
|
912e97dc3c | ||
|
|
ca2f924417 | ||
|
|
468fba8fcf | ||
|
|
b948dcee22 | ||
|
|
a633f4a3fe | ||
|
|
acb384c901 | ||
|
|
043228fe47 | ||
|
|
f29e7936d1 | ||
|
|
d4a7b8d9c9 | ||
|
|
fbabfe468a | ||
|
|
87c7eccee4 | ||
|
|
648eac5555 | ||
|
|
fe8ba8ca90 | ||
|
|
8fa4b5017c | ||
|
|
d2f39df3bf | ||
|
|
2ada289e74 | ||
|
|
1edf416461 | ||
|
|
6fae73b612 | ||
|
|
ec898748b1 | ||
|
|
5be67823ce | ||
|
|
fd868aec95 | ||
|
|
33bf5e26ff | ||
|
|
08363f95e0 | ||
|
|
dc62291fba | ||
|
|
43034cf3a3 | ||
|
|
88f047c312 | ||
|
|
46a635d4f4 | ||
|
|
882217be63 | ||
|
|
87a2650480 | ||
|
|
c41806bd70 | ||
|
|
ae7a6ba88c | ||
|
|
df78b31527 | ||
|
|
4040c62dda | ||
|
|
34bf9d5b45 | ||
|
|
80cd37fe4f | ||
|
|
21a7dbad53 | ||
|
|
c2b010d057 | ||
|
|
82b01851c7 | ||
|
|
0a81f0a09a | ||
|
|
e06cbb39b0 | ||
|
|
b52cc0c590 | ||
|
|
82376c8c47 | ||
|
|
e0b191b9a0 | ||
|
|
6ef6259629 | ||
|
|
52d08f9392 | ||
|
|
6812d3552a | ||
|
|
68ebb132cd | ||
|
|
203aa4824d | ||
|
|
047707dd35 | ||
|
|
7c7645c0c6 | ||
|
|
2d4ca15e10 | ||
|
|
9ca831713c | ||
|
|
56008c15c5 | ||
|
|
2a2811ec54 |
@@ -21,4 +21,7 @@ packages/editor/styles/
|
||||
packages/editor/languages/
|
||||
|
||||
# editor mobile
|
||||
packages/editor-mobile/build.bundle
|
||||
packages/editor-mobile/build.bundle
|
||||
|
||||
# snapshots
|
||||
tap-snapshots
|
||||
4
.github/dco.yml
vendored
Normal file
4
.github/dco.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
allowRemediationCommits:
|
||||
individual: true
|
||||
require:
|
||||
members: false
|
||||
26
.github/workflows/android.publish.yml
vendored
26
.github/workflows/android.publish.yml
vendored
@@ -50,7 +50,18 @@ jobs:
|
||||
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
|
||||
alias: ${{ secrets.ALIAS }}
|
||||
keyStorePassword: ${{ secrets.KEY_PASSWORD }}
|
||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||
|
||||
- name: Publish to Playstore
|
||||
id: deploy
|
||||
uses: r0adkll/upload-google-play@v1.1.1
|
||||
with:
|
||||
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
|
||||
packageName: com.streetwriters.notesnook
|
||||
releaseFiles: ${{steps.sign_app.outputs.signedReleaseFile}}
|
||||
track: production
|
||||
status: completed
|
||||
whatsNewDirectory: apps/mobile/native/android/releasenotes/
|
||||
|
||||
- name: Build apks for Github release
|
||||
run: yarn release:android
|
||||
@@ -91,15 +102,4 @@ jobs:
|
||||
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-arm64-v8a.apk
|
||||
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-armeabi-v7a.apk
|
||||
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86.apk
|
||||
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86_64.apk
|
||||
|
||||
- name: Publish to Playstore
|
||||
id: deploy
|
||||
uses: r0adkll/upload-google-play@v1
|
||||
with:
|
||||
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
|
||||
packageName: com.streetwriters.notesnook
|
||||
releaseFile: ${{steps.sign_app.outputs.signedReleaseFile}}
|
||||
track: production
|
||||
status: completed
|
||||
whatsNewDirectory: apps/mobile/native/android/releasenotes/
|
||||
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86_64.apk
|
||||
8
.github/workflows/desktop.publish.yml
vendored
8
.github/workflows/desktop.publish.yml
vendored
@@ -215,17 +215,17 @@ jobs:
|
||||
- name: Build snap
|
||||
if: inputs.publish-snap
|
||||
run: |
|
||||
npx electron-builder --linux snap -p never
|
||||
npx electron-builder --linux snap:x64 -p never
|
||||
working-directory: ./apps/web/desktop
|
||||
|
||||
- name: Build AppImage deb and rpm
|
||||
- name: Build AppImage
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
if [ ${{ inputs.publish-github }} == true ]; then
|
||||
npx electron-builder --linux AppImage deb rpm -p always
|
||||
npx electron-builder --linux AppImage:x64 AppImage:arm64 -p always
|
||||
else
|
||||
npx electron-builder --linux AppImage deb rpm -p never
|
||||
npx electron-builder --linux AppImage:x64 AppImage:arm64 -p never
|
||||
fi
|
||||
working-directory: ./apps/web/desktop
|
||||
|
||||
|
||||
57
.github/workflows/editor.tests.yml
vendored
Normal file
57
.github/workflows/editor.tests.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
name: Test @notesnook/editor
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
paths:
|
||||
- "packages/editor/**"
|
||||
# re-run workflow if workflow file changes
|
||||
- ".github/workflows/editor.tests.yml"
|
||||
pull_request:
|
||||
types:
|
||||
- "ready_for_review"
|
||||
- "opened"
|
||||
- "synchronize"
|
||||
- "reopened"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16.x
|
||||
cache: "npm"
|
||||
cache-dependency-path: |
|
||||
apps/mobile/package-lock.json
|
||||
apps/web/package-lock.json
|
||||
packages/core/package-lock.json
|
||||
packages/crypto/package-lock.json
|
||||
packages/crypto-worker/package-lock.json
|
||||
packages/editor-mobile/package-lock.json
|
||||
packages/editor/package-lock.json
|
||||
packages/logger/package-lock.json
|
||||
packages/streamable-fs/package-lock.json
|
||||
packages/theme/package-lock.json
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm i --ignore-scripts --prefer-offline --no-audit
|
||||
npx lerna bootstrap --ignore=@notesnook/mobile -- --prefer-offline --no-audit
|
||||
|
||||
- name: Setup environment
|
||||
run: |
|
||||
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build editor
|
||||
run: npx nx build @notesnook/editor
|
||||
|
||||
- name: Run all @notesnook/editor tests
|
||||
run: npx nx test @notesnook/editor
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -6,4 +6,5 @@ dist
|
||||
nx-cloud.env
|
||||
.idea
|
||||
.eslintcache
|
||||
.env.local
|
||||
.env.local
|
||||
.nyc_output
|
||||
@@ -28,12 +28,6 @@ If you've thought of a way that Notesnook could be better, we want to hear about
|
||||
|
||||
Before you open a new feature request, please make sure it's not a duplicate. **Duplicate feature requests & bug reports are closed immediately.**
|
||||
|
||||
### Improving documentation
|
||||
|
||||
All app related documentation can be found in the `docs/` directory. All the files in this directory are simple markdown files which you can edit either directly on GitHub, or in your favorite text editor.
|
||||
|
||||
If you find a mistake in the documentation or would like to contribute something new to the documentation, feel free to open a pull request; creating an issue for this is not required.
|
||||
|
||||
### Helping out in the issue tracker
|
||||
|
||||
There are always new issues getting opened that need to be triaged, sorted & organized, so the developers can easily find the most critical and/or relevant bugs to fix. Any help in this regard is appreciated.
|
||||
@@ -91,7 +85,11 @@ The DCO is a declaration attached to every contribution made by every developer
|
||||
Signed-off-by: Jane Smith <jane.smith@email.com>
|
||||
```
|
||||
|
||||
You may type this line on your own when writing your commit messages. However, if your [user.name](http://user.name) and [user.email](http://user.email) are set in your git configs, you can use `-s` or `--signoff` to add the `Signed-off-by` line to the end of the commit message.
|
||||
On VSCode, it looks like this:
|
||||
|
||||

|
||||
|
||||
You may type this line on your own when writing your commit messages. However, if your [user.name](http://user.name) and [user.email](http://user.email) are set in your git configs, you can use `-s` or `--signoff` to add the `Signed-off-by` line to the end of the commit message automatically.
|
||||
|
||||
#### Include `<scope>:` in your commit message
|
||||
|
||||
|
||||
@@ -32,8 +32,6 @@ import { useUserStore } from "./stores/use-user-store";
|
||||
import { View } from "react-native";
|
||||
import { useState } from "react";
|
||||
import NetInfo from "@react-native-community/netinfo";
|
||||
//import { BackgroundSync } from "./services/background-sync";
|
||||
Notifications.init();
|
||||
NetInfo.configure({
|
||||
reachabilityUrl: "https://notesnook.com",
|
||||
reachabilityTest: (response) => {
|
||||
|
||||
50
apps/mobile/app/common/filesystem/compress.js
Normal file
50
apps/mobile/app/common/filesystem/compress.js
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
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 { Dimensions, Platform } from "react-native";
|
||||
import ImageResizer from "@bam.tech/react-native-image-resizer";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
/**
|
||||
* Scale down & compress images to screen width
|
||||
* for loading in editor.
|
||||
* @returns
|
||||
*/
|
||||
export async function compressToBase64(path, type) {
|
||||
const { width, scale } = Dimensions.get("window");
|
||||
const response = await ImageResizer.createResizedImage(
|
||||
path,
|
||||
width * scale,
|
||||
9999,
|
||||
type,
|
||||
80,
|
||||
0,
|
||||
undefined,
|
||||
true,
|
||||
{
|
||||
mode: "contain",
|
||||
onlyScaleDown: true
|
||||
}
|
||||
);
|
||||
const base64 = await RNFetchBlob.fs.readFile(
|
||||
Platform.OS === "ios" ? response.uri?.replace("file://", "") : response.uri,
|
||||
"base64"
|
||||
);
|
||||
RNFetchBlob.fs.unlink(path.replace("file://", "")).catch(console.log);
|
||||
RNFetchBlob.fs.unlink(response.uri.replace("file://", "")).catch(console.log);
|
||||
return base64;
|
||||
}
|
||||
@@ -28,8 +28,16 @@ import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { db } from "../database";
|
||||
import Storage from "../database/storage";
|
||||
import { cacheDir } from "./utils";
|
||||
import { getFileNameWithExtension } from "@notesnook/core/utils/filename";
|
||||
|
||||
export default async function downloadAttachment(hash, global = true) {
|
||||
export default async function downloadAttachment(
|
||||
hash,
|
||||
global = true,
|
||||
options = {
|
||||
silent: false,
|
||||
cache: false
|
||||
}
|
||||
) {
|
||||
let attachment = db.attachments.attachment(hash);
|
||||
if (!attachment) {
|
||||
console.log("attachment not found");
|
||||
@@ -37,11 +45,13 @@ export default async function downloadAttachment(hash, global = true) {
|
||||
}
|
||||
|
||||
let folder = {};
|
||||
if (Platform.OS === "android") {
|
||||
folder = await ScopedStorage.openDocumentTree();
|
||||
if (!folder) return;
|
||||
} else {
|
||||
folder.uri = await Storage.checkAndCreateDir("/downloads/");
|
||||
if (!options.cache) {
|
||||
if (Platform.OS === "android") {
|
||||
folder = await ScopedStorage.openDocumentTree();
|
||||
if (!folder) return;
|
||||
} else {
|
||||
folder.uri = await Storage.checkAndCreateDir("/downloads/");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -54,6 +64,11 @@ export default async function downloadAttachment(hash, global = true) {
|
||||
)
|
||||
return;
|
||||
|
||||
let filename = getFileNameWithExtension(
|
||||
attachment.metadata.filename,
|
||||
attachment.metadata.type
|
||||
);
|
||||
|
||||
let key = await db.attachments.decryptKey(attachment.key);
|
||||
let info = {
|
||||
iv: attachment.iv,
|
||||
@@ -63,46 +78,50 @@ export default async function downloadAttachment(hash, global = true) {
|
||||
hash: attachment.metadata.hash,
|
||||
hashType: attachment.metadata.hashType,
|
||||
mime: attachment.metadata.type,
|
||||
fileName: attachment.metadata.filename,
|
||||
uri: folder.uri,
|
||||
fileName: options.cache ? undefined : filename,
|
||||
uri: options.cache ? undefined : folder.uri,
|
||||
chunkSize: attachment.chunkSize
|
||||
};
|
||||
|
||||
let fileUri = await Sodium.decryptFile(key, info, "file");
|
||||
ToastEvent.show({
|
||||
heading: "Download successful",
|
||||
message: attachment.metadata.filename + " downloaded",
|
||||
type: "success"
|
||||
});
|
||||
let fileUri = await Sodium.decryptFile(
|
||||
key,
|
||||
info,
|
||||
options.cache ? "cache" : "file"
|
||||
);
|
||||
|
||||
if (attachment.dateUploaded) {
|
||||
if (!options.silent) {
|
||||
ToastEvent.show({
|
||||
heading: "Download successful",
|
||||
message: filename + " downloaded",
|
||||
type: "success"
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
attachment.dateUploaded &&
|
||||
!attachment.metadata?.type?.startsWith("image")
|
||||
) {
|
||||
RNFetchBlob.fs
|
||||
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.metadata.hash}`)
|
||||
.catch(console.log);
|
||||
}
|
||||
|
||||
if (Platform.OS === "ios") {
|
||||
fileUri = folder.uri + `/${attachment.metadata.filename}`;
|
||||
if (Platform.OS === "ios" && !options.cache) {
|
||||
fileUri = folder.uri + `/${filename}`;
|
||||
}
|
||||
if (!options.silent) {
|
||||
presentSheet({
|
||||
title: "File downloaded",
|
||||
paragraph: `${filename} saved to ${
|
||||
Platform.OS === "android"
|
||||
? "selected path"
|
||||
: "File Manager/Notesnook/downloads"
|
||||
}`,
|
||||
icon: "download",
|
||||
context: global ? null : attachment.metadata.hash,
|
||||
component: <ShareComponent uri={fileUri} name={filename} padding={12} />
|
||||
});
|
||||
}
|
||||
console.log("saved file uri: ", fileUri);
|
||||
|
||||
presentSheet({
|
||||
title: "File downloaded",
|
||||
paragraph: `${attachment.metadata.filename} saved to ${
|
||||
Platform.OS === "android"
|
||||
? "selected path"
|
||||
: "File Manager/Notesnook/downloads"
|
||||
}`,
|
||||
icon: "download",
|
||||
context: global ? null : attachment.metadata.hash,
|
||||
component: (
|
||||
<ShareComponent
|
||||
uri={fileUri}
|
||||
name={attachment.metadata.filename}
|
||||
padding={12}
|
||||
/>
|
||||
)
|
||||
});
|
||||
return fileUri;
|
||||
} catch (e) {
|
||||
console.log("download attachment error: ", e);
|
||||
|
||||
@@ -23,14 +23,16 @@ import {
|
||||
deleteFile,
|
||||
exists,
|
||||
readEncrypted,
|
||||
writeEncrypted
|
||||
writeEncryptedBase64,
|
||||
hashBase64
|
||||
} from "./io";
|
||||
import { uploadFile } from "./upload";
|
||||
import { cancelable } from "./utils";
|
||||
|
||||
export default {
|
||||
readEncrypted,
|
||||
writeEncrypted,
|
||||
writeEncryptedBase64,
|
||||
hashBase64,
|
||||
uploadFile: cancelable(uploadFile),
|
||||
downloadFile: cancelable(downloadFile),
|
||||
deleteFile,
|
||||
|
||||
@@ -21,6 +21,8 @@ import { Platform } from "react-native";
|
||||
import Sodium from "@ammarahmed/react-native-sodium";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import { cacheDir, getRandomId } from "./utils";
|
||||
import { db } from "../database";
|
||||
import { compressToBase64 } from "./compress";
|
||||
|
||||
export async function readEncrypted(filename, key, cipherData) {
|
||||
let path = `${cacheDir}/${filename}`;
|
||||
@@ -29,6 +31,9 @@ export async function readEncrypted(filename, key, cipherData) {
|
||||
if (!exists) {
|
||||
return false;
|
||||
}
|
||||
const attachment = db.attachments.attachment(filename);
|
||||
const isPng = /(png)/g.test(attachment?.metadata.type);
|
||||
const isJpeg = /(jpeg|jpg)/g.test(attachment?.metadata.type);
|
||||
|
||||
let output = await Sodium.decryptFile(
|
||||
key,
|
||||
@@ -36,8 +41,20 @@ export async function readEncrypted(filename, key, cipherData) {
|
||||
...cipherData,
|
||||
hash: filename
|
||||
},
|
||||
cipherData.outputType === "base64" ? "base64" : "text"
|
||||
cipherData.outputType === "base64"
|
||||
? isPng || isJpeg
|
||||
? "cache"
|
||||
: "base64"
|
||||
: "text"
|
||||
);
|
||||
if (cipherData.outputType === "base64" && (isPng || isJpeg)) {
|
||||
const dCachePath = `${cacheDir}/${output}`;
|
||||
output = await compressToBase64(
|
||||
`file://${dCachePath}`,
|
||||
isPng ? "PNG" : "JPEG"
|
||||
);
|
||||
}
|
||||
|
||||
return output;
|
||||
} catch (e) {
|
||||
RNFetchBlob.fs.unlink(path).catch(console.log);
|
||||
@@ -46,17 +63,26 @@ export async function readEncrypted(filename, key, cipherData) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function writeEncrypted(filename, { data, type, key }) {
|
||||
console.log("file input: ", { type, key });
|
||||
export async function hashBase64(data) {
|
||||
const hash = await Sodium.hashFile({
|
||||
type: "base64",
|
||||
data,
|
||||
uri: ""
|
||||
});
|
||||
return {
|
||||
hash: hash,
|
||||
type: "xxh64"
|
||||
};
|
||||
}
|
||||
|
||||
export async function writeEncryptedBase64({ data, key }) {
|
||||
let filepath = cacheDir + `/${getRandomId("imagecache_")}`;
|
||||
console.log(filepath);
|
||||
await RNFetchBlob.fs.writeFile(filepath, data, "base64");
|
||||
let output = await Sodium.encryptFile(key, {
|
||||
uri: Platform.OS === "ios" ? filepath : "file://" + filepath,
|
||||
type: "url"
|
||||
});
|
||||
RNFetchBlob.fs.unlink(filepath).catch(console.log);
|
||||
|
||||
console.log("encrypted file output: ", output);
|
||||
return {
|
||||
...output,
|
||||
|
||||
@@ -46,7 +46,6 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
|
||||
const encryptionProgress = useAttachmentStore(
|
||||
(state) => state.encryptionProgress
|
||||
);
|
||||
|
||||
const onPress = () => {
|
||||
Actions.present(attachment, setAttachments, attachment.metadata.hash);
|
||||
};
|
||||
@@ -122,7 +121,9 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{currentProgress || encryptionProgress || encryption ? (
|
||||
{currentProgress ||
|
||||
(encryptionProgress && encryptionProgress !== "0.00") ||
|
||||
encryption ? (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.9}
|
||||
onPress={() => {
|
||||
|
||||
@@ -17,70 +17,33 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { FlatList } from "react-native-gesture-handler";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../common/database";
|
||||
import filesystem from "../../common/filesystem";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import { presentSheet } from "../../services/event-manager";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import {
|
||||
eCloseAttachmentDialog,
|
||||
eOpenAttachmentsDialog
|
||||
} from "../../utils/events";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import DialogHeader from "../dialog/dialog-header";
|
||||
import { Toast } from "../toast";
|
||||
import Input from "../ui/input";
|
||||
import Seperator from "../ui/seperator";
|
||||
import SheetWrapper from "../ui/sheet";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { AttachmentItem } from "./attachment-item";
|
||||
export const AttachmentDialog = () => {
|
||||
import { FlatList } from "react-native-actions-sheet";
|
||||
|
||||
export const AttachmentDialog = ({ data }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [note, setNote] = useState(null);
|
||||
const actionSheetRef = useRef();
|
||||
const [attachments, setAttachments] = useState([]);
|
||||
const [note, setNote] = useState(data);
|
||||
const [attachments, setAttachments] = useState(
|
||||
data
|
||||
? db.attachments.ofNote(data.id, "all")
|
||||
: [...(db.attachments.all || [])]
|
||||
);
|
||||
const attachmentSearchValue = useRef();
|
||||
const searchTimer = useRef();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent(eOpenAttachmentsDialog, open);
|
||||
eSubscribeEvent(eCloseAttachmentDialog, close);
|
||||
return () => {
|
||||
eUnSubscribeEvent(eOpenAttachmentsDialog, open);
|
||||
eUnSubscribeEvent(eCloseAttachmentDialog, close);
|
||||
};
|
||||
}, [visible]);
|
||||
|
||||
const open = (data) => {
|
||||
if (data?.id) {
|
||||
setNote(data);
|
||||
let _attachments = db.attachments.ofNote(data.id, "all");
|
||||
setAttachments(_attachments);
|
||||
} else {
|
||||
setAttachments([...db.attachments.all]);
|
||||
}
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
actionSheetRef.current?.show();
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
const close = () => {
|
||||
actionSheetRef.current?.hide();
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
const onChangeText = (text) => {
|
||||
attachmentSearchValue.current = text;
|
||||
if (
|
||||
@@ -104,107 +67,101 @@ export const AttachmentDialog = () => {
|
||||
<AttachmentItem setAttachments={setAttachments} attachment={item} />
|
||||
);
|
||||
|
||||
return !visible ? null : (
|
||||
<SheetWrapper
|
||||
centered={false}
|
||||
fwdRef={actionSheetRef}
|
||||
onClose={async () => {
|
||||
setVisible(false);
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
alignSelf: "center",
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
>
|
||||
<Toast context="local" />
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
alignSelf: "center",
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
>
|
||||
<DialogHeader
|
||||
title={note ? "Attachments" : "Manage attachments"}
|
||||
paragraph="Tap on an attachment to view properties"
|
||||
button={{
|
||||
title: "Check all",
|
||||
type: "grayAccent",
|
||||
loading: loading,
|
||||
onPress: async () => {
|
||||
setLoading(true);
|
||||
for (let attachment of attachments) {
|
||||
let result = await filesystem.checkAttachment(
|
||||
attachment.metadata.hash
|
||||
<DialogHeader
|
||||
title={note ? "Attachments" : "Manage attachments"}
|
||||
paragraph="Tap on an attachment to view properties"
|
||||
button={{
|
||||
title: "Check all",
|
||||
type: "grayAccent",
|
||||
loading: loading,
|
||||
onPress: async () => {
|
||||
setLoading(true);
|
||||
for (let attachment of attachments) {
|
||||
let result = await filesystem.checkAttachment(
|
||||
attachment.metadata.hash
|
||||
);
|
||||
if (result.failed) {
|
||||
db.attachments.markAsFailed(
|
||||
attachment.metadata.hash,
|
||||
result.failed
|
||||
);
|
||||
if (result.failed) {
|
||||
db.attachments.markAsFailed(
|
||||
attachment.metadata.hash,
|
||||
result.failed
|
||||
);
|
||||
} else {
|
||||
db.attachments.markAsFailed(attachment.id, null);
|
||||
}
|
||||
setAttachments([...db.attachments.all]);
|
||||
} else {
|
||||
db.attachments.markAsFailed(attachment.id, null);
|
||||
}
|
||||
setLoading(false);
|
||||
setAttachments([...db.attachments.all]);
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Seperator />
|
||||
{!note ? (
|
||||
<Input
|
||||
placeholder="Filter attachments by filename, type or hash"
|
||||
onChangeText={onChangeText}
|
||||
onSubmit={() => {
|
||||
onChangeText(attachmentSearchValue.current);
|
||||
}}
|
||||
/>
|
||||
<Seperator />
|
||||
{!note ? (
|
||||
<Input
|
||||
placeholder="Filter attachments by filename, type or hash"
|
||||
onChangeText={onChangeText}
|
||||
onSubmit={() => {
|
||||
onChangeText(attachmentSearchValue.current);
|
||||
) : null}
|
||||
|
||||
<FlatList
|
||||
keyboardDismissMode="none"
|
||||
keyboardShouldPersistTaps="always"
|
||||
maxToRenderPerBatch={10}
|
||||
initialNumToRender={10}
|
||||
windowSize={5}
|
||||
ListEmptyComponent={
|
||||
<View
|
||||
style={{
|
||||
height: 150,
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Icon name="attachment" size={60} color={colors.icon} />
|
||||
<Paragraph>
|
||||
{note ? "No attachments on this note" : "No attachments"}
|
||||
</Paragraph>
|
||||
</View>
|
||||
}
|
||||
ListFooterComponent={
|
||||
<View
|
||||
style={{
|
||||
height: 350
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
}
|
||||
data={attachments}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={renderItem}
|
||||
/>
|
||||
|
||||
<FlatList
|
||||
nestedScrollEnabled
|
||||
overScrollMode="never"
|
||||
scrollToOverflowEnabled={false}
|
||||
keyboardDismissMode="none"
|
||||
keyboardShouldPersistTaps="always"
|
||||
onMomentumScrollEnd={() => {
|
||||
actionSheetRef.current?.handleChildScrollEnd();
|
||||
}}
|
||||
ListEmptyComponent={
|
||||
<View
|
||||
style={{
|
||||
height: 150,
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Icon name="attachment" size={60} color={colors.icon} />
|
||||
<Paragraph>
|
||||
{note ? "No attachments on this note" : "No attachments"}
|
||||
</Paragraph>
|
||||
</View>
|
||||
}
|
||||
ListFooterComponent={
|
||||
<View
|
||||
style={{
|
||||
height: 350
|
||||
}}
|
||||
/>
|
||||
}
|
||||
data={attachments}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={renderItem}
|
||||
/>
|
||||
|
||||
<Paragraph
|
||||
color={colors.icon}
|
||||
size={SIZE.xs}
|
||||
style={{
|
||||
textAlign: "center",
|
||||
marginTop: 10
|
||||
}}
|
||||
>
|
||||
<Icon name="shield-key-outline" size={SIZE.xs} color={colors.icon} />
|
||||
{" "}All attachments are end-to-end encrypted.
|
||||
</Paragraph>
|
||||
</View>
|
||||
</SheetWrapper>
|
||||
<Paragraph
|
||||
color={colors.icon}
|
||||
size={SIZE.xs}
|
||||
style={{
|
||||
textAlign: "center",
|
||||
marginTop: 10
|
||||
}}
|
||||
>
|
||||
<Icon name="shield-key-outline" size={SIZE.xs} color={colors.icon} />
|
||||
{" "}All attachments are end-to-end encrypted.
|
||||
</Paragraph>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
AttachmentDialog.present = (note) => {
|
||||
presentSheet({
|
||||
component: () => <AttachmentDialog data={note} />
|
||||
});
|
||||
};
|
||||
|
||||
@@ -26,7 +26,6 @@ import { eSendEvent } from "../../services/event-manager";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
import SheetProvider from "../sheet-provider";
|
||||
import { Progress } from "../sheets/progress";
|
||||
import { Button } from "../ui/button";
|
||||
@@ -83,9 +82,6 @@ export const Login = ({ changeMode }) => {
|
||||
<>
|
||||
<ForgotPassword />
|
||||
<SheetProvider context="two_factor_verify" />
|
||||
{loading ? (
|
||||
<BaseDialog transparent={true} visible={true} animation="fade" />
|
||||
) : null}
|
||||
<Animated.View
|
||||
entering={FadeInDown}
|
||||
exiting={FadeOutUp}
|
||||
@@ -134,6 +130,7 @@ export const Login = ({ changeMode }) => {
|
||||
marginTop: 5
|
||||
}}
|
||||
onPress={() => {
|
||||
if (loading) return;
|
||||
changeMode(1);
|
||||
}}
|
||||
size={SIZE.md}
|
||||
@@ -172,7 +169,7 @@ export const Login = ({ changeMode }) => {
|
||||
errorMessage="Email is invalid"
|
||||
placeholder="Enter your email"
|
||||
defaultValue={email.current}
|
||||
editable={step === LoginSteps.emailAuth}
|
||||
editable={step === LoginSteps.emailAuth && !loading}
|
||||
onSubmit={() => {
|
||||
passwordInputRef.current?.focus();
|
||||
}}
|
||||
@@ -194,6 +191,7 @@ export const Login = ({ changeMode }) => {
|
||||
autoCorrect={false}
|
||||
placeholder="Password"
|
||||
marginBottom={0}
|
||||
editable={!loading}
|
||||
defaultValue={password.current}
|
||||
onSubmit={() => login()}
|
||||
/>
|
||||
@@ -205,6 +203,7 @@ export const Login = ({ changeMode }) => {
|
||||
paddingHorizontal: 0
|
||||
}}
|
||||
onPress={() => {
|
||||
if (loading) return;
|
||||
SheetManager.show("forgotpassword_sheet", email.current);
|
||||
}}
|
||||
textStyle={{
|
||||
@@ -228,7 +227,10 @@ export const Login = ({ changeMode }) => {
|
||||
borderRadius: 100
|
||||
}}
|
||||
loading={loading}
|
||||
onPress={login}
|
||||
onPress={() => {
|
||||
if (loading) return;
|
||||
login();
|
||||
}}
|
||||
type="accent"
|
||||
title={
|
||||
loading
|
||||
@@ -248,6 +250,7 @@ export const Login = ({ changeMode }) => {
|
||||
marginTop: 10
|
||||
}}
|
||||
onPress={() => {
|
||||
if (loading) return;
|
||||
setStep(LoginSteps.emailAuth);
|
||||
setLoading(false);
|
||||
}}
|
||||
|
||||
@@ -30,7 +30,6 @@ import { useUserStore } from "../../stores/use-user-store";
|
||||
import { openLinkInBrowser } from "../../utils/functions";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
import { Button } from "../ui/button";
|
||||
import Input from "../ui/input";
|
||||
import { SvgView } from "../ui/svg";
|
||||
@@ -97,9 +96,6 @@ export const Signup = ({ changeMode, trial }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{loading ? (
|
||||
<BaseDialog transparent={true} visible={true} animation="fade" />
|
||||
) : null}
|
||||
<Animated.View
|
||||
entering={FadeInDown}
|
||||
exiting={FadeOutUp}
|
||||
@@ -148,6 +144,7 @@ export const Signup = ({ changeMode, trial }) => {
|
||||
textAlign: "center"
|
||||
}}
|
||||
onPress={() => {
|
||||
if (loading) return;
|
||||
changeMode(0);
|
||||
}}
|
||||
size={SIZE.md}
|
||||
@@ -235,7 +232,10 @@ export const Signup = ({ changeMode, trial }) => {
|
||||
borderRadius: 100
|
||||
}}
|
||||
loading={loading}
|
||||
onPress={signup}
|
||||
onPress={() => {
|
||||
if (loading) return;
|
||||
signup();
|
||||
}}
|
||||
type="accent"
|
||||
title={loading ? null : "Agree and continue"}
|
||||
/>
|
||||
|
||||
@@ -65,9 +65,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
||||
};
|
||||
|
||||
const onNext = async () => {
|
||||
const length = currentMethod.method === "recoveryCode" ? 10 : 6;
|
||||
|
||||
if (!code.current || code.current.length !== length) return;
|
||||
if (!code.current || code.current.length < 6) return;
|
||||
setLoading(true);
|
||||
inputRef.current?.blur();
|
||||
await onMfaLogin(
|
||||
@@ -203,16 +201,18 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
||||
<Input
|
||||
placeholder={
|
||||
currentMethod.method === "recoveryCode"
|
||||
? "xxxxxxxxxx"
|
||||
? "xxxxx-xxxxx"
|
||||
: "xxxxxx"
|
||||
}
|
||||
testID={"input.totp"}
|
||||
maxLength={currentMethod.method === "recoveryCode" ? 10 : 6}
|
||||
maxLength={
|
||||
currentMethod.method === "recoveryCode" ? undefined : 6
|
||||
}
|
||||
fwdRef={inputRef}
|
||||
textAlign="center"
|
||||
onChangeText={(value) => {
|
||||
code.current = value;
|
||||
onNext();
|
||||
//onNext();
|
||||
}}
|
||||
inputStyle={{
|
||||
fontSize: SIZE.lg,
|
||||
@@ -302,7 +302,9 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
||||
|
||||
TwoFactorVerification.present = (onMfaLogin, data, context) => {
|
||||
presentSheet({
|
||||
component: <TwoFactorVerification onMfaLogin={onMfaLogin} mfaInfo={data} />,
|
||||
component: () => (
|
||||
<TwoFactorVerification onMfaLogin={onMfaLogin} mfaInfo={data} />
|
||||
),
|
||||
context: context || "two_factor_verify",
|
||||
onClose: () => {
|
||||
onMfaLogin();
|
||||
|
||||
@@ -75,6 +75,7 @@ export const useLogin = (onFinishLogin) => {
|
||||
mfa.code,
|
||||
mfa.method
|
||||
);
|
||||
|
||||
if (success) {
|
||||
setStep(LoginSteps.passwordAuth);
|
||||
setLoading(false);
|
||||
@@ -93,6 +94,8 @@ export const useLogin = (onFinishLogin) => {
|
||||
}
|
||||
}
|
||||
}, mfaInfo);
|
||||
} else {
|
||||
finishWithError(new Error("Unable to send 2FA code"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import React from "react";
|
||||
import { useNoteStore } from "../../stores/use-notes-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { AnnouncementDialog } from "../announcements";
|
||||
import { AttachmentDialog } from "../attachments";
|
||||
import AuthModal from "../auth/auth-modal";
|
||||
import { SessionExpired } from "../auth/session-expired";
|
||||
import { Dialog } from "../dialog";
|
||||
@@ -34,10 +33,6 @@ import MergeConflicts from "../merge-conflicts";
|
||||
import PremiumDialog from "../premium";
|
||||
import { Expiring } from "../premium/expiring";
|
||||
import SheetProvider from "../sheet-provider";
|
||||
import { AddNotebookSheet } from "../sheets/add-notebook";
|
||||
import AddToNotebookSheet from "../sheets/add-to";
|
||||
import ManageTagsSheet from "../sheets/manage-tags";
|
||||
import PublishNoteSheet from "../sheets/publish-note";
|
||||
import RateAppSheet from "../sheets/rate-app";
|
||||
import RecoveryKeySheet from "../sheets/recovery-key";
|
||||
import RestoreDataSheet from "../sheets/restore-data";
|
||||
@@ -51,7 +46,6 @@ const DialogProvider = () => {
|
||||
<LoadingDialog />
|
||||
<Dialog context="global" />
|
||||
<AddTopicDialog colors={colors} />
|
||||
<AddNotebookSheet colors={colors} />
|
||||
<PremiumDialog colors={colors} />
|
||||
<AuthModal colors={colors} />
|
||||
<MergeConflicts />
|
||||
@@ -61,12 +55,8 @@ const DialogProvider = () => {
|
||||
<RestoreDataSheet />
|
||||
<ResultDialog />
|
||||
<VaultDialog colors={colors} />
|
||||
<AddToNotebookSheet colors={colors} />
|
||||
<RateAppSheet />
|
||||
<ImagePreview />
|
||||
<PublishNoteSheet />
|
||||
<ManageTagsSheet />
|
||||
<AttachmentDialog />
|
||||
{loading ? null : <Expiring />}
|
||||
<AnnouncementDialog />
|
||||
<SessionExpired />
|
||||
|
||||
@@ -35,6 +35,7 @@ import BaseDialog from "./base-dialog";
|
||||
import DialogButtons from "./dialog-buttons";
|
||||
import DialogHeader from "./dialog-header";
|
||||
import { useCallback } from "react";
|
||||
import { Button } from "../ui/button";
|
||||
|
||||
export const Dialog = ({ context = "global" }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
@@ -54,7 +55,11 @@ export const Dialog = ({ context = "global" }) => {
|
||||
input: false,
|
||||
inputPlaceholder: "Enter some text",
|
||||
defaultValue: "",
|
||||
disableBackdropClosing: false
|
||||
disableBackdropClosing: false,
|
||||
check: {
|
||||
info: "Check",
|
||||
type: "transparent"
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -87,6 +92,7 @@ export const Dialog = ({ context = "global" }) => {
|
||||
if (data.context !== context) return;
|
||||
setDialogInfo(data);
|
||||
setVisible(true);
|
||||
setInputValue(data.defaultValue);
|
||||
},
|
||||
[context]
|
||||
);
|
||||
@@ -137,6 +143,9 @@ export const Dialog = ({ context = "global" }) => {
|
||||
paragraph={dialogInfo.paragraph}
|
||||
paragraphColor={dialogInfo.paragraphColor}
|
||||
padding={12}
|
||||
style={{
|
||||
minHeight: 0
|
||||
}}
|
||||
/>
|
||||
<Seperator half />
|
||||
|
||||
@@ -163,6 +172,28 @@ export const Dialog = ({ context = "global" }) => {
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
{dialogInfo.check ? (
|
||||
<>
|
||||
<Button
|
||||
onPress={() => {
|
||||
setInputValue(!inputValue);
|
||||
}}
|
||||
icon={
|
||||
inputValue
|
||||
? "check-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
}
|
||||
style={{
|
||||
justifyContent: "flex-start"
|
||||
}}
|
||||
height={35}
|
||||
width="100%"
|
||||
title={dialogInfo.check.info}
|
||||
type={inputValue ? dialogInfo.check.type : "gray"}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<DialogButtons
|
||||
onPressNegative={onNegativePress}
|
||||
onPressPositive={dialogInfo.positivePress && onPressPositive}
|
||||
|
||||
@@ -59,14 +59,12 @@ export class AddTopicDialog extends React.Component {
|
||||
|
||||
addNewTopic = async () => {
|
||||
try {
|
||||
this.setState({ loading: true });
|
||||
if (!this.title || this.title?.trim() === "") {
|
||||
ToastEvent.show({
|
||||
heading: "Topic title is required",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
this.setState({ loading: false });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -78,10 +76,11 @@ export class AddTopicDialog extends React.Component {
|
||||
|
||||
await db.notebooks.notebook(topic.notebookId).topics.add(topic);
|
||||
}
|
||||
this.setState({ loading: false });
|
||||
this.close();
|
||||
Navigation.queueRoutesForUpdate("Notebooks", "Notebook", "TopicNotes");
|
||||
useMenuStore.getState().setMenuPins();
|
||||
setTimeout(() => {
|
||||
Navigation.queueRoutesForUpdate("Notebooks", "Notebook", "TopicNotes");
|
||||
useMenuStore.getState().setMenuPins();
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
@@ -177,7 +176,6 @@ export class AddTopicDialog extends React.Component {
|
||||
positiveTitle={this.toEdit ? "Save" : "Add"}
|
||||
onPressNegative={() => this.close()}
|
||||
onPressPositive={() => this.addNewTopic()}
|
||||
loading={this.state.loading}
|
||||
/>
|
||||
</DialogContainer>
|
||||
<Toast context="local" />
|
||||
|
||||
@@ -30,7 +30,6 @@ import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { eScrollEvent } from "../../utils/events";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { useCallback } from "react";
|
||||
import Tag from "../ui/tag";
|
||||
|
||||
@@ -64,13 +63,15 @@ export const Title = () => {
|
||||
}
|
||||
if (data.y > 150) {
|
||||
if (!hide) return;
|
||||
titleState[currentScreen.id] = false;
|
||||
setHide(false);
|
||||
} else {
|
||||
if (hide) return;
|
||||
titleState[currentScreen.id] = true;
|
||||
setHide(true);
|
||||
}
|
||||
},
|
||||
[currentScreen.name, hide]
|
||||
[currentScreen.id, currentScreen.name, hide]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -85,10 +86,6 @@ export const Title = () => {
|
||||
}
|
||||
}, [currentScreen.id, currentScreen.name]);
|
||||
|
||||
useEffect(() => {
|
||||
titleState[currentScreen.id] = hide;
|
||||
}, [currentScreen.id, hide]);
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent(eScrollEvent, onScroll);
|
||||
return () => {
|
||||
@@ -113,24 +110,15 @@ export const Title = () => {
|
||||
<Heading
|
||||
onPress={navigateToNotebook}
|
||||
numberOfLines={isTopic ? 2 : 1}
|
||||
size={isTopic ? SIZE.md + 2 : SIZE.xl}
|
||||
size={SIZE.xl}
|
||||
style={{
|
||||
flexWrap: "wrap",
|
||||
marginTop: Platform.OS === "ios" ? -1 : 0
|
||||
}}
|
||||
color={currentScreen.color || colors.heading}
|
||||
>
|
||||
{isTopic ? (
|
||||
<Paragraph numberOfLines={1} size={SIZE.xs + 1}>
|
||||
{notebook?.title}
|
||||
{"\n"}
|
||||
</Paragraph>
|
||||
) : null}
|
||||
{isTag ? (
|
||||
<Heading
|
||||
size={isTopic ? SIZE.md + 2 : SIZE.xl}
|
||||
color={colors.accent}
|
||||
>
|
||||
<Heading size={SIZE.xl} color={colors.accent}>
|
||||
#
|
||||
</Heading>
|
||||
) : null}
|
||||
|
||||
@@ -18,18 +18,26 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { Platform, View } from "react-native";
|
||||
import ImageViewer from "react-native-image-zoom-viewer";
|
||||
import downloadAttachment from "../../common/filesystem/download-attachment";
|
||||
import { cacheDir } from "../../common/filesystem/utils";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import { ProgressBarComponent } from "../ui/svg/lazy";
|
||||
import Sodium from "@ammarahmed/react-native-sodium";
|
||||
import dataurl from "@notesnook/core/utils/dataurl";
|
||||
|
||||
const ImagePreview = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [image, setImage] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent("ImagePreview", open);
|
||||
@@ -39,9 +47,29 @@ const ImagePreview = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const open = (image) => {
|
||||
setImage(image);
|
||||
const open = async (image) => {
|
||||
setVisible(true);
|
||||
setLoading(true);
|
||||
setTimeout(async () => {
|
||||
let hash = image.hash;
|
||||
if (!hash && dataurl.toObject(image.src)) {
|
||||
const data = dataurl.toObject(image.src);
|
||||
if (!data) return;
|
||||
hash = await Sodium.hashFile({
|
||||
data: data.data,
|
||||
type: "base64",
|
||||
uri: ""
|
||||
});
|
||||
}
|
||||
if (!hash) return;
|
||||
const uri = await downloadAttachment(hash, false, {
|
||||
silent: true,
|
||||
cache: true
|
||||
});
|
||||
const path = `${cacheDir}/${uri}`;
|
||||
setImage("file://" + path);
|
||||
setLoading(false);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
@@ -59,44 +87,60 @@ const ImagePreview = () => {
|
||||
backgroundColor: "black"
|
||||
}}
|
||||
>
|
||||
<ImageViewer
|
||||
enableImageZoom={true}
|
||||
renderIndicator={() => <></>}
|
||||
enableSwipeDown
|
||||
useNativeDriver
|
||||
onSwipeDown={close}
|
||||
saveToLocalByLongPress={false}
|
||||
renderHeader={() => (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
width: "100%",
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
height: 80,
|
||||
marginTop: 0,
|
||||
paddingHorizontal: 12,
|
||||
position: "absolute",
|
||||
zIndex: 999,
|
||||
backgroundColor: "rgba(0,0,0,0.3)",
|
||||
paddingTop: 30
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
name="close"
|
||||
color="white"
|
||||
onPress={() => {
|
||||
close();
|
||||
{loading ? (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<ProgressBarComponent
|
||||
indeterminate
|
||||
color={colors.accent}
|
||||
borderColor="transparent"
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<ImageViewer
|
||||
enableImageZoom={true}
|
||||
renderIndicator={() => <></>}
|
||||
enableSwipeDown
|
||||
useNativeDriver
|
||||
onSwipeDown={close}
|
||||
saveToLocalByLongPress={false}
|
||||
renderHeader={() => (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
width: "100%",
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
height: 80,
|
||||
marginTop: 0,
|
||||
paddingHorizontal: 12,
|
||||
position: "absolute",
|
||||
zIndex: 999,
|
||||
backgroundColor: "rgba(0,0,0,0.3)",
|
||||
paddingTop: Platform.OS === "android" ? 30 : 0
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
imageUrls={[
|
||||
{
|
||||
url: image
|
||||
}
|
||||
]}
|
||||
/>
|
||||
>
|
||||
<IconButton
|
||||
name="close"
|
||||
color="white"
|
||||
onPress={() => {
|
||||
close();
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
imageUrls={[
|
||||
{
|
||||
url: image
|
||||
}
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</BaseDialog>
|
||||
)
|
||||
|
||||
@@ -54,7 +54,6 @@ import Config from "react-native-config";
|
||||
import { getGithubVersion } from "../../utils/github-version";
|
||||
import notifee from "@notifee/react-native";
|
||||
|
||||
|
||||
const Launcher = React.memo(
|
||||
function Launcher() {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
@@ -151,7 +150,7 @@ const Launcher = React.memo(
|
||||
}, [introCompleted]);
|
||||
|
||||
const checkAppUpdateAvailable = async () => {
|
||||
if (__DEV__) return;
|
||||
if (__DEV__ || Config.isTesting === "true") return;
|
||||
try {
|
||||
const version =
|
||||
Config.GITHUB_RELEASE === "true"
|
||||
|
||||
@@ -24,6 +24,7 @@ import { View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { notesnook } from "../../../../e2e/test.ids";
|
||||
import { db } from "../../../common/database";
|
||||
import Notebook from "../../../screens/notebook";
|
||||
import { TaggedNotes } from "../../../screens/notes/tagged";
|
||||
import { TopicNotes } from "../../../screens/notes/topic-notes";
|
||||
import { useRelationStore } from "../../../stores/use-relation-store";
|
||||
@@ -39,10 +40,6 @@ import { TimeSince } from "../../ui/time-since";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
|
||||
const navigateToTopic = (topic) => {
|
||||
TopicNotes.navigate(topic, true);
|
||||
};
|
||||
|
||||
function navigateToTag(item) {
|
||||
const tag = db.tags.tag(item.id);
|
||||
if (!tag) return;
|
||||
@@ -55,24 +52,29 @@ const showActionSheet = (item) => {
|
||||
|
||||
function getNotebook(item) {
|
||||
const isTrash = item.type === "trash";
|
||||
if (isTrash || !item.notebooks || item.notebooks.length < 1) return [];
|
||||
if (isTrash) return [];
|
||||
const items = [];
|
||||
const notebooks = db.relations.to(item, "notebook") || [];
|
||||
|
||||
return item.notebooks.reduce(function (prev, curr) {
|
||||
if (prev && prev.length > 0) return prev;
|
||||
const topicId = curr.topics[0];
|
||||
const notebook = db.notebooks?.notebook(curr.id)?.data;
|
||||
if (!notebook) return [];
|
||||
const topic = notebook.topics.find((t) => t.id === topicId);
|
||||
if (!topic) return [];
|
||||
for (let notebook of notebooks) {
|
||||
if (items.length > 1) break;
|
||||
items.push(notebook);
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
title: `${notebook?.title} › ${topic?.title}`,
|
||||
notebook: notebook,
|
||||
topic: topic
|
||||
if (item.notebooks) {
|
||||
for (let nb of item.notebooks) {
|
||||
if (items.length > 1) break;
|
||||
const notebook = db.notebooks?.notebook(nb.id)?.data;
|
||||
if (!notebook) continue;
|
||||
for (let topicId of nb.topics) {
|
||||
if (items.length > 1) break;
|
||||
const topic = notebook.topics.find((t) => t.id === topicId);
|
||||
if (!topic) continue;
|
||||
items.push(topic);
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
const NoteItem = ({
|
||||
@@ -88,10 +90,11 @@ const NoteItem = ({
|
||||
);
|
||||
const compactMode = notesListMode === "compact";
|
||||
const attachmentCount = db.attachments?.ofNote(item.id, "all")?.length || 0;
|
||||
const notebooks = React.useMemo(() => getNotebook(item), [item]);
|
||||
const _update = useRelationStore((state) => state.updater);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const notebooks = React.useMemo(() => getNotebook(item), [item, _update]);
|
||||
const reminders = db.relations.from(item, "reminder");
|
||||
const reminder = getUpcomingReminder(reminders);
|
||||
const _update = useRelationStore((state) => state.updater);
|
||||
const noteColor = COLORS_NOTE[item.color?.toLowerCase()];
|
||||
return (
|
||||
<>
|
||||
@@ -112,12 +115,12 @@ const NoteItem = ({
|
||||
flexWrap: "wrap"
|
||||
}}
|
||||
>
|
||||
{notebooks?.map((_item) => (
|
||||
{notebooks?.map((item) => (
|
||||
<Button
|
||||
title={_item.title}
|
||||
key={_item}
|
||||
title={item.title}
|
||||
key={item}
|
||||
height={25}
|
||||
icon="book-outline"
|
||||
icon={item.type === "topic" ? "bookmark" : "book-outline"}
|
||||
type="grayBg"
|
||||
fontSize={SIZE.xs}
|
||||
iconSize={SIZE.sm}
|
||||
@@ -132,7 +135,13 @@ const NoteItem = ({
|
||||
paddingHorizontal: 6,
|
||||
marginBottom: 5
|
||||
}}
|
||||
onPress={() => navigateToTopic(_item.topic)}
|
||||
onPress={() => {
|
||||
if (item.type === "topic") {
|
||||
TopicNotes.navigate(item, true);
|
||||
} else {
|
||||
Notebook.navigate(item);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ const RenderItem = ({ item, index, type, ...restArgs }) => {
|
||||
};
|
||||
})
|
||||
.filter((t) => t !== null) || [];
|
||||
|
||||
return (
|
||||
<Item
|
||||
item={item}
|
||||
@@ -103,7 +104,9 @@ const List = ({
|
||||
ListHeader,
|
||||
warning,
|
||||
isSheet = false,
|
||||
onMomentumScrollEnd
|
||||
onMomentumScrollEnd,
|
||||
handlers,
|
||||
ScrollComponent
|
||||
}) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const scrollRef = useRef();
|
||||
@@ -158,7 +161,7 @@ const List = ({
|
||||
};
|
||||
|
||||
const _keyExtractor = (item) => item.id || item.title;
|
||||
|
||||
const ListView = ScrollComponent ? ScrollComponent : FlashList;
|
||||
return (
|
||||
<>
|
||||
<Animated.View
|
||||
@@ -167,7 +170,8 @@ const List = ({
|
||||
}}
|
||||
entering={type === "search" ? undefined : FadeInDown}
|
||||
>
|
||||
<FlashList
|
||||
<ListView
|
||||
{...handlers}
|
||||
style={styles}
|
||||
ref={scrollRef}
|
||||
testID={notesnook.list.id}
|
||||
|
||||
@@ -89,6 +89,7 @@ export const ColorTags = ({ item }) => {
|
||||
flexWrap: "wrap",
|
||||
flexGrow: isTablet ? undefined : 1,
|
||||
paddingHorizontal: 12,
|
||||
paddingRight: 0,
|
||||
alignItems: "center",
|
||||
justifyContent: isTablet ? "center" : "space-between"
|
||||
}}
|
||||
|
||||
@@ -16,10 +16,9 @@ GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { Platform, View } from "react-native";
|
||||
import { ScrollView } from "react-native-gesture-handler";
|
||||
import { FlatList } from "react-native-actions-sheet";
|
||||
import { db } from "../../common/database";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import { presentSheet } from "../../services/event-manager";
|
||||
@@ -37,7 +36,6 @@ import { Items } from "./items";
|
||||
import Notebooks from "./notebooks";
|
||||
import { Synced } from "./synced";
|
||||
import { Tags, TagStrip } from "./tags";
|
||||
|
||||
const Line = ({ top = 6, bottom = 6 }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
return (
|
||||
@@ -53,20 +51,11 @@ const Line = ({ top = 6, bottom = 6 }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const Properties = ({
|
||||
close = () => {},
|
||||
item,
|
||||
buttons = [],
|
||||
getRef
|
||||
}) => {
|
||||
export const Properties = ({ close = () => {}, item, buttons = [] }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const alias = item.alias || item.title;
|
||||
const isColor = !!COLORS_NOTE[item.title];
|
||||
|
||||
const onScrollEnd = () => {
|
||||
getRef().current?.handleChildScrollEnd();
|
||||
};
|
||||
|
||||
if (!item || !item.id) {
|
||||
return (
|
||||
<Paragraph style={{ marginVertical: 10, alignSelf: "center" }}>
|
||||
@@ -75,89 +64,92 @@ export const Properties = ({
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ScrollView
|
||||
nestedScrollEnabled
|
||||
onMomentumScrollEnd={onScrollEnd}
|
||||
<FlatList
|
||||
keyboardShouldPersistTaps="always"
|
||||
keyboardDismissMode="none"
|
||||
style={{
|
||||
backgroundColor: colors.bg,
|
||||
paddingHorizontal: 0,
|
||||
borderBottomRightRadius: DDS.isLargeTablet() ? 10 : 1,
|
||||
borderBottomLeftRadius: DDS.isLargeTablet() ? 10 : 1,
|
||||
maxHeight: "100%"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
marginTop: 5,
|
||||
zIndex: 10
|
||||
}}
|
||||
>
|
||||
<Heading size={SIZE.lg}>
|
||||
{item.type === "tag" && !isColor ? (
|
||||
<Heading size={SIZE.xl} color={colors.accent}>
|
||||
#
|
||||
data={[0]}
|
||||
keyExtractor={() => "properties-scroll-item"}
|
||||
renderItem={() => (
|
||||
<View>
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
marginTop: 5,
|
||||
zIndex: 10
|
||||
}}
|
||||
>
|
||||
<Heading size={SIZE.lg}>
|
||||
{item.type === "tag" && !isColor ? (
|
||||
<Heading size={SIZE.xl} color={colors.accent}>
|
||||
#
|
||||
</Heading>
|
||||
) : null}
|
||||
{alias}
|
||||
</Heading>
|
||||
|
||||
) : null}
|
||||
{alias}
|
||||
</Heading>
|
||||
{item.type === "note" ? (
|
||||
<TagStrip close={close} item={item} />
|
||||
) : null}
|
||||
|
||||
{item.type === "note" ? <TagStrip close={close} item={item} /> : null}
|
||||
{item.type === "reminder" ? (
|
||||
<ReminderTime
|
||||
reminder={item}
|
||||
style={{
|
||||
justifyContent: "flex-start",
|
||||
borderWidth: 0,
|
||||
height: 30,
|
||||
alignSelf: "flex-start",
|
||||
backgroundColor: "transparent",
|
||||
paddingHorizontal: 0
|
||||
}}
|
||||
fontSize={SIZE.xs + 1}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
<Line top={12} />
|
||||
|
||||
{item.type === "reminder" ? (
|
||||
<ReminderTime
|
||||
reminder={item}
|
||||
<DateMeta item={item} />
|
||||
<Line bottom={0} />
|
||||
{item.type === "note" ? <Tags close={close} item={item} /> : null}
|
||||
|
||||
<View
|
||||
style={{
|
||||
justifyContent: "flex-start",
|
||||
borderWidth: 0,
|
||||
height: 30,
|
||||
alignSelf: "flex-start",
|
||||
backgroundColor: "transparent",
|
||||
paddingHorizontal: 0
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
>
|
||||
<Notebooks note={item} close={close} />
|
||||
</View>
|
||||
|
||||
<Items
|
||||
item={item}
|
||||
buttons={buttons}
|
||||
close={() => {
|
||||
close();
|
||||
setTimeout(() => {
|
||||
SearchService.updateAndSearch();
|
||||
}, 1000);
|
||||
}}
|
||||
fontSize={SIZE.xs + 1}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
<Line top={12} />
|
||||
<Synced item={item} close={close} />
|
||||
<DevMode item={item} />
|
||||
|
||||
<DateMeta item={item} />
|
||||
<Line bottom={0} />
|
||||
{item.type === "note" ? <Tags close={close} item={item} /> : null}
|
||||
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
>
|
||||
{item.notebooks ? <Notebooks note={item} close={close} /> : null}
|
||||
</View>
|
||||
|
||||
<Items
|
||||
item={item}
|
||||
buttons={buttons}
|
||||
close={() => {
|
||||
close();
|
||||
setTimeout(() => {
|
||||
SearchService.updateAndSearch();
|
||||
}, 1000);
|
||||
}}
|
||||
/>
|
||||
<Synced item={item} close={close} />
|
||||
<DevMode item={item} />
|
||||
|
||||
{DDS.isTab ? (
|
||||
<View
|
||||
style={{
|
||||
height: 20
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<SheetProvider context="properties" />
|
||||
</ScrollView>
|
||||
{DDS.isTab ? (
|
||||
<View
|
||||
style={{
|
||||
height: 20
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<SheetProvider context="properties" />
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -187,6 +179,7 @@ Properties.present = (item, buttons = [], isSheet) => {
|
||||
"lock-unlock",
|
||||
"trash",
|
||||
"remove-from-topic",
|
||||
"remove-from-notebook",
|
||||
"history",
|
||||
"read-only",
|
||||
"reminders",
|
||||
@@ -219,12 +212,13 @@ Properties.present = (item, buttons = [], isSheet) => {
|
||||
if (!props[0]) return;
|
||||
presentSheet({
|
||||
context: isSheet ? "local" : undefined,
|
||||
enableGesturesInScrollView: true,
|
||||
component: (ref, close) => (
|
||||
<Properties
|
||||
close={() => {
|
||||
close();
|
||||
}}
|
||||
getRef={() => ref}
|
||||
actionSheetRef={ref}
|
||||
item={props[0]}
|
||||
buttons={props[1]}
|
||||
/>
|
||||
|
||||
@@ -18,8 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { FlatList } from "react-native-gesture-handler";
|
||||
import { FlatList, ScrollView, View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { useActions } from "../../hooks/use-actions";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
@@ -85,8 +84,9 @@ export const Items = ({ item, buttons, close }) => {
|
||||
</View>
|
||||
);
|
||||
|
||||
const renderColumnItem = ({ item }) => (
|
||||
const renderColumnItem = (item) => (
|
||||
<Button
|
||||
key={item.name + item.title}
|
||||
buttonType={{
|
||||
text: item.on
|
||||
? colors.accent
|
||||
@@ -108,7 +108,8 @@ export const Items = ({ item, buttons, close }) => {
|
||||
/>
|
||||
);
|
||||
|
||||
const renderTopBarItem = ({ item }) => {
|
||||
const renderTopBarItem = (item, index) => {
|
||||
const isLast = index === topBarItems.length;
|
||||
return (
|
||||
<PressableButton
|
||||
onPress={item.func}
|
||||
@@ -118,7 +119,7 @@ export const Items = ({ item, buttons, close }) => {
|
||||
alignItems: "center",
|
||||
width: topBarItemWidth,
|
||||
marginBottom: 10,
|
||||
marginRight: 10,
|
||||
marginRight: isLast ? 0 : 10,
|
||||
backgroundColor: "transparent"
|
||||
}}
|
||||
>
|
||||
@@ -169,7 +170,7 @@ export const Items = ({ item, buttons, close }) => {
|
||||
"lock-unlock",
|
||||
"publish"
|
||||
];
|
||||
const bottomBarItems = data.filter(
|
||||
const topBarItems = data.filter(
|
||||
(item) => topBarItemsList.indexOf(item.id) > -1
|
||||
);
|
||||
|
||||
@@ -178,21 +179,19 @@ export const Items = ({ item, buttons, close }) => {
|
||||
);
|
||||
|
||||
const topBarItemWidth =
|
||||
(width - (bottomBarItems.length * 10 + 14)) / bottomBarItems.length;
|
||||
(width - (topBarItems.length * 10 + 14)) / topBarItems.length;
|
||||
|
||||
return item.type === "note" ? (
|
||||
<>
|
||||
<FlatList
|
||||
data={bottomBarItems}
|
||||
keyExtractor={(item) => item.title}
|
||||
<ScrollView
|
||||
horizontal
|
||||
disableVirtualization={true}
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
paddingTop: 12
|
||||
}}
|
||||
renderItem={renderTopBarItem}
|
||||
/>
|
||||
>
|
||||
{topBarItems.map(renderTopBarItem)}
|
||||
</ScrollView>
|
||||
|
||||
<FlatList
|
||||
data={bottomGridItems}
|
||||
@@ -202,10 +201,8 @@ export const Items = ({ item, buttons, close }) => {
|
||||
disableVirtualization={true}
|
||||
style={{
|
||||
marginTop: item.type !== "note" ? 10 : 0,
|
||||
paddingTop: 10
|
||||
}}
|
||||
columnWrapperStyle={{
|
||||
justifyContent: "flex-start"
|
||||
paddingTop: 10,
|
||||
marginLeft: 6
|
||||
}}
|
||||
contentContainerStyle={{
|
||||
alignSelf: "center",
|
||||
@@ -216,11 +213,6 @@ export const Items = ({ item, buttons, close }) => {
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<FlatList
|
||||
data={data}
|
||||
keyExtractor={(item) => item.title}
|
||||
renderItem={renderColumnItem}
|
||||
disableVirtualization={true}
|
||||
/>
|
||||
<View data={data}>{data.map(renderColumnItem)}</View>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -35,8 +35,16 @@ export default function Notebooks({ note, close, full }) {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const notebooks = useNotebookStore((state) => state.notebooks);
|
||||
function getNotebooks(item) {
|
||||
if (!item.notebooks || item.notebooks.length < 1) return [];
|
||||
let filteredNotebooks = [];
|
||||
const relations = db.relations.to(note, "notebook");
|
||||
filteredNotebooks.push(
|
||||
...relations.map((notebook) => ({
|
||||
...notebook,
|
||||
topics: []
|
||||
}))
|
||||
);
|
||||
if (!item.notebooks || item.notebooks.length < 1) return filteredNotebooks;
|
||||
|
||||
for (let notebookReference of item.notebooks) {
|
||||
let notebook = {
|
||||
...(notebooks.find((item) => item.id === notebookReference.id) || {})
|
||||
@@ -45,7 +53,14 @@ export default function Notebooks({ note, close, full }) {
|
||||
notebook.topics = notebook.topics.filter((topic) => {
|
||||
return notebookReference.topics.findIndex((t) => t === topic.id) > -1;
|
||||
});
|
||||
filteredNotebooks.push(notebook);
|
||||
const index = filteredNotebooks.findIndex(
|
||||
(item) => item.id === notebook.id
|
||||
);
|
||||
if (index > -1) {
|
||||
filteredNotebooks[index].topics = notebook.topics;
|
||||
} else {
|
||||
filteredNotebooks.push(notebook);
|
||||
}
|
||||
}
|
||||
}
|
||||
return filteredNotebooks;
|
||||
@@ -63,7 +78,6 @@ export default function Notebooks({ note, close, full }) {
|
||||
if (!item) return;
|
||||
TopicNotes.navigate(item, true);
|
||||
};
|
||||
|
||||
const renderItem = (item) => (
|
||||
<View
|
||||
key={item.id}
|
||||
@@ -78,7 +92,8 @@ export default function Notebooks({ note, close, full }) {
|
||||
borderWidth: full ? 0 : 1,
|
||||
borderColor: colors.nav,
|
||||
borderRadius: 10,
|
||||
backgroundColor: full ? "transparent" : colors.nav
|
||||
backgroundColor: full ? "transparent" : colors.nav,
|
||||
minHeight: 42
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
@@ -160,7 +175,7 @@ export default function Notebooks({ note, close, full }) {
|
||||
</View>
|
||||
);
|
||||
|
||||
return !note.notebooks || note.notebooks.length === 0 ? null : (
|
||||
return noteNotebooks.length === 0 ? null : (
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
|
||||
@@ -21,11 +21,10 @@ import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { db } from "../../common/database";
|
||||
import { TaggedNotes } from "../../screens/notes/tagged";
|
||||
import { eSendEvent } from "../../services/event-manager";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { eOpenTagsDialog } from "../../utils/events";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
import ManageTagsSheet from "../sheets/manage-tags";
|
||||
import { Button } from "../ui/button";
|
||||
import { ColorTags } from "./color-tags";
|
||||
export const Tags = ({ item, close }) => {
|
||||
@@ -41,14 +40,14 @@ export const Tags = ({ item, close }) => {
|
||||
flexWrap: "wrap",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 12,
|
||||
alignSelf: "center"
|
||||
alignSelf: "center",
|
||||
justifyContent: "space-between",
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
onPress={async () => {
|
||||
close();
|
||||
await sleep(300);
|
||||
eSendEvent(eOpenTagsDialog, item);
|
||||
ManageTagsSheet.present(item);
|
||||
}}
|
||||
buttonType={{
|
||||
text: colors.accent
|
||||
@@ -79,8 +78,8 @@ export const TagStrip = ({ item, close }) => {
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
{item.tags.map((item) =>
|
||||
item ? <TagItem key={item} tag={item} close={close} /> : null
|
||||
{item.tags.map((tag) =>
|
||||
tag ? <TagItem key={tag} tag={tag} close={close} /> : null
|
||||
)}
|
||||
</View>
|
||||
) : null;
|
||||
@@ -102,7 +101,6 @@ const TagItem = ({ tag, close }) => {
|
||||
marginTop: 0,
|
||||
backgroundColor: "transparent"
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
onPress={onPress}
|
||||
|
||||
@@ -201,7 +201,7 @@ export const SelectionHeader = React.memo(() => {
|
||||
}}
|
||||
>
|
||||
<Heading size={SIZE.md} color={colors.accent}>
|
||||
{selectedItemsList.length + " Selected"}
|
||||
{selectedItemsList.length}
|
||||
</Heading>
|
||||
</View>
|
||||
</View>
|
||||
@@ -264,19 +264,29 @@ export const SelectionHeader = React.memo(() => {
|
||||
</>
|
||||
)}
|
||||
|
||||
{screen === "TopicNotes" ? (
|
||||
{screen === "TopicNotes" || screen === "Notebook" ? (
|
||||
<IconButton
|
||||
onPress={async () => {
|
||||
if (selectedItemsList.length > 0) {
|
||||
const currentTopic =
|
||||
const currentScreen =
|
||||
useNavigationStore.getState().currentScreen;
|
||||
await db.notes.removeFromNotebook(
|
||||
{
|
||||
id: currentTopic.notebookId,
|
||||
topic: currentTopic.id
|
||||
},
|
||||
...selectedItemsList.map((item) => item.id)
|
||||
);
|
||||
|
||||
if (screen === "Notebook") {
|
||||
for (const item of selectedItemsList) {
|
||||
await db.relations.unlink(
|
||||
{ type: "notebook", id: currentScreen.id },
|
||||
item
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await db.notes.removeFromNotebook(
|
||||
{
|
||||
id: currentScreen.notebookId,
|
||||
topic: currentScreen.id
|
||||
},
|
||||
...selectedItemsList.map((item) => item.id)
|
||||
);
|
||||
}
|
||||
|
||||
Navigation.queueRoutesForUpdate(
|
||||
"Notes",
|
||||
@@ -293,7 +303,9 @@ export const SelectionHeader = React.memo(() => {
|
||||
customStyle={{
|
||||
marginLeft: 10
|
||||
}}
|
||||
tooltipText="Remove from topic"
|
||||
tooltipText={`Remove from ${
|
||||
screen === "Notebook" ? "notebook" : "topic"
|
||||
}`}
|
||||
tooltipPosition={4}
|
||||
testID="select-minus"
|
||||
color={colors.pri}
|
||||
|
||||
@@ -36,7 +36,7 @@ import Paragraph from "../ui/typography/paragraph";
|
||||
const SheetProvider = ({ context = "global" }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [dialogData, setDialogData] = useState(null);
|
||||
const [data, setData] = useState(null);
|
||||
const actionSheetRef = useRef();
|
||||
const editor = useRef({
|
||||
refocus: false
|
||||
@@ -52,42 +52,35 @@ const SheetProvider = ({ context = "global" }) => {
|
||||
}, [close, open, visible]);
|
||||
|
||||
const open = useCallback(
|
||||
async (data) => {
|
||||
if (!data.context) data.context = "global";
|
||||
if (data.context !== context) return;
|
||||
if (visible || dialogData) {
|
||||
setDialogData(null);
|
||||
setVisible(false);
|
||||
await sleep(0);
|
||||
}
|
||||
setDialogData(data);
|
||||
async (payload) => {
|
||||
if (!payload.context) payload.context = "global";
|
||||
if (payload.context !== context) return;
|
||||
setData(payload);
|
||||
setVisible(true);
|
||||
if (data.editor) {
|
||||
if (payload.editor) {
|
||||
editor.current.refocus = false;
|
||||
if (editorState().keyboardState) {
|
||||
// tiny.call(EditorWebView, tiny.cacheRange + tiny.blur);
|
||||
editor.current.refocus = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
[context, dialogData, visible]
|
||||
[context]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (visible && dialogData) {
|
||||
if (dialogData.editor) await sleep(100);
|
||||
if (visible && data) {
|
||||
if (data.editor) await sleep(100);
|
||||
actionSheetRef.current?.setModalVisible(true);
|
||||
return;
|
||||
} else {
|
||||
if (editor.current?.refocus) {
|
||||
editorState().isFocused = true;
|
||||
// tiny.call(EditorWebView, tiny.restoreRange + tiny.clearRange);
|
||||
editor.current.refocus = false;
|
||||
}
|
||||
}
|
||||
})();
|
||||
}, [visible, dialogData]);
|
||||
}, [visible, data]);
|
||||
|
||||
const close = useCallback(
|
||||
(ctx) => {
|
||||
@@ -98,34 +91,30 @@ const SheetProvider = ({ context = "global" }) => {
|
||||
[context]
|
||||
);
|
||||
|
||||
return !visible || !dialogData ? null : (
|
||||
return !visible || !data ? null : (
|
||||
<SheetWrapper
|
||||
fwdRef={actionSheetRef}
|
||||
gestureEnabled={!dialogData?.progress && !dialogData?.disableClosing}
|
||||
closeOnTouchBackdrop={
|
||||
!dialogData?.progress && !dialogData?.disableClosing
|
||||
}
|
||||
gestureEnabled={!data?.progress && !data?.disableClosing}
|
||||
closeOnTouchBackdrop={!data?.progress && !data?.disableClosing}
|
||||
onClose={() => {
|
||||
dialogData.onClose && dialogData.onClose();
|
||||
data.onClose && data.onClose();
|
||||
setVisible(false);
|
||||
setDialogData(null);
|
||||
setData(null);
|
||||
}}
|
||||
enableGesturesInScrollView={data.enableGesturesInScrollView}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginBottom:
|
||||
!dialogData.progress &&
|
||||
!dialogData.icon &&
|
||||
!dialogData.title &&
|
||||
!dialogData.paragraph
|
||||
!data.progress && !data.icon && !data.title && !data.paragraph
|
||||
? 0
|
||||
: 10,
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
>
|
||||
{dialogData?.progress ? (
|
||||
{data?.progress ? (
|
||||
<ActivityIndicator
|
||||
style={{
|
||||
marginTop: 15
|
||||
@@ -135,47 +124,48 @@ const SheetProvider = ({ context = "global" }) => {
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{dialogData?.icon ? (
|
||||
{data?.icon ? (
|
||||
<Icon
|
||||
color={colors[dialogData.iconColor] || colors.accent}
|
||||
name={dialogData.icon}
|
||||
color={colors[data.iconColor] || colors.accent}
|
||||
name={data.icon}
|
||||
size={50}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{dialogData?.title ? <Heading> {dialogData?.title}</Heading> : null}
|
||||
{data?.title ? <Heading> {data?.title}</Heading> : null}
|
||||
|
||||
{dialogData?.paragraph ? (
|
||||
{data?.paragraph ? (
|
||||
<Paragraph style={{ textAlign: "center" }}>
|
||||
{dialogData?.paragraph}
|
||||
{data?.paragraph}
|
||||
</Paragraph>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
{typeof dialogData.component === "function"
|
||||
? dialogData.component(
|
||||
{typeof data.component === "function"
|
||||
? data.component(
|
||||
actionSheetRef,
|
||||
() => close(context),
|
||||
(data) => {
|
||||
if (!data) return;
|
||||
setDialogData((prevData) => {
|
||||
setData((prevData) => {
|
||||
return {
|
||||
...prevData,
|
||||
...data
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
colors
|
||||
)
|
||||
: dialogData.component}
|
||||
: data.component}
|
||||
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
marginBottom: dialogData.valueArray ? 12 : 0
|
||||
marginBottom: data.valueArray ? 12 : 0
|
||||
}}
|
||||
>
|
||||
{dialogData.valueArray &&
|
||||
dialogData.valueArray.map((v) => (
|
||||
{data.valueArray &&
|
||||
data.valueArray.map((v) => (
|
||||
<Button
|
||||
title={v}
|
||||
type="gray"
|
||||
@@ -197,12 +187,12 @@ const SheetProvider = ({ context = "global" }) => {
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
>
|
||||
{dialogData?.action ? (
|
||||
{data?.action ? (
|
||||
<Button
|
||||
onPress={dialogData.action}
|
||||
key={dialogData.actionText}
|
||||
title={dialogData.actionText}
|
||||
accentColor={dialogData.iconColor || "accent"}
|
||||
onPress={data.action}
|
||||
key={data.actionText}
|
||||
title={data.actionText}
|
||||
accentColor={data.iconColor || "accent"}
|
||||
accentText="light"
|
||||
type="accent"
|
||||
height={45}
|
||||
@@ -214,8 +204,8 @@ const SheetProvider = ({ context = "global" }) => {
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{dialogData?.actionsArray &&
|
||||
dialogData?.actionsArray.map((item) => (
|
||||
{data?.actionsArray &&
|
||||
data?.actionsArray.map((item) => (
|
||||
<Button
|
||||
onPress={item.action}
|
||||
key={item.accentText}
|
||||
@@ -231,7 +221,7 @@ const SheetProvider = ({ context = "global" }) => {
|
||||
/>
|
||||
))}
|
||||
|
||||
{dialogData?.learnMore ? (
|
||||
{data?.learnMore ? (
|
||||
<Paragraph
|
||||
style={{
|
||||
alignSelf: "center",
|
||||
@@ -239,7 +229,7 @@ const SheetProvider = ({ context = "global" }) => {
|
||||
textDecorationLine: "underline"
|
||||
}}
|
||||
size={SIZE.xs}
|
||||
onPress={dialogData.learnMorePress}
|
||||
onPress={data.learnMorePress}
|
||||
color={colors.icon}
|
||||
>
|
||||
<Icon
|
||||
@@ -247,7 +237,7 @@ const SheetProvider = ({ context = "global" }) => {
|
||||
name="information-outline"
|
||||
size={SIZE.xs}
|
||||
/>{" "}
|
||||
{dialogData.learnMore}
|
||||
{data.learnMore}
|
||||
</Paragraph>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
@@ -25,41 +25,34 @@ import {
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from "react-native";
|
||||
import { FlatList } from "react-native-gesture-handler";
|
||||
import { notesnook } from "../../../../e2e/test.ids";
|
||||
import { useMenuStore } from "../../../stores/use-menu-store";
|
||||
import { DDS } from "../../../services/device-detection";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent,
|
||||
ToastEvent
|
||||
} from "../../../services/event-manager";
|
||||
import Navigation from "../../../services/navigation";
|
||||
import { db } from "../../../common/database";
|
||||
import {
|
||||
eCloseAddNotebookDialog,
|
||||
eOpenAddNotebookDialog
|
||||
} from "../../../utils/events";
|
||||
import { DDS } from "../../../services/device-detection";
|
||||
import { presentSheet, ToastEvent } from "../../../services/event-manager";
|
||||
import Navigation from "../../../services/navigation";
|
||||
import { useMenuStore } from "../../../stores/use-menu-store";
|
||||
import { useRelationStore } from "../../../stores/use-relation-store";
|
||||
import { ph, pv, SIZE } from "../../../utils/size";
|
||||
import { sleep } from "../../../utils/time";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import { Button } from "../../ui/button";
|
||||
import DialogHeader from "../../dialog/dialog-header";
|
||||
import { Button } from "../../ui/button";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import Input from "../../ui/input";
|
||||
import { MoveNotes } from "../move-notes/movenote";
|
||||
import Seperator from "../../ui/seperator";
|
||||
import SheetWrapper from "../../ui/sheet";
|
||||
import { Toast } from "../../toast";
|
||||
import { MoveNotes } from "../move-notes/movenote";
|
||||
import { FlatList } from "react-native-actions-sheet";
|
||||
|
||||
let refs = [];
|
||||
|
||||
export class AddNotebookSheet extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
refs = [];
|
||||
this.state = {
|
||||
notebook: null,
|
||||
visible: false,
|
||||
topics: [],
|
||||
notebook: props.notebook,
|
||||
topics:
|
||||
props.notebook?.topics?.map((item) => {
|
||||
return item.title;
|
||||
}) || [],
|
||||
description: null,
|
||||
titleFocused: false,
|
||||
descFocused: false,
|
||||
@@ -68,13 +61,14 @@ export class AddNotebookSheet extends React.Component {
|
||||
editTopic: false,
|
||||
loading: false
|
||||
};
|
||||
this.title = null;
|
||||
this.description = null;
|
||||
|
||||
this.title = props.notebook?.title;
|
||||
this.description = props.notebook?.description;
|
||||
this.listRef;
|
||||
this.prevItem = null;
|
||||
this.prevIndex = null;
|
||||
this.currentSelectedInput = null;
|
||||
this.id = null;
|
||||
this.id = props.notebook?.id;
|
||||
this.backPressCount = 0;
|
||||
this.currentInputValue = null;
|
||||
this.titleRef;
|
||||
@@ -83,57 +77,22 @@ export class AddNotebookSheet extends React.Component {
|
||||
this.hiddenInput = createRef();
|
||||
this.topicInputRef = createRef();
|
||||
this.addingTopic = false;
|
||||
this.actionSheetRef = createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
eSubscribeEvent(eOpenAddNotebookDialog, this.open);
|
||||
eSubscribeEvent(eCloseAddNotebookDialog, this.close);
|
||||
this.actionSheetRef = props.actionSheetRef;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
eUnSubscribeEvent(eOpenAddNotebookDialog, this.open);
|
||||
eUnSubscribeEvent(eCloseAddNotebookDialog, this.close);
|
||||
refs = [];
|
||||
}
|
||||
|
||||
open = (notebook) => {
|
||||
refs = [];
|
||||
|
||||
if (notebook) {
|
||||
let topicsList = [];
|
||||
notebook.topics.forEach((item) => {
|
||||
topicsList.push(item.title);
|
||||
});
|
||||
this.id = notebook.id;
|
||||
this.title = notebook.title;
|
||||
this.description = notebook.description;
|
||||
|
||||
this.setState({
|
||||
topics: [...topicsList],
|
||||
visible: true,
|
||||
notebook: notebook
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
visible: true,
|
||||
notebook: null
|
||||
});
|
||||
}
|
||||
sleep(100).then(() => {
|
||||
this.actionSheetRef.current?.show();
|
||||
componentDidMount() {
|
||||
sleep(300).then(() => {
|
||||
!this.state.notebook && this.titleRef?.focus();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
close = () => {
|
||||
this.actionSheetRef.current?.hide();
|
||||
refs = [];
|
||||
this.prevIndex = null;
|
||||
this.prevItem = null;
|
||||
this.currentSelectedInput = null;
|
||||
this.title = null;
|
||||
this.description = null;
|
||||
this.currentInputValue = null;
|
||||
this.id = null;
|
||||
this.props.close();
|
||||
};
|
||||
|
||||
onDelete = (index) => {
|
||||
@@ -240,15 +199,8 @@ export class AddNotebookSheet extends React.Component {
|
||||
"Notebooks",
|
||||
"Notebook"
|
||||
);
|
||||
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
this.close();
|
||||
await sleep(300);
|
||||
if (!notebook) {
|
||||
MoveNotes.present(db.notebooks.notebook(newNotebookId).data);
|
||||
}
|
||||
useRelationStore.getState().update();
|
||||
MoveNotes.present(db.notebooks.notebook(newNotebookId).data);
|
||||
};
|
||||
|
||||
onSubmit = (forward = true) => {
|
||||
@@ -265,7 +217,7 @@ export class AddNotebookSheet extends React.Component {
|
||||
topics: prevTopics
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.listRef.scrollToEnd({ animated: true });
|
||||
this.listRef.current?.scrollToEnd?.({ animated: true });
|
||||
}, 30);
|
||||
this.currentInputValue = null;
|
||||
} else {
|
||||
@@ -289,7 +241,7 @@ export class AddNotebookSheet extends React.Component {
|
||||
|
||||
if (forward) {
|
||||
setTimeout(() => {
|
||||
this.listRef.scrollToEnd({ animated: true });
|
||||
this.listRef.current?.scrollToEnd?.({ animated: true });
|
||||
}, 30);
|
||||
}
|
||||
}
|
||||
@@ -301,166 +253,139 @@ export class AddNotebookSheet extends React.Component {
|
||||
|
||||
render() {
|
||||
const { colors } = this.props;
|
||||
const { topics, visible, topicInputFocused, notebook } = this.state;
|
||||
if (!visible) return null;
|
||||
const { topics, topicInputFocused, notebook } = this.state;
|
||||
return (
|
||||
<SheetWrapper
|
||||
onOpen={async () => {
|
||||
this.topicsToDelete = [];
|
||||
await sleep(300);
|
||||
!this.state.notebook && this.titleRef?.focus();
|
||||
<View
|
||||
style={{
|
||||
maxHeight: DDS.isTab ? "90%" : "96%",
|
||||
borderRadius: DDS.isTab ? 5 : 0,
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
fwdRef={this.actionSheetRef}
|
||||
onClose={() => {
|
||||
this.close();
|
||||
this.setState({
|
||||
visible: false,
|
||||
topics: [],
|
||||
descFocused: false,
|
||||
titleFocused: false,
|
||||
editTopic: false,
|
||||
notebook: null
|
||||
});
|
||||
}}
|
||||
statusBarTranslucent={false}
|
||||
onRequestClose={this.close}
|
||||
>
|
||||
<View
|
||||
<TextInput
|
||||
ref={this.hiddenInput}
|
||||
style={{
|
||||
maxHeight: DDS.isTab ? "90%" : "96%",
|
||||
borderRadius: DDS.isTab ? 5 : 0,
|
||||
paddingHorizontal: 12
|
||||
width: 1,
|
||||
height: 1,
|
||||
opacity: 0,
|
||||
position: "absolute"
|
||||
}}
|
||||
>
|
||||
<TextInput
|
||||
ref={this.hiddenInput}
|
||||
style={{
|
||||
width: 1,
|
||||
height: 1,
|
||||
opacity: 0,
|
||||
position: "absolute"
|
||||
}}
|
||||
blurOnSubmit={false}
|
||||
/>
|
||||
<DialogHeader
|
||||
title={
|
||||
notebook && notebook.dateCreated
|
||||
? "Edit Notebook"
|
||||
: "New Notebook"
|
||||
blurOnSubmit={false}
|
||||
/>
|
||||
<DialogHeader
|
||||
title={
|
||||
notebook && notebook.dateCreated ? "Edit Notebook" : "New Notebook"
|
||||
}
|
||||
paragraph={
|
||||
notebook && notebook.dateCreated
|
||||
? "You are editing " + this.title + " notebook."
|
||||
: "Notebooks are the best way to organize your notes."
|
||||
}
|
||||
/>
|
||||
<Seperator half />
|
||||
|
||||
<Input
|
||||
fwdRef={(ref) => (this.titleRef = ref)}
|
||||
testID={notesnook.ids.dialogs.notebook.inputs.title}
|
||||
onChangeText={(value) => {
|
||||
this.title = value;
|
||||
}}
|
||||
placeholder="Enter a title"
|
||||
onSubmit={() => {
|
||||
this.descriptionRef.focus();
|
||||
}}
|
||||
returnKeyLabel="Next"
|
||||
returnKeyType="next"
|
||||
defaultValue={notebook ? notebook.title : null}
|
||||
/>
|
||||
|
||||
<Input
|
||||
fwdRef={(ref) => (this.descriptionRef = ref)}
|
||||
testID={notesnook.ids.dialogs.notebook.inputs.description}
|
||||
onChangeText={(value) => {
|
||||
this.description = value;
|
||||
}}
|
||||
placeholder="Describe your notebook."
|
||||
onSubmit={() => {
|
||||
this.topicInputRef.current?.focus();
|
||||
}}
|
||||
returnKeyLabel="Next"
|
||||
returnKeyType="next"
|
||||
defaultValue={notebook ? notebook.description : null}
|
||||
/>
|
||||
|
||||
<Input
|
||||
fwdRef={this.topicInputRef}
|
||||
testID={notesnook.ids.dialogs.notebook.inputs.topic}
|
||||
onChangeText={(value) => {
|
||||
this.currentInputValue = value;
|
||||
if (this.prevItem !== null) {
|
||||
refs[this.prevIndex].setNativeProps({
|
||||
text: value,
|
||||
style: {
|
||||
borderBottomColor: colors.accent
|
||||
}
|
||||
});
|
||||
}
|
||||
paragraph={
|
||||
notebook && notebook.dateCreated
|
||||
? "You are editing " + this.title + " notebook."
|
||||
: "Notebooks are the best way to organize your notes."
|
||||
}
|
||||
/>
|
||||
<Seperator half />
|
||||
}}
|
||||
returnKeyLabel="Done"
|
||||
returnKeyType="done"
|
||||
onSubmit={() => {
|
||||
this.onSubmit();
|
||||
}}
|
||||
blurOnSubmit={false}
|
||||
button={{
|
||||
testID: "topic-add-button",
|
||||
icon: this.state.editTopic ? "check" : "plus",
|
||||
onPress: this.onSubmit,
|
||||
color: topicInputFocused ? colors.accent : colors.icon
|
||||
}}
|
||||
placeholder="Add a topic"
|
||||
/>
|
||||
|
||||
<Input
|
||||
fwdRef={(ref) => (this.titleRef = ref)}
|
||||
testID={notesnook.ids.dialogs.notebook.inputs.title}
|
||||
onChangeText={(value) => {
|
||||
this.title = value;
|
||||
}}
|
||||
placeholder="Enter a title"
|
||||
onSubmit={() => {
|
||||
this.descriptionRef.focus();
|
||||
}}
|
||||
returnKeyLabel="Next"
|
||||
returnKeyType="next"
|
||||
defaultValue={notebook ? notebook.title : null}
|
||||
/>
|
||||
|
||||
<Input
|
||||
fwdRef={(ref) => (this.descriptionRef = ref)}
|
||||
testID={notesnook.ids.dialogs.notebook.inputs.description}
|
||||
onChangeText={(value) => {
|
||||
this.description = value;
|
||||
}}
|
||||
placeholder="Describe your notebook."
|
||||
onSubmit={() => {
|
||||
this.topicInputRef.current?.focus();
|
||||
}}
|
||||
returnKeyLabel="Next"
|
||||
returnKeyType="next"
|
||||
defaultValue={notebook ? notebook.description : null}
|
||||
/>
|
||||
|
||||
<Input
|
||||
fwdRef={this.topicInputRef}
|
||||
testID={notesnook.ids.dialogs.notebook.inputs.topic}
|
||||
onChangeText={(value) => {
|
||||
this.currentInputValue = value;
|
||||
if (this.prevItem !== null) {
|
||||
refs[this.prevIndex].setNativeProps({
|
||||
text: value,
|
||||
style: {
|
||||
borderBottomColor: colors.accent
|
||||
}
|
||||
<FlatList
|
||||
data={topics}
|
||||
ref={(ref) => (this.listRef = ref)}
|
||||
nestedScrollEnabled
|
||||
keyExtractor={(item, index) => item + index.toString()}
|
||||
keyboardShouldPersistTaps="always"
|
||||
keyboardDismissMode="interactive"
|
||||
ListFooterComponent={<View style={{ height: 50 }} />}
|
||||
renderItem={({ item, index }) => (
|
||||
<TopicItem
|
||||
item={item}
|
||||
onPress={(item, index) => {
|
||||
this.prevIndex = index;
|
||||
this.prevItem = item;
|
||||
this.topicInputRef.current?.setNativeProps({
|
||||
text: item
|
||||
});
|
||||
}
|
||||
}}
|
||||
returnKeyLabel="Done"
|
||||
returnKeyType="done"
|
||||
onSubmit={() => {
|
||||
this.onSubmit();
|
||||
}}
|
||||
blurOnSubmit={false}
|
||||
button={{
|
||||
testID: "topic-add-button",
|
||||
icon: this.state.editTopic ? "check" : "plus",
|
||||
onPress: this.onSubmit,
|
||||
color: topicInputFocused ? colors.accent : colors.icon
|
||||
}}
|
||||
placeholder="Add a topic"
|
||||
/>
|
||||
|
||||
<FlatList
|
||||
data={topics}
|
||||
ref={(ref) => (this.listRef = ref)}
|
||||
nestedScrollEnabled
|
||||
keyExtractor={(item, index) => item + index.toString()}
|
||||
onMomentumScrollEnd={() => {
|
||||
this.actionSheetRef.current?.handleChildScrollEnd();
|
||||
}}
|
||||
keyboardShouldPersistTaps="always"
|
||||
keyboardDismissMode="interactive"
|
||||
ListFooterComponent={<View style={{ height: 50 }} />}
|
||||
renderItem={({ item, index }) => (
|
||||
<TopicItem
|
||||
item={item}
|
||||
onPress={(item, index) => {
|
||||
this.prevIndex = index;
|
||||
this.prevItem = item;
|
||||
this.topicInputRef.current?.setNativeProps({
|
||||
text: item
|
||||
});
|
||||
this.topicInputRef.current?.focus();
|
||||
this.currentInputValue = item;
|
||||
this.setState({
|
||||
editTopic: true
|
||||
});
|
||||
}}
|
||||
onDelete={this.onDelete}
|
||||
index={index}
|
||||
colors={colors}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Seperator />
|
||||
<Button
|
||||
width="100%"
|
||||
height={50}
|
||||
fontSize={SIZE.md}
|
||||
title={
|
||||
notebook && notebook.dateCreated
|
||||
? "Save changes"
|
||||
: "Create notebook"
|
||||
}
|
||||
type="accent"
|
||||
onPress={this.addNewNotebook}
|
||||
/>
|
||||
{/*
|
||||
this.topicInputRef.current?.focus();
|
||||
this.currentInputValue = item;
|
||||
this.setState({
|
||||
editTopic: true
|
||||
});
|
||||
}}
|
||||
onDelete={this.onDelete}
|
||||
index={index}
|
||||
colors={colors}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Seperator />
|
||||
<Button
|
||||
width="100%"
|
||||
height={50}
|
||||
fontSize={SIZE.md}
|
||||
title={
|
||||
notebook && notebook.dateCreated
|
||||
? "Save changes"
|
||||
: "Create notebook"
|
||||
}
|
||||
type="accent"
|
||||
onPress={this.addNewNotebook}
|
||||
/>
|
||||
{/*
|
||||
{Platform.OS === 'ios' && (
|
||||
<View
|
||||
style={{
|
||||
@@ -468,14 +393,24 @@ export class AddNotebookSheet extends React.Component {
|
||||
}}
|
||||
/>
|
||||
)} */}
|
||||
</View>
|
||||
|
||||
<Toast context="local" />
|
||||
</SheetWrapper>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AddNotebookSheet.present = (notebook) => {
|
||||
presentSheet({
|
||||
component: (ref, close, _update, colors) => (
|
||||
<AddNotebookSheet
|
||||
actionSheetRef={ref}
|
||||
notebook={notebook}
|
||||
close={close}
|
||||
colors={colors}
|
||||
/>
|
||||
)
|
||||
});
|
||||
};
|
||||
|
||||
const TopicItem = ({ item, index, colors, onPress, onDelete }) => {
|
||||
const topicRef = (ref) => (refs[index] = ref);
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { FlatList } from "react-native-gesture-handler";
|
||||
import { FlatList } from "react-native-actions-sheet";
|
||||
import { db } from "../../../common/database";
|
||||
import { ListHeaderInputItem } from "./list-header-item.js";
|
||||
|
||||
@@ -27,6 +27,7 @@ export const FilteredList = ({
|
||||
itemType,
|
||||
onAddItem,
|
||||
hasHeaderSearch,
|
||||
listRef,
|
||||
...restProps
|
||||
}) => {
|
||||
const [filtered, setFiltered] = useState(data);
|
||||
@@ -56,6 +57,7 @@ export const FilteredList = ({
|
||||
<FlatList
|
||||
{...restProps}
|
||||
data={filtered}
|
||||
ref={listRef}
|
||||
ListHeaderComponent={
|
||||
hasHeaderSearch ? (
|
||||
<ListHeaderInputItem
|
||||
|
||||
@@ -17,91 +17,37 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, {
|
||||
createRef,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState
|
||||
} from "react";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Keyboard, TouchableOpacity, View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../../common/database";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent,
|
||||
ToastEvent
|
||||
} from "../../../services/event-manager";
|
||||
import { presentSheet, ToastEvent } from "../../../services/event-manager";
|
||||
import Navigation from "../../../services/navigation";
|
||||
import SearchService from "../../../services/search";
|
||||
import { useNotebookStore } from "../../../stores/use-notebook-store";
|
||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { eOpenMoveNoteDialog } from "../../../utils/events";
|
||||
import { Dialog } from "../../dialog";
|
||||
import DialogHeader from "../../dialog/dialog-header";
|
||||
import { presentDialog } from "../../dialog/functions";
|
||||
import { Button } from "../../ui/button";
|
||||
import SheetWrapper from "../../ui/sheet";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { SelectionProvider } from "./context";
|
||||
import { FilteredList } from "./filtered-list";
|
||||
import { ListItem } from "./list-item";
|
||||
|
||||
const actionSheetRef = createRef();
|
||||
const AddToNotebookSheet = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [note, setNote] = useState(null);
|
||||
|
||||
function open(note) {
|
||||
setNote(note);
|
||||
setVisible(true);
|
||||
actionSheetRef.current?.setModalVisible(true);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent(eOpenMoveNoteDialog, open);
|
||||
return () => {
|
||||
eUnSubscribeEvent(eOpenMoveNoteDialog, open);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const _onClose = () => {
|
||||
setVisible(false);
|
||||
setNote(null);
|
||||
Navigation.queueRoutesForUpdate(
|
||||
"Notes",
|
||||
"Favorites",
|
||||
"ColoredNotes",
|
||||
"TaggedNotes",
|
||||
"TopicNotes",
|
||||
"Notebooks",
|
||||
"Notebook"
|
||||
);
|
||||
};
|
||||
|
||||
return !visible ? null : (
|
||||
<SheetWrapper fwdRef={actionSheetRef} onClose={_onClose}>
|
||||
<MoveNoteComponent note={note} />
|
||||
</SheetWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddToNotebookSheet;
|
||||
|
||||
const MoveNoteComponent = ({ note }) => {
|
||||
const MoveNoteSheet = ({ note, actionSheetRef }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [multiSelect, setMultiSelect] = useState(false);
|
||||
const notebooks = useNotebookStore((state) =>
|
||||
state.notebooks.filter((n) => n?.type === "notebook")
|
||||
);
|
||||
const [edited, setEdited] = useState(false);
|
||||
|
||||
const selectedItemsList = useSelectionStore(
|
||||
(state) => state.selectedItemsList
|
||||
);
|
||||
const setNotebooks = useNotebookStore((state) => state.setNotebooks);
|
||||
const [itemState, setItemState] = useState({});
|
||||
|
||||
const onAddNotebook = async (title) => {
|
||||
if (!title || title.trim().length === 0) {
|
||||
ToastEvent.show({
|
||||
@@ -159,13 +105,13 @@ const MoveNoteComponent = ({ note }) => {
|
||||
(item) => {
|
||||
switch (item.type) {
|
||||
case "notebook": {
|
||||
const noteIds = [];
|
||||
for (let topic of item.topics) {
|
||||
noteIds.push(...(db.notes?.topicReferences.get(topic.id) || []));
|
||||
}
|
||||
const notes = db.relations.from(item, "note");
|
||||
if (notes.length === 0) return 0;
|
||||
let count = 0;
|
||||
selectedItemsList.forEach((item) =>
|
||||
noteIds.indexOf(item.id) > -1 ? count++ : undefined
|
||||
notes.findIndex((note) => note.id === item.id) > -1
|
||||
? count++
|
||||
: undefined
|
||||
);
|
||||
return count;
|
||||
}
|
||||
@@ -195,8 +141,7 @@ const MoveNoteComponent = ({ note }) => {
|
||||
for (let notebook of notebooks) {
|
||||
itemState[notebook.id] = state
|
||||
? state
|
||||
: areAllSelectedItemsInAllTopics(notebook, selectedItemsList) &&
|
||||
getSelectedNotesCountInItem(notebook, selectedItemsList) > 0
|
||||
: areAllSelectedItemsInNotebook(notebook, selectedItemsList)
|
||||
? "selected"
|
||||
: getSelectedNotesCountInItem(notebook, selectedItemsList) > 0
|
||||
? "intermediate"
|
||||
@@ -231,11 +176,11 @@ const MoveNoteComponent = ({ note }) => {
|
||||
}
|
||||
};
|
||||
|
||||
function areAllSelectedItemsInAllTopics(notebook, items) {
|
||||
function areAllSelectedItemsInNotebook(notebook, items) {
|
||||
const notes = db.relations.from(notebook, "note");
|
||||
if (notes.length === 0) return false;
|
||||
return items.every((item) => {
|
||||
return notebook.topics.every((topic) => {
|
||||
return db.notes.topicReferences.get(topic.id).indexOf(item.id) > -1;
|
||||
});
|
||||
return notes.find((note) => note.id === item.id);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -250,25 +195,6 @@ const MoveNoteComponent = ({ note }) => {
|
||||
const mergeState = {
|
||||
[item.id]: state
|
||||
};
|
||||
const notebooks = db.notebooks.all;
|
||||
const notebook =
|
||||
item.type === "notebook"
|
||||
? item
|
||||
: notebooks.find((n) => n.id === item.notebookId);
|
||||
const intermediate = notebook.topics.some((topic) => {
|
||||
return topic.id === item.id
|
||||
? state === "selected"
|
||||
: itemState[topic.id] === "selected";
|
||||
});
|
||||
if (intermediate) mergeState[notebook.id] = "intermediate";
|
||||
const selected = notebook.topics.every((topic) => {
|
||||
return topic.id === item.id
|
||||
? state === "selected"
|
||||
: itemState[topic.id] === "selected";
|
||||
});
|
||||
if (selected) mergeState[notebook.id] = "selected";
|
||||
if (!selected && !intermediate) mergeState[notebook.id] = "deselected";
|
||||
|
||||
return {
|
||||
...itemState,
|
||||
...mergeState
|
||||
@@ -314,28 +240,39 @@ const MoveNoteComponent = ({ note }) => {
|
||||
};
|
||||
|
||||
const onSave = async () => {
|
||||
const noteIds = note ? [note.id] : selectedItemsList.map((n) => n.id);
|
||||
for (const id in itemState) {
|
||||
const item = getItemFromId(id);
|
||||
if (item.type === "notebook") continue;
|
||||
const noteIds = selectedItemsList.map((n) => n.id);
|
||||
if (itemState[id] === "selected") {
|
||||
await db.notes.addToNotebook(
|
||||
{
|
||||
topic: item.id,
|
||||
id: item.notebookId,
|
||||
rebuildCache: true
|
||||
},
|
||||
...noteIds
|
||||
);
|
||||
if (item.type === "notebook") {
|
||||
for (let noteId of noteIds) {
|
||||
db.relations.add(item, { id: noteId, type: "note" });
|
||||
}
|
||||
} else {
|
||||
await db.notes.addToNotebook(
|
||||
{
|
||||
topic: item.id,
|
||||
id: item.notebookId,
|
||||
rebuildCache: true
|
||||
},
|
||||
...noteIds
|
||||
);
|
||||
}
|
||||
} else if (itemState[id] === "deselected") {
|
||||
await db.notes.removeFromNotebook(
|
||||
{
|
||||
id: item.notebookId,
|
||||
topic: item.id,
|
||||
rebuildCache: true
|
||||
},
|
||||
...noteIds
|
||||
);
|
||||
if (item.type === "notebook") {
|
||||
for (let noteId of noteIds) {
|
||||
db.relations.unlink(item, { id: noteId, type: "note" });
|
||||
}
|
||||
} else {
|
||||
await db.notes.removeFromNotebook(
|
||||
{
|
||||
id: item.notebookId,
|
||||
topic: item.id,
|
||||
rebuildCache: true
|
||||
},
|
||||
...noteIds
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Navigation.queueRoutesForUpdate(
|
||||
@@ -343,7 +280,8 @@ const MoveNoteComponent = ({ note }) => {
|
||||
"Favorites",
|
||||
"ColoredNotes",
|
||||
"TaggedNotes",
|
||||
"TopicNotes"
|
||||
"TopicNotes",
|
||||
"Notebook"
|
||||
);
|
||||
setNotebooks();
|
||||
SearchService.updateAndSearch();
|
||||
@@ -420,9 +358,6 @@ const MoveNoteComponent = ({ note }) => {
|
||||
|
||||
<SelectionProvider value={contextValue}>
|
||||
<FilteredList
|
||||
onMomentumScrollEnd={() => {
|
||||
actionSheetRef.current?.handleChildScrollEnd();
|
||||
}}
|
||||
style={{
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
@@ -455,6 +390,7 @@ const MoveNoteComponent = ({ note }) => {
|
||||
itemState[item.id] === "deselected" &&
|
||||
getSelectedNotesCountInItem(item) > 0
|
||||
}
|
||||
sheetRef={actionSheetRef}
|
||||
isSelected={itemState[item.id] === "selected"}
|
||||
infoText={
|
||||
<>
|
||||
@@ -480,10 +416,6 @@ const MoveNoteComponent = ({ note }) => {
|
||||
if (currentState !== "selected") {
|
||||
resetItemState("deselected");
|
||||
contextValue.select(item);
|
||||
updateItemState(
|
||||
notebooks.find((n) => n.id === item.notebookId),
|
||||
"intermediate"
|
||||
);
|
||||
} else {
|
||||
contextValue.deselect(item);
|
||||
}
|
||||
@@ -514,16 +446,24 @@ const MoveNoteComponent = ({ note }) => {
|
||||
onAddItem={async (title) => {
|
||||
return await onAddNotebook(title);
|
||||
}}
|
||||
ListFooterComponent={
|
||||
<View
|
||||
style={{
|
||||
height: 200
|
||||
}}
|
||||
/>
|
||||
}
|
||||
// ListFooterComponent={
|
||||
// <View
|
||||
// style={{
|
||||
// height: 200
|
||||
// }}
|
||||
// />
|
||||
// }
|
||||
/>
|
||||
</SelectionProvider>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
MoveNoteSheet.present = (note) => {
|
||||
presentSheet({
|
||||
component: (ref) => <MoveNoteSheet actionSheetRef={ref} note={note} />,
|
||||
enableGesturesInScrollView: false
|
||||
});
|
||||
};
|
||||
export default MoveNoteSheet;
|
||||
|
||||
@@ -46,25 +46,15 @@ const _ListItem = ({
|
||||
removed,
|
||||
isSelected,
|
||||
hasHeaderSearch,
|
||||
onAddSublistItem
|
||||
onAddSublistItem,
|
||||
sheetRef
|
||||
}) => {
|
||||
const { enabled, toggleSelection, setMultiSelect, select, deselect } =
|
||||
useSelectionContext();
|
||||
const { enabled, toggleSelection, setMultiSelect } = useSelectionContext();
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
function selectItem() {
|
||||
const currentState = isSelected;
|
||||
toggleSelection(item);
|
||||
if (item.type === "notebook") {
|
||||
item.topics.forEach((item) => {
|
||||
if (currentState) {
|
||||
deselect(item);
|
||||
} else {
|
||||
select(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return (
|
||||
<View
|
||||
@@ -122,9 +112,6 @@ const _ListItem = ({
|
||||
: colors.icon
|
||||
}
|
||||
onPress={() => {
|
||||
if (item.type === "notebook") {
|
||||
setMultiSelect(true);
|
||||
}
|
||||
selectItem();
|
||||
if (enabled) return;
|
||||
onPress?.(item);
|
||||
@@ -205,7 +192,7 @@ const _ListItem = ({
|
||||
style={{
|
||||
width: "95%",
|
||||
alignSelf: "flex-end",
|
||||
maxHeight: 500
|
||||
maxHeight: 250
|
||||
}}
|
||||
itemType={sublistItemType}
|
||||
hasHeaderSearch={hasHeaderSearch}
|
||||
|
||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import React, { RefObject, useRef, useState } from "react";
|
||||
import { TextInput, View } from "react-native";
|
||||
import ActionSheet from "react-native-actions-sheet";
|
||||
import { ActionSheetRef } from "react-native-actions-sheet";
|
||||
import { db } from "../../../common/database";
|
||||
import {
|
||||
eSendEvent,
|
||||
@@ -31,7 +31,7 @@ import { Button } from "../../ui/button";
|
||||
import Input from "../../ui/input";
|
||||
|
||||
type ChangeEmailProps = {
|
||||
actionSheetRef: RefObject<ActionSheet>;
|
||||
actionSheetRef: RefObject<ActionSheetRef>;
|
||||
close?: () => void;
|
||||
update?: (options: PresentSheetOptions) => void;
|
||||
};
|
||||
|
||||
@@ -17,51 +17,32 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { ScrollView, View } from "react-native";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { ScrollView } from "react-native-actions-sheet";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../../common/database";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent,
|
||||
ToastEvent
|
||||
} from "../../../services/event-manager";
|
||||
import { presentSheet, ToastEvent } from "../../../services/event-manager";
|
||||
import Navigation from "../../../services/navigation";
|
||||
import { useTagStore } from "../../../stores/use-tag-store";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { eCloseTagsDialog, eOpenTagsDialog } from "../../../utils/events";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { sleep } from "../../../utils/time";
|
||||
import Input from "../../ui/input";
|
||||
import { PressableButton } from "../../ui/pressable";
|
||||
import SheetWrapper from "../../ui/sheet";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { useCallback } from "react";
|
||||
const ManageTagsSheet = () => {
|
||||
const ManageTagsSheet = (props) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [note, setNote] = useState(null);
|
||||
const [note, setNote] = useState(props.note);
|
||||
const allTags = useTagStore((state) => state.tags);
|
||||
const [tags, setTags] = useState([]);
|
||||
const [query, setQuery] = useState(null);
|
||||
const inputRef = useRef();
|
||||
const actionSheetRef = useRef();
|
||||
const [focus, setFocus] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent(eOpenTagsDialog, open);
|
||||
eSubscribeEvent(eCloseTagsDialog, close);
|
||||
return () => {
|
||||
eUnSubscribeEvent(eOpenTagsDialog, open);
|
||||
eUnSubscribeEvent(eCloseTagsDialog, close);
|
||||
};
|
||||
}, [open]);
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
sortTags();
|
||||
}
|
||||
}, [allTags, note, query, sortTags, visible]);
|
||||
sortTags();
|
||||
}, [allTags, note, query, sortTags]);
|
||||
|
||||
const sortTags = useCallback(() => {
|
||||
let _tags = [...allTags];
|
||||
@@ -88,26 +69,9 @@ const ManageTagsSheet = () => {
|
||||
setTags(combinedTags);
|
||||
}, [allTags, note, query]);
|
||||
|
||||
const open = useCallback(
|
||||
(item) => {
|
||||
setNote(item);
|
||||
useTagStore.getState().setTags();
|
||||
sortTags();
|
||||
setVisible(true);
|
||||
},
|
||||
[sortTags]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
actionSheetRef.current?.show();
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
const close = () => {
|
||||
setQuery(null);
|
||||
actionSheetRef.current?.hide();
|
||||
};
|
||||
useTagStore.getState().setTags();
|
||||
}, []);
|
||||
|
||||
const onSubmit = async () => {
|
||||
let _query = query;
|
||||
@@ -150,104 +114,96 @@ const ManageTagsSheet = () => {
|
||||
);
|
||||
};
|
||||
|
||||
return !visible ? null : (
|
||||
<SheetWrapper
|
||||
centered={false}
|
||||
fwdRef={actionSheetRef}
|
||||
onOpen={async () => {
|
||||
await sleep(300);
|
||||
inputRef.current?.focus();
|
||||
}}
|
||||
onClose={async () => {
|
||||
setQuery(null);
|
||||
setVisible(false);
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
alignSelf: "center",
|
||||
paddingHorizontal: 12,
|
||||
minHeight: focus ? "100%" : "60%"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
alignSelf: "center",
|
||||
paddingHorizontal: 12,
|
||||
minHeight: "60%"
|
||||
<Input
|
||||
button={{
|
||||
icon: "magnify",
|
||||
color: colors.accent,
|
||||
size: SIZE.lg
|
||||
}}
|
||||
testID="tag-input"
|
||||
fwdRef={inputRef}
|
||||
autoCapitalize="none"
|
||||
onChangeText={(v) => {
|
||||
setQuery(db.tags.sanitize(v));
|
||||
}}
|
||||
onFocusInput={() => {
|
||||
setFocus(true);
|
||||
}}
|
||||
onBlurInput={() => {
|
||||
setFocus(false);
|
||||
}}
|
||||
onSubmit={onSubmit}
|
||||
height={50}
|
||||
placeholder="Search or add a tag"
|
||||
/>
|
||||
|
||||
<ScrollView
|
||||
overScrollMode="never"
|
||||
scrollToOverflowEnabled={false}
|
||||
keyboardDismissMode="none"
|
||||
keyboardShouldPersistTaps="always"
|
||||
>
|
||||
<Input
|
||||
button={{
|
||||
icon: "magnify",
|
||||
color: colors.accent,
|
||||
size: SIZE.lg
|
||||
}}
|
||||
testID="tag-input"
|
||||
fwdRef={inputRef}
|
||||
autoCapitalize="none"
|
||||
onChangeText={(v) => {
|
||||
setQuery(db.tags.sanitize(v));
|
||||
}}
|
||||
onSubmit={onSubmit}
|
||||
height={50}
|
||||
placeholder="Search or add a tag"
|
||||
/>
|
||||
{query && query !== tags[0]?.title ? (
|
||||
<PressableButton
|
||||
key={"query_item"}
|
||||
customStyle={{
|
||||
flexDirection: "row",
|
||||
marginVertical: 5,
|
||||
justifyContent: "space-between",
|
||||
padding: 12
|
||||
}}
|
||||
onPress={onSubmit}
|
||||
type="accent"
|
||||
>
|
||||
<Heading size={SIZE.sm} color={colors.light}>
|
||||
Add {'"' + "#" + query + '"'}
|
||||
</Heading>
|
||||
<Icon name="plus" color={colors.light} size={SIZE.lg} />
|
||||
</PressableButton>
|
||||
) : null}
|
||||
{!allTags || allTags.length === 0 ? (
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
height: 200,
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Heading size={50} color={colors.icon}>
|
||||
#
|
||||
</Heading>
|
||||
<Paragraph textBreakStrategy="balanced" color={colors.icon}>
|
||||
You do not have any tags.
|
||||
</Paragraph>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
<ScrollView
|
||||
nestedScrollEnabled
|
||||
overScrollMode="never"
|
||||
scrollToOverflowEnabled={false}
|
||||
keyboardDismissMode="none"
|
||||
keyboardShouldPersistTaps="always"
|
||||
onMomentumScrollEnd={() => {
|
||||
actionSheetRef.current?.handleChildScrollEnd();
|
||||
}}
|
||||
>
|
||||
{query && query !== tags[0]?.title ? (
|
||||
<PressableButton
|
||||
key={"query_item"}
|
||||
customStyle={{
|
||||
flexDirection: "row",
|
||||
marginVertical: 5,
|
||||
justifyContent: "space-between",
|
||||
padding: 12
|
||||
}}
|
||||
onPress={onSubmit}
|
||||
type="accent"
|
||||
>
|
||||
<Heading size={SIZE.sm} color={colors.light}>
|
||||
Add {'"' + "#" + query + '"'}
|
||||
</Heading>
|
||||
<Icon name="plus" color={colors.light} size={SIZE.lg} />
|
||||
</PressableButton>
|
||||
) : null}
|
||||
{!allTags || allTags.length === 0 ? (
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
height: 200,
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Heading size={50} color={colors.icon}>
|
||||
#
|
||||
</Heading>
|
||||
<Paragraph textBreakStrategy="balanced" color={colors.icon}>
|
||||
You do not have any tags.
|
||||
</Paragraph>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
{tags.map((item) => (
|
||||
<TagItem
|
||||
key={item.title}
|
||||
tag={item}
|
||||
note={note}
|
||||
setNote={setNote}
|
||||
/>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</SheetWrapper>
|
||||
{tags.map((item) => (
|
||||
<TagItem key={item.title} tag={item} note={note} setNote={setNote} />
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
ManageTagsSheet.present = (note) => {
|
||||
presentSheet({
|
||||
component: (ref) => {
|
||||
return <ManageTagsSheet actionSheetRef={ref} note={note} />;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default ManageTagsSheet;
|
||||
|
||||
const TagItem = ({ tag, note, setNote }) => {
|
||||
|
||||
@@ -17,11 +17,11 @@ 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 { FlashList } from "@shopify/flash-list";
|
||||
import { NotebookType, NoteType, TopicType } from "app/utils/types";
|
||||
import React, { RefObject, useState } from "react";
|
||||
import { Platform, useWindowDimensions, View } from "react-native";
|
||||
import ActionSheet from "react-native-actions-sheet";
|
||||
import { ActionSheetRef } from "react-native-actions-sheet";
|
||||
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
|
||||
import { db } from "../../../common/database";
|
||||
import {
|
||||
eSendEvent,
|
||||
@@ -58,7 +58,7 @@ export const MoveNotes = ({
|
||||
}: {
|
||||
notebook: NotebookType;
|
||||
selectedTopic?: TopicType;
|
||||
fwdRef: RefObject<ActionSheet>;
|
||||
fwdRef: RefObject<ActionSheetRef>;
|
||||
}) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [currentNotebook, setCurrentNotebook] = useState(notebook);
|
||||
@@ -277,10 +277,6 @@ export const MoveNotes = ({
|
||||
)}
|
||||
|
||||
<FlashList
|
||||
nestedScrollEnabled
|
||||
onMomentumScrollEnd={() => {
|
||||
fwdRef.current?.handleChildScrollEnd();
|
||||
}}
|
||||
ListEmptyComponent={
|
||||
<View
|
||||
style={{
|
||||
@@ -345,7 +341,7 @@ export const MoveNotes = ({
|
||||
|
||||
MoveNotes.present = (notebook: NotebookType, topic: TopicType) => {
|
||||
presentSheet({
|
||||
component: (ref: RefObject<ActionSheet>) => (
|
||||
component: (ref: RefObject<ActionSheetRef>) => (
|
||||
<MoveNotes fwdRef={ref} notebook={notebook} selectedTopic={topic} />
|
||||
)
|
||||
});
|
||||
|
||||
@@ -93,7 +93,13 @@ NewFeature.present = () => {
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (version && version === getVersion()) return false;
|
||||
if (!version || version === getVersion()) {
|
||||
SettingsService.set({
|
||||
version: getVersion()
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
SettingsService.set({
|
||||
version: getVersion()
|
||||
});
|
||||
|
||||
@@ -18,22 +18,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Clipboard from "@react-native-clipboard/clipboard";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { ActivityIndicator, TouchableOpacity, View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../../common/database";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent,
|
||||
ToastEvent
|
||||
} from "../../../services/event-manager";
|
||||
import { presentSheet, ToastEvent } from "../../../services/event-manager";
|
||||
import Navigation from "../../../services/navigation";
|
||||
import { useAttachmentStore } from "../../../stores/use-attachment-store";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import {
|
||||
eClosePublishNoteDialog,
|
||||
eOpenPublishNoteDialog
|
||||
} from "../../../utils/events";
|
||||
import { openLinkInBrowser } from "../../../utils/functions";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import DialogHeader from "../../dialog/dialog-header";
|
||||
@@ -41,59 +33,27 @@ import { Button } from "../../ui/button";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import Input from "../../ui/input";
|
||||
import Seperator from "../../ui/seperator";
|
||||
import SheetWrapper from "../../ui/sheet";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
|
||||
let passwordValue = null;
|
||||
const PublishNoteSheet = () => {
|
||||
const PublishNoteSheet = ({ note: item, update }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const actionSheetRef = useRef();
|
||||
const loading = useAttachmentStore((state) => state.loading);
|
||||
const [selfDestruct, setSelfDestruct] = useState(false);
|
||||
const [isLocked, setIsLocked] = useState(false);
|
||||
const [note, setNote] = useState(null);
|
||||
const [note, setNote] = useState(item);
|
||||
const [publishing, setPublishing] = useState(false);
|
||||
const publishUrl =
|
||||
note &&
|
||||
`https://monograph.notesnook.com/${db?.monographs.monograph(note?.id)}`;
|
||||
const isPublished = note && db?.monographs.isPublished(note?.id);
|
||||
const pwdInput = useRef();
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent(eOpenPublishNoteDialog, open);
|
||||
eSubscribeEvent(eClosePublishNoteDialog, close);
|
||||
return () => {
|
||||
eUnSubscribeEvent(eOpenPublishNoteDialog, open);
|
||||
eUnSubscribeEvent(eClosePublishNoteDialog, close);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const open = (item) => {
|
||||
if (!item) return;
|
||||
setNote(item);
|
||||
setPublishing(false);
|
||||
setSelfDestruct(false);
|
||||
setIsLocked(false);
|
||||
setVisible(true);
|
||||
passwordValue = null;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
actionSheetRef.current?.show();
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
const close = () => {
|
||||
passwordValue = null;
|
||||
actionSheetRef.current?.hide();
|
||||
};
|
||||
const passwordValue = useRef();
|
||||
|
||||
const publishNote = async () => {
|
||||
if (publishing) return;
|
||||
setPublishing(true);
|
||||
setPublishLoading(true);
|
||||
|
||||
try {
|
||||
if (note?.id) {
|
||||
@@ -110,6 +70,7 @@ const PublishNoteSheet = () => {
|
||||
"TaggedNotes",
|
||||
"TopicNotes"
|
||||
);
|
||||
setPublishLoading(false);
|
||||
}
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
@@ -120,12 +81,19 @@ const PublishNoteSheet = () => {
|
||||
});
|
||||
}
|
||||
|
||||
setPublishing(false);
|
||||
setPublishLoading(false);
|
||||
};
|
||||
|
||||
const setPublishLoading = (value) => {
|
||||
setPublishing(value);
|
||||
update({
|
||||
progress: value
|
||||
});
|
||||
};
|
||||
|
||||
const deletePublishedNote = async () => {
|
||||
if (publishing) return;
|
||||
setPublishing(true);
|
||||
setPublishLoading(true);
|
||||
try {
|
||||
if (note?.id) {
|
||||
await db.monographs.unpublish(note.id);
|
||||
@@ -137,6 +105,7 @@ const PublishNoteSheet = () => {
|
||||
"TaggedNotes",
|
||||
"TopicNotes"
|
||||
);
|
||||
setPublishLoading(false);
|
||||
}
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
@@ -147,267 +116,268 @@ const PublishNoteSheet = () => {
|
||||
});
|
||||
}
|
||||
actionSheetRef.current?.hide();
|
||||
setPublishing(false);
|
||||
setPublishLoading(false);
|
||||
};
|
||||
|
||||
return !visible ? null : (
|
||||
<SheetWrapper
|
||||
centered={false}
|
||||
fwdRef={actionSheetRef}
|
||||
closeOnTouchBackdrop={!publishing}
|
||||
gestureEnabled={!publishing}
|
||||
onClose={async () => {
|
||||
passwordValue = null;
|
||||
setVisible(false);
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
alignSelf: "center",
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
alignSelf: "center",
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
>
|
||||
<DialogHeader
|
||||
title={note?.title}
|
||||
paragraph={`Anyone with the link${
|
||||
isLocked ? " and password" : ""
|
||||
} of the published note can view it.`}
|
||||
/>
|
||||
<DialogHeader
|
||||
title={note?.title}
|
||||
paragraph={`Anyone with the link${
|
||||
isLocked ? " and password" : ""
|
||||
} of the published note can view it.`}
|
||||
/>
|
||||
|
||||
{publishing ? (
|
||||
<View
|
||||
{publishing ? (
|
||||
<View
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
alignContent: "center",
|
||||
height: 150,
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<ActivityIndicator size={25} color={colors.accent} />
|
||||
<Paragraph
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
alignContent: "center",
|
||||
height: 150,
|
||||
width: "100%"
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
<ActivityIndicator size={25} color={colors.accent} />
|
||||
<Paragraph
|
||||
Please wait...
|
||||
{loading && loading.current && loading.total
|
||||
? `\nDownloading attachments (${
|
||||
loading?.current / loading?.total
|
||||
})`
|
||||
: ""}
|
||||
</Paragraph>
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
{isPublished && (
|
||||
<View
|
||||
style={{
|
||||
textAlign: "center"
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginTop: 15,
|
||||
backgroundColor: colors.nav,
|
||||
padding: 12,
|
||||
borderRadius: 5
|
||||
}}
|
||||
>
|
||||
Please wait...
|
||||
{loading && loading.current && loading.total
|
||||
? `\nDownloading attachments (${
|
||||
loading?.current / loading?.total
|
||||
})`
|
||||
: ""}
|
||||
</Paragraph>
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
{isPublished && (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginTop: 15,
|
||||
backgroundColor: colors.nav,
|
||||
padding: 12,
|
||||
borderRadius: 5
|
||||
width: "100%",
|
||||
flexShrink: 1
|
||||
}}
|
||||
>
|
||||
<View
|
||||
<Heading size={SIZE.sm}>Published at:</Heading>
|
||||
<Paragraph size={SIZE.xs} numberOfLines={1}>
|
||||
{publishUrl}
|
||||
</Paragraph>
|
||||
<Paragraph
|
||||
onPress={async () => {
|
||||
try {
|
||||
await openLinkInBrowser(publishUrl, colors.accent);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}}
|
||||
size={SIZE.xs}
|
||||
style={{
|
||||
width: "100%",
|
||||
flexShrink: 1
|
||||
marginTop: 5,
|
||||
color: colors.pri
|
||||
}}
|
||||
>
|
||||
<Heading size={SIZE.sm}>Published at:</Heading>
|
||||
<Paragraph size={SIZE.xs} numberOfLines={1}>
|
||||
{publishUrl}
|
||||
</Paragraph>
|
||||
<Paragraph
|
||||
onPress={async () => {
|
||||
try {
|
||||
await openLinkInBrowser(publishUrl, colors.accent);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}}
|
||||
size={SIZE.xs}
|
||||
style={{
|
||||
marginTop: 5,
|
||||
color: colors.pri
|
||||
}}
|
||||
>
|
||||
<Icon color={colors.accent} name="open-in-new" /> Open in
|
||||
browser
|
||||
</Paragraph>
|
||||
</View>
|
||||
|
||||
<IconButton
|
||||
onPress={() => {
|
||||
Clipboard.setString(publishUrl);
|
||||
ToastEvent.show({
|
||||
heading: "Note publish url copied",
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
}}
|
||||
color={colors.accent}
|
||||
size={SIZE.lg}
|
||||
name="content-copy"
|
||||
/>
|
||||
<Icon color={colors.accent} name="open-in-new" /> Open in
|
||||
browser
|
||||
</Paragraph>
|
||||
</View>
|
||||
)}
|
||||
<Seperator />
|
||||
|
||||
<TouchableOpacity
|
||||
<IconButton
|
||||
onPress={() => {
|
||||
Clipboard.setString(publishUrl);
|
||||
ToastEvent.show({
|
||||
heading: "Note publish url copied",
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
}}
|
||||
color={colors.accent}
|
||||
size={SIZE.lg}
|
||||
name="content-copy"
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
<Seperator />
|
||||
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
if (publishing) return;
|
||||
setIsLocked(!isLocked);
|
||||
}}
|
||||
activeOpacity={0.9}
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginBottom: 10
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
onPress={() => {
|
||||
if (publishing) return;
|
||||
setIsLocked(!isLocked);
|
||||
}}
|
||||
activeOpacity={0.9}
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginBottom: 10
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
onPress={() => {
|
||||
if (publishing) return;
|
||||
setIsLocked(!isLocked);
|
||||
}}
|
||||
color={isLocked ? colors.accent : colors.icon}
|
||||
size={SIZE.lg}
|
||||
name={
|
||||
isLocked
|
||||
? "check-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
flexShrink: 1
|
||||
}}
|
||||
>
|
||||
<Heading size={SIZE.md}>Password protection</Heading>
|
||||
<Paragraph>
|
||||
Published note can only be viewed by someone with the
|
||||
password.
|
||||
</Paragraph>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
setSelfDestruct(!selfDestruct);
|
||||
}}
|
||||
activeOpacity={0.9}
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
onPress={() => {
|
||||
setSelfDestruct(!selfDestruct);
|
||||
}}
|
||||
color={selfDestruct ? colors.accent : colors.icon}
|
||||
size={SIZE.lg}
|
||||
name={
|
||||
selfDestruct
|
||||
? "check-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
flexShrink: 1
|
||||
}}
|
||||
>
|
||||
<Heading size={SIZE.md}>Self destruct</Heading>
|
||||
<Paragraph>
|
||||
Published note link will be automatically deleted once it is
|
||||
viewed by someone.
|
||||
</Paragraph>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
color={isLocked ? colors.accent : colors.icon}
|
||||
size={SIZE.lg}
|
||||
name={
|
||||
isLocked
|
||||
? "check-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
alignSelf: "center",
|
||||
marginTop: 10
|
||||
flexShrink: 1
|
||||
}}
|
||||
>
|
||||
{isLocked ? (
|
||||
<>
|
||||
<Input
|
||||
fwdRef={pwdInput}
|
||||
onChangeText={(value) => (passwordValue = value)}
|
||||
blurOnSubmit
|
||||
secureTextEntry
|
||||
defaultValue={passwordValue}
|
||||
placeholder="Enter Password"
|
||||
/>
|
||||
<Seperator half />
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<Button
|
||||
onPress={publishNote}
|
||||
fontSize={SIZE.md}
|
||||
width="100%"
|
||||
style={{
|
||||
marginTop: 10
|
||||
}}
|
||||
height={50}
|
||||
type="accent"
|
||||
title={isPublished ? "Update published note" : "Publish note"}
|
||||
/>
|
||||
|
||||
{isPublished && (
|
||||
<>
|
||||
<Seperator half />
|
||||
<Button
|
||||
onPress={deletePublishedNote}
|
||||
fontSize={SIZE.md}
|
||||
width="100%"
|
||||
height={50}
|
||||
type="error"
|
||||
title="Unpublish note"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Heading size={SIZE.md}>Password protection</Heading>
|
||||
<Paragraph>
|
||||
Published note can only be viewed by someone with the password.
|
||||
</Paragraph>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
|
||||
<Paragraph
|
||||
color={colors.icon}
|
||||
size={SIZE.xs}
|
||||
style={{
|
||||
textAlign: "center",
|
||||
marginTop: 5,
|
||||
textDecorationLine: "underline"
|
||||
}}
|
||||
onPress={async () => {
|
||||
try {
|
||||
await openLinkInBrowser(
|
||||
"https://docs.notesnook.com/monographs/",
|
||||
colors.accent
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Learn more about Notesnook Monograph
|
||||
</Paragraph>
|
||||
</View>
|
||||
</SheetWrapper>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
setSelfDestruct(!selfDestruct);
|
||||
}}
|
||||
activeOpacity={0.9}
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
onPress={() => {
|
||||
setSelfDestruct(!selfDestruct);
|
||||
}}
|
||||
color={selfDestruct ? colors.accent : colors.icon}
|
||||
size={SIZE.lg}
|
||||
name={
|
||||
selfDestruct
|
||||
? "check-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
flexShrink: 1
|
||||
}}
|
||||
>
|
||||
<Heading size={SIZE.md}>Self destruct</Heading>
|
||||
<Paragraph>
|
||||
Published note link will be automatically deleted once it is
|
||||
viewed by someone.
|
||||
</Paragraph>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
alignSelf: "center",
|
||||
marginTop: 10
|
||||
}}
|
||||
>
|
||||
{isLocked ? (
|
||||
<>
|
||||
<Input
|
||||
fwdRef={pwdInput}
|
||||
onChangeText={(value) => (passwordValue.current = value)}
|
||||
blurOnSubmit
|
||||
secureTextEntry
|
||||
defaultValue={passwordValue.current}
|
||||
placeholder="Enter Password"
|
||||
/>
|
||||
<Seperator half />
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<Button
|
||||
onPress={publishNote}
|
||||
fontSize={SIZE.md}
|
||||
width="100%"
|
||||
style={{
|
||||
marginTop: 10
|
||||
}}
|
||||
height={50}
|
||||
type="accent"
|
||||
title={isPublished ? "Update published note" : "Publish note"}
|
||||
/>
|
||||
|
||||
{isPublished && (
|
||||
<>
|
||||
<Seperator half />
|
||||
<Button
|
||||
onPress={deletePublishedNote}
|
||||
fontSize={SIZE.md}
|
||||
width="100%"
|
||||
height={50}
|
||||
type="error"
|
||||
title="Unpublish note"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Paragraph
|
||||
color={colors.icon}
|
||||
size={SIZE.xs}
|
||||
style={{
|
||||
textAlign: "center",
|
||||
marginTop: 5,
|
||||
textDecorationLine: "underline"
|
||||
}}
|
||||
onPress={async () => {
|
||||
try {
|
||||
await openLinkInBrowser(
|
||||
"https://docs.notesnook.com/monographs/",
|
||||
colors.accent
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Learn more about Notesnook Monograph
|
||||
</Paragraph>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
PublishNoteSheet.present = (note) => {
|
||||
presentSheet({
|
||||
component: (ref, close, update) => (
|
||||
<PublishNoteSheet
|
||||
actionSheetRef={ref}
|
||||
close={close}
|
||||
update={update}
|
||||
note={note}
|
||||
/>
|
||||
)
|
||||
});
|
||||
};
|
||||
|
||||
export default PublishNoteSheet;
|
||||
|
||||
@@ -18,12 +18,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import React, { RefObject, useEffect, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import ActionSheet from "react-native-actions-sheet";
|
||||
import { ActionSheetRef } from "react-native-actions-sheet";
|
||||
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../../common/database";
|
||||
import {
|
||||
PresentSheetOptions,
|
||||
presentSheet
|
||||
presentSheet,
|
||||
PresentSheetOptions
|
||||
} from "../../../services/event-manager";
|
||||
import { Reminder } from "../../../services/notifications";
|
||||
import { useRelationStore } from "../../../stores/use-relation-store";
|
||||
@@ -37,7 +38,7 @@ import { PressableButtonProps } from "../../ui/pressable";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
|
||||
type RelationsListProps = {
|
||||
actionSheetRef: RefObject<ActionSheet>;
|
||||
actionSheetRef: RefObject<ActionSheetRef>;
|
||||
close?: () => void;
|
||||
update?: (options: PresentSheetOptions) => void;
|
||||
item: { id: string; type: string };
|
||||
@@ -62,8 +63,6 @@ const IconsByType = {
|
||||
|
||||
export const RelationsList = ({
|
||||
actionSheetRef,
|
||||
close,
|
||||
update,
|
||||
item,
|
||||
referenceType,
|
||||
relationType,
|
||||
@@ -75,7 +74,6 @@ export const RelationsList = ({
|
||||
const [items, setItems] = useState<Reminder[]>([]);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const hasNoRelations = !items || items.length === 0;
|
||||
|
||||
useEffect(() => {
|
||||
setItems(
|
||||
db.relations?.[relationType]?.(
|
||||
@@ -123,6 +121,7 @@ export const RelationsList = ({
|
||||
) : (
|
||||
<List
|
||||
listData={items}
|
||||
ScrollComponent={FlashList}
|
||||
loading={false}
|
||||
type={referenceType}
|
||||
headerProps={null}
|
||||
|
||||
@@ -18,8 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import dayjs from "dayjs";
|
||||
import React, { RefObject } from "react";
|
||||
import { ScrollView, View } from "react-native";
|
||||
import ActionSheet from "react-native-actions-sheet";
|
||||
import { View } from "react-native";
|
||||
import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../../common/database";
|
||||
import {
|
||||
@@ -36,7 +36,7 @@ import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
|
||||
type ReminderSheetProps = {
|
||||
actionSheetRef: RefObject<ActionSheet>;
|
||||
actionSheetRef: RefObject<ActionSheetRef>;
|
||||
close?: () => void;
|
||||
update?: (options: PresentSheetOptions) => void;
|
||||
reminder?: Reminder;
|
||||
|
||||
@@ -17,14 +17,8 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import React, { RefObject, useRef, useState } from "react";
|
||||
import {
|
||||
Platform,
|
||||
ScrollView,
|
||||
TextInput,
|
||||
useWindowDimensions,
|
||||
View
|
||||
} from "react-native";
|
||||
import ActionSheet from "react-native-actions-sheet";
|
||||
import { Platform, TextInput, View } from "react-native";
|
||||
import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
|
||||
import DateTimePickerModal from "react-native-modal-datetime-picker";
|
||||
import {
|
||||
presentSheet,
|
||||
@@ -44,13 +38,13 @@ import Notifications, { Reminder } from "../../../services/notifications";
|
||||
import PremiumService from "../../../services/premium";
|
||||
import SettingsService from "../../../services/settings";
|
||||
import { useRelationStore } from "../../../stores/use-relation-store";
|
||||
import { ReminderTime } from "../../ui/reminder-time";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { NoteType } from "../../../utils/types";
|
||||
import { Dialog } from "../../dialog";
|
||||
import { ReminderTime } from "../../ui/reminder-time";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
|
||||
type ReminderSheetProps = {
|
||||
actionSheetRef: RefObject<ActionSheet>;
|
||||
actionSheetRef: RefObject<ActionSheetRef>;
|
||||
close?: (ctx?: string) => void;
|
||||
update?: (options: PresentSheetOptions) => void;
|
||||
reminder?: Reminder;
|
||||
@@ -117,13 +111,15 @@ export default function ReminderSheet({
|
||||
>(reminder?.priority || SettingsService.get().reminderNotificationMode);
|
||||
const [isDatePickerVisible, setDatePickerVisibility] = useState(false);
|
||||
const [repeatFrequency, setRepeatFrequency] = useState(1);
|
||||
const title = useRef<string | undefined>(reminder?.title);
|
||||
const details = useRef<string | undefined>(reminder?.description);
|
||||
const titleRef = useRef<TextInput>(null);
|
||||
const { height } = useWindowDimensions();
|
||||
const referencedItem = reference
|
||||
? (db.notes?.note(reference.id)?.data as NoteType)
|
||||
: null;
|
||||
const title = useRef<string | undefined>(
|
||||
reminder?.title || referencedItem?.title
|
||||
);
|
||||
const details = useRef<string | undefined>(reminder?.description);
|
||||
const titleRef = useRef<TextInput>(null);
|
||||
const timer = useRef<NodeJS.Timeout>();
|
||||
|
||||
const showDatePicker = () => {
|
||||
setDatePickerVisibility(true);
|
||||
@@ -134,9 +130,10 @@ export default function ReminderSheet({
|
||||
};
|
||||
|
||||
const handleConfirm = (date: Date) => {
|
||||
hideDatePicker();
|
||||
setDate(date);
|
||||
console.log(date);
|
||||
timer.current = setTimeout(() => {
|
||||
hideDatePicker();
|
||||
setDate(date);
|
||||
}, 50);
|
||||
};
|
||||
function nth(n: number) {
|
||||
return (
|
||||
@@ -233,13 +230,8 @@ export default function ReminderSheet({
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
>
|
||||
<Dialog context="local"/>
|
||||
<ScrollView
|
||||
onScrollEndDrag={() => actionSheetRef.current?.handleChildScrollEnd()}
|
||||
style={{
|
||||
maxHeight: height * 0.85
|
||||
}}
|
||||
>
|
||||
<Dialog context="local" />
|
||||
<ScrollView keyboardShouldPersistTaps="always">
|
||||
<Input
|
||||
fwdRef={titleRef}
|
||||
defaultValue={reminder?.title || referencedItem?.title}
|
||||
@@ -249,7 +241,9 @@ export default function ReminderSheet({
|
||||
/>
|
||||
|
||||
<Input
|
||||
defaultValue={reminder ? reminder?.description : referencedItem?.headline}
|
||||
defaultValue={
|
||||
reminder ? reminder?.description : referencedItem?.headline
|
||||
}
|
||||
placeholder="Add a quick note"
|
||||
onChangeText={(text) => (details.current = text)}
|
||||
containerStyle={{
|
||||
@@ -440,6 +434,12 @@ export default function ReminderSheet({
|
||||
|
||||
<DatePicker
|
||||
date={date}
|
||||
minimumDate={
|
||||
dayjs(date).subtract(3, "months").isBefore(dayjs())
|
||||
? dayjs().toDate()
|
||||
: dayjs(date).subtract(3, "months").toDate()
|
||||
}
|
||||
maximumDate={dayjs(date).add(3, "months").toDate()}
|
||||
onDateChange={handleConfirm}
|
||||
textColor={colors.night ? "#ffffff" : "#000000"}
|
||||
fadeToColor={colors.bg}
|
||||
@@ -561,16 +561,16 @@ export default function ReminderSheet({
|
||||
alignSelf: "flex-start"
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
title="Save"
|
||||
type="accent"
|
||||
fontSize={SIZE.md}
|
||||
onPress={saveReminder}
|
||||
/>
|
||||
</ScrollView>
|
||||
<Button
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
title="Save"
|
||||
type="accent"
|
||||
fontSize={SIZE.md}
|
||||
onPress={saveReminder}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -582,6 +582,7 @@ ReminderSheet.present = (
|
||||
) => {
|
||||
presentSheet({
|
||||
context: isSheet ? "local" : undefined,
|
||||
enableGesturesInScrollView: true,
|
||||
component: (ref, close, update) => (
|
||||
<ReminderSheet
|
||||
actionSheetRef={ref}
|
||||
|
||||
@@ -18,10 +18,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { EVENTS } from "@notesnook/core/common";
|
||||
import React, { createRef, useCallback, useEffect, useState } from "react";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { ActivityIndicator, Platform, View } from "react-native";
|
||||
import { FlatList } from "react-native-actions-sheet";
|
||||
import DocumentPicker from "react-native-document-picker";
|
||||
import { FlatList } from "react-native-gesture-handler";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import { db } from "../../../common/database";
|
||||
import storage from "../../../common/database/storage";
|
||||
@@ -35,7 +35,7 @@ import { initialize } from "../../../stores";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { eCloseRestoreDialog, eOpenRestoreDialog } from "../../../utils/events";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { sleep, timeConverter } from "../../../utils/time";
|
||||
import { timeConverter } from "../../../utils/time";
|
||||
import { Dialog } from "../../dialog";
|
||||
import DialogHeader from "../../dialog/dialog-header";
|
||||
import { presentDialog } from "../../dialog/functions";
|
||||
@@ -44,12 +44,18 @@ import { Button } from "../../ui/button";
|
||||
import Seperator from "../../ui/seperator";
|
||||
import SheetWrapper from "../../ui/sheet";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
const actionSheetRef = createRef();
|
||||
let RNFetchBlob;
|
||||
const RestoreDataSheet = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [restoring, setRestoring] = useState(false);
|
||||
const sheet = useRef();
|
||||
useEffect(() => {
|
||||
const open = async () => {
|
||||
setVisible(true);
|
||||
setTimeout(() => {
|
||||
sheet.current?.show();
|
||||
}, 1);
|
||||
};
|
||||
eSubscribeEvent(eOpenRestoreDialog, open);
|
||||
eSubscribeEvent(eCloseRestoreDialog, close);
|
||||
return () => {
|
||||
@@ -58,21 +64,15 @@ const RestoreDataSheet = () => {
|
||||
};
|
||||
}, [close]);
|
||||
|
||||
const open = async () => {
|
||||
setVisible(true);
|
||||
await sleep(30);
|
||||
actionSheetRef.current?.setModalVisible(true);
|
||||
};
|
||||
|
||||
const close = useCallback(() => {
|
||||
if (restoring) {
|
||||
showIsWorking();
|
||||
return;
|
||||
}
|
||||
actionSheetRef.current?.setModalVisible(false);
|
||||
sheet.current?.hide();
|
||||
setTimeout(() => {
|
||||
setVisible(false);
|
||||
}, 300);
|
||||
}, 150);
|
||||
}, [restoring]);
|
||||
|
||||
const showIsWorking = () => {
|
||||
@@ -86,15 +86,19 @@ const RestoreDataSheet = () => {
|
||||
|
||||
return !visible ? null : (
|
||||
<SheetWrapper
|
||||
fwdRef={actionSheetRef}
|
||||
fwdRef={sheet}
|
||||
gestureEnabled={!restoring}
|
||||
closeOnTouchBackdrop={!restoring}
|
||||
onClose={close}
|
||||
onClose={() => {
|
||||
setVisible(false);
|
||||
close();
|
||||
}}
|
||||
>
|
||||
<RestoreDataComponent
|
||||
close={close}
|
||||
restoring={restoring}
|
||||
setRestoring={setRestoring}
|
||||
actionSheetRef={sheet}
|
||||
/>
|
||||
<Toast context="local" />
|
||||
</SheetWrapper>
|
||||
@@ -109,7 +113,6 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [backupDirectoryAndroid, setBackupDirectoryAndroid] = useState(false);
|
||||
const [progress, setProgress] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = db.eventManager.subscribe(
|
||||
EVENTS.migrationProgress,
|
||||
@@ -123,7 +126,9 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
checkBackups();
|
||||
setTimeout(() => {
|
||||
checkBackups();
|
||||
}, 300);
|
||||
}, []);
|
||||
|
||||
const restore = async (item) => {
|
||||
@@ -362,10 +367,6 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
|
||||
</View>
|
||||
<Seperator half />
|
||||
<FlatList
|
||||
nestedScrollEnabled
|
||||
onMomentumScrollEnd={() => {
|
||||
actionSheetRef.current?.handleChildScrollEnd();
|
||||
}}
|
||||
ListEmptyComponent={
|
||||
!restoring ? (
|
||||
loading ? (
|
||||
|
||||
@@ -100,7 +100,7 @@ const Sort = ({ type, screen }) => {
|
||||
height={25}
|
||||
iconPosition="right"
|
||||
fontSize={SIZE.sm - 1}
|
||||
type="grayBg"
|
||||
type="transparent"
|
||||
buttonType={{
|
||||
text: colors.accent
|
||||
}}
|
||||
|
||||
451
apps/mobile/app/components/sheets/topic-sheet/index.tsx
Normal file
451
apps/mobile/app/components/sheets/topic-sheet/index.tsx
Normal file
@@ -0,0 +1,451 @@
|
||||
/*
|
||||
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 qclone from "qclone";
|
||||
import React, {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState
|
||||
} from "react";
|
||||
import { Animated, Dimensions, View, RefreshControl } from "react-native";
|
||||
import ActionSheet, {
|
||||
ActionSheetRef,
|
||||
FlatList
|
||||
} from "react-native-actions-sheet";
|
||||
import { db } from "../../../common/database";
|
||||
import { IconButton } from "../../../components/ui/icon-button";
|
||||
import { PressableButton } from "../../../components/ui/pressable";
|
||||
import Paragraph from "../../../components/ui/typography/paragraph";
|
||||
import { TopicNotes } from "../../../screens/notes/topic-notes";
|
||||
import {
|
||||
eSendEvent,
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../../services/event-manager";
|
||||
import useNavigationStore, {
|
||||
NotebookScreenParams
|
||||
} from "../../../stores/use-navigation-store";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import {
|
||||
eOnNewTopicAdded,
|
||||
eOnTopicSheetUpdate,
|
||||
eOpenAddTopicDialog
|
||||
} from "../../../utils/events";
|
||||
import { normalize, SIZE } from "../../../utils/size";
|
||||
import { NotebookType, TopicType } from "../../../utils/types";
|
||||
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { openEditor } from "../../../screens/notes/common";
|
||||
import { getTotalNotes, history } from "../../../utils";
|
||||
import { Properties } from "../../properties";
|
||||
import { deleteItems } from "../../../utils/functions";
|
||||
import { presentDialog } from "../../dialog/functions";
|
||||
import Config from "react-native-config";
|
||||
import { notesnook } from "../../../../e2e/test.ids";
|
||||
|
||||
export const TopicsSheet = () => {
|
||||
const currentScreen = useNavigationStore((state) => state.currentScreen);
|
||||
const canShow =
|
||||
currentScreen.name === "Notebook" || currentScreen.name === "TopicNotes";
|
||||
const [notebook, setNotebook] = useState(
|
||||
canShow
|
||||
? db.notebooks?.notebook(
|
||||
currentScreen?.notebookId || currentScreen?.id || ""
|
||||
)?.data
|
||||
: null
|
||||
);
|
||||
const [selection, setSelection] = useState<TopicType[]>([]);
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const ref = useRef<ActionSheetRef>(null);
|
||||
const [topics, setTopics] = useState(notebook ? qclone(notebook.topics) : []);
|
||||
const [animations] = useState({
|
||||
translate: new Animated.Value(0),
|
||||
display: new Animated.Value(-5000),
|
||||
opacity: new Animated.Value(0)
|
||||
});
|
||||
const onRequestUpdate = React.useCallback(
|
||||
(data?: NotebookScreenParams) => {
|
||||
if (!canShow) return;
|
||||
if (!data) data = { item: notebook } as NotebookScreenParams;
|
||||
const _notebook = db.notebooks?.notebook(data.item?.id)
|
||||
?.data as NotebookType;
|
||||
if (_notebook) {
|
||||
setNotebook(_notebook);
|
||||
setTopics(qclone(_notebook.topics));
|
||||
}
|
||||
},
|
||||
[notebook, canShow]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const onTopicUpdate = () => {
|
||||
onRequestUpdate();
|
||||
};
|
||||
eSubscribeEvent(eOnTopicSheetUpdate, onTopicUpdate);
|
||||
eSubscribeEvent(eOnNewTopicAdded, onRequestUpdate);
|
||||
return () => {
|
||||
eUnSubscribeEvent(eOnTopicSheetUpdate, onRequestUpdate);
|
||||
eUnSubscribeEvent(eOnNewTopicAdded, onTopicUpdate);
|
||||
};
|
||||
}, [onRequestUpdate]);
|
||||
|
||||
const PLACEHOLDER_DATA = {
|
||||
heading: "Topics",
|
||||
paragraph: "You have not added any topics yet.",
|
||||
button: "Add first topic",
|
||||
action: () => {
|
||||
eSendEvent(eOpenAddTopicDialog, { notebookId: notebook.id });
|
||||
},
|
||||
loading: "Loading notebook topics"
|
||||
};
|
||||
|
||||
const renderTopic = ({ item, index }: { item: TopicType; index: number }) => (
|
||||
<TopicItem item={item} index={index} />
|
||||
);
|
||||
|
||||
const selectionContext = {
|
||||
selection: selection,
|
||||
enabled,
|
||||
setEnabled,
|
||||
toggleSelection: (item: TopicType) => {
|
||||
setSelection((state) => {
|
||||
const selection = [...state];
|
||||
const index = selection.findIndex(
|
||||
(selected) => selected.id === item.id
|
||||
);
|
||||
if (index > -1) {
|
||||
selection.splice(index, 1);
|
||||
if (selection.length === 0) {
|
||||
setEnabled(false);
|
||||
}
|
||||
return selection;
|
||||
}
|
||||
selection.push(item);
|
||||
return selection;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (canShow) {
|
||||
const isTopic = currentScreen.name === "TopicNotes";
|
||||
const id = isTopic ? currentScreen?.notebookId : currentScreen?.id;
|
||||
if (!ref.current?.isOpen()) {
|
||||
animations.display.setValue(5000);
|
||||
animations.opacity.setValue(0);
|
||||
}
|
||||
if (id) {
|
||||
onRequestUpdate({
|
||||
item: db.notebooks?.notebook(id).data
|
||||
} as any);
|
||||
}
|
||||
ref.current?.show();
|
||||
} else {
|
||||
ref.current?.hide();
|
||||
}
|
||||
}, [
|
||||
animations.display,
|
||||
animations.opacity,
|
||||
canShow,
|
||||
currentScreen?.id,
|
||||
currentScreen.name,
|
||||
currentScreen?.notebookId,
|
||||
onRequestUpdate
|
||||
]);
|
||||
|
||||
return (
|
||||
<ActionSheet
|
||||
ref={ref}
|
||||
isModal={false}
|
||||
containerStyle={{
|
||||
maxHeight: 400,
|
||||
borderTopRightRadius: 15,
|
||||
borderTopLeftRadius: 15,
|
||||
backgroundColor: colors.bg,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border,
|
||||
borderBottomWidth: 0
|
||||
}}
|
||||
closable={!canShow}
|
||||
elevation={10}
|
||||
indicatorStyle={{
|
||||
width: 100,
|
||||
backgroundColor: colors.nav
|
||||
}}
|
||||
keyboardHandlerEnabled={false}
|
||||
snapPoints={Config.isTesting === "true" ? [100] : [15, 100]}
|
||||
initialSnapIndex={0}
|
||||
backgroundInteractionEnabled
|
||||
onChange={(position, height) => {
|
||||
animations.translate.setValue(position - 60);
|
||||
const h = Dimensions.get("window").height;
|
||||
const minPos = h - height;
|
||||
if (position - 100 < minPos || !canShow) {
|
||||
animations.display.setValue(5000);
|
||||
animations.opacity.setValue(0);
|
||||
} else {
|
||||
animations.display.setValue(0);
|
||||
setTimeout(() => {
|
||||
animations.opacity.setValue(1);
|
||||
}, 300);
|
||||
}
|
||||
}}
|
||||
gestureEnabled
|
||||
ExtraOverlayComponent={
|
||||
<Animated.View
|
||||
style={{
|
||||
top: animations.translate,
|
||||
position: "absolute",
|
||||
right: 12,
|
||||
opacity: animations.opacity,
|
||||
transform: [
|
||||
{
|
||||
translateY: animations.display
|
||||
}
|
||||
]
|
||||
}}
|
||||
>
|
||||
<PressableButton
|
||||
testID={notesnook.buttons.add}
|
||||
type="accent"
|
||||
accentColor={"accent"}
|
||||
accentText="light"
|
||||
onPress={openEditor}
|
||||
customStyle={{
|
||||
borderRadius: 100
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
height: normalize(60),
|
||||
width: normalize(60)
|
||||
}}
|
||||
>
|
||||
<Icon name="plus" color="white" size={SIZE.xxl} />
|
||||
</View>
|
||||
</PressableButton>
|
||||
</Animated.View>
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
maxHeight: 400,
|
||||
height: 400,
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
paddingHorizontal: 12,
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Paragraph size={SIZE.xs} color={colors.icon}>
|
||||
TOPICS
|
||||
</Paragraph>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row"
|
||||
}}
|
||||
>
|
||||
{enabled ? (
|
||||
<IconButton
|
||||
customStyle={{
|
||||
marginLeft: 10
|
||||
}}
|
||||
onPress={async () => {
|
||||
//@ts-ignore
|
||||
history.selectedItemsList = selection;
|
||||
presentDialog({
|
||||
title: `Delete ${
|
||||
selection.length > 1 ? "topics" : "topics"
|
||||
}`,
|
||||
paragraph: `Are you sure you want to delete ${
|
||||
selection.length > 1 ? "these topicss?" : "this topics?"
|
||||
}`,
|
||||
positiveText: "Delete",
|
||||
negativeText: "Cancel",
|
||||
positivePress: async () => {
|
||||
await deleteItems();
|
||||
history.selectedItemsList = [];
|
||||
setEnabled(false);
|
||||
setSelection([]);
|
||||
},
|
||||
positiveType: "errorShade"
|
||||
});
|
||||
return;
|
||||
}}
|
||||
color={colors.pri}
|
||||
tooltipText="Move to trash"
|
||||
tooltipPosition={1}
|
||||
name="delete"
|
||||
size={22}
|
||||
/>
|
||||
) : (
|
||||
<IconButton
|
||||
name="plus"
|
||||
onPress={PLACEHOLDER_DATA.action}
|
||||
testID="add-topic-button"
|
||||
color={colors.pri}
|
||||
size={22}
|
||||
customStyle={{
|
||||
width: 40,
|
||||
height: 40
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
<SelectionContext.Provider value={selectionContext}>
|
||||
<FlatList
|
||||
data={topics}
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={false}
|
||||
onRefresh={() => {
|
||||
onRequestUpdate();
|
||||
}}
|
||||
colors={[colors.accent]}
|
||||
progressBackgroundColor={colors.bg}
|
||||
/>
|
||||
}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={renderTopic}
|
||||
ListEmptyComponent={
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: 300
|
||||
}}
|
||||
>
|
||||
<Paragraph color={colors.icon}>No topics</Paragraph>
|
||||
</View>
|
||||
}
|
||||
ListFooterComponent={<View style={{ height: 50 }} />}
|
||||
/>
|
||||
</SelectionContext.Provider>
|
||||
</View>
|
||||
</ActionSheet>
|
||||
);
|
||||
};
|
||||
|
||||
const SelectionContext = createContext<{
|
||||
selection: TopicType[];
|
||||
enabled: boolean;
|
||||
setEnabled: (value: boolean) => void;
|
||||
toggleSelection: (item: TopicType) => void;
|
||||
}>({
|
||||
selection: [],
|
||||
enabled: false,
|
||||
setEnabled: (value: boolean) => {},
|
||||
toggleSelection: (item: TopicType) => {}
|
||||
});
|
||||
const useSelection = () => useContext(SelectionContext);
|
||||
|
||||
const TopicItem = ({ item, index }: { item: TopicType; index: number }) => {
|
||||
const screen = useNavigationStore((state) => state.currentScreen);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const selection = useSelection();
|
||||
const isSelected =
|
||||
selection.selection.findIndex((selected) => selected.id === item.id) > -1;
|
||||
const isFocused = screen.id === item.id;
|
||||
const notesCount = getTotalNotes(item);
|
||||
|
||||
return (
|
||||
<PressableButton
|
||||
type={isSelected || isFocused ? "grayBg" : "transparent"}
|
||||
onLongPress={() => {
|
||||
if (selection.enabled) return;
|
||||
selection.setEnabled(true);
|
||||
selection.toggleSelection(item);
|
||||
}}
|
||||
testID={`topic-sheet-item-${index}`}
|
||||
onPress={() => {
|
||||
if (selection.enabled) {
|
||||
selection.toggleSelection(item);
|
||||
return;
|
||||
}
|
||||
TopicNotes.navigate(item, true);
|
||||
}}
|
||||
customStyle={{
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
alignItems: "center",
|
||||
flexDirection: "row",
|
||||
paddingHorizontal: 12,
|
||||
borderRadius: 0
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
{selection.enabled ? (
|
||||
<IconButton
|
||||
size={SIZE.lg}
|
||||
color={isSelected ? colors.accent : colors.icon}
|
||||
name={
|
||||
isSelected
|
||||
? "check-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
<Paragraph size={SIZE.sm}>
|
||||
{item.title}{" "}
|
||||
{notesCount ? (
|
||||
<Paragraph size={SIZE.xs} color={colors.icon}>
|
||||
{notesCount}
|
||||
</Paragraph>
|
||||
) : null}
|
||||
</Paragraph>
|
||||
</View>
|
||||
<IconButton
|
||||
name="dots-horizontal"
|
||||
customStyle={{
|
||||
width: 40,
|
||||
height: 40
|
||||
}}
|
||||
testID={notesnook.ids.notebook.menu}
|
||||
onPress={() => {
|
||||
Properties.present(item);
|
||||
}}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
top={0}
|
||||
color={colors.pri}
|
||||
size={SIZE.xl}
|
||||
/>
|
||||
</PressableButton>
|
||||
);
|
||||
};
|
||||
@@ -19,10 +19,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Linking, View } from "react-native";
|
||||
import { ScrollView } from "react-native-actions-sheet";
|
||||
import { checkVersion } from "react-native-check-version";
|
||||
import Config from "react-native-config";
|
||||
import deviceInfoModule from "react-native-device-info";
|
||||
import { ScrollView } from "react-native-gesture-handler";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { STORE_LINK } from "../../../utils/constants";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
@@ -148,9 +148,6 @@ export const Update = ({ version: appVersion, fwdRef }) => {
|
||||
<Seperator />
|
||||
<ScrollView
|
||||
nestedScrollEnabled={true}
|
||||
onMomentumScrollEnd={() => {
|
||||
fwdRef?.current?.handleChildScrollEnd();
|
||||
}}
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
|
||||
@@ -37,7 +37,8 @@ const SheetWrapper = ({
|
||||
onHasReachedTop,
|
||||
keyboardMode,
|
||||
overlay,
|
||||
overlayOpacity = 0.3
|
||||
overlayOpacity = 0.3,
|
||||
enableGesturesInScrollView = false
|
||||
}) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||
@@ -59,8 +60,8 @@ const SheetWrapper = ({
|
||||
zIndex: 10,
|
||||
paddingTop: 5,
|
||||
paddingBottom: 0,
|
||||
borderTopRightRadius: 20,
|
||||
borderTopLeftRadius: 20,
|
||||
borderTopRightRadius: 15,
|
||||
borderTopLeftRadius: 15,
|
||||
alignSelf: "center",
|
||||
borderBottomRightRadius: 0,
|
||||
borderBottomLeftRadius: 0
|
||||
@@ -84,7 +85,8 @@ const SheetWrapper = ({
|
||||
backdrop: "sheet-backdrop"
|
||||
}}
|
||||
indicatorStyle={{
|
||||
width: 100
|
||||
width: 100,
|
||||
backgroundColor: colors.nav
|
||||
}}
|
||||
drawUnderStatusBar={false}
|
||||
containerStyle={style}
|
||||
@@ -98,8 +100,9 @@ const SheetWrapper = ({
|
||||
indicatorColor={colors.nav}
|
||||
onOpen={_onOpen}
|
||||
keyboardDismissMode="none"
|
||||
enableGesturesInScrollView={enableGesturesInScrollView}
|
||||
defaultOverlayOpacity={overlayOpacity}
|
||||
overlayColor={pitchBlack ? "#585858" : "#000000"}
|
||||
overlayColor={pitchBlack ? "#585858" : "#2b2b2b"}
|
||||
keyboardShouldPersistTaps="always"
|
||||
ExtraOverlayComponent={
|
||||
<>
|
||||
|
||||
@@ -46,6 +46,7 @@ export default function Tag({
|
||||
marginLeft: 2,
|
||||
marginTop: -10,
|
||||
height: 20,
|
||||
justifyContent: "center",
|
||||
...style
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -19,4 +19,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { FeatureType } from "./components/sheets/new-feature";
|
||||
|
||||
export const features: FeatureType[] = [];
|
||||
export const features: FeatureType[] = [
|
||||
{
|
||||
title: "Top level notes in notebooks",
|
||||
body: "Now you can add notes to Notesnooks directly without creating any topics."
|
||||
},
|
||||
{
|
||||
title: "Image preview & download",
|
||||
body: "Now you can preview images in fullscreen & download them from editor"
|
||||
}
|
||||
];
|
||||
|
||||
@@ -22,10 +22,15 @@ import React, { useCallback, useEffect, useState } from "react";
|
||||
import { Platform } from "react-native";
|
||||
import Share from "react-native-share";
|
||||
import { db } from "../common/database";
|
||||
import { AttachmentDialog } from "../components/attachments";
|
||||
import { presentDialog } from "../components/dialog/functions";
|
||||
import NoteHistory from "../components/note-history";
|
||||
import { AddNotebookSheet } from "../components/sheets/add-notebook";
|
||||
import MoveNoteSheet from "../components/sheets/add-to";
|
||||
import ExportNotesSheet from "../components/sheets/export-notes";
|
||||
import { MoveNotes } from "../components/sheets/move-notes/movenote";
|
||||
import PublishNoteSheet from "../components/sheets/publish-note";
|
||||
import { RelationsList } from "../components/sheets/relations-list/index";
|
||||
import ReminderSheet from "../components/sheets/reminder";
|
||||
import {
|
||||
eSendEvent,
|
||||
@@ -40,24 +45,16 @@ import Notifications from "../services/notifications";
|
||||
import { useEditorStore } from "../stores/use-editor-store";
|
||||
import { useMenuStore } from "../stores/use-menu-store";
|
||||
import useNavigationStore from "../stores/use-navigation-store";
|
||||
import { useRelationStore } from "../stores/use-relation-store";
|
||||
import { useSelectionStore } from "../stores/use-selection-store";
|
||||
import { useTagStore } from "../stores/use-tag-store";
|
||||
import { useThemeStore } from "../stores/use-theme-store";
|
||||
import { useUserStore } from "../stores/use-user-store";
|
||||
import { toTXT } from "../utils";
|
||||
import { toggleDarkMode } from "../utils/color-scheme/utils";
|
||||
import {
|
||||
eOpenAddNotebookDialog,
|
||||
eOpenAddTopicDialog,
|
||||
eOpenAttachmentsDialog,
|
||||
eOpenLoginDialog,
|
||||
eOpenMoveNoteDialog,
|
||||
eOpenPublishNoteDialog
|
||||
} from "../utils/events";
|
||||
import { eOpenAddTopicDialog, eOpenLoginDialog } from "../utils/events";
|
||||
import { deleteItems } from "../utils/functions";
|
||||
import { sleep } from "../utils/time";
|
||||
import { RelationsList } from "../components/sheets/relations-list/index";
|
||||
import { useRelationStore } from "../stores/use-relation-store";
|
||||
|
||||
export const useActions = ({ close = () => null, item }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
@@ -105,6 +102,15 @@ export const useActions = ({ close = () => null, item }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const isNoteInNotebook = () => {
|
||||
const currentScreen = useNavigationStore.getState().currentScreen;
|
||||
if (item.type !== "note" || currentScreen.name !== "Notebook") return;
|
||||
|
||||
return !!db.relations
|
||||
.to(item, "notebook")
|
||||
.find((notebook) => notebook.id === currentScreen.id);
|
||||
};
|
||||
|
||||
const onUpdate = useCallback(
|
||||
async (type) => {
|
||||
if (type === "unpin") {
|
||||
@@ -129,12 +135,9 @@ export const useActions = ({ close = () => null, item }) => {
|
||||
}
|
||||
|
||||
function addTo() {
|
||||
close();
|
||||
clearSelection(true);
|
||||
setSelectedItem(item);
|
||||
setTimeout(() => {
|
||||
eSendEvent(eOpenMoveNoteDialog, item);
|
||||
}, 300);
|
||||
MoveNoteSheet.present(item);
|
||||
}
|
||||
|
||||
async function addToFavorites() {
|
||||
@@ -270,9 +273,7 @@ export const useActions = ({ close = () => null, item }) => {
|
||||
});
|
||||
return;
|
||||
}
|
||||
close();
|
||||
await sleep(300);
|
||||
eSendEvent(eOpenPublishNoteDialog, item);
|
||||
PublishNoteSheet.present(item);
|
||||
}
|
||||
|
||||
const checkNoteSynced = () => {
|
||||
@@ -514,6 +515,22 @@ export const useActions = ({ close = () => null, item }) => {
|
||||
close();
|
||||
}
|
||||
|
||||
async function removeNoteFromNotebook() {
|
||||
const currentScreen = useNavigationStore.getState().currentScreen;
|
||||
if (currentScreen.name !== "Notebook") return;
|
||||
await db.relations.unlink({ type: "notebook", id: currentScreen.id }, item);
|
||||
Navigation.queueRoutesForUpdate(
|
||||
"TaggedNotes",
|
||||
"ColoredNotes",
|
||||
"TopicNotes",
|
||||
"Favorites",
|
||||
"Notes",
|
||||
"Notebook",
|
||||
"Notebooks"
|
||||
);
|
||||
close();
|
||||
}
|
||||
|
||||
async function deleteTrashItem() {
|
||||
if (!checkNoteSynced()) return;
|
||||
close();
|
||||
@@ -538,22 +555,16 @@ export const useActions = ({ close = () => null, item }) => {
|
||||
}
|
||||
|
||||
async function openHistory() {
|
||||
close();
|
||||
await sleep(300);
|
||||
presentSheet({
|
||||
component: (ref) => <NoteHistory fwdRef={ref} note={item} />
|
||||
});
|
||||
}
|
||||
|
||||
async function showAttachments() {
|
||||
close();
|
||||
await sleep(300);
|
||||
eSendEvent(eOpenAttachmentsDialog, item);
|
||||
AttachmentDialog.present();
|
||||
}
|
||||
|
||||
async function exportNote() {
|
||||
close();
|
||||
await sleep(300);
|
||||
ExportNotesSheet.present([item]);
|
||||
}
|
||||
|
||||
@@ -647,8 +658,6 @@ export const useActions = ({ close = () => null, item }) => {
|
||||
title: "Add notes",
|
||||
icon: "plus",
|
||||
func: async () => {
|
||||
close();
|
||||
await sleep(500);
|
||||
MoveNotes.present(db.notebooks.notebook(item.notebookId).data, item);
|
||||
}
|
||||
},
|
||||
@@ -689,9 +698,7 @@ export const useActions = ({ close = () => null, item }) => {
|
||||
title: "Edit notebook",
|
||||
icon: "square-edit-outline",
|
||||
func: async () => {
|
||||
close();
|
||||
await sleep(300);
|
||||
eSendEvent(eOpenAddNotebookDialog, item);
|
||||
AddNotebookSheet.present(item);
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -777,8 +784,6 @@ export const useActions = ({ close = () => null, item }) => {
|
||||
title: "Edit reminder",
|
||||
icon: "pencil",
|
||||
func: async () => {
|
||||
close();
|
||||
await sleep(300);
|
||||
ReminderSheet.present(item);
|
||||
},
|
||||
close: false
|
||||
@@ -789,7 +794,6 @@ export const useActions = ({ close = () => null, item }) => {
|
||||
title: "Reminders",
|
||||
icon: "clock-outline",
|
||||
func: async () => {
|
||||
close();
|
||||
RelationsList.present({
|
||||
reference: item,
|
||||
referenceType: "reminder",
|
||||
@@ -849,6 +853,13 @@ export const useActions = ({ close = () => null, item }) => {
|
||||
icon: "minus-circle-outline",
|
||||
func: removeNoteFromTopic
|
||||
},
|
||||
{
|
||||
id: "remove-from-notebook",
|
||||
title: "Remove from notebook",
|
||||
hidden: !isNoteInNotebook(),
|
||||
icon: "minus-circle-outline",
|
||||
func: removeNoteFromNotebook
|
||||
},
|
||||
{
|
||||
id: "trash",
|
||||
title:
|
||||
|
||||
@@ -26,12 +26,13 @@ import {
|
||||
Linking,
|
||||
NativeEventEmitter,
|
||||
NativeModules,
|
||||
Platform
|
||||
Platform,
|
||||
Keyboard
|
||||
} from "react-native";
|
||||
import * as RNIap from "react-native-iap";
|
||||
import { enabled } from "react-native-privacy-snapshot";
|
||||
import { Walkthrough } from "../components/walkthroughs";
|
||||
import { editorState } from "../screens/editor/tiptap/utils";
|
||||
import { editorController, editorState } from "../screens/editor/tiptap/utils";
|
||||
import {
|
||||
clearMessage,
|
||||
setEmailVerifyMessage,
|
||||
@@ -494,6 +495,10 @@ export const useAppEvents = () => {
|
||||
!useUserStore.getState().verifyUser
|
||||
) {
|
||||
useUserStore.getState().setVerifyUser(true);
|
||||
if (Platform.OS === "ios") {
|
||||
editorController.current?.commands.blur();
|
||||
Keyboard.dismiss();
|
||||
}
|
||||
}
|
||||
if (
|
||||
SettingsService.get().privacyScreen ||
|
||||
|
||||
@@ -24,6 +24,7 @@ import { SafeAreaView } from "react-native";
|
||||
import Container from "../components/container";
|
||||
import DelayLayout from "../components/delay-layout";
|
||||
import Intro from "../components/intro";
|
||||
import { TopicsSheet } from "../components/sheets/topic-sheet";
|
||||
import useGlobalSafeAreaInsets from "../hooks/use-global-safe-area-insets";
|
||||
import { hideAllTooltips } from "../hooks/use-tooltip";
|
||||
import Favorites from "../screens/favorites";
|
||||
@@ -198,6 +199,7 @@ const _NavigationStack = () => {
|
||||
<NavigationContainer onStateChange={onStateChange} ref={rootNavigatorRef}>
|
||||
<Tabs />
|
||||
</NavigationContainer>
|
||||
<TopicsSheet />
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -235,14 +235,17 @@ const _TabsHolder = () => {
|
||||
let needsUpdate = current !== deviceMode;
|
||||
|
||||
if (fullscreen && current !== "mobile") {
|
||||
editorRef.current?.setNativeProps({
|
||||
style: {
|
||||
width: size.width,
|
||||
zIndex: 999,
|
||||
paddingHorizontal:
|
||||
current === "smallTablet" ? size.width * 0 : size.width * 0.15
|
||||
}
|
||||
});
|
||||
// Runs after size is set via state.
|
||||
setTimeout(() => {
|
||||
editorRef.current?.setNativeProps({
|
||||
style: {
|
||||
width: size.width,
|
||||
zIndex: 999,
|
||||
paddingHorizontal:
|
||||
current === "smallTablet" ? size.width * 0 : size.width * 0.15
|
||||
}
|
||||
});
|
||||
}, 1);
|
||||
} else {
|
||||
if (fullscreen) eSendEvent(eCloseFullscreenEditor, current);
|
||||
editorRef.current?.setNativeProps({
|
||||
@@ -281,10 +284,8 @@ const _TabsHolder = () => {
|
||||
!editorState().movedAway &&
|
||||
useEditorStore.getState().currentEditingNote
|
||||
) {
|
||||
console.log("editor");
|
||||
tabBarRef.current?.goToIndex(2, false);
|
||||
} else {
|
||||
console.log("home");
|
||||
tabBarRef.current?.goToIndex(1, false);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
"main": "./App.js",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"react": "18.0.0",
|
||||
"react-native": "0.69.7",
|
||||
"@flyerhq/react-native-link-preview": "^1.6.0",
|
||||
"@mdi/js": "^6.7.96",
|
||||
"absolutify": "^0.1.0",
|
||||
@@ -13,7 +15,7 @@
|
||||
"html-to-text": "8.1.0",
|
||||
"phone": "^3.1.14",
|
||||
"qclone": "^1.2.0",
|
||||
"react-native-actions-sheet": "^0.7.2",
|
||||
"react-native-actions-sheet": "^0.9.0-alpha.12",
|
||||
"react-native-check-version": "https://github.com/flexible-agency/react-native-check-version",
|
||||
"react-native-drax": "^0.10.2",
|
||||
"react-native-image-zoom-viewer": "^3.0.1",
|
||||
@@ -28,7 +30,6 @@
|
||||
"zustand": "^3.6.0",
|
||||
"fflate": "^0.7.3",
|
||||
"timeago.js": "4.0.2"
|
||||
|
||||
},
|
||||
"sideEffects": false
|
||||
}
|
||||
|
||||
@@ -107,6 +107,7 @@ const Editor = React.memo(
|
||||
attachmentType: string;
|
||||
}) => {
|
||||
if (groupId !== editor.note.current?.id) return;
|
||||
editorController.current.markImageLoaded(hash);
|
||||
if (attachmentType === "webclip") {
|
||||
editor.commands.updateWebclip({
|
||||
hash: hash,
|
||||
|
||||
@@ -78,7 +78,7 @@ const EditorOverlay = ({ editorId = "", editor }) => {
|
||||
setTimeout(() => {
|
||||
translateValue.value = 6000;
|
||||
}, 500);
|
||||
}, 100);
|
||||
}, 0);
|
||||
}
|
||||
},
|
||||
[opacity, translateValue]
|
||||
|
||||
@@ -48,6 +48,7 @@ const fn = (fn: string) => {
|
||||
const id = randId("fn_");
|
||||
return {
|
||||
job: `(async () => {
|
||||
if (typeof __PLATFORM__ === "undefined") __PLATFORM__ = "${Platform.OS}";
|
||||
try {
|
||||
let response = true;
|
||||
${fn}
|
||||
@@ -57,7 +58,7 @@ const fn = (fn: string) => {
|
||||
if (DEV_MODE && typeof logger !== "undefined") logger('error', "webview: ", e.message, e.stack);
|
||||
}
|
||||
return true;
|
||||
})();`,
|
||||
})();true;`,
|
||||
id: id
|
||||
};
|
||||
};
|
||||
@@ -228,7 +229,7 @@ typeof globalThis.statusBar !== "undefined" && statusBar.current.set({date:"",sa
|
||||
await this.doAsync(
|
||||
`editor && editor.commands.updateImage(${JSON.stringify({
|
||||
hash
|
||||
})},${JSON.stringify({ src, hash, preventUpdate: true })})`
|
||||
})},${JSON.stringify({ dataurl: src, hash, preventUpdate: true })})`
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -34,5 +34,6 @@ export const EventTypes = {
|
||||
fullscreen: "editor-event:fullscreen",
|
||||
link: "editor-event:link",
|
||||
contentchange: "editor-event:content-change",
|
||||
reminders: "editor-event:reminders"
|
||||
reminders: "editor-event:reminders",
|
||||
previewAttachment: "editor-event:preview-attachment"
|
||||
};
|
||||
|
||||
@@ -17,13 +17,14 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Sodium from "@ammarahmed/react-native-sodium";
|
||||
import React from "react";
|
||||
import { Platform, View } from "react-native";
|
||||
import DocumentPicker from "react-native-document-picker";
|
||||
import { launchCamera, launchImageLibrary } from "react-native-image-picker";
|
||||
import Sodium from "@ammarahmed/react-native-sodium";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import { db } from "../../../common/database";
|
||||
import { compressToBase64 } from "../../../common/filesystem/compress";
|
||||
import { AttachmentItem } from "../../../components/attachments/attachment-item";
|
||||
import {
|
||||
eSendEvent,
|
||||
@@ -32,7 +33,6 @@ import {
|
||||
} from "../../../services/event-manager";
|
||||
import PremiumService from "../../../services/premium";
|
||||
import { eCloseSheet } from "../../../utils/events";
|
||||
import { sleep } from "../../../utils/time";
|
||||
import { editorController, editorState } from "./utils";
|
||||
const FILE_SIZE_LIMIT = 500 * 1024 * 1024;
|
||||
const IMAGE_SIZE_LIMIT = 50 * 1024 * 1024;
|
||||
@@ -124,6 +124,7 @@ const file = async (fileOptions) => {
|
||||
});
|
||||
if (!(await attachFile(uri, hash, file.type, file.name, fileOptions)))
|
||||
return;
|
||||
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
|
||||
editorController.current?.commands.insertAttachment({
|
||||
hash: hash,
|
||||
filename: file.name,
|
||||
@@ -147,8 +148,6 @@ const file = async (fileOptions) => {
|
||||
const camera = async (options) => {
|
||||
try {
|
||||
await db.attachments.generateKey();
|
||||
eSendEvent(eCloseSheet);
|
||||
await sleep(400);
|
||||
launchCamera(
|
||||
{
|
||||
includeBase64: true,
|
||||
@@ -170,8 +169,6 @@ const camera = async (options) => {
|
||||
const gallery = async (options) => {
|
||||
try {
|
||||
await db.attachments.generateKey();
|
||||
eSendEvent(eCloseSheet);
|
||||
await sleep(400);
|
||||
launchImageLibrary(
|
||||
{
|
||||
includeBase64: true,
|
||||
@@ -205,7 +202,7 @@ const pick = async (options) => {
|
||||
return;
|
||||
}
|
||||
if (options?.type.startsWith("image") || options?.type === "camera") {
|
||||
if (options.type === "image") {
|
||||
if (options.type.startsWith("image")) {
|
||||
gallery(options);
|
||||
} else {
|
||||
camera(options);
|
||||
@@ -234,7 +231,7 @@ const handleImageResponse = async (response, options) => {
|
||||
});
|
||||
return;
|
||||
}
|
||||
const b64 = `data:${image.type};base64, ` + image.base64;
|
||||
let b64 = `data:${image.type};base64, ` + image.base64;
|
||||
const uri = decodeURI(image.uri);
|
||||
const hash = await Sodium.hashFile({
|
||||
uri: uri,
|
||||
@@ -243,12 +240,24 @@ const handleImageResponse = async (response, options) => {
|
||||
|
||||
let fileName = image.originalFileName || image.fileName;
|
||||
if (!(await attachFile(uri, hash, image.type, fileName, options))) return;
|
||||
const isPng = /(png)/g.test(image.type);
|
||||
const isJpeg = /(jpeg|jpg)/g.test(image.type);
|
||||
if (isPng || isJpeg) {
|
||||
b64 =
|
||||
`data:${image.type};base64, ` +
|
||||
(await compressToBase64(
|
||||
Platform.OS === "ios" ? "file://" + image.uri : image.uri,
|
||||
isPng ? "PNG" : "JPEG"
|
||||
));
|
||||
}
|
||||
|
||||
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
|
||||
|
||||
editorController.current?.commands.insertImage({
|
||||
hash: hash,
|
||||
type: image.type,
|
||||
title: fileName,
|
||||
src: b64,
|
||||
dataurl: b64,
|
||||
size: image.fileSize,
|
||||
filename: fileName
|
||||
});
|
||||
@@ -289,7 +298,6 @@ async function attachFile(uri, hash, type, filename, options) {
|
||||
encryptionInfo,
|
||||
editorController.current?.note?.id
|
||||
);
|
||||
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
} from "react-native";
|
||||
import { WebViewMessageEvent } from "react-native-webview";
|
||||
import { db } from "../../../common/database";
|
||||
import ManageTagsSheet from "../../../components/sheets/manage-tags";
|
||||
import { RelationsList } from "../../../components/sheets/relations-list";
|
||||
import ReminderSheet from "../../../components/sheets/reminder";
|
||||
import useKeyboard from "../../../hooks/use-keyboard";
|
||||
@@ -51,8 +52,7 @@ import {
|
||||
eOpenFullscreenEditor,
|
||||
eOpenLoginDialog,
|
||||
eOpenPremiumDialog,
|
||||
eOpenPublishNoteDialog,
|
||||
eOpenTagsDialog
|
||||
eOpenPublishNoteDialog
|
||||
} from "../../../utils/events";
|
||||
import { openLinkInBrowser } from "../../../utils/functions";
|
||||
import { tabBarRef } from "../../../utils/global-refs";
|
||||
@@ -326,7 +326,7 @@ export const useEditorEvents = (
|
||||
});
|
||||
return;
|
||||
}
|
||||
eSendEvent(eOpenTagsDialog, editor.note.current);
|
||||
ManageTagsSheet.present(editor.note.current);
|
||||
break;
|
||||
case EventTypes.tag:
|
||||
if (editorMessage.value) {
|
||||
@@ -380,6 +380,10 @@ export const useEditorEvents = (
|
||||
case EventTypes.link:
|
||||
openLinkInBrowser(editorMessage.value as string);
|
||||
break;
|
||||
|
||||
case EventTypes.previewAttachment:
|
||||
eSendEvent("ImagePreview", editorMessage.value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -17,6 +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 { EVENTS } from "@notesnook/core/common";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import WebView from "react-native-webview";
|
||||
import { db } from "../../../common/database";
|
||||
@@ -29,6 +30,7 @@ import {
|
||||
openVault
|
||||
} from "../../../services/event-manager";
|
||||
import Navigation from "../../../services/navigation";
|
||||
import SettingsService from "../../../services/settings";
|
||||
import { TipManager } from "../../../services/tip-manager";
|
||||
import { useEditorStore } from "../../../stores/use-editor-store";
|
||||
import { useNoteStore } from "../../../stores/use-notes-store";
|
||||
@@ -50,8 +52,6 @@ import {
|
||||
makeSessionId,
|
||||
post
|
||||
} from "./utils";
|
||||
import { EVENTS } from "@notesnook/core/common";
|
||||
import SettingsService from "../../../services/settings";
|
||||
|
||||
export const useEditor = (
|
||||
editorId = "",
|
||||
@@ -76,6 +76,7 @@ export const useEditor = (
|
||||
const saveCount = useRef(0);
|
||||
const lastContentChangeTime = useRef<number>(0);
|
||||
const lock = useRef(false);
|
||||
const loadedImages = useRef<{ [name: string]: boolean }>({});
|
||||
|
||||
const postMessage = useCallback(
|
||||
async <T>(type: string, data: T) =>
|
||||
@@ -154,7 +155,9 @@ export const useEditor = (
|
||||
async (resetState = true) => {
|
||||
currentNote.current?.id && db.fs.cancel(currentNote.current.id);
|
||||
currentNote.current = null;
|
||||
loadedImages.current = {};
|
||||
currentContent.current = null;
|
||||
clearTimeout(timers.current["loading-images"]);
|
||||
sessionHistoryId.current = undefined;
|
||||
saveCount.current = 0;
|
||||
useEditorStore.getState().setReadonly(false);
|
||||
@@ -290,6 +293,58 @@ export const useEditor = (
|
||||
}
|
||||
}, []);
|
||||
|
||||
const getMediaToLoad = (previousContent?: string) => {
|
||||
if (!currentNote.current?.id) return [];
|
||||
|
||||
const previousAttachments =
|
||||
previousContent?.matchAll(/data-hash="(.+?)"/gm) || [];
|
||||
const attachments =
|
||||
currentContent.current?.data?.matchAll(/data-hash="(.+?)"/gm) || [];
|
||||
|
||||
const media: string[] = [];
|
||||
|
||||
const oldMatches = Array.from(previousAttachments).map((match) => match[1]);
|
||||
const matches = Array.from(attachments).map((match) => match[1]);
|
||||
|
||||
for (let i = 0; i < matches.length; i++) {
|
||||
const currentHash = matches[i];
|
||||
const oldHash = oldMatches[i];
|
||||
if (currentHash !== oldHash) {
|
||||
media.push(currentHash);
|
||||
loadedImages.current[currentHash] = false;
|
||||
}
|
||||
}
|
||||
return media;
|
||||
};
|
||||
|
||||
const markImageLoaded = (hash: string) => {
|
||||
const attachment = loadedImages.current[hash];
|
||||
if (typeof attachment === "boolean") {
|
||||
loadedImages.current[hash] = true;
|
||||
}
|
||||
};
|
||||
|
||||
const loadImages = useCallback((previousContent?: string) => {
|
||||
if (!currentNote.current?.id) return;
|
||||
const timerId = "loading-images";
|
||||
clearTimeout(timers.current[timerId]);
|
||||
timers.current[timerId] = setTimeout(() => {
|
||||
if (!currentNote.current?.id) return;
|
||||
if (currentNote.current?.content?.isPreview) {
|
||||
db.content?.downloadMedia(
|
||||
currentNote.current?.id,
|
||||
currentNote.current.content,
|
||||
true
|
||||
);
|
||||
} else {
|
||||
const media = getMediaToLoad(previousContent);
|
||||
if (media.length > 0) {
|
||||
db.attachments?.downloadMedia(currentNote.current?.id, media);
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
}, []);
|
||||
|
||||
const loadNote = useCallback(
|
||||
async (
|
||||
item: Omit<NoteType, "type"> & {
|
||||
@@ -333,25 +388,17 @@ export const useEditor = (
|
||||
loadImages();
|
||||
}
|
||||
},
|
||||
[commands, isDefaultEditor, loadContent, overlay, postMessage, reset]
|
||||
[
|
||||
commands,
|
||||
isDefaultEditor,
|
||||
loadContent,
|
||||
loadImages,
|
||||
overlay,
|
||||
postMessage,
|
||||
reset
|
||||
]
|
||||
);
|
||||
|
||||
const loadImages = () => {
|
||||
if (!currentNote.current?.id) return;
|
||||
setTimeout(() => {
|
||||
if (!currentNote.current?.id) return;
|
||||
if (currentNote.current?.content?.isPreview) {
|
||||
db.content?.downloadMedia(
|
||||
currentNote.current?.id,
|
||||
currentNote.current.content,
|
||||
true
|
||||
);
|
||||
} else {
|
||||
db.attachments?.downloadMedia(currentNote.current?.id);
|
||||
}
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const lockNoteWithVault = useCallback((note: NoteType) => {
|
||||
eSendEvent(eClearEditor);
|
||||
openVault({
|
||||
@@ -378,6 +425,9 @@ export const useEditor = (
|
||||
return;
|
||||
|
||||
lock.current = true;
|
||||
|
||||
const previousContent = currentContent.current?.data;
|
||||
|
||||
if (data.type === "tiptap") {
|
||||
if (!currentNote.current.locked && isContentEncrypted) {
|
||||
lockNoteWithVault(note);
|
||||
@@ -408,16 +458,20 @@ export const useEditor = (
|
||||
}
|
||||
await commands.setStatus(timeConverter(note.dateEdited), "Saved");
|
||||
}
|
||||
db.eventManager.subscribe(
|
||||
EVENTS.syncCompleted,
|
||||
async () => {
|
||||
loadImages();
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
lock.current = false;
|
||||
if (data.type === "tiptap") {
|
||||
loadImages(previousContent);
|
||||
db.eventManager.subscribe(
|
||||
EVENTS.syncCompleted,
|
||||
() => {
|
||||
loadImages(previousContent);
|
||||
},
|
||||
true
|
||||
);
|
||||
}
|
||||
},
|
||||
[commands, postMessage, lockNoteWithVault]
|
||||
[loadImages, lockNoteWithVault, postMessage, commands]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -560,6 +614,7 @@ export const useEditor = (
|
||||
onReady,
|
||||
saveContent,
|
||||
onContentChanged,
|
||||
editorId: editorId
|
||||
editorId: editorId,
|
||||
markImageLoaded
|
||||
};
|
||||
};
|
||||
|
||||
@@ -65,7 +65,6 @@ export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
|
||||
onBlur: () => false,
|
||||
delay: SettingsService.get().homepage === route.name ? 1 : -1
|
||||
});
|
||||
|
||||
return (
|
||||
<DelayLayout wait={loading} delay={500}>
|
||||
<List
|
||||
|
||||
@@ -16,12 +16,9 @@ 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 { groupArray } from "@notesnook/core/utils/grouping";
|
||||
import qclone from "qclone";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { db } from "../../common/database";
|
||||
import { FloatingButton } from "../../components/container/floating-button";
|
||||
import DelayLayout from "../../components/delay-layout";
|
||||
import List from "../../components/list";
|
||||
import { NotebookHeader } from "../../components/list-items/headers/notebook-header";
|
||||
@@ -36,28 +33,29 @@ import SearchService from "../../services/search";
|
||||
import useNavigationStore, {
|
||||
NotebookScreenParams
|
||||
} from "../../stores/use-navigation-store";
|
||||
import {
|
||||
eOnNewTopicAdded,
|
||||
eOpenAddNotebookDialog,
|
||||
eOpenAddTopicDialog
|
||||
} from "../../utils/events";
|
||||
import { eOnNewTopicAdded, eOpenAddNotebookDialog } from "../../utils/events";
|
||||
import { NotebookType } from "../../utils/types";
|
||||
import { openEditor, setOnFirstSave } from "../notes/common";
|
||||
const Notebook = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
const [topics, setTopics] = useState(
|
||||
const [notes, setNotes] = useState(
|
||||
groupArray(
|
||||
qclone(route?.params.item?.topics) || [],
|
||||
db.settings?.getGroupOptions("topics")
|
||||
db.relations?.from(route.params.item, "note") || [],
|
||||
db.settings?.getGroupOptions("notes")
|
||||
)
|
||||
);
|
||||
const params = useRef<NotebookScreenParams>(route?.params);
|
||||
|
||||
useNavigationFocus(navigation, {
|
||||
onFocus: () => {
|
||||
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
|
||||
syncWithNavigation();
|
||||
useNavigationStore.getState().setButtonAction(onPressFloatingButton);
|
||||
useNavigationStore.getState().setButtonAction(openEditor);
|
||||
return false;
|
||||
},
|
||||
onBlur: () => false
|
||||
onBlur: () => {
|
||||
setOnFirstSave(null);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
const syncWithNavigation = React.useCallback(() => {
|
||||
@@ -70,6 +68,10 @@ const Notebook = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
},
|
||||
params.current?.canGoBack
|
||||
);
|
||||
setOnFirstSave({
|
||||
type: "notebook",
|
||||
id: params.current.item.id
|
||||
});
|
||||
SearchService.prepareSearch = prepareSearch;
|
||||
}, [route.name]);
|
||||
|
||||
@@ -82,11 +84,9 @@ const Notebook = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
?.data as NotebookType;
|
||||
if (notebook) {
|
||||
params.current.item = notebook;
|
||||
setTopics(
|
||||
groupArray(
|
||||
qclone(notebook.topics),
|
||||
db.settings?.getGroupOptions("topics")
|
||||
)
|
||||
const notes = db.relations?.from(notebook, "note");
|
||||
setNotes(
|
||||
groupArray(notes || [], db.settings?.getGroupOptions("notes"))
|
||||
);
|
||||
syncWithNavigation();
|
||||
}
|
||||
@@ -102,60 +102,61 @@ const Notebook = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
return () => {
|
||||
eUnSubscribeEvent(eOnNewTopicAdded, onRequestUpdate);
|
||||
};
|
||||
}, [onRequestUpdate, topics]);
|
||||
}, [onRequestUpdate]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
setOnFirstSave(null);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const prepareSearch = () => {
|
||||
SearchService.update({
|
||||
placeholder: `Search in "${params.current.title}"`,
|
||||
type: "topics",
|
||||
type: "notes",
|
||||
title: params.current.title,
|
||||
get: () => {
|
||||
const notebook = db.notebooks?.notebook(params?.current?.item?.id)
|
||||
?.data as NotebookType;
|
||||
return notebook?.topics;
|
||||
return db.relations?.from(notebook, "note");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onPressFloatingButton = () => {
|
||||
const n = params.current.item;
|
||||
eSendEvent(eOpenAddTopicDialog, { notebookId: n.id });
|
||||
};
|
||||
|
||||
const PLACEHOLDER_DATA = {
|
||||
heading: params.current.item?.title,
|
||||
paragraph: "You have not added any topics yet.",
|
||||
button: "Add first topic",
|
||||
action: onPressFloatingButton,
|
||||
loading: "Loading notebook topics"
|
||||
paragraph: "You have not added any notes yet.",
|
||||
button: "Add your first note",
|
||||
action: openEditor,
|
||||
loading: "Loading notebook notes"
|
||||
};
|
||||
|
||||
return (
|
||||
<DelayLayout>
|
||||
<List
|
||||
listData={topics}
|
||||
type="topics"
|
||||
refreshCallback={() => {
|
||||
onRequestUpdate();
|
||||
}}
|
||||
screen="Notebook"
|
||||
headerProps={{
|
||||
heading: params.current.title
|
||||
}}
|
||||
loading={false}
|
||||
ListHeader={
|
||||
<NotebookHeader
|
||||
onEditNotebook={() => {
|
||||
eSendEvent(eOpenAddNotebookDialog, params.current.item);
|
||||
}}
|
||||
notebook={params.current.item}
|
||||
/>
|
||||
}
|
||||
placeholderData={PLACEHOLDER_DATA}
|
||||
/>
|
||||
|
||||
<FloatingButton title="Add new topic" onPress={onPressFloatingButton} />
|
||||
</DelayLayout>
|
||||
<>
|
||||
<DelayLayout>
|
||||
<List
|
||||
listData={notes}
|
||||
type="notes"
|
||||
refreshCallback={() => {
|
||||
onRequestUpdate();
|
||||
}}
|
||||
screen="Notebook"
|
||||
headerProps={{
|
||||
heading: params.current.title
|
||||
}}
|
||||
loading={false}
|
||||
ListHeader={
|
||||
<NotebookHeader
|
||||
onEditNotebook={() => {
|
||||
eSendEvent(eOpenAddNotebookDialog, params.current.item);
|
||||
}}
|
||||
notebook={params.current.item}
|
||||
/>
|
||||
}
|
||||
placeholderData={PLACEHOLDER_DATA}
|
||||
/>
|
||||
</DelayLayout>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -18,11 +18,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { Config } from "react-native-config";
|
||||
import { db } from "../../common/database";
|
||||
import { FloatingButton } from "../../components/container/floating-button";
|
||||
import DelayLayout from "../../components/delay-layout";
|
||||
import { AddNotebookEvent } from "../../components/dialog-provider/recievers";
|
||||
import List from "../../components/list";
|
||||
import { AddNotebookSheet } from "../../components/sheets/add-notebook";
|
||||
import { Walkthrough } from "../../components/walkthroughs";
|
||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||
@@ -30,10 +31,9 @@ import SearchService from "../../services/search";
|
||||
import SettingsService from "../../services/settings";
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
import { useNotebookStore } from "../../stores/use-notebook-store";
|
||||
import { Config } from "react-native-config";
|
||||
|
||||
const onPressFloatingButton = () => {
|
||||
AddNotebookEvent();
|
||||
AddNotebookSheet.present();
|
||||
};
|
||||
|
||||
const prepareSearch = () => {
|
||||
@@ -83,7 +83,7 @@ export const Notebooks = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<DelayLayout>
|
||||
<DelayLayout delay={1}>
|
||||
<List
|
||||
listData={notebooks}
|
||||
type="notebooks"
|
||||
|
||||
@@ -24,20 +24,21 @@ import Navigation from "../../services/navigation";
|
||||
import { useMenuStore } from "../../stores/use-menu-store";
|
||||
import { NotesScreenParams } from "../../stores/use-navigation-store";
|
||||
import { useTagStore } from "../../stores/use-tag-store";
|
||||
import { eOnLoadNote } from "../../utils/events";
|
||||
import { eOnLoadNote, eOnTopicSheetUpdate } from "../../utils/events";
|
||||
import { openLinkInBrowser } from "../../utils/functions";
|
||||
import { tabBarRef } from "../../utils/global-refs";
|
||||
import { TopicType } from "../../utils/types";
|
||||
import { editorController, editorState } from "../editor/tiptap/utils";
|
||||
|
||||
export function toCamelCase(title: string) {
|
||||
if (!title) return "";
|
||||
return title.slice(0, 1).toUpperCase() + title.slice(1);
|
||||
}
|
||||
|
||||
export function getAlias(params: Partial<NotesScreenParams>) {
|
||||
if (!params) return "";
|
||||
const { item } = params;
|
||||
return (item as TopicType)?.alias || item?.title;
|
||||
return (item as TopicType)?.alias || item?.title || "";
|
||||
}
|
||||
|
||||
export function openMonographsWebpage() {
|
||||
@@ -80,12 +81,31 @@ export const setOnFirstSave = (
|
||||
editorState().onNoteCreated = null;
|
||||
return;
|
||||
}
|
||||
editorState().onNoteCreated = (id) => onNoteCreated(id, data);
|
||||
setTimeout(() => {
|
||||
editorState().onNoteCreated = (id) => onNoteCreated(id, data);
|
||||
}, 0);
|
||||
};
|
||||
|
||||
async function onNoteCreated(id: string, params: FirstSaveData) {
|
||||
if (!params) return;
|
||||
switch (params.type) {
|
||||
case "notebook": {
|
||||
await db.relations?.add(
|
||||
{ type: "notebook", id: params.id },
|
||||
{ type: "note", id: id }
|
||||
);
|
||||
Navigation.queueRoutesForUpdate(
|
||||
"TaggedNotes",
|
||||
"ColoredNotes",
|
||||
"TopicNotes",
|
||||
"Favorites",
|
||||
"Notes",
|
||||
"Notebook",
|
||||
"Notebooks"
|
||||
);
|
||||
editorState().onNoteCreated = null;
|
||||
break;
|
||||
}
|
||||
case "topic": {
|
||||
if (!params.notebook) break;
|
||||
await db.notes?.addToNotebook(
|
||||
@@ -105,6 +125,7 @@ async function onNoteCreated(id: string, params: FirstSaveData) {
|
||||
"Notebook",
|
||||
"Notebooks"
|
||||
);
|
||||
eSendEvent(eOnTopicSheetUpdate);
|
||||
break;
|
||||
}
|
||||
case "tag": {
|
||||
|
||||
@@ -42,6 +42,13 @@ import {
|
||||
setOnFirstSave,
|
||||
toCamelCase
|
||||
} from "./common";
|
||||
import { View } from "react-native";
|
||||
import { db } from "../../common/database";
|
||||
import Paragraph from "../../components/ui/typography/paragraph";
|
||||
import { IconButton } from "../../components/ui/icon-button";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import Notebook from "../notebook/index";
|
||||
export const WARNING_DATA = {
|
||||
title: "Some notes in this topic are not synced"
|
||||
};
|
||||
@@ -92,13 +99,18 @@ const NotesPage = ({
|
||||
}: RouteProps<
|
||||
"NotesPage" | "TaggedNotes" | "Monographs" | "ColoredNotes" | "TopicNotes"
|
||||
>) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const params = useRef<NotesScreenParams>(route?.params);
|
||||
const [notes, setNotes] = useState<NoteType[]>(get(route.params, true));
|
||||
const loading = useNoteStore((state) => state.loading);
|
||||
const [loadingNotes, setLoadingNotes] = useState(false);
|
||||
const alias = getAlias(params.current);
|
||||
const isMonograph = route.name === "Monographs";
|
||||
|
||||
const notebook =
|
||||
route.name === "TopicNotes" && (params.current.item as TopicType).notebookId
|
||||
? db.notebooks?.notebook((params.current.item as TopicType).notebookId)
|
||||
?.data
|
||||
: null;
|
||||
const isFocused = useNavigationFocus(navigation, {
|
||||
onFocus: (prev) => {
|
||||
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
|
||||
@@ -176,6 +188,7 @@ const NotesPage = ({
|
||||
) {
|
||||
return Navigation.goBack();
|
||||
}
|
||||
if (notes.length === 0) setLoadingNotes(false);
|
||||
setNotes(notes);
|
||||
syncWithNavigation();
|
||||
} catch (e) {
|
||||
@@ -187,7 +200,7 @@ const NotesPage = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (loadingNotes) {
|
||||
setTimeout(() => setLoadingNotes(false), 300);
|
||||
setTimeout(() => setLoadingNotes(false), 50);
|
||||
}
|
||||
}, [loadingNotes, notes]);
|
||||
|
||||
@@ -208,6 +221,45 @@ const NotesPage = ({
|
||||
}
|
||||
wait={loading || loadingNotes}
|
||||
>
|
||||
{route.name === "TopicNotes" ? (
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
paddingHorizontal: 12,
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
// borderBottomWidth: 1,
|
||||
// borderBottomColor: colors.nav
|
||||
}}
|
||||
>
|
||||
<Paragraph
|
||||
onPress={() => {
|
||||
Navigation.navigate(
|
||||
{
|
||||
name: "Notebooks"
|
||||
},
|
||||
{}
|
||||
);
|
||||
}}
|
||||
size={SIZE.xs}
|
||||
>
|
||||
Notebooks
|
||||
</Paragraph>
|
||||
<IconButton
|
||||
name="chevron-right"
|
||||
size={14}
|
||||
customStyle={{ width: 25, height: 25 }}
|
||||
/>
|
||||
<Paragraph
|
||||
onPress={() => {
|
||||
Notebook.navigate(notebook, true);
|
||||
}}
|
||||
size={SIZE.xs}
|
||||
>
|
||||
{notebook.title}
|
||||
</Paragraph>
|
||||
</View>
|
||||
) : null}
|
||||
<List
|
||||
listData={notes}
|
||||
type="notes"
|
||||
|
||||
@@ -58,16 +58,18 @@ export const TopicNotes = ({
|
||||
route
|
||||
}: NavigationProps<"TopicNotes">) => {
|
||||
return (
|
||||
<NotesPage
|
||||
navigation={navigation}
|
||||
route={route}
|
||||
get={TopicNotes.get}
|
||||
placeholderData={PLACEHOLDER_DATA}
|
||||
onPressFloatingButton={openEditor}
|
||||
rightButtons={headerRightButtons}
|
||||
canGoBack={route.params.canGoBack}
|
||||
focusControl={true}
|
||||
/>
|
||||
<>
|
||||
<NotesPage
|
||||
navigation={navigation}
|
||||
route={route}
|
||||
get={TopicNotes.get}
|
||||
placeholderData={PLACEHOLDER_DATA}
|
||||
onPressFloatingButton={openEditor}
|
||||
rightButtons={headerRightButtons}
|
||||
canGoBack={route.params.canGoBack}
|
||||
focusControl={true}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -22,8 +22,10 @@ import { AccentColorPicker, HomagePageSelector } from "./appearance";
|
||||
import { AutomaticBackupsSelector } from "./backup-restore";
|
||||
import DebugLogs from "./debug";
|
||||
import { ConfigureToolbar } from "./editor/configure-toolbar";
|
||||
import { Licenses } from "./licenses";
|
||||
import SoundPicker from "./sound-picker";
|
||||
import { Subscription } from "./subscription";
|
||||
import { TrashIntervalSelector } from "./trash-interval-selector";
|
||||
export const components: { [name: string]: ReactElement } = {
|
||||
colorpicker: <AccentColorPicker />,
|
||||
homeselector: <HomagePageSelector />,
|
||||
@@ -31,5 +33,7 @@ export const components: { [name: string]: ReactElement } = {
|
||||
subscription: <Subscription />,
|
||||
configuretoolbar: <ConfigureToolbar />,
|
||||
"debug-logs": <DebugLogs />,
|
||||
"sound-picker": <SoundPicker />
|
||||
"sound-picker": <SoundPicker />,
|
||||
licenses: <Licenses />,
|
||||
"trash-interval-selector": <TrashIntervalSelector />
|
||||
};
|
||||
|
||||
466
apps/mobile/app/screens/settings/license-data.js
Normal file
466
apps/mobile/app/screens/settings/license-data.js
Normal file
@@ -0,0 +1,466 @@
|
||||
/* eslint-disable header/header */
|
||||
export const LICENSES = [
|
||||
{
|
||||
name: "@callstack/repack",
|
||||
licenseType: "MIT",
|
||||
author: "zamotany <zamotany.oss@gmail.com>",
|
||||
link: "https://github.com/callstack/repack"
|
||||
},
|
||||
{
|
||||
name: "@react-native-clipboard/clipboard",
|
||||
licenseType: "MIT",
|
||||
author: "M.Haris Baig <harisbaig100@gmail.com>",
|
||||
link: "https://github.com/react-native-clipboard/clipboard"
|
||||
},
|
||||
{
|
||||
name: "@react-native-community/checkbox",
|
||||
licenseType: "MIT",
|
||||
author: "Nicholas Lee <nicholaslee119@gmail.com>",
|
||||
link: "https://github.com/react-native-community/react-native-checkbox"
|
||||
},
|
||||
{
|
||||
name: "@react-native-community/netinfo",
|
||||
licenseType: "MIT",
|
||||
author: "Matt Oakes <hello@mattoakes.net>",
|
||||
link: "https://github.com/react-native-netinfo/react-native-netinfo"
|
||||
},
|
||||
{
|
||||
name: "@react-native-community/toolbar-android",
|
||||
licenseType: "MIT",
|
||||
author: "Connectdotz <vsun@connectdotz.com>",
|
||||
link: "https://github.com/react-native-community/toolbar-android"
|
||||
},
|
||||
{
|
||||
name: "@react-native-masked-view/masked-view",
|
||||
licenseType: "MIT",
|
||||
author: "Mike Nedosekin <crespo8800@gmail.com>",
|
||||
link: "https://github.com/react-native-masked-view/masked-view"
|
||||
},
|
||||
{
|
||||
name: "@react-navigation/elements",
|
||||
licenseType: "MIT",
|
||||
author: "n/a",
|
||||
link: "https://github.com/react-navigation/react-navigation"
|
||||
},
|
||||
{
|
||||
name: "@react-navigation/native",
|
||||
licenseType: "MIT",
|
||||
author: "n/a",
|
||||
link: "https://github.com/react-navigation/react-navigation"
|
||||
},
|
||||
{
|
||||
name: "@react-navigation/native-stack",
|
||||
licenseType: "MIT",
|
||||
author: "n/a",
|
||||
link: "https://github.com/react-navigation/react-navigation"
|
||||
},
|
||||
{
|
||||
name: "@sayem314/react-native-keep-awake",
|
||||
licenseType: "MIT",
|
||||
author: "Sayem Chowdhury",
|
||||
link: "https://github.com/sayem314/react-native-keep-awake"
|
||||
},
|
||||
{
|
||||
name: "deprecated-react-native-prop-types",
|
||||
licenseType: "MIT",
|
||||
author: "n/a",
|
||||
link: "git@github.com:facebookarchive/react-native-deprecated-modules"
|
||||
},
|
||||
{
|
||||
name: "react",
|
||||
licenseType: "MIT",
|
||||
author: "n/a",
|
||||
link: "https://github.com/facebook/react"
|
||||
},
|
||||
{
|
||||
name: "react-native",
|
||||
licenseType: "MIT",
|
||||
author: "n/a",
|
||||
link: "https://github.com/facebook/react-native"
|
||||
},
|
||||
{
|
||||
name: "react-native-background-actions",
|
||||
licenseType: "MIT",
|
||||
author: "Rapsssito contact@rodrigomartin.dev",
|
||||
link: "https://github.com/Rapsssito/react-native-background-actions"
|
||||
},
|
||||
{
|
||||
name: "react-native-begin-background-task",
|
||||
licenseType: "MIT",
|
||||
author: "Ivan Zotov <github@ivanzotov.com>",
|
||||
link: "n/a"
|
||||
},
|
||||
{
|
||||
name: "react-native-bootsplash",
|
||||
licenseType: "MIT",
|
||||
author: "Mathieu Acthernoene <zoontek@gmail.com>",
|
||||
link: "https://github.com/zoontek/react-native-bootsplash"
|
||||
},
|
||||
{
|
||||
name: "react-native-config",
|
||||
licenseType: "MIT",
|
||||
author: "Pedro Belo",
|
||||
link: "https://github.com/luggit/react-native-config"
|
||||
},
|
||||
{
|
||||
name: "react-native-device-info",
|
||||
licenseType: "MIT",
|
||||
author:
|
||||
"Rebecca Hughes <rebecca@learnium.net> (https://github.com/rebeccahughes)",
|
||||
link: "https://github.com/react-native-device-info/react-native-device-info"
|
||||
},
|
||||
{
|
||||
name: "react-native-document-picker",
|
||||
licenseType: "MIT",
|
||||
author:
|
||||
"Elyx0 <elyx00@gmail.com> (https://github.com/rnmods), Vojtech Novak (hello@react-native-training.eu)",
|
||||
link: "https://github.com/rnmods/react-native-document-picker"
|
||||
},
|
||||
{
|
||||
name: "react-native-eventsource",
|
||||
licenseType: "MIT",
|
||||
author: "João Ribeiro jonnybgod@gmail.com",
|
||||
link: "n/a"
|
||||
},
|
||||
{
|
||||
name: "react-native-exit-app",
|
||||
licenseType: "MIT",
|
||||
author: "Wumke",
|
||||
link: "n/a"
|
||||
},
|
||||
{
|
||||
name: "react-native-file-viewer",
|
||||
licenseType: "MIT",
|
||||
author: "Vincenzo Scamporlino",
|
||||
link: "git://github.com/vinzscam/react-native-file-viewer"
|
||||
},
|
||||
{
|
||||
name: "react-native-fingerprint-scanner",
|
||||
licenseType: "MIT",
|
||||
author: "Hieu Van (https://github.com/hieuvp)",
|
||||
link: "n/a"
|
||||
},
|
||||
{
|
||||
name: "react-native-gesture-handler",
|
||||
licenseType: "MIT",
|
||||
author: "Krzysztof Magiera krzys.magiera@gmail.com",
|
||||
link: "https://github.com/software-mansion/react-native-gesture-handler"
|
||||
},
|
||||
{
|
||||
name: "react-native-get-random-values",
|
||||
licenseType: "MIT",
|
||||
author: "n/a",
|
||||
link: "https://github.com/LinusU/react-native-get-random-values"
|
||||
},
|
||||
{
|
||||
name: "react-native-html-to-pdf-lite",
|
||||
licenseType: "MIT",
|
||||
author: "Oni Benjamin <onibenjo@gmail.com>",
|
||||
link: "https://github.com/Onibenjo/react-native-html-to-pdf"
|
||||
},
|
||||
{
|
||||
name: "react-native-iap",
|
||||
licenseType: "MIT",
|
||||
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",
|
||||
author: "Joel Arvidsson joel@oblador.se",
|
||||
link: "git://github.com/oblador/react-native-keychain"
|
||||
},
|
||||
{
|
||||
name: "react-native-mmkv-storage",
|
||||
licenseType: "MIT",
|
||||
author: "Ammar Ahmed for @ammarahm-ed",
|
||||
link: "https://github.com/ammarahm-ed/react-native-mmkv-storage"
|
||||
},
|
||||
{
|
||||
name: "react-native-orientation",
|
||||
licenseType: "ISC",
|
||||
author: "Yamill Vallecillo <yamill@gmail.com> (github.com/yamill)",
|
||||
link: "n/a"
|
||||
},
|
||||
{
|
||||
name: "react-native-privacy-snapshot",
|
||||
licenseType: "MIT",
|
||||
author: "Roger Chapman",
|
||||
link: "n/a"
|
||||
},
|
||||
{
|
||||
name: "react-native-reanimated",
|
||||
licenseType: "MIT",
|
||||
author: "Krzysztof Magiera krzys.magiera@gmail.com",
|
||||
link: "https://github.com/software-mansion/react-native-reanimated"
|
||||
},
|
||||
{
|
||||
name: "react-native-safe-area-context",
|
||||
licenseType: "MIT",
|
||||
author: "Janic Duplessis <janicduplessis@gmail.com>",
|
||||
link: "https://github.com/th3rdwave/react-native-safe-area-context"
|
||||
},
|
||||
{
|
||||
name: "react-native-scoped-storage",
|
||||
licenseType: "MIT",
|
||||
author: "Ammar Ahmed @ammarahm-ed",
|
||||
link: "https://github.com/ammarahm-ed/react-native-scoped-storage"
|
||||
},
|
||||
{
|
||||
name: "react-native-screens",
|
||||
licenseType: "MIT",
|
||||
author: "Krzysztof Magiera krzys.magiera@gmail.com",
|
||||
link: "https://github.com/software-mansion/react-native-screens"
|
||||
},
|
||||
{
|
||||
name: "react-native-securerandom",
|
||||
licenseType: "MIT",
|
||||
author: "Rob Hogan npm@rhogan.net https://github.com/robhogan",
|
||||
link: "https://github.com/robhogan/react-native-securerandom"
|
||||
},
|
||||
{
|
||||
name: "react-native-share",
|
||||
licenseType: "MIT",
|
||||
author: "Esteban Fuentealba efuentealba@json.cl",
|
||||
link: "https://github.com/react-native-community/react-native-share"
|
||||
},
|
||||
{
|
||||
name: "@ammarahmed/react-native-sodium",
|
||||
licenseType: "n/a",
|
||||
author: "n/a",
|
||||
link: "https://github.com/lyubo/react-native-sodium"
|
||||
},
|
||||
{
|
||||
name: "react-native-svg",
|
||||
licenseType: "MIT",
|
||||
author: "n/a",
|
||||
link: "https://github.com/react-native-community/react-native-svg"
|
||||
},
|
||||
{
|
||||
name: "react-native-tooltips",
|
||||
licenseType: "Apache License",
|
||||
author: "Pranav Raj Singh Chauhan",
|
||||
link: "https://github.com/prscX/react-native-tooltips"
|
||||
},
|
||||
{
|
||||
name: "react-native-vector-icons",
|
||||
licenseType: "MIT",
|
||||
author: "Joel Arvidsson joel@oblador.se",
|
||||
link: "git://github.com/oblador/react-native-vector-icons"
|
||||
},
|
||||
{
|
||||
name: "react-native-webview",
|
||||
licenseType: "MIT",
|
||||
author: "Jamon Holmgren <jamon@infinite.red>",
|
||||
link: "https://github.com/react-native-webview/react-native-webview"
|
||||
},
|
||||
{
|
||||
name: "rn-extensions-share",
|
||||
licenseType: "MIT",
|
||||
author:
|
||||
"Djorkaeff Alexandre djorkaeffalexandre@gmail.com http://github.com/djorkaeffalexandre",
|
||||
link: "https://github.com/RocketChat/rn-extensions-share"
|
||||
},
|
||||
{
|
||||
name: "rn-fetch-blob",
|
||||
licenseType: "MIT",
|
||||
author: "Joltup",
|
||||
link: "https://github.com/joltup/rn-fetch-blob"
|
||||
},
|
||||
{
|
||||
name: "react-native-gzip",
|
||||
licenseType: "MIT",
|
||||
author:
|
||||
"ammarahm-ed <ammarahmed6506@gmail.com> (https://github.com/ammarahm-ed)",
|
||||
link: "https://github.com/ammarahm-ed/react-native-gzip"
|
||||
},
|
||||
{
|
||||
name: "@shopify/flash-list",
|
||||
licenseType: "MIT",
|
||||
author: "shopify",
|
||||
link: "https://github.com/Shopify/flash-list"
|
||||
},
|
||||
{
|
||||
name: "@notifee/react-native",
|
||||
licenseType: "Apache-2.0",
|
||||
author: "Invertase <oss@invertase.io> (http://invertase.io)",
|
||||
link: "https://github.com/invertase/notifee"
|
||||
},
|
||||
{
|
||||
name: "react-native-modal-datetime-picker",
|
||||
licenseType: "MIT",
|
||||
author: "Mazzarolo Matteo",
|
||||
link: "https://github.com/mmazzarolo/react-native-modal-datetime-picker"
|
||||
},
|
||||
{
|
||||
name: "@react-native-community/datetimepicker",
|
||||
licenseType: "MIT",
|
||||
author:
|
||||
"Martijn Swaagman <mswaagman@godaddy.com> (https://github.com/swaagie)",
|
||||
link: "https://github.com/react-native-community/datetimepicker"
|
||||
},
|
||||
{
|
||||
name: "react-native-date-picker",
|
||||
licenseType: "MIT",
|
||||
author: "henninghall",
|
||||
link: "ssh://git@github.com/henninghall/react-native-date-picker"
|
||||
},
|
||||
{
|
||||
name: "react-native-notification-sounds",
|
||||
licenseType: "MIT",
|
||||
author: "Asad Iqbal asad.dsoft@gmail.com",
|
||||
link: "https://github.com/saadqbal/react-native-notification-sounds"
|
||||
},
|
||||
{
|
||||
name: "@flyerhq/react-native-link-preview",
|
||||
licenseType: "MIT",
|
||||
author: "Oleksandr Demchenko <alexdemchenko@yahoo.com>",
|
||||
link: "https://github.com/flyerhq/react-native-link-preview#readme"
|
||||
},
|
||||
{
|
||||
name: "@mdi/js",
|
||||
licenseType: "Apache-2.0",
|
||||
author: "Austin Andrews",
|
||||
link: "https://github.com/Templarian/MaterialDesign-JS"
|
||||
},
|
||||
{
|
||||
name: "absolutify",
|
||||
licenseType: "MIT",
|
||||
author:
|
||||
"Beau Sorensen <mail@beausorensen.com> (http://github.com/sorensen)",
|
||||
link: "git://github.com/sorensen/absolutify"
|
||||
},
|
||||
{
|
||||
name: "buffer",
|
||||
licenseType: "MIT",
|
||||
author: "Feross Aboukhadijeh feross@feross.org https://feross.org",
|
||||
link: "git://github.com/feross/buffer"
|
||||
},
|
||||
{
|
||||
name: "dayjs",
|
||||
licenseType: "MIT",
|
||||
author: "iamkun",
|
||||
link: "https://github.com/iamkun/dayjs"
|
||||
},
|
||||
{
|
||||
name: "entities",
|
||||
licenseType: "BSD-2-Clause",
|
||||
author: "Felix Boehm <me@feedic.com>",
|
||||
link: "git://github.com/fb55/entities"
|
||||
},
|
||||
{
|
||||
name: "html-to-text",
|
||||
licenseType: "MIT",
|
||||
author: "Malte Legenhausen legenhausen@werk85.de",
|
||||
link: "git://github.com/html-to-text/node-html-to-text"
|
||||
},
|
||||
{
|
||||
name: "phone",
|
||||
licenseType: "MIT",
|
||||
author: "AfterShip",
|
||||
link: "https://github.com/aftership/phone"
|
||||
},
|
||||
{
|
||||
name: "qclone",
|
||||
licenseType: "MIT",
|
||||
author: "Yuriy Khomenko",
|
||||
link: "https://github.com/Yuriy-Khomenko/qclone"
|
||||
},
|
||||
{
|
||||
name: "react-native-actions-sheet",
|
||||
licenseType: "MIT",
|
||||
author: "ammarahm-ed",
|
||||
link: "https://github.com/ammarahm-ed/react-native-actions-sheet"
|
||||
},
|
||||
{
|
||||
name: "react-native-check-version",
|
||||
licenseType: "MIT",
|
||||
author: "Flexible Agency <info@flexible.agency>",
|
||||
link: "n/a"
|
||||
},
|
||||
{
|
||||
name: "react-native-drax",
|
||||
licenseType: "MIT",
|
||||
author: "Joe Lafiosca <joe@proxi.gg>",
|
||||
link: "ssh://git@github.com/nuclearpasta/react-native-drax"
|
||||
},
|
||||
{
|
||||
name: "react-native-image-zoom-viewer",
|
||||
licenseType: "MIT",
|
||||
author: "ascoders",
|
||||
link: "https://github.com/ascoders/react-native-image-viewer"
|
||||
},
|
||||
{
|
||||
name: "react-native-progress",
|
||||
licenseType: "MIT",
|
||||
author: "Joel Arvidsson joel@oblador.se",
|
||||
link: "git://github.com/oblador/react-native-progress"
|
||||
},
|
||||
{
|
||||
name: "react-native-qrcode-svg",
|
||||
licenseType: "MIT",
|
||||
author: "awesomejerry",
|
||||
link: "ssh://git@github.com/awesomejerry/react-native-qrcode-svg"
|
||||
},
|
||||
{
|
||||
name: "react-native-reanimated-material-menu",
|
||||
licenseType: "MIT",
|
||||
author:
|
||||
"Maksim Milyutin <mmxckk@gmail.com> (https://github.com/mxck) & Ammar Ahmed <ammarahmed6506@gmail.com> (https://github.com/ammarahm-ed/)",
|
||||
link: "n/a"
|
||||
},
|
||||
{
|
||||
name: "react-native-reanimated-progress-bar",
|
||||
licenseType: "MIT",
|
||||
author: "Serhii Tanskiy",
|
||||
link: "https://github.com/serzmerz/react-native-progress-bar"
|
||||
},
|
||||
{
|
||||
name: "@streetwriters/showdown",
|
||||
licenseType: "MIT",
|
||||
author: "Estevão Santos",
|
||||
link: "https://github.com/showdownjs/showdown"
|
||||
},
|
||||
{
|
||||
name: "toggle-switch-react-native",
|
||||
licenseType: "MIT",
|
||||
author: "aminebenkeroum",
|
||||
link: "https://github.com/aminebenkeroum/toggle-switch-react-native"
|
||||
},
|
||||
{
|
||||
name: "url",
|
||||
licenseType: "MIT",
|
||||
author: "n/a",
|
||||
link: "https://github.com/defunctzombie/node-url"
|
||||
},
|
||||
{
|
||||
name: "validator",
|
||||
licenseType: "MIT",
|
||||
author: "Chris O'Hara <cohara87@gmail.com>",
|
||||
link: "https://github.com/validatorjs/validator.js"
|
||||
},
|
||||
{
|
||||
name: "zustand",
|
||||
licenseType: "MIT",
|
||||
author: "Paul Henschel",
|
||||
link: "https://github.com/pmndrs/zustand"
|
||||
},
|
||||
{
|
||||
name: "fflate",
|
||||
licenseType: "MIT",
|
||||
author: "Arjun Barrett <arjunbarrett@gmail.com>",
|
||||
link: "https://github.com/101arrowz/fflate"
|
||||
},
|
||||
{
|
||||
name: "timeago.js",
|
||||
licenseType: "MIT",
|
||||
author: "hustcc https://timeago.org",
|
||||
link: "https://github.com/hustcc/timeago.js"
|
||||
}
|
||||
];
|
||||
77
apps/mobile/app/screens/settings/licenses.tsx
Normal file
77
apps/mobile/app/screens/settings/licenses.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import React from "react";
|
||||
import { LICENSES } from "./license-data";
|
||||
import { FlatList, Linking, Platform } from "react-native";
|
||||
import { PressableButton } from "../../components/ui/pressable";
|
||||
import Heading from "../../components/ui/typography/heading";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import Paragraph from "../../components/ui/typography/paragraph";
|
||||
|
||||
type LicenseEntry = {
|
||||
name: string;
|
||||
licenseType: string;
|
||||
author: string;
|
||||
link: string;
|
||||
};
|
||||
|
||||
export const Licenses = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const items =
|
||||
Platform.OS === "ios"
|
||||
? LICENSES.filter((l) => l.name.indexOf("android") === -1)
|
||||
: LICENSES;
|
||||
|
||||
const renderItem = React.useCallback(
|
||||
({ item }: { item: LicenseEntry }) => (
|
||||
<PressableButton
|
||||
key={item.name}
|
||||
customStyle={{
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "flex-start",
|
||||
alignSelf: "flex-start",
|
||||
padding: 12,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.nav,
|
||||
borderRadius: 0
|
||||
}}
|
||||
onPress={() => {
|
||||
if (!item.link) return;
|
||||
Linking.openURL(item.link).catch(console.log);
|
||||
}}
|
||||
>
|
||||
<Heading size={SIZE.sm}>{item.name}</Heading>
|
||||
<Paragraph>
|
||||
{item.licenseType} | {item.author?.split("<")[0]}
|
||||
</Paragraph>
|
||||
</PressableButton>
|
||||
),
|
||||
[colors.nav]
|
||||
);
|
||||
return (
|
||||
<FlatList
|
||||
data={items}
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
renderItem={renderItem}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -17,6 +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 notifee from "@notifee/react-native";
|
||||
import dayjs from "dayjs";
|
||||
import React from "react";
|
||||
import { Linking, Platform } from "react-native";
|
||||
@@ -25,6 +26,7 @@ import * as RNIap from "react-native-iap";
|
||||
import { enabled } from "react-native-privacy-snapshot";
|
||||
import { db } from "../../common/database";
|
||||
import { MMKV } from "../../common/database/mmkv";
|
||||
import { AttachmentDialog } from "../../components/attachments";
|
||||
import { ChangePassword } from "../../components/auth/change-password";
|
||||
import { presentDialog } from "../../components/dialog/functions";
|
||||
import { ChangeEmail } from "../../components/sheets/change-email";
|
||||
@@ -56,7 +58,6 @@ import { SUBSCRIPTION_STATUS } from "../../utils/constants";
|
||||
import {
|
||||
eCloseSheet,
|
||||
eCloseSimpleDialog,
|
||||
eOpenAttachmentsDialog,
|
||||
eOpenLoginDialog,
|
||||
eOpenRecoveryKeyDialog,
|
||||
eOpenRestoreDialog
|
||||
@@ -68,7 +69,6 @@ import { useDragState } from "./editor/state";
|
||||
import { verifyUser } from "./functions";
|
||||
import { SettingSection } from "./types";
|
||||
import { getTimeLeft } from "./user-section";
|
||||
import notifee from "@notifee/react-native";
|
||||
|
||||
type User = any;
|
||||
|
||||
@@ -149,7 +149,7 @@ export const settingsGroups: SettingSection[] = [
|
||||
name: "Manage attachments",
|
||||
icon: "attachment",
|
||||
modifer: () => {
|
||||
eSendEvent(eOpenAttachmentsDialog);
|
||||
AttachmentDialog.present();
|
||||
},
|
||||
description: "Manage all attachments in one place."
|
||||
},
|
||||
@@ -474,7 +474,14 @@ export const settingsGroups: SettingSection[] = [
|
||||
description:
|
||||
"Automatically switch to dark mode when system theme changes",
|
||||
property: "useSystemTheme",
|
||||
icon: "circle-half"
|
||||
icon: "circle-half",
|
||||
modifer: () => {
|
||||
const current = SettingsService.get().useSystemTheme;
|
||||
SettingsService.set({
|
||||
useSystemTheme: !current
|
||||
});
|
||||
getColorScheme();
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "enable-dark-mode",
|
||||
@@ -516,6 +523,14 @@ export const settingsGroups: SettingSection[] = [
|
||||
name: "Homepage",
|
||||
description: "Default screen to open on app startup",
|
||||
component: "homeselector"
|
||||
},
|
||||
{
|
||||
id: "clear-trash-interval",
|
||||
type: "component",
|
||||
name: "Clear trash interval",
|
||||
description:
|
||||
"Select the duration after which trash items will be cleared",
|
||||
component: "trash-interval-selector"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1088,6 +1103,14 @@ export const settingsGroups: SettingSection[] = [
|
||||
}
|
||||
},
|
||||
description: "Read our privacy policy"
|
||||
},
|
||||
{
|
||||
id: "licenses",
|
||||
name: "Open source Licenses",
|
||||
type: "screen",
|
||||
component: "licenses",
|
||||
description: "Open source libraries used in Notesnook",
|
||||
icon: "open-source-initiative"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
117
apps/mobile/app/screens/settings/trash-interval-selector.jsx
Normal file
117
apps/mobile/app/screens/settings/trash-interval-selector.jsx
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import Menu, { MenuItem } from "react-native-reanimated-material-menu";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../common/database";
|
||||
import { PressableButton } from "../../components/ui/pressable";
|
||||
import Paragraph from "../../components/ui/typography/paragraph";
|
||||
import PremiumService from "../../services/premium";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { SIZE } from "../../utils/size";
|
||||
|
||||
export const TrashIntervalSelector = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [trashInterval, setTrashInterval] = useState(
|
||||
db.settings.getTrashCleanupInterval()
|
||||
);
|
||||
const menuRef = useRef();
|
||||
const [width, setWidth] = useState(0);
|
||||
|
||||
const onChange = (item) => {
|
||||
menuRef.current?.hide();
|
||||
setTrashInterval(item);
|
||||
db.settings.setTrashCleanupInterval(item);
|
||||
};
|
||||
|
||||
return (
|
||||
<View
|
||||
onLayout={(event) => {
|
||||
setWidth(event.nativeEvent.layout.width);
|
||||
}}
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<Menu
|
||||
ref={menuRef}
|
||||
animationDuration={200}
|
||||
style={{
|
||||
borderRadius: 5,
|
||||
backgroundColor: colors.bg,
|
||||
width: width,
|
||||
marginTop: 60
|
||||
}}
|
||||
onRequestClose={() => {
|
||||
menuRef.current?.hide();
|
||||
}}
|
||||
anchor={
|
||||
<PressableButton
|
||||
onPress={async () => {
|
||||
menuRef.current?.show();
|
||||
}}
|
||||
type="grayBg"
|
||||
customStyle={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginTop: 10,
|
||||
width: "100%",
|
||||
justifyContent: "space-between",
|
||||
padding: 12
|
||||
}}
|
||||
>
|
||||
<Paragraph>
|
||||
{trashInterval === -1 ? "Never" : trashInterval + " days"}
|
||||
</Paragraph>
|
||||
<Icon color={colors.icon} name="menu-down" size={SIZE.md} />
|
||||
</PressableButton>
|
||||
}
|
||||
>
|
||||
{[-1, 7, 30, 365].map((item) => (
|
||||
<MenuItem
|
||||
key={item.name}
|
||||
onPress={async () => {
|
||||
if (item === -1) {
|
||||
await PremiumService.verify(() => {
|
||||
onChange(item);
|
||||
});
|
||||
return;
|
||||
}
|
||||
onChange(item);
|
||||
}}
|
||||
style={{
|
||||
backgroundColor:
|
||||
trashInterval === item ? colors.nav : "transparent",
|
||||
width: "100%",
|
||||
maxWidth: width
|
||||
}}
|
||||
textStyle={{
|
||||
fontSize: SIZE.md,
|
||||
color: trashInterval === item ? colors.accent : colors.pri
|
||||
}}
|
||||
>
|
||||
{item === -1 ? "Never" : item + " days"}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -17,106 +17,107 @@ 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 BackgroundFetch from "react-native-background-fetch";
|
||||
import { DatabaseLogger, db } from "../common/database";
|
||||
import { AppState, AppRegistry } from "react-native";
|
||||
import Notifications from "./notifications";
|
||||
import SettingsService from "./settings";
|
||||
// import BackgroundFetch from "react-native-background-fetch";
|
||||
// import { DatabaseLogger, db } from "../common/database";
|
||||
// import { AppState, AppRegistry } from "react-native";
|
||||
// import Notifications from "./notifications";
|
||||
// import SettingsService from "./settings";
|
||||
|
||||
let backgroundFetchStarted = false;
|
||||
async function start() {
|
||||
if (backgroundFetchStarted) return;
|
||||
backgroundFetchStarted = true;
|
||||
// BackgroundFetch event handler.
|
||||
const onEvent = async (taskId: string) => {
|
||||
console.log("[BackgroundFetch] task: ", taskId, AppState.currentState);
|
||||
// Do your background work...
|
||||
await onBackgroundSyncStarted();
|
||||
// IMPORTANT: You must signal to the OS that your task is complete.
|
||||
BackgroundFetch.finish(taskId);
|
||||
};
|
||||
// let backgroundFetchStarted = false;
|
||||
// async function start() {
|
||||
// if (backgroundFetchStarted) return;
|
||||
// backgroundFetchStarted = true;
|
||||
// // BackgroundFetch event handler.
|
||||
// const onEvent = async (taskId: string) => {
|
||||
// console.log("[BackgroundFetch] task: ", taskId, AppState.currentState);
|
||||
// // Do your background work...
|
||||
// await onBackgroundSyncStarted();
|
||||
// // IMPORTANT: You must signal to the OS that your task is complete.
|
||||
// BackgroundFetch.finish(taskId);
|
||||
// };
|
||||
|
||||
// Timeout callback is executed when your Task has exceeded its allowed running-time.
|
||||
// You must stop what you're doing immediately BackgroundFetch.finish(taskId)
|
||||
const onTimeout = async (taskId: string) => {
|
||||
console.warn("[BackgroundFetch] TIMEOUT: ", taskId);
|
||||
BackgroundFetch.finish(taskId);
|
||||
};
|
||||
// // Timeout callback is executed when your Task has exceeded its allowed running-time.
|
||||
// // You must stop what you're doing immediately BackgroundFetch.finish(taskId)
|
||||
// const onTimeout = async (taskId: string) => {
|
||||
// console.warn("[BackgroundFetch] TIMEOUT: ", taskId);
|
||||
// BackgroundFetch.finish(taskId);
|
||||
// };
|
||||
|
||||
// Initialize BackgroundFetch only once when component mounts.
|
||||
const status = await BackgroundFetch.configure(
|
||||
{
|
||||
minimumFetchInterval: 15,
|
||||
enableHeadless: true,
|
||||
startOnBoot: true,
|
||||
stopOnTerminate: false,
|
||||
requiredNetworkType: BackgroundFetch.NETWORK_TYPE_ANY
|
||||
},
|
||||
onEvent,
|
||||
onTimeout
|
||||
);
|
||||
DatabaseLogger.info(`[BackgroundFetch] configure status: ${status}`);
|
||||
console.log(`[BackgroundFetch] configure status: ${status}`);
|
||||
}
|
||||
// // Initialize BackgroundFetch only once when component mounts.
|
||||
// const status = await BackgroundFetch.configure(
|
||||
// {
|
||||
// minimumFetchInterval: 15,
|
||||
// enableHeadless: true,
|
||||
// startOnBoot: true,
|
||||
// stopOnTerminate: false,
|
||||
// requiredNetworkType: BackgroundFetch.NETWORK_TYPE_ANY
|
||||
// },
|
||||
// onEvent,
|
||||
// onTimeout
|
||||
// );
|
||||
// DatabaseLogger.info(`[BackgroundFetch] configure status: ${status}`);
|
||||
// console.log(`[BackgroundFetch] configure status: ${status}`);
|
||||
// }
|
||||
|
||||
const task = async (event: { taskId: string; timeout: boolean }) => {
|
||||
// Get task id from event {}:
|
||||
const taskId = event.taskId;
|
||||
const isTimeout = event.timeout; // <-- true when your background-time has expired.
|
||||
if (isTimeout) {
|
||||
console.log("[BackgroundFetch] Headless TIMEOUT:", taskId);
|
||||
BackgroundFetch.finish(taskId);
|
||||
return;
|
||||
}
|
||||
DatabaseLogger.info(
|
||||
"[BackgroundFetch HeadlessTask] start: " + taskId + AppState.currentState
|
||||
);
|
||||
await onBackgroundSyncStarted();
|
||||
BackgroundFetch.finish(taskId);
|
||||
};
|
||||
// const task = async (event: { taskId: string; timeout: boolean }) => {
|
||||
// // Get task id from event {}:
|
||||
// const taskId = event.taskId;
|
||||
// const isTimeout = event.timeout; // <-- true when your background-time has expired.
|
||||
// if (isTimeout) {
|
||||
// console.log("[BackgroundFetch] Headless TIMEOUT:", taskId);
|
||||
// BackgroundFetch.finish(taskId);
|
||||
// return;
|
||||
// }
|
||||
// DatabaseLogger.info(
|
||||
// "[BackgroundFetch HeadlessTask] start: " + taskId + AppState.currentState
|
||||
// );
|
||||
// await onBackgroundSyncStarted();
|
||||
// BackgroundFetch.finish(taskId);
|
||||
// };
|
||||
|
||||
BackgroundFetch.registerHeadlessTask(task);
|
||||
// BackgroundFetch.registerHeadlessTask(task);
|
||||
|
||||
async function onBackgroundSyncStarted() {
|
||||
try {
|
||||
DatabaseLogger.info("Background Sync" + "start");
|
||||
await db.init();
|
||||
const user = await db.user?.getUser();
|
||||
if (user) {
|
||||
await db.sync(true, false);
|
||||
}
|
||||
await Notifications.setupReminders();
|
||||
DatabaseLogger.info("Background Sync" + "end");
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e as Error);
|
||||
console.log("Background Sync Error", (e as Error).message);
|
||||
}
|
||||
}
|
||||
// async function onBackgroundSyncStarted() {
|
||||
// try {
|
||||
// DatabaseLogger.info("Background Sync" + "start");
|
||||
// await db.init();
|
||||
// const user = await db.user?.getUser();
|
||||
// if (user) {
|
||||
// await db.sync(true, false);
|
||||
// }
|
||||
// await Notifications.setupReminders();
|
||||
// DatabaseLogger.info("Background Sync" + "end");
|
||||
// } catch (e) {
|
||||
// DatabaseLogger.error(e as Error);
|
||||
// console.log("Background Sync Error", (e as Error).message);
|
||||
// }
|
||||
// }
|
||||
|
||||
const onBoot = async () => {
|
||||
try {
|
||||
DatabaseLogger.info("BOOT TASK STARTED");
|
||||
await db.init();
|
||||
await Notifications.setupReminders();
|
||||
SettingsService.init();
|
||||
if (SettingsService.get().notifNotes) {
|
||||
Notifications.pinQuickNote(false);
|
||||
}
|
||||
DatabaseLogger.info("BOOT TASK COMPLETE");
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
// const onBoot = async () => {
|
||||
// try {
|
||||
// DatabaseLogger.info("BOOT TASK STARTED");
|
||||
// await db.init();
|
||||
// await Notifications.setupReminders();
|
||||
// SettingsService.init();
|
||||
// if (SettingsService.get().notifNotes) {
|
||||
// Notifications.pinQuickNote(false);
|
||||
// }
|
||||
// DatabaseLogger.info("BOOT TASK COMPLETE");
|
||||
// } catch (e) {
|
||||
// console.log(e);
|
||||
// }
|
||||
// };
|
||||
|
||||
const registerHeadlessTask = () =>
|
||||
AppRegistry.registerHeadlessTask(
|
||||
"com.streetwriters.notesnook.BOOT_TASK",
|
||||
() => {
|
||||
return onBoot;
|
||||
}
|
||||
);
|
||||
// const registerHeadlessTask = () =>
|
||||
// AppRegistry.registerHeadlessTask(
|
||||
// "com.streetwriters.notesnook.BOOT_TASK",
|
||||
// () => {
|
||||
// return onBoot;
|
||||
// }
|
||||
// );
|
||||
|
||||
export const BackgroundSync = {
|
||||
start,
|
||||
registerHeadlessTask
|
||||
};
|
||||
// export const BackgroundSync = {
|
||||
// start,
|
||||
// registerHeadlessTask
|
||||
// };
|
||||
export {};
|
||||
|
||||
@@ -17,10 +17,10 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Clipboard from "@react-native-clipboard/clipboard";
|
||||
import EventManager from "@notesnook/core/utils/event-manager";
|
||||
import Clipboard from "@react-native-clipboard/clipboard";
|
||||
import { RefObject } from "react";
|
||||
import ActionSheet from "react-native-actions-sheet";
|
||||
import { ActionSheetRef } from "react-native-actions-sheet";
|
||||
import Config from "react-native-config";
|
||||
import {
|
||||
eCloseSheet,
|
||||
@@ -97,7 +97,7 @@ export type PresentSheetOptions = {
|
||||
component:
|
||||
| JSX.Element
|
||||
| ((
|
||||
ref: RefObject<ActionSheet>,
|
||||
ref: RefObject<ActionSheetRef>,
|
||||
close?: (ctx?: string) => void,
|
||||
update?: (props: PresentSheetOptions) => void
|
||||
) => JSX.Element);
|
||||
@@ -114,6 +114,7 @@ export type PresentSheetOptions = {
|
||||
actionsArray: SheetAction[];
|
||||
learnMore: string;
|
||||
learnMorePress: () => void;
|
||||
enableGesturesInScrollView: boolean;
|
||||
};
|
||||
|
||||
export function presentSheet(data: Partial<PresentSheetOptions>) {
|
||||
|
||||
@@ -85,7 +85,7 @@ async function save(path, data, fileName, extension) {
|
||||
uri = await ScopedStorage.writeFile(
|
||||
path,
|
||||
data,
|
||||
fileName + `.${extension}`,
|
||||
`${fileName}.${extension}`,
|
||||
MIMETypes[extension],
|
||||
extension === "pdf" ? "base64" : "utf8",
|
||||
false
|
||||
@@ -124,7 +124,6 @@ async function exportAs(type, note, bulk) {
|
||||
case "pdf":
|
||||
{
|
||||
let html = await makeHtml(note);
|
||||
console.log(html);
|
||||
let fileName = sanitizeFilename(note.title + Date.now(), {
|
||||
replacement: "_"
|
||||
});
|
||||
@@ -197,6 +196,19 @@ function zipsync(results) {
|
||||
return Buffer.from(data.buffer).toString("base64");
|
||||
}
|
||||
|
||||
function getUniqueFileName(fileName, results) {
|
||||
const chunks = fileName.split(".");
|
||||
const ext = chunks.pop();
|
||||
const name = chunks.join(".");
|
||||
let resolvedName = fileName;
|
||||
let count = 0;
|
||||
while (results[resolvedName]) {
|
||||
resolvedName = `${name}${++count}.${ext}`;
|
||||
}
|
||||
|
||||
return resolvedName;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {"txt" | "pdf" | "md" | "html"} type
|
||||
@@ -212,14 +224,12 @@ async function bulkExport(notes, type, callback) {
|
||||
let note = notes[i];
|
||||
if (note.locked) continue;
|
||||
let result = await exportAs(type, note);
|
||||
let fileName = sanitizeFilename(note.title + Date.now(), {
|
||||
let fileName = sanitizeFilename(note.title, {
|
||||
replacement: "_"
|
||||
});
|
||||
if (result) {
|
||||
results[fileName + `.${type}`] = Buffer.from(
|
||||
result,
|
||||
type === "pdf" ? "base64" : "utf-8"
|
||||
);
|
||||
results[getUniqueFileName(fileName + `.${type}`, results)] =
|
||||
Buffer.from(result, type === "pdf" ? "base64" : "utf-8");
|
||||
}
|
||||
callback(`${i + 1}/${notes.length}`);
|
||||
} catch (e) {
|
||||
|
||||
@@ -41,7 +41,6 @@ import { DDS } from "./device-detection";
|
||||
import { eSendEvent } from "./event-manager";
|
||||
import SettingsService from "./settings";
|
||||
import { useSettingStore } from "../stores/use-setting-store";
|
||||
import ReminderNotify from "../components/sheets/reminder-notify";
|
||||
import { sleep } from "../utils/time";
|
||||
import { useRelationStore } from "../stores/use-relation-store";
|
||||
import { useReminderStore } from "../stores/use-reminder-store";
|
||||
@@ -100,16 +99,18 @@ const onEvent = async ({ type, detail }: Event) => {
|
||||
if (type === EventType.PRESS) {
|
||||
notifee.decrementBadgeCount();
|
||||
if (notification?.data?.type === "quickNote") return;
|
||||
editorState().movedAway = false;
|
||||
MMKV.removeItem("appState");
|
||||
await db.init();
|
||||
await db.notes?.init();
|
||||
if (notification?.data?.type === "reminder") {
|
||||
const reminder = db.reminders?.reminder(notification.id?.split("_")[0]);
|
||||
await sleep(1000);
|
||||
const ReminderNotify =
|
||||
require("../components/sheets/reminder-notify").default;
|
||||
ReminderNotify.present(reminder);
|
||||
return;
|
||||
}
|
||||
|
||||
editorState().movedAway = false;
|
||||
const noteId = notification?.id;
|
||||
if (useNoteStore?.getState()?.loading === false) {
|
||||
loadNote(noteId as string, false);
|
||||
@@ -451,6 +452,10 @@ async function displayNotification({
|
||||
localOnly: true,
|
||||
channelId: await getChannelId("default"),
|
||||
autoCancel: false,
|
||||
pressAction: {
|
||||
id: "default",
|
||||
mainComponent: "notesnook"
|
||||
},
|
||||
actions: actions?.map((action) => ({
|
||||
pressAction: {
|
||||
id: action
|
||||
@@ -516,6 +521,7 @@ async function checkAndRequestPermissions(
|
||||
await notifee.openAlarmPermissionSettings();
|
||||
}
|
||||
permissionStatus = await notifee.getNotificationSettings();
|
||||
|
||||
if (
|
||||
permissionStatus.authorizationStatus === AuthorizationStatus.AUTHORIZED &&
|
||||
permissionStatus.android.alarm === 1
|
||||
|
||||
@@ -145,23 +145,23 @@ export const useTip = (
|
||||
|
||||
const tips: TTip[] = [
|
||||
{
|
||||
text: "You can swipe left anywhere in the app to start a new note",
|
||||
text: "You can swipe left anywhere in the app to start a new note.",
|
||||
contexts: ["notes", "first-note"]
|
||||
},
|
||||
{
|
||||
text: "Long press on any item in list to open quick actions menu.",
|
||||
text: "Long press on any item in list to enter multi-select mode.",
|
||||
contexts: ["notes", "notebook", "notebook", "tags", "topics"]
|
||||
},
|
||||
{
|
||||
text: "Monographs enable you to share your notes in a secure and private way",
|
||||
text: "Monographs enable you to share your notes in a secure and private way.",
|
||||
contexts: ["monographs"]
|
||||
},
|
||||
{
|
||||
text: "Monographs can be encrypted with a secret key and shared with anyone",
|
||||
text: "Monographs can be encrypted with a secret key and shared with anyone.",
|
||||
contexts: ["monographs"]
|
||||
},
|
||||
{
|
||||
text: "Frequently accessed notebooks can be pinned to Side Menu so that they are easily accessible",
|
||||
text: "You can pin frequently used Notebooks to the Side Menu to quickly access them.",
|
||||
contexts: ["notebook", "notebooks"]
|
||||
},
|
||||
{
|
||||
@@ -173,15 +173,15 @@ const tips: TTip[] = [
|
||||
contexts: ["notebook", "topics"]
|
||||
},
|
||||
{
|
||||
text: "Items in trash are kept for 7 days after which they are permanently deleted.",
|
||||
contexts: ["trash"]
|
||||
},
|
||||
{
|
||||
text: "Mark important notes by adding them to favorites",
|
||||
text: "Mark important notes by adding them to favorites.",
|
||||
contexts: ["notes"]
|
||||
},
|
||||
{
|
||||
text: "Have to scroll down a lot to open a note you are working on? Pin it to top from properties.",
|
||||
text: "Are you scrolling a lot to find a specific note? Pin it to the top from Note properties.",
|
||||
contexts: ["notes"]
|
||||
},
|
||||
{
|
||||
text: "You can view & restore older versions of any note by going to its properties -> History.",
|
||||
contexts: ["notes"]
|
||||
}
|
||||
];
|
||||
|
||||
@@ -47,8 +47,9 @@ export const useUserStore = create<UserStore>((set) => ({
|
||||
verifyUser: false,
|
||||
setUser: (user) => set({ user: user }),
|
||||
setPremium: (premium) => set({ premium: premium }),
|
||||
setSyncing: (syncing, status = SyncStatus.Passed) =>
|
||||
set({ syncing: syncing, lastSyncStatus: status }),
|
||||
setSyncing: (syncing, status = SyncStatus.Passed) => {
|
||||
set({ syncing: syncing, lastSyncStatus: status });
|
||||
},
|
||||
setLastSynced: (lastSynced) => set({ lastSynced: lastSynced }),
|
||||
setVerifyUser: (verified) => set({ verifyUser: verified }),
|
||||
lastSyncStatus: SyncStatus.Never
|
||||
|
||||
@@ -158,3 +158,5 @@ export const eCloseAnnouncementDialog = "604";
|
||||
|
||||
export const eOpenLoading = "605";
|
||||
export const eCloseLoading = "606";
|
||||
|
||||
export const eOnTopicSheetUpdate = "607";
|
||||
|
||||
@@ -29,22 +29,28 @@ import { eClearEditor } from "./events";
|
||||
import { useRelationStore } from "../stores/use-relation-store";
|
||||
import { presentDialog } from "../components/dialog/functions";
|
||||
|
||||
function deleteNotesConfirmDialog(items, type, context) {
|
||||
function deleteConfirmDialog(items, type, context) {
|
||||
return new Promise((resolve) => {
|
||||
presentDialog({
|
||||
title: "Delete Contained Notes?",
|
||||
paragraph: `Do you want to delete notes within ${
|
||||
items.length > 1 ? `these ${type}s` : `this ${type}`
|
||||
title: `Delete ${
|
||||
items.length > 1 ? `${items.length} ${type}s` : `${type}`
|
||||
}?`,
|
||||
positiveText: "Yes",
|
||||
negativeText: "No",
|
||||
positivePress: () => {
|
||||
resolve(true);
|
||||
positiveText: "Delete",
|
||||
negativeText: "Cancel",
|
||||
positivePress: (value) => {
|
||||
console.log(value);
|
||||
resolve({ delete: true, deleteNotes: value });
|
||||
},
|
||||
onClose: () => {
|
||||
resolve(false);
|
||||
resolve({ delete: false });
|
||||
},
|
||||
context: context
|
||||
context: context,
|
||||
check: {
|
||||
info: `Move all notes in ${
|
||||
items.length > 1 ? `these ${type}s` : `this ${type}`
|
||||
} to trash`,
|
||||
type: "transparent"
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -107,50 +113,49 @@ export const deleteItems = async (item, context) => {
|
||||
}
|
||||
|
||||
if (topics?.length > 0) {
|
||||
const deleteNotes = await deleteNotesConfirmDialog(
|
||||
topics,
|
||||
"topic",
|
||||
context
|
||||
);
|
||||
for (const topic of topics) {
|
||||
if (deleteNotes) {
|
||||
const notes = db.notebooks
|
||||
.notebook(topic.notebookId)
|
||||
.topics.topic(topic.id).all;
|
||||
await db.notes.delete(...notes.map((note) => note.id));
|
||||
}
|
||||
await db.notebooks.notebook(topic.notebookId).topics.delete(topic.id);
|
||||
}
|
||||
routesForUpdate.push("Notebook", "Notebooks");
|
||||
useMenuStore.getState().setMenuPins();
|
||||
ToastEvent.show({
|
||||
heading: `${topics.length > 1 ? "Topics" : "Topic"} deleted`,
|
||||
type: "success"
|
||||
});
|
||||
}
|
||||
|
||||
if (notebooks?.length > 0) {
|
||||
const deleteNotes = await deleteNotesConfirmDialog(
|
||||
notebooks,
|
||||
"notebook",
|
||||
context
|
||||
);
|
||||
|
||||
let ids = notebooks.map((i) => i.id);
|
||||
if (deleteNotes) {
|
||||
for (let id of ids) {
|
||||
const topics = db.notebooks.notebook(id).topics.all;
|
||||
for (let topic of topics) {
|
||||
const result = await deleteConfirmDialog(topics, "topic", context);
|
||||
if (result.delete) {
|
||||
for (const topic of topics) {
|
||||
if (result.deleteNotes) {
|
||||
const notes = db.notebooks
|
||||
.notebook(topic.notebookId)
|
||||
.topics.topic(topic.id).all;
|
||||
await db.notes.delete(...notes.map((note) => note.id));
|
||||
}
|
||||
await db.notebooks.notebook(topic.notebookId).topics.delete(topic.id);
|
||||
}
|
||||
routesForUpdate.push("Notebook", "Notebooks");
|
||||
useMenuStore.getState().setMenuPins();
|
||||
ToastEvent.show({
|
||||
heading: `${topics.length > 1 ? "Topics" : "Topic"} deleted`,
|
||||
type: "success"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (notebooks?.length > 0) {
|
||||
const result = await deleteConfirmDialog(notebooks, "notebook", context);
|
||||
|
||||
if (result.delete) {
|
||||
let ids = notebooks.map((i) => i.id);
|
||||
if (result.deleteNotes) {
|
||||
for (let id of ids) {
|
||||
const notebook = db.notebooks.notebook(id);
|
||||
const topics = notebook.topics.all;
|
||||
for (let topic of topics) {
|
||||
const notes = db.notebooks
|
||||
.notebook(topic.notebookId)
|
||||
.topics.topic(topic.id).all;
|
||||
await db.notes.delete(...notes.map((note) => note.id));
|
||||
}
|
||||
const notes = db.relations.from(notebook.data, "note");
|
||||
await db.notes.delete(...notes.map((note) => note.id));
|
||||
}
|
||||
}
|
||||
await db.notebooks.delete(...ids);
|
||||
routesForUpdate.push("Notebook", "Notebooks");
|
||||
useMenuStore.getState().setMenuPins();
|
||||
}
|
||||
await db.notebooks.delete(...ids);
|
||||
routesForUpdate.push("Notebook", "Notebooks");
|
||||
useMenuStore.getState().setMenuPins();
|
||||
}
|
||||
|
||||
Navigation.queueRoutesForUpdate(...routesForUpdate);
|
||||
|
||||
@@ -51,7 +51,7 @@ var illegalRe = /[/?<>\\:*|"]/g;
|
||||
var reservedRe = /^\.+$/;
|
||||
var windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
|
||||
var windowsTrailingRe = /[. ]+$/;
|
||||
var whitespace = /\W+/g;
|
||||
var whitespace = /\s+/g;
|
||||
|
||||
function sanitize(input, replacement) {
|
||||
if (typeof input !== "string") {
|
||||
|
||||
@@ -29,7 +29,8 @@ import {
|
||||
navigate,
|
||||
elementByText,
|
||||
sleep,
|
||||
notVisibleByText
|
||||
notVisibleByText,
|
||||
visibleById
|
||||
} from "./utils";
|
||||
|
||||
async function createNotebook(
|
||||
@@ -128,11 +129,11 @@ describe("NOTEBOOKS", () => {
|
||||
await device.pressBack();
|
||||
await sleep(500);
|
||||
await tapByText("Notebook 1");
|
||||
await tapById(notesnook.buttons.add);
|
||||
await tapById("add-topic-button");
|
||||
await elementById("input-title").typeText("Topic");
|
||||
await tapByText("Add");
|
||||
await sleep(500);
|
||||
await visibleByText("Topic");
|
||||
await visibleById("topic-sheet-item-0");
|
||||
});
|
||||
|
||||
it("Edit topic", async () => {
|
||||
@@ -145,12 +146,11 @@ describe("NOTEBOOKS", () => {
|
||||
await sleep(500);
|
||||
await tapByText("Notebook 1");
|
||||
await sleep(300);
|
||||
await visibleByText("Topic");
|
||||
await visibleById("topic-sheet-item-0");
|
||||
await tapById(notesnook.ids.notebook.menu);
|
||||
await tapByText("Edit topic");
|
||||
await elementById("input-title").typeText(" (edited)");
|
||||
await tapByText("Save");
|
||||
await visibleByText("Topic (edited)");
|
||||
});
|
||||
|
||||
it("Add new note to topic", async () => {
|
||||
@@ -175,12 +175,11 @@ describe("NOTEBOOKS", () => {
|
||||
await tapByText("Topic");
|
||||
let note = await createNote();
|
||||
await elementByText(note.body).longPress();
|
||||
await tapByText("Select");
|
||||
await tapById("select-minus");
|
||||
await notVisibleById(note.title);
|
||||
});
|
||||
|
||||
it.only("Add/Remove note to notebook from home", async () => {
|
||||
it("Add/Remove note to notebook from home", async () => {
|
||||
await prepare();
|
||||
await navigate("Notebooks");
|
||||
await sleep(500);
|
||||
@@ -192,31 +191,31 @@ describe("NOTEBOOKS", () => {
|
||||
await createNote();
|
||||
console.log("ADD TO A SINGLE TOPIC");
|
||||
await tapById(notesnook.listitem.menu);
|
||||
await tapById("icon-Add to notebook");
|
||||
await tapById("icon-notebooks");
|
||||
await sleep(500);
|
||||
await tapByText("Notebook 1");
|
||||
await tapByText("Topic");
|
||||
await tapByText("Save");
|
||||
await sleep(300);
|
||||
await visibleByText("Notebook 1 › Topic");
|
||||
await visibleByText("Topic");
|
||||
console.log("MOVE FROM ONE TOPIC TO ANOTHER");
|
||||
await tapById(notesnook.listitem.menu);
|
||||
await tapById("icon-Add to notebook");
|
||||
await tapById("icon-notebooks");
|
||||
await tapByText("Notebook 1");
|
||||
await tapByText("Topic 2");
|
||||
await tapByText("Save");
|
||||
await visibleByText("Notebook 1 › Topic 2");
|
||||
await visibleByText("Topic 2");
|
||||
console.log("REMOVE FROM TOPIC");
|
||||
await tapById(notesnook.listitem.menu);
|
||||
await tapById("icon-Add to notebook");
|
||||
await tapById("icon-notebooks");
|
||||
await tapByText("Notebook 1");
|
||||
await tapByText("Topic 2");
|
||||
await tapByText("Save");
|
||||
await sleep(300);
|
||||
await notVisibleByText("Notebook 1 › Topic 2");
|
||||
await notVisibleByText("Topic 2");
|
||||
console.log("MOVE TO MULTIPLE TOPICS");
|
||||
await tapById(notesnook.listitem.menu);
|
||||
await tapById("icon-Add to notebook");
|
||||
await tapById("icon-notebooks");
|
||||
await tapByText("Notebook 1");
|
||||
await elementByText("Topic").longPress();
|
||||
await visibleByText("Reset selection");
|
||||
|
||||
@@ -139,6 +139,10 @@ def reactNativeArchitectures() {
|
||||
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
|
||||
}
|
||||
|
||||
def fdroidBuild() {
|
||||
return project.hasProperty("fdroidBuild") && project.fdroidBuild == "true"
|
||||
}
|
||||
|
||||
def getNpmVersion() {
|
||||
def inputFile = file("$rootDir/../../package.json")
|
||||
def jsonPackage = new JsonSlurper().parseText(inputFile.text)
|
||||
@@ -160,7 +164,7 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
multiDexEnabled true
|
||||
versionCode 2026
|
||||
versionCode 2038
|
||||
versionName getNpmVersion()
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
@@ -280,18 +284,21 @@ android {
|
||||
def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
|
||||
def abi = output.getFilter(OutputFile.ABI)
|
||||
if (abi != null) { // null for the universal-debug, universal-release variants
|
||||
output.versionCodeOverride =
|
||||
if (fdroidBuild()) {
|
||||
output.versionCodeOverride =
|
||||
defaultConfig.versionCode * 5 + versionCodes.get(abi)
|
||||
println("Fdroid Version code: ${output.versionCodeOverride} for abi ${versionCodes.get(abi)}");
|
||||
} else {
|
||||
println("Version code: ${output.versionCodeOverride} for abi ${versionCodes.get(abi)}");
|
||||
output.versionCodeOverride =
|
||||
versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -69,8 +69,8 @@ allprojects {
|
||||
|
||||
|
||||
maven { url 'https://www.jitpack.io' }
|
||||
maven {
|
||||
url("${project(':react-native-background-fetch').projectDir}/libs")
|
||||
}
|
||||
// maven {
|
||||
// url("${project(':react-native-background-fetch').projectDir}/libs")
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,4 +42,7 @@ newArchEnabled=false
|
||||
|
||||
# V8 Cache Mode Config
|
||||
# v8.cacheMode=none
|
||||
# v8.android.tools.dir=/home/ammarahm-ed/Repos/notesnook-mobile/node_modules/v8-android-jit-nointl/dist/tools/android
|
||||
# v8.android.tools.dir=/home/ammarahm-ed/Repos/notesnook-mobile/node_modules/v8-android-jit-nointl/dist/tools/android
|
||||
|
||||
# fdroid
|
||||
fdroidBuild=false
|
||||
@@ -1,9 +1,6 @@
|
||||
- Improved UX for linking Notebooks
|
||||
- Redesign properties sheet to be simple & handy
|
||||
- Added support for RTL languages in editor
|
||||
- Telemetery is now opt-in
|
||||
- Get default reminder title/description from note
|
||||
- Fix unable to input recovery code on 2FA sheet
|
||||
- Bug fixes & small improvements
|
||||
- Top level notes in notebooks
|
||||
- Image fullscreen preview & download from editor
|
||||
- Change auto clear trash interval from settings
|
||||
- Focus cursor on end when tapping bottom empty area in editor.
|
||||
|
||||
Thank you for using Notesnook!
|
||||
@@ -6,7 +6,8 @@ import { AppRegistry, LogBox, Platform, UIManager } from 'react-native';
|
||||
import Config from 'react-native-config';
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||
import appJson from './app.json';
|
||||
//import {BackgroundSync} from '../app/services/background-sync';
|
||||
import Notifications from "../app/services/notifications";
|
||||
Notifications.init();
|
||||
|
||||
const appName = appJson.name;
|
||||
if (Config.isTesting) {
|
||||
@@ -21,7 +22,6 @@ if (__DEV__) {
|
||||
}
|
||||
let NotesnookShare;
|
||||
|
||||
//BackgroundSync.registerHeadlessTask();
|
||||
let QuickNoteIOS;
|
||||
|
||||
const AppProvider = () => {
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
65AA857E25E6DDEC00772A01 /* NotesWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65AA857D25E6DDEC00772A01 /* NotesWidget.swift */; };
|
||||
65AA858025E6DDEE00772A01 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 65AA857F25E6DDEE00772A01 /* Assets.xcassets */; };
|
||||
65AA858425E6DDEE00772A01 /* NotesWidgetExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 65AA857725E6DDEC00772A01 /* NotesWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
65B450AD2976AF0C00EA090B /* RNBackgroundFetch+AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 65B450AC2976AF0B00EA090B /* RNBackgroundFetch+AppDelegate.m */; };
|
||||
65B5014425A672B200E2D264 /* ShareViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 65B5014325A672B200E2D264 /* ShareViewController.m */; };
|
||||
65B5014725A672B200E2D264 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 65B5014525A672B200E2D264 /* MainInterface.storyboard */; };
|
||||
65B5014B25A672B200E2D264 /* Make Note.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 65B5014025A672B200E2D264 /* Make Note.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
@@ -123,7 +122,6 @@
|
||||
65AA857D25E6DDEC00772A01 /* NotesWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotesWidget.swift; sourceTree = "<group>"; };
|
||||
65AA857F25E6DDEE00772A01 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
65AA858125E6DDEE00772A01 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
65B450AC2976AF0B00EA090B /* RNBackgroundFetch+AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "RNBackgroundFetch+AppDelegate.m"; path = "../../node_modules/react-native-background-fetch/ios/RNBackgroundFetch/RNBackgroundFetch+AppDelegate.m"; sourceTree = "<group>"; };
|
||||
65B5014025A672B200E2D264 /* Make Note.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Make Note.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
65B5014325A672B200E2D264 /* ShareViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ShareViewController.m; sourceTree = "<group>"; };
|
||||
65B5014625A672B200E2D264 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
|
||||
@@ -223,7 +221,6 @@
|
||||
13B07FAE1A68108700A75B9A /* Notesnook */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
65B450AC2976AF0B00EA090B /* RNBackgroundFetch+AppDelegate.m */,
|
||||
6593E4A2281C345400492C50 /* AppDelegate.mm */,
|
||||
6529A13D279BC4C70048D4A8 /* BootSplash.storyboard */,
|
||||
659BE46625E11A5100E05671 /* notesnook-text.png */,
|
||||
@@ -944,7 +941,6 @@
|
||||
65E0340B257B9FF100793428 /* File.swift in Sources */,
|
||||
6593E4A3281C345400492C50 /* AppDelegate.mm in Sources */,
|
||||
13B07FC11A68108700A75B9A /* main.m in Sources */,
|
||||
65B450AD2976AF0C00EA090B /* RNBackgroundFetch+AppDelegate.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1087,7 +1083,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2026;
|
||||
CURRENT_PROJECT_VERSION = 2030;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
ENABLE_BITCODE = NO;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1161,7 +1157,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4.3;
|
||||
MARKETING_VERSION = 2.4.7;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -1191,7 +1187,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Notesnook/Notesnook.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 2026;
|
||||
CURRENT_PROJECT_VERSION = 2030;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
|
||||
@@ -1264,7 +1260,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4.3;
|
||||
MARKETING_VERSION = 2.4.7;
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
@@ -1422,7 +1418,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2026;
|
||||
CURRENT_PROJECT_VERSION = 2030;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1434,7 +1430,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4.3;
|
||||
MARKETING_VERSION = 2.4.7;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
@@ -1464,7 +1460,7 @@
|
||||
CODE_SIGN_IDENTITY = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2026;
|
||||
CURRENT_PROJECT_VERSION = 2030;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1476,7 +1472,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4.3;
|
||||
MARKETING_VERSION = 2.4.7;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -1505,7 +1501,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2026;
|
||||
CURRENT_PROJECT_VERSION = 2030;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1579,7 +1575,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4.3;
|
||||
MARKETING_VERSION = 2.4.7;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
@@ -1609,7 +1605,7 @@
|
||||
CODE_SIGN_IDENTITY = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2026;
|
||||
CURRENT_PROJECT_VERSION = 2030;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1683,7 +1679,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4.3;
|
||||
MARKETING_VERSION = 2.4.7;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#import <React/RCTAppSetupUtils.h>
|
||||
#import <React/RCTLinkingManager.h>
|
||||
#import "RNShortcuts.h"
|
||||
#import <TSBackgroundFetch/TSBackgroundFetch.h>
|
||||
//#import <TSBackgroundFetch/TSBackgroundFetch.h>
|
||||
|
||||
#if RCT_NEW_ARCH_ENABLED
|
||||
#import <React/CoreModulesPlugins.h>
|
||||
@@ -63,7 +63,7 @@ RCTBridge *bridge;
|
||||
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||
self.window.rootViewController = rootViewController;
|
||||
[self.window makeKeyAndVisible];
|
||||
[[TSBackgroundFetch sharedInstance] didFinishLaunching];
|
||||
//[[TSBackgroundFetch sharedInstance] didFinishLaunching];
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
@@ -254,6 +254,8 @@ PODS:
|
||||
- React
|
||||
- react-native-image-picker (4.1.2):
|
||||
- React-Core
|
||||
- react-native-image-resizer (3.0.5):
|
||||
- React-Core
|
||||
- react-native-keep-awake (1.1.0):
|
||||
- React-Core
|
||||
- react-native-mmkv-storage (0.8.0):
|
||||
@@ -271,7 +273,7 @@ PODS:
|
||||
- RCTTypeSafety
|
||||
- React-Core
|
||||
- ReactCommon/turbomodule/core
|
||||
- react-native-sodium (1.2.0):
|
||||
- react-native-sodium (1.3.0):
|
||||
- React
|
||||
- react-native-webview (11.23.1):
|
||||
- React-Core
|
||||
@@ -345,8 +347,6 @@ PODS:
|
||||
- React-Core
|
||||
- rn-fetch-blob (0.12.0):
|
||||
- React-Core
|
||||
- RNBackgroundFetch (4.1.7):
|
||||
- React-Core
|
||||
- RNBootSplash (4.3.2):
|
||||
- React-Core
|
||||
- RNCCheckbox (0.5.12):
|
||||
@@ -371,10 +371,10 @@ PODS:
|
||||
- React-Core
|
||||
- RNKeychain (4.0.5):
|
||||
- React
|
||||
- RNNotifee (7.3.1):
|
||||
- RNNotifee (7.4.4):
|
||||
- React-Core
|
||||
- RNNotifee/NotifeeCore (= 7.3.1)
|
||||
- RNNotifee/NotifeeCore (7.3.1):
|
||||
- RNNotifee/NotifeeCore (= 7.4.4)
|
||||
- RNNotifee/NotifeeCore (7.4.4):
|
||||
- React-Core
|
||||
- RNPrivacySnapshot (1.0.0):
|
||||
- React-Core
|
||||
@@ -461,6 +461,7 @@ DEPENDENCIES:
|
||||
- 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-keep-awake (from `../../node_modules/@sayem314/react-native-keep-awake`)"
|
||||
- react-native-mmkv-storage (from `../../node_modules/react-native-mmkv-storage`)
|
||||
- "react-native-netinfo (from `../../node_modules/@react-native-community/netinfo`)"
|
||||
@@ -483,7 +484,6 @@ DEPENDENCIES:
|
||||
- ReactCommon/turbomodule/core (from `../../node_modules/react-native/ReactCommon`)
|
||||
- rn-extensions-share (from `../../node_modules/rn-extensions-share`)
|
||||
- rn-fetch-blob (from `../../node_modules/rn-fetch-blob`)
|
||||
- RNBackgroundFetch (from `../../node_modules/react-native-background-fetch`)
|
||||
- RNBootSplash (from `../../node_modules/react-native-bootsplash`)
|
||||
- "RNCCheckbox (from `../../node_modules/@react-native-community/checkbox`)"
|
||||
- "RNCClipboard (from `../../node_modules/@react-native-clipboard/clipboard`)"
|
||||
@@ -581,6 +581,8 @@ EXTERNAL SOURCES:
|
||||
: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-keep-awake:
|
||||
:path: "../../node_modules/@sayem314/react-native-keep-awake"
|
||||
react-native-mmkv-storage:
|
||||
@@ -625,8 +627,6 @@ EXTERNAL SOURCES:
|
||||
:path: "../../node_modules/rn-extensions-share"
|
||||
rn-fetch-blob:
|
||||
:path: "../../node_modules/rn-fetch-blob"
|
||||
RNBackgroundFetch:
|
||||
:path: "../../node_modules/react-native-background-fetch"
|
||||
RNBootSplash:
|
||||
:path: "../../node_modules/react-native-bootsplash"
|
||||
RNCCheckbox:
|
||||
@@ -719,13 +719,14 @@ SPEC CHECKSUMS:
|
||||
react-native-gzip: 02f9968afa759e189f0414d41f8f4a951a86b4f1
|
||||
react-native-html-to-pdf-lite: 21bfb169bf4cbcd7bec9f736975ee1b3f5292d4a
|
||||
react-native-image-picker: 9c8a2687b69300ad9e95cec5d38f35ab9d32467d
|
||||
react-native-image-resizer: 00ceb0e05586c7aadf061eea676957a6c2ec60fa
|
||||
react-native-keep-awake: acbee258db16483744910f0da3ace39eb9ab47fd
|
||||
react-native-mmkv-storage: 8ba3c0216a6df283ece11205b442a3e435aec4e5
|
||||
react-native-netinfo: 2517ad504b3d303e90d7a431b0fcaef76d207983
|
||||
react-native-notification-sounds: da78c828fe1bcbb92d8b505d5261890ed315ff39
|
||||
react-native-orientation: f1caf84d65f1a4fd4511a18f2b924e634ad7a628
|
||||
react-native-safe-area-context: 99b24a0c5acd0d5dcac2b1a7f18c49ea317be99a
|
||||
react-native-sodium: 460beee758415bd72cd8d820820fae0bc4076f07
|
||||
react-native-sodium: 1681828855ec18fa952f4557cd595bf048cf5c32
|
||||
react-native-webview: d33e2db8925d090871ffeb232dfa50cb3a727581
|
||||
React-perflogger: 8e832d4e21fdfa613033c76d58d7e617341e804b
|
||||
React-RCTActionSheet: 9ca778182a9523991bff6381045885b6e808bb73
|
||||
@@ -741,7 +742,6 @@ SPEC CHECKSUMS:
|
||||
ReactCommon: 1e783348b9aa73ae68236271df972ba898560a95
|
||||
rn-extensions-share: 3f0ecce20dfbca1f0358deb4ebfb9ee121a6d92a
|
||||
rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba
|
||||
RNBackgroundFetch: 13fbe7b23d4a082eac8caa7b08c5bcbeabd92f79
|
||||
RNBootSplash: 5f346163977573d6b2aeba1b25df9d2245c0d73c
|
||||
RNCCheckbox: ed1b4ca295475b41e7251ebae046360a703b6eb5
|
||||
RNCClipboard: 2834e1c4af68697089cdd455ee4a4cdd198fa7dd
|
||||
@@ -754,7 +754,7 @@ SPEC CHECKSUMS:
|
||||
RNGestureHandler: b7a872907ee289ada902127f2554fa1d2c076122
|
||||
RNIap: d248609d1b8937e63bd904e865c318e9b1457eff
|
||||
RNKeychain: 840f8e6f13be0576202aefcdffd26a4f54bfe7b5
|
||||
RNNotifee: 2fab820e17613db15a1f836053ee3b87af7e96b9
|
||||
RNNotifee: 2ae3c18196e6f307fa62ae5c8e5305dea03ff147
|
||||
RNPrivacySnapshot: 8eaf571478a353f2e5184f5c803164f22428b023
|
||||
RNReanimated: f1b109fb8341505ace9d7d2eedd150da1686716b
|
||||
RNScreens: 34cc502acf1b916c582c60003dc3089fa01dc66d
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
"react-native-screens": "^3.13.1",
|
||||
"react-native-securerandom": "^1.0.1",
|
||||
"react-native-share": "^7.2.0",
|
||||
"@ammarahmed/react-native-sodium": "1.2.0",
|
||||
"@ammarahmed/react-native-sodium": "1.3.0",
|
||||
"react-native-svg": "^12.3.0",
|
||||
"react-native-tooltips": "^1.0.3",
|
||||
"react-native-vector-icons": "^9.0.0",
|
||||
@@ -52,12 +52,12 @@
|
||||
"rn-fetch-blob": "^0.12.0",
|
||||
"react-native-gzip":"1.0.0",
|
||||
"@shopify/flash-list":"1.4.0",
|
||||
"@ammarahmed/notifee-react-native": "7.3.1",
|
||||
"@ammarahmed/notifee-react-native": "7.4.4",
|
||||
"react-native-modal-datetime-picker":"14.0.0",
|
||||
"@react-native-community/datetimepicker":"6.6.0",
|
||||
"react-native-date-picker": "4.2.6",
|
||||
"react-native-notification-sounds": "0.5.5",
|
||||
"react-native-background-fetch": "4.1.7"
|
||||
"@bam.tech/react-native-image-resizer": "3.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.9",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
const isGithubRelease = process.env.GITHUB_RELEASE;
|
||||
const isGithubRelease = false;
|
||||
const config = {
|
||||
commands: require('@callstack/repack/commands'),
|
||||
project: {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
- Improved UX for linking Notebooks
|
||||
- Redesign properties sheet to be simple & handy
|
||||
- Added support for RTL languages in editor
|
||||
- Telemetery is now opt-in
|
||||
- Get default reminder title/description from note
|
||||
- Fix unable to input recovery code on 2FA sheet
|
||||
- Bug fixes & small improvements
|
||||
- Top level notes in notebooks
|
||||
- Image fullscreen preview & download from editor
|
||||
- Change auto clear trash interval from settings
|
||||
- Fixed image upload not working since last update
|
||||
- Fixed adding images to notes via copy/paste from other apps
|
||||
- Focus cursor on end when tapping bottom empty area in editor.
|
||||
- Fixed iPad orientation change editor resizing
|
||||
|
||||
Thank you for using Notesnook!
|
||||
87
apps/mobile/package-lock.json
generated
87
apps/mobile/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "2.4.2",
|
||||
"version": "2.4.7",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "2.4.2",
|
||||
"version": "2.4.7",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"workspaces": [
|
||||
"native/",
|
||||
@@ -40,7 +40,9 @@
|
||||
"html-to-text": "8.1.0",
|
||||
"phone": "^3.1.14",
|
||||
"qclone": "^1.2.0",
|
||||
"react-native-actions-sheet": "^0.7.2",
|
||||
"react": "18.0.0",
|
||||
"react-native": "0.69.7",
|
||||
"react-native-actions-sheet": "^0.9.0-alpha.12",
|
||||
"react-native-check-version": "https://github.com/flexible-agency/react-native-check-version",
|
||||
"react-native-drax": "^0.10.2",
|
||||
"react-native-image-zoom-viewer": "^3.0.1",
|
||||
@@ -60,8 +62,9 @@
|
||||
"version": "1.0.0",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@ammarahmed/notifee-react-native": "7.3.1",
|
||||
"@ammarahmed/react-native-sodium": "1.2.0",
|
||||
"@ammarahmed/notifee-react-native": "7.4.4",
|
||||
"@ammarahmed/react-native-sodium": "1.3.0",
|
||||
"@bam.tech/react-native-image-resizer": "3.0.5",
|
||||
"@callstack/repack": "^3.0.0",
|
||||
"@react-native-clipboard/clipboard": "^1.9.0",
|
||||
"@react-native-community/checkbox": "^0.5.8",
|
||||
@@ -78,7 +81,6 @@
|
||||
"react": "18.0.0",
|
||||
"react-native": "0.69.7",
|
||||
"react-native-background-actions": "^2.6.6",
|
||||
"react-native-background-fetch": "4.1.7",
|
||||
"react-native-begin-background-task": "https://github.com/blockfirm/react-native-begin-background-task.git",
|
||||
"react-native-bootsplash": "^4.1.4",
|
||||
"react-native-config": "^1.4.6",
|
||||
@@ -155,17 +157,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@ammarahmed/notifee-react-native": {
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@ammarahmed/notifee-react-native/-/notifee-react-native-7.3.1.tgz",
|
||||
"integrity": "sha512-kzBDw2NAt9kGD0CJ193JME4bS4uQolY+UubGiTnJwHfaLH7dFNju6RC15/u8tzG59ebDuCalkMLr+rAONtg2Rw==",
|
||||
"version": "7.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@ammarahmed/notifee-react-native/-/notifee-react-native-7.4.4.tgz",
|
||||
"integrity": "sha512-soiJp11voU1MvwmzN66vlbq+NHW7nG3OlKtMPVzJ2O4Q44LP+yrm5u+N8kohMd7jGW14s3E+d27rT7NTzeK+ww==",
|
||||
"peerDependencies": {
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@ammarahmed/react-native-sodium": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-sodium/-/react-native-sodium-1.2.0.tgz",
|
||||
"integrity": "sha512-KOVTeQMIeo8BXHBABIlYDj9QhhcrVzqxaLRra2gg2pB08V3Nq/fPPVexRf0Ir8q7vChd1+gzEFWjMYlvTIssQg=="
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-sodium/-/react-native-sodium-1.3.0.tgz",
|
||||
"integrity": "sha512-Z0wjclwl69QttV6kupzY4bHrRX4ijjZMhjhy1P+WodNCrJpSnG4hI1Jne0hjKi0ghajuFZi+yPB8rjnDvsgQjA=="
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
"version": "2.2.0",
|
||||
@@ -2132,6 +2134,15 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@bam.tech/react-native-image-resizer": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@bam.tech/react-native-image-resizer/-/react-native-image-resizer-3.0.5.tgz",
|
||||
"integrity": "sha512-u5QGUQGGVZiVCJ786k9/kd7pPRZ6eYfJCYO18myVCH8FbVI7J8b5GT2Svjj2x808DlWeqfaZOOzxPqo27XYvrQ==",
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@bcoe/v8-coverage": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
|
||||
@@ -17946,11 +17957,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-actions-sheet": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-actions-sheet/-/react-native-actions-sheet-0.7.2.tgz",
|
||||
"integrity": "sha512-au9QkDnSC+lhiTMHYA2cNdOhrKW/6v/vdeOTNigRFuvYoVVS2+vJOJpt2Z3mumRjmD02UocV0WmHu4anSsqqpA==",
|
||||
"version": "0.9.0-alpha.12",
|
||||
"resolved": "https://registry.npmjs.org/react-native-actions-sheet/-/react-native-actions-sheet-0.9.0-alpha.12.tgz",
|
||||
"integrity": "sha512-VyXsfOFkKCBWB6jn+IlKUTo0KWhItkRC8f6RMvx/RYc5WRhj4Ka5O7/BZVDs86ELJkUT08D7ufzWPjmOQ8hUKw==",
|
||||
"peerDependencies": {
|
||||
"react-native": "*"
|
||||
"react-native": "*",
|
||||
"react-native-gesture-handler": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-actions-shortcuts": {
|
||||
@@ -17977,11 +17989,6 @@
|
||||
"react-native": ">=0.47.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-background-fetch": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/react-native-background-fetch/-/react-native-background-fetch-4.1.7.tgz",
|
||||
"integrity": "sha512-dFY/AOZbEH+ldwZvq/UhhWI5+dyBYL1Xo5wqy6u5DLH+ef8jLIrwKQRmfLAWiP5BtZe6ktA55HM19X39qHoKEQ=="
|
||||
},
|
||||
"node_modules/react-native-begin-background-task": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "git+ssh://git@github.com/blockfirm/react-native-begin-background-task.git#c2aa793249db6cc6298a812905f955a99b864e78",
|
||||
@@ -21516,14 +21523,14 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ammarahmed/notifee-react-native": {
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@ammarahmed/notifee-react-native/-/notifee-react-native-7.3.1.tgz",
|
||||
"integrity": "sha512-kzBDw2NAt9kGD0CJ193JME4bS4uQolY+UubGiTnJwHfaLH7dFNju6RC15/u8tzG59ebDuCalkMLr+rAONtg2Rw=="
|
||||
"version": "7.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@ammarahmed/notifee-react-native/-/notifee-react-native-7.4.4.tgz",
|
||||
"integrity": "sha512-soiJp11voU1MvwmzN66vlbq+NHW7nG3OlKtMPVzJ2O4Q44LP+yrm5u+N8kohMd7jGW14s3E+d27rT7NTzeK+ww=="
|
||||
},
|
||||
"@ammarahmed/react-native-sodium": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-sodium/-/react-native-sodium-1.2.0.tgz",
|
||||
"integrity": "sha512-KOVTeQMIeo8BXHBABIlYDj9QhhcrVzqxaLRra2gg2pB08V3Nq/fPPVexRf0Ir8q7vChd1+gzEFWjMYlvTIssQg=="
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-sodium/-/react-native-sodium-1.3.0.tgz",
|
||||
"integrity": "sha512-Z0wjclwl69QttV6kupzY4bHrRX4ijjZMhjhy1P+WodNCrJpSnG4hI1Jne0hjKi0ghajuFZi+yPB8rjnDvsgQjA=="
|
||||
},
|
||||
"@ampproject/remapping": {
|
||||
"version": "2.2.0",
|
||||
@@ -22862,6 +22869,11 @@
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@bam.tech/react-native-image-resizer": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@bam.tech/react-native-image-resizer/-/react-native-image-resizer-3.0.5.tgz",
|
||||
"integrity": "sha512-u5QGUQGGVZiVCJ786k9/kd7pPRZ6eYfJCYO18myVCH8FbVI7J8b5GT2Svjj2x808DlWeqfaZOOzxPqo27XYvrQ=="
|
||||
},
|
||||
"@bcoe/v8-coverage": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
|
||||
@@ -24298,7 +24310,9 @@
|
||||
"html-to-text": "8.1.0",
|
||||
"phone": "^3.1.14",
|
||||
"qclone": "^1.2.0",
|
||||
"react-native-actions-sheet": "^0.7.2",
|
||||
"react": "18.0.0",
|
||||
"react-native": "0.69.7",
|
||||
"react-native-actions-sheet": "^0.9.0-alpha.12",
|
||||
"react-native-check-version": "https://github.com/flexible-agency/react-native-check-version",
|
||||
"react-native-drax": "^0.10.2",
|
||||
"react-native-image-zoom-viewer": "^3.0.1",
|
||||
@@ -24316,13 +24330,14 @@
|
||||
"@notesnook/mobile-native": {
|
||||
"version": "file:native",
|
||||
"requires": {
|
||||
"@ammarahmed/notifee-react-native": "7.3.1",
|
||||
"@ammarahmed/react-native-sodium": "1.2.0",
|
||||
"@ammarahmed/notifee-react-native": "7.4.4",
|
||||
"@ammarahmed/react-native-sodium": "1.3.0",
|
||||
"@babel/core": "^7.12.9",
|
||||
"@babel/eslint-parser": "^7.16.5",
|
||||
"@babel/plugin-transform-named-capturing-groups-regex": "^7.16.5",
|
||||
"@babel/preset-env": "^7.18.6",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@bam.tech/react-native-image-resizer": "3.0.5",
|
||||
"@callstack/repack": "^3.0.0",
|
||||
"@react-native-clipboard/clipboard": "^1.9.0",
|
||||
"@react-native-community/checkbox": "^0.5.8",
|
||||
@@ -24365,7 +24380,6 @@
|
||||
"react-native": "0.69.7",
|
||||
"react-native-actions-shortcuts": "^1.0.1",
|
||||
"react-native-background-actions": "^2.6.6",
|
||||
"react-native-background-fetch": "4.1.7",
|
||||
"react-native-begin-background-task": "https://github.com/blockfirm/react-native-begin-background-task.git",
|
||||
"react-native-bootsplash": "^4.1.4",
|
||||
"react-native-bundle-visualizer": "^3.1.1",
|
||||
@@ -34842,9 +34856,9 @@
|
||||
}
|
||||
},
|
||||
"react-native-actions-sheet": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-actions-sheet/-/react-native-actions-sheet-0.7.2.tgz",
|
||||
"integrity": "sha512-au9QkDnSC+lhiTMHYA2cNdOhrKW/6v/vdeOTNigRFuvYoVVS2+vJOJpt2Z3mumRjmD02UocV0WmHu4anSsqqpA=="
|
||||
"version": "0.9.0-alpha.12",
|
||||
"resolved": "https://registry.npmjs.org/react-native-actions-sheet/-/react-native-actions-sheet-0.9.0-alpha.12.tgz",
|
||||
"integrity": "sha512-VyXsfOFkKCBWB6jn+IlKUTo0KWhItkRC8f6RMvx/RYc5WRhj4Ka5O7/BZVDs86ELJkUT08D7ufzWPjmOQ8hUKw=="
|
||||
},
|
||||
"react-native-actions-shortcuts": {
|
||||
"version": "1.0.1",
|
||||
@@ -34859,11 +34873,6 @@
|
||||
"eventemitter3": "^4.0.7"
|
||||
}
|
||||
},
|
||||
"react-native-background-fetch": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/react-native-background-fetch/-/react-native-background-fetch-4.1.7.tgz",
|
||||
"integrity": "sha512-dFY/AOZbEH+ldwZvq/UhhWI5+dyBYL1Xo5wqy6u5DLH+ef8jLIrwKQRmfLAWiP5BtZe6ktA55HM19X39qHoKEQ=="
|
||||
},
|
||||
"react-native-begin-background-task": {
|
||||
"version": "git+ssh://git@github.com/blockfirm/react-native-begin-background-task.git#c2aa793249db6cc6298a812905f955a99b864e78",
|
||||
"from": "react-native-begin-background-task@https://github.com/blockfirm/react-native-begin-background-task.git"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "2.4.3",
|
||||
"version": "2.4.7",
|
||||
"private": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"workspaces": [
|
||||
@@ -15,7 +15,7 @@
|
||||
"repack": "cd native && react-native webpack-start",
|
||||
"install-pods": "cd native/ios && pod install",
|
||||
"build-ios": "cd native && detox build -c ios.sim.release",
|
||||
"build-android": "cd native && detox build -c android.emu.release",
|
||||
"build-android": " cd native && detox build -c android.emu.release",
|
||||
"e2e-android": "cd native && detox test --configuration android.emu.release --detectOpenHandles",
|
||||
"e2e-ios": "cd native && detox test -c ios.sim.release --detectOpenHandles",
|
||||
"bump": "cd native && npx react-native bump-version --skip-semver-for android",
|
||||
|
||||
@@ -1,16 +1,28 @@
|
||||
diff --git a/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js b/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js
|
||||
index 4536402..5ceaf65 100644
|
||||
index 4536402..2a100d9 100644
|
||||
--- a/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js
|
||||
+++ b/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js
|
||||
@@ -82,6 +82,7 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
|
||||
@@ -64,7 +64,7 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
|
||||
_subscriptions: Array<EventSubscription> = [];
|
||||
viewRef: {current: React.ElementRef<typeof View> | null, ...};
|
||||
_initialFrameHeight: number = 0;
|
||||
-
|
||||
+ keyboardShown = false;
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {bottom: 0};
|
||||
@@ -80,7 +80,9 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
|
||||
const keyboardY =
|
||||
keyboardFrame.screenY - (this.props.keyboardVerticalOffset ?? 0);
|
||||
|
||||
// Calculate the displacement needed for the view such that it
|
||||
- // Calculate the displacement needed for the view such that it
|
||||
+
|
||||
+ if (this._initialFrameHeight && frame.height < this._initialFrameHeight && this.keyboardShown) frame.height = this._initialFrameHeight;
|
||||
+ // Calculate the displacement needed for the view such that it
|
||||
// no longer overlaps with the keyboard
|
||||
+ if (this._initialFrameHeight && frame.height < this._initialFrameHeight) frame.height = this._initialFrameHeight;
|
||||
return Math.max(frame.y + frame.height - keyboardY, 0);
|
||||
}
|
||||
|
||||
@@ -92,7 +93,9 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
|
||||
@@ -92,7 +94,9 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
|
||||
|
||||
_onLayout = (event: ViewLayoutEvent) => {
|
||||
const wasFrameNull = this._frame == null;
|
||||
@@ -20,6 +32,19 @@ index 4536402..5ceaf65 100644
|
||||
if (!this._initialFrameHeight) {
|
||||
// save the initial frame height, before the keyboard is visible
|
||||
this._initialFrameHeight = this._frame.height;
|
||||
@@ -142,6 +146,8 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
|
||||
this._subscriptions = [
|
||||
Keyboard.addListener('keyboardDidHide', this._onKeyboardChange),
|
||||
Keyboard.addListener('keyboardDidShow', this._onKeyboardChange),
|
||||
+ Keyboard.addListener('keyboardDidHide', () => this.keyboardShown = true),
|
||||
+ Keyboard.addListener('keyboardDidShow', () => this.keyboardShown = false),
|
||||
];
|
||||
}
|
||||
}
|
||||
diff --git a/node_modules/react-native/React/.DS_Store b/node_modules/react-native/React/.DS_Store
|
||||
new file mode 100644
|
||||
index 0000000..5155fbe
|
||||
Binary files /dev/null and b/node_modules/react-native/React/.DS_Store differ
|
||||
diff --git a/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m b/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m
|
||||
index f0f6402..d645d81 100644
|
||||
--- a/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m
|
||||
@@ -44,6 +69,13 @@ index f0f6402..d645d81 100644
|
||||
CGFloat smallerOffset = 0.0;
|
||||
CGFloat largerOffset = maximumOffset;
|
||||
|
||||
diff --git a/node_modules/react-native/scripts/.packager.env b/node_modules/react-native/scripts/.packager.env
|
||||
new file mode 100644
|
||||
index 0000000..361f5fb
|
||||
--- /dev/null
|
||||
+++ b/node_modules/react-native/scripts/.packager.env
|
||||
@@ -0,0 +1 @@
|
||||
+export RCT_METRO_PORT=8081
|
||||
diff --git a/node_modules/react-native/scripts/packager.sh b/node_modules/react-native/scripts/packager.sh
|
||||
index b9f9016..8859130 100755
|
||||
--- a/node_modules/react-native/scripts/packager.sh
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
diff --git a/node_modules/react-native-actions-sheet/dist/src/index.js b/node_modules/react-native-actions-sheet/dist/src/index.js
|
||||
index 7eb4357..18bf26d 100644
|
||||
--- a/node_modules/react-native-actions-sheet/dist/src/index.js
|
||||
+++ b/node_modules/react-native-actions-sheet/dist/src/index.js
|
||||
@@ -747,7 +747,7 @@ var ActionSheet = /** @class */ (function (_super) {
|
||||
width: "100%"
|
||||
},
|
||||
]}>
|
||||
- {this.props.ExtraOverlayComponent}
|
||||
+
|
||||
|
||||
<FlatList testID={(_a = this.props.testIDs) === null || _a === void 0 ? void 0 : _a.scrollview} bounces={false} keyboardShouldPersistTaps={keyboardShouldPersistTaps} keyboardDismissMode={keyboardDismissMode} ref={this.scrollViewRef} scrollEventThrottle={16} overScrollMode="never" showsVerticalScrollIndicator={false} onMomentumScrollBegin={this._onScrollBegin} onScrollEndDrag={this._onScrollEnd} onMomentumScrollEnd={this._onScrollEnd} scrollEnabled={scrollable} onScrollBeginDrag={this._onScrollBeginDrag} onTouchEnd={this._onTouchEnd} onScroll={this._onScroll} scrollsToTop={false} style={[
|
||||
styles.scrollView,
|
||||
@@ -821,6 +821,7 @@ var ActionSheet = /** @class */ (function (_super) {
|
||||
</Animated.View>
|
||||
</View>);
|
||||
}}/>
|
||||
+ {this.props.ExtraOverlayComponent}
|
||||
</Animated.View>
|
||||
</Root>
|
||||
</>);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user