Compare commits
222 Commits
v3-beta
...
fix/error-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd94d9685c | ||
|
|
5028bb1275 | ||
|
|
27ae5bb5a2 | ||
|
|
83316fe299 | ||
|
|
8869224de2 | ||
|
|
c4152f0fc0 | ||
|
|
d803005637 | ||
|
|
53d896be2b | ||
|
|
8c37c6409a | ||
|
|
9195244890 | ||
|
|
152794ccd0 | ||
|
|
2e8b234b63 | ||
|
|
055c647698 | ||
|
|
2b7bcfe7c6 | ||
|
|
3c8b8a7bc9 | ||
|
|
fde45fb6de | ||
|
|
4a616a3e67 | ||
|
|
bd0a338dd6 | ||
|
|
5ab721e3e2 | ||
|
|
cb70bb9429 | ||
|
|
82cadd473c | ||
|
|
0e8ea3ddeb | ||
|
|
511acc5fca | ||
|
|
001cf8ee92 | ||
|
|
a965cd694b | ||
|
|
d3c1c1959b | ||
|
|
9a866a552a | ||
|
|
837c296821 | ||
|
|
9aed6faed8 | ||
|
|
734f491e8d | ||
|
|
0ea13024e9 | ||
|
|
cb27039245 | ||
|
|
714ac2deac | ||
|
|
d605bc29bf | ||
|
|
080101bc45 | ||
|
|
4819e17da3 | ||
|
|
69a5d5a1b6 | ||
|
|
68a6d6c313 | ||
|
|
c51ef4d5f6 | ||
|
|
d9c5c1f893 | ||
|
|
5a75ca73a9 | ||
|
|
626d393376 | ||
|
|
68d06ff3f8 | ||
|
|
79f20d100c | ||
|
|
9d5bec3bfb | ||
|
|
ac96354b4c | ||
|
|
63f44d6fbc | ||
|
|
f9bfa88c83 | ||
|
|
ef192f3d22 | ||
|
|
c9a8134265 | ||
|
|
cd7a5d2ec0 | ||
|
|
b92bc03fa9 | ||
|
|
5f5e472800 | ||
|
|
0951906cac | ||
|
|
6f0fe03760 | ||
|
|
159ce97e42 | ||
|
|
8a9848634c | ||
|
|
282705a683 | ||
|
|
c91b706ed0 | ||
|
|
16afc07b2f | ||
|
|
04f8be5a62 | ||
|
|
0e8a5ca1fa | ||
|
|
1491bf771e | ||
|
|
0f5ce465c3 | ||
|
|
647ff7ae0e | ||
|
|
45a552adef | ||
|
|
8e207d787d | ||
|
|
00754593ac | ||
|
|
b22770f1ae | ||
|
|
523da4e91f | ||
|
|
de6c8154a3 | ||
|
|
9ef32e8081 | ||
|
|
5f53593a26 | ||
|
|
1f404944d9 | ||
|
|
23dca36475 | ||
|
|
3dd8e20e9a | ||
|
|
8f412f9487 | ||
|
|
bf790219db | ||
|
|
c6af310b2d | ||
|
|
8d89b5dca9 | ||
|
|
fa35244607 | ||
|
|
d96815fa36 | ||
|
|
4a9f5b1d8b | ||
|
|
43fac79011 | ||
|
|
f2c62c8d8b | ||
|
|
70c9dd0083 | ||
|
|
ae6d8c5f2b | ||
|
|
38e2250fd1 | ||
|
|
17e2c5a8a2 | ||
|
|
2e226f0f72 | ||
|
|
167c54578b | ||
|
|
fb656af5ca | ||
|
|
ddfab272d2 | ||
|
|
28958311c1 | ||
|
|
f1b8275b4b | ||
|
|
c8c75d40dd | ||
|
|
bace90808d | ||
|
|
fe7c7e8db4 | ||
|
|
519a569366 | ||
|
|
89d602a40f | ||
|
|
a8db65b5af | ||
|
|
716239abfd | ||
|
|
62dd5a5416 | ||
|
|
96558c5df5 | ||
|
|
45997dd2e4 | ||
|
|
bde190f01c | ||
|
|
042c97688f | ||
|
|
95b5114070 | ||
|
|
20742e2a1c | ||
|
|
1e3149284e | ||
|
|
d77c09f1fd | ||
|
|
aa602ea8e9 | ||
|
|
8ab6b6dc31 | ||
|
|
404d136a73 | ||
|
|
cf9aeecd7e | ||
|
|
1adda96ce2 | ||
|
|
6dd6d818c9 | ||
|
|
c7e83961d4 | ||
|
|
03428e5848 | ||
|
|
d0fceed56d | ||
|
|
b8c68b5163 | ||
|
|
2496004896 | ||
|
|
16ec35de25 | ||
|
|
d248b8e2b8 | ||
|
|
641e7484e3 | ||
|
|
a46409ff44 | ||
|
|
5dbabc2706 | ||
|
|
2d4d656dd9 | ||
|
|
ab51b22e4a | ||
|
|
781d09daca | ||
|
|
f21533e5e4 | ||
|
|
2ea9b04863 | ||
|
|
675d517cbc | ||
|
|
c336c57bda | ||
|
|
7b5fa09c71 | ||
|
|
d85974ed7a | ||
|
|
9598ea7542 | ||
|
|
42048ad53a | ||
|
|
1fbd2efb92 | ||
|
|
fe91ef79a0 | ||
|
|
583d585b2f | ||
|
|
94b28f86ca | ||
|
|
f927f05f86 | ||
|
|
bde8dea048 | ||
|
|
511bea9b23 | ||
|
|
eda95530de | ||
|
|
5173decaa5 | ||
|
|
00dcb55f6e | ||
|
|
6dbe1073cd | ||
|
|
9be3e4fcfa | ||
|
|
f9f0a866bd | ||
|
|
daafeb2fa8 | ||
|
|
a92b34b81c | ||
|
|
e29b6838b3 | ||
|
|
16ff7c92e4 | ||
|
|
07e1ea6edf | ||
|
|
b26ede95fb | ||
|
|
971793a3ad | ||
|
|
6a2b5e1499 | ||
|
|
d18b775d06 | ||
|
|
d6cc899d95 | ||
|
|
ea10058276 | ||
|
|
568c88be3e | ||
|
|
f047552f18 | ||
|
|
9a58430b5d | ||
|
|
205885edf5 | ||
|
|
8ffb49b700 | ||
|
|
25b18a2edd | ||
|
|
cfea85c8fb | ||
|
|
82fc6d31c3 | ||
|
|
fb2de1753e | ||
|
|
ceb5ce5cdd | ||
|
|
98d443001c | ||
|
|
5115dee069 | ||
|
|
0dc29c7026 | ||
|
|
a5eb5e9df3 | ||
|
|
b680575328 | ||
|
|
c94ef616a5 | ||
|
|
3ae40c09fc | ||
|
|
5de4e00ce2 | ||
|
|
99e4e61a55 | ||
|
|
1ad7bfb82f | ||
|
|
30ef679432 | ||
|
|
bb97f5e757 | ||
|
|
6cd5a04470 | ||
|
|
0c32ebf163 | ||
|
|
e150e08f29 | ||
|
|
bbd603f10c | ||
|
|
677944cba9 | ||
|
|
dd2cee53d3 | ||
|
|
60c8d5ac89 | ||
|
|
59276de406 | ||
|
|
67981be516 | ||
|
|
77dfcec08c | ||
|
|
c4360ab95f | ||
|
|
17c4b72381 | ||
|
|
e91cc2bdca | ||
|
|
811f2305ec | ||
|
|
98a707b5f5 | ||
|
|
aa9b24fa10 | ||
|
|
0b3060e4c0 | ||
|
|
d523db84cc | ||
|
|
6a6a9ae6b3 | ||
|
|
b1bc28e069 | ||
|
|
f95c068bc1 | ||
|
|
0e4cd087d4 | ||
|
|
f217b8cfec | ||
|
|
d3787efbf0 | ||
|
|
e279878b12 | ||
|
|
154a227be0 | ||
|
|
756259c907 | ||
|
|
453ed4820a | ||
|
|
10ab612366 | ||
|
|
8dc4f7ed3d | ||
|
|
a73538bafc | ||
|
|
014887c7dc | ||
|
|
246e49bd8c | ||
|
|
146ac12935 | ||
|
|
4f5245adcf | ||
|
|
0bc0cc5729 | ||
|
|
c54759e766 | ||
|
|
f15446b1d6 |
10
.github/workflows/android.publish.internal.yml
vendored
@@ -147,8 +147,10 @@ jobs:
|
||||
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: Upload signed aab to Github
|
||||
uses: actions/upload-artifact@v2
|
||||
- name: Upload sourcemaps
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Notesnook.aab
|
||||
path: ${{steps.sign_app.outputs.signedReleaseFile}}
|
||||
name: sourcemaps
|
||||
path: |
|
||||
apps/mobile/native/android/app/build/**/*.map
|
||||
packages/editor-mobile/sourcemaps/*.map
|
||||
|
||||
11
.github/workflows/android.publish.yml
vendored
@@ -133,7 +133,6 @@ jobs:
|
||||
status: completed
|
||||
whatsNewDirectory: apps/mobile/native/android/releasenotes/
|
||||
|
||||
|
||||
- name: Create release draft on Github
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
@@ -148,8 +147,10 @@ jobs:
|
||||
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: Upload signed aab to Github
|
||||
uses: actions/upload-artifact@v2
|
||||
- name: Upload sourcemaps
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Notesnook.aab
|
||||
path: ${{steps.sign_app.outputs.signedReleaseFile}}
|
||||
name: sourcemaps
|
||||
path: |
|
||||
apps/mobile/native/android/app/build/**/*.map
|
||||
packages/editor-mobile/sourcemaps/*.map
|
||||
|
||||
16
.github/workflows/desktop.publish.yml
vendored
@@ -147,7 +147,7 @@ jobs:
|
||||
CSC_KEY_PASSWORD: ${{ secrets.mac_certs_password }}
|
||||
run: |
|
||||
npx nx run release --project @notesnook/desktop -- --variant=mas
|
||||
npx electron-builder --mac mas --universal -p never
|
||||
yarn electron-builder --mac mas --universal -p never
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Build zip and dmg
|
||||
@@ -161,9 +161,9 @@ jobs:
|
||||
run: |
|
||||
npx nx run release --project @notesnook/desktop
|
||||
if [ ${{ inputs.publish-github }} == true ]; then
|
||||
npx electron-builder --mac zip dmg --arm64 --x64 -p always
|
||||
yarn electron-builder --mac zip dmg --arm64 --x64 -p always
|
||||
else
|
||||
npx electron-builder --mac zip dmg --arm64 --x64 -p never
|
||||
yarn electron-builder --mac zip dmg --arm64 --x64 -p never
|
||||
fi
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
@@ -219,7 +219,7 @@ jobs:
|
||||
- name: Build snap
|
||||
if: inputs.publish-snap
|
||||
run: |
|
||||
npx electron-builder --linux snap:x64 -p never
|
||||
yarn electron-builder --linux snap:x64 -p never
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Build AppImage
|
||||
@@ -227,9 +227,9 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
if [ ${{ inputs.publish-github }} == true ]; then
|
||||
npx electron-builder --linux AppImage:x64 AppImage:arm64 -p always
|
||||
yarn electron-builder --linux AppImage:x64 AppImage:arm64 -p always
|
||||
else
|
||||
npx electron-builder --linux AppImage:x64 AppImage:arm64 -p never
|
||||
yarn electron-builder --linux AppImage:x64 AppImage:arm64 -p never
|
||||
fi
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
@@ -254,8 +254,8 @@ jobs:
|
||||
with:
|
||||
dotnet-version: 6.0.x
|
||||
|
||||
- name: Install AzureCodeSigning
|
||||
run: Install-Module -Name AzureCodeSigning -RequiredVersion 0.3.0 -Force -Repository PSGallery
|
||||
- name: Install TrustedSigning
|
||||
run: Install-Module -Name TrustedSigning -RequiredVersion 0.3.15 -Force -Repository PSGallery
|
||||
shell: pwsh
|
||||
|
||||
- name: Setup Node
|
||||
|
||||
9
.github/workflows/ios.publish.yml
vendored
@@ -98,7 +98,10 @@ jobs:
|
||||
api-private-key: ${{ secrets.API_KEY }}
|
||||
|
||||
- name: Upload Notesnook.ipa to Github
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Notesnook.ipa
|
||||
path: Notesnook.ipa
|
||||
name: Notesnook.zip
|
||||
path: |
|
||||
Notesnook.ipa
|
||||
apps/mobile/native/ios/**/*.map
|
||||
packages/editor-mobile/sourcemaps/*.map
|
||||
|
||||
69
.github/workflows/web.beta.publish.yml
vendored
@@ -1,69 +0,0 @@
|
||||
name: Publish @notesnook/web Beta
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- "!v3-beta"
|
||||
paths-ignore:
|
||||
- "apps/mobile/**"
|
||||
- "packages/editor-mobile/**"
|
||||
- "apps/desktop/**"
|
||||
- "apps/vericrypt/**"
|
||||
- "servers/**"
|
||||
- "packages/clipper/**"
|
||||
- "extensions/web-clipper/**"
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
- ready_for_review
|
||||
- reopened
|
||||
- opened
|
||||
- synchronize
|
||||
branches:
|
||||
- master
|
||||
- "!v3-beta"
|
||||
paths-ignore:
|
||||
- "apps/mobile/**"
|
||||
- "packages/editor-mobile/**"
|
||||
- "apps/desktop/**"
|
||||
- "apps/vericrypt/**"
|
||||
- "servers/**"
|
||||
- "packages/clipper/**"
|
||||
- "extensions/web-clipper/**"
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=web
|
||||
|
||||
- name: Setup environment
|
||||
run: |
|
||||
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
|
||||
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
|
||||
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Generate beta build
|
||||
run: npm run build:beta:web
|
||||
|
||||
- name: Publish to Cloudflare Pages
|
||||
uses: unlike-ltd/github-actions-cloudflare-pages@v0.1.1
|
||||
id: pages
|
||||
with:
|
||||
cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
cloudflare-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
cloudflare-project-name: notesnook-beta-app
|
||||
directory: ./apps/web/build
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
github-environment: ${{ (github.ref == 'refs/heads/master' && 'beta') || 'preview' }}
|
||||
8
.github/workflows/web.tests.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
run: npm run build:test:web
|
||||
|
||||
- name: Archive build artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build
|
||||
path: apps/web/build/**/*
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Download build
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build
|
||||
path: ./apps/web/build
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm install -D @playwright/test otplib
|
||||
npm install -D @playwright/test otplib dayjs @zip.js/zip.js
|
||||
|
||||
- name: Validate build extraction
|
||||
run: ls -ld ./apps/web/build/index.html
|
||||
@@ -88,7 +88,7 @@ jobs:
|
||||
working-directory: apps/web
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-${{ matrix.shard }}
|
||||
|
||||
32
.github/workflows/web.v3-beta.publish.yml
vendored
@@ -1,32 +0,0 @@
|
||||
name: Publish @notesnook/web v3 Beta
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=web
|
||||
|
||||
- name: Setup environment
|
||||
run: |
|
||||
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
|
||||
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
|
||||
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build
|
||||
run: npm run build:web
|
||||
|
||||
- name: Publish to Cloudflare Pages
|
||||
run: npx --yes wrangler pages deploy --project-name=notesnook-v3-beta ./apps/web/build/
|
||||
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 520 B After Width: | Height: | Size: 401 B |
|
Before Width: | Height: | Size: 917 B After Width: | Height: | Size: 543 B |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 763 B |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 353 KiB After Width: | Height: | Size: 353 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 7.3 KiB |
558
apps/desktop/package-lock.json
generated
@@ -1,23 +1,23 @@
|
||||
{
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "3.0.14-beta",
|
||||
"version": "3.0.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "3.0.14-beta",
|
||||
"version": "3.0.3",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@notesnook/crypto": "file:../../packages/crypto",
|
||||
"@trpc/client": "10.38.3",
|
||||
"@trpc/server": "10.38.3",
|
||||
"better-sqlite3-multiple-ciphers": "^9.4.0",
|
||||
"better-sqlite3-multiple-ciphers": "^9.5.0",
|
||||
"electron-trpc": "0.5.2",
|
||||
"electron-updater": "6.1.4",
|
||||
"icojs": "^0.17.1",
|
||||
"sodium-native": "^4.0.6",
|
||||
"sodium-native": "^4.1.1",
|
||||
"typed-emitter": "^2.1.0",
|
||||
"yargs": "^17.6.2",
|
||||
"zod": "^3.21.4"
|
||||
@@ -26,12 +26,13 @@
|
||||
"@types/node": "18.16.1",
|
||||
"@types/yargs": "^17.0.24",
|
||||
"chokidar": "^3.5.3",
|
||||
"electron": "^28.2.1",
|
||||
"electron-builder": "^24.9.1",
|
||||
"electron": "^29.3.1",
|
||||
"electron-builder": "^24.13.3",
|
||||
"esbuild": "^0.20.0",
|
||||
"prebuildify": "^6.0.0",
|
||||
"node-gyp-build": "^4.8.0",
|
||||
"prebuildify": "^6.0.1",
|
||||
"tree-kill": "^1.2.2",
|
||||
"undici": "^6.6.1"
|
||||
"undici": "^6.14.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"dmg-license": "^1.0.11"
|
||||
@@ -69,9 +70,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@electron/asar": {
|
||||
"version": "3.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.8.tgz",
|
||||
"integrity": "sha512-cmskk5M06ewHMZAplSiF4AlME3IrnnZhKnWbtwKVLRkdJkKyUVjMLhDIiPIx/+6zQWVlKX/LtmK9xDme7540Sg==",
|
||||
"version": "3.2.9",
|
||||
"resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.9.tgz",
|
||||
"integrity": "sha512-Vu2P3X2gcZ3MY9W7yH72X9+AMXwUQZEJBrsPIbX0JsdllLtoh62/Q8Wg370/DawIEVKOyfD6KtTLo645ezqxUA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"commander": "^5.0.0",
|
||||
@@ -170,9 +171,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@electron/notarize": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.1.0.tgz",
|
||||
"integrity": "sha512-Q02xem1D0sg4v437xHgmBLxI2iz/fc0D4K7fiVWHa/AnW8o7D751xyKNXgziA6HrTOme9ul1JfWN5ark8WH1xA==",
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.2.1.tgz",
|
||||
"integrity": "sha512-aL+bFMIkpR0cmmj5Zgy0LMKEpgy43/hw5zadEArgmAMWWlKc5buwFvFT9G/o/YJkvXAJm5q3iuTuLaiaXW39sg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.1.1",
|
||||
@@ -232,9 +233,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@electron/universal": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@electron/universal/-/universal-1.4.1.tgz",
|
||||
"integrity": "sha512-lE/U3UNw1YHuowNbTmKNs9UlS3En3cPgwM5MI+agIgr/B1hSze9NdOP0qn7boZaI9Lph8IDv3/24g9IxnJP7aQ==",
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@electron/universal/-/universal-1.5.1.tgz",
|
||||
"integrity": "sha512-kbgXxyEauPJiQQUNG2VgUeyfQNFk6hBF11ISN2PNI6agUgPl55pv4eQmaqHzTAzchBvqZ2tQuRVaPStGf0mxGw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@electron/asar": "^3.2.1",
|
||||
@@ -654,13 +655,100 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@fastify/busboy": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.0.tgz",
|
||||
"integrity": "sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==",
|
||||
"node_modules/@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"string-width": "^5.1.2",
|
||||
"string-width-cjs": "npm:string-width@^4.2.0",
|
||||
"strip-ansi": "^7.0.1",
|
||||
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
|
||||
"wrap-ansi": "^8.1.0",
|
||||
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui/node_modules/ansi-regex": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
|
||||
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui/node_modules/ansi-styles": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
|
||||
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui/node_modules/emoji-regex": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@isaacs/cliui/node_modules/string-width": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"eastasianwidth": "^0.2.0",
|
||||
"emoji-regex": "^9.2.2",
|
||||
"strip-ansi": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui/node_modules/strip-ansi": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
|
||||
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
||||
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.1.0",
|
||||
"string-width": "^5.0.1",
|
||||
"strip-ansi": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@malept/cross-spawn-promise": {
|
||||
@@ -719,6 +807,16 @@
|
||||
"resolved": "../../packages/crypto",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@pkgjs/parseargs": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@sindresorhus/is": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
|
||||
@@ -968,26 +1066,25 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/app-builder-lib": {
|
||||
"version": "24.9.1",
|
||||
"resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-24.9.1.tgz",
|
||||
"integrity": "sha512-Q1nYxZcio4r+W72cnIRVYofEAyjBd3mG47o+zms8HlD51zWtA/YxJb01Jei5F+jkWhge/PTQK+uldsPh6d0/4g==",
|
||||
"version": "24.13.3",
|
||||
"resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-24.13.3.tgz",
|
||||
"integrity": "sha512-FAzX6IBit2POXYGnTCT8YHFO/lr5AapAII6zzhQO3Rw4cEDOgK+t1xhLc5tNcKlicTHlo9zxIwnYCX9X2DLkig==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@develar/schema-utils": "~2.6.5",
|
||||
"@electron/notarize": "2.1.0",
|
||||
"@electron/notarize": "2.2.1",
|
||||
"@electron/osx-sign": "1.0.5",
|
||||
"@electron/universal": "1.4.1",
|
||||
"@electron/universal": "1.5.1",
|
||||
"@malept/flatpak-bundler": "^0.4.0",
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"7zip-bin": "~5.2.0",
|
||||
"async-exit-hook": "^2.0.1",
|
||||
"bluebird-lst": "^1.0.9",
|
||||
"builder-util": "24.8.1",
|
||||
"builder-util-runtime": "9.2.3",
|
||||
"builder-util": "24.13.1",
|
||||
"builder-util-runtime": "9.2.4",
|
||||
"chromium-pickle-js": "^0.2.0",
|
||||
"debug": "^4.3.4",
|
||||
"ejs": "^3.1.8",
|
||||
"electron-publish": "24.8.1",
|
||||
"electron-publish": "24.13.1",
|
||||
"form-data": "^4.0.0",
|
||||
"fs-extra": "^10.1.0",
|
||||
"hosted-git-info": "^4.1.0",
|
||||
@@ -1004,12 +1101,16 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"dmg-builder": "24.13.3",
|
||||
"electron-builder-squirrel-windows": "24.13.3"
|
||||
}
|
||||
},
|
||||
"node_modules/app-builder-lib/node_modules/builder-util-runtime": {
|
||||
"version": "9.2.3",
|
||||
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz",
|
||||
"integrity": "sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==",
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.4.tgz",
|
||||
"integrity": "sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4",
|
||||
@@ -1098,9 +1199,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/better-sqlite3-multiple-ciphers": {
|
||||
"version": "9.4.0",
|
||||
"resolved": "https://registry.npmjs.org/better-sqlite3-multiple-ciphers/-/better-sqlite3-multiple-ciphers-9.4.0.tgz",
|
||||
"integrity": "sha512-uQoBdShnpCdAPUmCPuLSrHtfSHAxjIFQvLnMbXHsvbWkfBP3sPfnnIaUTz7KHYRqgWNLlcQfs+9F9uqvMqau6Q==",
|
||||
"version": "9.5.0",
|
||||
"resolved": "https://registry.npmjs.org/better-sqlite3-multiple-ciphers/-/better-sqlite3-multiple-ciphers-9.5.0.tgz",
|
||||
"integrity": "sha512-6A9xBRvssONJW07Lur6pmkaUwlCGz1bnjFmXrrAR3qXETUBk92OI4C/+LhMOatn5wQ8AQzLScFZC/CKUpxkTaw==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"bindings": "^1.5.0",
|
||||
@@ -1233,16 +1334,16 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/builder-util": {
|
||||
"version": "24.8.1",
|
||||
"resolved": "https://registry.npmjs.org/builder-util/-/builder-util-24.8.1.tgz",
|
||||
"integrity": "sha512-ibmQ4BnnqCnJTNrdmdNlnhF48kfqhNzSeqFMXHLIl+o9/yhn6QfOaVrloZ9YUu3m0k3rexvlT5wcki6LWpjTZw==",
|
||||
"version": "24.13.1",
|
||||
"resolved": "https://registry.npmjs.org/builder-util/-/builder-util-24.13.1.tgz",
|
||||
"integrity": "sha512-NhbCSIntruNDTOVI9fdXz0dihaqX2YuE1D6zZMrwiErzH4ELZHE6mdiB40wEgZNprDia+FghRFgKoAqMZRRjSA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/debug": "^4.1.6",
|
||||
"7zip-bin": "~5.2.0",
|
||||
"app-builder-bin": "4.0.0",
|
||||
"bluebird-lst": "^1.0.9",
|
||||
"builder-util-runtime": "9.2.3",
|
||||
"builder-util-runtime": "9.2.4",
|
||||
"chalk": "^4.1.2",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"debug": "^4.3.4",
|
||||
@@ -1269,9 +1370,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/builder-util/node_modules/builder-util-runtime": {
|
||||
"version": "9.2.3",
|
||||
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz",
|
||||
"integrity": "sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==",
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.4.tgz",
|
||||
"integrity": "sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4",
|
||||
@@ -1475,13 +1576,59 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/config-file-ts": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/config-file-ts/-/config-file-ts-0.2.4.tgz",
|
||||
"integrity": "sha512-cKSW0BfrSaAUnxpgvpXPLaaW/umg4bqg4k3GO1JqlRfpx+d5W0GDXznCMkWotJQek5Mmz1MJVChQnz3IVaeMZQ==",
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/config-file-ts/-/config-file-ts-0.2.6.tgz",
|
||||
"integrity": "sha512-6boGVaglwblBgJqGyxm4+xCmEGcWgnWHSWHY5jad58awQhB6gftq0G8HbzU39YqCIYHMLAiL1yjwiZ36m/CL8w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"glob": "^7.1.6",
|
||||
"typescript": "^4.0.2"
|
||||
"glob": "^10.3.10",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/config-file-ts/node_modules/glob": {
|
||||
"version": "10.3.12",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",
|
||||
"integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"foreground-child": "^3.1.0",
|
||||
"jackspeak": "^2.3.6",
|
||||
"minimatch": "^9.0.1",
|
||||
"minipass": "^7.0.4",
|
||||
"path-scurry": "^1.10.2"
|
||||
},
|
||||
"bin": {
|
||||
"glob": "dist/esm/bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/config-file-ts/node_modules/minimatch": {
|
||||
"version": "9.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
|
||||
"integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/config-file-ts/node_modules/minipass": {
|
||||
"version": "7.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
|
||||
"integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/core-util-is": {
|
||||
@@ -1670,14 +1817,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dmg-builder": {
|
||||
"version": "24.9.1",
|
||||
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-24.9.1.tgz",
|
||||
"integrity": "sha512-huC+O6hvHd24Ubj3cy2GMiGLe2xGFKN3klqVMLAdcbB6SWMd1yPSdZvV8W1O01ICzCCRlZDHiv4VrNUgnPUfbQ==",
|
||||
"version": "24.13.3",
|
||||
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-24.13.3.tgz",
|
||||
"integrity": "sha512-rcJUkMfnJpfCboZoOOPf4L29TRtEieHNOeAbYPWPxlaBw/Z1RKrRA86dOI9rwaI4tQSc/RD82zTNHprfUHXsoQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"app-builder-lib": "24.9.1",
|
||||
"builder-util": "24.8.1",
|
||||
"builder-util-runtime": "9.2.3",
|
||||
"app-builder-lib": "24.13.3",
|
||||
"builder-util": "24.13.1",
|
||||
"builder-util-runtime": "9.2.4",
|
||||
"fs-extra": "^10.1.0",
|
||||
"iconv-lite": "^0.6.2",
|
||||
"js-yaml": "^4.1.0"
|
||||
@@ -1687,9 +1834,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dmg-builder/node_modules/builder-util-runtime": {
|
||||
"version": "9.2.3",
|
||||
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz",
|
||||
"integrity": "sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==",
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.4.tgz",
|
||||
"integrity": "sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4",
|
||||
@@ -1724,16 +1871,31 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "9.0.2",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz",
|
||||
"integrity": "sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv-expand": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
|
||||
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/eastasianwidth": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ejs": {
|
||||
"version": "3.1.9",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz",
|
||||
"integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==",
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
|
||||
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"jake": "^10.8.5"
|
||||
@@ -1746,14 +1908,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron": {
|
||||
"version": "28.2.1",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-28.2.1.tgz",
|
||||
"integrity": "sha512-wlzXf+OvOiVlBf9dcSeMMf7Q+N6DG+wtgFbMK0sA/JpIJcdosRbLMQwLg/LTwNVKIbmayqFLDp4FmmFkEMhbYA==",
|
||||
"version": "29.3.1",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-29.3.1.tgz",
|
||||
"integrity": "sha512-auge1/6RVqgUd6TgIq88wKdUCJi2cjESi3jy7d+6X4JzvBGprKBqMJ8JSSFpu/Px1YJrFUKAxfy6SC+TQf1uLw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@electron/get": "^2.0.0",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/node": "^20.9.0",
|
||||
"extract-zip": "^2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
@@ -1764,16 +1926,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-builder": {
|
||||
"version": "24.9.1",
|
||||
"resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-24.9.1.tgz",
|
||||
"integrity": "sha512-v7BuakDuY6sKMUYM8mfQGrwyjBpZ/ObaqnenU0H+igEL10nc6ht049rsCw2HghRBdEwJxGIBuzs3jbEhNaMDmg==",
|
||||
"version": "24.13.3",
|
||||
"resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-24.13.3.tgz",
|
||||
"integrity": "sha512-yZSgVHft5dNVlo31qmJAe4BVKQfFdwpRw7sFp1iQglDRCDD6r22zfRJuZlhtB5gp9FHUxCMEoWGq10SkCnMAIg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"app-builder-lib": "24.9.1",
|
||||
"builder-util": "24.8.1",
|
||||
"builder-util-runtime": "9.2.3",
|
||||
"app-builder-lib": "24.13.3",
|
||||
"builder-util": "24.13.1",
|
||||
"builder-util-runtime": "9.2.4",
|
||||
"chalk": "^4.1.2",
|
||||
"dmg-builder": "24.9.1",
|
||||
"dmg-builder": "24.13.3",
|
||||
"fs-extra": "^10.1.0",
|
||||
"is-ci": "^3.0.0",
|
||||
"lazy-val": "^1.0.5",
|
||||
@@ -1790,9 +1952,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-builder/node_modules/builder-util-runtime": {
|
||||
"version": "9.2.3",
|
||||
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz",
|
||||
"integrity": "sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==",
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.4.tgz",
|
||||
"integrity": "sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4",
|
||||
@@ -1803,14 +1965,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-publish": {
|
||||
"version": "24.8.1",
|
||||
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-24.8.1.tgz",
|
||||
"integrity": "sha512-IFNXkdxMVzUdweoLJNXSupXkqnvgbrn3J4vognuOY06LaS/m0xvfFYIf+o1CM8if6DuWYWoQFKPcWZt/FUjZPw==",
|
||||
"version": "24.13.1",
|
||||
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-24.13.1.tgz",
|
||||
"integrity": "sha512-2ZgdEqJ8e9D17Hwp5LEq5mLQPjqU3lv/IALvgp+4W8VeNhryfGhYEQC/PgDPMrnWUp+l60Ou5SJLsu+k4mhQ8A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/fs-extra": "^9.0.11",
|
||||
"builder-util": "24.8.1",
|
||||
"builder-util-runtime": "9.2.3",
|
||||
"builder-util": "24.13.1",
|
||||
"builder-util-runtime": "9.2.4",
|
||||
"chalk": "^4.1.2",
|
||||
"fs-extra": "^10.1.0",
|
||||
"lazy-val": "^1.0.5",
|
||||
@@ -1818,9 +1980,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-publish/node_modules/builder-util-runtime": {
|
||||
"version": "9.2.3",
|
||||
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz",
|
||||
"integrity": "sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==",
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.4.tgz",
|
||||
"integrity": "sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4",
|
||||
@@ -1858,6 +2020,15 @@
|
||||
"tiny-typed-emitter": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron/node_modules/@types/node": {
|
||||
"version": "20.12.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz",
|
||||
"integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
@@ -1952,15 +2123,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/execspawn": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/execspawn/-/execspawn-1.0.1.tgz",
|
||||
"integrity": "sha512-s2k06Jy9i8CUkYe0+DxRlvtkZoOkwwfhB+Xxo5HGUtrISVW2m98jO2tr67DGRFxZwkjQqloA3v/tNtjhBRBieg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"util-extend": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/expand-template": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
||||
@@ -2061,6 +2223,22 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/foreground-child": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
|
||||
"integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.0",
|
||||
"signal-exit": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
@@ -2571,12 +2749,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/isbinaryfile": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.0.tgz",
|
||||
"integrity": "sha512-UDdnyGvMajJUWCkib7Cei/dvyJrrvo4FIrsvSFWdPpXSUorzXrDJ0S+X5Q4ZlasfPjca4yqCNNsjbCeiy8FFeg==",
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.2.tgz",
|
||||
"integrity": "sha512-GvcjojwonMjWbTkfMpnVHVqXW/wKMYDfEpY94/8zy8HFMOqb/VL6oeONq9v87q4ttVlaTLnGXnJD4B5B1OTGIg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 14.0.0"
|
||||
"node": ">= 18.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/gjtorikian/"
|
||||
@@ -2588,6 +2766,24 @@
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/jackspeak": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
|
||||
"integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@isaacs/cliui": "^8.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@pkgjs/parseargs": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jake": {
|
||||
"version": "10.8.7",
|
||||
"resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz",
|
||||
@@ -2977,6 +3173,31 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-scurry": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz",
|
||||
"integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^10.2.0",
|
||||
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/path-scurry/node_modules/lru-cache": {
|
||||
"version": "10.2.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.1.tgz",
|
||||
"integrity": "sha512-tS24spDe/zXhWbNPErCHs/AGOzbKGHT+ybSBqmdLm8WZ1xXLWvH8Qn71QPAlqVhd0qUTWjy+Kl9JmISgDdEjsA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "14 || >=16.14"
|
||||
}
|
||||
},
|
||||
"node_modules/peek-readable": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz",
|
||||
@@ -3054,12 +3275,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prebuildify": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/prebuildify/-/prebuildify-6.0.0.tgz",
|
||||
"integrity": "sha512-DEvK4C3tcimIp7Pzqbs036n9i6CTKGp1XVEpMnr4wV3enKU5sBogPP+lP3KZw7993i42bXnsd5eIxAXQ566Cqw==",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/prebuildify/-/prebuildify-6.0.1.tgz",
|
||||
"integrity": "sha512-8Y2oOOateom/s8dNBsGIcnm6AxPmLH4/nanQzL5lQMU+sC0CMhzARZHizwr36pUPLdvBnOkCNQzxg4djuFSgIw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"execspawn": "^1.0.1",
|
||||
"minimist": "^1.2.5",
|
||||
"mkdirp-classic": "^0.5.3",
|
||||
"node-abi": "^3.3.0",
|
||||
@@ -3154,15 +3374,6 @@
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/read-config-file/node_modules/dotenv": {
|
||||
"version": "9.0.2",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz",
|
||||
"integrity": "sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
@@ -3238,21 +3449,6 @@
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"glob": "^7.1.3"
|
||||
},
|
||||
"bin": {
|
||||
"rimraf": "bin.js"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/roarr": {
|
||||
"version": "2.15.4",
|
||||
"resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz",
|
||||
@@ -3390,6 +3586,18 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/signal-exit": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-concat": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||
@@ -3470,12 +3678,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sodium-native": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.0.6.tgz",
|
||||
"integrity": "sha512-uYsyycwcz9kYDwpXxJmL2YZosynsxcP6RPySbARVJdC9uNDa2CMjzJ7/WsMMvThKgvAYsBWdZc7L/WSVj9lTcA==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.1.1.tgz",
|
||||
"integrity": "sha512-LXkAfRd4FHtkQS4X6g+nRcVaN7mWVNepV06phIsC6+IZFvGh1voW5TNQiQp2twVaMf05gZqQjuS+uWLM6gHhNQ==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"node-gyp-build": "^4.6.0"
|
||||
"node-gyp-build": "^4.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
@@ -3534,6 +3742,21 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width-cjs": {
|
||||
"name": "string-width",
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
@@ -3545,6 +3768,19 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi-cjs": {
|
||||
"name": "strip-ansi",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-json-comments": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||
@@ -3594,9 +3830,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz",
|
||||
"integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==",
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
|
||||
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chownr": "^2.0.0",
|
||||
@@ -3657,15 +3893,12 @@
|
||||
"integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA=="
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
|
||||
"integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
|
||||
"integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"rimraf": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.17.0"
|
||||
"node": ">=14.14"
|
||||
}
|
||||
},
|
||||
"node_modules/tmp-promise": {
|
||||
@@ -3754,30 +3987,33 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "4.9.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
||||
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
||||
"version": "5.4.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
|
||||
"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.2.0"
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "6.6.1",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.6.1.tgz",
|
||||
"integrity": "sha512-J0GaEp0ztu/grIE2Uq57AbK6TRb+bWbOlxu0POCzhFKA6LKbwSAev+hDQaQcgUUA9CPs8Ky+cauzTHnQrtAQEA==",
|
||||
"version": "6.14.1",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.14.1.tgz",
|
||||
"integrity": "sha512-mAel3i4BsYhkeVPXeIPXVGPJKeBzqCieZYoFsbWfUzd68JmHByhc1Plit5WlylxXFaGpgkZB8mExlxnt+Q1p7A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@fastify/busboy": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0"
|
||||
"node": ">=18.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
@@ -3806,12 +4042,6 @@
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||
},
|
||||
"node_modules/util-extend": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz",
|
||||
"integrity": "sha512-mLs5zAK+ctllYBj+iAQvlDCwoxU/WDOUaJkcFudeiAX6OajC6BKXJUa9a+tbtkC11dz2Ufb7h0lyvIOVn4LADA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/verror": {
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz",
|
||||
@@ -3857,6 +4087,24 @@
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs": {
|
||||
"name": "wrap-ansi",
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@notesnook/desktop",
|
||||
"productName": "Notesnook",
|
||||
"description": "Your private note taking space",
|
||||
"version": "3.0.14-beta",
|
||||
"version": "3.0.3",
|
||||
"appAppleId": "1544027013",
|
||||
"private": true,
|
||||
"main": "./dist/index.js",
|
||||
@@ -14,11 +14,11 @@
|
||||
"@notesnook/crypto": "file:../../packages/crypto",
|
||||
"@trpc/client": "10.38.3",
|
||||
"@trpc/server": "10.38.3",
|
||||
"better-sqlite3-multiple-ciphers": "^9.4.0",
|
||||
"better-sqlite3-multiple-ciphers": "^9.5.0",
|
||||
"electron-trpc": "0.5.2",
|
||||
"electron-updater": "6.1.4",
|
||||
"icojs": "^0.17.1",
|
||||
"sodium-native": "^4.0.6",
|
||||
"sodium-native": "^4.1.1",
|
||||
"typed-emitter": "^2.1.0",
|
||||
"yargs": "^17.6.2",
|
||||
"zod": "^3.21.4"
|
||||
@@ -27,12 +27,13 @@
|
||||
"@types/node": "18.16.1",
|
||||
"@types/yargs": "^17.0.24",
|
||||
"chokidar": "^3.5.3",
|
||||
"electron": "^28.2.1",
|
||||
"electron-builder": "^24.9.1",
|
||||
"electron": "^29.3.1",
|
||||
"electron-builder": "^24.13.3",
|
||||
"esbuild": "^0.20.0",
|
||||
"prebuildify": "^6.0.0",
|
||||
"node-gyp-build": "^4.8.0",
|
||||
"prebuildify": "^6.0.1",
|
||||
"tree-kill": "^1.2.2",
|
||||
"undici": "^6.6.1"
|
||||
"undici": "^6.14.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"dmg-license": "^1.0.11"
|
||||
@@ -56,8 +57,8 @@
|
||||
"productName": "Notesnook",
|
||||
"copyright": "Copyright © 2023 Streetwriters (Private) Limited",
|
||||
"artifactName": "notesnook_${os}_${arch}.${ext}",
|
||||
"asar": false,
|
||||
"generateUpdatesFilesForAllChannels": true,
|
||||
"asar": false,
|
||||
"files": [
|
||||
"!*.chunk.js.map",
|
||||
"!*.chunk.js.LICENSE.txt",
|
||||
@@ -220,8 +221,7 @@
|
||||
{
|
||||
"provider": "github",
|
||||
"repo": "notesnook",
|
||||
"owner": "streetwriters",
|
||||
"channel": "beta"
|
||||
"owner": "streetwriters"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
diff --git a/node_modules/prebuildify/index.js b/node_modules/prebuildify/index.js
|
||||
index ca9829a..7729aaf 100644
|
||||
--- a/node_modules/prebuildify/index.js
|
||||
+++ b/node_modules/prebuildify/index.js
|
||||
@@ -1,5 +1,4 @@
|
||||
var proc = require('child_process')
|
||||
-var execspawn = require('execspawn')
|
||||
var os = require('os')
|
||||
var path = require('path')
|
||||
var fs = require('fs')
|
||||
@@ -179,11 +178,12 @@ function copySharedLibs (builds, folder, opts, cb) {
|
||||
function run (cmd, opts, cb) {
|
||||
if (!cmd) return cb()
|
||||
|
||||
- var child = execspawn(cmd, {
|
||||
+ var child = proc.spawn(cmd, [], {
|
||||
cwd: opts.cwd,
|
||||
env: opts.env,
|
||||
stdio: 'inherit',
|
||||
- shell: opts.shell
|
||||
+ shell: opts.shell || true,
|
||||
+ windowsHide: true
|
||||
})
|
||||
|
||||
child.on('exit', function (code) {
|
||||
@@ -230,6 +230,8 @@ function build (target, runtime, opts, cb) {
|
||||
var child = proc.spawn(opts.nodeGyp, args, {
|
||||
cwd: opts.cwd,
|
||||
env: opts.env,
|
||||
+ shell: opts.shell,
|
||||
+ windowsHide: true,
|
||||
stdio: opts.quiet ? 'ignore' : 'inherit'
|
||||
})
|
||||
|
||||
@@ -266,7 +268,11 @@ function strip (file, opts, cb) {
|
||||
if (!opts.strip || (platform !== 'darwin' && platform !== 'linux')) return cb()
|
||||
|
||||
var args = platform === 'darwin' ? [file, '-Sx'] : [file, '--strip-all']
|
||||
- var child = proc.spawn(opts.stripBin, args, { stdio: 'ignore' })
|
||||
+ var child = proc.spawn(opts.stripBin, args, {
|
||||
+ stdio: 'ignore',
|
||||
+ shell: opts.shell,
|
||||
+ windowsHide: true
|
||||
+ })
|
||||
|
||||
child.on('exit', function (code) {
|
||||
if (code) return cb(spawnError(opts.stripBin, code))
|
||||
@@ -300,7 +306,11 @@ function npmbin (name) {
|
||||
}
|
||||
|
||||
function shell () {
|
||||
- return os.platform() === 'android' ? 'sh' : undefined
|
||||
+ switch (os.platform()) {
|
||||
+ case 'win32': return true
|
||||
+ case 'android': return 'sh'
|
||||
+ default: return undefined
|
||||
+ }
|
||||
}
|
||||
|
||||
function resolveTargets (targets, all, napi, electronCompat) {
|
||||
@@ -19,27 +19,32 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import path from "path";
|
||||
import fs, { readFile, writeFile } from "fs/promises";
|
||||
import { existsSync } from "fs";
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import yargs from "yargs-parser";
|
||||
import os from "os";
|
||||
import * as childProcess from "child_process";
|
||||
import { fileURLToPath } from "url";
|
||||
import packageJson from "../package.json" with {type: "json"};
|
||||
|
||||
const args = yargs(process.argv);
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const packageJson = JSON.parse(
|
||||
readFileSync(path.join(__dirname, "..", "package.json"), "utf-8")
|
||||
);
|
||||
|
||||
const webAppPath = path.resolve(path.join(__dirname, "..", "..", "web"));
|
||||
|
||||
await fs.rm("./build/", { force: true, recursive: true });
|
||||
|
||||
if (args.rebuild || !existsSync(path.join(webAppPath, "build"))) {
|
||||
await exec(`cd ${webAppPath} && npm run build:desktop`);
|
||||
await exec(
|
||||
"yarn nx build:desktop @notesnook/web",
|
||||
path.join(__dirname, "..", "..", "..")
|
||||
);
|
||||
}
|
||||
|
||||
// temporary until there's support for prebuilt binaries for windows ARM & linux ARM
|
||||
await patchBetterSQLite3();
|
||||
// temporary until there's support for prebuilt binaries for linux ARM
|
||||
if (os.platform() === "linux") await patchBetterSQLite3();
|
||||
|
||||
if (os.platform() === "win32")
|
||||
await exec(
|
||||
@@ -53,15 +58,15 @@ await fs.cp(path.join(webAppPath, "build"), "build", {
|
||||
});
|
||||
|
||||
if (args.variant === "mas") {
|
||||
await exec(`npm run bundle:mas`);
|
||||
await exec(`yarn run bundle:mas`);
|
||||
} else {
|
||||
await exec(`npm run bundle`);
|
||||
await exec(`yarn run bundle`);
|
||||
}
|
||||
|
||||
await exec(`npx tsc`);
|
||||
await exec(`yarn tsc`);
|
||||
|
||||
if (args.run) {
|
||||
await exec(`npx electron-builder --dir --x64`);
|
||||
await exec(`yarn electron-builder --dir --x64`);
|
||||
if (process.platform === "win32") {
|
||||
await exec(`.\\output\\win-unpacked\\Notesnook.exe`);
|
||||
} else if (process.platform === "darwin") {
|
||||
@@ -89,7 +94,7 @@ async function patchBetterSQLite3() {
|
||||
);
|
||||
const json = JSON.parse(await readFile(jsonPath, "utf-8"));
|
||||
|
||||
json.version = "9.4.1";
|
||||
json.version = "9.5.1";
|
||||
json.homepage = "https://github.com/thecodrr/better-sqlite3-multiple-ciphers";
|
||||
json.repository.url =
|
||||
"git://github.com/thecodrr/better-sqlite3-multiple-ciphers.git";
|
||||
|
||||
@@ -53,11 +53,11 @@ async function onChange(first) {
|
||||
if (first) {
|
||||
await fs.rm("./build/", { force: true, recursive: true });
|
||||
|
||||
await exec("npx electron-builder install-app-deps");
|
||||
await exec("yarn electron-builder install-app-deps");
|
||||
}
|
||||
|
||||
await exec(`npm run bundle`);
|
||||
execAsync(`npx`, [`tsc`]);
|
||||
await exec(`yarn run bundle`);
|
||||
execAsync(`yarn`, [`tsc`]);
|
||||
|
||||
if (await isBundleSame()) {
|
||||
console.log("Bundle is same. Doing nothing.");
|
||||
@@ -66,7 +66,7 @@ async function onChange(first) {
|
||||
|
||||
if (first) {
|
||||
await spawnAndWaitUntil(
|
||||
["npx", "nx", "start:desktop", "@notesnook/web"],
|
||||
["yarn", "nx", "start:desktop", "@notesnook/web"],
|
||||
path.join(__dirname, "..", "..", ".."),
|
||||
(data) => data.includes("Network: use --host to expose")
|
||||
);
|
||||
@@ -78,7 +78,7 @@ async function onChange(first) {
|
||||
}
|
||||
|
||||
execAsync(
|
||||
"npx",
|
||||
"yarn",
|
||||
["electron", path.join("build", "electron.js")],
|
||||
true,
|
||||
cleanup
|
||||
|
||||
@@ -33,7 +33,7 @@ module.exports = async function (configuration) {
|
||||
const DescriptionUrl = "https://notesnook.com/";
|
||||
const FilesCatalog = createCatalog(configuration.path);
|
||||
|
||||
const command = `Invoke-AzureCodeSigning -Endpoint "${Endpoint}" -CodeSigningAccountName "${CodeSigningAccountName}" -CertificateProfileName "${CertificateProfileName}" -FileDigest "${FileDigest}" -TimestampRfc3161 "${TimestampRfc3161}" -TimestampDigest "${TimestampDigest}" -Description "${Description}" -DescriptionUrl "${DescriptionUrl}" -FilesCatalog "${FilesCatalog}"`;
|
||||
const command = `Invoke-TrustedSigning -Endpoint "${Endpoint}" -CodeSigningAccountName "${CodeSigningAccountName}" -CertificateProfileName "${CertificateProfileName}" -FileDigest "${FileDigest}" -TimestampRfc3161 "${TimestampRfc3161}" -TimestampDigest "${TimestampDigest}" -Description "${Description}" -DescriptionUrl "${DescriptionUrl}" -FilesCatalog "${FilesCatalog}"`;
|
||||
|
||||
console.debug("Signing", configuration.path, "using command", command);
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ import { AssetManager } from "../utils/asset-manager";
|
||||
import { isFlatpak } from "../utils";
|
||||
import { setupDesktopIntegration } from "../utils/desktop-integration";
|
||||
import { rm } from "fs/promises";
|
||||
import { disableCustomDns, enableCustomDns } from "../utils/custom-dns";
|
||||
|
||||
const t = initTRPC.create();
|
||||
|
||||
@@ -55,6 +56,15 @@ export const osIntegrationRouter = t.router({
|
||||
config.zoomFactor = factor;
|
||||
}),
|
||||
|
||||
customDns: t.procedure.query(() => config.customDns),
|
||||
setCustomDns: t.procedure
|
||||
.input(z.boolean().optional())
|
||||
.mutation(({ input: customDns }) => {
|
||||
if (customDns) enableCustomDns();
|
||||
else disableCustomDns();
|
||||
config.customDns = !!customDns;
|
||||
}),
|
||||
|
||||
proxyRules: t.procedure.query(() => config.proxyRules),
|
||||
setProxyRules: t.procedure
|
||||
.input(z.string().optional())
|
||||
|
||||
@@ -35,6 +35,7 @@ import path from "path";
|
||||
import { bringToFront } from "./utils/bring-to-front";
|
||||
import { bridge } from "./api/bridge";
|
||||
import { setupDesktopIntegration } from "./utils/desktop-integration";
|
||||
import { disableCustomDns, enableCustomDns } from "./utils/custom-dns";
|
||||
|
||||
// only run a single instance
|
||||
if (!MAC_APP_STORE && !app.requestSingleInstanceLock()) {
|
||||
@@ -151,14 +152,8 @@ async function createWindow() {
|
||||
app.once("ready", async () => {
|
||||
console.info("App ready. Opening window.");
|
||||
|
||||
app.configureHostResolver({
|
||||
secureDnsServers: [
|
||||
"https://mozilla.cloudflare-dns.com/dns-query",
|
||||
"https://dns.quad9.net/dns-query"
|
||||
],
|
||||
enableBuiltInResolver: true,
|
||||
secureDnsMode: "automatic"
|
||||
});
|
||||
if (config.customDns) enableCustomDns();
|
||||
else disableCustomDns();
|
||||
|
||||
if (!isDevelopment()) registerProtocol();
|
||||
await createWindow();
|
||||
|
||||
@@ -43,6 +43,7 @@ export const config = {
|
||||
theme: nativeTheme.themeSource,
|
||||
automaticUpdates: true,
|
||||
proxyRules: "",
|
||||
customDns: true,
|
||||
|
||||
backgroundColor: nativeTheme.themeSource === "dark" ? "#0f0f0f" : "#ffffff",
|
||||
windowControlsIconColor:
|
||||
|
||||
37
apps/desktop/src/utils/custom-dns.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { app } from "electron";
|
||||
|
||||
export function enableCustomDns() {
|
||||
app.configureHostResolver({
|
||||
secureDnsServers: [
|
||||
"https://mozilla.cloudflare-dns.com/dns-query",
|
||||
"https://dns.quad9.net/dns-query"
|
||||
],
|
||||
enableBuiltInResolver: true
|
||||
});
|
||||
}
|
||||
|
||||
export function disableCustomDns() {
|
||||
app.configureHostResolver({
|
||||
secureDnsServers: [],
|
||||
enableBuiltInResolver: true
|
||||
});
|
||||
}
|
||||
@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { protocol } from "electron";
|
||||
import { net, protocol } from "electron";
|
||||
import { isDevelopment } from "./index";
|
||||
import { createReadStream } from "fs";
|
||||
import { extname, normalize } from "path";
|
||||
@@ -62,26 +62,7 @@ function registerProtocol() {
|
||||
headers: { "Content-Type": extensionToMimeType[fileExtension] }
|
||||
});
|
||||
} else {
|
||||
if (request.headers.has("X-Content-Length")) {
|
||||
request.headers.set(
|
||||
"Content-Length",
|
||||
request.headers.get("X-Content-Length") || "0"
|
||||
);
|
||||
request.headers.delete("X-Content-Length");
|
||||
}
|
||||
const headers = Object.fromEntries(request.headers.entries());
|
||||
|
||||
return await fetch(request.url, {
|
||||
signal: request.signal,
|
||||
mode: request.mode,
|
||||
headers,
|
||||
method: request.method,
|
||||
body: request.body,
|
||||
credentials: request.credentials,
|
||||
referrer: (request as any).referrer,
|
||||
duplex: "half",
|
||||
redirect: "manual"
|
||||
});
|
||||
return net.fetch(request, { bypassCustomProtocolHandlers: true });
|
||||
}
|
||||
});
|
||||
console.info(`${SCHEME} protocol inteception "successful"`);
|
||||
|
||||
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 108 KiB |
@@ -218,12 +218,7 @@ export default async function downloadAttachment(
|
||||
}
|
||||
|
||||
if (options.base64 || options.text) {
|
||||
console.log(
|
||||
"starting decrypt base64 file...",
|
||||
options.base64,
|
||||
options.text,
|
||||
attachment.hash
|
||||
);
|
||||
DatabaseLogger.log(`Starting to decrypt... hash: ${attachment.hash}`);
|
||||
return await db.attachments.read(
|
||||
attachment.hash,
|
||||
options.base64 ? "base64" : "text"
|
||||
@@ -292,7 +287,7 @@ export default async function downloadAttachment(
|
||||
|
||||
return fileUri;
|
||||
} catch (e) {
|
||||
console.log("download attachment error: ", e);
|
||||
DatabaseLogger.error(e);
|
||||
if (attachment.dateUploaded) {
|
||||
RNFetchBlob.fs
|
||||
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}`)
|
||||
|
||||
@@ -22,21 +22,24 @@ import NetInfo from "@react-native-community/netinfo";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import { ToastManager } from "../../services/event-manager";
|
||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { db } from "../database";
|
||||
import { DatabaseLogger, db } from "../database";
|
||||
import { cacheDir, fileCheck } from "./utils";
|
||||
import { createCacheDir, exists } from "./io";
|
||||
|
||||
export async function downloadFile(filename, data, cancelToken) {
|
||||
if (!data) return false;
|
||||
if (!data) {
|
||||
DatabaseLogger.log(`Error downloading file: ${filename}, reason: No data`);
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log("Downloading", filename);
|
||||
DatabaseLogger.log(`Downloading ${filename}`);
|
||||
await createCacheDir();
|
||||
let { url, headers } = data;
|
||||
let path = `${cacheDir}/${filename}`;
|
||||
|
||||
try {
|
||||
if (await exists(filename)) {
|
||||
console.log("Exists already", filename);
|
||||
DatabaseLogger.log(`File Exists already: ${filename}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -44,13 +47,24 @@ export async function downloadFile(filename, data, cancelToken) {
|
||||
method: "GET",
|
||||
headers
|
||||
});
|
||||
if (!res.ok)
|
||||
|
||||
if (!res.ok) {
|
||||
DatabaseLogger.log(
|
||||
`Error downloading file: ${filename}, ${res.status}, ${res.statusText}, reason: Unable to resolve download url`
|
||||
);
|
||||
throw new Error(`${res.status}: Unable to resolve download url`);
|
||||
}
|
||||
|
||||
const downloadUrl = await res.text();
|
||||
|
||||
if (!downloadUrl) throw new Error("Unable to resolve download url");
|
||||
if (!downloadUrl) {
|
||||
DatabaseLogger.log(
|
||||
`Error downloading file: ${filename}, reason: Unable to resolve download url`
|
||||
);
|
||||
throw new Error("Unable to resolve download url");
|
||||
}
|
||||
let totalSize = 0;
|
||||
console.log("Download starting");
|
||||
DatabaseLogger.log(`Download starting: ${filename}`);
|
||||
let request = RNFetchBlob.config({
|
||||
path: path,
|
||||
IOSBackgroundTask: true
|
||||
@@ -61,13 +75,15 @@ export async function downloadFile(filename, data, cancelToken) {
|
||||
.getState()
|
||||
.setProgress(0, total, filename, recieved, "download");
|
||||
totalSize = total;
|
||||
console.log("downloading: ", recieved, total);
|
||||
DatabaseLogger.log(`Downloading: ${filename}, ${recieved}/${total}`);
|
||||
});
|
||||
|
||||
cancelToken.cancel = () => {
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
request.cancel();
|
||||
DatabaseLogger.log(`Download cancelled: ${filename}`);
|
||||
};
|
||||
|
||||
let response = await request;
|
||||
await fileCheck(response, totalSize);
|
||||
let status = response.info().status;
|
||||
@@ -91,7 +107,10 @@ export async function downloadFile(filename, data, cancelToken) {
|
||||
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
RNFetchBlob.fs.unlink(path).catch(console.log);
|
||||
console.log("Download file error:", e, url, headers);
|
||||
DatabaseLogger.error(e, {
|
||||
url,
|
||||
headers
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -100,18 +119,17 @@ export async function getUploadedFileSize(hash) {
|
||||
try {
|
||||
const url = `${hosts.API_HOST}/s3?name=${hash}`;
|
||||
const token = await db.tokenManager.getAccessToken();
|
||||
|
||||
const attachmentInfo = await fetch(url, {
|
||||
method: "HEAD",
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
});
|
||||
|
||||
const contentLength = parseInt(
|
||||
attachmentInfo.headers?.get("content-length")
|
||||
);
|
||||
return isNaN(contentLength) ? 0 : contentLength;
|
||||
} catch (e) {
|
||||
return 0;
|
||||
DatabaseLogger.error(e);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +143,9 @@ export async function checkAttachment(hash) {
|
||||
|
||||
try {
|
||||
const size = await getUploadedFileSize(hash);
|
||||
if (size <= 0) return { failed: "File length is 0." };
|
||||
if (size === -1) return { success: true };
|
||||
|
||||
if (size === 0) return { failed: "File length is 0." };
|
||||
} catch (e) {
|
||||
return { failed: e?.message };
|
||||
}
|
||||
|
||||
@@ -21,12 +21,12 @@ import Sodium from "@ammarahmed/react-native-sodium";
|
||||
import { Platform } from "react-native";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import { IOS_APPGROUPID } from "../../utils/constants";
|
||||
import { db } from "../database";
|
||||
import { DatabaseLogger, db } from "../database";
|
||||
import { cacheDir, cacheDirOld, getRandomId } from "./utils";
|
||||
|
||||
export async function readEncrypted(filename, key, cipherData) {
|
||||
await migrateFilesFromCache();
|
||||
console.log("Read encrypted file...");
|
||||
DatabaseLogger.log("Read encrypted file...");
|
||||
let path = `${cacheDir}/${filename}`;
|
||||
|
||||
try {
|
||||
@@ -34,8 +34,6 @@ export async function readEncrypted(filename, key, cipherData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const attachment = await db.attachments.attachment(filename);
|
||||
console.log("decrypting....");
|
||||
let output = await Sodium.decryptFile(
|
||||
key,
|
||||
{
|
||||
@@ -45,12 +43,13 @@ export async function readEncrypted(filename, key, cipherData) {
|
||||
},
|
||||
cipherData.outputType === "base64" ? "base64" : "text"
|
||||
);
|
||||
console.log("file decrypted...", attachment?.mimeType);
|
||||
|
||||
DatabaseLogger.log("File decrypted...");
|
||||
|
||||
return output;
|
||||
} catch (e) {
|
||||
RNFetchBlob.fs.unlink(path).catch(console.log);
|
||||
console.log("readEncrypted", e);
|
||||
DatabaseLogger.error(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -128,7 +127,7 @@ export async function clearFileStorage() {
|
||||
export async function createCacheDir() {
|
||||
if (!(await RNFetchBlob.fs.exists(cacheDir))) {
|
||||
await RNFetchBlob.fs.mkdir(cacheDir);
|
||||
console.log("Cache directory created");
|
||||
DatabaseLogger.log("Cache directory created");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,6 +187,9 @@ export async function exists(filename) {
|
||||
);
|
||||
|
||||
if (stat.size !== expectedFileSize) {
|
||||
DatabaseLogger.log(
|
||||
`File size mismatch: ${filename}, expected: ${expectedFileSize}, actual: ${stat.size}`
|
||||
);
|
||||
RNFetchBlob.fs
|
||||
.unlink(existsInAppGroup ? appGroupPath : path)
|
||||
.catch(console.log);
|
||||
|
||||
@@ -25,6 +25,7 @@ import { isImage, isDocument } from "@notesnook/core/dist/utils/filename";
|
||||
import { Platform } from "react-native";
|
||||
import { IOS_APPGROUPID } from "../../utils/constants";
|
||||
import { createCacheDir } from "./io";
|
||||
import { getUploadedFileSize } from "./download";
|
||||
|
||||
export async function uploadFile(filename, data, cancelToken) {
|
||||
if (!data) return false;
|
||||
@@ -33,6 +34,18 @@ export async function uploadFile(filename, data, cancelToken) {
|
||||
DatabaseLogger.info(`Preparing to upload file: ${filename}`);
|
||||
|
||||
try {
|
||||
const uploadedFileSize = await getUploadedFileSize(filename);
|
||||
if (uploadedFileSize === -1) {
|
||||
DatabaseLogger.log("Upload verification failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const isUploaded = uploadedFileSize !== 0;
|
||||
if (isUploaded) {
|
||||
DatabaseLogger.log(`File ${filename} is already uploaded.`);
|
||||
return true;
|
||||
}
|
||||
|
||||
let res = await fetch(url, {
|
||||
method: "PUT",
|
||||
headers
|
||||
@@ -50,7 +63,15 @@ export async function uploadFile(filename, data, cancelToken) {
|
||||
let exists = await RNFetchBlob.fs.exists(uploadFilePath);
|
||||
if (!exists && Platform.OS === "ios") {
|
||||
uploadFilePath = appGroupPath;
|
||||
exists = await RNFetchBlob.fs.exists(uploadFilePath);
|
||||
}
|
||||
|
||||
if (!exists) {
|
||||
throw new Error(
|
||||
`Trying to upload file at path ${uploadFilePath} that doest not exist.`
|
||||
);
|
||||
}
|
||||
|
||||
DatabaseLogger.info(`Starting upload: ${filename}`);
|
||||
|
||||
let request = RNFetchBlob.config({
|
||||
|
||||
@@ -20,15 +20,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import React from "react";
|
||||
import { Linking, View } from "react-native";
|
||||
//import SettingsBackupAndRestore from '../../screens/settings/backup-restore';
|
||||
import { eSendEvent, presentSheet } from "../../services/event-manager";
|
||||
import Sync from "../../services/sync";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { eCloseAnnouncementDialog, eCloseSheet } from "../../utils/events";
|
||||
import { eSendEvent, presentSheet } from "../../services/event-manager";
|
||||
import { eCloseAnnouncementDialog } from "../../utils/events";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
import { PricingPlans } from "../premium/pricing-plans";
|
||||
import SheetProvider from "../sheet-provider";
|
||||
import { Progress } from "../sheets/progress";
|
||||
import { Button } from "../ui/button";
|
||||
import { allowedOnPlatform, getStyle } from "./functions";
|
||||
|
||||
@@ -56,13 +54,6 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
|
||||
/>
|
||||
)
|
||||
});
|
||||
} else if (item.type === "force-sync") {
|
||||
eSendEvent(eCloseSheet);
|
||||
await sleep(300);
|
||||
Progress.present();
|
||||
Sync.run("global", true, true, () => {
|
||||
eSendEvent(eCloseSheet);
|
||||
});
|
||||
}
|
||||
};
|
||||
return (
|
||||
|
||||
@@ -56,6 +56,7 @@ import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { useTabStore } from "../../screens/editor/tiptap/use-tab-store";
|
||||
import { editorController } from "../../screens/editor/tiptap/utils";
|
||||
import SheetProvider from "../sheet-provider";
|
||||
|
||||
const Actions = ({
|
||||
attachment,
|
||||
@@ -218,10 +219,11 @@ const Actions = ({
|
||||
}}
|
||||
>
|
||||
<Dialog context={contextId} />
|
||||
<SheetProvider context={contextId} />
|
||||
<View
|
||||
style={{
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.secondary.background,
|
||||
borderBottomColor: colors.primary.border,
|
||||
marginBottom: notes && notes.length > 0 ? 0 : 12
|
||||
}}
|
||||
>
|
||||
@@ -238,36 +240,20 @@ const Actions = ({
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
marginBottom: 10,
|
||||
paddingHorizontal: 12
|
||||
paddingHorizontal: 12,
|
||||
marginTop: 6,
|
||||
gap: 10
|
||||
}}
|
||||
>
|
||||
<Paragraph
|
||||
size={SIZE.xs}
|
||||
style={{
|
||||
marginRight: 10
|
||||
}}
|
||||
color={colors.secondary.paragraph}
|
||||
>
|
||||
{attachment.type}
|
||||
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
|
||||
{attachment.mimeType}
|
||||
</Paragraph>
|
||||
<Paragraph
|
||||
style={{
|
||||
marginRight: 10
|
||||
}}
|
||||
size={SIZE.xs}
|
||||
color={colors.secondary.paragraph}
|
||||
>
|
||||
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
|
||||
{formatBytes(attachment.size)}
|
||||
</Paragraph>
|
||||
|
||||
{notes.length ? (
|
||||
<Paragraph
|
||||
style={{
|
||||
marginRight: 10
|
||||
}}
|
||||
size={SIZE.xs}
|
||||
color={colors.secondary.paragraph}
|
||||
>
|
||||
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
|
||||
{notes.length} note
|
||||
{notes.length > 1 ? "s" : ""}
|
||||
</Paragraph>
|
||||
@@ -295,7 +281,7 @@ const Actions = ({
|
||||
<View
|
||||
style={{
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.secondary.background,
|
||||
borderBottomColor: colors.primary.border,
|
||||
marginBottom: 12,
|
||||
paddingVertical: 12
|
||||
}}
|
||||
@@ -366,7 +352,7 @@ const Actions = ({
|
||||
{failed ? (
|
||||
<Notice
|
||||
type="alert"
|
||||
text={`File check failed with error: ${attachment.failed} Try reuploading the file to fix the issue.`}
|
||||
text={`File check failed: ${failed} Try reuploading the file to fix the issue.`}
|
||||
size="small"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -177,7 +177,7 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
|
||||
case "audio":
|
||||
items = note
|
||||
? db.attachments.ofNote(note.id, "audio")
|
||||
: db.attachments.all;
|
||||
: db.attachments.audios;
|
||||
break;
|
||||
case "documents":
|
||||
items = note
|
||||
|
||||
@@ -67,7 +67,7 @@ export const Login = ({ changeMode }) => {
|
||||
Progress.present();
|
||||
setTimeout(() => {
|
||||
if (!useUserStore.getState().syncing) {
|
||||
Sync.run("global", false, true);
|
||||
Sync.run("global", false, "full");
|
||||
}
|
||||
}, 5000);
|
||||
});
|
||||
@@ -296,11 +296,6 @@ export const Login = ({ changeMode }) => {
|
||||
</Paragraph>
|
||||
</TouchableOpacity>
|
||||
) : null}
|
||||
|
||||
<Notice
|
||||
type="alert"
|
||||
text="After logging in from V3 beta, your account data will be migrated and you won't be able to use V2 clients to sync your notes."
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -112,7 +112,7 @@ export const SessionExpired = () => {
|
||||
if (!res) throw new Error("no token found");
|
||||
if (db.tokenManager._isTokenExpired(res))
|
||||
throw new Error("token expired");
|
||||
Sync.run("global", false, true, async (complete) => {
|
||||
Sync.run("global", false, "full", async (complete) => {
|
||||
if (!complete) {
|
||||
let user = await db.user.getUser();
|
||||
if (!user) return;
|
||||
|
||||
@@ -21,8 +21,10 @@ import React, { PropsWithChildren, useEffect, useRef, useState } from "react";
|
||||
import { Dimensions, Platform, View, useWindowDimensions } from "react-native";
|
||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||
import useKeyboard from "../../hooks/use-keyboard";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
|
||||
export const Container = ({ children }: PropsWithChildren) => {
|
||||
const { colors } = useThemeColors();
|
||||
const insets = useGlobalSafeAreaInsets();
|
||||
const keyboard = useKeyboard();
|
||||
const [height, setHeight] = useState(0);
|
||||
@@ -43,7 +45,8 @@ export const Container = ({ children }: PropsWithChildren) => {
|
||||
paddingTop: Platform.OS === "android" ? 0 : insets.top,
|
||||
paddingBottom: Platform.OS === "android" ? 0 : insets.bottom,
|
||||
height: height || "100%",
|
||||
width: "100%"
|
||||
width: "100%",
|
||||
backgroundColor: colors.primary.background
|
||||
}}
|
||||
onLayout={(event) => {
|
||||
const height = event.nativeEvent.layout.height;
|
||||
|
||||
@@ -45,8 +45,8 @@ const DialogButtons = ({
|
||||
{
|
||||
backgroundColor: colors.secondary.background,
|
||||
height: 60,
|
||||
borderBottomRightRadius: 10,
|
||||
borderBottomLeftRadius: 10,
|
||||
borderBottomRightRadius: 5,
|
||||
borderBottomLeftRadius: 5,
|
||||
paddingHorizontal: 12,
|
||||
borderTopWidth: 0.7,
|
||||
borderTopColor: getColorLinearShade(
|
||||
|
||||
@@ -36,6 +36,7 @@ import DialogButtons from "./dialog-buttons";
|
||||
import DialogHeader from "./dialog-header";
|
||||
import { useCallback } from "react";
|
||||
import { Button } from "../ui/button";
|
||||
import { getContainerBorder } from "../../utils/colors";
|
||||
|
||||
export const Dialog = ({ context = "global" }) => {
|
||||
const { colors } = useThemeColors();
|
||||
@@ -121,7 +122,8 @@ export const Dialog = ({ context = "global" }) => {
|
||||
maxHeight: 450,
|
||||
borderRadius: 5,
|
||||
backgroundColor: colors.primary.background,
|
||||
paddingTop: 12
|
||||
paddingTop: 12,
|
||||
...getContainerBorder(colors.primary.border, 0.5)
|
||||
};
|
||||
|
||||
return visible ? (
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
setAppLockVerificationCipher,
|
||||
validateAppLockPassword
|
||||
} from "../../../common/database/encryption";
|
||||
import BiometicService from "../../../services/biometrics";
|
||||
import BiometricService from "../../../services/biometrics";
|
||||
import { DDS } from "../../../services/device-detection";
|
||||
import {
|
||||
ToastManager,
|
||||
@@ -323,7 +323,7 @@ export const AppLockPassword = () => {
|
||||
SettingsService.setProperty("appLockHasPasswordSecurity", false);
|
||||
|
||||
if (
|
||||
!(await BiometicService.isBiometryAvailable()) ||
|
||||
!(await BiometricService.isBiometryAvailable()) ||
|
||||
SettingsService.getProperty("biometricsAuthEnabled") === false
|
||||
) {
|
||||
SettingsService.setProperty("appLockEnabled", false);
|
||||
|
||||
@@ -17,8 +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 React, { useState } from "react";
|
||||
import { Image, ScrollView, TouchableOpacity, View } from "react-native";
|
||||
import { ImagePickerResponse } from "react-native-image-picker";
|
||||
import {
|
||||
Image,
|
||||
Platform,
|
||||
ScrollView,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from "react-native";
|
||||
import { Image as ImageType } from "react-native-image-crop-picker";
|
||||
import { useThemeColors } from "../../../../../../packages/theme/dist";
|
||||
import { presentSheet } from "../../../services/event-manager";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
@@ -32,7 +38,7 @@ export default function AttachImage({
|
||||
onAttach,
|
||||
close
|
||||
}: {
|
||||
response: ImagePickerResponse;
|
||||
response: ImageType[];
|
||||
onAttach: ({ compress }: { compress: boolean }) => void;
|
||||
close: ((ctx?: string | undefined) => void) | undefined;
|
||||
}) {
|
||||
@@ -58,14 +64,17 @@ export default function AttachImage({
|
||||
}}
|
||||
>
|
||||
<Paragraph style={{ color: colors.primary.paragraph, marginBottom: 6 }}>
|
||||
Attaching {response.assets?.length} image(s):
|
||||
Attaching {response?.length} image(s):
|
||||
</Paragraph>
|
||||
<ScrollView horizontal>
|
||||
{response.assets?.map((item) => (
|
||||
<TouchableOpacity key={item.fileName} activeOpacity={0.9}>
|
||||
{response?.map?.((item) => (
|
||||
<TouchableOpacity key={item.filename} activeOpacity={0.9}>
|
||||
<Image
|
||||
source={{
|
||||
uri: item.uri
|
||||
uri:
|
||||
Platform.OS === "ios"
|
||||
? item.sourceURL || item.path
|
||||
: item.path
|
||||
}}
|
||||
style={{
|
||||
width: 100,
|
||||
@@ -142,7 +151,7 @@ export default function AttachImage({
|
||||
|
||||
<Button
|
||||
title={`${
|
||||
(response.assets?.length || 0) > 1 ? "Attach Images" : "Attach Image"
|
||||
(response?.length || 0) > 1 ? "Attach Images" : "Attach Image"
|
||||
}`}
|
||||
type="accent"
|
||||
width="100%"
|
||||
@@ -156,10 +165,16 @@ export default function AttachImage({
|
||||
);
|
||||
}
|
||||
|
||||
AttachImage.present = (response: ImagePickerResponse) => {
|
||||
return new Promise((resolve) => {
|
||||
AttachImage.present = (response: ImageType[], context?: string) => {
|
||||
return new Promise<
|
||||
| {
|
||||
compress: boolean;
|
||||
}
|
||||
| undefined
|
||||
>((resolve) => {
|
||||
let resolved = false;
|
||||
presentSheet({
|
||||
context: context,
|
||||
component: (ref, close, update) => (
|
||||
<AttachImage
|
||||
response={response}
|
||||
|
||||
@@ -128,7 +128,7 @@ export class VaultDialog extends Component {
|
||||
: this.state.copyNote
|
||||
? "Unlock note to copy it. If biometrics are not working, you can enter device pin to unlock vault."
|
||||
: this.state.goToEditor
|
||||
? "Unlock note to open it in editor. If biometrics are not working, you can enter device pin to unlock vault."
|
||||
? "Unlock note to open it in editor."
|
||||
: "Enter vault password to unlock note. If biometrics are not working, you can enter device pin to unlock vault."
|
||||
: "Enter vault password to lock note. If biometrics are not working, you can enter device pin to lock note.");
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import { eScrollEvent } from "../../utils/events";
|
||||
import { LeftMenus } from "./left-menus";
|
||||
import { RightMenus } from "./right-menus";
|
||||
import { Title } from "./title";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
|
||||
type HeaderRightButton = {
|
||||
title: string;
|
||||
@@ -66,6 +67,8 @@ export const Header = ({
|
||||
hasSearch?: boolean;
|
||||
onSearch?: () => void;
|
||||
}) => {
|
||||
const navigation = useNavigation();
|
||||
|
||||
const { colors } = useThemeColors();
|
||||
const insets = useGlobalSafeAreaInsets();
|
||||
const [borderHidden, setBorderHidden] = useState(true);
|
||||
|
||||
@@ -71,7 +71,7 @@ export const RightMenus = ({
|
||||
testID={notesnook.ids.default.addBtn}
|
||||
icon={renderedInRoute === "Trash" ? "delete" : "plus"}
|
||||
iconSize={SIZE.xl}
|
||||
type="shade"
|
||||
type="accent"
|
||||
hitSlop={{
|
||||
top: 10,
|
||||
right: 10,
|
||||
@@ -82,7 +82,7 @@ export const RightMenus = ({
|
||||
marginLeft: 10,
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 5,
|
||||
borderRadius: 100,
|
||||
paddingHorizontal: 0,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.primary.accent
|
||||
|
||||
@@ -19,10 +19,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import Sodium from "@ammarahmed/react-native-sodium";
|
||||
import dataurl from "@notesnook/core/dist/utils/dataurl";
|
||||
import type { ImageAttributes } from "@notesnook/editor/dist/extensions/image/index";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Platform, View } from "react-native";
|
||||
import ImageViewer from "react-native-image-zoom-viewer";
|
||||
import { db } from "../../common/database";
|
||||
import downloadAttachment from "../../common/filesystem/download-attachment";
|
||||
import { cacheDir } from "../../common/filesystem/utils";
|
||||
import {
|
||||
@@ -32,13 +34,13 @@ import {
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import { ProgressBarComponent } from "../ui/svg/lazy";
|
||||
import type { ImageAttributes } from "@notesnook/editor/dist/extensions/image/index";
|
||||
const ImagePreview = () => {
|
||||
const { colors } = useThemeColors("dialog");
|
||||
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [image, setImage] = useState<string>();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const imageRef = useRef<ImageAttributes>();
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent("ImagePreview", open);
|
||||
@@ -48,6 +50,7 @@ const ImagePreview = () => {
|
||||
}, []);
|
||||
|
||||
const open = async (image: ImageAttributes) => {
|
||||
imageRef.current = image;
|
||||
setVisible(true);
|
||||
setLoading(true);
|
||||
setTimeout(async () => {
|
||||
@@ -60,6 +63,9 @@ const ImagePreview = () => {
|
||||
type: "base64",
|
||||
uri: ""
|
||||
});
|
||||
if (imageRef.current) {
|
||||
imageRef.current.hash = hash;
|
||||
}
|
||||
}
|
||||
if (!hash) return;
|
||||
//@ts-ignore // FIX ME
|
||||
@@ -80,7 +86,13 @@ const ImagePreview = () => {
|
||||
|
||||
return (
|
||||
visible && (
|
||||
<BaseDialog animation="slide" visible={true} onRequestClose={close}>
|
||||
<BaseDialog
|
||||
background="black"
|
||||
animation="slide"
|
||||
visible={true}
|
||||
onRequestClose={close}
|
||||
transparent
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
@@ -101,6 +113,21 @@ const ImagePreview = () => {
|
||||
color={colors.primary.accent}
|
||||
borderColor="transparent"
|
||||
/>
|
||||
<IconButton
|
||||
onPress={() => {
|
||||
if (imageRef.current?.hash) {
|
||||
db.fs().cancel(imageRef.current?.hash);
|
||||
}
|
||||
close();
|
||||
}}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: Platform.OS === "android" ? 35 : 0,
|
||||
right: 12
|
||||
}}
|
||||
color={colors.static.white}
|
||||
name="close"
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<ImageViewer
|
||||
@@ -117,13 +144,12 @@ const ImagePreview = () => {
|
||||
width: "100%",
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
height: 80,
|
||||
marginTop: 0,
|
||||
paddingHorizontal: 12,
|
||||
height: 50,
|
||||
paddingHorizontal: 24,
|
||||
position: "absolute",
|
||||
zIndex: 999,
|
||||
backgroundColor: "rgba(0,0,0,0.3)",
|
||||
paddingTop: Platform.OS === "android" ? 30 : 0
|
||||
marginTop: Platform.OS === "android" ? 30 : 0
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
@@ -137,7 +163,7 @@ const ImagePreview = () => {
|
||||
)}
|
||||
imageUrls={[
|
||||
{
|
||||
url: image
|
||||
url: image as string
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -19,6 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { View } from "react-native";
|
||||
import React from "react";
|
||||
export const Footer = () => {
|
||||
return <View style={{ height: 150 }} />;
|
||||
export const Footer = ({ height = 150 }) => {
|
||||
return <View style={{ height: height }} />;
|
||||
};
|
||||
|
||||
@@ -95,7 +95,7 @@ export const NotebookHeader = ({
|
||||
style={{
|
||||
flexShrink: 1
|
||||
}}
|
||||
size={SIZE.xxl}
|
||||
size={SIZE.lg}
|
||||
>
|
||||
{notebook.title}
|
||||
</Heading>
|
||||
@@ -114,7 +114,7 @@ export const NotebookHeader = ({
|
||||
width: 40,
|
||||
height: 40
|
||||
}}
|
||||
type={isPinnedToMenu ? "secondary" : "secondary"}
|
||||
type="transparent"
|
||||
color={isPinnedToMenu ? colors.primary.accent : colors.primary.icon}
|
||||
size={SIZE.lg}
|
||||
/>
|
||||
@@ -123,7 +123,7 @@ export const NotebookHeader = ({
|
||||
onPress={onEditNotebook}
|
||||
tooltipText="Edit this notebook"
|
||||
name="pencil"
|
||||
type="secondary"
|
||||
type="transparent"
|
||||
color={colors.primary.icon}
|
||||
style={{
|
||||
width: 40,
|
||||
@@ -134,7 +134,7 @@ export const NotebookHeader = ({
|
||||
</View>
|
||||
|
||||
{notebook.description ? (
|
||||
<Paragraph size={SIZE.md} color={colors.primary.paragraph}>
|
||||
<Paragraph size={SIZE.sm} color={colors.primary.paragraph}>
|
||||
{notebook.description}
|
||||
</Paragraph>
|
||||
) : null}
|
||||
|
||||
@@ -99,7 +99,6 @@ export const SectionHeader = React.memo<
|
||||
color={color || colors.primary.accent}
|
||||
size={SIZE.sm}
|
||||
style={{
|
||||
minWidth: 60,
|
||||
alignSelf: "center",
|
||||
textAlignVertical: "center"
|
||||
}}
|
||||
|
||||
@@ -28,13 +28,12 @@ import {
|
||||
hideSheet,
|
||||
presentSheet
|
||||
} from "../../../services/event-manager";
|
||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||
import { eOnLoadNote, eShowMergeDialog } from "../../../utils/events";
|
||||
import { tabBarRef } from "../../../utils/global-refs";
|
||||
|
||||
import { NotebooksWithDateEdited, TagsWithDateEdited } from "@notesnook/common";
|
||||
import NotePreview from "../../note-history/preview";
|
||||
import SelectionWrapper from "../selection-wrapper";
|
||||
import SelectionWrapper, { selectItem } from "../selection-wrapper";
|
||||
|
||||
export const openNote = async (
|
||||
item: Note,
|
||||
@@ -47,21 +46,8 @@ export const openNote = async (
|
||||
if (!isTrash) {
|
||||
note = (await db.notes.note(item.id)) as Note;
|
||||
}
|
||||
if (useSelectionStore.getState().selectionMode === item.type) {
|
||||
const {
|
||||
selectedItemsList,
|
||||
selectionMode,
|
||||
clearSelection,
|
||||
setSelectedItem
|
||||
} = useSelectionStore.getState();
|
||||
|
||||
if (selectedItemsList.length > 0 && selectionMode === item.type) {
|
||||
setSelectedItem(note.id);
|
||||
} else {
|
||||
clearSelection();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (selectItem(item)) return;
|
||||
|
||||
if (note.conflicted) {
|
||||
eSendEvent(eShowMergeDialog, note);
|
||||
|
||||
@@ -27,18 +27,12 @@ import Navigation from "../../../services/navigation";
|
||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||
import { useTrashStore } from "../../../stores/use-trash-store";
|
||||
import { presentDialog } from "../../dialog/functions";
|
||||
import SelectionWrapper from "../selection-wrapper";
|
||||
import SelectionWrapper, { selectItem } from "../selection-wrapper";
|
||||
|
||||
export const openNotebook = (item: Notebook | BaseTrashItem<Notebook>) => {
|
||||
const isTrash = item.type === "trash";
|
||||
const { selectedItemsList, setSelectedItem, selectionMode, clearSelection } =
|
||||
useSelectionStore.getState();
|
||||
if (selectedItemsList.length > 0 && selectionMode === item.type) {
|
||||
setSelectedItem(item.id);
|
||||
return;
|
||||
} else {
|
||||
clearSelection();
|
||||
}
|
||||
|
||||
if (selectItem(item)) return;
|
||||
|
||||
if (isTrash) {
|
||||
presentDialog({
|
||||
|
||||
@@ -22,7 +22,6 @@ import React from "react";
|
||||
import { View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { notesnook } from "../../../../e2e/test.ids";
|
||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { Properties } from "../../properties";
|
||||
import ReminderSheet from "../../sheets/reminder";
|
||||
@@ -30,7 +29,7 @@ import { IconButton } from "../../ui/icon-button";
|
||||
import { ReminderTime } from "../../ui/reminder-time";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import SelectionWrapper from "../selection-wrapper";
|
||||
import SelectionWrapper, { selectItem } from "../selection-wrapper";
|
||||
|
||||
const ReminderItem = React.memo(
|
||||
({
|
||||
@@ -44,18 +43,7 @@ const ReminderItem = React.memo(
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
const openReminder = () => {
|
||||
const {
|
||||
selectedItemsList,
|
||||
setSelectedItem,
|
||||
selectionMode,
|
||||
clearSelection
|
||||
} = useSelectionStore.getState();
|
||||
if (selectedItemsList.length > 0 && selectionMode === item.type) {
|
||||
setSelectedItem(item.id);
|
||||
return;
|
||||
} else {
|
||||
clearSelection();
|
||||
}
|
||||
if (selectItem(item)) return;
|
||||
|
||||
ReminderSheet.present(item, undefined, isSheet);
|
||||
};
|
||||
|
||||
@@ -26,6 +26,23 @@ import { Filler } from "./back-fill";
|
||||
import { SelectionIcon } from "./selection";
|
||||
import { Item, TrashItem } from "@notesnook/core";
|
||||
|
||||
export function selectItem(item: Item) {
|
||||
if (useSelectionStore.getState().selectionMode === item.type) {
|
||||
const { selectionMode, clearSelection, setSelectedItem } =
|
||||
useSelectionStore.getState();
|
||||
|
||||
if (selectionMode === item.type) {
|
||||
setSelectedItem(item.id);
|
||||
}
|
||||
|
||||
if (useSelectionStore.getState().selectedItemsList.length === 0) {
|
||||
clearSelection();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
type SelectionWrapperProps = PropsWithChildren<{
|
||||
item: Item;
|
||||
onPress: () => void;
|
||||
|
||||
@@ -23,13 +23,12 @@ import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { notesnook } from "../../../../e2e/test.ids";
|
||||
import { TaggedNotes } from "../../../screens/notes/tagged";
|
||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { Properties } from "../../properties";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import SelectionWrapper from "../selection-wrapper";
|
||||
import SelectionWrapper, { selectItem } from "../selection-wrapper";
|
||||
|
||||
const TagItem = React.memo(
|
||||
({
|
||||
@@ -43,18 +42,7 @@ const TagItem = React.memo(
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
const onPress = () => {
|
||||
const {
|
||||
selectedItemsList,
|
||||
setSelectedItem,
|
||||
selectionMode,
|
||||
clearSelection
|
||||
} = useSelectionStore.getState();
|
||||
if (selectedItemsList.length > 0 && selectionMode === item.type) {
|
||||
setSelectedItem(item.id);
|
||||
return;
|
||||
} else {
|
||||
clearSelection();
|
||||
}
|
||||
if (selectItem(item)) return;
|
||||
|
||||
TaggedNotes.navigate(item, true);
|
||||
};
|
||||
|
||||
@@ -80,7 +80,7 @@ export default function List(props: ListProps) {
|
||||
const groupOptions = useGroupOptions(groupType);
|
||||
|
||||
const _onRefresh = async () => {
|
||||
Sync.run("global", false, true, () => {
|
||||
Sync.run("global", false, "full", () => {
|
||||
props.onRefresh?.();
|
||||
});
|
||||
};
|
||||
@@ -192,7 +192,9 @@ export default function List(props: ListProps) {
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
ListFooterComponent={<Footer />}
|
||||
ListFooterComponent={
|
||||
<Footer height={props.renderedInRoute === "Notebook" ? 300 : 150} />
|
||||
}
|
||||
ListHeaderComponent={
|
||||
<>
|
||||
{props.CustomLisHeader ? (
|
||||
|
||||
@@ -48,6 +48,7 @@ import { NoteWrapper } from "../list-items/note/wrapper";
|
||||
import { NotebookWrapper } from "../list-items/notebook/wrapper";
|
||||
import ReminderItem from "../list-items/reminder";
|
||||
import TagItem from "../list-items/tag";
|
||||
import { useIsCompactModeEnabled } from "../../hooks/use-is-compact-mode-enabled";
|
||||
|
||||
type ListItemWrapperProps<TItem = Item> = {
|
||||
group?: GroupingKey;
|
||||
@@ -75,6 +76,7 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
|
||||
const refreshTimeout = useRef<NodeJS.Timeout>();
|
||||
const currentItemId = useRef<string>();
|
||||
const locked = useRef(false);
|
||||
const compactMode = useIsCompactModeEnabled(props.dataType as ItemType);
|
||||
|
||||
const refreshItem = useCallback((resolvedItem: any) => {
|
||||
if (!resolvedItem || !resolvedItem.data) {
|
||||
@@ -117,14 +119,24 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
|
||||
}
|
||||
currentItemId.current = resolvedItem.item.id;
|
||||
setItem(resolvedItem.item);
|
||||
}
|
||||
|
||||
if (resolvedItem?.group) {
|
||||
setGroupHeader(resolvedItem.group);
|
||||
} else {
|
||||
setGroupHeader(undefined);
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (previousIndex.current !== index) {
|
||||
previousIndex.current = index;
|
||||
const resolvedItem = items?.cacheItem(index);
|
||||
refreshItem(resolvedItem);
|
||||
if (resolvedItem) {
|
||||
refreshItem(resolvedItem);
|
||||
} else {
|
||||
setItem(undefined);
|
||||
setGroupHeader(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@@ -132,12 +144,15 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
|
||||
try {
|
||||
clearTimeout(refreshTimeout.current);
|
||||
const idx = index;
|
||||
refreshTimeout.current = setTimeout(async () => {
|
||||
if (idx !== previousIndex.current) return;
|
||||
const resolvedItem = await items?.item(idx, resolveItems);
|
||||
if (idx !== previousIndex.current) return;
|
||||
refreshItem(resolvedItem);
|
||||
}, 100);
|
||||
refreshTimeout.current = setTimeout(
|
||||
async () => {
|
||||
if (idx !== previousIndex.current) return;
|
||||
const resolvedItem = await items?.item(idx, resolveItems);
|
||||
if (idx !== previousIndex.current) return;
|
||||
refreshItem(resolvedItem);
|
||||
},
|
||||
items?.cacheItem(index) ? 100 : 0
|
||||
);
|
||||
} catch (e) {
|
||||
console.log("Error", e);
|
||||
}
|
||||
@@ -148,7 +163,7 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
height: 120,
|
||||
height: compactMode ? 50 : 120,
|
||||
width: "100%"
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -313,7 +313,7 @@ const MergeConflicts = () => {
|
||||
height: height / 2 - (50 + insets.top / 2),
|
||||
backgroundColor: colors.primary.background,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.secondary.background
|
||||
borderBottomColor: colors.primary.border
|
||||
}}
|
||||
>
|
||||
<ReadonlyEditor
|
||||
|
||||
@@ -48,7 +48,7 @@ export const Synced = ({ item, close }) => {
|
||||
paddingTop: 10,
|
||||
marginTop: 10,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: colors.secondary.background
|
||||
borderTopColor: colors.primary.border
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
|
||||
@@ -19,14 +19,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { Item, ItemType, VirtualizedGrouping } from "@notesnook/core";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useEffect } from "react";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import {
|
||||
BackHandler,
|
||||
NativeEventSubscription,
|
||||
Platform,
|
||||
View
|
||||
} from "react-native";
|
||||
import Animated, { FadeInUp } from "react-native-reanimated";
|
||||
import Menu from "react-native-reanimated-material-menu/src/Menu";
|
||||
import { db } from "../../common/database";
|
||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||
import { ToastManager } from "../../services/event-manager";
|
||||
@@ -42,9 +42,10 @@ import { presentDialog } from "../dialog/functions";
|
||||
import MoveNoteSheet from "../sheets/add-to";
|
||||
import ExportNotesSheet from "../sheets/export-notes";
|
||||
import ManageTagsSheet from "../sheets/manage-tags";
|
||||
import { MoveNotebookSheet } from "../sheets/move-notebook";
|
||||
import { Button } from "../ui/button";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import { MoveNotebookSheet } from "../sheets/move-notebook";
|
||||
|
||||
export const SelectionHeader = React.memo(
|
||||
({
|
||||
@@ -58,6 +59,8 @@ export const SelectionHeader = React.memo(
|
||||
type?: ItemType;
|
||||
renderedInRoute?: string;
|
||||
}) => {
|
||||
const menuRef = useRef<Menu>(null);
|
||||
const { colors: contextMenuColors } = useThemeColors("contextMenu");
|
||||
const { colors } = useThemeColors();
|
||||
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||
const selectedItemsList = useSelectionStore(
|
||||
@@ -132,8 +135,7 @@ export const SelectionHeader = React.memo(
|
||||
}, [clearSelection, selectionMode]);
|
||||
|
||||
return selectionMode !== type || focusedRouteId !== id ? null : (
|
||||
<Animated.View
|
||||
entering={FadeInUp}
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
height: Platform.OS === "android" ? 50 + insets.top : 50,
|
||||
@@ -143,8 +145,7 @@ export const SelectionHeader = React.memo(
|
||||
alignItems: "center",
|
||||
flexDirection: "row",
|
||||
zIndex: 999,
|
||||
paddingHorizontal: 12,
|
||||
marginVertical: 10
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
>
|
||||
<View
|
||||
@@ -164,18 +165,15 @@ export const SelectionHeader = React.memo(
|
||||
borderRadius: 100,
|
||||
marginRight: 10
|
||||
}}
|
||||
type="secondary"
|
||||
onPress={() => {
|
||||
clearSelection();
|
||||
}}
|
||||
size={SIZE.xl}
|
||||
color={colors.primary.icon}
|
||||
name="close"
|
||||
/>
|
||||
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: colors.secondary.background,
|
||||
height: 40,
|
||||
borderRadius: 100,
|
||||
paddingHorizontal: 16,
|
||||
@@ -184,11 +182,12 @@ export const SelectionHeader = React.memo(
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Heading size={SIZE.md} color={colors.primary.accent}>
|
||||
<Heading size={SIZE.lg} color={colors.primary.paragraph}>
|
||||
{selectedItemsList.length}
|
||||
</Heading>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
@@ -211,161 +210,142 @@ export const SelectionHeader = React.memo(
|
||||
allSelected ? colors.primary.accent : colors.primary.paragraph
|
||||
}
|
||||
name="select-all"
|
||||
size={SIZE.xl}
|
||||
/>
|
||||
|
||||
{renderedInRoute === "Notebooks" ? (
|
||||
<IconButton
|
||||
{selectedItemsList.length ? (
|
||||
<Menu
|
||||
ref={menuRef}
|
||||
animationDuration={200}
|
||||
style={{
|
||||
marginLeft: 10
|
||||
borderRadius: 5,
|
||||
backgroundColor: contextMenuColors.primary.background,
|
||||
marginTop: -20
|
||||
}}
|
||||
onPress={async () => {
|
||||
const ids = selectedItemsList;
|
||||
const notebooks = await db.notebooks.all.items(ids);
|
||||
MoveNotebookSheet.present(notebooks);
|
||||
onRequestClose={() => {
|
||||
menuRef.current?.hide();
|
||||
}}
|
||||
tooltipText="Move notebooks"
|
||||
tooltipPosition={1}
|
||||
name="arrow-right-bold-box-outline"
|
||||
size={SIZE.xl}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{type !== "note" ? null : (
|
||||
<>
|
||||
<IconButton
|
||||
onPress={async () => {
|
||||
await sleep(100);
|
||||
ManageTagsSheet.present(selectedItemsList);
|
||||
}}
|
||||
style={{
|
||||
marginLeft: 10
|
||||
}}
|
||||
color={colors.primary.icon}
|
||||
tooltipText="Manage tags"
|
||||
tooltipPosition={4}
|
||||
name="pound"
|
||||
size={SIZE.xl}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
onPress={async () => {
|
||||
ExportNotesSheet.present(selectedItemsList);
|
||||
}}
|
||||
tooltipText="Export"
|
||||
tooltipPosition={4}
|
||||
style={{
|
||||
marginLeft: 10
|
||||
}}
|
||||
color={colors.primary.paragraph}
|
||||
name="export"
|
||||
size={SIZE.xl}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
onPress={async () => {
|
||||
MoveNoteSheet.present();
|
||||
}}
|
||||
style={{
|
||||
marginLeft: 10
|
||||
}}
|
||||
tooltipText="Add to notebooks"
|
||||
tooltipPosition={4}
|
||||
color={colors.primary.paragraph}
|
||||
name="plus"
|
||||
size={SIZE.xl}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{renderedInRoute === "Notebook" ? (
|
||||
<IconButton
|
||||
onPress={async () => {
|
||||
if (selectedItemsList.length > 0) {
|
||||
if (!id) return;
|
||||
await db.notes.removeFromNotebook(id, ...selectedItemsList);
|
||||
updateNotebook(id);
|
||||
Navigation.queueRoutesForUpdate();
|
||||
clearSelection();
|
||||
anchor={
|
||||
<IconButton
|
||||
onPress={() => {
|
||||
menuRef.current?.show();
|
||||
}}
|
||||
name="dots-vertical"
|
||||
color={colors.primary.paragraph}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{[
|
||||
{
|
||||
title:
|
||||
selectedItemsList.length > 1
|
||||
? "Move notebooks"
|
||||
: "Move notebook",
|
||||
onPress: async () => {
|
||||
const ids = selectedItemsList;
|
||||
const notebooks = await db.notebooks.all.items(ids);
|
||||
MoveNotebookSheet.present(notebooks);
|
||||
},
|
||||
visible: renderedInRoute === "Notebooks",
|
||||
icon: "arrow-right-bold-box-outline"
|
||||
},
|
||||
{
|
||||
title: "Manage tags",
|
||||
onPress: async () => {
|
||||
await sleep(100);
|
||||
ManageTagsSheet.present(selectedItemsList);
|
||||
},
|
||||
visible: type === "note",
|
||||
icon: "pound"
|
||||
},
|
||||
{
|
||||
title: "Export",
|
||||
onPress: async () => {
|
||||
await sleep(100);
|
||||
ExportNotesSheet.present(selectedItemsList);
|
||||
},
|
||||
visible: type === "note",
|
||||
icon: "export"
|
||||
},
|
||||
{
|
||||
title: "Link notebook",
|
||||
onPress: async () => {
|
||||
await sleep(100);
|
||||
MoveNoteSheet.present();
|
||||
},
|
||||
visible: type === "note",
|
||||
icon: "plus"
|
||||
},
|
||||
{
|
||||
title: "Unlink notebook",
|
||||
onPress: async () => {
|
||||
if (!id) return;
|
||||
await db.notes.removeFromNotebook(id, ...selectedItemsList);
|
||||
updateNotebook(id);
|
||||
Navigation.queueRoutesForUpdate();
|
||||
clearSelection();
|
||||
},
|
||||
visible: renderedInRoute === "Notebook",
|
||||
icon: "minus"
|
||||
},
|
||||
{
|
||||
title: "Unfavorite",
|
||||
onPress: addToFavorite,
|
||||
visible: focusedRouteId === "Favorites",
|
||||
icon: "star-off"
|
||||
},
|
||||
{
|
||||
title: `Move to trash`,
|
||||
onPress: async () => {
|
||||
deleteItems(
|
||||
undefined,
|
||||
useSelectionStore.getState().selectionMode
|
||||
).then(() => {
|
||||
useSelectionStore.getState().clearSelection();
|
||||
useSelectionStore.getState().setSelectionMode(undefined);
|
||||
});
|
||||
},
|
||||
visible: type !== "trash",
|
||||
icon: "delete"
|
||||
},
|
||||
{
|
||||
title: `Restore`,
|
||||
onPress: restoreItem,
|
||||
visible: type === "trash",
|
||||
icon: "delete-restore"
|
||||
},
|
||||
{
|
||||
title: `Delete`,
|
||||
onPress: deleteItem,
|
||||
visible: type === "trash",
|
||||
icon: "delete"
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
marginLeft: 10
|
||||
}}
|
||||
tooltipText={`Remove from Notebook`}
|
||||
tooltipPosition={4}
|
||||
testID="select-minus"
|
||||
color={colors.primary.paragraph}
|
||||
name="minus"
|
||||
size={SIZE.xl}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{focusedRouteId === "Favorites" ? (
|
||||
<IconButton
|
||||
onPress={addToFavorite}
|
||||
style={{
|
||||
marginLeft: 10
|
||||
}}
|
||||
tooltipText="Remove from favorites"
|
||||
tooltipPosition={4}
|
||||
color={colors.primary.paragraph}
|
||||
name="star-off"
|
||||
size={SIZE.xl}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{type === "trash" ? null : (
|
||||
<IconButton
|
||||
style={{
|
||||
marginLeft: 10
|
||||
}}
|
||||
onPress={() => {
|
||||
deleteItems(
|
||||
undefined,
|
||||
useSelectionStore.getState().selectionMode
|
||||
).then(() => {
|
||||
useSelectionStore.getState().clearSelection();
|
||||
useSelectionStore.getState().setSelectionMode(undefined);
|
||||
});
|
||||
}}
|
||||
tooltipText="Move to trash"
|
||||
tooltipPosition={1}
|
||||
color={colors.primary.paragraph}
|
||||
name="delete"
|
||||
size={SIZE.xl}
|
||||
/>
|
||||
)}
|
||||
|
||||
{type === "trash" ? (
|
||||
<>
|
||||
<IconButton
|
||||
style={{
|
||||
marginLeft: 10
|
||||
}}
|
||||
color={colors.primary.paragraph}
|
||||
onPress={restoreItem}
|
||||
name="delete-restore"
|
||||
tooltipText="Restore"
|
||||
tooltipPosition={4}
|
||||
size={SIZE.xl - 3}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
style={{
|
||||
marginLeft: 10
|
||||
}}
|
||||
color={colors.primary.paragraph}
|
||||
onPress={deleteItem}
|
||||
tooltipText="Delete"
|
||||
tooltipPosition={4}
|
||||
name="delete"
|
||||
size={SIZE.xl - 3}
|
||||
/>
|
||||
</>
|
||||
].map((item) =>
|
||||
!item.visible ? null : (
|
||||
<Button
|
||||
style={{
|
||||
width: 150,
|
||||
justifyContent: "flex-start",
|
||||
borderRadius: 0
|
||||
}}
|
||||
type="plain"
|
||||
buttonType={{
|
||||
text: contextMenuColors.primary.paragraph
|
||||
}}
|
||||
icon={item.icon}
|
||||
key={item.title}
|
||||
title={item.title}
|
||||
onPress={async () => {
|
||||
menuRef.current?.hide();
|
||||
if (Platform.OS === "ios") await sleep(300);
|
||||
item.onPress();
|
||||
}}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</Menu>
|
||||
) : null}
|
||||
</View>
|
||||
</Animated.View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -45,14 +45,16 @@ export const AddNotebookSheet = ({
|
||||
notebook,
|
||||
parentNotebook,
|
||||
close,
|
||||
showMoveNotesOnComplete
|
||||
showMoveNotesOnComplete,
|
||||
defaultTitle
|
||||
}: {
|
||||
notebook?: Notebook;
|
||||
parentNotebook?: Notebook;
|
||||
close?: (didAddNotebook: boolean) => void;
|
||||
showMoveNotesOnComplete: boolean;
|
||||
defaultTitle?: string;
|
||||
}) => {
|
||||
const title = useRef(notebook?.title);
|
||||
const title = useRef(notebook?.title || defaultTitle);
|
||||
const description = useRef(notebook?.description);
|
||||
const titleInput = useRef<TextInput>(null);
|
||||
const descriptionInput = useRef<TextInput>(null);
|
||||
@@ -139,7 +141,7 @@ export const AddNotebookSheet = ({
|
||||
}}
|
||||
returnKeyLabel="Next"
|
||||
returnKeyType="next"
|
||||
defaultValue={notebook ? notebook.title : ""}
|
||||
defaultValue={notebook ? notebook.title : title.current}
|
||||
/>
|
||||
|
||||
<Input
|
||||
@@ -174,7 +176,8 @@ AddNotebookSheet.present = (
|
||||
parentNotebook?: Notebook,
|
||||
context?: string,
|
||||
onClose?: (didAddNotebook: boolean) => void,
|
||||
showMoveNotesOnComplete = true
|
||||
showMoveNotesOnComplete = true,
|
||||
defaultTitle?: string
|
||||
) => {
|
||||
presentSheet({
|
||||
context: context,
|
||||
@@ -187,6 +190,7 @@ AddNotebookSheet.present = (
|
||||
onClose?.(didAddNotebook);
|
||||
}}
|
||||
showMoveNotesOnComplete={showMoveNotesOnComplete || false}
|
||||
defaultTitle={defaultTitle}
|
||||
/>
|
||||
)
|
||||
});
|
||||
|
||||
@@ -19,7 +19,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { Note } from "@notesnook/core";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { RefObject, useCallback, useEffect } from "react";
|
||||
import React, {
|
||||
RefObject,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState
|
||||
} from "react";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Keyboard,
|
||||
@@ -47,6 +53,8 @@ import Paragraph from "../../ui/typography/paragraph";
|
||||
import { NotebookItem } from "./notebook-item";
|
||||
import { useNotebookItemSelectionStore } from "./store";
|
||||
import { AddNotebookSheet } from "../add-notebook";
|
||||
import Input from "../../ui/input";
|
||||
import { presentDialog } from "../../dialog/functions";
|
||||
|
||||
async function updateInitialSelectionState(items: string[]) {
|
||||
const relations = await db.relations
|
||||
@@ -93,7 +101,11 @@ const MoveNoteSheet = ({
|
||||
actionSheetRef: RefObject<ActionSheetRef>;
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
const [notebooks, loading] = useNotebooks();
|
||||
const [rootNotebooks, loading] = useNotebooks();
|
||||
const searchQuery = useRef("");
|
||||
const searchTimer = useRef<NodeJS.Timeout>();
|
||||
const [notebooks, setNotebooks] = useState(rootNotebooks);
|
||||
|
||||
const dimensions = useSettingStore((state) => state.dimensions);
|
||||
const selectedItemsList = useSelectionStore(
|
||||
(state) => state.selectedItemsList
|
||||
@@ -103,6 +115,12 @@ const MoveNoteSheet = ({
|
||||
(state) => state.multiSelect
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
setNotebooks(rootNotebooks);
|
||||
}
|
||||
}, [loading, rootNotebooks]);
|
||||
|
||||
useEffect(() => {
|
||||
const items = note ? [note.id] : selectedItemsList;
|
||||
updateInitialSelectionState(items);
|
||||
@@ -199,7 +217,6 @@ const MoveNoteSheet = ({
|
||||
{hasSelected() ? (
|
||||
<IconButton
|
||||
name="restore"
|
||||
type="secondaryAccented"
|
||||
color={colors.primary.icon}
|
||||
onPress={() => {
|
||||
const items = note ? [note.id] : selectedItemsList;
|
||||
@@ -239,33 +256,48 @@ const MoveNoteSheet = ({
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
ListHeaderComponent={
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
width: "100%"
|
||||
width: "100%",
|
||||
paddingTop: 12
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
title="Add new notebook"
|
||||
style={{
|
||||
alignSelf: "flex-start",
|
||||
paddingHorizontal: 12,
|
||||
justifyContent: "space-between"
|
||||
<Input
|
||||
placeholder="Search notebooks...."
|
||||
button={{
|
||||
icon: "plus",
|
||||
onPress: () => {
|
||||
AddNotebookSheet.present(
|
||||
undefined,
|
||||
undefined,
|
||||
"link-notebooks",
|
||||
undefined,
|
||||
false,
|
||||
searchQuery.current
|
||||
);
|
||||
},
|
||||
color: colors.primary.icon
|
||||
}}
|
||||
onPress={() => {
|
||||
AddNotebookSheet.present(
|
||||
undefined,
|
||||
undefined,
|
||||
"link-notebooks",
|
||||
undefined,
|
||||
false
|
||||
);
|
||||
onChangeText={(value) => {
|
||||
searchQuery.current = value;
|
||||
if (!searchQuery.current) {
|
||||
setNotebooks(rootNotebooks);
|
||||
return;
|
||||
}
|
||||
searchTimer.current = setTimeout(() => {
|
||||
db.lookup
|
||||
.notebooks(searchQuery.current)
|
||||
.sorted()
|
||||
.then((result) => {
|
||||
if (searchQuery.current === value) {
|
||||
setNotebooks(result);
|
||||
}
|
||||
});
|
||||
}, 300);
|
||||
}}
|
||||
icon="plus"
|
||||
iconPosition="right"
|
||||
type="secondaryAccented"
|
||||
width="100%"
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import { Note } from "@notesnook/core";
|
||||
import { EVENTS } from "@notesnook/core/dist/common";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useEffect } from "react";
|
||||
import { Platform, View } from "react-native";
|
||||
import { View } from "react-native";
|
||||
import { FlatList } from "react-native-actions-sheet";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../../common/database";
|
||||
@@ -43,6 +43,7 @@ type TabItem = {
|
||||
locked?: boolean;
|
||||
noteLocked?: boolean;
|
||||
readonly?: boolean;
|
||||
pinned?: boolean;
|
||||
};
|
||||
|
||||
const TabItemComponent = (props: {
|
||||
@@ -140,24 +141,50 @@ const TabItemComponent = (props: {
|
||||
</Paragraph>
|
||||
</View>
|
||||
|
||||
<IconButton
|
||||
name="close"
|
||||
size={SIZE.lg}
|
||||
color={colors.primary.icon}
|
||||
onPress={() => {
|
||||
const isLastTab = useTabStore.getState().tabs.length === 1;
|
||||
useTabStore.getState().removeTab(props.tab.id);
|
||||
// The last tab is not actually removed, it is just cleaned up.
|
||||
if (isLastTab) {
|
||||
editorController.current?.reset(props.tab.id, true, true);
|
||||
props.close?.();
|
||||
}
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
gap: 5,
|
||||
paddingRight: 12
|
||||
}}
|
||||
top={0}
|
||||
left={0}
|
||||
right={20}
|
||||
bottom={0}
|
||||
/>
|
||||
>
|
||||
<IconButton
|
||||
name="pin"
|
||||
size={SIZE.lg}
|
||||
color={props.tab.pinned ? colors.primary.accent : colors.primary.icon}
|
||||
onPress={() => {
|
||||
useTabStore.getState().updateTab(props.tab.id, {
|
||||
pinned: !props.tab.pinned,
|
||||
previewTab: false
|
||||
});
|
||||
}}
|
||||
top={0}
|
||||
left={0}
|
||||
right={20}
|
||||
bottom={0}
|
||||
/>
|
||||
|
||||
{!props.tab?.pinned ? (
|
||||
<IconButton
|
||||
name="close"
|
||||
size={SIZE.lg}
|
||||
color={colors.primary.icon}
|
||||
onPress={() => {
|
||||
const isLastTab = useTabStore.getState().tabs.length === 1;
|
||||
useTabStore.getState().removeTab(props.tab.id);
|
||||
// The last tab is not actually removed, it is just cleaned up.
|
||||
if (isLastTab) {
|
||||
editorController.current?.reset(props.tab.id, true, true);
|
||||
props.close?.();
|
||||
}
|
||||
}}
|
||||
top={0}
|
||||
left={0}
|
||||
right={20}
|
||||
bottom={0}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
@@ -225,7 +252,16 @@ export default function EditorTabs({
|
||||
/>
|
||||
</View>
|
||||
|
||||
<FlatList windowSize={3} data={tabs} renderItem={renderTabItem} />
|
||||
<FlatList
|
||||
windowSize={3}
|
||||
data={tabs.sort((t1, t2) => {
|
||||
if (t1.pinned && t2.pinned) return 0;
|
||||
if (t1.pinned) return -1;
|
||||
if (t2.pinned) return 1;
|
||||
return 0;
|
||||
})}
|
||||
renderItem={renderTabItem}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { Fragment, useState } from "react";
|
||||
import { ActivityIndicator, Platform, StyleSheet, View } from "react-native";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Linking,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
View
|
||||
} from "react-native";
|
||||
import FileViewer from "react-native-file-viewer";
|
||||
import Share from "react-native-share";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
@@ -29,14 +35,12 @@ import { requestInAppReview } from "../../../services/app-review";
|
||||
import {
|
||||
PresentSheetOptions,
|
||||
ToastManager,
|
||||
eSendEvent,
|
||||
presentSheet
|
||||
} from "../../../services/event-manager";
|
||||
import Exporter from "../../../services/exporter";
|
||||
import PremiumService from "../../../services/premium";
|
||||
import { useUserStore } from "../../../stores/use-user-store";
|
||||
import { getElevationStyle } from "../../../utils/elevation";
|
||||
import { eCloseSheet } from "../../../utils/events";
|
||||
import { SIZE, ph, pv } from "../../../utils/size";
|
||||
import { sleep } from "../../../utils/time";
|
||||
import { Dialog } from "../../dialog";
|
||||
@@ -65,6 +69,7 @@ const ExportNotesSheet = ({
|
||||
filePath: string;
|
||||
name: string;
|
||||
type: string;
|
||||
fileDir: string;
|
||||
}
|
||||
| undefined
|
||||
>();
|
||||
@@ -295,7 +300,9 @@ const ExportNotesSheet = ({
|
||||
successfully as {result?.fileName}
|
||||
</Paragraph>
|
||||
<Button
|
||||
title="Open"
|
||||
title={
|
||||
Platform.OS === "android" ? "Open file location" : "Open"
|
||||
}
|
||||
type="accent"
|
||||
width={250}
|
||||
fontSize={SIZE.md}
|
||||
@@ -305,20 +312,23 @@ const ExportNotesSheet = ({
|
||||
}}
|
||||
onPress={async () => {
|
||||
if (!result?.filePath) return;
|
||||
eSendEvent(eCloseSheet);
|
||||
await sleep(500);
|
||||
FileViewer.open(result?.filePath, {
|
||||
showOpenWithDialog: true,
|
||||
showAppsSuggestions: true
|
||||
}).catch((e) => {
|
||||
console.log(e);
|
||||
ToastManager.show({
|
||||
heading: "Cannot open",
|
||||
message: `No application found to open ${result.name} file.`,
|
||||
type: "success",
|
||||
context: "local"
|
||||
if (Platform.OS === "android") {
|
||||
Linking.openURL(result.fileDir).catch((e) => {
|
||||
ToastManager.error(e as Error);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
FileViewer.open(result?.filePath, {
|
||||
showOpenWithDialog: true,
|
||||
showAppsSuggestions: true
|
||||
}).catch((e) => {
|
||||
ToastManager.show({
|
||||
heading: "Cannot open",
|
||||
message: `No application found to open ${result.name} file.`,
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
@@ -331,10 +341,10 @@ const ExportNotesSheet = ({
|
||||
borderRadius: 100
|
||||
}}
|
||||
onPress={async () => {
|
||||
if (!result?.filePath) return;
|
||||
if (!result) return;
|
||||
if (Platform.OS === "ios") {
|
||||
Share.open({
|
||||
url: result.filePath
|
||||
url: result?.fileDir + result.fileName
|
||||
}).catch(console.log);
|
||||
} else {
|
||||
FileViewer.open(result.filePath, {
|
||||
@@ -347,7 +357,7 @@ const ExportNotesSheet = ({
|
||||
/>
|
||||
<Button
|
||||
title="Export in another format"
|
||||
type="secondaryAccented"
|
||||
type="inverted"
|
||||
width={250}
|
||||
fontSize={SIZE.md}
|
||||
style={{
|
||||
|
||||
@@ -141,7 +141,7 @@ Logged in: ${user ? "yes" : "no"}`,
|
||||
defaultValue={title.current}
|
||||
style={{
|
||||
borderWidth: 1,
|
||||
borderColor: colors.secondary.background,
|
||||
borderColor: colors.primary.border,
|
||||
borderRadius: 5,
|
||||
padding: 12,
|
||||
fontFamily: "OpenSans-Regular",
|
||||
@@ -178,7 +178,7 @@ For example:
|
||||
}}
|
||||
style={{
|
||||
borderWidth: 1,
|
||||
borderColor: colors.secondary.background,
|
||||
borderColor: colors.primary.border,
|
||||
borderRadius: 5,
|
||||
padding: 12,
|
||||
fontFamily: "OpenSans-Regular",
|
||||
|
||||
@@ -35,7 +35,10 @@ import Input from "../../ui/input";
|
||||
import { Pressable } from "../../ui/pressable";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import type { LinkAttributes } from "@notesnook/editor/dist/extensions/link";
|
||||
import { editorController } from "../../../screens/editor/tiptap/utils";
|
||||
import {
|
||||
EditorEvents,
|
||||
editorController
|
||||
} from "../../../screens/editor/tiptap/utils";
|
||||
|
||||
const ListNoteItem = ({
|
||||
id,
|
||||
@@ -190,13 +193,13 @@ export default function LinkNote(props: {
|
||||
}
|
||||
: undefined
|
||||
);
|
||||
editorController.current.commands.createInternalLink(
|
||||
{
|
||||
editorController.current?.postMessage(EditorEvents.resolve, {
|
||||
data: {
|
||||
href: link,
|
||||
title: selectedNote.title
|
||||
},
|
||||
props.resolverId
|
||||
);
|
||||
resolverId: props.resolverId
|
||||
});
|
||||
};
|
||||
|
||||
const onSelectNote = async (note: Note) => {
|
||||
@@ -219,7 +222,7 @@ export default function LinkNote(props: {
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
minHeight: 400,
|
||||
minHeight: "100%",
|
||||
maxHeight: "100%"
|
||||
}}
|
||||
>
|
||||
@@ -311,6 +314,7 @@ export default function LinkNote(props: {
|
||||
style={{
|
||||
marginTop: 10
|
||||
}}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
windowSize={3}
|
||||
keyExtractor={(item) => item.id}
|
||||
data={nodes}
|
||||
@@ -324,6 +328,7 @@ export default function LinkNote(props: {
|
||||
onSelectNote={onSelectNote}
|
||||
/>
|
||||
)}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
style={{
|
||||
marginTop: 10
|
||||
}}
|
||||
|
||||
@@ -96,9 +96,12 @@ export default function Migrate() {
|
||||
});
|
||||
setLoading(true);
|
||||
await sleep(1);
|
||||
const { error } = await BackupService.run(false, "local");
|
||||
const { error, report } = await BackupService.run(false, "local");
|
||||
if (error) {
|
||||
ToastManager.error(error, "Backup failed");
|
||||
if (report) {
|
||||
reportError(error);
|
||||
}
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
@@ -159,13 +162,14 @@ export default function Migrate() {
|
||||
}}
|
||||
>
|
||||
Migrating database{progress ? `(${progress?.collection})` : null}
|
||||
{progress ? `(${progress.current}/${progress.total}) ` : null}...
|
||||
please wait
|
||||
{progress ? `(${progress.current}/${progress.total}) ` : null}
|
||||
... please wait
|
||||
</Paragraph>
|
||||
|
||||
<View
|
||||
style={{
|
||||
width: 200
|
||||
width: 200,
|
||||
alignSelf: "center"
|
||||
}}
|
||||
>
|
||||
<ProgressBarComponent
|
||||
|
||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { Notebook, VirtualizedGrouping } from "@notesnook/core";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useCallback } from "react";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { Text, View } from "react-native";
|
||||
import { FlatList } from "react-native-actions-sheet";
|
||||
import create from "zustand";
|
||||
@@ -72,6 +72,20 @@ export const MoveNotebookSheet = ({
|
||||
}) => {
|
||||
const [notebooks] = useNotebooks();
|
||||
const { colors } = useThemeColors();
|
||||
const [moveToTop, setMoveToTop] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
for (const notebook of selectedNotebooks) {
|
||||
const root = await findRootNotebookId(notebook.id);
|
||||
if (root !== notebook.id) {
|
||||
setMoveToTop(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
})();
|
||||
}, [selectedNotebooks]);
|
||||
|
||||
const renderItem = useCallback(
|
||||
({ index }: { index: number }) => {
|
||||
return (
|
||||
@@ -93,6 +107,7 @@ export const MoveNotebookSheet = ({
|
||||
: selectedNotebooks[0].title
|
||||
} to ${selectedNotebook.title}?`,
|
||||
positiveText: "Move",
|
||||
context: "move-notebook",
|
||||
positivePress: async () => {
|
||||
for (const notebook of selectedNotebooks) {
|
||||
const parent = await getParentNotebookId(notebook.id);
|
||||
@@ -157,36 +172,38 @@ export const MoveNotebookSheet = ({
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
title="Move to top"
|
||||
style={{
|
||||
alignSelf: "flex-start",
|
||||
width: "100%",
|
||||
justifyContent: "space-between"
|
||||
}}
|
||||
icon="arrow-up-bold"
|
||||
iconPosition="right"
|
||||
type="secondaryAccented"
|
||||
onPress={async () => {
|
||||
for (const notebook of selectedNotebooks) {
|
||||
const parent = await getParentNotebookId(notebook.id);
|
||||
const root = await findRootNotebookId(notebook.id);
|
||||
if (root !== notebook.id) {
|
||||
await db.relations.unlink(
|
||||
{
|
||||
type: "notebook",
|
||||
id: parent
|
||||
},
|
||||
notebook
|
||||
);
|
||||
eSendEvent(eOnNotebookUpdated, parent);
|
||||
eSendEvent(eOnNotebookUpdated, notebook.id);
|
||||
{moveToTop ? (
|
||||
<Button
|
||||
title="Move to top"
|
||||
style={{
|
||||
alignSelf: "flex-start",
|
||||
width: "100%",
|
||||
justifyContent: "space-between"
|
||||
}}
|
||||
icon="arrow-up-bold"
|
||||
iconPosition="right"
|
||||
type="secondaryAccented"
|
||||
onPress={async () => {
|
||||
for (const notebook of selectedNotebooks) {
|
||||
const parent = await getParentNotebookId(notebook.id);
|
||||
const root = await findRootNotebookId(notebook.id);
|
||||
if (root !== notebook.id) {
|
||||
await db.relations.unlink(
|
||||
{
|
||||
type: "notebook",
|
||||
id: parent
|
||||
},
|
||||
notebook
|
||||
);
|
||||
eSendEvent(eOnNotebookUpdated, parent);
|
||||
eSendEvent(eOnNotebookUpdated, notebook.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
useNotebookStore.getState().refresh();
|
||||
close?.();
|
||||
}}
|
||||
/>
|
||||
useNotebookStore.getState().refresh();
|
||||
close?.();
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
}
|
||||
ListEmptyComponent={
|
||||
|
||||
@@ -20,8 +20,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { VirtualizedGrouping } from "@notesnook/core";
|
||||
import { Note, Notebook } from "@notesnook/core/dist/types";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { RefObject, useEffect, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import React, { RefObject, useEffect, useRef, useState } from "react";
|
||||
import { ActivityIndicator, TextInput, View } from "react-native";
|
||||
import { ActionSheetRef } from "react-native-actions-sheet";
|
||||
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
|
||||
import { db } from "../../../common/database";
|
||||
@@ -35,6 +35,7 @@ import { Dialog } from "../../dialog";
|
||||
import DialogHeader from "../../dialog/dialog-header";
|
||||
import { Button } from "../../ui/button";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import Input from "../../ui/input";
|
||||
import { Pressable } from "../../ui/pressable";
|
||||
import Seperator from "../../ui/seperator";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
@@ -50,39 +51,60 @@ export const MoveNotes = ({
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
const currentNotebook = notebook;
|
||||
const inputRef = useRef<TextInput>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const selectionCount = useItemSelectionStore(
|
||||
(state) =>
|
||||
Object.keys(state.selection).filter(
|
||||
(k) =>
|
||||
state.initialState?.[k] !== "selected" &&
|
||||
state.selection[k] !== "deselected"
|
||||
state.selection?.[k] === "selected" ||
|
||||
state.selection[k] === "deselected"
|
||||
)?.length > 0
|
||||
);
|
||||
|
||||
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
|
||||
|
||||
const loadNotes = React.useCallback(
|
||||
async (query?: string) => {
|
||||
setLoading(true);
|
||||
const notes = query
|
||||
? db.lookup.notes(query).sorted()
|
||||
: db.notes?.all.sorted(db.settings.getGroupOptions("notes") as any);
|
||||
|
||||
notes
|
||||
.then(async (notes) => {
|
||||
await notes.item(0);
|
||||
setNotes(notes);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
db.relations
|
||||
.from(currentNotebook, "note")
|
||||
.get()
|
||||
.then((existingNotes) => {
|
||||
const selection: { [name: string]: any } = {};
|
||||
existingNotes.forEach((rel) => {
|
||||
selection[rel.toId] = "selected";
|
||||
});
|
||||
useItemSelectionStore.setState({
|
||||
selection: selection,
|
||||
initialState: selection
|
||||
});
|
||||
});
|
||||
},
|
||||
[currentNotebook]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
db.notes?.all.sorted(db.settings.getGroupOptions("notes")).then((notes) => {
|
||||
setNotes(notes);
|
||||
});
|
||||
db.relations
|
||||
.from(currentNotebook, "note")
|
||||
.get()
|
||||
.then((existingNotes) => {
|
||||
const selection: { [name: string]: any } = {};
|
||||
existingNotes.forEach((rel) => {
|
||||
selection[rel.toId] = "selected";
|
||||
});
|
||||
useItemSelectionStore.setState({
|
||||
selection: selection,
|
||||
initialState: selection
|
||||
});
|
||||
});
|
||||
loadNotes();
|
||||
|
||||
return () => {
|
||||
useItemSelectionStore.getState().reset();
|
||||
};
|
||||
}, [currentNotebook]);
|
||||
}, [currentNotebook, loadNotes]);
|
||||
|
||||
const renderItem = React.useCallback(
|
||||
({ index }: { item: boolean; index: number }) => {
|
||||
@@ -96,9 +118,7 @@ export const MoveNotes = ({
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
maxHeight: "100%",
|
||||
height: notes?.placeholders.length
|
||||
? 50 * (notes?.placeholders.length + 2)
|
||||
: 300
|
||||
height: "100%"
|
||||
}}
|
||||
>
|
||||
<Dialog context="local" />
|
||||
@@ -109,6 +129,22 @@ export const MoveNotes = ({
|
||||
/>
|
||||
<Seperator />
|
||||
|
||||
<Input
|
||||
button={{
|
||||
icon: "magnify",
|
||||
color: colors.primary.accent,
|
||||
size: SIZE.lg,
|
||||
onPress: () => {}
|
||||
}}
|
||||
testID="search-input"
|
||||
fwdRef={inputRef}
|
||||
autoCapitalize="none"
|
||||
onChangeText={(v) => {
|
||||
loadNotes(v && v.trim() === "" ? undefined : v.trim());
|
||||
}}
|
||||
placeholder="Search a note"
|
||||
/>
|
||||
|
||||
<FlashList
|
||||
ListEmptyComponent={
|
||||
<View
|
||||
@@ -118,13 +154,17 @@ export const MoveNotes = ({
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Paragraph color={colors.secondary.paragraph}>
|
||||
No notes to show
|
||||
</Paragraph>
|
||||
{loading ? (
|
||||
<ActivityIndicator size="large" color={colors.primary.accent} />
|
||||
) : (
|
||||
<Paragraph color={colors.secondary.paragraph}>
|
||||
No notes to show
|
||||
</Paragraph>
|
||||
)}
|
||||
</View>
|
||||
}
|
||||
estimatedItemSize={50}
|
||||
data={notes?.placeholders}
|
||||
data={loading ? [] : notes?.placeholders}
|
||||
renderItem={renderItem}
|
||||
/>
|
||||
|
||||
@@ -135,6 +175,12 @@ export const MoveNotes = ({
|
||||
currentNotebook.id,
|
||||
...useItemSelectionStore.getState().getSelectedItemIds()
|
||||
);
|
||||
|
||||
await db.notes?.removeFromNotebook(
|
||||
currentNotebook.id,
|
||||
...useItemSelectionStore.getState().getDeselectedItemIds()
|
||||
);
|
||||
|
||||
updateNotebook(currentNotebook.id);
|
||||
Navigation.queueRoutesForUpdate();
|
||||
fwdRef?.current?.hide();
|
||||
@@ -161,11 +207,12 @@ const SelectableNoteItem = React.memo(
|
||||
const selected = useItemSelectionStore((state) =>
|
||||
item?.id ? state.selection[item.id] === "selected" : false
|
||||
);
|
||||
|
||||
const exists = useItemSelectionStore((state) =>
|
||||
item?.id ? state.initialState[item.id] === "selected" : false
|
||||
);
|
||||
|
||||
return exists ? null : (
|
||||
return (
|
||||
<Pressable
|
||||
testID="listitem.select"
|
||||
onPress={() => {
|
||||
@@ -205,8 +252,14 @@ const SelectableNoteItem = React.memo(
|
||||
? "check-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
}
|
||||
type="selected"
|
||||
color={selected ? colors.selected.icon : colors.primary.icon}
|
||||
type="plain"
|
||||
color={
|
||||
selected
|
||||
? colors.selected.icon
|
||||
: exists && !selected
|
||||
? colors.static.red
|
||||
: colors.primary.icon
|
||||
}
|
||||
/>
|
||||
|
||||
<View
|
||||
|
||||
@@ -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 { useThemeColors } from "@notesnook/theme";
|
||||
import React from "react";
|
||||
import { Platform, View } from "react-native";
|
||||
import { ScrollView } from "react-native-actions-sheet";
|
||||
import { getVersion } from "react-native-device-info";
|
||||
import { features } from "../../../features";
|
||||
import { eSendEvent, presentSheet } from "../../../services/event-manager";
|
||||
import SettingsService from "../../../services/settings";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { eCloseSheet } from "../../../utils/events";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { Button } from "../../ui/button";
|
||||
@@ -36,7 +37,13 @@ export type FeatureType = {
|
||||
platform?: "ios" | "android";
|
||||
};
|
||||
|
||||
const NewFeature = ({ features }: { features: FeatureType[] }) => {
|
||||
const NewFeature = ({
|
||||
features,
|
||||
version
|
||||
}: {
|
||||
features: FeatureType[];
|
||||
version?: string | null;
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
|
||||
return (
|
||||
@@ -44,30 +51,33 @@ const NewFeature = ({ features }: { features: FeatureType[] }) => {
|
||||
style={{
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 12,
|
||||
paddingTop: 12
|
||||
paddingTop: 12,
|
||||
maxHeight: "100%"
|
||||
}}
|
||||
>
|
||||
<Heading color={colors.secondary.heading} size={SIZE.md}>
|
||||
New Version Highlights 🎉
|
||||
{!version ? "New version " : `v${version} `}Highlights 🎉
|
||||
</Heading>
|
||||
|
||||
<Seperator />
|
||||
|
||||
{features.map((item) => (
|
||||
<View
|
||||
key={item.title}
|
||||
style={{
|
||||
backgroundColor: colors.secondary.background,
|
||||
padding: 12,
|
||||
borderRadius: 10,
|
||||
width: "100%",
|
||||
marginBottom: 10
|
||||
}}
|
||||
>
|
||||
<Heading size={SIZE.lg - 2}>{item.title}</Heading>
|
||||
<Paragraph>{item.body}</Paragraph>
|
||||
</View>
|
||||
))}
|
||||
<ScrollView>
|
||||
{features.map((item) => (
|
||||
<View
|
||||
key={item.title}
|
||||
style={{
|
||||
backgroundColor: colors.secondary.background,
|
||||
padding: 12,
|
||||
borderRadius: 10,
|
||||
width: "100%",
|
||||
marginBottom: 10
|
||||
}}
|
||||
>
|
||||
<Heading size={SIZE.lg - 2}>{item.title}</Heading>
|
||||
<Paragraph selectable>{item.body}</Paragraph>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
<Seperator />
|
||||
|
||||
<Button
|
||||
@@ -108,7 +118,13 @@ NewFeature.present = () => {
|
||||
);
|
||||
if (_features.length === 0) return;
|
||||
presentSheet({
|
||||
component: <NewFeature features={features} />
|
||||
component: (
|
||||
<NewFeature
|
||||
features={features}
|
||||
version={SettingsService.getProperty("version")}
|
||||
/>
|
||||
),
|
||||
disableClosing: true
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -19,13 +19,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { Notebook, VirtualizedGrouping } from "@notesnook/core";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { RefreshControl, View, useWindowDimensions } from "react-native";
|
||||
import {
|
||||
Platform,
|
||||
RefreshControl,
|
||||
View,
|
||||
useWindowDimensions
|
||||
} from "react-native";
|
||||
import ActionSheet, { ActionSheetRef } from "react-native-actions-sheet";
|
||||
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
|
||||
import Config from "react-native-config";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import create from "zustand";
|
||||
import { notesnook } from "../../../../e2e/test.ids";
|
||||
import { db } from "../../../common/database";
|
||||
import { MMKV } from "../../../common/database/mmkv";
|
||||
import { useNotebook } from "../../../hooks/use-notebook";
|
||||
import NotebookScreen from "../../../screens/notebook";
|
||||
@@ -43,9 +49,8 @@ import { IconButton } from "../../ui/icon-button";
|
||||
import { Pressable } from "../../ui/pressable";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { AddNotebookSheet } from "../add-notebook";
|
||||
import Sort from "../sort";
|
||||
import { MoveNotebookSheet } from "../move-notebook";
|
||||
import { db } from "../../../common/database";
|
||||
import Sort from "../sort";
|
||||
|
||||
const useItemSelectionStore = createItemSelectionStore(true, false);
|
||||
|
||||
@@ -63,7 +68,8 @@ class NotebookSheetConfig {
|
||||
}
|
||||
|
||||
static get(item: ConfigItem) {
|
||||
return MMKV.getInt(NotebookSheetConfig.makeId(item)) || 0;
|
||||
const value = MMKV.getInt(NotebookSheetConfig.makeId(item));
|
||||
return typeof value === "number" ? value : 0;
|
||||
}
|
||||
|
||||
static set(item: ConfigItem, index = 0) {
|
||||
@@ -126,11 +132,6 @@ export const NotebookSheet = () => {
|
||||
if (!focusedRouteId) return;
|
||||
const nextRoot = await findRootNotebookId(focusedRouteId);
|
||||
if (nextRoot !== currentItem.current) {
|
||||
console.log(
|
||||
"NotebookSheet.useEffect.canShow",
|
||||
"Root changed to",
|
||||
nextRoot
|
||||
);
|
||||
useItemSelectionStore.setState({
|
||||
enabled: false,
|
||||
selection: {}
|
||||
@@ -139,17 +140,14 @@ export const NotebookSheet = () => {
|
||||
currentItem.current = nextRoot;
|
||||
const snapPoint = NotebookSheetConfig.get({
|
||||
type: "notebook",
|
||||
id: nextRoot as string
|
||||
id: focusedRouteId as string
|
||||
});
|
||||
|
||||
if (ref.current?.isOpen()) {
|
||||
ref.current?.snapToIndex(snapPoint);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
ref.current?.show(snapPoint);
|
||||
}, 150);
|
||||
ref.current?.show(snapPoint);
|
||||
}
|
||||
console.log("NotebookSheet.useEffect.didShow", focusedRouteId);
|
||||
setRoot(nextRoot);
|
||||
onRequestUpdate();
|
||||
});
|
||||
@@ -162,7 +160,7 @@ export const NotebookSheet = () => {
|
||||
ref.current?.hide();
|
||||
}
|
||||
}
|
||||
}, [canShow, onRequestUpdate, focusedRouteId]);
|
||||
}, [canShow, focusedRouteId]);
|
||||
|
||||
return (
|
||||
<ActionSheet
|
||||
@@ -198,7 +196,11 @@ export const NotebookSheet = () => {
|
||||
backgroundColor: colors.secondary.background
|
||||
}}
|
||||
keyboardHandlerEnabled={false}
|
||||
snapPoints={Config.isTesting === "true" ? [100] : [20, 100]}
|
||||
snapPoints={
|
||||
Config.isTesting === "true"
|
||||
? [100]
|
||||
: [Platform.OS === "android" ? 15 : 10, 100]
|
||||
}
|
||||
initialSnapIndex={1}
|
||||
backgroundInteractionEnabled
|
||||
gestureEnabled
|
||||
@@ -237,7 +239,7 @@ export const NotebookSheet = () => {
|
||||
>
|
||||
<Icon
|
||||
name="notebook-plus"
|
||||
color={colors.primary.accent}
|
||||
color={colors.primary.icon}
|
||||
size={SIZE.xxl}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -159,7 +159,7 @@ export default function ReminderNotify({
|
||||
? 160 * references?.placeholders?.length
|
||||
: 500,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: colors.secondary.background,
|
||||
borderTopColor: colors.primary.border,
|
||||
marginTop: 5,
|
||||
paddingTop: 5
|
||||
}}
|
||||
|
||||
@@ -18,7 +18,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { RefObject, useRef, useState } from "react";
|
||||
import { Platform, TextInput, View } from "react-native";
|
||||
import {
|
||||
Platform,
|
||||
TextInput,
|
||||
View,
|
||||
ScrollView as RNScrollView
|
||||
} from "react-native";
|
||||
import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
|
||||
import DateTimePickerModal from "react-native-modal-datetime-picker";
|
||||
import {
|
||||
@@ -364,7 +369,7 @@ export default function ReminderSheet({
|
||||
))}
|
||||
</View>
|
||||
|
||||
<ScrollView showsHorizontalScrollIndicator={false} horizontal>
|
||||
<RNScrollView showsHorizontalScrollIndicator={false} horizontal>
|
||||
{recurringMode === RecurringModes.Daily ||
|
||||
recurringMode === RecurringModes.Year
|
||||
? null
|
||||
@@ -425,7 +430,7 @@ export default function ReminderSheet({
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</ScrollView>
|
||||
</RNScrollView>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ import { Button } from "../../ui/button";
|
||||
import Seperator from "../../ui/seperator";
|
||||
import SheetWrapper from "../../ui/sheet";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { useUserStore } from "../../../stores/use-user-store";
|
||||
|
||||
const RestoreDataSheet = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
@@ -246,7 +247,6 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
|
||||
await db.backup.import(backup, password, key);
|
||||
});
|
||||
await db.initCollections();
|
||||
initialize();
|
||||
refreshAllStores();
|
||||
ToastManager.show({
|
||||
heading: "Backup restored successfully.",
|
||||
@@ -285,9 +285,9 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
|
||||
|
||||
const backupFiles = await RNFetchBlob.fs.ls(zipOutputFolder);
|
||||
|
||||
if (backupFiles.findIndex((file) => file === ".nnbackup") === -1) {
|
||||
throw new Error("Backup file is invalid");
|
||||
}
|
||||
// if (backupFiles.findIndex((file) => file === ".nnbackup") === -1) {
|
||||
// throw new Error("Backup file is invalid");
|
||||
// }
|
||||
|
||||
await db.transaction(async () => {
|
||||
let password;
|
||||
@@ -384,10 +384,20 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
useUserStore.setState({
|
||||
disableAppLockRequests: true
|
||||
});
|
||||
console.log("disabled...");
|
||||
const file = await DocumentPicker.pickSingle({
|
||||
copyTo: "cachesDirectory"
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
useUserStore.setState({
|
||||
disableAppLockRequests: false
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
if (file.name.endsWith(".nnbackupz")) {
|
||||
setRestoring(true);
|
||||
await restoreFromZip(file.fileCopyUri, true);
|
||||
@@ -399,6 +409,11 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("error", e.stack);
|
||||
setTimeout(() => {
|
||||
useUserStore.setState({
|
||||
disableAppLockRequests: false
|
||||
});
|
||||
}, 1000);
|
||||
setRestoring(false);
|
||||
backupError(e);
|
||||
}
|
||||
@@ -415,7 +430,7 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
|
||||
borderRadius: 0,
|
||||
flexDirection: "row",
|
||||
borderBottomWidth: 0.5,
|
||||
borderBottomColor: colors.secondary.background
|
||||
borderBottomColor: colors.primary.border
|
||||
}}
|
||||
>
|
||||
<View
|
||||
|
||||
@@ -50,7 +50,7 @@ const Sort = ({ type, screen }) => {
|
||||
const setOrderBy = async () => {
|
||||
let _groupOptions = {
|
||||
...groupOptions,
|
||||
sortDirection: groupOptions.sortDirection === "asc" ? "desc" : "asc"
|
||||
sortDirection: groupOptions?.sortDirection === "asc" ? "desc" : "asc"
|
||||
};
|
||||
if (type === "topics") {
|
||||
_groupOptions.groupBy = "none";
|
||||
@@ -85,22 +85,22 @@ const Sort = ({ type, screen }) => {
|
||||
|
||||
<Button
|
||||
title={
|
||||
groupOptions.sortDirection === "asc"
|
||||
? groupOptions.groupBy === "abc" ||
|
||||
groupOptions.sortBy === "title"
|
||||
groupOptions?.sortDirection === "asc"
|
||||
? groupOptions?.groupBy === "abc" ||
|
||||
groupOptions?.sortBy === "title"
|
||||
? "A - Z"
|
||||
: groupOptions.sortBy === "dueDate"
|
||||
: groupOptions?.sortBy === "dueDate"
|
||||
? "Earliest first"
|
||||
: "Old - New"
|
||||
: groupOptions.groupBy === "abc" ||
|
||||
groupOptions.sortBy === "title"
|
||||
: groupOptions?.groupBy === "abc" ||
|
||||
groupOptions?.sortBy === "title"
|
||||
? "Z - A"
|
||||
: groupOptions.sortBy === "dueDate"
|
||||
: groupOptions?.sortBy === "dueDate"
|
||||
? "Latest first"
|
||||
: "New - Old"
|
||||
}
|
||||
icon={
|
||||
groupOptions.sortDirection === "asc"
|
||||
groupOptions?.sortDirection === "asc"
|
||||
? "sort-ascending"
|
||||
: "sort-descending"
|
||||
}
|
||||
@@ -127,14 +127,14 @@ const Sort = ({ type, screen }) => {
|
||||
justifyContent: "flex-start",
|
||||
flexWrap: "wrap",
|
||||
borderBottomWidth: isTopicSheet ? 0 : 1,
|
||||
borderBottomColor: colors.secondary.background,
|
||||
borderBottomColor: colors.primary.border,
|
||||
marginBottom: 12,
|
||||
paddingHorizontal: 12,
|
||||
paddingBottom: 12,
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
{groupOptions.groupBy === "abc" ? (
|
||||
{groupOptions?.groupBy === "abc" ? (
|
||||
<Button
|
||||
type="secondary"
|
||||
title="Title"
|
||||
@@ -154,18 +154,18 @@ const Sort = ({ type, screen }) => {
|
||||
item === "dateEdited") ? null : (
|
||||
<Button
|
||||
key={item}
|
||||
type={groupOptions.sortBy === item ? "selected" : "plain"}
|
||||
type={groupOptions?.sortBy === item ? "selected" : "plain"}
|
||||
title={SORT[item]}
|
||||
height={40}
|
||||
iconPosition="left"
|
||||
icon={groupOptions.sortBy === item ? "check" : null}
|
||||
icon={groupOptions?.sortBy === item ? "check" : null}
|
||||
style={{
|
||||
marginRight: 10,
|
||||
paddingHorizontal: 8
|
||||
}}
|
||||
buttonType={{
|
||||
text:
|
||||
groupOptions.sortBy === item
|
||||
groupOptions?.sortBy === item
|
||||
? colors.primary.accent
|
||||
: colors.secondary.paragraph
|
||||
}}
|
||||
@@ -213,11 +213,11 @@ const Sort = ({ type, screen }) => {
|
||||
key={item}
|
||||
testID={"btn-" + item}
|
||||
type={
|
||||
groupOptions.groupBy === GROUP[item] ? "selected" : "plain"
|
||||
groupOptions?.groupBy === GROUP[item] ? "selected" : "plain"
|
||||
}
|
||||
buttonType={{
|
||||
text:
|
||||
groupOptions.groupBy === GROUP[item]
|
||||
groupOptions?.groupBy === GROUP[item]
|
||||
? colors.primary.accent
|
||||
: colors.secondary.paragraph
|
||||
}}
|
||||
@@ -229,13 +229,13 @@ const Sort = ({ type, screen }) => {
|
||||
|
||||
if (item === "abc") {
|
||||
_groupOptions.sortBy = "title";
|
||||
_groupOptions.sortDirection = "asc";
|
||||
_groupOptions.sortDirection = "desc";
|
||||
}
|
||||
|
||||
updateGroupOptions(_groupOptions);
|
||||
}}
|
||||
height={40}
|
||||
icon={groupOptions.groupBy === GROUP[item] ? "check" : null}
|
||||
icon={groupOptions?.groupBy === GROUP[item] ? "check" : null}
|
||||
title={
|
||||
item.slice(0, 1).toUpperCase() + item.slice(1, item.length)
|
||||
}
|
||||
|
||||
@@ -170,6 +170,7 @@ export const Update = ({ version: appVersion, fwdRef }) => {
|
||||
lineHeight: 20,
|
||||
marginTop: 10
|
||||
}}
|
||||
selectable
|
||||
>
|
||||
{version.body}
|
||||
</Paragraph>
|
||||
@@ -181,6 +182,7 @@ export const Update = ({ version: appVersion, fwdRef }) => {
|
||||
style={{
|
||||
marginBottom: 5
|
||||
}}
|
||||
selectable
|
||||
>
|
||||
• {item}
|
||||
</Paragraph>
|
||||
|
||||
@@ -21,15 +21,11 @@ import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useCallback } from "react";
|
||||
import { View } from "react-native";
|
||||
import { DraxProvider, DraxScrollView } from "react-native-drax";
|
||||
import { notesnook } from "../../../e2e/test.ids";
|
||||
import { db } from "../../common/database";
|
||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import { eSendEvent } from "../../services/event-manager";
|
||||
import Navigation from "../../services/navigation";
|
||||
import { useMenuStore } from "../../stores/use-menu-store";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { SUBSCRIPTION_STATUS } from "../../utils/constants";
|
||||
import { eOpenPremiumDialog } from "../../utils/events";
|
||||
@@ -59,26 +55,6 @@ export const SideMenu = React.memo(
|
||||
const introCompleted = useSettingStore(
|
||||
(state) => state.settings.introCompleted
|
||||
);
|
||||
const BottomItemsList = [
|
||||
{
|
||||
name: isDark ? "Day" : "Night",
|
||||
icon: "theme-light-dark",
|
||||
func: () => {
|
||||
useThemeStore.getState().setColorScheme();
|
||||
},
|
||||
switch: true,
|
||||
on: !!isDark,
|
||||
close: false
|
||||
},
|
||||
{
|
||||
name: "Settings",
|
||||
icon: "cog-outline",
|
||||
close: true,
|
||||
func: () => {
|
||||
Navigation.navigate("Settings");
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const pro = {
|
||||
name: "Notesnook Pro",
|
||||
@@ -121,7 +97,7 @@ export const SideMenu = React.memo(
|
||||
<PinnedSection />
|
||||
</>
|
||||
),
|
||||
[]
|
||||
[order, hiddensItems]
|
||||
);
|
||||
|
||||
return !isAppLoading && introCompleted ? (
|
||||
@@ -155,26 +131,6 @@ export const SideMenu = React.memo(
|
||||
subscriptionType === SUBSCRIPTION_STATUS.BASIC ? (
|
||||
<MenuItem testID={pro.name} key={pro.name} item={pro} index={0} />
|
||||
) : null}
|
||||
|
||||
{BottomItemsList.slice(DDS.isLargeTablet() ? 0 : 1, 3).map(
|
||||
(item, index) => (
|
||||
<MenuItem
|
||||
testID={
|
||||
item.name == "Night mode"
|
||||
? notesnook.ids.menu.nightmode
|
||||
: item.name
|
||||
}
|
||||
key={item.name}
|
||||
item={item}
|
||||
index={index}
|
||||
rightBtn={
|
||||
DDS.isLargeTablet() || item.name === "Notesnook Pro"
|
||||
? undefined
|
||||
: BottomItemsList[0]
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</View>
|
||||
|
||||
{dragging ? (
|
||||
@@ -206,14 +162,7 @@ export const SideMenu = React.memo(
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
paddingHorizontal: 0
|
||||
}}
|
||||
>
|
||||
<UserStatus />
|
||||
</View>
|
||||
<UserStatus />
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -25,17 +25,20 @@ import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||
import useSyncProgress from "../../hooks/use-sync-progress";
|
||||
import { eSendEvent } from "../../services/event-manager";
|
||||
import Navigation from "../../services/navigation";
|
||||
import Sync from "../../services/sync";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { SyncStatus, useUserStore } from "../../stores/use-user-store";
|
||||
import { eOpenLoginDialog } from "../../utils/events";
|
||||
import { tabBarRef } from "../../utils/global-refs";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import { Pressable } from "../ui/pressable";
|
||||
import { TimeSince } from "../ui/time-since";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
|
||||
export const UserStatus = () => {
|
||||
const { colors } = useThemeColors();
|
||||
const { colors, isDark } = useThemeColors();
|
||||
const user = useUserStore((state) => state.user);
|
||||
const syncing = useUserStore((state) => state.syncing);
|
||||
const lastSyncStatus = useUserStore((state) => state.lastSyncStatus);
|
||||
@@ -53,7 +56,8 @@ export const UserStatus = () => {
|
||||
alignSelf: "center",
|
||||
paddingBottom: Platform.OS === "ios" ? insets.bottom / 2 : null,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: colors.secondary.background
|
||||
borderTopColor: colors.primary.border,
|
||||
backgroundColor: colors.primary.background
|
||||
}}
|
||||
>
|
||||
<View
|
||||
@@ -64,13 +68,9 @@ export const UserStatus = () => {
|
||||
}}
|
||||
>
|
||||
<Pressable
|
||||
onPress={async () => {
|
||||
if (user) {
|
||||
Sync.run();
|
||||
} else {
|
||||
tabBarRef.current?.closeDrawer();
|
||||
eSendEvent(eOpenLoginDialog);
|
||||
}
|
||||
onPress={() => {
|
||||
Navigation.navigate("Settings");
|
||||
tabBarRef.current.closeDrawer();
|
||||
}}
|
||||
type="plain"
|
||||
style={{
|
||||
@@ -78,7 +78,8 @@ export const UserStatus = () => {
|
||||
justifyContent: "flex-start",
|
||||
padding: 12,
|
||||
borderRadius: 0,
|
||||
alignItems: "center"
|
||||
alignItems: "center",
|
||||
gap: 10
|
||||
}}
|
||||
>
|
||||
{userProfile?.profilePicture ? (
|
||||
@@ -89,11 +90,19 @@ export const UserStatus = () => {
|
||||
style={{
|
||||
width: 35,
|
||||
height: 35,
|
||||
borderRadius: 100,
|
||||
marginRight: 10
|
||||
borderRadius: 100
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
) : (
|
||||
<Icon
|
||||
name="cog-outline"
|
||||
size={SIZE.lg - 2}
|
||||
color={colors.secondary.icon}
|
||||
style={{
|
||||
paddingLeft: 8
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<View
|
||||
style={{
|
||||
@@ -102,22 +111,12 @@ export const UserStatus = () => {
|
||||
}}
|
||||
>
|
||||
<Paragraph
|
||||
style={{
|
||||
flexWrap: "wrap"
|
||||
}}
|
||||
numberOfLines={1}
|
||||
size={SIZE.sm}
|
||||
color={colors.primary.heading}
|
||||
>
|
||||
{!user
|
||||
? "Login to sync your notes."
|
||||
: lastSyncStatus === SyncStatus.Failed
|
||||
? "Sync failed, tap to retry"
|
||||
: syncing
|
||||
? `Syncing your notes${
|
||||
progress ? ` (${progress.current})` : ""
|
||||
}`
|
||||
: !userProfile?.fullName
|
||||
? "Tap to sync"
|
||||
{!user || !userProfile?.fullName
|
||||
? "Settings"
|
||||
: userProfile.fullName}
|
||||
</Paragraph>
|
||||
|
||||
@@ -132,16 +131,20 @@ export const UserStatus = () => {
|
||||
"Not logged in"
|
||||
) : lastSynced && lastSynced !== "Never" ? (
|
||||
<>
|
||||
{lastSyncStatus === SyncStatus.Failed
|
||||
{syncing
|
||||
? `Syncing ${progress ? `(${progress.current})` : ""}`
|
||||
: lastSyncStatus === SyncStatus.Failed
|
||||
? "Sync failed"
|
||||
: "Synced"}{" "}
|
||||
<TimeSince
|
||||
style={{
|
||||
fontSize: SIZE.xs,
|
||||
color: colors.secondary.paragraph
|
||||
}}
|
||||
time={lastSynced}
|
||||
/>
|
||||
{!syncing ? (
|
||||
<TimeSince
|
||||
style={{
|
||||
fontSize: SIZE.xs,
|
||||
color: colors.secondary.paragraph
|
||||
}}
|
||||
time={lastSynced}
|
||||
/>
|
||||
) : null}
|
||||
{isOffline ? " (offline)" : ""}
|
||||
</>
|
||||
) : (
|
||||
@@ -149,7 +152,7 @@ export const UserStatus = () => {
|
||||
)}{" "}
|
||||
<Icon
|
||||
name="checkbox-blank-circle"
|
||||
size={11}
|
||||
size={9}
|
||||
allowFontScaling
|
||||
color={
|
||||
!user || lastSyncStatus === SyncStatus.Failed
|
||||
@@ -162,25 +165,80 @@ export const UserStatus = () => {
|
||||
</Paragraph>
|
||||
</View>
|
||||
|
||||
{user ? (
|
||||
syncing ? (
|
||||
<ActivityIndicator color={colors.primary.accent} size={SIZE.xl} />
|
||||
) : lastSyncStatus === SyncStatus.Failed ? (
|
||||
<Icon
|
||||
color={colors.error.icon}
|
||||
name="sync-alert"
|
||||
size={SIZE.lg}
|
||||
allowFontScaling
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
allowFontScaling
|
||||
color={colors.primary.accent}
|
||||
name="sync"
|
||||
size={SIZE.lg}
|
||||
/>
|
||||
)
|
||||
) : null}
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
gap: 0
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
hitSlop={{
|
||||
top: 10,
|
||||
bottom: 10
|
||||
}}
|
||||
onPress={() => {
|
||||
useThemeStore.getState().setColorScheme();
|
||||
}}
|
||||
name="theme-light-dark"
|
||||
color={isDark ? colors.primary.accent : colors.primary.icon}
|
||||
size={SIZE.lg}
|
||||
style={{
|
||||
borderRadius: 100,
|
||||
width: 40,
|
||||
height: 40
|
||||
}}
|
||||
/>
|
||||
|
||||
<Pressable
|
||||
style={{
|
||||
borderRadius: 100,
|
||||
width: 40,
|
||||
height: 40
|
||||
}}
|
||||
hitSlop={{
|
||||
top: 10,
|
||||
bottom: 10
|
||||
}}
|
||||
onPress={() => {
|
||||
if (user) {
|
||||
Sync.run();
|
||||
} else {
|
||||
tabBarRef.current?.closeDrawer();
|
||||
eSendEvent(eOpenLoginDialog);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{user ? (
|
||||
syncing ? (
|
||||
<ActivityIndicator
|
||||
color={colors.primary.accent}
|
||||
size={SIZE.xl}
|
||||
/>
|
||||
) : lastSyncStatus === SyncStatus.Failed ? (
|
||||
<Icon
|
||||
color={colors.error.icon}
|
||||
name="sync-alert"
|
||||
size={SIZE.lg}
|
||||
allowFontScaling
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
allowFontScaling
|
||||
color={colors.primary.icon}
|
||||
name="sync"
|
||||
size={SIZE.lg}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<Icon
|
||||
allowFontScaling
|
||||
color={colors.primary.accent}
|
||||
size={SIZE.lg}
|
||||
name="login"
|
||||
/>
|
||||
)}
|
||||
</Pressable>
|
||||
</View>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -59,7 +59,7 @@ export interface TabsRef {
|
||||
lock: () => boolean;
|
||||
openDrawer: (animated?: boolean) => void;
|
||||
closeDrawer: (animated?: boolean) => void;
|
||||
page: number;
|
||||
page: () => number;
|
||||
setScrollEnabled: () => true;
|
||||
isDrawerOpen: () => boolean;
|
||||
node: RefObject<Animated.View>;
|
||||
@@ -121,7 +121,12 @@ export const FluidTabs = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
} else {
|
||||
if (prevWidths.current?.sidebar !== widths.sidebar) {
|
||||
translateX.value =
|
||||
!appState || appState?.movedAway ? widths.sidebar : editorPosition;
|
||||
!appState || (appState && appState?.movedAway)
|
||||
? widths.sidebar
|
||||
: editorPosition;
|
||||
if (translateX.value === editorPosition) {
|
||||
onChangeTab?.({ i: 2, from: 1 });
|
||||
}
|
||||
}
|
||||
}
|
||||
isLoaded.current = true;
|
||||
@@ -234,7 +239,7 @@ export const FluidTabs = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
onDrawerStateChange(false);
|
||||
isDrawerOpen.value = false;
|
||||
},
|
||||
page: currentTab.value,
|
||||
page: () => currentTab.value,
|
||||
setScrollEnabled: () => true,
|
||||
node: node
|
||||
}),
|
||||
|
||||
@@ -28,6 +28,7 @@ import { Toast } from "../../toast";
|
||||
import { useAppState } from "../../../hooks/use-app-state";
|
||||
import SettingsService from "../../../services/settings";
|
||||
import { useUserStore } from "../../../stores/use-user-store";
|
||||
import { getContainerBorder } from "../../../utils/colors";
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -73,9 +74,17 @@ const SheetWrapper = ({
|
||||
borderTopLeftRadius: 15,
|
||||
alignSelf: "center",
|
||||
borderBottomRightRadius: 0,
|
||||
borderBottomLeftRadius: 0
|
||||
borderBottomLeftRadius: 0,
|
||||
...getContainerBorder(colors.primary.border, 0.5),
|
||||
borderBottomWidth: 0
|
||||
};
|
||||
}, [colors.primary.background, largeTablet, smallTablet, width]);
|
||||
}, [
|
||||
colors.primary.background,
|
||||
colors.primary.border,
|
||||
largeTablet,
|
||||
smallTablet,
|
||||
width
|
||||
]);
|
||||
|
||||
const _onOpen = () => {
|
||||
if (lockEvents.current) return;
|
||||
|
||||
@@ -85,8 +85,8 @@ import { SyncStatus, useUserStore } from "../stores/use-user-store";
|
||||
import { updateStatusBarColor } from "../utils/colors";
|
||||
import { BETA } from "../utils/constants";
|
||||
import {
|
||||
eClearEditor,
|
||||
eCloseSheet,
|
||||
eEditorReset,
|
||||
eLoginSessionExpired,
|
||||
eOnLoadNote,
|
||||
eOpenAnnouncementDialog,
|
||||
@@ -201,11 +201,13 @@ const onRequestPartialSync = async (
|
||||
`onRequestPartialSync full:${full}, force:${force}, lastSyncTime:${lastSyncTime}`
|
||||
);
|
||||
|
||||
if (full || force) {
|
||||
await Sync.run("global", force, full, undefined, lastSyncTime);
|
||||
} else {
|
||||
await Sync.run("global", false, false, undefined, lastSyncTime);
|
||||
}
|
||||
await Sync.run(
|
||||
"global",
|
||||
force,
|
||||
full ? "full" : "send",
|
||||
undefined,
|
||||
lastSyncTime
|
||||
);
|
||||
};
|
||||
|
||||
const onLogout = async (reason: string) => {
|
||||
@@ -234,7 +236,7 @@ async function checkForShareExtensionLaunchedInBackground() {
|
||||
if (notesAddedFromIntent || shareExtensionOpened) {
|
||||
const id = useTabStore.getState().getCurrentNoteId();
|
||||
const note = id && (await db.notes.note(id));
|
||||
eSendEvent("webview_reset");
|
||||
eSendEvent(eEditorReset);
|
||||
if (note) setTimeout(() => eSendEvent("loadingNote", note), 1);
|
||||
MMKV.removeItem("shareExtensionOpened");
|
||||
}
|
||||
@@ -531,13 +533,15 @@ export const useAppEvents = () => {
|
||||
}
|
||||
//@ts-ignore
|
||||
globalThis["IS_SHARE_EXTENSION"] = false;
|
||||
} else {
|
||||
const id = useTabStore.getState().getCurrentNoteId();
|
||||
const note = id ? await db.notes.note(id) : undefined;
|
||||
const locked = note && (await db.vaults.itemExists(note));
|
||||
if (locked && SettingsService.canLockAppInBackground()) {
|
||||
eSendEvent(eClearEditor);
|
||||
|
||||
if (
|
||||
SettingsService.getBackgroundEnterTime() + 60 * 1000 * 10 <
|
||||
Date.now()
|
||||
) {
|
||||
// Reset the editor if the app has been in background for more than 10 minutes.
|
||||
eSendEvent(eEditorReset);
|
||||
}
|
||||
} else {
|
||||
await saveEditorState();
|
||||
if (
|
||||
SettingsService.canLockAppInBackground() &&
|
||||
@@ -679,6 +683,9 @@ export const useAppEvents = () => {
|
||||
|
||||
if (db.isInitialized) {
|
||||
Notifications.setupReminders(true);
|
||||
if (SettingsService.get().notifNotes) {
|
||||
Notifications.pinQuickNote(false);
|
||||
}
|
||||
useSettingStore.getState().setAppLoading(false);
|
||||
DatabaseLogger.info("Database initialized");
|
||||
}
|
||||
|
||||
@@ -71,12 +71,10 @@ export const useDBItem = <T extends keyof ItemTypeKey>(
|
||||
if (typeof itemId === "string" && itemId !== itemIdRef.current) return;
|
||||
if (!isValidIdOrIndex(idOrIndex)) return;
|
||||
|
||||
console.log("useDBItem.onUpdateItem", idOrIndex, type);
|
||||
|
||||
if (items && typeof idOrIndex === "number") {
|
||||
items.item(idOrIndex).then((item) => {
|
||||
setItem(item.item);
|
||||
itemIdRef.current = item.item.id;
|
||||
itemIdRef.current = item.item?.id;
|
||||
});
|
||||
} else {
|
||||
if (!(db as any)[type + "s"][type]) {
|
||||
|
||||
@@ -36,7 +36,6 @@ export const useNotebook = (
|
||||
|
||||
const onRequestUpdate = React.useCallback(() => {
|
||||
if (!item?.id) return;
|
||||
console.log("useNotebook.onRequestUpdate", item?.id, Date.now());
|
||||
|
||||
const selector = db.relations.from(
|
||||
{
|
||||
@@ -59,7 +58,6 @@ export const useNotebook = (
|
||||
|
||||
useEffect(() => {
|
||||
if (nestedNotebooks) {
|
||||
console.log("useNotebook.useEffect.onRequestUpdate");
|
||||
onRequestUpdate();
|
||||
}
|
||||
}, [item?.id, onRequestUpdate, nestedNotebooks]);
|
||||
|
||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import BiometicService from "../services/biometrics";
|
||||
import BiometricService from "../services/biometrics";
|
||||
import { eSubscribeEvent, eUnSubscribeEvent } from "../services/event-manager";
|
||||
import { db } from "../common/database";
|
||||
|
||||
@@ -39,8 +39,8 @@ export const useVaultStatus = () => {
|
||||
|
||||
const checkVaultStatus = useCallback(() => {
|
||||
db.vault?.exists().then(async (exists) => {
|
||||
const available = await BiometicService.isBiometryAvailable();
|
||||
const fingerprint = await BiometicService.hasInternetCredentials();
|
||||
const available = await BiometricService.isBiometryAvailable();
|
||||
const fingerprint = await BiometricService.hasInternetCredentials();
|
||||
if (
|
||||
VaultStatusCache.exists === exists &&
|
||||
VaultStatusCache.biometryEnrolled === fingerprint &&
|
||||
|
||||
@@ -64,6 +64,8 @@ import { useSettingStore } from "../stores/use-setting-store";
|
||||
import {
|
||||
eClearEditor,
|
||||
eCloseFullscreenEditor,
|
||||
eOnEnterEditor,
|
||||
eOnExitEditor,
|
||||
eOnLoadNote,
|
||||
eOpenFullscreenEditor,
|
||||
eUnlockNote
|
||||
@@ -164,9 +166,7 @@ const _TabsHolder = () => {
|
||||
_deviceMode === "smallTablet"
|
||||
? dimensions.width -
|
||||
valueLimiter(dimensions.width * 0.4, 300, 450)
|
||||
: dimensions.width > 1100
|
||||
? dimensions.width * 0.55
|
||||
: dimensions.width * 0.5,
|
||||
: dimensions.width * 0.48,
|
||||
zIndex: null,
|
||||
paddingHorizontal: 0
|
||||
}
|
||||
@@ -257,9 +257,7 @@ const _TabsHolder = () => {
|
||||
position: "relative",
|
||||
width:
|
||||
current === "tablet"
|
||||
? size.width > 1100
|
||||
? size.width * 0.55
|
||||
: size.width * 0.5
|
||||
? size.width * 0.48
|
||||
: current === "smallTablet"
|
||||
? size.width - valueLimiter(size.width * 0.4, 300, 450)
|
||||
: size.width,
|
||||
@@ -371,15 +369,9 @@ const _TabsHolder = () => {
|
||||
editor: dimensions.width - valueLimiter(dimensions.width * 0.4, 300, 450)
|
||||
},
|
||||
tablet: {
|
||||
sidebar:
|
||||
dimensions.width > 1100
|
||||
? dimensions.width * 0.15
|
||||
: dimensions.width * 0.2,
|
||||
sidebar: dimensions.width * 0.22,
|
||||
list: dimensions.width * 0.3,
|
||||
editor:
|
||||
dimensions.width > 1100
|
||||
? dimensions.width * 0.55
|
||||
: dimensions.width * 0.5
|
||||
editor: dimensions.width * 0.48
|
||||
}
|
||||
};
|
||||
|
||||
@@ -515,11 +507,13 @@ const _TabsHolder = () => {
|
||||
};
|
||||
export const TabHolder = React.memo(_TabsHolder, () => true);
|
||||
|
||||
const onChangeTab = async (obj) => {
|
||||
if (obj.i === 2) {
|
||||
const onChangeTab = async (event) => {
|
||||
if (event.i === 2) {
|
||||
editorState().movedAway = false;
|
||||
editorState().isFocused = true;
|
||||
activateKeepAwake();
|
||||
eSendEvent(eOnEnterEditor);
|
||||
|
||||
if (!useTabStore.getState().getCurrentNoteId()) {
|
||||
eSendEvent(eOnLoadNote, {
|
||||
newNote: true
|
||||
@@ -529,17 +523,14 @@ const onChangeTab = async (obj) => {
|
||||
useTabStore.getState().getTab(useTabStore.getState().currentTab).locked
|
||||
) {
|
||||
eSendEvent(eUnlockNote);
|
||||
} else {
|
||||
editorState().currentlyEditing = true;
|
||||
eSendEvent(eOnLoadNote);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (obj.from === 2) {
|
||||
if (event.from === 2) {
|
||||
deactivateKeepAwake();
|
||||
editorState().movedAway = true;
|
||||
editorState().isFocused = false;
|
||||
eSendEvent(eClearEditor, "removeHandler");
|
||||
eSendEvent(eOnExitEditor);
|
||||
|
||||
// Lock all tabs with locked notes...
|
||||
for (const tab of useTabStore.getState().tabs) {
|
||||
|
||||
@@ -32,13 +32,14 @@ import WebView from "react-native-webview";
|
||||
import { ShouldStartLoadRequest } from "react-native-webview/lib/WebViewTypes";
|
||||
import { notesnook } from "../../../e2e/test.ids";
|
||||
import { db } from "../../common/database";
|
||||
import BiometicService from "../../services/biometrics";
|
||||
import BiometricService from "../../services/biometrics";
|
||||
import {
|
||||
ToastManager,
|
||||
eSendEvent,
|
||||
eSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import {
|
||||
eEditorReset,
|
||||
eOnLoadNote,
|
||||
eUnlockNote,
|
||||
eUnlockWithBiometrics,
|
||||
@@ -51,7 +52,13 @@ import { EditorProps, useEditorType } from "./tiptap/types";
|
||||
import { useEditor } from "./tiptap/use-editor";
|
||||
import { useEditorEvents } from "./tiptap/use-editor-events";
|
||||
import { syncTabs, useTabStore } from "./tiptap/use-tab-store";
|
||||
import { editorController, editorState } from "./tiptap/utils";
|
||||
import {
|
||||
editorController,
|
||||
editorState,
|
||||
openInternalLink,
|
||||
randId
|
||||
} from "./tiptap/utils";
|
||||
import { tabBarRef } from "../../utils/global-refs";
|
||||
|
||||
const style: ViewStyle = {
|
||||
height: "100%",
|
||||
@@ -61,7 +68,10 @@ const style: ViewStyle = {
|
||||
backgroundColor: "transparent"
|
||||
};
|
||||
const onShouldStartLoadWithRequest = (request: ShouldStartLoadRequest) => {
|
||||
if (request.url.includes("https")) {
|
||||
if (request.url.includes("nn://")) {
|
||||
openInternalLink(request.url);
|
||||
return false;
|
||||
} else if (request.url.includes("https")) {
|
||||
if (Platform.OS === "ios" && !request.isTopFrame) return true;
|
||||
openLinkInBrowser(request.url);
|
||||
return false;
|
||||
@@ -95,24 +105,20 @@ const Editor = React.memo(
|
||||
noToolbar,
|
||||
noHeader
|
||||
});
|
||||
const renderKey = useRef(`editor-0` + editorId);
|
||||
const renderKey = useRef(randId("editor-id") + editorId);
|
||||
useImperativeHandle(ref, () => ({
|
||||
get: () => editor
|
||||
}));
|
||||
useLockedNoteHandler();
|
||||
|
||||
const onError = useCallback(() => {
|
||||
renderKey.current =
|
||||
renderKey.current === `editor-0`
|
||||
? `editor-1` + editorId
|
||||
: `editor-0` + editorId;
|
||||
|
||||
renderKey.current = randId("editor-id") + editorId;
|
||||
editor.state.current.ready = false;
|
||||
editor.setLoading(true);
|
||||
}, [editor, editorId]);
|
||||
|
||||
useEffect(() => {
|
||||
const sub = [eSubscribeEvent("webview_reset", onError)];
|
||||
const sub = [eSubscribeEvent(eEditorReset, onError)];
|
||||
return () => {
|
||||
sub.forEach((s) => s?.unsubscribe());
|
||||
};
|
||||
@@ -139,6 +145,7 @@ const Editor = React.memo(
|
||||
nestedScrollEnabled
|
||||
onError={onError}
|
||||
injectedJavaScriptBeforeContentLoaded={`
|
||||
globalThis.__DEV__ = ${__DEV__}
|
||||
globalThis.readonly=${readonly};
|
||||
globalThis.noToolbar=${noToolbar};
|
||||
globalThis.noHeader=${noHeader};
|
||||
@@ -201,8 +208,8 @@ const useLockedNoteHandler = () => {
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const biometry = await BiometicService.isBiometryAvailable();
|
||||
const fingerprint = await BiometicService.hasInternetCredentials();
|
||||
const biometry = await BiometricService.isBiometryAvailable();
|
||||
const fingerprint = await BiometricService.hasInternetCredentials();
|
||||
useTabStore.setState({
|
||||
biometryAvailable: !!biometry,
|
||||
biometryEnrolled: !!fingerprint
|
||||
@@ -216,9 +223,9 @@ const useLockedNoteHandler = () => {
|
||||
try {
|
||||
if (!tabRef.current?.noteLocked || !tabRef.current) return;
|
||||
console.log("Trying to unlock with biometrics...");
|
||||
const credentials = await BiometicService.getCredentials(
|
||||
const credentials = await BiometricService.getCredentials(
|
||||
"Unlock note",
|
||||
"Unlock note to open it in editor. If biometrics are not working, you can enter device pin to unlock vault."
|
||||
"Unlock note to open it in editor."
|
||||
);
|
||||
|
||||
if (credentials && credentials?.password && tabRef.current.noteId) {
|
||||
@@ -226,6 +233,7 @@ const useLockedNoteHandler = () => {
|
||||
tabRef.current.noteId,
|
||||
credentials?.password
|
||||
);
|
||||
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: note
|
||||
});
|
||||
@@ -262,7 +270,7 @@ const useLockedNoteHandler = () => {
|
||||
try {
|
||||
const unlocked = await db.vault.unlock(password);
|
||||
if (!unlocked) throw new Error("Incorrect vault password");
|
||||
await BiometicService.storeCredentials(password);
|
||||
await BiometricService.storeCredentials(password);
|
||||
eSendEvent("vaultUpdated");
|
||||
ToastManager.show({
|
||||
heading: "Biometric unlocking enabled!",
|
||||
@@ -271,8 +279,8 @@ const useLockedNoteHandler = () => {
|
||||
context: "global"
|
||||
});
|
||||
|
||||
const biometry = await BiometicService.isBiometryAvailable();
|
||||
const fingerprint = await BiometicService.hasInternetCredentials();
|
||||
const biometry = await BiometricService.isBiometryAvailable();
|
||||
const fingerprint = await BiometricService.hasInternetCredentials();
|
||||
useTabStore.setState({
|
||||
biometryAvailable: !!biometry,
|
||||
biometryEnrolled: !!fingerprint
|
||||
@@ -331,7 +339,7 @@ const useLockedNoteHandler = () => {
|
||||
}),
|
||||
eSubscribeEvent(eUnlockWithPassword, onSubmit)
|
||||
];
|
||||
if (tabRef.current?.locked) {
|
||||
if (tabRef.current?.locked && tabBarRef.current?.page() === 2) {
|
||||
unlock();
|
||||
}
|
||||
return () => {
|
||||
|
||||
@@ -48,5 +48,6 @@ export const EventTypes = {
|
||||
unlockWithBiometrics: "editor-events:unlock-biometrics",
|
||||
disableReadonlyMode: "editor-events:disable-readonly-mode",
|
||||
readonlyEditorLoaded: "readonlyEditorLoaded",
|
||||
error: "editorError"
|
||||
error: "editorError",
|
||||
dbLogger: "editor-events:dbLogger"
|
||||
};
|
||||
|
||||
@@ -21,12 +21,11 @@ import Sodium from "@ammarahmed/react-native-sodium";
|
||||
import { isImage } from "@notesnook/core/dist/utils/filename";
|
||||
import { Platform } from "react-native";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import DocumentPicker from "react-native-document-picker";
|
||||
import {
|
||||
ImagePickerResponse,
|
||||
launchCamera,
|
||||
launchImageLibrary
|
||||
} from "react-native-image-picker";
|
||||
import DocumentPicker, {
|
||||
DocumentPickerOptions,
|
||||
DocumentPickerResponse
|
||||
} from "react-native-document-picker";
|
||||
import { Image, openCamera, openPicker } from "react-native-image-crop-picker";
|
||||
import { DatabaseLogger, db } from "../../../common/database";
|
||||
import filesystem from "../../../common/filesystem";
|
||||
import { compressToFile } from "../../../common/filesystem/compress";
|
||||
@@ -42,8 +41,9 @@ import { FILE_SIZE_LIMIT, IMAGE_SIZE_LIMIT } from "../../../utils/constants";
|
||||
import { eCloseSheet } from "../../../utils/events";
|
||||
import { useTabStore } from "./use-tab-store";
|
||||
import { editorController, editorState } from "./utils";
|
||||
import { basename } from "pathe";
|
||||
|
||||
const showEncryptionSheet = (file) => {
|
||||
const showEncryptionSheet = (file: DocumentPickerResponse) => {
|
||||
presentSheet({
|
||||
title: "Encrypting attachment",
|
||||
paragraph: `Please wait while we encrypt ${file.name} file for upload`,
|
||||
@@ -51,24 +51,25 @@ const showEncryptionSheet = (file) => {
|
||||
});
|
||||
};
|
||||
|
||||
const santizeUri = (uri) => {
|
||||
const santizeUri = (uri: string) => {
|
||||
uri = decodeURI(uri);
|
||||
uri = Platform.OS === "ios" ? uri.replace("file:///", "/") : uri;
|
||||
return uri;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* noteId: string,
|
||||
* tabId: string,
|
||||
* type: "image" | "camera" | "file"
|
||||
* reupload: boolean
|
||||
* hash?: string
|
||||
* }} fileOptions
|
||||
*/
|
||||
const file = async (fileOptions) => {
|
||||
type PickerOptions = {
|
||||
noteId?: string;
|
||||
tabId?: number;
|
||||
type: "image" | "camera" | "file";
|
||||
reupload: boolean;
|
||||
hash?: string;
|
||||
context?: string;
|
||||
outputType?: "base64" | "url" | "cache";
|
||||
};
|
||||
|
||||
const file = async (fileOptions: PickerOptions) => {
|
||||
try {
|
||||
const options = {
|
||||
const options: DocumentPickerOptions<"ios"> = {
|
||||
mode: "import",
|
||||
allowMultiSelection: false
|
||||
};
|
||||
@@ -87,11 +88,11 @@ const file = async (fileOptions) => {
|
||||
|
||||
file = file[0];
|
||||
|
||||
let uri = Platform.OS === "ios" ? file.fileCopyUri : file.uri;
|
||||
let uri = Platform.OS === "ios" ? file.fileCopyUri || file.uri : file.uri;
|
||||
|
||||
if (file.size > FILE_SIZE_LIMIT) {
|
||||
if ((file.size || 0) > FILE_SIZE_LIMIT) {
|
||||
ToastManager.show({
|
||||
title: "File too large",
|
||||
heading: "File too large",
|
||||
message: "The maximum allowed size per file is 500 MB",
|
||||
type: "error"
|
||||
});
|
||||
@@ -108,29 +109,40 @@ const file = async (fileOptions) => {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("file uri: ", uri);
|
||||
uri = Platform.OS === "ios" ? santizeUri(uri) : uri;
|
||||
showEncryptionSheet(file);
|
||||
const hash = await Sodium.hashFile({
|
||||
uri: uri,
|
||||
type: "url"
|
||||
});
|
||||
if (!(await attachFile(uri, hash, file.type, file.name, fileOptions)))
|
||||
if (
|
||||
!(await attachFile(
|
||||
uri,
|
||||
hash,
|
||||
file.type || "application/octet-stream",
|
||||
file.name,
|
||||
fileOptions
|
||||
))
|
||||
)
|
||||
return;
|
||||
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
|
||||
|
||||
if (!fileOptions.tabId) return;
|
||||
if (
|
||||
useTabStore.getState().getNoteIdForTab(options.tabId) === options.noteId
|
||||
fileOptions.tabId &&
|
||||
fileOptions.noteId &&
|
||||
useTabStore.getState().getNoteIdForTab(fileOptions.tabId) ===
|
||||
fileOptions.noteId
|
||||
) {
|
||||
if (isImage(file.type)) {
|
||||
if (isImage(file.type || "application/octet-stream")) {
|
||||
editorController.current?.commands.insertImage(
|
||||
{
|
||||
hash: hash,
|
||||
filename: file.name,
|
||||
mime: file.type,
|
||||
size: file.size,
|
||||
dataurl: await db.attachments.read(hash, "base64"),
|
||||
title: file.name
|
||||
mime: file.type || "application/octet-stream",
|
||||
size: file.size || 0,
|
||||
dataurl: (await db.attachments.read(hash, "base64")) as string,
|
||||
type: "image"
|
||||
},
|
||||
fileOptions.tabId
|
||||
);
|
||||
@@ -139,8 +151,9 @@ const file = async (fileOptions) => {
|
||||
{
|
||||
hash: hash,
|
||||
filename: file.name,
|
||||
mime: file.type,
|
||||
size: file.size
|
||||
mime: file.type || "application/octet-stream",
|
||||
size: file.size || 0,
|
||||
type: "file"
|
||||
},
|
||||
fileOptions.tabId
|
||||
);
|
||||
@@ -151,39 +164,72 @@ const file = async (fileOptions) => {
|
||||
eSendEvent(eCloseSheet);
|
||||
}, 1000);
|
||||
} catch (e) {
|
||||
eSendEvent(eCloseSheet);
|
||||
ToastManager.show({
|
||||
heading: e.message,
|
||||
heading: (e as Error).message,
|
||||
message: "You need internet access to attach a file",
|
||||
type: "error",
|
||||
context: "global"
|
||||
});
|
||||
console.log("attachment error: ", e);
|
||||
DatabaseLogger.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* noteId: string,
|
||||
* tabId: string,
|
||||
* type: "image" | "camera" | "file"
|
||||
* reupload: boolean
|
||||
* hash?: string
|
||||
* }} options
|
||||
*/
|
||||
const camera = async (options) => {
|
||||
const camera = async (options: PickerOptions) => {
|
||||
try {
|
||||
await db.attachments.generateKey();
|
||||
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
|
||||
launchCamera(
|
||||
{
|
||||
includeBase64: true,
|
||||
mediaType: "photo"
|
||||
},
|
||||
(response) => handleImageResponse(response, options)
|
||||
);
|
||||
openCamera({
|
||||
mediaType: "photo",
|
||||
includeBase64: true,
|
||||
cropping: false,
|
||||
multiple: true,
|
||||
maxFiles: 10,
|
||||
writeTempFile: true
|
||||
})
|
||||
.then((response) => {
|
||||
handleImageResponse(
|
||||
Array.isArray(response) ? response : [response],
|
||||
options
|
||||
);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log("camera error: ", e);
|
||||
});
|
||||
} catch (e) {
|
||||
ToastManager.show({
|
||||
heading: e.message,
|
||||
heading: (e as Error).message,
|
||||
type: "error",
|
||||
context: "global"
|
||||
});
|
||||
console.log("attachment error:", e);
|
||||
}
|
||||
};
|
||||
|
||||
const gallery = async (options: PickerOptions) => {
|
||||
try {
|
||||
await db.attachments.generateKey();
|
||||
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
|
||||
openPicker({
|
||||
includeBase64: true,
|
||||
mediaType: "photo",
|
||||
maxFiles: 10,
|
||||
cropping: false,
|
||||
multiple: true
|
||||
})
|
||||
.then((response) =>
|
||||
handleImageResponse(
|
||||
Array.isArray(response) ? response : [response],
|
||||
options
|
||||
)
|
||||
)
|
||||
.catch((e) => {
|
||||
console.log("gallery error: ", e);
|
||||
});
|
||||
} catch (e) {
|
||||
useSettingStore.getState().setAppDidEnterBackgroundForAction(false);
|
||||
ToastManager.show({
|
||||
heading: (e as Error).message,
|
||||
message: "You need internet access to attach a file",
|
||||
type: "error",
|
||||
context: "global"
|
||||
@@ -192,53 +238,9 @@ const camera = async (options) => {
|
||||
}
|
||||
};
|
||||
|
||||
const gallery = async (options) => {
|
||||
try {
|
||||
await db.attachments.generateKey();
|
||||
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
|
||||
launchImageLibrary(
|
||||
{
|
||||
includeBase64: true,
|
||||
mediaType: "photo",
|
||||
selectionLimit: 10
|
||||
},
|
||||
(response) => handleImageResponse(response, options)
|
||||
);
|
||||
} catch (e) {
|
||||
ToastManager.show({
|
||||
heading: e.message,
|
||||
message: "You need internet access to attach a file",
|
||||
type: "error",
|
||||
context: "global"
|
||||
});
|
||||
console.log("attachment error:", e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @typedef {{
|
||||
* noteId?: string,
|
||||
* tabId?: string,
|
||||
* type: "image" | "camera" | "file"
|
||||
* reupload: boolean
|
||||
* hash?: string
|
||||
* context?: string
|
||||
* }} ImagePickerOptions
|
||||
*
|
||||
* @param {{
|
||||
* noteId?: string,
|
||||
* tabId?: string,
|
||||
* type: "image" | "camera" | "file"
|
||||
* reupload: boolean
|
||||
* hash?: string
|
||||
* context?: string
|
||||
* }} options
|
||||
* @returns
|
||||
*/
|
||||
const pick = async (options) => {
|
||||
const pick = async (options: PickerOptions) => {
|
||||
if (!PremiumService.get()) {
|
||||
let user = await db.user.getUser();
|
||||
const user = await db.user.getUser();
|
||||
if (editorState().isFocused) {
|
||||
editorState().isFocused = true;
|
||||
}
|
||||
@@ -259,58 +261,55 @@ const pick = async (options) => {
|
||||
file(options);
|
||||
}
|
||||
};
|
||||
/**
|
||||
*
|
||||
* @param {ImagePickerResponse} response
|
||||
* @param {ImagePickerOptions} options
|
||||
* @returns
|
||||
*/
|
||||
const handleImageResponse = async (response, options) => {
|
||||
if (
|
||||
response.didCancel ||
|
||||
response.errorMessage ||
|
||||
!response.assets ||
|
||||
response.assets?.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await AttachImage.present(response);
|
||||
|
||||
const handleImageResponse = async (
|
||||
response: Image[],
|
||||
options: PickerOptions
|
||||
) => {
|
||||
console.log(response, "result-file-picker");
|
||||
const result = await AttachImage.present(response, options.context);
|
||||
if (!result) return;
|
||||
const compress = result.compress;
|
||||
|
||||
for (let image of response.assets) {
|
||||
const isPng = /(png)/g.test(image.type);
|
||||
const isJpeg = /(jpeg|jpg)/g.test(image.type);
|
||||
for (const image of response) {
|
||||
const isPng = /(png)/g.test(image.mime);
|
||||
const isJpeg = /(jpeg|jpg)/g.test(image.mime);
|
||||
|
||||
if (compress && (isPng || isJpeg)) {
|
||||
image.uri = await compressToFile(
|
||||
Platform.OS === "ios" ? "file://" + image.uri : image.uri,
|
||||
image.path = await compressToFile(
|
||||
Platform.OS === "ios" ? "file://" + image.path : image.path,
|
||||
isPng ? "PNG" : "JPEG"
|
||||
);
|
||||
const stat = await RNFetchBlob.fs.stat(image.uri.replace("file://", ""));
|
||||
image.fileSize = stat.size;
|
||||
|
||||
const stat = await RNFetchBlob.fs.stat(image.path.replace("file://", ""));
|
||||
image.size = stat.size;
|
||||
image.path =
|
||||
Platform.OS === "ios" ? image.path.replace("file://", "") : image.path;
|
||||
}
|
||||
|
||||
if (image.fileSize > IMAGE_SIZE_LIMIT) {
|
||||
if (image.size > IMAGE_SIZE_LIMIT) {
|
||||
ToastManager.show({
|
||||
title: "File too large",
|
||||
heading: "File too large",
|
||||
message: "The maximum allowed size per image is 50 MB",
|
||||
type: "error"
|
||||
});
|
||||
return;
|
||||
}
|
||||
let b64 = `data:${image.type};base64, ` + image.base64;
|
||||
const uri = decodeURI(image.uri);
|
||||
const b64 = `data:${image.mime};base64, ` + image.data;
|
||||
const uri = decodeURI(image.path);
|
||||
const hash = await Sodium.hashFile({
|
||||
uri: uri,
|
||||
type: "url"
|
||||
});
|
||||
|
||||
let fileName = image.originalFileName || image.fileName;
|
||||
const fileName = image.sourceURL
|
||||
? basename(image.sourceURL)
|
||||
: image.filename || "image";
|
||||
|
||||
console.log("attaching image...", fileName);
|
||||
|
||||
console.log("attaching file...");
|
||||
if (!(await attachFile(uri, hash, image.type, fileName, options))) return;
|
||||
if (!(await attachFile(uri, hash, image.mime, fileName, options))) return;
|
||||
|
||||
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
|
||||
console.log("attaching image to note...");
|
||||
@@ -322,11 +321,13 @@ const handleImageResponse = async (response, options) => {
|
||||
editorController.current?.commands.insertImage(
|
||||
{
|
||||
hash: hash,
|
||||
mime: image.type,
|
||||
title: fileName,
|
||||
mime: image.mime,
|
||||
type: "image",
|
||||
dataurl: b64,
|
||||
size: image.fileSize,
|
||||
filename: fileName
|
||||
size: image.size,
|
||||
filename: fileName as string,
|
||||
width: image.width,
|
||||
height: image.height
|
||||
},
|
||||
options.tabId
|
||||
);
|
||||
@@ -343,10 +344,16 @@ const handleImageResponse = async (response, options) => {
|
||||
* @param {ImagePickerOptions} options
|
||||
* @returns
|
||||
*/
|
||||
export async function attachFile(uri, hash, type, filename, options) {
|
||||
export async function attachFile(
|
||||
uri: string,
|
||||
hash: string,
|
||||
type: string,
|
||||
filename: string,
|
||||
options: PickerOptions
|
||||
) {
|
||||
try {
|
||||
let exists = await db.attachments.exists(hash);
|
||||
let encryptionInfo;
|
||||
const exists = await db.attachments.exists(hash);
|
||||
let encryptionInfo: any;
|
||||
if (options?.hash && options.hash !== hash) {
|
||||
ToastManager.show({
|
||||
heading: "Please select the same file for reuploading",
|
||||
@@ -358,26 +365,36 @@ export async function attachFile(uri, hash, type, filename, options) {
|
||||
}
|
||||
|
||||
if (!options.reupload && exists) {
|
||||
options.reupload = (await filesystem.getUploadedFileSize(hash)) <= 0;
|
||||
options.reupload = (await filesystem.getUploadedFileSize(hash)) === 0;
|
||||
}
|
||||
|
||||
if (options.reupload) {
|
||||
DatabaseLogger.log(`Deleting file before reupload. ${hash}`);
|
||||
const deleted = await db.fs().deleteFile(hash, false);
|
||||
if (!deleted)
|
||||
throw new Error(`Failed to delete file before reupload. ${hash}`);
|
||||
}
|
||||
|
||||
if (!exists || options?.reupload) {
|
||||
let key = await db.attachments.generateKey();
|
||||
const key = await db.attachments.generateKey();
|
||||
encryptionInfo = await Sodium.encryptFile(key, {
|
||||
uri: uri,
|
||||
type: options.type || "url",
|
||||
type: options.outputType || "url",
|
||||
hash: hash
|
||||
});
|
||||
} as any);
|
||||
encryptionInfo.mimeType = type;
|
||||
encryptionInfo.filename = filename;
|
||||
encryptionInfo.alg = "xcha-stream";
|
||||
encryptionInfo.size = encryptionInfo.length;
|
||||
encryptionInfo.key = key;
|
||||
if (options?.reupload && exists) await db.attachments.reset(hash);
|
||||
if (options?.reupload && exists) {
|
||||
const attachment = await db.attachments.attachment(hash);
|
||||
if (attachment) await db.attachments.reset(attachment?.id);
|
||||
}
|
||||
} else {
|
||||
encryptionInfo = { hash: hash };
|
||||
}
|
||||
await db.attachments.add(encryptionInfo, options.noteId);
|
||||
await db.attachments.add(encryptionInfo);
|
||||
return true;
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e);
|
||||
@@ -37,6 +37,7 @@ export type EditorState = {
|
||||
isAwaitingResult: boolean;
|
||||
scrollPosition: number;
|
||||
overlay?: boolean;
|
||||
initialLoadCalled?: boolean;
|
||||
};
|
||||
|
||||
export type Settings = {
|
||||
@@ -74,6 +75,8 @@ export type EditorMessage<T> = {
|
||||
type: string;
|
||||
noteId: string;
|
||||
tabId: number;
|
||||
resolverId?: string;
|
||||
hasTimeout?: boolean;
|
||||
};
|
||||
|
||||
export type SavePayload = {
|
||||
@@ -84,6 +87,7 @@ export type SavePayload = {
|
||||
sessionHistoryId?: number;
|
||||
ignoreEdit: boolean;
|
||||
tabId: number;
|
||||
pendingChanges?: boolean;
|
||||
};
|
||||
|
||||
export type AppState = {
|
||||
|
||||
@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/* eslint-disable no-case-declarations */
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
import { parseInternalLink } from "@notesnook/core";
|
||||
import { ItemReference } from "@notesnook/core/dist/types";
|
||||
import type { Attachment } from "@notesnook/editor/dist/extensions/attachment/index";
|
||||
import { getDefaultPresets } from "@notesnook/editor/dist/toolbar/tool-definitions";
|
||||
@@ -27,7 +26,6 @@ import Clipboard from "@react-native-clipboard/clipboard";
|
||||
import React, { useCallback, useEffect, useRef } from "react";
|
||||
import {
|
||||
BackHandler,
|
||||
InteractionManager,
|
||||
Keyboard,
|
||||
KeyboardEventListener,
|
||||
NativeEventSubscription,
|
||||
@@ -37,6 +35,7 @@ import { WebViewMessageEvent } from "react-native-webview";
|
||||
import { DatabaseLogger, db } from "../../../common/database";
|
||||
import downloadAttachment from "../../../common/filesystem/download-attachment";
|
||||
import EditorTabs from "../../../components/sheets/editor-tabs";
|
||||
import { Issue } from "../../../components/sheets/github/issue";
|
||||
import LinkNote from "../../../components/sheets/link-note";
|
||||
import ManageTagsSheet from "../../../components/sheets/manage-tags";
|
||||
import { RelationsList } from "../../../components/sheets/relations-list";
|
||||
@@ -60,6 +59,8 @@ import {
|
||||
eClearEditor,
|
||||
eCloseFullscreenEditor,
|
||||
eEditorTabFocused,
|
||||
eOnEnterEditor,
|
||||
eOnExitEditor,
|
||||
eOnLoadNote,
|
||||
eOpenFullscreenEditor,
|
||||
eOpenLoginDialog,
|
||||
@@ -74,8 +75,7 @@ import { useDragState } from "../../settings/editor/state";
|
||||
import { EventTypes } from "./editor-events";
|
||||
import { EditorMessage, EditorProps, useEditorType } from "./types";
|
||||
import { useTabStore } from "./use-tab-store";
|
||||
import { EditorEvents, editorState } from "./utils";
|
||||
import { Issue } from "../../../components/sheets/github/issue";
|
||||
import { EditorEvents, editorState, openInternalLink } from "./utils";
|
||||
|
||||
const publishNote = async () => {
|
||||
const user = useUserStore.getState().user;
|
||||
@@ -269,22 +269,20 @@ export const useEditorEvents = (
|
||||
}, [editor, deviceMode, fullscreen]);
|
||||
|
||||
const onHardwareBackPress = useCallback(() => {
|
||||
if (editorState().currentlyEditing) {
|
||||
console.log(tabBarRef.current?.page());
|
||||
if (tabBarRef.current?.page() === 2) {
|
||||
onBackPress();
|
||||
return true;
|
||||
}
|
||||
}, [onBackPress]);
|
||||
|
||||
const onLoadNote = useCallback(async () => {
|
||||
console.log("Loading...");
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
if (!DDS.isTab) {
|
||||
handleBack.current = BackHandler.addEventListener(
|
||||
"hardwareBackPress",
|
||||
onHardwareBackPress
|
||||
);
|
||||
}
|
||||
});
|
||||
const onEnterEditor = useCallback(async () => {
|
||||
if (!DDS.isTab) {
|
||||
handleBack.current = BackHandler.addEventListener(
|
||||
"hardwareBackPress",
|
||||
onHardwareBackPress
|
||||
);
|
||||
}
|
||||
}, [onHardwareBackPress]);
|
||||
|
||||
const onClearEditorSessionRequest = useCallback(
|
||||
@@ -331,24 +329,39 @@ export const useEditorEvents = (
|
||||
}, [fullscreen, onHardwareBackPress]);
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent(eOnLoadNote + editor.editorId, onLoadNote);
|
||||
const onExitEditor = () => {
|
||||
if (handleBack.current) {
|
||||
handleBack.current.remove();
|
||||
}
|
||||
};
|
||||
|
||||
eSubscribeEvent(eOnEnterEditor, onEnterEditor);
|
||||
eSubscribeEvent(eOnExitEditor, onExitEditor);
|
||||
eSubscribeEvent(
|
||||
eClearEditor + editor.editorId,
|
||||
onClearEditorSessionRequest
|
||||
);
|
||||
return () => {
|
||||
eUnSubscribeEvent(eClearEditor, onClearEditorSessionRequest);
|
||||
eUnSubscribeEvent(eOnLoadNote, onLoadNote);
|
||||
eUnSubscribeEvent(eOnEnterEditor, onEnterEditor);
|
||||
eUnSubscribeEvent(eOnExitEditor, onExitEditor);
|
||||
};
|
||||
}, [editor.editorId, onClearEditorSessionRequest, onLoadNote]);
|
||||
}, [editor.editorId, onClearEditorSessionRequest, onEnterEditor]);
|
||||
|
||||
const onMessage = useCallback(
|
||||
async (event: WebViewMessageEvent) => {
|
||||
const data = event.nativeEvent.data;
|
||||
const editorMessage = JSON.parse(data) as EditorMessage<any>;
|
||||
|
||||
if (editorMessage.hasTimeout && editorMessage.resolverId) {
|
||||
editor.postMessage(EditorEvents.resolve, {
|
||||
data: true,
|
||||
resolverId: editorMessage.resolverId
|
||||
});
|
||||
}
|
||||
|
||||
if (editorMessage.type === EventTypes.load) {
|
||||
console.log("Editor loaded");
|
||||
DatabaseLogger.log("Editor is ready");
|
||||
editor.onLoad();
|
||||
return;
|
||||
}
|
||||
@@ -376,21 +389,36 @@ export const useEditorEvents = (
|
||||
content: editorMessage.value.html as string,
|
||||
noteId: noteId,
|
||||
tabId: editorMessage.tabId,
|
||||
ignoreEdit: (editorMessage.value as ContentMessage).ignoreEdit
|
||||
ignoreEdit: (editorMessage.value as ContentMessage).ignoreEdit,
|
||||
pendingChanges: editorMessage.value?.pendingChanges
|
||||
});
|
||||
break;
|
||||
case EventTypes.title:
|
||||
DatabaseLogger.log("EventTypes.title");
|
||||
editor.saveContent({
|
||||
type: editorMessage.type,
|
||||
title: editorMessage.value as string,
|
||||
title: editorMessage.value?.title as string,
|
||||
noteId: noteId,
|
||||
tabId: editorMessage.tabId,
|
||||
ignoreEdit: false
|
||||
ignoreEdit: false,
|
||||
pendingChanges: editorMessage.value?.pendingChanges
|
||||
});
|
||||
break;
|
||||
case EventTypes.logger:
|
||||
logger.info("[WEBVIEW LOG]", editorMessage.value);
|
||||
logger.info("[EDITOR LOG]", editorMessage.value);
|
||||
break;
|
||||
case EventTypes.dbLogger:
|
||||
if (editorMessage.value.error) {
|
||||
DatabaseLogger.error(
|
||||
editorMessage.value.error,
|
||||
editorMessage.value.error,
|
||||
{
|
||||
message: "[EDITOR_ERROR]" + editorMessage.value.message
|
||||
}
|
||||
);
|
||||
} else {
|
||||
DatabaseLogger.info("[EDITOR_LOG]" + editorMessage.value.message);
|
||||
}
|
||||
break;
|
||||
case EventTypes.contentchange:
|
||||
editor.onContentChanged(editorMessage.noteId);
|
||||
@@ -443,7 +471,7 @@ export const useEditorEvents = (
|
||||
break;
|
||||
case EventTypes.filepicker:
|
||||
editorState().isAwaitingResult = true;
|
||||
const { pick } = require("./picker.js").default;
|
||||
const { pick } = require("./picker").default;
|
||||
pick({
|
||||
type: editorMessage.value,
|
||||
noteId: noteId,
|
||||
@@ -462,12 +490,10 @@ export const useEditorEvents = (
|
||||
|
||||
case EventTypes.getAttachmentData: {
|
||||
const attachment = (editorMessage.value as any)
|
||||
.attachment as Attachment;
|
||||
?.attachment as Attachment;
|
||||
|
||||
console.log(
|
||||
"Getting attachment data:",
|
||||
attachment.hash,
|
||||
attachment.type
|
||||
DatabaseLogger.log(
|
||||
`Getting attachment data: ${attachment?.hash} ${attachment?.type}`
|
||||
);
|
||||
downloadAttachment(attachment.hash, true, {
|
||||
base64: attachment.type === "image",
|
||||
@@ -480,17 +506,17 @@ export const useEditorEvents = (
|
||||
console.log(
|
||||
"Got attachment data:",
|
||||
!!data,
|
||||
(editorMessage.value as any).resolverId
|
||||
editorMessage.resolverId
|
||||
);
|
||||
editor.postMessage(EditorEvents.attachmentData, {
|
||||
resolverId: (editorMessage.value as any).resolverId,
|
||||
editor.postMessage(EditorEvents.resolve, {
|
||||
resolverId: editorMessage.resolverId,
|
||||
data
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
console.log("Error downloading attachment data");
|
||||
editor.postMessage(EditorEvents.attachmentData, {
|
||||
resolverId: (editorMessage.value as any).resolverId,
|
||||
.catch((e) => {
|
||||
DatabaseLogger.error(e);
|
||||
editor.postMessage(EditorEvents.resolve, {
|
||||
resolverId: editorMessage.resolverId,
|
||||
data: undefined
|
||||
});
|
||||
});
|
||||
@@ -519,27 +545,7 @@ export const useEditorEvents = (
|
||||
break;
|
||||
case EventTypes.link:
|
||||
if (editorMessage.value.startsWith("nn://")) {
|
||||
const data = parseInternalLink(editorMessage.value);
|
||||
if (!data?.id) break;
|
||||
if (
|
||||
data.id ===
|
||||
useTabStore
|
||||
.getState()
|
||||
.getNoteIdForTab(useTabStore.getState().currentTab)
|
||||
) {
|
||||
if (data.params?.blockId) {
|
||||
setTimeout(() => {
|
||||
if (!data.params?.blockId) return;
|
||||
editor.commands.scrollIntoViewById(data.params.blockId);
|
||||
}, 150);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: await db.notes.note(data?.id),
|
||||
blockId: data.params?.blockId
|
||||
});
|
||||
openInternalLink(editorMessage.value);
|
||||
console.log(
|
||||
"Opening note from internal link:",
|
||||
editorMessage.value
|
||||
@@ -637,7 +643,7 @@ export const useEditorEvents = (
|
||||
case EventTypes.createInternalLink: {
|
||||
LinkNote.present(
|
||||
editorMessage.value.attributes,
|
||||
editorMessage.value.resolverId
|
||||
editorMessage.resolverId as string
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -662,10 +668,8 @@ export const useEditorEvents = (
|
||||
.updateTab(useTabStore.getState().currentTab, {
|
||||
readonly: false
|
||||
});
|
||||
Navigation.queueRoutesForUpdate();
|
||||
ToastManager.show({
|
||||
heading: "Readonly mode disabled.",
|
||||
type: "success"
|
||||
setTimeout(() => {
|
||||
Navigation.queueRoutesForUpdate();
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -39,6 +39,7 @@ import { DatabaseLogger, db } from "../../../common/database";
|
||||
import useGlobalSafeAreaInsets from "../../../hooks/use-global-safe-area-insets";
|
||||
import { DDS } from "../../../services/device-detection";
|
||||
import {
|
||||
ToastManager,
|
||||
eSendEvent,
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
@@ -46,16 +47,20 @@ import {
|
||||
import Navigation from "../../../services/navigation";
|
||||
import Notifications from "../../../services/notifications";
|
||||
import SettingsService from "../../../services/settings";
|
||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||
import { useTagStore } from "../../../stores/use-tag-store";
|
||||
import {
|
||||
eEditorReset,
|
||||
eEditorTabFocused,
|
||||
eOnLoadNote,
|
||||
eShowMergeDialog,
|
||||
eUpdateNoteInEditor
|
||||
} from "../../../utils/events";
|
||||
import { tabBarRef } from "../../../utils/global-refs";
|
||||
import { unlockVault } from "../../../utils/unlock-vault";
|
||||
import { onNoteCreated } from "../../notes/common";
|
||||
import Commands from "./commands";
|
||||
import { EventTypes } from "./editor-events";
|
||||
import { SessionHistory } from "./session-history";
|
||||
import { EditorState, SavePayload } from "./types";
|
||||
import { syncTabs, useTabStore } from "./use-tab-store";
|
||||
@@ -68,6 +73,7 @@ import {
|
||||
isEditorLoaded,
|
||||
post
|
||||
} from "./utils";
|
||||
import { sleep } from "../../../utils/time";
|
||||
|
||||
type NoteWithContent = Note & {
|
||||
content?: NoteContent<false>;
|
||||
@@ -145,8 +151,9 @@ export const useEditor = (
|
||||
|
||||
useEffect(() => {
|
||||
const event = eSubscribeEvent(eEditorTabFocused, (tabId) => {
|
||||
console.log("Editot tab focus changed", lastTabFocused.current, tabId);
|
||||
if (lastTabFocused.current !== tabId) lock.current = false;
|
||||
lastTabFocused.current = tabId as number;
|
||||
console.log(tabId);
|
||||
});
|
||||
return () => {
|
||||
event?.unsubscribe();
|
||||
@@ -165,10 +172,11 @@ export const useEditor = (
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
overlay(true);
|
||||
state.current.ready = false;
|
||||
setLoading(false);
|
||||
}
|
||||
}, [loading]);
|
||||
}, [loading, overlay]);
|
||||
|
||||
const withTimer = useCallback(
|
||||
(id: string, fn: () => void, duration: number) => {
|
||||
@@ -216,7 +224,8 @@ export const useEditor = (
|
||||
type,
|
||||
ignoreEdit,
|
||||
sessionHistoryId: currentSessionHistoryId,
|
||||
tabId
|
||||
tabId,
|
||||
pendingChanges
|
||||
}: SavePayload) => {
|
||||
if (currentNotes.current[id as string]?.readonly || readonly) return;
|
||||
try {
|
||||
@@ -277,12 +286,26 @@ export const useEditor = (
|
||||
);
|
||||
}, 50);
|
||||
|
||||
const saveTimer = setTimeout(() => {
|
||||
DatabaseLogger.log(`Note save timeout: ${id}...`);
|
||||
ToastManager.error(
|
||||
new Error(
|
||||
"Copy your changes and restart the app to avoid data loss. If the issue persists, please report to us at support@streetwriters.co."
|
||||
),
|
||||
"Saving note is taking too long",
|
||||
"global",
|
||||
15000
|
||||
);
|
||||
}, 30 * 1000);
|
||||
|
||||
if (!locked) {
|
||||
DatabaseLogger.log(`Saving note: ${id}...`);
|
||||
id = await db.notes?.add({ ...noteData });
|
||||
saved = true;
|
||||
DatabaseLogger.log(`Note saved: ${id}...`);
|
||||
|
||||
clearTimeout(saveTimer);
|
||||
|
||||
if (!note && id) {
|
||||
editorSessionHistory.newSession(id);
|
||||
if (id) {
|
||||
@@ -316,9 +339,22 @@ export const useEditor = (
|
||||
Notifications.pinNote(id as string);
|
||||
}
|
||||
} else {
|
||||
if (!db.vault.unlocked) {
|
||||
if (pendingChanges) await sleep(3000);
|
||||
const unlocked = await unlockVault({
|
||||
title: "Unlock vault to save note",
|
||||
paragraph: `This note is locked, unlock to save ${
|
||||
pendingChanges ? "some pending" : ""
|
||||
} changes`,
|
||||
context: "global"
|
||||
});
|
||||
if (!unlocked)
|
||||
throw new Error("Could not save note, vault is locked");
|
||||
}
|
||||
noteData.contentId = note?.contentId;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
await db.vault?.save(noteData as any);
|
||||
clearTimeout(saveTimer);
|
||||
}
|
||||
|
||||
if (id && useTabStore.getState().getTabForNote(id) === tabId) {
|
||||
@@ -341,6 +377,16 @@ export const useEditor = (
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
id &&
|
||||
id === useTabStore.getState().getCurrentNoteId() &&
|
||||
pendingChanges
|
||||
) {
|
||||
postMessage(EditorEvents.title, title || note?.title, tabId);
|
||||
postMessage(EditorEvents.html, data, tabId);
|
||||
currentNotes.current[id] = note;
|
||||
}
|
||||
|
||||
saveCount.current++;
|
||||
return id;
|
||||
} catch (e) {
|
||||
@@ -415,7 +461,11 @@ export const useEditor = (
|
||||
commands.focus(tabId);
|
||||
});
|
||||
} else {
|
||||
if (!event.item) return;
|
||||
if (!event.item) {
|
||||
overlay(false);
|
||||
return;
|
||||
}
|
||||
console.log("LOADING NOTE", event.item.id);
|
||||
const item = event.item;
|
||||
|
||||
const currentTab = useTabStore
|
||||
@@ -443,7 +493,6 @@ export const useEditor = (
|
||||
useTabStore.getState().focusTab(tabId);
|
||||
setTimeout(() => {
|
||||
if (blockIdRef.current) {
|
||||
console.log("scrolling to block", blockIdRef.current);
|
||||
commands.scrollIntoViewById(blockIdRef.current);
|
||||
blockIdRef.current = undefined;
|
||||
}
|
||||
@@ -502,6 +551,7 @@ export const useEditor = (
|
||||
loadingState.current === currentContents.current[item.id]?.data
|
||||
) {
|
||||
// If note is already loading, return.
|
||||
console.log("Note is already loading...");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -535,11 +585,8 @@ export const useEditor = (
|
||||
10000
|
||||
);
|
||||
|
||||
console.log("blockId", blockIdRef.current);
|
||||
|
||||
setTimeout(() => {
|
||||
if (blockIdRef.current) {
|
||||
console.log("scrolling to block", blockIdRef.current);
|
||||
commands.scrollIntoViewById(blockIdRef.current);
|
||||
blockIdRef.current = undefined;
|
||||
}
|
||||
@@ -573,124 +620,131 @@ export const useEditor = (
|
||||
data: Note | ContentItem | TrashItem | DeletedItem,
|
||||
isLocal?: boolean
|
||||
) => {
|
||||
if (SettingsService.get().disableRealtimeSync && !isLocal) return;
|
||||
if (!data) return;
|
||||
try {
|
||||
await (async () => {
|
||||
if (SettingsService.get().disableRealtimeSync && !isLocal) return;
|
||||
if (!data) return;
|
||||
|
||||
if (isDeleted(data) || isTrashItem(data)) {
|
||||
const tabId = useTabStore.getState().getTabForNote(data.id);
|
||||
if (tabId !== undefined) {
|
||||
console.log("Removing tab");
|
||||
await commands.clearContent(tabId);
|
||||
useTabStore.getState().removeTab(tabId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const noteId =
|
||||
(data as ContentItem).type === "tiptap"
|
||||
? (data as ContentItem).noteId
|
||||
: data.id;
|
||||
|
||||
if (!useTabStore.getState().hasTabForNote(noteId)) return;
|
||||
const tabId = useTabStore.getState().getTabForNote(noteId) as number;
|
||||
|
||||
const tab = useTabStore.getState().getTab(tabId);
|
||||
|
||||
const note = data.type === "note" ? data : await db.notes?.note(noteId);
|
||||
|
||||
lock.current = true;
|
||||
|
||||
// Handle this case where note was locked on another device and synced.
|
||||
const locked = await db.vaults.itemExists(
|
||||
currentNotes.current[noteId] as ItemReference
|
||||
);
|
||||
|
||||
if (note) {
|
||||
if (!locked && tab?.noteLocked) {
|
||||
// Note lock removed.
|
||||
if (tab.locked) {
|
||||
if (useTabStore.getState().currentTab === tabId) {
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: note,
|
||||
forced: true
|
||||
});
|
||||
} else {
|
||||
useTabStore.getState().updateTab(tabId, {
|
||||
locked: false,
|
||||
noteLocked: false
|
||||
});
|
||||
commands.setLoading(true, tabId);
|
||||
if (isDeleted(data) || isTrashItem(data)) {
|
||||
const tabId = useTabStore.getState().getTabForNote(data.id);
|
||||
if (tabId !== undefined) {
|
||||
console.log("Removing tab");
|
||||
await commands.clearContent(tabId);
|
||||
useTabStore.getState().removeTab(tabId);
|
||||
}
|
||||
}
|
||||
} else if (!tab?.noteLocked && locked) {
|
||||
// Note lock added.
|
||||
useTabStore.getState().updateTab(tabId, {
|
||||
locked: true,
|
||||
noteLocked: true
|
||||
});
|
||||
if (useTabStore.getState().currentTab !== tabId) {
|
||||
commands.clearContent(tabId);
|
||||
commands.setLoading(true, tabId);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentNotes.current[noteId]?.title !== note.title) {
|
||||
postMessage(EditorEvents.title, note.title, tabId);
|
||||
}
|
||||
commands.setTags(note);
|
||||
if (currentNotes.current[noteId]?.dateEdited !== note.dateEdited) {
|
||||
commands.setStatus(
|
||||
getFormattedDate(note.dateEdited, "date-time"),
|
||||
"Saved",
|
||||
tabId as number
|
||||
);
|
||||
}
|
||||
|
||||
console.log("readonly state changed...", note.readonly);
|
||||
useTabStore.getState().updateTab(tabId, {
|
||||
readonly: note.readonly
|
||||
});
|
||||
}
|
||||
|
||||
if (data.type === "tiptap" && note && !isLocal) {
|
||||
if (lastContentChangeTime.current[noteId] >= data.dateEdited) {
|
||||
lock.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (locked && isEncryptedContent(data)) {
|
||||
const decryptedContent = await db.vault?.decryptContent(data, noteId);
|
||||
if (!decryptedContent) {
|
||||
useTabStore.getState().updateTab(tabId, {
|
||||
locked: true,
|
||||
noteLocked: true
|
||||
});
|
||||
if (useTabStore.getState().currentTab !== tabId) {
|
||||
commands.clearContent(tabId);
|
||||
commands.setLoading(true, tabId);
|
||||
}
|
||||
} else {
|
||||
await postMessage(
|
||||
EditorEvents.updatehtml,
|
||||
decryptedContent.data,
|
||||
tabId
|
||||
);
|
||||
currentContents.current[note.id] = decryptedContent;
|
||||
}
|
||||
} else {
|
||||
const _nextContent = data.data;
|
||||
if (_nextContent === currentContents.current?.data) {
|
||||
lock.current = false;
|
||||
return;
|
||||
}
|
||||
lastContentChangeTime.current[note.id] = note.dateEdited;
|
||||
await postMessage(EditorEvents.updatehtml, _nextContent, tabId);
|
||||
if (!isEncryptedContent(data)) {
|
||||
currentContents.current[note.id] = data as UnencryptedContentItem;
|
||||
|
||||
const noteId =
|
||||
(data as ContentItem).type === "tiptap"
|
||||
? (data as ContentItem).noteId
|
||||
: data.id;
|
||||
|
||||
if (!useTabStore.getState().hasTabForNote(noteId)) return;
|
||||
const tabId = useTabStore.getState().getTabForNote(noteId) as number;
|
||||
|
||||
const tab = useTabStore.getState().getTab(tabId);
|
||||
|
||||
const note =
|
||||
data.type === "note" ? data : await db.notes?.note(noteId);
|
||||
|
||||
lock.current = true;
|
||||
|
||||
// Handle this case where note was locked on another device and synced.
|
||||
const locked = await db.vaults.itemExists(
|
||||
currentNotes.current[noteId] as ItemReference
|
||||
);
|
||||
|
||||
if (note) {
|
||||
if (!locked && tab?.noteLocked) {
|
||||
// Note lock removed.
|
||||
if (tab.locked) {
|
||||
if (useTabStore.getState().currentTab === tabId) {
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: note,
|
||||
forced: true
|
||||
});
|
||||
} else {
|
||||
useTabStore.getState().updateTab(tabId, {
|
||||
locked: false,
|
||||
noteLocked: false
|
||||
});
|
||||
commands.setLoading(true, tabId);
|
||||
}
|
||||
}
|
||||
} else if (!tab?.noteLocked && locked) {
|
||||
// Note lock added.
|
||||
useTabStore.getState().updateTab(tabId, {
|
||||
locked: true,
|
||||
noteLocked: true
|
||||
});
|
||||
if (useTabStore.getState().currentTab !== tabId) {
|
||||
commands.clearContent(tabId);
|
||||
commands.setLoading(true, tabId);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentNotes.current[noteId]?.title !== note.title) {
|
||||
postMessage(EditorEvents.title, note.title, tabId);
|
||||
}
|
||||
commands.setTags(note);
|
||||
if (currentNotes.current[noteId]?.dateEdited !== note.dateEdited) {
|
||||
commands.setStatus(
|
||||
getFormattedDate(note.dateEdited, "date-time"),
|
||||
"Saved",
|
||||
tabId as number
|
||||
);
|
||||
}
|
||||
|
||||
console.log("readonly state changed...", note.readonly);
|
||||
useTabStore.getState().updateTab(tabId, {
|
||||
readonly: note.readonly
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (data.type === "tiptap" && note && !isLocal) {
|
||||
if (lastContentChangeTime.current[noteId] >= data.dateEdited) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (locked && isEncryptedContent(data)) {
|
||||
const decryptedContent = await db.vault?.decryptContent(data);
|
||||
if (!decryptedContent) {
|
||||
useTabStore.getState().updateTab(tabId, {
|
||||
locked: true,
|
||||
noteLocked: true
|
||||
});
|
||||
if (useTabStore.getState().currentTab !== tabId) {
|
||||
commands.clearContent(tabId);
|
||||
commands.setLoading(true, tabId);
|
||||
}
|
||||
} else {
|
||||
await postMessage(
|
||||
EditorEvents.updatehtml,
|
||||
decryptedContent.data,
|
||||
tabId
|
||||
);
|
||||
currentContents.current[note.id] = decryptedContent;
|
||||
}
|
||||
} else {
|
||||
const _nextContent = data.data;
|
||||
if (_nextContent === currentContents.current?.data) {
|
||||
return;
|
||||
}
|
||||
lastContentChangeTime.current[note.id] = note.dateEdited;
|
||||
await postMessage(EditorEvents.updatehtml, _nextContent, tabId);
|
||||
if (!isEncryptedContent(data)) {
|
||||
currentContents.current[note.id] =
|
||||
data as UnencryptedContentItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e as Error, "Error when applying sync changes");
|
||||
} finally {
|
||||
lock.current = false;
|
||||
}
|
||||
lock.current = false;
|
||||
},
|
||||
[postMessage, commands]
|
||||
);
|
||||
@@ -713,7 +767,8 @@ export const useEditor = (
|
||||
type,
|
||||
ignoreEdit,
|
||||
noteId,
|
||||
tabId
|
||||
tabId,
|
||||
pendingChanges
|
||||
}: {
|
||||
noteId?: string;
|
||||
title?: string;
|
||||
@@ -721,18 +776,29 @@ export const useEditor = (
|
||||
type: string;
|
||||
ignoreEdit: boolean;
|
||||
tabId: number;
|
||||
pendingChanges?: boolean;
|
||||
}) => {
|
||||
DatabaseLogger.log(`Saving content...`);
|
||||
DatabaseLogger.log(
|
||||
`saveContent... title: ${!!title}, content: ${!!content}, noteId: ${noteId}`
|
||||
);
|
||||
if (
|
||||
lock.current ||
|
||||
(currentLoadingNoteId.current &&
|
||||
currentLoadingNoteId.current === noteId)
|
||||
) {
|
||||
DatabaseLogger.log(`Skipped saving conent:
|
||||
DatabaseLogger.log(`Skipped saving content:
|
||||
|
||||
lock.current: ${lock.current}
|
||||
currentLoadingNoteId.current: ${currentLoadingNoteId.current}
|
||||
`);
|
||||
if (lock.current) {
|
||||
setTimeout(() => {
|
||||
if (lock.current) {
|
||||
DatabaseLogger.warn("Editor force removed lock after 5 seconds");
|
||||
lock.current = false;
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -740,7 +806,7 @@ export const useEditor = (
|
||||
lastContentChangeTime.current[noteId] = Date.now();
|
||||
}
|
||||
|
||||
if (type === EditorEvents.content && noteId) {
|
||||
if (type === EventTypes.content && noteId) {
|
||||
currentContents.current[noteId as string] = {
|
||||
data: content,
|
||||
type: "tiptap",
|
||||
@@ -755,7 +821,8 @@ export const useEditor = (
|
||||
id: noteId,
|
||||
ignoreEdit,
|
||||
sessionHistoryId: noteId ? editorSessionHistory.get(noteId) : undefined,
|
||||
tabId: tabId
|
||||
tabId: tabId,
|
||||
pendingChanges
|
||||
};
|
||||
withTimer(
|
||||
noteId || "newnote",
|
||||
@@ -767,7 +834,16 @@ export const useEditor = (
|
||||
onChange(params.data);
|
||||
return;
|
||||
}
|
||||
saveNote(params);
|
||||
if (useSettingStore.getState().isAppLoading) {
|
||||
const sub = useSettingStore.subscribe((state) => {
|
||||
if (!state.isAppLoading) {
|
||||
saveNote(params);
|
||||
sub();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
saveNote(params);
|
||||
}
|
||||
},
|
||||
ignoreEdit ? 0 : 150
|
||||
);
|
||||
@@ -810,7 +886,7 @@ export const useEditor = (
|
||||
useTabStore.getState().currentTab
|
||||
))
|
||||
) {
|
||||
eSendEvent("webview_reset", "onReady");
|
||||
eSendEvent(eEditorReset, "onReady");
|
||||
return false;
|
||||
} else {
|
||||
syncTabs();
|
||||
@@ -820,7 +896,6 @@ export const useEditor = (
|
||||
}, [isDefaultEditor, restoreEditorState]);
|
||||
|
||||
const onLoad = useCallback(async () => {
|
||||
if (currentNotes.current) overlay(true);
|
||||
setTimeout(() => {
|
||||
postMessage(EditorEvents.theme, theme);
|
||||
});
|
||||
@@ -835,12 +910,22 @@ export const useEditor = (
|
||||
|
||||
const noteId = useTabStore.getState().getCurrentNoteId();
|
||||
if (!noteId) {
|
||||
overlay(false);
|
||||
loadNote({ newNote: true });
|
||||
if (tabBarRef.current?.page === 1) {
|
||||
if (tabBarRef.current?.page() === 1) {
|
||||
state.current.currentlyEditing = false;
|
||||
}
|
||||
} else if (state.current?.initialLoadCalled) {
|
||||
const note = currentNotes.current[noteId];
|
||||
if (note) {
|
||||
loadNote({
|
||||
item: note
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!state.current?.initialLoadCalled) {
|
||||
state.current.initialLoadCalled = true;
|
||||
}
|
||||
overlay(false);
|
||||
}, [
|
||||
postMessage,
|
||||
theme,
|
||||
|
||||
@@ -76,6 +76,7 @@ export type TabItem = {
|
||||
readonly?: boolean;
|
||||
locked?: boolean;
|
||||
noteLocked?: boolean;
|
||||
pinned?: boolean;
|
||||
};
|
||||
|
||||
const history = new History();
|
||||
|
||||
@@ -22,10 +22,15 @@ import { TextInput } from "react-native";
|
||||
import WebView from "react-native-webview";
|
||||
import { MMKV } from "../../../common/database/mmkv";
|
||||
import {
|
||||
eSendEvent,
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../../services/event-manager";
|
||||
import { AppState, EditorState, useEditorType } from "./types";
|
||||
import { useTabStore } from "./use-tab-store";
|
||||
import { parseInternalLink } from "@notesnook/core";
|
||||
import { eOnLoadNote } from "../../../utils/events";
|
||||
import { db } from "../../../common/database";
|
||||
export const textInput = createRef<TextInput>();
|
||||
export const editorController =
|
||||
createRef<useEditorType>() as MutableRefObject<useEditorType>;
|
||||
@@ -41,7 +46,7 @@ export function editorState() {
|
||||
return editorController.current?.state.current || defaultState;
|
||||
}
|
||||
|
||||
export const EditorEvents: { [name: string]: string } = {
|
||||
export const EditorEvents = {
|
||||
html: "native:html",
|
||||
updatehtml: "native:updatehtml",
|
||||
title: "native:title",
|
||||
@@ -50,7 +55,8 @@ export const EditorEvents: { [name: string]: string } = {
|
||||
logger: "native:logger",
|
||||
status: "native:status",
|
||||
keyboardShown: "native:keyboardShown",
|
||||
attachmentData: "native:attachment-data"
|
||||
attachmentData: "native:attachment-data",
|
||||
resolve: "native:resolve"
|
||||
};
|
||||
|
||||
export function randId(prefix: string) {
|
||||
@@ -176,3 +182,27 @@ export function clearAppState() {
|
||||
appState = undefined;
|
||||
MMKV.removeItem("appState");
|
||||
}
|
||||
|
||||
export async function openInternalLink(url: string) {
|
||||
const data = parseInternalLink(url);
|
||||
if (!data?.id) return false;
|
||||
if (
|
||||
data.id ===
|
||||
useTabStore.getState().getNoteIdForTab(useTabStore.getState().currentTab)
|
||||
) {
|
||||
if (data.params?.blockId) {
|
||||
setTimeout(() => {
|
||||
if (!data.params?.blockId) return;
|
||||
editorController.current.commands.scrollIntoViewById(
|
||||
data.params.blockId
|
||||
);
|
||||
}, 150);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: await db.notes.note(data?.id),
|
||||
blockId: data.params?.blockId
|
||||
});
|
||||
}
|
||||
|
||||
@@ -29,10 +29,10 @@ import SettingsService from "../../services/settings";
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
import { useNotes } from "../../stores/use-notes-store";
|
||||
import { openEditor } from "../notes/common";
|
||||
import LinkNote from "../../components/sheets/link-note";
|
||||
|
||||
export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
|
||||
const [notes, loading] = useNotes();
|
||||
|
||||
const isFocused = useNavigationFocus(navigation, {
|
||||
onFocus: (prev) => {
|
||||
Navigation.routeNeedsUpdate(
|
||||
@@ -80,7 +80,9 @@ export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
|
||||
loading: "Loading your notes"
|
||||
}}
|
||||
/>
|
||||
<FloatingButton title="Create a new note" onPress={openEditor} />
|
||||
{!notes || !notes.placeholders?.length ? null : (
|
||||
<FloatingButton title="Create a new note" onPress={openEditor} />
|
||||
)}
|
||||
</DelayLayout>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -16,40 +16,37 @@ GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { resolveItems } from "@notesnook/common";
|
||||
import { VirtualizedGrouping } from "@notesnook/core";
|
||||
import { Note, Notebook } from "@notesnook/core/dist/types";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { db } from "../../common/database";
|
||||
import DelayLayout from "../../components/delay-layout";
|
||||
import { Header } from "../../components/header";
|
||||
import List from "../../components/list";
|
||||
import { NotebookHeader } from "../../components/list-items/headers/notebook-header";
|
||||
import SelectionHeader from "../../components/selection-header";
|
||||
import { AddNotebookSheet } from "../../components/sheets/add-notebook";
|
||||
import { IconButton } from "../../components/ui/icon-button";
|
||||
import { Pressable } from "../../components/ui/pressable";
|
||||
import Paragraph from "../../components/ui/typography/paragraph";
|
||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||
import {
|
||||
eSendEvent,
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import { eSendEvent, eSubscribeEvent } from "../../services/event-manager";
|
||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||
import useNavigationStore, {
|
||||
NotebookScreenParams
|
||||
} from "../../stores/use-navigation-store";
|
||||
import { eUpdateNotebookRoute } from "../../utils/events";
|
||||
import { findRootNotebookId } from "../../utils/notebooks";
|
||||
import { openEditor, setOnFirstSave } from "../notes/common";
|
||||
import SelectionHeader from "../../components/selection-header";
|
||||
import Paragraph from "../../components/ui/typography/paragraph";
|
||||
import { View } from "react-native";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { IconButton } from "../../components/ui/icon-button";
|
||||
import { Pressable } from "../../components/ui/pressable";
|
||||
import { resolveItems } from "@notesnook/common";
|
||||
import { openEditor, setOnFirstSave } from "../notes/common";
|
||||
|
||||
const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
|
||||
const params = useRef<NotebookScreenParams>(route?.params);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const updateOnFocus = useRef(false);
|
||||
const [breadcrumbs, setBreadcrumbs] = useState<
|
||||
{
|
||||
id: string;
|
||||
@@ -59,11 +56,17 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
|
||||
useNavigationFocus(navigation, {
|
||||
onFocus: () => {
|
||||
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
|
||||
if (updateOnFocus.current) {
|
||||
onRequestUpdate();
|
||||
updateOnFocus.current = false;
|
||||
} else {
|
||||
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
|
||||
}
|
||||
syncWithNavigation();
|
||||
return false;
|
||||
},
|
||||
onBlur: () => {
|
||||
updateOnFocus.current = false;
|
||||
setOnFirstSave(null);
|
||||
return false;
|
||||
}
|
||||
@@ -79,6 +82,15 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
|
||||
const onRequestUpdate = React.useCallback(
|
||||
async (data?: NotebookScreenParams) => {
|
||||
if (
|
||||
useNavigationStore.getState().focusedRouteId !==
|
||||
params.current.item.id &&
|
||||
!data
|
||||
) {
|
||||
updateOnFocus.current = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (data?.item?.id && params.current.item?.id !== data?.item?.id) {
|
||||
const nextRootNotebookId = await findRootNotebookId(data?.item?.id);
|
||||
const currentNotebookRoot = await findRootNotebookId(
|
||||
@@ -104,7 +116,7 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
|
||||
if (notebook) {
|
||||
const breadcrumbs = await db.notebooks.breadcrumbs(notebook.id);
|
||||
setBreadcrumbs(breadcrumbs);
|
||||
setBreadcrumbs(breadcrumbs.slice(0, breadcrumbs.length - 1));
|
||||
params.current.item = notebook;
|
||||
const notes = await db.relations
|
||||
.from(notebook, "note")
|
||||
@@ -122,10 +134,10 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
onRequestUpdate();
|
||||
eSubscribeEvent(eUpdateNotebookRoute, onRequestUpdate);
|
||||
onRequestUpdate(params.current);
|
||||
const sub = eSubscribeEvent(eUpdateNotebookRoute, onRequestUpdate);
|
||||
return () => {
|
||||
eUnSubscribeEvent(eUpdateNotebookRoute, onRequestUpdate);
|
||||
sub?.unsubscribe();
|
||||
};
|
||||
}, [onRequestUpdate]);
|
||||
|
||||
@@ -167,7 +179,7 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
onPressDefaultRightButton={openEditor}
|
||||
/>
|
||||
|
||||
{breadcrumbs ? (
|
||||
{breadcrumbs && breadcrumbs.length > 0 ? (
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
|
||||
@@ -81,16 +81,21 @@ const NotesPage = ({
|
||||
route.name === "ColoredNotes"
|
||||
? (params.current?.item as Color)?.colorCode
|
||||
: undefined;
|
||||
|
||||
const updateOnFocus = useRef(false);
|
||||
const isFocused = useNavigationFocus(navigation, {
|
||||
onFocus: (prev) => {
|
||||
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
|
||||
if (updateOnFocus.current) {
|
||||
onRequestUpdate();
|
||||
updateOnFocus.current = false;
|
||||
} else {
|
||||
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
|
||||
}
|
||||
syncWithNavigation();
|
||||
|
||||
if (focusControl) return !prev.current;
|
||||
return false;
|
||||
},
|
||||
onBlur: () => {
|
||||
updateOnFocus.current = false;
|
||||
setOnFirstSave(null);
|
||||
return false;
|
||||
},
|
||||
@@ -99,7 +104,6 @@ const NotesPage = ({
|
||||
|
||||
const syncWithNavigation = React.useCallback(() => {
|
||||
const { item } = params.current;
|
||||
|
||||
useNavigationStore
|
||||
.getState()
|
||||
.setFocusedRouteId(params?.current?.item?.id || route.name);
|
||||
@@ -113,6 +117,14 @@ const NotesPage = ({
|
||||
|
||||
const onRequestUpdate = React.useCallback(
|
||||
async (data?: NotesScreenParams) => {
|
||||
if (
|
||||
useNavigationStore.getState().focusedRouteId !==
|
||||
params.current.item.id &&
|
||||
!data
|
||||
) {
|
||||
updateOnFocus.current = false;
|
||||
return;
|
||||
}
|
||||
const isNew = data && data?.item?.id !== params.current?.item?.id;
|
||||
if (data) params.current = data;
|
||||
|
||||
|
||||