mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-29 18:19:38 +02:00
Compare commits
357 Commits
v2.6.3
...
fix-github
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e68dd129e | ||
|
|
2350d0e41d | ||
|
|
fbd5c8e3cd | ||
|
|
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 | ||
|
|
14d6714209 | ||
|
|
e8e50bebcf |
28
.github/workflows/android.publish.yml
vendored
28
.github/workflows/android.publish.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Publish @notesnook/mobile
|
||||
name: Publish @notesnook/android
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
@@ -28,30 +28,6 @@ jobs:
|
||||
- name: Make Gradlew Executable
|
||||
run: cd apps/mobile/native/android && chmod +x ./gradlew
|
||||
|
||||
- name: Build unsigned app bundle
|
||||
run: yarn release:android:bundle
|
||||
|
||||
- name: Sign app bundle for Playstore release
|
||||
id: sign_app
|
||||
uses: r0adkll/sign-android-release@v1
|
||||
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 }}
|
||||
|
||||
- name: Publish to Playstore
|
||||
id: deploy
|
||||
uses: r0adkll/upload-google-play@v1.1.1
|
||||
with:
|
||||
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
|
||||
packageName: com.streetwriters.notesnook
|
||||
releaseFiles: ${{steps.sign_app.outputs.signedReleaseFile}}
|
||||
track: production
|
||||
status: completed
|
||||
whatsNewDirectory: apps/mobile/native/android/releasenotes/
|
||||
|
||||
- name: Build apks for Github release
|
||||
run: yarn release:android
|
||||
|
||||
@@ -64,6 +40,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.15",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "2.6.3",
|
||||
"version": "2.6.15",
|
||||
"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.15",
|
||||
"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,6 +46,8 @@ 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);
|
||||
@@ -79,6 +83,7 @@ export const osIntegrationRouter = t.router({
|
||||
AutoLaunch.disable();
|
||||
}
|
||||
config.desktopSettings = settings;
|
||||
setupDesktopIntegration(settings);
|
||||
}),
|
||||
|
||||
selectDirectory: t.procedure
|
||||
@@ -117,6 +122,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 }) => {
|
||||
|
||||
@@ -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,8 +107,7 @@ async function createWindow() {
|
||||
}
|
||||
|
||||
await AssetManager.loadIcons();
|
||||
setupDesktopIntegration();
|
||||
createIPCHandler({ router, windows: [mainWindow] });
|
||||
setupDesktopIntegration(config.desktopSettings);
|
||||
|
||||
mainWindow.webContents.session.setSpellCheckerDictionaryDownloadURL(
|
||||
"http://dictionaries.notesnook.com/"
|
||||
@@ -146,8 +148,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 +177,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),
|
||||
|
||||
@@ -35,6 +35,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 +49,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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
/>
|
||||
)
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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,12 @@ export const AttachmentDialog = ({ note }) => {
|
||||
}
|
||||
];
|
||||
|
||||
const filterAttachments = (type) => {
|
||||
const attachments = note
|
||||
? db.attachments.ofNote(note.id, "all")
|
||||
: [...(db.attachments.all || [])];
|
||||
isDocument;
|
||||
const filterAttachments = (type, _attachments) => {
|
||||
const attachments =
|
||||
_attachments || note
|
||||
? db.attachments.ofNote(note.id, "all")
|
||||
: [...(db.attachments.all || [])];
|
||||
|
||||
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 ? (
|
||||
|
||||
@@ -46,6 +46,7 @@ import Input from "../ui/input";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { LoginSteps, useLogin } from "./use-login";
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
|
||||
function getObfuscatedEmail(email) {
|
||||
if (!email) return "";
|
||||
@@ -68,7 +69,8 @@ export const SessionExpired = () => {
|
||||
eSendEvent(eUserLoggedIn, true);
|
||||
setVisible(false);
|
||||
setFocused(false);
|
||||
}
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
const logout = async () => {
|
||||
@@ -130,15 +132,20 @@ export const SessionExpired = () => {
|
||||
|
||||
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);
|
||||
}}
|
||||
enableSheetKeyboardHandler={true}
|
||||
visible={true}
|
||||
>
|
||||
<SheetProvider context="two_factor_verify" />
|
||||
<View
|
||||
style={{
|
||||
width: focused ? "100%" : "99.9%",
|
||||
@@ -231,7 +238,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%",
|
||||
|
||||
@@ -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={{
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,9 @@ 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 DocumentPicker, {
|
||||
DocumentPickerResponse
|
||||
} from "react-native-document-picker";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import { db } from "../../../common/database";
|
||||
import storage from "../../../common/database/storage";
|
||||
@@ -45,6 +47,8 @@ import Seperator from "../../ui/seperator";
|
||||
import SheetWrapper from "../../ui/sheet";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { getFormattedDate } from "@notesnook/common";
|
||||
import { unzip } from "react-native-zip-archive";
|
||||
import { cacheDir, copyFileAsync } from "../../../common/filesystem/utils";
|
||||
|
||||
const RestoreDataSheet = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
@@ -129,75 +133,71 @@ 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"
|
||||
});
|
||||
return false;
|
||||
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 to restore it",
|
||||
positiveText: "Restore",
|
||||
secureTextEntry: true,
|
||||
onClose: () => {
|
||||
if (resolved) return;
|
||||
resolve(undefined);
|
||||
},
|
||||
negativeText: "Cancel",
|
||||
positivePress: async (password) => {
|
||||
resolve(password);
|
||||
resolved = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -217,21 +217,164 @@ 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) => {
|
||||
await db.backup.import(backup, password);
|
||||
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;
|
||||
|
||||
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");
|
||||
password = await withPassword();
|
||||
if (!password) throw new Error("Failed to decrypt backup");
|
||||
}
|
||||
await db.backup.import(parsed, password);
|
||||
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 password = await withPassword();
|
||||
if (password) {
|
||||
try {
|
||||
await restoreBackup(backup, password);
|
||||
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 +414,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>
|
||||
|
||||
@@ -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(e);
|
||||
|
||||
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,7 @@ export type Settings = {
|
||||
fontFamily: string;
|
||||
dateFormat: string;
|
||||
timeFormat: string;
|
||||
fontScale: number;
|
||||
};
|
||||
|
||||
export type EditorProps = {
|
||||
@@ -96,6 +97,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
|
||||
);
|
||||
@@ -186,7 +192,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 +201,13 @@ export const useEditorEvents = (
|
||||
: defaultFontSize,
|
||||
fontFamily: SettingsService.get().defaultFontFamily,
|
||||
dateFormat: db.settings?.getDateFormat(),
|
||||
timeFormat: db.settings?.getTimeFormat()
|
||||
timeFormat: db.settings?.getTimeFormat(),
|
||||
fontScale
|
||||
});
|
||||
}, [
|
||||
fullscreen,
|
||||
isPremium,
|
||||
readonly,
|
||||
editor.sessionId,
|
||||
editor.loading,
|
||||
deviceMode,
|
||||
tools,
|
||||
@@ -215,7 +221,8 @@ export const useEditorEvents = (
|
||||
defaultFontFamily,
|
||||
dateFormat,
|
||||
timeFormat,
|
||||
loading
|
||||
loading,
|
||||
fontScale
|
||||
]);
|
||||
|
||||
const onBackPress = useCallback(async () => {
|
||||
@@ -224,6 +231,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 +245,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 +333,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 +421,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 +475,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
|
||||
});
|
||||
|
||||
@@ -320,13 +320,13 @@ export const settingsGroups: SettingSection[] = [
|
||||
name: "Delete account",
|
||||
icon: "alert",
|
||||
description:
|
||||
"All your data will be removed permanantly. Make sure you have saved backup of your notes. This action is IRREVERSIBLE.",
|
||||
"All your data will be removed permanently. Make sure you have saved backup of your notes. This action is IRREVERSIBLE.",
|
||||
modifer: () => {
|
||||
presentDialog({
|
||||
title: "Delete account",
|
||||
paragraphColor: "red",
|
||||
paragraph:
|
||||
"All your data will be removed permanantly. Make sure you have saved backup of your notes. This action is IRREVERSIBLE.",
|
||||
"All your data will be removed permanently. Make sure you have saved backup of your notes. This action is IRREVERSIBLE.",
|
||||
positiveType: "errorShade",
|
||||
input: true,
|
||||
inputPlaceholder: "Enter account password",
|
||||
@@ -628,7 +628,9 @@ export const settingsGroups: SettingSection[] = [
|
||||
name: "CORS bypass proxy",
|
||||
description: "You can set a custom proxy URL to increase your privacy.",
|
||||
inputProperties: {
|
||||
defaultValue: "https://cors.notesnook.com"
|
||||
defaultValue: "https://cors.notesnook.com",
|
||||
autoCorrect: false,
|
||||
keyboardType: "url"
|
||||
},
|
||||
property: "corsProxy",
|
||||
icon: "arrow-decision-outline"
|
||||
@@ -890,16 +892,6 @@ export const settingsGroups: SettingSection[] = [
|
||||
name: "Restore backup",
|
||||
description: "Restore backup from phone storage.",
|
||||
modifer: () => {
|
||||
const user = useUserStore.getState().user;
|
||||
if (!user || !user?.email) {
|
||||
ToastEvent.show({
|
||||
heading: "Login required",
|
||||
message: "Please log in to your account to restore backup",
|
||||
type: "error",
|
||||
context: "global"
|
||||
});
|
||||
return;
|
||||
}
|
||||
eSendEvent(eOpenRestoreDialog);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -42,6 +42,9 @@ export const Subscription = () => {
|
||||
user?.subscription?.type !== SUBSCRIPTION_STATUS.PREMIUM &&
|
||||
user?.subscription?.type !== SUBSCRIPTION_STATUS.BETA;
|
||||
|
||||
const hasCancelledPremium =
|
||||
SUBSCRIPTION_STATUS.PREMIUM_CANCELLED === user?.subscription?.type;
|
||||
|
||||
const subscriptionProviderInfo =
|
||||
SUBSCRIPTION_PROVIDER[user?.subscription?.provider];
|
||||
|
||||
@@ -63,10 +66,7 @@ export const Subscription = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
user?.subscription?.type === SUBSCRIPTION_STATUS.PREMIUM_CANCELLED &&
|
||||
Platform.OS === "android"
|
||||
) {
|
||||
if (hasCancelledPremium && Platform.OS === "android") {
|
||||
if (user.subscription?.provider === 3) {
|
||||
ToastEvent.show({
|
||||
heading: "Subscribed on web",
|
||||
@@ -88,6 +88,13 @@ export const Subscription = () => {
|
||||
}
|
||||
};
|
||||
|
||||
function getPrice() {
|
||||
return Platform.OS === "android"
|
||||
? monthlyPlan?.product?.subscriptionOfferDetails[0].pricingPhases
|
||||
.pricingPhaseList?.[0]?.formattedPrice
|
||||
: monthlyPlan?.product?.localizedPrice;
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
{isNotPro ? (
|
||||
@@ -104,22 +111,16 @@ export const Subscription = () => {
|
||||
title={
|
||||
!user?.isEmailConfirmed
|
||||
? "Confirm your email"
|
||||
: user.subscription?.provider === 3 &&
|
||||
user.subscription?.type ===
|
||||
SUBSCRIPTION_STATUS.PREMIUM_CANCELLED
|
||||
: user.subscription?.provider === 3 && hasCancelledPremium
|
||||
? "Manage subscription from desktop app"
|
||||
: user.subscription?.type ===
|
||||
SUBSCRIPTION_STATUS.PREMIUM_CANCELLED &&
|
||||
: hasCancelledPremium &&
|
||||
Platform.OS === "android" &&
|
||||
Config.GITHUB_RELEASE !== "true"
|
||||
? "Resubscribe from Google Playstore"
|
||||
: user.subscription?.type === SUBSCRIPTION_STATUS.PREMIUM_EXPIRED
|
||||
? `Resubscribe to Pro (${
|
||||
monthlyPlan?.product?.localizedPrice || "$4.49"
|
||||
} / mo)`
|
||||
: `Get Pro (${
|
||||
monthlyPlan?.product?.localizedPrice || "$4.49"
|
||||
} / mo)`
|
||||
: user.subscription?.type ===
|
||||
SUBSCRIPTION_STATUS.PREMIUM_EXPIRED || hasCancelledPremium
|
||||
? `Resubscribe to Pro (${getPrice() || "$4.49"} / mo)`
|
||||
: `Get Pro (${getPrice() || "$4.49"} / mo)`
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -52,7 +52,7 @@ const onPressFloatingButton = () => {
|
||||
ToastEvent.show({
|
||||
heading: "Trash cleared",
|
||||
message:
|
||||
"All notes and notebooks in the trash have been removed permanantly.",
|
||||
"All notes and notebooks in the trash have been removed permanently.",
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
@@ -65,7 +65,11 @@ const PLACEHOLDER_DATA = (trashCleanupInterval = 7) => ({
|
||||
paragraph:
|
||||
trashCleanupInterval === -1
|
||||
? "Set automatic trash cleanup interval from Settings > Behaviour > Clean trash interval."
|
||||
: `Items in the trash will be permanently deleted after after ${trashCleanupInterval} days.`,
|
||||
: `Trash gets automatically cleaned up ${
|
||||
trashCleanupInterval === 1
|
||||
? "daily."
|
||||
: `after ${trashCleanupInterval} days.`
|
||||
}`,
|
||||
button: null,
|
||||
loading: "Loading trash items"
|
||||
});
|
||||
|
||||
@@ -17,7 +17,6 @@ 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 { sanitizeFilename } from "@notesnook/common";
|
||||
import { Platform } from "react-native";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import FileViewer from "react-native-file-viewer";
|
||||
@@ -30,6 +29,8 @@ import { eCloseSheet } from "../utils/events";
|
||||
import { sleep } from "../utils/time";
|
||||
import { ToastEvent, eSendEvent, presentSheet } from "./event-manager";
|
||||
import SettingsService from "./settings";
|
||||
import { cacheDir, copyFileAsync } from "../common/filesystem/utils";
|
||||
import { zip } from "react-native-zip-archive";
|
||||
|
||||
const MS_DAY = 86400000;
|
||||
const MS_WEEK = MS_DAY * 7;
|
||||
@@ -152,8 +153,6 @@ async function run(progress, context) {
|
||||
let androidBackupDirectory = await checkBackupDirExists(false, context);
|
||||
if (!androidBackupDirectory) return;
|
||||
|
||||
let backup;
|
||||
|
||||
if (progress) {
|
||||
presentSheet({
|
||||
title: "Backing up your data",
|
||||
@@ -163,43 +162,67 @@ async function run(progress, context) {
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
backup = await db.backup.export(
|
||||
"mobile",
|
||||
SettingsService.get().encryptedBackup
|
||||
);
|
||||
if (!backup) throw new Error("Backup returned empty.");
|
||||
} catch (e) {
|
||||
await sleep(300);
|
||||
eSendEvent(eCloseSheet);
|
||||
ToastEvent.error(e, "Backup failed!");
|
||||
return null;
|
||||
let path;
|
||||
let backupFilePath;
|
||||
let backupFileName = "notesnook_backup_" + Date.now();
|
||||
|
||||
if (Platform.OS === "ios") {
|
||||
path = await storage.checkAndCreateDir("/backups/");
|
||||
}
|
||||
|
||||
try {
|
||||
let backupName = "notesnook_backup_" + Date.now();
|
||||
backupName =
|
||||
sanitizeFilename(backupName, { replacement: "_" }) + ".nnbackup";
|
||||
let path;
|
||||
let backupFilePath;
|
||||
const zipSourceFolder = `${cacheDir}/${backupFileName}`;
|
||||
const zipOutputFile =
|
||||
Platform.OS === "ios"
|
||||
? `${path}/${backupFileName}.nnbackupz`
|
||||
: `${cacheDir}/${backupFileName}.nnbackupz`;
|
||||
|
||||
if (Platform.OS === "ios") {
|
||||
path = await storage.checkAndCreateDir("/backups/");
|
||||
await RNFetchBlob.fs.writeFile(path + backupName, backup, "utf8");
|
||||
backupFilePath = path + backupName;
|
||||
} else {
|
||||
backupFilePath = await ScopedStorage.writeFile(
|
||||
androidBackupDirectory.uri,
|
||||
backup,
|
||||
backupName,
|
||||
"nnbackup/json",
|
||||
"utf8",
|
||||
false
|
||||
if (await RNFetchBlob.fs.exists(zipSourceFolder))
|
||||
await RNFetchBlob.fs.unlink(zipSourceFolder);
|
||||
|
||||
await RNFetchBlob.fs.mkdir(zipSourceFolder);
|
||||
|
||||
try {
|
||||
for await (const file of db.backup.export(
|
||||
"mobile",
|
||||
SettingsService.get().encryptedBackup
|
||||
)) {
|
||||
console.log("Writing backup chunk of size...", file?.data?.length);
|
||||
await RNFetchBlob.fs.writeFile(
|
||||
`${zipSourceFolder}/${file.path}`,
|
||||
file.data,
|
||||
"utf8"
|
||||
);
|
||||
}
|
||||
|
||||
await zip(zipSourceFolder, zipOutputFile);
|
||||
|
||||
console.log("Final zip:", await RNFetchBlob.fs.stat(zipOutputFile));
|
||||
|
||||
if (Platform.OS === "android") {
|
||||
// Move the zip to user selected directory.
|
||||
const file = await ScopedStorage.createFile(
|
||||
androidBackupDirectory.uri,
|
||||
`${backupFileName}.nnbackupz`,
|
||||
"application/nnbackupz"
|
||||
);
|
||||
await copyFileAsync(`file://${zipOutputFile}`, file.uri);
|
||||
path = file.uri;
|
||||
} else {
|
||||
path = zipOutputFile;
|
||||
}
|
||||
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
|
||||
updateNextBackupTime();
|
||||
|
||||
let showBackupCompleteSheet = SettingsService.get().showBackupCompleteSheet;
|
||||
|
||||
if (context) return path;
|
||||
await sleep(300);
|
||||
if (showBackupCompleteSheet) {
|
||||
presentBackupCompleteSheet(backupFilePath);
|
||||
} else {
|
||||
progress && eSendEvent(eCloseSheet);
|
||||
}
|
||||
|
||||
ToastEvent.show({
|
||||
heading: "Backup successful",
|
||||
message: "Your backup is stored in Notesnook folder on your phone.",
|
||||
@@ -207,18 +230,10 @@ async function run(progress, context) {
|
||||
context: "global"
|
||||
});
|
||||
|
||||
let showBackupCompleteSheet = SettingsService.get().showBackupCompleteSheet;
|
||||
|
||||
if (context) return backupFilePath;
|
||||
await sleep(300);
|
||||
if (showBackupCompleteSheet) {
|
||||
presentBackupCompleteSheet(backupFilePath);
|
||||
} else {
|
||||
progress && eSendEvent(eCloseSheet);
|
||||
}
|
||||
return backupFilePath;
|
||||
return path;
|
||||
} catch (e) {
|
||||
progress && eSendEvent(eCloseSheet);
|
||||
await sleep(300);
|
||||
eSendEvent(eCloseSheet);
|
||||
ToastEvent.error(e, "Backup failed!");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -18,17 +18,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { decode, EntityLevel } from "entities";
|
||||
import { zipSync } from "fflate";
|
||||
import { Platform } from "react-native";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import RNHTMLtoPDF from "react-native-html-to-pdf-lite";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import { zip } from "react-native-zip-archive";
|
||||
import { DatabaseLogger, db } from "../common/database/index";
|
||||
import Storage from "../common/database/storage";
|
||||
import { convertNoteToText } from "../utils/note-to-text";
|
||||
|
||||
import { sleep } from "../utils/time";
|
||||
import { sanitizeFilename } from "@notesnook/common";
|
||||
import { presentDialog } from "../components/dialog/functions";
|
||||
import { useSettingStore } from "../stores/use-setting-store";
|
||||
import BiometicService from "./biometrics";
|
||||
import { ToastEvent } from "./event-manager";
|
||||
|
||||
const MIMETypes = {
|
||||
txt: "text/plain",
|
||||
@@ -99,8 +101,8 @@ async function save(path, data, fileName, extension) {
|
||||
return uri || path;
|
||||
}
|
||||
|
||||
async function makeHtml(note) {
|
||||
let html = await db.notes.note(note.id).export("html");
|
||||
async function makeHtml(note, content) {
|
||||
let html = await db.notes.note(note.id).export("html", content);
|
||||
html = decode(html, {
|
||||
level: EntityLevel.HTML
|
||||
});
|
||||
@@ -109,22 +111,27 @@ async function makeHtml(note) {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {"txt" | "pdf" | "md" | "html"} type
|
||||
* @param {"txt" | "pdf" | "md" | "html" | "md-frontmatter"} type
|
||||
*/
|
||||
async function exportAs(type, note, bulk) {
|
||||
async function exportAs(type, note, bulk, content) {
|
||||
let data;
|
||||
switch (type) {
|
||||
case "html":
|
||||
{
|
||||
data = await makeHtml(note);
|
||||
data = await makeHtml(note, content);
|
||||
}
|
||||
break;
|
||||
case "md":
|
||||
data = await db.notes.note(note.id).export("md");
|
||||
data = await db.notes.note(note.id).export("md", content);
|
||||
break;
|
||||
case "md-frontmatter":
|
||||
data = await db.notes
|
||||
.note(note.id)
|
||||
.export("md-frontmatter", content?.data);
|
||||
break;
|
||||
case "pdf":
|
||||
{
|
||||
let html = await makeHtml(note);
|
||||
let html = await makeHtml(note, content);
|
||||
let fileName = sanitizeFilename(note.title + Date.now(), {
|
||||
replacement: "_"
|
||||
});
|
||||
@@ -150,21 +157,84 @@ async function exportAs(type, note, bulk) {
|
||||
}
|
||||
break;
|
||||
case "txt":
|
||||
data = await convertNoteToText(note);
|
||||
{
|
||||
data = await db.notes?.note(note.id).export("txt", content);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async function unlockVault() {
|
||||
let biometry = await BiometicService.isBiometryAvailable();
|
||||
let fingerprint = await BiometicService.hasInternetCredentials("nn_vault");
|
||||
if (biometry && fingerprint) {
|
||||
let credentials = await BiometicService.getCredentials(
|
||||
"Unlock vault",
|
||||
"Unlock vault to export locked notes"
|
||||
);
|
||||
if (credentials) {
|
||||
return db.vault.unlock(credentials.password);
|
||||
}
|
||||
}
|
||||
useSettingStore.getState().setSheetKeyboardHandler(false);
|
||||
return new Promise((resolve) => {
|
||||
setImmediate(() => {
|
||||
presentDialog({
|
||||
context: "export-notes",
|
||||
input: true,
|
||||
secureTextEntry: true,
|
||||
positiveText: "Unlock",
|
||||
title: "Unlock vault",
|
||||
paragraph: "Some exported notes are locked, Unlock to export them",
|
||||
inputPlaceholder: "Enter password",
|
||||
positivePress: async (value) => {
|
||||
const unlocked = await db.vault.unlock(value);
|
||||
if (!unlocked) {
|
||||
ToastEvent.show({
|
||||
heading: "Invalid password",
|
||||
message: "Please enter a valid password",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
return false;
|
||||
}
|
||||
resolve(unlocked);
|
||||
useSettingStore.getState().setSheetKeyboardHandler(true);
|
||||
return true;
|
||||
},
|
||||
onClose: () => {
|
||||
resolve(false);
|
||||
useSettingStore.getState().setSheetKeyboardHandler(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {"txt" | "pdf" | "md" | "html"} type
|
||||
* @param {"txt" | "pdf" | "md" | "html" | "md-frontmatter"} type
|
||||
*/
|
||||
async function exportNote(note, type) {
|
||||
let content;
|
||||
|
||||
if (note.locked) {
|
||||
try {
|
||||
let unlocked = await unlockVault();
|
||||
if (!unlocked) return null;
|
||||
const unlockedNote = await db.vault.open(note.id);
|
||||
content = unlockedNote.content;
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
let path = await getPath(FolderNames[type]);
|
||||
if (!path) return;
|
||||
let result = await exportAs(type, note);
|
||||
|
||||
let result = await exportAs(type, note, false, content);
|
||||
if (!result) return null;
|
||||
let fileName = sanitizeFilename(note.title + Date.now(), {
|
||||
replacement: "_"
|
||||
@@ -180,7 +250,7 @@ async function exportNote(note, type) {
|
||||
filePath: path,
|
||||
type: "text/plain",
|
||||
name: "Text",
|
||||
fileName: fileName + `.${type}`
|
||||
fileName: fileName + `.${type === "md-frontmatter" ? "md" : type}`
|
||||
};
|
||||
}
|
||||
|
||||
@@ -192,11 +262,6 @@ function copyFileAsync(source, dest) {
|
||||
});
|
||||
}
|
||||
|
||||
function zipsync(results) {
|
||||
let data = zipSync(results);
|
||||
return Buffer.from(data.buffer).toString("base64");
|
||||
}
|
||||
|
||||
function getUniqueFileName(fileName, results) {
|
||||
const chunks = fileName.split(".");
|
||||
const ext = chunks.pop();
|
||||
@@ -212,25 +277,114 @@ function getUniqueFileName(fileName, results) {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {"txt" | "pdf" | "md" | "html"} type
|
||||
* @param {"txt" | "pdf" | "md" | "html" | "md-frontmatter"} type
|
||||
*/
|
||||
async function bulkExport(notes, type, callback) {
|
||||
let path = await getPath(FolderNames[type]);
|
||||
if (!path) return;
|
||||
|
||||
const exportCacheFolder =
|
||||
RNFetchBlob.fs.dirs.CacheDir + `/export_${Date.now()}`;
|
||||
|
||||
await RNFetchBlob.fs.mkdir(exportCacheFolder).catch((e) => console.log(e));
|
||||
|
||||
const mkdir = async (dir) => {
|
||||
const folder = `${exportCacheFolder}/${dir}`;
|
||||
if (!(await RNFetchBlob.fs.exists(folder))) {
|
||||
await RNFetchBlob.fs.mkdir(folder);
|
||||
}
|
||||
};
|
||||
|
||||
const writeFile = async (path, result) => {
|
||||
const cacheFilePath = exportCacheFolder + path;
|
||||
await RNFetchBlob.fs.writeFile(
|
||||
cacheFilePath,
|
||||
result,
|
||||
type === "pdf" ? "base64" : "utf8"
|
||||
);
|
||||
};
|
||||
|
||||
const results = {};
|
||||
for (var i = 0; i < notes.length; i++) {
|
||||
try {
|
||||
await sleep(1);
|
||||
let note = notes[i];
|
||||
if (note.locked) continue;
|
||||
let result = await exportAs(type, note);
|
||||
let content;
|
||||
if (note.locked) {
|
||||
try {
|
||||
let unlocked = !db.vault.unlocked ? await unlockVault() : true;
|
||||
if (!unlocked) {
|
||||
continue;
|
||||
}
|
||||
const unlockedNote = await db.vault.open(note.id);
|
||||
content = unlockedNote.content;
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let result = await exportAs(type, note, true, content);
|
||||
let fileName = sanitizeFilename(note.title, {
|
||||
replacement: "_"
|
||||
});
|
||||
if (result) {
|
||||
results[getUniqueFileName(fileName + `.${type}`, results)] =
|
||||
Buffer.from(result, type === "pdf" ? "base64" : "utf-8");
|
||||
const notebooks = [
|
||||
...(db.relations
|
||||
?.to({ id: note.id, type: "note" }, "notebook")
|
||||
.map((notebook) => ({
|
||||
title: notebook.title
|
||||
})) || []),
|
||||
...(note.notebooks || []).map((ref) => {
|
||||
const notebook = db.notebooks?.notebook(ref.id);
|
||||
const topics = notebook?.topics.all || [];
|
||||
|
||||
return {
|
||||
title: notebook?.title,
|
||||
topics: ref.topics
|
||||
.map((topicId) => topics.find((topic) => topic.id === topicId))
|
||||
.filter(Boolean)
|
||||
};
|
||||
})
|
||||
];
|
||||
|
||||
for (const notebook of notebooks) {
|
||||
results[notebook.title] = results[notebook.title] || {};
|
||||
await mkdir(notebook.title);
|
||||
|
||||
if (notebook.topics && notebook.topics.length) {
|
||||
for (const topic of notebook.topics) {
|
||||
results[notebook.title][topic.title] =
|
||||
results[notebook.title][topic.title] || {};
|
||||
|
||||
await mkdir(`${notebook.title}/${topic.title}`);
|
||||
const exportedNoteName = getUniqueFileName(
|
||||
fileName + `.${type}`,
|
||||
results[notebook.title][topic.title]
|
||||
);
|
||||
results[notebook.title][topic.title][exportedNoteName] = true;
|
||||
|
||||
writeFile(
|
||||
`/${notebook.title}/${topic.title}/${exportedNoteName}`,
|
||||
result
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const exportedNoteName = getUniqueFileName(
|
||||
fileName + `.${type}`,
|
||||
results[notebook.title]
|
||||
);
|
||||
results[notebook.title][exportedNoteName] = true;
|
||||
writeFile(`/${notebook.title}/${exportedNoteName}`, result);
|
||||
}
|
||||
}
|
||||
|
||||
if (!notebooks.length) {
|
||||
const exportedNoteName = getUniqueFileName(
|
||||
fileName + `.${type}`,
|
||||
results
|
||||
);
|
||||
results[exportedNoteName] = true;
|
||||
writeFile(`/${exportedNoteName}`, result);
|
||||
}
|
||||
}
|
||||
callback(`${i + 1}/${notes.length}`);
|
||||
} catch (e) {
|
||||
@@ -241,26 +395,24 @@ async function bulkExport(notes, type, callback) {
|
||||
|
||||
try {
|
||||
callback("zipping");
|
||||
await sleep(1);
|
||||
const zipped = zipsync(results);
|
||||
const zipOutputPath =
|
||||
Platform.OS === "ios"
|
||||
? path + `/${fileName}`
|
||||
: RNFetchBlob.fs.dirs.CacheDir + `/${fileName}`;
|
||||
await zip(exportCacheFolder, zipOutputPath);
|
||||
callback("saving to disk");
|
||||
await sleep(1);
|
||||
|
||||
if (Platform.OS === "ios") {
|
||||
RNFetchBlob.fs.writeFile(path + `/${fileName}`, zipped, "base64");
|
||||
} else {
|
||||
const templFile = RNFetchBlob.fs.dirs.CacheDir + `/${fileName}`;
|
||||
await RNFetchBlob.fs.writeFile(templFile, zipped, "base64");
|
||||
if (Platform.OS === "android") {
|
||||
const file = await ScopedStorage.createFile(
|
||||
path,
|
||||
fileName,
|
||||
"application/zip"
|
||||
);
|
||||
path = file.uri;
|
||||
await copyFileAsync("file://" + templFile, path);
|
||||
await RNFetchBlob.fs.unlink(templFile);
|
||||
await copyFileAsync("file://" + zipOutputPath, path);
|
||||
await RNFetchBlob.fs.unlink(zipOutputPath);
|
||||
callback();
|
||||
}
|
||||
RNFetchBlob.fs.unlink(exportCacheFolder);
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ export function setRateAppMessage() {
|
||||
|
||||
const recoveryKeyMessage = {
|
||||
visible: true,
|
||||
message: "Keep your data safe if you lose password",
|
||||
message: "Keep your data safe",
|
||||
actionText: "Save your account recovery key",
|
||||
onPress: () => {
|
||||
verifyUser(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user