mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-29 10:09:26 +02:00
Compare commits
422 Commits
fix/delete
...
fix-defaul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53baa1a852 | ||
|
|
c54759e766 | ||
|
|
f15446b1d6 | ||
|
|
aa2a8ab6f4 | ||
|
|
8e59ff1318 | ||
|
|
ad99c87f72 | ||
|
|
ba776b5854 | ||
|
|
e526145e48 | ||
|
|
45c5e65bf6 | ||
|
|
87bec4e2ab | ||
|
|
433e67a6dc | ||
|
|
6acba51b88 | ||
|
|
cf1322952d | ||
|
|
b5ac9dd430 | ||
|
|
a3b590bcb7 | ||
|
|
d4f7e3ef07 | ||
|
|
16119f6958 | ||
|
|
e39c2583a5 | ||
|
|
eecff009dd | ||
|
|
d9ed61a7da | ||
|
|
58db265702 | ||
|
|
bd39f0cbf8 | ||
|
|
255015108a | ||
|
|
b80f366f8d | ||
|
|
c845c7fce6 | ||
|
|
6e7b7ce0e1 | ||
|
|
9b728902d4 | ||
|
|
9a1f54c41a | ||
|
|
b3251eb016 | ||
|
|
f4064b7939 | ||
|
|
f014f08ff4 | ||
|
|
dee1cf4b5a | ||
|
|
f35a3d8fa3 | ||
|
|
ca9a8d14fa | ||
|
|
35c35fa9d7 | ||
|
|
09fff95d58 | ||
|
|
287307a1ba | ||
|
|
d628e62302 | ||
|
|
a070bc46ad | ||
|
|
9027c44511 | ||
|
|
722aa7b9c6 | ||
|
|
a4e777b24b | ||
|
|
af7120e953 | ||
|
|
d5adc3bbac | ||
|
|
aa3d778ad1 | ||
|
|
3465a816c6 | ||
|
|
3d1726c5aa | ||
|
|
488de2d583 | ||
|
|
c415b114a6 | ||
|
|
da36784e9c | ||
|
|
78fafdc272 | ||
|
|
7d57055f73 | ||
|
|
b84cc747cb | ||
|
|
9a4f3ccbad | ||
|
|
68de3cc928 | ||
|
|
6e135cdd36 | ||
|
|
696116ac85 | ||
|
|
08a13ff35a | ||
|
|
af1cea81dc | ||
|
|
2c22a7ca90 | ||
|
|
6fd6bd0375 | ||
|
|
1a96dc965a | ||
|
|
9550350f25 | ||
|
|
0ed605a5b9 | ||
|
|
18910d931c | ||
|
|
87648b7d8b | ||
|
|
7e7a1aeecb | ||
|
|
cb353358b7 | ||
|
|
ba1619711b | ||
|
|
b3cc9475ad | ||
|
|
ee5c4077fe | ||
|
|
47bbfc618e | ||
|
|
b377329a8b | ||
|
|
ae8bcf3865 | ||
|
|
2f67611829 | ||
|
|
058c612f4f | ||
|
|
be8df90ccb | ||
|
|
aa730b5b93 | ||
|
|
34bbcf6387 | ||
|
|
33c3c78e2f | ||
|
|
bf144dcda4 | ||
|
|
284ebdb86f | ||
|
|
9dac4cf7f4 | ||
|
|
fb973a5514 | ||
|
|
f9226510f9 | ||
|
|
5c0a964699 | ||
|
|
41ffaab3dc | ||
|
|
764c721f1d | ||
|
|
3d267f91ef | ||
|
|
f10f28a7f8 | ||
|
|
8d04f89a18 | ||
|
|
9d1374dcd4 | ||
|
|
094a65e514 | ||
|
|
372da979e1 | ||
|
|
d3371326f2 | ||
|
|
57452bd0e6 | ||
|
|
993b8460bf | ||
|
|
081201aa42 | ||
|
|
270e6778d1 | ||
|
|
2074320583 | ||
|
|
cd90030616 | ||
|
|
807bd683b7 | ||
|
|
35929026a4 | ||
|
|
41eb94ee47 | ||
|
|
ba51e110ad | ||
|
|
ecad06d249 | ||
|
|
19a151f637 | ||
|
|
7c4a6f1e17 | ||
|
|
bc88e953d1 | ||
|
|
7d89ec676a | ||
|
|
c8f8bcca93 | ||
|
|
9fa904e849 | ||
|
|
05cc947cb4 | ||
|
|
a20a683b17 | ||
|
|
5fc43c931e | ||
|
|
0879c3b4d6 | ||
|
|
2eded71de3 | ||
|
|
9e30ee50b8 | ||
|
|
08621a5873 | ||
|
|
17f5bd28ba | ||
|
|
4558c0b2eb | ||
|
|
a442677e91 | ||
|
|
95666bee36 | ||
|
|
c4232ac95a | ||
|
|
0283037289 | ||
|
|
b6fd0df715 | ||
|
|
f53fb58024 | ||
|
|
21d452308d | ||
|
|
823608cca1 | ||
|
|
f3a0b998e4 | ||
|
|
a47a38b16b | ||
|
|
a184c53dd5 | ||
|
|
a3464255ec | ||
|
|
088c6670a9 | ||
|
|
27840b9bd6 | ||
|
|
ae819504f0 | ||
|
|
af792e394c | ||
|
|
76a4d5c2a5 | ||
|
|
4e23acd608 | ||
|
|
3c65dcdd8b | ||
|
|
efea6e3972 | ||
|
|
185cca7880 | ||
|
|
fbded93405 | ||
|
|
674881b873 | ||
|
|
248fb6dd2d | ||
|
|
3211e7a149 | ||
|
|
08496c699e | ||
|
|
060fa25814 | ||
|
|
a6b1effc77 | ||
|
|
5cf9c4808e | ||
|
|
d5a44067fd | ||
|
|
0de463526f | ||
|
|
5fd79875ef | ||
|
|
bdb19ad325 | ||
|
|
7a12efc823 | ||
|
|
2f3bddf860 | ||
|
|
6127eb97af | ||
|
|
86386e14f3 | ||
|
|
9cc17c29f6 | ||
|
|
a31e863c7b | ||
|
|
15a32ac83a | ||
|
|
fa5b84ec0d | ||
|
|
7905584eac | ||
|
|
afc9b96a11 | ||
|
|
4e6466fc18 | ||
|
|
233baf78ce | ||
|
|
42d0bee37f | ||
|
|
ccadab6a10 | ||
|
|
bfe5a5cb96 | ||
|
|
88fc24c4cf | ||
|
|
8be4fea7a3 | ||
|
|
a3d39d8b6d | ||
|
|
ff36f0259a | ||
|
|
d13654088a | ||
|
|
8a6b97c757 | ||
|
|
d71e0c109b | ||
|
|
7e345303fe | ||
|
|
d881bdb77c | ||
|
|
4961102d8f | ||
|
|
4f2ab4f1da | ||
|
|
7540e34366 | ||
|
|
4d7a8b9ea0 | ||
|
|
812ecf93c8 | ||
|
|
74d99d97bc | ||
|
|
5bbc453268 | ||
|
|
d71b9edc7f | ||
|
|
134904b551 | ||
|
|
3eeb292d70 | ||
|
|
04125bbb98 | ||
|
|
cfc3a8341c | ||
|
|
cbb0a511a8 | ||
|
|
dff1c7ed32 | ||
|
|
7987ef81a6 | ||
|
|
8d8d4ee782 | ||
|
|
3a50c73cab | ||
|
|
4ee0071eca | ||
|
|
248192f404 | ||
|
|
e56b766c3a | ||
|
|
170b2b24ab | ||
|
|
50137e0994 | ||
|
|
8b5babd5f8 | ||
|
|
39e83200c8 | ||
|
|
ead0e0107e | ||
|
|
1c888bd4fd | ||
|
|
ff92530339 | ||
|
|
ee38032403 | ||
|
|
2ce245659a | ||
|
|
2c635eb4d7 | ||
|
|
d653223df0 | ||
|
|
290b0dfc78 | ||
|
|
06bb9e654b | ||
|
|
576fe78e33 | ||
|
|
0c16bfb5ff | ||
|
|
a101dbdf31 | ||
|
|
682609044f | ||
|
|
c0a7fb3a5c | ||
|
|
a153aa791c | ||
|
|
76b3dd1f96 | ||
|
|
8b9e2c8526 | ||
|
|
0dc2d14736 | ||
|
|
fc5dada701 | ||
|
|
f34060c4e0 | ||
|
|
006a239680 | ||
|
|
c6ed4bcab1 | ||
|
|
b0fe967087 | ||
|
|
3f32673b0a | ||
|
|
0525cecaee | ||
|
|
7d84a9582b | ||
|
|
289acd8ab6 | ||
|
|
bb6d9f604b | ||
|
|
c20a8232cd | ||
|
|
c886212e89 | ||
|
|
799ae111ae | ||
|
|
2a990979a0 | ||
|
|
2d1071117b | ||
|
|
aefce1ee67 | ||
|
|
64d10f5050 | ||
|
|
95b9edca62 | ||
|
|
06c8422cbe | ||
|
|
177d8b5112 | ||
|
|
39afa98fce | ||
|
|
5bc1ad4ba5 | ||
|
|
0b1223976c | ||
|
|
3873758fe5 | ||
|
|
3c876cb1d8 | ||
|
|
0a1a16c197 | ||
|
|
3c56325fa6 | ||
|
|
e5db3353ae | ||
|
|
3416ba4eaa | ||
|
|
cdb69bf553 | ||
|
|
1aff6d0d55 | ||
|
|
67b7bdcea3 | ||
|
|
9b6a6de415 | ||
|
|
8325246a56 | ||
|
|
3d11fb5e3f | ||
|
|
4627a58a52 | ||
|
|
e557047af1 | ||
|
|
8b4cea7339 | ||
|
|
ba5e35ec5c | ||
|
|
f53817e94d | ||
|
|
846efc5351 | ||
|
|
0149d00d64 | ||
|
|
275f9730f1 | ||
|
|
43bc954989 | ||
|
|
c88bc722e1 | ||
|
|
ec6fc6839e | ||
|
|
8383c93292 | ||
|
|
37d8d85935 | ||
|
|
95db7c6329 | ||
|
|
636783335f | ||
|
|
f88431ded4 | ||
|
|
91b0de0580 | ||
|
|
a5704ad75c | ||
|
|
ba8c09a7d9 | ||
|
|
dbe4015655 | ||
|
|
76237ad7dd | ||
|
|
dbfb1168fb | ||
|
|
830d28fbe5 | ||
|
|
c6def2a8fe | ||
|
|
ebc95fcea7 | ||
|
|
f78cfe59e8 | ||
|
|
a2cbc93d34 | ||
|
|
4728f14e8c | ||
|
|
e94b431d58 | ||
|
|
0ac5e2b995 | ||
|
|
365764e3e3 | ||
|
|
8d2b5aa3de | ||
|
|
9a031a4a43 | ||
|
|
d412679fd7 | ||
|
|
22c52d4432 | ||
|
|
9e5bdf9e33 | ||
|
|
7253eca5c0 | ||
|
|
b1f3dc29c5 | ||
|
|
8e3df8d41b | ||
|
|
efad22c00a | ||
|
|
b7690f13b1 | ||
|
|
6e1739da2e | ||
|
|
77ab423041 | ||
|
|
96a8e885d3 | ||
|
|
2b917c7eca | ||
|
|
942d2f7a4e | ||
|
|
75142ff1b2 | ||
|
|
74172dc3fa | ||
|
|
848fdda8d7 | ||
|
|
3895569a0c | ||
|
|
0aac29ca76 | ||
|
|
25f9eece8d | ||
|
|
0e22b8ab2c | ||
|
|
577c2ce6cd | ||
|
|
5b9cb8ca4b | ||
|
|
2eda93c0c2 | ||
|
|
d916adf2e3 | ||
|
|
1f065bcbb2 | ||
|
|
3efdd9505e | ||
|
|
ab3f5f6a6c | ||
|
|
9027be7bbb | ||
|
|
7b08b5c035 | ||
|
|
4a557c67b7 | ||
|
|
a6ebe092e8 | ||
|
|
9759e1b396 | ||
|
|
b6147ca5a1 | ||
|
|
83887db77a | ||
|
|
1facfbec0e | ||
|
|
d6608325b4 | ||
|
|
439b090470 | ||
|
|
ed98319a21 | ||
|
|
54f94f44c0 | ||
|
|
57cf0e23cf | ||
|
|
9e66269aca | ||
|
|
a4f50fa2c3 | ||
|
|
7a0bafb02c | ||
|
|
a24658ac0e | ||
|
|
bb77385b66 | ||
|
|
e8b1bb649c | ||
|
|
023347da3f | ||
|
|
f10146be03 | ||
|
|
7bac3c1326 | ||
|
|
528c0a10de | ||
|
|
c1e1a8d1a9 | ||
|
|
21720a510d | ||
|
|
a2d5e7f32f | ||
|
|
629fb7b9cd | ||
|
|
f6a4af8c00 | ||
|
|
837e60316a | ||
|
|
2592a90853 | ||
|
|
788429efb7 | ||
|
|
674d8672f0 | ||
|
|
8d371fce79 | ||
|
|
f0f391ec73 | ||
|
|
abc3b5569c | ||
|
|
c42dc0f12e | ||
|
|
7ec14f2eff | ||
|
|
31d4e88efd | ||
|
|
da06184d51 | ||
|
|
08296793f1 | ||
|
|
3b6fde2ea6 | ||
|
|
1f2948c9a5 | ||
|
|
e62267042e | ||
|
|
883fe48946 | ||
|
|
6ba18c5bd1 | ||
|
|
207347190d | ||
|
|
0cf76e1f11 | ||
|
|
9d76311f82 | ||
|
|
c327a292c7 | ||
|
|
5a965f6d52 | ||
|
|
a81c621d49 | ||
|
|
2e7b5d0d08 | ||
|
|
6d324fe8da | ||
|
|
6d2be3a083 | ||
|
|
d252c6fc5c | ||
|
|
30c5e87c67 | ||
|
|
2db819de2b | ||
|
|
bff5e64484 | ||
|
|
3492bef2c7 | ||
|
|
6d9aa56084 | ||
|
|
94c40b3444 | ||
|
|
278c8b1115 | ||
|
|
8e8581cce5 | ||
|
|
bd29f4e9e2 | ||
|
|
eb2b92d85c | ||
|
|
042ac0d338 | ||
|
|
a7011f0d6b | ||
|
|
22144ee18d | ||
|
|
b85484fceb | ||
|
|
b58956371f | ||
|
|
f487e9f7e2 | ||
|
|
f14efd3b1e | ||
|
|
3496a42c19 | ||
|
|
0882c3ecf5 | ||
|
|
1f6d76bac7 | ||
|
|
db7c3f4321 | ||
|
|
9ea4651fb0 | ||
|
|
ed5a75447f | ||
|
|
d08994a314 | ||
|
|
2bf6e7073c | ||
|
|
7990458cf3 | ||
|
|
f3b13735f3 | ||
|
|
ae680a32bc | ||
|
|
cbd0f18c6e | ||
|
|
fd62b1b631 | ||
|
|
2159f6720e | ||
|
|
67c3a36464 | ||
|
|
652efc8624 | ||
|
|
30da041ea3 | ||
|
|
e90c401503 | ||
|
|
83d919f06e | ||
|
|
74fef0d2d9 | ||
|
|
5906b24da1 | ||
|
|
8dd36f7adf | ||
|
|
f2dc26dbff | ||
|
|
87915ecd00 | ||
|
|
3cf4e55621 | ||
|
|
4c7cd840df | ||
|
|
bd238a6fe4 | ||
|
|
beaace9b54 | ||
|
|
c5352c2a73 | ||
|
|
dd6410c33e | ||
|
|
ac9dca8098 | ||
|
|
74ade6fecc | ||
|
|
e1cfbb155d | ||
|
|
030aba2ac3 | ||
|
|
9ab7e722e0 |
8
.github/workflows/android.publish.yml
vendored
8
.github/workflows/android.publish.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Publish @notesnook/mobile
|
||||
name: Publish @notesnook/android
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
@@ -33,13 +33,15 @@ jobs:
|
||||
|
||||
- name: Sign app bundle for Playstore release
|
||||
id: sign_app
|
||||
uses: r0adkll/sign-android-release@v1
|
||||
uses: r0adkll/sign-android-release@master
|
||||
with:
|
||||
releaseDirectory: apps/mobile/native/android/app/build/outputs/bundle/release
|
||||
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
|
||||
alias: ${{ secrets.ALIAS }}
|
||||
keyStorePassword: ${{ secrets.KEY_PASSWORD }}
|
||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||
env:
|
||||
BUILD_TOOLS_VERSION: "33.0.0"
|
||||
|
||||
- name: Publish to Playstore
|
||||
id: deploy
|
||||
@@ -64,6 +66,8 @@ jobs:
|
||||
alias: ${{ secrets.PUBLIC_ALIAS }}
|
||||
keyStorePassword: ${{ secrets.PUBLIC_KEY_PASSWORD }}
|
||||
keyPassword: ${{ secrets.PUBLIC_KEY_PASSWORD }}
|
||||
env:
|
||||
BUILD_TOOLS_VERSION: "33.0.0"
|
||||
|
||||
- name: Rename apk files
|
||||
run: |
|
||||
|
||||
31
.github/workflows/desktop.publish.yml
vendored
31
.github/workflows/desktop.publish.yml
vendored
@@ -18,6 +18,21 @@ on:
|
||||
required: true
|
||||
default: true
|
||||
description: "Publish on GitHub releases?"
|
||||
build-windows:
|
||||
type: boolean
|
||||
required: true
|
||||
default: true
|
||||
description: "Build for Windows?"
|
||||
build-linux:
|
||||
type: boolean
|
||||
required: true
|
||||
default: true
|
||||
description: "Build for Linux?"
|
||||
build-mac:
|
||||
type: boolean
|
||||
required: true
|
||||
default: true
|
||||
description: "Build for macOS?"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -71,6 +86,7 @@ jobs:
|
||||
build-macos:
|
||||
name: Build for macOS
|
||||
needs: build
|
||||
if: inputs.build-mac
|
||||
runs-on: macos-12
|
||||
|
||||
steps:
|
||||
@@ -97,6 +113,7 @@ jobs:
|
||||
- name: Get App Store Version
|
||||
id: appstore
|
||||
uses: streetwriters/appstore-connect-app-version@develop
|
||||
if: inputs.publish-apple
|
||||
with:
|
||||
app-id: ${{ steps.app_metadata.outputs.apple_app_id }}
|
||||
key-id: ${{ secrets.api_key_id }}
|
||||
@@ -162,6 +179,7 @@ jobs:
|
||||
build-linux:
|
||||
name: Build for Linux
|
||||
needs: build
|
||||
if: inputs.build-linux
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
@@ -220,12 +238,22 @@ jobs:
|
||||
build-windows:
|
||||
name: Build for Windows
|
||||
needs: build
|
||||
if: inputs.build-windows
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup .NET Core SDK
|
||||
uses: actions/setup-dotnet@v2
|
||||
with:
|
||||
dotnet-version: 6.0.x
|
||||
|
||||
- name: Install AzureCodeSigning
|
||||
run: Install-Module -Name AzureCodeSigning -RequiredVersion 0.3.0 -Force -Repository PSGallery
|
||||
shell: pwsh
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
@@ -246,6 +274,9 @@ jobs:
|
||||
- name: Publish
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
|
||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
|
||||
run: |
|
||||
if ($${{ inputs.publish-github }} -eq $true) {
|
||||
yarn electron-builder --win --publish always
|
||||
|
||||
67
.github/workflows/ios.publish.yml
vendored
Normal file
67
.github/workflows/ios.publish.yml
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
name: Publish @notesnook/ios
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-13
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
- name: Install node modules
|
||||
run: |
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=mobile
|
||||
|
||||
- name: Build packages
|
||||
run: npx nx run @notesnook/mobile:build
|
||||
|
||||
- name: Install Pods
|
||||
run: npm run prepare:ios
|
||||
|
||||
- name: Build iOS App
|
||||
uses: yukiarrr/ios-build-action@v1.11.2
|
||||
with:
|
||||
bundle-identifier: org.streetwriters.notesnook
|
||||
scheme: Notesnook
|
||||
configuration: "Release"
|
||||
export-options: apps/mobile/native/ios/ExportOptions.plist
|
||||
project-path: apps/mobile/native/ios/Notesnook.xcodeproj
|
||||
workspace-path: apps/mobile/native/ios/Notesnook.xcworkspace
|
||||
update-targets: |
|
||||
Notesnook
|
||||
Make Note
|
||||
NotesWidgetExtension
|
||||
disable-targets: Notesnook-tvOS,Notesnook-tvOSTests,NotesnookTests
|
||||
code-signing-identity: Apple Distribution
|
||||
team-id: ${{ secrets.APPLE_TEAM_ID }}
|
||||
p12-base64: ${{ secrets.APPLE_CERTIFICATE_P12 }}
|
||||
certificate-password: ${{ secrets.APPLE_CERTIFICATE_P12_PASSWORD }}
|
||||
app-store-connect-api-key-issuer-id: ${{ secrets.API_KEY_ISSUER_ID }}
|
||||
app-store-connect-api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
|
||||
app-store-connect-api-key-base64: ${{ secrets.APPSTORE_CONNECT_API_KEY_BASE64 }}
|
||||
output-path: Notesnook.ipa
|
||||
mobileprovision-base64: |
|
||||
${{ secrets.APPLE_MOBILE_PROVISION_APP }}
|
||||
${{ secrets.APPLE_MOBILE_PROVISION_SHARE }}
|
||||
${{ secrets.APPLE_MOBILE_PROVISION_WIDGET }}
|
||||
|
||||
- name: 'Upload app to TestFlight'
|
||||
uses: apple-actions/upload-testflight-build@v1
|
||||
with:
|
||||
app-path: Notesnook.ipa
|
||||
issuer-id: ${{ secrets.API_KEY_ISSUER_ID }}
|
||||
api-key-id: ${{ secrets.API_KEY_ID }}
|
||||
api-private-key: ${{ secrets.API_KEY }}
|
||||
|
||||
- name: Upload to Github
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Notesnook.ipa
|
||||
path: Notesnook.ipa
|
||||
4
.github/workflows/web.beta.publish.yml
vendored
4
.github/workflows/web.beta.publish.yml
vendored
@@ -1,10 +1,11 @@
|
||||
name: Publish @notesnook/web Beta
|
||||
|
||||
on:
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- '!v3-beta'
|
||||
paths-ignore:
|
||||
- "apps/mobile/**"
|
||||
- "packages/editor-mobile/**"
|
||||
@@ -22,6 +23,7 @@ on:
|
||||
- synchronize
|
||||
branches:
|
||||
- master
|
||||
- '!v3-beta'
|
||||
paths-ignore:
|
||||
- "apps/mobile/**"
|
||||
- "packages/editor-mobile/**"
|
||||
|
||||
40
.github/workflows/web.v3-beta.publish.yml
vendored
Normal file
40
.github/workflows/web.v3-beta.publish.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
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: 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-v3-app
|
||||
directory: ./apps/web/build
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
github-environment: ${{ (github.ref == 'refs/heads/master' && 'beta') || 'preview' }}
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -9,4 +9,5 @@ nx-cloud.env
|
||||
.env.local
|
||||
.nyc_output
|
||||
site
|
||||
node_modules.backup
|
||||
node_modules.backup
|
||||
.nx
|
||||
3
apps/desktop/app-update.yml
Normal file
3
apps/desktop/app-update.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
owner: streetwriters
|
||||
repo: notesnook
|
||||
provider: github
|
||||
276
apps/desktop/package-lock.json
generated
276
apps/desktop/package-lock.json
generated
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "2.6.3",
|
||||
"version": "2.6.17",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "2.6.3",
|
||||
"version": "2.6.17",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@notesnook/crypto": "file:../../packages/crypto",
|
||||
"@trpc/client": "10.31.0",
|
||||
"@trpc/server": "10.31.0",
|
||||
"@trpc/client": "10.38.3",
|
||||
"@trpc/server": "10.38.3",
|
||||
"electron-trpc": "0.5.2",
|
||||
"electron-updater": "6.1.4",
|
||||
"icojs": "^0.17.1",
|
||||
@@ -24,8 +24,8 @@
|
||||
"@types/node": "18.16.1",
|
||||
"@types/yargs": "^17.0.24",
|
||||
"chokidar": "^3.5.3",
|
||||
"electron": "25.8.0",
|
||||
"electron-builder": "^24.6.4",
|
||||
"electron": "25.9.8",
|
||||
"electron-builder": "^24.9.1",
|
||||
"electron-builder-notarize": "^1.5.1",
|
||||
"esbuild": "^0.17.19",
|
||||
"tree-kill": "^1.2.2",
|
||||
@@ -173,12 +173,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@electron/asar": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.4.tgz",
|
||||
"integrity": "sha512-lykfY3TJRRWFeTxccEKdf1I6BLl2Plw81H0bbp4Fc5iEc67foDCa5pjJQULVgo0wF+Dli75f3xVcdb/67FFZ/g==",
|
||||
"version": "3.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.8.tgz",
|
||||
"integrity": "sha512-cmskk5M06ewHMZAplSiF4AlME3IrnnZhKnWbtwKVLRkdJkKyUVjMLhDIiPIx/+6zQWVlKX/LtmK9xDme7540Sg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chromium-pickle-js": "^0.2.0",
|
||||
"commander": "^5.0.0",
|
||||
"glob": "^7.1.6",
|
||||
"minimatch": "^3.0.4"
|
||||
@@ -216,6 +215,7 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.2.tgz",
|
||||
"integrity": "sha512-eFZVFoRXb3GFGd7Ak7W4+6jBl9wBtiZ4AaYOse97ej6mKj5tkyO0dUnUChs1IhJZtx1BENo4/p4WUTXpi6vT+g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.1.1",
|
||||
"env-paths": "^2.2.0",
|
||||
@@ -236,6 +236,7 @@
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^4.0.0",
|
||||
@@ -249,6 +250,7 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
|
||||
"dev": true,
|
||||
"optionalDependencies": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
@@ -257,6 +259,7 @@
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
@@ -265,6 +268,7 @@
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
@@ -798,6 +802,7 @@
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
|
||||
"integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
@@ -809,6 +814,7 @@
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
|
||||
"integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"defer-to-connect": "^2.0.0"
|
||||
},
|
||||
@@ -831,20 +837,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@trpc/client": {
|
||||
"version": "10.31.0",
|
||||
"resolved": "https://registry.npmjs.org/@trpc/client/-/client-10.31.0.tgz",
|
||||
"integrity": "sha512-VCqbJEvFJb8C4hQFw7AD+dkQTjgEdV/QAzO4D+/cX5e93u5NpfNXI+PKS0QFXwG/zqgwQwVV6OkYc/D/MFwA6g==",
|
||||
"version": "10.38.3",
|
||||
"resolved": "https://registry.npmjs.org/@trpc/client/-/client-10.38.3.tgz",
|
||||
"integrity": "sha512-hHPsElTL4sB+UGzuAZ4iwRVHjELrk/Nsl2owsYw9ITJz3EY7VWRsQ6vK48kJvlHPYPhP+efID5UOgyZMs4bFXA==",
|
||||
"funding": [
|
||||
"https://trpc.io/sponsor"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"@trpc/server": "10.31.0"
|
||||
"@trpc/server": "10.38.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@trpc/server": {
|
||||
"version": "10.31.0",
|
||||
"resolved": "https://registry.npmjs.org/@trpc/server/-/server-10.31.0.tgz",
|
||||
"integrity": "sha512-9EnRTSDE9nF11LZsvSOqNKqkRYzHqFX4ch5AJ6VIu8uta2vxVTN4FxxsNRSOluTzVYZDeaCISbwmOJ5iihCCIg==",
|
||||
"version": "10.38.3",
|
||||
"resolved": "https://registry.npmjs.org/@trpc/server/-/server-10.38.3.tgz",
|
||||
"integrity": "sha512-9s8/kwo2IDB5hwB2SKZZrfevRhdb1f9fdXtIYd3lbQuf2jQaC/LyQuHaIQjDQoUx9updBfsHXcFFPiCP1DL6pg==",
|
||||
"funding": [
|
||||
"https://trpc.io/sponsor"
|
||||
]
|
||||
@@ -853,6 +859,7 @@
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
|
||||
"integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/http-cache-semantics": "*",
|
||||
"@types/keyv": "^3.1.4",
|
||||
@@ -861,9 +868,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/debug": {
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz",
|
||||
"integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==",
|
||||
"version": "4.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
|
||||
"integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/ms": "*"
|
||||
@@ -881,26 +888,29 @@
|
||||
"node_modules/@types/http-cache-semantics": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz",
|
||||
"integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ=="
|
||||
"integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/keyv": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
|
||||
"integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ms": {
|
||||
"version": "0.7.31",
|
||||
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",
|
||||
"integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==",
|
||||
"version": "0.7.34",
|
||||
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
|
||||
"integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.1.tgz",
|
||||
"integrity": "sha512-DZxSZWXxFfOlx7k7Rv4LAyiMroaxa3Ly/7OOzZO8cBNho0YzAi4qlbrx8W27JGqG57IgR/6J7r+nOJWw6kcvZA=="
|
||||
"integrity": "sha512-DZxSZWXxFfOlx7k7Rv4LAyiMroaxa3Ly/7OOzZO8cBNho0YzAi4qlbrx8W27JGqG57IgR/6J7r+nOJWw6kcvZA==",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/@types/normalize-package-data": {
|
||||
"version": "2.4.1",
|
||||
@@ -922,6 +932,7 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz",
|
||||
"integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
@@ -951,15 +962,16 @@
|
||||
"version": "2.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz",
|
||||
"integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/7zip-bin": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.1.1.tgz",
|
||||
"integrity": "sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ==",
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz",
|
||||
"integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
@@ -1041,9 +1053,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/app-builder-lib": {
|
||||
"version": "24.6.4",
|
||||
"resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-24.6.4.tgz",
|
||||
"integrity": "sha512-m9931WXb83teb32N0rKg+ulbn6+Hl8NV5SUpVDOVz9MWOXfhV6AQtTdftf51zJJvCQnQugGtSqoLvgw6mdF/Rg==",
|
||||
"version": "24.9.1",
|
||||
"resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-24.9.1.tgz",
|
||||
"integrity": "sha512-Q1nYxZcio4r+W72cnIRVYofEAyjBd3mG47o+zms8HlD51zWtA/YxJb01Jei5F+jkWhge/PTQK+uldsPh6d0/4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@develar/schema-utils": "~2.6.5",
|
||||
@@ -1052,15 +1064,15 @@
|
||||
"@electron/universal": "1.4.1",
|
||||
"@malept/flatpak-bundler": "^0.4.0",
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"7zip-bin": "~5.1.1",
|
||||
"7zip-bin": "~5.2.0",
|
||||
"async-exit-hook": "^2.0.1",
|
||||
"bluebird-lst": "^1.0.9",
|
||||
"builder-util": "24.5.0",
|
||||
"builder-util-runtime": "9.2.1",
|
||||
"builder-util": "24.8.1",
|
||||
"builder-util-runtime": "9.2.3",
|
||||
"chromium-pickle-js": "^0.2.0",
|
||||
"debug": "^4.3.4",
|
||||
"ejs": "^3.1.8",
|
||||
"electron-publish": "24.5.0",
|
||||
"electron-publish": "24.8.1",
|
||||
"form-data": "^4.0.0",
|
||||
"fs-extra": "^10.1.0",
|
||||
"hosted-git-info": "^4.1.0",
|
||||
@@ -1079,6 +1091,19 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/app-builder-lib/node_modules/builder-util-runtime": {
|
||||
"version": "9.2.3",
|
||||
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz",
|
||||
"integrity": "sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4",
|
||||
"sax": "^1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
@@ -1103,9 +1128,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
|
||||
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
|
||||
"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/async-exit-hook": {
|
||||
@@ -1191,6 +1216,7 @@
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
|
||||
"integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
@@ -1242,6 +1268,7 @@
|
||||
"version": "0.2.13",
|
||||
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
|
||||
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
@@ -1265,16 +1292,16 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/builder-util": {
|
||||
"version": "24.5.0",
|
||||
"resolved": "https://registry.npmjs.org/builder-util/-/builder-util-24.5.0.tgz",
|
||||
"integrity": "sha512-STnBmZN/M5vGcv01u/K8l+H+kplTaq4PAIn3yeuufUKSpcdro0DhJWxPI81k5XcNfC//bjM3+n9nr8F9uV4uAQ==",
|
||||
"version": "24.8.1",
|
||||
"resolved": "https://registry.npmjs.org/builder-util/-/builder-util-24.8.1.tgz",
|
||||
"integrity": "sha512-ibmQ4BnnqCnJTNrdmdNlnhF48kfqhNzSeqFMXHLIl+o9/yhn6QfOaVrloZ9YUu3m0k3rexvlT5wcki6LWpjTZw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/debug": "^4.1.6",
|
||||
"7zip-bin": "~5.1.1",
|
||||
"7zip-bin": "~5.2.0",
|
||||
"app-builder-bin": "4.0.0",
|
||||
"bluebird-lst": "^1.0.9",
|
||||
"builder-util-runtime": "9.2.1",
|
||||
"builder-util-runtime": "9.2.3",
|
||||
"chalk": "^4.1.2",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"debug": "^4.3.4",
|
||||
@@ -1300,6 +1327,19 @@
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/builder-util/node_modules/builder-util-runtime": {
|
||||
"version": "9.2.3",
|
||||
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz",
|
||||
"integrity": "sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4",
|
||||
"sax": "^1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/busboy": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||
@@ -1316,6 +1356,7 @@
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
|
||||
"integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10.6.0"
|
||||
}
|
||||
@@ -1324,6 +1365,7 @@
|
||||
"version": "7.0.4",
|
||||
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz",
|
||||
"integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"clone-response": "^1.0.2",
|
||||
"get-stream": "^5.1.0",
|
||||
@@ -1396,9 +1438,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ci-info": {
|
||||
"version": "3.8.0",
|
||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz",
|
||||
"integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==",
|
||||
"version": "3.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
|
||||
"integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -1443,6 +1485,7 @@
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
|
||||
"integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mimic-response": "^1.0.0"
|
||||
},
|
||||
@@ -1586,6 +1629,7 @@
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
|
||||
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mimic-response": "^3.1.0"
|
||||
},
|
||||
@@ -1600,6 +1644,7 @@
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
|
||||
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
@@ -1611,6 +1656,7 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
|
||||
"integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
@@ -1619,6 +1665,7 @@
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
|
||||
"integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"has-property-descriptors": "^1.0.0",
|
||||
@@ -1644,6 +1691,7 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
|
||||
"integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/dir-compare": {
|
||||
@@ -1679,14 +1727,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dmg-builder": {
|
||||
"version": "24.6.4",
|
||||
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-24.6.4.tgz",
|
||||
"integrity": "sha512-BNcHRc9CWEuI9qt0E655bUBU/j/3wUCYBVKGu1kVpbN5lcUdEJJJeiO0NHK3dgKmra6LUUZlo+mWqc+OCbi0zw==",
|
||||
"version": "24.9.1",
|
||||
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-24.9.1.tgz",
|
||||
"integrity": "sha512-huC+O6hvHd24Ubj3cy2GMiGLe2xGFKN3klqVMLAdcbB6SWMd1yPSdZvV8W1O01ICzCCRlZDHiv4VrNUgnPUfbQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"app-builder-lib": "24.6.4",
|
||||
"builder-util": "24.5.0",
|
||||
"builder-util-runtime": "9.2.1",
|
||||
"app-builder-lib": "24.9.1",
|
||||
"builder-util": "24.8.1",
|
||||
"builder-util-runtime": "9.2.3",
|
||||
"fs-extra": "^10.1.0",
|
||||
"iconv-lite": "^0.6.2",
|
||||
"js-yaml": "^4.1.0"
|
||||
@@ -1695,6 +1743,19 @@
|
||||
"dmg-license": "^1.0.11"
|
||||
}
|
||||
},
|
||||
"node_modules/dmg-builder/node_modules/builder-util-runtime": {
|
||||
"version": "9.2.3",
|
||||
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz",
|
||||
"integrity": "sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4",
|
||||
"sax": "^1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dmg-license": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz",
|
||||
@@ -1751,9 +1812,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron": {
|
||||
"version": "25.8.0",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-25.8.0.tgz",
|
||||
"integrity": "sha512-T3kC1a/3ntSaYMCVVfUUc9v7myPzi6J2GP0Ad/CyfWKDPp054dGyKxb2EEjKnxQQ7wfjsT1JTEdBG04x6ekVBw==",
|
||||
"version": "25.9.8",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-25.9.8.tgz",
|
||||
"integrity": "sha512-PGgp6PH46QVENHuAHc2NT1Su8Q1qov7qIl2jI5tsDpTibwV2zD8539AeWBQySeBU4dhbj9onIl7+1bXQ0wefBg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@electron/get": "^2.0.0",
|
||||
@@ -1768,16 +1830,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-builder": {
|
||||
"version": "24.6.4",
|
||||
"resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-24.6.4.tgz",
|
||||
"integrity": "sha512-uNWQoU7pE7qOaIQ6CJHpBi44RJFVG8OHRBIadUxrsDJVwLLo8Nma3K/EEtx5/UyWAQYdcK4nVPYKoRqBb20hbA==",
|
||||
"version": "24.9.1",
|
||||
"resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-24.9.1.tgz",
|
||||
"integrity": "sha512-v7BuakDuY6sKMUYM8mfQGrwyjBpZ/ObaqnenU0H+igEL10nc6ht049rsCw2HghRBdEwJxGIBuzs3jbEhNaMDmg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"app-builder-lib": "24.6.4",
|
||||
"builder-util": "24.5.0",
|
||||
"builder-util-runtime": "9.2.1",
|
||||
"app-builder-lib": "24.9.1",
|
||||
"builder-util": "24.8.1",
|
||||
"builder-util-runtime": "9.2.3",
|
||||
"chalk": "^4.1.2",
|
||||
"dmg-builder": "24.6.4",
|
||||
"dmg-builder": "24.9.1",
|
||||
"fs-extra": "^10.1.0",
|
||||
"is-ci": "^3.0.0",
|
||||
"lazy-val": "^1.0.5",
|
||||
@@ -1839,6 +1901,19 @@
|
||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/electron-builder/node_modules/builder-util-runtime": {
|
||||
"version": "9.2.3",
|
||||
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz",
|
||||
"integrity": "sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4",
|
||||
"sax": "^1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-notarize": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/electron-notarize/-/electron-notarize-1.2.2.tgz",
|
||||
@@ -1869,20 +1944,33 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-publish": {
|
||||
"version": "24.5.0",
|
||||
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-24.5.0.tgz",
|
||||
"integrity": "sha512-zwo70suH15L15B4ZWNDoEg27HIYoPsGJUF7xevLJLSI7JUPC8l2yLBdLGwqueJ5XkDL7ucYyRZzxJVR8ElV9BA==",
|
||||
"version": "24.8.1",
|
||||
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-24.8.1.tgz",
|
||||
"integrity": "sha512-IFNXkdxMVzUdweoLJNXSupXkqnvgbrn3J4vognuOY06LaS/m0xvfFYIf+o1CM8if6DuWYWoQFKPcWZt/FUjZPw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/fs-extra": "^9.0.11",
|
||||
"builder-util": "24.5.0",
|
||||
"builder-util-runtime": "9.2.1",
|
||||
"builder-util": "24.8.1",
|
||||
"builder-util-runtime": "9.2.3",
|
||||
"chalk": "^4.1.2",
|
||||
"fs-extra": "^10.1.0",
|
||||
"lazy-val": "^1.0.5",
|
||||
"mime": "^2.5.2"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-publish/node_modules/builder-util-runtime": {
|
||||
"version": "9.2.3",
|
||||
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz",
|
||||
"integrity": "sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4",
|
||||
"sax": "^1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-trpc": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/electron-trpc/-/electron-trpc-0.5.2.tgz",
|
||||
@@ -1920,6 +2008,7 @@
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
@@ -1928,6 +2017,7 @@
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
|
||||
"integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
@@ -1951,6 +2041,7 @@
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
|
||||
"integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
@@ -2002,6 +2093,7 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
@@ -2027,6 +2119,7 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
|
||||
"integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.1.1",
|
||||
"get-stream": "^5.1.0",
|
||||
@@ -2067,6 +2160,7 @@
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
|
||||
"integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"pend": "~1.2.0"
|
||||
}
|
||||
@@ -2183,7 +2277,7 @@
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||
"devOptional": true
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
@@ -2197,6 +2291,7 @@
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
|
||||
"integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1",
|
||||
@@ -2212,6 +2307,7 @@
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
|
||||
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"pump": "^3.0.0"
|
||||
},
|
||||
@@ -2280,6 +2376,7 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz",
|
||||
"integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"boolean": "^3.0.1",
|
||||
@@ -2297,6 +2394,7 @@
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz",
|
||||
"integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"define-properties": "^1.1.3"
|
||||
@@ -2312,6 +2410,7 @@
|
||||
"version": "11.8.6",
|
||||
"resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz",
|
||||
"integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@sindresorhus/is": "^4.0.0",
|
||||
"@szmarczak/http-timer": "^4.0.5",
|
||||
@@ -2341,7 +2440,7 @@
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1"
|
||||
},
|
||||
@@ -2362,6 +2461,7 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
|
||||
"integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.1"
|
||||
@@ -2374,6 +2474,7 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -2386,6 +2487,7 @@
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -2409,7 +2511,8 @@
|
||||
"node_modules/http-cache-semantics": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
|
||||
"integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ=="
|
||||
"integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/http-proxy-agent": {
|
||||
"version": "5.0.0",
|
||||
@@ -2429,6 +2532,7 @@
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
|
||||
"integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"quick-lru": "^5.1.1",
|
||||
"resolve-alpn": "^1.0.0"
|
||||
@@ -2691,7 +2795,8 @@
|
||||
"node_modules/json-buffer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
|
||||
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="
|
||||
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/json-parse-even-better-errors": {
|
||||
"version": "2.3.1",
|
||||
@@ -2709,6 +2814,7 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
||||
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/json5": {
|
||||
@@ -2738,6 +2844,7 @@
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz",
|
||||
"integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"json-buffer": "3.0.1"
|
||||
}
|
||||
@@ -2773,6 +2880,7 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
|
||||
"integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -2792,6 +2900,7 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
|
||||
"integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"escape-string-regexp": "^4.0.0"
|
||||
@@ -2837,6 +2946,7 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
|
||||
"integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
@@ -2959,6 +3069,7 @@
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
|
||||
"integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
@@ -2970,6 +3081,7 @@
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
|
||||
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -2979,6 +3091,7 @@
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
@@ -2987,6 +3100,7 @@
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
|
||||
"integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -3066,7 +3180,8 @@
|
||||
"node_modules/pend": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
||||
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="
|
||||
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
@@ -3105,6 +3220,7 @@
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
|
||||
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
@@ -3126,6 +3242,7 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"end-of-stream": "^1.1.0",
|
||||
"once": "^1.3.1"
|
||||
@@ -3144,6 +3261,7 @@
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
|
||||
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
@@ -3338,12 +3456,14 @@
|
||||
"node_modules/resolve-alpn": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
|
||||
"integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g=="
|
||||
"integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/responselike": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz",
|
||||
"integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lowercase-keys": "^2.0.0"
|
||||
},
|
||||
@@ -3379,6 +3499,7 @@
|
||||
"version": "2.15.4",
|
||||
"resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz",
|
||||
"integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"boolean": "^3.0.1",
|
||||
@@ -3458,12 +3579,14 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
|
||||
"integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/serialize-error": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz",
|
||||
"integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"type-fest": "^0.13.1"
|
||||
@@ -3479,6 +3602,7 @@
|
||||
"version": "0.13.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
|
||||
"integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
@@ -3599,6 +3723,7 @@
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
|
||||
"integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/stat-mode": {
|
||||
@@ -3671,6 +3796,7 @@
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
|
||||
"integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.1.0"
|
||||
},
|
||||
@@ -3940,7 +4066,8 @@
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/xmlbuilder": {
|
||||
"version": "15.1.1",
|
||||
@@ -3993,6 +4120,7 @@
|
||||
"version": "2.10.0",
|
||||
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
|
||||
"integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"buffer-crc32": "~0.2.3",
|
||||
"fd-slicer": "~1.1.0"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@notesnook/desktop",
|
||||
"productName": "Notesnook",
|
||||
"description": "Your private note taking space",
|
||||
"version": "2.6.3",
|
||||
"version": "2.6.17",
|
||||
"appAppleId": "1544027013",
|
||||
"private": true,
|
||||
"main": "./dist/index.js",
|
||||
@@ -12,8 +12,8 @@
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@notesnook/crypto": "file:../../packages/crypto",
|
||||
"@trpc/client": "10.31.0",
|
||||
"@trpc/server": "10.31.0",
|
||||
"@trpc/client": "10.38.3",
|
||||
"@trpc/server": "10.38.3",
|
||||
"electron-trpc": "0.5.2",
|
||||
"electron-updater": "6.1.4",
|
||||
"icojs": "^0.17.1",
|
||||
@@ -25,8 +25,8 @@
|
||||
"@types/node": "18.16.1",
|
||||
"@types/yargs": "^17.0.24",
|
||||
"chokidar": "^3.5.3",
|
||||
"electron": "25.8.0",
|
||||
"electron-builder": "^24.6.4",
|
||||
"electron": "25.9.8",
|
||||
"electron-builder": "^24.9.1",
|
||||
"electron-builder-notarize": "^1.5.1",
|
||||
"esbuild": "^0.17.19",
|
||||
"tree-kill": "^1.2.2",
|
||||
@@ -135,6 +135,10 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"signingHashAlgorithms": [
|
||||
"sha256"
|
||||
],
|
||||
"sign": "./sign.js",
|
||||
"icon": "assets/icons/app.ico"
|
||||
},
|
||||
"portable": {
|
||||
@@ -191,6 +195,7 @@
|
||||
"allowNativeWayland": true
|
||||
},
|
||||
"extraResources": [
|
||||
"app-update.yml",
|
||||
"./assets/**"
|
||||
],
|
||||
"extraMetadata": {
|
||||
|
||||
60
apps/desktop/sign.js
Normal file
60
apps/desktop/sign.js
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
const { writeFileSync, rmSync, readFileSync } = require("fs");
|
||||
const { execSync } = require("child_process");
|
||||
const { relative, join } = require("path");
|
||||
|
||||
module.exports = async function (configuration) {
|
||||
const Endpoint = "https://weu.codesigning.azure.net";
|
||||
const CodeSigningAccountName = "Notesnook";
|
||||
const CertificateProfileName = "Notesnook";
|
||||
const FileDigest = configuration.hash.toUpperCase();
|
||||
const TimestampRfc3161 = "http://timestamp.acs.microsoft.com";
|
||||
const TimestampDigest = configuration.hash.toUpperCase();
|
||||
const Description = "The Notesnook app";
|
||||
const DescriptionUrl = "https://notesnook.com/";
|
||||
const FilesCatalog = createCatalog(configuration.path);
|
||||
|
||||
const command = `Invoke-AzureCodeSigning -Endpoint "${Endpoint}" -CodeSigningAccountName "${CodeSigningAccountName}" -CertificateProfileName "${CertificateProfileName}" -FileDigest "${FileDigest}" -TimestampRfc3161 "${TimestampRfc3161}" -TimestampDigest "${TimestampDigest}" -Description "${Description}" -DescriptionUrl "${DescriptionUrl}" -FilesCatalog "${FilesCatalog}"`;
|
||||
|
||||
console.debug("Signing", configuration.path, "using command", command);
|
||||
|
||||
psexec(command);
|
||||
|
||||
console.debug("Signed", configuration.path);
|
||||
|
||||
deleteCatalog();
|
||||
};
|
||||
|
||||
function createCatalog(path) {
|
||||
writeFileSync("_catalog", relative(__dirname, path));
|
||||
return join(__dirname, "_catalog");
|
||||
}
|
||||
|
||||
function deleteCatalog() {
|
||||
rmSync("_catalog");
|
||||
}
|
||||
|
||||
function psexec(cmd) {
|
||||
return execSync(cmd, {
|
||||
env: process.env,
|
||||
stdio: "inherit",
|
||||
shell: "pwsh"
|
||||
});
|
||||
}
|
||||
@@ -29,6 +29,8 @@ import { dirname } from "path";
|
||||
import { resolvePath } from "../utils/resolve-path";
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import { AssetManager } from "../utils/asset-manager";
|
||||
import { isFlatpak } from "../utils";
|
||||
import { setupDesktopIntegration } from "../utils/desktop-integration";
|
||||
|
||||
const t = initTRPC.create();
|
||||
|
||||
@@ -44,12 +46,22 @@ const NotificationOptions = z.object({
|
||||
});
|
||||
|
||||
export const osIntegrationRouter = t.router({
|
||||
isFlatpak: t.procedure.query(() => isFlatpak()),
|
||||
|
||||
zoomFactor: t.procedure.query(() => config.zoomFactor),
|
||||
setZoomFactor: t.procedure.input(z.number()).mutation(({ input: factor }) => {
|
||||
globalThis.window?.webContents.setZoomFactor(factor);
|
||||
config.zoomFactor = factor;
|
||||
}),
|
||||
|
||||
proxyRules: t.procedure.query(() => config.proxyRules),
|
||||
setProxyRules: t.procedure
|
||||
.input(z.string().optional())
|
||||
.mutation(({ input: proxyRules }) => {
|
||||
globalThis.window?.webContents.session.setProxy({ proxyRules });
|
||||
config.proxyRules = proxyRules || "";
|
||||
}),
|
||||
|
||||
privacyMode: t.procedure.query(() => config.privacyMode),
|
||||
setPrivacyMode: t.procedure
|
||||
.input(z.object({ enabled: z.boolean() }))
|
||||
@@ -79,6 +91,7 @@ export const osIntegrationRouter = t.router({
|
||||
AutoLaunch.disable();
|
||||
}
|
||||
config.desktopSettings = settings;
|
||||
setupDesktopIntegration(settings);
|
||||
}),
|
||||
|
||||
selectDirectory: t.procedure
|
||||
@@ -117,6 +130,14 @@ export const osIntegrationRouter = t.router({
|
||||
writeFileSync(resolvedPath, data);
|
||||
}),
|
||||
|
||||
resolvePath: t.procedure
|
||||
.input(z.object({ filePath: z.string() }))
|
||||
.query(({ input }) => {
|
||||
const { filePath } = input;
|
||||
if (!filePath) return;
|
||||
return resolvePath(filePath);
|
||||
}),
|
||||
|
||||
showNotification: t.procedure
|
||||
.input(NotificationOptions)
|
||||
.query(({ input }) => {
|
||||
|
||||
@@ -118,5 +118,13 @@ export const spellCheckerRouter = t.router({
|
||||
.mutation(({ input: { enabled } }) => {
|
||||
globalThis.window?.webContents.session.setSpellCheckerEnabled(enabled);
|
||||
config.isSpellCheckerEnabled = enabled;
|
||||
})
|
||||
}),
|
||||
words: t.procedure.query(() =>
|
||||
globalThis.window?.webContents.session.listWordsInSpellCheckerDictionary()
|
||||
),
|
||||
deleteWord: t.procedure.input(z.string()).mutation(({ input: word }) => {
|
||||
globalThis.window?.webContents.session.removeWordFromSpellCheckerDictionary(
|
||||
word
|
||||
);
|
||||
})
|
||||
});
|
||||
|
||||
@@ -27,19 +27,23 @@ export type CLIOptions = {
|
||||
hidden: boolean;
|
||||
};
|
||||
|
||||
export async function parseArguments(): Promise<CLIOptions> {
|
||||
export async function parseArguments(argv: string[]): Promise<CLIOptions> {
|
||||
const result: CLIOptions = {
|
||||
note: false,
|
||||
notebook: false,
|
||||
reminder: false,
|
||||
hidden: false
|
||||
};
|
||||
const { hidden } = await yargs(hideBin(process.argv))
|
||||
const { hidden } = await yargs(hideBin(argv))
|
||||
.boolean("hidden")
|
||||
// have to account for this flag added on Windows when launching
|
||||
// via Jumplist
|
||||
.boolean("allow-file-access-from-files")
|
||||
.command("new", "Create a new item", (yargs) => {
|
||||
return yargs
|
||||
.command("note", "Create a new note", {}, () => {
|
||||
result.note = true;
|
||||
console.log("HERE!");
|
||||
})
|
||||
.command("notebook", "Create a new notebook", {}, () => {
|
||||
result.notebook = true;
|
||||
|
||||
@@ -24,7 +24,6 @@ import { configureAutoUpdater } from "./utils/autoupdater";
|
||||
import { getBackgroundColor, getTheme, setTheme } from "./utils/theme";
|
||||
import { setupMenu } from "./utils/menu";
|
||||
import { WindowState } from "./utils/window-state";
|
||||
import { AutoLaunch } from "./utils/autolaunch";
|
||||
import { setupJumplist } from "./utils/jumplist";
|
||||
import { setupTray } from "./utils/tray";
|
||||
import { CLIOptions, parseArguments } from "./cli";
|
||||
@@ -34,6 +33,8 @@ import { router, api } from "./api";
|
||||
import { config } from "./utils/config";
|
||||
import path from "path";
|
||||
import { bringToFront } from "./utils/bring-to-front";
|
||||
import { bridge } from "./api/bridge";
|
||||
import { setupDesktopIntegration } from "./utils/desktop-integration";
|
||||
|
||||
// only run a single instance
|
||||
if (!MAC_APP_STORE && !app.requestSingleInstanceLock()) {
|
||||
@@ -63,7 +64,7 @@ process.on("unhandledRejection", (reason) => {
|
||||
app.commandLine.appendSwitch("lang", "en-US");
|
||||
|
||||
async function createWindow() {
|
||||
const cliOptions = await parseArguments();
|
||||
const cliOptions = await parseArguments(process.argv);
|
||||
setTheme(getTheme());
|
||||
|
||||
const mainWindowState = new WindowState({});
|
||||
@@ -76,7 +77,7 @@ async function createWindow() {
|
||||
darkTheme: getTheme() === "dark",
|
||||
backgroundColor: getBackgroundColor(),
|
||||
opacity: 0,
|
||||
autoHideMenuBar: true,
|
||||
autoHideMenuBar: false,
|
||||
icon: AssetManager.appIcon({
|
||||
size: 512,
|
||||
format: process.platform === "win32" ? "ico" : "png"
|
||||
@@ -90,7 +91,9 @@ async function createWindow() {
|
||||
}
|
||||
});
|
||||
|
||||
createIPCHandler({ router, windows: [mainWindow] });
|
||||
globalThis.window = mainWindow;
|
||||
mainWindow.setMenuBarVisibility(false);
|
||||
mainWindowState.manage(mainWindow);
|
||||
|
||||
if (cliOptions.hidden && !config.desktopSettings.minimizeToSystemTray)
|
||||
@@ -104,12 +107,12 @@ async function createWindow() {
|
||||
}
|
||||
|
||||
await AssetManager.loadIcons();
|
||||
setupDesktopIntegration();
|
||||
createIPCHandler({ router, windows: [mainWindow] });
|
||||
setupDesktopIntegration(config.desktopSettings);
|
||||
|
||||
mainWindow.webContents.session.setSpellCheckerDictionaryDownloadURL(
|
||||
"http://dictionaries.notesnook.com/"
|
||||
);
|
||||
mainWindow.webContents.session.setProxy({ proxyRules: config.proxyRules });
|
||||
|
||||
mainWindow.once("closed", () => {
|
||||
globalThis.window = null;
|
||||
@@ -146,8 +149,12 @@ app.once("window-all-closed", () => {
|
||||
}
|
||||
});
|
||||
|
||||
app.on("second-instance", () => {
|
||||
app.on("second-instance", async (_ev, argv) => {
|
||||
if (!globalThis.window) return;
|
||||
const cliOptions = await parseArguments(argv);
|
||||
if (cliOptions.note) bridge.onCreateItem("note");
|
||||
if (cliOptions.notebook) bridge.onCreateItem("notebook");
|
||||
if (cliOptions.reminder) bridge.onCreateItem("reminder");
|
||||
bringToFront();
|
||||
});
|
||||
|
||||
@@ -171,49 +178,3 @@ function createURL(options: CLIOptions, path = "/") {
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
function setupDesktopIntegration() {
|
||||
const desktopIntegration = config.desktopSettings;
|
||||
|
||||
if (
|
||||
desktopIntegration.closeToSystemTray ||
|
||||
desktopIntegration.minimizeToSystemTray
|
||||
) {
|
||||
setupTray();
|
||||
}
|
||||
|
||||
// when close to system tray is enabled, it becomes nigh impossible
|
||||
// to "quit" the app. This is necessary in order to fix that.
|
||||
if (desktopIntegration.closeToSystemTray) {
|
||||
app.on("before-quit", () => app.exit(0));
|
||||
}
|
||||
|
||||
globalThis.window?.once("close", (e) => {
|
||||
if (config.desktopSettings.closeToSystemTray) {
|
||||
e.preventDefault();
|
||||
if (process.platform == "darwin") {
|
||||
// on macOS window cannot be minimized/hidden if it is already fullscreen
|
||||
// so we just close it.
|
||||
if (globalThis.window?.isFullScreen()) app.exit(0);
|
||||
else app.hide();
|
||||
} else {
|
||||
globalThis.window?.minimize();
|
||||
globalThis.window?.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
globalThis.window?.on("minimize", () => {
|
||||
if (config.desktopSettings.minimizeToSystemTray) {
|
||||
if (process.platform == "darwin") {
|
||||
app.hide();
|
||||
} else {
|
||||
globalThis.window?.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (desktopIntegration.autoStart) {
|
||||
AutoLaunch.enable(!!desktopIntegration.startMinimized);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ declare global {
|
||||
}
|
||||
|
||||
process.once("loaded", async () => {
|
||||
console.log("HELLO!");
|
||||
const electronTRPC: RendererGlobalElectronTRPC = {
|
||||
sendMessage: (operation) =>
|
||||
ipcRenderer.send(ELECTRON_TRPC_CHANNEL, operation),
|
||||
|
||||
@@ -25,7 +25,11 @@ Type=Application
|
||||
Version=${app.getVersion()}
|
||||
Name=${app.getName()}
|
||||
Comment=${app.getName()} startup script
|
||||
Exec=${process.execPath}${hidden ? " --hidden" : ""}
|
||||
Exec=${
|
||||
process.env.APPIMAGE
|
||||
? `${process.env.APPIMAGE}${hidden ? " --hidden" : ""}`
|
||||
: `${process.execPath}${hidden ? " --hidden" : ""}`
|
||||
}
|
||||
StartupNotify=false
|
||||
Terminal=false`;
|
||||
|
||||
@@ -35,6 +39,8 @@ const LINUX_AUTOSTART_DIRECTORY_PATH = path.join(
|
||||
"autostart"
|
||||
);
|
||||
|
||||
const STARTUP_ARGS = ["--hidden"];
|
||||
|
||||
export class AutoLaunch {
|
||||
static enable(hidden: boolean) {
|
||||
if (process.platform === "linux") {
|
||||
@@ -47,10 +53,14 @@ export class AutoLaunch {
|
||||
LINUX_DESKTOP_ENTRY(hidden)
|
||||
);
|
||||
} else {
|
||||
const loginItemSettings = app.getLoginItemSettings({
|
||||
args: STARTUP_ARGS
|
||||
});
|
||||
if (loginItemSettings.openAtLogin) return;
|
||||
app.setLoginItemSettings({
|
||||
openAtLogin: true,
|
||||
openAsHidden: hidden,
|
||||
args: ["--hidden"]
|
||||
args: STARTUP_ARGS
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,8 @@ export const config = {
|
||||
isSpellCheckerEnabled: true,
|
||||
zoomFactor: 1,
|
||||
theme: nativeTheme.themeSource,
|
||||
automaticUpdates: true
|
||||
automaticUpdates: true,
|
||||
proxyRules: ""
|
||||
};
|
||||
|
||||
type ConfigKey = keyof typeof config;
|
||||
|
||||
74
apps/desktop/src/utils/desktop-integration.ts
Normal file
74
apps/desktop/src/utils/desktop-integration.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
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";
|
||||
import { DesktopIntegration, config } from "./config";
|
||||
import { setupTray, destroyTray } from "./tray";
|
||||
import { AutoLaunch } from "./autolaunch";
|
||||
|
||||
export function setupDesktopIntegration(
|
||||
desktopIntegration: DesktopIntegration
|
||||
) {
|
||||
if (
|
||||
desktopIntegration.closeToSystemTray ||
|
||||
desktopIntegration.minimizeToSystemTray
|
||||
) {
|
||||
setupTray();
|
||||
} else {
|
||||
destroyTray();
|
||||
}
|
||||
|
||||
// when close to system tray is enabled, it becomes nigh impossible
|
||||
// to "quit" the app. This is necessary in order to fix that.
|
||||
app.on("before-quit", () =>
|
||||
desktopIntegration.closeToSystemTray ? app.exit(0) : null
|
||||
);
|
||||
|
||||
globalThis.window?.on("close", (e) => {
|
||||
if (config.desktopSettings.closeToSystemTray) {
|
||||
e.preventDefault();
|
||||
if (process.platform == "darwin") {
|
||||
// on macOS window cannot be minimized/hidden if it is already fullscreen
|
||||
// so we just close it.
|
||||
if (globalThis.window?.isFullScreen()) app.exit(0);
|
||||
else app.hide();
|
||||
} else {
|
||||
try {
|
||||
globalThis.window?.minimize();
|
||||
globalThis.window?.hide();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
globalThis.window?.on("minimize", () => {
|
||||
if (config.desktopSettings.minimizeToSystemTray) {
|
||||
if (process.platform == "darwin") {
|
||||
app.hide();
|
||||
} else {
|
||||
globalThis.window?.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (desktopIntegration.autoStart) {
|
||||
AutoLaunch.enable(!!desktopIntegration.startMinimized);
|
||||
}
|
||||
}
|
||||
@@ -131,6 +131,17 @@ function setupMenu() {
|
||||
);
|
||||
}
|
||||
|
||||
if (params.mediaType === "image")
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
id: "copy-image",
|
||||
label: "Copy Image",
|
||||
click() {
|
||||
globalThis.window?.webContents.copyImageAt(params.x, params.y);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
if (params.isEditable)
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
|
||||
@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { protocol, net } from "electron";
|
||||
import { protocol } from "electron";
|
||||
import { isDevelopment } from "./index";
|
||||
import { createReadStream } from "fs";
|
||||
import { extname, normalize } from "path";
|
||||
@@ -62,8 +62,25 @@ function registerProtocol() {
|
||||
headers: { "Content-Type": extensionToMimeType[fileExtension] }
|
||||
});
|
||||
} else {
|
||||
return net.fetch(request, {
|
||||
bypassCustomProtocolHandlers: true
|
||||
if (request.headers.has("X-Content-Length")) {
|
||||
request.headers.set(
|
||||
"Content-Length",
|
||||
request.headers.get("X-Content-Length") || "0"
|
||||
);
|
||||
request.headers.delete("X-Content-Length");
|
||||
}
|
||||
const headers = Object.fromEntries(request.headers.entries());
|
||||
|
||||
return await fetch(request.url, {
|
||||
signal: request.signal,
|
||||
mode: request.mode,
|
||||
headers,
|
||||
method: request.method,
|
||||
body: request.body,
|
||||
credentials: request.credentials,
|
||||
referrer: (request as any).referrer,
|
||||
duplex: "half",
|
||||
redirect: "manual"
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -17,13 +17,12 @@ 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 "@azure/core-asynciterator-polyfill";
|
||||
import SettingsService from "./services/settings";
|
||||
import {
|
||||
THEME_COMPATIBILITY_VERSION,
|
||||
useThemeEngineStore
|
||||
} from "@notesnook/theme";
|
||||
import React, { useEffect } from "react";
|
||||
import { View } from "react-native";
|
||||
import { I18nManager, View } from "react-native";
|
||||
import "react-native-gesture-handler";
|
||||
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
||||
import { SafeAreaProvider } from "react-native-safe-area-context";
|
||||
@@ -32,11 +31,16 @@ import { withErrorBoundry } from "./components/exception-handler";
|
||||
import GlobalSafeAreaProvider from "./components/globalsafearea";
|
||||
import { useAppEvents } from "./hooks/use-app-events";
|
||||
import { ApplicationHolder } from "./navigation";
|
||||
import { themeTrpcClient } from "./screens/settings/theme-selector";
|
||||
import Notifications from "./services/notifications";
|
||||
import SettingsService from "./services/settings";
|
||||
import { TipManager } from "./services/tip-manager";
|
||||
import { useThemeStore } from "./stores/use-theme-store";
|
||||
import { useUserStore } from "./stores/use-user-store";
|
||||
import { themeTrpcClient } from "./screens/settings/theme-selector";
|
||||
|
||||
I18nManager.allowRTL(false);
|
||||
I18nManager.forceRTL(false);
|
||||
I18nManager.swapLeftAndRightInRTL(false);
|
||||
|
||||
SettingsService.checkOrientation();
|
||||
const App = () => {
|
||||
|
||||
@@ -16,8 +16,8 @@ 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 { MMKVLoader } from "react-native-mmkv-storage";
|
||||
import { initalize } from "@notesnook/core/dist/logger";
|
||||
import { MMKVLoader } from "react-native-mmkv-storage";
|
||||
import { KV } from "./storage";
|
||||
|
||||
const LoggerStorage = new MMKVLoader()
|
||||
@@ -26,4 +26,4 @@ const LoggerStorage = new MMKVLoader()
|
||||
|
||||
initalize(new KV(LoggerStorage));
|
||||
|
||||
export {};
|
||||
export { LoggerStorage };
|
||||
|
||||
@@ -90,6 +90,10 @@ export class KV {
|
||||
return this.storage.removeItem(key);
|
||||
}
|
||||
|
||||
async removeMulti(keys) {
|
||||
return this.storage.removeItems(...keys);
|
||||
}
|
||||
|
||||
async clear() {
|
||||
return this.storage.clearStore();
|
||||
}
|
||||
@@ -145,6 +149,7 @@ export default {
|
||||
clear: () => DefaultStorage.clear(),
|
||||
getAllKeys: () => DefaultStorage.getAllKeys(),
|
||||
writeMulti: (items) => DefaultStorage.writeMulti(items),
|
||||
removeMulti: (keys) => DefaultStorage.removeMulti(keys),
|
||||
encrypt,
|
||||
decrypt,
|
||||
decryptMulti,
|
||||
|
||||
@@ -34,6 +34,8 @@ import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { db } from "../database";
|
||||
import Storage from "../database/storage";
|
||||
import { cacheDir, copyFileAsync, releasePermissions } from "./utils";
|
||||
import { createCacheDir, exists } from "./io";
|
||||
import { IOS_APPGROUPID } from "../../utils/constants";
|
||||
|
||||
export const FileDownloadStatus = {
|
||||
Success: 1,
|
||||
@@ -62,6 +64,7 @@ export async function downloadAttachments(
|
||||
canceled,
|
||||
groupId
|
||||
) {
|
||||
await createCacheDir();
|
||||
if (!attachments || !attachments.length) return;
|
||||
const result = new Map();
|
||||
|
||||
@@ -179,9 +182,13 @@ export default async function downloadAttachment(
|
||||
silent: false,
|
||||
cache: false,
|
||||
throwError: false,
|
||||
groupId: undefined
|
||||
groupId: undefined,
|
||||
base64: false,
|
||||
text: false
|
||||
}
|
||||
) {
|
||||
await createCacheDir();
|
||||
|
||||
let attachment = db.attachments.attachment(hash);
|
||||
if (!attachment) {
|
||||
console.log("attachment not found");
|
||||
@@ -208,10 +215,17 @@ export default async function downloadAttachment(
|
||||
options.groupId || attachment.metadata.hash,
|
||||
attachment.metadata.hash
|
||||
);
|
||||
if (
|
||||
!(await RNFetchBlob.fs.exists(`${cacheDir}/${attachment.metadata.hash}`))
|
||||
)
|
||||
|
||||
if (!(await exists(attachment.metadata.hash))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.base64 || options.text) {
|
||||
return await db.attachments.read(
|
||||
attachment.metadata.hash,
|
||||
options.base64 ? "base64" : "text"
|
||||
);
|
||||
}
|
||||
|
||||
let filename = getFileNameWithExtension(
|
||||
attachment.metadata.filename,
|
||||
@@ -229,7 +243,8 @@ export default async function downloadAttachment(
|
||||
mime: attachment.metadata.type,
|
||||
fileName: options.cache ? undefined : filename,
|
||||
uri: options.cache ? undefined : folder.uri,
|
||||
chunkSize: attachment.chunkSize
|
||||
chunkSize: attachment.chunkSize,
|
||||
appGroupId: IOS_APPGROUPID
|
||||
};
|
||||
|
||||
let fileUri = await Sodium.decryptFile(
|
||||
|
||||
@@ -24,15 +24,18 @@ import { ToastEvent } from "../../services/event-manager";
|
||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { db } from "../database";
|
||||
import { cacheDir, fileCheck } from "./utils";
|
||||
import { createCacheDir, exists } from "./io";
|
||||
|
||||
export async function downloadFile(filename, data, cancelToken) {
|
||||
if (!data) return false;
|
||||
let { url, headers } = data;
|
||||
|
||||
await createCacheDir();
|
||||
|
||||
let { url, headers } = data;
|
||||
let path = `${cacheDir}/${filename}`;
|
||||
|
||||
try {
|
||||
let exists = await RNFetchBlob.fs.exists(path);
|
||||
if (exists) {
|
||||
if (await exists(filename)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -60,44 +63,55 @@ export async function downloadFile(filename, data, cancelToken) {
|
||||
console.log("downloading: ", recieved, total);
|
||||
});
|
||||
|
||||
cancelToken.cancel = request.cancel;
|
||||
cancelToken.cancel = () => {
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
request.cancel();
|
||||
};
|
||||
let response = await request;
|
||||
await fileCheck(response, totalSize);
|
||||
let status = response.info().status;
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
return status >= 200 && status < 300;
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
heading: "Error downloading file",
|
||||
message: e.message,
|
||||
type: "error",
|
||||
context: "global"
|
||||
});
|
||||
ToastEvent.show({
|
||||
heading: "Error downloading file",
|
||||
message: e.message,
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
if (e.message !== "canceled") {
|
||||
ToastEvent.show({
|
||||
heading: "Error downloading file",
|
||||
message: e.message,
|
||||
type: "error",
|
||||
context: "global"
|
||||
});
|
||||
ToastEvent.show({
|
||||
heading: "Error downloading file",
|
||||
message: e.message,
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
}
|
||||
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
RNFetchBlob.fs.unlink(path).catch(console.log);
|
||||
console.log("download file error: ", e, url, headers);
|
||||
console.log("Download file error:", e, url, headers);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUploadedFileSize(hash) {
|
||||
const url = `${hosts.API_HOST}/s3?name=${hash}`;
|
||||
const token = await db.user.tokenManager.getAccessToken();
|
||||
try {
|
||||
const url = `${hosts.API_HOST}/s3?name=${hash}`;
|
||||
const token = await db.user.tokenManager.getAccessToken();
|
||||
|
||||
const attachmentInfo = await fetch(url, {
|
||||
method: "HEAD",
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
});
|
||||
const attachmentInfo = await fetch(url, {
|
||||
method: "HEAD",
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
});
|
||||
|
||||
const contentLength = parseInt(attachmentInfo.headers?.get("content-length"));
|
||||
return isNaN(contentLength) ? 0 : contentLength;
|
||||
const contentLength = parseInt(
|
||||
attachmentInfo.headers?.get("content-length")
|
||||
);
|
||||
return isNaN(contentLength) ? 0 : contentLength;
|
||||
} catch (e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkAttachment(hash) {
|
||||
|
||||
@@ -20,31 +20,28 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { Platform } from "react-native";
|
||||
import Sodium from "@ammarahmed/react-native-sodium";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import { cacheDir, getRandomId } from "./utils";
|
||||
import { cacheDir, cacheDirOld, getRandomId } from "./utils";
|
||||
import { db } from "../database";
|
||||
import { compressToBase64 } from "./compress";
|
||||
import { IOS_APPGROUPID } from "../../utils/constants";
|
||||
|
||||
export async function readEncrypted(filename, key, cipherData) {
|
||||
await migrateFilesFromCache();
|
||||
console.log("Read encrypted file...");
|
||||
let path = `${cacheDir}/${filename}`;
|
||||
|
||||
try {
|
||||
const iosAppGroup =
|
||||
Platform.OS === "ios"
|
||||
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
|
||||
: null;
|
||||
|
||||
const appGroupPath = `${iosAppGroup}/${filename}`;
|
||||
let exists =
|
||||
(await RNFetchBlob.fs.exists(path)) ||
|
||||
(Platform.OS === "ios" && (await RNFetchBlob.fs.exists(appGroupPath)));
|
||||
|
||||
if (!exists) {
|
||||
if (!(await exists(filename))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const attachment = db.attachments.attachment(filename);
|
||||
const isPng = /(png)/g.test(attachment?.metadata.type);
|
||||
const isJpeg = /(jpeg|jpg)/g.test(attachment?.metadata.type);
|
||||
const isPng = !attachment.metadata.type
|
||||
? false
|
||||
: /(png)/g.test(attachment?.metadata.type);
|
||||
const isJpeg = !attachment.metadata.type
|
||||
? false
|
||||
: /(jpeg|jpg)/g.test(attachment?.metadata.type);
|
||||
|
||||
let output = await Sodium.decryptFile(
|
||||
key,
|
||||
@@ -59,6 +56,7 @@ export async function readEncrypted(filename, key, cipherData) {
|
||||
: "base64"
|
||||
: "text"
|
||||
);
|
||||
console.log("file decrypted...");
|
||||
if (cipherData.outputType === "base64" && (isPng || isJpeg)) {
|
||||
const dCachePath = `${cacheDir}/${output}`;
|
||||
output = await compressToBase64(
|
||||
@@ -70,7 +68,7 @@ export async function readEncrypted(filename, key, cipherData) {
|
||||
return output;
|
||||
} catch (e) {
|
||||
RNFetchBlob.fs.unlink(path).catch(console.log);
|
||||
console.log(e);
|
||||
console.log("readEncrypted", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -88,6 +86,7 @@ export async function hashBase64(data) {
|
||||
}
|
||||
|
||||
export async function writeEncryptedBase64({ data, key }) {
|
||||
await createCacheDir();
|
||||
let filepath = cacheDir + `/${getRandomId("imagecache_")}`;
|
||||
await RNFetchBlob.fs.writeFile(filepath, data, "base64");
|
||||
let output = await Sodium.encryptFile(key, {
|
||||
@@ -103,6 +102,7 @@ export async function writeEncryptedBase64({ data, key }) {
|
||||
}
|
||||
|
||||
export async function deleteFile(filename, data) {
|
||||
await createCacheDir();
|
||||
let delFilePath = cacheDir + `/${filename}`;
|
||||
if (!data) {
|
||||
if (!filename) return;
|
||||
@@ -128,19 +128,89 @@ export async function deleteFile(filename, data) {
|
||||
export async function clearFileStorage() {
|
||||
try {
|
||||
let files = await RNFetchBlob.fs.ls(cacheDir);
|
||||
let oldCache = await RNFetchBlob.fs.ls(cacheDirOld);
|
||||
|
||||
for (let file of files) {
|
||||
try {
|
||||
await RNFetchBlob.fs.unlink(cacheDir + `/${file}`);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
await RNFetchBlob.fs.unlink(cacheDir + `/${file}`).catch(console.log);
|
||||
}
|
||||
for (let file of oldCache) {
|
||||
await RNFetchBlob.fs.unlink(cacheDirOld + `/${file}`).catch(console.log);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
console.log("clearFileStorage", e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function createCacheDir() {
|
||||
if (!(await RNFetchBlob.fs.exists(cacheDir))) {
|
||||
await RNFetchBlob.fs.mkdir(cacheDir);
|
||||
console.log("Cache directory created");
|
||||
}
|
||||
}
|
||||
|
||||
export async function migrateFilesFromCache() {
|
||||
try {
|
||||
await createCacheDir();
|
||||
const migratedFilesPath = cacheDir + "/.migrated_1";
|
||||
const migrated = await RNFetchBlob.fs.exists(migratedFilesPath);
|
||||
if (migrated) {
|
||||
console.log("Files migrated already");
|
||||
return;
|
||||
}
|
||||
|
||||
let files = await RNFetchBlob.fs.ls(cacheDir);
|
||||
console.log("Files to migrate:", files.join(","));
|
||||
|
||||
let oldCache = await RNFetchBlob.fs.ls(cacheDirOld);
|
||||
for (let file of oldCache) {
|
||||
if (file.startsWith("org.") || file.startsWith("com.")) continue;
|
||||
RNFetchBlob.fs
|
||||
.mv(cacheDirOld + `/${file}`, cacheDir + `/${file}`)
|
||||
.catch(console.log);
|
||||
console.log("Moved", file);
|
||||
}
|
||||
await RNFetchBlob.fs.createFile(migratedFilesPath, "1", "utf8");
|
||||
} catch (e) {
|
||||
console.log("migrateFilesFromCache", e);
|
||||
}
|
||||
}
|
||||
|
||||
const ABYTES = 17;
|
||||
export async function exists(filename) {
|
||||
let exists = await RNFetchBlob.fs.exists(`${cacheDir}/${filename}`);
|
||||
let path = `${cacheDir}/${filename}`;
|
||||
|
||||
const iosAppGroup =
|
||||
Platform.OS === "ios"
|
||||
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
|
||||
: null;
|
||||
const appGroupPath = `${iosAppGroup}/${filename}`;
|
||||
|
||||
let exists = await RNFetchBlob.fs.exists(path);
|
||||
|
||||
// Check if file is present in app group path.
|
||||
let existsInAppGroup = false;
|
||||
if (!exists && Platform.OS === "ios") {
|
||||
existsInAppGroup = await RNFetchBlob.fs.exists(appGroupPath);
|
||||
}
|
||||
|
||||
if (exists || existsInAppGroup) {
|
||||
const attachment = db.attachments.attachment(filename);
|
||||
const totalChunks = Math.ceil(attachment.length / attachment.chunkSize);
|
||||
const totalAbytes = totalChunks * ABYTES;
|
||||
const expectedFileSize = attachment.length + totalAbytes;
|
||||
|
||||
const stat = await RNFetchBlob.fs.stat(
|
||||
existsInAppGroup ? appGroupPath : path
|
||||
);
|
||||
|
||||
if (stat.size !== expectedFileSize) {
|
||||
RNFetchBlob.fs
|
||||
.unlink(existsInAppGroup ? appGroupPath : path)
|
||||
.catch(console.log);
|
||||
return false;
|
||||
}
|
||||
|
||||
exists = true;
|
||||
}
|
||||
return exists;
|
||||
}
|
||||
|
||||
@@ -24,11 +24,12 @@ import { cacheDir } from "./utils";
|
||||
import { isImage, isDocument } from "@notesnook/core/dist/utils/filename";
|
||||
import { Platform } from "react-native";
|
||||
import { IOS_APPGROUPID } from "../../utils/constants";
|
||||
import { createCacheDir } from "./io";
|
||||
|
||||
export async function uploadFile(filename, data, cancelToken) {
|
||||
if (!data) return false;
|
||||
let { url, headers } = data;
|
||||
|
||||
await createCacheDir();
|
||||
DatabaseLogger.info(`Preparing to upload file: ${filename}`);
|
||||
|
||||
try {
|
||||
@@ -72,7 +73,10 @@ export async function uploadFile(filename, data, cancelToken) {
|
||||
);
|
||||
});
|
||||
|
||||
cancelToken.cancel = request.cancel;
|
||||
cancelToken.cancel = () => {
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
request.cancel();
|
||||
};
|
||||
let response = await request;
|
||||
|
||||
let status = response.info().status;
|
||||
|
||||
@@ -21,7 +21,12 @@ import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import { Platform } from "react-native";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
|
||||
export const cacheDir = RNFetchBlob.fs.dirs.CacheDir;
|
||||
export const cacheDirOld = RNFetchBlob.fs.dirs.CacheDir;
|
||||
|
||||
export const cacheDir =
|
||||
Platform.OS == "ios"
|
||||
? RNFetchBlob.fs.dirs.LibraryDir + "/.cache"
|
||||
: RNFetchBlob.fs.dirs.DocumentDir + "/.cache";
|
||||
|
||||
export function getRandomId(prefix) {
|
||||
return Math.random()
|
||||
@@ -66,9 +71,12 @@ export function cancelable(operation) {
|
||||
}
|
||||
|
||||
export function copyFileAsync(source, dest) {
|
||||
return new Promise((resolve) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
ScopedStorage.copyFile(source, dest, (e, r) => {
|
||||
console.log(e, r);
|
||||
if (e) {
|
||||
reject(e);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,10 +34,69 @@ import BaseDialog from "../dialog/base-dialog";
|
||||
import { allowedOnPlatform, renderItem } from "./functions";
|
||||
import { useCallback } from "react";
|
||||
|
||||
/**
|
||||
* Test announcement
|
||||
* {
|
||||
id: "some-announcement",
|
||||
type: "dialog",
|
||||
body: [
|
||||
{
|
||||
type: "title",
|
||||
text: "This is a title",
|
||||
platforms: ["all"]
|
||||
},
|
||||
{
|
||||
type: "description",
|
||||
text: "Most of you are too busy to keep up to date with what's happening in Notesnook. That is unfortunate because Notesnook has come a looooong way.",
|
||||
style: {
|
||||
marginBottom: 1
|
||||
},
|
||||
platforms: ["all"]
|
||||
},
|
||||
{
|
||||
type: "description",
|
||||
text: "To solve this, we are launching the Notesnook Digest — a newsletter to help you stay updated about Notesnook development. And to keep things interesting I'll also sprinkle this newsletter with other interesting stuff like privacy tips & news, interesting books, things I am looking forward to etc.",
|
||||
style: {
|
||||
marginBottom: 1
|
||||
},
|
||||
platforms: ["all"]
|
||||
},
|
||||
{
|
||||
type: "description",
|
||||
text: "So be sure to subscribe. There won't be a proper schedule to this (yet) maybe once or twice a month. I promise no spam — only more awesomeness.",
|
||||
style: {
|
||||
marginBottom: 1
|
||||
},
|
||||
platforms: ["all"]
|
||||
},
|
||||
{
|
||||
type: "description",
|
||||
text: "— May privacy reign.",
|
||||
style: {
|
||||
marginBottom: 1
|
||||
},
|
||||
platforms: ["all"]
|
||||
},
|
||||
{
|
||||
type: "callToActions",
|
||||
actions: [
|
||||
{
|
||||
type: "promo",
|
||||
title: "15% Off",
|
||||
platforms: ["android"],
|
||||
data: "com.streetwriters.notesnook.sub.yr.15"
|
||||
}
|
||||
],
|
||||
platforms: ["all"]
|
||||
}
|
||||
]
|
||||
}
|
||||
*/
|
||||
|
||||
export const AnnouncementDialog = () => {
|
||||
const { colors } = useThemeColors();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [info, setInfo] = useState(null);
|
||||
const [info, setInfo] = useState();
|
||||
const remove = useMessageStore((state) => state.remove);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -51,7 +110,9 @@ export const AnnouncementDialog = () => {
|
||||
|
||||
const open = (data) => {
|
||||
setInfo(data);
|
||||
setVisible(true);
|
||||
setImmediate(() => {
|
||||
setVisible(true);
|
||||
});
|
||||
};
|
||||
|
||||
const close = useCallback(() => {
|
||||
|
||||
@@ -48,7 +48,7 @@ import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { formatBytes } from "@notesnook/common";
|
||||
|
||||
const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
|
||||
const { colors } = useThemeColors();
|
||||
const contextId = attachment.metadata.hash;
|
||||
const [filename, setFilename] = useState(attachment.metadata.filename);
|
||||
@@ -64,7 +64,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
name: "Download",
|
||||
onPress: async () => {
|
||||
if (currentProgress) {
|
||||
await db.fs.cancel(attachment.metadata.hash, "download");
|
||||
await db.fs.cancel(attachment.metadata.hash);
|
||||
useAttachmentStore.getState().remove(attachment.metadata.hash);
|
||||
}
|
||||
downloadAttachment(attachment.metadata.hash, false);
|
||||
@@ -117,7 +117,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
});
|
||||
}
|
||||
|
||||
setAttachments([...db.attachments.all]);
|
||||
setAttachments();
|
||||
setLoading({
|
||||
name: null
|
||||
});
|
||||
@@ -140,7 +140,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
filename: value
|
||||
});
|
||||
setFilename(value);
|
||||
setAttachments([...db.attachments.all]);
|
||||
setAttachments();
|
||||
}
|
||||
},
|
||||
positiveText: "Rename"
|
||||
@@ -152,8 +152,8 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
name: "Delete",
|
||||
onPress: async () => {
|
||||
await db.attachments.remove(attachment.metadata.hash, false);
|
||||
setAttachments([...db.attachments.all]);
|
||||
eSendEvent(eCloseSheet, contextId);
|
||||
setAttachments();
|
||||
close();
|
||||
},
|
||||
icon: "delete-outline"
|
||||
}
|
||||
@@ -362,8 +362,13 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
Actions.present = (attachment, set, context) => {
|
||||
presentSheet({
|
||||
context: context,
|
||||
component: (ref) => (
|
||||
<Actions fwdRef={ref} setAttachments={set} attachment={attachment} />
|
||||
component: (ref, close) => (
|
||||
<Actions
|
||||
fwdRef={ref}
|
||||
setAttachments={set}
|
||||
close={close}
|
||||
attachment={attachment}
|
||||
/>
|
||||
)
|
||||
});
|
||||
};
|
||||
|
||||
@@ -49,7 +49,7 @@ export const AttachmentDialog = ({ note }) => {
|
||||
const { height } = useSettingStore((state) => state.dimensions);
|
||||
const [attachments, setAttachments] = useState(
|
||||
note
|
||||
? db.attachments.ofNote(note.id, "all")
|
||||
? db.attachments.ofNote(note?.id, "all")
|
||||
: [...(db.attachments.all || [])]
|
||||
);
|
||||
|
||||
@@ -59,8 +59,8 @@ export const AttachmentDialog = ({ note }) => {
|
||||
const [currentFilter, setCurrentFilter] = useState("all");
|
||||
|
||||
const onChangeText = (text) => {
|
||||
const attachments = note
|
||||
? db.attachments.ofNote(note.id, "all")
|
||||
const attachments = note?.id
|
||||
? db.attachments.ofNote(note?.id, "all")
|
||||
: [...(db.attachments.all || [])];
|
||||
|
||||
attachmentSearchValue.current = text;
|
||||
@@ -68,7 +68,7 @@ export const AttachmentDialog = ({ note }) => {
|
||||
!attachmentSearchValue.current ||
|
||||
attachmentSearchValue.current === ""
|
||||
) {
|
||||
setAttachments([...attachments]);
|
||||
setAttachments(filterAttachments(currentFilter));
|
||||
}
|
||||
clearTimeout(searchTimer.current);
|
||||
searchTimer.current = setTimeout(() => {
|
||||
@@ -77,13 +77,16 @@ export const AttachmentDialog = ({ note }) => {
|
||||
attachmentSearchValue.current
|
||||
);
|
||||
if (results.length === 0) return;
|
||||
setAttachments(results);
|
||||
|
||||
setAttachments(filterAttachments(currentFilter, results));
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
<AttachmentItem
|
||||
setAttachments={setAttachments}
|
||||
setAttachments={() => {
|
||||
setAttachments(filterAttachments(currentFilter));
|
||||
}}
|
||||
attachment={item}
|
||||
context="attachments-list"
|
||||
/>
|
||||
@@ -133,11 +136,13 @@ export const AttachmentDialog = ({ note }) => {
|
||||
}
|
||||
];
|
||||
|
||||
const filterAttachments = (type) => {
|
||||
const attachments = note
|
||||
? db.attachments.ofNote(note.id, "all")
|
||||
const filterAttachments = (type, _attachments) => {
|
||||
const attachments = _attachments
|
||||
? _attachments
|
||||
: note
|
||||
? db.attachments.ofNote(note?.id, "all")
|
||||
: [...(db.attachments.all || [])];
|
||||
isDocument;
|
||||
|
||||
switch (type) {
|
||||
case "all":
|
||||
return attachments;
|
||||
|
||||
@@ -17,14 +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/>.
|
||||
*/
|
||||
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Platform, View } from "react-native";
|
||||
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
|
||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { eCloseLoginDialog, eOpenLoginDialog } from "../../utils/events";
|
||||
import { sleep } from "../../utils/time";
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
@@ -34,7 +36,6 @@ import { IconButton } from "../ui/icon-button";
|
||||
import { hideAuth, initialAuthMode } from "./common";
|
||||
import { Login } from "./login";
|
||||
import { Signup } from "./signup";
|
||||
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
|
||||
|
||||
export const AuthMode = {
|
||||
login: 0,
|
||||
@@ -68,6 +69,9 @@ const AuthModal = () => {
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
useUserStore.setState({
|
||||
disableAppLockRequests: false
|
||||
});
|
||||
actionSheetRef.current?.hide();
|
||||
setCurrentAuthMode(AuthMode.login);
|
||||
setVisible(false);
|
||||
@@ -77,6 +81,11 @@ const AuthModal = () => {
|
||||
<BaseDialog
|
||||
overlayOpacity={0}
|
||||
statusBarTranslucent={false}
|
||||
onShow={() => {
|
||||
useUserStore.setState({
|
||||
disableAppLockRequests: true
|
||||
});
|
||||
}}
|
||||
onRequestClose={currentAuthMode !== AuthMode.welcomeSignup && close}
|
||||
visible={true}
|
||||
onClose={close}
|
||||
@@ -85,11 +94,14 @@ const AuthModal = () => {
|
||||
background={colors.primary.background}
|
||||
transparent={false}
|
||||
animated={false}
|
||||
centered={false}
|
||||
enableSheetKeyboardHandler
|
||||
>
|
||||
<KeyboardAwareScrollView
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
enableAutomaticScroll={false}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
>
|
||||
{currentAuthMode !== AuthMode.login ? (
|
||||
|
||||
@@ -26,12 +26,15 @@ import {
|
||||
ToastEvent
|
||||
} from "../../services/event-manager";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { eCloseSheet } from "../../utils/events";
|
||||
import { eCloseSheet, eOpenRecoveryKeyDialog } from "../../utils/events";
|
||||
import DialogHeader from "../dialog/dialog-header";
|
||||
import { Button } from "../ui/button";
|
||||
import Input from "../ui/input";
|
||||
import { Notice } from "../ui/notice";
|
||||
import Seperator from "../ui/seperator";
|
||||
import { Dialog } from "../dialog";
|
||||
import BackupService from "../../services/backup";
|
||||
import { sleep } from "../../utils/time";
|
||||
|
||||
export const ChangePassword = () => {
|
||||
const passwordInputRef = useRef();
|
||||
@@ -64,6 +67,9 @@ export const ChangePassword = () => {
|
||||
}
|
||||
setLoading(true);
|
||||
try {
|
||||
const result = await BackupService.run(false, "change-password-dialog");
|
||||
if (!result) throw new Error("Failed to create backup");
|
||||
|
||||
await db.user.clearSessions();
|
||||
await db.user.changePassword(oldPassword.current, password.current);
|
||||
ToastEvent.show({
|
||||
@@ -73,6 +79,8 @@ export const ChangePassword = () => {
|
||||
});
|
||||
setLoading(false);
|
||||
eSendEvent(eCloseSheet);
|
||||
await sleep(300);
|
||||
eSendEvent(eOpenRecoveryKeyDialog);
|
||||
} catch (e) {
|
||||
setLoading(false);
|
||||
ToastEvent.show({
|
||||
@@ -92,6 +100,7 @@ export const ChangePassword = () => {
|
||||
padding: 12
|
||||
}}
|
||||
>
|
||||
<Dialog context="change-password-dialog" />
|
||||
<DialogHeader
|
||||
title="Change password"
|
||||
paragraph="Enter your old and new passwords"
|
||||
@@ -129,7 +138,14 @@ export const ChangePassword = () => {
|
||||
/>
|
||||
|
||||
<Notice
|
||||
text="Changing password is a non-undoable process. You will be logged out from all your devices. Please make sure you do not close the app while your password is changing and have good internet connection."
|
||||
text={`Changing password is an irreversible process. You will be logged out from all your devices. Please make sure you do not close the app while your password is changing and have good internet connection.`}
|
||||
type="alert"
|
||||
/>
|
||||
|
||||
<View style={{ height: 10 }} />
|
||||
|
||||
<Notice
|
||||
text={`Once your password is changed, please make sure to save the new account recovery key.`}
|
||||
type="alert"
|
||||
/>
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Modal, View } from "react-native";
|
||||
import { View } from "react-native";
|
||||
import { db } from "../../common/database";
|
||||
import { MMKV } from "../../common/database/mmkv";
|
||||
import BiometricService from "../../services/biometrics";
|
||||
@@ -37,6 +37,7 @@ import { eLoginSessionExpired, eUserLoggedIn } from "../../utils/events";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
import { Dialog } from "../dialog";
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
import { presentDialog } from "../dialog/functions";
|
||||
import SheetProvider from "../sheet-provider";
|
||||
import { Toast } from "../toast";
|
||||
@@ -68,7 +69,11 @@ export const SessionExpired = () => {
|
||||
eSendEvent(eUserLoggedIn, true);
|
||||
setVisible(false);
|
||||
setFocused(false);
|
||||
}
|
||||
useUserStore.setState({
|
||||
disableAppLockRequests: false
|
||||
});
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
const logout = async () => {
|
||||
@@ -82,6 +87,9 @@ export const SessionExpired = () => {
|
||||
MMKV.clearStore();
|
||||
clearAllStores();
|
||||
setVisible(false);
|
||||
useUserStore.setState({
|
||||
disableAppLockRequests: false
|
||||
});
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
heading: e.message,
|
||||
@@ -125,20 +133,31 @@ export const SessionExpired = () => {
|
||||
email.current = user.email;
|
||||
setFocused(false);
|
||||
setVisible(true);
|
||||
useUserStore.setState({
|
||||
disableAppLockRequests: true
|
||||
});
|
||||
}
|
||||
}, [email]);
|
||||
|
||||
return (
|
||||
visible && (
|
||||
<Modal
|
||||
<BaseDialog
|
||||
transparent={false}
|
||||
background={colors.primary.background}
|
||||
bounce={false}
|
||||
animated={false}
|
||||
centered={false}
|
||||
onShow={async () => {
|
||||
await sleep(300);
|
||||
passwordInputRef.current?.focus();
|
||||
setFocused(true);
|
||||
useUserStore.setState({
|
||||
disableAppLockRequests: true
|
||||
});
|
||||
}}
|
||||
enableSheetKeyboardHandler={true}
|
||||
visible={true}
|
||||
>
|
||||
<SheetProvider context="two_factor_verify" />
|
||||
<View
|
||||
style={{
|
||||
width: focused ? "100%" : "99.9%",
|
||||
@@ -231,7 +250,9 @@ export const SessionExpired = () => {
|
||||
</View>
|
||||
<Toast context="local" />
|
||||
<Dialog context="session_expiry" />
|
||||
</Modal>
|
||||
|
||||
<SheetProvider context="two_factor_verify" />
|
||||
</BaseDialog>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -33,7 +33,7 @@ export const LoginSteps = {
|
||||
passwordAuth: 3
|
||||
};
|
||||
|
||||
export const useLogin = (onFinishLogin) => {
|
||||
export const useLogin = (onFinishLogin, sessionExpired = false) => {
|
||||
const [error, setError] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const setUser = useUserStore((state) => state.setUser);
|
||||
@@ -69,6 +69,7 @@ export const useLogin = (onFinishLogin) => {
|
||||
switch (step) {
|
||||
case LoginSteps.emailAuth: {
|
||||
const mfaInfo = await db.user.authenticateEmail(email.current);
|
||||
console.log("email auth", mfaInfo);
|
||||
if (mfaInfo) {
|
||||
TwoFactorVerification.present(async (mfa, callback) => {
|
||||
try {
|
||||
@@ -101,7 +102,12 @@ export const useLogin = (onFinishLogin) => {
|
||||
break;
|
||||
}
|
||||
case LoginSteps.passwordAuth: {
|
||||
await db.user.authenticatePassword(email.current, password.current);
|
||||
await db.user.authenticatePassword(
|
||||
email.current,
|
||||
password.current,
|
||||
null,
|
||||
sessionExpired
|
||||
);
|
||||
finishLogin();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useEffect } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
KeyboardAvoidingView,
|
||||
Modal,
|
||||
@@ -31,6 +31,9 @@ import useIsFloatingKeyboard from "../../hooks/use-is-floating-keyboard";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { BouncingView } from "../ui/transitions/bouncing-view";
|
||||
import { ScopedThemeProvider } from "@notesnook/theme";
|
||||
import SettingsService from "../../services/settings";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { useAppState } from "../../hooks/use-app-state";
|
||||
|
||||
const BaseDialog = ({
|
||||
visible,
|
||||
@@ -48,9 +51,13 @@ const BaseDialog = ({
|
||||
bounce = true,
|
||||
closeOnTouch = true,
|
||||
useSafeArea = true,
|
||||
avoidKeyboardResize = false
|
||||
avoidKeyboardResize = false,
|
||||
enableSheetKeyboardHandler = false
|
||||
}) => {
|
||||
const floating = useIsFloatingKeyboard();
|
||||
const appState = useAppState();
|
||||
const lockEvents = useRef(false);
|
||||
const [internalVisible, setIntervalVisible] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
@@ -58,14 +65,34 @@ const BaseDialog = ({
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (useUserStore.getState().disableAppLockRequests) return;
|
||||
if (SettingsService.get().appLockMode === "background") {
|
||||
if (appState === "background") {
|
||||
setIntervalVisible(false);
|
||||
if (useUserStore.getState().appLocked) {
|
||||
lockEvents.current = true;
|
||||
const unsub = useUserStore.subscribe((state) => {
|
||||
if (!state.appLocked) {
|
||||
setIntervalVisible(true);
|
||||
unsub();
|
||||
setTimeout(() => {
|
||||
lockEvents.current = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [appState]);
|
||||
|
||||
const Wrapper = useSafeArea ? SafeAreaView : View;
|
||||
|
||||
return (
|
||||
<ScopedThemeProvider value="dialog">
|
||||
<Modal
|
||||
visible={visible}
|
||||
visible={visible && internalVisible}
|
||||
transparent={true}
|
||||
animated
|
||||
statusBarTranslucent={statusBarTranslucent}
|
||||
supportedOrientations={[
|
||||
"portrait",
|
||||
@@ -75,13 +102,17 @@ const BaseDialog = ({
|
||||
"landscape-right"
|
||||
]}
|
||||
onShow={() => {
|
||||
if (lockEvents.current) return;
|
||||
if (onShow) {
|
||||
onShow();
|
||||
useSettingStore.getState().setSheetKeyboardHandler(false);
|
||||
if (!enableSheetKeyboardHandler) {
|
||||
useSettingStore.getState().setSheetKeyboardHandler(false);
|
||||
}
|
||||
}
|
||||
}}
|
||||
animationType={animation}
|
||||
onRequestClose={() => {
|
||||
if (lockEvents.current) return;
|
||||
if (!closeOnTouch) return null;
|
||||
useSettingStore.getState().setSheetKeyboardHandler(true);
|
||||
onRequestClose && onRequestClose();
|
||||
@@ -107,6 +138,7 @@ const BaseDialog = ({
|
||||
style={[
|
||||
styles.backdrop,
|
||||
{
|
||||
alignItems: centered ? "center" : undefined,
|
||||
justifyContent: centered
|
||||
? "center"
|
||||
: bottom
|
||||
@@ -133,8 +165,7 @@ const styles = StyleSheet.create({
|
||||
backdrop: {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
justifyContent: "center"
|
||||
},
|
||||
overlayButton: {
|
||||
width: "100%",
|
||||
|
||||
@@ -40,7 +40,10 @@ import { Button } from "../ui/button";
|
||||
export const Dialog = ({ context = "global" }) => {
|
||||
const { colors } = useThemeColors();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [inputValue, setInputValue] = useState(null);
|
||||
const [checked, setChecked] = useState(false);
|
||||
const values = useRef({
|
||||
inputValue: undefined
|
||||
});
|
||||
const inputRef = useRef();
|
||||
const [dialogInfo, setDialogInfo] = useState({
|
||||
title: "",
|
||||
@@ -76,7 +79,8 @@ export const Dialog = ({ context = "global" }) => {
|
||||
if (dialogInfo.positivePress) {
|
||||
inputRef.current?.blur();
|
||||
let result = await dialogInfo.positivePress(
|
||||
inputValue || dialogInfo.defaultValue
|
||||
values.current.inputValue || dialogInfo.defaultValue,
|
||||
checked
|
||||
);
|
||||
if (result === false) {
|
||||
return;
|
||||
@@ -91,14 +95,16 @@ export const Dialog = ({ context = "global" }) => {
|
||||
if (!data.context) data.context = "global";
|
||||
if (data.context !== context) return;
|
||||
setDialogInfo(data);
|
||||
setChecked(false);
|
||||
values.current.inputValue = data.defaultValue;
|
||||
setVisible(true);
|
||||
setInputValue(data.defaultValue);
|
||||
},
|
||||
[context]
|
||||
);
|
||||
|
||||
const hide = () => {
|
||||
setInputValue(null);
|
||||
setChecked(false);
|
||||
values.current.inputValue = undefined;
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
@@ -106,7 +112,6 @@ export const Dialog = ({ context = "global" }) => {
|
||||
if (dialogInfo.onClose) {
|
||||
await dialogInfo.onClose();
|
||||
}
|
||||
|
||||
hide();
|
||||
};
|
||||
|
||||
@@ -159,7 +164,7 @@ export const Dialog = ({ context = "global" }) => {
|
||||
fwdRef={inputRef}
|
||||
autoCapitalize="none"
|
||||
onChangeText={(value) => {
|
||||
setInputValue(value);
|
||||
values.current.inputValue = value;
|
||||
}}
|
||||
testID="input-value"
|
||||
secureTextEntry={dialogInfo.secureTextEntry}
|
||||
@@ -176,10 +181,10 @@ export const Dialog = ({ context = "global" }) => {
|
||||
<>
|
||||
<Button
|
||||
onPress={() => {
|
||||
setInputValue(!inputValue);
|
||||
setChecked(!checked);
|
||||
}}
|
||||
icon={
|
||||
inputValue
|
||||
checked
|
||||
? "check-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
}
|
||||
@@ -187,9 +192,10 @@ export const Dialog = ({ context = "global" }) => {
|
||||
justifyContent: "flex-start"
|
||||
}}
|
||||
height={35}
|
||||
iconSize={20}
|
||||
width="100%"
|
||||
title={dialogInfo.check.info}
|
||||
type={inputValue ? dialogInfo.check.type : "gray"}
|
||||
type={checked ? dialogInfo.check.type : "gray"}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
@@ -48,7 +48,7 @@ export const ProFeatures = ({ count = 6 }) => {
|
||||
content: "Instantly sync to unlimited devices"
|
||||
},
|
||||
{
|
||||
content: "A private vault to keep everything imporant always locked"
|
||||
content: "A private vault to keep everything important always locked"
|
||||
},
|
||||
{
|
||||
content:
|
||||
|
||||
@@ -442,6 +442,7 @@ export class VaultDialog extends Component {
|
||||
if (this.state.biometricUnlock && !this.state.isBiometryEnrolled) {
|
||||
await this._enrollFingerprint(this.password);
|
||||
}
|
||||
|
||||
if (this.state.goToEditor) {
|
||||
this._openInEditor(note);
|
||||
} else if (this.state.share) {
|
||||
@@ -452,7 +453,6 @@ export class VaultDialog extends Component {
|
||||
await this._copyNote(note);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
this._takeErrorAction(e);
|
||||
}
|
||||
};
|
||||
@@ -467,44 +467,40 @@ export class VaultDialog extends Component {
|
||||
}
|
||||
|
||||
async _enrollFingerprint(password) {
|
||||
try {
|
||||
this.setState(
|
||||
{
|
||||
loading: true
|
||||
},
|
||||
async () => {
|
||||
try {
|
||||
await db.vault.unlock(password);
|
||||
await BiometricService.storeCredentials(password);
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
eSendEvent("vaultUpdated");
|
||||
ToastEvent.show({
|
||||
heading: "Biometric unlocking enabled!",
|
||||
message: "Now you can unlock notes in vault with biometrics.",
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
this.close();
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
heading: "Incorrect password",
|
||||
message:
|
||||
"Please enter the correct vault password to enable biometrics.",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.setState(
|
||||
{
|
||||
loading: true
|
||||
},
|
||||
async () => {
|
||||
try {
|
||||
await db.vault.unlock(password);
|
||||
await BiometricService.storeCredentials(password);
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
eSendEvent("vaultUpdated");
|
||||
ToastEvent.show({
|
||||
heading: "Biometric unlocking enabled!",
|
||||
message: "Now you can unlock notes in vault with biometrics.",
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
this.close();
|
||||
} catch (e) {
|
||||
this.close();
|
||||
ToastEvent.show({
|
||||
heading: "Incorrect password",
|
||||
message:
|
||||
"Please enter the correct vault password to enable biometrics.",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
this._takeErrorAction(e);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async _createVault() {
|
||||
@@ -543,7 +539,7 @@ export class VaultDialog extends Component {
|
||||
.remove(this.state.note.id, this.password)
|
||||
.then(() => {
|
||||
ToastEvent.show({
|
||||
heading: "Note permanantly unlocked.",
|
||||
heading: "Note permanently unlocked.",
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
|
||||
@@ -39,9 +39,9 @@ class ExceptionHandler extends React.Component<{
|
||||
error: Error | null;
|
||||
hasError: boolean;
|
||||
} = {
|
||||
hasError: false,
|
||||
error: null
|
||||
};
|
||||
hasError: false,
|
||||
error: null
|
||||
};
|
||||
static getDerivedStateFromError(error: Error) {
|
||||
return { hasError: true, error: error };
|
||||
}
|
||||
@@ -69,7 +69,7 @@ class ExceptionHandler extends React.Component<{
|
||||
this.props.component
|
||||
)}
|
||||
defaultTitle={this.state.error?.message}
|
||||
issueTitle="An exception occured"
|
||||
issueTitle="An exception occurred"
|
||||
/>
|
||||
<Dialog />
|
||||
</SafeAreaView>
|
||||
|
||||
@@ -17,35 +17,25 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { TouchableOpacity, useWindowDimensions, View } from "react-native";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||
import {
|
||||
eSendEvent,
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent,
|
||||
presentSheet
|
||||
} from "../../../services/event-manager";
|
||||
import React, { useRef } from "react";
|
||||
import { TouchableOpacity, View, useWindowDimensions } from "react-native";
|
||||
import { eSendEvent, presentSheet } from "../../../services/event-manager";
|
||||
import SettingsService from "../../../services/settings";
|
||||
import { GROUP } from "../../../utils/constants";
|
||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||
import { ColorValues } from "../../../utils/colors";
|
||||
import { db } from "../../../common/database";
|
||||
import { GROUP } from "../../../utils/constants";
|
||||
import { eOpenJumpToDialog } from "../../../utils/events";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import { Button } from "../../ui/button";
|
||||
import Sort from "../../sheets/sort";
|
||||
import { Button } from "../../ui/button";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import { useCallback } from "react";
|
||||
|
||||
export const SectionHeader = React.memo(
|
||||
function SectionHeader({ item, index, type, color, screen }) {
|
||||
function SectionHeader({ item, index, type, color, screen, groupOptions }) {
|
||||
const { colors } = useThemeColors();
|
||||
const { fontScale } = useWindowDimensions();
|
||||
const [groupOptions, setGroupOptions] = useState(
|
||||
db.settings?.getGroupOptions(type)
|
||||
);
|
||||
let groupBy = Object.keys(GROUP).find(
|
||||
(key) => GROUP[key] === groupOptions.groupBy
|
||||
);
|
||||
@@ -65,17 +55,6 @@ export const SectionHeader = React.memo(
|
||||
? "Default"
|
||||
: groupBy.slice(0, 1).toUpperCase() + groupBy.slice(1, groupBy.length);
|
||||
|
||||
const onUpdate = useCallback(() => {
|
||||
setGroupOptions({ ...db.settings?.getGroupOptions(type) });
|
||||
}, [type]);
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent("groupOptionsUpdate", onUpdate);
|
||||
return () => {
|
||||
eUnSubscribeEvent("groupOptionsUpdate", onUpdate);
|
||||
};
|
||||
}, [onUpdate]);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
@@ -122,7 +101,7 @@ export const SectionHeader = React.memo(
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
{index === 0 && (
|
||||
{index === 0 ? (
|
||||
<>
|
||||
<Button
|
||||
onPress={() => {
|
||||
@@ -130,7 +109,6 @@ export const SectionHeader = React.memo(
|
||||
component: <Sort screen={screen} type={type} />
|
||||
});
|
||||
}}
|
||||
hidden={screen === "Reminders"}
|
||||
tooltipText="Change sorting of items in list"
|
||||
fwdRef={sortRef}
|
||||
title={groupBy}
|
||||
@@ -181,7 +159,7 @@ export const SectionHeader = React.memo(
|
||||
size={SIZE.lg - 2}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -99,8 +99,8 @@ const NoteItem = ({
|
||||
dateBy = "dateCreated",
|
||||
noOpen = false
|
||||
}) => {
|
||||
const currentEditingNote = useEditorStore(
|
||||
(state) => state.currentEditingNote
|
||||
const isEditingNote = useEditorStore(
|
||||
(state) => state.currentEditingNote === item.id
|
||||
);
|
||||
const { colors } = useThemeColors();
|
||||
const compactMode = useIsCompactModeEnabled(item);
|
||||
@@ -112,8 +112,7 @@ const NoteItem = ({
|
||||
const reminder = getUpcomingReminder(reminders);
|
||||
const noteColor = ColorValues[item.color?.toLowerCase()];
|
||||
const tags = getTags(item);
|
||||
const primaryColors =
|
||||
currentEditingNote === item.id ? colors.selected : colors.primary;
|
||||
const primaryColors = isEditingNote ? colors.selected : colors.primary;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -77,7 +77,7 @@ export const openNotebookTopic = (item) => {
|
||||
useTrashStore.getState().setTrash();
|
||||
useSelectionStore.getState().setSelectionMode(false);
|
||||
ToastEvent.show({
|
||||
heading: "Permanantly deleted items",
|
||||
heading: "Permanently deleted items",
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
|
||||
@@ -25,13 +25,13 @@ import { useEditorStore } from "../../../stores/use-editor-store";
|
||||
|
||||
export const Filler = ({ item }) => {
|
||||
const { colors } = useThemeColors();
|
||||
const currentEditingNote = useEditorStore(
|
||||
(state) => state.currentEditingNote
|
||||
const isEditingNote = useEditorStore(
|
||||
(state) => state.currentEditingNote === item.id
|
||||
);
|
||||
|
||||
const [selected] = useIsSelected(item);
|
||||
|
||||
return currentEditingNote === item.id || selected ? (
|
||||
return isEditingNote || selected ? (
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
@@ -39,12 +39,11 @@ export const Filler = ({ item }) => {
|
||||
height: "150%",
|
||||
backgroundColor: colors.selected.background,
|
||||
borderLeftWidth: 5,
|
||||
borderLeftColor:
|
||||
currentEditingNote === item.id
|
||||
? item.color
|
||||
? colors.static[item.color]
|
||||
: colors.selected.accent
|
||||
: "transparent"
|
||||
borderLeftColor: isEditingNote
|
||||
? item.color
|
||||
? colors.static[item.color]
|
||||
: colors.selected.accent
|
||||
: "transparent"
|
||||
}}
|
||||
collapsable={false}
|
||||
/>
|
||||
|
||||
@@ -43,6 +43,9 @@ const SelectionWrapper = ({
|
||||
|
||||
const onLongPress = () => {
|
||||
if (!useSelectionStore.getState().selectionMode) {
|
||||
useSelectionStore.setState({
|
||||
selectedItemsList: []
|
||||
});
|
||||
useSelectionStore.getState().setSelectionMode(true);
|
||||
}
|
||||
useSelectionStore.getState().setSelectedItem(item);
|
||||
|
||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { Dimensions, View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { useMessageStore } from "../../stores/use-message-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
@@ -32,6 +32,7 @@ export const Card = ({ color, warning }) => {
|
||||
color = color ? color : colors.primary.accent;
|
||||
const messageBoardState = useMessageStore((state) => state.message);
|
||||
const announcement = useMessageStore((state) => state.announcement);
|
||||
const fontScale = Dimensions.get("window").fontScale;
|
||||
|
||||
return !messageBoardState.visible || announcement || warning ? null : (
|
||||
<View
|
||||
@@ -44,7 +45,6 @@ export const Card = ({ color, warning }) => {
|
||||
type="gray"
|
||||
customStyle={{
|
||||
paddingVertical: 12,
|
||||
width: "95%",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
@@ -54,17 +54,18 @@ export const Card = ({ color, warning }) => {
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
alignItems: "center",
|
||||
flexShrink: 1
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: 40,
|
||||
width: 40 * fontScale,
|
||||
backgroundColor:
|
||||
messageBoardState.type === "error"
|
||||
? hexToRGBA(colors.static.red, 0.15)
|
||||
: hexToRGBA(color, 0.15),
|
||||
height: 40,
|
||||
height: 40 * fontScale,
|
||||
borderRadius: 100,
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
@@ -75,6 +76,7 @@ export const Card = ({ color, warning }) => {
|
||||
color={
|
||||
messageBoardState.type === "error" ? colors.error.icon : color
|
||||
}
|
||||
allowFontScaling
|
||||
name={messageBoardState.icon}
|
||||
/>
|
||||
</View>
|
||||
@@ -91,7 +93,7 @@ export const Card = ({ color, warning }) => {
|
||||
</Paragraph>
|
||||
<Paragraph
|
||||
style={{
|
||||
flexWrap: "wrap",
|
||||
flexWrap: "nowrap",
|
||||
flexShrink: 1
|
||||
}}
|
||||
color={colors.primary.heading}
|
||||
@@ -101,22 +103,24 @@ export const Card = ({ color, warning }) => {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name="chevron-right"
|
||||
color={
|
||||
messageBoardState.type === "error" ? colors.error.icon : color
|
||||
}
|
||||
size={SIZE.lg}
|
||||
/>
|
||||
</View>
|
||||
{fontScale > 1 ? null : (
|
||||
<View
|
||||
style={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name="chevron-right"
|
||||
color={
|
||||
messageBoardState.type === "error" ? colors.error.icon : color
|
||||
}
|
||||
size={SIZE.lg}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</PressableButton>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -39,6 +39,7 @@ import { Empty } from "./empty";
|
||||
import { getTotalNotes } from "@notesnook/common";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import ReminderItem from "../list-items/reminder";
|
||||
import { useGroupOptions } from "../../hooks/use-group-options";
|
||||
|
||||
const renderItems = {
|
||||
note: NoteWrapper,
|
||||
@@ -99,9 +100,10 @@ const List = ({
|
||||
type === "notebooks" ||
|
||||
notebooksListMode === "compact";
|
||||
const groupType =
|
||||
screen === "Notes" ? "home" : screen === "Favorites" ? "favorites" : type;
|
||||
screen === "Home" ? "home" : screen === "Favorites" ? "favorites" : type;
|
||||
|
||||
const groupOptions = useGroupOptions(groupType);
|
||||
|
||||
const groupOptions = db.settings?.getGroupOptions(groupType);
|
||||
const dateBy =
|
||||
groupOptions.sortBy !== "title" ? groupOptions.sortBy : "dateEdited";
|
||||
|
||||
@@ -113,18 +115,21 @@ const List = ({
|
||||
color={headerProps?.color}
|
||||
title={headerProps?.heading}
|
||||
dateBy={dateBy}
|
||||
type={
|
||||
screen === "Notes"
|
||||
? "home"
|
||||
: screen === "Favorites"
|
||||
? "favorites"
|
||||
: type
|
||||
}
|
||||
type={groupType}
|
||||
screen={screen}
|
||||
isSheet={isSheet}
|
||||
groupOptions={groupOptions}
|
||||
/>
|
||||
),
|
||||
[headerProps?.color, headerProps?.heading, screen, type, isSheet, dateBy]
|
||||
[
|
||||
headerProps?.color,
|
||||
headerProps?.heading,
|
||||
screen,
|
||||
isSheet,
|
||||
dateBy,
|
||||
groupType,
|
||||
groupOptions
|
||||
]
|
||||
);
|
||||
|
||||
const _onRefresh = async () => {
|
||||
@@ -230,7 +235,7 @@ const List = ({
|
||||
<JumpToSectionDialog
|
||||
screen={screen}
|
||||
data={listData}
|
||||
type={screen === "Notes" ? "home" : type}
|
||||
type={screen === "Home" ? "home" : type}
|
||||
scrollRef={scrollRef}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -245,13 +245,17 @@ const MergeConflicts = () => {
|
||||
};
|
||||
|
||||
return !visible ? null : (
|
||||
<Modal
|
||||
<BaseDialog
|
||||
statusBarTranslucent
|
||||
transparent={false}
|
||||
animationType="slide"
|
||||
animated={false}
|
||||
bounce={false}
|
||||
onRequestClose={() => {
|
||||
close();
|
||||
}}
|
||||
centered={false}
|
||||
background={colors?.primary.background}
|
||||
supportedOrientations={[
|
||||
"portrait",
|
||||
"portrait-upside-down",
|
||||
@@ -363,7 +367,7 @@ const MergeConflicts = () => {
|
||||
</Animated.View>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</Modal>
|
||||
</BaseDialog>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ export default function NotePreview({ session, content, note }) {
|
||||
useTrashStore.getState().setTrash();
|
||||
useSelectionStore.getState().setSelectionMode(false);
|
||||
ToastEvent.show({
|
||||
heading: "Permanantly deleted items",
|
||||
heading: "Permanently deleted items",
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
|
||||
@@ -56,7 +56,7 @@ export const CompactFeatures = ({
|
||||
icon: "shield"
|
||||
},
|
||||
{
|
||||
highlight: "Daily, weekly & montly",
|
||||
highlight: "Daily, weekly & monthly",
|
||||
content: "recurring reminders",
|
||||
icon: "bell"
|
||||
},
|
||||
|
||||
@@ -165,7 +165,12 @@ export const Component = ({ close, promo }) => {
|
||||
}}
|
||||
size={SIZE.md}
|
||||
>
|
||||
({pricing?.product?.localizedPrice} / mo)
|
||||
(
|
||||
{Platform.OS === "android"
|
||||
? pricing.product?.subscriptionOfferDetails[0].pricingPhases
|
||||
.pricingPhaseList?.[0].formattedPrice
|
||||
: pricing.product?.localizedPrice}{" "}
|
||||
/ mo)
|
||||
</Paragraph>
|
||||
)}
|
||||
|
||||
|
||||
@@ -48,23 +48,23 @@ export const Expiring = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [status, setStatus] = useState({
|
||||
title: "Your trial is ending soon",
|
||||
offer: null,
|
||||
offer: "Get 30% off",
|
||||
extend: true
|
||||
});
|
||||
const pricing = usePricing("yearly");
|
||||
|
||||
const promo = status.offer
|
||||
? {
|
||||
promoCode:
|
||||
pricing?.info?.discount > 30
|
||||
? pricing.info.sku
|
||||
: "com.streetwriters.notesnook.sub.yr.trialoffer",
|
||||
text: `GET ${
|
||||
pricing?.info?.discount > 30 ? pricing?.info?.discount : 30
|
||||
}% OFF on yearly`,
|
||||
discount: pricing?.info?.discount > 30 ? pricing?.info?.discount : 30
|
||||
}
|
||||
: null;
|
||||
const promo =
|
||||
status.offer && pricing?.info
|
||||
? {
|
||||
promoCode:
|
||||
pricing?.info?.discount > 30
|
||||
? pricing.info.sku
|
||||
: "com.streetwriters.notesnook.sub.yr.trialoffer",
|
||||
text: `GET ${
|
||||
pricing?.info?.discount > 30 ? pricing?.info?.discount : 30
|
||||
}% OFF on yearly`,
|
||||
discount: pricing?.info?.discount > 30 ? pricing?.info?.discount : 30
|
||||
}
|
||||
: null;
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent(eOpenTrialEndingDialog, open);
|
||||
|
||||
@@ -98,7 +98,7 @@ export const features = [
|
||||
icon: "bell"
|
||||
},
|
||||
{
|
||||
highlight: "Daily, weekly & montly",
|
||||
highlight: "Daily, weekly & monthly",
|
||||
content: "reminders",
|
||||
icon: "refresh",
|
||||
pro: true
|
||||
@@ -194,7 +194,7 @@ export const features = [
|
||||
"Having the right tool at the right time is crucial for note taking. Lists, tables, codeblocks — you name it, we have it.",
|
||||
features: [
|
||||
{
|
||||
highlight: "Basic formating",
|
||||
highlight: "Basic formatting",
|
||||
content: "and lists",
|
||||
icon: "format-bold"
|
||||
},
|
||||
|
||||
@@ -18,13 +18,29 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { Platform, View } from "react-native";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { PressableButton } from "../ui/pressable";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import RNIap from "react-native-iap";
|
||||
|
||||
export const PricingItem = ({ product, onPress, compact }) => {
|
||||
export const PricingItem = ({
|
||||
product,
|
||||
onPress,
|
||||
compact,
|
||||
strikethrough
|
||||
}: {
|
||||
product: {
|
||||
type: "yearly" | "monthly";
|
||||
data?: RNIap.Subscription;
|
||||
info: string;
|
||||
offerType?: "yearly" | "monthly";
|
||||
};
|
||||
strikethrough?: boolean;
|
||||
onPress?: () => void;
|
||||
compact?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<PressableButton
|
||||
onPress={onPress}
|
||||
@@ -36,8 +52,10 @@ export const PricingItem = ({ product, onPress, compact }) => {
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: compact ? 15 : 10,
|
||||
width: compact ? null : "100%",
|
||||
minWidth: 150
|
||||
minWidth: 150,
|
||||
opacity: strikethrough ? 0.7 : 1
|
||||
}}
|
||||
disabled={strikethrough}
|
||||
>
|
||||
{!compact && (
|
||||
<View>
|
||||
@@ -53,8 +71,25 @@ export const PricingItem = ({ product, onPress, compact }) => {
|
||||
)}
|
||||
|
||||
<View>
|
||||
<Paragraph size={SIZE.sm}>
|
||||
<Heading size={SIZE.lg - 2}>{product?.data?.localizedPrice}/</Heading>
|
||||
<Paragraph
|
||||
style={{
|
||||
textDecorationLine: strikethrough ? "line-through" : undefined
|
||||
}}
|
||||
size={SIZE.sm}
|
||||
>
|
||||
<Heading
|
||||
style={{
|
||||
textDecorationLine: strikethrough ? "line-through" : undefined
|
||||
}}
|
||||
size={SIZE.lg - 2}
|
||||
>
|
||||
{Platform.OS === "android"
|
||||
? (product.data as RNIap.SubscriptionAndroid | undefined)
|
||||
?.subscriptionOfferDetails[0].pricingPhases
|
||||
.pricingPhaseList?.[0]?.formattedPrice
|
||||
: (product.data as RNIap.SubscriptionIOS | undefined)
|
||||
?.localizedPrice}
|
||||
</Heading>
|
||||
{product?.type === "yearly" || product?.offerType === "yearly"
|
||||
? "/year"
|
||||
: "/month"}
|
||||
@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { ActivityIndicator, Platform, Text, View } from "react-native";
|
||||
import * as RNIap from "react-native-iap";
|
||||
import { db } from "../../common/database";
|
||||
import { DatabaseLogger, db } from "../../common/database";
|
||||
import { usePricing } from "../../hooks/use-pricing";
|
||||
import {
|
||||
eSendEvent,
|
||||
@@ -47,11 +47,29 @@ import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { Walkthrough } from "../walkthroughs";
|
||||
import { PricingItem } from "./pricing-item";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
|
||||
const UUID_PREFIX = "0bdaea";
|
||||
const UUID_VERSION = "4";
|
||||
const UUID_VARIANT = "a";
|
||||
|
||||
function toUUID(str: string) {
|
||||
return [
|
||||
UUID_PREFIX + str.substring(0, 2), // 6 digit prefix + first 2 oid digits
|
||||
str.substring(2, 6), // # next 4 oid digits
|
||||
UUID_VERSION + str.substring(6, 9), // # 1 digit version(0x4) + next 3 oid digits
|
||||
UUID_VARIANT + str.substring(9, 12), // # 1 digit variant(0b101) + 1 zero bit + next 3 oid digits
|
||||
str.substring(12)
|
||||
].join("-");
|
||||
}
|
||||
|
||||
const promoCyclesMonthly = {
|
||||
1: "first month",
|
||||
2: "first 2 months",
|
||||
3: "first 3 months"
|
||||
3: "first 3 months",
|
||||
4: "first 4 months",
|
||||
5: "first 5 months",
|
||||
6: "first 3 months"
|
||||
};
|
||||
|
||||
const promoCyclesYearly = {
|
||||
@@ -65,10 +83,24 @@ export const PricingPlans = ({
|
||||
marginTop,
|
||||
heading = true,
|
||||
compact = false
|
||||
}: {
|
||||
promo?: {
|
||||
promoCode: string;
|
||||
};
|
||||
marginTop?: any;
|
||||
heading?: boolean;
|
||||
compact?: boolean;
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
const user = useUserStore((state) => state.user);
|
||||
const [product, setProduct] = useState(null);
|
||||
const [product, setProduct] = useState<{
|
||||
type: string;
|
||||
offerType: "monthly" | "yearly";
|
||||
data: RNIap.Subscription;
|
||||
cycleText: string;
|
||||
info: string;
|
||||
}>();
|
||||
|
||||
const [buying, setBuying] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const userCanRequestTrial =
|
||||
@@ -90,27 +122,40 @@ export const PricingPlans = ({
|
||||
}
|
||||
}, [promo?.promoCode]);
|
||||
|
||||
const getPromo = async (code) => {
|
||||
const getPromo = async (code: string) => {
|
||||
try {
|
||||
let productId;
|
||||
let skuId: string;
|
||||
if (code.startsWith("com.streetwriters.notesnook")) {
|
||||
productId = code;
|
||||
skuId = code;
|
||||
} else {
|
||||
productId = await db.offers.getCode(code.split(":")[0], Platform.OS);
|
||||
skuId = await db.offers?.getCode(code.split(":")[0], Platform.OS);
|
||||
}
|
||||
|
||||
let products = await PremiumService.getProducts();
|
||||
let product = products.find((p) => p.productId === productId);
|
||||
const products = await PremiumService.getProducts();
|
||||
const product = products.find((p) => p.productId === skuId);
|
||||
if (!product) return false;
|
||||
let isMonthly = product.productId.indexOf(".mo") > -1;
|
||||
let cycleText = isMonthly
|
||||
const isMonthly = product.productId.indexOf(".mo") > -1;
|
||||
|
||||
const cycleText = isMonthly
|
||||
? promoCyclesMonthly[
|
||||
product.introductoryPriceCyclesAndroid ||
|
||||
product.introductoryPriceNumberOfPeriodsIOS
|
||||
(Platform.OS === "android"
|
||||
? (product as RNIap.SubscriptionAndroid)
|
||||
.subscriptionOfferDetails[0]?.pricingPhases
|
||||
.pricingPhaseList?.[0].billingCycleCount
|
||||
: parseInt(
|
||||
(product as RNIap.SubscriptionIOS)
|
||||
.introductoryPriceNumberOfPeriodsIOS as string
|
||||
)) as keyof typeof promoCyclesMonthly
|
||||
]
|
||||
: promoCyclesYearly[
|
||||
product.introductoryPriceCyclesAndroid ||
|
||||
product.introductoryPriceNumberOfPeriodsIOS
|
||||
(Platform.OS === "android"
|
||||
? (product as RNIap.SubscriptionAndroid)
|
||||
.subscriptionOfferDetails[0]?.pricingPhases
|
||||
.pricingPhaseList?.[0].billingCycleCount
|
||||
: parseInt(
|
||||
(product as RNIap.SubscriptionIOS)
|
||||
.introductoryPriceNumberOfPeriodsIOS as string
|
||||
)) as keyof typeof promoCyclesYearly
|
||||
];
|
||||
|
||||
setProduct({
|
||||
@@ -118,7 +163,7 @@ export const PricingPlans = ({
|
||||
offerType: isMonthly ? "monthly" : "yearly",
|
||||
data: product,
|
||||
cycleText: cycleText,
|
||||
info: "Pay monthly, cancel anytime"
|
||||
info: `Pay ${isMonthly ? "monthly" : "yearly"}, cancel anytime`
|
||||
});
|
||||
return true;
|
||||
} catch (e) {
|
||||
@@ -131,22 +176,41 @@ export const PricingPlans = ({
|
||||
getSkus();
|
||||
}, [getSkus]);
|
||||
|
||||
const buySubscription = async (product) => {
|
||||
if (buying) return;
|
||||
const buySubscription = async (product: RNIap.Subscription) => {
|
||||
if (buying || !product) return;
|
||||
setBuying(true);
|
||||
try {
|
||||
if (!user) {
|
||||
setBuying(false);
|
||||
return;
|
||||
}
|
||||
await RNIap.requestSubscription(
|
||||
product?.productId,
|
||||
false,
|
||||
null,
|
||||
-1,
|
||||
user.id,
|
||||
user.id
|
||||
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
|
||||
const androidOfferToken =
|
||||
Platform.OS === "android"
|
||||
? (product as RNIap.SubscriptionAndroid).subscriptionOfferDetails[0]
|
||||
.offerToken
|
||||
: null;
|
||||
|
||||
DatabaseLogger.info(
|
||||
`Subscription Requested initiated for user ${toUUID(user.id)}`
|
||||
);
|
||||
|
||||
await RNIap.requestSubscription({
|
||||
sku: product?.productId,
|
||||
obfuscatedAccountIdAndroid: user.id,
|
||||
obfuscatedProfileIdAndroid: user.id,
|
||||
appAccountToken: toUUID(user.id),
|
||||
andDangerouslyFinishTransactionAutomaticallyIOS: false,
|
||||
subscriptionOffers: androidOfferToken
|
||||
? [
|
||||
{
|
||||
offerToken: androidOfferToken,
|
||||
sku: product?.productId
|
||||
}
|
||||
]
|
||||
: undefined
|
||||
});
|
||||
useSettingStore.getState().setAppDidEnterBackgroundForAction(false);
|
||||
setBuying(false);
|
||||
eSendEvent(eCloseSheet);
|
||||
eSendEvent(eClosePremiumDialog);
|
||||
@@ -167,6 +231,35 @@ export const PricingPlans = ({
|
||||
}
|
||||
};
|
||||
|
||||
function getStandardPrice() {
|
||||
if (!product) return;
|
||||
const productType = product.offerType;
|
||||
|
||||
if (Platform.OS === "android") {
|
||||
const pricingPhaseListItem = (product.data as RNIap.SubscriptionAndroid)
|
||||
?.subscriptionOfferDetails[0]?.pricingPhases.pricingPhaseList?.[1];
|
||||
|
||||
if (!pricingPhaseListItem) {
|
||||
const product =
|
||||
productType === "monthly"
|
||||
? monthlyPlan?.product
|
||||
: yearlyPlan?.product;
|
||||
return (product as RNIap.SubscriptionAndroid)
|
||||
?.subscriptionOfferDetails[0]?.pricingPhases.pricingPhaseList?.[0]
|
||||
?.formattedPrice;
|
||||
}
|
||||
|
||||
return pricingPhaseListItem?.formattedPrice;
|
||||
} else {
|
||||
const productDefault =
|
||||
productType === "monthly" ? monthlyPlan?.product : yearlyPlan?.product;
|
||||
return (
|
||||
(product.data as RNIap.SubscriptionIOS)?.localizedPrice ||
|
||||
(productDefault as RNIap.SubscriptionIOS)?.localizedPrice
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return loading ? (
|
||||
<View
|
||||
style={{
|
||||
@@ -198,7 +291,14 @@ export const PricingPlans = ({
|
||||
}}
|
||||
size={SIZE.lg}
|
||||
>
|
||||
{PremiumService.getMontlySub().localizedPrice} / mo
|
||||
{(Platform.OS === "android"
|
||||
? (monthlyPlan?.product as RNIap.SubscriptionAndroid | undefined)
|
||||
?.subscriptionOfferDetails[0]?.pricingPhases
|
||||
.pricingPhaseList?.[0]?.formattedPrice
|
||||
: (monthlyPlan?.product as RNIap.SubscriptionIOS | undefined)
|
||||
?.localizedPrice) ||
|
||||
(PremiumService.getMontlySub() as any)?.localizedPrice}
|
||||
/ mo
|
||||
</Paragraph>
|
||||
<Button
|
||||
onPress={() => {
|
||||
@@ -218,7 +318,7 @@ export const PricingPlans = ({
|
||||
<Button
|
||||
onPress={async () => {
|
||||
try {
|
||||
await db.user.activateTrial();
|
||||
await db.user?.activateTrial();
|
||||
eSendEvent(eClosePremiumDialog);
|
||||
eSendEvent(eCloseSheet);
|
||||
await sleep(300);
|
||||
@@ -239,33 +339,91 @@ export const PricingPlans = ({
|
||||
) : (
|
||||
<>
|
||||
{product?.type === "promo" ? (
|
||||
<Heading
|
||||
<View
|
||||
style={{
|
||||
paddingVertical: 15,
|
||||
alignSelf: "center",
|
||||
textAlign: "center"
|
||||
alignItems: "center"
|
||||
}}
|
||||
size={SIZE.lg - 4}
|
||||
>
|
||||
{product.data.introductoryPrice}
|
||||
<Paragraph
|
||||
{product?.offerType === "monthly" ? (
|
||||
<PricingItem
|
||||
product={{
|
||||
type: "monthly",
|
||||
data: monthlyPlan?.product,
|
||||
info: "Pay once a month, cancel anytime."
|
||||
}}
|
||||
strikethrough={true}
|
||||
/>
|
||||
) : (
|
||||
<PricingItem
|
||||
onPress={() => {
|
||||
if (!monthlyPlan?.product) return;
|
||||
buySubscription(monthlyPlan?.product);
|
||||
}}
|
||||
product={{
|
||||
type: "yearly",
|
||||
data: yearlyPlan?.product,
|
||||
info: "Pay once a year, cancel anytime."
|
||||
}}
|
||||
strikethrough={true}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Heading
|
||||
style={{
|
||||
textDecorationLine: "line-through",
|
||||
color: colors.secondary.paragraph
|
||||
paddingTop: 15,
|
||||
fontSize: SIZE.lg
|
||||
}}
|
||||
size={SIZE.sm}
|
||||
>
|
||||
({product.data.localizedPrice})
|
||||
</Paragraph>{" "}
|
||||
for {product.cycleText}
|
||||
</Heading>
|
||||
Special offer for you
|
||||
</Heading>
|
||||
|
||||
<View
|
||||
style={{
|
||||
paddingVertical: 20,
|
||||
paddingBottom: 10
|
||||
}}
|
||||
>
|
||||
<Heading
|
||||
style={{
|
||||
alignSelf: "center",
|
||||
textAlign: "center"
|
||||
}}
|
||||
size={SIZE.xxl}
|
||||
>
|
||||
{Platform.OS === "android"
|
||||
? (product.data as RNIap.SubscriptionAndroid)
|
||||
?.subscriptionOfferDetails[0].pricingPhases
|
||||
.pricingPhaseList?.[0]?.formattedPrice
|
||||
: (product.data as RNIap.SubscriptionIOS)
|
||||
?.introductoryPrice ||
|
||||
(product.data as RNIap.SubscriptionIOS)
|
||||
?.localizedPrice}{" "}
|
||||
{product?.cycleText
|
||||
? `for ${product.cycleText}`
|
||||
: product?.offerType}
|
||||
</Heading>
|
||||
{product?.cycleText ? (
|
||||
<Paragraph
|
||||
style={{
|
||||
color: colors.secondary.paragraph,
|
||||
alignSelf: "center",
|
||||
textAlign: "center"
|
||||
}}
|
||||
size={SIZE.md}
|
||||
>
|
||||
then {getStandardPrice()} {product?.offerType}.
|
||||
</Paragraph>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
{user && !product ? (
|
||||
<>
|
||||
{heading || monthlyPlan?.info?.discount > 0 ? (
|
||||
{heading || (monthlyPlan?.info?.discount || 0) > 0 ? (
|
||||
<>
|
||||
{monthlyPlan && monthlyPlan?.info?.discount > 0 ? (
|
||||
{monthlyPlan && (monthlyPlan?.info?.discount || 0) > 0 ? (
|
||||
<View
|
||||
style={{
|
||||
alignSelf: "center",
|
||||
@@ -273,7 +431,12 @@ export const PricingPlans = ({
|
||||
marginBottom: 20
|
||||
}}
|
||||
>
|
||||
<Heading color={colors.primary.accent}>
|
||||
<Heading
|
||||
style={{
|
||||
textAlign: "center"
|
||||
}}
|
||||
color={colors.primary.accent}
|
||||
>
|
||||
Get {monthlyPlan?.info?.discount}% off in{" "}
|
||||
{monthlyPlan?.info?.country}
|
||||
</Heading>
|
||||
@@ -300,12 +463,15 @@ export const PricingPlans = ({
|
||||
}}
|
||||
>
|
||||
<PricingItem
|
||||
onPress={() => buySubscription(monthlyPlan?.product)}
|
||||
onPress={() => {
|
||||
if (!monthlyPlan?.product) return;
|
||||
buySubscription(monthlyPlan?.product);
|
||||
}}
|
||||
compact={compact}
|
||||
product={{
|
||||
type: "monthly",
|
||||
data: monthlyPlan?.product,
|
||||
info: "Pay monthly, cancel anytime."
|
||||
info: "Pay once a month, cancel anytime."
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -319,12 +485,15 @@ export const PricingPlans = ({
|
||||
)}
|
||||
|
||||
<PricingItem
|
||||
onPress={() => buySubscription(yearlyPlan?.product)}
|
||||
onPress={() => {
|
||||
if (!yearlyPlan?.product) return;
|
||||
buySubscription(yearlyPlan?.product);
|
||||
}}
|
||||
compact={compact}
|
||||
product={{
|
||||
type: "yearly",
|
||||
data: yearlyPlan?.product,
|
||||
info: "Pay yearly"
|
||||
info: "Pay once a year, cancel anytime."
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
@@ -346,7 +515,7 @@ export const PricingPlans = ({
|
||||
eSendEvent(eCloseSimpleDialog);
|
||||
setBuying(true);
|
||||
try {
|
||||
if (!(await getPromo(value)))
|
||||
if (!(await getPromo(value as string)))
|
||||
throw new Error("Error applying promo code");
|
||||
ToastEvent.show({
|
||||
heading: "Discount applied!",
|
||||
@@ -358,7 +527,7 @@ export const PricingPlans = ({
|
||||
setBuying(false);
|
||||
ToastEvent.show({
|
||||
heading: "Promo code invalid or expired",
|
||||
message: e.message,
|
||||
message: (e as Error).message,
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
@@ -396,7 +565,7 @@ export const PricingPlans = ({
|
||||
width={250}
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
marginTop: 30,
|
||||
marginTop: product?.type === "promo" ? 0 : 30,
|
||||
marginBottom: 10
|
||||
}}
|
||||
/>
|
||||
@@ -427,7 +596,10 @@ export const PricingPlans = ({
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
onPress={() => buySubscription(product.data)}
|
||||
onPress={() => {
|
||||
if (!product?.data) return;
|
||||
buySubscription(product.data);
|
||||
}}
|
||||
height={40}
|
||||
width="50%"
|
||||
type="accent"
|
||||
@@ -436,7 +608,7 @@ export const PricingPlans = ({
|
||||
|
||||
<Button
|
||||
onPress={() => {
|
||||
setProduct(null);
|
||||
setProduct(undefined);
|
||||
}}
|
||||
style={{
|
||||
marginTop: 5
|
||||
@@ -500,7 +672,7 @@ export const PricingPlans = ({
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
By subscribing, your will be charged on your Google Account, and
|
||||
By subscribing, you will be charged on your Google Account, and
|
||||
your subscription will automatically renew until you cancel prior
|
||||
to the end of the then current period.
|
||||
</Paragraph>
|
||||
@@ -523,7 +695,7 @@ export const PricingPlans = ({
|
||||
<Paragraph
|
||||
size={SIZE.xs}
|
||||
onPress={() => {
|
||||
openLinkInBrowser("https://notesnook.com/tos", colors)
|
||||
openLinkInBrowser("https://notesnook.com/tos")
|
||||
.catch(() => {})
|
||||
.then(() => {});
|
||||
}}
|
||||
@@ -538,7 +710,7 @@ export const PricingPlans = ({
|
||||
<Paragraph
|
||||
size={SIZE.xs}
|
||||
onPress={() => {
|
||||
openLinkInBrowser("https://notesnook.com/privacy", colors)
|
||||
openLinkInBrowser("https://notesnook.com/privacy")
|
||||
.catch(() => {})
|
||||
.then(() => {});
|
||||
}}
|
||||
@@ -16,8 +16,8 @@ GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import React from "react";
|
||||
import { Platform, View } from "react-native";
|
||||
import React, { useRef } from "react";
|
||||
import { Dimensions, Platform, View, useWindowDimensions } from "react-native";
|
||||
import { FlatList } from "react-native-actions-sheet";
|
||||
import { db } from "../../common/database";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
@@ -54,7 +54,6 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
|
||||
const { colors } = useThemeColors();
|
||||
const alias = item.alias || item.title;
|
||||
const isColor = !!ColorValues[item.title];
|
||||
|
||||
if (!item || !item.id) {
|
||||
return (
|
||||
<Paragraph style={{ marginVertical: 10, alignSelf: "center" }}>
|
||||
@@ -62,6 +61,7 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
|
||||
</Paragraph>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
keyboardShouldPersistTaps="always"
|
||||
@@ -135,6 +135,7 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
|
||||
}, 1000);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Synced item={item} close={close} />
|
||||
|
||||
{DDS.isTab ? (
|
||||
|
||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { FlatList, ScrollView, View } from "react-native";
|
||||
import { Dimensions, FlatList, ScrollView, View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { useActions } from "../../hooks/use-actions";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
@@ -33,9 +33,11 @@ export const Items = ({ item, buttons, close }) => {
|
||||
const dimensions = useSettingStore((state) => state.dimensions);
|
||||
const actions = useActions({ item, close });
|
||||
const data = actions.filter((i) => buttons.indexOf(i.id) > -1 && !i.hidden);
|
||||
|
||||
let width = dimensions.width > 600 ? 600 : dimensions.width;
|
||||
let columnItemsCount = DDS.isLargeTablet() ? 7 : 5;
|
||||
const shouldShrink =
|
||||
Dimensions.get("window").fontScale > 1 &&
|
||||
Dimensions.get("window").width < 450;
|
||||
let columnItemsCount = DDS.isLargeTablet() ? 7 : shouldShrink ? 4 : 5;
|
||||
let columnItemWidth = DDS.isTab
|
||||
? (width - 12) / columnItemsCount
|
||||
: (width - 12) / columnItemsCount;
|
||||
@@ -66,6 +68,7 @@ export const Items = ({ item, buttons, close }) => {
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
allowFontScaling
|
||||
name={item.icon}
|
||||
size={DDS.isTab ? SIZE.xxl : SIZE.lg}
|
||||
color={
|
||||
@@ -78,7 +81,11 @@ export const Items = ({ item, buttons, close }) => {
|
||||
/>
|
||||
</PressableButton>
|
||||
|
||||
<Paragraph size={SIZE.xs} style={{ textAlign: "center" }}>
|
||||
<Paragraph
|
||||
size={SIZE.xs}
|
||||
textBreakStrategy="simple"
|
||||
style={{ textAlign: "center" }}
|
||||
>
|
||||
{item.title}
|
||||
</Paragraph>
|
||||
</View>
|
||||
@@ -139,6 +146,7 @@ export const Items = ({ item, buttons, close }) => {
|
||||
>
|
||||
<Icon
|
||||
name={item.icon}
|
||||
allowFontScaling
|
||||
size={DDS.isTab ? SIZE.xxl : SIZE.md + 4}
|
||||
color={
|
||||
item.on
|
||||
@@ -150,7 +158,11 @@ export const Items = ({ item, buttons, close }) => {
|
||||
/>
|
||||
</PressableButton>
|
||||
|
||||
<Paragraph size={SIZE.xxs + 1} style={{ textAlign: "center" }}>
|
||||
<Paragraph
|
||||
textBreakStrategy="simple"
|
||||
size={SIZE.xxs + 1}
|
||||
style={{ textAlign: "center" }}
|
||||
>
|
||||
{item.title}
|
||||
</Paragraph>
|
||||
</PressableButton>
|
||||
@@ -163,9 +175,12 @@ export const Items = ({ item, buttons, close }) => {
|
||||
"copy",
|
||||
"share",
|
||||
"export",
|
||||
"lock-unlock",
|
||||
"publish"
|
||||
"lock-unlock"
|
||||
];
|
||||
if (!shouldShrink) {
|
||||
topBarItemsList.push("publish");
|
||||
}
|
||||
|
||||
const topBarItems = data.filter(
|
||||
(item) => topBarItemsList.indexOf(item.id) > -1
|
||||
);
|
||||
|
||||
@@ -17,10 +17,10 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React from "react";
|
||||
import { View, useWindowDimensions } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { openLinkInBrowser } from "../../utils/functions";
|
||||
import { SIZE } from "../../utils/size";
|
||||
@@ -33,6 +33,8 @@ export const Synced = ({ item, close }) => {
|
||||
const user = useUserStore((state) => state.user);
|
||||
const lastSynced = useUserStore((state) => state.lastSynced);
|
||||
|
||||
const dimensions = useWindowDimensions();
|
||||
const shouldShrink = dimensions.fontScale > 1 && dimensions.width < 450;
|
||||
return user && lastSynced >= item.dateModified ? (
|
||||
<View
|
||||
style={{
|
||||
@@ -49,7 +51,11 @@ export const Synced = ({ item, close }) => {
|
||||
borderTopColor: colors.secondary.background
|
||||
}}
|
||||
>
|
||||
<Icon name="shield-key-outline" color={colors.primary.accent} size={SIZE.xxxl} />
|
||||
<Icon
|
||||
name="shield-key-outline"
|
||||
color={colors.primary.accent}
|
||||
size={shouldShrink ? SIZE.xxl : SIZE.xxxl}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={{
|
||||
@@ -67,15 +73,17 @@ export const Synced = ({ item, close }) => {
|
||||
>
|
||||
Encrypted and synced
|
||||
</Heading>
|
||||
<Paragraph
|
||||
style={{
|
||||
flexWrap: "wrap"
|
||||
}}
|
||||
size={SIZE.xs}
|
||||
color={colors.primary.paragraph}
|
||||
>
|
||||
No one can view this {item.itemType || item.type} except you.
|
||||
</Paragraph>
|
||||
{shouldShrink ? null : (
|
||||
<Paragraph
|
||||
style={{
|
||||
flexWrap: "wrap"
|
||||
}}
|
||||
size={SIZE.xs}
|
||||
color={colors.primary.paragraph}
|
||||
>
|
||||
No one can view this {item.itemType || item.type} except you.
|
||||
</Paragraph>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<Button
|
||||
@@ -91,8 +99,8 @@ export const Synced = ({ item, close }) => {
|
||||
console.error(e);
|
||||
}
|
||||
}}
|
||||
fontSize={SIZE.xs}
|
||||
title="Learn more"
|
||||
fontSize={SIZE.xs}
|
||||
height={30}
|
||||
type="grayAccent"
|
||||
/>
|
||||
|
||||
@@ -207,7 +207,6 @@ export const SelectionHeader = React.memo(() => {
|
||||
|
||||
{screen === "Trash" ||
|
||||
screen === "Notebooks" ||
|
||||
screen === "Notebook" ||
|
||||
screen === "Reminders" ? null : (
|
||||
<>
|
||||
<IconButton
|
||||
@@ -224,6 +223,21 @@ export const SelectionHeader = React.memo(() => {
|
||||
name="pound"
|
||||
size={SIZE.xl}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
onPress={async () => {
|
||||
ExportNotesSheet.present(selectedItemsList);
|
||||
}}
|
||||
tooltipText="Export"
|
||||
tooltipPosition={4}
|
||||
customStyle={{
|
||||
marginLeft: 10
|
||||
}}
|
||||
color={colors.primary.paragraph}
|
||||
name="export"
|
||||
size={SIZE.xl}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
onPress={async () => {
|
||||
//setSelectionMode(false);
|
||||
@@ -239,20 +253,6 @@ export const SelectionHeader = React.memo(() => {
|
||||
name="plus"
|
||||
size={SIZE.xl}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
onPress={async () => {
|
||||
ExportNotesSheet.present(selectedItemsList);
|
||||
}}
|
||||
tooltipText="Export"
|
||||
tooltipPosition={4}
|
||||
customStyle={{
|
||||
marginLeft: 10
|
||||
}}
|
||||
color={colors.primary.paragraph}
|
||||
name="export"
|
||||
size={SIZE.xl}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -317,23 +317,8 @@ export const SelectionHeader = React.memo(() => {
|
||||
customStyle={{
|
||||
marginLeft: 10
|
||||
}}
|
||||
onPress={async () => {
|
||||
presentDialog({
|
||||
title: `Delete ${
|
||||
selectedItemsList.length > 1 ? "items" : "item"
|
||||
}`,
|
||||
paragraph: `Are you sure you want to delete ${
|
||||
selectedItemsList.length > 1 ? "these items?" : "this item?"
|
||||
}`,
|
||||
positiveText: "Delete",
|
||||
negativeText: "Cancel",
|
||||
positivePress: () => {
|
||||
deleteItems();
|
||||
},
|
||||
positiveType: "errorShade"
|
||||
});
|
||||
|
||||
return;
|
||||
onPress={() => {
|
||||
deleteItems();
|
||||
}}
|
||||
tooltipText="Move to trash"
|
||||
tooltipPosition={1}
|
||||
|
||||
@@ -101,6 +101,7 @@ const SheetProvider = ({ context = "global" }) => {
|
||||
setVisible(false);
|
||||
setData(null);
|
||||
}}
|
||||
keyboardHandlerDisabled={data?.keyboardHandlerDisabled}
|
||||
bottomPadding={!data.noBottomPadding}
|
||||
enableGesturesInScrollView={
|
||||
typeof data.enableGesturesInScrollView === "undefined"
|
||||
@@ -198,7 +199,6 @@ const SheetProvider = ({ context = "global" }) => {
|
||||
key={data.actionText}
|
||||
title={data.actionText}
|
||||
accentColor={data.iconColor}
|
||||
accentText="light"
|
||||
type="accent"
|
||||
height={40}
|
||||
width={250}
|
||||
|
||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { FlatList } from "react-native-actions-sheet";
|
||||
import { FlashList } from "react-native-actions-sheet";
|
||||
import { db } from "../../../common/database";
|
||||
import { ListHeaderInputItem } from "./list-header-item.js";
|
||||
|
||||
@@ -54,7 +54,7 @@ export const FilteredList = ({
|
||||
}, [data, onChangeText]);
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
<FlashList
|
||||
{...restProps}
|
||||
data={filtered}
|
||||
ref={listRef}
|
||||
|
||||
@@ -161,9 +161,6 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
|
||||
: "deselected";
|
||||
if (itemState[notebook.id] === "selected") {
|
||||
count++;
|
||||
contextValue.select(notebook);
|
||||
} else {
|
||||
contextValue.deselect(notebook);
|
||||
}
|
||||
for (let topic of notebook.topics) {
|
||||
itemState[topic.id] = state
|
||||
@@ -176,9 +173,6 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
|
||||
: "deselected";
|
||||
if (itemState[topic.id] === "selected") {
|
||||
count++;
|
||||
contextValue.select(topic);
|
||||
} else {
|
||||
contextValue.deselect(topic);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -189,7 +183,7 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
|
||||
}
|
||||
useItemSelectionStore.getState().setItemState(itemState);
|
||||
},
|
||||
[contextValue, getSelectedNotesCountInItem, selectedItemsList]
|
||||
[getSelectedNotesCountInItem, selectedItemsList]
|
||||
);
|
||||
|
||||
const getItemsForItem = (item) => {
|
||||
@@ -214,7 +208,7 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
|
||||
}
|
||||
|
||||
const updateItemState = useCallback(function (item, state) {
|
||||
const itemState = useItemSelectionStore.getState().itemState;
|
||||
const itemState = { ...useItemSelectionStore.getState().itemState };
|
||||
const mergeState = {
|
||||
[item.id]: state
|
||||
};
|
||||
@@ -370,56 +364,93 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
|
||||
</View>
|
||||
|
||||
<SelectionProvider value={contextValue}>
|
||||
<FilteredList
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
maxHeight: dimensions.height * 0.85
|
||||
maxHeight: dimensions.height * 0.85,
|
||||
height: 50 * (notebooks.length + 2)
|
||||
}}
|
||||
ListEmptyComponent={
|
||||
notebooks.length > 0 ? null : (
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
>
|
||||
<FilteredList
|
||||
ListEmptyComponent={
|
||||
notebooks.length > 0 ? null : (
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name="book-outline"
|
||||
color={colors.primary.icon}
|
||||
size={100}
|
||||
/>
|
||||
<Paragraph style={{ marginBottom: 10 }}>
|
||||
You do not have any notebooks.
|
||||
</Paragraph>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
estimatedItemSize={50}
|
||||
data={notebooks}
|
||||
hasHeaderSearch={true}
|
||||
renderItem={({ item, index }) => (
|
||||
<ListItem
|
||||
item={item}
|
||||
key={item.id}
|
||||
index={index}
|
||||
hasNotes={getSelectedNotesCountInItem(item) > 0}
|
||||
sheetRef={actionSheetRef}
|
||||
infoText={
|
||||
<>
|
||||
{item.topics.length === 1
|
||||
? item.topics.length + " topic"
|
||||
: item.topics.length + " topics"}
|
||||
</>
|
||||
}
|
||||
getListItems={getItemsForItem}
|
||||
getSublistItemProps={(topic) => ({
|
||||
hasNotes: getSelectedNotesCountInItem(topic) > 0,
|
||||
style: {
|
||||
marginBottom: 0,
|
||||
height: 40
|
||||
},
|
||||
onPress: (item) => {
|
||||
const itemState =
|
||||
useItemSelectionStore.getState().itemState;
|
||||
const currentState = itemState[item.id];
|
||||
if (currentState !== "selected") {
|
||||
resetItemState("deselected");
|
||||
contextValue.select(item);
|
||||
} else {
|
||||
contextValue.deselect(item);
|
||||
}
|
||||
},
|
||||
key: item.id,
|
||||
type: "transparent"
|
||||
})}
|
||||
icon={(expanded) => ({
|
||||
name: expanded ? "chevron-up" : "chevron-down",
|
||||
color: expanded
|
||||
? colors.primary.accent
|
||||
: colors.primary.paragraph
|
||||
})}
|
||||
onScrollEnd={() => {
|
||||
actionSheetRef.current?.handleChildScrollEnd();
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name="book-outline"
|
||||
color={colors.primary.icon}
|
||||
size={100}
|
||||
/>
|
||||
<Paragraph style={{ marginBottom: 10 }}>
|
||||
You do not have any notebooks.
|
||||
</Paragraph>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
data={notebooks}
|
||||
hasHeaderSearch={true}
|
||||
renderItem={({ item, index }) => (
|
||||
<ListItem
|
||||
item={item}
|
||||
key={item.id}
|
||||
index={index}
|
||||
hasNotes={getSelectedNotesCountInItem(item) > 0}
|
||||
sheetRef={actionSheetRef}
|
||||
infoText={
|
||||
<>
|
||||
{item.topics.length === 1
|
||||
? item.topics.length + " topic"
|
||||
: item.topics.length + " topics"}
|
||||
</>
|
||||
}
|
||||
getListItems={getItemsForItem}
|
||||
getSublistItemProps={(topic) => ({
|
||||
hasNotes: getSelectedNotesCountInItem(topic) > 0,
|
||||
style: {
|
||||
marginBottom: 0,
|
||||
height: 40
|
||||
},
|
||||
onPress: (item) => {
|
||||
hasSubList={true}
|
||||
hasHeaderSearch={false}
|
||||
type="grayBg"
|
||||
sublistItemType="topic"
|
||||
onAddItem={(title) => {
|
||||
return onAddTopic(title, item);
|
||||
}}
|
||||
onAddSublistItem={(item) => {
|
||||
openAddTopicDialog(item);
|
||||
}}
|
||||
onPress={(item) => {
|
||||
const itemState =
|
||||
useItemSelectionStore.getState().itemState;
|
||||
const currentState = itemState[item.id];
|
||||
@@ -429,47 +460,16 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
|
||||
} else {
|
||||
contextValue.deselect(item);
|
||||
}
|
||||
},
|
||||
key: item.id,
|
||||
type: "transparent"
|
||||
})}
|
||||
icon={(expanded) => ({
|
||||
name: expanded ? "chevron-up" : "chevron-down",
|
||||
color: expanded
|
||||
? colors.primary.accent
|
||||
: colors.primary.paragraph
|
||||
})}
|
||||
onScrollEnd={() => {
|
||||
actionSheetRef.current?.handleChildScrollEnd();
|
||||
}}
|
||||
hasSubList={true}
|
||||
hasHeaderSearch={false}
|
||||
type="grayBg"
|
||||
sublistItemType="topic"
|
||||
onAddItem={(title) => {
|
||||
return onAddTopic(title, item);
|
||||
}}
|
||||
onAddSublistItem={(item) => {
|
||||
openAddTopicDialog(item);
|
||||
}}
|
||||
onPress={(item) => {
|
||||
const itemState = useItemSelectionStore.getState().itemState;
|
||||
const currentState = itemState[item.id];
|
||||
if (currentState !== "selected") {
|
||||
resetItemState("deselected");
|
||||
contextValue.select(item);
|
||||
} else {
|
||||
contextValue.deselect(item);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
itemType="notebook"
|
||||
onAddItem={async (title) => {
|
||||
return await onAddNotebook(title);
|
||||
}}
|
||||
ListFooterComponent={<View style={{ height: 20 }} />}
|
||||
/>
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
itemType="notebook"
|
||||
onAddItem={async (title) => {
|
||||
return await onAddNotebook(title);
|
||||
}}
|
||||
ListFooterComponent={<View style={{ height: 20 }} />}
|
||||
/>
|
||||
</View>
|
||||
</SelectionProvider>
|
||||
</View>
|
||||
</>
|
||||
|
||||
@@ -273,6 +273,7 @@ export const ListItem = ({
|
||||
alignSelf: "flex-end",
|
||||
maxHeight: 250
|
||||
}}
|
||||
estimatedItemSize={40}
|
||||
itemType={sublistItemType}
|
||||
hasHeaderSearch={hasHeaderSearch}
|
||||
renderItem={({ item, index }) => (
|
||||
|
||||
@@ -43,6 +43,7 @@ import Paragraph from "../../ui/typography/paragraph";
|
||||
import { eSendEvent } from "../../../services/event-manager";
|
||||
import { eCloseSheet } from "../../../utils/events";
|
||||
import { requestInAppReview } from "../../../services/app-review";
|
||||
import { Dialog } from "../../dialog";
|
||||
|
||||
const ExportNotesSheet = ({ notes, update }) => {
|
||||
const { colors } = useThemeColors();
|
||||
@@ -97,6 +98,16 @@ const ExportNotesSheet = ({ notes, update }) => {
|
||||
id: notesnook.ids.dialogs.export.md,
|
||||
pro: premium
|
||||
},
|
||||
{
|
||||
title: "Markdown + Frontmatter",
|
||||
func: async () => {
|
||||
await save("md-frontmatter");
|
||||
},
|
||||
icon: "language-markdown",
|
||||
desc: "View in any text or markdown editor",
|
||||
id: notesnook.ids.dialogs.export.md,
|
||||
pro: premium
|
||||
},
|
||||
{
|
||||
title: "Plain Text",
|
||||
func: async () => {
|
||||
@@ -147,6 +158,8 @@ const ExportNotesSheet = ({ notes, update }) => {
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<Dialog context="export-notes" />
|
||||
|
||||
<View style={styles.buttonContainer}>
|
||||
{!exporting && !complete ? (
|
||||
actions.map((item) => (
|
||||
@@ -178,7 +191,9 @@ const ExportNotesSheet = ({ notes, update }) => {
|
||||
>
|
||||
<Icon
|
||||
name={item.icon}
|
||||
color={item.pro ? colors.primary.accent : colors.primary.icon}
|
||||
color={
|
||||
item.pro ? colors.primary.accent : colors.primary.icon
|
||||
}
|
||||
size={SIZE.xxxl + 10}
|
||||
/>
|
||||
</View>
|
||||
@@ -334,7 +349,8 @@ ExportNotesSheet.present = (notes, allNotes) => {
|
||||
notes={allNotes ? db.notes.all : notes}
|
||||
update={update}
|
||||
/>
|
||||
)
|
||||
),
|
||||
keyboardHandlerDisabled: true
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ Logged in: ${user ? "yes" : "no"}`,
|
||||
} catch (e) {
|
||||
setLoading(false);
|
||||
ToastEvent.show({
|
||||
heading: "An error occured",
|
||||
heading: "An error occurred",
|
||||
message: e.message,
|
||||
type: "error"
|
||||
});
|
||||
|
||||
@@ -177,7 +177,7 @@ export default function Migrate() {
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
An error occured while migrating your data. You can logout of your
|
||||
An error occurred while migrating your data. You can logout of your
|
||||
account and try to relogin. However this is not recommended as it
|
||||
may result in some data loss if your data was not synced.
|
||||
</Paragraph>
|
||||
|
||||
@@ -37,17 +37,18 @@ import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { requestInAppReview } from "../../../services/app-review";
|
||||
|
||||
const PublishNoteSheet = ({ note: item, update }) => {
|
||||
const PublishNoteSheet = ({ note: item }) => {
|
||||
const { colors } = useThemeColors();
|
||||
const actionSheetRef = useRef();
|
||||
const loading = useAttachmentStore((state) => state.loading);
|
||||
|
||||
const attachmentDownloads = useAttachmentStore((state) => state.downloading);
|
||||
const downloading = attachmentDownloads[`monograph-${item.id}`];
|
||||
const [selfDestruct, setSelfDestruct] = useState(false);
|
||||
const [isLocked, setIsLocked] = useState(false);
|
||||
const [note, setNote] = useState(item);
|
||||
const [publishing, setPublishing] = useState(false);
|
||||
const publishUrl =
|
||||
note &&
|
||||
`https://monograph.notesnook.com/${db?.monographs.monograph(note?.id)}`;
|
||||
note && `https://monogr.ph/${db?.monographs.monograph(note?.id)}`;
|
||||
const isPublished = note && db?.monographs.isPublished(note?.id);
|
||||
const pwdInput = useRef();
|
||||
const passwordValue = useRef();
|
||||
@@ -137,9 +138,9 @@ const PublishNoteSheet = ({ note: item, update }) => {
|
||||
}}
|
||||
>
|
||||
Please wait...
|
||||
{loading && loading.current && loading.total
|
||||
{downloading && downloading.current && downloading.total
|
||||
? `\nDownloading attachments (${
|
||||
loading?.current / loading?.total
|
||||
downloading?.current / downloading?.total
|
||||
})`
|
||||
: ""}
|
||||
</Paragraph>
|
||||
|
||||
@@ -253,6 +253,57 @@ export default function ReminderSheet({
|
||||
marginBottom: DDS.isTab ? 25 : undefined
|
||||
}}
|
||||
>
|
||||
{reminderMode === ReminderModes.Permanent ? null : (
|
||||
<ScrollView
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
borderWidth: 1,
|
||||
marginTop: 12,
|
||||
borderRadius: 5,
|
||||
borderColor: colors.primary.border,
|
||||
paddingLeft: 12,
|
||||
height: 50
|
||||
}}
|
||||
horizontal
|
||||
>
|
||||
{Object.keys(ReminderNotificationModes).map((mode) => (
|
||||
<Button
|
||||
key={mode}
|
||||
title={mode}
|
||||
style={{
|
||||
marginRight: 12,
|
||||
borderRadius: 100
|
||||
}}
|
||||
icon={
|
||||
mode === "Silent"
|
||||
? "minus-circle"
|
||||
: mode === "Vibrate"
|
||||
? "vibrate"
|
||||
: "volume-high"
|
||||
}
|
||||
height={35}
|
||||
type={
|
||||
reminderNotificationMode ===
|
||||
ReminderNotificationModes[
|
||||
mode as keyof typeof ReminderNotificationModes
|
||||
]
|
||||
? "grayAccent"
|
||||
: "gray"
|
||||
}
|
||||
onPress={() => {
|
||||
const _mode = ReminderNotificationModes[
|
||||
mode as keyof typeof ReminderNotificationModes
|
||||
] as Reminder["priority"];
|
||||
SettingsService.set({
|
||||
reminderNotificationMode: _mode
|
||||
});
|
||||
setReminderNotificatioMode(_mode);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</ScrollView>
|
||||
)}
|
||||
|
||||
<Input
|
||||
fwdRef={titleRef}
|
||||
defaultValue={reminder?.title || referencedItem?.title}
|
||||
@@ -280,15 +331,21 @@ export default function ReminderSheet({
|
||||
}}
|
||||
height={80}
|
||||
wrapperStyle={{
|
||||
marginBottom: 20
|
||||
marginBottom: 12
|
||||
}}
|
||||
/>
|
||||
|
||||
<View
|
||||
<ScrollView
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
marginBottom: 12
|
||||
marginBottom: 12,
|
||||
height: 50,
|
||||
borderWidth: 1,
|
||||
borderRadius: 5,
|
||||
borderColor: colors.primary.border,
|
||||
paddingLeft: 12
|
||||
}}
|
||||
horizontal
|
||||
>
|
||||
{Object.keys(ReminderModes).map((mode) => (
|
||||
<Button
|
||||
@@ -327,7 +384,7 @@ export default function ReminderSheet({
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
{reminderMode === ReminderModes.Repeat ? (
|
||||
<View
|
||||
@@ -474,9 +531,7 @@ export default function ReminderSheet({
|
||||
androidVariant="nativeAndroid"
|
||||
is24hourSource="locale"
|
||||
locale={
|
||||
db.settings?.getTimeFormat() === "24-hour"
|
||||
? "en_GB.UTF8"
|
||||
: "en_US.UTF8"
|
||||
db.settings?.getTimeFormat() === "24-hour" ? "en_GB" : "en_US"
|
||||
}
|
||||
mode={reminderMode === ReminderModes.Repeat ? "time" : "datetime"}
|
||||
/>
|
||||
@@ -498,53 +553,6 @@ export default function ReminderSheet({
|
||||
</View>
|
||||
)}
|
||||
|
||||
{reminderMode === ReminderModes.Permanent ? null : (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
marginBottom: 12,
|
||||
paddingTop: 12,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: colors.secondary.background
|
||||
}}
|
||||
>
|
||||
{Object.keys(ReminderNotificationModes).map((mode) => (
|
||||
<Button
|
||||
key={mode}
|
||||
title={mode}
|
||||
style={{
|
||||
marginRight: 12,
|
||||
borderRadius: 100
|
||||
}}
|
||||
icon={
|
||||
mode === "Silent"
|
||||
? "minus-circle"
|
||||
: mode === "Vibrate"
|
||||
? "vibrate"
|
||||
: "volume-high"
|
||||
}
|
||||
height={35}
|
||||
type={
|
||||
reminderNotificationMode ===
|
||||
ReminderNotificationModes[
|
||||
mode as keyof typeof ReminderNotificationModes
|
||||
]
|
||||
? "grayAccent"
|
||||
: "gray"
|
||||
}
|
||||
onPress={() => {
|
||||
const _mode = ReminderNotificationModes[
|
||||
mode as keyof typeof ReminderNotificationModes
|
||||
] as Reminder["priority"];
|
||||
SettingsService.set({
|
||||
reminderNotificationMode: _mode
|
||||
});
|
||||
setReminderNotificatioMode(_mode);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
{reminderMode === ReminderModes.Once ||
|
||||
reminderMode === ReminderModes.Permanent ? null : (
|
||||
<View
|
||||
@@ -595,6 +603,11 @@ export default function ReminderSheet({
|
||||
}}
|
||||
/>
|
||||
</ScrollView>
|
||||
<View
|
||||
style={{
|
||||
height: 10
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,15 +17,19 @@ 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 { getFormattedDate } from "@notesnook/common";
|
||||
import { EVENTS } from "@notesnook/core/dist/common";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { ActivityIndicator, Platform, View } from "react-native";
|
||||
import { FlatList } from "react-native-actions-sheet";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import DocumentPicker from "react-native-document-picker";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import { unzip } from "react-native-zip-archive";
|
||||
import { db } from "../../../common/database";
|
||||
import storage from "../../../common/database/storage";
|
||||
import { cacheDir, copyFileAsync } from "../../../common/filesystem/utils";
|
||||
import {
|
||||
ToastEvent,
|
||||
eSubscribeEvent,
|
||||
@@ -33,7 +37,6 @@ import {
|
||||
} from "../../../services/event-manager";
|
||||
import SettingsService from "../../../services/settings";
|
||||
import { initialize } from "../../../stores";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { eCloseRestoreDialog, eOpenRestoreDialog } from "../../../utils/events";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { Dialog } from "../../dialog";
|
||||
@@ -44,7 +47,6 @@ import { Button } from "../../ui/button";
|
||||
import Seperator from "../../ui/seperator";
|
||||
import SheetWrapper from "../../ui/sheet";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { getFormattedDate } from "@notesnook/common";
|
||||
|
||||
const RestoreDataSheet = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
@@ -129,75 +131,78 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
checkBackups();
|
||||
}, 300);
|
||||
}, 1000);
|
||||
}, []);
|
||||
|
||||
const restore = async (item) => {
|
||||
if (restoring) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setRestoring(true);
|
||||
let prefix = Platform.OS === "ios" ? "" : "file:/";
|
||||
let backup;
|
||||
if (Platform.OS === "android") {
|
||||
backup = await ScopedStorage.readFile(item.uri, "utf8");
|
||||
} else {
|
||||
backup = await RNFetchBlob.fs.readFile(prefix + item.path, "utf8");
|
||||
}
|
||||
backup = JSON.parse(backup);
|
||||
|
||||
if (backup.data.iv && backup.data.salt) {
|
||||
withPassword(
|
||||
async (value) => {
|
||||
try {
|
||||
await restoreBackup(backup, value);
|
||||
close();
|
||||
setRestoring(false);
|
||||
return true;
|
||||
} catch (e) {
|
||||
backupError(e);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
() => {
|
||||
setRestoring(false);
|
||||
try {
|
||||
const file = Platform.OS === "ios" ? item.path : item.uri;
|
||||
console.log(file);
|
||||
if (file.endsWith(".nnbackupz")) {
|
||||
setRestoring(true);
|
||||
|
||||
if (Platform.OS === "android") {
|
||||
const cacheFile = `file://${RNFetchBlob.fs.dirs.CacheDir}/backup.zip`;
|
||||
if (await RNFetchBlob.fs.exists(cacheFile)) {
|
||||
await RNFetchBlob.fs.unlink(cacheFile);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
await restoreBackup(backup);
|
||||
close();
|
||||
await RNFetchBlob.fs.createFile(cacheFile, "", "utf8");
|
||||
console.log("copying");
|
||||
await copyFileAsync(file, cacheFile);
|
||||
console.log("copied");
|
||||
await restoreFromZip(cacheFile);
|
||||
} else {
|
||||
await restoreFromZip(file, false);
|
||||
}
|
||||
} else if (file.endsWith(".nnbackup")) {
|
||||
let backup;
|
||||
if (Platform.OS === "android") {
|
||||
backup = await ScopedStorage.readFile(file, "utf8");
|
||||
} else {
|
||||
backup = await RNFetchBlob.fs.readFile(file, "utf8");
|
||||
}
|
||||
await restoreFromNNBackup(JSON.parse(backup));
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("error", e);
|
||||
setRestoring(false);
|
||||
backupError(e);
|
||||
}
|
||||
};
|
||||
|
||||
const withPassword = (onsubmit, onclose = () => {}) => {
|
||||
presentDialog({
|
||||
context: "local",
|
||||
title: "Encrypted backup",
|
||||
input: true,
|
||||
inputPlaceholder: "Password",
|
||||
paragraph: "Please enter password of this backup file to restore it",
|
||||
positiveText: "Restore",
|
||||
secureTextEntry: true,
|
||||
onClose: onclose,
|
||||
negativeText: "Cancel",
|
||||
positivePress: async (password) => {
|
||||
try {
|
||||
return await onsubmit(password);
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
heading: "Failed to backup data",
|
||||
message: e.message,
|
||||
type: "error",
|
||||
context: "global"
|
||||
const withPassword = () => {
|
||||
return new Promise((resolve) => {
|
||||
let resolved = false;
|
||||
presentDialog({
|
||||
context: "local",
|
||||
title: "Encrypted backup",
|
||||
input: true,
|
||||
inputPlaceholder: "Password",
|
||||
paragraph: "Please enter password of this backup file",
|
||||
positiveText: "Restore",
|
||||
secureTextEntry: true,
|
||||
onClose: () => {
|
||||
if (resolved) return;
|
||||
resolve(undefined);
|
||||
},
|
||||
negativeText: "Cancel",
|
||||
positivePress: async (password, isEncryptionKey) => {
|
||||
resolve({
|
||||
value: password,
|
||||
isEncryptionKey
|
||||
});
|
||||
return false;
|
||||
resolved = true;
|
||||
return true;
|
||||
},
|
||||
check: {
|
||||
info: "Use encryption key",
|
||||
type: "transparent"
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -217,21 +222,180 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
|
||||
let path = await storage.checkAndCreateDir("/backups/");
|
||||
files = await RNFetchBlob.fs.lstat(path);
|
||||
}
|
||||
files = files.sort(function (a, b) {
|
||||
let timeA = a.lastModified;
|
||||
let timeB = b.lastModified;
|
||||
return timeB - timeA;
|
||||
});
|
||||
files = files
|
||||
.filter((file) => {
|
||||
const name = Platform.OS === "android" ? file.name : file.filename;
|
||||
return name.endsWith(".nnbackup") || name.endsWith(".nnbackupz");
|
||||
})
|
||||
.sort(function (a, b) {
|
||||
let timeA = a.lastModified;
|
||||
let timeB = b.lastModified;
|
||||
return timeB - timeA;
|
||||
});
|
||||
setFiles(files);
|
||||
setTimeout(() => {
|
||||
setLoading(false);
|
||||
}, 1000);
|
||||
setLoading(false);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const restoreBackup = async (backup, password, key) => {
|
||||
await db.backup.import(backup, password, key);
|
||||
await db.initCollections();
|
||||
initialize();
|
||||
ToastEvent.show({
|
||||
heading: "Backup restored successfully.",
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
const backupError = (e) => {
|
||||
ToastEvent.show({
|
||||
heading: "Restore failed",
|
||||
message:
|
||||
e.message ||
|
||||
"The selected backup data file is invalid. You must select a *.nnbackup file to restore.",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} file
|
||||
*/
|
||||
async function restoreFromZip(file, remove) {
|
||||
try {
|
||||
const zipOutputFolder = `${cacheDir}/backup_extracted`;
|
||||
if (await RNFetchBlob.fs.exists(zipOutputFolder)) {
|
||||
await RNFetchBlob.fs.unlink(zipOutputFolder);
|
||||
await RNFetchBlob.fs.mkdir(zipOutputFolder);
|
||||
}
|
||||
await unzip(file, zipOutputFolder);
|
||||
console.log("Unzipped files successfully to", zipOutputFolder);
|
||||
|
||||
const backupFiles = await RNFetchBlob.fs.ls(zipOutputFolder);
|
||||
|
||||
if (backupFiles.findIndex((file) => file === ".nnbackup") === -1) {
|
||||
throw new Error("Backup file is invalid");
|
||||
}
|
||||
|
||||
let password;
|
||||
let key;
|
||||
|
||||
console.log(`Found ${backupFiles?.length} files to restore from backup`);
|
||||
for (const path of backupFiles) {
|
||||
if (path === ".nnbackup") continue;
|
||||
const filePath = `${zipOutputFolder}/${path}`;
|
||||
const data = await RNFetchBlob.fs.readFile(filePath, "utf8");
|
||||
const parsed = JSON.parse(data);
|
||||
|
||||
if (parsed.encrypted && !password) {
|
||||
console.log("Backup is encrypted...", "requesting password");
|
||||
const { value, isEncryptionKey } = await withPassword();
|
||||
|
||||
if (isEncryptionKey) {
|
||||
key = value;
|
||||
} else {
|
||||
password = value;
|
||||
}
|
||||
if (!password && !key) throw new Error("Failed to decrypt backup");
|
||||
}
|
||||
await db.backup.import(parsed, password, key);
|
||||
console.log("Imported", path);
|
||||
}
|
||||
// Remove files from cache
|
||||
RNFetchBlob.fs.unlink(zipOutputFolder).catch(console.log);
|
||||
if (remove) {
|
||||
RNFetchBlob.fs.unlink(file).catch(console.log);
|
||||
}
|
||||
|
||||
await db.initCollections();
|
||||
initialize();
|
||||
setRestoring(false);
|
||||
close();
|
||||
ToastEvent.show({
|
||||
heading: "Backup restored successfully.",
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
} catch (e) {
|
||||
backupError(e);
|
||||
setRestoring(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} file
|
||||
*/
|
||||
async function restoreFromNNBackup(backup) {
|
||||
try {
|
||||
if (backup.data.iv && backup.data.salt) {
|
||||
const { value, isEncryptionKey } = await withPassword();
|
||||
|
||||
let key;
|
||||
let password;
|
||||
if (isEncryptionKey) {
|
||||
key = value;
|
||||
} else {
|
||||
password = value;
|
||||
}
|
||||
|
||||
if (key || password) {
|
||||
try {
|
||||
await restoreBackup(backup, password, key);
|
||||
close();
|
||||
setRestoring(false);
|
||||
} catch (e) {
|
||||
setRestoring(false);
|
||||
backupError(e);
|
||||
}
|
||||
} else {
|
||||
setRestoring(false);
|
||||
}
|
||||
} else {
|
||||
await restoreBackup(backup);
|
||||
setRestoring(false);
|
||||
close();
|
||||
}
|
||||
} catch (e) {
|
||||
setRestoring(false);
|
||||
backupError(e);
|
||||
}
|
||||
}
|
||||
|
||||
const button = {
|
||||
title: "Restore from files",
|
||||
onPress: async () => {
|
||||
if (restoring) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const file = await DocumentPicker.pickSingle({
|
||||
copyTo: "cachesDirectory"
|
||||
});
|
||||
|
||||
if (file.name.endsWith(".nnbackupz")) {
|
||||
setRestoring(true);
|
||||
await restoreFromZip(file.fileCopyUri, true);
|
||||
} else if (file.name.endsWith(".nnbackup")) {
|
||||
RNFetchBlob.fs.unlink(file.fileCopyUri).catch(console.log);
|
||||
setRestoring(true);
|
||||
const data = await fetch(file.uri);
|
||||
await restoreFromNNBackup(await data.json());
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("error", e.stack);
|
||||
setRestoring(false);
|
||||
backupError(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const renderItem = ({ item, index }) => (
|
||||
<View
|
||||
style={{
|
||||
@@ -271,76 +435,6 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
|
||||
</View>
|
||||
);
|
||||
|
||||
const restoreBackup = async (backup, password) => {
|
||||
await db.backup.import(backup, password);
|
||||
setRestoring(false);
|
||||
initialize();
|
||||
ToastEvent.show({
|
||||
heading: "Backup restored successfully.",
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
};
|
||||
|
||||
const backupError = (e) => {
|
||||
ToastEvent.show({
|
||||
heading: "Restore failed",
|
||||
message:
|
||||
e.message ||
|
||||
"The selected backup data file is invalid. You must select a *.nnbackup file to restore.",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
};
|
||||
|
||||
const button = {
|
||||
title: "Restore from files",
|
||||
onPress: () => {
|
||||
if (restoring) {
|
||||
return;
|
||||
}
|
||||
|
||||
DocumentPicker.pickSingle()
|
||||
.then((r) => {
|
||||
setRestoring(true);
|
||||
fetch(r.uri)
|
||||
.then(async (r) => {
|
||||
try {
|
||||
let backup = await r.json();
|
||||
if (backup.data.iv && backup.data.salt) {
|
||||
withPassword(
|
||||
async (value) => {
|
||||
try {
|
||||
restoreBackup(backup, value).then(() => {
|
||||
close();
|
||||
setRestoring(false);
|
||||
});
|
||||
return true;
|
||||
} catch (e) {
|
||||
backupError(e);
|
||||
setRestoring(false);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
() => {
|
||||
setRestoring(false);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
await restoreBackup(backup);
|
||||
close();
|
||||
}
|
||||
} catch (e) {
|
||||
setRestoring(false);
|
||||
backupError(e);
|
||||
}
|
||||
})
|
||||
.catch(console.log);
|
||||
})
|
||||
.catch(console.log);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<View>
|
||||
|
||||
@@ -88,10 +88,14 @@ const Sort = ({ type, screen }) => {
|
||||
? groupOptions.groupBy === "abc" ||
|
||||
groupOptions.sortBy === "title"
|
||||
? "A - Z"
|
||||
: groupOptions.sortBy === "dueDate"
|
||||
? "Earliest first"
|
||||
: "Old - New"
|
||||
: groupOptions.groupBy === "abc" ||
|
||||
groupOptions.sortBy === "title"
|
||||
? "Z - A"
|
||||
: groupOptions.sortBy === "dueDate"
|
||||
? "Latest first"
|
||||
: "New - Old"
|
||||
}
|
||||
icon={
|
||||
@@ -142,6 +146,7 @@ const Sort = ({ type, screen }) => {
|
||||
/>
|
||||
) : (
|
||||
Object.keys(SORT).map((item) =>
|
||||
(item === "dueDate" && screen !== "Reminders") ||
|
||||
(item === "title" && groupOptions.groupBy !== "none") ||
|
||||
((screen !== "Tags" || screen !== "Reminders") &&
|
||||
item === "dateModified") ||
|
||||
|
||||
@@ -16,6 +16,7 @@ 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 { useThemeColors } from "@notesnook/theme";
|
||||
import qclone from "qclone";
|
||||
import React, {
|
||||
createContext,
|
||||
@@ -26,7 +27,7 @@ import React, {
|
||||
useRef,
|
||||
useState
|
||||
} from "react";
|
||||
import { RefreshControl, View } from "react-native";
|
||||
import { RefreshControl, useWindowDimensions, View } from "react-native";
|
||||
import ActionSheet, {
|
||||
ActionSheetRef,
|
||||
FlatList
|
||||
@@ -45,7 +46,6 @@ import {
|
||||
import useNavigationStore, {
|
||||
NotebookScreenParams
|
||||
} from "../../../stores/use-navigation-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import {
|
||||
eOnNewTopicAdded,
|
||||
eOnTopicSheetUpdate,
|
||||
@@ -54,18 +54,17 @@ import {
|
||||
import { normalize, SIZE } from "../../../utils/size";
|
||||
import { GroupHeader, NotebookType, TopicType } from "../../../utils/types";
|
||||
|
||||
import { getTotalNotes } from "@notesnook/common";
|
||||
import { groupArray } from "@notesnook/core/dist/utils/grouping";
|
||||
import Config from "react-native-config";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { notesnook } from "../../../../e2e/test.ids";
|
||||
import { MMKV } from "../../../common/database/mmkv";
|
||||
import { openEditor } from "../../../screens/notes/common";
|
||||
import { getTotalNotes } from "@notesnook/common";
|
||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||
import { deleteItems } from "../../../utils/functions";
|
||||
import { presentDialog } from "../../dialog/functions";
|
||||
import { Properties } from "../../properties";
|
||||
import Sort from "../sort";
|
||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||
|
||||
type ConfigItem = { id: string; type: string };
|
||||
class TopicSheetConfig {
|
||||
@@ -108,7 +107,8 @@ export const TopicsSheet = () => {
|
||||
)
|
||||
: []
|
||||
);
|
||||
|
||||
const currentItem = useRef<string>();
|
||||
const { fontScale } = useWindowDimensions();
|
||||
const [groupOptions, setGroupOptions] = useState(
|
||||
db.settings?.getGroupOptions("topics")
|
||||
);
|
||||
@@ -206,6 +206,11 @@ export const TopicsSheet = () => {
|
||||
if (canShow) {
|
||||
setTimeout(() => {
|
||||
const id = isTopic ? currentScreen?.notebookId : currentScreen?.id;
|
||||
if (currentItem.current !== id) {
|
||||
setSelection([]);
|
||||
setEnabled(false);
|
||||
}
|
||||
currentItem.current = id;
|
||||
const notebook = db.notebooks?.notebook(id as string)?.data;
|
||||
const snapPoint = isTopic
|
||||
? 0
|
||||
@@ -226,6 +231,8 @@ export const TopicsSheet = () => {
|
||||
}
|
||||
}, 300);
|
||||
} else {
|
||||
setSelection([]);
|
||||
setEnabled(false);
|
||||
ref.current?.hide();
|
||||
}
|
||||
}, [
|
||||
@@ -271,7 +278,7 @@ export const TopicsSheet = () => {
|
||||
backgroundColor: colors.secondary.background
|
||||
}}
|
||||
keyboardHandlerEnabled={false}
|
||||
snapPoints={Config.isTesting === "true" ? [100] : [25, 100]}
|
||||
snapPoints={Config.isTesting === "true" ? [100] : [20, 100]}
|
||||
initialSnapIndex={1}
|
||||
backgroundInteractionEnabled
|
||||
gestureEnabled
|
||||
@@ -305,8 +312,8 @@ export const TopicsSheet = () => {
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
maxHeight: 300,
|
||||
height: 300,
|
||||
maxHeight: 450,
|
||||
height: 450,
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
@@ -329,30 +336,19 @@ export const TopicsSheet = () => {
|
||||
{enabled ? (
|
||||
<IconButton
|
||||
customStyle={{
|
||||
marginLeft: 10
|
||||
marginLeft: 10,
|
||||
width: 40 * fontScale,
|
||||
height: 40 * fontScale
|
||||
}}
|
||||
onPress={async () => {
|
||||
//@ts-ignore
|
||||
useSelectionStore.setState({
|
||||
selectedItemsList: selection
|
||||
});
|
||||
presentDialog({
|
||||
title: `Delete ${
|
||||
selection.length > 1 ? "topics" : "topics"
|
||||
}`,
|
||||
paragraph: `Are you sure you want to delete ${
|
||||
selection.length > 1 ? "these topics?" : "this topic?"
|
||||
}`,
|
||||
positiveText: "Delete",
|
||||
negativeText: "Cancel",
|
||||
positivePress: async () => {
|
||||
await deleteItems();
|
||||
useSelectionStore.getState().clearSelection();
|
||||
setEnabled(false);
|
||||
setSelection([]);
|
||||
},
|
||||
positiveType: "errorShade"
|
||||
});
|
||||
await deleteItems();
|
||||
useSelectionStore.getState().clearSelection();
|
||||
setEnabled(false);
|
||||
setSelection([]);
|
||||
return;
|
||||
}}
|
||||
color={colors.primary.icon}
|
||||
@@ -378,8 +374,8 @@ export const TopicsSheet = () => {
|
||||
color={colors.primary.icon}
|
||||
size={22}
|
||||
customStyle={{
|
||||
width: 40,
|
||||
height: 40
|
||||
width: 40 * fontScale,
|
||||
height: 40 * fontScale
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
@@ -389,8 +385,8 @@ export const TopicsSheet = () => {
|
||||
color={colors.primary.icon}
|
||||
size={22}
|
||||
customStyle={{
|
||||
width: 40,
|
||||
height: 40
|
||||
width: 40 * fontScale,
|
||||
height: 40 * fontScale
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -408,8 +404,8 @@ export const TopicsSheet = () => {
|
||||
color={colors.primary.icon}
|
||||
size={22}
|
||||
customStyle={{
|
||||
width: 40,
|
||||
height: 40
|
||||
width: 40 * fontScale,
|
||||
height: 40 * fontScale
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
@@ -483,6 +479,7 @@ const TopicItem = ({
|
||||
selection.selection.findIndex((selected) => selected.id === item.id) > -1;
|
||||
const isFocused = screen.id === item.id;
|
||||
const notesCount = getTotalNotes(item);
|
||||
const { fontScale } = useWindowDimensions();
|
||||
|
||||
return (
|
||||
<PressableButton
|
||||
@@ -519,6 +516,10 @@ const TopicItem = ({
|
||||
<IconButton
|
||||
size={SIZE.lg}
|
||||
color={isSelected ? colors.selected.icon : colors.primary.icon}
|
||||
top={0}
|
||||
left={0}
|
||||
bottom={0}
|
||||
right={0}
|
||||
name={
|
||||
isSelected
|
||||
? "check-circle-outline"
|
||||
@@ -538,8 +539,8 @@ const TopicItem = ({
|
||||
<IconButton
|
||||
name="dots-horizontal"
|
||||
customStyle={{
|
||||
width: 40,
|
||||
height: 40
|
||||
width: 40 * fontScale,
|
||||
height: 40 * fontScale
|
||||
}}
|
||||
testID={notesnook.ids.notebook.menu}
|
||||
onPress={() => {
|
||||
|
||||
@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { Dimensions, View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import ToggleSwitch from "toggle-switch-react-native";
|
||||
import Navigation from "../../services/navigation";
|
||||
@@ -109,6 +109,7 @@ export const MenuItem = React.memo(
|
||||
textAlignVertical: "center",
|
||||
textAlign: "left"
|
||||
}}
|
||||
allowFontScaling
|
||||
name={item.icon}
|
||||
color={
|
||||
item.icon === "crown"
|
||||
|
||||
@@ -209,13 +209,19 @@ export const PinItem = React.memo(
|
||||
justifyContent: "center"
|
||||
}}
|
||||
>
|
||||
<Icon color={color} size={SIZE.lg - 2} name={icons[item.type]} />
|
||||
<Icon
|
||||
allowFontScaling
|
||||
color={color}
|
||||
size={SIZE.lg - 2}
|
||||
name={icons[item.type]}
|
||||
/>
|
||||
<Icon
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: -6,
|
||||
left: -6
|
||||
}}
|
||||
allowFontScaling
|
||||
color={color}
|
||||
size={SIZE.xs}
|
||||
name="arrow-top-right-thick"
|
||||
|
||||
@@ -113,6 +113,7 @@ export const UserStatus = () => {
|
||||
<Icon
|
||||
name="checkbox-blank-circle"
|
||||
size={11}
|
||||
allowFontScaling
|
||||
color={
|
||||
!user || lastSyncStatus === SyncStatus.Failed
|
||||
? colors.error.icon
|
||||
@@ -149,9 +150,15 @@ export const UserStatus = () => {
|
||||
color={colors.error.icon}
|
||||
name="sync-alert"
|
||||
size={SIZE.lg}
|
||||
allowFontScaling
|
||||
/>
|
||||
) : (
|
||||
<Icon color={colors.primary.accent} name="sync" size={SIZE.lg} />
|
||||
<Icon
|
||||
allowFontScaling
|
||||
color={colors.primary.accent}
|
||||
name="sync"
|
||||
size={SIZE.lg}
|
||||
/>
|
||||
)
|
||||
) : null}
|
||||
</PressableButton>
|
||||
|
||||
@@ -134,6 +134,7 @@ export const Button = ({
|
||||
{icon && !loading && iconPosition === "left" ? (
|
||||
<Icon
|
||||
name={icon}
|
||||
allowFontScaling
|
||||
style={[{ marginRight: 0 }, iconStyle as any]}
|
||||
color={iconColor || buttonType?.text || textColor}
|
||||
size={iconSize}
|
||||
@@ -170,6 +171,7 @@ export const Button = ({
|
||||
{icon && !loading && iconPosition === "right" ? (
|
||||
<Icon
|
||||
name={icon}
|
||||
allowFontScaling
|
||||
style={[{ marginLeft: 0 }, iconStyle as any]}
|
||||
color={iconColor || buttonType?.text || textColor}
|
||||
size={iconSize}
|
||||
|
||||
@@ -40,7 +40,6 @@ interface IconButtonProps extends PressableButtonProps {
|
||||
iconStyle?: TextStyle;
|
||||
}
|
||||
|
||||
const AnimatedIcon = Animated.createAnimatedComponent(Icon);
|
||||
export const IconButton = ({
|
||||
onPress,
|
||||
name,
|
||||
@@ -88,10 +87,10 @@ export const IconButton = ({
|
||||
...customStyle
|
||||
}}
|
||||
>
|
||||
<AnimatedIcon
|
||||
layout={Layout}
|
||||
<Icon
|
||||
name={name}
|
||||
style={iconStyle as any}
|
||||
allowFontScaling
|
||||
color={
|
||||
restProps.disabled
|
||||
? RGB_Linear_Shade(-0.05, hexToRGBA(colors.secondary.background))
|
||||
|
||||
@@ -17,15 +17,17 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { ScopedThemeProvider, useThemeColors } from "@notesnook/theme";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { Platform, View } from "react-native";
|
||||
import ActionSheet from "react-native-actions-sheet";
|
||||
import useGlobalSafeAreaInsets from "../../../hooks/use-global-safe-area-insets";
|
||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||
import { ScopedThemeProvider, useThemeColors } from "@notesnook/theme";
|
||||
import { PremiumToast } from "../../premium/premium-toast";
|
||||
import { Toast } from "../../toast";
|
||||
import { BouncingView } from "../transitions/bouncing-view";
|
||||
import { useAppState } from "../../../hooks/use-app-state";
|
||||
import SettingsService from "../../../services/settings";
|
||||
import { useUserStore } from "../../../stores/use-user-store";
|
||||
|
||||
const SheetWrapper = ({
|
||||
children,
|
||||
@@ -35,12 +37,13 @@ const SheetWrapper = ({
|
||||
onOpen,
|
||||
closeOnTouchBackdrop = true,
|
||||
onHasReachedTop,
|
||||
keyboardMode,
|
||||
overlay,
|
||||
overlayOpacity = 0.3,
|
||||
enableGesturesInScrollView = false,
|
||||
bottomPadding = true
|
||||
bottomPadding = true,
|
||||
keyboardHandlerDisabled
|
||||
}) => {
|
||||
const localRef = useRef(null);
|
||||
const { colors } = useThemeColors("sheet");
|
||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||
const sheetKeyboardHandler = useSettingStore(
|
||||
@@ -50,6 +53,8 @@ const SheetWrapper = ({
|
||||
const smallTablet = deviceMode === "smallTablet";
|
||||
const dimensions = useSettingStore((state) => state.dimensions);
|
||||
const insets = useGlobalSafeAreaInsets();
|
||||
const appState = useAppState();
|
||||
const lockEvents = useRef(false);
|
||||
let width = dimensions.width > 600 ? 600 : 500;
|
||||
|
||||
const style = React.useMemo(() => {
|
||||
@@ -68,19 +73,41 @@ const SheetWrapper = ({
|
||||
}, [colors.primary.background, largeTablet, smallTablet, width]);
|
||||
|
||||
const _onOpen = () => {
|
||||
if (lockEvents.current) return;
|
||||
onOpen && onOpen();
|
||||
};
|
||||
|
||||
const _onClose = async () => {
|
||||
if (lockEvents.current) return;
|
||||
if (onClose) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (useUserStore.getState().disableAppLockRequests) return;
|
||||
if (SettingsService.get().appLockMode === "background") {
|
||||
if (appState === "background") {
|
||||
const ref = fwdRef || localRef;
|
||||
ref?.current?.hide();
|
||||
if (useUserStore.getState().appLocked) {
|
||||
lockEvents.current = true;
|
||||
const unsub = useUserStore.subscribe((state) => {
|
||||
if (!state.appLocked) {
|
||||
ref?.current?.show();
|
||||
unsub();
|
||||
lockEvents.current = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [appState, fwdRef]);
|
||||
|
||||
return (
|
||||
<ScopedThemeProvider value="sheet">
|
||||
<ActionSheet
|
||||
ref={fwdRef}
|
||||
ref={fwdRef || localRef}
|
||||
testIDs={{
|
||||
backdrop: "sheet-backdrop"
|
||||
}}
|
||||
@@ -94,8 +121,9 @@ const SheetWrapper = ({
|
||||
initialOffsetFromBottom={1}
|
||||
onPositionChanged={onHasReachedTop}
|
||||
closeOnTouchBackdrop={closeOnTouchBackdrop}
|
||||
keyboardMode={keyboardMode}
|
||||
keyboardHandlerEnabled={sheetKeyboardHandler}
|
||||
keyboardHandlerEnabled={
|
||||
keyboardHandlerDisabled ? false : sheetKeyboardHandler
|
||||
}
|
||||
closeOnPressBack={closeOnTouchBackdrop}
|
||||
indicatorColor={colors.secondary.background}
|
||||
onOpen={_onOpen}
|
||||
|
||||
@@ -59,7 +59,6 @@ const Heading = ({
|
||||
<Component
|
||||
layout={restProps.layout || Layout}
|
||||
allowFontScaling={true}
|
||||
maxFontSizeMultiplier={1}
|
||||
{...restProps}
|
||||
style={[
|
||||
{
|
||||
|
||||
@@ -45,8 +45,6 @@ const Paragraph = ({
|
||||
return (
|
||||
<Component
|
||||
layout={restProps.layout || Layout}
|
||||
allowFontScaling
|
||||
maxFontSizeMultiplier={1}
|
||||
{...restProps}
|
||||
style={[
|
||||
{
|
||||
|
||||
@@ -70,8 +70,8 @@ const NotebookWelcome = () => {
|
||||
count: 5
|
||||
},
|
||||
{
|
||||
title: "Recipies",
|
||||
description: "I love cooking and collecting recipies",
|
||||
title: "Recipes",
|
||||
description: "I love cooking and collecting recipes",
|
||||
count: 10
|
||||
}
|
||||
]);
|
||||
@@ -195,7 +195,7 @@ const notebooks: { id: string; steps: TStep[] } = {
|
||||
>
|
||||
<Paragraph size={SIZE.xs}>
|
||||
<Icon color={colors.primary.icon} size={SIZE.sm} name="note" />{" "}
|
||||
Feburary 2022 Week 2
|
||||
February 2022 Week 2
|
||||
</Paragraph>
|
||||
</View>
|
||||
<View
|
||||
@@ -211,7 +211,7 @@ const notebooks: { id: string; steps: TStep[] } = {
|
||||
>
|
||||
<Paragraph size={SIZE.xs}>
|
||||
<Icon color={colors.primary.icon} size={SIZE.sm} name="note" />{" "}
|
||||
Feburary 2022 Week 1
|
||||
February 2022 Week 1
|
||||
</Paragraph>
|
||||
</View>
|
||||
<View
|
||||
@@ -257,7 +257,7 @@ const notebooks: { id: string; steps: TStep[] } = {
|
||||
title: "Tasks",
|
||||
type: "topic"
|
||||
}}
|
||||
onPress={() => {}}
|
||||
onPress={() => { }}
|
||||
/>
|
||||
|
||||
<PinItem
|
||||
@@ -267,7 +267,7 @@ const notebooks: { id: string; steps: TStep[] } = {
|
||||
title: "Work and office",
|
||||
type: "notebook"
|
||||
}}
|
||||
onPress={() => {}}
|
||||
onPress={() => { }}
|
||||
/>
|
||||
</View>
|
||||
),
|
||||
|
||||
@@ -188,7 +188,7 @@ export const useActions = ({ close = () => null, item }) => {
|
||||
Notifications.displayNotification({
|
||||
title: item.title,
|
||||
message: item.headline || text,
|
||||
subtitle: item.headline || text,
|
||||
subtitle: "",
|
||||
bigText: html,
|
||||
ongoing: true,
|
||||
actions: ["UNPIN"],
|
||||
@@ -520,7 +520,7 @@ export const useActions = ({ close = () => null, item }) => {
|
||||
await sleep(300);
|
||||
presentDialog({
|
||||
title: "Permanent delete",
|
||||
paragraph: `Are you sure you want to delete this ${item.itemType} permanantly from trash?`,
|
||||
paragraph: `Are you sure you want to delete this ${item.itemType} permanently from trash?`,
|
||||
positiveText: "Delete",
|
||||
negativeText: "Cancel",
|
||||
positivePress: async () => {
|
||||
@@ -529,7 +529,7 @@ export const useActions = ({ close = () => null, item }) => {
|
||||
Navigation.queueRoutesForUpdate();
|
||||
useSelectionStore.getState().setSelectionMode(false);
|
||||
ToastEvent.show({
|
||||
heading: "Permanantly deleted items",
|
||||
heading: "Permanently deleted items",
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
@@ -550,15 +550,6 @@ export const useActions = ({ close = () => null, item }) => {
|
||||
}
|
||||
|
||||
async function exportNote() {
|
||||
if (item.locked) {
|
||||
ToastEvent.show({
|
||||
heading: "Note is locked",
|
||||
type: "error",
|
||||
message: "Locked notes cannot be exported",
|
||||
context: "local"
|
||||
});
|
||||
return;
|
||||
}
|
||||
ExportNotesSheet.present([item]);
|
||||
}
|
||||
|
||||
@@ -649,7 +640,7 @@ export const useActions = ({ close = () => null, item }) => {
|
||||
},
|
||||
{
|
||||
id: "favorite",
|
||||
title: item.favorite ? "Unfavorite" : "Favorite",
|
||||
title: item.favorite ? "Unfav" : "Fav",
|
||||
icon: item.favorite ? "star-off" : "star-outline",
|
||||
func: addToFavorites,
|
||||
close: false,
|
||||
|
||||
@@ -83,7 +83,6 @@ import {
|
||||
import { getGithubVersion } from "../utils/github-version";
|
||||
import { tabBarRef } from "../utils/global-refs";
|
||||
import { sleep } from "../utils/time";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
|
||||
const onCheckSyncStatus = async (type) => {
|
||||
const { disableSync, disableAutoSync } = SettingsService.get();
|
||||
@@ -107,10 +106,20 @@ const onFileEncryptionProgress = ({ total, progress }) => {
|
||||
.setEncryptionProgress((progress / total).toFixed(2));
|
||||
};
|
||||
|
||||
const onLoadingAttachmentProgress = (data) => {
|
||||
useAttachmentStore
|
||||
.getState()
|
||||
.setLoading(data.total === data.current ? null : data);
|
||||
const onDownloadingAttachmentProgress = (data) => {
|
||||
useAttachmentStore.getState().setDownloading(data);
|
||||
};
|
||||
|
||||
const onUploadingAttachmentProgress = (data) => {
|
||||
useAttachmentStore.getState().setUploading(data);
|
||||
};
|
||||
|
||||
const onDownloadedAttachmentProgress = (data) => {
|
||||
useAttachmentStore.getState().setDownloading(data);
|
||||
};
|
||||
|
||||
const onUploadedAttachmentProgress = (data) => {
|
||||
useAttachmentStore.getState().setUploading(data);
|
||||
};
|
||||
|
||||
const onUserSessionExpired = async () => {
|
||||
@@ -220,12 +229,25 @@ async function saveEditorState() {
|
||||
MMKV.setString("appState", state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {RNIap.Purchase} subscription
|
||||
*/
|
||||
const onSuccessfulSubscription = async (subscription) => {
|
||||
console.log(
|
||||
"Subscription success!",
|
||||
subscription.transactionId,
|
||||
subscription.obfuscatedAccountIdAndroid,
|
||||
subscription.obfuscatedProfileIdAndroid
|
||||
);
|
||||
await PremiumService.subscriptions.set(subscription);
|
||||
await PremiumService.subscriptions.verify(subscription);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {RNIap.PurchaseError} error
|
||||
*/
|
||||
const onSubscriptionError = async (error) => {
|
||||
ToastEvent.show({
|
||||
heading: "Failed to subscribe",
|
||||
@@ -301,7 +323,16 @@ export const useAppEvents = () => {
|
||||
EVENTS.userSubscriptionUpdated,
|
||||
onUserSubscriptionStatusChanged
|
||||
),
|
||||
EV.subscribe(EVENTS.attachmentsLoading, onLoadingAttachmentProgress),
|
||||
EV.subscribe(EVENTS.fileDownload, onDownloadingAttachmentProgress),
|
||||
EV.subscribe(EVENTS.fileUpload, onUploadingAttachmentProgress),
|
||||
EV.subscribe(EVENTS.fileDownloaded, onDownloadedAttachmentProgress),
|
||||
EV.subscribe(EVENTS.fileUploaded, onUploadedAttachmentProgress),
|
||||
EV.subscribe(EVENTS.downloadCanceled, (data) => {
|
||||
useAttachmentStore.getState().setDownloading(data);
|
||||
}),
|
||||
EV.subscribe(EVENTS.uploadCanceled, (data) => {
|
||||
useAttachmentStore.getState().setUploading(data);
|
||||
}),
|
||||
eSubscribeEvent(eUserLoggedIn, onUserUpdated)
|
||||
];
|
||||
|
||||
@@ -429,7 +460,6 @@ export const useAppEvents = () => {
|
||||
}
|
||||
|
||||
clearMessage();
|
||||
subscribeToIAPListeners();
|
||||
if (!login) {
|
||||
user = await db.user.fetchUser();
|
||||
setUser(user);
|
||||
@@ -448,8 +478,12 @@ export const useAppEvents = () => {
|
||||
userEmailConfirmed: true
|
||||
});
|
||||
}
|
||||
|
||||
subscribeToIAPListeners();
|
||||
} catch (e) {
|
||||
ToastEvent.error(e, "An error occured", "global");
|
||||
DatabaseLogger.error(error);
|
||||
|
||||
ToastEvent.error(e, "An error occurred", "global");
|
||||
}
|
||||
|
||||
user = await db.user.getUser();
|
||||
@@ -472,14 +506,15 @@ export const useAppEvents = () => {
|
||||
);
|
||||
|
||||
const subscribeToIAPListeners = useCallback(async () => {
|
||||
await RNIap.initConnection()
|
||||
.catch(() => null)
|
||||
.then(async () => {
|
||||
refValues.current.subsriptionSuccessListener =
|
||||
RNIap.purchaseUpdatedListener(onSuccessfulSubscription);
|
||||
refValues.current.subsriptionErrorListener =
|
||||
RNIap.purchaseErrorListener(onSubscriptionError);
|
||||
});
|
||||
if (Platform.OS === "android") {
|
||||
try {
|
||||
await RNIap.flushFailedPurchasesCachedAsPendingAndroid();
|
||||
} catch (e) {}
|
||||
}
|
||||
refValues.current.subsriptionSuccessListener =
|
||||
RNIap.purchaseUpdatedListener(onSuccessfulSubscription);
|
||||
refValues.current.subsriptionErrorListener =
|
||||
RNIap.purchaseErrorListener(onSubscriptionError);
|
||||
}, []);
|
||||
|
||||
const unSubscribeFromIAPListeners = () => {
|
||||
@@ -588,15 +623,20 @@ export const useAppEvents = () => {
|
||||
}, [loading, onUserUpdated]);
|
||||
|
||||
const initializeDatabase = useCallback(async () => {
|
||||
if (!db.isInitialized) {
|
||||
RNBootSplash.hide({ fade: true });
|
||||
DatabaseLogger.info("Initializing database");
|
||||
await db.init();
|
||||
try {
|
||||
if (!db.isInitialized) {
|
||||
RNBootSplash.hide({ fade: true });
|
||||
DatabaseLogger.info("Initializing database");
|
||||
await db.init();
|
||||
}
|
||||
if (IsDatabaseMigrationRequired()) return;
|
||||
initialize();
|
||||
setLoading(false);
|
||||
Walkthrough.init();
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e);
|
||||
ToastEvent.error(e, "Error initializing database", "global");
|
||||
}
|
||||
if (IsDatabaseMigrationRequired()) return;
|
||||
initialize();
|
||||
setLoading(false);
|
||||
Walkthrough.init();
|
||||
}, [IsDatabaseMigrationRequired]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { eSubscribeEvent, eUnSubscribeEvent } from "../services/event-manager";
|
||||
import { useEditorStore } from "../stores/use-editor-store";
|
||||
import { useTagStore } from "../stores/use-tag-store";
|
||||
import { db } from "../common/database";
|
||||
import { NoteType } from "app/utils/types";
|
||||
import { useCallback } from "react";
|
||||
|
||||
/**
|
||||
* A hook that injects/removes tags from tags bar in editor
|
||||
*/
|
||||
const useEditorTags = () => {
|
||||
const currentEditingNote = useEditorStore(
|
||||
(state) => state.currentEditingNote
|
||||
);
|
||||
const tags = useTagStore((state) => state.tags);
|
||||
const [note, setNote] = useState<NoteType | null>(null);
|
||||
const [noteTags, setNoteTags] = useState<string[]>([]);
|
||||
|
||||
const refreshNote = useCallback(() => {
|
||||
const current = useEditorStore.getState().currentEditingNote;
|
||||
if (!current) {
|
||||
setNote(null);
|
||||
setNoteTags([]);
|
||||
return;
|
||||
}
|
||||
const note = db.notes?.note(current)?.data as NoteType;
|
||||
setNote(note ? { ...note } : null);
|
||||
getTags(note);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
refreshNote();
|
||||
}, [currentEditingNote, refreshNote, tags]);
|
||||
|
||||
const load = useCallback(() => {
|
||||
if (!note) return;
|
||||
// tiny.call(EditorWebView, renderTags(noteTags));
|
||||
}, [note]);
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent("updateTags", load);
|
||||
return () => {
|
||||
eUnSubscribeEvent("updateTags", load);
|
||||
};
|
||||
}, [load, noteTags]);
|
||||
|
||||
function getTags(note: NoteType) {
|
||||
if (!note || !note.tags) return [];
|
||||
const tags = note.tags
|
||||
.map((t) => (db.tags?.tag(t) ? { ...db.tags.tag(t) } : null))
|
||||
.filter((t) => t !== null);
|
||||
setNoteTags(tags);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
load();
|
||||
}, [load, noteTags]);
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
export default useEditorTags;
|
||||
49
apps/mobile/app/hooks/use-group-options.ts
Normal file
49
apps/mobile/app/hooks/use-group-options.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
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 { useEffect, useState } from "react";
|
||||
import { db } from "../common/database";
|
||||
import { eSubscribeEvent, eUnSubscribeEvent } from "../services/event-manager";
|
||||
import Navigation from "../services/navigation";
|
||||
|
||||
export function useGroupOptions(type: any) {
|
||||
const [groupOptions, setGroupOptions] = useState(
|
||||
db.settings?.getGroupOptions(type)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const onUpdate = () => {
|
||||
const options = db.settings?.getGroupOptions(type) as any;
|
||||
if (
|
||||
groupOptions?.groupBy !== options.groupBy ||
|
||||
groupOptions?.sortBy !== options.sortBy ||
|
||||
groupOptions?.sortDirection !== groupOptions?.sortDirection
|
||||
) {
|
||||
setGroupOptions({ ...options });
|
||||
Navigation.queueRoutesForUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
eSubscribeEvent("groupOptionsUpdate", onUpdate);
|
||||
return () => {
|
||||
eUnSubscribeEvent("groupOptionsUpdate", onUpdate);
|
||||
};
|
||||
}, [type, groupOptions]);
|
||||
|
||||
return groupOptions;
|
||||
}
|
||||
@@ -384,7 +384,16 @@ const _TabsHolder = () => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
changeNavigationBarColor(colors.primary.background, isDark, true);
|
||||
function updateSystemBars() {
|
||||
changeNavigationBarColor(colors.primary.background, isDark, true);
|
||||
StatusBar.setBackgroundColor("transparent");
|
||||
StatusBar.setTranslucent(true);
|
||||
StatusBar.setBarStyle(isDark ? "light-content" : "dark-content");
|
||||
}
|
||||
updateSystemBars();
|
||||
setTimeout(() => {
|
||||
updateSystemBars();
|
||||
}, 1000);
|
||||
}, [colors.primary.background, isDark]);
|
||||
|
||||
return (
|
||||
@@ -406,11 +415,7 @@ const _TabsHolder = () => {
|
||||
: 0
|
||||
}}
|
||||
>
|
||||
<StatusBar
|
||||
barStyle={isDark ? "light-content" : "dark-content"}
|
||||
translucent={true}
|
||||
backgroundColor="transparent"
|
||||
/>
|
||||
<StatusBar translucent={true} backgroundColor="transparent" />
|
||||
|
||||
{!introCompleted ? (
|
||||
<NavigationStack />
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"html-to-text": "9.0.5",
|
||||
"phone": "^3.1.14",
|
||||
"qclone": "^1.2.0",
|
||||
"react-native-actions-sheet": "0.9.0-alpha.23",
|
||||
"react-native-actions-sheet": "0.9.0-alpha.24",
|
||||
"react-native-check-version": "https://github.com/flexible-agency/react-native-check-version",
|
||||
"react-native-drax": "^0.10.2",
|
||||
"react-native-image-zoom-viewer": "^3.0.1",
|
||||
@@ -31,7 +31,8 @@
|
||||
"url": "^0.11.0",
|
||||
"validator": "^13.5.2",
|
||||
"zustand": "^3.6.0",
|
||||
"katex": "0.16.2"
|
||||
"katex": "0.16.2",
|
||||
"@readme/data-urls": "3.0.0"
|
||||
},
|
||||
"sideEffects": false
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ import { EditorProps, useEditorType } from "./tiptap/types";
|
||||
import { useEditor } from "./tiptap/use-editor";
|
||||
import { useEditorEvents } from "./tiptap/use-editor-events";
|
||||
import { editorController } from "./tiptap/utils";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
|
||||
const style: ViewStyle = {
|
||||
height: "100%",
|
||||
@@ -93,35 +94,6 @@ const Editor = React.memo(
|
||||
get: () => editor
|
||||
}));
|
||||
|
||||
const onMediaDownloaded = useCallback(
|
||||
({
|
||||
hash,
|
||||
groupId,
|
||||
src,
|
||||
attachmentType
|
||||
}: {
|
||||
hash: string;
|
||||
groupId: string;
|
||||
src: string;
|
||||
attachmentType: string;
|
||||
}) => {
|
||||
if (groupId !== editor.note.current?.id) return;
|
||||
editorController.current.markImageLoaded(hash);
|
||||
if (attachmentType === "webclip") {
|
||||
editor.commands.updateWebclip({
|
||||
hash: hash,
|
||||
src: src
|
||||
});
|
||||
} else {
|
||||
editor.commands.updateImage({
|
||||
hash: hash,
|
||||
src: src
|
||||
});
|
||||
}
|
||||
},
|
||||
[editor.commands, editor.note]
|
||||
);
|
||||
|
||||
const onError = useCallback(() => {
|
||||
renderKey.current =
|
||||
renderKey.current === `editor-0` ? `editor-1` : `editor-0`;
|
||||
@@ -130,16 +102,11 @@ const Editor = React.memo(
|
||||
}, [editor]);
|
||||
|
||||
useEffect(() => {
|
||||
const sub = [
|
||||
eSubscribeEvent("webview_reset", onError),
|
||||
EV.subscribe(EVENTS.mediaAttachmentDownloaded, onMediaDownloaded)
|
||||
];
|
||||
|
||||
const sub = [eSubscribeEvent("webview_reset", onError)];
|
||||
return () => {
|
||||
sub.forEach((s) => s.unsubscribe());
|
||||
EV.unsubscribe(EVENTS.mediaAttachmentDownloaded, onMediaDownloaded);
|
||||
};
|
||||
}, [onError, onMediaDownloaded]);
|
||||
}, [onError]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
setImmediate(() => {
|
||||
@@ -167,7 +134,7 @@ const Editor = React.memo(
|
||||
globalThis.noToolbar=${noToolbar};
|
||||
globalThis.noHeader=${noHeader};
|
||||
`}
|
||||
injectedJavaScript={`globalThis.sessionId="${editor.sessionId}";`}
|
||||
useSharedProcessPool={false}
|
||||
javaScriptEnabled={true}
|
||||
focusable={true}
|
||||
onContentProcessDidTerminate={onError}
|
||||
@@ -210,6 +177,7 @@ export default Editor;
|
||||
const ReadonlyButton = ({ editor }: { editor: useEditorType }) => {
|
||||
const readonly = useEditorStore((state) => state.readonly);
|
||||
const keyboard = useKeyboard();
|
||||
const { colors } = useThemeColors();
|
||||
|
||||
const onPress = async () => {
|
||||
if (editor.note.current) {
|
||||
@@ -225,7 +193,7 @@ const ReadonlyButton = ({ editor }: { editor: useEditorType }) => {
|
||||
name="pencil-lock"
|
||||
type="grayBg"
|
||||
onPress={onPress}
|
||||
color="accent"
|
||||
color={colors.primary.accent}
|
||||
customStyle={{
|
||||
position: "absolute",
|
||||
bottom: 60,
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { ProgressBarComponent } from "../../components/ui/svg/lazy";
|
||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { SIZE } from "../../utils/size";
|
||||
export const ProgressBar = () => {
|
||||
const { colors } = useThemeColors();
|
||||
const loading = useAttachmentStore((state) => state.loading);
|
||||
const [prog, setProg] = useState(0);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const timer = useRef();
|
||||
const insets = useGlobalSafeAreaInsets();
|
||||
const [width, setWidth] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
if (loading.current !== loading.total) {
|
||||
setVisible(true);
|
||||
setProg(loading.current / loading.total);
|
||||
} else {
|
||||
clear();
|
||||
}
|
||||
} else {
|
||||
clear();
|
||||
}
|
||||
}, [loading]);
|
||||
|
||||
const clear = () => {
|
||||
clearTimeout(timer.current);
|
||||
timer.current = null;
|
||||
timer.current = setTimeout(() => {
|
||||
setProg(1);
|
||||
setTimeout(() => {
|
||||
setVisible(false);
|
||||
}, 1000);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
return visible ? (
|
||||
<View
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
position: "absolute",
|
||||
zIndex: 1,
|
||||
marginTop: insets.top + 45,
|
||||
width: "100%"
|
||||
}}
|
||||
onLayout={(event) => setWidth(event.nativeEvent.layout.width)}
|
||||
>
|
||||
<ProgressBarComponent
|
||||
size={SIZE.xxl}
|
||||
progress={prog}
|
||||
color={colors.primary.accent}
|
||||
borderWidth={0}
|
||||
height={1}
|
||||
width={width || 400}
|
||||
/>
|
||||
</View>
|
||||
) : null;
|
||||
};
|
||||
136
apps/mobile/app/screens/editor/progress.tsx
Normal file
136
apps/mobile/app/screens/editor/progress.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
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 { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { ProgressBarComponent } from "../../components/ui/svg/lazy";
|
||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { useEditorStore } from "../../stores/use-editor-store";
|
||||
|
||||
export const ProgressBar = () => {
|
||||
const { colors } = useThemeColors();
|
||||
const currentlyEditingNote = useEditorStore(
|
||||
(state) => state.currentEditingNote
|
||||
);
|
||||
const downloading = useAttachmentStore((state) => state.downloading);
|
||||
|
||||
const loading = currentlyEditingNote
|
||||
? downloading?.[currentlyEditingNote]
|
||||
: undefined;
|
||||
|
||||
const attachmentProgress = useAttachmentStore((state) => state.progress);
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const timer = useRef<NodeJS.Timeout>();
|
||||
const insets = useGlobalSafeAreaInsets();
|
||||
const [width, setWidth] = useState(400);
|
||||
|
||||
const groupProgressInfo = useRef<{
|
||||
[name: string]: {
|
||||
total: number;
|
||||
current: number;
|
||||
};
|
||||
}>({});
|
||||
|
||||
const currentItemProgress = loading?.filename
|
||||
? attachmentProgress?.[loading?.filename]
|
||||
: undefined;
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
console.log(loading);
|
||||
if (
|
||||
loading.current === loading.total &&
|
||||
typeof loading.success === "boolean"
|
||||
) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
setVisible(true);
|
||||
if (!loading.filename) return;
|
||||
|
||||
if (!groupProgressInfo.current[loading.filename]) {
|
||||
groupProgressInfo.current[loading.filename] = {
|
||||
total: 1,
|
||||
current: 0
|
||||
};
|
||||
}
|
||||
|
||||
const itemProgressCurrent = groupProgressInfo.current[loading.filename];
|
||||
|
||||
const itemTotalSize =
|
||||
currentItemProgress?.total || itemProgressCurrent.total;
|
||||
|
||||
const itemCurrentProgress =
|
||||
currentItemProgress?.recieved ||
|
||||
currentItemProgress?.sent ||
|
||||
itemProgressCurrent.current;
|
||||
|
||||
groupProgressInfo.current[loading.filename] = {
|
||||
current: itemCurrentProgress,
|
||||
total: itemTotalSize
|
||||
};
|
||||
|
||||
const itemProgressPercent = itemCurrentProgress / itemTotalSize;
|
||||
|
||||
setProgress(
|
||||
((loading.current || 0) + itemProgressPercent) / (loading.total || 1)
|
||||
);
|
||||
} else {
|
||||
clear();
|
||||
}
|
||||
}, [currentItemProgress, loading]);
|
||||
|
||||
const clear = () => {
|
||||
clearTimeout(timer.current);
|
||||
timer.current = undefined;
|
||||
timer.current = setTimeout(() => {
|
||||
setProgress(1);
|
||||
setTimeout(() => {
|
||||
setVisible(false);
|
||||
setProgress(0);
|
||||
groupProgressInfo.current = {};
|
||||
}, 1000);
|
||||
}, 0);
|
||||
};
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
position: "absolute",
|
||||
zIndex: visible ? 1 : -1,
|
||||
marginTop: insets.top + 45,
|
||||
width: "100%"
|
||||
}}
|
||||
onLayout={(event) => setWidth(event.nativeEvent.layout.width)}
|
||||
>
|
||||
<ProgressBarComponent
|
||||
progress={progress}
|
||||
color={colors.primary.accent}
|
||||
borderWidth={0}
|
||||
borderRadius={0}
|
||||
height={2}
|
||||
width={width || 400}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -17,10 +17,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import type {
|
||||
Attachment,
|
||||
AttachmentProgress
|
||||
} from "@notesnook/editor/dist/extensions/attachment/index";
|
||||
import type { Attachment } from "@notesnook/editor/dist/extensions/attachment/index";
|
||||
import type { ImageAttributes } from "@notesnook/editor/dist/extensions/image/index";
|
||||
import { createRef, RefObject } from "react";
|
||||
import { Platform } from "react-native";
|
||||
@@ -113,7 +110,7 @@ typeof globalThis.statusBar !== "undefined" && statusBar.current.set({date:"",sa
|
||||
};
|
||||
|
||||
setSessionId = async (id: string | null) =>
|
||||
await this.doAsync(`globalThis.sessionId = "${id}"`);
|
||||
await this.doAsync(`globalThis.sessionId = "${id}";`);
|
||||
|
||||
setStatus = async (date: string | undefined, saved: string) =>
|
||||
await this.doAsync(
|
||||
@@ -203,17 +200,33 @@ typeof globalThis.statusBar !== "undefined" && statusBar.current.set({date:"",sa
|
||||
);
|
||||
};
|
||||
|
||||
setAttachmentProgress = async (attachmentProgress: AttachmentProgress) => {
|
||||
setAttachmentProgress = async (attachmentProgress: Partial<Attachment>) => {
|
||||
await this.doAsync(
|
||||
`editor && editor.commands.setAttachmentProgress(${JSON.stringify(
|
||||
`editor && editor.commands.updateAttachment(${JSON.stringify(
|
||||
attachmentProgress
|
||||
)})`
|
||||
)}, {
|
||||
preventUpdate: true,
|
||||
query: (attachment) => {
|
||||
return attachment.hash === "${attachmentProgress.hash}";
|
||||
}
|
||||
})`
|
||||
);
|
||||
};
|
||||
|
||||
insertImage = async (image: ImageAttributes) => {
|
||||
insertImage = async (
|
||||
image: Omit<ImageAttributes, "bloburl"> & {
|
||||
dataurl: string;
|
||||
}
|
||||
) => {
|
||||
await this.doAsync(
|
||||
`editor && editor.commands.insertImage(${JSON.stringify(image)})`
|
||||
`const image = toBlobURL("${image.dataurl}", "${image.hash}");
|
||||
editor && editor.commands.insertImage({
|
||||
...${JSON.stringify({
|
||||
...image,
|
||||
dataurl: undefined
|
||||
})},
|
||||
bloburl: image
|
||||
})`
|
||||
);
|
||||
};
|
||||
|
||||
@@ -225,11 +238,20 @@ typeof globalThis.statusBar !== "undefined" && statusBar.current.set({date:"",sa
|
||||
);
|
||||
};
|
||||
|
||||
updateImage = async ({ src, hash }: Partial<ImageAttributes>) => {
|
||||
updateImage = async ({
|
||||
hash,
|
||||
dataurl
|
||||
}: Partial<Omit<ImageAttributes, "bloburl">> & {
|
||||
dataurl: string;
|
||||
}) => {
|
||||
await this.doAsync(
|
||||
`editor && editor.commands.updateImage(${JSON.stringify({
|
||||
`const image = toBlobURL("${dataurl}", "${hash}");
|
||||
editor && editor.commands.updateImage(${JSON.stringify({
|
||||
hash
|
||||
})},${JSON.stringify({ dataurl: src, hash, preventUpdate: true })})`
|
||||
})}, {
|
||||
...${JSON.stringify({ hash, preventUpdate: true })},
|
||||
bloburl: image
|
||||
})`
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -36,5 +36,6 @@ export const EventTypes = {
|
||||
contentchange: "editor-event:content-change",
|
||||
reminders: "editor-event:reminders",
|
||||
previewAttachment: "editor-event:preview-attachment",
|
||||
copyToClipboard: "editor-events:copy-to-clipboard"
|
||||
copyToClipboard: "editor-events:copy-to-clipboard",
|
||||
getAttachmentData: "editor-events:get-attachment-data"
|
||||
};
|
||||
|
||||
@@ -35,6 +35,7 @@ import { FILE_SIZE_LIMIT, IMAGE_SIZE_LIMIT } from "../../../utils/constants";
|
||||
import { eCloseSheet } from "../../../utils/events";
|
||||
import { editorController, editorState } from "./utils";
|
||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||
import filesystem from "../../../common/filesystem";
|
||||
|
||||
const showEncryptionSheet = (file) => {
|
||||
presentSheet({
|
||||
@@ -230,6 +231,16 @@ const handleImageResponse = async (response, options) => {
|
||||
});
|
||||
|
||||
let fileName = image.originalFileName || image.fileName;
|
||||
|
||||
editorController.current?.commands.insertImage({
|
||||
hash: hash,
|
||||
mime: image.type,
|
||||
title: fileName,
|
||||
dataurl: b64,
|
||||
size: image.fileSize,
|
||||
filename: fileName
|
||||
});
|
||||
|
||||
if (!(await attachFile(uri, hash, image.type, fileName, options))) return;
|
||||
const isPng = /(png)/g.test(image.type);
|
||||
const isJpeg = /(jpeg|jpg)/g.test(image.type);
|
||||
@@ -243,15 +254,6 @@ const handleImageResponse = async (response, options) => {
|
||||
}
|
||||
|
||||
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
|
||||
|
||||
editorController.current?.commands.insertImage({
|
||||
hash: hash,
|
||||
mime: image.type,
|
||||
title: fileName,
|
||||
dataurl: b64,
|
||||
size: image.fileSize,
|
||||
filename: fileName
|
||||
});
|
||||
};
|
||||
|
||||
export async function attachFile(uri, hash, type, filename, options) {
|
||||
@@ -269,6 +271,10 @@ export async function attachFile(uri, hash, type, filename, options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!options.reupload && exists) {
|
||||
options.reupload = (await filesystem.getUploadedFileSize(hash)) <= 0;
|
||||
}
|
||||
|
||||
if (!exists || options?.reupload) {
|
||||
let key = await db.attachments.generateKey();
|
||||
encryptionInfo = await Sodium.encryptFile(key, {
|
||||
|
||||
@@ -52,6 +52,8 @@ export type Settings = {
|
||||
fontFamily: string;
|
||||
dateFormat: string;
|
||||
timeFormat: string;
|
||||
fontScale: number;
|
||||
markdownShortcuts: boolean;
|
||||
};
|
||||
|
||||
export type EditorProps = {
|
||||
@@ -96,6 +98,7 @@ export type SavePayload = {
|
||||
type?: Content["type"];
|
||||
sessionId?: string | null;
|
||||
sessionHistoryId?: number;
|
||||
ignoreEdit: boolean;
|
||||
};
|
||||
|
||||
export type AppState = {
|
||||
|
||||
@@ -28,7 +28,8 @@ import {
|
||||
InteractionManager,
|
||||
Keyboard,
|
||||
KeyboardEventListener,
|
||||
NativeEventSubscription
|
||||
NativeEventSubscription,
|
||||
useWindowDimensions
|
||||
} from "react-native";
|
||||
import { WebViewMessageEvent } from "react-native-webview";
|
||||
import { db } from "../../../common/database";
|
||||
@@ -65,6 +66,7 @@ import { EditorMessage, EditorProps, useEditorType } from "./types";
|
||||
import { EditorEvents, editorState } from "./utils";
|
||||
import { useNoteStore } from "../../../stores/use-notes-store";
|
||||
import SettingsService from "../../../services/settings";
|
||||
import downloadAttachment from "../../../common/filesystem/download-attachment";
|
||||
|
||||
const publishNote = async (editor: useEditorType) => {
|
||||
const user = useUserStore.getState().user;
|
||||
@@ -126,6 +128,8 @@ const showActionsheet = async (editor: useEditorType) => {
|
||||
}
|
||||
};
|
||||
|
||||
type ContentMessage = { html: string; ignoreEdit: boolean };
|
||||
|
||||
export const useEditorEvents = (
|
||||
editor: useEditorType,
|
||||
{ readonly: editorPropReadonly, noHeader, noToolbar }: Partial<EditorProps>
|
||||
@@ -142,6 +146,8 @@ export const useEditorEvents = (
|
||||
const handleBack = useRef<NativeEventSubscription>();
|
||||
const readonly = useEditorStore((state) => state.readonly);
|
||||
const isPremium = useUserStore((state) => state.premium);
|
||||
const { fontScale } = useWindowDimensions();
|
||||
|
||||
const doubleSpacedLines = useSettingStore(
|
||||
(state) => state.settings?.doubleSpacedLines
|
||||
);
|
||||
@@ -151,6 +157,9 @@ export const useEditorEvents = (
|
||||
const defaultFontFamily = useSettingStore(
|
||||
(state) => state.settings.defaultFontFamily
|
||||
);
|
||||
const markdownShortcuts = useSettingStore(
|
||||
(state) => state.settings.markdownShortcuts
|
||||
);
|
||||
|
||||
const tools = useDragState((state) => state.data);
|
||||
|
||||
@@ -186,7 +195,7 @@ export const useEditorEvents = (
|
||||
readonly: readonly || editorPropReadonly,
|
||||
tools: tools || getDefaultPresets().default,
|
||||
noHeader: noHeader,
|
||||
noToolbar: readonly || editorPropReadonly || noToolbar,
|
||||
noToolbar: noToolbar,
|
||||
doubleSpacedLines: doubleSpacedLines,
|
||||
corsProxy: corsProxy,
|
||||
fontSize:
|
||||
@@ -195,13 +204,14 @@ export const useEditorEvents = (
|
||||
: defaultFontSize,
|
||||
fontFamily: SettingsService.get().defaultFontFamily,
|
||||
dateFormat: db.settings?.getDateFormat(),
|
||||
timeFormat: db.settings?.getTimeFormat()
|
||||
timeFormat: db.settings?.getTimeFormat(),
|
||||
fontScale,
|
||||
markdownShortcuts
|
||||
});
|
||||
}, [
|
||||
fullscreen,
|
||||
isPremium,
|
||||
readonly,
|
||||
editor.sessionId,
|
||||
editor.loading,
|
||||
deviceMode,
|
||||
tools,
|
||||
@@ -215,7 +225,9 @@ export const useEditorEvents = (
|
||||
defaultFontFamily,
|
||||
dateFormat,
|
||||
timeFormat,
|
||||
loading
|
||||
loading,
|
||||
fontScale,
|
||||
markdownShortcuts
|
||||
]);
|
||||
|
||||
const onBackPress = useCallback(async () => {
|
||||
@@ -224,6 +236,8 @@ export const useEditorEvents = (
|
||||
logger.info("editor handled back event", editorHandledBack);
|
||||
return;
|
||||
}
|
||||
editorState().currentlyEditing = false;
|
||||
editor.reset();
|
||||
setTimeout(async () => {
|
||||
if (deviceMode !== "mobile" && fullscreen) {
|
||||
if (fullscreen) {
|
||||
@@ -236,14 +250,10 @@ export const useEditorEvents = (
|
||||
editorState().movedAway = true;
|
||||
tabBarRef.current?.goToPage(0);
|
||||
}
|
||||
setImmediate(() => {
|
||||
useEditorStore.getState().setCurrentlyEditingNote(null);
|
||||
setTimeout(() => {
|
||||
Navigation.queueRoutesForUpdate();
|
||||
}, 500);
|
||||
});
|
||||
editorState().currentlyEditing = false;
|
||||
editor.reset();
|
||||
|
||||
setTimeout(() => {
|
||||
Navigation.queueRoutesForUpdate();
|
||||
}, 500);
|
||||
}, 1);
|
||||
}, [editor, deviceMode, fullscreen]);
|
||||
|
||||
@@ -328,19 +338,25 @@ export const useEditorEvents = (
|
||||
if (editorMessage.type === EventTypes.content) {
|
||||
editor.saveContent({
|
||||
type: editorMessage.type,
|
||||
content: editorMessage.value as string,
|
||||
content: (editorMessage.value as ContentMessage).html,
|
||||
ignoreEdit: (editorMessage.value as ContentMessage).ignoreEdit,
|
||||
forSessionId: editorMessage.sessionId
|
||||
});
|
||||
} else if (editorMessage.type === EventTypes.title) {
|
||||
editor.saveContent({
|
||||
type: editorMessage.type,
|
||||
title: editorMessage.value as string,
|
||||
forSessionId: editorMessage.sessionId
|
||||
forSessionId: editorMessage.sessionId,
|
||||
ignoreEdit: false
|
||||
});
|
||||
}
|
||||
|
||||
if (editorMessage.type === EventTypes.back) {
|
||||
return onBackPress();
|
||||
}
|
||||
|
||||
if (
|
||||
editorMessage.sessionId !== editor.sessionId &&
|
||||
editorMessage.sessionId !== editor.sessionId.current &&
|
||||
editorMessage.type !== EditorEvents.status
|
||||
) {
|
||||
return;
|
||||
@@ -410,6 +426,44 @@ export const useEditorEvents = (
|
||||
break;
|
||||
}
|
||||
|
||||
case EventTypes.getAttachmentData: {
|
||||
const attachment = (editorMessage.value as any)
|
||||
.attachment as Attachment;
|
||||
|
||||
console.log(
|
||||
"Getting attachment data:",
|
||||
attachment.hash,
|
||||
attachment.type
|
||||
);
|
||||
downloadAttachment(attachment.hash, true, {
|
||||
base64: attachment.type === "image",
|
||||
text: attachment.type === "web-clip",
|
||||
silent: true,
|
||||
groupId: editor.note.current?.id,
|
||||
cache: true
|
||||
} as any)
|
||||
.then((data: any) => {
|
||||
console.log(
|
||||
"Got attachment data:",
|
||||
!!data,
|
||||
(editorMessage.value as any).resolverId
|
||||
);
|
||||
editor.postMessage(EditorEvents.attachmentData, {
|
||||
resolverId: (editorMessage.value as any).resolverId,
|
||||
data
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
console.log("Error downloading attachment data");
|
||||
editor.postMessage(EditorEvents.attachmentData, {
|
||||
resolverId: (editorMessage.value as any).resolverId,
|
||||
data: undefined
|
||||
});
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case EventTypes.pro:
|
||||
if (editor.state.current?.isFocused) {
|
||||
editor.state.current.isFocused = true;
|
||||
@@ -426,9 +480,6 @@ export const useEditorEvents = (
|
||||
editorState().isFullscreen = true;
|
||||
eSendEvent(eOpenFullscreenEditor);
|
||||
break;
|
||||
case EventTypes.back:
|
||||
onBackPress();
|
||||
break;
|
||||
case EventTypes.link:
|
||||
openLinkInBrowser(editorMessage.value as string);
|
||||
break;
|
||||
|
||||
@@ -22,7 +22,7 @@ import { EVENTS } from "@notesnook/core/dist/common";
|
||||
import { useThemeEngineStore } from "@notesnook/theme";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import WebView from "react-native-webview";
|
||||
import { db } from "../../../common/database";
|
||||
import { DatabaseLogger, db } from "../../../common/database";
|
||||
import useGlobalSafeAreaInsets from "../../../hooks/use-global-safe-area-insets";
|
||||
import { DDS } from "../../../services/device-detection";
|
||||
import {
|
||||
@@ -63,8 +63,7 @@ export const useEditor = (
|
||||
const theme = useThemeEngineStore((state) => state.theme);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [sessionId, setSessionId] = useState<string>(makeSessionId());
|
||||
const sessionIdRef = useRef(sessionId);
|
||||
const sessionIdRef = useRef(makeSessionId());
|
||||
const editorRef = useRef<WebView>(null);
|
||||
const currentNote = useRef<NoteType | null>();
|
||||
const currentContent = useRef<Content | null>();
|
||||
@@ -79,7 +78,6 @@ export const useEditor = (
|
||||
const saveCount = useRef(0);
|
||||
const lastContentChangeTime = useRef<number>(0);
|
||||
const lock = useRef(false);
|
||||
const loadedImages = useRef<{ [name: string]: boolean }>({});
|
||||
const lockedSessionId = useRef<string>();
|
||||
const loadingState = useRef<string>();
|
||||
|
||||
@@ -99,10 +97,6 @@ export const useEditor = (
|
||||
postMessage(EditorEvents.theme, theme);
|
||||
}, [theme, postMessage]);
|
||||
|
||||
useEffect(() => {
|
||||
sessionIdRef.current = sessionId;
|
||||
}, [sessionId]);
|
||||
|
||||
useEffect(() => {
|
||||
commands.setTags(currentNote.current);
|
||||
}, [commands, tags]);
|
||||
@@ -134,20 +128,24 @@ export const useEditor = (
|
||||
|
||||
const reset = useCallback(
|
||||
async (resetState = true, resetContent = true) => {
|
||||
currentNote.current?.id && db.fs?.cancel(currentNote.current.id, null);
|
||||
currentNote.current?.id && db.fs?.cancel(currentNote.current.id);
|
||||
currentNote.current = null;
|
||||
loadedImages.current = {};
|
||||
currentContent.current = null;
|
||||
clearTimeout(timers.current["loading-images"]);
|
||||
sessionHistoryId.current = undefined;
|
||||
saveCount.current = 0;
|
||||
loadingState.current = undefined;
|
||||
lock.current = false;
|
||||
useEditorStore.getState().setReadonly(false);
|
||||
resetContent && postMessage(EditorEvents.title, "");
|
||||
lastContentChangeTime.current = 0;
|
||||
resetContent && (await commands.clearContent());
|
||||
resetContent && (await commands.clearTags());
|
||||
|
||||
if (resetState) {
|
||||
const newSessionId = makeSessionId();
|
||||
sessionIdRef.current = newSessionId;
|
||||
await commands.setSessionId(newSessionId);
|
||||
|
||||
isDefaultEditor &&
|
||||
useEditorStore.getState().setCurrentlyEditingNote(null);
|
||||
placeholderTip.current = TipManager.placeholderTip();
|
||||
@@ -164,7 +162,8 @@ export const useEditor = (
|
||||
data,
|
||||
type,
|
||||
sessionId: currentSessionId,
|
||||
sessionHistoryId: currentSessionHistoryId
|
||||
sessionHistoryId: currentSessionHistoryId,
|
||||
ignoreEdit
|
||||
}: SavePayload) => {
|
||||
if (
|
||||
readonly ||
|
||||
@@ -196,6 +195,12 @@ export const useEditor = (
|
||||
};
|
||||
|
||||
noteData.title = title;
|
||||
|
||||
if (ignoreEdit) {
|
||||
console.log("Ignoring edits...");
|
||||
noteData.dateEdited = note?.dateEdited;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
noteData.content = {
|
||||
data: data,
|
||||
@@ -264,7 +269,8 @@ export const useEditor = (
|
||||
saveCount.current++;
|
||||
return id;
|
||||
} catch (e) {
|
||||
console.log("Error saving note: ", e);
|
||||
console.error(e);
|
||||
DatabaseLogger.error(e as Error);
|
||||
}
|
||||
},
|
||||
[commands, isDefaultEditor, postMessage, readonly, reset]
|
||||
@@ -279,62 +285,11 @@ export const useEditor = (
|
||||
noteId: currentNote.current?.id as string
|
||||
};
|
||||
} else {
|
||||
if (!note.contentId) return;
|
||||
currentContent.current = await db.content?.raw(note.contentId);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const getMediaToLoad = (previousContent?: string) => {
|
||||
if (!currentNote.current?.id) return [];
|
||||
|
||||
const previousAttachments =
|
||||
previousContent?.matchAll(/data-hash="(.+?)"/gm) || [];
|
||||
const attachments =
|
||||
currentContent.current?.data?.matchAll(/data-hash="(.+?)"/gm) || [];
|
||||
|
||||
const media: string[] = [];
|
||||
|
||||
const oldMatches = Array.from(previousAttachments).map((match) => match[1]);
|
||||
const matches = Array.from(attachments).map((match) => match[1]);
|
||||
|
||||
for (let i = 0; i < matches.length; i++) {
|
||||
const currentHash = matches[i];
|
||||
const oldHash = oldMatches[i];
|
||||
if (currentHash !== oldHash) {
|
||||
media.push(currentHash);
|
||||
loadedImages.current[currentHash] = false;
|
||||
}
|
||||
}
|
||||
return media;
|
||||
};
|
||||
|
||||
const markImageLoaded = (hash: string) => {
|
||||
const attachment = loadedImages.current[hash];
|
||||
if (typeof attachment === "boolean") {
|
||||
loadedImages.current[hash] = true;
|
||||
}
|
||||
};
|
||||
|
||||
const loadImages = useCallback((previousContent?: string) => {
|
||||
if (!currentNote.current?.id) return;
|
||||
const timerId = "loading-images";
|
||||
clearTimeout(timers.current[timerId]);
|
||||
timers.current[timerId] = setTimeout(() => {
|
||||
if (!currentNote.current?.id) return;
|
||||
if (currentNote.current?.content?.isPreview) {
|
||||
db.content?.downloadMedia(
|
||||
currentNote.current?.id,
|
||||
currentNote.current.content,
|
||||
true
|
||||
);
|
||||
} else {
|
||||
const media = getMediaToLoad(previousContent);
|
||||
if (media.length > 0) {
|
||||
db.attachments?.downloadMedia(currentNote.current?.id, media);
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
}, []);
|
||||
|
||||
const loadNote = useCallback(
|
||||
async (
|
||||
item: Omit<NoteType, "type"> & {
|
||||
@@ -355,7 +310,6 @@ export const useEditor = (
|
||||
if (item && item.type === "new") {
|
||||
currentNote.current && (await reset());
|
||||
const nextSessionId = makeSessionId(item as NoteType);
|
||||
setSessionId(nextSessionId);
|
||||
sessionIdRef.current = nextSessionId;
|
||||
sessionHistoryId.current = Date.now();
|
||||
await commands.setSessionId(nextSessionId);
|
||||
@@ -366,12 +320,23 @@ export const useEditor = (
|
||||
if (!item.forced && currentNote.current?.id === item.id) return;
|
||||
state.current.movedAway = false;
|
||||
state.current.currentlyEditing = true;
|
||||
isDefaultEditor && editorState.setCurrentlyEditingNote(item.id);
|
||||
currentNote.current && (await reset(false, false));
|
||||
|
||||
if (currentNote.current?.id !== item.id) {
|
||||
currentNote.current && (await reset(false, false));
|
||||
isDefaultEditor && editorState.setCurrentlyEditingNote(item.id);
|
||||
}
|
||||
|
||||
await loadContent(item as NoteType);
|
||||
if (loadingState.current === currentContent.current?.data) {
|
||||
|
||||
if (
|
||||
currentNote.current?.id === item.id &&
|
||||
loadingState.current &&
|
||||
currentContent.current?.data &&
|
||||
loadingState.current === currentContent.current?.data
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!currentContent.current?.data ||
|
||||
currentContent.current?.data.length < 50000
|
||||
@@ -386,20 +351,21 @@ export const useEditor = (
|
||||
}
|
||||
lastContentChangeTime.current = item.dateEdited;
|
||||
const nextSessionId = makeSessionId(item as NoteType);
|
||||
sessionIdRef.current = nextSessionId;
|
||||
lockedSessionId.current = nextSessionId;
|
||||
sessionHistoryId.current = Date.now();
|
||||
setSessionId(nextSessionId);
|
||||
commands.setSessionId(nextSessionId);
|
||||
sessionIdRef.current = nextSessionId;
|
||||
await commands.setSessionId(nextSessionId);
|
||||
currentNote.current = item as NoteType;
|
||||
await commands.setStatus(getFormattedDate(item.dateEdited), "Saved");
|
||||
await postMessage(EditorEvents.title, item.title);
|
||||
loadingState.current = currentContent.current?.data;
|
||||
|
||||
await postMessage(
|
||||
EditorEvents.html,
|
||||
currentContent.current?.data,
|
||||
currentContent.current?.data || "",
|
||||
10000
|
||||
);
|
||||
|
||||
loadingState.current = undefined;
|
||||
useEditorStore.getState().setReadonly(item.readonly);
|
||||
await commands.setTags(currentNote.current);
|
||||
@@ -410,18 +376,9 @@ export const useEditor = (
|
||||
}
|
||||
}, 300);
|
||||
overlay(false);
|
||||
loadImages();
|
||||
}
|
||||
},
|
||||
[
|
||||
commands,
|
||||
isDefaultEditor,
|
||||
loadContent,
|
||||
loadImages,
|
||||
overlay,
|
||||
postMessage,
|
||||
reset
|
||||
]
|
||||
[commands, isDefaultEditor, loadContent, overlay, postMessage, reset]
|
||||
);
|
||||
|
||||
const lockNoteWithVault = useCallback((note: NoteType) => {
|
||||
@@ -451,8 +408,6 @@ export const useEditor = (
|
||||
|
||||
lock.current = true;
|
||||
|
||||
const previousContent = currentContent.current?.data;
|
||||
|
||||
if (data.type === "tiptap") {
|
||||
if (!currentNote.current.locked && isContentEncrypted) {
|
||||
lockNoteWithVault(note);
|
||||
@@ -485,18 +440,8 @@ export const useEditor = (
|
||||
}
|
||||
|
||||
lock.current = false;
|
||||
if (data.type === "tiptap") {
|
||||
loadImages(previousContent);
|
||||
db.eventManager.subscribe(
|
||||
EVENTS.syncCompleted,
|
||||
() => {
|
||||
loadImages(previousContent);
|
||||
},
|
||||
true
|
||||
);
|
||||
}
|
||||
},
|
||||
[loadImages, lockNoteWithVault, postMessage, commands]
|
||||
[lockNoteWithVault, postMessage, commands]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -516,15 +461,25 @@ export const useEditor = (
|
||||
title,
|
||||
content,
|
||||
type,
|
||||
forSessionId
|
||||
forSessionId,
|
||||
ignoreEdit
|
||||
}: {
|
||||
title?: string;
|
||||
content?: string;
|
||||
type: string;
|
||||
forSessionId: string;
|
||||
ignoreEdit: boolean;
|
||||
}) => {
|
||||
if (lock.current || lockedSessionId.current === forSessionId) return;
|
||||
lastContentChangeTime.current = Date.now();
|
||||
|
||||
if (
|
||||
sessionHistoryId.current &&
|
||||
Date.now() - sessionHistoryId.current > 5 * 60 * 1000
|
||||
) {
|
||||
sessionHistoryId.current = Date.now();
|
||||
}
|
||||
|
||||
if (type === EditorEvents.content) {
|
||||
currentContent.current = {
|
||||
data: content,
|
||||
@@ -544,7 +499,8 @@ export const useEditor = (
|
||||
type: "tiptap",
|
||||
sessionId: forSessionId,
|
||||
id: noteId,
|
||||
sessionHistoryId: sessionHistoryId.current
|
||||
sessionHistoryId: sessionHistoryId.current,
|
||||
ignoreEdit
|
||||
};
|
||||
withTimer(
|
||||
noteId || "newnote",
|
||||
@@ -562,7 +518,7 @@ export const useEditor = (
|
||||
}
|
||||
saveNote(params);
|
||||
},
|
||||
150
|
||||
ignoreEdit ? 0 : 150
|
||||
);
|
||||
},
|
||||
[withTimer, onChange, saveNote]
|
||||
@@ -608,7 +564,7 @@ export const useEditor = (
|
||||
|
||||
useEffect(() => {
|
||||
state.current.saveCount = 0;
|
||||
}, [sessionId, loading]);
|
||||
}, [loading]);
|
||||
|
||||
const onReady = useCallback(async () => {
|
||||
if (!(await isEditorLoaded(editorRef, sessionIdRef.current))) {
|
||||
@@ -660,14 +616,12 @@ export const useEditor = (
|
||||
loading,
|
||||
setLoading,
|
||||
state,
|
||||
sessionId,
|
||||
setSessionId,
|
||||
sessionId: sessionIdRef,
|
||||
note: currentNote,
|
||||
onReady,
|
||||
saveContent,
|
||||
onContentChanged,
|
||||
editorId: editorId,
|
||||
markImageLoaded,
|
||||
overlay,
|
||||
postMessage
|
||||
};
|
||||
|
||||
@@ -50,7 +50,8 @@ export const EditorEvents: { [name: string]: string } = {
|
||||
titleplaceholder: "native:titleplaceholder",
|
||||
logger: "native:logger",
|
||||
status: "native:status",
|
||||
keyboardShown: "native:keyboardShown"
|
||||
keyboardShown: "native:keyboardShown",
|
||||
attachmentData: "native:attachment-data"
|
||||
};
|
||||
|
||||
export function randId(prefix: string) {
|
||||
|
||||
@@ -115,7 +115,7 @@ export const EditorWrapper = ({ width }) => {
|
||||
style={{ height: 1, padding: 0, width: 1, position: "absolute" }}
|
||||
blurOnSubmit={false}
|
||||
/>
|
||||
<ProgressBar />
|
||||
{/* <ProgressBar /> */}
|
||||
<Editor key="editor" withController={true} />
|
||||
</KeyboardAvoidingViewIOS>
|
||||
)}
|
||||
|
||||
@@ -71,7 +71,7 @@ export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
|
||||
<List
|
||||
listData={notes}
|
||||
type="notes"
|
||||
screen="Notes"
|
||||
screen="Home"
|
||||
loading={loading || !isFocused}
|
||||
headerProps={{
|
||||
heading: "Notes"
|
||||
|
||||
@@ -71,7 +71,7 @@ export const SearchBar = () => {
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
ToastEvent.show({
|
||||
heading: "Error occured while searching",
|
||||
heading: "Error occurred while searching",
|
||||
message: e.message,
|
||||
type: "error"
|
||||
});
|
||||
|
||||
@@ -69,10 +69,10 @@ export const TrashIntervalPicker = createSettingsPicker({
|
||||
db.settings.setTrashCleanupInterval(item);
|
||||
},
|
||||
formatValue: (item) => {
|
||||
return item === -1 ? "Never" : item + " days";
|
||||
return item === -1 ? "Never" : item === 1 ? "Daily" : item + " days";
|
||||
},
|
||||
getItemKey: (item) => item.toString(),
|
||||
options: [-1, 7, 30, 365],
|
||||
options: [-1, 1, 7, 30, 365],
|
||||
compareValue: (current, item) => current === item,
|
||||
premium: true
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user