mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-09-01 11:39:21 +02:00
Compare commits
325 Commits
fix-theme-
...
gen_stack
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb42a2e1a4 | ||
|
|
12c5b6d227 | ||
|
|
364ae51e36 | ||
|
|
18947e0c93 | ||
|
|
3880db4339 | ||
|
|
ae5b0b6fcc | ||
|
|
2c729f90cc | ||
|
|
800514d769 | ||
|
|
b03b9482dc | ||
|
|
9019490dbd | ||
|
|
4a3e23c288 | ||
|
|
1800ebea2d | ||
|
|
a708c9e9eb | ||
|
|
2aeb875fec | ||
|
|
a8164e9f6c | ||
|
|
0ee8e738e5 | ||
|
|
8fdef2ac7d | ||
|
|
920b1d91f3 | ||
|
|
0ad382aced | ||
|
|
c8aa8e37cf | ||
|
|
9a37f7c506 | ||
|
|
717750d5d4 | ||
|
|
1274ecc3b5 | ||
|
|
4c2ad4567c | ||
|
|
ebc01af34a | ||
|
|
e66d8eee13 | ||
|
|
55e6a4b65a | ||
|
|
0aa1db584d | ||
|
|
a0e6266d28 | ||
|
|
bd0aaed863 | ||
|
|
9410c42f29 | ||
|
|
80186c1812 | ||
|
|
37dfda5c54 | ||
|
|
ec83517dc5 | ||
|
|
2eb4911592 | ||
|
|
cd0a6a8fcb | ||
|
|
b15f192244 | ||
|
|
406cb663c9 | ||
|
|
301a30a50a | ||
|
|
81e3432143 | ||
|
|
c43c2936ac | ||
|
|
fdea1807d2 | ||
|
|
5439578fcf | ||
|
|
e05e52f870 | ||
|
|
1c6243d12e | ||
|
|
a51dfb5b6e | ||
|
|
c98bddd35b | ||
|
|
1eb80ccc88 | ||
|
|
5a346aa7de | ||
|
|
910095a769 | ||
|
|
119059befa | ||
|
|
a8f5dea315 | ||
|
|
ca0a4cb02c | ||
|
|
a04cd6b8dc | ||
|
|
3e4ab0f6df | ||
|
|
a7fe1aca44 | ||
|
|
ff0188f602 | ||
|
|
c846f8abc8 | ||
|
|
8d8acfb99d | ||
|
|
3a5b5772ad | ||
|
|
8046932e4e | ||
|
|
c45aa84739 | ||
|
|
d98be849e3 | ||
|
|
727ac6d907 | ||
|
|
8302f8ba4c | ||
|
|
4db8b0db01 | ||
|
|
3f1614ab9a | ||
|
|
909d8c35f8 | ||
|
|
a73780f0b0 | ||
|
|
286fdbeec0 | ||
|
|
59099552f8 | ||
|
|
0b6013be58 | ||
|
|
2d93a17f34 | ||
|
|
5ef7e39e17 | ||
|
|
f7de4c9084 | ||
|
|
0672c864f7 | ||
|
|
cb3706c150 | ||
|
|
08c6a99336 | ||
|
|
adf961fa08 | ||
|
|
0eb9ae47e9 | ||
|
|
aa706300a4 | ||
|
|
fd183a0e1e | ||
|
|
b27cd4be0f | ||
|
|
9575561832 | ||
|
|
02f96f8d14 | ||
|
|
3cf46e7ad1 | ||
|
|
93551aa0b7 | ||
|
|
d00c35bdaf | ||
|
|
6e6b793568 | ||
|
|
9e064d88c6 | ||
|
|
1658ad8d18 | ||
|
|
c94da3bdf0 | ||
|
|
1f0fc83a7c | ||
|
|
a6af92c330 | ||
|
|
ef93ecc481 | ||
|
|
039709822c | ||
|
|
25c861ce77 | ||
|
|
670f1570b5 | ||
|
|
c93d82d2d6 | ||
|
|
bced20031b | ||
|
|
f8bd089b51 | ||
|
|
2a4d04a26f | ||
|
|
a0c32191f9 | ||
|
|
662d59f60a | ||
|
|
13befe1947 | ||
|
|
21f8b6d557 | ||
|
|
aef53cace9 | ||
|
|
382b5b0240 | ||
|
|
a45b66d449 | ||
|
|
dd67b1a803 | ||
|
|
00568ae839 | ||
|
|
2b2ea5717c | ||
|
|
5672de8565 | ||
|
|
1c70ad8cec | ||
|
|
0c344a2146 | ||
|
|
38f69b514c | ||
|
|
b502f6a08f | ||
|
|
d68c3269d3 | ||
|
|
6d9af85e2a | ||
|
|
1d58cfea3c | ||
|
|
c1fa4d3400 | ||
|
|
df179056d6 | ||
|
|
26f9e08c23 | ||
|
|
f719bebe5a | ||
|
|
9693c8ea54 | ||
|
|
040a677895 | ||
|
|
df2708147e | ||
|
|
faba5f6385 | ||
|
|
eab5bccb99 | ||
|
|
ed399c0d0a | ||
|
|
3c7e962fe0 | ||
|
|
56614ef19d | ||
|
|
169318c549 | ||
|
|
bd6a1b9bff | ||
|
|
443d6625f9 | ||
|
|
b6f95ff7cd | ||
|
|
1b13da54a1 | ||
|
|
adf0f132fd | ||
|
|
73ec04b42d | ||
|
|
54a68e4406 | ||
|
|
bd73ae9df4 | ||
|
|
fe41f7b687 | ||
|
|
c9adaa2442 | ||
|
|
10be5771c6 | ||
|
|
43f4669360 | ||
|
|
3b04925e54 | ||
|
|
ba3959af9e | ||
|
|
552d86d9d2 | ||
|
|
096602150e | ||
|
|
e5795bcbb2 | ||
|
|
9be52a69ad | ||
|
|
5809384f62 | ||
|
|
fff70dce16 | ||
|
|
258140b1e0 | ||
|
|
0a00844e64 | ||
|
|
03cea85efc | ||
|
|
8a16ef9b85 | ||
|
|
c82c30b530 | ||
|
|
46599f66da | ||
|
|
d1b532a4ae | ||
|
|
0baf7afc25 | ||
|
|
a0595a7d23 | ||
|
|
1e6f6ab477 | ||
|
|
62d677f9ad | ||
|
|
8de98d98ab | ||
|
|
5b57614bfe | ||
|
|
ae7c538ad7 | ||
|
|
c6394c9e37 | ||
|
|
acf3740a68 | ||
|
|
8423543904 | ||
|
|
b90cb28637 | ||
|
|
780e1e9db0 | ||
|
|
ca6f8e5e4b | ||
|
|
4c4cda1c39 | ||
|
|
6fcbde772c | ||
|
|
eac1750b1c | ||
|
|
5eea179a58 | ||
|
|
fe1ea6dc27 | ||
|
|
cf1b774255 | ||
|
|
198fa33fc0 | ||
|
|
e722866d62 | ||
|
|
58845e5508 | ||
|
|
543c6f17ed | ||
|
|
533f95fcff | ||
|
|
c02f1cde16 | ||
|
|
043f55ead0 | ||
|
|
fcece06a1c | ||
|
|
da7d79edc6 | ||
|
|
a2e7190a4a | ||
|
|
503f2b5876 | ||
|
|
68d57e2b27 | ||
|
|
400aa574cd | ||
|
|
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 |
10
.github/workflows/android.publish.internal.yml
vendored
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.apk
|
||||||
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86_64.apk
|
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86_64.apk
|
||||||
|
|
||||||
- name: Upload signed aab to Github
|
- name: Upload sourcemaps
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: Notesnook.aab
|
name: sourcemaps
|
||||||
path: ${{steps.sign_app.outputs.signedReleaseFile}}
|
path: |
|
||||||
|
apps/mobile/native/android/app/build/**/*.map
|
||||||
|
packages/editor-mobile/sourcemaps/*.map
|
||||||
|
|||||||
11
.github/workflows/android.publish.yml
vendored
11
.github/workflows/android.publish.yml
vendored
@@ -133,7 +133,6 @@ jobs:
|
|||||||
status: completed
|
status: completed
|
||||||
whatsNewDirectory: apps/mobile/native/android/releasenotes/
|
whatsNewDirectory: apps/mobile/native/android/releasenotes/
|
||||||
|
|
||||||
|
|
||||||
- name: Create release draft on Github
|
- name: Create release draft on Github
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
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.apk
|
||||||
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86_64.apk
|
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86_64.apk
|
||||||
|
|
||||||
- name: Upload signed aab to Github
|
- name: Upload sourcemaps
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: Notesnook.aab
|
name: sourcemaps
|
||||||
path: ${{steps.sign_app.outputs.signedReleaseFile}}
|
path: |
|
||||||
|
apps/mobile/native/android/app/build/**/*.map
|
||||||
|
packages/editor-mobile/sourcemaps/*.map
|
||||||
|
|||||||
23
.github/workflows/ios.publish.yml
vendored
23
.github/workflows/ios.publish.yml
vendored
@@ -89,16 +89,19 @@ jobs:
|
|||||||
- name: CCache Stats After Build
|
- name: CCache Stats After Build
|
||||||
run: ccache -sv
|
run: ccache -sv
|
||||||
|
|
||||||
- name: "Upload app to TestFlight"
|
# - name: "Upload app to TestFlight"
|
||||||
uses: apple-actions/upload-testflight-build@v1
|
# uses: apple-actions/upload-testflight-build@v1
|
||||||
with:
|
# with:
|
||||||
app-path: Notesnook.ipa
|
# app-path: Notesnook.ipa
|
||||||
issuer-id: ${{ secrets.API_KEY_ISSUER_ID }}
|
# issuer-id: ${{ secrets.API_KEY_ISSUER_ID }}
|
||||||
api-key-id: ${{ secrets.API_KEY_ID }}
|
# api-key-id: ${{ secrets.API_KEY_ID }}
|
||||||
api-private-key: ${{ secrets.API_KEY }}
|
# api-private-key: ${{ secrets.API_KEY }}
|
||||||
|
|
||||||
- name: Upload Notesnook.ipa to Github
|
- name: Upload Notesnook.ipa to Github
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: Notesnook.ipa
|
name: Notesnook.zip
|
||||||
path: Notesnook.ipa
|
path: |
|
||||||
|
Notesnook.ipa
|
||||||
|
apps/mobile/native/ios/**/*.map
|
||||||
|
packages/editor-mobile/sourcemaps/*.map
|
||||||
|
|||||||
69
.github/workflows/web.beta.publish.yml
vendored
69
.github/workflows/web.beta.publish.yml
vendored
@@ -1,69 +0,0 @@
|
|||||||
name: Publish @notesnook/web Beta
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
- "!v3-beta"
|
|
||||||
paths-ignore:
|
|
||||||
- "apps/mobile/**"
|
|
||||||
- "packages/editor-mobile/**"
|
|
||||||
- "apps/desktop/**"
|
|
||||||
- "apps/vericrypt/**"
|
|
||||||
- "servers/**"
|
|
||||||
- "packages/clipper/**"
|
|
||||||
- "extensions/web-clipper/**"
|
|
||||||
pull_request:
|
|
||||||
types:
|
|
||||||
- closed
|
|
||||||
- ready_for_review
|
|
||||||
- reopened
|
|
||||||
- opened
|
|
||||||
- synchronize
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
- "!v3-beta"
|
|
||||||
paths-ignore:
|
|
||||||
- "apps/mobile/**"
|
|
||||||
- "packages/editor-mobile/**"
|
|
||||||
- "apps/desktop/**"
|
|
||||||
- "apps/vericrypt/**"
|
|
||||||
- "servers/**"
|
|
||||||
- "packages/clipper/**"
|
|
||||||
- "extensions/web-clipper/**"
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Setup Node
|
|
||||||
uses: ./.github/actions/setup-node-with-cache
|
|
||||||
|
|
||||||
- name: Install packages
|
|
||||||
run: |
|
|
||||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
|
||||||
npm run bootstrap -- --scope=web
|
|
||||||
|
|
||||||
- name: Setup environment
|
|
||||||
run: |
|
|
||||||
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
|
|
||||||
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
|
|
||||||
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Generate beta build
|
|
||||||
run: npm run build:beta:web
|
|
||||||
|
|
||||||
- name: Publish to Cloudflare Pages
|
|
||||||
uses: unlike-ltd/github-actions-cloudflare-pages@v0.1.1
|
|
||||||
id: pages
|
|
||||||
with:
|
|
||||||
cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
||||||
cloudflare-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
||||||
cloudflare-project-name: notesnook-beta-app
|
|
||||||
directory: ./apps/web/build
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
github-environment: ${{ (github.ref == 'refs/heads/master' && 'beta') || 'preview' }}
|
|
||||||
6
.github/workflows/web.tests.yml
vendored
6
.github/workflows/web.tests.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
|||||||
run: npm run build:test:web
|
run: npm run build:test:web
|
||||||
|
|
||||||
- name: Archive build artifact
|
- name: Archive build artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: build
|
name: build
|
||||||
path: apps/web/build/**/*
|
path: apps/web/build/**/*
|
||||||
@@ -56,7 +56,7 @@ jobs:
|
|||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Download build
|
- name: Download build
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: build
|
name: build
|
||||||
path: ./apps/web/build
|
path: ./apps/web/build
|
||||||
@@ -88,7 +88,7 @@ jobs:
|
|||||||
working-directory: apps/web
|
working-directory: apps/web
|
||||||
|
|
||||||
- name: Upload test results
|
- name: Upload test results
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: test-results-${{ matrix.shard }}
|
name: test-results-${{ matrix.shard }}
|
||||||
|
|||||||
32
.github/workflows/web.v3-beta.publish.yml
vendored
32
.github/workflows/web.v3-beta.publish.yml
vendored
@@ -1,32 +0,0 @@
|
|||||||
name: Publish @notesnook/web v3 Beta
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Setup Node
|
|
||||||
uses: ./.github/actions/setup-node-with-cache
|
|
||||||
|
|
||||||
- name: Install packages
|
|
||||||
run: |
|
|
||||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
|
||||||
npm run bootstrap -- --scope=web
|
|
||||||
|
|
||||||
- name: Setup environment
|
|
||||||
run: |
|
|
||||||
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
|
|
||||||
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
|
|
||||||
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: npm run build:web
|
|
||||||
|
|
||||||
- name: Publish to Cloudflare Pages
|
|
||||||
run: npx --yes wrangler pages deploy --project-name=notesnook-v3-beta ./apps/web/build/
|
|
||||||
@@ -40,12 +40,35 @@ Once you are inside the `./notesnook` directory, run the preparation step:
|
|||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
Now you can finally start the desktop app:
|
Now you can finally start the desktop app for development:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run start:desktop
|
npm run start:desktop
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To run the app in release mode:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run staging -- --rebuild
|
||||||
|
```
|
||||||
|
|
||||||
|
This will compile and run the app in production mode but it won't generate any packages. To create the final packages, you'll have to run the following commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run release -- --rebuild
|
||||||
|
|
||||||
|
# For macOS
|
||||||
|
npx electron-builder --mac dmg --arm64 --x64 -publish never
|
||||||
|
|
||||||
|
# For Linux (AppImage)
|
||||||
|
npx electron-builder --linux AppImage:x64 AppImage:arm64 -publish never
|
||||||
|
|
||||||
|
# For Windows
|
||||||
|
npx electron-builder --win --publish never
|
||||||
|
```
|
||||||
|
|
||||||
|
Feel free to play around with the `electron-builder` command to get the packages you need. `npx electron-builder --help` is a great resource to learn different commands & platforms supported by `electron-builder`.
|
||||||
|
|
||||||
## Developer guide
|
## Developer guide
|
||||||
|
|
||||||
### The tech stack
|
### The tech stack
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
owner: streetwriters
|
owner: streetwriters
|
||||||
repo: notesnook
|
repo: notesnook
|
||||||
provider: github
|
provider: github
|
||||||
|
updaterCacheDirName: Notesnook
|
||||||
Binary file not shown.
490
apps/desktop/package-lock.json
generated
490
apps/desktop/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@notesnook/desktop",
|
"name": "@notesnook/desktop",
|
||||||
"version": "3.0.0",
|
"version": "3.0.13",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@notesnook/desktop",
|
"name": "@notesnook/desktop",
|
||||||
"version": "3.0.0",
|
"version": "3.0.13",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
"@trpc/server": "10.38.3",
|
"@trpc/server": "10.38.3",
|
||||||
"better-sqlite3-multiple-ciphers": "^9.5.0",
|
"better-sqlite3-multiple-ciphers": "^9.5.0",
|
||||||
"electron-trpc": "0.5.2",
|
"electron-trpc": "0.5.2",
|
||||||
"electron-updater": "6.1.4",
|
"electron-updater": "^6.2.1",
|
||||||
"icojs": "^0.17.1",
|
"icojs": "^0.17.1",
|
||||||
"sodium-native": "^4.1.1",
|
"sodium-native": "^4.1.1",
|
||||||
"typed-emitter": "^2.1.0",
|
"typed-emitter": "^2.1.0",
|
||||||
@@ -28,7 +28,8 @@
|
|||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
"electron": "^29.3.1",
|
"electron": "^29.3.1",
|
||||||
"electron-builder": "^24.13.3",
|
"electron-builder": "^24.13.3",
|
||||||
"esbuild": "^0.20.0",
|
"esbuild": "^0.23.0",
|
||||||
|
"kysely": "^0.27.3",
|
||||||
"node-gyp-build": "^4.8.0",
|
"node-gyp-build": "^4.8.0",
|
||||||
"prebuildify": "^6.0.1",
|
"prebuildify": "^6.0.1",
|
||||||
"tree-kill": "^1.2.2",
|
"tree-kill": "^1.2.2",
|
||||||
@@ -40,7 +41,7 @@
|
|||||||
},
|
},
|
||||||
"../../packages/crypto": {
|
"../../packages/crypto": {
|
||||||
"name": "@notesnook/crypto",
|
"name": "@notesnook/crypto",
|
||||||
"version": "1.1.1",
|
"version": "2.0.7",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@notesnook/sodium": "file:../sodium"
|
"@notesnook/sodium": "file:../sodium"
|
||||||
@@ -70,9 +71,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@electron/asar": {
|
"node_modules/@electron/asar": {
|
||||||
"version": "3.2.9",
|
"version": "3.2.10",
|
||||||
"resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.9.tgz",
|
"resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.10.tgz",
|
||||||
"integrity": "sha512-Vu2P3X2gcZ3MY9W7yH72X9+AMXwUQZEJBrsPIbX0JsdllLtoh62/Q8Wg370/DawIEVKOyfD6KtTLo645ezqxUA==",
|
"integrity": "sha512-mvBSwIBUeiRscrCeJE1LwctAriBj65eUDm0Pc11iE5gRwzkmsdbS7FnZ1XUWjpSeQWL1L5g12Fc/SchPM9DUOw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"commander": "^5.0.0",
|
"commander": "^5.0.0",
|
||||||
@@ -288,9 +289,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.20.0",
|
"version": "0.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz",
|
||||||
"integrity": "sha512-fGFDEctNh0CcSwsiRPxiaqX0P5rq+AqE0SRhYGZ4PX46Lg1FNR6oCxJghf8YgY0WQEgQuh3lErUFE4KxLeRmmw==",
|
"integrity": "sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -300,13 +301,13 @@
|
|||||||
"aix"
|
"aix"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm": {
|
"node_modules/@esbuild/android-arm": {
|
||||||
"version": "0.20.0",
|
"version": "0.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.0.tgz",
|
||||||
"integrity": "sha512-3bMAfInvByLHfJwYPJRlpTeaQA75n8C/QKpEaiS4HrFWFiJlNI0vzq/zCjBrhAYcPyVPG7Eo9dMrcQXuqmNk5g==",
|
"integrity": "sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -316,13 +317,13 @@
|
|||||||
"android"
|
"android"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm64": {
|
"node_modules/@esbuild/android-arm64": {
|
||||||
"version": "0.20.0",
|
"version": "0.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz",
|
||||||
"integrity": "sha512-aVpnM4lURNkp0D3qPoAzSG92VXStYmoVPOgXveAUoQBWRSuQzt51yvSju29J6AHPmwY1BjH49uR29oyfH1ra8Q==",
|
"integrity": "sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -332,13 +333,13 @@
|
|||||||
"android"
|
"android"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-x64": {
|
"node_modules/@esbuild/android-x64": {
|
||||||
"version": "0.20.0",
|
"version": "0.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.0.tgz",
|
||||||
"integrity": "sha512-uK7wAnlRvjkCPzh8jJ+QejFyrP8ObKuR5cBIsQZ+qbMunwR8sbd8krmMbxTLSrDhiPZaJYKQAU5Y3iMDcZPhyQ==",
|
"integrity": "sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -348,13 +349,13 @@
|
|||||||
"android"
|
"android"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-arm64": {
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
"version": "0.20.0",
|
"version": "0.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz",
|
||||||
"integrity": "sha512-AjEcivGAlPs3UAcJedMa9qYg9eSfU6FnGHJjT8s346HSKkrcWlYezGE8VaO2xKfvvlZkgAhyvl06OJOxiMgOYQ==",
|
"integrity": "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -364,13 +365,13 @@
|
|||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-x64": {
|
"node_modules/@esbuild/darwin-x64": {
|
||||||
"version": "0.20.0",
|
"version": "0.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz",
|
||||||
"integrity": "sha512-bsgTPoyYDnPv8ER0HqnJggXK6RyFy4PH4rtsId0V7Efa90u2+EifxytE9pZnsDgExgkARy24WUQGv9irVbTvIw==",
|
"integrity": "sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -380,13 +381,13 @@
|
|||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-arm64": {
|
"node_modules/@esbuild/freebsd-arm64": {
|
||||||
"version": "0.20.0",
|
"version": "0.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz",
|
||||||
"integrity": "sha512-kQ7jYdlKS335mpGbMW5tEe3IrQFIok9r84EM3PXB8qBFJPSc6dpWfrtsC/y1pyrz82xfUIn5ZrnSHQQsd6jebQ==",
|
"integrity": "sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -396,13 +397,13 @@
|
|||||||
"freebsd"
|
"freebsd"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-x64": {
|
"node_modules/@esbuild/freebsd-x64": {
|
||||||
"version": "0.20.0",
|
"version": "0.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz",
|
||||||
"integrity": "sha512-uG8B0WSepMRsBNVXAQcHf9+Ko/Tr+XqmK7Ptel9HVmnykupXdS4J7ovSQUIi0tQGIndhbqWLaIL/qO/cWhXKyQ==",
|
"integrity": "sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -412,13 +413,13 @@
|
|||||||
"freebsd"
|
"freebsd"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm": {
|
"node_modules/@esbuild/linux-arm": {
|
||||||
"version": "0.20.0",
|
"version": "0.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz",
|
||||||
"integrity": "sha512-2ezuhdiZw8vuHf1HKSf4TIk80naTbP9At7sOqZmdVwvvMyuoDiZB49YZKLsLOfKIr77+I40dWpHVeY5JHpIEIg==",
|
"integrity": "sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -428,13 +429,13 @@
|
|||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm64": {
|
"node_modules/@esbuild/linux-arm64": {
|
||||||
"version": "0.20.0",
|
"version": "0.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz",
|
||||||
"integrity": "sha512-uTtyYAP5veqi2z9b6Gr0NUoNv9F/rOzI8tOD5jKcCvRUn7T60Bb+42NDBCWNhMjkQzI0qqwXkQGo1SY41G52nw==",
|
"integrity": "sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -444,13 +445,13 @@
|
|||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ia32": {
|
"node_modules/@esbuild/linux-ia32": {
|
||||||
"version": "0.20.0",
|
"version": "0.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz",
|
||||||
"integrity": "sha512-c88wwtfs8tTffPaoJ+SQn3y+lKtgTzyjkD8NgsyCtCmtoIC8RDL7PrJU05an/e9VuAke6eJqGkoMhJK1RY6z4w==",
|
"integrity": "sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -460,13 +461,13 @@
|
|||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-loong64": {
|
"node_modules/@esbuild/linux-loong64": {
|
||||||
"version": "0.20.0",
|
"version": "0.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz",
|
||||||
"integrity": "sha512-lR2rr/128/6svngnVta6JN4gxSXle/yZEZL3o4XZ6esOqhyR4wsKyfu6qXAL04S4S5CgGfG+GYZnjFd4YiG3Aw==",
|
"integrity": "sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
@@ -476,13 +477,13 @@
|
|||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-mips64el": {
|
"node_modules/@esbuild/linux-mips64el": {
|
||||||
"version": "0.20.0",
|
"version": "0.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz",
|
||||||
"integrity": "sha512-9Sycc+1uUsDnJCelDf6ZNqgZQoK1mJvFtqf2MUz4ujTxGhvCWw+4chYfDLPepMEvVL9PDwn6HrXad5yOrNzIsQ==",
|
"integrity": "sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"mips64el"
|
"mips64el"
|
||||||
],
|
],
|
||||||
@@ -492,13 +493,13 @@
|
|||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ppc64": {
|
"node_modules/@esbuild/linux-ppc64": {
|
||||||
"version": "0.20.0",
|
"version": "0.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz",
|
||||||
"integrity": "sha512-CoWSaaAXOZd+CjbUTdXIJE/t7Oz+4g90A3VBCHLbfuc5yUQU/nFDLOzQsN0cdxgXd97lYW/psIIBdjzQIwTBGw==",
|
"integrity": "sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -508,13 +509,13 @@
|
|||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-riscv64": {
|
"node_modules/@esbuild/linux-riscv64": {
|
||||||
"version": "0.20.0",
|
"version": "0.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz",
|
||||||
"integrity": "sha512-mlb1hg/eYRJUpv8h/x+4ShgoNLL8wgZ64SUr26KwglTYnwAWjkhR2GpoKftDbPOCnodA9t4Y/b68H4J9XmmPzA==",
|
"integrity": "sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -524,13 +525,13 @@
|
|||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-s390x": {
|
"node_modules/@esbuild/linux-s390x": {
|
||||||
"version": "0.20.0",
|
"version": "0.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz",
|
||||||
"integrity": "sha512-fgf9ubb53xSnOBqyvWEY6ukBNRl1mVX1srPNu06B6mNsNK20JfH6xV6jECzrQ69/VMiTLvHMicQR/PgTOgqJUQ==",
|
"integrity": "sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
@@ -540,13 +541,13 @@
|
|||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-x64": {
|
"node_modules/@esbuild/linux-x64": {
|
||||||
"version": "0.20.0",
|
"version": "0.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz",
|
||||||
"integrity": "sha512-H9Eu6MGse++204XZcYsse1yFHmRXEWgadk2N58O/xd50P9EvFMLJTQLg+lB4E1cF2xhLZU5luSWtGTb0l9UeSg==",
|
"integrity": "sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -556,13 +557,13 @@
|
|||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/netbsd-x64": {
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
"version": "0.20.0",
|
"version": "0.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz",
|
||||||
"integrity": "sha512-lCT675rTN1v8Fo+RGrE5KjSnfY0x9Og4RN7t7lVrN3vMSjy34/+3na0q7RIfWDAj0e0rCh0OL+P88lu3Rt21MQ==",
|
"integrity": "sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -572,13 +573,29 @@
|
|||||||
"netbsd"
|
"netbsd"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/openbsd-arm64": {
|
||||||
|
"version": "0.23.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.0.tgz",
|
||||||
|
"integrity": "sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openbsd-x64": {
|
"node_modules/@esbuild/openbsd-x64": {
|
||||||
"version": "0.20.0",
|
"version": "0.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz",
|
||||||
"integrity": "sha512-HKoUGXz/TOVXKQ+67NhxyHv+aDSZf44QpWLa3I1lLvAwGq8x1k0T+e2HHSRvxWhfJrFxaaqre1+YyzQ99KixoA==",
|
"integrity": "sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -588,13 +605,13 @@
|
|||||||
"openbsd"
|
"openbsd"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/sunos-x64": {
|
"node_modules/@esbuild/sunos-x64": {
|
||||||
"version": "0.20.0",
|
"version": "0.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz",
|
||||||
"integrity": "sha512-GDwAqgHQm1mVoPppGsoq4WJwT3vhnz/2N62CzhvApFD1eJyTroob30FPpOZabN+FgCjhG+AgcZyOPIkR8dfD7g==",
|
"integrity": "sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -604,13 +621,13 @@
|
|||||||
"sunos"
|
"sunos"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-arm64": {
|
"node_modules/@esbuild/win32-arm64": {
|
||||||
"version": "0.20.0",
|
"version": "0.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz",
|
||||||
"integrity": "sha512-0vYsP8aC4TvMlOQYozoksiaxjlvUcQrac+muDqj1Fxy6jh9l9CZJzj7zmh8JGfiV49cYLTorFLxg7593pGldwQ==",
|
"integrity": "sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -620,13 +637,13 @@
|
|||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-ia32": {
|
"node_modules/@esbuild/win32-ia32": {
|
||||||
"version": "0.20.0",
|
"version": "0.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz",
|
||||||
"integrity": "sha512-p98u4rIgfh4gdpV00IqknBD5pC84LCub+4a3MO+zjqvU5MVXOc3hqR2UgT2jI2nh3h8s9EQxmOsVI3tyzv1iFg==",
|
"integrity": "sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -636,13 +653,13 @@
|
|||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-x64": {
|
"node_modules/@esbuild/win32-x64": {
|
||||||
"version": "0.20.0",
|
"version": "0.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz",
|
||||||
"integrity": "sha512-NgJnesu1RtWihtTtXGFMU5YSE6JyyHPMxCwBZK7a6/8d31GuSo9l0Ss7w1Jw5QnKUawG6UEehs883kcXf5fYwg==",
|
"integrity": "sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -652,7 +669,7 @@
|
|||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@isaacs/cliui": {
|
"node_modules/@isaacs/cliui": {
|
||||||
@@ -981,6 +998,15 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@xmldom/xmldom": {
|
||||||
|
"version": "0.8.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
|
||||||
|
"integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
|
||||||
|
"devOptional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/7zip-bin": {
|
"node_modules/7zip-bin": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz",
|
||||||
@@ -1107,19 +1133,6 @@
|
|||||||
"electron-builder-squirrel-windows": "24.13.3"
|
"electron-builder-squirrel-windows": "24.13.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/app-builder-lib/node_modules/builder-util-runtime": {
|
|
||||||
"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",
|
|
||||||
"sax": "^1.2.4"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/argparse": {
|
"node_modules/argparse": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||||
@@ -1358,22 +1371,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/builder-util-runtime": {
|
"node_modules/builder-util-runtime": {
|
||||||
"version": "9.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.1.tgz",
|
|
||||||
"integrity": "sha512-2rLv/uQD2x+dJ0J3xtsmI12AlRyk7p45TEbE/6o/fbb633e/S3pPgm+ct+JHsoY7r39dKHnGEFk/AASRFdnXmA==",
|
|
||||||
"dependencies": {
|
|
||||||
"debug": "^4.3.4",
|
|
||||||
"sax": "^1.2.4"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/builder-util/node_modules/builder-util-runtime": {
|
|
||||||
"version": "9.2.4",
|
"version": "9.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.4.tgz",
|
||||||
"integrity": "sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==",
|
"integrity": "sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"sax": "^1.2.4"
|
"sax": "^1.2.4"
|
||||||
@@ -1586,31 +1586,32 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/config-file-ts/node_modules/glob": {
|
"node_modules/config-file-ts/node_modules/glob": {
|
||||||
"version": "10.3.12",
|
"version": "10.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.3.tgz",
|
||||||
"integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==",
|
"integrity": "sha512-Q38SGlYRpVtDBPSWEylRyctn7uDeTp4NQERTLiCT1FqA9JXPYWqAVmQU6qh4r/zMM5ehxTcbaO8EjhWnvEhmyg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"foreground-child": "^3.1.0",
|
"foreground-child": "^3.1.0",
|
||||||
"jackspeak": "^2.3.6",
|
"jackspeak": "^3.1.2",
|
||||||
"minimatch": "^9.0.1",
|
"minimatch": "^9.0.4",
|
||||||
"minipass": "^7.0.4",
|
"minipass": "^7.1.2",
|
||||||
"path-scurry": "^1.10.2"
|
"package-json-from-dist": "^1.0.0",
|
||||||
|
"path-scurry": "^1.11.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"glob": "dist/esm/bin.mjs"
|
"glob": "dist/esm/bin.mjs"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16 || 14 >=14.17"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/config-file-ts/node_modules/minimatch": {
|
"node_modules/config-file-ts/node_modules/minimatch": {
|
||||||
"version": "9.0.4",
|
"version": "9.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||||
"integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
|
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^2.0.1"
|
"brace-expansion": "^2.0.1"
|
||||||
@@ -1623,9 +1624,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/config-file-ts/node_modules/minipass": {
|
"node_modules/config-file-ts/node_modules/minipass": {
|
||||||
"version": "7.0.4",
|
"version": "7.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||||
"integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
|
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16 || 14 >=14.17"
|
"node": ">=16 || 14 >=14.17"
|
||||||
@@ -1833,19 +1834,6 @@
|
|||||||
"dmg-license": "^1.0.11"
|
"dmg-license": "^1.0.11"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dmg-builder/node_modules/builder-util-runtime": {
|
|
||||||
"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",
|
|
||||||
"sax": "^1.2.4"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/dmg-license": {
|
"node_modules/dmg-license": {
|
||||||
"version": "1.0.11",
|
"version": "1.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz",
|
||||||
@@ -1951,19 +1939,6 @@
|
|||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron-builder/node_modules/builder-util-runtime": {
|
|
||||||
"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",
|
|
||||||
"sax": "^1.2.4"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/electron-publish": {
|
"node_modules/electron-publish": {
|
||||||
"version": "24.13.1",
|
"version": "24.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-24.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-24.13.1.tgz",
|
||||||
@@ -1979,19 +1954,6 @@
|
|||||||
"mime": "^2.5.2"
|
"mime": "^2.5.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron-publish/node_modules/builder-util-runtime": {
|
|
||||||
"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",
|
|
||||||
"sax": "^1.2.4"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/electron-trpc": {
|
"node_modules/electron-trpc": {
|
||||||
"version": "0.5.2",
|
"version": "0.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/electron-trpc/-/electron-trpc-0.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/electron-trpc/-/electron-trpc-0.5.2.tgz",
|
||||||
@@ -2006,11 +1968,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron-updater": {
|
"node_modules/electron-updater": {
|
||||||
"version": "6.1.4",
|
"version": "6.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.2.1.tgz",
|
||||||
"integrity": "sha512-yYAJc6RQjjV4WtInZVn+ZcLyXRhbVXoomKEfUUwDqIk5s2wxzLhWaor7lrNgxODyODhipjg4SVPMhJHi5EnsCA==",
|
"integrity": "sha512-83eKIPW14qwZqUUM6wdsIRwVKZyjmHxQ4/8G+1C6iS5PdDt7b1umYQyj1/qPpH510GmHEQe4q0kCPe3qmb3a0Q==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"builder-util-runtime": "9.2.1",
|
"builder-util-runtime": "9.2.4",
|
||||||
"fs-extra": "^10.1.0",
|
"fs-extra": "^10.1.0",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"lazy-val": "^1.0.5",
|
"lazy-val": "^1.0.5",
|
||||||
@@ -2065,41 +2027,42 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.20.0",
|
"version": "0.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz",
|
||||||
"integrity": "sha512-6iwE3Y2RVYCME1jLpBqq7LQWK3MW6vjV2bZy6gt/WrqkY+WE74Spyc0ThAOYpMtITvnjX09CrC6ym7A/m9mebA==",
|
"integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"esbuild": "bin/esbuild"
|
"esbuild": "bin/esbuild"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@esbuild/aix-ppc64": "0.20.0",
|
"@esbuild/aix-ppc64": "0.23.0",
|
||||||
"@esbuild/android-arm": "0.20.0",
|
"@esbuild/android-arm": "0.23.0",
|
||||||
"@esbuild/android-arm64": "0.20.0",
|
"@esbuild/android-arm64": "0.23.0",
|
||||||
"@esbuild/android-x64": "0.20.0",
|
"@esbuild/android-x64": "0.23.0",
|
||||||
"@esbuild/darwin-arm64": "0.20.0",
|
"@esbuild/darwin-arm64": "0.23.0",
|
||||||
"@esbuild/darwin-x64": "0.20.0",
|
"@esbuild/darwin-x64": "0.23.0",
|
||||||
"@esbuild/freebsd-arm64": "0.20.0",
|
"@esbuild/freebsd-arm64": "0.23.0",
|
||||||
"@esbuild/freebsd-x64": "0.20.0",
|
"@esbuild/freebsd-x64": "0.23.0",
|
||||||
"@esbuild/linux-arm": "0.20.0",
|
"@esbuild/linux-arm": "0.23.0",
|
||||||
"@esbuild/linux-arm64": "0.20.0",
|
"@esbuild/linux-arm64": "0.23.0",
|
||||||
"@esbuild/linux-ia32": "0.20.0",
|
"@esbuild/linux-ia32": "0.23.0",
|
||||||
"@esbuild/linux-loong64": "0.20.0",
|
"@esbuild/linux-loong64": "0.23.0",
|
||||||
"@esbuild/linux-mips64el": "0.20.0",
|
"@esbuild/linux-mips64el": "0.23.0",
|
||||||
"@esbuild/linux-ppc64": "0.20.0",
|
"@esbuild/linux-ppc64": "0.23.0",
|
||||||
"@esbuild/linux-riscv64": "0.20.0",
|
"@esbuild/linux-riscv64": "0.23.0",
|
||||||
"@esbuild/linux-s390x": "0.20.0",
|
"@esbuild/linux-s390x": "0.23.0",
|
||||||
"@esbuild/linux-x64": "0.20.0",
|
"@esbuild/linux-x64": "0.23.0",
|
||||||
"@esbuild/netbsd-x64": "0.20.0",
|
"@esbuild/netbsd-x64": "0.23.0",
|
||||||
"@esbuild/openbsd-x64": "0.20.0",
|
"@esbuild/openbsd-arm64": "0.23.0",
|
||||||
"@esbuild/sunos-x64": "0.20.0",
|
"@esbuild/openbsd-x64": "0.23.0",
|
||||||
"@esbuild/win32-arm64": "0.20.0",
|
"@esbuild/sunos-x64": "0.23.0",
|
||||||
"@esbuild/win32-ia32": "0.20.0",
|
"@esbuild/win32-arm64": "0.23.0",
|
||||||
"@esbuild/win32-x64": "0.20.0"
|
"@esbuild/win32-ia32": "0.23.0",
|
||||||
|
"@esbuild/win32-x64": "0.23.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/escalade": {
|
"node_modules/escalade": {
|
||||||
@@ -2224,9 +2187,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/foreground-child": {
|
"node_modules/foreground-child": {
|
||||||
"version": "3.1.1",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz",
|
||||||
"integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
|
"integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cross-spawn": "^7.0.0",
|
"cross-spawn": "^7.0.0",
|
||||||
@@ -2239,6 +2202,18 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/foreground-child/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/form-data": {
|
"node_modules/form-data": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||||
@@ -2370,6 +2345,7 @@
|
|||||||
"version": "7.2.3",
|
"version": "7.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||||
|
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fs.realpath": "^1.0.0",
|
"fs.realpath": "^1.0.0",
|
||||||
@@ -2670,6 +2646,7 @@
|
|||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||||
|
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"once": "^1.3.0",
|
"once": "^1.3.0",
|
||||||
@@ -2767,15 +2744,15 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/jackspeak": {
|
"node_modules/jackspeak": {
|
||||||
"version": "2.3.6",
|
"version": "3.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.1.tgz",
|
||||||
"integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
|
"integrity": "sha512-U23pQPDnmYybVkYjObcuYMk43VRlMLLqLI+RdZy8s8WV8WsxO9SnqSroKaluuvcNOdCAlauKszDwd+umbot5Mg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@isaacs/cliui": "^8.0.2"
|
"@isaacs/cliui": "^8.0.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
@@ -2785,9 +2762,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jake": {
|
"node_modules/jake": {
|
||||||
"version": "10.8.7",
|
"version": "10.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz",
|
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.1.tgz",
|
||||||
"integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==",
|
"integrity": "sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async": "^3.2.3",
|
"async": "^3.2.3",
|
||||||
@@ -2891,6 +2868,15 @@
|
|||||||
"json-buffer": "3.0.1"
|
"json-buffer": "3.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/kysely": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/kysely/-/kysely-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-lG03Ru+XyOJFsjH3OMY6R/9U38IjDPfnOfDgO3ynhbDr+Dz8fak+X6L62vqu3iybQnj+lG84OttBuU9KY3L9kA==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/lazy-val": {
|
"node_modules/lazy-val": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz",
|
||||||
@@ -3155,6 +3141,12 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/package-json-from-dist": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/path-is-absolute": {
|
"node_modules/path-is-absolute": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||||
@@ -3174,28 +3166,37 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/path-scurry": {
|
"node_modules/path-scurry": {
|
||||||
"version": "1.10.2",
|
"version": "1.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz",
|
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
|
||||||
"integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==",
|
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lru-cache": "^10.2.0",
|
"lru-cache": "^10.2.0",
|
||||||
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
|
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16 || 14 >=14.17"
|
"node": ">=16 || 14 >=14.18"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/path-scurry/node_modules/lru-cache": {
|
"node_modules/path-scurry/node_modules/lru-cache": {
|
||||||
"version": "10.2.1",
|
"version": "10.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.0.tgz",
|
||||||
"integrity": "sha512-tS24spDe/zXhWbNPErCHs/AGOzbKGHT+ybSBqmdLm8WZ1xXLWvH8Qn71QPAlqVhd0qUTWjy+Kl9JmISgDdEjsA==",
|
"integrity": "sha512-bfJaPTuEiTYBu+ulDaeQ0F+uLmlfFkMgXj4cbwfuMSjgObGMzb55FMMbDvbRU0fAHZ4sLGkz2mKwcMg8Dvm8Ww==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "14 || >=16.14"
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/path-scurry/node_modules/minipass": {
|
||||||
|
"version": "7.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||||
|
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16 || 14 >=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/peek-readable": {
|
"node_modules/peek-readable": {
|
||||||
@@ -3229,16 +3230,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/plist": {
|
"node_modules/plist": {
|
||||||
"version": "3.0.6",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
|
||||||
"integrity": "sha512-WiIVYyrp8TD4w8yCvyeIr+lkmrGRd5u0VbRnU+tP/aRLxP/YadJUYOMZJ/6hIa3oUyVCsycXvtNRgd5XBJIbiA==",
|
"integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@xmldom/xmldom": "^0.8.8",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
"xmlbuilder": "^15.1.1"
|
"xmlbuilder": "^15.1.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=10.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pngjs": {
|
"node_modules/pngjs": {
|
||||||
@@ -3586,18 +3588,6 @@
|
|||||||
"node": ">=8"
|
"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": {
|
"node_modules/simple-concat": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||||
@@ -3987,9 +3977,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.4.5",
|
"version": "5.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
|
||||||
"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
|
"integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
@@ -4032,9 +4022,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/utf8-byte-length": {
|
"node_modules/utf8-byte-length": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
|
||||||
"integrity": "sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA==",
|
"integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/util-deprecate": {
|
"node_modules/util-deprecate": {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "@notesnook/desktop",
|
"name": "@notesnook/desktop",
|
||||||
"productName": "Notesnook",
|
"productName": "Notesnook",
|
||||||
"description": "Your private note taking space",
|
"description": "Your private note taking space",
|
||||||
"version": "3.0.0",
|
"version": "3.0.13",
|
||||||
"appAppleId": "1544027013",
|
"appAppleId": "1544027013",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
"@trpc/server": "10.38.3",
|
"@trpc/server": "10.38.3",
|
||||||
"better-sqlite3-multiple-ciphers": "^9.5.0",
|
"better-sqlite3-multiple-ciphers": "^9.5.0",
|
||||||
"electron-trpc": "0.5.2",
|
"electron-trpc": "0.5.2",
|
||||||
"electron-updater": "6.1.4",
|
"electron-updater": "^6.2.1",
|
||||||
"icojs": "^0.17.1",
|
"icojs": "^0.17.1",
|
||||||
"sodium-native": "^4.1.1",
|
"sodium-native": "^4.1.1",
|
||||||
"typed-emitter": "^2.1.0",
|
"typed-emitter": "^2.1.0",
|
||||||
@@ -29,7 +29,8 @@
|
|||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
"electron": "^29.3.1",
|
"electron": "^29.3.1",
|
||||||
"electron-builder": "^24.13.3",
|
"electron-builder": "^24.13.3",
|
||||||
"esbuild": "^0.20.0",
|
"esbuild": "^0.23.0",
|
||||||
|
"kysely": "^0.27.3",
|
||||||
"node-gyp-build": "^4.8.0",
|
"node-gyp-build": "^4.8.0",
|
||||||
"prebuildify": "^6.0.1",
|
"prebuildify": "^6.0.1",
|
||||||
"tree-kill": "^1.2.2",
|
"tree-kill": "^1.2.2",
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ await fs.rm("./build/", { force: true, recursive: true });
|
|||||||
|
|
||||||
if (args.rebuild || !existsSync(path.join(webAppPath, "build"))) {
|
if (args.rebuild || !existsSync(path.join(webAppPath, "build"))) {
|
||||||
await exec(
|
await exec(
|
||||||
"npx nx build:desktop @notesnook/web",
|
"yarn nx build:desktop @notesnook/web",
|
||||||
path.join(__dirname, "..", "..", "..")
|
path.join(__dirname, "..", "..", "..")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -46,11 +46,11 @@ if (args.rebuild || !existsSync(path.join(webAppPath, "build"))) {
|
|||||||
// temporary until there's support for prebuilt binaries for linux ARM
|
// temporary until there's support for prebuilt binaries for linux ARM
|
||||||
if (os.platform() === "linux") await patchBetterSQLite3();
|
if (os.platform() === "linux") await patchBetterSQLite3();
|
||||||
|
|
||||||
if (os.platform() === "win32")
|
// if (os.platform() === "win32")
|
||||||
await exec(
|
// await exec(
|
||||||
`npx prebuildify --arch=arm64 --strip -t electron@${packageJson.devDependencies.electron}`,
|
// `npx prebuildify --arch=arm64 --strip -t electron@${packageJson.devDependencies.electron}`,
|
||||||
path.join(__dirname, "..", "node_modules", "sodium-native")
|
// path.join(__dirname, "..", "node_modules", "sodium-native")
|
||||||
);
|
// );
|
||||||
|
|
||||||
await fs.cp(path.join(webAppPath, "build"), "build", {
|
await fs.cp(path.join(webAppPath, "build"), "build", {
|
||||||
recursive: true,
|
recursive: true,
|
||||||
@@ -58,15 +58,15 @@ await fs.cp(path.join(webAppPath, "build"), "build", {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (args.variant === "mas") {
|
if (args.variant === "mas") {
|
||||||
await exec(`npm run bundle:mas`);
|
await exec(`yarn run bundle:mas`);
|
||||||
} else {
|
} else {
|
||||||
await exec(`npm run bundle`);
|
await exec(`yarn run bundle`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await exec(`npx tsc`);
|
await exec(`yarn tsc`);
|
||||||
|
|
||||||
if (args.run) {
|
if (args.run) {
|
||||||
await exec(`npx electron-builder --dir --x64`);
|
await exec(`yarn electron-builder --dir --x64`);
|
||||||
if (process.platform === "win32") {
|
if (process.platform === "win32") {
|
||||||
await exec(`.\\output\\win-unpacked\\Notesnook.exe`);
|
await exec(`.\\output\\win-unpacked\\Notesnook.exe`);
|
||||||
} else if (process.platform === "darwin") {
|
} else if (process.platform === "darwin") {
|
||||||
|
|||||||
@@ -53,11 +53,11 @@ async function onChange(first) {
|
|||||||
if (first) {
|
if (first) {
|
||||||
await fs.rm("./build/", { force: true, recursive: true });
|
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`);
|
await exec(`yarn run bundle`);
|
||||||
execAsync(`npx`, [`tsc`]);
|
execAsync(`yarn`, [`tsc`]);
|
||||||
|
|
||||||
if (await isBundleSame()) {
|
if (await isBundleSame()) {
|
||||||
console.log("Bundle is same. Doing nothing.");
|
console.log("Bundle is same. Doing nothing.");
|
||||||
@@ -66,7 +66,7 @@ async function onChange(first) {
|
|||||||
|
|
||||||
if (first) {
|
if (first) {
|
||||||
await spawnAndWaitUntil(
|
await spawnAndWaitUntil(
|
||||||
["npx", "nx", "start:desktop", "@notesnook/web"],
|
["yarn", "nx", "start:desktop", "@notesnook/web"],
|
||||||
path.join(__dirname, "..", "..", ".."),
|
path.join(__dirname, "..", "..", ".."),
|
||||||
(data) => data.includes("Network: use --host to expose")
|
(data) => data.includes("Network: use --host to expose")
|
||||||
);
|
);
|
||||||
@@ -78,7 +78,7 @@ async function onChange(first) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execAsync(
|
execAsync(
|
||||||
"npx",
|
"yarn",
|
||||||
["electron", path.join("build", "electron.js")],
|
["electron", path.join("build", "electron.js")],
|
||||||
true,
|
true,
|
||||||
cleanup
|
cleanup
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
import { initTRPC } from "@trpc/server";
|
import { initTRPC } from "@trpc/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { dialog, nativeTheme, Notification, shell } from "electron";
|
import { app, dialog, nativeTheme, Notification, shell } from "electron";
|
||||||
import { AutoLaunch } from "../utils/autolaunch";
|
import { AutoLaunch } from "../utils/autolaunch";
|
||||||
import { config, DesktopIntegration } from "../utils/config";
|
import { config, DesktopIntegration } from "../utils/config";
|
||||||
import { bringToFront } from "../utils/bring-to-front";
|
import { bringToFront } from "../utils/bring-to-front";
|
||||||
@@ -32,6 +32,7 @@ import { AssetManager } from "../utils/asset-manager";
|
|||||||
import { isFlatpak } from "../utils";
|
import { isFlatpak } from "../utils";
|
||||||
import { setupDesktopIntegration } from "../utils/desktop-integration";
|
import { setupDesktopIntegration } from "../utils/desktop-integration";
|
||||||
import { rm } from "fs/promises";
|
import { rm } from "fs/promises";
|
||||||
|
import { disableCustomDns, enableCustomDns } from "../utils/custom-dns";
|
||||||
|
|
||||||
const t = initTRPC.create();
|
const t = initTRPC.create();
|
||||||
|
|
||||||
@@ -55,6 +56,15 @@ export const osIntegrationRouter = t.router({
|
|||||||
config.zoomFactor = factor;
|
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),
|
proxyRules: t.procedure.query(() => config.proxyRules),
|
||||||
setProxyRules: t.procedure
|
setProxyRules: t.procedure
|
||||||
.input(z.string().optional())
|
.input(z.string().optional())
|
||||||
@@ -142,6 +152,10 @@ export const osIntegrationRouter = t.router({
|
|||||||
await rm(input);
|
await rm(input);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
restart: t.procedure.query(() => {
|
||||||
|
app.relaunch();
|
||||||
|
app.exit();
|
||||||
|
}),
|
||||||
showNotification: t.procedure
|
showNotification: t.procedure
|
||||||
.input(NotificationOptions)
|
.input(NotificationOptions)
|
||||||
.query(({ input }) => {
|
.query(({ input }) => {
|
||||||
@@ -181,7 +195,10 @@ export const osIntegrationRouter = t.router({
|
|||||||
({ input: { theme, windowControlsIconColor, backgroundColor } }) => {
|
({ input: { theme, windowControlsIconColor, backgroundColor } }) => {
|
||||||
if (windowControlsIconColor) {
|
if (windowControlsIconColor) {
|
||||||
config.windowControlsIconColor = windowControlsIconColor;
|
config.windowControlsIconColor = windowControlsIconColor;
|
||||||
if (process.platform === "win32")
|
if (
|
||||||
|
process.platform === "win32" &&
|
||||||
|
!config.desktopSettings.nativeTitlebar
|
||||||
|
)
|
||||||
globalThis.window?.setTitleBarOverlay({
|
globalThis.window?.setTitleBarOverlay({
|
||||||
symbolColor: windowControlsIconColor
|
symbolColor: windowControlsIconColor
|
||||||
});
|
});
|
||||||
|
|||||||
143
apps/desktop/src/api/sqlite-kysely.ts
Normal file
143
apps/desktop/src/api/sqlite-kysely.ts
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
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 type { Database, Statement } from "better-sqlite3-multiple-ciphers";
|
||||||
|
import type { QueryResult } from "kysely";
|
||||||
|
|
||||||
|
type SQLiteCompatibleType =
|
||||||
|
| number
|
||||||
|
| string
|
||||||
|
| Uint8Array
|
||||||
|
| Array<number>
|
||||||
|
| bigint
|
||||||
|
| null;
|
||||||
|
|
||||||
|
export class SQLite {
|
||||||
|
sqlite?: Database;
|
||||||
|
initialized = false;
|
||||||
|
preparedStatements: Map<string, Statement<unknown[]>> = new Map();
|
||||||
|
retryCounter: Record<string, number> = {};
|
||||||
|
constructor() {
|
||||||
|
console.log("new sqlite worker");
|
||||||
|
}
|
||||||
|
|
||||||
|
async open(filePath: string) {
|
||||||
|
if (this.sqlite) {
|
||||||
|
console.error("Database is already initialized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sqlite = require("better-sqlite3-multiple-ciphers")(
|
||||||
|
filePath
|
||||||
|
).unsafeMode(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper function for preparing SQL statements with caching
|
||||||
|
* to avoid unnecessary computations.
|
||||||
|
*/
|
||||||
|
async prepare(sql: string): Promise<Statement | undefined> {
|
||||||
|
if (!this.sqlite) throw new Error("Database is not initialized.");
|
||||||
|
try {
|
||||||
|
const cached = this.preparedStatements.get(sql);
|
||||||
|
if (cached !== undefined) return cached;
|
||||||
|
|
||||||
|
const prepared = this.sqlite.prepare(sql);
|
||||||
|
if (!prepared) return;
|
||||||
|
|
||||||
|
this.preparedStatements.set(sql, prepared);
|
||||||
|
|
||||||
|
// reset retry count on success
|
||||||
|
this.retryCounter[sql] = 0;
|
||||||
|
return prepared;
|
||||||
|
} catch (ex) {
|
||||||
|
console.error(ex);
|
||||||
|
|
||||||
|
// statement prepare process can be flaky so retry at least 5 times
|
||||||
|
// before giving up.
|
||||||
|
if (this.retryCounter[sql] < 5) {
|
||||||
|
this.retryCounter[sql] = (this.retryCounter[sql] || 0) + 1;
|
||||||
|
console.warn("Failed to prepare statement. Retrying:", sql);
|
||||||
|
return this.prepare(sql);
|
||||||
|
} else this.retryCounter[sql] = 0;
|
||||||
|
|
||||||
|
if (ex instanceof Error) ex.message += ` (query: ${sql})`;
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async exec<R>(
|
||||||
|
sql: string,
|
||||||
|
parameters: SQLiteCompatibleType[] = []
|
||||||
|
): Promise<QueryResult<R>> {
|
||||||
|
const prepared = await this.prepare(sql);
|
||||||
|
if (!prepared) return { rows: [] };
|
||||||
|
try {
|
||||||
|
if (prepared.reader) {
|
||||||
|
return {
|
||||||
|
rows: prepared.all(parameters) as R[]
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const { changes, lastInsertRowid } = prepared.run(parameters);
|
||||||
|
const numAffectedRows =
|
||||||
|
changes !== undefined && changes !== null && !isNaN(changes)
|
||||||
|
? BigInt(changes)
|
||||||
|
: undefined;
|
||||||
|
return {
|
||||||
|
numAffectedRows,
|
||||||
|
insertId:
|
||||||
|
lastInsertRowid !== undefined && lastInsertRowid !== null
|
||||||
|
? typeof lastInsertRowid === "bigint"
|
||||||
|
? lastInsertRowid
|
||||||
|
: BigInt(lastInsertRowid)
|
||||||
|
: undefined,
|
||||||
|
rows: [] as R[]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) e.message += ` (query: ${sql})`;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async run<R>(
|
||||||
|
sql: string,
|
||||||
|
parameters?: SQLiteCompatibleType[]
|
||||||
|
): Promise<QueryResult<R>> {
|
||||||
|
if (!this.sqlite) throw new Error("No database is not opened.");
|
||||||
|
return await this.exec(sql, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
async close() {
|
||||||
|
if (!this.sqlite) return;
|
||||||
|
|
||||||
|
this.preparedStatements.clear();
|
||||||
|
this.sqlite.close();
|
||||||
|
this.sqlite = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(filePath: string) {
|
||||||
|
await this.close();
|
||||||
|
await require("fs/promises").rm(filePath, {
|
||||||
|
force: true,
|
||||||
|
maxRetries: 5,
|
||||||
|
retryDelay: 500
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -61,7 +61,18 @@ export const updaterRouter = t.router({
|
|||||||
"update-not-available"
|
"update-not-available"
|
||||||
),
|
),
|
||||||
onAvailable: createSubscription<"update-available", UpdateInfo>(
|
onAvailable: createSubscription<"update-available", UpdateInfo>(
|
||||||
"update-available"
|
"update-available",
|
||||||
|
() => {
|
||||||
|
if (!config.automaticUpdates) return false;
|
||||||
|
autoUpdater.emit("download-progress", {
|
||||||
|
bytesPerSecond: 0,
|
||||||
|
delta: 0,
|
||||||
|
percent: 0,
|
||||||
|
total: 100,
|
||||||
|
transferred: 0
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
),
|
),
|
||||||
onError: createSubscription("error")
|
onError: createSubscription("error")
|
||||||
});
|
});
|
||||||
@@ -69,10 +80,11 @@ export const updaterRouter = t.router({
|
|||||||
function createSubscription<
|
function createSubscription<
|
||||||
TName extends keyof AppUpdaterEvents,
|
TName extends keyof AppUpdaterEvents,
|
||||||
TReturnType = Parameters<AppUpdaterEvents[TName]>[0]
|
TReturnType = Parameters<AppUpdaterEvents[TName]>[0]
|
||||||
>(eventName: TName) {
|
>(eventName: TName, handler?: (args: TReturnType) => boolean) {
|
||||||
return t.procedure.subscription(() => {
|
return t.procedure.subscription(() => {
|
||||||
return observable<TReturnType>((emit) => {
|
return observable<TReturnType>((emit) => {
|
||||||
const listener: AppUpdaterEvents[TName] = (...args: any[]) => {
|
const listener: AppUpdaterEvents[TName] = (...args: any[]) => {
|
||||||
|
if (handler?.(args[0])) return;
|
||||||
emit.next(args[0]);
|
emit.next(args[0]);
|
||||||
};
|
};
|
||||||
autoUpdater.removeAllListeners(eventName);
|
autoUpdater.removeAllListeners(eventName);
|
||||||
|
|||||||
@@ -21,3 +21,4 @@ export * from "./constants";
|
|||||||
export type { AppRouter } from "./api";
|
export type { AppRouter } from "./api";
|
||||||
export { type UpdateInfo } from "builder-util-runtime";
|
export { type UpdateInfo } from "builder-util-runtime";
|
||||||
export { type DesktopIntegration } from "./utils/config";
|
export { type DesktopIntegration } from "./utils/config";
|
||||||
|
export { SQLite } from "./api/sqlite-kysely.js";
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import path from "path";
|
|||||||
import { bringToFront } from "./utils/bring-to-front";
|
import { bringToFront } from "./utils/bring-to-front";
|
||||||
import { bridge } from "./api/bridge";
|
import { bridge } from "./api/bridge";
|
||||||
import { setupDesktopIntegration } from "./utils/desktop-integration";
|
import { setupDesktopIntegration } from "./utils/desktop-integration";
|
||||||
|
import { disableCustomDns, enableCustomDns } from "./utils/custom-dns";
|
||||||
|
|
||||||
// only run a single instance
|
// only run a single instance
|
||||||
if (!MAC_APP_STORE && !app.requestSingleInstanceLock()) {
|
if (!MAC_APP_STORE && !app.requestSingleInstanceLock()) {
|
||||||
@@ -83,22 +84,27 @@ async function createWindow() {
|
|||||||
format: process.platform === "win32" ? "ico" : "png"
|
format: process.platform === "win32" ? "ico" : "png"
|
||||||
}),
|
}),
|
||||||
|
|
||||||
titleBarStyle: "hidden",
|
...(config.desktopSettings.nativeTitlebar
|
||||||
frame: process.platform === "win32" || process.platform === "darwin",
|
? {}
|
||||||
titleBarOverlay: {
|
: {
|
||||||
height: 37,
|
titleBarStyle: "hidden",
|
||||||
color: "#00000000",
|
frame: process.platform === "win32" || process.platform === "darwin",
|
||||||
symbolColor: config.windowControlsIconColor
|
titleBarOverlay: {
|
||||||
},
|
height: 37,
|
||||||
trafficLightPosition: {
|
color: "#00000000",
|
||||||
x: 16,
|
symbolColor: config.windowControlsIconColor
|
||||||
y: 12
|
},
|
||||||
},
|
trafficLightPosition: {
|
||||||
|
x: 16,
|
||||||
|
y: 12
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
zoomFactor: config.zoomFactor,
|
zoomFactor: config.zoomFactor,
|
||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
contextIsolation: false,
|
contextIsolation: false,
|
||||||
|
nodeIntegrationInWorker: true,
|
||||||
spellcheck: config.isSpellCheckerEnabled,
|
spellcheck: config.isSpellCheckerEnabled,
|
||||||
preload: __dirname + "/preload.js"
|
preload: __dirname + "/preload.js"
|
||||||
}
|
}
|
||||||
@@ -151,14 +157,8 @@ async function createWindow() {
|
|||||||
app.once("ready", async () => {
|
app.once("ready", async () => {
|
||||||
console.info("App ready. Opening window.");
|
console.info("App ready. Opening window.");
|
||||||
|
|
||||||
app.configureHostResolver({
|
if (config.customDns) enableCustomDns();
|
||||||
secureDnsServers: [
|
else disableCustomDns();
|
||||||
"https://mozilla.cloudflare-dns.com/dns-query",
|
|
||||||
"https://dns.quad9.net/dns-query"
|
|
||||||
],
|
|
||||||
enableBuiltInResolver: true,
|
|
||||||
secureDnsMode: "automatic"
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isDevelopment()) registerProtocol();
|
if (!isDevelopment()) registerProtocol();
|
||||||
await createWindow();
|
await createWindow();
|
||||||
|
|||||||
@@ -23,13 +23,11 @@ import { type RendererGlobalElectronTRPC } from "electron-trpc/src/types";
|
|||||||
import type { NNCrypto } from "@notesnook/crypto";
|
import type { NNCrypto } from "@notesnook/crypto";
|
||||||
import { ipcRenderer } from "electron";
|
import { ipcRenderer } from "electron";
|
||||||
import { platform } from "os";
|
import { platform } from "os";
|
||||||
import sqlite3, { Database } from "better-sqlite3-multiple-ciphers";
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
var os: () => "mas" | ReturnType<typeof platform>;
|
var os: () => "mas" | ReturnType<typeof platform>;
|
||||||
var electronTRPC: RendererGlobalElectronTRPC;
|
var electronTRPC: RendererGlobalElectronTRPC;
|
||||||
var NativeNNCrypto: (new () => NNCrypto) | undefined;
|
var NativeNNCrypto: (new () => NNCrypto) | undefined;
|
||||||
var createSQLite3Database: (filename: string) => Database;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
process.once("loaded", async () => {
|
process.once("loaded", async () => {
|
||||||
@@ -43,5 +41,4 @@ process.once("loaded", async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
globalThis.NativeNNCrypto = require("@notesnook/crypto").NNCrypto;
|
globalThis.NativeNNCrypto = require("@notesnook/crypto").NNCrypto;
|
||||||
globalThis.createSQLite3Database = (filename) => sqlite3(filename);
|
|
||||||
globalThis.os = () => (MAC_APP_STORE ? "mas" : platform());
|
globalThis.os = () => (MAC_APP_STORE ? "mas" : platform());
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ async function configureAutoUpdater() {
|
|||||||
autoUpdater.allowDowngrade = false;
|
autoUpdater.allowDowngrade = false;
|
||||||
autoUpdater.allowPrerelease = false;
|
autoUpdater.allowPrerelease = false;
|
||||||
autoUpdater.autoInstallOnAppQuit = true;
|
autoUpdater.autoInstallOnAppQuit = true;
|
||||||
|
autoUpdater.disableWebInstaller = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { configureAutoUpdater };
|
export { configureAutoUpdater };
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ export const DesktopIntegration = z.object({
|
|||||||
autoStart: z.boolean().optional(),
|
autoStart: z.boolean().optional(),
|
||||||
startMinimized: z.boolean().optional(),
|
startMinimized: z.boolean().optional(),
|
||||||
minimizeToSystemTray: z.boolean().optional(),
|
minimizeToSystemTray: z.boolean().optional(),
|
||||||
closeToSystemTray: z.boolean().optional()
|
closeToSystemTray: z.boolean().optional(),
|
||||||
|
nativeTitlebar: z.boolean().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type DesktopIntegration = z.infer<typeof DesktopIntegration>;
|
export type DesktopIntegration = z.infer<typeof DesktopIntegration>;
|
||||||
@@ -35,7 +36,8 @@ export const config = {
|
|||||||
autoStart: false,
|
autoStart: false,
|
||||||
startMinimized: false,
|
startMinimized: false,
|
||||||
minimizeToSystemTray: false,
|
minimizeToSystemTray: false,
|
||||||
closeToSystemTray: false
|
closeToSystemTray: false,
|
||||||
|
nativeTitlebar: false
|
||||||
},
|
},
|
||||||
privacyMode: false,
|
privacyMode: false,
|
||||||
isSpellCheckerEnabled: true,
|
isSpellCheckerEnabled: true,
|
||||||
@@ -43,6 +45,7 @@ export const config = {
|
|||||||
theme: nativeTheme.themeSource,
|
theme: nativeTheme.themeSource,
|
||||||
automaticUpdates: true,
|
automaticUpdates: true,
|
||||||
proxyRules: "",
|
proxyRules: "",
|
||||||
|
customDns: true,
|
||||||
|
|
||||||
backgroundColor: nativeTheme.themeSource === "dark" ? "#0f0f0f" : "#ffffff",
|
backgroundColor: nativeTheme.themeSource === "dark" ? "#0f0f0f" : "#ffffff",
|
||||||
windowControlsIconColor:
|
windowControlsIconColor:
|
||||||
|
|||||||
37
apps/desktop/src/utils/custom-dns.ts
Normal file
37
apps/desktop/src/utils/custom-dns.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
This file is part of the Notesnook project (https://notesnook.com/)
|
||||||
|
|
||||||
|
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { app } from "electron";
|
||||||
|
|
||||||
|
export function enableCustomDns() {
|
||||||
|
app.configureHostResolver({
|
||||||
|
secureDnsServers: [
|
||||||
|
"https://mozilla.cloudflare-dns.com/dns-query",
|
||||||
|
"https://dns.quad9.net/dns-query"
|
||||||
|
],
|
||||||
|
enableBuiltInResolver: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function disableCustomDns() {
|
||||||
|
app.configureHostResolver({
|
||||||
|
secureDnsServers: [],
|
||||||
|
enableBuiltInResolver: true
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -16,13 +16,13 @@ GNU General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import "react-native-gesture-handler";
|
|
||||||
import {
|
import {
|
||||||
THEME_COMPATIBILITY_VERSION,
|
THEME_COMPATIBILITY_VERSION,
|
||||||
useThemeEngineStore
|
useThemeEngineStore
|
||||||
} from "@notesnook/theme";
|
} from "@notesnook/theme";
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { I18nManager, View } from "react-native";
|
import { I18nManager, View } from "react-native";
|
||||||
|
import "react-native-gesture-handler";
|
||||||
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
||||||
import { SafeAreaProvider } from "react-native-safe-area-context";
|
import { SafeAreaProvider } from "react-native-safe-area-context";
|
||||||
import AppLockedOverlay from "./components/app-lock-overlay";
|
import AppLockedOverlay from "./components/app-lock-overlay";
|
||||||
@@ -40,30 +40,25 @@ import { useUserStore } from "./stores/use-user-store";
|
|||||||
I18nManager.allowRTL(false);
|
I18nManager.allowRTL(false);
|
||||||
I18nManager.forceRTL(false);
|
I18nManager.forceRTL(false);
|
||||||
I18nManager.swapLeftAndRightInRTL(false);
|
I18nManager.swapLeftAndRightInRTL(false);
|
||||||
|
const { appLockEnabled, appLockMode } = SettingsService.get();
|
||||||
|
if (appLockEnabled || appLockMode !== "none") {
|
||||||
|
useUserStore.getState().lockApp(true);
|
||||||
|
}
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const init = useAppEvents();
|
useAppEvents();
|
||||||
|
//@ts-ignore
|
||||||
|
globalThis["IS_MAIN_APP_RUNNING"] = true;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { appLockEnabled, appLockMode } = SettingsService.get();
|
SettingsService.onFirstLaunch();
|
||||||
if (appLockEnabled || appLockMode !== "none") {
|
|
||||||
useUserStore.getState().lockApp(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
//@ts-ignore
|
|
||||||
globalThis["IS_MAIN_APP_RUNNING"] = true;
|
|
||||||
init();
|
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
SettingsService.onFirstLaunch();
|
|
||||||
await Notifications.get();
|
await Notifications.get();
|
||||||
if (SettingsService.get().notifNotes) {
|
if (SettingsService.get().notifNotes) {
|
||||||
Notifications.pinQuickNote(true);
|
Notifications.pinQuickNote(true);
|
||||||
}
|
}
|
||||||
TipManager.init();
|
TipManager.init();
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import { MMKVLoader, ProcessingModes } from "react-native-mmkv-storage";
|
|||||||
import { generateSecureRandom } from "react-native-securerandom";
|
import { generateSecureRandom } from "react-native-securerandom";
|
||||||
import { DatabaseLogger } from ".";
|
import { DatabaseLogger } from ".";
|
||||||
import { MMKV } from "./mmkv";
|
import { MMKV } from "./mmkv";
|
||||||
|
import { ToastManager } from "../../services/event-manager";
|
||||||
|
|
||||||
// Database key cipher is persisted across different user sessions hence it has
|
// Database key cipher is persisted across different user sessions hence it has
|
||||||
// it's independent storage which we will never clear. This is only used when application has
|
// it's independent storage which we will never clear. This is only used when application has
|
||||||
@@ -43,9 +44,8 @@ const IOS_KEYCHAIN_ACCESS_GROUP = "group.org.streetwriters.notesnook";
|
|||||||
const IOS_KEYCHAIN_SERVICE_NAME = "org.streetwriters.notesnook";
|
const IOS_KEYCHAIN_SERVICE_NAME = "org.streetwriters.notesnook";
|
||||||
const KEYCHAIN_SERVER_DBKEY = "notesnook:db";
|
const KEYCHAIN_SERVER_DBKEY = "notesnook:db";
|
||||||
|
|
||||||
const NOTESNOOK_APPLOCK_KEY_SALT = "3dqclWbOYllfk9kk";
|
const NOTESNOOK_APPLOCK_KEY_SALT = "kBwr1Kre86ebOZ8ThLu2OA";
|
||||||
const NOTESNOOK_DB_KEY_SALT = "2rcgSprDmRvZ1AAa";
|
const NOTESNOOK_DB_KEY_SALT = "SNuzOcEK3amoqL0WvPeKqw";
|
||||||
const NOTESNOOK_USER_KEY_SALT = "7qO4qeoM6PbsAJ0Q";
|
|
||||||
|
|
||||||
const DB_KEY_CIPHER = "databaseKeyCipher";
|
const DB_KEY_CIPHER = "databaseKeyCipher";
|
||||||
const USER_KEY_CIPHER = "userKeyCipher";
|
const USER_KEY_CIPHER = "userKeyCipher";
|
||||||
@@ -117,9 +117,7 @@ export async function setAppLockVerificationCipher(appLockPassword) {
|
|||||||
NOTESNOOK_APPLOCK_KEY_SALT
|
NOTESNOOK_APPLOCK_KEY_SALT
|
||||||
);
|
);
|
||||||
const encrypted = await encrypt(appLockCredentials, generatePassword());
|
const encrypted = await encrypt(appLockCredentials, generatePassword());
|
||||||
|
|
||||||
CipherStorage.setMap(APPLOCK_CIPHER, encrypted);
|
CipherStorage.setMap(APPLOCK_CIPHER, encrypted);
|
||||||
|
|
||||||
DatabaseLogger.info("setAppLockVerificationCipher");
|
DatabaseLogger.info("setAppLockVerificationCipher");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
DatabaseLogger.error(e);
|
DatabaseLogger.error(e);
|
||||||
@@ -135,10 +133,8 @@ export async function validateAppLockPassword(appLockPassword) {
|
|||||||
try {
|
try {
|
||||||
const appLockCipher = CipherStorage.getMap(APPLOCK_CIPHER);
|
const appLockCipher = CipherStorage.getMap(APPLOCK_CIPHER);
|
||||||
if (!appLockCipher) return true;
|
if (!appLockCipher) return true;
|
||||||
const decrypted = await decrypt(
|
const key = await Sodium.deriveKey(appLockPassword, appLockCipher.salt);
|
||||||
await Sodium.deriveKey(appLockPassword, NOTESNOOK_APPLOCK_KEY_SALT),
|
const decrypted = await decrypt(key, appLockCipher);
|
||||||
appLockCipher
|
|
||||||
);
|
|
||||||
|
|
||||||
DatabaseLogger.info(
|
DatabaseLogger.info(
|
||||||
`validateAppLockPassword: ${typeof decrypted === "string"}`
|
`validateAppLockPassword: ${typeof decrypted === "string"}`
|
||||||
@@ -214,7 +210,7 @@ export async function getDatabaseKey(appLockPassword) {
|
|||||||
const userKeyCipher = await encrypt(
|
const userKeyCipher = await encrypt(
|
||||||
{
|
{
|
||||||
key: DB_KEY,
|
key: DB_KEY,
|
||||||
salt: NOTESNOOK_USER_KEY_SALT
|
salt: NOTESNOOK_DB_KEY_SALT
|
||||||
},
|
},
|
||||||
userKeyCredentials.password
|
userKeyCredentials.password
|
||||||
);
|
);
|
||||||
@@ -227,6 +223,7 @@ export async function getDatabaseKey(appLockPassword) {
|
|||||||
|
|
||||||
return DB_KEY;
|
return DB_KEY;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
ToastManager.error(e, "Error getting database key");
|
||||||
console.log(e, "error");
|
console.log(e, "error");
|
||||||
DatabaseLogger.error(e);
|
DatabaseLogger.error(e);
|
||||||
return null;
|
return null;
|
||||||
@@ -239,7 +236,7 @@ export async function deriveCryptoKey(data) {
|
|||||||
const userKeyCipher = await encrypt(
|
const userKeyCipher = await encrypt(
|
||||||
{
|
{
|
||||||
key: await getDatabaseKey(),
|
key: await getDatabaseKey(),
|
||||||
salt: NOTESNOOK_USER_KEY_SALT
|
salt: NOTESNOOK_DB_KEY_SALT
|
||||||
},
|
},
|
||||||
credentials.key
|
credentials.key
|
||||||
);
|
);
|
||||||
@@ -264,13 +261,12 @@ export async function getCryptoKey(_name) {
|
|||||||
|
|
||||||
const key = await decrypt(
|
const key = await decrypt(
|
||||||
{
|
{
|
||||||
key: await getDatabaseKey()
|
key: await getDatabaseKey(),
|
||||||
|
salt: keyCipher.salt
|
||||||
},
|
},
|
||||||
keyCipher
|
keyCipher
|
||||||
);
|
);
|
||||||
|
|
||||||
DatabaseLogger.info("User key decrypted: ", !!key);
|
|
||||||
|
|
||||||
return key;
|
return key;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("getCryptoKey", e);
|
console.log("getCryptoKey", e);
|
||||||
|
|||||||
@@ -85,4 +85,10 @@ export async function setupDatabase(password) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const db = database;
|
export const db = database;
|
||||||
export const DatabaseLogger = dbLogger;
|
let DatabaseLogger = dbLogger.scope(Platform.OS);
|
||||||
|
|
||||||
|
const setLogger = () => {
|
||||||
|
DatabaseLogger = dbLogger.scope(Platform.OS);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { DatabaseLogger, setLogger };
|
||||||
|
|||||||
@@ -16,14 +16,30 @@ GNU General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import { MMKVLoader } from "react-native-mmkv-storage";
|
|
||||||
import { initialize } from "@notesnook/core/dist/logger";
|
import { initialize } from "@notesnook/core/dist/logger";
|
||||||
import { KV } from "./storage";
|
import { SqliteAdapter, SqliteIntrospector, SqliteQueryCompiler } from "kysely";
|
||||||
|
import { Platform } from "react-native";
|
||||||
|
import { setLogger } from ".";
|
||||||
|
import { RNSqliteDriver } from "./sqlite.kysely";
|
||||||
|
|
||||||
const LoggerStorage = new MMKVLoader()
|
let loggerLoaded = false;
|
||||||
.withInstanceID("notesnook_logs")
|
const initializeLogger = async () => {
|
||||||
.initialize();
|
if (loggerLoaded) return;
|
||||||
|
await initialize({
|
||||||
|
dialect: (name) => ({
|
||||||
|
createDriver: () => {
|
||||||
|
return new RNSqliteDriver({ async: true, dbName: name });
|
||||||
|
},
|
||||||
|
createAdapter: () => new SqliteAdapter(),
|
||||||
|
createIntrospector: (db) => new SqliteIntrospector(db),
|
||||||
|
createQueryCompiler: () => new SqliteQueryCompiler()
|
||||||
|
}),
|
||||||
|
tempStore: "memory",
|
||||||
|
journalMode: Platform.OS === "ios" ? "DELETE" : "WAL"
|
||||||
|
});
|
||||||
|
setLogger();
|
||||||
|
loggerLoaded = true;
|
||||||
|
};
|
||||||
|
|
||||||
initialize(new KV(LoggerStorage));
|
export { initializeLogger };
|
||||||
|
|
||||||
export { LoggerStorage };
|
|
||||||
|
|||||||
@@ -218,12 +218,7 @@ export default async function downloadAttachment(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (options.base64 || options.text) {
|
if (options.base64 || options.text) {
|
||||||
console.log(
|
DatabaseLogger.log(`Starting to decrypt... hash: ${attachment.hash}`);
|
||||||
"starting decrypt base64 file...",
|
|
||||||
options.base64,
|
|
||||||
options.text,
|
|
||||||
attachment.hash
|
|
||||||
);
|
|
||||||
return await db.attachments.read(
|
return await db.attachments.read(
|
||||||
attachment.hash,
|
attachment.hash,
|
||||||
options.base64 ? "base64" : "text"
|
options.base64 ? "base64" : "text"
|
||||||
@@ -292,7 +287,7 @@ export default async function downloadAttachment(
|
|||||||
|
|
||||||
return fileUri;
|
return fileUri;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("download attachment error: ", e);
|
DatabaseLogger.error(e);
|
||||||
if (attachment.dateUploaded) {
|
if (attachment.dateUploaded) {
|
||||||
RNFetchBlob.fs
|
RNFetchBlob.fs
|
||||||
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}`)
|
.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 RNFetchBlob from "react-native-blob-util";
|
||||||
import { ToastManager } from "../../services/event-manager";
|
import { ToastManager } from "../../services/event-manager";
|
||||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||||
import { db } from "../database";
|
import { DatabaseLogger, db } from "../database";
|
||||||
import { cacheDir, fileCheck } from "./utils";
|
import { cacheDir, fileCheck } from "./utils";
|
||||||
import { createCacheDir, exists } from "./io";
|
import { createCacheDir, exists } from "./io";
|
||||||
|
|
||||||
export async function downloadFile(filename, data, cancelToken) {
|
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();
|
await createCacheDir();
|
||||||
let { url, headers } = data;
|
let { url, headers } = data;
|
||||||
let path = `${cacheDir}/${filename}`;
|
let path = `${cacheDir}/${filename}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (await exists(filename)) {
|
if (await exists(filename)) {
|
||||||
console.log("Exists already", filename);
|
DatabaseLogger.log(`File Exists already: ${filename}`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,13 +47,24 @@ export async function downloadFile(filename, data, cancelToken) {
|
|||||||
method: "GET",
|
method: "GET",
|
||||||
headers
|
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`);
|
throw new Error(`${res.status}: Unable to resolve download url`);
|
||||||
|
}
|
||||||
|
|
||||||
const downloadUrl = await res.text();
|
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;
|
let totalSize = 0;
|
||||||
console.log("Download starting");
|
DatabaseLogger.log(`Download starting: ${filename}`);
|
||||||
let request = RNFetchBlob.config({
|
let request = RNFetchBlob.config({
|
||||||
path: path,
|
path: path,
|
||||||
IOSBackgroundTask: true
|
IOSBackgroundTask: true
|
||||||
@@ -61,13 +75,15 @@ export async function downloadFile(filename, data, cancelToken) {
|
|||||||
.getState()
|
.getState()
|
||||||
.setProgress(0, total, filename, recieved, "download");
|
.setProgress(0, total, filename, recieved, "download");
|
||||||
totalSize = total;
|
totalSize = total;
|
||||||
console.log("downloading: ", recieved, total);
|
DatabaseLogger.log(`Downloading: ${filename}, ${recieved}/${total}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
cancelToken.cancel = () => {
|
cancelToken.cancel = () => {
|
||||||
useAttachmentStore.getState().remove(filename);
|
useAttachmentStore.getState().remove(filename);
|
||||||
request.cancel();
|
request.cancel();
|
||||||
|
DatabaseLogger.log(`Download cancelled: ${filename}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = await request;
|
let response = await request;
|
||||||
await fileCheck(response, totalSize);
|
await fileCheck(response, totalSize);
|
||||||
let status = response.info().status;
|
let status = response.info().status;
|
||||||
@@ -91,7 +107,10 @@ export async function downloadFile(filename, data, cancelToken) {
|
|||||||
|
|
||||||
useAttachmentStore.getState().remove(filename);
|
useAttachmentStore.getState().remove(filename);
|
||||||
RNFetchBlob.fs.unlink(path).catch(console.log);
|
RNFetchBlob.fs.unlink(path).catch(console.log);
|
||||||
console.log("Download file error:", e, url, headers);
|
DatabaseLogger.error(e, {
|
||||||
|
url,
|
||||||
|
headers
|
||||||
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,18 +119,17 @@ export async function getUploadedFileSize(hash) {
|
|||||||
try {
|
try {
|
||||||
const url = `${hosts.API_HOST}/s3?name=${hash}`;
|
const url = `${hosts.API_HOST}/s3?name=${hash}`;
|
||||||
const token = await db.tokenManager.getAccessToken();
|
const token = await db.tokenManager.getAccessToken();
|
||||||
|
|
||||||
const attachmentInfo = await fetch(url, {
|
const attachmentInfo = await fetch(url, {
|
||||||
method: "HEAD",
|
method: "HEAD",
|
||||||
headers: { Authorization: `Bearer ${token}` }
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
});
|
});
|
||||||
|
|
||||||
const contentLength = parseInt(
|
const contentLength = parseInt(
|
||||||
attachmentInfo.headers?.get("content-length")
|
attachmentInfo.headers?.get("content-length")
|
||||||
);
|
);
|
||||||
return isNaN(contentLength) ? 0 : contentLength;
|
return isNaN(contentLength) ? 0 : contentLength;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return 0;
|
DatabaseLogger.error(e);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +143,9 @@ export async function checkAttachment(hash) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const size = await getUploadedFileSize(hash);
|
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) {
|
} catch (e) {
|
||||||
return { failed: e?.message };
|
return { failed: e?.message };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ import Sodium from "@ammarahmed/react-native-sodium";
|
|||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
import RNFetchBlob from "react-native-blob-util";
|
import RNFetchBlob from "react-native-blob-util";
|
||||||
import { IOS_APPGROUPID } from "../../utils/constants";
|
import { IOS_APPGROUPID } from "../../utils/constants";
|
||||||
import { db } from "../database";
|
import { DatabaseLogger, db } from "../database";
|
||||||
import { cacheDir, cacheDirOld, getRandomId } from "./utils";
|
import { cacheDir, cacheDirOld, getRandomId } from "./utils";
|
||||||
|
|
||||||
export async function readEncrypted(filename, key, cipherData) {
|
export async function readEncrypted(filename, key, cipherData) {
|
||||||
await migrateFilesFromCache();
|
await migrateFilesFromCache();
|
||||||
console.log("Read encrypted file...");
|
DatabaseLogger.log("Read encrypted file...");
|
||||||
let path = `${cacheDir}/${filename}`;
|
let path = `${cacheDir}/${filename}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -34,8 +34,6 @@ export async function readEncrypted(filename, key, cipherData) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const attachment = await db.attachments.attachment(filename);
|
|
||||||
console.log("decrypting....");
|
|
||||||
let output = await Sodium.decryptFile(
|
let output = await Sodium.decryptFile(
|
||||||
key,
|
key,
|
||||||
{
|
{
|
||||||
@@ -45,12 +43,13 @@ export async function readEncrypted(filename, key, cipherData) {
|
|||||||
},
|
},
|
||||||
cipherData.outputType === "base64" ? "base64" : "text"
|
cipherData.outputType === "base64" ? "base64" : "text"
|
||||||
);
|
);
|
||||||
console.log("file decrypted...", attachment?.mimeType);
|
|
||||||
|
DatabaseLogger.log("File decrypted...");
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
RNFetchBlob.fs.unlink(path).catch(console.log);
|
RNFetchBlob.fs.unlink(path).catch(console.log);
|
||||||
console.log("readEncrypted", e);
|
DatabaseLogger.error(e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,7 +127,7 @@ export async function clearFileStorage() {
|
|||||||
export async function createCacheDir() {
|
export async function createCacheDir() {
|
||||||
if (!(await RNFetchBlob.fs.exists(cacheDir))) {
|
if (!(await RNFetchBlob.fs.exists(cacheDir))) {
|
||||||
await RNFetchBlob.fs.mkdir(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) {
|
if (stat.size !== expectedFileSize) {
|
||||||
|
DatabaseLogger.log(
|
||||||
|
`File size mismatch: ${filename}, expected: ${expectedFileSize}, actual: ${stat.size}`
|
||||||
|
);
|
||||||
RNFetchBlob.fs
|
RNFetchBlob.fs
|
||||||
.unlink(existsInAppGroup ? appGroupPath : path)
|
.unlink(existsInAppGroup ? appGroupPath : path)
|
||||||
.catch(console.log);
|
.catch(console.log);
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ import { isImage, isDocument } from "@notesnook/core/dist/utils/filename";
|
|||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
import { IOS_APPGROUPID } from "../../utils/constants";
|
import { IOS_APPGROUPID } from "../../utils/constants";
|
||||||
import { createCacheDir } from "./io";
|
import { createCacheDir } from "./io";
|
||||||
|
import { getUploadedFileSize } from "./download";
|
||||||
|
import { ToastManager } from "../../services/event-manager";
|
||||||
|
|
||||||
export async function uploadFile(filename, data, cancelToken) {
|
export async function uploadFile(filename, data, cancelToken) {
|
||||||
if (!data) return false;
|
if (!data) return false;
|
||||||
@@ -33,13 +35,31 @@ export async function uploadFile(filename, data, cancelToken) {
|
|||||||
DatabaseLogger.info(`Preparing to upload file: ${filename}`);
|
DatabaseLogger.info(`Preparing to upload file: ${filename}`);
|
||||||
|
|
||||||
try {
|
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, {
|
let res = await fetch(url, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers
|
headers
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error(`${res.status}: Unable to resolve upload url`);
|
|
||||||
const uploadUrl = await res.text();
|
const uploadUrl = res.ok ? await res.text() : await res.json();
|
||||||
if (!uploadUrl) throw new Error("Unable to resolve attachment upload url");
|
|
||||||
|
if (typeof uploadUrl !== "string") {
|
||||||
|
throw new Error(
|
||||||
|
uploadUrl.error || "Unable to resolve attachment upload url."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let uploadFilePath = `${cacheDir}/${filename}`;
|
let uploadFilePath = `${cacheDir}/${filename}`;
|
||||||
|
|
||||||
const iosAppGroup =
|
const iosAppGroup =
|
||||||
@@ -50,7 +70,15 @@ export async function uploadFile(filename, data, cancelToken) {
|
|||||||
let exists = await RNFetchBlob.fs.exists(uploadFilePath);
|
let exists = await RNFetchBlob.fs.exists(uploadFilePath);
|
||||||
if (!exists && Platform.OS === "ios") {
|
if (!exists && Platform.OS === "ios") {
|
||||||
uploadFilePath = appGroupPath;
|
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}`);
|
DatabaseLogger.info(`Starting upload: ${filename}`);
|
||||||
|
|
||||||
let request = RNFetchBlob.config({
|
let request = RNFetchBlob.config({
|
||||||
@@ -104,8 +132,10 @@ export async function uploadFile(filename, data, cancelToken) {
|
|||||||
return result;
|
return result;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
useAttachmentStore.getState().remove(filename);
|
useAttachmentStore.getState().remove(filename);
|
||||||
DatabaseLogger.info(`File upload error: ${filename}, ${e}`);
|
ToastManager.error(e, "File upload failed");
|
||||||
DatabaseLogger.error(e);
|
DatabaseLogger.error(e, "File upload failed", {
|
||||||
|
filename
|
||||||
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,15 +20,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Linking, View } from "react-native";
|
import { Linking, View } from "react-native";
|
||||||
//import SettingsBackupAndRestore from '../../screens/settings/backup-restore';
|
//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 { 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 { SIZE } from "../../utils/size";
|
||||||
import { sleep } from "../../utils/time";
|
import { sleep } from "../../utils/time";
|
||||||
import { PricingPlans } from "../premium/pricing-plans";
|
import { PricingPlans } from "../premium/pricing-plans";
|
||||||
import SheetProvider from "../sheet-provider";
|
import SheetProvider from "../sheet-provider";
|
||||||
import { Progress } from "../sheets/progress";
|
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { allowedOnPlatform, getStyle } from "./functions";
|
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 (
|
return (
|
||||||
|
|||||||
@@ -18,10 +18,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback, useEffect, useRef } from "react";
|
import React, { useCallback, useEffect, useRef } from "react";
|
||||||
import { AppStateStatus, Platform, TextInput, View } from "react-native";
|
import {
|
||||||
|
AppStateStatus,
|
||||||
|
Platform,
|
||||||
|
TextInput,
|
||||||
|
useWindowDimensions,
|
||||||
|
View
|
||||||
|
} from "react-native";
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import { enabled } from "react-native-privacy-snapshot";
|
|
||||||
import { DatabaseLogger } from "../../common/database";
|
import { DatabaseLogger } from "../../common/database";
|
||||||
import {
|
import {
|
||||||
decrypt,
|
decrypt,
|
||||||
@@ -34,17 +39,20 @@ import {
|
|||||||
import { MMKV } from "../../common/database/mmkv";
|
import { MMKV } from "../../common/database/mmkv";
|
||||||
import { useAppState } from "../../hooks/use-app-state";
|
import { useAppState } from "../../hooks/use-app-state";
|
||||||
import BiometricService from "../../services/biometrics";
|
import BiometricService from "../../services/biometrics";
|
||||||
|
import { ToastManager } from "../../services/event-manager";
|
||||||
import SettingsService from "../../services/settings";
|
import SettingsService from "../../services/settings";
|
||||||
import { useSettingStore } from "../../stores/use-setting-store";
|
import { useSettingStore } from "../../stores/use-setting-store";
|
||||||
import { useUserStore } from "../../stores/use-user-store";
|
import { useUserStore } from "../../stores/use-user-store";
|
||||||
import { NotesnookModule } from "../../utils/notesnook-module";
|
import { NotesnookModule } from "../../utils/notesnook-module";
|
||||||
import { SIZE } from "../../utils/size";
|
import { SIZE } from "../../utils/size";
|
||||||
|
import { Toast } from "../toast";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { IconButton } from "../ui/icon-button";
|
import { IconButton } from "../ui/icon-button";
|
||||||
import Input from "../ui/input";
|
import Input from "../ui/input";
|
||||||
import Seperator from "../ui/seperator";
|
import Seperator from "../ui/seperator";
|
||||||
import Heading from "../ui/typography/heading";
|
import Heading from "../ui/typography/heading";
|
||||||
import Paragraph from "../ui/typography/paragraph";
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
|
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
|
||||||
|
|
||||||
const getUser = () => {
|
const getUser = () => {
|
||||||
const user = MMKV.getString("user");
|
const user = MMKV.getString("user");
|
||||||
@@ -85,6 +93,7 @@ const AppLockedOverlay = () => {
|
|||||||
const appState = useAppState();
|
const appState = useAppState();
|
||||||
const lastAppState = useRef<AppStateStatus>(appState);
|
const lastAppState = useRef<AppStateStatus>(appState);
|
||||||
const biometricUnlockAwaitingUserInput = useRef(false);
|
const biometricUnlockAwaitingUserInput = useRef(false);
|
||||||
|
const { height } = useWindowDimensions();
|
||||||
const keyboardType = useSettingStore(
|
const keyboardType = useSettingStore(
|
||||||
(state) => state.settings.applockKeyboardType
|
(state) => state.settings.applockKeyboardType
|
||||||
);
|
);
|
||||||
@@ -119,7 +128,6 @@ const AppLockedOverlay = () => {
|
|||||||
);
|
);
|
||||||
if (unlocked) {
|
if (unlocked) {
|
||||||
lockApp(false);
|
lockApp(false);
|
||||||
enabled(false);
|
|
||||||
password.current = undefined;
|
password.current = undefined;
|
||||||
}
|
}
|
||||||
biometricUnlockAwaitingUserInput.current = false;
|
biometricUnlockAwaitingUserInput.current = false;
|
||||||
@@ -146,8 +154,13 @@ const AppLockedOverlay = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lockApp(false);
|
lockApp(false);
|
||||||
enabled(false);
|
|
||||||
password.current = undefined;
|
password.current = undefined;
|
||||||
|
} else {
|
||||||
|
ToastManager.show({
|
||||||
|
heading: `Invalid ${keyboardType === "numeric" ? "pin" : "password"}`,
|
||||||
|
type: "error",
|
||||||
|
context: "local"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@@ -155,10 +168,6 @@ const AppLockedOverlay = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (appState === "active") {
|
|
||||||
enabled(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
const prevState = lastAppState.current;
|
const prevState = lastAppState.current;
|
||||||
lastAppState.current = appState;
|
lastAppState.current = appState;
|
||||||
|
|
||||||
@@ -184,31 +193,28 @@ const AppLockedOverlay = () => {
|
|||||||
DatabaseLogger.info("Biometric unlock request");
|
DatabaseLogger.info("Biometric unlock request");
|
||||||
onUnlockAppRequested();
|
onUnlockAppRequested();
|
||||||
}
|
}
|
||||||
|
|
||||||
enabled(false);
|
|
||||||
} else {
|
} else {
|
||||||
SettingsService.appEnteredBackground();
|
SettingsService.appEnteredBackground();
|
||||||
|
|
||||||
if (
|
|
||||||
SettingsService.get().privacyScreen ||
|
|
||||||
SettingsService.getProperty("appLockEnabled")
|
|
||||||
) {
|
|
||||||
enabled(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [appState, onUnlockAppRequested, appLocked]);
|
}, [appState, onUnlockAppRequested, appLocked]);
|
||||||
|
|
||||||
return appLocked ? (
|
return appLocked ? (
|
||||||
<View
|
<KeyboardAwareScrollView
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: colors.primary.background,
|
backgroundColor: colors.primary.background,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
zIndex: 999,
|
zIndex: 999
|
||||||
justifyContent: "center"
|
|
||||||
}}
|
}}
|
||||||
|
contentContainerStyle={{
|
||||||
|
justifyContent: "center",
|
||||||
|
minHeight: height
|
||||||
|
}}
|
||||||
|
keyboardDismissMode="interactive"
|
||||||
|
keyboardShouldPersistTaps="handled"
|
||||||
>
|
>
|
||||||
|
<Toast context="local" />
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@@ -321,7 +327,7 @@ const AppLockedOverlay = () => {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</KeyboardAwareScrollView>
|
||||||
) : null;
|
) : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ import Heading from "../ui/typography/heading";
|
|||||||
import Paragraph from "../ui/typography/paragraph";
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
import { useTabStore } from "../../screens/editor/tiptap/use-tab-store";
|
import { useTabStore } from "../../screens/editor/tiptap/use-tab-store";
|
||||||
import { editorController } from "../../screens/editor/tiptap/utils";
|
import { editorController } from "../../screens/editor/tiptap/utils";
|
||||||
|
import SheetProvider from "../sheet-provider";
|
||||||
|
|
||||||
const Actions = ({
|
const Actions = ({
|
||||||
attachment,
|
attachment,
|
||||||
@@ -218,6 +219,7 @@ const Actions = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Dialog context={contextId} />
|
<Dialog context={contextId} />
|
||||||
|
<SheetProvider context={contextId} />
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
@@ -238,36 +240,20 @@ const Actions = ({
|
|||||||
style={{
|
style={{
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
paddingHorizontal: 12
|
paddingHorizontal: 12,
|
||||||
|
marginTop: 6,
|
||||||
|
gap: 10
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Paragraph
|
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
|
||||||
size={SIZE.xs}
|
{attachment.mimeType}
|
||||||
style={{
|
|
||||||
marginRight: 10
|
|
||||||
}}
|
|
||||||
color={colors.secondary.paragraph}
|
|
||||||
>
|
|
||||||
{attachment.type}
|
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Paragraph
|
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
|
||||||
style={{
|
|
||||||
marginRight: 10
|
|
||||||
}}
|
|
||||||
size={SIZE.xs}
|
|
||||||
color={colors.secondary.paragraph}
|
|
||||||
>
|
|
||||||
{formatBytes(attachment.size)}
|
{formatBytes(attachment.size)}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
{notes.length ? (
|
{notes.length ? (
|
||||||
<Paragraph
|
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
|
||||||
style={{
|
|
||||||
marginRight: 10
|
|
||||||
}}
|
|
||||||
size={SIZE.xs}
|
|
||||||
color={colors.secondary.paragraph}
|
|
||||||
>
|
|
||||||
{notes.length} note
|
{notes.length} note
|
||||||
{notes.length > 1 ? "s" : ""}
|
{notes.length > 1 ? "s" : ""}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
@@ -366,7 +352,7 @@ const Actions = ({
|
|||||||
{failed ? (
|
{failed ? (
|
||||||
<Notice
|
<Notice
|
||||||
type="alert"
|
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"
|
size="small"
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
|
|||||||
case "audio":
|
case "audio":
|
||||||
items = note
|
items = note
|
||||||
? db.attachments.ofNote(note.id, "audio")
|
? db.attachments.ofNote(note.id, "audio")
|
||||||
: db.attachments.all;
|
: db.attachments.audios;
|
||||||
break;
|
break;
|
||||||
case "documents":
|
case "documents":
|
||||||
items = note
|
items = note
|
||||||
@@ -355,6 +355,7 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
|
|||||||
|
|
||||||
AttachmentDialog.present = (note?: Note) => {
|
AttachmentDialog.present = (note?: Note) => {
|
||||||
presentSheet({
|
presentSheet({
|
||||||
component: () => <AttachmentDialog note={note} />
|
component: () => <AttachmentDialog note={note} />,
|
||||||
|
keyboardHandlerDisabled: true
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,12 +17,16 @@ You should have received a copy of the GNU General Public License
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { TouchableOpacity, View, useWindowDimensions } from "react-native";
|
import { TouchableOpacity, View, useWindowDimensions } from "react-native";
|
||||||
import { SheetManager } from "react-native-actions-sheet";
|
import { SheetManager } from "react-native-actions-sheet";
|
||||||
import { DDS } from "../../services/device-detection";
|
import { DDS } from "../../services/device-detection";
|
||||||
import { eSendEvent } from "../../services/event-manager";
|
import { eSendEvent } from "../../services/event-manager";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import Sync from "../../services/sync";
|
||||||
|
import { useSettingStore } from "../../stores/use-setting-store";
|
||||||
|
import { useUserStore } from "../../stores/use-user-store";
|
||||||
|
import { eUserLoggedIn } from "../../utils/events";
|
||||||
import { SIZE } from "../../utils/size";
|
import { SIZE } from "../../utils/size";
|
||||||
import { sleep } from "../../utils/time";
|
import { sleep } from "../../utils/time";
|
||||||
import SheetProvider from "../sheet-provider";
|
import SheetProvider from "../sheet-provider";
|
||||||
@@ -34,11 +38,6 @@ import Paragraph from "../ui/typography/paragraph";
|
|||||||
import { hideAuth } from "./common";
|
import { hideAuth } from "./common";
|
||||||
import { ForgotPassword } from "./forgot-password";
|
import { ForgotPassword } from "./forgot-password";
|
||||||
import { useLogin } from "./use-login";
|
import { useLogin } from "./use-login";
|
||||||
import { useSettingStore } from "../../stores/use-setting-store";
|
|
||||||
import { eUserLoggedIn } from "../../utils/events";
|
|
||||||
import { useUserStore } from "../../stores/use-user-store";
|
|
||||||
import Sync from "../../services/sync";
|
|
||||||
import { Notice } from "../ui/notice";
|
|
||||||
|
|
||||||
const LoginSteps = {
|
const LoginSteps = {
|
||||||
emailAuth: 1,
|
emailAuth: 1,
|
||||||
@@ -67,7 +66,7 @@ export const Login = ({ changeMode }) => {
|
|||||||
Progress.present();
|
Progress.present();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!useUserStore.getState().syncing) {
|
if (!useUserStore.getState().syncing) {
|
||||||
Sync.run("global", false, true);
|
Sync.run("global", false, "full");
|
||||||
}
|
}
|
||||||
}, 5000);
|
}, 5000);
|
||||||
});
|
});
|
||||||
@@ -184,7 +183,11 @@ export const Login = ({ changeMode }) => {
|
|||||||
defaultValue={email.current}
|
defaultValue={email.current}
|
||||||
editable={step === LoginSteps.emailAuth && !loading}
|
editable={step === LoginSteps.emailAuth && !loading}
|
||||||
onSubmit={() => {
|
onSubmit={() => {
|
||||||
passwordInputRef.current?.focus();
|
if (step === LoginSteps.emailAuth) {
|
||||||
|
login();
|
||||||
|
} else {
|
||||||
|
passwordInputRef.current?.focus();
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -243,6 +246,7 @@ export const Login = ({ changeMode }) => {
|
|||||||
width: 250,
|
width: 250,
|
||||||
borderRadius: 100
|
borderRadius: 100
|
||||||
}}
|
}}
|
||||||
|
height={50}
|
||||||
fontSize={SIZE.md}
|
fontSize={SIZE.md}
|
||||||
type="accent"
|
type="accent"
|
||||||
title={!loading ? "Continue" : null}
|
title={!loading ? "Continue" : null}
|
||||||
|
|||||||
@@ -112,7 +112,11 @@ export const SessionExpired = () => {
|
|||||||
if (!res) throw new Error("no token found");
|
if (!res) throw new Error("no token found");
|
||||||
if (db.tokenManager._isTokenExpired(res))
|
if (db.tokenManager._isTokenExpired(res))
|
||||||
throw new Error("token expired");
|
throw new Error("token expired");
|
||||||
Sync.run("global", false, true, async (complete) => {
|
|
||||||
|
const key = await db.user.getEncryptionKey();
|
||||||
|
if (!key) throw new Error("No encryption key found.");
|
||||||
|
|
||||||
|
Sync.run("global", false, "full", async (complete) => {
|
||||||
if (!complete) {
|
if (!complete) {
|
||||||
let user = await db.user.getUser();
|
let user = await db.user.getUser();
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
@@ -127,7 +131,6 @@ export const SessionExpired = () => {
|
|||||||
setVisible(false);
|
setVisible(false);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
|
||||||
let user = await db.user.getUser();
|
let user = await db.user.getUser();
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
email.current = user.email;
|
email.current = user.email;
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import Seperator from "../ui/seperator";
|
|||||||
import Heading from "../ui/typography/heading";
|
import Heading from "../ui/typography/heading";
|
||||||
import Paragraph from "../ui/typography/paragraph";
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
|
import { ScrollView } from "react-native-actions-sheet";
|
||||||
|
|
||||||
const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
@@ -143,7 +144,10 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
|||||||
}, [currentMethod.method, mfaInfo.token, seconds, sending, start]);
|
}, [currentMethod.method, mfaInfo.token, seconds, sending, start]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<ScrollView
|
||||||
|
keyboardShouldPersistTaps="handled"
|
||||||
|
keyboardDismissMode="interactive"
|
||||||
|
>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
@@ -214,6 +218,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
|||||||
code.current = value;
|
code.current = value;
|
||||||
//onNext();
|
//onNext();
|
||||||
}}
|
}}
|
||||||
|
onSubmitEditing={onNext}
|
||||||
caretHidden
|
caretHidden
|
||||||
inputStyle={{
|
inputStyle={{
|
||||||
fontSize: SIZE.lg,
|
fontSize: SIZE.lg,
|
||||||
@@ -225,6 +230,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
|||||||
keyboardType={
|
keyboardType={
|
||||||
currentMethod.method === "recoveryCode" ? "default" : "numeric"
|
currentMethod.method === "recoveryCode" ? "default" : "numeric"
|
||||||
}
|
}
|
||||||
|
enablesReturnKeyAutomatically
|
||||||
containerStyle={{
|
containerStyle={{
|
||||||
height: 60,
|
height: 60,
|
||||||
borderWidth: 0,
|
borderWidth: 0,
|
||||||
@@ -297,7 +303,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ type DialogInfo = {
|
|||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
context: "global" | "local" | (string & {});
|
context: "global" | "local" | (string & {});
|
||||||
secureTextEntry?: boolean;
|
secureTextEntry?: boolean;
|
||||||
|
keyboardType?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function presentDialog(data: Partial<DialogInfo>): void {
|
export function presentDialog(data: Partial<DialogInfo>): void {
|
||||||
|
|||||||
@@ -174,6 +174,7 @@ export const Dialog = ({ context = "global" }) => {
|
|||||||
onSubmit={onPressPositive}
|
onSubmit={onPressPositive}
|
||||||
returnKeyLabel="Done"
|
returnKeyLabel="Done"
|
||||||
returnKeyType="done"
|
returnKeyType="done"
|
||||||
|
keyboardType={dialogInfo.keyboardType || "default"}
|
||||||
placeholder={dialogInfo.inputPlaceholder}
|
placeholder={dialogInfo.inputPlaceholder}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -19,12 +19,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { TextInput, View } from "react-native";
|
import { TextInput, View } from "react-native";
|
||||||
|
import { db } from "../../../common/database";
|
||||||
import {
|
import {
|
||||||
clearAppLockVerificationCipher,
|
clearAppLockVerificationCipher,
|
||||||
setAppLockVerificationCipher,
|
setAppLockVerificationCipher,
|
||||||
validateAppLockPassword
|
validateAppLockPassword
|
||||||
} from "../../../common/database/encryption";
|
} from "../../../common/database/encryption";
|
||||||
import BiometicService from "../../../services/biometrics";
|
import BiometricService from "../../../services/biometrics";
|
||||||
import { DDS } from "../../../services/device-detection";
|
import { DDS } from "../../../services/device-detection";
|
||||||
import {
|
import {
|
||||||
ToastManager,
|
ToastManager,
|
||||||
@@ -44,6 +45,7 @@ import BaseDialog from "../../dialog/base-dialog";
|
|||||||
import DialogButtons from "../../dialog/dialog-buttons";
|
import DialogButtons from "../../dialog/dialog-buttons";
|
||||||
import DialogHeader from "../../dialog/dialog-header";
|
import DialogHeader from "../../dialog/dialog-header";
|
||||||
import { Toast } from "../../toast";
|
import { Toast } from "../../toast";
|
||||||
|
import { Button } from "../../ui/button";
|
||||||
import { IconButton } from "../../ui/icon-button";
|
import { IconButton } from "../../ui/icon-button";
|
||||||
import Input from "../../ui/input";
|
import Input from "../../ui/input";
|
||||||
import Seperator from "../../ui/seperator";
|
import Seperator from "../../ui/seperator";
|
||||||
@@ -66,6 +68,7 @@ export const AppLockPassword = () => {
|
|||||||
confirmPassword?: string;
|
confirmPassword?: string;
|
||||||
}>({});
|
}>({});
|
||||||
const [secureTextEntry, setSecureTextEntry] = useState(true);
|
const [secureTextEntry, setSecureTextEntry] = useState(true);
|
||||||
|
const [accountPass, setAccountPass] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const subs = [
|
const subs = [
|
||||||
@@ -73,6 +76,7 @@ export const AppLockPassword = () => {
|
|||||||
eOpenAppLockPasswordDialog,
|
eOpenAppLockPasswordDialog,
|
||||||
(mode: "create" | "change" | "remove") => {
|
(mode: "create" | "change" | "remove") => {
|
||||||
setMode(mode);
|
setMode(mode);
|
||||||
|
setAccountPass(false);
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
@@ -126,7 +130,9 @@ export const AppLockPassword = () => {
|
|||||||
mode === "change"
|
mode === "change"
|
||||||
? `Change app lock ${keyboardType}`
|
? `Change app lock ${keyboardType}`
|
||||||
: mode === "remove"
|
: mode === "remove"
|
||||||
? `Remove app lock ${keyboardType}`
|
? `Enter ${
|
||||||
|
accountPass ? "account password" : `app lock ${keyboardType}`
|
||||||
|
} to remove ${keyboardType}`
|
||||||
: `Set up a custom app lock ${keyboardType} to unlock the app`
|
: `Set up a custom app lock ${keyboardType} to unlock the app`
|
||||||
}
|
}
|
||||||
icon="shield"
|
icon="shield"
|
||||||
@@ -169,33 +175,39 @@ export const AppLockPassword = () => {
|
|||||||
confirmPasswordInputRef.current?.focus();
|
confirmPasswordInputRef.current?.focus();
|
||||||
}}
|
}}
|
||||||
defaultValue={values.current.password}
|
defaultValue={values.current.password}
|
||||||
keyboardType={keyboardType === "pin" ? "number-pad" : "default"}
|
keyboardType={
|
||||||
|
keyboardType === "pin" && !accountPass ? "number-pad" : "default"
|
||||||
|
}
|
||||||
autoComplete="password"
|
autoComplete="password"
|
||||||
returnKeyLabel={mode !== "remove" ? "Next" : "Remove"}
|
returnKeyLabel={mode !== "remove" ? "Next" : "Remove"}
|
||||||
returnKeyType={mode !== "remove" ? "next" : "done"}
|
returnKeyType={mode !== "remove" ? "next" : "done"}
|
||||||
secureTextEntry={secureTextEntry}
|
secureTextEntry={secureTextEntry}
|
||||||
buttonLeft={
|
buttonLeft={
|
||||||
<IconButton
|
accountPass ? null : (
|
||||||
name={keyboardType === "password" ? "numeric" : "keyboard"}
|
<IconButton
|
||||||
onPress={() => {
|
name={keyboardType === "password" ? "numeric" : "keyboard"}
|
||||||
setKeyboardType(
|
onPress={() => {
|
||||||
keyboardType === "password" ? "pin" : "password"
|
setKeyboardType(
|
||||||
);
|
keyboardType === "password" ? "pin" : "password"
|
||||||
setSecureTextEntry(false);
|
);
|
||||||
setImmediate(() => {
|
setSecureTextEntry(false);
|
||||||
setSecureTextEntry(true);
|
setImmediate(() => {
|
||||||
});
|
setSecureTextEntry(true);
|
||||||
}}
|
});
|
||||||
style={{
|
}}
|
||||||
width: 25,
|
style={{
|
||||||
height: 25,
|
width: 25,
|
||||||
marginRight: 5
|
height: 25,
|
||||||
}}
|
marginRight: 5
|
||||||
size={SIZE.lg}
|
}}
|
||||||
/>
|
size={SIZE.lg}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
placeholder={
|
placeholder={
|
||||||
mode === "change"
|
accountPass
|
||||||
|
? "Account password"
|
||||||
|
: mode === "change"
|
||||||
? `New ${keyboardType}`
|
? `New ${keyboardType}`
|
||||||
: `${keyboardType === "pin" ? "Pin" : "Password"}`
|
: `${keyboardType === "pin" ? "Pin" : "Password"}`
|
||||||
}
|
}
|
||||||
@@ -222,6 +234,36 @@ export const AppLockPassword = () => {
|
|||||||
placeholder={`Confirm ${keyboardType}`}
|
placeholder={`Confirm ${keyboardType}`}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
{mode === "remove" ? (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
icon={
|
||||||
|
accountPass ? "checkbox-marked" : "checkbox-blank-outline"
|
||||||
|
}
|
||||||
|
onPress={() => {
|
||||||
|
setSecureTextEntry(false);
|
||||||
|
setAccountPass(!accountPass);
|
||||||
|
setTimeout(() => {
|
||||||
|
setSecureTextEntry(true);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
iconSize={SIZE.lg}
|
||||||
|
type="plain"
|
||||||
|
iconColor={
|
||||||
|
accountPass ? colors.primary.accent : colors.primary.icon
|
||||||
|
}
|
||||||
|
title="Use account password instead"
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
alignSelf: "flex-start",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
paddingHorizontal: 0,
|
||||||
|
height: 30
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<DialogButtons
|
<DialogButtons
|
||||||
@@ -249,7 +291,8 @@ export const AppLockPassword = () => {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await setAppLockVerificationCipher(values.current.password);
|
const password = values.current.password;
|
||||||
|
setAppLockVerificationCipher(password);
|
||||||
SettingsService.setProperty("appLockHasPasswordSecurity", true);
|
SettingsService.setProperty("appLockHasPasswordSecurity", true);
|
||||||
} else if (mode === "change") {
|
} else if (mode === "change") {
|
||||||
if (
|
if (
|
||||||
@@ -277,7 +320,6 @@ export const AppLockPassword = () => {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isCurrentPasswordCorrect = await validateAppLockPassword(
|
const isCurrentPasswordCorrect = await validateAppLockPassword(
|
||||||
values.current.currentPassword
|
values.current.currentPassword
|
||||||
);
|
);
|
||||||
@@ -293,8 +335,10 @@ export const AppLockPassword = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const password = values.current.password;
|
||||||
|
await clearAppLockVerificationCipher();
|
||||||
SettingsService.setProperty("appLockHasPasswordSecurity", true);
|
SettingsService.setProperty("appLockHasPasswordSecurity", true);
|
||||||
await setAppLockVerificationCipher(values.current.password);
|
await setAppLockVerificationCipher(password);
|
||||||
} else if (mode === "remove") {
|
} else if (mode === "remove") {
|
||||||
if (!values.current.password) {
|
if (!values.current.password) {
|
||||||
ToastManager.error(
|
ToastManager.error(
|
||||||
@@ -305,14 +349,18 @@ export const AppLockPassword = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isCurrentPasswordCorrect = await validateAppLockPassword(
|
const isCurrentPasswordCorrect = accountPass
|
||||||
values.current.password
|
? await db.user.verifyPassword(values.current.password)
|
||||||
);
|
: await validateAppLockPassword(values.current.password);
|
||||||
|
|
||||||
if (!isCurrentPasswordCorrect) {
|
if (!isCurrentPasswordCorrect) {
|
||||||
ToastManager.error(
|
ToastManager.error(
|
||||||
new Error(
|
new Error(
|
||||||
`${keyboardType === "pin" ? "Pin" : "Password"} incorrect`
|
accountPass
|
||||||
|
? "Account password incorrect"
|
||||||
|
: `${
|
||||||
|
keyboardType === "pin" ? "Pin" : "Password"
|
||||||
|
} incorrect`
|
||||||
),
|
),
|
||||||
undefined,
|
undefined,
|
||||||
"local"
|
"local"
|
||||||
@@ -323,7 +371,7 @@ export const AppLockPassword = () => {
|
|||||||
SettingsService.setProperty("appLockHasPasswordSecurity", false);
|
SettingsService.setProperty("appLockHasPasswordSecurity", false);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!(await BiometicService.isBiometryAvailable()) ||
|
!(await BiometricService.isBiometryAvailable()) ||
|
||||||
SettingsService.getProperty("biometricsAuthEnabled") === false
|
SettingsService.getProperty("biometricsAuthEnabled") === false
|
||||||
) {
|
) {
|
||||||
SettingsService.setProperty("appLockEnabled", false);
|
SettingsService.setProperty("appLockEnabled", false);
|
||||||
@@ -342,7 +390,9 @@ export const AppLockPassword = () => {
|
|||||||
|
|
||||||
close();
|
close();
|
||||||
}}
|
}}
|
||||||
positiveTitle="Save"
|
positiveTitle={
|
||||||
|
mode === "remove" ? "Remove" : mode === "change" ? "Change" : "Save"
|
||||||
|
}
|
||||||
negativeTitle="Cancel"
|
negativeTitle="Cancel"
|
||||||
positiveType="transparent"
|
positiveType="transparent"
|
||||||
loading={false}
|
loading={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/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Image, ScrollView, TouchableOpacity, View } from "react-native";
|
import {
|
||||||
import { ImagePickerResponse } from "react-native-image-picker";
|
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 { useThemeColors } from "../../../../../../packages/theme/dist";
|
||||||
import { presentSheet } from "../../../services/event-manager";
|
import { presentSheet } from "../../../services/event-manager";
|
||||||
import { SIZE } from "../../../utils/size";
|
import { SIZE } from "../../../utils/size";
|
||||||
@@ -32,7 +38,7 @@ export default function AttachImage({
|
|||||||
onAttach,
|
onAttach,
|
||||||
close
|
close
|
||||||
}: {
|
}: {
|
||||||
response: ImagePickerResponse;
|
response: ImageType[];
|
||||||
onAttach: ({ compress }: { compress: boolean }) => void;
|
onAttach: ({ compress }: { compress: boolean }) => void;
|
||||||
close: ((ctx?: string | undefined) => void) | undefined;
|
close: ((ctx?: string | undefined) => void) | undefined;
|
||||||
}) {
|
}) {
|
||||||
@@ -58,14 +64,17 @@ export default function AttachImage({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Paragraph style={{ color: colors.primary.paragraph, marginBottom: 6 }}>
|
<Paragraph style={{ color: colors.primary.paragraph, marginBottom: 6 }}>
|
||||||
Attaching {response.assets?.length} image(s):
|
Attaching {response?.length} image(s):
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<ScrollView horizontal>
|
<ScrollView horizontal>
|
||||||
{response.assets?.map((item) => (
|
{response?.map?.((item) => (
|
||||||
<TouchableOpacity key={item.fileName} activeOpacity={0.9}>
|
<TouchableOpacity key={item.filename} activeOpacity={0.9}>
|
||||||
<Image
|
<Image
|
||||||
source={{
|
source={{
|
||||||
uri: item.uri
|
uri:
|
||||||
|
Platform.OS === "ios"
|
||||||
|
? item.sourceURL || item.path
|
||||||
|
: item.path
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
width: 100,
|
width: 100,
|
||||||
@@ -142,7 +151,7 @@ export default function AttachImage({
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
title={`${
|
title={`${
|
||||||
(response.assets?.length || 0) > 1 ? "Attach Images" : "Attach Image"
|
(response?.length || 0) > 1 ? "Attach Images" : "Attach Image"
|
||||||
}`}
|
}`}
|
||||||
type="accent"
|
type="accent"
|
||||||
width="100%"
|
width="100%"
|
||||||
@@ -156,10 +165,16 @@ export default function AttachImage({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
AttachImage.present = (response: ImagePickerResponse) => {
|
AttachImage.present = (response: ImageType[], context?: string) => {
|
||||||
return new Promise((resolve) => {
|
return new Promise<
|
||||||
|
| {
|
||||||
|
compress: boolean;
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
>((resolve) => {
|
||||||
let resolved = false;
|
let resolved = false;
|
||||||
presentSheet({
|
presentSheet({
|
||||||
|
context: context,
|
||||||
component: (ref, close, update) => (
|
component: (ref, close, update) => (
|
||||||
<AttachImage
|
<AttachImage
|
||||||
response={response}
|
response={response}
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ export class VaultDialog extends Component {
|
|||||||
: this.state.copyNote
|
: this.state.copyNote
|
||||||
? "Unlock note to copy it. If biometrics are not working, you can enter device pin to unlock vault."
|
? "Unlock note to copy it. If biometrics are not working, you can enter device pin to unlock vault."
|
||||||
: this.state.goToEditor
|
: 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 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.");
|
: "Enter vault password to lock note. If biometrics are not working, you can enter device pin to lock note.");
|
||||||
}
|
}
|
||||||
@@ -586,7 +586,7 @@ export class VaultDialog extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _copyNote(note) {
|
async _copyNote(note) {
|
||||||
Clipboard.setString(await convertNoteToText(note));
|
Clipboard.setString((await convertNoteToText(note, true)) || "");
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
heading: "Note copied",
|
heading: "Note copied",
|
||||||
type: "success",
|
type: "success",
|
||||||
@@ -602,7 +602,7 @@ export class VaultDialog extends Component {
|
|||||||
await Share.open({
|
await Share.open({
|
||||||
heading: "Share note",
|
heading: "Share note",
|
||||||
failOnCancel: false,
|
failOnCancel: false,
|
||||||
message: await convertNoteToText(note)
|
message: (await convertNoteToText(note)) || ""
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|||||||
@@ -23,10 +23,7 @@ import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
|
|||||||
import { Dialog } from "../dialog";
|
import { Dialog } from "../dialog";
|
||||||
import { Issue } from "../sheets/github/issue";
|
import { Issue } from "../sheets/github/issue";
|
||||||
|
|
||||||
const error = (
|
const error = (stack: string, component: string) => `
|
||||||
stack: string,
|
|
||||||
component: string
|
|
||||||
) => `Please let us know what happened. What steps we can take to reproduce the issue here.
|
|
||||||
|
|
||||||
_______________________________
|
_______________________________
|
||||||
Stacktrace: In ${component}::${stack}`;
|
Stacktrace: In ${component}::${stack}`;
|
||||||
@@ -39,9 +36,9 @@ class ExceptionHandler extends React.Component<{
|
|||||||
error: Error | null;
|
error: Error | null;
|
||||||
hasError: boolean;
|
hasError: boolean;
|
||||||
} = {
|
} = {
|
||||||
hasError: false,
|
hasError: false,
|
||||||
error: null
|
error: null
|
||||||
};
|
};
|
||||||
static getDerivedStateFromError(error: Error) {
|
static getDerivedStateFromError(error: Error) {
|
||||||
return { hasError: true, error: error };
|
return { hasError: true, error: error };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import { eScrollEvent } from "../../utils/events";
|
|||||||
import { LeftMenus } from "./left-menus";
|
import { LeftMenus } from "./left-menus";
|
||||||
import { RightMenus } from "./right-menus";
|
import { RightMenus } from "./right-menus";
|
||||||
import { Title } from "./title";
|
import { Title } from "./title";
|
||||||
|
import { useNavigation } from "@react-navigation/native";
|
||||||
|
|
||||||
type HeaderRightButton = {
|
type HeaderRightButton = {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -66,6 +67,8 @@ export const Header = ({
|
|||||||
hasSearch?: boolean;
|
hasSearch?: boolean;
|
||||||
onSearch?: () => void;
|
onSearch?: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
|
const navigation = useNavigation();
|
||||||
|
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const insets = useGlobalSafeAreaInsets();
|
const insets = useGlobalSafeAreaInsets();
|
||||||
const [borderHidden, setBorderHidden] = useState(true);
|
const [borderHidden, setBorderHidden] = useState(true);
|
||||||
|
|||||||
@@ -19,10 +19,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
import Sodium from "@ammarahmed/react-native-sodium";
|
import Sodium from "@ammarahmed/react-native-sodium";
|
||||||
import dataurl from "@notesnook/core/dist/utils/dataurl";
|
import dataurl from "@notesnook/core/dist/utils/dataurl";
|
||||||
|
import type { ImageAttributes } from "@notesnook/editor/dist/extensions/image/index";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
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 { Platform, View } from "react-native";
|
||||||
import ImageViewer from "react-native-image-zoom-viewer";
|
import ImageViewer from "react-native-image-zoom-viewer";
|
||||||
|
import { db } from "../../common/database";
|
||||||
import downloadAttachment from "../../common/filesystem/download-attachment";
|
import downloadAttachment from "../../common/filesystem/download-attachment";
|
||||||
import { cacheDir } from "../../common/filesystem/utils";
|
import { cacheDir } from "../../common/filesystem/utils";
|
||||||
import {
|
import {
|
||||||
@@ -32,13 +34,13 @@ import {
|
|||||||
import BaseDialog from "../dialog/base-dialog";
|
import BaseDialog from "../dialog/base-dialog";
|
||||||
import { IconButton } from "../ui/icon-button";
|
import { IconButton } from "../ui/icon-button";
|
||||||
import { ProgressBarComponent } from "../ui/svg/lazy";
|
import { ProgressBarComponent } from "../ui/svg/lazy";
|
||||||
import type { ImageAttributes } from "@notesnook/editor/dist/extensions/image/index";
|
|
||||||
const ImagePreview = () => {
|
const ImagePreview = () => {
|
||||||
const { colors } = useThemeColors("dialog");
|
const { colors } = useThemeColors("dialog");
|
||||||
|
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [image, setImage] = useState<string>();
|
const [image, setImage] = useState<string>();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const imageRef = useRef<ImageAttributes>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
eSubscribeEvent("ImagePreview", open);
|
eSubscribeEvent("ImagePreview", open);
|
||||||
@@ -48,6 +50,7 @@ const ImagePreview = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const open = async (image: ImageAttributes) => {
|
const open = async (image: ImageAttributes) => {
|
||||||
|
imageRef.current = image;
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
@@ -60,6 +63,9 @@ const ImagePreview = () => {
|
|||||||
type: "base64",
|
type: "base64",
|
||||||
uri: ""
|
uri: ""
|
||||||
});
|
});
|
||||||
|
if (imageRef.current) {
|
||||||
|
imageRef.current.hash = hash;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!hash) return;
|
if (!hash) return;
|
||||||
//@ts-ignore // FIX ME
|
//@ts-ignore // FIX ME
|
||||||
@@ -80,7 +86,13 @@ const ImagePreview = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
visible && (
|
visible && (
|
||||||
<BaseDialog animation="slide" visible={true} onRequestClose={close}>
|
<BaseDialog
|
||||||
|
background="black"
|
||||||
|
animation="slide"
|
||||||
|
visible={true}
|
||||||
|
onRequestClose={close}
|
||||||
|
transparent
|
||||||
|
>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
@@ -101,6 +113,21 @@ const ImagePreview = () => {
|
|||||||
color={colors.primary.accent}
|
color={colors.primary.accent}
|
||||||
borderColor="transparent"
|
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>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<ImageViewer
|
<ImageViewer
|
||||||
@@ -117,13 +144,12 @@ const ImagePreview = () => {
|
|||||||
width: "100%",
|
width: "100%",
|
||||||
justifyContent: "flex-end",
|
justifyContent: "flex-end",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
height: 80,
|
height: 50,
|
||||||
marginTop: 0,
|
paddingHorizontal: 24,
|
||||||
paddingHorizontal: 12,
|
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
zIndex: 999,
|
zIndex: 999,
|
||||||
backgroundColor: "rgba(0,0,0,0.3)",
|
backgroundColor: "rgba(0,0,0,0.3)",
|
||||||
paddingTop: Platform.OS === "android" ? 30 : 0
|
marginTop: Platform.OS === "android" ? 30 : 0
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -137,7 +163,7 @@ const ImagePreview = () => {
|
|||||||
)}
|
)}
|
||||||
imageUrls={[
|
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 { View } from "react-native";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
export const Footer = () => {
|
export const Footer = ({ height = 150 }) => {
|
||||||
return <View style={{ height: 150 }} />;
|
return <View style={{ height: height }} />;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export const NotebookHeader = ({
|
|||||||
style={{
|
style={{
|
||||||
flexShrink: 1
|
flexShrink: 1
|
||||||
}}
|
}}
|
||||||
size={SIZE.xxl}
|
size={SIZE.lg}
|
||||||
>
|
>
|
||||||
{notebook.title}
|
{notebook.title}
|
||||||
</Heading>
|
</Heading>
|
||||||
@@ -114,7 +114,7 @@ export const NotebookHeader = ({
|
|||||||
width: 40,
|
width: 40,
|
||||||
height: 40
|
height: 40
|
||||||
}}
|
}}
|
||||||
type={isPinnedToMenu ? "secondary" : "secondary"}
|
type="transparent"
|
||||||
color={isPinnedToMenu ? colors.primary.accent : colors.primary.icon}
|
color={isPinnedToMenu ? colors.primary.accent : colors.primary.icon}
|
||||||
size={SIZE.lg}
|
size={SIZE.lg}
|
||||||
/>
|
/>
|
||||||
@@ -123,7 +123,7 @@ export const NotebookHeader = ({
|
|||||||
onPress={onEditNotebook}
|
onPress={onEditNotebook}
|
||||||
tooltipText="Edit this notebook"
|
tooltipText="Edit this notebook"
|
||||||
name="pencil"
|
name="pencil"
|
||||||
type="secondary"
|
type="transparent"
|
||||||
color={colors.primary.icon}
|
color={colors.primary.icon}
|
||||||
style={{
|
style={{
|
||||||
width: 40,
|
width: 40,
|
||||||
@@ -134,7 +134,7 @@ export const NotebookHeader = ({
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
{notebook.description ? (
|
{notebook.description ? (
|
||||||
<Paragraph size={SIZE.md} color={colors.primary.paragraph}>
|
<Paragraph size={SIZE.sm} color={colors.primary.paragraph}>
|
||||||
{notebook.description}
|
{notebook.description}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ export const Card = ({ color }: { color?: string }) => {
|
|||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
paddingHorizontal: 0
|
paddingHorizontal: 0,
|
||||||
|
width: "100%"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
@@ -91,7 +92,6 @@ export const Card = ({ color }: { color?: string }) => {
|
|||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
marginLeft: 10,
|
marginLeft: 10,
|
||||||
flexShrink: 1,
|
|
||||||
marginRight: 10
|
marginRight: 10
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export default function List(props: ListProps) {
|
|||||||
const groupOptions = useGroupOptions(groupType);
|
const groupOptions = useGroupOptions(groupType);
|
||||||
|
|
||||||
const _onRefresh = async () => {
|
const _onRefresh = async () => {
|
||||||
Sync.run("global", false, true, () => {
|
Sync.run("global", false, "full", () => {
|
||||||
props.onRefresh?.();
|
props.onRefresh?.();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -192,7 +192,9 @@ export default function List(props: ListProps) {
|
|||||||
/>
|
/>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
ListFooterComponent={<Footer />}
|
ListFooterComponent={
|
||||||
|
<Footer height={props.renderedInRoute === "Notebook" ? 300 : 150} />
|
||||||
|
}
|
||||||
ListHeaderComponent={
|
ListHeaderComponent={
|
||||||
<>
|
<>
|
||||||
{props.CustomLisHeader ? (
|
{props.CustomLisHeader ? (
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ import { useThemeColors } from "@notesnook/theme";
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import { db } from "../../common/database";
|
import { db } from "../../common/database";
|
||||||
import Editor from "../../screens/editor";
|
import { ReadonlyEditor } from "../../screens/editor/readonly-editor";
|
||||||
import { useTabStore } from "../../screens/editor/tiptap/use-tab-store";
|
import { useTabStore } from "../../screens/editor/tiptap/use-tab-store";
|
||||||
import { editorController } from "../../screens/editor/tiptap/utils";
|
import { editorController } from "../../screens/editor/tiptap/utils";
|
||||||
import { eSendEvent, ToastManager } from "../../services/event-manager";
|
import { ToastManager, eSendEvent } from "../../services/event-manager";
|
||||||
import Navigation from "../../services/navigation";
|
import Navigation from "../../services/navigation";
|
||||||
import { useSelectionStore } from "../../stores/use-selection-store";
|
import { useSelectionStore } from "../../stores/use-selection-store";
|
||||||
import { useTrashStore } from "../../stores/use-trash-store";
|
import { useTrashStore } from "../../stores/use-trash-store";
|
||||||
@@ -34,7 +34,7 @@ import DialogHeader from "../dialog/dialog-header";
|
|||||||
import { presentDialog } from "../dialog/functions";
|
import { presentDialog } from "../dialog/functions";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import Paragraph from "../ui/typography/paragraph";
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
import { ReadonlyEditor } from "../../screens/editor/readonly-editor";
|
import { diff } from "diffblazer";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -121,12 +121,21 @@ export default function NotePreview({ session, content, note }) {
|
|||||||
<ReadonlyEditor
|
<ReadonlyEditor
|
||||||
editorId="historyPreview"
|
editorId="historyPreview"
|
||||||
onLoad={async (loadContent) => {
|
onLoad={async (loadContent) => {
|
||||||
if (content.data) {
|
try {
|
||||||
const _note = note || (await db.notes.note(session?.noteId));
|
if (content.data) {
|
||||||
loadContent({
|
const _note = note || (await db.notes.note(session?.noteId));
|
||||||
data: content.data,
|
const currentContent = await db.content.get(_note.contentId);
|
||||||
id: _note.id
|
loadContent({
|
||||||
});
|
data: diff(currentContent.data, content.data),
|
||||||
|
id: _note.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
ToastManager.error(
|
||||||
|
e,
|
||||||
|
"Failed to load history preview",
|
||||||
|
"local"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -19,14 +19,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
import { Item, ItemType, VirtualizedGrouping } from "@notesnook/core";
|
import { Item, ItemType, VirtualizedGrouping } from "@notesnook/core";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect, useRef } from "react";
|
||||||
import {
|
import {
|
||||||
BackHandler,
|
BackHandler,
|
||||||
NativeEventSubscription,
|
NativeEventSubscription,
|
||||||
Platform,
|
Platform,
|
||||||
View
|
View
|
||||||
} from "react-native";
|
} 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 { db } from "../../common/database";
|
||||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||||
import { ToastManager } from "../../services/event-manager";
|
import { ToastManager } from "../../services/event-manager";
|
||||||
@@ -42,9 +42,10 @@ import { presentDialog } from "../dialog/functions";
|
|||||||
import MoveNoteSheet from "../sheets/add-to";
|
import MoveNoteSheet from "../sheets/add-to";
|
||||||
import ExportNotesSheet from "../sheets/export-notes";
|
import ExportNotesSheet from "../sheets/export-notes";
|
||||||
import ManageTagsSheet from "../sheets/manage-tags";
|
import ManageTagsSheet from "../sheets/manage-tags";
|
||||||
|
import { MoveNotebookSheet } from "../sheets/move-notebook";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
import { IconButton } from "../ui/icon-button";
|
import { IconButton } from "../ui/icon-button";
|
||||||
import Heading from "../ui/typography/heading";
|
import Heading from "../ui/typography/heading";
|
||||||
import { MoveNotebookSheet } from "../sheets/move-notebook";
|
|
||||||
|
|
||||||
export const SelectionHeader = React.memo(
|
export const SelectionHeader = React.memo(
|
||||||
({
|
({
|
||||||
@@ -58,6 +59,8 @@ export const SelectionHeader = React.memo(
|
|||||||
type?: ItemType;
|
type?: ItemType;
|
||||||
renderedInRoute?: string;
|
renderedInRoute?: string;
|
||||||
}) => {
|
}) => {
|
||||||
|
const menuRef = useRef<Menu>(null);
|
||||||
|
const { colors: contextMenuColors } = useThemeColors("contextMenu");
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||||
const selectedItemsList = useSelectionStore(
|
const selectedItemsList = useSelectionStore(
|
||||||
@@ -132,8 +135,7 @@ export const SelectionHeader = React.memo(
|
|||||||
}, [clearSelection, selectionMode]);
|
}, [clearSelection, selectionMode]);
|
||||||
|
|
||||||
return selectionMode !== type || focusedRouteId !== id ? null : (
|
return selectionMode !== type || focusedRouteId !== id ? null : (
|
||||||
<Animated.View
|
<View
|
||||||
entering={FadeInUp}
|
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: Platform.OS === "android" ? 50 + insets.top : 50,
|
height: Platform.OS === "android" ? 50 + insets.top : 50,
|
||||||
@@ -143,8 +145,7 @@ export const SelectionHeader = React.memo(
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
zIndex: 999,
|
zIndex: 999,
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12
|
||||||
marginVertical: 10
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
@@ -164,18 +165,15 @@ export const SelectionHeader = React.memo(
|
|||||||
borderRadius: 100,
|
borderRadius: 100,
|
||||||
marginRight: 10
|
marginRight: 10
|
||||||
}}
|
}}
|
||||||
type="secondary"
|
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
clearSelection();
|
clearSelection();
|
||||||
}}
|
}}
|
||||||
size={SIZE.xl}
|
|
||||||
color={colors.primary.icon}
|
color={colors.primary.icon}
|
||||||
name="close"
|
name="close"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: colors.secondary.background,
|
|
||||||
height: 40,
|
height: 40,
|
||||||
borderRadius: 100,
|
borderRadius: 100,
|
||||||
paddingHorizontal: 16,
|
paddingHorizontal: 16,
|
||||||
@@ -184,11 +182,12 @@ export const SelectionHeader = React.memo(
|
|||||||
alignItems: "center"
|
alignItems: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Heading size={SIZE.md} color={colors.primary.accent}>
|
<Heading size={SIZE.lg} color={colors.primary.paragraph}>
|
||||||
{selectedItemsList.length}
|
{selectedItemsList.length}
|
||||||
</Heading>
|
</Heading>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
@@ -211,159 +210,142 @@ export const SelectionHeader = React.memo(
|
|||||||
allSelected ? colors.primary.accent : colors.primary.paragraph
|
allSelected ? colors.primary.accent : colors.primary.paragraph
|
||||||
}
|
}
|
||||||
name="select-all"
|
name="select-all"
|
||||||
size={SIZE.xl}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{renderedInRoute === "Notebooks" && selectedItemsList.length ? (
|
{selectedItemsList.length ? (
|
||||||
<IconButton
|
<Menu
|
||||||
|
ref={menuRef}
|
||||||
|
animationDuration={200}
|
||||||
style={{
|
style={{
|
||||||
marginLeft: 10
|
borderRadius: 5,
|
||||||
|
backgroundColor: contextMenuColors.primary.background,
|
||||||
|
marginTop: -20
|
||||||
}}
|
}}
|
||||||
onPress={async () => {
|
onRequestClose={() => {
|
||||||
const ids = selectedItemsList;
|
menuRef.current?.hide();
|
||||||
const notebooks = await db.notebooks.all.items(ids);
|
|
||||||
MoveNotebookSheet.present(notebooks);
|
|
||||||
}}
|
}}
|
||||||
tooltipText="Move notebooks"
|
anchor={
|
||||||
tooltipPosition={1}
|
<IconButton
|
||||||
name="arrow-right-bold-box-outline"
|
onPress={() => {
|
||||||
size={SIZE.xl}
|
menuRef.current?.show();
|
||||||
/>
|
}}
|
||||||
) : null}
|
name="dots-vertical"
|
||||||
|
color={colors.primary.paragraph}
|
||||||
{type !== "note" || !selectedItemsList.length ? null : (
|
/>
|
||||||
<>
|
}
|
||||||
<IconButton
|
>
|
||||||
onPress={async () => {
|
{[
|
||||||
await sleep(100);
|
{
|
||||||
ManageTagsSheet.present(selectedItemsList);
|
title:
|
||||||
}}
|
selectedItemsList.length > 1
|
||||||
style={{
|
? "Move notebooks"
|
||||||
marginLeft: 10
|
: "Move notebook",
|
||||||
}}
|
onPress: async () => {
|
||||||
color={colors.primary.icon}
|
const ids = selectedItemsList;
|
||||||
tooltipText="Manage tags"
|
const notebooks = await db.notebooks.all.items(ids);
|
||||||
tooltipPosition={4}
|
MoveNotebookSheet.present(notebooks);
|
||||||
name="pound"
|
},
|
||||||
size={SIZE.xl}
|
visible: renderedInRoute === "Notebooks",
|
||||||
/>
|
icon: "arrow-right-bold-box-outline"
|
||||||
|
},
|
||||||
<IconButton
|
{
|
||||||
onPress={async () => {
|
title: "Manage tags",
|
||||||
ExportNotesSheet.present(selectedItemsList);
|
onPress: async () => {
|
||||||
}}
|
await sleep(100);
|
||||||
tooltipText="Export"
|
ManageTagsSheet.present(selectedItemsList);
|
||||||
tooltipPosition={4}
|
},
|
||||||
style={{
|
visible: type === "note",
|
||||||
marginLeft: 10
|
icon: "pound"
|
||||||
}}
|
},
|
||||||
color={colors.primary.paragraph}
|
{
|
||||||
name="export"
|
title: "Export",
|
||||||
size={SIZE.xl}
|
onPress: async () => {
|
||||||
/>
|
await sleep(100);
|
||||||
|
ExportNotesSheet.present(selectedItemsList);
|
||||||
<IconButton
|
},
|
||||||
onPress={async () => {
|
visible: type === "note",
|
||||||
MoveNoteSheet.present();
|
icon: "export"
|
||||||
}}
|
},
|
||||||
style={{
|
{
|
||||||
marginLeft: 10
|
title: "Link notebook",
|
||||||
}}
|
onPress: async () => {
|
||||||
tooltipText="Add to notebooks"
|
await sleep(100);
|
||||||
tooltipPosition={4}
|
MoveNoteSheet.present();
|
||||||
color={colors.primary.paragraph}
|
},
|
||||||
name="plus"
|
visible: type === "note",
|
||||||
size={SIZE.xl}
|
icon: "plus"
|
||||||
/>
|
},
|
||||||
</>
|
{
|
||||||
)}
|
title: "Unlink notebook",
|
||||||
|
onPress: async () => {
|
||||||
{renderedInRoute === "Notebook" && selectedItemsList.length ? (
|
if (!id) return;
|
||||||
<IconButton
|
await db.notes.removeFromNotebook(id, ...selectedItemsList);
|
||||||
onPress={async () => {
|
updateNotebook(id);
|
||||||
if (!id) return;
|
Navigation.queueRoutesForUpdate();
|
||||||
await db.notes.removeFromNotebook(id, ...selectedItemsList);
|
clearSelection();
|
||||||
updateNotebook(id);
|
},
|
||||||
Navigation.queueRoutesForUpdate();
|
visible: renderedInRoute === "Notebook",
|
||||||
clearSelection();
|
icon: "minus"
|
||||||
}}
|
},
|
||||||
style={{
|
{
|
||||||
marginLeft: 10
|
title: "Unfavorite",
|
||||||
}}
|
onPress: addToFavorite,
|
||||||
tooltipText={`Remove from Notebook`}
|
visible: focusedRouteId === "Favorites",
|
||||||
tooltipPosition={4}
|
icon: "star-off"
|
||||||
testID="select-minus"
|
},
|
||||||
color={colors.primary.paragraph}
|
{
|
||||||
name="minus"
|
title: `Move to trash`,
|
||||||
size={SIZE.xl}
|
onPress: async () => {
|
||||||
/>
|
deleteItems(
|
||||||
) : null}
|
undefined,
|
||||||
|
useSelectionStore.getState().selectionMode
|
||||||
{focusedRouteId === "Favorites" && selectedItemsList.length ? (
|
).then(() => {
|
||||||
<IconButton
|
useSelectionStore.getState().clearSelection();
|
||||||
onPress={addToFavorite}
|
useSelectionStore.getState().setSelectionMode(undefined);
|
||||||
style={{
|
});
|
||||||
marginLeft: 10
|
},
|
||||||
}}
|
visible: type !== "trash",
|
||||||
tooltipText="Remove from favorites"
|
icon: "delete"
|
||||||
tooltipPosition={4}
|
},
|
||||||
color={colors.primary.paragraph}
|
{
|
||||||
name="star-off"
|
title: `Restore`,
|
||||||
size={SIZE.xl}
|
onPress: restoreItem,
|
||||||
/>
|
visible: type === "trash",
|
||||||
) : null}
|
icon: "delete-restore"
|
||||||
|
},
|
||||||
{type === "trash" || !selectedItemsList.length ? null : (
|
{
|
||||||
<IconButton
|
title: `Delete`,
|
||||||
style={{
|
onPress: deleteItem,
|
||||||
marginLeft: 10
|
visible: type === "trash",
|
||||||
}}
|
icon: "delete"
|
||||||
onPress={() => {
|
}
|
||||||
deleteItems(
|
].map((item) =>
|
||||||
undefined,
|
!item.visible ? null : (
|
||||||
useSelectionStore.getState().selectionMode
|
<Button
|
||||||
).then(() => {
|
style={{
|
||||||
useSelectionStore.getState().clearSelection();
|
width: 150,
|
||||||
useSelectionStore.getState().setSelectionMode(undefined);
|
justifyContent: "flex-start",
|
||||||
});
|
borderRadius: 0
|
||||||
}}
|
}}
|
||||||
tooltipText="Move to trash"
|
type="plain"
|
||||||
tooltipPosition={1}
|
buttonType={{
|
||||||
color={colors.primary.paragraph}
|
text: contextMenuColors.primary.paragraph
|
||||||
name="delete"
|
}}
|
||||||
size={SIZE.xl}
|
icon={item.icon}
|
||||||
/>
|
key={item.title}
|
||||||
)}
|
title={item.title}
|
||||||
|
onPress={async () => {
|
||||||
{type === "trash" && selectedItemsList.length ? (
|
menuRef.current?.hide();
|
||||||
<>
|
if (Platform.OS === "ios") await sleep(300);
|
||||||
<IconButton
|
item.onPress();
|
||||||
style={{
|
}}
|
||||||
marginLeft: 10
|
/>
|
||||||
}}
|
)
|
||||||
color={colors.primary.paragraph}
|
)}
|
||||||
onPress={restoreItem}
|
</Menu>
|
||||||
name="delete-restore"
|
|
||||||
tooltipText="Restore"
|
|
||||||
tooltipPosition={4}
|
|
||||||
size={SIZE.xl - 3}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<IconButton
|
|
||||||
style={{
|
|
||||||
marginLeft: 10
|
|
||||||
}}
|
|
||||||
color={colors.primary.paragraph}
|
|
||||||
onPress={deleteItem}
|
|
||||||
tooltipText="Delete"
|
|
||||||
tooltipPosition={4}
|
|
||||||
name="delete"
|
|
||||||
size={SIZE.xl - 3}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
</Animated.View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -45,14 +45,16 @@ export const AddNotebookSheet = ({
|
|||||||
notebook,
|
notebook,
|
||||||
parentNotebook,
|
parentNotebook,
|
||||||
close,
|
close,
|
||||||
showMoveNotesOnComplete
|
showMoveNotesOnComplete,
|
||||||
|
defaultTitle
|
||||||
}: {
|
}: {
|
||||||
notebook?: Notebook;
|
notebook?: Notebook;
|
||||||
parentNotebook?: Notebook;
|
parentNotebook?: Notebook;
|
||||||
close?: (didAddNotebook: boolean) => void;
|
close?: (didAddNotebook: boolean) => void;
|
||||||
showMoveNotesOnComplete: boolean;
|
showMoveNotesOnComplete: boolean;
|
||||||
|
defaultTitle?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const title = useRef(notebook?.title);
|
const title = useRef(notebook?.title || defaultTitle);
|
||||||
const description = useRef(notebook?.description);
|
const description = useRef(notebook?.description);
|
||||||
const titleInput = useRef<TextInput>(null);
|
const titleInput = useRef<TextInput>(null);
|
||||||
const descriptionInput = useRef<TextInput>(null);
|
const descriptionInput = useRef<TextInput>(null);
|
||||||
@@ -139,7 +141,7 @@ export const AddNotebookSheet = ({
|
|||||||
}}
|
}}
|
||||||
returnKeyLabel="Next"
|
returnKeyLabel="Next"
|
||||||
returnKeyType="next"
|
returnKeyType="next"
|
||||||
defaultValue={notebook ? notebook.title : ""}
|
defaultValue={notebook ? notebook.title : title.current}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
@@ -174,7 +176,8 @@ AddNotebookSheet.present = (
|
|||||||
parentNotebook?: Notebook,
|
parentNotebook?: Notebook,
|
||||||
context?: string,
|
context?: string,
|
||||||
onClose?: (didAddNotebook: boolean) => void,
|
onClose?: (didAddNotebook: boolean) => void,
|
||||||
showMoveNotesOnComplete = true
|
showMoveNotesOnComplete = true,
|
||||||
|
defaultTitle?: string
|
||||||
) => {
|
) => {
|
||||||
presentSheet({
|
presentSheet({
|
||||||
context: context,
|
context: context,
|
||||||
@@ -187,6 +190,7 @@ AddNotebookSheet.present = (
|
|||||||
onClose?.(didAddNotebook);
|
onClose?.(didAddNotebook);
|
||||||
}}
|
}}
|
||||||
showMoveNotesOnComplete={showMoveNotesOnComplete || false}
|
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 { Note } from "@notesnook/core";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React, { RefObject, useCallback, useEffect } from "react";
|
import React, {
|
||||||
|
RefObject,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState
|
||||||
|
} from "react";
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
Keyboard,
|
Keyboard,
|
||||||
@@ -47,6 +53,8 @@ import Paragraph from "../../ui/typography/paragraph";
|
|||||||
import { NotebookItem } from "./notebook-item";
|
import { NotebookItem } from "./notebook-item";
|
||||||
import { useNotebookItemSelectionStore } from "./store";
|
import { useNotebookItemSelectionStore } from "./store";
|
||||||
import { AddNotebookSheet } from "../add-notebook";
|
import { AddNotebookSheet } from "../add-notebook";
|
||||||
|
import Input from "../../ui/input";
|
||||||
|
import { presentDialog } from "../../dialog/functions";
|
||||||
|
|
||||||
async function updateInitialSelectionState(items: string[]) {
|
async function updateInitialSelectionState(items: string[]) {
|
||||||
const relations = await db.relations
|
const relations = await db.relations
|
||||||
@@ -93,7 +101,11 @@ const MoveNoteSheet = ({
|
|||||||
actionSheetRef: RefObject<ActionSheetRef>;
|
actionSheetRef: RefObject<ActionSheetRef>;
|
||||||
}) => {
|
}) => {
|
||||||
const { colors } = useThemeColors();
|
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 dimensions = useSettingStore((state) => state.dimensions);
|
||||||
const selectedItemsList = useSelectionStore(
|
const selectedItemsList = useSelectionStore(
|
||||||
(state) => state.selectedItemsList
|
(state) => state.selectedItemsList
|
||||||
@@ -103,6 +115,12 @@ const MoveNoteSheet = ({
|
|||||||
(state) => state.multiSelect
|
(state) => state.multiSelect
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
setNotebooks(rootNotebooks);
|
||||||
|
}
|
||||||
|
}, [loading, rootNotebooks]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const items = note ? [note.id] : selectedItemsList;
|
const items = note ? [note.id] : selectedItemsList;
|
||||||
updateInitialSelectionState(items);
|
updateInitialSelectionState(items);
|
||||||
@@ -199,7 +217,6 @@ const MoveNoteSheet = ({
|
|||||||
{hasSelected() ? (
|
{hasSelected() ? (
|
||||||
<IconButton
|
<IconButton
|
||||||
name="restore"
|
name="restore"
|
||||||
type="secondaryAccented"
|
|
||||||
color={colors.primary.icon}
|
color={colors.primary.icon}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
const items = note ? [note.id] : selectedItemsList;
|
const items = note ? [note.id] : selectedItemsList;
|
||||||
@@ -239,33 +256,48 @@ const MoveNoteSheet = ({
|
|||||||
style={{
|
style={{
|
||||||
width: "100%"
|
width: "100%"
|
||||||
}}
|
}}
|
||||||
|
keyboardShouldPersistTaps="handled"
|
||||||
ListHeaderComponent={
|
ListHeaderComponent={
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
width: "100%"
|
width: "100%",
|
||||||
|
paddingTop: 12
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Input
|
||||||
title="Add new notebook"
|
placeholder="Search notebooks...."
|
||||||
style={{
|
button={{
|
||||||
alignSelf: "flex-start",
|
icon: "plus",
|
||||||
paddingHorizontal: 12,
|
onPress: () => {
|
||||||
justifyContent: "space-between"
|
AddNotebookSheet.present(
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
"link-notebooks",
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
searchQuery.current
|
||||||
|
);
|
||||||
|
},
|
||||||
|
color: colors.primary.icon
|
||||||
}}
|
}}
|
||||||
onPress={() => {
|
onChangeText={(value) => {
|
||||||
AddNotebookSheet.present(
|
searchQuery.current = value;
|
||||||
undefined,
|
if (!searchQuery.current) {
|
||||||
undefined,
|
setNotebooks(rootNotebooks);
|
||||||
"link-notebooks",
|
return;
|
||||||
undefined,
|
}
|
||||||
false
|
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>
|
</View>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React, { Fragment, useState } from "react";
|
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 FileViewer from "react-native-file-viewer";
|
||||||
import Share from "react-native-share";
|
import Share from "react-native-share";
|
||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
@@ -29,14 +35,12 @@ import { requestInAppReview } from "../../../services/app-review";
|
|||||||
import {
|
import {
|
||||||
PresentSheetOptions,
|
PresentSheetOptions,
|
||||||
ToastManager,
|
ToastManager,
|
||||||
eSendEvent,
|
|
||||||
presentSheet
|
presentSheet
|
||||||
} from "../../../services/event-manager";
|
} from "../../../services/event-manager";
|
||||||
import Exporter from "../../../services/exporter";
|
import Exporter from "../../../services/exporter";
|
||||||
import PremiumService from "../../../services/premium";
|
import PremiumService from "../../../services/premium";
|
||||||
import { useUserStore } from "../../../stores/use-user-store";
|
import { useUserStore } from "../../../stores/use-user-store";
|
||||||
import { getElevationStyle } from "../../../utils/elevation";
|
import { getElevationStyle } from "../../../utils/elevation";
|
||||||
import { eCloseSheet } from "../../../utils/events";
|
|
||||||
import { SIZE, ph, pv } from "../../../utils/size";
|
import { SIZE, ph, pv } from "../../../utils/size";
|
||||||
import { sleep } from "../../../utils/time";
|
import { sleep } from "../../../utils/time";
|
||||||
import { Dialog } from "../../dialog";
|
import { Dialog } from "../../dialog";
|
||||||
@@ -65,6 +69,7 @@ const ExportNotesSheet = ({
|
|||||||
filePath: string;
|
filePath: string;
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: string;
|
||||||
|
fileDir: string;
|
||||||
}
|
}
|
||||||
| undefined
|
| undefined
|
||||||
>();
|
>();
|
||||||
@@ -295,7 +300,9 @@ const ExportNotesSheet = ({
|
|||||||
successfully as {result?.fileName}
|
successfully as {result?.fileName}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Button
|
<Button
|
||||||
title="Open"
|
title={
|
||||||
|
Platform.OS === "android" ? "Open file location" : "Open"
|
||||||
|
}
|
||||||
type="accent"
|
type="accent"
|
||||||
width={250}
|
width={250}
|
||||||
fontSize={SIZE.md}
|
fontSize={SIZE.md}
|
||||||
@@ -305,20 +312,23 @@ const ExportNotesSheet = ({
|
|||||||
}}
|
}}
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
if (!result?.filePath) return;
|
if (!result?.filePath) return;
|
||||||
eSendEvent(eCloseSheet);
|
if (Platform.OS === "android") {
|
||||||
await sleep(500);
|
Linking.openURL(result.fileDir).catch((e) => {
|
||||||
FileViewer.open(result?.filePath, {
|
ToastManager.error(e as Error);
|
||||||
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"
|
|
||||||
});
|
});
|
||||||
});
|
} 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
|
<Button
|
||||||
@@ -331,10 +341,10 @@ const ExportNotesSheet = ({
|
|||||||
borderRadius: 100
|
borderRadius: 100
|
||||||
}}
|
}}
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
if (!result?.filePath) return;
|
if (!result) return;
|
||||||
if (Platform.OS === "ios") {
|
if (Platform.OS === "ios") {
|
||||||
Share.open({
|
Share.open({
|
||||||
url: result.filePath
|
url: result?.fileDir + result.fileName
|
||||||
}).catch(console.log);
|
}).catch(console.log);
|
||||||
} else {
|
} else {
|
||||||
FileViewer.open(result.filePath, {
|
FileViewer.open(result.filePath, {
|
||||||
@@ -347,7 +357,7 @@ const ExportNotesSheet = ({
|
|||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
title="Export in another format"
|
title="Export in another format"
|
||||||
type="secondaryAccented"
|
type="inverted"
|
||||||
width={250}
|
width={250}
|
||||||
fontSize={SIZE.md}
|
fontSize={SIZE.md}
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -18,35 +18,33 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Debug } from "@notesnook/core/dist/api/debug";
|
import { Debug } from "@notesnook/core/dist/api/debug";
|
||||||
|
import { getModel, getBrand, getSystemVersion } from "react-native-device-info";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import Clipboard from "@react-native-clipboard/clipboard";
|
|
||||||
import React, { useRef, useState } from "react";
|
import React, { useRef, useState } from "react";
|
||||||
import { Linking, Platform, Text, TextInput, View } from "react-native";
|
import { Linking, Platform, Text, TextInput, View } from "react-native";
|
||||||
import { getVersion } from "react-native-device-info";
|
import { getVersion } from "react-native-device-info";
|
||||||
import { useStoredRef } from "../../../hooks/use-stored-ref";
|
import { useStoredRef } from "../../../hooks/use-stored-ref";
|
||||||
import { ToastManager, eSendEvent } from "../../../services/event-manager";
|
import { ToastManager } from "../../../services/event-manager";
|
||||||
import PremiumService from "../../../services/premium";
|
import PremiumService from "../../../services/premium";
|
||||||
import { useUserStore } from "../../../stores/use-user-store";
|
import { useUserStore } from "../../../stores/use-user-store";
|
||||||
import { eCloseSheet } from "../../../utils/events";
|
|
||||||
import { openLinkInBrowser } from "../../../utils/functions";
|
import { openLinkInBrowser } from "../../../utils/functions";
|
||||||
import { SIZE } from "../../../utils/size";
|
import { SIZE } from "../../../utils/size";
|
||||||
import { sleep } from "../../../utils/time";
|
|
||||||
import DialogHeader from "../../dialog/dialog-header";
|
import DialogHeader from "../../dialog/dialog-header";
|
||||||
import { presentDialog } from "../../dialog/functions";
|
|
||||||
import { Button } from "../../ui/button";
|
import { Button } from "../../ui/button";
|
||||||
import Seperator from "../../ui/seperator";
|
import Seperator from "../../ui/seperator";
|
||||||
|
import Heading from "../../ui/typography/heading";
|
||||||
import Paragraph from "../../ui/typography/paragraph";
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
|
|
||||||
export const Issue = ({ defaultTitle, defaultBody, issueTitle }) => {
|
export const Issue = ({ defaultTitle, defaultBody, issueTitle }) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
|
const body = useStoredRef("issueBody");
|
||||||
const body = useStoredRef("issueBody", defaultBody);
|
|
||||||
const title = useStoredRef("issueTitle", defaultTitle);
|
const title = useStoredRef("issueTitle", defaultTitle);
|
||||||
|
const [done, setDone] = useState(false);
|
||||||
const user = useUserStore((state) => state.user);
|
const user = useUserStore((state) => state.user);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const bodyRef = useRef();
|
const bodyRef = useRef();
|
||||||
const initialLayout = useRef(false);
|
const initialLayout = useRef(false);
|
||||||
|
const issueUrl = useRef();
|
||||||
|
|
||||||
const onPress = async () => {
|
const onPress = async () => {
|
||||||
if (loading) return;
|
if (loading) return;
|
||||||
@@ -56,57 +54,24 @@ export const Issue = ({ defaultTitle, defaultBody, issueTitle }) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
let issue_url = await Debug.report({
|
issueUrl.current = await Debug.report({
|
||||||
title: title.current,
|
title: title.current,
|
||||||
body:
|
body:
|
||||||
body.current +
|
body.current +
|
||||||
|
`\n${defaultBody || ""}` +
|
||||||
`\n_______________
|
`\n_______________
|
||||||
**Device information:**
|
**Device information:**
|
||||||
App version: ${getVersion()}
|
App version: ${getVersion()}
|
||||||
Platform: ${Platform.OS}
|
Platform: ${Platform.OS}
|
||||||
Model: ${Platform.constants.Brand || ""}-${Platform.constants.Model || ""}-${
|
Device: ${getBrand() || ""}-${getModel() || ""}-${getSystemVersion() || ""}
|
||||||
Platform.constants.Version || ""
|
|
||||||
}
|
|
||||||
Pro: ${PremiumService.get()}
|
Pro: ${PremiumService.get()}
|
||||||
Logged in: ${user ? "yes" : "no"}`,
|
Logged in: ${user ? "yes" : "no"}`,
|
||||||
userId: user?.id
|
userId: user?.id
|
||||||
});
|
});
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
eSendEvent(eCloseSheet);
|
|
||||||
body.reset();
|
body.reset();
|
||||||
title.reset();
|
title.reset();
|
||||||
await sleep(300);
|
setDone(true);
|
||||||
presentDialog({
|
|
||||||
title: "Issue reported",
|
|
||||||
paragraph: (
|
|
||||||
<Text>
|
|
||||||
You can track your issue at{" "}
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
textDecorationLine: "underline",
|
|
||||||
color: colors.primary.accent
|
|
||||||
}}
|
|
||||||
onPress={() => {
|
|
||||||
Linking.openURL(issue_url);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{issue_url}.
|
|
||||||
</Text>{" "}
|
|
||||||
Please note that we will respond to your issue on the given link. We
|
|
||||||
recommend that you save it.
|
|
||||||
</Text>
|
|
||||||
),
|
|
||||||
positiveText: "Copy link",
|
|
||||||
positivePress: () => {
|
|
||||||
Clipboard.setString(issue_url);
|
|
||||||
ToastManager.show({
|
|
||||||
heading: "Issue url copied!",
|
|
||||||
type: "success",
|
|
||||||
context: "global"
|
|
||||||
});
|
|
||||||
},
|
|
||||||
negativeText: "Close"
|
|
||||||
});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
@@ -124,125 +89,177 @@ Logged in: ${user ? "yes" : "no"}`,
|
|||||||
width: "100%"
|
width: "100%"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogHeader
|
{done ? (
|
||||||
title={issueTitle || "Report issue"}
|
<>
|
||||||
paragraph={
|
<View
|
||||||
issueTitle
|
style={{
|
||||||
? "We are sorry, it seems that the app crashed due to an error. You can submit a bug report below so we can fix this asap."
|
height: 250,
|
||||||
: "Let us know if you have faced any issue/bug while using Notesnook."
|
justifyContent: "center",
|
||||||
}
|
alignItems: "center",
|
||||||
/>
|
gap: 10
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Heading>Issue submitted</Heading>
|
||||||
|
<Paragraph
|
||||||
|
style={{
|
||||||
|
textAlign: "center"
|
||||||
|
}}
|
||||||
|
selectable={true}
|
||||||
|
>
|
||||||
|
You can track your issue at{" "}
|
||||||
|
<Paragraph
|
||||||
|
style={{
|
||||||
|
textDecorationLine: "underline",
|
||||||
|
color: colors.primary.accent
|
||||||
|
}}
|
||||||
|
onPress={() => {
|
||||||
|
Linking.openURL(issueUrl.current);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{issueUrl.current}
|
||||||
|
</Paragraph>
|
||||||
|
. Please note that we will respond to your issue on the given
|
||||||
|
link. We recommend that you save it.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
<Seperator half />
|
<Button
|
||||||
|
title="Open issue"
|
||||||
<TextInput
|
onPress={() => {
|
||||||
placeholder="Title"
|
Linking.openURL(issueUrl.current);
|
||||||
onChangeText={(v) => (title.current = v)}
|
}}
|
||||||
defaultValue={title.current}
|
type="accent"
|
||||||
style={{
|
width="100%"
|
||||||
borderWidth: 1,
|
/>
|
||||||
borderColor: colors.primary.border,
|
</View>
|
||||||
borderRadius: 5,
|
</>
|
||||||
padding: 12,
|
) : (
|
||||||
fontFamily: "OpenSans-Regular",
|
<>
|
||||||
marginBottom: 10,
|
<DialogHeader
|
||||||
fontSize: SIZE.md,
|
title={issueTitle || "Report issue"}
|
||||||
color: colors.primary.heading
|
paragraph={
|
||||||
}}
|
issueTitle
|
||||||
placeholderTextColor={colors.primary.placeholder}
|
? "We are sorry, it seems that the app crashed due to an error. You can submit a bug report below so we can fix this asap."
|
||||||
/>
|
: "Let us know if you have faced any issue/bug while using Notesnook."
|
||||||
|
|
||||||
<TextInput
|
|
||||||
ref={bodyRef}
|
|
||||||
placeholder={`Tell us more about the issue you are facing.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
- Steps to reproduce the issue
|
|
||||||
- Things you have tried etc.`}
|
|
||||||
multiline
|
|
||||||
numberOfLines={5}
|
|
||||||
textAlignVertical="top"
|
|
||||||
onChangeText={(v) => (body.current = v)}
|
|
||||||
onLayout={() => {
|
|
||||||
if (initialLayout.current) return;
|
|
||||||
initialLayout.current = true;
|
|
||||||
if (body.current) {
|
|
||||||
bodyRef.current?.setNativeProps({
|
|
||||||
text: body.current,
|
|
||||||
selection: {
|
|
||||||
start: 0,
|
|
||||||
end: 0
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: colors.primary.border,
|
|
||||||
borderRadius: 5,
|
|
||||||
padding: 12,
|
|
||||||
fontFamily: "OpenSans-Regular",
|
|
||||||
maxHeight: 200,
|
|
||||||
fontSize: SIZE.sm,
|
|
||||||
marginBottom: 2.5,
|
|
||||||
color: colors.primary.paragraph
|
|
||||||
}}
|
|
||||||
placeholderTextColor={colors.primary.placeholder}
|
|
||||||
/>
|
|
||||||
<Paragraph
|
|
||||||
size={SIZE.xs}
|
|
||||||
color={colors.secondary.paragraph}
|
|
||||||
>{`App version: ${getVersion()} Platform: ${Platform.OS} Model: ${
|
|
||||||
Platform.constants.Brand
|
|
||||||
}-${Platform.constants.Model}-${Platform.constants.Version}`}</Paragraph>
|
|
||||||
|
|
||||||
<Seperator />
|
|
||||||
<Button
|
|
||||||
onPress={onPress}
|
|
||||||
title={loading ? null : "Submit"}
|
|
||||||
loading={loading}
|
|
||||||
width="100%"
|
|
||||||
type="accent"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Paragraph
|
|
||||||
color={colors.secondary.paragraph}
|
|
||||||
size={SIZE.xs}
|
|
||||||
style={{
|
|
||||||
marginTop: 10,
|
|
||||||
textAlign: "center"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
The information above will be publically available at{" "}
|
|
||||||
<Text
|
|
||||||
onPress={() => {
|
|
||||||
Linking.openURL("https://github.com/streetwriters/notesnook");
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
textDecorationLine: "underline",
|
|
||||||
color: colors.primary.accent
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
github.com/streetwriters/notesnook.
|
|
||||||
</Text>{" "}
|
|
||||||
If you want to ask something in general or need some assistance, we
|
|
||||||
would suggest that you{" "}
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
textDecorationLine: "underline",
|
|
||||||
color: colors.primary.accent
|
|
||||||
}}
|
|
||||||
onPress={async () => {
|
|
||||||
try {
|
|
||||||
await openLinkInBrowser("https://discord.gg/zQBK97EE22", colors);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
}
|
||||||
}}
|
/>
|
||||||
>
|
|
||||||
join our community on Discord.
|
<Seperator half />
|
||||||
</Text>
|
|
||||||
</Paragraph>
|
<TextInput
|
||||||
|
placeholder="Title"
|
||||||
|
onChangeText={(v) => (title.current = v)}
|
||||||
|
defaultValue={title.current}
|
||||||
|
style={{
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.primary.border,
|
||||||
|
borderRadius: 5,
|
||||||
|
padding: 12,
|
||||||
|
fontFamily: "OpenSans-Regular",
|
||||||
|
marginBottom: 10,
|
||||||
|
fontSize: SIZE.md,
|
||||||
|
color: colors.primary.heading
|
||||||
|
}}
|
||||||
|
placeholderTextColor={colors.primary.placeholder}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
ref={bodyRef}
|
||||||
|
multiline
|
||||||
|
placeholder={`Tell us more about the issue you are facing.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
- What were you trying to do in the app?
|
||||||
|
- What did you expect to happen?
|
||||||
|
- Steps to reproduce the issue
|
||||||
|
- Things you have tried etc.`}
|
||||||
|
numberOfLines={5}
|
||||||
|
textAlignVertical="top"
|
||||||
|
onChangeText={(v) => (body.current = v)}
|
||||||
|
onLayout={() => {
|
||||||
|
if (initialLayout.current) return;
|
||||||
|
initialLayout.current = true;
|
||||||
|
if (body.current) {
|
||||||
|
bodyRef.current?.setNativeProps({
|
||||||
|
text: body.current,
|
||||||
|
selection: {
|
||||||
|
start: 0,
|
||||||
|
end: 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.primary.border,
|
||||||
|
borderRadius: 5,
|
||||||
|
padding: 12,
|
||||||
|
fontFamily: "OpenSans-Regular",
|
||||||
|
maxHeight: 200,
|
||||||
|
fontSize: SIZE.sm,
|
||||||
|
marginBottom: 2.5,
|
||||||
|
color: colors.primary.paragraph
|
||||||
|
}}
|
||||||
|
placeholderTextColor={colors.primary.placeholder}
|
||||||
|
/>
|
||||||
|
<Paragraph
|
||||||
|
size={SIZE.xs}
|
||||||
|
color={colors.secondary.paragraph}
|
||||||
|
>{`App version: ${getVersion()} Platform: ${
|
||||||
|
Platform.OS
|
||||||
|
} Model: ${getBrand()}-${getModel()}-${getSystemVersion()}`}</Paragraph>
|
||||||
|
|
||||||
|
<Seperator />
|
||||||
|
<Button
|
||||||
|
onPress={onPress}
|
||||||
|
title={loading ? null : "Submit"}
|
||||||
|
loading={loading}
|
||||||
|
width="100%"
|
||||||
|
type="accent"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Paragraph
|
||||||
|
color={colors.secondary.paragraph}
|
||||||
|
size={SIZE.xs}
|
||||||
|
style={{
|
||||||
|
marginTop: 10,
|
||||||
|
textAlign: "center"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
The information above will be publically available at{" "}
|
||||||
|
<Text
|
||||||
|
onPress={() => {
|
||||||
|
Linking.openURL("https://github.com/streetwriters/notesnook");
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
textDecorationLine: "underline",
|
||||||
|
color: colors.primary.accent
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
github.com/streetwriters/notesnook.
|
||||||
|
</Text>{" "}
|
||||||
|
If you want to ask something in general or need some assistance, we
|
||||||
|
would suggest that you{" "}
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
textDecorationLine: "underline",
|
||||||
|
color: colors.primary.accent
|
||||||
|
}}
|
||||||
|
onPress={async () => {
|
||||||
|
try {
|
||||||
|
await openLinkInBrowser(
|
||||||
|
"https://discord.gg/zQBK97EE22",
|
||||||
|
colors
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
join our community on Discord.
|
||||||
|
</Text>
|
||||||
|
</Paragraph>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,7 +35,10 @@ import Input from "../../ui/input";
|
|||||||
import { Pressable } from "../../ui/pressable";
|
import { Pressable } from "../../ui/pressable";
|
||||||
import Paragraph from "../../ui/typography/paragraph";
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
import type { LinkAttributes } from "@notesnook/editor/dist/extensions/link";
|
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 = ({
|
const ListNoteItem = ({
|
||||||
id,
|
id,
|
||||||
@@ -190,13 +193,13 @@ export default function LinkNote(props: {
|
|||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
);
|
);
|
||||||
editorController.current.commands.createInternalLink(
|
editorController.current?.postMessage(EditorEvents.resolve, {
|
||||||
{
|
data: {
|
||||||
href: link,
|
href: link,
|
||||||
title: selectedNote.title
|
title: selectedNote.title
|
||||||
},
|
},
|
||||||
props.resolverId
|
resolverId: props.resolverId
|
||||||
);
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSelectNote = async (note: Note) => {
|
const onSelectNote = async (note: Note) => {
|
||||||
@@ -219,7 +222,7 @@ export default function LinkNote(props: {
|
|||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
minHeight: 400,
|
minHeight: "100%",
|
||||||
maxHeight: "100%"
|
maxHeight: "100%"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -311,6 +314,7 @@ export default function LinkNote(props: {
|
|||||||
style={{
|
style={{
|
||||||
marginTop: 10
|
marginTop: 10
|
||||||
}}
|
}}
|
||||||
|
keyboardShouldPersistTaps="handled"
|
||||||
windowSize={3}
|
windowSize={3}
|
||||||
keyExtractor={(item) => item.id}
|
keyExtractor={(item) => item.id}
|
||||||
data={nodes}
|
data={nodes}
|
||||||
@@ -324,6 +328,7 @@ export default function LinkNote(props: {
|
|||||||
onSelectNote={onSelectNote}
|
onSelectNote={onSelectNote}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
keyboardShouldPersistTaps="handled"
|
||||||
style={{
|
style={{
|
||||||
marginTop: 10
|
marginTop: 10
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -42,10 +42,7 @@ import { ProgressBarComponent } from "../../ui/svg/lazy";
|
|||||||
import Paragraph from "../../ui/typography/paragraph";
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
import { Issue } from "../github/issue";
|
import { Issue } from "../github/issue";
|
||||||
|
|
||||||
export const makeError = (
|
export const makeError = (stack: string, component: string) => `
|
||||||
stack: string,
|
|
||||||
component: string
|
|
||||||
) => `Please let us know what happened. What steps we can take to reproduce the issue here.
|
|
||||||
|
|
||||||
_______________________________
|
_______________________________
|
||||||
Stacktrace: In ${component}::${stack}`;
|
Stacktrace: In ${component}::${stack}`;
|
||||||
@@ -96,9 +93,12 @@ export default function Migrate() {
|
|||||||
});
|
});
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
await sleep(1);
|
await sleep(1);
|
||||||
const { error } = await BackupService.run(false, "local");
|
const { error, report } = await BackupService.run(false, "local");
|
||||||
if (error) {
|
if (error) {
|
||||||
ToastManager.error(error, "Backup failed");
|
ToastManager.error(error, "Backup failed");
|
||||||
|
if (report) {
|
||||||
|
reportError(error);
|
||||||
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
import { Notebook, VirtualizedGrouping } from "@notesnook/core";
|
import { Notebook, VirtualizedGrouping } from "@notesnook/core";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React, { useCallback } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import { Text, View } from "react-native";
|
import { Text, View } from "react-native";
|
||||||
import { FlatList } from "react-native-actions-sheet";
|
import { FlatList } from "react-native-actions-sheet";
|
||||||
import create from "zustand";
|
import create from "zustand";
|
||||||
@@ -72,6 +72,20 @@ export const MoveNotebookSheet = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [notebooks] = useNotebooks();
|
const [notebooks] = useNotebooks();
|
||||||
const { colors } = useThemeColors();
|
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(
|
const renderItem = useCallback(
|
||||||
({ index }: { index: number }) => {
|
({ index }: { index: number }) => {
|
||||||
return (
|
return (
|
||||||
@@ -93,6 +107,7 @@ export const MoveNotebookSheet = ({
|
|||||||
: selectedNotebooks[0].title
|
: selectedNotebooks[0].title
|
||||||
} to ${selectedNotebook.title}?`,
|
} to ${selectedNotebook.title}?`,
|
||||||
positiveText: "Move",
|
positiveText: "Move",
|
||||||
|
context: "move-notebook",
|
||||||
positivePress: async () => {
|
positivePress: async () => {
|
||||||
for (const notebook of selectedNotebooks) {
|
for (const notebook of selectedNotebooks) {
|
||||||
const parent = await getParentNotebookId(notebook.id);
|
const parent = await getParentNotebookId(notebook.id);
|
||||||
@@ -157,36 +172,38 @@ export const MoveNotebookSheet = ({
|
|||||||
paddingHorizontal: 12
|
paddingHorizontal: 12
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
{moveToTop ? (
|
||||||
title="Move to top"
|
<Button
|
||||||
style={{
|
title="Move to top"
|
||||||
alignSelf: "flex-start",
|
style={{
|
||||||
width: "100%",
|
alignSelf: "flex-start",
|
||||||
justifyContent: "space-between"
|
width: "100%",
|
||||||
}}
|
justifyContent: "space-between"
|
||||||
icon="arrow-up-bold"
|
}}
|
||||||
iconPosition="right"
|
icon="arrow-up-bold"
|
||||||
type="secondaryAccented"
|
iconPosition="right"
|
||||||
onPress={async () => {
|
type="secondaryAccented"
|
||||||
for (const notebook of selectedNotebooks) {
|
onPress={async () => {
|
||||||
const parent = await getParentNotebookId(notebook.id);
|
for (const notebook of selectedNotebooks) {
|
||||||
const root = await findRootNotebookId(notebook.id);
|
const parent = await getParentNotebookId(notebook.id);
|
||||||
if (root !== notebook.id) {
|
const root = await findRootNotebookId(notebook.id);
|
||||||
await db.relations.unlink(
|
if (root !== notebook.id) {
|
||||||
{
|
await db.relations.unlink(
|
||||||
type: "notebook",
|
{
|
||||||
id: parent
|
type: "notebook",
|
||||||
},
|
id: parent
|
||||||
notebook
|
},
|
||||||
);
|
notebook
|
||||||
eSendEvent(eOnNotebookUpdated, parent);
|
);
|
||||||
eSendEvent(eOnNotebookUpdated, notebook.id);
|
eSendEvent(eOnNotebookUpdated, parent);
|
||||||
|
eSendEvent(eOnNotebookUpdated, notebook.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
useNotebookStore.getState().refresh();
|
||||||
useNotebookStore.getState().refresh();
|
close?.();
|
||||||
close?.();
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
ListEmptyComponent={
|
ListEmptyComponent={
|
||||||
|
|||||||
@@ -19,13 +19,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
import { Notebook, VirtualizedGrouping } from "@notesnook/core";
|
import { Notebook, VirtualizedGrouping } from "@notesnook/core";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
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 ActionSheet, { ActionSheetRef } from "react-native-actions-sheet";
|
||||||
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
|
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
|
||||||
import Config from "react-native-config";
|
import Config from "react-native-config";
|
||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import create from "zustand";
|
import create from "zustand";
|
||||||
import { notesnook } from "../../../../e2e/test.ids";
|
import { notesnook } from "../../../../e2e/test.ids";
|
||||||
|
import { db } from "../../../common/database";
|
||||||
import { MMKV } from "../../../common/database/mmkv";
|
import { MMKV } from "../../../common/database/mmkv";
|
||||||
import { useNotebook } from "../../../hooks/use-notebook";
|
import { useNotebook } from "../../../hooks/use-notebook";
|
||||||
import NotebookScreen from "../../../screens/notebook";
|
import NotebookScreen from "../../../screens/notebook";
|
||||||
@@ -43,9 +49,8 @@ import { IconButton } from "../../ui/icon-button";
|
|||||||
import { Pressable } from "../../ui/pressable";
|
import { Pressable } from "../../ui/pressable";
|
||||||
import Paragraph from "../../ui/typography/paragraph";
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
import { AddNotebookSheet } from "../add-notebook";
|
import { AddNotebookSheet } from "../add-notebook";
|
||||||
import Sort from "../sort";
|
|
||||||
import { MoveNotebookSheet } from "../move-notebook";
|
import { MoveNotebookSheet } from "../move-notebook";
|
||||||
import { db } from "../../../common/database";
|
import Sort from "../sort";
|
||||||
|
|
||||||
const useItemSelectionStore = createItemSelectionStore(true, false);
|
const useItemSelectionStore = createItemSelectionStore(true, false);
|
||||||
|
|
||||||
@@ -63,7 +68,8 @@ class NotebookSheetConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static get(item: ConfigItem) {
|
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) {
|
static set(item: ConfigItem, index = 0) {
|
||||||
@@ -126,11 +132,6 @@ export const NotebookSheet = () => {
|
|||||||
if (!focusedRouteId) return;
|
if (!focusedRouteId) return;
|
||||||
const nextRoot = await findRootNotebookId(focusedRouteId);
|
const nextRoot = await findRootNotebookId(focusedRouteId);
|
||||||
if (nextRoot !== currentItem.current) {
|
if (nextRoot !== currentItem.current) {
|
||||||
console.log(
|
|
||||||
"NotebookSheet.useEffect.canShow",
|
|
||||||
"Root changed to",
|
|
||||||
nextRoot
|
|
||||||
);
|
|
||||||
useItemSelectionStore.setState({
|
useItemSelectionStore.setState({
|
||||||
enabled: false,
|
enabled: false,
|
||||||
selection: {}
|
selection: {}
|
||||||
@@ -145,11 +146,8 @@ export const NotebookSheet = () => {
|
|||||||
if (ref.current?.isOpen()) {
|
if (ref.current?.isOpen()) {
|
||||||
ref.current?.snapToIndex(snapPoint);
|
ref.current?.snapToIndex(snapPoint);
|
||||||
} else {
|
} else {
|
||||||
setTimeout(() => {
|
ref.current?.show(snapPoint);
|
||||||
ref.current?.show(snapPoint);
|
|
||||||
}, 150);
|
|
||||||
}
|
}
|
||||||
console.log("NotebookSheet.useEffect.didShow", focusedRouteId);
|
|
||||||
setRoot(nextRoot);
|
setRoot(nextRoot);
|
||||||
onRequestUpdate();
|
onRequestUpdate();
|
||||||
});
|
});
|
||||||
@@ -162,7 +160,7 @@ export const NotebookSheet = () => {
|
|||||||
ref.current?.hide();
|
ref.current?.hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [canShow, onRequestUpdate, focusedRouteId]);
|
}, [canShow, focusedRouteId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ActionSheet
|
<ActionSheet
|
||||||
@@ -198,7 +196,11 @@ export const NotebookSheet = () => {
|
|||||||
backgroundColor: colors.secondary.background
|
backgroundColor: colors.secondary.background
|
||||||
}}
|
}}
|
||||||
keyboardHandlerEnabled={false}
|
keyboardHandlerEnabled={false}
|
||||||
snapPoints={Config.isTesting === "true" ? [100] : [20, 100]}
|
snapPoints={
|
||||||
|
Config.isTesting === "true"
|
||||||
|
? [100]
|
||||||
|
: [Platform.OS === "android" ? 15 : 10, 100]
|
||||||
|
}
|
||||||
initialSnapIndex={1}
|
initialSnapIndex={1}
|
||||||
backgroundInteractionEnabled
|
backgroundInteractionEnabled
|
||||||
gestureEnabled
|
gestureEnabled
|
||||||
@@ -237,7 +239,7 @@ export const NotebookSheet = () => {
|
|||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
name="notebook-plus"
|
name="notebook-plus"
|
||||||
color={colors.primary.accent}
|
color={colors.primary.icon}
|
||||||
size={SIZE.xxl}
|
size={SIZE.xxl}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -18,7 +18,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React, { RefObject, useRef, useState } from "react";
|
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 { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
|
||||||
import DateTimePickerModal from "react-native-modal-datetime-picker";
|
import DateTimePickerModal from "react-native-modal-datetime-picker";
|
||||||
import {
|
import {
|
||||||
@@ -364,7 +369,7 @@ export default function ReminderSheet({
|
|||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<ScrollView showsHorizontalScrollIndicator={false} horizontal>
|
<RNScrollView showsHorizontalScrollIndicator={false} horizontal>
|
||||||
{recurringMode === RecurringModes.Daily ||
|
{recurringMode === RecurringModes.Daily ||
|
||||||
recurringMode === RecurringModes.Year
|
recurringMode === RecurringModes.Year
|
||||||
? null
|
? null
|
||||||
@@ -425,7 +430,7 @@ export default function ReminderSheet({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ScrollView>
|
</RNScrollView>
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|||||||
@@ -285,9 +285,9 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
|
|||||||
|
|
||||||
const backupFiles = await RNFetchBlob.fs.ls(zipOutputFolder);
|
const backupFiles = await RNFetchBlob.fs.ls(zipOutputFolder);
|
||||||
|
|
||||||
if (backupFiles.findIndex((file) => file === ".nnbackup") === -1) {
|
// if (backupFiles.findIndex((file) => file === ".nnbackup") === -1) {
|
||||||
throw new Error("Backup file is invalid");
|
// throw new Error("Backup file is invalid");
|
||||||
}
|
// }
|
||||||
|
|
||||||
await db.transaction(async () => {
|
await db.transaction(async () => {
|
||||||
let password;
|
let password;
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ const Sort = ({ type, screen }) => {
|
|||||||
const setOrderBy = async () => {
|
const setOrderBy = async () => {
|
||||||
let _groupOptions = {
|
let _groupOptions = {
|
||||||
...groupOptions,
|
...groupOptions,
|
||||||
sortDirection: groupOptions.sortDirection === "asc" ? "desc" : "asc"
|
sortDirection: groupOptions?.sortDirection === "asc" ? "desc" : "asc"
|
||||||
};
|
};
|
||||||
if (type === "topics") {
|
if (type === "topics") {
|
||||||
_groupOptions.groupBy = "none";
|
_groupOptions.groupBy = "none";
|
||||||
@@ -85,22 +85,22 @@ const Sort = ({ type, screen }) => {
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
title={
|
title={
|
||||||
groupOptions.sortDirection === "asc"
|
groupOptions?.sortDirection === "asc"
|
||||||
? groupOptions.groupBy === "abc" ||
|
? groupOptions?.groupBy === "abc" ||
|
||||||
groupOptions.sortBy === "title"
|
groupOptions?.sortBy === "title"
|
||||||
? "A - Z"
|
? "A - Z"
|
||||||
: groupOptions.sortBy === "dueDate"
|
: groupOptions?.sortBy === "dueDate"
|
||||||
? "Earliest first"
|
? "Earliest first"
|
||||||
: "Old - New"
|
: "Old - New"
|
||||||
: groupOptions.groupBy === "abc" ||
|
: groupOptions?.groupBy === "abc" ||
|
||||||
groupOptions.sortBy === "title"
|
groupOptions?.sortBy === "title"
|
||||||
? "Z - A"
|
? "Z - A"
|
||||||
: groupOptions.sortBy === "dueDate"
|
: groupOptions?.sortBy === "dueDate"
|
||||||
? "Latest first"
|
? "Latest first"
|
||||||
: "New - Old"
|
: "New - Old"
|
||||||
}
|
}
|
||||||
icon={
|
icon={
|
||||||
groupOptions.sortDirection === "asc"
|
groupOptions?.sortDirection === "asc"
|
||||||
? "sort-ascending"
|
? "sort-ascending"
|
||||||
: "sort-descending"
|
: "sort-descending"
|
||||||
}
|
}
|
||||||
@@ -134,7 +134,7 @@ const Sort = ({ type, screen }) => {
|
|||||||
alignItems: "center"
|
alignItems: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{groupOptions.groupBy === "abc" ? (
|
{groupOptions?.groupBy === "abc" ? (
|
||||||
<Button
|
<Button
|
||||||
type="secondary"
|
type="secondary"
|
||||||
title="Title"
|
title="Title"
|
||||||
@@ -154,18 +154,18 @@ const Sort = ({ type, screen }) => {
|
|||||||
item === "dateEdited") ? null : (
|
item === "dateEdited") ? null : (
|
||||||
<Button
|
<Button
|
||||||
key={item}
|
key={item}
|
||||||
type={groupOptions.sortBy === item ? "selected" : "plain"}
|
type={groupOptions?.sortBy === item ? "selected" : "plain"}
|
||||||
title={SORT[item]}
|
title={SORT[item]}
|
||||||
height={40}
|
height={40}
|
||||||
iconPosition="left"
|
iconPosition="left"
|
||||||
icon={groupOptions.sortBy === item ? "check" : null}
|
icon={groupOptions?.sortBy === item ? "check" : null}
|
||||||
style={{
|
style={{
|
||||||
marginRight: 10,
|
marginRight: 10,
|
||||||
paddingHorizontal: 8
|
paddingHorizontal: 8
|
||||||
}}
|
}}
|
||||||
buttonType={{
|
buttonType={{
|
||||||
text:
|
text:
|
||||||
groupOptions.sortBy === item
|
groupOptions?.sortBy === item
|
||||||
? colors.primary.accent
|
? colors.primary.accent
|
||||||
: colors.secondary.paragraph
|
: colors.secondary.paragraph
|
||||||
}}
|
}}
|
||||||
@@ -213,11 +213,11 @@ const Sort = ({ type, screen }) => {
|
|||||||
key={item}
|
key={item}
|
||||||
testID={"btn-" + item}
|
testID={"btn-" + item}
|
||||||
type={
|
type={
|
||||||
groupOptions.groupBy === GROUP[item] ? "selected" : "plain"
|
groupOptions?.groupBy === GROUP[item] ? "selected" : "plain"
|
||||||
}
|
}
|
||||||
buttonType={{
|
buttonType={{
|
||||||
text:
|
text:
|
||||||
groupOptions.groupBy === GROUP[item]
|
groupOptions?.groupBy === GROUP[item]
|
||||||
? colors.primary.accent
|
? colors.primary.accent
|
||||||
: colors.secondary.paragraph
|
: colors.secondary.paragraph
|
||||||
}}
|
}}
|
||||||
@@ -229,13 +229,13 @@ const Sort = ({ type, screen }) => {
|
|||||||
|
|
||||||
if (item === "abc") {
|
if (item === "abc") {
|
||||||
_groupOptions.sortBy = "title";
|
_groupOptions.sortBy = "title";
|
||||||
_groupOptions.sortDirection = "asc";
|
_groupOptions.sortDirection = "desc";
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGroupOptions(_groupOptions);
|
updateGroupOptions(_groupOptions);
|
||||||
}}
|
}}
|
||||||
height={40}
|
height={40}
|
||||||
icon={groupOptions.groupBy === GROUP[item] ? "check" : null}
|
icon={groupOptions?.groupBy === GROUP[item] ? "check" : null}
|
||||||
title={
|
title={
|
||||||
item.slice(0, 1).toUpperCase() + item.slice(1, item.length)
|
item.slice(0, 1).toUpperCase() + item.slice(1, item.length)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,15 +21,11 @@ import { useThemeColors } from "@notesnook/theme";
|
|||||||
import React, { useCallback } from "react";
|
import React, { useCallback } from "react";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import { DraxProvider, DraxScrollView } from "react-native-drax";
|
import { DraxProvider, DraxScrollView } from "react-native-drax";
|
||||||
import { notesnook } from "../../../e2e/test.ids";
|
|
||||||
import { db } from "../../common/database";
|
import { db } from "../../common/database";
|
||||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||||
import { DDS } from "../../services/device-detection";
|
|
||||||
import { eSendEvent } from "../../services/event-manager";
|
import { eSendEvent } from "../../services/event-manager";
|
||||||
import Navigation from "../../services/navigation";
|
|
||||||
import { useMenuStore } from "../../stores/use-menu-store";
|
import { useMenuStore } from "../../stores/use-menu-store";
|
||||||
import { useSettingStore } from "../../stores/use-setting-store";
|
import { useSettingStore } from "../../stores/use-setting-store";
|
||||||
import { useThemeStore } from "../../stores/use-theme-store";
|
|
||||||
import { useUserStore } from "../../stores/use-user-store";
|
import { useUserStore } from "../../stores/use-user-store";
|
||||||
import { SUBSCRIPTION_STATUS } from "../../utils/constants";
|
import { SUBSCRIPTION_STATUS } from "../../utils/constants";
|
||||||
import { eOpenPremiumDialog } from "../../utils/events";
|
import { eOpenPremiumDialog } from "../../utils/events";
|
||||||
@@ -59,26 +55,6 @@ export const SideMenu = React.memo(
|
|||||||
const introCompleted = useSettingStore(
|
const introCompleted = useSettingStore(
|
||||||
(state) => state.settings.introCompleted
|
(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 = {
|
const pro = {
|
||||||
name: "Notesnook Pro",
|
name: "Notesnook Pro",
|
||||||
@@ -121,7 +97,7 @@ export const SideMenu = React.memo(
|
|||||||
<PinnedSection />
|
<PinnedSection />
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
[]
|
[order, hiddensItems]
|
||||||
);
|
);
|
||||||
|
|
||||||
return !isAppLoading && introCompleted ? (
|
return !isAppLoading && introCompleted ? (
|
||||||
|
|||||||
@@ -20,29 +20,25 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import { useNetInfo } from "@react-native-community/netinfo";
|
import { useNetInfo } from "@react-native-community/netinfo";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
import { ActivityIndicator, Image, Platform, View } from "react-native";
|
||||||
ActivityIndicator,
|
|
||||||
Image,
|
|
||||||
Platform,
|
|
||||||
TouchableOpacity,
|
|
||||||
View
|
|
||||||
} from "react-native";
|
|
||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||||
import useSyncProgress from "../../hooks/use-sync-progress";
|
import useSyncProgress from "../../hooks/use-sync-progress";
|
||||||
import { eSendEvent } from "../../services/event-manager";
|
import { eSendEvent } from "../../services/event-manager";
|
||||||
|
import Navigation from "../../services/navigation";
|
||||||
import Sync from "../../services/sync";
|
import Sync from "../../services/sync";
|
||||||
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { SyncStatus, useUserStore } from "../../stores/use-user-store";
|
import { SyncStatus, useUserStore } from "../../stores/use-user-store";
|
||||||
import { eOpenLoginDialog } from "../../utils/events";
|
import { eOpenLoginDialog } from "../../utils/events";
|
||||||
import { tabBarRef } from "../../utils/global-refs";
|
import { tabBarRef } from "../../utils/global-refs";
|
||||||
import { SIZE } from "../../utils/size";
|
import { SIZE } from "../../utils/size";
|
||||||
|
import { IconButton } from "../ui/icon-button";
|
||||||
import { Pressable } from "../ui/pressable";
|
import { Pressable } from "../ui/pressable";
|
||||||
import { TimeSince } from "../ui/time-since";
|
import { TimeSince } from "../ui/time-since";
|
||||||
import Paragraph from "../ui/typography/paragraph";
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
import Navigation from "../../services/navigation";
|
|
||||||
|
|
||||||
export const UserStatus = () => {
|
export const UserStatus = () => {
|
||||||
const { colors } = useThemeColors();
|
const { colors, isDark } = useThemeColors();
|
||||||
const user = useUserStore((state) => state.user);
|
const user = useUserStore((state) => state.user);
|
||||||
const syncing = useUserStore((state) => state.syncing);
|
const syncing = useUserStore((state) => state.syncing);
|
||||||
const lastSyncStatus = useUserStore((state) => state.lastSyncStatus);
|
const lastSyncStatus = useUserStore((state) => state.lastSyncStatus);
|
||||||
@@ -97,7 +93,16 @@ export const UserStatus = () => {
|
|||||||
borderRadius: 100
|
borderRadius: 100
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : (
|
||||||
|
<Icon
|
||||||
|
name="cog-outline"
|
||||||
|
size={SIZE.lg - 2}
|
||||||
|
color={colors.secondary.icon}
|
||||||
|
style={{
|
||||||
|
paddingLeft: 8
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
@@ -110,14 +115,8 @@ export const UserStatus = () => {
|
|||||||
size={SIZE.sm}
|
size={SIZE.sm}
|
||||||
color={colors.primary.heading}
|
color={colors.primary.heading}
|
||||||
>
|
>
|
||||||
{!user
|
{!user || !userProfile?.fullName
|
||||||
? "Login to sync your notes."
|
? "Settings"
|
||||||
: lastSyncStatus === SyncStatus.Failed
|
|
||||||
? "Sync failed, tap to retry"
|
|
||||||
: syncing
|
|
||||||
? `Syncing ${progress ? `(${progress.current})` : ""}`
|
|
||||||
: !userProfile?.fullName
|
|
||||||
? "Tap to sync"
|
|
||||||
: userProfile.fullName}
|
: userProfile.fullName}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
@@ -132,16 +131,20 @@ export const UserStatus = () => {
|
|||||||
"Not logged in"
|
"Not logged in"
|
||||||
) : lastSynced && lastSynced !== "Never" ? (
|
) : lastSynced && lastSynced !== "Never" ? (
|
||||||
<>
|
<>
|
||||||
{lastSyncStatus === SyncStatus.Failed
|
{syncing
|
||||||
|
? `Syncing ${progress ? `(${progress.current})` : ""}`
|
||||||
|
: lastSyncStatus === SyncStatus.Failed
|
||||||
? "Sync failed"
|
? "Sync failed"
|
||||||
: "Synced"}{" "}
|
: "Synced"}{" "}
|
||||||
<TimeSince
|
{!syncing ? (
|
||||||
style={{
|
<TimeSince
|
||||||
fontSize: SIZE.xs,
|
style={{
|
||||||
color: colors.secondary.paragraph
|
fontSize: SIZE.xs,
|
||||||
}}
|
color: colors.secondary.paragraph
|
||||||
time={lastSynced}
|
}}
|
||||||
/>
|
time={lastSynced}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
{isOffline ? " (offline)" : ""}
|
{isOffline ? " (offline)" : ""}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@@ -149,7 +152,7 @@ export const UserStatus = () => {
|
|||||||
)}{" "}
|
)}{" "}
|
||||||
<Icon
|
<Icon
|
||||||
name="checkbox-blank-circle"
|
name="checkbox-blank-circle"
|
||||||
size={11}
|
size={9}
|
||||||
allowFontScaling
|
allowFontScaling
|
||||||
color={
|
color={
|
||||||
!user || lastSyncStatus === SyncStatus.Failed
|
!user || lastSyncStatus === SyncStatus.Failed
|
||||||
@@ -162,56 +165,80 @@ export const UserStatus = () => {
|
|||||||
</Paragraph>
|
</Paragraph>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Pressable
|
<View
|
||||||
style={{
|
style={{
|
||||||
borderRadius: 100,
|
flexDirection: "row",
|
||||||
width: 40,
|
gap: 0
|
||||||
height: 40
|
|
||||||
}}
|
|
||||||
hitSlop={{
|
|
||||||
top: 10,
|
|
||||||
bottom: 10,
|
|
||||||
left: 20
|
|
||||||
}}
|
|
||||||
onPress={() => {
|
|
||||||
if (user) {
|
|
||||||
Sync.run();
|
|
||||||
} else {
|
|
||||||
tabBarRef.current?.closeDrawer();
|
|
||||||
eSendEvent(eOpenLoginDialog);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{user ? (
|
<IconButton
|
||||||
syncing ? (
|
hitSlop={{
|
||||||
<ActivityIndicator
|
top: 10,
|
||||||
color={colors.primary.accent}
|
bottom: 10
|
||||||
size={SIZE.xl}
|
}}
|
||||||
/>
|
onPress={() => {
|
||||||
) : lastSyncStatus === SyncStatus.Failed ? (
|
useThemeStore.getState().setColorScheme();
|
||||||
<Icon
|
}}
|
||||||
color={colors.error.icon}
|
name="theme-light-dark"
|
||||||
name="sync-alert"
|
color={isDark ? colors.primary.accent : colors.primary.icon}
|
||||||
size={SIZE.lg}
|
size={SIZE.lg}
|
||||||
allowFontScaling
|
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
|
<Icon
|
||||||
allowFontScaling
|
allowFontScaling
|
||||||
color={colors.primary.accent}
|
color={colors.primary.accent}
|
||||||
name="sync"
|
|
||||||
size={SIZE.lg}
|
size={SIZE.lg}
|
||||||
|
name="login"
|
||||||
/>
|
/>
|
||||||
)
|
)}
|
||||||
) : (
|
</Pressable>
|
||||||
<Icon
|
</View>
|
||||||
allowFontScaling
|
|
||||||
color={colors.primary.accent}
|
|
||||||
size={SIZE.lg}
|
|
||||||
name="login"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Pressable>
|
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export interface TabsRef {
|
|||||||
lock: () => boolean;
|
lock: () => boolean;
|
||||||
openDrawer: (animated?: boolean) => void;
|
openDrawer: (animated?: boolean) => void;
|
||||||
closeDrawer: (animated?: boolean) => void;
|
closeDrawer: (animated?: boolean) => void;
|
||||||
page: number;
|
page: () => number;
|
||||||
setScrollEnabled: () => true;
|
setScrollEnabled: () => true;
|
||||||
isDrawerOpen: () => boolean;
|
isDrawerOpen: () => boolean;
|
||||||
node: RefObject<Animated.View>;
|
node: RefObject<Animated.View>;
|
||||||
@@ -239,7 +239,7 @@ export const FluidTabs = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
|||||||
onDrawerStateChange(false);
|
onDrawerStateChange(false);
|
||||||
isDrawerOpen.value = false;
|
isDrawerOpen.value = false;
|
||||||
},
|
},
|
||||||
page: currentTab.value,
|
page: () => currentTab.value,
|
||||||
setScrollEnabled: () => true,
|
setScrollEnabled: () => true,
|
||||||
node: node
|
node: node
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ import {
|
|||||||
DimensionValue,
|
DimensionValue,
|
||||||
TextStyle,
|
TextStyle,
|
||||||
View,
|
View,
|
||||||
ViewStyle
|
ViewStyle,
|
||||||
|
useWindowDimensions
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { useUserStore } from "../../../stores/use-user-store";
|
import { useUserStore } from "../../../stores/use-user-store";
|
||||||
@@ -92,6 +93,9 @@ export const Button = ({
|
|||||||
});
|
});
|
||||||
const textColor = buttonType?.text ? buttonType.text : text;
|
const textColor = buttonType?.text ? buttonType.text : text;
|
||||||
|
|
||||||
|
const { fontScale } = useWindowDimensions();
|
||||||
|
const growFactor = 1 + (fontScale - 1) / 10;
|
||||||
|
|
||||||
const Component = bold ? Heading : Paragraph;
|
const Component = bold ? Heading : Paragraph;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -117,8 +121,11 @@ export const Button = ({
|
|||||||
customOpacity={buttonType?.opacity}
|
customOpacity={buttonType?.opacity}
|
||||||
customAlpha={buttonType?.alpha}
|
customAlpha={buttonType?.alpha}
|
||||||
style={{
|
style={{
|
||||||
height: height,
|
height: typeof height === "number" ? height * growFactor : height,
|
||||||
width: (width as DimensionValue) || undefined,
|
width:
|
||||||
|
typeof width === "number"
|
||||||
|
? width * growFactor
|
||||||
|
: (width as DimensionValue) || undefined,
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
alignSelf: "center",
|
alignSelf: "center",
|
||||||
|
|||||||
@@ -21,11 +21,12 @@ import { VariantsWithStaticColors, useThemeColors } from "@notesnook/theme";
|
|||||||
import React, { RefObject, useCallback } from "react";
|
import React, { RefObject, useCallback } from "react";
|
||||||
import {
|
import {
|
||||||
ColorValue,
|
ColorValue,
|
||||||
|
PressableStateCallbackType,
|
||||||
Pressable as RNPressable,
|
Pressable as RNPressable,
|
||||||
PressableProps as RNPressableProps,
|
PressableProps as RNPressableProps,
|
||||||
PressableStateCallbackType,
|
|
||||||
View,
|
View,
|
||||||
ViewStyle
|
ViewStyle,
|
||||||
|
useWindowDimensions
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import {
|
import {
|
||||||
RGB_Linear_Shade,
|
RGB_Linear_Shade,
|
||||||
@@ -256,6 +257,8 @@ export const Pressable = ({
|
|||||||
? 1
|
? 1
|
||||||
: colorOpacity;
|
: colorOpacity;
|
||||||
const alpha = customAlpha ? customAlpha : isDark ? 0.03 : -0.03;
|
const alpha = customAlpha ? customAlpha : isDark ? 0.03 : -0.03;
|
||||||
|
const { fontScale } = useWindowDimensions();
|
||||||
|
const growFactor = 1 + (fontScale - 1) / 8;
|
||||||
|
|
||||||
const getStyle = useCallback(
|
const getStyle = useCallback(
|
||||||
({ pressed }: PressableStateCallbackType): ViewStyle | ViewStyle[] => [
|
({ pressed }: PressableStateCallbackType): ViewStyle | ViewStyle[] => [
|
||||||
@@ -276,7 +279,13 @@ export const Pressable = ({
|
|||||||
: borderColor || "transparent",
|
: borderColor || "transparent",
|
||||||
borderWidth: borderWidth
|
borderWidth: borderWidth
|
||||||
},
|
},
|
||||||
style
|
style,
|
||||||
|
{
|
||||||
|
height:
|
||||||
|
typeof style.height === "number"
|
||||||
|
? style.height * growFactor
|
||||||
|
: style.height
|
||||||
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
alpha,
|
alpha,
|
||||||
@@ -288,7 +297,8 @@ export const Pressable = ({
|
|||||||
borderSelectedColor,
|
borderSelectedColor,
|
||||||
borderColor,
|
borderColor,
|
||||||
borderWidth,
|
borderWidth,
|
||||||
style
|
style,
|
||||||
|
growFactor
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -19,35 +19,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
import { FeatureType } from "./components/sheets/new-feature";
|
import { FeatureType } from "./components/sheets/new-feature";
|
||||||
|
|
||||||
export const features: FeatureType[] = [
|
export const features: FeatureType[] = [];
|
||||||
{
|
|
||||||
title: "Bi-directional note linking",
|
|
||||||
body: "Now you can link notes to each other in both directions."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Tabs",
|
|
||||||
body: "Tabs allow you to have multiple notes open at the same time. You can switch between them easily."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Nested notebooks",
|
|
||||||
body: "You can now create notebooks inside other notebooks."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "At-rest encryption",
|
|
||||||
body: "Your notes are now encrypted when stored on the device."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Material You themed icon",
|
|
||||||
body: "The app now has a Material You themed icon that changes color based on the wallpaper.",
|
|
||||||
platform: "android"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "New note quick settings tile",
|
|
||||||
body: "You can now add a new note tile to quickly create notes from quick settings panel.",
|
|
||||||
platform: "android"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "And so much more...",
|
|
||||||
body: "V3 packs so much that it's hard to put everything here, check out our blog at Notesnook V3 is here, and it's packed with new features and improvements such as bi-directional note linking, better syncing, at-rest encryption, nested notebooks, editor tabs, better app lock and so much more. https://blog.notesnook.com/introducing-notesnook-v3."
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|||||||
@@ -600,20 +600,11 @@ export const useActions = ({
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const text = await convertNoteToText(item as Note, false);
|
const text = await convertNoteToText(item as Note, true);
|
||||||
if (!text) {
|
const html = (text || "").replace(/\n/g, "<br />");
|
||||||
ToastManager.error(
|
|
||||||
new Error(Errors.export("text")),
|
|
||||||
undefined,
|
|
||||||
"local"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const html = text.replace(/\n/g, "<br />");
|
|
||||||
await Notifications.displayNotification({
|
await Notifications.displayNotification({
|
||||||
title: item.title,
|
title: item.title,
|
||||||
message: (item as Note).headline || text,
|
message: (item as Note).headline || text || "",
|
||||||
subtitle: "",
|
subtitle: "",
|
||||||
bigText: html,
|
bigText: html,
|
||||||
ongoing: true,
|
ongoing: true,
|
||||||
@@ -684,15 +675,11 @@ export const useActions = ({
|
|||||||
} else {
|
} else {
|
||||||
processingId.current = "shareNote";
|
processingId.current = "shareNote";
|
||||||
const convertedText = await convertNoteToText(item);
|
const convertedText = await convertNoteToText(item);
|
||||||
if (!convertedText) {
|
|
||||||
ToastManager.error(new Error(Errors.export("text")));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
processingId.current = undefined;
|
processingId.current = undefined;
|
||||||
Share.open({
|
Share.open({
|
||||||
title: "Share note to",
|
title: "Share note to",
|
||||||
failOnCancel: false,
|
failOnCancel: false,
|
||||||
message: convertedText
|
message: convertedText || ""
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -772,11 +759,7 @@ export const useActions = ({
|
|||||||
} else {
|
} else {
|
||||||
processingId.current = "copyContent";
|
processingId.current = "copyContent";
|
||||||
const text = await convertNoteToText(item as Note, true);
|
const text = await convertNoteToText(item as Note, true);
|
||||||
if (!text) {
|
Clipboard.setString(text || "");
|
||||||
ToastManager.error(new Error(Errors.export("text")));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Clipboard.setString(text);
|
|
||||||
processingId.current = undefined;
|
processingId.current = undefined;
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
heading: "Note copied to clipboard",
|
heading: "Note copied to clipboard",
|
||||||
|
|||||||
@@ -85,8 +85,8 @@ import { SyncStatus, useUserStore } from "../stores/use-user-store";
|
|||||||
import { updateStatusBarColor } from "../utils/colors";
|
import { updateStatusBarColor } from "../utils/colors";
|
||||||
import { BETA } from "../utils/constants";
|
import { BETA } from "../utils/constants";
|
||||||
import {
|
import {
|
||||||
eClearEditor,
|
|
||||||
eCloseSheet,
|
eCloseSheet,
|
||||||
|
eEditorReset,
|
||||||
eLoginSessionExpired,
|
eLoginSessionExpired,
|
||||||
eOnLoadNote,
|
eOnLoadNote,
|
||||||
eOpenAnnouncementDialog,
|
eOpenAnnouncementDialog,
|
||||||
@@ -97,6 +97,7 @@ import {
|
|||||||
import { getGithubVersion } from "../utils/github-version";
|
import { getGithubVersion } from "../utils/github-version";
|
||||||
import { tabBarRef } from "../utils/global-refs";
|
import { tabBarRef } from "../utils/global-refs";
|
||||||
import { sleep } from "../utils/time";
|
import { sleep } from "../utils/time";
|
||||||
|
import { initializeLogger } from "../common/database/logger";
|
||||||
|
|
||||||
const onCheckSyncStatus = async (type: SyncStatusEvent) => {
|
const onCheckSyncStatus = async (type: SyncStatusEvent) => {
|
||||||
const { disableSync, disableAutoSync } = SettingsService.get();
|
const { disableSync, disableAutoSync } = SettingsService.get();
|
||||||
@@ -201,11 +202,13 @@ const onRequestPartialSync = async (
|
|||||||
`onRequestPartialSync full:${full}, force:${force}, lastSyncTime:${lastSyncTime}`
|
`onRequestPartialSync full:${full}, force:${force}, lastSyncTime:${lastSyncTime}`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (full || force) {
|
await Sync.run(
|
||||||
await Sync.run("global", force, full, undefined, lastSyncTime);
|
"global",
|
||||||
} else {
|
force,
|
||||||
await Sync.run("global", false, false, undefined, lastSyncTime);
|
full ? "full" : "send",
|
||||||
}
|
undefined,
|
||||||
|
lastSyncTime
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onLogout = async (reason: string) => {
|
const onLogout = async (reason: string) => {
|
||||||
@@ -234,7 +237,7 @@ async function checkForShareExtensionLaunchedInBackground() {
|
|||||||
if (notesAddedFromIntent || shareExtensionOpened) {
|
if (notesAddedFromIntent || shareExtensionOpened) {
|
||||||
const id = useTabStore.getState().getCurrentNoteId();
|
const id = useTabStore.getState().getCurrentNoteId();
|
||||||
const note = id && (await db.notes.note(id));
|
const note = id && (await db.notes.note(id));
|
||||||
eSendEvent("webview_reset");
|
eSendEvent(eEditorReset);
|
||||||
if (note) setTimeout(() => eSendEvent("loadingNote", note), 1);
|
if (note) setTimeout(() => eSendEvent("loadingNote", note), 1);
|
||||||
MMKV.removeItem("shareExtensionOpened");
|
MMKV.removeItem("shareExtensionOpened");
|
||||||
}
|
}
|
||||||
@@ -278,8 +281,108 @@ const onSubscriptionError = async (error: RNIap.PurchaseError) => {
|
|||||||
|
|
||||||
const SodiumEventEmitter = new NativeEventEmitter(NativeModules.Sodium);
|
const SodiumEventEmitter = new NativeEventEmitter(NativeModules.Sodium);
|
||||||
|
|
||||||
|
const doAppLoadActions = async () => {
|
||||||
|
if (SettingsService.get().sessionExpired) {
|
||||||
|
eSendEvent(eLoginSessionExpired);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
notifee.setBadgeCount(0);
|
||||||
|
|
||||||
|
if (!(await db.user.getUser())) {
|
||||||
|
setLoginMessage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await useMessageStore.getState().setAnnouncement();
|
||||||
|
if (NewFeature.present()) return;
|
||||||
|
if (await checkAppUpdateAvailable()) return;
|
||||||
|
if (await checkForRateAppRequest()) return;
|
||||||
|
if (await PremiumService.getRemainingTrialDaysStatus()) return;
|
||||||
|
if (SettingsService.get().introCompleted) {
|
||||||
|
useMessageStore.subscribe((state) => {
|
||||||
|
const dialogs = state.dialogs;
|
||||||
|
if (dialogs.length > 0) {
|
||||||
|
eSendEvent(eOpenAnnouncementDialog, dialogs[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkAppUpdateAvailable = async () => {
|
||||||
|
if (__DEV__ || Config.isTesting === "true" || Config.FDROID_BUILD || BETA)
|
||||||
|
return;
|
||||||
|
try {
|
||||||
|
const version =
|
||||||
|
Config.GITHUB_RELEASE === "true"
|
||||||
|
? await getGithubVersion()
|
||||||
|
: await checkVersion();
|
||||||
|
if (!version || !version?.needsUpdate) return false;
|
||||||
|
|
||||||
|
setUpdateAvailableMessage(version);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkForRateAppRequest = async () => {
|
||||||
|
const rateApp = SettingsService.get().rateApp as number;
|
||||||
|
if (
|
||||||
|
rateApp &&
|
||||||
|
rateApp < Date.now() &&
|
||||||
|
!useMessageStore.getState().message?.visible
|
||||||
|
) {
|
||||||
|
setRateAppMessage();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const IsDatabaseMigrationRequired = () => {
|
||||||
|
if (!db.migrations.required() || useUserStore.getState().appLocked)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
presentSheet({
|
||||||
|
component: <Migrate />,
|
||||||
|
onClose: () => {
|
||||||
|
if (!db.migrations.required()) {
|
||||||
|
initializeDatabase();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
disableClosing: true
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initializeDatabase = async (password?: string) => {
|
||||||
|
if (useUserStore.getState().appLocked) return;
|
||||||
|
if (!db.isInitialized) {
|
||||||
|
RNBootSplash.hide({ fade: true });
|
||||||
|
DatabaseLogger.info("Initializing database");
|
||||||
|
try {
|
||||||
|
await setupDatabase(password);
|
||||||
|
await db.init();
|
||||||
|
} catch (e) {
|
||||||
|
DatabaseLogger.error(e as Error);
|
||||||
|
ToastManager.error(e as Error, "Error initializing database", "global");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsDatabaseMigrationRequired()) return;
|
||||||
|
|
||||||
|
if (db.isInitialized) {
|
||||||
|
Notifications.setupReminders(true);
|
||||||
|
if (SettingsService.get().notifNotes) {
|
||||||
|
Notifications.pinQuickNote(false);
|
||||||
|
}
|
||||||
|
useSettingStore.getState().setAppLoading(false);
|
||||||
|
DatabaseLogger.info("Database initialized");
|
||||||
|
}
|
||||||
|
Walkthrough.init();
|
||||||
|
};
|
||||||
|
|
||||||
export const useAppEvents = () => {
|
export const useAppEvents = () => {
|
||||||
const loading = useSettingStore((state) => state.isAppLoading);
|
const isAppLoading = useSettingStore((state) => state.isAppLoading);
|
||||||
const [setLastSynced, setUser, appLocked, syncing] = useUserStore((state) => [
|
const [setLastSynced, setUser, appLocked, syncing] = useUserStore((state) => [
|
||||||
state.setLastSynced,
|
state.setLastSynced,
|
||||||
state.setUser,
|
state.setUser,
|
||||||
@@ -310,7 +413,7 @@ export const useAppEvents = () => {
|
|||||||
}, [setLastSynced]);
|
}, [setLastSynced]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loading) return;
|
if (isAppLoading) return;
|
||||||
|
|
||||||
let subscriptions: EventManagerSubscription[] = [];
|
let subscriptions: EventManagerSubscription[] = [];
|
||||||
const eventManager = db.eventManager;
|
const eventManager = db.eventManager;
|
||||||
@@ -325,7 +428,7 @@ export const useAppEvents = () => {
|
|||||||
return () => {
|
return () => {
|
||||||
subscriptions.forEach((sub) => sub?.unsubscribe?.());
|
subscriptions.forEach((sub) => sub?.unsubscribe?.());
|
||||||
};
|
};
|
||||||
}, [loading, onSyncComplete]);
|
}, [isAppLoading, onSyncComplete]);
|
||||||
|
|
||||||
const subscribeToPurchaseListeners = useCallback(async () => {
|
const subscribeToPurchaseListeners = useCallback(async () => {
|
||||||
if (Platform.OS === "android") {
|
if (Platform.OS === "android") {
|
||||||
@@ -531,13 +634,15 @@ export const useAppEvents = () => {
|
|||||||
}
|
}
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
globalThis["IS_SHARE_EXTENSION"] = false;
|
globalThis["IS_SHARE_EXTENSION"] = false;
|
||||||
} else {
|
|
||||||
const id = useTabStore.getState().getCurrentNoteId();
|
if (
|
||||||
const note = id ? await db.notes.note(id) : undefined;
|
SettingsService.getBackgroundEnterTime() + 60 * 1000 * 10 <
|
||||||
const locked = note && (await db.vaults.itemExists(note));
|
Date.now()
|
||||||
if (locked && SettingsService.canLockAppInBackground()) {
|
) {
|
||||||
eSendEvent(eClearEditor);
|
// Reset the editor if the app has been in background for more than 10 minutes.
|
||||||
|
eSendEvent(eEditorReset);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
await saveEditorState();
|
await saveEditorState();
|
||||||
if (
|
if (
|
||||||
SettingsService.canLockAppInBackground() &&
|
SettingsService.canLockAppInBackground() &&
|
||||||
@@ -568,7 +673,7 @@ export const useAppEvents = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
let sub: NativeEventSubscription;
|
let sub: NativeEventSubscription;
|
||||||
if (!loading && !appLocked) {
|
if (!isAppLoading && !appLocked) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
sub = AppState.addEventListener("change", onAppStateChanged);
|
sub = AppState.addEventListener("change", onAppStateChanged);
|
||||||
if (
|
if (
|
||||||
@@ -590,7 +695,7 @@ export const useAppEvents = () => {
|
|||||||
sub?.remove();
|
sub?.remove();
|
||||||
unsubscribePurchaseListeners();
|
unsubscribePurchaseListeners();
|
||||||
};
|
};
|
||||||
}, [loading, appLocked, checkAutoBackup]);
|
}, [isAppLoading, appLocked, checkAutoBackup]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!appLocked && !syncing && refValues.current.backupDidWait) {
|
if (!appLocked && !syncing && refValues.current.backupDidWait) {
|
||||||
@@ -639,127 +744,22 @@ export const useAppEvents = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!loading) {
|
if (!isAppLoading) {
|
||||||
onUserUpdated();
|
onUserUpdated();
|
||||||
doAppLoadActions();
|
doAppLoadActions();
|
||||||
}
|
}
|
||||||
}, [loading, onUserUpdated]);
|
}, [isAppLoading, onUserUpdated]);
|
||||||
|
|
||||||
const initializeDatabase = useCallback(async (password?: string) => {
|
|
||||||
const IsDatabaseMigrationRequired = () => {
|
|
||||||
if (!db.migrations.required() || useUserStore.getState().appLocked)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
presentSheet({
|
|
||||||
component: <Migrate />,
|
|
||||||
onClose: () => {
|
|
||||||
if (!db.migrations.required()) {
|
|
||||||
initializeDatabase();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
disableClosing: true
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (useUserStore.getState().appLocked) return;
|
|
||||||
if (!db.isInitialized) {
|
|
||||||
RNBootSplash.hide({ fade: true });
|
|
||||||
DatabaseLogger.info("Initializing database");
|
|
||||||
try {
|
|
||||||
await setupDatabase(password);
|
|
||||||
await db.init();
|
|
||||||
} catch (e) {
|
|
||||||
DatabaseLogger.error(e as Error);
|
|
||||||
ToastManager.error(e as Error, "Error initializing database", "global");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsDatabaseMigrationRequired()) return;
|
|
||||||
|
|
||||||
if (db.isInitialized) {
|
|
||||||
Notifications.setupReminders(true);
|
|
||||||
if (SettingsService.get().notifNotes) {
|
|
||||||
Notifications.pinQuickNote(false);
|
|
||||||
}
|
|
||||||
useSettingStore.getState().setAppLoading(false);
|
|
||||||
DatabaseLogger.info("Database initialized");
|
|
||||||
}
|
|
||||||
Walkthrough.init();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let sub: () => void;
|
if (!appLocked && isAppLoading) {
|
||||||
if (appLocked) {
|
initializeLogger()
|
||||||
const sub = useUserStore.subscribe((state) => {
|
.catch((e) => {
|
||||||
if (!state.appLocked && useSettingStore.getState().isAppLoading) {
|
console.log(e);
|
||||||
console.log("DB initialized");
|
})
|
||||||
|
.finally(() => {
|
||||||
|
//@ts-ignore
|
||||||
initializeDatabase();
|
initializeDatabase();
|
||||||
sub();
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return () => {
|
}, [appLocked, isAppLoading]);
|
||||||
sub?.();
|
|
||||||
};
|
|
||||||
}, [appLocked, initializeDatabase]);
|
|
||||||
|
|
||||||
return initializeDatabase;
|
|
||||||
};
|
|
||||||
|
|
||||||
const doAppLoadActions = async () => {
|
|
||||||
if (SettingsService.get().sessionExpired) {
|
|
||||||
eSendEvent(eLoginSessionExpired);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
notifee.setBadgeCount(0);
|
|
||||||
|
|
||||||
if (!(await db.user.getUser())) {
|
|
||||||
setLoginMessage();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await useMessageStore.getState().setAnnouncement();
|
|
||||||
if (NewFeature.present()) return;
|
|
||||||
if (await checkAppUpdateAvailable()) return;
|
|
||||||
if (await checkForRateAppRequest()) return;
|
|
||||||
if (await PremiumService.getRemainingTrialDaysStatus()) return;
|
|
||||||
if (SettingsService.get().introCompleted) {
|
|
||||||
useMessageStore.subscribe((state) => {
|
|
||||||
const dialogs = state.dialogs;
|
|
||||||
if (dialogs.length > 0) {
|
|
||||||
eSendEvent(eOpenAnnouncementDialog, dialogs[0]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const checkAppUpdateAvailable = async () => {
|
|
||||||
if (__DEV__ || Config.isTesting === "true" || Config.FDROID_BUILD || BETA)
|
|
||||||
return;
|
|
||||||
try {
|
|
||||||
const version =
|
|
||||||
Config.GITHUB_RELEASE === "true"
|
|
||||||
? await getGithubVersion()
|
|
||||||
: await checkVersion();
|
|
||||||
if (!version || !version?.needsUpdate) return false;
|
|
||||||
|
|
||||||
setUpdateAvailableMessage(version);
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const checkForRateAppRequest = async () => {
|
|
||||||
const rateApp = SettingsService.get().rateApp as number;
|
|
||||||
if (
|
|
||||||
rateApp &&
|
|
||||||
rateApp < Date.now() &&
|
|
||||||
!useMessageStore.getState().message?.visible
|
|
||||||
) {
|
|
||||||
setRateAppMessage();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -71,12 +71,10 @@ export const useDBItem = <T extends keyof ItemTypeKey>(
|
|||||||
if (typeof itemId === "string" && itemId !== itemIdRef.current) return;
|
if (typeof itemId === "string" && itemId !== itemIdRef.current) return;
|
||||||
if (!isValidIdOrIndex(idOrIndex)) return;
|
if (!isValidIdOrIndex(idOrIndex)) return;
|
||||||
|
|
||||||
console.log("useDBItem.onUpdateItem", idOrIndex, type);
|
|
||||||
|
|
||||||
if (items && typeof idOrIndex === "number") {
|
if (items && typeof idOrIndex === "number") {
|
||||||
items.item(idOrIndex).then((item) => {
|
items.item(idOrIndex).then((item) => {
|
||||||
setItem(item.item);
|
setItem(item.item);
|
||||||
itemIdRef.current = item.item.id;
|
itemIdRef.current = item.item?.id;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (!(db as any)[type + "s"][type]) {
|
if (!(db as any)[type + "s"][type]) {
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ export const useNotebook = (
|
|||||||
|
|
||||||
const onRequestUpdate = React.useCallback(() => {
|
const onRequestUpdate = React.useCallback(() => {
|
||||||
if (!item?.id) return;
|
if (!item?.id) return;
|
||||||
console.log("useNotebook.onRequestUpdate", item?.id, Date.now());
|
|
||||||
|
|
||||||
const selector = db.relations.from(
|
const selector = db.relations.from(
|
||||||
{
|
{
|
||||||
@@ -59,7 +58,6 @@ export const useNotebook = (
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (nestedNotebooks) {
|
if (nestedNotebooks) {
|
||||||
console.log("useNotebook.useEffect.onRequestUpdate");
|
|
||||||
onRequestUpdate();
|
onRequestUpdate();
|
||||||
}
|
}
|
||||||
}, [item?.id, onRequestUpdate, nestedNotebooks]);
|
}, [item?.id, onRequestUpdate, nestedNotebooks]);
|
||||||
|
|||||||
@@ -22,20 +22,14 @@ import { Platform } from "react-native";
|
|||||||
import { Subscription } from "react-native-iap";
|
import { Subscription } from "react-native-iap";
|
||||||
import PremiumService from "../services/premium";
|
import PremiumService from "../services/premium";
|
||||||
import { db } from "../common/database";
|
import { db } from "../common/database";
|
||||||
|
import { Product } from "@notesnook/core/dist/api/pricing";
|
||||||
|
|
||||||
type PurchaseInfo = {
|
const skuInfos: { [name: string]: Product | undefined } = {};
|
||||||
country: string;
|
|
||||||
countryCode: string;
|
|
||||||
sku: string;
|
|
||||||
discount: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const skuInfos: { [name: string]: PurchaseInfo | undefined } = {};
|
|
||||||
|
|
||||||
export const usePricing = (period: "monthly" | "yearly") => {
|
export const usePricing = (period: "monthly" | "yearly") => {
|
||||||
const [current, setCurrent] = useState<{
|
const [current, setCurrent] = useState<{
|
||||||
period: string;
|
period: string;
|
||||||
info?: PurchaseInfo;
|
info?: Product;
|
||||||
product?: Subscription;
|
product?: Subscription;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
@@ -54,6 +48,7 @@ export const usePricing = (period: "monthly" | "yearly") => {
|
|||||||
period
|
period
|
||||||
));
|
));
|
||||||
skuInfos[period] = skuInfo;
|
skuInfos[period] = skuInfo;
|
||||||
|
|
||||||
const products = (await PremiumService.getProducts()) as Subscription[];
|
const products = (await PremiumService.getProducts()) as Subscription[];
|
||||||
let product = products.find((p) => p.productId === skuInfo?.sku);
|
let product = products.find((p) => p.productId === skuInfo?.sku);
|
||||||
if (!product)
|
if (!product)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback, useEffect } from "react";
|
import React, { useCallback, useEffect } from "react";
|
||||||
import BiometicService from "../services/biometrics";
|
import BiometricService from "../services/biometrics";
|
||||||
import { eSubscribeEvent, eUnSubscribeEvent } from "../services/event-manager";
|
import { eSubscribeEvent, eUnSubscribeEvent } from "../services/event-manager";
|
||||||
import { db } from "../common/database";
|
import { db } from "../common/database";
|
||||||
|
|
||||||
@@ -39,8 +39,8 @@ export const useVaultStatus = () => {
|
|||||||
|
|
||||||
const checkVaultStatus = useCallback(() => {
|
const checkVaultStatus = useCallback(() => {
|
||||||
db.vault?.exists().then(async (exists) => {
|
db.vault?.exists().then(async (exists) => {
|
||||||
const available = await BiometicService.isBiometryAvailable();
|
const available = await BiometricService.isBiometryAvailable();
|
||||||
const fingerprint = await BiometicService.hasInternetCredentials();
|
const fingerprint = await BiometricService.hasInternetCredentials();
|
||||||
if (
|
if (
|
||||||
VaultStatusCache.exists === exists &&
|
VaultStatusCache.exists === exists &&
|
||||||
VaultStatusCache.biometryEnrolled === fingerprint &&
|
VaultStatusCache.biometryEnrolled === fingerprint &&
|
||||||
|
|||||||
@@ -62,9 +62,9 @@ import {
|
|||||||
} from "../services/event-manager";
|
} from "../services/event-manager";
|
||||||
import { useSettingStore } from "../stores/use-setting-store";
|
import { useSettingStore } from "../stores/use-setting-store";
|
||||||
import {
|
import {
|
||||||
eClearEditor,
|
|
||||||
eCloseFullscreenEditor,
|
eCloseFullscreenEditor,
|
||||||
eOnEnterEditor,
|
eOnEnterEditor,
|
||||||
|
eOnExitEditor,
|
||||||
eOnLoadNote,
|
eOnLoadNote,
|
||||||
eOpenFullscreenEditor,
|
eOpenFullscreenEditor,
|
||||||
eUnlockNote
|
eUnlockNote
|
||||||
@@ -285,9 +285,10 @@ const _TabsHolder = () => {
|
|||||||
break;
|
break;
|
||||||
case "mobile":
|
case "mobile":
|
||||||
if (
|
if (
|
||||||
state &&
|
(state &&
|
||||||
!state?.movedAway &&
|
!state?.movedAway &&
|
||||||
useTabStore.getState().getCurrentNoteId()
|
useTabStore.getState().getCurrentNoteId()) ||
|
||||||
|
editorState().movedAway === false
|
||||||
) {
|
) {
|
||||||
tabBarRef.current?.goToIndex(2, false);
|
tabBarRef.current?.goToIndex(2, false);
|
||||||
} else {
|
} else {
|
||||||
@@ -511,6 +512,7 @@ const onChangeTab = async (event) => {
|
|||||||
editorState().movedAway = false;
|
editorState().movedAway = false;
|
||||||
editorState().isFocused = true;
|
editorState().isFocused = true;
|
||||||
activateKeepAwake();
|
activateKeepAwake();
|
||||||
|
eSendEvent(eOnEnterEditor);
|
||||||
|
|
||||||
if (!useTabStore.getState().getCurrentNoteId()) {
|
if (!useTabStore.getState().getCurrentNoteId()) {
|
||||||
eSendEvent(eOnLoadNote, {
|
eSendEvent(eOnLoadNote, {
|
||||||
@@ -522,14 +524,13 @@ const onChangeTab = async (event) => {
|
|||||||
) {
|
) {
|
||||||
eSendEvent(eUnlockNote);
|
eSendEvent(eUnlockNote);
|
||||||
}
|
}
|
||||||
eSendEvent(eOnEnterEditor);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (event.from === 2) {
|
if (event.from === 2) {
|
||||||
deactivateKeepAwake();
|
deactivateKeepAwake();
|
||||||
editorState().movedAway = true;
|
editorState().movedAway = true;
|
||||||
editorState().isFocused = false;
|
editorState().isFocused = false;
|
||||||
eSendEvent(eClearEditor, "removeHandler");
|
eSendEvent(eOnExitEditor);
|
||||||
|
|
||||||
// Lock all tabs with locked notes...
|
// Lock all tabs with locked notes...
|
||||||
for (const tab of useTabStore.getState().tabs) {
|
for (const tab of useTabStore.getState().tabs) {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
"html-to-text": "9.0.5",
|
"html-to-text": "9.0.5",
|
||||||
"phone": "^3.1.14",
|
"phone": "^3.1.14",
|
||||||
"qclone": "^1.2.0",
|
"qclone": "^1.2.0",
|
||||||
"react-native-actions-sheet": "0.9.3",
|
"react-native-actions-sheet": "0.9.6",
|
||||||
"react-native-check-version": "https://github.com/flexible-agency/react-native-check-version",
|
"react-native-check-version": "https://github.com/flexible-agency/react-native-check-version",
|
||||||
"react-native-drax": "^0.10.2",
|
"react-native-drax": "^0.10.2",
|
||||||
"react-native-image-zoom-viewer": "^3.0.1",
|
"react-native-image-zoom-viewer": "^3.0.1",
|
||||||
|
|||||||
@@ -32,13 +32,14 @@ import WebView from "react-native-webview";
|
|||||||
import { ShouldStartLoadRequest } from "react-native-webview/lib/WebViewTypes";
|
import { ShouldStartLoadRequest } from "react-native-webview/lib/WebViewTypes";
|
||||||
import { notesnook } from "../../../e2e/test.ids";
|
import { notesnook } from "../../../e2e/test.ids";
|
||||||
import { db } from "../../common/database";
|
import { db } from "../../common/database";
|
||||||
import BiometicService from "../../services/biometrics";
|
import BiometricService from "../../services/biometrics";
|
||||||
import {
|
import {
|
||||||
ToastManager,
|
ToastManager,
|
||||||
eSendEvent,
|
eSendEvent,
|
||||||
eSubscribeEvent
|
eSubscribeEvent
|
||||||
} from "../../services/event-manager";
|
} from "../../services/event-manager";
|
||||||
import {
|
import {
|
||||||
|
eEditorReset,
|
||||||
eOnLoadNote,
|
eOnLoadNote,
|
||||||
eUnlockNote,
|
eUnlockNote,
|
||||||
eUnlockWithBiometrics,
|
eUnlockWithBiometrics,
|
||||||
@@ -51,7 +52,13 @@ import { EditorProps, useEditorType } from "./tiptap/types";
|
|||||||
import { useEditor } from "./tiptap/use-editor";
|
import { useEditor } from "./tiptap/use-editor";
|
||||||
import { useEditorEvents } from "./tiptap/use-editor-events";
|
import { useEditorEvents } from "./tiptap/use-editor-events";
|
||||||
import { syncTabs, useTabStore } from "./tiptap/use-tab-store";
|
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 = {
|
const style: ViewStyle = {
|
||||||
height: "100%",
|
height: "100%",
|
||||||
@@ -61,7 +68,10 @@ const style: ViewStyle = {
|
|||||||
backgroundColor: "transparent"
|
backgroundColor: "transparent"
|
||||||
};
|
};
|
||||||
const onShouldStartLoadWithRequest = (request: ShouldStartLoadRequest) => {
|
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;
|
if (Platform.OS === "ios" && !request.isTopFrame) return true;
|
||||||
openLinkInBrowser(request.url);
|
openLinkInBrowser(request.url);
|
||||||
return false;
|
return false;
|
||||||
@@ -95,24 +105,20 @@ const Editor = React.memo(
|
|||||||
noToolbar,
|
noToolbar,
|
||||||
noHeader
|
noHeader
|
||||||
});
|
});
|
||||||
const renderKey = useRef(`editor-0` + editorId);
|
const renderKey = useRef(randId("editor-id") + editorId);
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
get: () => editor
|
get: () => editor
|
||||||
}));
|
}));
|
||||||
useLockedNoteHandler();
|
useLockedNoteHandler();
|
||||||
|
|
||||||
const onError = useCallback(() => {
|
const onError = useCallback(() => {
|
||||||
renderKey.current =
|
renderKey.current = randId("editor-id") + editorId;
|
||||||
renderKey.current === `editor-0`
|
|
||||||
? `editor-1` + editorId
|
|
||||||
: `editor-0` + editorId;
|
|
||||||
|
|
||||||
editor.state.current.ready = false;
|
editor.state.current.ready = false;
|
||||||
editor.setLoading(true);
|
editor.setLoading(true);
|
||||||
}, [editor, editorId]);
|
}, [editor, editorId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const sub = [eSubscribeEvent("webview_reset", onError)];
|
const sub = [eSubscribeEvent(eEditorReset, onError)];
|
||||||
return () => {
|
return () => {
|
||||||
sub.forEach((s) => s?.unsubscribe());
|
sub.forEach((s) => s?.unsubscribe());
|
||||||
};
|
};
|
||||||
@@ -139,6 +145,7 @@ const Editor = React.memo(
|
|||||||
nestedScrollEnabled
|
nestedScrollEnabled
|
||||||
onError={onError}
|
onError={onError}
|
||||||
injectedJavaScriptBeforeContentLoaded={`
|
injectedJavaScriptBeforeContentLoaded={`
|
||||||
|
globalThis.__DEV__ = ${__DEV__}
|
||||||
globalThis.readonly=${readonly};
|
globalThis.readonly=${readonly};
|
||||||
globalThis.noToolbar=${noToolbar};
|
globalThis.noToolbar=${noToolbar};
|
||||||
globalThis.noHeader=${noHeader};
|
globalThis.noHeader=${noHeader};
|
||||||
@@ -201,8 +208,8 @@ const useLockedNoteHandler = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const biometry = await BiometicService.isBiometryAvailable();
|
const biometry = await BiometricService.isBiometryAvailable();
|
||||||
const fingerprint = await BiometicService.hasInternetCredentials();
|
const fingerprint = await BiometricService.hasInternetCredentials();
|
||||||
useTabStore.setState({
|
useTabStore.setState({
|
||||||
biometryAvailable: !!biometry,
|
biometryAvailable: !!biometry,
|
||||||
biometryEnrolled: !!fingerprint
|
biometryEnrolled: !!fingerprint
|
||||||
@@ -216,9 +223,9 @@ const useLockedNoteHandler = () => {
|
|||||||
try {
|
try {
|
||||||
if (!tabRef.current?.noteLocked || !tabRef.current) return;
|
if (!tabRef.current?.noteLocked || !tabRef.current) return;
|
||||||
console.log("Trying to unlock with biometrics...");
|
console.log("Trying to unlock with biometrics...");
|
||||||
const credentials = await BiometicService.getCredentials(
|
const credentials = await BiometricService.getCredentials(
|
||||||
"Unlock note",
|
"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) {
|
if (credentials && credentials?.password && tabRef.current.noteId) {
|
||||||
@@ -226,6 +233,7 @@ const useLockedNoteHandler = () => {
|
|||||||
tabRef.current.noteId,
|
tabRef.current.noteId,
|
||||||
credentials?.password
|
credentials?.password
|
||||||
);
|
);
|
||||||
|
|
||||||
eSendEvent(eOnLoadNote, {
|
eSendEvent(eOnLoadNote, {
|
||||||
item: note
|
item: note
|
||||||
});
|
});
|
||||||
@@ -262,7 +270,7 @@ const useLockedNoteHandler = () => {
|
|||||||
try {
|
try {
|
||||||
const unlocked = await db.vault.unlock(password);
|
const unlocked = await db.vault.unlock(password);
|
||||||
if (!unlocked) throw new Error("Incorrect vault password");
|
if (!unlocked) throw new Error("Incorrect vault password");
|
||||||
await BiometicService.storeCredentials(password);
|
await BiometricService.storeCredentials(password);
|
||||||
eSendEvent("vaultUpdated");
|
eSendEvent("vaultUpdated");
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
heading: "Biometric unlocking enabled!",
|
heading: "Biometric unlocking enabled!",
|
||||||
@@ -271,8 +279,8 @@ const useLockedNoteHandler = () => {
|
|||||||
context: "global"
|
context: "global"
|
||||||
});
|
});
|
||||||
|
|
||||||
const biometry = await BiometicService.isBiometryAvailable();
|
const biometry = await BiometricService.isBiometryAvailable();
|
||||||
const fingerprint = await BiometicService.hasInternetCredentials();
|
const fingerprint = await BiometricService.hasInternetCredentials();
|
||||||
useTabStore.setState({
|
useTabStore.setState({
|
||||||
biometryAvailable: !!biometry,
|
biometryAvailable: !!biometry,
|
||||||
biometryEnrolled: !!fingerprint
|
biometryEnrolled: !!fingerprint
|
||||||
@@ -331,7 +339,7 @@ const useLockedNoteHandler = () => {
|
|||||||
}),
|
}),
|
||||||
eSubscribeEvent(eUnlockWithPassword, onSubmit)
|
eSubscribeEvent(eUnlockWithPassword, onSubmit)
|
||||||
];
|
];
|
||||||
if (tabRef.current?.locked) {
|
if (tabRef.current?.locked && tabBarRef.current?.page() === 2) {
|
||||||
unlock();
|
unlock();
|
||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
@@ -75,14 +75,16 @@ export function ReadonlyEditor(props: {
|
|||||||
if (editorMessage.type === EventTypes.readonlyEditorLoaded) {
|
if (editorMessage.type === EventTypes.readonlyEditorLoaded) {
|
||||||
console.log("Readonly editor loaded.");
|
console.log("Readonly editor loaded.");
|
||||||
props.onLoad?.((content: { data: string; id: string }) => {
|
props.onLoad?.((content: { data: string; id: string }) => {
|
||||||
noteId.current = content.id;
|
setTimeout(() => {
|
||||||
editorRef.current?.postMessage(
|
noteId.current = content.id;
|
||||||
JSON.stringify({
|
editorRef.current?.postMessage(
|
||||||
type: "native:html",
|
JSON.stringify({
|
||||||
value: content.data
|
type: "native:html",
|
||||||
})
|
value: content.data
|
||||||
);
|
})
|
||||||
setLoading(false);
|
);
|
||||||
|
setLoading(false);
|
||||||
|
}, 300);
|
||||||
});
|
});
|
||||||
} else if (editorMessage.type === EventTypes.getAttachmentData) {
|
} else if (editorMessage.type === EventTypes.getAttachmentData) {
|
||||||
const attachment = (editorMessage.value as any).attachment as Attachment;
|
const attachment = (editorMessage.value as any).attachment as Attachment;
|
||||||
|
|||||||
@@ -48,5 +48,6 @@ export const EventTypes = {
|
|||||||
unlockWithBiometrics: "editor-events:unlock-biometrics",
|
unlockWithBiometrics: "editor-events:unlock-biometrics",
|
||||||
disableReadonlyMode: "editor-events:disable-readonly-mode",
|
disableReadonlyMode: "editor-events:disable-readonly-mode",
|
||||||
readonlyEditorLoaded: "readonlyEditorLoaded",
|
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 { isImage } from "@notesnook/core/dist/utils/filename";
|
||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
import RNFetchBlob from "react-native-blob-util";
|
import RNFetchBlob from "react-native-blob-util";
|
||||||
import DocumentPicker from "react-native-document-picker";
|
import DocumentPicker, {
|
||||||
import {
|
DocumentPickerOptions,
|
||||||
ImagePickerResponse,
|
DocumentPickerResponse
|
||||||
launchCamera,
|
} from "react-native-document-picker";
|
||||||
launchImageLibrary
|
import { Image, openCamera, openPicker } from "react-native-image-crop-picker";
|
||||||
} from "react-native-image-picker";
|
|
||||||
import { DatabaseLogger, db } from "../../../common/database";
|
import { DatabaseLogger, db } from "../../../common/database";
|
||||||
import filesystem from "../../../common/filesystem";
|
import filesystem from "../../../common/filesystem";
|
||||||
import { compressToFile } from "../../../common/filesystem/compress";
|
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 { eCloseSheet } from "../../../utils/events";
|
||||||
import { useTabStore } from "./use-tab-store";
|
import { useTabStore } from "./use-tab-store";
|
||||||
import { editorController, editorState } from "./utils";
|
import { editorController, editorState } from "./utils";
|
||||||
|
import { basename } from "pathe";
|
||||||
|
|
||||||
const showEncryptionSheet = (file) => {
|
const showEncryptionSheet = (file: DocumentPickerResponse) => {
|
||||||
presentSheet({
|
presentSheet({
|
||||||
title: "Encrypting attachment",
|
title: "Encrypting attachment",
|
||||||
paragraph: `Please wait while we encrypt ${file.name} file for upload`,
|
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 = decodeURI(uri);
|
||||||
uri = Platform.OS === "ios" ? uri.replace("file:///", "/") : uri;
|
uri = Platform.OS === "ios" ? uri.replace("file:///", "/") : uri;
|
||||||
return uri;
|
return uri;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
type PickerOptions = {
|
||||||
* @param {{
|
noteId?: string;
|
||||||
* noteId: string,
|
tabId?: number;
|
||||||
* tabId: string,
|
type: "image" | "camera" | "file";
|
||||||
* type: "image" | "camera" | "file"
|
reupload: boolean;
|
||||||
* reupload: boolean
|
hash?: string;
|
||||||
* hash?: string
|
context?: string;
|
||||||
* }} fileOptions
|
outputType?: "base64" | "url" | "cache";
|
||||||
*/
|
};
|
||||||
const file = async (fileOptions) => {
|
|
||||||
|
const file = async (fileOptions: PickerOptions) => {
|
||||||
try {
|
try {
|
||||||
const options = {
|
const options: DocumentPickerOptions<"ios"> = {
|
||||||
mode: "import",
|
mode: "import",
|
||||||
allowMultiSelection: false
|
allowMultiSelection: false
|
||||||
};
|
};
|
||||||
@@ -87,11 +88,11 @@ const file = async (fileOptions) => {
|
|||||||
|
|
||||||
file = file[0];
|
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({
|
ToastManager.show({
|
||||||
title: "File too large",
|
heading: "File too large",
|
||||||
message: "The maximum allowed size per file is 500 MB",
|
message: "The maximum allowed size per file is 500 MB",
|
||||||
type: "error"
|
type: "error"
|
||||||
});
|
});
|
||||||
@@ -108,29 +109,40 @@ const file = async (fileOptions) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("file uri: ", uri);
|
|
||||||
uri = Platform.OS === "ios" ? santizeUri(uri) : uri;
|
uri = Platform.OS === "ios" ? santizeUri(uri) : uri;
|
||||||
showEncryptionSheet(file);
|
showEncryptionSheet(file);
|
||||||
const hash = await Sodium.hashFile({
|
const hash = await Sodium.hashFile({
|
||||||
uri: uri,
|
uri: uri,
|
||||||
type: "url"
|
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;
|
return;
|
||||||
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
|
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
|
||||||
|
|
||||||
|
if (!fileOptions.tabId) return;
|
||||||
if (
|
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(
|
editorController.current?.commands.insertImage(
|
||||||
{
|
{
|
||||||
hash: hash,
|
hash: hash,
|
||||||
filename: file.name,
|
filename: file.name,
|
||||||
mime: file.type,
|
mime: file.type || "application/octet-stream",
|
||||||
size: file.size,
|
size: file.size || 0,
|
||||||
dataurl: await db.attachments.read(hash, "base64"),
|
dataurl: (await db.attachments.read(hash, "base64")) as string,
|
||||||
title: file.name
|
type: "image"
|
||||||
},
|
},
|
||||||
fileOptions.tabId
|
fileOptions.tabId
|
||||||
);
|
);
|
||||||
@@ -139,8 +151,9 @@ const file = async (fileOptions) => {
|
|||||||
{
|
{
|
||||||
hash: hash,
|
hash: hash,
|
||||||
filename: file.name,
|
filename: file.name,
|
||||||
mime: file.type,
|
mime: file.type || "application/octet-stream",
|
||||||
size: file.size
|
size: file.size || 0,
|
||||||
|
type: "file"
|
||||||
},
|
},
|
||||||
fileOptions.tabId
|
fileOptions.tabId
|
||||||
);
|
);
|
||||||
@@ -151,39 +164,74 @@ const file = async (fileOptions) => {
|
|||||||
eSendEvent(eCloseSheet);
|
eSendEvent(eCloseSheet);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
eSendEvent(eCloseSheet);
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
heading: e.message,
|
heading: (e as Error).message,
|
||||||
message: "You need internet access to attach a file",
|
message: "You need internet access to attach a file",
|
||||||
type: "error",
|
type: "error",
|
||||||
context: "global"
|
context: "global"
|
||||||
});
|
});
|
||||||
console.log("attachment error: ", e);
|
DatabaseLogger.error(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
const camera = async (options: PickerOptions) => {
|
||||||
* @param {{
|
|
||||||
* noteId: string,
|
|
||||||
* tabId: string,
|
|
||||||
* type: "image" | "camera" | "file"
|
|
||||||
* reupload: boolean
|
|
||||||
* hash?: string
|
|
||||||
* }} options
|
|
||||||
*/
|
|
||||||
const camera = async (options) => {
|
|
||||||
try {
|
try {
|
||||||
await db.attachments.generateKey();
|
await db.attachments.generateKey();
|
||||||
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
|
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
|
||||||
launchCamera(
|
openCamera({
|
||||||
{
|
mediaType: "photo",
|
||||||
includeBase64: true,
|
includeBase64: true,
|
||||||
mediaType: "photo"
|
cropping: false,
|
||||||
},
|
multiple: true,
|
||||||
(response) => handleImageResponse(response, options)
|
maxFiles: 10,
|
||||||
);
|
writeTempFile: true,
|
||||||
|
compressImageQuality: 1
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
handleImageResponse(
|
||||||
|
Array.isArray(response) ? response : [response],
|
||||||
|
options
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log("camera error: ", e);
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ToastManager.show({
|
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,
|
||||||
|
compressImageQuality: 1
|
||||||
|
})
|
||||||
|
.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",
|
message: "You need internet access to attach a file",
|
||||||
type: "error",
|
type: "error",
|
||||||
context: "global"
|
context: "global"
|
||||||
@@ -192,53 +240,9 @@ const camera = async (options) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const gallery = async (options) => {
|
const pick = async (options: PickerOptions) => {
|
||||||
try {
|
|
||||||
await db.attachments.generateKey();
|
|
||||||
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
|
|
||||||
launchImageLibrary(
|
|
||||||
{
|
|
||||||
includeBase64: true,
|
|
||||||
mediaType: "photo",
|
|
||||||
selectionLimit: 10
|
|
||||||
},
|
|
||||||
(response) => handleImageResponse(response, options)
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
ToastManager.show({
|
|
||||||
heading: e.message,
|
|
||||||
message: "You need internet access to attach a file",
|
|
||||||
type: "error",
|
|
||||||
context: "global"
|
|
||||||
});
|
|
||||||
console.log("attachment error:", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @typedef {{
|
|
||||||
* noteId?: string,
|
|
||||||
* tabId?: string,
|
|
||||||
* type: "image" | "camera" | "file"
|
|
||||||
* reupload: boolean
|
|
||||||
* hash?: string
|
|
||||||
* context?: string
|
|
||||||
* }} ImagePickerOptions
|
|
||||||
*
|
|
||||||
* @param {{
|
|
||||||
* noteId?: string,
|
|
||||||
* tabId?: string,
|
|
||||||
* type: "image" | "camera" | "file"
|
|
||||||
* reupload: boolean
|
|
||||||
* hash?: string
|
|
||||||
* context?: string
|
|
||||||
* }} options
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const pick = async (options) => {
|
|
||||||
if (!PremiumService.get()) {
|
if (!PremiumService.get()) {
|
||||||
let user = await db.user.getUser();
|
const user = await db.user.getUser();
|
||||||
if (editorState().isFocused) {
|
if (editorState().isFocused) {
|
||||||
editorState().isFocused = true;
|
editorState().isFocused = true;
|
||||||
}
|
}
|
||||||
@@ -259,58 +263,59 @@ const pick = async (options) => {
|
|||||||
file(options);
|
file(options);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {ImagePickerResponse} response
|
|
||||||
* @param {ImagePickerOptions} options
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const handleImageResponse = async (response, options) => {
|
|
||||||
if (
|
|
||||||
response.didCancel ||
|
|
||||||
response.errorMessage ||
|
|
||||||
!response.assets ||
|
|
||||||
response.assets?.length === 0
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await AttachImage.present(response);
|
|
||||||
|
|
||||||
|
const handleImageResponse = async (
|
||||||
|
response: Image[],
|
||||||
|
options: PickerOptions
|
||||||
|
) => {
|
||||||
|
const result = await AttachImage.present(response, options.context);
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
const compress = result.compress;
|
const compress = result.compress;
|
||||||
|
|
||||||
for (let image of response.assets) {
|
for (const image of response) {
|
||||||
const isPng = /(png)/g.test(image.type);
|
const isPng = /(png)/g.test(image.mime);
|
||||||
const isJpeg = /(jpeg|jpg)/g.test(image.type);
|
const isJpeg = /(jpeg|jpg)/g.test(image.mime);
|
||||||
|
|
||||||
if (compress && (isPng || isJpeg)) {
|
if (compress && (isPng || isJpeg)) {
|
||||||
image.uri = await compressToFile(
|
image.path = await compressToFile(
|
||||||
Platform.OS === "ios" ? "file://" + image.uri : image.uri,
|
Platform.OS === "ios" ? "file://" + image.path : image.path,
|
||||||
isPng ? "PNG" : "JPEG"
|
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({
|
ToastManager.show({
|
||||||
title: "File too large",
|
heading: "File too large",
|
||||||
message: "The maximum allowed size per image is 50 MB",
|
message: "The maximum allowed size per image is 50 MB",
|
||||||
type: "error"
|
type: "error"
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let b64 = `data:${image.type};base64, ` + image.base64;
|
const b64 = `data:${image.mime};base64, ` + image.data;
|
||||||
const uri = decodeURI(image.uri);
|
const uri = decodeURI(image.path);
|
||||||
const hash = await Sodium.hashFile({
|
const hash = await Sodium.hashFile({
|
||||||
uri: uri,
|
uri: uri,
|
||||||
type: "url"
|
type: "url"
|
||||||
});
|
});
|
||||||
|
|
||||||
let fileName = image.originalFileName || image.fileName;
|
let fileName = image.sourceURL
|
||||||
|
? basename(image.sourceURL)
|
||||||
|
: image.filename || "image";
|
||||||
|
|
||||||
|
fileName =
|
||||||
|
image.mime === "image/jpeg"
|
||||||
|
? fileName.replace(/HEIC|HEIF/, "jpeg")
|
||||||
|
: fileName;
|
||||||
|
|
||||||
|
console.log("attaching image...", fileName);
|
||||||
|
|
||||||
console.log("attaching file...");
|
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);
|
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
|
||||||
console.log("attaching image to note...");
|
console.log("attaching image to note...");
|
||||||
@@ -322,11 +327,13 @@ const handleImageResponse = async (response, options) => {
|
|||||||
editorController.current?.commands.insertImage(
|
editorController.current?.commands.insertImage(
|
||||||
{
|
{
|
||||||
hash: hash,
|
hash: hash,
|
||||||
mime: image.type,
|
mime: image.mime,
|
||||||
title: fileName,
|
type: "image",
|
||||||
dataurl: b64,
|
dataurl: b64,
|
||||||
size: image.fileSize,
|
size: image.size,
|
||||||
filename: fileName
|
filename: fileName as string,
|
||||||
|
width: image.width,
|
||||||
|
height: image.height
|
||||||
},
|
},
|
||||||
options.tabId
|
options.tabId
|
||||||
);
|
);
|
||||||
@@ -343,10 +350,16 @@ const handleImageResponse = async (response, options) => {
|
|||||||
* @param {ImagePickerOptions} options
|
* @param {ImagePickerOptions} options
|
||||||
* @returns
|
* @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 {
|
try {
|
||||||
let exists = await db.attachments.exists(hash);
|
const exists = await db.attachments.exists(hash);
|
||||||
let encryptionInfo;
|
let encryptionInfo: any;
|
||||||
if (options?.hash && options.hash !== hash) {
|
if (options?.hash && options.hash !== hash) {
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
heading: "Please select the same file for reuploading",
|
heading: "Please select the same file for reuploading",
|
||||||
@@ -358,26 +371,36 @@ export async function attachFile(uri, hash, type, filename, options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!options.reupload && exists) {
|
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) {
|
if (!exists || options?.reupload) {
|
||||||
let key = await db.attachments.generateKey();
|
const key = await db.attachments.generateKey();
|
||||||
encryptionInfo = await Sodium.encryptFile(key, {
|
encryptionInfo = await Sodium.encryptFile(key, {
|
||||||
uri: uri,
|
uri: uri,
|
||||||
type: options.type || "url",
|
type: options.outputType || "url",
|
||||||
hash: hash
|
hash: hash
|
||||||
});
|
} as any);
|
||||||
encryptionInfo.mimeType = type;
|
encryptionInfo.mimeType = type;
|
||||||
encryptionInfo.filename = filename;
|
encryptionInfo.filename = filename;
|
||||||
encryptionInfo.alg = "xcha-stream";
|
encryptionInfo.alg = "xcha-stream";
|
||||||
encryptionInfo.size = encryptionInfo.length;
|
encryptionInfo.size = encryptionInfo.length;
|
||||||
encryptionInfo.key = key;
|
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 {
|
} else {
|
||||||
encryptionInfo = { hash: hash };
|
encryptionInfo = { hash: hash };
|
||||||
}
|
}
|
||||||
await db.attachments.add(encryptionInfo, options.noteId);
|
await db.attachments.add(encryptionInfo);
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
DatabaseLogger.error(e);
|
DatabaseLogger.error(e);
|
||||||
@@ -37,6 +37,7 @@ export type EditorState = {
|
|||||||
isAwaitingResult: boolean;
|
isAwaitingResult: boolean;
|
||||||
scrollPosition: number;
|
scrollPosition: number;
|
||||||
overlay?: boolean;
|
overlay?: boolean;
|
||||||
|
initialLoadCalled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Settings = {
|
export type Settings = {
|
||||||
@@ -74,6 +75,8 @@ export type EditorMessage<T> = {
|
|||||||
type: string;
|
type: string;
|
||||||
noteId: string;
|
noteId: string;
|
||||||
tabId: number;
|
tabId: number;
|
||||||
|
resolverId?: string;
|
||||||
|
hasTimeout?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SavePayload = {
|
export type SavePayload = {
|
||||||
@@ -84,6 +87,7 @@ export type SavePayload = {
|
|||||||
sessionHistoryId?: number;
|
sessionHistoryId?: number;
|
||||||
ignoreEdit: boolean;
|
ignoreEdit: boolean;
|
||||||
tabId: number;
|
tabId: number;
|
||||||
|
pendingChanges?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AppState = {
|
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 no-case-declarations */
|
||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
import { parseInternalLink } from "@notesnook/core";
|
|
||||||
import { ItemReference } from "@notesnook/core/dist/types";
|
import { ItemReference } from "@notesnook/core/dist/types";
|
||||||
import type { Attachment } from "@notesnook/editor/dist/extensions/attachment/index";
|
import type { Attachment } from "@notesnook/editor/dist/extensions/attachment/index";
|
||||||
import { getDefaultPresets } from "@notesnook/editor/dist/toolbar/tool-definitions";
|
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 React, { useCallback, useEffect, useRef } from "react";
|
||||||
import {
|
import {
|
||||||
BackHandler,
|
BackHandler,
|
||||||
InteractionManager,
|
|
||||||
Keyboard,
|
Keyboard,
|
||||||
KeyboardEventListener,
|
KeyboardEventListener,
|
||||||
NativeEventSubscription,
|
NativeEventSubscription,
|
||||||
@@ -37,6 +35,7 @@ import { WebViewMessageEvent } from "react-native-webview";
|
|||||||
import { DatabaseLogger, db } from "../../../common/database";
|
import { DatabaseLogger, db } from "../../../common/database";
|
||||||
import downloadAttachment from "../../../common/filesystem/download-attachment";
|
import downloadAttachment from "../../../common/filesystem/download-attachment";
|
||||||
import EditorTabs from "../../../components/sheets/editor-tabs";
|
import EditorTabs from "../../../components/sheets/editor-tabs";
|
||||||
|
import { Issue } from "../../../components/sheets/github/issue";
|
||||||
import LinkNote from "../../../components/sheets/link-note";
|
import LinkNote from "../../../components/sheets/link-note";
|
||||||
import ManageTagsSheet from "../../../components/sheets/manage-tags";
|
import ManageTagsSheet from "../../../components/sheets/manage-tags";
|
||||||
import { RelationsList } from "../../../components/sheets/relations-list";
|
import { RelationsList } from "../../../components/sheets/relations-list";
|
||||||
@@ -61,6 +60,7 @@ import {
|
|||||||
eCloseFullscreenEditor,
|
eCloseFullscreenEditor,
|
||||||
eEditorTabFocused,
|
eEditorTabFocused,
|
||||||
eOnEnterEditor,
|
eOnEnterEditor,
|
||||||
|
eOnExitEditor,
|
||||||
eOnLoadNote,
|
eOnLoadNote,
|
||||||
eOpenFullscreenEditor,
|
eOpenFullscreenEditor,
|
||||||
eOpenLoginDialog,
|
eOpenLoginDialog,
|
||||||
@@ -75,8 +75,7 @@ import { useDragState } from "../../settings/editor/state";
|
|||||||
import { EventTypes } from "./editor-events";
|
import { EventTypes } from "./editor-events";
|
||||||
import { EditorMessage, EditorProps, useEditorType } from "./types";
|
import { EditorMessage, EditorProps, useEditorType } from "./types";
|
||||||
import { useTabStore } from "./use-tab-store";
|
import { useTabStore } from "./use-tab-store";
|
||||||
import { EditorEvents, editorState } from "./utils";
|
import { EditorEvents, editorState, openInternalLink } from "./utils";
|
||||||
import { Issue } from "../../../components/sheets/github/issue";
|
|
||||||
|
|
||||||
const publishNote = async () => {
|
const publishNote = async () => {
|
||||||
const user = useUserStore.getState().user;
|
const user = useUserStore.getState().user;
|
||||||
@@ -270,21 +269,20 @@ export const useEditorEvents = (
|
|||||||
}, [editor, deviceMode, fullscreen]);
|
}, [editor, deviceMode, fullscreen]);
|
||||||
|
|
||||||
const onHardwareBackPress = useCallback(() => {
|
const onHardwareBackPress = useCallback(() => {
|
||||||
if (tabBarRef.current?.page === 2) {
|
console.log(tabBarRef.current?.page());
|
||||||
|
if (tabBarRef.current?.page() === 2) {
|
||||||
onBackPress();
|
onBackPress();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}, [onBackPress]);
|
}, [onBackPress]);
|
||||||
|
|
||||||
const onEnterEditor = useCallback(async () => {
|
const onEnterEditor = useCallback(async () => {
|
||||||
InteractionManager.runAfterInteractions(() => {
|
if (!DDS.isTab) {
|
||||||
if (!DDS.isTab) {
|
handleBack.current = BackHandler.addEventListener(
|
||||||
handleBack.current = BackHandler.addEventListener(
|
"hardwareBackPress",
|
||||||
"hardwareBackPress",
|
onHardwareBackPress
|
||||||
onHardwareBackPress
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [onHardwareBackPress]);
|
}, [onHardwareBackPress]);
|
||||||
|
|
||||||
const onClearEditorSessionRequest = useCallback(
|
const onClearEditorSessionRequest = useCallback(
|
||||||
@@ -331,7 +329,14 @@ export const useEditorEvents = (
|
|||||||
}, [fullscreen, onHardwareBackPress]);
|
}, [fullscreen, onHardwareBackPress]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const onExitEditor = () => {
|
||||||
|
if (handleBack.current) {
|
||||||
|
handleBack.current.remove();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
eSubscribeEvent(eOnEnterEditor, onEnterEditor);
|
eSubscribeEvent(eOnEnterEditor, onEnterEditor);
|
||||||
|
eSubscribeEvent(eOnExitEditor, onExitEditor);
|
||||||
eSubscribeEvent(
|
eSubscribeEvent(
|
||||||
eClearEditor + editor.editorId,
|
eClearEditor + editor.editorId,
|
||||||
onClearEditorSessionRequest
|
onClearEditorSessionRequest
|
||||||
@@ -339,6 +344,7 @@ export const useEditorEvents = (
|
|||||||
return () => {
|
return () => {
|
||||||
eUnSubscribeEvent(eClearEditor, onClearEditorSessionRequest);
|
eUnSubscribeEvent(eClearEditor, onClearEditorSessionRequest);
|
||||||
eUnSubscribeEvent(eOnEnterEditor, onEnterEditor);
|
eUnSubscribeEvent(eOnEnterEditor, onEnterEditor);
|
||||||
|
eUnSubscribeEvent(eOnExitEditor, onExitEditor);
|
||||||
};
|
};
|
||||||
}, [editor.editorId, onClearEditorSessionRequest, onEnterEditor]);
|
}, [editor.editorId, onClearEditorSessionRequest, onEnterEditor]);
|
||||||
|
|
||||||
@@ -347,8 +353,15 @@ export const useEditorEvents = (
|
|||||||
const data = event.nativeEvent.data;
|
const data = event.nativeEvent.data;
|
||||||
const editorMessage = JSON.parse(data) as EditorMessage<any>;
|
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) {
|
if (editorMessage.type === EventTypes.load) {
|
||||||
console.log("Editor loaded");
|
DatabaseLogger.log("Editor is ready");
|
||||||
editor.onLoad();
|
editor.onLoad();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -376,21 +389,36 @@ export const useEditorEvents = (
|
|||||||
content: editorMessage.value.html as string,
|
content: editorMessage.value.html as string,
|
||||||
noteId: noteId,
|
noteId: noteId,
|
||||||
tabId: editorMessage.tabId,
|
tabId: editorMessage.tabId,
|
||||||
ignoreEdit: (editorMessage.value as ContentMessage).ignoreEdit
|
ignoreEdit: (editorMessage.value as ContentMessage).ignoreEdit,
|
||||||
|
pendingChanges: editorMessage.value?.pendingChanges
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case EventTypes.title:
|
case EventTypes.title:
|
||||||
DatabaseLogger.log("EventTypes.title");
|
DatabaseLogger.log("EventTypes.title");
|
||||||
editor.saveContent({
|
editor.saveContent({
|
||||||
type: editorMessage.type,
|
type: editorMessage.type,
|
||||||
title: editorMessage.value as string,
|
title: editorMessage.value?.title as string,
|
||||||
noteId: noteId,
|
noteId: noteId,
|
||||||
tabId: editorMessage.tabId,
|
tabId: editorMessage.tabId,
|
||||||
ignoreEdit: false
|
ignoreEdit: false,
|
||||||
|
pendingChanges: editorMessage.value?.pendingChanges
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case EventTypes.logger:
|
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;
|
break;
|
||||||
case EventTypes.contentchange:
|
case EventTypes.contentchange:
|
||||||
editor.onContentChanged(editorMessage.noteId);
|
editor.onContentChanged(editorMessage.noteId);
|
||||||
@@ -443,7 +471,7 @@ export const useEditorEvents = (
|
|||||||
break;
|
break;
|
||||||
case EventTypes.filepicker:
|
case EventTypes.filepicker:
|
||||||
editorState().isAwaitingResult = true;
|
editorState().isAwaitingResult = true;
|
||||||
const { pick } = require("./picker.js").default;
|
const { pick } = require("./picker").default;
|
||||||
pick({
|
pick({
|
||||||
type: editorMessage.value,
|
type: editorMessage.value,
|
||||||
noteId: noteId,
|
noteId: noteId,
|
||||||
@@ -462,12 +490,10 @@ export const useEditorEvents = (
|
|||||||
|
|
||||||
case EventTypes.getAttachmentData: {
|
case EventTypes.getAttachmentData: {
|
||||||
const attachment = (editorMessage.value as any)
|
const attachment = (editorMessage.value as any)
|
||||||
.attachment as Attachment;
|
?.attachment as Attachment;
|
||||||
|
|
||||||
console.log(
|
DatabaseLogger.log(
|
||||||
"Getting attachment data:",
|
`Getting attachment data: ${attachment?.hash} ${attachment?.type}`
|
||||||
attachment.hash,
|
|
||||||
attachment.type
|
|
||||||
);
|
);
|
||||||
downloadAttachment(attachment.hash, true, {
|
downloadAttachment(attachment.hash, true, {
|
||||||
base64: attachment.type === "image",
|
base64: attachment.type === "image",
|
||||||
@@ -480,17 +506,17 @@ export const useEditorEvents = (
|
|||||||
console.log(
|
console.log(
|
||||||
"Got attachment data:",
|
"Got attachment data:",
|
||||||
!!data,
|
!!data,
|
||||||
(editorMessage.value as any).resolverId
|
editorMessage.resolverId
|
||||||
);
|
);
|
||||||
editor.postMessage(EditorEvents.attachmentData, {
|
editor.postMessage(EditorEvents.resolve, {
|
||||||
resolverId: (editorMessage.value as any).resolverId,
|
resolverId: editorMessage.resolverId,
|
||||||
data
|
data
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((e) => {
|
||||||
console.log("Error downloading attachment data");
|
DatabaseLogger.error(e);
|
||||||
editor.postMessage(EditorEvents.attachmentData, {
|
editor.postMessage(EditorEvents.resolve, {
|
||||||
resolverId: (editorMessage.value as any).resolverId,
|
resolverId: editorMessage.resolverId,
|
||||||
data: undefined
|
data: undefined
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -519,27 +545,7 @@ export const useEditorEvents = (
|
|||||||
break;
|
break;
|
||||||
case EventTypes.link:
|
case EventTypes.link:
|
||||||
if (editorMessage.value.startsWith("nn://")) {
|
if (editorMessage.value.startsWith("nn://")) {
|
||||||
const data = parseInternalLink(editorMessage.value);
|
openInternalLink(editorMessage.value);
|
||||||
if (!data?.id) break;
|
|
||||||
if (
|
|
||||||
data.id ===
|
|
||||||
useTabStore
|
|
||||||
.getState()
|
|
||||||
.getNoteIdForTab(useTabStore.getState().currentTab)
|
|
||||||
) {
|
|
||||||
if (data.params?.blockId) {
|
|
||||||
setTimeout(() => {
|
|
||||||
if (!data.params?.blockId) return;
|
|
||||||
editor.commands.scrollIntoViewById(data.params.blockId);
|
|
||||||
}, 150);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
eSendEvent(eOnLoadNote, {
|
|
||||||
item: await db.notes.note(data?.id),
|
|
||||||
blockId: data.params?.blockId
|
|
||||||
});
|
|
||||||
console.log(
|
console.log(
|
||||||
"Opening note from internal link:",
|
"Opening note from internal link:",
|
||||||
editorMessage.value
|
editorMessage.value
|
||||||
@@ -637,7 +643,7 @@ export const useEditorEvents = (
|
|||||||
case EventTypes.createInternalLink: {
|
case EventTypes.createInternalLink: {
|
||||||
LinkNote.present(
|
LinkNote.present(
|
||||||
editorMessage.value.attributes,
|
editorMessage.value.attributes,
|
||||||
editorMessage.value.resolverId
|
editorMessage.resolverId as string
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -662,10 +668,8 @@ export const useEditorEvents = (
|
|||||||
.updateTab(useTabStore.getState().currentTab, {
|
.updateTab(useTabStore.getState().currentTab, {
|
||||||
readonly: false
|
readonly: false
|
||||||
});
|
});
|
||||||
Navigation.queueRoutesForUpdate();
|
setTimeout(() => {
|
||||||
ToastManager.show({
|
Navigation.queueRoutesForUpdate();
|
||||||
heading: "Readonly mode disabled.",
|
|
||||||
type: "success"
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import { DatabaseLogger, db } from "../../../common/database";
|
|||||||
import useGlobalSafeAreaInsets from "../../../hooks/use-global-safe-area-insets";
|
import useGlobalSafeAreaInsets from "../../../hooks/use-global-safe-area-insets";
|
||||||
import { DDS } from "../../../services/device-detection";
|
import { DDS } from "../../../services/device-detection";
|
||||||
import {
|
import {
|
||||||
|
ToastManager,
|
||||||
eSendEvent,
|
eSendEvent,
|
||||||
eSubscribeEvent,
|
eSubscribeEvent,
|
||||||
eUnSubscribeEvent
|
eUnSubscribeEvent
|
||||||
@@ -46,16 +47,20 @@ import {
|
|||||||
import Navigation from "../../../services/navigation";
|
import Navigation from "../../../services/navigation";
|
||||||
import Notifications from "../../../services/notifications";
|
import Notifications from "../../../services/notifications";
|
||||||
import SettingsService from "../../../services/settings";
|
import SettingsService from "../../../services/settings";
|
||||||
|
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||||
import { useTagStore } from "../../../stores/use-tag-store";
|
import { useTagStore } from "../../../stores/use-tag-store";
|
||||||
import {
|
import {
|
||||||
|
eEditorReset,
|
||||||
eEditorTabFocused,
|
eEditorTabFocused,
|
||||||
eOnLoadNote,
|
eOnLoadNote,
|
||||||
eShowMergeDialog,
|
eShowMergeDialog,
|
||||||
eUpdateNoteInEditor
|
eUpdateNoteInEditor
|
||||||
} from "../../../utils/events";
|
} from "../../../utils/events";
|
||||||
import { tabBarRef } from "../../../utils/global-refs";
|
import { tabBarRef } from "../../../utils/global-refs";
|
||||||
|
import { unlockVault } from "../../../utils/unlock-vault";
|
||||||
import { onNoteCreated } from "../../notes/common";
|
import { onNoteCreated } from "../../notes/common";
|
||||||
import Commands from "./commands";
|
import Commands from "./commands";
|
||||||
|
import { EventTypes } from "./editor-events";
|
||||||
import { SessionHistory } from "./session-history";
|
import { SessionHistory } from "./session-history";
|
||||||
import { EditorState, SavePayload } from "./types";
|
import { EditorState, SavePayload } from "./types";
|
||||||
import { syncTabs, useTabStore } from "./use-tab-store";
|
import { syncTabs, useTabStore } from "./use-tab-store";
|
||||||
@@ -68,6 +73,7 @@ import {
|
|||||||
isEditorLoaded,
|
isEditorLoaded,
|
||||||
post
|
post
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
|
import { sleep } from "../../../utils/time";
|
||||||
|
|
||||||
type NoteWithContent = Note & {
|
type NoteWithContent = Note & {
|
||||||
content?: NoteContent<false>;
|
content?: NoteContent<false>;
|
||||||
@@ -145,8 +151,9 @@ export const useEditor = (
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const event = eSubscribeEvent(eEditorTabFocused, (tabId) => {
|
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;
|
lastTabFocused.current = tabId as number;
|
||||||
console.log(tabId);
|
|
||||||
});
|
});
|
||||||
return () => {
|
return () => {
|
||||||
event?.unsubscribe();
|
event?.unsubscribe();
|
||||||
@@ -165,10 +172,11 @@ export const useEditor = (
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
|
overlay(true);
|
||||||
state.current.ready = false;
|
state.current.ready = false;
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [loading]);
|
}, [loading, overlay]);
|
||||||
|
|
||||||
const withTimer = useCallback(
|
const withTimer = useCallback(
|
||||||
(id: string, fn: () => void, duration: number) => {
|
(id: string, fn: () => void, duration: number) => {
|
||||||
@@ -216,7 +224,8 @@ export const useEditor = (
|
|||||||
type,
|
type,
|
||||||
ignoreEdit,
|
ignoreEdit,
|
||||||
sessionHistoryId: currentSessionHistoryId,
|
sessionHistoryId: currentSessionHistoryId,
|
||||||
tabId
|
tabId,
|
||||||
|
pendingChanges
|
||||||
}: SavePayload) => {
|
}: SavePayload) => {
|
||||||
if (currentNotes.current[id as string]?.readonly || readonly) return;
|
if (currentNotes.current[id as string]?.readonly || readonly) return;
|
||||||
try {
|
try {
|
||||||
@@ -277,12 +286,26 @@ export const useEditor = (
|
|||||||
);
|
);
|
||||||
}, 50);
|
}, 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) {
|
if (!locked) {
|
||||||
DatabaseLogger.log(`Saving note: ${id}...`);
|
DatabaseLogger.log(`Saving note: ${id}...`);
|
||||||
id = await db.notes?.add({ ...noteData });
|
id = await db.notes?.add({ ...noteData });
|
||||||
saved = true;
|
saved = true;
|
||||||
DatabaseLogger.log(`Note saved: ${id}...`);
|
DatabaseLogger.log(`Note saved: ${id}...`);
|
||||||
|
|
||||||
|
clearTimeout(saveTimer);
|
||||||
|
|
||||||
if (!note && id) {
|
if (!note && id) {
|
||||||
editorSessionHistory.newSession(id);
|
editorSessionHistory.newSession(id);
|
||||||
if (id) {
|
if (id) {
|
||||||
@@ -316,9 +339,31 @@ export const useEditor = (
|
|||||||
Notifications.pinNote(id as string);
|
Notifications.pinNote(id as string);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
noteData.contentId = note?.contentId;
|
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");
|
||||||
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
await db.vault?.save(noteData as any);
|
if (typeof noteData.title === "string") {
|
||||||
|
await db.notes.add({
|
||||||
|
title: noteData.title,
|
||||||
|
id: noteData.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
noteData.contentId = note?.contentId;
|
||||||
|
if (data) {
|
||||||
|
await db.vault?.save(noteData as any);
|
||||||
|
}
|
||||||
|
clearTimeout(saveTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id && useTabStore.getState().getTabForNote(id) === tabId) {
|
if (id && useTabStore.getState().getTabForNote(id) === tabId) {
|
||||||
@@ -341,6 +386,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++;
|
saveCount.current++;
|
||||||
return id;
|
return id;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -415,7 +470,11 @@ export const useEditor = (
|
|||||||
commands.focus(tabId);
|
commands.focus(tabId);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (!event.item) return;
|
if (!event.item) {
|
||||||
|
overlay(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("LOADING NOTE", event.item.id);
|
||||||
const item = event.item;
|
const item = event.item;
|
||||||
|
|
||||||
const currentTab = useTabStore
|
const currentTab = useTabStore
|
||||||
@@ -443,7 +502,6 @@ export const useEditor = (
|
|||||||
useTabStore.getState().focusTab(tabId);
|
useTabStore.getState().focusTab(tabId);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (blockIdRef.current) {
|
if (blockIdRef.current) {
|
||||||
console.log("scrolling to block", blockIdRef.current);
|
|
||||||
commands.scrollIntoViewById(blockIdRef.current);
|
commands.scrollIntoViewById(blockIdRef.current);
|
||||||
blockIdRef.current = undefined;
|
blockIdRef.current = undefined;
|
||||||
}
|
}
|
||||||
@@ -502,6 +560,7 @@ export const useEditor = (
|
|||||||
loadingState.current === currentContents.current[item.id]?.data
|
loadingState.current === currentContents.current[item.id]?.data
|
||||||
) {
|
) {
|
||||||
// If note is already loading, return.
|
// If note is already loading, return.
|
||||||
|
console.log("Note is already loading...");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -510,7 +569,7 @@ export const useEditor = (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastContentChangeTime.current[item.id] = item.dateEdited;
|
lastContentChangeTime.current[item.id] = 0;
|
||||||
currentLoadingNoteId.current = item.id;
|
currentLoadingNoteId.current = item.id;
|
||||||
currentNotes.current[item.id] = item;
|
currentNotes.current[item.id] = item;
|
||||||
|
|
||||||
@@ -535,11 +594,8 @@ export const useEditor = (
|
|||||||
10000
|
10000
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log("blockId", blockIdRef.current);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (blockIdRef.current) {
|
if (blockIdRef.current) {
|
||||||
console.log("scrolling to block", blockIdRef.current);
|
|
||||||
commands.scrollIntoViewById(blockIdRef.current);
|
commands.scrollIntoViewById(blockIdRef.current);
|
||||||
blockIdRef.current = undefined;
|
blockIdRef.current = undefined;
|
||||||
}
|
}
|
||||||
@@ -573,124 +629,130 @@ export const useEditor = (
|
|||||||
data: Note | ContentItem | TrashItem | DeletedItem,
|
data: Note | ContentItem | TrashItem | DeletedItem,
|
||||||
isLocal?: boolean
|
isLocal?: boolean
|
||||||
) => {
|
) => {
|
||||||
if (SettingsService.get().disableRealtimeSync && !isLocal) return;
|
try {
|
||||||
if (!data) return;
|
await (async () => {
|
||||||
|
if (SettingsService.get().disableRealtimeSync && !isLocal) return;
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
if (isDeleted(data) || isTrashItem(data)) {
|
if (isDeleted(data) || isTrashItem(data)) {
|
||||||
const tabId = useTabStore.getState().getTabForNote(data.id);
|
const tabId = useTabStore.getState().getTabForNote(data.id);
|
||||||
if (tabId !== undefined) {
|
if (tabId !== undefined) {
|
||||||
console.log("Removing tab");
|
console.log("Removing tab");
|
||||||
await commands.clearContent(tabId);
|
await commands.clearContent(tabId);
|
||||||
useTabStore.getState().removeTab(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);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} 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;
|
return;
|
||||||
}
|
}
|
||||||
lastContentChangeTime.current[note.id] = note.dateEdited;
|
|
||||||
await postMessage(EditorEvents.updatehtml, _nextContent, tabId);
|
const noteId =
|
||||||
if (!isEncryptedContent(data)) {
|
(data as ContentItem).type === "tiptap"
|
||||||
currentContents.current[note.id] = data as UnencryptedContentItem;
|
? (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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
useTabStore.getState().updateTab(tabId, {
|
||||||
|
readonly: note.readonly
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if (data.type === "tiptap" && note && !isLocal) {
|
||||||
|
if (lastContentChangeTime.current[noteId] >= data.dateModified) {
|
||||||
|
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[note.id]?.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]
|
[postMessage, commands]
|
||||||
);
|
);
|
||||||
@@ -713,7 +775,8 @@ export const useEditor = (
|
|||||||
type,
|
type,
|
||||||
ignoreEdit,
|
ignoreEdit,
|
||||||
noteId,
|
noteId,
|
||||||
tabId
|
tabId,
|
||||||
|
pendingChanges
|
||||||
}: {
|
}: {
|
||||||
noteId?: string;
|
noteId?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
@@ -721,18 +784,29 @@ export const useEditor = (
|
|||||||
type: string;
|
type: string;
|
||||||
ignoreEdit: boolean;
|
ignoreEdit: boolean;
|
||||||
tabId: number;
|
tabId: number;
|
||||||
|
pendingChanges?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
DatabaseLogger.log(`Saving content...`);
|
DatabaseLogger.log(
|
||||||
|
`saveContent... title: ${!!title}, content: ${!!content}, noteId: ${noteId}`
|
||||||
|
);
|
||||||
if (
|
if (
|
||||||
lock.current ||
|
lock.current ||
|
||||||
(currentLoadingNoteId.current &&
|
(currentLoadingNoteId.current &&
|
||||||
currentLoadingNoteId.current === noteId)
|
currentLoadingNoteId.current === noteId)
|
||||||
) {
|
) {
|
||||||
DatabaseLogger.log(`Skipped saving conent:
|
DatabaseLogger.log(`Skipped saving content:
|
||||||
|
|
||||||
lock.current: ${lock.current}
|
lock.current: ${lock.current}
|
||||||
currentLoadingNoteId.current: ${currentLoadingNoteId.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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -740,7 +814,7 @@ export const useEditor = (
|
|||||||
lastContentChangeTime.current[noteId] = Date.now();
|
lastContentChangeTime.current[noteId] = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === EditorEvents.content && noteId) {
|
if (type === EventTypes.content && noteId) {
|
||||||
currentContents.current[noteId as string] = {
|
currentContents.current[noteId as string] = {
|
||||||
data: content,
|
data: content,
|
||||||
type: "tiptap",
|
type: "tiptap",
|
||||||
@@ -755,7 +829,8 @@ export const useEditor = (
|
|||||||
id: noteId,
|
id: noteId,
|
||||||
ignoreEdit,
|
ignoreEdit,
|
||||||
sessionHistoryId: noteId ? editorSessionHistory.get(noteId) : undefined,
|
sessionHistoryId: noteId ? editorSessionHistory.get(noteId) : undefined,
|
||||||
tabId: tabId
|
tabId: tabId,
|
||||||
|
pendingChanges
|
||||||
};
|
};
|
||||||
withTimer(
|
withTimer(
|
||||||
noteId || "newnote",
|
noteId || "newnote",
|
||||||
@@ -767,7 +842,16 @@ export const useEditor = (
|
|||||||
onChange(params.data);
|
onChange(params.data);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
saveNote(params);
|
if (useSettingStore.getState().isAppLoading) {
|
||||||
|
const sub = useSettingStore.subscribe((state) => {
|
||||||
|
if (!state.isAppLoading) {
|
||||||
|
saveNote(params);
|
||||||
|
sub();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
saveNote(params);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
ignoreEdit ? 0 : 150
|
ignoreEdit ? 0 : 150
|
||||||
);
|
);
|
||||||
@@ -810,7 +894,7 @@ export const useEditor = (
|
|||||||
useTabStore.getState().currentTab
|
useTabStore.getState().currentTab
|
||||||
))
|
))
|
||||||
) {
|
) {
|
||||||
eSendEvent("webview_reset", "onReady");
|
eSendEvent(eEditorReset, "onReady");
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
syncTabs();
|
syncTabs();
|
||||||
@@ -820,7 +904,6 @@ export const useEditor = (
|
|||||||
}, [isDefaultEditor, restoreEditorState]);
|
}, [isDefaultEditor, restoreEditorState]);
|
||||||
|
|
||||||
const onLoad = useCallback(async () => {
|
const onLoad = useCallback(async () => {
|
||||||
if (currentNotes.current) overlay(true);
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
postMessage(EditorEvents.theme, theme);
|
postMessage(EditorEvents.theme, theme);
|
||||||
});
|
});
|
||||||
@@ -835,12 +918,22 @@ export const useEditor = (
|
|||||||
|
|
||||||
const noteId = useTabStore.getState().getCurrentNoteId();
|
const noteId = useTabStore.getState().getCurrentNoteId();
|
||||||
if (!noteId) {
|
if (!noteId) {
|
||||||
overlay(false);
|
|
||||||
loadNote({ newNote: true });
|
loadNote({ newNote: true });
|
||||||
if (tabBarRef.current?.page === 1) {
|
if (tabBarRef.current?.page() === 1) {
|
||||||
state.current.currentlyEditing = false;
|
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,
|
postMessage,
|
||||||
theme,
|
theme,
|
||||||
|
|||||||
@@ -22,10 +22,15 @@ import { TextInput } from "react-native";
|
|||||||
import WebView from "react-native-webview";
|
import WebView from "react-native-webview";
|
||||||
import { MMKV } from "../../../common/database/mmkv";
|
import { MMKV } from "../../../common/database/mmkv";
|
||||||
import {
|
import {
|
||||||
|
eSendEvent,
|
||||||
eSubscribeEvent,
|
eSubscribeEvent,
|
||||||
eUnSubscribeEvent
|
eUnSubscribeEvent
|
||||||
} from "../../../services/event-manager";
|
} from "../../../services/event-manager";
|
||||||
import { AppState, EditorState, useEditorType } from "./types";
|
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 textInput = createRef<TextInput>();
|
||||||
export const editorController =
|
export const editorController =
|
||||||
createRef<useEditorType>() as MutableRefObject<useEditorType>;
|
createRef<useEditorType>() as MutableRefObject<useEditorType>;
|
||||||
@@ -41,7 +46,7 @@ export function editorState() {
|
|||||||
return editorController.current?.state.current || defaultState;
|
return editorController.current?.state.current || defaultState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EditorEvents: { [name: string]: string } = {
|
export const EditorEvents = {
|
||||||
html: "native:html",
|
html: "native:html",
|
||||||
updatehtml: "native:updatehtml",
|
updatehtml: "native:updatehtml",
|
||||||
title: "native:title",
|
title: "native:title",
|
||||||
@@ -50,7 +55,8 @@ export const EditorEvents: { [name: string]: string } = {
|
|||||||
logger: "native:logger",
|
logger: "native:logger",
|
||||||
status: "native:status",
|
status: "native:status",
|
||||||
keyboardShown: "native:keyboardShown",
|
keyboardShown: "native:keyboardShown",
|
||||||
attachmentData: "native:attachment-data"
|
attachmentData: "native:attachment-data",
|
||||||
|
resolve: "native:resolve"
|
||||||
};
|
};
|
||||||
|
|
||||||
export function randId(prefix: string) {
|
export function randId(prefix: string) {
|
||||||
@@ -176,3 +182,27 @@ export function clearAppState() {
|
|||||||
appState = undefined;
|
appState = undefined;
|
||||||
MMKV.removeItem("appState");
|
MMKV.removeItem("appState");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function openInternalLink(url: string) {
|
||||||
|
const data = parseInternalLink(url);
|
||||||
|
if (!data?.id) return false;
|
||||||
|
if (
|
||||||
|
data.id ===
|
||||||
|
useTabStore.getState().getNoteIdForTab(useTabStore.getState().currentTab)
|
||||||
|
) {
|
||||||
|
if (data.params?.blockId) {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!data.params?.blockId) return;
|
||||||
|
editorController.current.commands.scrollIntoViewById(
|
||||||
|
data.params.blockId
|
||||||
|
);
|
||||||
|
}, 150);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
eSendEvent(eOnLoadNote, {
|
||||||
|
item: await db.notes.note(data?.id),
|
||||||
|
blockId: data.params?.blockId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import { openEditor } from "../notes/common";
|
|||||||
|
|
||||||
export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
|
export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
|
||||||
const [notes, loading] = useNotes();
|
const [notes, loading] = useNotes();
|
||||||
|
|
||||||
const isFocused = useNavigationFocus(navigation, {
|
const isFocused = useNavigationFocus(navigation, {
|
||||||
onFocus: (prev) => {
|
onFocus: (prev) => {
|
||||||
Navigation.routeNeedsUpdate(
|
Navigation.routeNeedsUpdate(
|
||||||
|
|||||||
@@ -16,40 +16,37 @@ GNU General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
import { resolveItems } from "@notesnook/common";
|
||||||
import { VirtualizedGrouping } from "@notesnook/core";
|
import { VirtualizedGrouping } from "@notesnook/core";
|
||||||
import { Note, Notebook } from "@notesnook/core/dist/types";
|
import { Note, Notebook } from "@notesnook/core/dist/types";
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import { View } from "react-native";
|
||||||
import { db } from "../../common/database";
|
import { db } from "../../common/database";
|
||||||
import DelayLayout from "../../components/delay-layout";
|
import DelayLayout from "../../components/delay-layout";
|
||||||
import { Header } from "../../components/header";
|
import { Header } from "../../components/header";
|
||||||
import List from "../../components/list";
|
import List from "../../components/list";
|
||||||
import { NotebookHeader } from "../../components/list-items/headers/notebook-header";
|
import { NotebookHeader } from "../../components/list-items/headers/notebook-header";
|
||||||
|
import SelectionHeader from "../../components/selection-header";
|
||||||
import { AddNotebookSheet } from "../../components/sheets/add-notebook";
|
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 { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||||
import {
|
import { eSendEvent, eSubscribeEvent } from "../../services/event-manager";
|
||||||
eSendEvent,
|
|
||||||
eSubscribeEvent,
|
|
||||||
eUnSubscribeEvent
|
|
||||||
} from "../../services/event-manager";
|
|
||||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||||
import useNavigationStore, {
|
import useNavigationStore, {
|
||||||
NotebookScreenParams
|
NotebookScreenParams
|
||||||
} from "../../stores/use-navigation-store";
|
} from "../../stores/use-navigation-store";
|
||||||
import { eUpdateNotebookRoute } from "../../utils/events";
|
import { eUpdateNotebookRoute } from "../../utils/events";
|
||||||
import { findRootNotebookId } from "../../utils/notebooks";
|
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 { SIZE } from "../../utils/size";
|
||||||
import { IconButton } from "../../components/ui/icon-button";
|
import { openEditor, setOnFirstSave } from "../notes/common";
|
||||||
import { Pressable } from "../../components/ui/pressable";
|
|
||||||
import { resolveItems } from "@notesnook/common";
|
|
||||||
|
|
||||||
const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||||
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
|
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
|
||||||
const params = useRef<NotebookScreenParams>(route?.params);
|
const params = useRef<NotebookScreenParams>(route?.params);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const updateOnFocus = useRef(false);
|
||||||
const [breadcrumbs, setBreadcrumbs] = useState<
|
const [breadcrumbs, setBreadcrumbs] = useState<
|
||||||
{
|
{
|
||||||
id: string;
|
id: string;
|
||||||
@@ -59,11 +56,17 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
|||||||
|
|
||||||
useNavigationFocus(navigation, {
|
useNavigationFocus(navigation, {
|
||||||
onFocus: () => {
|
onFocus: () => {
|
||||||
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
|
if (updateOnFocus.current) {
|
||||||
|
onRequestUpdate();
|
||||||
|
updateOnFocus.current = false;
|
||||||
|
} else {
|
||||||
|
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
|
||||||
|
}
|
||||||
syncWithNavigation();
|
syncWithNavigation();
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
onBlur: () => {
|
onBlur: () => {
|
||||||
|
updateOnFocus.current = false;
|
||||||
setOnFirstSave(null);
|
setOnFirstSave(null);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -79,6 +82,15 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
|||||||
|
|
||||||
const onRequestUpdate = React.useCallback(
|
const onRequestUpdate = React.useCallback(
|
||||||
async (data?: NotebookScreenParams) => {
|
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) {
|
if (data?.item?.id && params.current.item?.id !== data?.item?.id) {
|
||||||
const nextRootNotebookId = await findRootNotebookId(data?.item?.id);
|
const nextRootNotebookId = await findRootNotebookId(data?.item?.id);
|
||||||
const currentNotebookRoot = await findRootNotebookId(
|
const currentNotebookRoot = await findRootNotebookId(
|
||||||
@@ -104,7 +116,7 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
|||||||
|
|
||||||
if (notebook) {
|
if (notebook) {
|
||||||
const breadcrumbs = await db.notebooks.breadcrumbs(notebook.id);
|
const breadcrumbs = await db.notebooks.breadcrumbs(notebook.id);
|
||||||
setBreadcrumbs(breadcrumbs);
|
setBreadcrumbs(breadcrumbs.slice(0, breadcrumbs.length - 1));
|
||||||
params.current.item = notebook;
|
params.current.item = notebook;
|
||||||
const notes = await db.relations
|
const notes = await db.relations
|
||||||
.from(notebook, "note")
|
.from(notebook, "note")
|
||||||
@@ -122,10 +134,10 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onRequestUpdate();
|
onRequestUpdate(params.current);
|
||||||
eSubscribeEvent(eUpdateNotebookRoute, onRequestUpdate);
|
const sub = eSubscribeEvent(eUpdateNotebookRoute, onRequestUpdate);
|
||||||
return () => {
|
return () => {
|
||||||
eUnSubscribeEvent(eUpdateNotebookRoute, onRequestUpdate);
|
sub?.unsubscribe();
|
||||||
};
|
};
|
||||||
}, [onRequestUpdate]);
|
}, [onRequestUpdate]);
|
||||||
|
|
||||||
@@ -167,7 +179,7 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
|||||||
onPressDefaultRightButton={openEditor}
|
onPressDefaultRightButton={openEditor}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{breadcrumbs ? (
|
{breadcrumbs && breadcrumbs.length > 0 ? (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
|
|||||||
@@ -81,16 +81,21 @@ const NotesPage = ({
|
|||||||
route.name === "ColoredNotes"
|
route.name === "ColoredNotes"
|
||||||
? (params.current?.item as Color)?.colorCode
|
? (params.current?.item as Color)?.colorCode
|
||||||
: undefined;
|
: undefined;
|
||||||
|
const updateOnFocus = useRef(false);
|
||||||
const isFocused = useNavigationFocus(navigation, {
|
const isFocused = useNavigationFocus(navigation, {
|
||||||
onFocus: (prev) => {
|
onFocus: (prev) => {
|
||||||
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
|
if (updateOnFocus.current) {
|
||||||
|
onRequestUpdate();
|
||||||
|
updateOnFocus.current = false;
|
||||||
|
} else {
|
||||||
|
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
|
||||||
|
}
|
||||||
syncWithNavigation();
|
syncWithNavigation();
|
||||||
|
|
||||||
if (focusControl) return !prev.current;
|
if (focusControl) return !prev.current;
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
onBlur: () => {
|
onBlur: () => {
|
||||||
|
updateOnFocus.current = false;
|
||||||
setOnFirstSave(null);
|
setOnFirstSave(null);
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
@@ -99,7 +104,6 @@ const NotesPage = ({
|
|||||||
|
|
||||||
const syncWithNavigation = React.useCallback(() => {
|
const syncWithNavigation = React.useCallback(() => {
|
||||||
const { item } = params.current;
|
const { item } = params.current;
|
||||||
|
|
||||||
useNavigationStore
|
useNavigationStore
|
||||||
.getState()
|
.getState()
|
||||||
.setFocusedRouteId(params?.current?.item?.id || route.name);
|
.setFocusedRouteId(params?.current?.item?.id || route.name);
|
||||||
@@ -113,6 +117,14 @@ const NotesPage = ({
|
|||||||
|
|
||||||
const onRequestUpdate = React.useCallback(
|
const onRequestUpdate = React.useCallback(
|
||||||
async (data?: NotesScreenParams) => {
|
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;
|
const isNew = data && data?.item?.id !== params.current?.item?.id;
|
||||||
if (data) params.current = data;
|
if (data) params.current = data;
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React, { useRef } from "react";
|
import React, { useRef } from "react";
|
||||||
import { View } from "react-native";
|
import { Platform, View } from "react-native";
|
||||||
import { TextInput } from "react-native-gesture-handler";
|
import { TextInput } from "react-native-gesture-handler";
|
||||||
import { IconButton } from "../../components/ui/icon-button";
|
import { IconButton } from "../../components/ui/icon-button";
|
||||||
import Navigation from "../../services/navigation";
|
import Navigation from "../../services/navigation";
|
||||||
@@ -49,8 +49,8 @@ export const SearchBar = ({
|
|||||||
return selectionMode && isFocused ? null : (
|
return selectionMode && isFocused ? null : (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
height: 50 + insets.top,
|
height: Platform.OS === "android" ? 50 + insets.top : 50,
|
||||||
paddingTop: insets.top,
|
paddingTop: Platform.OS === "ios" ? 0 : insets.top,
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
flexShrink: 1,
|
flexShrink: 1,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import { Pressable } from "../../components/ui/pressable";
|
|||||||
import Seperator from "../../components/ui/seperator";
|
import Seperator from "../../components/ui/seperator";
|
||||||
import Heading from "../../components/ui/typography/heading";
|
import Heading from "../../components/ui/typography/heading";
|
||||||
import Paragraph from "../../components/ui/typography/paragraph";
|
import Paragraph from "../../components/ui/typography/paragraph";
|
||||||
import BiometicService from "../../services/biometrics";
|
import BiometricService from "../../services/biometrics";
|
||||||
import { ToastManager, presentSheet } from "../../services/event-manager";
|
import { ToastManager, presentSheet } from "../../services/event-manager";
|
||||||
import SettingsService from "../../services/settings";
|
import SettingsService from "../../services/settings";
|
||||||
import { useSettingStore } from "../../stores/use-setting-store";
|
import { useSettingStore } from "../../stores/use-setting-store";
|
||||||
@@ -119,7 +119,7 @@ const AppLock = () => {
|
|||||||
type={appLockMode === item.value ? "secondary" : "transparent"}
|
type={appLockMode === item.value ? "secondary" : "transparent"}
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
if (
|
if (
|
||||||
!(await BiometicService.isBiometryAvailable()) &&
|
!(await BiometricService.isBiometryAvailable()) &&
|
||||||
!useUserStore.getState().user &&
|
!useUserStore.getState().user &&
|
||||||
item.value !== modes[0].value &&
|
item.value !== modes[0].value &&
|
||||||
!SettingsService.getProperty("appLockHasPasswordSecurity")
|
!SettingsService.getProperty("appLockHasPasswordSecurity")
|
||||||
@@ -139,7 +139,7 @@ const AppLock = () => {
|
|||||||
) &&
|
) &&
|
||||||
item.value !== modes[0].value
|
item.value !== modes[0].value
|
||||||
) {
|
) {
|
||||||
const verified = await BiometicService.validateUser(
|
const verified = await BiometricService.validateUser(
|
||||||
"Verify it's you"
|
"Verify it's you"
|
||||||
);
|
);
|
||||||
if (verified) {
|
if (verified) {
|
||||||
|
|||||||
@@ -122,7 +122,8 @@ export default function DebugLogs() {
|
|||||||
colors.primary.paragraph,
|
colors.primary.paragraph,
|
||||||
colors.error.paragraph,
|
colors.error.paragraph,
|
||||||
colors.static.black,
|
colors.static.black,
|
||||||
colors.static.orange
|
colors.static.orange,
|
||||||
|
colors.primary.border
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
import { db } from "../../common/database";
|
import { db } from "../../common/database";
|
||||||
import { validateAppLockPassword } from "../../common/database/encryption";
|
import { validateAppLockPassword } from "../../common/database/encryption";
|
||||||
import { presentDialog } from "../../components/dialog/functions";
|
import { presentDialog } from "../../components/dialog/functions";
|
||||||
import BiometicService from "../../services/biometrics";
|
import BiometricService from "../../services/biometrics";
|
||||||
import { ToastManager } from "../../services/event-manager";
|
import { ToastManager } from "../../services/event-manager";
|
||||||
import SettingsService from "../../services/settings";
|
import SettingsService from "../../services/settings";
|
||||||
import { useUserStore } from "../../stores/use-user-store";
|
import { useUserStore } from "../../stores/use-user-store";
|
||||||
@@ -90,19 +90,32 @@ export async function verifyUserWithApplock() {
|
|||||||
positiveText: "Verify",
|
positiveText: "Verify",
|
||||||
secureTextEntry: true,
|
secureTextEntry: true,
|
||||||
negativeText: "Cancel",
|
negativeText: "Cancel",
|
||||||
|
keyboardType: keyboardType,
|
||||||
positivePress: async (value) => {
|
positivePress: async (value) => {
|
||||||
try {
|
try {
|
||||||
const verified = await validateAppLockPassword(value);
|
const verified = await validateAppLockPassword(value);
|
||||||
|
if (!verified) {
|
||||||
|
ToastManager.show({
|
||||||
|
heading: `Invalid ${
|
||||||
|
keyboardType === "numeric" ? "pin" : "password"
|
||||||
|
}`,
|
||||||
|
type: "error",
|
||||||
|
context: "local"
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
resolve(verified);
|
resolve(verified);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
resolve(false);
|
resolve(false);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
BiometicService.isBiometryAvailable().then((available) => {
|
BiometricService.isBiometryAvailable().then((available) => {
|
||||||
if (available) {
|
if (available) {
|
||||||
BiometicService.validateUser("Verify it's you").then((verified) => {
|
BiometricService.validateUser("Verify it's you").then((verified) => {
|
||||||
resolve(verified);
|
resolve(verified);
|
||||||
});
|
});
|
||||||
} else if (useUserStore.getState().user) {
|
} else if (useUserStore.getState().user) {
|
||||||
|
|||||||
@@ -164,12 +164,6 @@ export const LICENSES = [
|
|||||||
author: "dooboolab",
|
author: "dooboolab",
|
||||||
link: "https://github.com/dooboolab/react-native-iap"
|
link: "https://github.com/dooboolab/react-native-iap"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "react-native-image-picker",
|
|
||||||
licenseType: "MIT",
|
|
||||||
author: "Johan du Toit (Johan-dutoit)",
|
|
||||||
link: "https://github.com/react-native-image-picker/react-native-image-picker"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "react-native-keychain",
|
name: "react-native-keychain",
|
||||||
licenseType: "MIT",
|
licenseType: "MIT",
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import { Update } from "../../components/sheets/update";
|
|||||||
import { VaultStatusType, useVaultStatus } from "../../hooks/use-vault-status";
|
import { VaultStatusType, useVaultStatus } from "../../hooks/use-vault-status";
|
||||||
import { BackgroundSync } from "../../services/background-sync";
|
import { BackgroundSync } from "../../services/background-sync";
|
||||||
import BackupService from "../../services/backup";
|
import BackupService from "../../services/backup";
|
||||||
import BiometicService from "../../services/biometrics";
|
import BiometricService from "../../services/biometrics";
|
||||||
import {
|
import {
|
||||||
ToastManager,
|
ToastManager,
|
||||||
eSendEvent,
|
eSendEvent,
|
||||||
@@ -70,6 +70,7 @@ import { useDragState } from "./editor/state";
|
|||||||
import { verifyUser, verifyUserWithApplock } from "./functions";
|
import { verifyUser, verifyUserWithApplock } from "./functions";
|
||||||
import { SettingSection } from "./types";
|
import { SettingSection } from "./types";
|
||||||
import { getTimeLeft } from "./user-section";
|
import { getTimeLeft } from "./user-section";
|
||||||
|
import ScreenGuardModule from "react-native-screenguard";
|
||||||
|
|
||||||
type User = any;
|
type User = any;
|
||||||
|
|
||||||
@@ -333,12 +334,14 @@ export const settingsGroups: SettingSection[] = [
|
|||||||
{
|
{
|
||||||
id: "logout",
|
id: "logout",
|
||||||
name: "Log out",
|
name: "Log out",
|
||||||
description: "Clear all your data and reset the app.",
|
description:
|
||||||
|
"Logging out will clear all data stored on THIS DEVICE.",
|
||||||
icon: "logout",
|
icon: "logout",
|
||||||
modifer: () => {
|
modifer: () => {
|
||||||
presentDialog({
|
presentDialog({
|
||||||
title: "Logout",
|
title: "Logout",
|
||||||
paragraph: "Clear all your data and reset the app.",
|
paragraph:
|
||||||
|
"Logging out will clear all data stored on THIS DEVICE. Make sure you have synced all your changes before logging out.",
|
||||||
positiveText: "Logout",
|
positiveText: "Logout",
|
||||||
positivePress: async () => {
|
positivePress: async () => {
|
||||||
try {
|
try {
|
||||||
@@ -349,7 +352,7 @@ export const settingsGroups: SettingSection[] = [
|
|||||||
await db.user?.logout();
|
await db.user?.logout();
|
||||||
setLoginMessage();
|
setLoginMessage();
|
||||||
await PremiumService.setPremiumStatus();
|
await PremiumService.setPremiumStatus();
|
||||||
await BiometicService.resetCredentials();
|
await BiometricService.resetCredentials();
|
||||||
MMKV.clearStore();
|
MMKV.clearStore();
|
||||||
clearAllStores();
|
clearAllStores();
|
||||||
refreshAllStores();
|
refreshAllStores();
|
||||||
@@ -394,7 +397,7 @@ export const settingsGroups: SettingSection[] = [
|
|||||||
if (verified) {
|
if (verified) {
|
||||||
eSendEvent("settings-loading", true);
|
eSendEvent("settings-loading", true);
|
||||||
await db.user?.deleteUser(value);
|
await db.user?.deleteUser(value);
|
||||||
await BiometicService.resetCredentials();
|
await BiometricService.resetCredentials();
|
||||||
SettingsService.set({
|
SettingsService.set({
|
||||||
introCompleted: true
|
introCompleted: true
|
||||||
});
|
});
|
||||||
@@ -458,7 +461,7 @@ export const settingsGroups: SettingSection[] = [
|
|||||||
id: "background-sync",
|
id: "background-sync",
|
||||||
name: "Background sync (experimental)",
|
name: "Background sync (experimental)",
|
||||||
description:
|
description:
|
||||||
"Periodically wake up the app in background to sync your notes from other devices.",
|
"Periodically wake up the app in background to run sync.",
|
||||||
type: "switch",
|
type: "switch",
|
||||||
property: "backgroundSync",
|
property: "backgroundSync",
|
||||||
onChange: (value) => {
|
onChange: (value) => {
|
||||||
@@ -470,22 +473,43 @@ export const settingsGroups: SettingSection[] = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "sync-issues-fix",
|
id: "pull-sync",
|
||||||
name: "Having problems with sync",
|
name: "Force pull changes",
|
||||||
description: "Try force sync to resolve issues with syncing",
|
description: `Use this if changes from other devices are not appearing on this device. This will overwrite the data on this device with the latest data from the server.\n\nThis must only be used for troubleshooting. Using it regularly for sync is not recommended and will lead to unexpected data loss and other issues. If you are having persistent issues with sync, please report them to us at support@streetwriters.co.`,
|
||||||
icon: "sync-alert",
|
modifer: () => {
|
||||||
modifer: async () => {
|
|
||||||
presentDialog({
|
presentDialog({
|
||||||
title: "Force sync",
|
title: "Force Pull changes",
|
||||||
paragraph:
|
paragraph:
|
||||||
"If your data on two devices is out of sync even after trying to sync normally. You can run force sync to solve such problems. Usually you should never need to run this otherwise. Force sync means that all your data on this device is reuploaded to the server.",
|
"This must only be used for troubleshooting. Using this regularly for sync is not recommended and will lead to unexpected data loss and other issues. If you are having persistent issues with sync, please report them to us at support@streetwriters.co.",
|
||||||
negativeText: "Cancel",
|
negativeText: "Cancel",
|
||||||
positiveText: "Start",
|
positiveText: "Start",
|
||||||
positivePress: async () => {
|
positivePress: async () => {
|
||||||
eSendEvent(eCloseSheet);
|
eSendEvent(eCloseSheet);
|
||||||
await sleep(300);
|
await sleep(300);
|
||||||
Progress.present();
|
Progress.present();
|
||||||
Sync.run("global", true, true, () => {
|
Sync.run("global", true, "fetch", () => {
|
||||||
|
eSendEvent(eCloseSheet);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "push-sync",
|
||||||
|
name: "Force push changes",
|
||||||
|
description: `Use this if changes made on this device are not appearing on other devices. This will overwrite the data on the server with the data from this device.\n\nThis must only be used for troubleshooting. Using it regularly for sync is not recommended and will lead to unexpected data loss and other issues. If you are having persistent issues with sync, please report them to us at support@streetwriters.co.`,
|
||||||
|
modifer: () => {
|
||||||
|
presentDialog({
|
||||||
|
title: "Force Push changes",
|
||||||
|
paragraph:
|
||||||
|
"This must only be used for troubleshooting. Using this regularly for sync is not recommended and will lead to unexpected data loss and other issues. If you are having persistent issues with sync, please report them to us at support@streetwriters.co.",
|
||||||
|
negativeText: "Cancel",
|
||||||
|
positiveText: "Start",
|
||||||
|
positivePress: async () => {
|
||||||
|
eSendEvent(eCloseSheet);
|
||||||
|
await sleep(300);
|
||||||
|
Progress.present();
|
||||||
|
Sync.run("global", true, "send", () => {
|
||||||
eSendEvent(eCloseSheet);
|
eSendEvent(eCloseSheet);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -789,7 +813,7 @@ export const settingsGroups: SettingSection[] = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "biometic-unlock",
|
id: "biometric-unlock",
|
||||||
type: "switch",
|
type: "switch",
|
||||||
name: "Biometric unlocking",
|
name: "Biometric unlocking",
|
||||||
icon: "fingerprint",
|
icon: "fingerprint",
|
||||||
@@ -827,9 +851,18 @@ export const settingsGroups: SettingSection[] = [
|
|||||||
"Hide app contents when you switch to other apps. This will also disable screenshot taking in the app.",
|
"Hide app contents when you switch to other apps. This will also disable screenshot taking in the app.",
|
||||||
modifer: () => {
|
modifer: () => {
|
||||||
const settings = SettingsService.get();
|
const settings = SettingsService.get();
|
||||||
Platform.OS === "android"
|
if (Platform.OS === "ios") {
|
||||||
? NotesnookModule.setSecureMode(!settings.privacyScreen)
|
enabled(!settings.privacyScreen);
|
||||||
: enabled(true);
|
if (settings.privacyScreen) {
|
||||||
|
ScreenGuardModule.unregister();
|
||||||
|
} else {
|
||||||
|
ScreenGuardModule.register({
|
||||||
|
backgroundColor: "#000000"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NotesnookModule.setSecureMode(!settings.privacyScreen);
|
||||||
|
}
|
||||||
|
|
||||||
SettingsService.set({ privacyScreen: !settings.privacyScreen });
|
SettingsService.set({ privacyScreen: !settings.privacyScreen });
|
||||||
},
|
},
|
||||||
@@ -850,6 +883,9 @@ export const settingsGroups: SettingSection[] = [
|
|||||||
type: "switch",
|
type: "switch",
|
||||||
property: "appLockEnabled",
|
property: "appLockEnabled",
|
||||||
onChange: () => {
|
onChange: () => {
|
||||||
|
SettingsService.set({
|
||||||
|
privacyScreen: true
|
||||||
|
});
|
||||||
SettingsService.setPrivacyScreen(SettingsService.get());
|
SettingsService.setPrivacyScreen(SettingsService.get());
|
||||||
},
|
},
|
||||||
onVerify: async () => {
|
onVerify: async () => {
|
||||||
@@ -859,13 +895,13 @@ export const settingsGroups: SettingSection[] = [
|
|||||||
if (!SettingsService.getProperty("appLockEnabled")) {
|
if (!SettingsService.getProperty("appLockEnabled")) {
|
||||||
if (
|
if (
|
||||||
!SettingsService.getProperty("appLockHasPasswordSecurity") &&
|
!SettingsService.getProperty("appLockHasPasswordSecurity") &&
|
||||||
(await BiometicService.isBiometryAvailable())
|
(await BiometricService.isBiometryAvailable())
|
||||||
) {
|
) {
|
||||||
SettingsService.setProperty("biometricsAuthEnabled", true);
|
SettingsService.setProperty("biometricsAuthEnabled", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!(await BiometicService.isBiometryAvailable()) &&
|
!(await BiometricService.isBiometryAvailable()) &&
|
||||||
!SettingsService.getProperty("appLockHasPasswordSecurity")
|
!SettingsService.getProperty("appLockHasPasswordSecurity")
|
||||||
) {
|
) {
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
@@ -952,7 +988,7 @@ export const settingsGroups: SettingSection[] = [
|
|||||||
SettingsService.getProperty("applockKeyboardType") === "numeric"
|
SettingsService.getProperty("applockKeyboardType") === "numeric"
|
||||||
? "pin"
|
? "pin"
|
||||||
: "password"
|
: "password"
|
||||||
}, app lock will fallback to using account password to unlock the app`,
|
}, app lock will be disabled if no other security method is enabled.`,
|
||||||
hidden: () => {
|
hidden: () => {
|
||||||
return !SettingsService.getProperty("appLockHasPasswordSecurity");
|
return !SettingsService.getProperty("appLockHasPasswordSecurity");
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -141,7 +141,6 @@ const onBoot = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await Notifications.setupReminders();
|
await Notifications.setupReminders();
|
||||||
SettingsService.init();
|
|
||||||
if (SettingsService.get().notifNotes) {
|
if (SettingsService.get().notifNotes) {
|
||||||
Notifications.pinQuickNote(false);
|
Notifications.pinQuickNote(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,13 +151,14 @@ async function updateNextBackupTime() {
|
|||||||
/**
|
/**
|
||||||
* @param {boolean=} progress
|
* @param {boolean=} progress
|
||||||
* @param {string=} context
|
* @param {string=} context
|
||||||
* @returns {Promise<{path?: string, error?: Error}}>
|
* @returns {Promise<{path?: string, error?: Error, report?: boolean}}>
|
||||||
*/
|
*/
|
||||||
async function run(progress = false, context) {
|
async function run(progress = false, context) {
|
||||||
let androidBackupDirectory = await checkBackupDirExists(false, context);
|
let androidBackupDirectory = await checkBackupDirExists(false, context);
|
||||||
if (!androidBackupDirectory)
|
if (!androidBackupDirectory)
|
||||||
return {
|
return {
|
||||||
error: new Error("Backup directory not selected")
|
error: new Error("Backup directory not selected"),
|
||||||
|
report: false
|
||||||
};
|
};
|
||||||
|
|
||||||
if (progress) {
|
if (progress) {
|
||||||
@@ -170,7 +171,6 @@ async function run(progress = false, context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let path;
|
let path;
|
||||||
let backupFilePath;
|
|
||||||
let backupFileName = "notesnook_backup_" + Date.now();
|
let backupFileName = "notesnook_backup_" + Date.now();
|
||||||
|
|
||||||
if (Platform.OS === "ios") {
|
if (Platform.OS === "ios") {
|
||||||
@@ -191,9 +191,10 @@ async function run(progress = false, context) {
|
|||||||
await RNFetchBlob.fs.mkdir(zipSourceFolder);
|
await RNFetchBlob.fs.mkdir(zipSourceFolder);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const user = await db.user.getUser();
|
||||||
for await (const file of db.backup.export(
|
for await (const file of db.backup.export(
|
||||||
"mobile",
|
"mobile",
|
||||||
SettingsService.get().encryptedBackup
|
SettingsService.get().encryptedBackup && user
|
||||||
)) {
|
)) {
|
||||||
console.log("Writing backup chunk of size...", file?.data?.length);
|
console.log("Writing backup chunk of size...", file?.data?.length);
|
||||||
await RNFetchBlob.fs.writeFile(
|
await RNFetchBlob.fs.writeFile(
|
||||||
@@ -234,7 +235,7 @@ async function run(progress = false, context) {
|
|||||||
await sleep(300);
|
await sleep(300);
|
||||||
|
|
||||||
if (showBackupCompleteSheet) {
|
if (showBackupCompleteSheet) {
|
||||||
presentBackupCompleteSheet(backupFilePath);
|
presentBackupCompleteSheet(path);
|
||||||
} else {
|
} else {
|
||||||
progress && eSendEvent(eCloseSheet);
|
progress && eSendEvent(eCloseSheet);
|
||||||
}
|
}
|
||||||
@@ -257,11 +258,12 @@ async function run(progress = false, context) {
|
|||||||
return run(progress, context);
|
return run(progress, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
DatabaseLogger.error(e, "Backup failed");
|
DatabaseLogger.error(e);
|
||||||
await sleep(300);
|
await sleep(300);
|
||||||
progress && eSendEvent(eCloseSheet);
|
progress && eSendEvent(eCloseSheet);
|
||||||
return {
|
return {
|
||||||
error: e
|
error: e,
|
||||||
|
report: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ async function validateUser(title: string, description?: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const BiometicService = {
|
const BiometricService = {
|
||||||
isBiometryAvailable,
|
isBiometryAvailable,
|
||||||
enableFingerprintAuth,
|
enableFingerprintAuth,
|
||||||
isFingerprintAuthEnabled,
|
isFingerprintAuthEnabled,
|
||||||
@@ -190,4 +190,4 @@ const BiometicService = {
|
|||||||
validateUser
|
validateUser
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BiometicService;
|
export default BiometricService;
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ export const ToastManager = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
hide: () => eSendEvent(eHideToast),
|
hide: () => eSendEvent(eHideToast),
|
||||||
error: (e: Error, title?: string, context?: any) => {
|
error: (e: Error, title?: string, context?: any, duration = 5000) => {
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
heading: title,
|
heading: title,
|
||||||
message: e?.message || "",
|
message: e?.message || "",
|
||||||
@@ -176,7 +176,7 @@ export const ToastManager = {
|
|||||||
heading: "Logs copied!",
|
heading: "Logs copied!",
|
||||||
type: "success",
|
type: "success",
|
||||||
context: "global",
|
context: "global",
|
||||||
duration: 5000
|
duration: duration
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,40 +17,26 @@ You should have received a copy of the GNU General Public License
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { decode, EntityLevel } from "entities";
|
|
||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
import RNFetchBlob from "react-native-blob-util";
|
import RNFetchBlob from "react-native-blob-util";
|
||||||
import RNHTMLtoPDF from "react-native-html-to-pdf-lite";
|
import RNHTMLtoPDF from "react-native-html-to-pdf-lite";
|
||||||
import * as ScopedStorage from "react-native-scoped-storage";
|
import * as ScopedStorage from "react-native-scoped-storage";
|
||||||
import { zip } from "react-native-zip-archive";
|
import { zip } from "react-native-zip-archive";
|
||||||
import { DatabaseLogger, db } from "../common/database/index";
|
import { DatabaseLogger } from "../common/database/index";
|
||||||
import Storage from "../common/database/storage";
|
import Storage from "../common/database/storage";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
exportNote as _exportNote,
|
exportNote as _exportNote,
|
||||||
ExportableAttachment,
|
ExportableAttachment,
|
||||||
ExportableNote,
|
ExportableNote,
|
||||||
exportNotes,
|
exportNotes
|
||||||
sanitizeFilename
|
|
||||||
} from "@notesnook/common";
|
} from "@notesnook/common";
|
||||||
import { Note } from "@notesnook/core";
|
import { Note } from "@notesnook/core";
|
||||||
import { NoteContent } from "@notesnook/core/dist/collections/session-content";
|
|
||||||
import { FilteredSelector } from "@notesnook/core/dist/database/sql-collection";
|
import { FilteredSelector } from "@notesnook/core/dist/database/sql-collection";
|
||||||
import { basename, dirname, join } from "pathe";
|
import { basename, dirname, join } from "pathe";
|
||||||
import downloadAttachment from "../common/filesystem/download-attachment";
|
import downloadAttachment from "../common/filesystem/download-attachment";
|
||||||
import { presentDialog } from "../components/dialog/functions";
|
|
||||||
import { useSettingStore } from "../stores/use-setting-store";
|
|
||||||
import BiometicService from "./biometrics";
|
|
||||||
import { ToastManager } from "./event-manager";
|
|
||||||
import { cacheDir } from "../common/filesystem/utils";
|
import { cacheDir } from "../common/filesystem/utils";
|
||||||
|
import { unlockVault } from "../utils/unlock-vault";
|
||||||
const MIMETypes = {
|
|
||||||
txt: "text/plain",
|
|
||||||
pdf: "application/pdf",
|
|
||||||
md: "text/markdown",
|
|
||||||
"md-frontmatter": "text/markdown",
|
|
||||||
html: "text/html"
|
|
||||||
};
|
|
||||||
|
|
||||||
const FolderNames: { [name: string]: string } = {
|
const FolderNames: { [name: string]: string } = {
|
||||||
txt: "Text",
|
txt: "Text",
|
||||||
@@ -59,16 +45,6 @@ const FolderNames: { [name: string]: string } = {
|
|||||||
html: "Html"
|
html: "Html"
|
||||||
};
|
};
|
||||||
|
|
||||||
async function releasePermissions(path: string) {
|
|
||||||
if (Platform.OS === "ios") return;
|
|
||||||
const uris = await ScopedStorage.getPersistedUriPermissions();
|
|
||||||
for (const uri of uris) {
|
|
||||||
if (path.startsWith(uri)) {
|
|
||||||
await ScopedStorage.releasePersistableUriPermission(uri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getPath(type: string) {
|
async function getPath(type: string) {
|
||||||
let path =
|
let path =
|
||||||
Platform.OS === "ios" &&
|
Platform.OS === "ios" &&
|
||||||
@@ -82,153 +58,11 @@ async function getPath(type: string) {
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function save(
|
async function unlockVaultForNoteExport() {
|
||||||
path: string,
|
return await unlockVault({
|
||||||
data: string,
|
title: "Unlock vault",
|
||||||
fileName: string,
|
paragraph: "Some exported notes are locked, Unlock to export them",
|
||||||
extension: "txt" | "pdf" | "md" | "html" | "md-frontmatter"
|
context: "export-notes"
|
||||||
) {
|
|
||||||
let uri;
|
|
||||||
if (Platform.OS === "android") {
|
|
||||||
uri = await ScopedStorage.writeFile(
|
|
||||||
path,
|
|
||||||
data,
|
|
||||||
`${fileName}.${extension}`,
|
|
||||||
MIMETypes[extension],
|
|
||||||
extension === "pdf" ? "base64" : "utf8",
|
|
||||||
false
|
|
||||||
);
|
|
||||||
await releasePermissions(path);
|
|
||||||
} else {
|
|
||||||
path = path + fileName + `.${extension}`;
|
|
||||||
await RNFetchBlob.fs.writeFile(path, data, "utf8");
|
|
||||||
}
|
|
||||||
return uri || path;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function makeHtml(note: Note, content?: NoteContent<false>) {
|
|
||||||
let html = await db.notes.export(note.id, {
|
|
||||||
format: "html",
|
|
||||||
contentItem: content
|
|
||||||
});
|
|
||||||
if (!html) return "";
|
|
||||||
|
|
||||||
html = decode(html, {
|
|
||||||
level: EntityLevel.HTML
|
|
||||||
});
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function exportAs(
|
|
||||||
type: string,
|
|
||||||
note: Note,
|
|
||||||
bulk?: boolean,
|
|
||||||
content?: NoteContent<false>
|
|
||||||
) {
|
|
||||||
let data;
|
|
||||||
switch (type) {
|
|
||||||
case "html":
|
|
||||||
{
|
|
||||||
data = await makeHtml(note, content);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "md":
|
|
||||||
data = await db.notes.export(note.id, {
|
|
||||||
format: "md",
|
|
||||||
contentItem: content
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "md-frontmatter":
|
|
||||||
data = await db.notes.export(note.id, {
|
|
||||||
format: "md-frontmatter",
|
|
||||||
contentItem: content
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "pdf":
|
|
||||||
{
|
|
||||||
const html = await makeHtml(note, content);
|
|
||||||
const fileName = sanitizeFilename(note.title + Date.now(), {
|
|
||||||
replacement: "_"
|
|
||||||
});
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
html: html,
|
|
||||||
fileName:
|
|
||||||
Platform.OS === "ios" ? "/exported/PDF/" + fileName : fileName,
|
|
||||||
width: 595,
|
|
||||||
height: 852,
|
|
||||||
bgColor: "#FFFFFF",
|
|
||||||
padding: 30,
|
|
||||||
base64: bulk || Platform.OS === "android"
|
|
||||||
} as { [name: string]: any };
|
|
||||||
|
|
||||||
if (Platform.OS === "ios") {
|
|
||||||
options.directory = "Documents";
|
|
||||||
}
|
|
||||||
const res = await RNHTMLtoPDF.convert(options);
|
|
||||||
data = !bulk && Platform.OS === "ios" ? res.filePath : res.base64;
|
|
||||||
if (bulk && res.filePath) {
|
|
||||||
RNFetchBlob.fs.unlink(res.filePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "txt":
|
|
||||||
{
|
|
||||||
data = await db.notes.export(note.id, {
|
|
||||||
format: "txt",
|
|
||||||
contentItem: content
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function unlockVault() {
|
|
||||||
const biometry = await BiometicService.isBiometryAvailable();
|
|
||||||
const fingerprint = await BiometicService.hasInternetCredentials();
|
|
||||||
if (biometry && fingerprint) {
|
|
||||||
const credentials = await BiometicService.getCredentials(
|
|
||||||
"Unlock vault",
|
|
||||||
"Unlock vault to export locked notes"
|
|
||||||
);
|
|
||||||
if (credentials) {
|
|
||||||
return db.vault.unlock(credentials.password);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
useSettingStore.getState().setSheetKeyboardHandler(false);
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
setImmediate(() => {
|
|
||||||
presentDialog({
|
|
||||||
context: "export-notes",
|
|
||||||
input: true,
|
|
||||||
secureTextEntry: true,
|
|
||||||
positiveText: "Unlock",
|
|
||||||
title: "Unlock vault",
|
|
||||||
paragraph: "Some exported notes are locked, Unlock to export them",
|
|
||||||
inputPlaceholder: "Enter password",
|
|
||||||
positivePress: async (value) => {
|
|
||||||
const unlocked = await db.vault.unlock(value);
|
|
||||||
if (!unlocked) {
|
|
||||||
ToastManager.show({
|
|
||||||
heading: "Invalid password",
|
|
||||||
message: "Please enter a valid password",
|
|
||||||
type: "error",
|
|
||||||
context: "local"
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
resolve(unlocked);
|
|
||||||
useSettingStore.getState().setSheetKeyboardHandler(true);
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
onClose: () => {
|
|
||||||
resolve(false);
|
|
||||||
useSettingStore.getState().setSheetKeyboardHandler(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,6 +119,7 @@ async function createZip(
|
|||||||
callback: (progress?: string) => void
|
callback: (progress?: string) => void
|
||||||
) {
|
) {
|
||||||
const fileName = `nn-export-${totalNotes}-${type}-${Date.now()}.zip`;
|
const fileName = `nn-export-${totalNotes}-${type}-${Date.now()}.zip`;
|
||||||
|
const dir = path;
|
||||||
try {
|
try {
|
||||||
callback("Creating zip");
|
callback("Creating zip");
|
||||||
const zipOutputPath =
|
const zipOutputPath =
|
||||||
@@ -292,6 +127,7 @@ async function createZip(
|
|||||||
? join(path, fileName)
|
? join(path, fileName)
|
||||||
: join(RNFetchBlob.fs.dirs.CacheDir, fileName);
|
: join(RNFetchBlob.fs.dirs.CacheDir, fileName);
|
||||||
await zip(cacheFolder, zipOutputPath);
|
await zip(cacheFolder, zipOutputPath);
|
||||||
|
|
||||||
callback("Saving zip file");
|
callback("Saving zip file");
|
||||||
if (Platform.OS === "android") {
|
if (Platform.OS === "android") {
|
||||||
const file = await ScopedStorage.createFile(
|
const file = await ScopedStorage.createFile(
|
||||||
@@ -303,6 +139,8 @@ async function createZip(
|
|||||||
await copyFileAsync("file://" + zipOutputPath, path);
|
await copyFileAsync("file://" + zipOutputPath, path);
|
||||||
await RNFetchBlob.fs.unlink(zipOutputPath);
|
await RNFetchBlob.fs.unlink(zipOutputPath);
|
||||||
callback();
|
callback();
|
||||||
|
} else {
|
||||||
|
path = zipOutputPath;
|
||||||
}
|
}
|
||||||
RNFetchBlob.fs.unlink(cacheFolder);
|
RNFetchBlob.fs.unlink(cacheFolder);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -311,6 +149,7 @@ async function createZip(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
filePath: path,
|
filePath: path,
|
||||||
|
fileDir: dir,
|
||||||
type: "application/zip",
|
type: "application/zip",
|
||||||
name: "zip",
|
name: "zip",
|
||||||
fileName: fileName,
|
fileName: fileName,
|
||||||
@@ -388,7 +227,7 @@ async function bulkExport(
|
|||||||
let currentAttachmentProgress = 0;
|
let currentAttachmentProgress = 0;
|
||||||
for await (const item of exportNotes(notes, {
|
for await (const item of exportNotes(notes, {
|
||||||
format: type,
|
format: type,
|
||||||
unlockVault: unlockVault as () => Promise<boolean>
|
unlockVault: unlockVaultForNoteExport as () => Promise<boolean>
|
||||||
})) {
|
})) {
|
||||||
if (item instanceof Error) {
|
if (item instanceof Error) {
|
||||||
DatabaseLogger.error(item);
|
DatabaseLogger.error(item);
|
||||||
@@ -432,7 +271,7 @@ async function exportNote(
|
|||||||
let currentAttachmentProgress = 0;
|
let currentAttachmentProgress = 0;
|
||||||
for await (const item of _exportNote(note, {
|
for await (const item of _exportNote(note, {
|
||||||
format: type,
|
format: type,
|
||||||
unlockVault: unlockVault as () => Promise<boolean>
|
unlockVault: unlockVaultForNoteExport as () => Promise<boolean>
|
||||||
})) {
|
})) {
|
||||||
if (item instanceof Error) {
|
if (item instanceof Error) {
|
||||||
DatabaseLogger.error(item);
|
DatabaseLogger.error(item);
|
||||||
|
|||||||
@@ -59,14 +59,14 @@ const recoveryKeyMessage = {
|
|||||||
() => {
|
() => {
|
||||||
eSendEvent(eOpenRecoveryKeyDialog);
|
eSendEvent(eOpenRecoveryKeyDialog);
|
||||||
},
|
},
|
||||||
true,
|
false,
|
||||||
async () => {
|
async () => {
|
||||||
SettingsService.set({
|
SettingsService.set({
|
||||||
recoveryKeySaved: true
|
recoveryKeySaved: true
|
||||||
});
|
});
|
||||||
clearMessage();
|
clearMessage();
|
||||||
},
|
},
|
||||||
"I have saved my key already"
|
"Cancel"
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
data: {},
|
data: {},
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ import { DDS } from "./device-detection";
|
|||||||
import { eSendEvent } from "./event-manager";
|
import { eSendEvent } from "./event-manager";
|
||||||
import Navigation from "./navigation";
|
import Navigation from "./navigation";
|
||||||
import SettingsService from "./settings";
|
import SettingsService from "./settings";
|
||||||
|
import { useUserStore } from "../stores/use-user-store";
|
||||||
|
import { useTabStore } from "../screens/editor/tiptap/use-tab-store";
|
||||||
|
|
||||||
let pinned: DisplayedNotification[] = [];
|
let pinned: DisplayedNotification[] = [];
|
||||||
|
|
||||||
@@ -139,6 +141,7 @@ const onEvent = async ({ type, detail }: Event) => {
|
|||||||
}
|
}
|
||||||
editorState().movedAway = false;
|
editorState().movedAway = false;
|
||||||
const noteId = notification?.id;
|
const noteId = notification?.id;
|
||||||
|
console.log("NOTE ID", noteId);
|
||||||
loadNote(noteId as string, true);
|
loadNote(noteId as string, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,18 +410,31 @@ async function loadNote(id: string, jump: boolean) {
|
|||||||
if (!DDS.isTab && jump) {
|
if (!DDS.isTab && jump) {
|
||||||
tabBarRef.current?.goToPage(1);
|
tabBarRef.current?.goToPage(1);
|
||||||
}
|
}
|
||||||
eSendEvent("loadingNote", note);
|
|
||||||
setTimeout(
|
MMKV.setString(
|
||||||
() => {
|
"appState",
|
||||||
eSendEvent(eOnLoadNote, {
|
JSON.stringify({
|
||||||
item: note
|
editing: true,
|
||||||
});
|
movedAway: false,
|
||||||
if (!jump && !DDS.isTab) {
|
timestamp: Date.now()
|
||||||
tabBarRef.current?.goToPage(1);
|
})
|
||||||
}
|
|
||||||
},
|
|
||||||
tabBarRef?.current ? 0 : 2000
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isLocked = await db.vaults.itemExists({
|
||||||
|
type: "note",
|
||||||
|
id: id
|
||||||
|
});
|
||||||
|
|
||||||
|
const tab = useTabStore.getState().getTabForNote(id);
|
||||||
|
if (tab !== undefined) {
|
||||||
|
useTabStore.getState().focusTab(tab);
|
||||||
|
} else {
|
||||||
|
useTabStore.getState().focusPreviewTab(id, {
|
||||||
|
noteId: id,
|
||||||
|
readonly: note.readonly,
|
||||||
|
noteLocked: isLocked
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getChannelId(id: "silent" | "vibrate" | "urgent" | "default") {
|
async function getChannelId(id: "silent" | "vibrate" | "urgent" | "default") {
|
||||||
@@ -476,7 +492,15 @@ async function displayNotification({
|
|||||||
reply_button_text?: string;
|
reply_button_text?: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
}) {
|
}) {
|
||||||
if (!(await checkAndRequestPermissions())) return;
|
useUserStore.setState({
|
||||||
|
disableAppLockRequests: true
|
||||||
|
});
|
||||||
|
const permission = await checkAndRequestPermissions();
|
||||||
|
useUserStore.setState({
|
||||||
|
disableAppLockRequests: false
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!permission) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await notifee.displayNotification({
|
await notifee.displayNotification({
|
||||||
@@ -846,7 +870,16 @@ async function remove(id: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function pinQuickNote(launch: boolean) {
|
async function pinQuickNote(launch: boolean) {
|
||||||
if (!(await checkAndRequestPermissions())) return;
|
useUserStore.setState({
|
||||||
|
disableAppLockRequests: true
|
||||||
|
});
|
||||||
|
const permission = await checkAndRequestPermissions();
|
||||||
|
useUserStore.setState({
|
||||||
|
disableAppLockRequests: false
|
||||||
|
});
|
||||||
|
if (!permission) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
get().then((items) => {
|
get().then((items) => {
|
||||||
const notification = items.filter((n) => n.id === "notesnook_note_input");
|
const notification = items.filter((n) => n.id === "notesnook_note_input");
|
||||||
if (notification && launch) {
|
if (notification && launch) {
|
||||||
@@ -919,13 +952,13 @@ async function pinNote(id: string) {
|
|||||||
const note = await db.notes.note(id as string);
|
const note = await db.notes.note(id as string);
|
||||||
if (!note) return;
|
if (!note) return;
|
||||||
|
|
||||||
let text = await convertNoteToText(note, false);
|
let text = await convertNoteToText(note, true);
|
||||||
if (!text) text = "";
|
if (!text) text = "";
|
||||||
const html = text.replace(/\n/g, "<br />");
|
const html = text.replace(/\n/g, "<br />");
|
||||||
Notifications.displayNotification({
|
Notifications.displayNotification({
|
||||||
title: note.title,
|
title: note.title,
|
||||||
message: note.headline || text,
|
message: note.headline || text,
|
||||||
subtitle: note.headline || text,
|
subtitle: "",
|
||||||
bigText: html,
|
bigText: html,
|
||||||
ongoing: true,
|
ongoing: true,
|
||||||
actions: ["UNPIN"],
|
actions: ["UNPIN"],
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user