mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-09-01 11:39:21 +02:00
Compare commits
267 Commits
fix-loadin
...
fix-task-i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2bbccf19e | ||
|
|
375dd18618 | ||
|
|
82f635305e | ||
|
|
00345176f7 | ||
|
|
9032e3b4bd | ||
|
|
432a9c4e84 | ||
|
|
aaaa9ebbc5 | ||
|
|
1ac4dca6b5 | ||
|
|
9dc549d0d3 | ||
|
|
775570fc13 | ||
|
|
997f0bc93e | ||
|
|
a2016e9406 | ||
|
|
87623aced1 | ||
|
|
24977277d0 | ||
|
|
2f890fd86b | ||
|
|
0220bb4040 | ||
|
|
09a8a0ba6d | ||
|
|
808670f6e1 | ||
|
|
62ab799a4a | ||
|
|
ecee87dac9 | ||
|
|
a5d52ded77 | ||
|
|
4e786a83f9 | ||
|
|
bf48ce788d | ||
|
|
f6637da274 | ||
|
|
6cd4a4a041 | ||
|
|
02ece7139e | ||
|
|
95203d723b | ||
|
|
b423cef2e6 | ||
|
|
e46b3ea85f | ||
|
|
0019c1f7af | ||
|
|
ffc4e2d263 | ||
|
|
c01ad2e49c | ||
|
|
9ff235e767 | ||
|
|
9b6f7ff225 | ||
|
|
99f97ac565 | ||
|
|
7e598098fe | ||
|
|
02afbf6c90 | ||
|
|
b464fafc18 | ||
|
|
e06320108b | ||
|
|
a377b13e2b | ||
|
|
4728212531 | ||
|
|
71fc2a3cac | ||
|
|
303282624e | ||
|
|
2ca02937df | ||
|
|
00b62df2e8 | ||
|
|
6d377ded9a | ||
|
|
087c404225 | ||
|
|
4932763468 | ||
|
|
abcc19d5da | ||
|
|
f1ce1e9b60 | ||
|
|
5a6f99a41b | ||
|
|
1862802052 | ||
|
|
1adddbdb4c | ||
|
|
15f4f2accb | ||
|
|
725f3c3522 | ||
|
|
efa73a1c32 | ||
|
|
00250c6669 | ||
|
|
d11afda1a8 | ||
|
|
1350784c33 | ||
|
|
1852a216a2 | ||
|
|
422c2492c4 | ||
|
|
c9c72741b3 | ||
|
|
99dbcaff7d | ||
|
|
8d83f5474d | ||
|
|
a912a1a22e | ||
|
|
ea7afbe93e | ||
|
|
7b2a8c3e20 | ||
|
|
911adfc151 | ||
|
|
23f710856e | ||
|
|
7f6274821c | ||
|
|
4700843517 | ||
|
|
8823054227 | ||
|
|
7c63a3e004 | ||
|
|
2a59154268 | ||
|
|
ed15d53484 | ||
|
|
fc8a4bb55d | ||
|
|
dd1c1bb11d | ||
|
|
3ffbad5727 | ||
|
|
207ae39d13 | ||
|
|
ae141ac9c5 | ||
|
|
40f058b14b | ||
|
|
865c9f3e32 | ||
|
|
26df9bfdd8 | ||
|
|
1e52e01fb8 | ||
|
|
5fece5faaa | ||
|
|
d7fdaf6c46 | ||
|
|
8e1faf9f60 | ||
|
|
9dce39b32d | ||
|
|
80a0b75887 | ||
|
|
c9704e1031 | ||
|
|
31e32cd154 | ||
|
|
61c296fc37 | ||
|
|
c8d38235b2 | ||
|
|
b717a64607 | ||
|
|
0f2b1d3e14 | ||
|
|
4abefe63db | ||
|
|
fc31b2a8d7 | ||
|
|
2708bac4fb | ||
|
|
a47967dd53 | ||
|
|
53ba5562d3 | ||
|
|
347df32943 | ||
|
|
ac8c63526b | ||
|
|
a48bd360ca | ||
|
|
f476969e9e | ||
|
|
c45539dea6 | ||
|
|
d8f5815b84 | ||
|
|
2eaf07118a | ||
|
|
b636b09a96 | ||
|
|
c16bbcaad5 | ||
|
|
49ffcbea03 | ||
|
|
f772cf0f14 | ||
|
|
3f1406a05b | ||
|
|
21760b2b74 | ||
|
|
d8011f0239 | ||
|
|
b9a8b054e2 | ||
|
|
2ab321869e | ||
|
|
fcb45a24d6 | ||
|
|
7cdb13def6 | ||
|
|
2ab58f9203 | ||
|
|
df74448e17 | ||
|
|
ada98fca93 | ||
|
|
1d3650659f | ||
|
|
9b43af475b | ||
|
|
d9c592c7fb | ||
|
|
f10ac9fbb5 | ||
|
|
b7334c09d4 | ||
|
|
0cd3bd5739 | ||
|
|
33d69480eb | ||
|
|
5cae4eb0d9 | ||
|
|
840c7fda5e | ||
|
|
4ce24ac8fd | ||
|
|
ae9e80e12e | ||
|
|
43356e5b6a | ||
|
|
7b13f35424 | ||
|
|
0517c6ce41 | ||
|
|
54d06328c1 | ||
|
|
c74677acd4 | ||
|
|
7689e1a7c5 | ||
|
|
5ffae2a08b | ||
|
|
5fa80abdc8 | ||
|
|
a30598fedf | ||
|
|
48ffecfb5f | ||
|
|
89fe712104 | ||
|
|
5dd298ee86 | ||
|
|
275b285a8b | ||
|
|
96310a46e9 | ||
|
|
42f9bf1593 | ||
|
|
25aab6913a | ||
|
|
3bd7da68be | ||
|
|
a218738eff | ||
|
|
8bf283b3bc | ||
|
|
cb09430ff4 | ||
|
|
66d75492bb | ||
|
|
d772d38dc7 | ||
|
|
46a74f9512 | ||
|
|
7df1037e3f | ||
|
|
5c3136d07d | ||
|
|
6a9ddb770e | ||
|
|
f4c54fd27f | ||
|
|
05b12df7d7 | ||
|
|
dcf053eede | ||
|
|
5430966b02 | ||
|
|
7271140627 | ||
|
|
eb5ae0773b | ||
|
|
9661dd0c5e | ||
|
|
8e70860918 | ||
|
|
3540ecf4ab | ||
|
|
f1dba9ffc2 | ||
|
|
3f1761a540 | ||
|
|
a25d21038a | ||
|
|
d55a6a6fc0 | ||
|
|
e5cf902780 | ||
|
|
a99996fcf6 | ||
|
|
0cc9c31bc3 | ||
|
|
62da8484ad | ||
|
|
821b8eebaa | ||
|
|
269941c297 | ||
|
|
e37ff88f1f | ||
|
|
1a1b4bc43c | ||
|
|
2c59df9fb7 | ||
|
|
4fc30cc224 | ||
|
|
9bd7d392af | ||
|
|
3bcd288bc7 | ||
|
|
3b86e51c49 | ||
|
|
c286b1d595 | ||
|
|
2f6b1e6bcc | ||
|
|
abb918e179 | ||
|
|
dd0dc09977 | ||
|
|
211ab47165 | ||
|
|
bc44e28e1e | ||
|
|
ade60e4272 | ||
|
|
3f5bfcb499 | ||
|
|
782a979397 | ||
|
|
e7f1e2d533 | ||
|
|
12e5f2acc1 | ||
|
|
dccc79ace3 | ||
|
|
a9a5ba09ff | ||
|
|
a6c8bbbf0a | ||
|
|
e19e9eb96f | ||
|
|
9208140b97 | ||
|
|
8241bb8ae5 | ||
|
|
329c2e220f | ||
|
|
140e343289 | ||
|
|
0d99367a93 | ||
|
|
b802b3e165 | ||
|
|
46583e12d9 | ||
|
|
c9a3f31e8f | ||
|
|
53f68aaf81 | ||
|
|
f31df9a6cc | ||
|
|
c812581bb9 | ||
|
|
1e6e940f17 | ||
|
|
9ab670d933 | ||
|
|
c46e57418b | ||
|
|
83557401f5 | ||
|
|
2277644e89 | ||
|
|
5eb09a1d3e | ||
|
|
d957be7a7b | ||
|
|
44ddb49d0e | ||
|
|
24fcd5cd0c | ||
|
|
2a34225d49 | ||
|
|
e81c825fc7 | ||
|
|
c80286b587 | ||
|
|
e85f8b60b0 | ||
|
|
5daa99d969 | ||
|
|
073bb576b5 | ||
|
|
eaeac130df | ||
|
|
b21519c24e | ||
|
|
4f9fe9b1e0 | ||
|
|
f657380ec7 | ||
|
|
05b2e559d8 | ||
|
|
ab21f02c2d | ||
|
|
12b51ed72a | ||
|
|
b672fd540c | ||
|
|
95a20b3740 | ||
|
|
6f5a7a4923 | ||
|
|
1b4d98ac0e | ||
|
|
ef2ed2bba0 | ||
|
|
abf4612977 | ||
|
|
9c6637908d | ||
|
|
e1006ed501 | ||
|
|
7d034f7f16 | ||
|
|
76eec5f8bb | ||
|
|
698866f53d | ||
|
|
21e1e73bee | ||
|
|
52e89c7842 | ||
|
|
8b357559fa | ||
|
|
2be35fadff | ||
|
|
5fe366f4f3 | ||
|
|
a13cbadd44 | ||
|
|
aa993a2fce | ||
|
|
975af5701b | ||
|
|
3add56cfe4 | ||
|
|
522ea1520d | ||
|
|
c680121eb0 | ||
|
|
a9de0696c9 | ||
|
|
4a4c52e4c8 | ||
|
|
92f5e04788 | ||
|
|
0cad098551 | ||
|
|
46105b2175 | ||
|
|
8ffbda9626 | ||
|
|
1fc55a69c9 | ||
|
|
c417d4df91 | ||
|
|
43ccf72773 | ||
|
|
a177d36571 | ||
|
|
4428ccd3a0 | ||
|
|
218ffbd758 | ||
|
|
5ace95c9d1 |
@@ -14,6 +14,8 @@ runs:
|
|||||||
apps/mobile/package-lock.json
|
apps/mobile/package-lock.json
|
||||||
apps/desktop/package-lock.json
|
apps/desktop/package-lock.json
|
||||||
apps/web/package-lock.json
|
apps/web/package-lock.json
|
||||||
|
apps/monograph/package-lock.json
|
||||||
|
apps/theme-builder/package-lock.json
|
||||||
extensions/web-clipper/package-lock.json
|
extensions/web-clipper/package-lock.json
|
||||||
packages/core/package-lock.json
|
packages/core/package-lock.json
|
||||||
packages/crypto/package-lock.json
|
packages/crypto/package-lock.json
|
||||||
@@ -24,3 +26,4 @@ runs:
|
|||||||
packages/logger/package-lock.json
|
packages/logger/package-lock.json
|
||||||
packages/streamable-fs/package-lock.json
|
packages/streamable-fs/package-lock.json
|
||||||
packages/theme/package-lock.json
|
packages/theme/package-lock.json
|
||||||
|
packages/intl/package-lock.json
|
||||||
|
|||||||
2
.github/workflows/android.publish.yml
vendored
2
.github/workflows/android.publish.yml
vendored
@@ -4,7 +4,7 @@ on: workflow_dispatch
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
timeout-minutes: 40
|
timeout-minutes: 40
|
||||||
env:
|
env:
|
||||||
CMAKE_C_COMPILER_LAUNCHER: ccache
|
CMAKE_C_COMPILER_LAUNCHER: ccache
|
||||||
|
|||||||
2
.github/workflows/core.tests.yml
vendored
2
.github/workflows/core.tests.yml
vendored
@@ -18,7 +18,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|||||||
50
.github/workflows/desktop.publish.yml
vendored
50
.github/workflows/desktop.publish.yml
vendored
@@ -33,11 +33,19 @@ on:
|
|||||||
required: true
|
required: true
|
||||||
default: true
|
default: true
|
||||||
description: "Build for macOS?"
|
description: "Build for macOS?"
|
||||||
|
release-track:
|
||||||
|
type: choice
|
||||||
|
required: true
|
||||||
|
default: stable
|
||||||
|
description: "Select the release track"
|
||||||
|
options:
|
||||||
|
- stable
|
||||||
|
- beta
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -49,14 +57,30 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||||
npm run bootstrap -- --scope=web
|
npm run bootstrap -- --scope=web
|
||||||
|
npm run bootstrap -- --scope=desktop
|
||||||
|
|
||||||
|
- name: Install sqlite-better-trigram for all arch
|
||||||
|
run: |
|
||||||
|
npm i --cpu arm64 sqlite-better-trigram
|
||||||
|
npm i --cpu x64 sqlite-better-trigram
|
||||||
|
working-directory: ./apps/desktop
|
||||||
|
|
||||||
- name: Setup environment
|
- name: Setup environment
|
||||||
run: |
|
run: |
|
||||||
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
|
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Generate desktop build
|
- name: Generate desktop build (stable)
|
||||||
|
if: ${{ inputs.release-track == 'stable' }}
|
||||||
run: npx nx build:desktop @notesnook/web
|
run: npx nx build:desktop @notesnook/web
|
||||||
|
|
||||||
|
- name: Generate desktop build (beta)
|
||||||
|
if: ${{ inputs.release-track == 'beta' }}
|
||||||
|
run: BETA=true npx nx build:desktop @notesnook/web
|
||||||
|
|
||||||
|
- name: Build desktop bundle
|
||||||
|
working-directory: ./apps/desktop
|
||||||
|
run: npm run bundle
|
||||||
|
|
||||||
- name: Archive build artifact
|
- name: Archive build artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
@@ -71,7 +95,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Package desktop build
|
- name: Package desktop build
|
||||||
run: |
|
run: |
|
||||||
zip -r ./notesnook_build_v${{ steps.app_metadata.outputs.app_version }}.zip ./apps/web/build/
|
cp -r ./apps/web/build ./apps/desktop/
|
||||||
|
zip -r ./notesnook_build_v${{ steps.app_metadata.outputs.app_version }}.zip ./apps/desktop/build/
|
||||||
echo "Build folder archived to ./notesnook_build_v${{ steps.app_metadata.outputs.app_version }}.zip"
|
echo "Build folder archived to ./notesnook_build_v${{ steps.app_metadata.outputs.app_version }}.zip"
|
||||||
|
|
||||||
- name: Upload desktop build
|
- name: Upload desktop build
|
||||||
@@ -87,7 +112,8 @@ jobs:
|
|||||||
- name: Generate flatpak sources
|
- name: Generate flatpak sources
|
||||||
if: inputs.publish-github && inputs.build-linux
|
if: inputs.publish-github && inputs.build-linux
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get install -y flatpak flatpak-builder pipx
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y --quiet flatpak flatpak-builder pipx
|
||||||
git clone https://github.com/flatpak/flatpak-builder-tools.git /tmp/flatpak-builder-tools
|
git clone https://github.com/flatpak/flatpak-builder-tools.git /tmp/flatpak-builder-tools
|
||||||
cd /tmp/flatpak-builder-tools/node
|
cd /tmp/flatpak-builder-tools/node
|
||||||
pipx install .
|
pipx install .
|
||||||
@@ -173,7 +199,7 @@ jobs:
|
|||||||
CSC_KEY_PASSWORD: ${{ secrets.mac_certs_password }}
|
CSC_KEY_PASSWORD: ${{ secrets.mac_certs_password }}
|
||||||
run: |
|
run: |
|
||||||
npx nx run release --project @notesnook/desktop -- --variant=mas
|
npx nx run release --project @notesnook/desktop -- --variant=mas
|
||||||
yarn electron-builder --mac mas --universal -p never
|
yarn electron-builder --config=electron-builder.config.js --mac mas --universal -p never
|
||||||
working-directory: ./apps/desktop
|
working-directory: ./apps/desktop
|
||||||
|
|
||||||
- name: Build zip and dmg
|
- name: Build zip and dmg
|
||||||
@@ -187,9 +213,9 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
npx nx run release --project @notesnook/desktop
|
npx nx run release --project @notesnook/desktop
|
||||||
if [ ${{ inputs.publish-github }} == true ]; then
|
if [ ${{ inputs.publish-github }} == true ]; then
|
||||||
yarn electron-builder --mac zip dmg --arm64 --x64 -p always
|
yarn electron-builder --config=electron-builder.config.js --mac zip dmg --arm64 --x64 -p always
|
||||||
else
|
else
|
||||||
yarn electron-builder --mac zip dmg --arm64 --x64 -p never
|
yarn electron-builder --config=electron-builder.config.js --mac zip dmg --arm64 --x64 -p never
|
||||||
fi
|
fi
|
||||||
working-directory: ./apps/desktop
|
working-directory: ./apps/desktop
|
||||||
|
|
||||||
@@ -251,7 +277,7 @@ jobs:
|
|||||||
- name: Build snap
|
- name: Build snap
|
||||||
if: inputs.publish-snap
|
if: inputs.publish-snap
|
||||||
run: |
|
run: |
|
||||||
yarn electron-builder --linux snap:x64 -p never
|
yarn electron-builder --config=electron-builder.config.js --linux snap:x64 -p never
|
||||||
working-directory: ./apps/desktop
|
working-directory: ./apps/desktop
|
||||||
|
|
||||||
- name: Build AppImage
|
- name: Build AppImage
|
||||||
@@ -259,9 +285,9 @@ jobs:
|
|||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
if [ ${{ inputs.publish-github }} == true ]; then
|
if [ ${{ inputs.publish-github }} == true ]; then
|
||||||
yarn electron-builder --linux AppImage:x64 AppImage:arm64 -p always
|
yarn electron-builder --config=electron-builder.config.js --linux AppImage:x64 AppImage:arm64 -p always
|
||||||
else
|
else
|
||||||
yarn electron-builder --linux AppImage:x64 AppImage:arm64 -p never
|
yarn electron-builder --config=electron-builder.config.js --linux AppImage:x64 AppImage:arm64 -p never
|
||||||
fi
|
fi
|
||||||
working-directory: ./apps/desktop
|
working-directory: ./apps/desktop
|
||||||
|
|
||||||
@@ -321,8 +347,8 @@ jobs:
|
|||||||
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
|
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
|
||||||
run: |
|
run: |
|
||||||
if ($${{ inputs.publish-github }} -eq $true) {
|
if ($${{ inputs.publish-github }} -eq $true) {
|
||||||
yarn electron-builder --win --publish always
|
yarn electron-builder --config=electron-builder.config.js --win --publish always
|
||||||
} else {
|
} else {
|
||||||
yarn electron-builder --win --publish never
|
yarn electron-builder --config=electron-builder.config.js --win --publish never
|
||||||
}
|
}
|
||||||
working-directory: ./apps/desktop
|
working-directory: ./apps/desktop
|
||||||
|
|||||||
24
.github/workflows/desktop.tests.yml
vendored
24
.github/workflows/desktop.tests.yml
vendored
@@ -14,7 +14,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -69,12 +69,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Build app
|
- name: Build app
|
||||||
run: |
|
run: |
|
||||||
yarn electron-builder --mac --dir --x64
|
yarn electron-builder --config=electron-builder.config.js --mac --dir --x64
|
||||||
working-directory: ./apps/desktop
|
working-directory: ./apps/desktop
|
||||||
|
|
||||||
- name: Run tests x64
|
- name: Run tests x64
|
||||||
run: |
|
run: npm run test
|
||||||
EXECUTABLE_PATH=output/mac/Notesnook.app/Contents/MacOS/Notesnook npm run test
|
|
||||||
working-directory: ./apps/desktop
|
working-directory: ./apps/desktop
|
||||||
|
|
||||||
- name: Upload test results
|
- name: Upload test results
|
||||||
@@ -114,12 +113,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Build app
|
- name: Build app
|
||||||
run: |
|
run: |
|
||||||
yarn electron-builder --mac --dir --arm64
|
yarn electron-builder --config=electron-builder.config.js --mac --dir --arm64
|
||||||
working-directory: ./apps/desktop
|
working-directory: ./apps/desktop
|
||||||
|
|
||||||
- name: Run tests arm64
|
- name: Run tests arm64
|
||||||
run: |
|
run: npm run test
|
||||||
EXECUTABLE_PATH=output/mac-arm64/Notesnook.app/Contents/MacOS/Notesnook npm run test
|
|
||||||
working-directory: ./apps/desktop
|
working-directory: ./apps/desktop
|
||||||
|
|
||||||
- name: Upload test results
|
- name: Upload test results
|
||||||
@@ -133,7 +131,7 @@ jobs:
|
|||||||
test-linux:
|
test-linux:
|
||||||
name: Test for Linux
|
name: Test for Linux
|
||||||
needs: build
|
needs: build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out Git repository
|
- name: Check out Git repository
|
||||||
@@ -165,12 +163,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Build app
|
- name: Build app
|
||||||
run: |
|
run: |
|
||||||
yarn electron-builder --linux --dir --arm64 --x64
|
yarn electron-builder --config=electron-builder.config.js --linux --dir --arm64 --x64
|
||||||
working-directory: ./apps/desktop
|
working-directory: ./apps/desktop
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: |
|
run: xvfb-run --auto-servernum --server-args="-screen 0 1920x1080x24" -- npm run test
|
||||||
EXECUTABLE_PATH=output/linux-unpacked/notesnook xvfb-run --auto-servernum --server-args="-screen 0 1920x1080x24" -- npm run test
|
|
||||||
working-directory: ./apps/desktop
|
working-directory: ./apps/desktop
|
||||||
|
|
||||||
- name: Upload test results
|
- name: Upload test results
|
||||||
@@ -216,12 +213,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Build app
|
- name: Build app
|
||||||
run: |
|
run: |
|
||||||
npx cross-env NOTESNOOK_STAGING=true yarn electron-builder --win --dir --arm64 --x64
|
npx cross-env NOTESNOOK_STAGING=true yarn electron-builder --config=electron-builder.config.js --win --dir --arm64 --x64
|
||||||
working-directory: ./apps/desktop
|
working-directory: ./apps/desktop
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: |
|
run: npm run test
|
||||||
npx cross-env EXECUTABLE_PATH=output/win-unpacked/Notesnook.exe npm run test
|
|
||||||
working-directory: ./apps/desktop
|
working-directory: ./apps/desktop
|
||||||
|
|
||||||
- name: Upload test results
|
- name: Upload test results
|
||||||
|
|||||||
2
.github/workflows/editor.tests.yml
vendored
2
.github/workflows/editor.tests.yml
vendored
@@ -19,7 +19,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|||||||
2
.github/workflows/help.publish.yml
vendored
2
.github/workflows/help.publish.yml
vendored
@@ -11,7 +11,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|||||||
2
.github/workflows/monograph.publish.yml
vendored
2
.github/workflows/monograph.publish.yml
vendored
@@ -14,7 +14,7 @@ on: workflow_dispatch
|
|||||||
jobs:
|
jobs:
|
||||||
push_to_registry:
|
push_to_registry:
|
||||||
name: Push Docker image to Docker Hub
|
name: Push Docker image to Docker Hub
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
permissions:
|
permissions:
|
||||||
packages: write
|
packages: write
|
||||||
contents: read
|
contents: read
|
||||||
|
|||||||
2
.github/workflows/theme-builder.publish.yml
vendored
2
.github/workflows/theme-builder.publish.yml
vendored
@@ -5,7 +5,7 @@ on: workflow_dispatch
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|||||||
2
.github/workflows/vericrypt.publish.yml
vendored
2
.github/workflows/vericrypt.publish.yml
vendored
@@ -5,7 +5,7 @@ on: workflow_dispatch
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|||||||
2
.github/workflows/web.benchmarks.yml
vendored
2
.github/workflows/web.benchmarks.yml
vendored
@@ -14,7 +14,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
bench:
|
bench:
|
||||||
name: Bench
|
name: Bench
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|||||||
32
.github/workflows/web.publish.yml
vendored
32
.github/workflows/web.publish.yml
vendored
@@ -1,11 +1,21 @@
|
|||||||
name: Publish @notesnook/web
|
name: Publish @notesnook/web
|
||||||
|
|
||||||
on: workflow_dispatch
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
release-track:
|
||||||
|
type: choice
|
||||||
|
required: true
|
||||||
|
default: stable
|
||||||
|
description: "Select the release track"
|
||||||
|
options:
|
||||||
|
- stable
|
||||||
|
- beta
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -24,8 +34,20 @@ jobs:
|
|||||||
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
|
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
|
||||||
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV
|
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Generate build
|
- name: Generate build (stable)
|
||||||
|
if: ${{ inputs.release-track == 'stable' }}
|
||||||
run: npm run build:web
|
run: npm run build:web
|
||||||
|
|
||||||
- name: Publish to Cloudflare Pages
|
- name: Publish to Cloudflare Pages (stable)
|
||||||
run: npx --yes wrangler pages deploy --project-name=notesnook-app ./apps/web/build/
|
if: ${{ inputs.release-track == 'stable' }}
|
||||||
|
working-directory: ./apps/web
|
||||||
|
run: npx --yes wrangler pages deploy --project-name=notesnook-app ./build/
|
||||||
|
|
||||||
|
- name: Generate build (beta)
|
||||||
|
if: ${{ inputs.release-track == 'beta' }}
|
||||||
|
run: npm run build:beta:web
|
||||||
|
|
||||||
|
- name: Publish to Cloudflare Pages (beta)
|
||||||
|
if: ${{ inputs.release-track == 'beta' }}
|
||||||
|
working-directory: ./apps/web
|
||||||
|
run: npx --yes wrangler pages deploy --branch=beta --project-name=notesnook-app-beta ./build/
|
||||||
|
|||||||
4
.github/workflows/web.tests.yml
vendored
4
.github/workflows/web.tests.yml
vendored
@@ -19,7 +19,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -49,7 +49,7 @@ jobs:
|
|||||||
name: 🧪 Test (${{ matrix.shard }}/${{ strategy.job-total }})
|
name: 🧪 Test (${{ matrix.shard }}/${{ strategy.job-total }})
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
shard: [1, 2, 3, 4]
|
shard: [1, 2, 3, 4, 5]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,3 +11,4 @@ nx-cloud.env
|
|||||||
site
|
site
|
||||||
node_modules.backup
|
node_modules.backup
|
||||||
.nx
|
.nx
|
||||||
|
*.patch
|
||||||
|
|||||||
4
apps/desktop/.gitignore
vendored
4
apps/desktop/.gitignore
vendored
@@ -2,4 +2,6 @@ node_modules
|
|||||||
build
|
build
|
||||||
output
|
output
|
||||||
dist
|
dist
|
||||||
_catalog
|
_catalog
|
||||||
|
test-results
|
||||||
|
test-artifacts
|
||||||
|
|||||||
@@ -58,13 +58,13 @@ This will compile and run the app in production mode but it won't generate any p
|
|||||||
npm run release -- --rebuild
|
npm run release -- --rebuild
|
||||||
|
|
||||||
# For macOS
|
# For macOS
|
||||||
npx electron-builder --mac dmg --arm64 --x64 -publish never
|
npx electron-builder --config=electron-builder.config.js --mac dmg --arm64 --x64 -publish never
|
||||||
|
|
||||||
# For Linux (AppImage)
|
# For Linux (AppImage)
|
||||||
npx electron-builder --linux AppImage:x64 AppImage:arm64 -publish never
|
npx electron-builder --config=electron-builder.config.js --linux AppImage:x64 AppImage:arm64 -publish never
|
||||||
|
|
||||||
# For Windows
|
# For Windows
|
||||||
npx electron-builder --win --publish never
|
npx electron-builder --config=electron-builder.config.js --win --publish never
|
||||||
```
|
```
|
||||||
|
|
||||||
Feel free to play around with the `electron-builder` command to get the packages you need. `npx electron-builder --help` is a great resource to learn different commands & platforms supported by `electron-builder`.
|
Feel free to play around with the `electron-builder` command to get the packages you need. `npx electron-builder --help` is a great resource to learn different commands & platforms supported by `electron-builder`.
|
||||||
|
|||||||
246
apps/desktop/__tests__/auto-updates.test.ts
Normal file
246
apps/desktop/__tests__/auto-updates.test.ts
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
/*
|
||||||
|
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 { test } from "vitest";
|
||||||
|
import { harness } from "./utils.js";
|
||||||
|
import { writeFile } from "fs/promises";
|
||||||
|
import { Page } from "playwright";
|
||||||
|
import { gt, lt } from "semver";
|
||||||
|
|
||||||
|
test("update starts downloading if version is outdated", async (t) => {
|
||||||
|
await harness(
|
||||||
|
t,
|
||||||
|
async ({ page }) => {
|
||||||
|
await page.waitForSelector("#authForm");
|
||||||
|
|
||||||
|
t.expect(
|
||||||
|
await page.getByRole("button", { name: "Create account" }).isVisible()
|
||||||
|
).toBe(true);
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByRole("button", { name: "Skip & go directly to the app" })
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await skipDialog(page);
|
||||||
|
|
||||||
|
await page.waitForSelector(".ProseMirror");
|
||||||
|
|
||||||
|
await page
|
||||||
|
.locator(".theme-scope-statusBar")
|
||||||
|
.getByRole("button", { name: /updating/i })
|
||||||
|
.waitFor({ state: "attached" });
|
||||||
|
},
|
||||||
|
{ version: "3.0.0" }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("update is only shown if version is outdated and auto updates are disabled", async (t) => {
|
||||||
|
await harness(
|
||||||
|
t,
|
||||||
|
async (ctx) => {
|
||||||
|
await ctx.app.close();
|
||||||
|
await writeFile(
|
||||||
|
ctx.configPath,
|
||||||
|
JSON.stringify({
|
||||||
|
automaticUpdates: false
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
await ctx.relaunch();
|
||||||
|
|
||||||
|
const { page } = ctx;
|
||||||
|
|
||||||
|
await page.waitForSelector("#authForm");
|
||||||
|
|
||||||
|
t.expect(
|
||||||
|
await page.getByRole("button", { name: "Create account" }).isVisible()
|
||||||
|
).toBe(true);
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByRole("button", { name: "Skip & go directly to the app" })
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await skipDialog(page);
|
||||||
|
|
||||||
|
await page.waitForSelector(".ProseMirror");
|
||||||
|
|
||||||
|
await page
|
||||||
|
.locator(".theme-scope-statusBar")
|
||||||
|
.getByRole("button", { name: /available/i })
|
||||||
|
.waitFor({ state: "attached" });
|
||||||
|
},
|
||||||
|
{ version: "3.0.0" }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("update to stable if it is newer", async (t) => {
|
||||||
|
await harness(
|
||||||
|
t,
|
||||||
|
async (ctx) => {
|
||||||
|
await ctx.app.close();
|
||||||
|
await writeFile(
|
||||||
|
ctx.configPath,
|
||||||
|
JSON.stringify({
|
||||||
|
automaticUpdates: false,
|
||||||
|
releaseTrack: "beta"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
await ctx.relaunch();
|
||||||
|
|
||||||
|
const { page } = ctx;
|
||||||
|
|
||||||
|
await page.waitForSelector("#authForm");
|
||||||
|
|
||||||
|
t.expect(
|
||||||
|
await page.getByRole("button", { name: "Create account" }).isVisible()
|
||||||
|
).toBe(true);
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByRole("button", { name: "Skip & go directly to the app" })
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await skipDialog(page);
|
||||||
|
|
||||||
|
await page.waitForSelector(".ProseMirror");
|
||||||
|
|
||||||
|
const updateButton = page
|
||||||
|
.locator(".theme-scope-statusBar")
|
||||||
|
.getByRole("button", { name: /available/i });
|
||||||
|
await updateButton.waitFor({ state: "visible" });
|
||||||
|
const content = await updateButton.textContent();
|
||||||
|
const version = content?.split(" ")?.[0] || "";
|
||||||
|
t.expect(gt(version, "3.0.0-beta.0")).toBe(true);
|
||||||
|
},
|
||||||
|
{ version: "3.0.0-beta.0" }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("update is not available if it latest stable version is older", async (t) => {
|
||||||
|
await harness(
|
||||||
|
t,
|
||||||
|
async (ctx) => {
|
||||||
|
await ctx.app.close();
|
||||||
|
await writeFile(
|
||||||
|
ctx.configPath,
|
||||||
|
JSON.stringify({
|
||||||
|
automaticUpdates: false,
|
||||||
|
releaseTrack: "beta"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
await ctx.relaunch();
|
||||||
|
|
||||||
|
const { page } = ctx;
|
||||||
|
|
||||||
|
await page.waitForSelector("#authForm");
|
||||||
|
|
||||||
|
t.expect(
|
||||||
|
await page.getByRole("button", { name: "Create account" }).isVisible()
|
||||||
|
).toBe(true);
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByRole("button", { name: "Skip & go directly to the app" })
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await skipDialog(page);
|
||||||
|
|
||||||
|
await page.waitForSelector(".ProseMirror");
|
||||||
|
|
||||||
|
await page
|
||||||
|
.locator(".theme-scope-statusBar")
|
||||||
|
.getByRole("button", { name: /checking for updates/i })
|
||||||
|
.waitFor({ state: "hidden" });
|
||||||
|
|
||||||
|
t.expect(
|
||||||
|
await page
|
||||||
|
.locator(".theme-scope-statusBar")
|
||||||
|
.getByRole("button", { name: /available/i })
|
||||||
|
.isHidden()
|
||||||
|
).toBe(true);
|
||||||
|
},
|
||||||
|
{ version: "99.0.0-beta.0" }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("downgrade to stable on switching to stable release track", async (t) => {
|
||||||
|
await harness(
|
||||||
|
t,
|
||||||
|
async (ctx) => {
|
||||||
|
await ctx.app.close();
|
||||||
|
await writeFile(
|
||||||
|
ctx.configPath,
|
||||||
|
JSON.stringify({
|
||||||
|
automaticUpdates: false,
|
||||||
|
releaseTrack: "stable"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
await ctx.relaunch();
|
||||||
|
|
||||||
|
const { page } = ctx;
|
||||||
|
|
||||||
|
await page.waitForSelector("#authForm");
|
||||||
|
|
||||||
|
t.expect(
|
||||||
|
await page.getByRole("button", { name: "Create account" }).isVisible()
|
||||||
|
).toBe(true);
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByRole("button", { name: "Skip & go directly to the app" })
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await skipDialog(page);
|
||||||
|
|
||||||
|
await page.waitForSelector(".ProseMirror");
|
||||||
|
|
||||||
|
await page
|
||||||
|
.locator(".theme-scope-statusBar")
|
||||||
|
.getByRole("button", { name: /checking for updates/i })
|
||||||
|
.waitFor({ state: "hidden" });
|
||||||
|
|
||||||
|
const updateButton = page
|
||||||
|
.locator(".theme-scope-statusBar")
|
||||||
|
.getByRole("button", { name: /available/i });
|
||||||
|
await updateButton.waitFor({ state: "visible" });
|
||||||
|
const content = await updateButton.textContent();
|
||||||
|
const version = content?.split(" ")?.[0] || "";
|
||||||
|
t.expect(lt(version, "99.0.0-beta.0")).toBe(true);
|
||||||
|
},
|
||||||
|
{ version: "99.0.0-beta.0" }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function skipDialog(page: Page) {
|
||||||
|
await page
|
||||||
|
.waitForSelector(".ReactModal__Content", {
|
||||||
|
timeout: 1000
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
.then(async () => {
|
||||||
|
const positiveButton = page.locator(
|
||||||
|
"button[data-role='positive-button']"
|
||||||
|
);
|
||||||
|
const negativeButton = page.locator(
|
||||||
|
"button[data-role='negative-button']"
|
||||||
|
);
|
||||||
|
if (await positiveButton.isVisible()) await positiveButton.click();
|
||||||
|
else if (await negativeButton.isVisible()) await negativeButton.click();
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -17,16 +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/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import test from "node:test";
|
import { test } from "vitest";
|
||||||
import { launchApp } from "./utils.mjs";
|
import { harness } from "./utils.js";
|
||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import slugify from "slugify";
|
|
||||||
import path from "path";
|
|
||||||
import { mkdir } from "fs/promises";
|
|
||||||
|
|
||||||
test("make sure app loads", async (t) => {
|
test("make sure app loads", async (t) => {
|
||||||
const { app, page } = await launchApp();
|
await harness(t, async ({ page }) => {
|
||||||
try {
|
|
||||||
await page.waitForSelector("#authForm");
|
await page.waitForSelector("#authForm");
|
||||||
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
@@ -38,16 +34,5 @@ test("make sure app loads", async (t) => {
|
|||||||
.click();
|
.click();
|
||||||
|
|
||||||
await page.waitForSelector(".ProseMirror");
|
await page.waitForSelector(".ProseMirror");
|
||||||
} catch (e) {
|
});
|
||||||
await mkdir("test-results", { recursive: true });
|
|
||||||
await page.screenshot({
|
|
||||||
path: path.join(
|
|
||||||
"test-results",
|
|
||||||
`${slugify(t.name)}-${process.platform}-${process.arch}-error.png`
|
|
||||||
)
|
|
||||||
});
|
|
||||||
throw e;
|
|
||||||
} finally {
|
|
||||||
await app.close();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
172
apps/desktop/__tests__/utils.ts
Normal file
172
apps/desktop/__tests__/utils.ts
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
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 { execSync } from "child_process";
|
||||||
|
import { mkdir } from "fs/promises";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import path from "path";
|
||||||
|
import { _electron as electron } from "playwright";
|
||||||
|
import slugify from "slugify";
|
||||||
|
import { TaskContext } from "vitest";
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const IS_DEBUG = process.env.NN_DEBUG === "true" || process.env.CI === "true";
|
||||||
|
|
||||||
|
interface AppContext {
|
||||||
|
app: import("playwright").ElectronApplication;
|
||||||
|
page: import("playwright").Page;
|
||||||
|
configPath: string;
|
||||||
|
relaunch: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TestOptions {
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function harness(
|
||||||
|
t: TaskContext,
|
||||||
|
cb: (ctx: AppContext) => Promise<void>,
|
||||||
|
options?: TestOptions
|
||||||
|
) {
|
||||||
|
const ctx = await buildAndLaunchApp(options);
|
||||||
|
|
||||||
|
t.onTestFinished(async (result) => {
|
||||||
|
if (result.state === "fail") {
|
||||||
|
await mkdir("test-results", { recursive: true });
|
||||||
|
await ctx.page.screenshot({
|
||||||
|
path: path.join(
|
||||||
|
"test-results",
|
||||||
|
`${slugify(t.task.name)}-${process.platform}-${
|
||||||
|
process.arch
|
||||||
|
}-error.png`
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await ctx.app.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
await cb(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildAndLaunchApp(options?: TestOptions): Promise<AppContext> {
|
||||||
|
const productName = makeid(10);
|
||||||
|
const executablePath = await buildApp({ ...options, productName });
|
||||||
|
const { app, page, configPath } = await launchApp(executablePath);
|
||||||
|
const ctx: AppContext = {
|
||||||
|
app,
|
||||||
|
page,
|
||||||
|
configPath,
|
||||||
|
relaunch: async () => {
|
||||||
|
const { app, page, configPath } = await launchApp(executablePath);
|
||||||
|
ctx.app = app;
|
||||||
|
ctx.page = page;
|
||||||
|
ctx.configPath = configPath;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function launchApp(executablePath: string) {
|
||||||
|
const app = await electron.launch({
|
||||||
|
executablePath,
|
||||||
|
args: IS_DEBUG ? [] : ["--hidden"],
|
||||||
|
env:
|
||||||
|
process.platform === "linux"
|
||||||
|
? {
|
||||||
|
...(process.env as Record<string, string>),
|
||||||
|
APPIMAGE: "true"
|
||||||
|
}
|
||||||
|
: (process.env as Record<string, string>)
|
||||||
|
});
|
||||||
|
|
||||||
|
const page = await app.firstWindow();
|
||||||
|
|
||||||
|
const userDataDirectory = await app.evaluate((a) => {
|
||||||
|
return a.app.getPath("userData");
|
||||||
|
});
|
||||||
|
const configPath = path.join(userDataDirectory, "config.json");
|
||||||
|
return {
|
||||||
|
app,
|
||||||
|
page,
|
||||||
|
configPath
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildApp({
|
||||||
|
version,
|
||||||
|
productName
|
||||||
|
}: {
|
||||||
|
version?: string;
|
||||||
|
productName: string;
|
||||||
|
}) {
|
||||||
|
const buildRoot = path.join("test-artifacts", `${productName}-build`);
|
||||||
|
const output = path.join("test-artifacts", `${productName}-output`);
|
||||||
|
execSync(`npm run release -- --root ${buildRoot} --skip-tsc-build`, {
|
||||||
|
stdio: IS_DEBUG ? "inherit" : "ignore"
|
||||||
|
});
|
||||||
|
|
||||||
|
const args = [
|
||||||
|
`--config electron-builder.config.js`,
|
||||||
|
`--c.extraMetadata.productName=${productName}`,
|
||||||
|
"--publish=never"
|
||||||
|
];
|
||||||
|
if (version) args.push(`--c.extraMetadata.version=${version}`);
|
||||||
|
|
||||||
|
execSync(`npx electron-builder --dir --${process.arch} ${args.join(" ")}`, {
|
||||||
|
stdio: IS_DEBUG ? "inherit" : "ignore",
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
NOTESNOOK_STAGING: "true",
|
||||||
|
NN_BUILD_ROOT: buildRoot,
|
||||||
|
NN_PRODUCT_NAME: productName,
|
||||||
|
NN_APP_ID: `com.notesnook.test.${productName}`,
|
||||||
|
NN_OUTPUT_DIR: output
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return path.join(
|
||||||
|
__dirname,
|
||||||
|
"..",
|
||||||
|
output,
|
||||||
|
process.platform === "linux"
|
||||||
|
? process.arch === "arm64"
|
||||||
|
? "linux-arm64-unpacked"
|
||||||
|
: "linux-unpacked"
|
||||||
|
: process.platform === "darwin"
|
||||||
|
? process.arch === "arm64"
|
||||||
|
? `mac-arm64/${productName}.app/Contents/MacOS/`
|
||||||
|
: `mac/${productName}.app/Contents/MacOS/`
|
||||||
|
: "win-unpacked",
|
||||||
|
process.platform === "win32" ? `${productName}.exe` : productName
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeid(length: number) {
|
||||||
|
let result = "";
|
||||||
|
const characters =
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
const charactersLength = characters.length;
|
||||||
|
let counter = 0;
|
||||||
|
while (counter < length) {
|
||||||
|
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||||
|
counter += 1;
|
||||||
|
}
|
||||||
|
return result.toLowerCase();
|
||||||
|
}
|
||||||
Binary file not shown.
203
apps/desktop/electron-builder.config.js
Normal file
203
apps/desktop/electron-builder.config.js
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
/*
|
||||||
|
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 path = require("path");
|
||||||
|
const pkg = require("./package.json");
|
||||||
|
|
||||||
|
const buildRoot = process.env.NN_BUILD_ROOT || ".";
|
||||||
|
const buildFiles = [
|
||||||
|
`${buildRoot}/build/`,
|
||||||
|
`!${buildRoot}/build/screenshots\${/*}`,
|
||||||
|
`!${buildRoot}/build/banner.jpg`,
|
||||||
|
`!${buildRoot}/build/*.ico`,
|
||||||
|
`!${buildRoot}/build/*.png`
|
||||||
|
];
|
||||||
|
|
||||||
|
const productName = process.env.NN_PRODUCT_NAME || "Notesnook";
|
||||||
|
const appId = process.env.NN_APP_ID || "org.streetwriters.notesnook";
|
||||||
|
const outputDir = process.env.NN_OUTPUT_DIR || "output";
|
||||||
|
const linuxExecutableName = process.env.NN_PRODUCT_NAME
|
||||||
|
? process.env.NN_PRODUCT_NAME.toLowerCase().replace(/\s+/g, "-")
|
||||||
|
: "notesnook";
|
||||||
|
const year = new Date().getFullYear();
|
||||||
|
const isBeta = pkg.version.includes("-beta");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
appId: appId,
|
||||||
|
productName: productName,
|
||||||
|
copyright: `Copyright © ${year} Streetwriters (Private) Limited`,
|
||||||
|
artifactName: "notesnook_${os}_${arch}.${ext}",
|
||||||
|
generateUpdatesFilesForAllChannels: true,
|
||||||
|
asar: false,
|
||||||
|
files: [
|
||||||
|
"!*.chunk.js.map",
|
||||||
|
"!*.chunk.js.LICENSE.txt",
|
||||||
|
...buildFiles,
|
||||||
|
"!node_modules${/*}",
|
||||||
|
"node_modules/better-sqlite3-multiple-ciphers/build/Release/better_sqlite3.node",
|
||||||
|
"node_modules/better-sqlite3-multiple-ciphers/lib",
|
||||||
|
"node_modules/better-sqlite3-multiple-ciphers/package.json",
|
||||||
|
"node_modules/file-uri-to-path",
|
||||||
|
"node_modules/bindings",
|
||||||
|
"node_modules/node-gyp-build",
|
||||||
|
"node_modules/sqlite-better-trigram",
|
||||||
|
"node_modules/sodium-native/prebuilds/${platform}-${arch}",
|
||||||
|
{
|
||||||
|
from: "node_modules/sqlite-better-trigram-linux-${arch}",
|
||||||
|
to: "node_modules/sqlite-better-trigram-linux-${arch}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: "node_modules/sqlite-better-trigram-darwin-${arch}",
|
||||||
|
to: "node_modules/sqlite-better-trigram-darwin-${arch}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: "node_modules/sqlite-better-trigram-windows-${arch}",
|
||||||
|
to: "node_modules/sqlite-better-trigram-windows-${arch}"
|
||||||
|
},
|
||||||
|
"node_modules/sodium-native/index.js",
|
||||||
|
"node_modules/sodium-native/package.json"
|
||||||
|
],
|
||||||
|
afterPack: "./scripts/removeLocales.js",
|
||||||
|
mac: {
|
||||||
|
bundleVersion: "240",
|
||||||
|
minimumSystemVersion: "10.12.0",
|
||||||
|
target: [
|
||||||
|
{
|
||||||
|
target: "dmg",
|
||||||
|
arch: ["arm64", "x64"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: "zip",
|
||||||
|
arch: ["arm64", "x64"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
category: "public.app-category.productivity",
|
||||||
|
darkModeSupport: true,
|
||||||
|
type: "distribution",
|
||||||
|
hardenedRuntime: true,
|
||||||
|
entitlements: "assets/entitlements.mac.plist",
|
||||||
|
entitlementsInherit: "assets/entitlements.mac.plist",
|
||||||
|
gatekeeperAssess: false,
|
||||||
|
icon: "assets/icons/app.icns",
|
||||||
|
notarize: true
|
||||||
|
},
|
||||||
|
dmg: {
|
||||||
|
contents: [
|
||||||
|
{
|
||||||
|
x: 130,
|
||||||
|
y: 220
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 410,
|
||||||
|
y: 220,
|
||||||
|
type: "link",
|
||||||
|
path: "/Applications"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
icon: "assets/icons/app.icns",
|
||||||
|
title: "Install Notesnook"
|
||||||
|
},
|
||||||
|
mas: {
|
||||||
|
entitlements: "assets/entitlements.mas.plist",
|
||||||
|
entitlementsInherit: "assets/entitlements.mas.inherit.plist",
|
||||||
|
entitlementsLoginHelper: "assets/entitlements.mas.loginhelper.plist",
|
||||||
|
hardenedRuntime: true
|
||||||
|
},
|
||||||
|
win: {
|
||||||
|
target: [
|
||||||
|
{
|
||||||
|
target: "nsis",
|
||||||
|
arch: ["x64", "arm64"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: "portable",
|
||||||
|
arch: ["x64", "arm64"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
signtoolOptions: {
|
||||||
|
signingHashAlgorithms: ["sha256"],
|
||||||
|
sign: "./scripts/sign.js"
|
||||||
|
},
|
||||||
|
icon: "assets/icons/app.ico"
|
||||||
|
},
|
||||||
|
portable: {
|
||||||
|
artifactName: "notesnook_${os}_${arch}_portable.${ext}"
|
||||||
|
},
|
||||||
|
nsis: {
|
||||||
|
oneClick: true,
|
||||||
|
createDesktopShortcut: "always",
|
||||||
|
deleteAppDataOnUninstall: true
|
||||||
|
},
|
||||||
|
linux: {
|
||||||
|
target: [
|
||||||
|
{
|
||||||
|
target: "AppImage",
|
||||||
|
arch: ["x64", "arm64"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: "snap",
|
||||||
|
arch: ["x64", "arm64"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
category: "Office",
|
||||||
|
icon: "assets/icons/app.icns",
|
||||||
|
description: "Your private note taking space",
|
||||||
|
executableName: linuxExecutableName,
|
||||||
|
desktop: {
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: "new-note",
|
||||||
|
name: "New note",
|
||||||
|
args: "new note"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "new-notebook",
|
||||||
|
name: "New notebook",
|
||||||
|
args: "new notebook"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "new-reminder",
|
||||||
|
name: "New reminder",
|
||||||
|
args: "new reminder"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
snap: {
|
||||||
|
autoStart: false,
|
||||||
|
confinement: "strict",
|
||||||
|
allowNativeWayland: true
|
||||||
|
},
|
||||||
|
extraResources: ["app-update.yml", "./assets/**"],
|
||||||
|
extraMetadata: {
|
||||||
|
main: path.join(buildRoot, "build", "electron.js")
|
||||||
|
},
|
||||||
|
directories: {
|
||||||
|
buildResources: "assets",
|
||||||
|
output: outputDir
|
||||||
|
},
|
||||||
|
publish: [
|
||||||
|
{
|
||||||
|
provider: "github",
|
||||||
|
repo: "notesnook",
|
||||||
|
owner: "streetwriters",
|
||||||
|
channel: isBeta ? "beta" : "latest"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
1134
apps/desktop/package-lock.json
generated
1134
apps/desktop/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
|||||||
"name": "@notesnook/desktop",
|
"name": "@notesnook/desktop",
|
||||||
"productName": "Notesnook",
|
"productName": "Notesnook",
|
||||||
"description": "Your private note taking space",
|
"description": "Your private note taking space",
|
||||||
"version": "3.0.23",
|
"version": "3.1.0-beta.1",
|
||||||
"appAppleId": "1544027013",
|
"appAppleId": "1544027013",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "./dist/cjs/index.js",
|
"main": "./dist/cjs/index.js",
|
||||||
@@ -25,18 +25,18 @@
|
|||||||
"repository": "https://github.com/streetwriters/notesnook",
|
"repository": "https://github.com/streetwriters/notesnook",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lingui/core": "4.11.4",
|
"@lingui/core": "5.1.2",
|
||||||
"@notesnook/intl": "file:../../packages/intl",
|
"@notesnook/intl": "file:../../packages/intl",
|
||||||
"@trpc/client": "10.45.2",
|
"@trpc/client": "10.45.2",
|
||||||
"@trpc/server": "10.45.2",
|
"@trpc/server": "10.45.2",
|
||||||
"better-sqlite3-multiple-ciphers": "11.5.0",
|
"better-sqlite3-multiple-ciphers": "11.5.0",
|
||||||
"electron-trpc": "0.6.1",
|
"electron-trpc": "0.7.1",
|
||||||
"electron-updater": "^6.3.4",
|
"electron-updater": "^6.3.4",
|
||||||
"icojs": "^0.19.4",
|
"icojs": "^0.19.4",
|
||||||
"sqlite-better-trigram": "^0.0.2",
|
"sqlite-better-trigram": "0.0.2",
|
||||||
"typed-emitter": "^2.1.0",
|
"typed-emitter": "^2.1.0",
|
||||||
"yargs": "^17.7.2",
|
"yargs": "^17.7.2",
|
||||||
"zod": "^3.23.8"
|
"zod": "3.23.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@streetwriters/kysely": "^0.27.4",
|
"@streetwriters/kysely": "^0.27.4",
|
||||||
@@ -45,15 +45,15 @@
|
|||||||
"chokidar": "^4.0.1",
|
"chokidar": "^4.0.1",
|
||||||
"electron": "^31.7.4",
|
"electron": "^31.7.4",
|
||||||
"electron-builder": "^25.1.8",
|
"electron-builder": "^25.1.8",
|
||||||
"esbuild": "^0.24.0",
|
"esbuild": "0.21.5",
|
||||||
"node-abi": "^3.68.0",
|
"node-abi": "^3.68.0",
|
||||||
"node-gyp-build": "^4.8.2",
|
"node-gyp-build": "^4.8.2",
|
||||||
"playwright": "^1.48.2",
|
"playwright": "^1.48.2",
|
||||||
"prebuildify": "^6.0.1",
|
"prebuildify": "^6.0.1",
|
||||||
"slugify": "^1.6.6",
|
"slugify": "1.6.6",
|
||||||
"tree-kill": "^1.2.2",
|
"tree-kill": "^1.2.2",
|
||||||
"undici": "^6.19.8",
|
"undici": "^6.19.8",
|
||||||
"vitest": "^2.1.5"
|
"vitest": "^2.1.8"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"dmg-license": "^1.0.11"
|
"dmg-license": "^1.0.11"
|
||||||
@@ -63,203 +63,14 @@
|
|||||||
"staging": "node scripts/build.mjs --run",
|
"staging": "node scripts/build.mjs --run",
|
||||||
"release": "node scripts/build.mjs",
|
"release": "node scripts/build.mjs",
|
||||||
"build": "node ../../scripts/build.mjs",
|
"build": "node ../../scripts/build.mjs",
|
||||||
"bundle": "esbuild electron=./src/main.ts ./src/preload.ts --external:electron --external:fsevents --external:better-sqlite3-multiple-ciphers --external:sodium-native --minify --bundle --outdir=./build --platform=node --tsconfig=tsconfig.json --define:MAC_APP_STORE=false --define:RELEASE=true",
|
"bundle": "esbuild electron=./src/main.ts ./src/preload.ts --external:electron --external:fsevents --external:better-sqlite3-multiple-ciphers --external:sodium-native --bundle --outdir=./build --platform=node --tsconfig=tsconfig.json --define:MAC_APP_STORE=false --define:RELEASE=true",
|
||||||
"bundle:mas": "esbuild electron=./src/main.ts ./src/preload.ts --minify --external:electron --external:fsevents --bundle --outdir=./build --platform=node --tsconfig=tsconfig.json --define:MAC_APP_STORE=true --define:RELEASE=true",
|
"bundle:mas": "esbuild electron=./src/main.ts ./src/preload.ts --minify --external:electron --external:fsevents --bundle --outdir=./build --platform=node --tsconfig=tsconfig.json --define:MAC_APP_STORE=true --define:RELEASE=true",
|
||||||
"postinstall": "patch-package",
|
"postinstall": "patch-package",
|
||||||
"test": "node --test --test-timeout 30000 --test-force-exit"
|
"test": "vitest run"
|
||||||
},
|
},
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Streetwriters (Private) Limited",
|
"name": "Streetwriters (Private) Limited",
|
||||||
"email": "support@streetwriters.co",
|
"email": "support@streetwriters.co",
|
||||||
"url": "https://streetwriters.co"
|
"url": "https://streetwriters.co"
|
||||||
},
|
|
||||||
"build": {
|
|
||||||
"appId": "org.streetwriters.notesnook",
|
|
||||||
"productName": "Notesnook",
|
|
||||||
"copyright": "Copyright © 2023 Streetwriters (Private) Limited",
|
|
||||||
"artifactName": "notesnook_${os}_${arch}.${ext}",
|
|
||||||
"generateUpdatesFilesForAllChannels": true,
|
|
||||||
"asar": false,
|
|
||||||
"files": [
|
|
||||||
"!*.chunk.js.map",
|
|
||||||
"!*.chunk.js.LICENSE.txt",
|
|
||||||
"build/",
|
|
||||||
"!build/screenshots${/*}",
|
|
||||||
"!build/banner.jpg",
|
|
||||||
"!build/*.ico",
|
|
||||||
"!build/*.png",
|
|
||||||
"!node_modules${/*}",
|
|
||||||
"node_modules/better-sqlite3-multiple-ciphers/build/Release/better_sqlite3.node",
|
|
||||||
"node_modules/better-sqlite3-multiple-ciphers/lib",
|
|
||||||
"node_modules/better-sqlite3-multiple-ciphers/package.json",
|
|
||||||
"node_modules/file-uri-to-path",
|
|
||||||
"node_modules/bindings",
|
|
||||||
"node_modules/node-gyp-build",
|
|
||||||
"node_modules/sqlite-better-trigram",
|
|
||||||
"node_modules/sodium-native/prebuilds/${platform}-${arch}",
|
|
||||||
{
|
|
||||||
"from": "node_modules/sqlite-better-trigram-linux-${arch}",
|
|
||||||
"to": "node_modules/sqlite-better-trigram-linux-${arch}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"from": "node_modules/sqlite-better-trigram-darwin-${arch}",
|
|
||||||
"to": "node_modules/sqlite-better-trigram-darwin-${arch}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"from": "node_modules/sqlite-better-trigram-windows-${arch}",
|
|
||||||
"to": "node_modules/sqlite-better-trigram-windows-${arch}"
|
|
||||||
},
|
|
||||||
"node_modules/sodium-native/index.js",
|
|
||||||
"node_modules/sodium-native/package.json"
|
|
||||||
],
|
|
||||||
"afterPack": "./scripts/removeLocales.js",
|
|
||||||
"mac": {
|
|
||||||
"bundleVersion": "240",
|
|
||||||
"minimumSystemVersion": "10.12.0",
|
|
||||||
"target": [
|
|
||||||
{
|
|
||||||
"target": "dmg",
|
|
||||||
"arch": [
|
|
||||||
"arm64",
|
|
||||||
"x64"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"target": "zip",
|
|
||||||
"arch": [
|
|
||||||
"arm64",
|
|
||||||
"x64"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"category": "public.app-category.productivity",
|
|
||||||
"darkModeSupport": true,
|
|
||||||
"type": "distribution",
|
|
||||||
"hardenedRuntime": true,
|
|
||||||
"entitlements": "assets/entitlements.mac.plist",
|
|
||||||
"entitlementsInherit": "assets/entitlements.mac.plist",
|
|
||||||
"gatekeeperAssess": false,
|
|
||||||
"icon": "assets/icons/app.icns",
|
|
||||||
"notarize": true
|
|
||||||
},
|
|
||||||
"dmg": {
|
|
||||||
"contents": [
|
|
||||||
{
|
|
||||||
"x": 130,
|
|
||||||
"y": 220
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": 410,
|
|
||||||
"y": 220,
|
|
||||||
"type": "link",
|
|
||||||
"path": "/Applications"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"icon": "assets/icons/app.icns",
|
|
||||||
"title": "Install Notesnook"
|
|
||||||
},
|
|
||||||
"mas": {
|
|
||||||
"entitlements": "assets/entitlements.mas.plist",
|
|
||||||
"entitlementsInherit": "assets/entitlements.mas.inherit.plist",
|
|
||||||
"entitlementsLoginHelper": "assets/entitlements.mas.loginhelper.plist",
|
|
||||||
"hardenedRuntime": true
|
|
||||||
},
|
|
||||||
"win": {
|
|
||||||
"target": [
|
|
||||||
{
|
|
||||||
"target": "nsis",
|
|
||||||
"arch": [
|
|
||||||
"x64",
|
|
||||||
"arm64"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"target": "portable",
|
|
||||||
"arch": [
|
|
||||||
"x64",
|
|
||||||
"arm64"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"signtoolOptions": {
|
|
||||||
"signingHashAlgorithms": [
|
|
||||||
"sha256"
|
|
||||||
],
|
|
||||||
"sign": "./scripts/sign.js"
|
|
||||||
},
|
|
||||||
"icon": "assets/icons/app.ico"
|
|
||||||
},
|
|
||||||
"portable": {
|
|
||||||
"artifactName": "notesnook_${os}_${arch}_portable.${ext}"
|
|
||||||
},
|
|
||||||
"nsis": {
|
|
||||||
"oneClick": true,
|
|
||||||
"createDesktopShortcut": "always",
|
|
||||||
"deleteAppDataOnUninstall": true
|
|
||||||
},
|
|
||||||
"linux": {
|
|
||||||
"target": [
|
|
||||||
{
|
|
||||||
"target": "AppImage",
|
|
||||||
"arch": [
|
|
||||||
"x64",
|
|
||||||
"arm64"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"target": "snap",
|
|
||||||
"arch": [
|
|
||||||
"x64",
|
|
||||||
"arm64"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"category": "Office",
|
|
||||||
"icon": "assets/icons/app.icns",
|
|
||||||
"description": "Your private note taking space",
|
|
||||||
"executableName": "notesnook",
|
|
||||||
"desktop": {
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"id": "new-note",
|
|
||||||
"name": "New note",
|
|
||||||
"args": "new note"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "new-notebook",
|
|
||||||
"name": "New notebook",
|
|
||||||
"args": "new notebook"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "new-reminder",
|
|
||||||
"name": "New reminder",
|
|
||||||
"args": "new reminder"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"snap": {
|
|
||||||
"autoStart": false,
|
|
||||||
"confinement": "strict",
|
|
||||||
"allowNativeWayland": true
|
|
||||||
},
|
|
||||||
"extraResources": [
|
|
||||||
"app-update.yml",
|
|
||||||
"./assets/**"
|
|
||||||
],
|
|
||||||
"extraMetadata": {
|
|
||||||
"main": "./build/electron.js"
|
|
||||||
},
|
|
||||||
"directories": {
|
|
||||||
"buildResources": "assets",
|
|
||||||
"output": "./output/"
|
|
||||||
},
|
|
||||||
"publish": [
|
|
||||||
{
|
|
||||||
"provider": "github",
|
|
||||||
"repo": "notesnook",
|
|
||||||
"owner": "streetwriters"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,10 +29,12 @@ import { patchBetterSQLite3 } from "./patch-better-sqlite3.mjs";
|
|||||||
const args = yargs(process.argv);
|
const args = yargs(process.argv);
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
|
const root = args.root || path.join(__dirname, "..");
|
||||||
|
const skipTscBuild = args.skipTscBuild || false;
|
||||||
|
|
||||||
const webAppPath = path.resolve(path.join(__dirname, "..", "..", "web"));
|
const webAppPath = path.resolve(path.join(__dirname, "..", "..", "web"));
|
||||||
|
|
||||||
await fs.rm("./build/", { force: true, recursive: true });
|
await fs.rm(path.join(root, "build"), { force: true, recursive: true });
|
||||||
|
|
||||||
if (args.rebuild || !existsSync(path.join(webAppPath, "build"))) {
|
if (args.rebuild || !existsSync(path.join(webAppPath, "build"))) {
|
||||||
await exec(
|
await exec(
|
||||||
@@ -44,21 +46,25 @@ if (args.rebuild || !existsSync(path.join(webAppPath, "build"))) {
|
|||||||
// temporary until there's support for prebuilt binaries for linux ARM
|
// temporary until there's support for prebuilt binaries for linux ARM
|
||||||
if (os.platform() === "linux") await patchBetterSQLite3();
|
if (os.platform() === "linux") await patchBetterSQLite3();
|
||||||
|
|
||||||
await fs.cp(path.join(webAppPath, "build"), "build", {
|
await fs.cp(path.join(webAppPath, "build"), path.join(root, "build"), {
|
||||||
recursive: true,
|
recursive: true,
|
||||||
force: true
|
force: true
|
||||||
});
|
});
|
||||||
|
|
||||||
if (args.variant === "mas") {
|
if (args.variant === "mas") {
|
||||||
await exec(`yarn run bundle:mas`);
|
await exec(`yarn run bundle:mas --outdir=${path.join(root, "build")}`);
|
||||||
} else {
|
} else {
|
||||||
await exec(`yarn run bundle`);
|
await exec(`yarn run bundle --outdir=${path.join(root, "build")}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await exec(`yarn run build`);
|
if (!skipTscBuild) {
|
||||||
|
await exec(`yarn run build`);
|
||||||
|
}
|
||||||
|
|
||||||
if (args.run) {
|
if (args.run) {
|
||||||
await exec(`yarn electron-builder --dir --${process.arch}`);
|
await exec(
|
||||||
|
`yarn electron-builder --dir --${process.arch} --config=electron-builder.config.js`
|
||||||
|
);
|
||||||
if (process.platform === "win32") {
|
if (process.platform === "win32") {
|
||||||
await exec(`.\\output\\win-unpacked\\Notesnook.exe`);
|
await exec(`.\\output\\win-unpacked\\Notesnook.exe`);
|
||||||
} else if (process.platform === "darwin") {
|
} else if (process.platform === "darwin") {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import { dirname } from "path";
|
|||||||
import { resolvePath } from "../utils/resolve-path";
|
import { resolvePath } from "../utils/resolve-path";
|
||||||
import { observable } from "@trpc/server/observable";
|
import { observable } from "@trpc/server/observable";
|
||||||
import { AssetManager } from "../utils/asset-manager";
|
import { AssetManager } from "../utils/asset-manager";
|
||||||
import { isFlatpak } from "../utils";
|
import { isFlatpak, isSnap } from "../utils";
|
||||||
import { setupDesktopIntegration } from "../utils/desktop-integration";
|
import { setupDesktopIntegration } from "../utils/desktop-integration";
|
||||||
import { rm } from "fs/promises";
|
import { rm } from "fs/promises";
|
||||||
import { disableCustomDns, enableCustomDns } from "../utils/custom-dns";
|
import { disableCustomDns, enableCustomDns } from "../utils/custom-dns";
|
||||||
@@ -49,6 +49,7 @@ const NotificationOptions = z.object({
|
|||||||
|
|
||||||
export const osIntegrationRouter = t.router({
|
export const osIntegrationRouter = t.router({
|
||||||
isFlatpak: t.procedure.query(() => isFlatpak()),
|
isFlatpak: t.procedure.query(() => isFlatpak()),
|
||||||
|
isSnap: t.procedure.query(() => isSnap()),
|
||||||
|
|
||||||
zoomFactor: t.procedure.query(() => config.zoomFactor),
|
zoomFactor: t.procedure.query(() => config.zoomFactor),
|
||||||
setZoomFactor: t.procedure.input(z.number()).mutation(({ input: factor }) => {
|
setZoomFactor: t.procedure.input(z.number()).mutation(({ input: factor }) => {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { CancellationToken, autoUpdater } from "electron-updater";
|
|||||||
import type { AppUpdaterEvents } from "electron-updater/out/AppUpdater";
|
import type { AppUpdaterEvents } from "electron-updater/out/AppUpdater";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { config } from "../utils/config";
|
import { config } from "../utils/config";
|
||||||
|
import { app } from "electron";
|
||||||
|
|
||||||
type UpdateInfo = { version: string };
|
type UpdateInfo = { version: string };
|
||||||
type Progress = { percent: number };
|
type Progress = { percent: number };
|
||||||
@@ -33,6 +34,7 @@ let downloadTimeout: NodeJS.Timeout | undefined = undefined;
|
|||||||
|
|
||||||
export const updaterRouter = t.router({
|
export const updaterRouter = t.router({
|
||||||
autoUpdates: t.procedure.query(() => config.automaticUpdates),
|
autoUpdates: t.procedure.query(() => config.automaticUpdates),
|
||||||
|
releaseTrack: t.procedure.query(() => config.releaseTrack),
|
||||||
install: t.procedure.query(() => autoUpdater.quitAndInstall()),
|
install: t.procedure.query(() => autoUpdater.quitAndInstall()),
|
||||||
download: t.procedure.query(async () => {
|
download: t.procedure.query(async () => {
|
||||||
if (cancellationToken) return;
|
if (cancellationToken) return;
|
||||||
@@ -67,7 +69,13 @@ export const updaterRouter = t.router({
|
|||||||
.mutation(({ input: { enabled } }) => {
|
.mutation(({ input: { enabled } }) => {
|
||||||
config.automaticUpdates = enabled;
|
config.automaticUpdates = enabled;
|
||||||
}),
|
}),
|
||||||
|
changeReleaseTrack: t.procedure
|
||||||
|
.input(z.object({ track: z.string() }))
|
||||||
|
.mutation(({ input: { track } }) => {
|
||||||
|
config.releaseTrack = track;
|
||||||
|
app.relaunch();
|
||||||
|
app.exit();
|
||||||
|
}),
|
||||||
onChecking: createSubscription("checking-for-update"),
|
onChecking: createSubscription("checking-for-update"),
|
||||||
onDownloaded: createSubscription<"update-downloaded", UpdateInfo>(
|
onDownloaded: createSubscription<"update-downloaded", UpdateInfo>(
|
||||||
"update-downloaded"
|
"update-downloaded"
|
||||||
|
|||||||
@@ -85,6 +85,8 @@ async function createWindow() {
|
|||||||
const mainWindowState = new WindowState({});
|
const mainWindowState = new WindowState({});
|
||||||
const mainWindow = new BrowserWindow({
|
const mainWindow = new BrowserWindow({
|
||||||
show: !cliOptions.hidden,
|
show: !cliOptions.hidden,
|
||||||
|
paintWhenInitiallyHidden: cliOptions.hidden,
|
||||||
|
skipTaskbar: cliOptions.hidden,
|
||||||
x: mainWindowState.x,
|
x: mainWindowState.x,
|
||||||
y: mainWindowState.y,
|
y: mainWindowState.y,
|
||||||
width: mainWindowState.width,
|
width: mainWindowState.width,
|
||||||
@@ -101,7 +103,10 @@ async function createWindow() {
|
|||||||
...(config.desktopSettings.nativeTitlebar
|
...(config.desktopSettings.nativeTitlebar
|
||||||
? {}
|
? {}
|
||||||
: {
|
: {
|
||||||
titleBarStyle: process.platform === "win32" || process.platform === "darwin" ? "hidden" : "default",
|
titleBarStyle:
|
||||||
|
process.platform === "win32" || process.platform === "darwin"
|
||||||
|
? "hidden"
|
||||||
|
: "default",
|
||||||
frame: process.platform === "win32" || process.platform === "darwin",
|
frame: process.platform === "win32" || process.platform === "darwin",
|
||||||
titleBarOverlay: {
|
titleBarOverlay: {
|
||||||
height: 37,
|
height: 37,
|
||||||
@@ -142,6 +147,11 @@ async function createWindow() {
|
|||||||
await AssetManager.loadIcons();
|
await AssetManager.loadIcons();
|
||||||
setupDesktopIntegration(config.desktopSettings);
|
setupDesktopIntegration(config.desktopSettings);
|
||||||
|
|
||||||
|
mainWindow.webContents.session.setPermissionRequestHandler(
|
||||||
|
(webContents, permission, callback) => {
|
||||||
|
callback(permission === "geolocation" ? false : true);
|
||||||
|
}
|
||||||
|
);
|
||||||
mainWindow.webContents.session.setSpellCheckerDictionaryDownloadURL(
|
mainWindow.webContents.session.setSpellCheckerDictionaryDownloadURL(
|
||||||
"http://dictionaries.notesnook.com/"
|
"http://dictionaries.notesnook.com/"
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -20,19 +20,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
import { autoUpdater } from "electron-updater";
|
import { autoUpdater } from "electron-updater";
|
||||||
import { config } from "./config";
|
import { config } from "./config";
|
||||||
|
|
||||||
const CHANNEL = autoUpdater.currentVersion.raw.endsWith("-beta")
|
|
||||||
? "beta"
|
|
||||||
: "latest";
|
|
||||||
async function configureAutoUpdater() {
|
async function configureAutoUpdater() {
|
||||||
|
const releaseTrack =
|
||||||
|
config.releaseTrack === "stable" ? "latest" : config.releaseTrack;
|
||||||
autoUpdater.setFeedURL({
|
autoUpdater.setFeedURL({
|
||||||
provider: "generic",
|
provider: "generic",
|
||||||
url: `https://notesnook.com/api/v1/releases/${process.platform}/${CHANNEL}`,
|
url: `https://notesnook.com/api/v1/releases/${process.platform}/${releaseTrack}`,
|
||||||
useMultipleRangeRequest: false,
|
useMultipleRangeRequest: false,
|
||||||
channel: CHANNEL
|
channel: releaseTrack
|
||||||
});
|
});
|
||||||
|
|
||||||
autoUpdater.autoDownload = config.automaticUpdates;
|
autoUpdater.autoDownload = config.automaticUpdates;
|
||||||
autoUpdater.allowDowngrade = false;
|
autoUpdater.allowDowngrade =
|
||||||
|
// only allow downgrade if the current version is a prerelease
|
||||||
|
// and the user has changed the release track to stable
|
||||||
|
config.releaseTrack === "stable" &&
|
||||||
|
autoUpdater.currentVersion.prerelease.length > 0;
|
||||||
autoUpdater.allowPrerelease = false;
|
autoUpdater.allowPrerelease = false;
|
||||||
autoUpdater.autoInstallOnAppQuit = true;
|
autoUpdater.autoInstallOnAppQuit = true;
|
||||||
autoUpdater.disableWebInstaller = true;
|
autoUpdater.disableWebInstaller = true;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
import { nativeTheme } from "electron";
|
import { nativeTheme } from "electron";
|
||||||
import { JSONStorage } from "./json-storage";
|
import { JSONStorage } from "./json-storage";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { autoUpdater } from "electron-updater";
|
||||||
|
|
||||||
export const DesktopIntegration = z.object({
|
export const DesktopIntegration = z.object({
|
||||||
autoStart: z.boolean().optional(),
|
autoStart: z.boolean().optional(),
|
||||||
@@ -46,6 +47,9 @@ export const config = {
|
|||||||
automaticUpdates: true,
|
automaticUpdates: true,
|
||||||
proxyRules: "",
|
proxyRules: "",
|
||||||
customDns: true,
|
customDns: true,
|
||||||
|
releaseTrack: autoUpdater.currentVersion.raw.includes("-beta")
|
||||||
|
? "beta"
|
||||||
|
: "stable",
|
||||||
|
|
||||||
backgroundColor: nativeTheme.themeSource === "dark" ? "#0f0f0f" : "#ffffff",
|
backgroundColor: nativeTheme.themeSource === "dark" ? "#0f0f0f" : "#ffffff",
|
||||||
windowControlsIconColor:
|
windowControlsIconColor:
|
||||||
|
|||||||
@@ -29,3 +29,7 @@ export function isDevelopment() {
|
|||||||
export function isFlatpak() {
|
export function isFlatpak() {
|
||||||
return existsSync("/.flatpak-info");
|
return existsSync("/.flatpak-info");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isSnap() {
|
||||||
|
return process.env.SNAP !== undefined;
|
||||||
|
}
|
||||||
|
|||||||
40
apps/desktop/vitest.config.ts
Normal file
40
apps/desktop/vitest.config.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
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 { defineConfig } from "vitest/config";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
testTimeout: process.env.CI ? 120 * 1000 : 120 * 1000,
|
||||||
|
sequence: {
|
||||||
|
concurrent: true,
|
||||||
|
shuffle: true
|
||||||
|
},
|
||||||
|
dir: "./__tests__/",
|
||||||
|
exclude: [
|
||||||
|
"**/node_modules/**",
|
||||||
|
"**/dist/**",
|
||||||
|
"**/cypress/**",
|
||||||
|
"**/.{idea,git,cache,output,temp}/**",
|
||||||
|
"**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build,eslint,prettier}.config.*",
|
||||||
|
"**/test-results/**",
|
||||||
|
"**/test-artifacts/**"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -32,6 +32,7 @@ import { withErrorBoundry } from "./components/exception-handler";
|
|||||||
import GlobalSafeAreaProvider from "./components/globalsafearea";
|
import GlobalSafeAreaProvider from "./components/globalsafearea";
|
||||||
import { useAppEvents } from "./hooks/use-app-events";
|
import { useAppEvents } from "./hooks/use-app-events";
|
||||||
import { ApplicationHolder } from "./navigation";
|
import { ApplicationHolder } from "./navigation";
|
||||||
|
import { NotePreviewConfigure } from "./screens/note-preview-configure";
|
||||||
import { themeTrpcClient } from "./screens/settings/theme-selector";
|
import { themeTrpcClient } from "./screens/settings/theme-selector";
|
||||||
import Notifications from "./services/notifications";
|
import Notifications from "./services/notifications";
|
||||||
import SettingsService from "./services/settings";
|
import SettingsService from "./services/settings";
|
||||||
@@ -47,7 +48,7 @@ if (appLockEnabled || appLockMode !== "none") {
|
|||||||
useUserStore.getState().lockApp(true);
|
useUserStore.getState().lockApp(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const App = () => {
|
const App = (props: { configureMode: "note-preview" }) => {
|
||||||
useAppEvents();
|
useAppEvents();
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
globalThis["IS_MAIN_APP_RUNNING"] = true;
|
globalThis["IS_MAIN_APP_RUNNING"] = true;
|
||||||
@@ -61,6 +62,7 @@ const App = () => {
|
|||||||
TipManager.init();
|
TipManager.init();
|
||||||
}, 100);
|
}, 100);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
@@ -90,7 +92,11 @@ const App = () => {
|
|||||||
width: "100%"
|
width: "100%"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ApplicationHolder />
|
{props.configureMode === "note-preview" ? (
|
||||||
|
<NotePreviewConfigure />
|
||||||
|
) : (
|
||||||
|
<ApplicationHolder />
|
||||||
|
)}
|
||||||
</GestureHandlerRootView>
|
</GestureHandlerRootView>
|
||||||
<AppLockedOverlay />
|
<AppLockedOverlay />
|
||||||
</View>
|
</View>
|
||||||
@@ -103,8 +109,8 @@ let currTheme =
|
|||||||
: SettingsService.getProperty("lighTheme");
|
: SettingsService.getProperty("lighTheme");
|
||||||
useThemeEngineStore.getState().setTheme(currTheme);
|
useThemeEngineStore.getState().setTheme(currTheme);
|
||||||
|
|
||||||
export const withTheme = (Element: () => JSX.Element) => {
|
export const withTheme = (Element: (props: any) => JSX.Element) => {
|
||||||
return function AppWithThemeProvider() {
|
return function AppWithThemeProvider(props: any) {
|
||||||
const [colorScheme, darkTheme, lightTheme] = useThemeStore((state) => [
|
const [colorScheme, darkTheme, lightTheme] = useThemeStore((state) => [
|
||||||
state.colorScheme,
|
state.colorScheme,
|
||||||
state.darkTheme,
|
state.darkTheme,
|
||||||
@@ -123,13 +129,14 @@ export const withTheme = (Element: () => JSX.Element) => {
|
|||||||
})
|
})
|
||||||
.then((theme) => {
|
.then((theme) => {
|
||||||
if (theme) {
|
if (theme) {
|
||||||
console.log(theme.version, "theme updated");
|
|
||||||
theme.colorScheme === "dark"
|
theme.colorScheme === "dark"
|
||||||
? useThemeStore.getState().setDarkTheme(theme)
|
? useThemeStore.getState().setDarkTheme(theme)
|
||||||
: useThemeStore.getState().setLightTheme(theme);
|
: useThemeStore.getState().setLightTheme(theme);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(console.log);
|
.catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
}, 1000);
|
}, 1000);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
@@ -144,7 +151,7 @@ export const withTheme = (Element: () => JSX.Element) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<I18nProvider i18n={i18n}>
|
<I18nProvider i18n={i18n}>
|
||||||
<Element />
|
<Element {...props} />
|
||||||
</I18nProvider>
|
</I18nProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -126,7 +126,6 @@ export async function setAppLockVerificationCipher(appLockPassword: string) {
|
|||||||
DatabaseLogger.info("setAppLockVerificationCipher");
|
DatabaseLogger.info("setAppLockVerificationCipher");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
DatabaseLogger.error(e);
|
DatabaseLogger.error(e);
|
||||||
console.log(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,7 +309,6 @@ export async function getCryptoKey() {
|
|||||||
|
|
||||||
return key;
|
return key;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("getCryptoKey", e);
|
|
||||||
DatabaseLogger.error(e);
|
DatabaseLogger.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,8 +39,6 @@ export async function setupDatabase(password?: string) {
|
|||||||
const key = await getDatabaseKey(password);
|
const key = await getDatabaseKey(password);
|
||||||
if (!key) throw new Error(strings.databaseSetupFailed());
|
if (!key) throw new Error(strings.databaseSetupFailed());
|
||||||
|
|
||||||
console.log("Opening database with key:", !!key);
|
|
||||||
|
|
||||||
database.host({
|
database.host({
|
||||||
API_HOST: "https://api.notesnook.com",
|
API_HOST: "https://api.notesnook.com",
|
||||||
AUTH_HOST: "https://auth.streetwriters.co",
|
AUTH_HOST: "https://auth.streetwriters.co",
|
||||||
|
|||||||
@@ -27,13 +27,9 @@ import RNFetchBlob from "react-native-blob-util";
|
|||||||
*/
|
*/
|
||||||
export async function compressToBase64(path, type) {
|
export async function compressToBase64(path, type) {
|
||||||
const { width: screenWidth, scale } = Dimensions.get("window");
|
const { width: screenWidth, scale } = Dimensions.get("window");
|
||||||
console.log("COMPRESSING TO BASE64...");
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
console.log(path, "image path...");
|
|
||||||
|
|
||||||
Image.getSize(path, async (width) => {
|
Image.getSize(path, async (width) => {
|
||||||
console.log("image width", width);
|
|
||||||
|
|
||||||
const response = await ImageResizer.createResizedImage(
|
const response = await ImageResizer.createResizedImage(
|
||||||
path,
|
path,
|
||||||
screenWidth * scale,
|
screenWidth * scale,
|
||||||
@@ -55,10 +51,12 @@ export async function compressToBase64(path, type) {
|
|||||||
: response.uri,
|
: response.uri,
|
||||||
"base64"
|
"base64"
|
||||||
);
|
);
|
||||||
RNFetchBlob.fs.unlink(path.replace("file://", "")).catch(console.log);
|
RNFetchBlob.fs.unlink(path.replace("file://", "")).catch(() => {
|
||||||
RNFetchBlob.fs
|
/* empty */
|
||||||
.unlink(response.uri.replace("file://", ""))
|
});
|
||||||
.catch(console.log);
|
RNFetchBlob.fs.unlink(response.uri.replace("file://", "")).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
|
|
||||||
resolve(base64);
|
resolve(base64);
|
||||||
});
|
});
|
||||||
@@ -80,6 +78,8 @@ export async function compressToFile(path, type) {
|
|||||||
onlyScaleDown: true
|
onlyScaleDown: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
RNFetchBlob.fs.unlink(path.replace("file://", "")).catch(console.log);
|
RNFetchBlob.fs.unlink(path.replace("file://", "")).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
return response.uri;
|
return response.uri;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,9 @@ export async function downloadAttachments(attachmentIds: string[]) {
|
|||||||
|
|
||||||
const isCancelled = () => {
|
const isCancelled = () => {
|
||||||
if (useAttachmentStore.getState().downloading?.[groupId]?.canceled) {
|
if (useAttachmentStore.getState().downloading?.[groupId]?.canceled) {
|
||||||
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
|
RNFetchBlob.fs.unlink(zipSourceFolder).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
useAttachmentStore.getState().setDownloading({
|
useAttachmentStore.getState().setDownloading({
|
||||||
groupId,
|
groupId,
|
||||||
current: 0,
|
current: 0,
|
||||||
@@ -188,9 +190,13 @@ export async function downloadAttachments(attachmentIds: string[]) {
|
|||||||
ToastManager.error(e as Error, "Error zipping attachments");
|
ToastManager.error(e as Error, "Error zipping attachments");
|
||||||
}
|
}
|
||||||
// Remove source & zip file from cache.
|
// Remove source & zip file from cache.
|
||||||
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
|
RNFetchBlob.fs.unlink(zipSourceFolder).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
if (Platform.OS === "android") {
|
if (Platform.OS === "android") {
|
||||||
RNFetchBlob.fs.unlink(zipOutputFile).catch(console.log);
|
RNFetchBlob.fs.unlink(zipOutputFile).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,10 +327,14 @@ export default async function downloadAttachment(
|
|||||||
if (attachment.dateUploaded) {
|
if (attachment.dateUploaded) {
|
||||||
RNFetchBlob.fs
|
RNFetchBlob.fs
|
||||||
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}`)
|
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}`)
|
||||||
.catch(console.log);
|
.catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
RNFetchBlob.fs
|
RNFetchBlob.fs
|
||||||
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}_dcache`)
|
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}_dcache`)
|
||||||
.catch(console.log);
|
.catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
useAttachmentStore.getState().setDownloading({
|
useAttachmentStore.getState().setDownloading({
|
||||||
|
|||||||
@@ -123,7 +123,9 @@ export async function downloadFile(
|
|||||||
cancelToken.cancel = async (reason) => {
|
cancelToken.cancel = async (reason) => {
|
||||||
useAttachmentStore.getState().remove(filename);
|
useAttachmentStore.getState().remove(filename);
|
||||||
request.cancel();
|
request.cancel();
|
||||||
RNFetchBlob.fs.unlink(tempFilePath).catch(console.log);
|
RNFetchBlob.fs.unlink(tempFilePath).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
DatabaseLogger.log(`Download cancelled: ${reason} ${filename}`);
|
DatabaseLogger.log(`Download cancelled: ${reason} ${filename}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -142,7 +144,9 @@ export async function downloadFile(
|
|||||||
useAttachmentStore.getState().remove(filename);
|
useAttachmentStore.getState().remove(filename);
|
||||||
|
|
||||||
if (await exists(originalFilePath)) {
|
if (await exists(originalFilePath)) {
|
||||||
await RNFetchBlob.fs.unlink(originalFilePath).catch(console.log);
|
await RNFetchBlob.fs.unlink(originalFilePath).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await RNFetchBlob.fs.mv(tempFilePath, originalFilePath);
|
await RNFetchBlob.fs.mv(tempFilePath, originalFilePath);
|
||||||
@@ -169,8 +173,12 @@ export async function downloadFile(
|
|||||||
}
|
}
|
||||||
|
|
||||||
useAttachmentStore.getState().remove(filename);
|
useAttachmentStore.getState().remove(filename);
|
||||||
RNFetchBlob.fs.unlink(tempFilePath).catch(console.log);
|
RNFetchBlob.fs.unlink(tempFilePath).catch(() => {
|
||||||
RNFetchBlob.fs.unlink(originalFilePath).catch(console.log);
|
/* empty */
|
||||||
|
});
|
||||||
|
RNFetchBlob.fs.unlink(originalFilePath).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
DatabaseLogger.error(e, "Download failed: ", {
|
DatabaseLogger.error(e, "Download failed: ", {
|
||||||
url
|
url
|
||||||
});
|
});
|
||||||
@@ -188,7 +196,7 @@ export async function checkAttachment(hash: string) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const size = await getUploadedFileSize(hash);
|
const size = await getUploadedFileSize(hash);
|
||||||
console.log("File Size", size);
|
|
||||||
if (size === -1) return { success: true };
|
if (size === -1) return { success: true };
|
||||||
|
|
||||||
if (size === 0)
|
if (size === 0)
|
||||||
|
|||||||
@@ -60,7 +60,9 @@ export async function readEncrypted<TOutputFormat extends DataFormat>(
|
|||||||
|
|
||||||
return output as Output<TOutputFormat>;
|
return output as Output<TOutputFormat>;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
RNFetchBlob.fs.unlink(path).catch(console.log);
|
RNFetchBlob.fs.unlink(path).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
DatabaseLogger.error(e);
|
DatabaseLogger.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,8 +92,9 @@ export async function writeEncryptedBase64(
|
|||||||
type: "url"
|
type: "url"
|
||||||
});
|
});
|
||||||
|
|
||||||
RNFetchBlob.fs.unlink(filepath).catch(console.log);
|
RNFetchBlob.fs.unlink(filepath).catch(() => {
|
||||||
console.log("encrypted file output: ", output);
|
/* empty */
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...output,
|
...output,
|
||||||
@@ -106,7 +109,9 @@ export async function deleteFile(
|
|||||||
await createCacheDir();
|
await createCacheDir();
|
||||||
const localFilePath = cacheDir + `/${filename}`;
|
const localFilePath = cacheDir + `/${filename}`;
|
||||||
if (!requestOptions) {
|
if (!requestOptions) {
|
||||||
RNFetchBlob.fs.unlink(localFilePath).catch(console.log);
|
RNFetchBlob.fs.unlink(localFilePath).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +122,9 @@ export async function deleteFile(
|
|||||||
const status = response.info().status;
|
const status = response.info().status;
|
||||||
const ok = status >= 200 && status < 300;
|
const ok = status >= 200 && status < 300;
|
||||||
if (ok) {
|
if (ok) {
|
||||||
RNFetchBlob.fs.unlink(localFilePath).catch(console.log);
|
RNFetchBlob.fs.unlink(localFilePath).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return ok;
|
return ok;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -134,10 +141,14 @@ export async function clearFileStorage() {
|
|||||||
const oldCache = await RNFetchBlob.fs.ls(cacheDirOld);
|
const oldCache = await RNFetchBlob.fs.ls(cacheDirOld);
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
await RNFetchBlob.fs.unlink(cacheDir + `/${file}`).catch(console.log);
|
await RNFetchBlob.fs.unlink(cacheDir + `/${file}`).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
}
|
}
|
||||||
for (const file of oldCache) {
|
for (const file of oldCache) {
|
||||||
await RNFetchBlob.fs.unlink(cacheDirOld + `/${file}`).catch(console.log);
|
await RNFetchBlob.fs.unlink(cacheDirOld + `/${file}`).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
DatabaseLogger.error(e, "clearFileStorage");
|
DatabaseLogger.error(e, "clearFileStorage");
|
||||||
@@ -161,20 +172,19 @@ export async function migrateFilesFromCache() {
|
|||||||
const migratedFilesPath = cacheDir + "/.migrated_1";
|
const migratedFilesPath = cacheDir + "/.migrated_1";
|
||||||
const migrated = await RNFetchBlob.fs.exists(migratedFilesPath);
|
const migrated = await RNFetchBlob.fs.exists(migratedFilesPath);
|
||||||
if (migrated) {
|
if (migrated) {
|
||||||
console.log("Files migrated already");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const files = await RNFetchBlob.fs.ls(cacheDir);
|
const files = await RNFetchBlob.fs.ls(cacheDir);
|
||||||
console.log("Files to migrate:", files.join(","));
|
|
||||||
|
|
||||||
const oldCache = await RNFetchBlob.fs.ls(cacheDirOld);
|
const oldCache = await RNFetchBlob.fs.ls(cacheDirOld);
|
||||||
for (const file of oldCache) {
|
for (const file of oldCache) {
|
||||||
if (file.startsWith("org.") || file.startsWith("com.")) continue;
|
if (file.startsWith("org.") || file.startsWith("com.")) continue;
|
||||||
RNFetchBlob.fs
|
RNFetchBlob.fs
|
||||||
.mv(cacheDirOld + `/${file}`, cacheDir + `/${file}`)
|
.mv(cacheDirOld + `/${file}`, cacheDir + `/${file}`)
|
||||||
.catch(console.log);
|
.catch(() => {
|
||||||
console.log("Moved", file);
|
/* empty */
|
||||||
|
});
|
||||||
}
|
}
|
||||||
await RNFetchBlob.fs.createFile(migratedFilesPath, "1", "utf8");
|
await RNFetchBlob.fs.createFile(migratedFilesPath, "1", "utf8");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -183,13 +193,17 @@ export async function migrateFilesFromCache() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function clearCache() {
|
export async function clearCache() {
|
||||||
await RNFetchBlob.fs.unlink(cacheDir).catch(console.log);
|
await RNFetchBlob.fs.unlink(cacheDir).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
await createCacheDir();
|
await createCacheDir();
|
||||||
eSendEvent("cache-cleared");
|
eSendEvent("cache-cleared");
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteCacheFileByPath(path: string) {
|
export async function deleteCacheFileByPath(path: string) {
|
||||||
await RNFetchBlob.fs.unlink(path).catch(console.log);
|
await RNFetchBlob.fs.unlink(path).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteCacheFileByName(name: string) {
|
export async function deleteCacheFileByName(name: string) {
|
||||||
@@ -198,15 +212,21 @@ export async function deleteCacheFileByName(name: string) {
|
|||||||
? await (RNFetchBlob.fs as any).pathForAppGroup(IOS_APPGROUPID)
|
? await (RNFetchBlob.fs as any).pathForAppGroup(IOS_APPGROUPID)
|
||||||
: null;
|
: null;
|
||||||
const appGroupPath = `${iosAppGroup}/${name}`;
|
const appGroupPath = `${iosAppGroup}/${name}`;
|
||||||
await RNFetchBlob.fs.unlink(appGroupPath).catch(console.log);
|
await RNFetchBlob.fs.unlink(appGroupPath).catch(() => {
|
||||||
await RNFetchBlob.fs.unlink(`${cacheDir}/${name}`).catch(console.log);
|
/* empty */
|
||||||
|
});
|
||||||
|
await RNFetchBlob.fs.unlink(`${cacheDir}/${name}`).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteDCacheFiles() {
|
export async function deleteDCacheFiles() {
|
||||||
const files = await RNFetchBlob.fs.ls(cacheDir);
|
const files = await RNFetchBlob.fs.ls(cacheDir);
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (file.includes("_dcache")) {
|
if (file.includes("_dcache") || file.startsWith("NN_")) {
|
||||||
await RNFetchBlob.fs.unlink(file).catch(console.log);
|
await RNFetchBlob.fs.unlink(file).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -245,7 +265,9 @@ export async function exists(filename: string) {
|
|||||||
);
|
);
|
||||||
RNFetchBlob.fs
|
RNFetchBlob.fs
|
||||||
.unlink(existsInAppGroup ? appGroupPath : path)
|
.unlink(existsInAppGroup ? appGroupPath : path)
|
||||||
.catch(console.log);
|
.catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +303,7 @@ export async function getCacheSize() {
|
|||||||
await createCacheDir();
|
await createCacheDir();
|
||||||
const stat = await RNFetchBlob.fs.lstat(`file://` + cacheDir);
|
const stat = await RNFetchBlob.fs.lstat(`file://` + cacheDir);
|
||||||
let total = 0;
|
let total = 0;
|
||||||
console.log("Total files", stat.length);
|
|
||||||
stat.forEach((file) => {
|
stat.forEach((file) => {
|
||||||
total += parseInt(file.size as unknown as string);
|
total += parseInt(file.size as unknown as string);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ export const FileSizeResult = {
|
|||||||
Error: -1
|
Error: -1
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function getUploadedFileSize(hash: string) {
|
export async function getUploadedFileSize(hash: string, retry = 0) {
|
||||||
try {
|
try {
|
||||||
const url = `${hosts.API_HOST}/s3?name=${hash}`;
|
const url = `${hosts.API_HOST}/s3?name=${hash}`;
|
||||||
const token = await db.tokenManager.getAccessToken();
|
const token = await db.tokenManager.getAccessToken();
|
||||||
@@ -123,8 +123,24 @@ export async function getUploadedFileSize(hash: string) {
|
|||||||
method: "HEAD",
|
method: "HEAD",
|
||||||
headers: { Authorization: `Bearer ${token}` }
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
!attachmentInfo.ok ||
|
||||||
|
attachmentInfo.headers?.get("content-length") === null
|
||||||
|
) {
|
||||||
|
if (retry < 3) {
|
||||||
|
DatabaseLogger.log(`Retrying file size check: ${hash}, ${retry}`);
|
||||||
|
return getUploadedFileSize(hash, retry + 1);
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
`File size check failed: ${hash}, ${
|
||||||
|
attachmentInfo.status
|
||||||
|
}, ${attachmentInfo.headers?.get("content-length")}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const contentLength = parseInt(
|
const contentLength = parseInt(
|
||||||
attachmentInfo.headers?.get("content-length") || "0"
|
attachmentInfo.headers?.get("content-length") as string
|
||||||
);
|
);
|
||||||
return isNaN(contentLength) ? FileSizeResult.Empty : contentLength;
|
return isNaN(contentLength) ? FileSizeResult.Empty : contentLength;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -17,9 +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/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function info(context: string, ...logs: unknown[]) {
|
function info(context: string, ...logs: unknown[]) {}
|
||||||
console.log(`${new Date().toLocaleDateString()}::info::${context}:`, ...logs);
|
|
||||||
}
|
|
||||||
|
|
||||||
function error(context: string, ...logs: unknown[]) {
|
function error(context: string, ...logs: unknown[]) {
|
||||||
console.log(
|
console.log(
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
|
|||||||
await sleep(500);
|
await sleep(500);
|
||||||
}
|
}
|
||||||
if (item.type === "link") {
|
if (item.type === "link") {
|
||||||
Linking.openURL(item.data).catch(console.log);
|
Linking.openURL(item.data).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
} else if (item.type === "promo") {
|
} else if (item.type === "promo") {
|
||||||
presentSheet({
|
presentSheet({
|
||||||
component: (
|
component: (
|
||||||
|
|||||||
@@ -112,14 +112,19 @@ const AppLockedOverlay = () => {
|
|||||||
if (
|
if (
|
||||||
!biometricsAuthEnabled ||
|
!biometricsAuthEnabled ||
|
||||||
!(await BiometricService.isBiometryAvailable())
|
!(await BiometricService.isBiometryAvailable())
|
||||||
)
|
) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
biometricUnlockAwaitingUserInput.current = true;
|
biometricUnlockAwaitingUserInput.current = true;
|
||||||
|
|
||||||
if (Platform.OS === "android") {
|
if (Platform.OS === "android") {
|
||||||
const activityName = await NotesnookModule.getActivityName();
|
const activityName = await NotesnookModule.getActivityName();
|
||||||
if (activityName !== "MainActivity") return;
|
if (
|
||||||
|
activityName !== "MainActivity" &&
|
||||||
|
activityName !== "NotePreviewConfigureActivity"
|
||||||
|
)
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
useSettingStore.getState().setRequestBiometrics(true);
|
useSettingStore.getState().setRequestBiometrics(true);
|
||||||
|
|
||||||
@@ -180,7 +185,6 @@ const AppLockedOverlay = () => {
|
|||||||
DatabaseLogger.info("Locking app on entering foreground");
|
DatabaseLogger.info("Locking app on entering foreground");
|
||||||
useUserStore.getState().lockApp(true);
|
useUserStore.getState().lockApp(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!(
|
!(
|
||||||
biometricUnlockAwaitingUserInput.current ||
|
biometricUnlockAwaitingUserInput.current ||
|
||||||
|
|||||||
@@ -182,18 +182,17 @@ const Actions = ({
|
|||||||
relations
|
relations
|
||||||
.map((relation) => relation.fromId)
|
.map((relation) => relation.fromId)
|
||||||
.forEach(async (id) => {
|
.forEach(async (id) => {
|
||||||
const tab = useTabStore.getState().getTabForNote(id);
|
useTabStore.getState().forEachNoteTab(id, async (tab) => {
|
||||||
if (tab !== undefined) {
|
const isFocused = useTabStore.getState().currentTab === tab.id;
|
||||||
const isFocused = useTabStore.getState().currentTab === tab;
|
|
||||||
if (isFocused) {
|
if (isFocused) {
|
||||||
eSendEvent(eOnLoadNote, {
|
eSendEvent(eOnLoadNote, {
|
||||||
item: await db.notes.note(id),
|
item: await db.notes.note(id),
|
||||||
forced: true
|
forced: true
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
editorController.current.commands.setLoading(true, tab);
|
editorController.current.commands.setLoading(true, tab.id);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
close?.();
|
close?.();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -528,7 +528,9 @@ export const AttachmentDialog = ({
|
|||||||
setAttachments(results);
|
setAttachments(results);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
})
|
})
|
||||||
.catch(console.log);
|
.catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ export const ChangePassword = () => {
|
|||||||
await sleep(300);
|
await sleep(300);
|
||||||
eSendEvent(eOpenRecoveryKeyDialog);
|
eSendEvent(eOpenRecoveryKeyDialog);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e.stack);
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
heading: strings.passwordChangeFailed(),
|
heading: strings.passwordChangeFailed(),
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export const useLogin = (onFinishLogin, sessionExpired = false) => {
|
|||||||
switch (step) {
|
switch (step) {
|
||||||
case LoginSteps.emailAuth: {
|
case LoginSteps.emailAuth: {
|
||||||
const mfaInfo = await db.user.authenticateEmail(email.current);
|
const mfaInfo = await db.user.authenticateEmail(email.current);
|
||||||
console.log("email auth", mfaInfo);
|
|
||||||
if (mfaInfo) {
|
if (mfaInfo) {
|
||||||
TwoFactorVerification.present(
|
TwoFactorVerification.present(
|
||||||
async (mfa, callback) => {
|
async (mfa, callback) => {
|
||||||
|
|||||||
@@ -75,9 +75,9 @@ const BaseDialog = ({
|
|||||||
background
|
background
|
||||||
}: BaseDialogProps) => {
|
}: BaseDialogProps) => {
|
||||||
const floating = useIsFloatingKeyboard();
|
const floating = useIsFloatingKeyboard();
|
||||||
const appState = useAppState();
|
|
||||||
const lockEvents = useRef(false);
|
const lockEvents = useRef(false);
|
||||||
const [internalVisible, setIntervalVisible] = useState(true);
|
const [internalVisible, setIntervalVisible] = useState(true);
|
||||||
|
const locked = useUserStore((state) => state.appLocked);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
@@ -86,26 +86,20 @@ const BaseDialog = ({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (useUserStore.getState().disableAppLockRequests) return;
|
if (locked) {
|
||||||
|
setIntervalVisible(false);
|
||||||
if (SettingsService.canLockAppInBackground()) {
|
lockEvents.current = true;
|
||||||
if (appState === "background") {
|
const unsub = useUserStore.subscribe((state) => {
|
||||||
setIntervalVisible(false);
|
if (!state.appLocked) {
|
||||||
if (useUserStore.getState().appLocked) {
|
setIntervalVisible(true);
|
||||||
lockEvents.current = true;
|
unsub();
|
||||||
const unsub = useUserStore.subscribe((state) => {
|
setTimeout(() => {
|
||||||
if (!state.appLocked) {
|
lockEvents.current = false;
|
||||||
setIntervalVisible(true);
|
|
||||||
unsub();
|
|
||||||
setTimeout(() => {
|
|
||||||
lockEvents.current = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
}, [appState]);
|
}, [locked]);
|
||||||
|
|
||||||
const Wrapper = useSafeArea ? SafeAreaView : View;
|
const Wrapper = useSafeArea ? SafeAreaView : View;
|
||||||
|
|
||||||
|
|||||||
@@ -80,8 +80,9 @@ export const Dialog = ({ context = "global" }) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setChecked(false);
|
||||||
hide();
|
values.current.inputValue = undefined;
|
||||||
|
setVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const show = useCallback(
|
const show = useCallback(
|
||||||
@@ -104,13 +105,14 @@ export const Dialog = ({ context = "global" }) => {
|
|||||||
eUnSubscribeEvent(eOpenSimpleDialog, show);
|
eUnSubscribeEvent(eOpenSimpleDialog, show);
|
||||||
eUnSubscribeEvent(eCloseSimpleDialog, hide);
|
eUnSubscribeEvent(eCloseSimpleDialog, hide);
|
||||||
};
|
};
|
||||||
}, [show]);
|
}, [hide, show]);
|
||||||
|
|
||||||
const hide = () => {
|
const hide = React.useCallback(() => {
|
||||||
setChecked(false);
|
setChecked(false);
|
||||||
values.current.inputValue = undefined;
|
values.current.inputValue = undefined;
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
};
|
dialogInfo.onClose?.();
|
||||||
|
}, [dialogInfo]);
|
||||||
|
|
||||||
const onNegativePress = async () => {
|
const onNegativePress = async () => {
|
||||||
if (dialogInfo.onClose) {
|
if (dialogInfo.onClose) {
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ AttachImage.present = (response: ImageType[], context?: string) => {
|
|||||||
close={close}
|
close={close}
|
||||||
onAttach={(result) => {
|
onAttach={(result) => {
|
||||||
resolved = true;
|
resolved = true;
|
||||||
console.log("closing");
|
|
||||||
resolve(result);
|
resolve(result);
|
||||||
close?.();
|
close?.();
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -307,9 +307,7 @@ const PDFPreview = () => {
|
|||||||
password={password}
|
password={password}
|
||||||
maxScale={6}
|
maxScale={6}
|
||||||
onError={onError}
|
onError={onError}
|
||||||
onPressLink={(uri) => {
|
onPressLink={(uri) => {}}
|
||||||
console.log(`Link pressed: ${uri}`);
|
|
||||||
}}
|
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
width: width,
|
width: width,
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export default function Progress() {
|
|||||||
eSubscribeEvent(PROGRESS_EVENTS.start, (options: ProgressOptions) => {
|
eSubscribeEvent(PROGRESS_EVENTS.start, (options: ProgressOptions) => {
|
||||||
setProgress(options.progress);
|
setProgress(options.progress);
|
||||||
cancelCallback.current = options.cancelCallback;
|
cancelCallback.current = options.cancelCallback;
|
||||||
console.log("options", options.fillBackground);
|
|
||||||
setData({
|
setData({
|
||||||
title: options.title,
|
title: options.title,
|
||||||
paragraph: options.paragraph,
|
paragraph: options.paragraph,
|
||||||
|
|||||||
@@ -336,7 +336,7 @@ export class VaultDialog extends Component {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log("VAULT UPDATED EVENT");
|
|
||||||
eSendEvent("vaultUpdated");
|
eSendEvent("vaultUpdated");
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false
|
loading: false
|
||||||
@@ -406,7 +406,6 @@ export class VaultDialog extends Component {
|
|||||||
} else {
|
} else {
|
||||||
await db.vault.add(this.state.note.id);
|
await db.vault.add(this.state.note.id);
|
||||||
|
|
||||||
console.log("update note event...");
|
|
||||||
eSendEvent(eUpdateNoteInEditor, this.state.note, true);
|
eSendEvent(eUpdateNoteInEditor, this.state.note, true);
|
||||||
|
|
||||||
this.close();
|
this.close();
|
||||||
@@ -543,7 +542,6 @@ export class VaultDialog extends Component {
|
|||||||
this.close();
|
this.close();
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.log("Error", e);
|
|
||||||
this._takeErrorAction(e);
|
this._takeErrorAction(e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,10 +78,10 @@ class ExceptionHandler extends React.Component<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const withErrorBoundry = (Element: React.ElementType, name: string) => {
|
export const withErrorBoundry = (Element: React.ElementType, name: string) => {
|
||||||
return function ErrorBoundary() {
|
return function ErrorBoundary(props: any) {
|
||||||
return (
|
return (
|
||||||
<ExceptionHandler component={name}>
|
<ExceptionHandler component={name}>
|
||||||
<Element />
|
<Element {...props} />
|
||||||
</ExceptionHandler>
|
</ExceptionHandler>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export const Header = ({
|
|||||||
onSearch
|
onSearch
|
||||||
}: {
|
}: {
|
||||||
onLeftMenuButtonPress?: () => void;
|
onLeftMenuButtonPress?: () => void;
|
||||||
renderedInRoute: RouteName;
|
renderedInRoute?: RouteName;
|
||||||
id?: string;
|
id?: string;
|
||||||
title: string;
|
title: string;
|
||||||
headerRightButtons?: HeaderRightButton[];
|
headerRightButtons?: HeaderRightButton[];
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export const RightMenus = ({
|
|||||||
onSearch
|
onSearch
|
||||||
}: {
|
}: {
|
||||||
headerRightButtons?: HeaderRightButton[];
|
headerRightButtons?: HeaderRightButton[];
|
||||||
renderedInRoute: RouteName;
|
renderedInRoute?: RouteName;
|
||||||
id?: string;
|
id?: string;
|
||||||
onPressDefaultRightButton?: () => void;
|
onPressDefaultRightButton?: () => void;
|
||||||
search?: boolean;
|
search?: boolean;
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export const Title = ({
|
|||||||
isHiddenOnRender?: boolean;
|
isHiddenOnRender?: boolean;
|
||||||
accentColor?: string;
|
accentColor?: string;
|
||||||
isBeta?: boolean;
|
isBeta?: boolean;
|
||||||
renderedInRoute: string;
|
renderedInRoute?: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Sodium from "@ammarahmed/react-native-sodium";
|
import Sodium from "@ammarahmed/react-native-sodium";
|
||||||
import { DataURL } from "@notesnook/core";
|
import { DataURL, getFileNameWithExtension } from "@notesnook/core";
|
||||||
import type { ImageAttributes } from "@notesnook/editor";
|
import type { ImageAttributes } from "@notesnook/editor";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
@@ -34,6 +34,11 @@ import {
|
|||||||
import BaseDialog from "../dialog/base-dialog";
|
import BaseDialog from "../dialog/base-dialog";
|
||||||
import { IconButton } from "../ui/icon-button";
|
import { IconButton } from "../ui/icon-button";
|
||||||
import { ProgressBarComponent } from "../ui/svg/lazy";
|
import { ProgressBarComponent } from "../ui/svg/lazy";
|
||||||
|
import RNFetchBlob from "react-native-blob-util";
|
||||||
|
import Share from "react-native-share";
|
||||||
|
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||||
|
import { useSettingStore } from "../../stores/use-setting-store";
|
||||||
|
|
||||||
const ImagePreview = () => {
|
const ImagePreview = () => {
|
||||||
const { colors } = useThemeColors("dialog");
|
const { colors } = useThemeColors("dialog");
|
||||||
|
|
||||||
@@ -41,6 +46,8 @@ const ImagePreview = () => {
|
|||||||
const [image, setImage] = useState<string>();
|
const [image, setImage] = useState<string>();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const imageRef = useRef<ImageAttributes>();
|
const imageRef = useRef<ImageAttributes>();
|
||||||
|
const insets = useGlobalSafeAreaInsets();
|
||||||
|
const [showHeader, setShowHeader] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
eSubscribeEvent("ImagePreview", open);
|
eSubscribeEvent("ImagePreview", open);
|
||||||
@@ -73,16 +80,86 @@ const ImagePreview = () => {
|
|||||||
silent: true,
|
silent: true,
|
||||||
cache: true
|
cache: true
|
||||||
});
|
});
|
||||||
const path = `${cacheDir}/${uri}`;
|
|
||||||
|
if (!uri) {
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const attachment = await db.attachments.attachment(hash);
|
||||||
|
const path = `${cacheDir}/${
|
||||||
|
"NN_" + (await getFileNameWithExtension(hash, attachment?.mimeType))
|
||||||
|
}`;
|
||||||
|
await RNFetchBlob.fs.mv(`${cacheDir}/${uri}`, path).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
setImage("file://" + path);
|
setImage("file://" + path);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}, 100);
|
}, 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
const close = () => {
|
const close = React.useCallback(() => {
|
||||||
|
image &&
|
||||||
|
RNFetchBlob.fs.unlink(image.replace("file://", "")).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
setImage(undefined);
|
setImage(undefined);
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
};
|
}, [image]);
|
||||||
|
|
||||||
|
const renderHeader = React.useCallback(
|
||||||
|
() => (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
paddingTop: insets.top,
|
||||||
|
backgroundColor: "rgba(0,0,0,0.3)",
|
||||||
|
position: "absolute",
|
||||||
|
zIndex: 999,
|
||||||
|
display: showHeader ? "flex" : "none"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: "row",
|
||||||
|
width: "100%",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
alignItems: "center",
|
||||||
|
height: 50,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
gap: 10
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
name="share"
|
||||||
|
color="white"
|
||||||
|
style={{
|
||||||
|
borderWidth: 0
|
||||||
|
}}
|
||||||
|
onPress={async () => {
|
||||||
|
useSettingStore
|
||||||
|
.getState()
|
||||||
|
.setAppDidEnterBackgroundForAction(true);
|
||||||
|
await Share.open({
|
||||||
|
url: image
|
||||||
|
}).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
|
useSettingStore
|
||||||
|
.getState()
|
||||||
|
.setAppDidEnterBackgroundForAction(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
name="close"
|
||||||
|
color="white"
|
||||||
|
onPress={() => {
|
||||||
|
close();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
),
|
||||||
|
[close, image, insets.top, showHeader]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
visible && (
|
visible && (
|
||||||
@@ -90,6 +167,7 @@ const ImagePreview = () => {
|
|||||||
background="black"
|
background="black"
|
||||||
animation="slide"
|
animation="slide"
|
||||||
visible={true}
|
visible={true}
|
||||||
|
useSafeArea={false}
|
||||||
onRequestClose={close}
|
onRequestClose={close}
|
||||||
transparent
|
transparent
|
||||||
>
|
>
|
||||||
@@ -130,43 +208,25 @@ const ImagePreview = () => {
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<ImageViewer
|
<>
|
||||||
enableImageZoom={true}
|
<ImageViewer
|
||||||
renderIndicator={() => <></>}
|
enableImageZoom={true}
|
||||||
enableSwipeDown
|
renderIndicator={() => <></>}
|
||||||
useNativeDriver
|
enableSwipeDown
|
||||||
onSwipeDown={close}
|
useNativeDriver
|
||||||
saveToLocalByLongPress={false}
|
onSwipeDown={close}
|
||||||
renderHeader={() => (
|
saveToLocalByLongPress={false}
|
||||||
<View
|
onClick={() => {
|
||||||
style={{
|
setShowHeader(!showHeader);
|
||||||
flexDirection: "row",
|
}}
|
||||||
width: "100%",
|
renderHeader={renderHeader}
|
||||||
justifyContent: "flex-end",
|
imageUrls={[
|
||||||
alignItems: "center",
|
{
|
||||||
height: 50,
|
url: image as string
|
||||||
paddingHorizontal: 24,
|
}
|
||||||
position: "absolute",
|
]}
|
||||||
zIndex: 999,
|
/>
|
||||||
backgroundColor: "rgba(0,0,0,0.3)",
|
</>
|
||||||
marginTop: Platform.OS === "android" ? 30 : 0
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
name="close"
|
|
||||||
color="white"
|
|
||||||
onPress={() => {
|
|
||||||
close();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
imageUrls={[
|
|
||||||
{
|
|
||||||
url: image as string
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|||||||
@@ -76,7 +76,8 @@ const NoteItem = ({
|
|||||||
}: NoteItemProps) => {
|
}: NoteItemProps) => {
|
||||||
const isEditingNote = useTabStore(
|
const isEditingNote = useTabStore(
|
||||||
(state) =>
|
(state) =>
|
||||||
state.tabs.find((t) => t.id === state.currentTab)?.noteId === item.id
|
state.tabs.find((t) => t.id === state.currentTab)?.session?.noteId ===
|
||||||
|
item.id
|
||||||
);
|
);
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const compactMode = useIsCompactModeEnabled(
|
const compactMode = useIsCompactModeEnabled(
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ export const Filler = ({ item, color }: { item: Item; color?: string }) => {
|
|||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const isEditingNote = useTabStore(
|
const isEditingNote = useTabStore(
|
||||||
(state) =>
|
(state) =>
|
||||||
state.tabs.find((t) => t.id === state.currentTab)?.noteId === item.id
|
state.tabs.find((t) => t.id === state.currentTab)?.session?.noteId ===
|
||||||
|
item.id
|
||||||
);
|
);
|
||||||
|
|
||||||
const [selected] = useIsSelected(item);
|
const [selected] = useIsSelected(item);
|
||||||
|
|||||||
@@ -153,9 +153,7 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
|
|||||||
},
|
},
|
||||||
items?.cacheItem(index) ? 100 : 0
|
items?.cacheItem(index) ? 100 : 0
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
console.log("Error", e);
|
|
||||||
}
|
|
||||||
})();
|
})();
|
||||||
}, [index, items, refreshItem]);
|
}, [index, items, refreshItem]);
|
||||||
|
|
||||||
|
|||||||
@@ -119,7 +119,6 @@ export const PricingPlans = ({
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
console.log("error getting sku", e);
|
|
||||||
}
|
}
|
||||||
}, [promo?.promoCode]);
|
}, [promo?.promoCode]);
|
||||||
|
|
||||||
@@ -171,7 +170,6 @@ export const PricingPlans = ({
|
|||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("PROMOCODE ERROR:", code, e);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -231,7 +229,6 @@ export const PricingPlans = ({
|
|||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setBuying(false);
|
setBuying(false);
|
||||||
console.log(e);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
|
|||||||
close();
|
close();
|
||||||
eSendEvent(eOnLoadNote, {
|
eSendEvent(eOnLoadNote, {
|
||||||
item: item,
|
item: item,
|
||||||
presistTab: true
|
newTab: true
|
||||||
});
|
});
|
||||||
if (!DDS.isTab) {
|
if (!DDS.isTab) {
|
||||||
tabBarRef.current?.goToPage(1);
|
tabBarRef.current?.goToPage(1);
|
||||||
|
|||||||
@@ -100,7 +100,6 @@ export const AddNotebookSheet = ({
|
|||||||
eSendEvent(eOnNotebookUpdated, parent);
|
eSendEvent(eOnNotebookUpdated, parent);
|
||||||
if (notebook) {
|
if (notebook) {
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
console.log(parent, notebook.id);
|
|
||||||
eSendEvent(eOnNotebookUpdated, notebook.id);
|
eSendEvent(eOnNotebookUpdated, notebook.id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,13 +90,12 @@ export const NotebookItem = ({
|
|||||||
for (const key in keys) {
|
for (const key in keys) {
|
||||||
nextState[key] = !state.initialState[key] ? undefined : "deselected";
|
nextState[key] = !state.initialState[key] ? undefined : "deselected";
|
||||||
}
|
}
|
||||||
console.log("Single item selection");
|
|
||||||
state.setSelection({
|
state.setSelection({
|
||||||
[item.id]: "selected",
|
[item.id]: "selected",
|
||||||
...nextState
|
...nextState
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log("Multi item selection");
|
|
||||||
state.markAs(item, "selected");
|
state.markAs(item, "selected");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ GNU General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import { Note } from "@notesnook/core";
|
import { EVENTS, Note } from "@notesnook/core";
|
||||||
import { EVENTS } from "@notesnook/core";
|
import { strings } from "@notesnook/intl";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
@@ -25,27 +25,18 @@ import { FlatList } from "react-native-actions-sheet";
|
|||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { db } from "../../../common/database";
|
import { db } from "../../../common/database";
|
||||||
import { useDBItem } from "../../../hooks/use-db-item";
|
import { useDBItem } from "../../../hooks/use-db-item";
|
||||||
import { useTabStore } from "../../../screens/editor/tiptap/use-tab-store";
|
import {
|
||||||
|
TabItem,
|
||||||
|
useTabStore
|
||||||
|
} from "../../../screens/editor/tiptap/use-tab-store";
|
||||||
import { editorController } from "../../../screens/editor/tiptap/utils";
|
import { editorController } from "../../../screens/editor/tiptap/utils";
|
||||||
import { eSendEvent, presentSheet } from "../../../services/event-manager";
|
import { eSendEvent, presentSheet } from "../../../services/event-manager";
|
||||||
import { eUnlockNote } from "../../../utils/events";
|
import { eUnlockNote } from "../../../utils/events";
|
||||||
import { SIZE } from "../../../utils/size";
|
import { SIZE } from "../../../utils/size";
|
||||||
import { Button } from "../../ui/button";
|
|
||||||
import { IconButton } from "../../ui/icon-button";
|
import { IconButton } from "../../ui/icon-button";
|
||||||
import { Pressable } from "../../ui/pressable";
|
import { Pressable } from "../../ui/pressable";
|
||||||
import Heading from "../../ui/typography/heading";
|
import Heading from "../../ui/typography/heading";
|
||||||
import Paragraph from "../../ui/typography/paragraph";
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
import { strings } from "@notesnook/intl";
|
|
||||||
|
|
||||||
type TabItem = {
|
|
||||||
id: number;
|
|
||||||
noteId?: string;
|
|
||||||
previewTab?: boolean;
|
|
||||||
locked?: boolean;
|
|
||||||
noteLocked?: boolean;
|
|
||||||
readonly?: boolean;
|
|
||||||
pinned?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const TabItemComponent = (props: {
|
const TabItemComponent = (props: {
|
||||||
tab: TabItem;
|
tab: TabItem;
|
||||||
@@ -53,7 +44,7 @@ const TabItemComponent = (props: {
|
|||||||
close?: (ctx?: string | undefined) => void;
|
close?: (ctx?: string | undefined) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const [item, update] = useDBItem(props.tab.noteId, "note");
|
const [item, update] = useDBItem(props.tab.session?.noteId, "note");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const syncCompletedSubscription = db.eventManager?.subscribe(
|
const syncCompletedSubscription = db.eventManager?.subscribe(
|
||||||
@@ -85,11 +76,11 @@ const TabItemComponent = (props: {
|
|||||||
onPress={() => {
|
onPress={() => {
|
||||||
if (!props.isFocused) {
|
if (!props.isFocused) {
|
||||||
useTabStore.getState().focusTab(props.tab.id);
|
useTabStore.getState().focusTab(props.tab.id);
|
||||||
if (props.tab.locked) {
|
if (props.tab.session?.locked) {
|
||||||
eSendEvent(eUnlockNote);
|
eSendEvent(eUnlockNote);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!props.tab.noteId) {
|
if (!props.tab.session?.noteId) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
editorController?.current?.commands?.focus(props.tab.id);
|
editorController?.current?.commands?.focus(props.tab.id);
|
||||||
}, 300);
|
}, 300);
|
||||||
@@ -97,11 +88,6 @@ const TabItemComponent = (props: {
|
|||||||
}
|
}
|
||||||
props.close?.();
|
props.close?.();
|
||||||
}}
|
}}
|
||||||
onLongPress={() => {
|
|
||||||
useTabStore.getState().updateTab(props.tab.id, {
|
|
||||||
previewTab: false
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
@@ -112,9 +98,9 @@ const TabItemComponent = (props: {
|
|||||||
flexShrink: 1
|
flexShrink: 1
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.tab.noteLocked ? (
|
{props.tab.session?.noteLocked ? (
|
||||||
<>
|
<>
|
||||||
{props.tab.locked ? (
|
{props.tab.session?.locked ? (
|
||||||
<Icon size={SIZE.md} name="lock" />
|
<Icon size={SIZE.md} name="lock" />
|
||||||
) : (
|
) : (
|
||||||
<Icon size={SIZE.md} name="lock-open-outline" />
|
<Icon size={SIZE.md} name="lock-open-outline" />
|
||||||
@@ -122,7 +108,9 @@ const TabItemComponent = (props: {
|
|||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{props.tab.readonly ? <Icon size={SIZE.md} name="pencil-lock" /> : null}
|
{props.tab.session?.readonly ? (
|
||||||
|
<Icon size={SIZE.md} name="pencil-lock" />
|
||||||
|
) : null}
|
||||||
|
|
||||||
<Paragraph
|
<Paragraph
|
||||||
color={
|
color={
|
||||||
@@ -130,15 +118,10 @@ const TabItemComponent = (props: {
|
|||||||
? colors.selected.paragraph
|
? colors.selected.paragraph
|
||||||
: colors.primary.paragraph
|
: colors.primary.paragraph
|
||||||
}
|
}
|
||||||
style={{
|
|
||||||
fontFamily: props.tab.previewTab
|
|
||||||
? "OpenSans-Italic"
|
|
||||||
: "OpenSans-Regular"
|
|
||||||
}}
|
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
size={SIZE.md}
|
size={SIZE.md}
|
||||||
>
|
>
|
||||||
{props.tab.noteId
|
{props.tab.session?.noteId
|
||||||
? item?.title || strings.untitledNote()
|
? item?.title || strings.untitledNote()
|
||||||
: strings.newNote()}
|
: strings.newNote()}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
@@ -157,8 +140,7 @@ const TabItemComponent = (props: {
|
|||||||
color={props.tab.pinned ? colors.primary.accent : colors.primary.icon}
|
color={props.tab.pinned ? colors.primary.accent : colors.primary.icon}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
useTabStore.getState().updateTab(props.tab.id, {
|
useTabStore.getState().updateTab(props.tab.id, {
|
||||||
pinned: !props.tab.pinned,
|
pinned: !props.tab.pinned
|
||||||
previewTab: false
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
top={0}
|
top={0}
|
||||||
@@ -197,6 +179,7 @@ export default function EditorTabs({
|
|||||||
}: {
|
}: {
|
||||||
close?: (ctx?: string | undefined) => void;
|
close?: (ctx?: string | undefined) => void;
|
||||||
}) {
|
}) {
|
||||||
|
const { colors } = useThemeColors();
|
||||||
const [tabs, currentTab] = useTabStore((state) => [
|
const [tabs, currentTab] = useTabStore((state) => [
|
||||||
state.tabs,
|
state.tabs,
|
||||||
state.currentTab
|
state.currentTab
|
||||||
@@ -233,25 +216,13 @@ export default function EditorTabs({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Heading size={SIZE.lg}>{strings.tabs()}</Heading>
|
<Heading size={SIZE.lg}>{strings.tabs()}</Heading>
|
||||||
<Button
|
<IconButton
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
useTabStore.getState().newTab();
|
useTabStore.getState().newTab();
|
||||||
setTimeout(() => {
|
|
||||||
editorController?.current?.commands?.focus(
|
|
||||||
useTabStore.getState().currentTab
|
|
||||||
);
|
|
||||||
}, 500);
|
|
||||||
close?.();
|
close?.();
|
||||||
}}
|
}}
|
||||||
title={strings.newTab()}
|
name="plus"
|
||||||
icon="plus"
|
color={colors.primary.accent}
|
||||||
style={{
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "flex-start",
|
|
||||||
borderRadius: 100,
|
|
||||||
height: 35
|
|
||||||
}}
|
|
||||||
iconSize={SIZE.lg}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
|||||||
@@ -333,13 +333,17 @@ const ExportNotesSheet = ({
|
|||||||
if (Platform.OS === "ios") {
|
if (Platform.OS === "ios") {
|
||||||
Share.open({
|
Share.open({
|
||||||
url: result?.fileDir + result.fileName
|
url: result?.fileDir + result.fileName
|
||||||
}).catch(console.log);
|
}).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
FileViewer.open(result.filePath, {
|
FileViewer.open(result.filePath, {
|
||||||
showOpenWithDialog: true,
|
showOpenWithDialog: true,
|
||||||
showAppsSuggestions: true,
|
showAppsSuggestions: true,
|
||||||
shareFile: true
|
shareFile: true
|
||||||
} as any).catch(console.log);
|
} as any).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -62,7 +62,9 @@ export const ShareComponent = ({ uri, name, padding }) => {
|
|||||||
showOpenWithDialog: true,
|
showOpenWithDialog: true,
|
||||||
showAppsSuggestions: true,
|
showAppsSuggestions: true,
|
||||||
shareFile: true
|
shareFile: true
|
||||||
}).catch(console.log);
|
}).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -22,24 +22,22 @@ import {
|
|||||||
VirtualizedGrouping,
|
VirtualizedGrouping,
|
||||||
createInternalLink
|
createInternalLink
|
||||||
} from "@notesnook/core";
|
} from "@notesnook/core";
|
||||||
|
import type { LinkAttributes } from "@notesnook/editor";
|
||||||
|
import { NativeEvents } from "@notesnook/editor-mobile/src/utils/native-events";
|
||||||
|
import { strings } from "@notesnook/intl";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { TextInput, View } from "react-native";
|
import { TextInput, View } from "react-native";
|
||||||
import { FlatList } from "react-native-actions-sheet";
|
import { FlatList } from "react-native-actions-sheet";
|
||||||
import { db } from "../../../common/database";
|
import { db } from "../../../common/database";
|
||||||
import { useDBItem } from "../../../hooks/use-db-item";
|
import { useDBItem } from "../../../hooks/use-db-item";
|
||||||
|
import { editorController } from "../../../screens/editor/tiptap/utils";
|
||||||
import { presentSheet } from "../../../services/event-manager";
|
import { presentSheet } from "../../../services/event-manager";
|
||||||
import { SIZE } from "../../../utils/size";
|
import { SIZE } from "../../../utils/size";
|
||||||
import { Button } from "../../ui/button";
|
import { Button } from "../../ui/button";
|
||||||
import Input from "../../ui/input";
|
import Input from "../../ui/input";
|
||||||
import { Pressable } from "../../ui/pressable";
|
import { Pressable } from "../../ui/pressable";
|
||||||
import Paragraph from "../../ui/typography/paragraph";
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
import type { LinkAttributes } from "@notesnook/editor";
|
|
||||||
import {
|
|
||||||
EditorEvents,
|
|
||||||
editorController
|
|
||||||
} from "../../../screens/editor/tiptap/utils";
|
|
||||||
import { strings } from "@notesnook/intl";
|
|
||||||
|
|
||||||
const ListNoteItem = ({
|
const ListNoteItem = ({
|
||||||
id,
|
id,
|
||||||
@@ -194,7 +192,7 @@ export default function LinkNote(props: {
|
|||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
);
|
);
|
||||||
editorController.current?.postMessage(EditorEvents.resolve, {
|
editorController.current?.postMessage(NativeEvents.resolve, {
|
||||||
data: {
|
data: {
|
||||||
href: link,
|
href: link,
|
||||||
title: selectedNote.title
|
title: selectedNote.title
|
||||||
|
|||||||
@@ -113,11 +113,9 @@ const ManageTagsSheet = (props: {
|
|||||||
.sorted()
|
.sorted()
|
||||||
.then((items) => {
|
.then((items) => {
|
||||||
setTags(items);
|
setTags(items);
|
||||||
console.log("searched tags");
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
db.tags.all.sorted(db.settings.getGroupOptions("tags")).then((items) => {
|
db.tags.all.sorted(db.settings.getGroupOptions("tags")).then((items) => {
|
||||||
console.log("items loaded tags", items.placeholders.length);
|
|
||||||
setTags(items);
|
setTags(items);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -191,13 +191,17 @@ class RecoveryKeySheet extends React.Component {
|
|||||||
Share.open({
|
Share.open({
|
||||||
url: path,
|
url: path,
|
||||||
failOnCancel: false
|
failOnCancel: false
|
||||||
}).catch(console.log);
|
}).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
FileViewer.open(path, {
|
FileViewer.open(path, {
|
||||||
showOpenWithDialog: true,
|
showOpenWithDialog: true,
|
||||||
showAppsSuggestions: true,
|
showAppsSuggestions: true,
|
||||||
shareFile: true
|
shareFile: true
|
||||||
}).catch(console.log);
|
}).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ const ListNoteItem = ({
|
|||||||
listType,
|
listType,
|
||||||
noteInternalLinks.length
|
noteInternalLinks.length
|
||||||
]);
|
]);
|
||||||
console.log(noteInternalLinks);
|
|
||||||
const renderBlock = React.useCallback(
|
const renderBlock = React.useCallback(
|
||||||
(block: ContentBlock) => (
|
(block: ContentBlock) => (
|
||||||
<ListBlockItem
|
<ListBlockItem
|
||||||
@@ -419,7 +419,6 @@ export const ReferencesList = ({ item, close }: ReferencesListProps) => {
|
|||||||
id={index}
|
id={index}
|
||||||
items={items}
|
items={items}
|
||||||
onSelect={(note, blockId) => {
|
onSelect={(note, blockId) => {
|
||||||
console.log(note.id, blockId);
|
|
||||||
eSendEvent(eOnLoadNote, {
|
eSendEvent(eOnLoadNote, {
|
||||||
item: note,
|
item: note,
|
||||||
blockId: blockId
|
blockId: blockId
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const Sort = ({ type, screen }) => {
|
|||||||
);
|
);
|
||||||
const updateGroupOptions = async (_groupOptions) => {
|
const updateGroupOptions = async (_groupOptions) => {
|
||||||
const groupType = screen === "Notes" ? "home" : type + "s";
|
const groupType = screen === "Notes" ? "home" : type + "s";
|
||||||
console.log("updateGroupOptions for group", groupType, "in", screen);
|
|
||||||
await db.settings.setGroupOptions(groupType, _groupOptions);
|
await db.settings.setGroupOptions(groupType, _groupOptions);
|
||||||
setGroupOptions(_groupOptions);
|
setGroupOptions(_groupOptions);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export const Update = ({ version: appVersion, fwdRef }) => {
|
|||||||
let notes = version?.notes
|
let notes = version?.notes
|
||||||
? version.notes.replace("Thank you for using Notesnook!", "").split("- ")
|
? version.notes.replace("Thank you for using Notesnook!", "").split("- ")
|
||||||
: ["Bug fixes and performance improvements"];
|
: ["Bug fixes and performance improvements"];
|
||||||
notes = notes?.map((n) => n.replace(/\n/g, ""));
|
notes = notes?.map((n) => n.replace(/\n|<br>/g, ""));
|
||||||
const isGithubRelease = Config.GITHUB_RELEASE === "true";
|
const isGithubRelease = Config.GITHUB_RELEASE === "true";
|
||||||
|
|
||||||
const getSupportedAbi = () => {
|
const getSupportedAbi = () => {
|
||||||
@@ -178,18 +178,20 @@ export const Update = ({ version: appVersion, fwdRef }) => {
|
|||||||
{version.body}
|
{version.body}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
) : null}
|
) : null}
|
||||||
{notes.map((item) => (
|
{notes.map((item) =>
|
||||||
<Paragraph
|
item && item !== "" ? (
|
||||||
key={item}
|
<Paragraph
|
||||||
color={colors.secondary.paragraph}
|
key={item}
|
||||||
style={{
|
color={colors.secondary.paragraph}
|
||||||
marginBottom: 5
|
style={{
|
||||||
}}
|
marginBottom: 5
|
||||||
selectable
|
}}
|
||||||
>
|
selectable
|
||||||
• {item}
|
>
|
||||||
</Paragraph>
|
• {item}
|
||||||
))}
|
</Paragraph>
|
||||||
|
) : null
|
||||||
|
)}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
<Seperator />
|
<Seperator />
|
||||||
<Button
|
<Button
|
||||||
@@ -215,7 +217,9 @@ export const Update = ({ version: appVersion, fwdRef }) => {
|
|||||||
marginTop: 10
|
marginTop: 10
|
||||||
}}
|
}}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
Linking.openURL(GITHUB_PAGE_URL).catch(console.log);
|
Linking.openURL(GITHUB_PAGE_URL).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{strings.readReleaseNotes()}
|
{strings.readReleaseNotes()}
|
||||||
|
|||||||
@@ -263,20 +263,22 @@ export const Pressable = ({
|
|||||||
const getStyle = useCallback(
|
const getStyle = useCallback(
|
||||||
({ pressed }: PressableStateCallbackType): ViewStyle | ViewStyle[] => [
|
({ pressed }: PressableStateCallbackType): ViewStyle | ViewStyle[] => [
|
||||||
{
|
{
|
||||||
backgroundColor: pressed
|
backgroundColor:
|
||||||
? RGB_Linear_Shade(alpha, hexToRGBA(selectedColor, opacity || 1))
|
pressed && !disabled
|
||||||
: hexToRGBA(primaryColor, opacity || 1),
|
? RGB_Linear_Shade(alpha, hexToRGBA(selectedColor, opacity || 1))
|
||||||
|
: hexToRGBA(primaryColor, opacity || 1),
|
||||||
width: "100%",
|
width: "100%",
|
||||||
alignSelf: "center",
|
alignSelf: "center",
|
||||||
borderRadius: noborder ? 0 : br,
|
borderRadius: noborder ? 0 : br,
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
marginBottom: 0,
|
marginBottom: 0,
|
||||||
borderColor: pressed
|
borderColor:
|
||||||
? customSelectedColor
|
pressed && !disabled
|
||||||
? getColorLinearShade(customSelectedColor, 0.3, false)
|
? customSelectedColor
|
||||||
: borderSelectedColor || borderColor
|
? getColorLinearShade(customSelectedColor, 0.3, false)
|
||||||
: borderColor || "transparent",
|
: borderSelectedColor || borderColor
|
||||||
|
: borderColor || "transparent",
|
||||||
borderWidth: borderWidth
|
borderWidth: borderWidth
|
||||||
},
|
},
|
||||||
style,
|
style,
|
||||||
@@ -298,7 +300,8 @@ export const Pressable = ({
|
|||||||
borderColor,
|
borderColor,
|
||||||
borderWidth,
|
borderWidth,
|
||||||
style,
|
style,
|
||||||
growFactor
|
growFactor,
|
||||||
|
disabled
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -59,8 +59,9 @@ const SheetWrapper = ({
|
|||||||
const smallTablet = deviceMode === "smallTablet";
|
const smallTablet = deviceMode === "smallTablet";
|
||||||
const dimensions = useSettingStore((state) => state.dimensions);
|
const dimensions = useSettingStore((state) => state.dimensions);
|
||||||
const insets = useGlobalSafeAreaInsets();
|
const insets = useGlobalSafeAreaInsets();
|
||||||
const appState = useAppState();
|
|
||||||
const lockEvents = useRef(false);
|
const lockEvents = useRef(false);
|
||||||
|
const locked = useUserStore((state) => state.appLocked);
|
||||||
|
|
||||||
let width = dimensions.width > 600 ? 600 : 500;
|
let width = dimensions.width > 600 ? 600 : 500;
|
||||||
|
|
||||||
const style = React.useMemo(() => {
|
const style = React.useMemo(() => {
|
||||||
@@ -99,24 +100,21 @@ const SheetWrapper = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (useUserStore.getState().disableAppLockRequests) return;
|
if (locked) {
|
||||||
if (SettingsService.canLockAppInBackground()) {
|
const ref = fwdRef || localRef;
|
||||||
if (appState === "background") {
|
ref?.current?.hide();
|
||||||
const ref = fwdRef || localRef;
|
if (useUserStore.getState().appLocked) {
|
||||||
ref?.current?.hide();
|
lockEvents.current = true;
|
||||||
if (useUserStore.getState().appLocked) {
|
const unsub = useUserStore.subscribe((state) => {
|
||||||
lockEvents.current = true;
|
if (!state.appLocked) {
|
||||||
const unsub = useUserStore.subscribe((state) => {
|
ref?.current?.show();
|
||||||
if (!state.appLocked) {
|
unsub();
|
||||||
ref?.current?.show();
|
lockEvents.current = false;
|
||||||
unsub();
|
}
|
||||||
lockEvents.current = false;
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [appState, fwdRef]);
|
}, [locked, fwdRef]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScopedThemeProvider value="sheet">
|
<ScopedThemeProvider value="sheet">
|
||||||
|
|||||||
@@ -315,7 +315,9 @@ const trialstarted: { id: string; steps: TStep[] } = {
|
|||||||
actionButton: {
|
actionButton: {
|
||||||
text: strings.joinDiscord(),
|
text: strings.joinDiscord(),
|
||||||
action: () => {
|
action: () => {
|
||||||
Linking.openURL("https://discord.gg/zQBK97EE22").catch(console.log);
|
Linking.openURL("https://discord.gg/zQBK97EE22").catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -366,7 +368,9 @@ const Support = () => {
|
|||||||
width: "90%"
|
width: "90%"
|
||||||
}}
|
}}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
Linking.openURL("https://discord.gg/zQBK97EE22").catch(console.log);
|
Linking.openURL("https://discord.gg/zQBK97EE22").catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
icon="discord"
|
icon="discord"
|
||||||
type="secondary"
|
type="secondary"
|
||||||
@@ -380,7 +384,9 @@ const Support = () => {
|
|||||||
width: "90%"
|
width: "90%"
|
||||||
}}
|
}}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
Linking.openURL("https://t.me/notesnook").catch(console.log);
|
Linking.openURL("https://t.me/notesnook").catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
icon="telegram"
|
icon="telegram"
|
||||||
type="secondary"
|
type="secondary"
|
||||||
|
|||||||
@@ -19,14 +19,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
/* eslint-disable no-inner-declarations */
|
/* eslint-disable no-inner-declarations */
|
||||||
import {
|
import {
|
||||||
Color,
|
Color,
|
||||||
|
createInternalLink,
|
||||||
ItemReference,
|
ItemReference,
|
||||||
Note,
|
Note,
|
||||||
Notebook,
|
Notebook,
|
||||||
Reminder,
|
Reminder,
|
||||||
Tag,
|
Tag,
|
||||||
TrashItem,
|
TrashItem,
|
||||||
VAULT_ERRORS,
|
VAULT_ERRORS
|
||||||
createInternalLink
|
|
||||||
} from "@notesnook/core";
|
} from "@notesnook/core";
|
||||||
import { strings } from "@notesnook/intl";
|
import { strings } from "@notesnook/intl";
|
||||||
import { DisplayedNotification } from "@notifee/react-native";
|
import { DisplayedNotification } from "@notifee/react-native";
|
||||||
@@ -50,11 +50,11 @@ import ReminderSheet from "../components/sheets/reminder";
|
|||||||
import { useSideBarDraggingStore } from "../components/side-menu/dragging-store";
|
import { useSideBarDraggingStore } from "../components/side-menu/dragging-store";
|
||||||
import { useTabStore } from "../screens/editor/tiptap/use-tab-store";
|
import { useTabStore } from "../screens/editor/tiptap/use-tab-store";
|
||||||
import {
|
import {
|
||||||
ToastManager,
|
|
||||||
eSendEvent,
|
eSendEvent,
|
||||||
eSubscribeEvent,
|
eSubscribeEvent,
|
||||||
openVault,
|
openVault,
|
||||||
presentSheet
|
presentSheet,
|
||||||
|
ToastManager
|
||||||
} from "../services/event-manager";
|
} from "../services/event-manager";
|
||||||
import Navigation from "../services/navigation";
|
import Navigation from "../services/navigation";
|
||||||
import Notifications from "../services/notifications";
|
import Notifications from "../services/notifications";
|
||||||
@@ -544,14 +544,13 @@ export const useActions = ({
|
|||||||
const toggleReadyOnlyMode = async () => {
|
const toggleReadyOnlyMode = async () => {
|
||||||
const currentReadOnly = (item as Note).readonly;
|
const currentReadOnly = (item as Note).readonly;
|
||||||
await db.notes.readonly(!currentReadOnly, item?.id);
|
await db.notes.readonly(!currentReadOnly, item?.id);
|
||||||
|
useTabStore.getState().forEachNoteTab(item.id, (tab) => {
|
||||||
if (useTabStore.getState().hasTabForNote(item.id)) {
|
useTabStore.getState().updateTab(tab.id, {
|
||||||
const tabId = useTabStore.getState().getTabForNote(item.id);
|
session: {
|
||||||
if (!tabId) return;
|
readonly: !currentReadOnly
|
||||||
useTabStore.getState().updateTab(tabId, {
|
}
|
||||||
readonly: !currentReadOnly
|
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
Navigation.queueRoutesForUpdate();
|
Navigation.queueRoutesForUpdate();
|
||||||
close();
|
close();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,12 +50,17 @@ import { MMKV } from "../common/database/mmkv";
|
|||||||
import { endProgress, startProgress } from "../components/dialogs/progress";
|
import { endProgress, startProgress } from "../components/dialogs/progress";
|
||||||
import Migrate from "../components/sheets/migrate";
|
import Migrate from "../components/sheets/migrate";
|
||||||
import NewFeature from "../components/sheets/new-feature";
|
import NewFeature from "../components/sheets/new-feature";
|
||||||
|
import ReminderSheet from "../components/sheets/reminder";
|
||||||
import { Walkthrough } from "../components/walkthroughs";
|
import { Walkthrough } from "../components/walkthroughs";
|
||||||
import { useTabStore } from "../screens/editor/tiptap/use-tab-store";
|
import {
|
||||||
|
resetTabStore,
|
||||||
|
useTabStore
|
||||||
|
} from "../screens/editor/tiptap/use-tab-store";
|
||||||
import {
|
import {
|
||||||
clearAppState,
|
clearAppState,
|
||||||
editorController,
|
editorController,
|
||||||
editorState
|
editorState,
|
||||||
|
setAppState
|
||||||
} from "../screens/editor/tiptap/utils";
|
} from "../screens/editor/tiptap/utils";
|
||||||
import { useDragState } from "../screens/settings/editor/state";
|
import { useDragState } from "../screens/settings/editor/state";
|
||||||
import BackupService from "../services/backup";
|
import BackupService from "../services/backup";
|
||||||
@@ -94,7 +99,6 @@ import {
|
|||||||
eLoginSessionExpired,
|
eLoginSessionExpired,
|
||||||
eOnLoadNote,
|
eOnLoadNote,
|
||||||
eOpenAnnouncementDialog,
|
eOpenAnnouncementDialog,
|
||||||
eUnlockNote,
|
|
||||||
eUserLoggedIn,
|
eUserLoggedIn,
|
||||||
refreshNotesPage
|
refreshNotesPage
|
||||||
} from "../utils/events";
|
} from "../utils/events";
|
||||||
@@ -148,7 +152,6 @@ const onUploadedAttachmentProgress = (data: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onUserSessionExpired = async () => {
|
const onUserSessionExpired = async () => {
|
||||||
console.log("LOGGED OUT USER....");
|
|
||||||
SettingsService.set({
|
SettingsService.set({
|
||||||
sessionExpired: true
|
sessionExpired: true
|
||||||
});
|
});
|
||||||
@@ -157,6 +160,7 @@ const onUserSessionExpired = async () => {
|
|||||||
|
|
||||||
const onAppOpenedFromURL = async (event: { url: string }) => {
|
const onAppOpenedFromURL = async (event: { url: string }) => {
|
||||||
const url = event.url;
|
const url = event.url;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (url.startsWith("https://app.notesnook.com/account/verified")) {
|
if (url.startsWith("https://app.notesnook.com/account/verified")) {
|
||||||
await onUserEmailVerified();
|
await onUserEmailVerified();
|
||||||
@@ -166,6 +170,25 @@ const onAppOpenedFromURL = async (event: { url: string }) => {
|
|||||||
eSendEvent(eOnLoadNote, { newNote: true });
|
eSendEvent(eOnLoadNote, { newNote: true });
|
||||||
tabBarRef.current?.goToPage(1, false);
|
tabBarRef.current?.goToPage(1, false);
|
||||||
return;
|
return;
|
||||||
|
} else if (url.startsWith("https://notesnook.com/open_note")) {
|
||||||
|
const id = new URL(url).searchParams.get("id");
|
||||||
|
if (id) {
|
||||||
|
const note = await db.notes.note(id);
|
||||||
|
if (note) {
|
||||||
|
eSendEvent(eOnLoadNote, {
|
||||||
|
item: note
|
||||||
|
});
|
||||||
|
tabBarRef.current?.goToPage(1, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (url.startsWith("https://notesnook.com/open_reminder")) {
|
||||||
|
const id = new URL(url).searchParams.get("id");
|
||||||
|
if (id) {
|
||||||
|
const reminder = await db.reminders.reminder(id);
|
||||||
|
if (reminder) ReminderSheet.present(reminder);
|
||||||
|
}
|
||||||
|
} else if (url.startsWith("https://notesnook.com/new_reminder")) {
|
||||||
|
ReminderSheet.present();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@@ -222,6 +245,7 @@ const onLogout = async (reason: string) => {
|
|||||||
await PremiumService.setPremiumStatus();
|
await PremiumService.setPremiumStatus();
|
||||||
await BiometricService.resetCredentials();
|
await BiometricService.resetCredentials();
|
||||||
MMKV.clearStore();
|
MMKV.clearStore();
|
||||||
|
resetTabStore();
|
||||||
clearAllStores();
|
clearAllStores();
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
refreshAllStores();
|
refreshAllStores();
|
||||||
@@ -254,9 +278,7 @@ async function checkForShareExtensionLaunchedInBackground() {
|
|||||||
if (note) setTimeout(() => eSendEvent("loadingNote", note), 1);
|
if (note) setTimeout(() => eSendEvent("loadingNote", note), 1);
|
||||||
MMKV.removeItem("shareExtensionOpened");
|
MMKV.removeItem("shareExtensionOpened");
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveEditorState() {
|
async function saveEditorState() {
|
||||||
@@ -502,7 +524,9 @@ export const useAppEvents = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (SettingsService.getProperty("offlineMode")) {
|
if (SettingsService.getProperty("offlineMode")) {
|
||||||
db.attachments.cacheAttachments().catch(console.log);
|
db.attachments.cacheAttachments().catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
@@ -550,7 +574,6 @@ export const useAppEvents = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
|
||||||
ToastManager.error(e as Error, "Error updating user", "global");
|
ToastManager.error(e as Error, "Error updating user", "global");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -621,22 +644,17 @@ export const useAppEvents = () => {
|
|||||||
EV.subscribe(EVENTS.vaultLocked, async () => {
|
EV.subscribe(EVENTS.vaultLocked, async () => {
|
||||||
// Lock all notes in all tabs...
|
// Lock all notes in all tabs...
|
||||||
for (const tab of useTabStore.getState().tabs) {
|
for (const tab of useTabStore.getState().tabs) {
|
||||||
const noteId = useTabStore.getState().getTab(tab.id)?.noteId;
|
const noteId = useTabStore.getState().getTab(tab.id)?.session?.noteId;
|
||||||
if (!noteId) continue;
|
if (!noteId) continue;
|
||||||
const note = await db.notes.note(noteId);
|
const note = await db.notes.note(noteId);
|
||||||
const locked = note && (await db.vaults.itemExists(note));
|
const locked = note && (await db.vaults.itemExists(note));
|
||||||
if (locked) {
|
if (locked) {
|
||||||
useTabStore.getState().updateTab(tab.id, {
|
useTabStore.getState().updateTab(tab.id, {
|
||||||
locked: true
|
session: {
|
||||||
|
locked: true,
|
||||||
|
noteLocked: true
|
||||||
|
}
|
||||||
});
|
});
|
||||||
if (
|
|
||||||
tab.id === useTabStore.getState().currentTab &&
|
|
||||||
locked &&
|
|
||||||
!editorState().movedAway
|
|
||||||
) {
|
|
||||||
// Show unlock note screen.
|
|
||||||
eSendEvent(eUnlockNote);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@@ -645,6 +663,7 @@ export const useAppEvents = () => {
|
|||||||
|
|
||||||
const emitterSubscriptions = [
|
const emitterSubscriptions = [
|
||||||
Linking.addEventListener("url", onAppOpenedFromURL),
|
Linking.addEventListener("url", onAppOpenedFromURL),
|
||||||
|
|
||||||
SodiumEventEmitter.addListener(
|
SodiumEventEmitter.addListener(
|
||||||
"onSodiumProgress",
|
"onSodiumProgress",
|
||||||
onFileEncryptionProgress
|
onFileEncryptionProgress
|
||||||
@@ -730,13 +749,15 @@ export const useAppEvents = () => {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
sub = AppState.addEventListener("change", onAppStateChanged);
|
sub = AppState.addEventListener("change", onAppStateChanged);
|
||||||
if (
|
if (
|
||||||
refValues.current.initialUrl?.startsWith(
|
refValues.current.initialUrl &&
|
||||||
"https://app.notesnook.com/account/verified"
|
!refValues.current.initialUrl?.includes("open_note")
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
onUserEmailVerified();
|
onAppOpenedFromURL({
|
||||||
|
url: refValues.current.initialUrl!
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
refValues.current.removeInternetStateListener = NetInfo.addEventListener(
|
refValues.current.removeInternetStateListener = NetInfo.addEventListener(
|
||||||
onInternetStateChanged
|
onInternetStateChanged
|
||||||
);
|
);
|
||||||
@@ -779,9 +800,7 @@ export const useAppEvents = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!appLocked && isAppLoading) {
|
if (!appLocked && isAppLoading) {
|
||||||
initializeLogger()
|
initializeLogger()
|
||||||
.catch((e) => {
|
.catch((e) => {})
|
||||||
console.log(e);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
initializeDatabase();
|
initializeDatabase();
|
||||||
|
|||||||
@@ -196,7 +196,6 @@ export const useTotalNotes = (type: "notebook" | "tag" | "color") => {
|
|||||||
}
|
}
|
||||||
setTotalNotesById(totalNotesById);
|
setTotalNotesById(totalNotesById);
|
||||||
});
|
});
|
||||||
console.log("useTotalNotes.getTotalNotes");
|
|
||||||
},
|
},
|
||||||
[type]
|
[type]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export function useGroupOptions(type: any) {
|
|||||||
const onUpdate = (groupType: string) => {
|
const onUpdate = (groupType: string) => {
|
||||||
if (groupType !== type) return;
|
if (groupType !== type) return;
|
||||||
const options = db.settings?.getGroupOptions(type) as any;
|
const options = db.settings?.getGroupOptions(type) as any;
|
||||||
console.log("useGroupOptions.onUpdate", type, options);
|
|
||||||
if (
|
if (
|
||||||
groupOptions?.groupBy !== options.groupBy ||
|
groupOptions?.groupBy !== options.groupBy ||
|
||||||
groupOptions?.sortBy !== options.sortBy ||
|
groupOptions?.sortBy !== options.sortBy ||
|
||||||
|
|||||||
@@ -74,10 +74,8 @@ export const useNotebook = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onUpdate = (type: string) => {
|
const onUpdate = (type: string) => {
|
||||||
console.log("event", type);
|
|
||||||
if (type !== "notebooks") return;
|
if (type !== "notebooks") return;
|
||||||
onRequestUpdate();
|
onRequestUpdate();
|
||||||
console.log("useNotebook.onUpdate", item?.id, Date.now());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
eSubscribeEvent(eGroupOptionsUpdated, onUpdate);
|
eSubscribeEvent(eGroupOptionsUpdated, onUpdate);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { strings } from "@notesnook/intl";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import { NavigationContainer } from "@react-navigation/native";
|
import { NavigationContainer } from "@react-navigation/native";
|
||||||
import { createNativeStackNavigator } from "@react-navigation/native-stack";
|
import { createNativeStackNavigator } from "@react-navigation/native-stack";
|
||||||
@@ -44,7 +45,6 @@ import useNavigationStore from "../stores/use-navigation-store";
|
|||||||
import { useSelectionStore } from "../stores/use-selection-store";
|
import { useSelectionStore } from "../stores/use-selection-store";
|
||||||
import { useSettingStore } from "../stores/use-setting-store";
|
import { useSettingStore } from "../stores/use-setting-store";
|
||||||
import { rootNavigatorRef } from "../utils/global-refs";
|
import { rootNavigatorRef } from "../utils/global-refs";
|
||||||
import { strings } from "@notesnook/intl";
|
|
||||||
|
|
||||||
const NativeStack = createNativeStackNavigator();
|
const NativeStack = createNativeStackNavigator();
|
||||||
const IntroStack = createNativeStackNavigator();
|
const IntroStack = createNativeStackNavigator();
|
||||||
@@ -76,11 +76,12 @@ const _Tabs = () => {
|
|||||||
const introCompleted = useSettingStore(
|
const introCompleted = useSettingStore(
|
||||||
(state) => state.settings.introCompleted
|
(state) => state.settings.introCompleted
|
||||||
);
|
);
|
||||||
|
|
||||||
const height = useSettingStore((state) => state.dimensions.height);
|
const height = useSettingStore((state) => state.dimensions.height);
|
||||||
const insets = useGlobalSafeAreaInsets();
|
const insets = useGlobalSafeAreaInsets();
|
||||||
const screenHeight = height - (50 + insets.top + insets.bottom);
|
const screenHeight = height - (50 + insets.top + insets.bottom);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
setTimeout(() => {
|
setTimeout(async () => {
|
||||||
useNavigationStore.getState().update(homepage);
|
useNavigationStore.getState().update(homepage);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}, [homepage]);
|
}, [homepage]);
|
||||||
|
|||||||
@@ -509,7 +509,8 @@ const onChangeTab = async (event) => {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (
|
if (
|
||||||
useTabStore.getState().getTab(useTabStore.getState().currentTab).locked
|
useTabStore.getState().getTab(useTabStore.getState().currentTab).session
|
||||||
|
?.locked
|
||||||
) {
|
) {
|
||||||
eSendEvent(eUnlockNote);
|
eSendEvent(eUnlockNote);
|
||||||
}
|
}
|
||||||
@@ -523,13 +524,15 @@ const onChangeTab = async (event) => {
|
|||||||
|
|
||||||
// Lock all tabs with locked notes...
|
// Lock all tabs with locked notes...
|
||||||
for (const tab of useTabStore.getState().tabs) {
|
for (const tab of useTabStore.getState().tabs) {
|
||||||
const noteId = useTabStore.getState().getTab(tab.id)?.noteId;
|
const noteId = useTabStore.getState().getTab(tab.id)?.session?.noteId;
|
||||||
if (!noteId) continue;
|
if (!noteId) continue;
|
||||||
const note = await db.notes.note(noteId);
|
const note = await db.notes.note(noteId);
|
||||||
const locked = note && (await db.vaults.itemExists(note));
|
const locked = note && (await db.vaults.itemExists(note));
|
||||||
if (locked) {
|
if (locked) {
|
||||||
useTabStore.getState().updateTab(tab.id, {
|
useTabStore.getState().updateTab(tab.id, {
|
||||||
locked: true
|
session: {
|
||||||
|
locked: true
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,13 @@
|
|||||||
"@streetwriters/showdown": "^3.0.1-alpha.2",
|
"@streetwriters/showdown": "^3.0.1-alpha.2",
|
||||||
"absolutify": "^0.1.0",
|
"absolutify": "^0.1.0",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"dayjs": "^1.10.4",
|
|
||||||
"deprecated-react-native-prop-types": "^4.1.0",
|
"deprecated-react-native-prop-types": "^4.1.0",
|
||||||
"entities": "^3.0.1",
|
"entities": "^3.0.1",
|
||||||
"fflate": "^0.7.3",
|
"fflate": "^0.7.3",
|
||||||
"html-to-text": "9.0.5",
|
"html-to-text": "9.0.5",
|
||||||
"phone": "^3.1.14",
|
"phone": "^3.1.14",
|
||||||
"qclone": "^1.2.0",
|
"qclone": "^1.2.0",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
"react-native-actions-sheet": "0.9.7",
|
"react-native-actions-sheet": "0.9.7",
|
||||||
"react-native-drax": "^0.10.2",
|
"react-native-drax": "^0.10.2",
|
||||||
"react-native-image-zoom-viewer": "^3.0.1",
|
"react-native-image-zoom-viewer": "^3.0.1",
|
||||||
@@ -36,10 +36,15 @@
|
|||||||
"@streetwriters/kysely": "^0.27.4",
|
"@streetwriters/kysely": "^0.27.4",
|
||||||
"pathe": "1.1.2",
|
"pathe": "1.1.2",
|
||||||
"react-native-format-currency": "0.0.5",
|
"react-native-format-currency": "0.0.5",
|
||||||
"@lingui/react": "4.11.2",
|
"@lingui/core": "5.1.2",
|
||||||
"@lingui/core": "4.11.2",
|
"@lingui/react": "5.1.2",
|
||||||
"react-native-check-version": "^1.3.0",
|
"react-native-check-version": "^1.3.0",
|
||||||
"react-native-material-menu": "^2.0.0"
|
"react-native-material-menu": "^2.0.0",
|
||||||
|
"@trpc/client": "^10.45.2",
|
||||||
|
"@trpc/react-query": "^10.45.2",
|
||||||
|
"@trpc/server": "^10.45.2",
|
||||||
|
"@tanstack/react-query": "^4.36.1",
|
||||||
|
"async-mutex": "0.5.0"
|
||||||
},
|
},
|
||||||
"sideEffects": false
|
"sideEffects": false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
|
|
||||||
|
import { i18n } from "@lingui/core";
|
||||||
|
import { strings } from "@notesnook/intl";
|
||||||
import React, {
|
import React, {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
useCallback,
|
useCallback,
|
||||||
@@ -46,6 +48,7 @@ import {
|
|||||||
eUnlockWithPassword
|
eUnlockWithPassword
|
||||||
} from "../../utils/events";
|
} from "../../utils/events";
|
||||||
import { openLinkInBrowser } from "../../utils/functions";
|
import { openLinkInBrowser } from "../../utils/functions";
|
||||||
|
import { tabBarRef } from "../../utils/global-refs";
|
||||||
import EditorOverlay from "./loading";
|
import EditorOverlay from "./loading";
|
||||||
import { EDITOR_URI } from "./source";
|
import { EDITOR_URI } from "./source";
|
||||||
import { EditorProps, useEditorType } from "./tiptap/types";
|
import { EditorProps, useEditorType } from "./tiptap/types";
|
||||||
@@ -58,9 +61,6 @@ import {
|
|||||||
openInternalLink,
|
openInternalLink,
|
||||||
randId
|
randId
|
||||||
} from "./tiptap/utils";
|
} from "./tiptap/utils";
|
||||||
import { tabBarRef } from "../../utils/global-refs";
|
|
||||||
import { strings } from "@notesnook/intl";
|
|
||||||
import { i18n } from "@lingui/core";
|
|
||||||
|
|
||||||
const style: ViewStyle = {
|
const style: ViewStyle = {
|
||||||
height: "100%",
|
height: "100%",
|
||||||
@@ -203,11 +203,13 @@ const useLockedNoteHandler = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
for (const tab of useTabStore.getState().tabs) {
|
for (const tab of useTabStore.getState().tabs) {
|
||||||
const noteId = useTabStore.getState().getTab(tab.id)?.noteId;
|
const noteId = useTabStore.getState().getTab(tab.id)?.session?.noteId;
|
||||||
if (!noteId) continue;
|
if (!noteId) continue;
|
||||||
if (tabRef.current && tabRef.current.noteLocked) {
|
if (tabRef.current && tabRef.current.session?.noteLocked) {
|
||||||
useTabStore.getState().updateTab(tabRef.current.id, {
|
useTabStore.getState().updateTab(tabRef.current.id, {
|
||||||
locked: true
|
session: {
|
||||||
|
locked: true
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,33 +223,38 @@ const useLockedNoteHandler = () => {
|
|||||||
biometryAvailable: !!biometry,
|
biometryAvailable: !!biometry,
|
||||||
biometryEnrolled: !!fingerprint
|
biometryEnrolled: !!fingerprint
|
||||||
});
|
});
|
||||||
syncTabs();
|
syncTabs("biometry");
|
||||||
})();
|
})();
|
||||||
}, [tab?.id]);
|
}, [tab?.id]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unlockWithBiometrics = async () => {
|
const unlockWithBiometrics = async () => {
|
||||||
try {
|
try {
|
||||||
if (!tabRef.current?.noteLocked || !tabRef.current) return;
|
if (!tabRef.current?.session?.noteLocked || !tabRef.current) return;
|
||||||
console.log("Trying to unlock with biometrics...");
|
console.log("Trying to unlock with biometrics...");
|
||||||
const credentials = await BiometricService.getCredentials(
|
const credentials = await BiometricService.getCredentials(
|
||||||
"Unlock note",
|
"Unlock note",
|
||||||
"Unlock note to open it in editor."
|
"Unlock note to open it in editor."
|
||||||
);
|
);
|
||||||
|
|
||||||
if (credentials && credentials?.password && tabRef.current.noteId) {
|
if (
|
||||||
|
credentials &&
|
||||||
|
credentials?.password &&
|
||||||
|
tabRef.current.session?.noteId
|
||||||
|
) {
|
||||||
const note = await db.vault.open(
|
const note = await db.vault.open(
|
||||||
tabRef.current.noteId,
|
tabRef.current.session?.noteId,
|
||||||
credentials?.password
|
credentials?.password
|
||||||
);
|
);
|
||||||
|
|
||||||
eSendEvent(eOnLoadNote, {
|
eSendEvent(eOnLoadNote, {
|
||||||
item: note
|
item: note,
|
||||||
});
|
refresh: true
|
||||||
|
|
||||||
useTabStore.getState().updateTab(tabRef.current.id, {
|
|
||||||
locked: false
|
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
if (tabRef.current && tabRef.current.session?.locked) {
|
||||||
|
editorController.current?.commands.focusPassInput();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@@ -261,7 +268,7 @@ const useLockedNoteHandler = () => {
|
|||||||
password: string;
|
password: string;
|
||||||
biometrics?: boolean;
|
biometrics?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
if (!tabRef.current?.noteId || !tabRef.current) return;
|
if (!tabRef.current?.session?.noteId || !tabRef.current) return;
|
||||||
if (!password || password.trim().length === 0) {
|
if (!password || password.trim().length === 0) {
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
heading: strings.passwordNotEntered(),
|
heading: strings.passwordNotEntered(),
|
||||||
@@ -271,7 +278,10 @@ const useLockedNoteHandler = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const note = await db.vault.open(tabRef.current?.noteId, password);
|
const note = await db.vault.open(
|
||||||
|
tabRef.current?.session?.noteId,
|
||||||
|
password
|
||||||
|
);
|
||||||
if (enrollBiometrics && note) {
|
if (enrollBiometrics && note) {
|
||||||
try {
|
try {
|
||||||
const unlocked = await db.vault.unlock(password);
|
const unlocked = await db.vault.unlock(password);
|
||||||
@@ -299,13 +309,10 @@ const useLockedNoteHandler = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
eSendEvent(eOnLoadNote, {
|
eSendEvent(eOnLoadNote, {
|
||||||
item: note
|
item: note,
|
||||||
});
|
refresh: true
|
||||||
useTabStore.getState().updateTab(tabRef.current.id, {
|
|
||||||
locked: false
|
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
heading: strings.passwordIncorrect(),
|
heading: strings.passwordIncorrect(),
|
||||||
type: "error"
|
type: "error"
|
||||||
@@ -315,7 +322,7 @@ const useLockedNoteHandler = () => {
|
|||||||
|
|
||||||
const unlock = () => {
|
const unlock = () => {
|
||||||
if (
|
if (
|
||||||
(tabRef.current?.locked,
|
(tabRef.current?.session?.locked,
|
||||||
useTabStore.getState().biometryAvailable &&
|
useTabStore.getState().biometryAvailable &&
|
||||||
useTabStore.getState().biometryEnrolled &&
|
useTabStore.getState().biometryEnrolled &&
|
||||||
!editorState().movedAway)
|
!editorState().movedAway)
|
||||||
@@ -324,10 +331,9 @@ const useLockedNoteHandler = () => {
|
|||||||
unlockWithBiometrics();
|
unlockWithBiometrics();
|
||||||
}, 150);
|
}, 150);
|
||||||
} else {
|
} else {
|
||||||
console.log("Biometrics unavailable.", editorState().movedAway);
|
|
||||||
if (!editorState().movedAway) {
|
if (!editorState().movedAway) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (tabRef.current && tabRef.current?.locked) {
|
if (tabRef.current && tabRef.current?.session?.locked) {
|
||||||
editorController.current?.commands.focus(tabRef.current?.id);
|
editorController.current?.commands.focus(tabRef.current?.id);
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
@@ -342,13 +348,13 @@ const useLockedNoteHandler = () => {
|
|||||||
}),
|
}),
|
||||||
eSubscribeEvent(eUnlockWithPassword, onSubmit)
|
eSubscribeEvent(eUnlockWithPassword, onSubmit)
|
||||||
];
|
];
|
||||||
if (tabRef.current?.locked && tabBarRef.current?.page() === 2) {
|
if (tabRef.current?.session?.locked && tabBarRef.current?.page() === 2) {
|
||||||
unlock();
|
unlock();
|
||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
subs.map((s) => s?.unsubscribe());
|
subs.map((s) => s?.unsubscribe());
|
||||||
};
|
};
|
||||||
}, [tab?.id, tab?.locked]);
|
}, [tab?.id, tab?.session?.locked]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,10 +27,10 @@ import WebView from "react-native-webview";
|
|||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import { EDITOR_URI } from "./source";
|
import { EDITOR_URI } from "./source";
|
||||||
import { EditorMessage } from "./tiptap/types";
|
import { EditorMessage } from "./tiptap/types";
|
||||||
import { EventTypes } from "./tiptap/editor-events";
|
import { EditorEvents } from "@notesnook/editor-mobile/src/utils/editor-events";
|
||||||
import { Attachment } from "@notesnook/editor";
|
import { Attachment } from "@notesnook/editor";
|
||||||
import downloadAttachment from "../../common/filesystem/download-attachment";
|
import downloadAttachment from "../../common/filesystem/download-attachment";
|
||||||
import { EditorEvents } from "./tiptap/utils";
|
import { NativeEvents } from "@notesnook/editor-mobile/src/utils/native-events";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||||
import { db } from "../../common/database";
|
import { db } from "../../common/database";
|
||||||
@@ -69,12 +69,11 @@ export function ReadonlyEditor(props: {
|
|||||||
const data = event.nativeEvent.data;
|
const data = event.nativeEvent.data;
|
||||||
const editorMessage = JSON.parse(data) as EditorMessage<any>;
|
const editorMessage = JSON.parse(data) as EditorMessage<any>;
|
||||||
|
|
||||||
if (editorMessage.type === EventTypes.logger) {
|
if (editorMessage.type === EditorEvents.logger) {
|
||||||
logger.info("[READONLY EDITOR LOG]", editorMessage.value);
|
logger.info("[READONLY EDITOR LOG]", editorMessage.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (editorMessage.type === EventTypes.readonlyEditorLoaded) {
|
if (editorMessage.type === EditorEvents.readonlyEditorLoaded) {
|
||||||
console.log("Readonly editor loaded.");
|
|
||||||
props.onLoad?.((content: { data: string; id: string }) => {
|
props.onLoad?.((content: { data: string; id: string }) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
noteId.current = content.id;
|
noteId.current = content.id;
|
||||||
@@ -87,10 +86,9 @@ export function ReadonlyEditor(props: {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
}, 300);
|
}, 300);
|
||||||
});
|
});
|
||||||
} else if (editorMessage.type === EventTypes.getAttachmentData) {
|
} else if (editorMessage.type === EditorEvents.getAttachmentData) {
|
||||||
const attachment = (editorMessage.value as any).attachment as Attachment;
|
const attachment = (editorMessage.value as any).attachment as Attachment;
|
||||||
|
|
||||||
console.log("Getting attachment data:", attachment.hash, attachment.type);
|
|
||||||
downloadAttachment(attachment.hash, true, {
|
downloadAttachment(attachment.hash, true, {
|
||||||
base64: attachment.type === "image",
|
base64: attachment.type === "image",
|
||||||
text: attachment.type === "web-clip",
|
text: attachment.type === "web-clip",
|
||||||
@@ -106,7 +104,7 @@ export function ReadonlyEditor(props: {
|
|||||||
);
|
);
|
||||||
editorRef.current?.postMessage(
|
editorRef.current?.postMessage(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
type: EditorEvents.attachmentData,
|
type: NativeEvents.attachmentData,
|
||||||
value: {
|
value: {
|
||||||
resolverId: (editorMessage.value as any).resolverId,
|
resolverId: (editorMessage.value as any).resolverId,
|
||||||
data
|
data
|
||||||
@@ -115,10 +113,9 @@ export function ReadonlyEditor(props: {
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
console.log("Error downloading attachment data");
|
|
||||||
editorRef.current?.postMessage(
|
editorRef.current?.postMessage(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
type: EditorEvents.attachmentData,
|
type: NativeEvents.attachmentData,
|
||||||
data: {
|
data: {
|
||||||
resolverId: (editorMessage.value as any).resolverId,
|
resolverId: (editorMessage.value as any).resolverId,
|
||||||
data: undefined
|
data: undefined
|
||||||
|
|||||||
@@ -18,9 +18,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Note } from "@notesnook/core";
|
import { Note } from "@notesnook/core";
|
||||||
import type { Attachment } from "@notesnook/editor";
|
import type {
|
||||||
import type { ImageAttributes } from "@notesnook/editor";
|
Attachment,
|
||||||
import type { LinkAttributes } from "@notesnook/editor";
|
ImageAttributes,
|
||||||
|
LinkAttributes
|
||||||
|
} from "@notesnook/editor";
|
||||||
import { createRef, RefObject } from "react";
|
import { createRef, RefObject } from "react";
|
||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
import { EdgeInsets } from "react-native-safe-area-context";
|
import { EdgeInsets } from "react-native-safe-area-context";
|
||||||
@@ -37,9 +39,6 @@ async function call(webview: RefObject<WebView | undefined>, action?: Action) {
|
|||||||
if (!webview.current || !action) return;
|
if (!webview.current || !action) return;
|
||||||
setImmediate(() => webview.current?.injectJavaScript(action.job));
|
setImmediate(() => webview.current?.injectJavaScript(action.job));
|
||||||
const response = await getResponse(action.id);
|
const response = await getResponse(action.id);
|
||||||
// if (!response) {
|
|
||||||
// console.warn("webview job failed", action.id);
|
|
||||||
// }
|
|
||||||
return response ? response.value : response;
|
return response ? response.value : response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,110 +74,71 @@ class Commands {
|
|||||||
return call(this.ref, fn(job, name)) as Promise<T>;
|
return call(this.ref, fn(job, name)) as Promise<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
focus = async (tabId: number) => {
|
async sendCommand<T>(command: string, ...args: any[]) {
|
||||||
|
return this.doAsync(
|
||||||
|
`response = globalThis.commands.${command}(${args
|
||||||
|
.map((arg) =>
|
||||||
|
typeof arg === "string" ? `"${arg}"` : JSON.stringify(arg)
|
||||||
|
)
|
||||||
|
.join(",")})`,
|
||||||
|
command
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
focus = async (tabId: string) => {
|
||||||
if (!this.ref.current) return;
|
if (!this.ref.current) return;
|
||||||
|
|
||||||
|
const locked = useTabStore.getState().getTab(tabId)?.session?.locked;
|
||||||
if (Platform.OS === "android") {
|
if (Platform.OS === "android") {
|
||||||
//this.ref.current?.requestFocus();
|
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
|
if (
|
||||||
|
locked &&
|
||||||
|
useTabStore.getState().biometryAvailable &&
|
||||||
|
useTabStore.getState().biometryEnrolled
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
if (!this.ref) return;
|
if (!this.ref) return;
|
||||||
textInput.current?.focus();
|
textInput.current?.focus();
|
||||||
|
await this.sendCommand("focus", tabId, locked);
|
||||||
const locked = useTabStore.getState().getTab(tabId)?.locked;
|
|
||||||
await this.doAsync(
|
|
||||||
locked
|
|
||||||
? `editorControllers[${tabId}]?.focusPassInput();`
|
|
||||||
: `editors[${tabId}]?.commands.focus()`,
|
|
||||||
"focus"
|
|
||||||
);
|
|
||||||
|
|
||||||
this.ref?.current?.requestFocus();
|
this.ref?.current?.requestFocus();
|
||||||
}, 1);
|
}, 1);
|
||||||
} else {
|
} else {
|
||||||
await sleep(400);
|
await sleep(400);
|
||||||
await this.doAsync(`editors[${tabId}]?.commands.focus()`, "focus");
|
await this.sendCommand("focus", tabId, locked);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
blur = async (tabId: number) =>
|
blur = async (tabId: string) => this.sendCommand("blur", tabId);
|
||||||
await this.doAsync(
|
|
||||||
`
|
|
||||||
const editor = editors[${tabId}];
|
|
||||||
const editorTitle = editorTitles[${tabId}];
|
|
||||||
typeof editor !== "undefined" && editor.commands.blur();
|
|
||||||
typeof editorTitle !== "undefined" && editorTitle.current && editorTitle.current.blur();
|
|
||||||
|
|
||||||
editorControllers[${tabId}]?.blurPassInput();
|
|
||||||
|
|
||||||
`,
|
clearContent = async (tabId: string) => {
|
||||||
"blur"
|
|
||||||
);
|
|
||||||
|
|
||||||
clearContent = async (tabId: number) => {
|
|
||||||
this.previousSettings = null;
|
this.previousSettings = null;
|
||||||
await this.doAsync(
|
await this.sendCommand("clearContent", tabId);
|
||||||
`
|
|
||||||
const editor = editors[${tabId}];
|
|
||||||
const editorController = editorControllers[${tabId}];
|
|
||||||
const editorTitle = editorTitles[${tabId}];
|
|
||||||
const statusBar = statusBars[${tabId}];
|
|
||||||
|
|
||||||
if (typeof editor !== "undefined") {
|
|
||||||
editor.commands.blur();
|
|
||||||
editor.commands.clearContent(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
typeof editorTitle !== "undefined" && editorTitle.current && editorTitle.current?.blur();
|
|
||||||
if (typeof editorController.content !== undefined) editorController.content.current = '';
|
|
||||||
editorController.onUpdate();
|
|
||||||
editorController.setTitle('');
|
|
||||||
if (typeof statusBar !== "undefined") {
|
|
||||||
statusBar.current.resetWords();
|
|
||||||
statusBar.current.set({date:"",saved:""});
|
|
||||||
}`,
|
|
||||||
"clearContent"
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
setSessionId = async (id: string | null) =>
|
setSessionId = async (id: string | null) =>
|
||||||
await this.doAsync(`globalThis.sessionId = "${id}";`);
|
await this.sendCommand("setSessionId", id);
|
||||||
|
|
||||||
setStatus = async (
|
setStatus = async (
|
||||||
date: string | undefined,
|
date: string | undefined,
|
||||||
saved: string,
|
saved: string,
|
||||||
tabId: number
|
tabId: string
|
||||||
) => {
|
) => {
|
||||||
await this.doAsync(
|
this.sendCommand("setStatus", date, saved, tabId);
|
||||||
`
|
};
|
||||||
const statusBar = statusBars[${tabId}];
|
|
||||||
typeof statusBar !== "undefined" && statusBar.current.set({date:"${date}",saved:"${saved}"})`,
|
setPlaceholder = async (placeholder: string) => {};
|
||||||
"setStatus"
|
|
||||||
|
setLoading = async (loading?: boolean, tabId?: string) => {
|
||||||
|
this.sendCommand(
|
||||||
|
"setLoading",
|
||||||
|
loading,
|
||||||
|
tabId === undefined ? useTabStore.getState().currentTab : tabId
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
setPlaceholder = async (placeholder: string) => {
|
|
||||||
// await this.doAsync(`
|
|
||||||
// const element = document.querySelector(".is-editor-empty");
|
|
||||||
// if (element) {
|
|
||||||
// element.setAttribute("data-placeholder","${placeholder}");
|
|
||||||
// }
|
|
||||||
// `);
|
|
||||||
};
|
|
||||||
|
|
||||||
setLoading = async (loading?: boolean, tabId?: number) => {
|
|
||||||
await this.doAsync(`
|
|
||||||
const editorController = editorControllers[${
|
|
||||||
tabId || useTabStore.getState().currentTab
|
|
||||||
}];
|
|
||||||
editorController.setLoading(${loading})
|
|
||||||
`);
|
|
||||||
};
|
|
||||||
|
|
||||||
setInsets = async (insets: EdgeInsets) => {
|
setInsets = async (insets: EdgeInsets) => {
|
||||||
await this.doAsync(`
|
this.sendCommand("setInsets", insets);
|
||||||
if (typeof safeAreaController !== "undefined") {
|
|
||||||
safeAreaController.update(${JSON.stringify(insets)})
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
updateSettings = async (settings?: Partial<Settings>) => {
|
updateSettings = async (settings?: Partial<Settings>) => {
|
||||||
@@ -187,13 +147,7 @@ if (typeof statusBar !== "undefined") {
|
|||||||
...this.previousSettings,
|
...this.previousSettings,
|
||||||
...settings
|
...settings
|
||||||
};
|
};
|
||||||
await this.doAsync(`
|
this.sendCommand("updateSettings", settings);
|
||||||
if (typeof globalThis.settingsController !== "undefined") {
|
|
||||||
globalThis.settingsController.update(${JSON.stringify(
|
|
||||||
this.previousSettings
|
|
||||||
)})
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
setSettings = async (settings?: Partial<Settings>) => {
|
setSettings = async (settings?: Partial<Settings>) => {
|
||||||
@@ -206,123 +160,63 @@ if (typeof statusBar !== "undefined") {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await this.doAsync(`
|
this.sendCommand("setSettings", settings);
|
||||||
if (typeof globalThis.settingsController !== "undefined") {
|
|
||||||
globalThis.settingsController.update(${JSON.stringify(settings)})
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
setTags = async (note: Note | null | undefined) => {
|
setTags = async (note: Note | null | undefined) => {
|
||||||
if (!note) return;
|
if (!note) return;
|
||||||
const tabId = useTabStore.getState().getTabForNote(note.id);
|
useTabStore.getState().forEachNoteTab(note.id, async (tab) => {
|
||||||
|
const tabId = tab.id;
|
||||||
const tags = await db.relations.to(note, "tag").resolve();
|
const tags = await db.relations.to(note, "tag").resolve();
|
||||||
await this.doAsync(
|
await this.sendCommand("setTags", tabId, tags);
|
||||||
`
|
});
|
||||||
const tags = editorTags[${tabId}];
|
|
||||||
if (tags && tags.current) {
|
|
||||||
tags.current.setTags(${JSON.stringify(
|
|
||||||
tags.map((tag) => ({
|
|
||||||
title: tag.title,
|
|
||||||
alias: tag.title,
|
|
||||||
id: tag.id,
|
|
||||||
type: tag.type
|
|
||||||
}))
|
|
||||||
)});
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
"setTags"
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
clearTags = async (tabId: number) => {
|
clearTags = async (tabId: string) => {
|
||||||
await this.doAsync(
|
await this.sendCommand("clearTags", tabId);
|
||||||
`
|
|
||||||
const tags = editorTags[${tabId}];
|
|
||||||
logger("info", Object.keys(editorTags), typeof editorTags[0]);
|
|
||||||
if (tags && tags.current) {
|
|
||||||
tags.current.setTags([]);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
"clearTags"
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
insertAttachment = async (attachment: Attachment, tabId: number) => {
|
insertAttachment = async (attachment: Attachment, tabId: string) => {
|
||||||
await this.doAsync(
|
await this.sendCommand("insertAttachment", attachment, tabId);
|
||||||
`const editor = editors[${tabId}];
|
|
||||||
editor && editor.commands.insertAttachment(${JSON.stringify(attachment)})`
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
setAttachmentProgress = async (
|
setAttachmentProgress = async (
|
||||||
attachmentProgress: Partial<Attachment>,
|
attachmentProgress: Partial<Attachment>,
|
||||||
tabId: number
|
tabId: string
|
||||||
) => {
|
) => {
|
||||||
await this.doAsync(
|
await this.sendCommand("setAttachmentProgress", attachmentProgress, tabId);
|
||||||
`const editor = editors[${tabId}];
|
|
||||||
editor && editor.commands.updateAttachment(${JSON.stringify(
|
|
||||||
attachmentProgress
|
|
||||||
)}, {
|
|
||||||
preventUpdate: true,
|
|
||||||
query: (attachment) => {
|
|
||||||
return attachment.hash === "${attachmentProgress.hash}";
|
|
||||||
}
|
|
||||||
})`
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
insertImage = async (
|
insertImage = async (
|
||||||
image: Omit<ImageAttributes, "bloburl"> & {
|
image: Omit<ImageAttributes, "bloburl"> & {
|
||||||
dataurl: string;
|
dataurl: string;
|
||||||
},
|
},
|
||||||
tabId: number
|
tabId: string
|
||||||
) => {
|
) => {
|
||||||
await this.doAsync(
|
await this.sendCommand("insertImage", image, tabId);
|
||||||
`const editor = editors[${tabId}];
|
|
||||||
|
|
||||||
const image = toBlobURL("${image.dataurl}", "${image.hash}");
|
|
||||||
|
|
||||||
editor && editor.commands.insertImage({
|
|
||||||
...${JSON.stringify({
|
|
||||||
...image,
|
|
||||||
dataurl: undefined
|
|
||||||
})},
|
|
||||||
bloburl: image
|
|
||||||
})`
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
handleBack = async () => {
|
handleBack = async () => {
|
||||||
return this.doAsync<boolean>(
|
return this.sendCommand("handleBack");
|
||||||
'response = window.dispatchEvent(new Event("handleBackPress",{cancelable:true}));'
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
keyboardShown = async (keyboardShown: boolean) => {
|
keyboardShown = async (keyboardShown: boolean) => {
|
||||||
return this.doAsync(`globalThis['keyboardShown']=${keyboardShown};`);
|
return this.sendCommand("keyboardShown", keyboardShown);
|
||||||
};
|
};
|
||||||
|
|
||||||
getTableOfContents = async () => {
|
getTableOfContents = async () => {
|
||||||
const tabId = useTabStore.getState().currentTab;
|
const tabId = useTabStore.getState().currentTab;
|
||||||
return this.doAsync(`
|
return this.sendCommand("getTableOfContents", tabId);
|
||||||
response = editorControllers[${tabId}]?.getTableOfContents() || [];
|
|
||||||
`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
focusPassInput = async () => {
|
focusPassInput = async () => {
|
||||||
const tabId = useTabStore.getState().currentTab;
|
const tabId = useTabStore.getState().currentTab;
|
||||||
return this.doAsync(`
|
return this.sendCommand("focusPassInput", tabId);
|
||||||
response = editorControllers[${tabId}]?.focusPassInput() || [];
|
|
||||||
`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
blurPassInput = async () => {
|
blurPassInput = async () => {
|
||||||
const tabId = useTabStore.getState().currentTab;
|
const tabId = useTabStore.getState().currentTab;
|
||||||
return this.doAsync(`
|
return this.sendCommand("blurPassInput", tabId);
|
||||||
response = editorControllers[${tabId}]?.blurPassInput() || [];
|
|
||||||
`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
createInternalLink = async (
|
createInternalLink = async (
|
||||||
@@ -330,30 +224,18 @@ editor && editor.commands.insertImage({
|
|||||||
resolverId: string
|
resolverId: string
|
||||||
) => {
|
) => {
|
||||||
if (!resolverId) return;
|
if (!resolverId) return;
|
||||||
return this.doAsync(`
|
return this.sendCommand("createInternalLink", attributes, resolverId);
|
||||||
if (globalThis.pendingResolvers["${resolverId}"]) {
|
|
||||||
globalThis.pendingResolvers["${resolverId}"](${JSON.stringify(
|
|
||||||
attributes
|
|
||||||
)});
|
|
||||||
}`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
dismissCreateInternalLinkRequest = async (resolverId: string) => {
|
dismissCreateInternalLinkRequest = async (resolverId: string) => {
|
||||||
if (!resolverId) return;
|
if (!resolverId) return;
|
||||||
return this.doAsync(`
|
return this.sendCommand("dismissCreateInternalLinkRequest", resolverId);
|
||||||
if (globalThis.pendingResolvers["${resolverId}"]) {
|
|
||||||
globalThis.pendingResolvers["${resolverId}"](undefined);
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
scrollIntoViewById = async (id: string) => {
|
scrollIntoViewById = async (id: string) => {
|
||||||
const tabId = useTabStore.getState().currentTab;
|
const tabId = useTabStore.getState().currentTab;
|
||||||
return this.doAsync(`
|
return this.sendCommand("scrollIntoViewById", id, tabId);
|
||||||
response = editorControllers[${tabId}]?.scrollIntoView("${id}") || [];
|
|
||||||
`);
|
|
||||||
};
|
};
|
||||||
//todo add replace image function
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Commands;
|
export default Commands;
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import { eCloseSheet } from "../../../utils/events";
|
|||||||
import { useTabStore } from "./use-tab-store";
|
import { useTabStore } from "./use-tab-store";
|
||||||
import { editorController, editorState } from "./utils";
|
import { editorController, editorState } from "./utils";
|
||||||
import { strings } from "@notesnook/intl";
|
import { strings } from "@notesnook/intl";
|
||||||
|
import { useUserStore } from "../../../stores/use-user-store";
|
||||||
|
|
||||||
const showEncryptionSheet = (file: DocumentPickerResponse) => {
|
const showEncryptionSheet = (file: DocumentPickerResponse) => {
|
||||||
presentSheet({
|
presentSheet({
|
||||||
@@ -60,7 +61,7 @@ const santizeUri = (uri: string) => {
|
|||||||
|
|
||||||
type PickerOptions = {
|
type PickerOptions = {
|
||||||
noteId?: string;
|
noteId?: string;
|
||||||
tabId?: number;
|
tabId?: string;
|
||||||
type: "image" | "camera" | "file";
|
type: "image" | "camera" | "file";
|
||||||
reupload: boolean;
|
reupload: boolean;
|
||||||
hash?: string;
|
hash?: string;
|
||||||
@@ -131,7 +132,6 @@ const file = async (fileOptions: PickerOptions) => {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
fileOptions.tabId !== undefined &&
|
fileOptions.tabId !== undefined &&
|
||||||
fileOptions.noteId &&
|
|
||||||
useTabStore.getState().getNoteIdForTab(fileOptions.tabId) ===
|
useTabStore.getState().getNoteIdForTab(fileOptions.tabId) ===
|
||||||
fileOptions.noteId
|
fileOptions.noteId
|
||||||
) {
|
) {
|
||||||
@@ -194,9 +194,7 @@ const camera = async (options: PickerOptions) => {
|
|||||||
options
|
options
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {});
|
||||||
console.log("camera error: ", e);
|
|
||||||
});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
heading: (e as Error).message,
|
heading: (e as Error).message,
|
||||||
@@ -224,9 +222,7 @@ const gallery = async (options: PickerOptions) => {
|
|||||||
options
|
options
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.catch((e) => {
|
.catch((e) => {});
|
||||||
console.log("gallery error: ", e);
|
|
||||||
});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
useSettingStore.getState().setAppDidEnterBackgroundForAction(false);
|
useSettingStore.getState().setAppDidEnterBackgroundForAction(false);
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
@@ -234,13 +230,19 @@ const gallery = async (options: PickerOptions) => {
|
|||||||
type: "error",
|
type: "error",
|
||||||
context: "global"
|
context: "global"
|
||||||
});
|
});
|
||||||
console.log("attachment error:", e);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const pick = async (options: PickerOptions) => {
|
const pick = async (options: PickerOptions) => {
|
||||||
if (!PremiumService.get()) {
|
if (!PremiumService.get()) {
|
||||||
const user = await db.user.getUser();
|
const user = await db.user.getUser();
|
||||||
|
if (!user) {
|
||||||
|
ToastManager.show({
|
||||||
|
heading: strings.loginRequired(),
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (editorState().isFocused) {
|
if (editorState().isFocused) {
|
||||||
editorState().isFocused = true;
|
editorState().isFocused = true;
|
||||||
}
|
}
|
||||||
@@ -251,6 +253,7 @@ const pick = async (options: PickerOptions) => {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
useUserStore.getState().setDisableAppLockRequests(true);
|
||||||
if (options?.type.startsWith("image") || options?.type === "camera") {
|
if (options?.type.startsWith("image") || options?.type === "camera") {
|
||||||
if (options.type.startsWith("image")) {
|
if (options.type.startsWith("image")) {
|
||||||
gallery(options);
|
gallery(options);
|
||||||
@@ -267,6 +270,7 @@ const handleImageResponse = async (
|
|||||||
options: PickerOptions
|
options: PickerOptions
|
||||||
) => {
|
) => {
|
||||||
const result = await AttachImage.present(response, options.context);
|
const result = await AttachImage.present(response, options.context);
|
||||||
|
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
const compress = result.compress;
|
const compress = result.compress;
|
||||||
|
|
||||||
@@ -310,18 +314,14 @@ const handleImageResponse = async (
|
|||||||
? fileName.replace(/HEIC|HEIF/, "jpeg")
|
? fileName.replace(/HEIC|HEIF/, "jpeg")
|
||||||
: fileName;
|
: fileName;
|
||||||
|
|
||||||
console.log("attaching image...", fileName);
|
|
||||||
|
|
||||||
console.log("attaching file...");
|
|
||||||
if (!(await attachFile(uri, hash, image.mime, fileName, options))) return;
|
if (!(await attachFile(uri, hash, image.mime, fileName, options))) return;
|
||||||
|
|
||||||
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
|
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
|
||||||
console.log("attaching image to note...");
|
|
||||||
if (
|
if (
|
||||||
options.tabId !== undefined &&
|
options.tabId !== undefined &&
|
||||||
useTabStore.getState().getNoteIdForTab(options.tabId) === options.noteId
|
useTabStore.getState().getNoteIdForTab(options.tabId) === options.noteId
|
||||||
) {
|
) {
|
||||||
console.log("attaching image to note...");
|
|
||||||
editorController.current?.commands.insertImage(
|
editorController.current?.commands.insertImage(
|
||||||
{
|
{
|
||||||
hash: hash,
|
hash: hash,
|
||||||
@@ -396,7 +396,7 @@ export async function attachFile(
|
|||||||
} else {
|
} else {
|
||||||
encryptionInfo = { hash: hash };
|
encryptionInfo = { hash: hash };
|
||||||
}
|
}
|
||||||
console.log("FILE ENCRYPTED....", encryptionInfo);
|
|
||||||
await db.attachments.add(encryptionInfo);
|
await db.attachments.add(encryptionInfo);
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export type EditorMessage<T> = {
|
|||||||
value: T;
|
value: T;
|
||||||
type: string;
|
type: string;
|
||||||
noteId: string;
|
noteId: string;
|
||||||
tabId: number;
|
tabId: string;
|
||||||
resolverId?: string;
|
resolverId?: string;
|
||||||
hasTimeout?: boolean;
|
hasTimeout?: boolean;
|
||||||
};
|
};
|
||||||
@@ -86,7 +86,7 @@ export type SavePayload = {
|
|||||||
type?: "tiptap";
|
type?: "tiptap";
|
||||||
sessionHistoryId?: number;
|
sessionHistoryId?: number;
|
||||||
ignoreEdit: boolean;
|
ignoreEdit: boolean;
|
||||||
tabId: number;
|
tabId: string;
|
||||||
pendingChanges?: boolean;
|
pendingChanges?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -94,4 +94,5 @@ export type AppState = {
|
|||||||
editing: boolean;
|
editing: boolean;
|
||||||
movedAway: boolean;
|
movedAway: boolean;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
|
noteId?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,7 +21,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
import { ItemReference } from "@notesnook/core";
|
import { ItemReference } from "@notesnook/core";
|
||||||
import type { Attachment } from "@notesnook/editor";
|
import type { Attachment } from "@notesnook/editor";
|
||||||
|
import { EditorEvents } from "@notesnook/editor-mobile/src/utils/editor-events";
|
||||||
|
import { NativeEvents } from "@notesnook/editor-mobile/src/utils/native-events";
|
||||||
import { getDefaultPresets } from "@notesnook/editor/dist/cjs/toolbar/tool-definitions";
|
import { getDefaultPresets } from "@notesnook/editor/dist/cjs/toolbar/tool-definitions";
|
||||||
|
import { strings } from "@notesnook/intl";
|
||||||
import Clipboard from "@react-native-clipboard/clipboard";
|
import Clipboard from "@react-native-clipboard/clipboard";
|
||||||
import React, { useCallback, useEffect, useRef } from "react";
|
import React, { useCallback, useEffect, useRef } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -72,11 +75,9 @@ import {
|
|||||||
import { openLinkInBrowser } from "../../../utils/functions";
|
import { openLinkInBrowser } from "../../../utils/functions";
|
||||||
import { tabBarRef } from "../../../utils/global-refs";
|
import { tabBarRef } from "../../../utils/global-refs";
|
||||||
import { useDragState } from "../../settings/editor/state";
|
import { useDragState } from "../../settings/editor/state";
|
||||||
import { EventTypes } from "./editor-events";
|
|
||||||
import { EditorMessage, EditorProps, useEditorType } from "./types";
|
import { EditorMessage, EditorProps, useEditorType } from "./types";
|
||||||
import { useTabStore } from "./use-tab-store";
|
import { useTabStore } from "./use-tab-store";
|
||||||
import { EditorEvents, editorState, openInternalLink } from "./utils";
|
import { editorState, openInternalLink } from "./utils";
|
||||||
import { strings } from "@notesnook/intl";
|
|
||||||
|
|
||||||
const publishNote = async () => {
|
const publishNote = async () => {
|
||||||
const user = useUserStore.getState().user;
|
const user = useUserStore.getState().user;
|
||||||
@@ -101,7 +102,7 @@ const publishNote = async () => {
|
|||||||
}
|
}
|
||||||
const noteId = useTabStore
|
const noteId = useTabStore
|
||||||
.getState()
|
.getState()
|
||||||
.getNoteIdForTab(useTabStore.getState().currentTab);
|
.getNoteIdForTab(useTabStore.getState().currentTab!);
|
||||||
|
|
||||||
if (noteId) {
|
if (noteId) {
|
||||||
const note = await db.notes?.note(noteId);
|
const note = await db.notes?.note(noteId);
|
||||||
@@ -124,7 +125,7 @@ const publishNote = async () => {
|
|||||||
const showActionsheet = async () => {
|
const showActionsheet = async () => {
|
||||||
const noteId = useTabStore
|
const noteId = useTabStore
|
||||||
.getState()
|
.getState()
|
||||||
.getNoteIdForTab(useTabStore.getState().currentTab);
|
.getNoteIdForTab(useTabStore.getState().currentTab!);
|
||||||
if (noteId) {
|
if (noteId) {
|
||||||
const note = await db.notes?.note(noteId);
|
const note = await db.notes?.note(noteId);
|
||||||
if (editorState().isFocused || editorState().isFocused) {
|
if (editorState().isFocused || editorState().isFocused) {
|
||||||
@@ -177,7 +178,7 @@ export const useEditorEvents = (
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyboardDidShow: KeyboardEventListener = () => {
|
const handleKeyboardDidShow: KeyboardEventListener = () => {
|
||||||
editor.commands.keyboardShown(true);
|
editor.commands.keyboardShown(true);
|
||||||
editor.postMessage(EditorEvents.keyboardShown, undefined);
|
editor.postMessage(NativeEvents.keyboardShown, undefined);
|
||||||
};
|
};
|
||||||
const handleKeyboardDidHide: KeyboardEventListener = () => {
|
const handleKeyboardDidHide: KeyboardEventListener = () => {
|
||||||
editor.commands.keyboardShown(false);
|
editor.commands.keyboardShown(false);
|
||||||
@@ -247,7 +248,7 @@ export const useEditorEvents = (
|
|||||||
}
|
}
|
||||||
editorState().currentlyEditing = false;
|
editorState().currentlyEditing = false;
|
||||||
// editor.reset(); Notes remain open.
|
// editor.reset(); Notes remain open.
|
||||||
editor.commands?.blur(useTabStore.getState().currentTab);
|
editor.commands?.blur(useTabStore.getState().currentTab!);
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
if (deviceMode !== "mobile" && fullscreen) {
|
if (deviceMode !== "mobile" && fullscreen) {
|
||||||
if (fullscreen) {
|
if (fullscreen) {
|
||||||
@@ -268,7 +269,6 @@ export const useEditorEvents = (
|
|||||||
}, [editor, deviceMode, fullscreen]);
|
}, [editor, deviceMode, fullscreen]);
|
||||||
|
|
||||||
const onHardwareBackPress = useCallback(() => {
|
const onHardwareBackPress = useCallback(() => {
|
||||||
console.log(tabBarRef.current?.page());
|
|
||||||
if (tabBarRef.current?.page() === 2) {
|
if (tabBarRef.current?.page() === 2) {
|
||||||
onBackPress();
|
onBackPress();
|
||||||
return true;
|
return true;
|
||||||
@@ -353,25 +353,25 @@ export const useEditorEvents = (
|
|||||||
const editorMessage = JSON.parse(data) as EditorMessage<any>;
|
const editorMessage = JSON.parse(data) as EditorMessage<any>;
|
||||||
|
|
||||||
if (editorMessage.hasTimeout && editorMessage.resolverId) {
|
if (editorMessage.hasTimeout && editorMessage.resolverId) {
|
||||||
editor.postMessage(EditorEvents.resolve, {
|
editor.postMessage(NativeEvents.resolve, {
|
||||||
data: true,
|
data: true,
|
||||||
resolverId: editorMessage.resolverId
|
resolverId: editorMessage.resolverId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (editorMessage.type === EventTypes.load) {
|
if (editorMessage.type === EditorEvents.load) {
|
||||||
DatabaseLogger.log("Editor is ready");
|
DatabaseLogger.log("Editor is ready");
|
||||||
editor.onLoad();
|
editor.onLoad();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (editorMessage.type === EventTypes.back) {
|
if (editorMessage.type === EditorEvents.back) {
|
||||||
return onBackPress();
|
return onBackPress();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
editorMessage.sessionId !== editor.sessionId.current &&
|
editorMessage.sessionId !== editor.sessionId.current &&
|
||||||
editorMessage.type !== EditorEvents.status
|
editorMessage.type !== NativeEvents.status
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -381,8 +381,8 @@ export const useEditorEvents = (
|
|||||||
.getNoteIdForTab(editorMessage.tabId);
|
.getNoteIdForTab(editorMessage.tabId);
|
||||||
|
|
||||||
switch (editorMessage.type) {
|
switch (editorMessage.type) {
|
||||||
case EventTypes.content:
|
case EditorEvents.content:
|
||||||
DatabaseLogger.log("EventTypes.content");
|
DatabaseLogger.log("EditorEvents.content");
|
||||||
editor.saveContent({
|
editor.saveContent({
|
||||||
type: editorMessage.type,
|
type: editorMessage.type,
|
||||||
content: editorMessage.value.html as string,
|
content: editorMessage.value.html as string,
|
||||||
@@ -392,8 +392,8 @@ export const useEditorEvents = (
|
|||||||
pendingChanges: editorMessage.value?.pendingChanges
|
pendingChanges: editorMessage.value?.pendingChanges
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case EventTypes.title:
|
case EditorEvents.title:
|
||||||
DatabaseLogger.log("EventTypes.title");
|
DatabaseLogger.log("EditorEvents.title");
|
||||||
editor.saveContent({
|
editor.saveContent({
|
||||||
type: editorMessage.type,
|
type: editorMessage.type,
|
||||||
title: editorMessage.value?.title as string,
|
title: editorMessage.value?.title as string,
|
||||||
@@ -403,10 +403,10 @@ export const useEditorEvents = (
|
|||||||
pendingChanges: editorMessage.value?.pendingChanges
|
pendingChanges: editorMessage.value?.pendingChanges
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case EventTypes.logger:
|
case EditorEvents.logger:
|
||||||
logger.info("[EDITOR LOG]", editorMessage.value);
|
logger.info("[EDITOR LOG]", editorMessage.value);
|
||||||
break;
|
break;
|
||||||
case EventTypes.dbLogger:
|
case EditorEvents.dbLogger:
|
||||||
if (editorMessage.value.error) {
|
if (editorMessage.value.error) {
|
||||||
DatabaseLogger.error(
|
DatabaseLogger.error(
|
||||||
editorMessage.value.error,
|
editorMessage.value.error,
|
||||||
@@ -419,12 +419,12 @@ export const useEditorEvents = (
|
|||||||
DatabaseLogger.info("[EDITOR_LOG]" + editorMessage.value.message);
|
DatabaseLogger.info("[EDITOR_LOG]" + editorMessage.value.message);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case EventTypes.contentchange:
|
case EditorEvents.contentchange:
|
||||||
editor.onContentChanged(editorMessage.noteId);
|
editor.onContentChanged(editorMessage.noteId);
|
||||||
break;
|
break;
|
||||||
case EventTypes.selection:
|
case EditorEvents.selection:
|
||||||
break;
|
break;
|
||||||
case EventTypes.reminders:
|
case EditorEvents.reminders:
|
||||||
if (!noteId) {
|
if (!noteId) {
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
heading: strings.createNoteFirst(),
|
heading: strings.createNoteFirst(),
|
||||||
@@ -442,7 +442,7 @@ export const useEditorEvents = (
|
|||||||
onAdd: () => ReminderSheet.present(undefined, note, true)
|
onAdd: () => ReminderSheet.present(undefined, note, true)
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case EventTypes.newtag:
|
case EditorEvents.newtag:
|
||||||
if (!noteId) {
|
if (!noteId) {
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
heading: strings.createNoteFirst(),
|
heading: strings.createNoteFirst(),
|
||||||
@@ -452,7 +452,7 @@ export const useEditorEvents = (
|
|||||||
}
|
}
|
||||||
ManageTagsSheet.present([noteId]);
|
ManageTagsSheet.present([noteId]);
|
||||||
break;
|
break;
|
||||||
case EventTypes.tag:
|
case EditorEvents.tag:
|
||||||
if (editorMessage.value) {
|
if (editorMessage.value) {
|
||||||
if (!noteId) return;
|
if (!noteId) return;
|
||||||
const note = await db.notes.note(noteId);
|
const note = await db.notes.note(noteId);
|
||||||
@@ -468,7 +468,7 @@ export const useEditorEvents = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case EventTypes.filepicker:
|
case EditorEvents.filepicker:
|
||||||
editorState().isAwaitingResult = true;
|
editorState().isAwaitingResult = true;
|
||||||
const { pick } = require("./picker").default;
|
const { pick } = require("./picker").default;
|
||||||
pick({
|
pick({
|
||||||
@@ -480,14 +480,14 @@ export const useEditorEvents = (
|
|||||||
editorState().isAwaitingResult = false;
|
editorState().isAwaitingResult = false;
|
||||||
}, 1000);
|
}, 1000);
|
||||||
break;
|
break;
|
||||||
case EventTypes.download: {
|
case EditorEvents.download: {
|
||||||
const downloadAttachment =
|
const downloadAttachment =
|
||||||
require("../../../common/filesystem/download-attachment").default;
|
require("../../../common/filesystem/download-attachment").default;
|
||||||
downloadAttachment((editorMessage.value as Attachment)?.hash, true);
|
downloadAttachment((editorMessage.value as Attachment)?.hash, true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case EventTypes.getAttachmentData: {
|
case EditorEvents.getAttachmentData: {
|
||||||
const attachment = (editorMessage.value as any)
|
const attachment = (editorMessage.value as any)
|
||||||
?.attachment as Attachment;
|
?.attachment as Attachment;
|
||||||
|
|
||||||
@@ -507,14 +507,14 @@ export const useEditorEvents = (
|
|||||||
!!data,
|
!!data,
|
||||||
editorMessage.resolverId
|
editorMessage.resolverId
|
||||||
);
|
);
|
||||||
editor.postMessage(EditorEvents.resolve, {
|
editor.postMessage(NativeEvents.resolve, {
|
||||||
resolverId: editorMessage.resolverId,
|
resolverId: editorMessage.resolverId,
|
||||||
data
|
data
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
DatabaseLogger.error(e);
|
DatabaseLogger.error(e);
|
||||||
editor.postMessage(EditorEvents.resolve, {
|
editor.postMessage(NativeEvents.resolve, {
|
||||||
resolverId: editorMessage.resolverId,
|
resolverId: editorMessage.resolverId,
|
||||||
data: undefined
|
data: undefined
|
||||||
});
|
});
|
||||||
@@ -523,26 +523,26 @@ export const useEditorEvents = (
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case EventTypes.pro:
|
case EditorEvents.pro:
|
||||||
if (editor.state.current?.isFocused) {
|
if (editor.state.current?.isFocused) {
|
||||||
editor.state.current.isFocused = true;
|
editor.state.current.isFocused = true;
|
||||||
}
|
}
|
||||||
eSendEvent(eOpenPremiumDialog);
|
eSendEvent(eOpenPremiumDialog);
|
||||||
break;
|
break;
|
||||||
case EventTypes.monograph:
|
case EditorEvents.monograph:
|
||||||
publishNote();
|
publishNote();
|
||||||
break;
|
break;
|
||||||
case EventTypes.properties:
|
case EditorEvents.properties:
|
||||||
showActionsheet();
|
showActionsheet();
|
||||||
break;
|
break;
|
||||||
case EventTypes.scroll:
|
case EditorEvents.scroll:
|
||||||
editorState().scrollPosition = editorMessage.value;
|
editorState().scrollPosition = editorMessage.value;
|
||||||
break;
|
break;
|
||||||
case EventTypes.fullscreen:
|
case EditorEvents.fullscreen:
|
||||||
editorState().isFullscreen = true;
|
editorState().isFullscreen = true;
|
||||||
eSendEvent(eOpenFullscreenEditor);
|
eSendEvent(eOpenFullscreenEditor);
|
||||||
break;
|
break;
|
||||||
case EventTypes.link:
|
case EditorEvents.link:
|
||||||
if (editorMessage.value.startsWith("nn://")) {
|
if (editorMessage.value.startsWith("nn://")) {
|
||||||
openInternalLink(editorMessage.value);
|
openInternalLink(editorMessage.value);
|
||||||
console.log(
|
console.log(
|
||||||
@@ -554,7 +554,7 @@ export const useEditorEvents = (
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EventTypes.previewAttachment: {
|
case EditorEvents.previewAttachment: {
|
||||||
const hash = (editorMessage.value as Attachment)?.hash;
|
const hash = (editorMessage.value as Attachment)?.hash;
|
||||||
const attachment = await db.attachments?.attachment(hash);
|
const attachment = await db.attachments?.attachment(hash);
|
||||||
if (!attachment) return;
|
if (!attachment) return;
|
||||||
@@ -565,26 +565,41 @@ export const useEditorEvents = (
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EventTypes.copyToClipboard: {
|
case EditorEvents.copyToClipboard: {
|
||||||
Clipboard.setString(editorMessage.value as string);
|
Clipboard.setString(editorMessage.value as string);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EventTypes.tabsChanged: {
|
case EditorEvents.saveScroll: {
|
||||||
|
useTabStore.getState().updateTab(editorMessage.tabId, {
|
||||||
|
session: {
|
||||||
|
...editorMessage.value
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EditorEvents.newNote: {
|
||||||
|
eSendEvent(eOnLoadNote, {
|
||||||
|
tabId: editorMessage.tabId,
|
||||||
|
newNote: true
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EditorEvents.tabsChanged: {
|
||||||
// useTabStore.setState({
|
// useTabStore.setState({
|
||||||
// tabs: (editorMessage.value as any)?.tabs,
|
// tabs: (editorMessage.value as any)?.tabs,
|
||||||
// currentTab: (editorMessage.value as any)?.currentTab
|
// currentTab: (editorMessage.value as any)?.currentTab
|
||||||
// });
|
// });
|
||||||
// console.log("Tabs updated");
|
//
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EventTypes.toc:
|
case EditorEvents.toc:
|
||||||
TableOfContents.present(editorMessage.value);
|
TableOfContents.present(editorMessage.value);
|
||||||
break;
|
break;
|
||||||
case EventTypes.showTabs: {
|
case EditorEvents.showTabs: {
|
||||||
EditorTabs.present();
|
EditorTabs.present();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EventTypes.error: {
|
case EditorEvents.error: {
|
||||||
presentSheet({
|
presentSheet({
|
||||||
component: (
|
component: (
|
||||||
<Issue
|
<Issue
|
||||||
@@ -596,27 +611,15 @@ export const useEditorEvents = (
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EventTypes.tabFocused: {
|
case EditorEvents.tabFocused: {
|
||||||
console.log(
|
|
||||||
"Focused tab",
|
|
||||||
editorMessage.tabId,
|
|
||||||
editorMessage.noteId,
|
|
||||||
"Content:",
|
|
||||||
editorMessage.value
|
|
||||||
);
|
|
||||||
|
|
||||||
eSendEvent(eEditorTabFocused, editorMessage.tabId);
|
eSendEvent(eEditorTabFocused, editorMessage.tabId);
|
||||||
|
if (!editor.state.current.initialLoadCalled) break;
|
||||||
if (
|
if (editorMessage.noteId) {
|
||||||
(!editorMessage.value || editor.currentLoadingNoteId.current) &&
|
|
||||||
editorMessage.noteId
|
|
||||||
) {
|
|
||||||
if (!useSettingStore.getState().isAppLoading) {
|
if (!useSettingStore.getState().isAppLoading) {
|
||||||
const note = await db.notes.note(editorMessage.noteId);
|
const note = await db.notes.note(editorMessage.noteId);
|
||||||
if (note) {
|
if (note) {
|
||||||
eSendEvent(eOnLoadNote, {
|
eSendEvent(eOnLoadNote, {
|
||||||
item: note,
|
item: note,
|
||||||
forced: true,
|
|
||||||
tabId: editorMessage.tabId
|
tabId: editorMessage.tabId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -628,7 +631,6 @@ export const useEditorEvents = (
|
|||||||
if (note) {
|
if (note) {
|
||||||
eSendEvent(eOnLoadNote, {
|
eSendEvent(eOnLoadNote, {
|
||||||
item: note,
|
item: note,
|
||||||
forced: true,
|
|
||||||
tabId: editorMessage.tabId
|
tabId: editorMessage.tabId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -639,7 +641,7 @@ export const useEditorEvents = (
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EventTypes.createInternalLink: {
|
case EditorEvents.createInternalLink: {
|
||||||
LinkNote.present(
|
LinkNote.present(
|
||||||
editorMessage.value.attributes,
|
editorMessage.value.attributes,
|
||||||
editorMessage.resolverId as string
|
editorMessage.resolverId as string
|
||||||
@@ -647,25 +649,37 @@ export const useEditorEvents = (
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case EventTypes.unlock: {
|
case EditorEvents.unlock: {
|
||||||
eSendEvent(eUnlockWithPassword, editorMessage.value);
|
eSendEvent(eUnlockWithPassword, editorMessage.value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case EventTypes.unlockWithBiometrics: {
|
case EditorEvents.goBack: {
|
||||||
|
useTabStore.getState().goBack();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EditorEvents.goForward: {
|
||||||
|
useTabStore.getState().goForward();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EditorEvents.unlockWithBiometrics: {
|
||||||
eSendEvent(eUnlockWithBiometrics);
|
eSendEvent(eUnlockWithBiometrics);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case EventTypes.disableReadonlyMode: {
|
case EditorEvents.disableReadonlyMode: {
|
||||||
const noteId = editorMessage.value;
|
const noteId = editorMessage.value;
|
||||||
if (noteId) {
|
if (noteId) {
|
||||||
await db.notes.readonly(false, noteId);
|
await db.notes.readonly(false, noteId);
|
||||||
editor.note.current[noteId] = await db.notes?.note(noteId);
|
editor.note.current[noteId] = await db.notes?.note(noteId);
|
||||||
useTabStore
|
useTabStore
|
||||||
.getState()
|
.getState()
|
||||||
.updateTab(useTabStore.getState().currentTab, {
|
.updateTab(useTabStore.getState().currentTab!, {
|
||||||
readonly: false
|
session: {
|
||||||
|
readonly: false
|
||||||
|
}
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
Navigation.queueRoutesForUpdate();
|
Navigation.queueRoutesForUpdate();
|
||||||
|
|||||||
@@ -32,8 +32,11 @@ import {
|
|||||||
isEncryptedContent,
|
isEncryptedContent,
|
||||||
isTrashItem
|
isTrashItem
|
||||||
} from "@notesnook/core";
|
} from "@notesnook/core";
|
||||||
|
import { EditorEvents } from "@notesnook/editor-mobile/src/utils/editor-events";
|
||||||
|
import { NativeEvents } from "@notesnook/editor-mobile/src/utils/native-events";
|
||||||
import { strings } from "@notesnook/intl";
|
import { strings } from "@notesnook/intl";
|
||||||
import { useThemeEngineStore } from "@notesnook/theme";
|
import { useThemeEngineStore } from "@notesnook/theme";
|
||||||
|
import { Mutex } from "async-mutex";
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import WebView from "react-native-webview";
|
import WebView from "react-native-webview";
|
||||||
import { DatabaseLogger, db } from "../../../common/database";
|
import { DatabaseLogger, db } from "../../../common/database";
|
||||||
@@ -46,6 +49,7 @@ import {
|
|||||||
eUnSubscribeEvent
|
eUnSubscribeEvent
|
||||||
} from "../../../services/event-manager";
|
} from "../../../services/event-manager";
|
||||||
import Navigation from "../../../services/navigation";
|
import Navigation from "../../../services/navigation";
|
||||||
|
import { NotePreviewWidget } from "../../../services/note-preview-widget";
|
||||||
import Notifications from "../../../services/notifications";
|
import Notifications from "../../../services/notifications";
|
||||||
import SettingsService from "../../../services/settings";
|
import SettingsService from "../../../services/settings";
|
||||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||||
@@ -62,12 +66,10 @@ import { sleep } from "../../../utils/time";
|
|||||||
import { unlockVault } from "../../../utils/unlock-vault";
|
import { unlockVault } from "../../../utils/unlock-vault";
|
||||||
import { onNoteCreated } from "../../notes/common";
|
import { onNoteCreated } from "../../notes/common";
|
||||||
import Commands from "./commands";
|
import Commands from "./commands";
|
||||||
import { EventTypes } from "./editor-events";
|
|
||||||
import { SessionHistory } from "./session-history";
|
import { SessionHistory } from "./session-history";
|
||||||
import { EditorState, SavePayload } from "./types";
|
import { EditorState, SavePayload } from "./types";
|
||||||
import { syncTabs, useTabStore } from "./use-tab-store";
|
import { TabSessionItem, syncTabs, useTabStore } from "./use-tab-store";
|
||||||
import {
|
import {
|
||||||
EditorEvents,
|
|
||||||
clearAppState,
|
clearAppState,
|
||||||
defaultState,
|
defaultState,
|
||||||
getAppState,
|
getAppState,
|
||||||
@@ -75,11 +77,63 @@ import {
|
|||||||
isEditorLoaded,
|
isEditorLoaded,
|
||||||
post
|
post
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
|
import { Linking } from "react-native";
|
||||||
|
|
||||||
|
const loadNoteMutex = new Mutex();
|
||||||
|
|
||||||
type NoteWithContent = Note & {
|
type NoteWithContent = Note & {
|
||||||
content?: NoteContent<false>;
|
content?: NoteContent<false>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type LocalTabStateT = {
|
||||||
|
editedAt: number;
|
||||||
|
lastFocusedAt: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LocalTabState {
|
||||||
|
state: Record<string, LocalTabStateT> = {};
|
||||||
|
noteEditedTime: Record<string, number> = {};
|
||||||
|
|
||||||
|
setEditTime(noteId: string, time: number) {
|
||||||
|
this.noteEditedTime[noteId] = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(tabId: string) {
|
||||||
|
return this.state[tabId] || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
set(tabId: string, state: Partial<LocalTabStateT>) {
|
||||||
|
this.state[tabId] = {
|
||||||
|
...this.state[tabId],
|
||||||
|
...state
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(tabId: string) {
|
||||||
|
delete this.state[tabId];
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.state = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
needsRefresh(tabId: string, locked: boolean, readonly: boolean) {
|
||||||
|
const state = this.get(tabId);
|
||||||
|
const tabSession = useTabStore.getState().getTab(tabId)?.session;
|
||||||
|
const noteId = useTabStore.getState().getNoteIdForTab(tabId);
|
||||||
|
|
||||||
|
if (
|
||||||
|
tabSession?.locked !== locked ||
|
||||||
|
tabSession?.readonly !== readonly ||
|
||||||
|
!noteId
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !state.editedAt || state.editedAt < this.noteEditedTime[noteId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const useEditor = (
|
export const useEditor = (
|
||||||
editorId = "",
|
editorId = "",
|
||||||
readonly?: boolean,
|
readonly?: boolean,
|
||||||
@@ -98,7 +152,6 @@ export const useEditor = (
|
|||||||
isPreview?: boolean;
|
isPreview?: boolean;
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
| null
|
|
||||||
| undefined
|
| undefined
|
||||||
>
|
>
|
||||||
>({});
|
>({});
|
||||||
@@ -118,15 +171,17 @@ export const useEditor = (
|
|||||||
const lastContentChangeTime = useRef<Record<string, number>>({});
|
const lastContentChangeTime = useRef<Record<string, number>>({});
|
||||||
const lock = useRef(false);
|
const lock = useRef(false);
|
||||||
const currentLoadingNoteId = useRef<string>();
|
const currentLoadingNoteId = useRef<string>();
|
||||||
const loadingState = useRef<string>();
|
const lastTabFocused = useRef<string>();
|
||||||
const lastTabFocused = useRef(0);
|
|
||||||
|
const localTabState = useRef<LocalTabState>(new LocalTabState());
|
||||||
|
|
||||||
const blockIdRef = useRef<string>();
|
const blockIdRef = useRef<string>();
|
||||||
const postMessage = useCallback(
|
const postMessage = useCallback(
|
||||||
async <T>(type: string, data: T, tabId?: number, waitFor = 300) =>
|
async <T>(type: string, data: T, tabId?: string, waitFor = 300) =>
|
||||||
await post(
|
await post(
|
||||||
editorRef,
|
editorRef,
|
||||||
sessionIdRef.current,
|
sessionIdRef.current,
|
||||||
typeof tabId !== "number" ? useTabStore.getState().currentTab : tabId,
|
tabId || useTabStore.getState().currentTab!,
|
||||||
type,
|
type,
|
||||||
data,
|
data,
|
||||||
waitFor
|
waitFor
|
||||||
@@ -141,7 +196,7 @@ export const useEditor = (
|
|||||||
}, [commands, insets, isDefaultEditor]);
|
}, [commands, insets, isDefaultEditor]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
postMessage(EditorEvents.theme, theme);
|
postMessage(NativeEvents.theme, theme);
|
||||||
}, [theme, postMessage]);
|
}, [theme, postMessage]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -151,15 +206,14 @@ export const useEditor = (
|
|||||||
}, [commands, tags]);
|
}, [commands, tags]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const event = eSubscribeEvent(eEditorTabFocused, (tabId) => {
|
const event = eSubscribeEvent(eEditorTabFocused, (tabId: string) => {
|
||||||
console.log("Editot tab focus changed", lastTabFocused.current, tabId);
|
|
||||||
if (lastTabFocused.current !== tabId) lock.current = false;
|
if (lastTabFocused.current !== tabId) lock.current = false;
|
||||||
lastTabFocused.current = tabId as number;
|
lastTabFocused.current = tabId;
|
||||||
});
|
});
|
||||||
return () => {
|
return () => {
|
||||||
event?.unsubscribe();
|
event?.unsubscribe();
|
||||||
};
|
};
|
||||||
});
|
}, []);
|
||||||
|
|
||||||
const overlay = useCallback(
|
const overlay = useCallback(
|
||||||
(show: boolean, data = { type: "new" }) => {
|
(show: boolean, data = { type: "new" }) => {
|
||||||
@@ -188,12 +242,11 @@ export const useEditor = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
const reset = useCallback(
|
const reset = useCallback(
|
||||||
async (tabId: number, resetState = true, resetContent = true) => {
|
async (tabId: string, resetState = true, resetContent = true) => {
|
||||||
console.log("Resetting tab:", tabId);
|
|
||||||
const noteId = useTabStore.getState().getNoteIdForTab(tabId);
|
const noteId = useTabStore.getState().getNoteIdForTab(tabId);
|
||||||
if (noteId) {
|
if (noteId) {
|
||||||
currentNotes.current?.id && db.fs().cancel(noteId);
|
currentNotes.current?.id && db.fs().cancel(noteId);
|
||||||
currentNotes.current[noteId] = null;
|
currentNotes.current[noteId] = undefined;
|
||||||
currentContents.current[noteId] = null;
|
currentContents.current[noteId] = null;
|
||||||
editorSessionHistory.clearSession(noteId);
|
editorSessionHistory.clearSession(noteId);
|
||||||
lastContentChangeTime.current[noteId] = 0;
|
lastContentChangeTime.current[noteId] = 0;
|
||||||
@@ -201,18 +254,11 @@ export const useEditor = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
saveCount.current = 0;
|
saveCount.current = 0;
|
||||||
loadingState.current = undefined;
|
currentLoadingNoteId.current = undefined;
|
||||||
lock.current = false;
|
lock.current = false;
|
||||||
resetContent && postMessage(EditorEvents.title, "", tabId);
|
resetContent && postMessage(NativeEvents.title, "", tabId);
|
||||||
|
|
||||||
resetContent && (await commands.clearContent(tabId));
|
resetContent && (await commands.clearContent(tabId));
|
||||||
resetContent && (await commands.clearTags(tabId));
|
resetContent && (await commands.clearTags(tabId));
|
||||||
useTabStore.getState().updateTab(tabId, {
|
|
||||||
noteId: undefined,
|
|
||||||
locked: false,
|
|
||||||
noteLocked: false,
|
|
||||||
readonly: false
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[commands, editorSessionHistory, postMessage]
|
[commands, editorSessionHistory, postMessage]
|
||||||
);
|
);
|
||||||
@@ -232,6 +278,16 @@ export const useEditor = (
|
|||||||
try {
|
try {
|
||||||
if (id && !(await db.notes?.note(id))) {
|
if (id && !(await db.notes?.note(id))) {
|
||||||
await reset(tabId);
|
await reset(tabId);
|
||||||
|
useTabStore.getState().updateTab(tabId, {
|
||||||
|
session: {
|
||||||
|
noteId: undefined,
|
||||||
|
noteLocked: undefined,
|
||||||
|
locked: undefined,
|
||||||
|
readonly: undefined,
|
||||||
|
scrollTop: undefined,
|
||||||
|
selection: undefined
|
||||||
|
}
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let note = id ? await db.notes?.note(id) : undefined;
|
let note = id ? await db.notes?.note(id) : undefined;
|
||||||
@@ -270,13 +326,6 @@ export const useEditor = (
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// If note is edited, the tab becomes a persistent tab automatically.
|
|
||||||
if (useTabStore.getState().getTab(tabId)?.previewTab) {
|
|
||||||
useTabStore.getState().updateTab(tabId, {
|
|
||||||
previewTab: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let saved = false;
|
let saved = false;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (saved) return;
|
if (saved) return;
|
||||||
@@ -311,7 +360,9 @@ export const useEditor = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
useTabStore.getState().updateTab(tabId, {
|
useTabStore.getState().updateTab(tabId, {
|
||||||
noteId: id
|
session: {
|
||||||
|
noteId: id
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultNotebook = db.settings.getDefaultNotebook();
|
const defaultNotebook = db.settings.getDefaultNotebook();
|
||||||
@@ -326,7 +377,7 @@ export const useEditor = (
|
|||||||
|
|
||||||
if (!noteData.title) {
|
if (!noteData.title) {
|
||||||
postMessage(
|
postMessage(
|
||||||
EditorEvents.title,
|
NativeEvents.title,
|
||||||
currentNotes.current[id]?.title,
|
currentNotes.current[id]?.title,
|
||||||
tabId
|
tabId
|
||||||
);
|
);
|
||||||
@@ -386,12 +437,19 @@ export const useEditor = (
|
|||||||
id === useTabStore.getState().getCurrentNoteId() &&
|
id === useTabStore.getState().getCurrentNoteId() &&
|
||||||
pendingChanges
|
pendingChanges
|
||||||
) {
|
) {
|
||||||
postMessage(EditorEvents.title, title || note?.title, tabId);
|
postMessage(NativeEvents.title, title || note?.title, tabId);
|
||||||
postMessage(EditorEvents.html, data, tabId);
|
postMessage(NativeEvents.html, data, tabId);
|
||||||
currentNotes.current[id] = note;
|
currentNotes.current[id] = note;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveCount.current++;
|
saveCount.current++;
|
||||||
|
|
||||||
|
clearTimeout(timers.current.onsave);
|
||||||
|
timers.current.onsave = setTimeout(async () => {
|
||||||
|
if (!id || !note) return;
|
||||||
|
NotePreviewWidget.updateNote(id, note);
|
||||||
|
}, 500);
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@@ -429,183 +487,222 @@ export const useEditor = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
const loadNote = useCallback(
|
const loadNote = useCallback(
|
||||||
async (event: {
|
(event: {
|
||||||
item?: Note;
|
item?: Note;
|
||||||
forced?: boolean;
|
|
||||||
newNote?: boolean;
|
newNote?: boolean;
|
||||||
tabId?: number;
|
tabId?: string;
|
||||||
blockId?: string;
|
blockId?: string;
|
||||||
presistTab?: boolean;
|
session?: TabSessionItem;
|
||||||
|
newTab?: boolean;
|
||||||
|
refresh?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
if (!event) return;
|
loadNoteMutex.runExclusive(async () => {
|
||||||
console.log(event.item?.id, event?.item?.title, "loading note...");
|
if (!event) return;
|
||||||
|
if (event.blockId) {
|
||||||
if (event.blockId) {
|
blockIdRef.current = event.blockId;
|
||||||
blockIdRef.current = event.blockId;
|
|
||||||
}
|
|
||||||
state.current.currentlyEditing = true;
|
|
||||||
|
|
||||||
if (
|
|
||||||
!state.current.ready &&
|
|
||||||
(await isEditorLoaded(
|
|
||||||
editorRef,
|
|
||||||
sessionIdRef.current,
|
|
||||||
useTabStore.getState().currentTab
|
|
||||||
))
|
|
||||||
) {
|
|
||||||
state.current.ready = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.newNote) {
|
|
||||||
useTabStore.getState().focusEmptyTab();
|
|
||||||
const tabId = useTabStore.getState().currentTab;
|
|
||||||
currentNotes.current && (await reset(tabId));
|
|
||||||
setTimeout(() => {
|
|
||||||
if (state.current?.ready && !state.current.movedAway)
|
|
||||||
commands.focus(tabId);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (!event.item) {
|
|
||||||
overlay(false);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
console.log("LOADING NOTE", event.item.id);
|
|
||||||
const item = event.item;
|
|
||||||
|
|
||||||
const currentTab = useTabStore
|
|
||||||
.getState()
|
|
||||||
.getTab(useTabStore.getState().currentTab);
|
|
||||||
if (currentTab?.previewTab && item.id !== currentTab.noteId) {
|
|
||||||
await commands.setLoading(true, useTabStore.getState().currentTab);
|
|
||||||
}
|
|
||||||
const isLockedNote = await db.vaults.itemExists(
|
|
||||||
event.item as ItemReference
|
|
||||||
);
|
|
||||||
const tabLocked =
|
|
||||||
isLockedNote && !(event.item as NoteWithContent).content;
|
|
||||||
|
|
||||||
// If note was already opened in a tab, focus that tab.
|
|
||||||
if (typeof event.tabId !== "number") {
|
|
||||||
if (useTabStore.getState().hasTabForNote(event.item.id)) {
|
|
||||||
const tabId = useTabStore.getState().getTabForNote(event.item.id);
|
|
||||||
if (typeof tabId === "number") {
|
|
||||||
useTabStore.getState().updateTab(tabId, {
|
|
||||||
readonly: event.item.readonly || readonly,
|
|
||||||
locked: tabLocked,
|
|
||||||
noteLocked: isLockedNote
|
|
||||||
});
|
|
||||||
useTabStore.getState().focusTab(tabId);
|
|
||||||
setTimeout(() => {
|
|
||||||
if (blockIdRef.current) {
|
|
||||||
commands.scrollIntoViewById(blockIdRef.current);
|
|
||||||
blockIdRef.current = undefined;
|
|
||||||
}
|
|
||||||
}, 150);
|
|
||||||
}
|
|
||||||
console.log("Note already loaded, focusing the tab");
|
|
||||||
} else {
|
|
||||||
if (event.presistTab) {
|
|
||||||
// Open note in new tab.
|
|
||||||
useTabStore.getState().newTab({
|
|
||||||
readonly: event.item.readonly || readonly,
|
|
||||||
locked: tabLocked,
|
|
||||||
noteLocked: isLockedNote,
|
|
||||||
noteId: event.item.id,
|
|
||||||
previewTab: false
|
|
||||||
});
|
|
||||||
console.log("Opening note in new tab");
|
|
||||||
} else {
|
|
||||||
console.log("Opening note in preview tab");
|
|
||||||
// Otherwise we focus the preview tab or create one to open the note in.
|
|
||||||
useTabStore.getState().focusPreviewTab(event.item.id, {
|
|
||||||
readonly: event.item.readonly || readonly,
|
|
||||||
locked: tabLocked,
|
|
||||||
noteLocked: isLockedNote
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (lastTabFocused.current !== event.tabId) {
|
|
||||||
useTabStore.getState().focusTab(event.tabId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const tabId = event.tabId || useTabStore.getState().currentTab;
|
|
||||||
if (lastTabFocused.current !== tabId) {
|
|
||||||
// if ((await waitForEvent(eEditorTabFocused, 1000)) !== tabId) {
|
|
||||||
// console.log("tab id did not match after focus in 1000ms");
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
currentLoadingNoteId.current = item.id;
|
|
||||||
console.log("Waiting for tab to focus");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
state.current.movedAway = false;
|
|
||||||
state.current.currentlyEditing = true;
|
state.current.currentlyEditing = true;
|
||||||
|
|
||||||
if (!tabLocked) {
|
|
||||||
await loadContent(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
currentNotes.current[item.id] &&
|
!state.current.ready &&
|
||||||
loadingState.current &&
|
(await isEditorLoaded(
|
||||||
currentContents.current[item.id]?.data &&
|
editorRef,
|
||||||
loadingState.current === currentContents.current[item.id]?.data
|
sessionIdRef.current,
|
||||||
|
useTabStore.getState().currentTab!
|
||||||
|
))
|
||||||
) {
|
) {
|
||||||
// If note is already loading, return.
|
state.current.ready = true;
|
||||||
console.log("Note is already loading...");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
if (event.newNote && !currentLoadingNoteId.current) {
|
||||||
|
let tabId;
|
||||||
|
|
||||||
if (!state.current.ready) {
|
const currentTab = useTabStore
|
||||||
|
.getState()
|
||||||
|
.getTab(useTabStore.getState().currentTab as string);
|
||||||
|
|
||||||
|
if (useTabStore.getState().tabs.length === 0 || currentTab?.pinned) {
|
||||||
|
tabId = useTabStore.getState().newTab();
|
||||||
|
} else {
|
||||||
|
tabId = useTabStore.getState().currentTab;
|
||||||
|
await reset(tabId!, true, true);
|
||||||
|
if (
|
||||||
|
event.session?.noteId ||
|
||||||
|
useTabStore.getState().getTab(tabId!)?.session?.noteId
|
||||||
|
) {
|
||||||
|
useTabStore.getState().newTabSession(tabId!, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (state.current?.ready && !state.current.movedAway)
|
||||||
|
commands.focus(tabId!);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (!event.item) {
|
||||||
|
overlay(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = event.item;
|
||||||
|
|
||||||
|
const currentTab = useTabStore
|
||||||
|
.getState()
|
||||||
|
.getTab(useTabStore.getState().currentTab as string);
|
||||||
|
|
||||||
|
// If note is already open in a tab, focus that tab.
|
||||||
|
if (useTabStore.getState().hasTabForNote(item.id) && !event.newTab) {
|
||||||
|
const tabId = useTabStore.getState().getTabForNote(item.id);
|
||||||
|
|
||||||
|
if (
|
||||||
|
currentTab?.session?.noteId !== item.id &&
|
||||||
|
tabId !== useTabStore.getState().currentTab
|
||||||
|
) {
|
||||||
|
useTabStore.getState().focusTab(tabId as string);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLockedNote = await db.vaults.itemExists(
|
||||||
|
event.item as ItemReference
|
||||||
|
);
|
||||||
|
|
||||||
|
const tabLocked =
|
||||||
|
isLockedNote && !(event.item as NoteWithContent).content;
|
||||||
|
|
||||||
|
const tabId = event.tabId
|
||||||
|
? event.tabId
|
||||||
|
: useTabStore.getState().currentTab;
|
||||||
|
|
||||||
|
// Check if tab needs to be refreshed.
|
||||||
|
if (!event.newTab) {
|
||||||
|
if (
|
||||||
|
!event.refresh &&
|
||||||
|
tabId &&
|
||||||
|
event.item.id === useTabStore.getState().getNoteIdForTab(tabId) &&
|
||||||
|
!localTabState.current?.needsRefresh(
|
||||||
|
tabId,
|
||||||
|
isLockedNote,
|
||||||
|
item.readonly
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
commands.setLoading(false, tabId);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
localTabState.current?.setEditTime(
|
||||||
|
item.id,
|
||||||
|
localTabState.current?.noteEditedTime[item.id] ||
|
||||||
|
item.dateEdited
|
||||||
|
);
|
||||||
|
localTabState.current?.set(tabId!, {
|
||||||
|
editedAt:
|
||||||
|
localTabState.current?.noteEditedTime[item.id] ||
|
||||||
|
item.dateEdited
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentLoadingNoteId.current = item.id;
|
||||||
|
|
||||||
|
// Show loading overlay if note is not already loaded.
|
||||||
|
if (
|
||||||
|
tabId &&
|
||||||
|
(event.item?.id !== useTabStore.getState().getNoteIdForTab(tabId) ||
|
||||||
|
!currentContents.current[event.item.id]?.data)
|
||||||
|
) {
|
||||||
|
await commands.setLoading(true, tabId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const session: Partial<TabSessionItem> = event.session || {
|
||||||
|
noteId: event.item.id
|
||||||
|
};
|
||||||
|
|
||||||
|
session.noteLocked = isLockedNote;
|
||||||
|
session.locked = tabLocked;
|
||||||
|
session.readonly = item.readonly;
|
||||||
|
|
||||||
|
const tab = useTabStore.getState().getTab(tabId!);
|
||||||
|
|
||||||
|
if (
|
||||||
|
useTabStore.getState().tabs.length === 0 ||
|
||||||
|
event.newTab ||
|
||||||
|
(currentTab?.pinned &&
|
||||||
|
event.item.id !== currentTab?.session?.noteId)
|
||||||
|
) {
|
||||||
|
useTabStore.getState().newTab({
|
||||||
|
session: session
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// A new session is created if the note is changed.
|
||||||
|
// If the note is already opened, the session is updated.
|
||||||
|
if (
|
||||||
|
!tab?.session ||
|
||||||
|
(event.item.id !== tab?.session?.noteId && tab?.session?.noteId)
|
||||||
|
) {
|
||||||
|
useTabStore.getState().newTabSession(tabId!, session);
|
||||||
|
} else {
|
||||||
|
useTabStore.getState().updateTab(tabId!, {
|
||||||
|
session: session
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastTabFocused.current !== tabId) return;
|
||||||
|
|
||||||
|
if (tabBarRef.current?.page() === 2) {
|
||||||
|
state.current.movedAway = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.current.currentlyEditing = true;
|
||||||
|
if (!tabLocked) {
|
||||||
|
await loadContent(item);
|
||||||
|
} else {
|
||||||
|
commands.focus(tabId!);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastContentChangeTime.current[item.id] = item.dateEdited;
|
||||||
currentNotes.current[item.id] = item;
|
currentNotes.current[item.id] = item;
|
||||||
return;
|
|
||||||
|
if (!currentNotes.current[item.id]) return;
|
||||||
|
|
||||||
|
editorSessionHistory.newSession(item.id);
|
||||||
|
|
||||||
|
await commands.setStatus(
|
||||||
|
getFormattedDate(item.dateEdited, "date-time"),
|
||||||
|
"Saved",
|
||||||
|
tabId!
|
||||||
|
);
|
||||||
|
await postMessage(NativeEvents.title, item.title, tabId);
|
||||||
|
overlay(false);
|
||||||
|
|
||||||
|
await postMessage(
|
||||||
|
NativeEvents.html,
|
||||||
|
{
|
||||||
|
data: currentContents.current[item.id]?.data || "",
|
||||||
|
scrollTop: tab?.session?.scrollTop,
|
||||||
|
selection: tab?.session?.selection
|
||||||
|
},
|
||||||
|
tabId,
|
||||||
|
10000
|
||||||
|
);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (blockIdRef.current) {
|
||||||
|
commands.scrollIntoViewById(blockIdRef.current);
|
||||||
|
blockIdRef.current = undefined;
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
await commands.setTags(item);
|
||||||
|
commands.setSettings();
|
||||||
|
setTimeout(() => {
|
||||||
|
if (currentLoadingNoteId.current === event.item?.id) {
|
||||||
|
currentLoadingNoteId.current = undefined;
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
}
|
}
|
||||||
|
postMessage(NativeEvents.theme, theme);
|
||||||
lastContentChangeTime.current[item.id] = 0;
|
});
|
||||||
currentLoadingNoteId.current = item.id;
|
|
||||||
currentNotes.current[item.id] = item;
|
|
||||||
|
|
||||||
if (!currentNotes.current[item.id]) return;
|
|
||||||
|
|
||||||
editorSessionHistory.newSession(item.id);
|
|
||||||
|
|
||||||
await commands.setStatus(
|
|
||||||
getFormattedDate(item.dateEdited, "date-time"),
|
|
||||||
strings.saved(),
|
|
||||||
tabId
|
|
||||||
);
|
|
||||||
|
|
||||||
await postMessage(EditorEvents.title, item.title, tabId);
|
|
||||||
overlay(false);
|
|
||||||
loadingState.current = currentContents.current[item.id]?.data;
|
|
||||||
|
|
||||||
await postMessage(
|
|
||||||
EditorEvents.html,
|
|
||||||
currentContents.current[item.id]?.data || "",
|
|
||||||
tabId,
|
|
||||||
10000
|
|
||||||
);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
if (blockIdRef.current) {
|
|
||||||
commands.scrollIntoViewById(blockIdRef.current);
|
|
||||||
blockIdRef.current = undefined;
|
|
||||||
}
|
|
||||||
}, 300);
|
|
||||||
|
|
||||||
loadingState.current = undefined;
|
|
||||||
await commands.setTags(item);
|
|
||||||
commands.setSettings();
|
|
||||||
setTimeout(() => {
|
|
||||||
if (currentLoadingNoteId.current === event.item?.id) {
|
|
||||||
currentLoadingNoteId.current = undefined;
|
|
||||||
}
|
|
||||||
}, 300);
|
|
||||||
}
|
|
||||||
postMessage(EditorEvents.theme, theme);
|
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
commands,
|
commands,
|
||||||
@@ -613,7 +710,6 @@ export const useEditor = (
|
|||||||
loadContent,
|
loadContent,
|
||||||
overlay,
|
overlay,
|
||||||
postMessage,
|
postMessage,
|
||||||
readonly,
|
|
||||||
reset,
|
reset,
|
||||||
theme
|
theme
|
||||||
]
|
]
|
||||||
@@ -625,97 +721,116 @@ export const useEditor = (
|
|||||||
isLocal?: boolean
|
isLocal?: boolean
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
await (async () => {
|
if (SettingsService.get().disableRealtimeSync && !isLocal) return;
|
||||||
if (SettingsService.get().disableRealtimeSync && !isLocal) return;
|
if (!data) return;
|
||||||
if (!data) return;
|
|
||||||
|
|
||||||
if (isDeleted(data) || isTrashItem(data)) {
|
if (isDeleted(data) || isTrashItem(data)) {
|
||||||
const tabId = useTabStore.getState().getTabForNote(data.id);
|
const tabId = useTabStore.getState().getTabForNote(data.id);
|
||||||
if (tabId !== undefined) {
|
if (tabId !== undefined) {
|
||||||
console.log("Removing tab");
|
await commands.clearContent(tabId);
|
||||||
await commands.clearContent(tabId);
|
useTabStore.getState().removeTab(tabId);
|
||||||
useTabStore.getState().removeTab(tabId);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const noteId =
|
const noteId =
|
||||||
(data as ContentItem).type === "tiptap"
|
(data as ContentItem).type === "tiptap"
|
||||||
? (data as ContentItem).noteId
|
? (data as ContentItem).noteId
|
||||||
: data.id;
|
: data.id;
|
||||||
|
|
||||||
if (!useTabStore.getState().hasTabForNote(noteId)) return;
|
const note = data.type === "note" ? data : await db.notes?.note(noteId);
|
||||||
const tabId = useTabStore.getState().getTabForNote(noteId) as number;
|
|
||||||
|
|
||||||
const tab = useTabStore.getState().getTab(tabId);
|
lock.current = true;
|
||||||
|
|
||||||
const note =
|
// Handle this case where note was locked on another device and synced.
|
||||||
data.type === "note" ? data : await db.notes?.note(noteId);
|
let locked = note
|
||||||
|
? await db.vaults.itemExists(note as ItemReference)
|
||||||
|
: false;
|
||||||
|
|
||||||
lock.current = true;
|
if (data.type === "tiptap") {
|
||||||
|
locked = data.locked;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle this case where note was locked on another device and synced.
|
useTabStore.getState().forEachNoteTab(noteId, async (tab) => {
|
||||||
const locked = note
|
const tabId = tab.id;
|
||||||
? await db.vaults.itemExists(note as ItemReference)
|
|
||||||
: false;
|
let didUnlock = false;
|
||||||
|
|
||||||
if (note) {
|
if (note) {
|
||||||
if (!locked && tab?.noteLocked) {
|
if (!locked && tab?.session?.noteLocked) {
|
||||||
// Note lock removed.
|
if (tab?.session?.noteLocked || tab?.session?.locked) {
|
||||||
if (tab.locked) {
|
if (useTabStore.getState().currentTab !== tabId) {
|
||||||
if (useTabStore.getState().currentTab === tabId) {
|
localTabState.current?.set(tabId, {
|
||||||
eSendEvent(eOnLoadNote, {
|
editedAt: 0
|
||||||
item: note,
|
|
||||||
forced: true
|
|
||||||
});
|
});
|
||||||
} else {
|
}
|
||||||
useTabStore.getState().updateTab(tabId, {
|
|
||||||
|
didUnlock = true;
|
||||||
|
useTabStore.getState().updateTab(tabId, {
|
||||||
|
session: {
|
||||||
locked: false,
|
locked: false,
|
||||||
noteLocked: false
|
noteLocked: false
|
||||||
});
|
}
|
||||||
commands.setLoading(true, tabId);
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (!tab?.noteLocked && locked) {
|
} else if (!tab?.session?.noteLocked && locked) {
|
||||||
// Note lock added.
|
|
||||||
useTabStore.getState().updateTab(tabId, {
|
useTabStore.getState().updateTab(tabId, {
|
||||||
locked: true,
|
session: {
|
||||||
noteLocked: true
|
locked: true,
|
||||||
|
noteLocked: true
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
commands.clearContent(tabId);
|
||||||
if (useTabStore.getState().currentTab !== tabId) {
|
if (useTabStore.getState().currentTab !== tabId) {
|
||||||
commands.clearContent(tabId);
|
|
||||||
commands.setLoading(true, tabId);
|
commands.setLoading(true, tabId);
|
||||||
}
|
}
|
||||||
|
localTabState.current?.set(tabId, {
|
||||||
|
editedAt: 0
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentNotes.current[noteId]?.title !== note.title) {
|
if (
|
||||||
postMessage(EditorEvents.title, note.title, tabId);
|
currentNotes.current[noteId]?.title !== note.title ||
|
||||||
|
didUnlock
|
||||||
|
) {
|
||||||
|
postMessage(NativeEvents.title, note.title, tabId);
|
||||||
}
|
}
|
||||||
commands.setTags(note);
|
commands.setTags(note);
|
||||||
if (currentNotes.current[noteId]?.dateEdited !== note.dateEdited) {
|
if (
|
||||||
|
currentNotes.current[noteId]?.dateEdited !== note.dateEdited ||
|
||||||
|
didUnlock
|
||||||
|
) {
|
||||||
commands.setStatus(
|
commands.setStatus(
|
||||||
getFormattedDate(note.dateEdited, "date-time"),
|
getFormattedDate(note.dateEdited, "date-time"),
|
||||||
strings.saved(),
|
strings.saved(),
|
||||||
tabId as number
|
tabId as string
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (tab.session?.readonly !== note.readonly || didUnlock) {
|
||||||
useTabStore.getState().updateTab(tabId, {
|
useTabStore.getState().updateTab(tabId, {
|
||||||
readonly: note.readonly
|
session: {
|
||||||
});
|
readonly: note.readonly
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.type === "tiptap" && note && !isLocal) {
|
if (data.type === "tiptap" && note && !isLocal) {
|
||||||
if (lastContentChangeTime.current[noteId] >= data.dateModified) {
|
if (
|
||||||
|
lastContentChangeTime.current[noteId] >= data.dateModified &&
|
||||||
|
!didUnlock
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (locked && isEncryptedContent(data)) {
|
if (locked && isEncryptedContent(data)) {
|
||||||
const decryptedContent = await db.vault?.decryptContent(data);
|
const decryptedContent = await db.vault?.decryptContent(data);
|
||||||
if (!decryptedContent) {
|
if (!decryptedContent) {
|
||||||
useTabStore.getState().updateTab(tabId, {
|
useTabStore.getState().updateTab(tab.id, {
|
||||||
locked: true,
|
session: {
|
||||||
noteLocked: true
|
locked: true,
|
||||||
|
noteLocked: true
|
||||||
|
}
|
||||||
});
|
});
|
||||||
if (useTabStore.getState().currentTab !== tabId) {
|
if (useTabStore.getState().currentTab !== tabId) {
|
||||||
commands.clearContent(tabId);
|
commands.clearContent(tabId);
|
||||||
@@ -723,26 +838,43 @@ export const useEditor = (
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await postMessage(
|
await postMessage(
|
||||||
EditorEvents.updatehtml,
|
NativeEvents.updatehtml,
|
||||||
decryptedContent.data,
|
{
|
||||||
|
data: decryptedContent.data,
|
||||||
|
selection: tab.session?.selection,
|
||||||
|
scrollTop: tab.session?.scrollTop
|
||||||
|
},
|
||||||
tabId
|
tabId
|
||||||
);
|
);
|
||||||
currentContents.current[note.id] = decryptedContent;
|
currentContents.current[note.id] = decryptedContent;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const _nextContent = data.data;
|
const _nextContent = data.data;
|
||||||
if (_nextContent === currentContents.current[note.id]?.data) {
|
if (
|
||||||
|
_nextContent === currentContents.current[note.id]?.data &&
|
||||||
|
!didUnlock
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastContentChangeTime.current[note.id] = note.dateEdited;
|
lastContentChangeTime.current[note.id] = note.dateEdited;
|
||||||
await postMessage(EditorEvents.updatehtml, _nextContent, tabId);
|
console.log(tab.session?.selection);
|
||||||
|
await postMessage(
|
||||||
|
NativeEvents.updatehtml,
|
||||||
|
{
|
||||||
|
data: _nextContent,
|
||||||
|
selection: tab.session?.selection,
|
||||||
|
scrollTop: tab.session?.scrollTop
|
||||||
|
},
|
||||||
|
tabId
|
||||||
|
);
|
||||||
if (!isEncryptedContent(data)) {
|
if (!isEncryptedContent(data)) {
|
||||||
currentContents.current[note.id] =
|
currentContents.current[note.id] =
|
||||||
data as UnencryptedContentItem;
|
data as UnencryptedContentItem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})();
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
DatabaseLogger.error(e as Error, "Error when applying sync changes");
|
DatabaseLogger.error(e as Error, "Error when applying sync changes");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -778,7 +910,7 @@ export const useEditor = (
|
|||||||
content?: string;
|
content?: string;
|
||||||
type: string;
|
type: string;
|
||||||
ignoreEdit: boolean;
|
ignoreEdit: boolean;
|
||||||
tabId: number;
|
tabId: string;
|
||||||
pendingChanges?: boolean;
|
pendingChanges?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
DatabaseLogger.log(
|
DatabaseLogger.log(
|
||||||
@@ -807,9 +939,13 @@ export const useEditor = (
|
|||||||
|
|
||||||
if (noteId) {
|
if (noteId) {
|
||||||
lastContentChangeTime.current[noteId] = Date.now();
|
lastContentChangeTime.current[noteId] = Date.now();
|
||||||
|
localTabState.current?.setEditTime(noteId, Date.now());
|
||||||
|
localTabState?.current?.set(tabId, {
|
||||||
|
editedAt: Date.now()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === EventTypes.content && noteId) {
|
if (type === EditorEvents.content && noteId) {
|
||||||
currentContents.current[noteId as string] = {
|
currentContents.current[noteId as string] = {
|
||||||
data: content,
|
data: content,
|
||||||
type: "tiptap",
|
type: "tiptap",
|
||||||
@@ -859,7 +995,9 @@ export const useEditor = (
|
|||||||
if (!appState) return;
|
if (!appState) return;
|
||||||
state.current.isRestoringState = true;
|
state.current.isRestoringState = true;
|
||||||
state.current.currentlyEditing = true;
|
state.current.currentlyEditing = true;
|
||||||
state.current.movedAway = false;
|
if (tabBarRef.current?.page() === 2) {
|
||||||
|
state.current.movedAway = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!state.current.editorStateRestored) {
|
if (!state.current.editorStateRestored) {
|
||||||
state.current.isRestoringState = true;
|
state.current.isRestoringState = true;
|
||||||
@@ -890,9 +1028,12 @@ export const useEditor = (
|
|||||||
!(await isEditorLoaded(
|
!(await isEditorLoaded(
|
||||||
editorRef,
|
editorRef,
|
||||||
sessionIdRef.current,
|
sessionIdRef.current,
|
||||||
useTabStore.getState().currentTab
|
useTabStore.getState().currentTab!
|
||||||
))
|
))
|
||||||
) {
|
) {
|
||||||
|
localTabState.current?.set(useTabStore.getState().currentTab!, {
|
||||||
|
editedAt: 0
|
||||||
|
});
|
||||||
eSendEvent(eEditorReset, "onReady");
|
eSendEvent(eEditorReset, "onReady");
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
@@ -903,36 +1044,63 @@ export const useEditor = (
|
|||||||
}, [isDefaultEditor, restoreEditorState]);
|
}, [isDefaultEditor, restoreEditorState]);
|
||||||
|
|
||||||
const onLoad = useCallback(async () => {
|
const onLoad = useCallback(async () => {
|
||||||
|
const isAppLoading = useSettingStore.getState().isAppLoading;
|
||||||
|
if (isAppLoading) {
|
||||||
|
const sub = useSettingStore.subscribe((state) => {
|
||||||
|
if (!state.isAppLoading) {
|
||||||
|
sub();
|
||||||
|
onLoad();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
postMessage(EditorEvents.theme, theme);
|
postMessage(NativeEvents.theme, theme);
|
||||||
});
|
});
|
||||||
commands.setInsets(
|
commands.setInsets(
|
||||||
isDefaultEditor ? insets : { top: 0, left: 0, right: 0, bottom: 0 }
|
isDefaultEditor ? insets : { top: 0, left: 0, right: 0, bottom: 0 }
|
||||||
);
|
);
|
||||||
await commands.setSettings();
|
await commands.setSettings();
|
||||||
|
|
||||||
|
localTabState.current?.set(useTabStore.getState().currentTab!, {
|
||||||
|
editedAt: 0
|
||||||
|
});
|
||||||
|
|
||||||
if (!state.current.ready) {
|
if (!state.current.ready) {
|
||||||
state.current.ready = true;
|
state.current.ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const noteId = useTabStore.getState().getCurrentNoteId();
|
|
||||||
if (!noteId) {
|
|
||||||
loadNote({ newNote: true });
|
|
||||||
if (tabBarRef.current?.page() === 1) {
|
|
||||||
state.current.currentlyEditing = false;
|
|
||||||
}
|
|
||||||
} else if (state.current?.initialLoadCalled) {
|
|
||||||
const note = currentNotes.current[noteId];
|
|
||||||
if (note) {
|
|
||||||
loadNote({
|
|
||||||
item: note
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!state.current?.initialLoadCalled) {
|
if (!state.current?.initialLoadCalled) {
|
||||||
|
const url = await Linking.getInitialURL();
|
||||||
|
let noteId = url && new URL(url).searchParams.get("id");
|
||||||
|
if (noteId) {
|
||||||
|
const note = await db.notes?.note(noteId);
|
||||||
|
if (note) {
|
||||||
|
loadNote({
|
||||||
|
item: note
|
||||||
|
});
|
||||||
|
}
|
||||||
|
tabBarRef.current?.goToPage(1);
|
||||||
|
} else {
|
||||||
|
noteId = useTabStore.getState().getCurrentNoteId() || null;
|
||||||
|
if (!noteId) {
|
||||||
|
loadNote({ newNote: true });
|
||||||
|
if (tabBarRef.current?.page() === 1) {
|
||||||
|
state.current.currentlyEditing = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const note = await db.notes.note(noteId);
|
||||||
|
if (note) {
|
||||||
|
loadNote({
|
||||||
|
item: note
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
state.current.initialLoadCalled = true;
|
state.current.initialLoadCalled = true;
|
||||||
|
overlay(false);
|
||||||
}
|
}
|
||||||
overlay(false);
|
|
||||||
}, [
|
}, [
|
||||||
postMessage,
|
postMessage,
|
||||||
theme,
|
theme,
|
||||||
|
|||||||
@@ -16,18 +16,27 @@ GNU General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
import {
|
||||||
|
TabHistory as TabHistoryType,
|
||||||
|
TabSessionHistory
|
||||||
|
} from "@notesnook/common";
|
||||||
|
import { getId } from "@notesnook/core";
|
||||||
|
import { MMKVLoader } from "react-native-mmkv-storage";
|
||||||
import create from "zustand";
|
import create from "zustand";
|
||||||
import { persist, StateStorage } from "zustand/middleware";
|
import { persist, StateStorage } from "zustand/middleware";
|
||||||
|
import { db } from "../../../common/database";
|
||||||
import { MMKV } from "../../../common/database/mmkv";
|
import { MMKV } from "../../../common/database/mmkv";
|
||||||
|
import { eSendEvent } from "../../../services/event-manager";
|
||||||
|
import { eOnLoadNote } from "../../../utils/events";
|
||||||
import { editorController } from "./utils";
|
import { editorController } from "./utils";
|
||||||
|
|
||||||
class History {
|
class TabHistory {
|
||||||
history: number[];
|
history: string[];
|
||||||
constructor() {
|
constructor() {
|
||||||
this.history = [0];
|
this.history = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
add(item: number) {
|
add(item: string) {
|
||||||
const index = this.history.findIndex((id) => item === id);
|
const index = this.history.findIndex((id) => item === id);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
// Item already exists, move it to the top
|
// Item already exists, move it to the top
|
||||||
@@ -36,19 +45,19 @@ class History {
|
|||||||
this.history.unshift(item); // Add item to the beginning of the array
|
this.history.unshift(item); // Add item to the beginning of the array
|
||||||
|
|
||||||
useTabStore.setState({
|
useTabStore.setState({
|
||||||
tabHistory: this.history.slice()
|
historyNew: this.history.slice()
|
||||||
});
|
});
|
||||||
return true; // Item added successfully
|
return true; // Item added successfully
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(id: number) {
|
remove(id: string) {
|
||||||
const index = this.history.findIndex((item) => item === id);
|
const index = this.history.findIndex((item) => item === id);
|
||||||
if (index >= -1 && index < this.history.length) {
|
if (index >= -1 && index < this.history.length) {
|
||||||
const removedItem = this.history.splice(index, 1)[0];
|
const removedItem = this.history.splice(index, 1)[0];
|
||||||
return removedItem;
|
return removedItem;
|
||||||
}
|
}
|
||||||
useTabStore.setState({
|
useTabStore.setState({
|
||||||
tabHistory: this.history.slice()
|
historyNew: this.history.slice()
|
||||||
});
|
});
|
||||||
return null; // Invalid index
|
return null; // Invalid index
|
||||||
}
|
}
|
||||||
@@ -59,7 +68,7 @@ class History {
|
|||||||
return restoredItem;
|
return restoredItem;
|
||||||
}
|
}
|
||||||
useTabStore.setState({
|
useTabStore.setState({
|
||||||
tabHistory: this.history.slice()
|
historyNew: this.history.slice()
|
||||||
});
|
});
|
||||||
return null; // History is empty
|
return null; // History is empty
|
||||||
}
|
}
|
||||||
@@ -69,149 +78,366 @@ class History {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TabItem = {
|
export type TabSessionItem = {
|
||||||
id: number;
|
id: string;
|
||||||
noteId?: string;
|
noteId?: string;
|
||||||
previewTab?: boolean;
|
scrollTop?: number;
|
||||||
readonly?: boolean;
|
selection?: { to: number; from: number };
|
||||||
locked?: boolean;
|
|
||||||
noteLocked?: boolean;
|
noteLocked?: boolean;
|
||||||
pinned?: boolean;
|
locked?: boolean;
|
||||||
|
readonly?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const history = new History();
|
const TabSessionStorageKV = new MMKVLoader()
|
||||||
|
.withInstanceID("tab-session-storage")
|
||||||
|
.disableIndexing()
|
||||||
|
.initialize();
|
||||||
|
|
||||||
|
class TabSessionStorage {
|
||||||
|
static storage: typeof TabSessionStorageKV = TabSessionStorageKV;
|
||||||
|
|
||||||
|
static get(id: string): TabSessionItem | null {
|
||||||
|
if (!id) return null;
|
||||||
|
return TabSessionStorage.storage.getMap(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static set(id: string, session: TabSessionItem): void {
|
||||||
|
if (!id) return;
|
||||||
|
TabSessionStorage.storage.setMap(id, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
static update(id: string, session: Partial<TabSessionItem>) {
|
||||||
|
const currentSession = TabSessionStorage.get(id);
|
||||||
|
const newSession = {
|
||||||
|
...currentSession,
|
||||||
|
...session,
|
||||||
|
id: currentSession?.id || session.id || id
|
||||||
|
};
|
||||||
|
|
||||||
|
TabSessionStorage.set(id, newSession as TabSessionItem);
|
||||||
|
return newSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
static remove(id: string) {
|
||||||
|
if (!id) return;
|
||||||
|
TabSessionStorageKV.removeItem(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function syncTabs(
|
||||||
|
type: "tabs" | "history" | "biometry" | "all" = "all"
|
||||||
|
) {
|
||||||
|
const data: Partial<TabStore> = {};
|
||||||
|
if (type === "tabs" || type === "all") {
|
||||||
|
data.tabs = useTabStore.getState().tabs;
|
||||||
|
data.currentTab = useTabStore.getState().currentTab;
|
||||||
|
}
|
||||||
|
if (type === "history" || type === "all") {
|
||||||
|
data.canGoBack = useTabStore.getState().canGoBack;
|
||||||
|
data.canGoForward = useTabStore.getState().canGoForward;
|
||||||
|
data.sessionId = useTabStore.getState().sessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "biometry" || type === "all") {
|
||||||
|
data.biometryAvailable = useTabStore.getState().biometryAvailable;
|
||||||
|
data.biometryEnrolled = useTabStore.getState().biometryEnrolled;
|
||||||
|
}
|
||||||
|
|
||||||
|
editorController.current?.commands.doAsync(`
|
||||||
|
globalThis.tabStore?.setState(${JSON.stringify(data)});
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
export const tabSessionHistory = new TabSessionHistory({
|
||||||
|
get() {
|
||||||
|
return useTabStore.getState();
|
||||||
|
},
|
||||||
|
set(state) {
|
||||||
|
useTabStore.setState({
|
||||||
|
...state
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TabItem = {
|
||||||
|
id: string;
|
||||||
|
pinned?: boolean;
|
||||||
|
needsRefresh?: boolean;
|
||||||
|
session?: Partial<TabSessionItem>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const history = new TabHistory();
|
||||||
|
|
||||||
export type TabStore = {
|
export type TabStore = {
|
||||||
tabs: TabItem[];
|
tabs: TabItem[];
|
||||||
currentTab: number;
|
currentTab: string;
|
||||||
updateTab: (id: number, options: Omit<Partial<TabItem>, "id">) => void;
|
updateTab: (id: string, options: Omit<Partial<TabItem>, "id">) => void;
|
||||||
focusPreviewTab: (
|
focusPreviewTab: (
|
||||||
noteId: string,
|
noteId: string,
|
||||||
options: Omit<Partial<TabItem>, "id">
|
options: Omit<Partial<TabItem>, "id">
|
||||||
) => void;
|
) => void;
|
||||||
removeTab: (index: number) => void;
|
removeTab: (index: string) => void;
|
||||||
moveTab: (index: number, toIndex: number) => void;
|
moveTab: (index: number, toIndex: number) => void;
|
||||||
newTab: (options?: Omit<Partial<TabItem>, "id">) => void;
|
newTab: (options?: Omit<Partial<TabItem>, "id">) => string;
|
||||||
focusTab: (id: number) => void;
|
focusTab: (id: string) => void;
|
||||||
getNoteIdForTab: (id: number) => string | undefined;
|
getNoteIdForTab: (id: string) => string | undefined;
|
||||||
getTabForNote: (noteId: string) => number | undefined;
|
getTabForNote: (noteId: string) => string | undefined;
|
||||||
|
getTabsForNote: (noteId: string) => TabItem[];
|
||||||
|
forEachNoteTab: (noteId: string, cb: (tab: TabItem) => void) => void;
|
||||||
hasTabForNote: (noteId: string) => boolean;
|
hasTabForNote: (noteId: string) => boolean;
|
||||||
focusEmptyTab: () => void;
|
focusEmptyTab: () => void;
|
||||||
getCurrentNoteId: () => string | undefined;
|
getCurrentNoteId: () => string | undefined;
|
||||||
getTab: (tabId: number) => TabItem | undefined;
|
getTab: (tabId: string) => TabItem | undefined;
|
||||||
tabHistory: number[];
|
newTabSession: (
|
||||||
|
id: string,
|
||||||
|
options?: Omit<Partial<TabSessionItem>, "id">
|
||||||
|
) => void;
|
||||||
|
historyNew: string[];
|
||||||
biometryAvailable?: boolean;
|
biometryAvailable?: boolean;
|
||||||
biometryEnrolled?: boolean;
|
biometryEnrolled?: boolean;
|
||||||
|
tabSessionHistory: TabHistoryType;
|
||||||
|
goBack(): void;
|
||||||
|
goForward(): void;
|
||||||
|
loadSession: (id: string) => Promise<boolean>;
|
||||||
|
canGoBack?: boolean;
|
||||||
|
canGoForward?: boolean;
|
||||||
|
sessionId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getId(id: number, tabs: TabItem[]): number {
|
const DEFAULT_TABS = {
|
||||||
const exists = tabs.find((t) => t.id === id);
|
tabs: [
|
||||||
if (exists) {
|
{
|
||||||
return getId(id + 1, tabs);
|
id: "679da59a3924d4bd56d16d3f",
|
||||||
}
|
session: {
|
||||||
return id;
|
id: "679da5a5667a16db2353a062"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
tabSessionHistory: {
|
||||||
|
"679da59a3924d4bd56d16d3f": {
|
||||||
|
backStack: ["679da5a5667a16db2353a062"],
|
||||||
|
forwardStack: [] as string[]
|
||||||
|
}
|
||||||
|
} as TabHistoryType,
|
||||||
|
historyNew: ["679da59a3924d4bd56d16d3f"],
|
||||||
|
currentTab: "679da59a3924d4bd56d16d3f"
|
||||||
|
} as TabStore;
|
||||||
|
|
||||||
export function syncTabs() {
|
export function resetTabStore() {
|
||||||
editorController.current?.commands.doAsync(`
|
useTabStore.setState({
|
||||||
globalThis.tabStore?.setState({
|
...DEFAULT_TABS
|
||||||
tabs: ${JSON.stringify(useTabStore.getState().tabs)},
|
});
|
||||||
currentTab: ${useTabStore.getState().currentTab},
|
TabSessionStorage.storage.clearStore();
|
||||||
biometryAvailable: ${useTabStore.getState().biometryAvailable},
|
|
||||||
biometryEnrolled: ${useTabStore.getState().biometryEnrolled}
|
|
||||||
});
|
|
||||||
`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useTabStore = create<TabStore>(
|
export const useTabStore = create<TabStore>(
|
||||||
persist(
|
persist(
|
||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
tabs: [
|
...DEFAULT_TABS,
|
||||||
{
|
newTabSession: (
|
||||||
id: 0
|
_id?: string,
|
||||||
|
options?: Omit<Partial<TabSessionItem>, "id">
|
||||||
|
) => {
|
||||||
|
const tabId = _id || (get().currentTab as string);
|
||||||
|
|
||||||
|
const sessionHistory = get().tabSessionHistory[tabId];
|
||||||
|
|
||||||
|
let oldSessionId: string | undefined = undefined;
|
||||||
|
if (sessionHistory) {
|
||||||
|
const allSessions = sessionHistory.backStack.concat(
|
||||||
|
sessionHistory.forwardStack
|
||||||
|
);
|
||||||
|
allSessions.forEach((id) => {
|
||||||
|
if (TabSessionStorage.get(id)?.noteId === options?.noteId) {
|
||||||
|
oldSessionId = id;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
],
|
|
||||||
tabHistory: [0],
|
const sessionId =
|
||||||
history: new History(),
|
oldSessionId &&
|
||||||
currentTab: 0,
|
tabSessionHistory.currentSessionId(tabId) === oldSessionId
|
||||||
updateTab: (id: number, options: Omit<Partial<TabItem>, "id">) => {
|
? oldSessionId
|
||||||
if (!options) return;
|
: tabSessionHistory.add(tabId, oldSessionId);
|
||||||
const index = get().tabs.findIndex((t) => t.id === id);
|
|
||||||
|
let session: Partial<TabSessionItem>;
|
||||||
|
|
||||||
|
if (!oldSessionId) {
|
||||||
|
session = {
|
||||||
|
id: sessionId,
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
TabSessionStorage.set(sessionId, session as TabSessionItem);
|
||||||
|
} else {
|
||||||
|
session = {
|
||||||
|
id: sessionId,
|
||||||
|
...TabSessionStorage.get(oldSessionId),
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = get().tabs.findIndex((t) => t.id === tabId);
|
||||||
if (index == -1) return;
|
if (index == -1) return;
|
||||||
const tabs = [...get().tabs];
|
const tabs = [...get().tabs];
|
||||||
tabs[index] = {
|
tabs[index] = {
|
||||||
...tabs[index],
|
...tabs[index],
|
||||||
...options
|
session: session
|
||||||
};
|
} as TabItem;
|
||||||
|
|
||||||
set({
|
set({
|
||||||
tabs: tabs
|
tabs: tabs
|
||||||
});
|
});
|
||||||
syncTabs();
|
syncTabs();
|
||||||
},
|
},
|
||||||
focusPreviewTab: (
|
updateTab: (id: string, options: Omit<Partial<TabItem>, "id">) => {
|
||||||
noteId: string,
|
if (!options) return;
|
||||||
options: Omit<Partial<TabItem>, "id" | "noteId">
|
const index = get().tabs.findIndex((t) => t.id === id);
|
||||||
) => {
|
if (index == -1) return;
|
||||||
const index = get().tabs.findIndex((t) => t.previewTab);
|
|
||||||
if (index === -1)
|
|
||||||
return get().newTab({
|
|
||||||
noteId,
|
|
||||||
previewTab: true,
|
|
||||||
...options
|
|
||||||
});
|
|
||||||
const tabs = [...get().tabs];
|
const tabs = [...get().tabs];
|
||||||
|
const sessionId =
|
||||||
|
options.session?.id || (tabs[index].session?.id as string);
|
||||||
|
|
||||||
|
const updatedSession = !options.session
|
||||||
|
? tabs[index].session
|
||||||
|
: TabSessionStorage.update(sessionId, options.session);
|
||||||
|
|
||||||
tabs[index] = {
|
tabs[index] = {
|
||||||
...tabs[index],
|
...tabs[index],
|
||||||
...options,
|
...options,
|
||||||
previewTab: true,
|
session: updatedSession
|
||||||
noteId: noteId
|
} as TabItem;
|
||||||
};
|
|
||||||
console.log("focus preview", noteId);
|
|
||||||
set({
|
set({
|
||||||
tabs: tabs
|
tabs: tabs
|
||||||
});
|
});
|
||||||
get().focusTab(tabs[index].id);
|
syncTabs();
|
||||||
},
|
},
|
||||||
removeTab: (id: number) => {
|
goBack: async () => {
|
||||||
const index = get().tabs.findIndex((t) => t.id === id);
|
const currentTab = get().currentTab;
|
||||||
|
if (!currentTab) return;
|
||||||
|
if (!tabSessionHistory.canGoBack(currentTab)) return;
|
||||||
|
|
||||||
|
const id = tabSessionHistory.back(currentTab) as string;
|
||||||
|
const sessionLoaded = await get().loadSession(id);
|
||||||
|
|
||||||
|
if (!sessionLoaded) {
|
||||||
|
const canGoBack = tabSessionHistory.canGoBack(currentTab);
|
||||||
|
if (!canGoBack) {
|
||||||
|
tabSessionHistory.forward(currentTab);
|
||||||
|
syncTabs();
|
||||||
|
} else {
|
||||||
|
get().goBack();
|
||||||
|
tabSessionHistory.remove(currentTab, id);
|
||||||
|
TabSessionStorage.remove(id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
syncTabs();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
goForward: async () => {
|
||||||
|
const currentTab = get().currentTab;
|
||||||
|
if (!currentTab) return;
|
||||||
|
|
||||||
|
if (!tabSessionHistory.canGoForward(currentTab)) return;
|
||||||
|
const id = tabSessionHistory.forward(currentTab) as string;
|
||||||
|
if (!(await get().loadSession(id))) {
|
||||||
|
const canGoForward = tabSessionHistory.canGoForward(currentTab);
|
||||||
|
if (!canGoForward) {
|
||||||
|
tabSessionHistory.back(currentTab);
|
||||||
|
syncTabs();
|
||||||
|
} else {
|
||||||
|
get().goForward();
|
||||||
|
tabSessionHistory.remove(currentTab, id);
|
||||||
|
TabSessionStorage.remove(id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
syncTabs();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loadSession: async (id: string) => {
|
||||||
|
const session = TabSessionStorage.get(id);
|
||||||
|
if (!session) return false;
|
||||||
|
|
||||||
|
const note = session?.noteId
|
||||||
|
? await db.notes.note(session?.noteId)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (note) {
|
||||||
|
const isLocked = await db.vaults.itemExists(note);
|
||||||
|
session.noteLocked = isLocked;
|
||||||
|
session.locked = isLocked;
|
||||||
|
|
||||||
|
session.readonly = note.readonly;
|
||||||
|
} else if (session.noteId) {
|
||||||
|
console.log("Failed to load session...");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
eSendEvent(eOnLoadNote, {
|
||||||
|
item: note,
|
||||||
|
newNote: !note,
|
||||||
|
tabId: get().currentTab,
|
||||||
|
session: session
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
focusPreviewTab: (
|
||||||
|
noteId: string,
|
||||||
|
options: Omit<Partial<TabItem>, "id" | "noteId">
|
||||||
|
) => {},
|
||||||
|
|
||||||
|
removeTab: (id: string) => {
|
||||||
|
const index = get().tabs.findIndex((t) => t.id === id);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
const isFocused = id === get().currentTab;
|
const isFocused = id === get().currentTab;
|
||||||
const nextTabs = get().tabs.slice();
|
const nextTabs = get().tabs.slice();
|
||||||
nextTabs.splice(index, 1);
|
nextTabs.splice(index, 1);
|
||||||
history.remove(id);
|
history.remove(id);
|
||||||
|
|
||||||
|
const tabSessions = tabSessionHistory.getTabHistory(id);
|
||||||
|
tabSessions.back.forEach((id) => TabSessionStorage.remove(id));
|
||||||
|
tabSessions.forward.forEach((id) => TabSessionStorage.remove(id));
|
||||||
|
tabSessionHistory.clearStackForTab(id);
|
||||||
|
|
||||||
if (nextTabs.length === 0) {
|
if (nextTabs.length === 0) {
|
||||||
nextTabs.push({
|
const id = getId();
|
||||||
id: 0
|
set({
|
||||||
|
tabs: [{ id: id }]
|
||||||
});
|
});
|
||||||
|
get().newTabSession(id);
|
||||||
|
get().focusTab(id);
|
||||||
|
} else {
|
||||||
|
set({
|
||||||
|
tabs: nextTabs
|
||||||
|
});
|
||||||
|
if (isFocused) {
|
||||||
|
const lastTab = history.restoreLast();
|
||||||
|
if (lastTab) get().focusTab(lastTab);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
set({
|
syncTabs();
|
||||||
tabs: nextTabs
|
|
||||||
});
|
|
||||||
get().focusTab(
|
|
||||||
isFocused ? history.restoreLast() || 0 : get().currentTab
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
newTab: (options) => {
|
newTab: (options) => {
|
||||||
const id = getId(get().tabs.length, get().tabs);
|
const id = getId();
|
||||||
const nextTabs = [
|
|
||||||
...get().tabs,
|
|
||||||
{
|
|
||||||
id: id,
|
|
||||||
...options
|
|
||||||
}
|
|
||||||
];
|
|
||||||
set({
|
set({
|
||||||
tabs: nextTabs
|
tabs: [
|
||||||
|
...get().tabs,
|
||||||
|
{
|
||||||
|
id: id,
|
||||||
|
...options
|
||||||
|
}
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
get().newTabSession(id, options?.session || {});
|
||||||
get().focusTab(id);
|
get().focusTab(id);
|
||||||
|
return id;
|
||||||
},
|
},
|
||||||
focusEmptyTab: () => {
|
focusEmptyTab: () => {
|
||||||
const index = get().tabs.findIndex((t) => !t.noteId);
|
const index = get().tabs.findIndex((t) => !t.session?.noteId);
|
||||||
if (index === -1) return get().newTab();
|
if (index === -1) return get().newTab();
|
||||||
console.log("focus empty tab", get().tabs[index]);
|
|
||||||
|
|
||||||
get().focusTab(get().tabs[index].id);
|
get().focusTab(get().tabs[index].id);
|
||||||
},
|
},
|
||||||
@@ -224,38 +450,46 @@ export const useTabStore = create<TabStore>(
|
|||||||
syncTabs();
|
syncTabs();
|
||||||
},
|
},
|
||||||
|
|
||||||
focusTab: (id: number) => {
|
focusTab: (id: string) => {
|
||||||
console.log(history.getHistory(), id);
|
|
||||||
history.add(id);
|
history.add(id);
|
||||||
set({
|
set({
|
||||||
currentTab: id
|
currentTab: id,
|
||||||
|
canGoBack: tabSessionHistory.canGoBack(id),
|
||||||
|
canGoForward: tabSessionHistory.canGoForward(id),
|
||||||
|
sessionId: tabSessionHistory.currentSessionId(id)
|
||||||
});
|
});
|
||||||
syncTabs();
|
syncTabs();
|
||||||
},
|
},
|
||||||
getNoteIdForTab: (id: number) => {
|
getNoteIdForTab: (id: string) => {
|
||||||
return get().tabs.find((t) => t.id === id)?.noteId;
|
return get().tabs.find((t) => t.id === id)?.session?.noteId;
|
||||||
},
|
},
|
||||||
hasTabForNote: (noteId: string) => {
|
hasTabForNote: (noteId: string) => {
|
||||||
return (
|
return !!get().tabs.find((t) => t.session?.noteId === noteId);
|
||||||
typeof get().tabs.find((t) => t.noteId === noteId)?.id === "number"
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
getTabForNote: (noteId: string) => {
|
getTabForNote: (noteId: string) => {
|
||||||
return get().tabs.find((t) => t.noteId === noteId)?.id;
|
return get().tabs.find((t) => t.session?.noteId === noteId)?.id;
|
||||||
|
},
|
||||||
|
getTabsForNote(noteId: string) {
|
||||||
|
return get().tabs.filter((t) => t.session?.noteId === noteId);
|
||||||
|
},
|
||||||
|
forEachNoteTab: (noteId: string, cb: (tab: TabItem) => void) => {
|
||||||
|
const tabs = get().tabs.filter((t) => t.session?.noteId === noteId);
|
||||||
|
tabs.forEach(cb);
|
||||||
},
|
},
|
||||||
getCurrentNoteId: () => {
|
getCurrentNoteId: () => {
|
||||||
return get().tabs.find((t) => t.id === get().currentTab)?.noteId;
|
return get().tabs.find((t) => t.id === get().currentTab)?.session
|
||||||
|
?.noteId;
|
||||||
},
|
},
|
||||||
getTab: (tabId) => {
|
getTab: (tabId) => {
|
||||||
return get().tabs.find((t) => t.id === tabId);
|
return get().tabs.find((t) => t.id === tabId);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: "tabs-storage",
|
name: "tabs-storage-v5",
|
||||||
getStorage: () => MMKV as unknown as StateStorage,
|
getStorage: () => MMKV as unknown as StateStorage,
|
||||||
onRehydrateStorage: () => {
|
onRehydrateStorage: () => {
|
||||||
return (state) => {
|
return (state) => {
|
||||||
history.history = state?.tabHistory.slice() || [];
|
history.history = state?.historyNew || [];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ import { eOnLoadNote } from "../../../utils/events";
|
|||||||
import { NotesnookModule } from "../../../utils/notesnook-module";
|
import { NotesnookModule } from "../../../utils/notesnook-module";
|
||||||
import { AppState, EditorState, useEditorType } from "./types";
|
import { AppState, EditorState, useEditorType } from "./types";
|
||||||
import { useTabStore } from "./use-tab-store";
|
import { useTabStore } from "./use-tab-store";
|
||||||
|
import { NativeEvents } from "@notesnook/editor-mobile/src/utils/native-events";
|
||||||
|
|
||||||
export const textInput = createRef<TextInput>();
|
export const textInput = createRef<TextInput>();
|
||||||
export const editorController =
|
export const editorController =
|
||||||
createRef<useEditorType>() as MutableRefObject<useEditorType>;
|
createRef<useEditorType>() as MutableRefObject<useEditorType>;
|
||||||
@@ -46,19 +48,6 @@ export function editorState() {
|
|||||||
return editorController.current?.state.current || defaultState;
|
return editorController.current?.state.current || defaultState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EditorEvents = {
|
|
||||||
html: "native:html",
|
|
||||||
updatehtml: "native:updatehtml",
|
|
||||||
title: "native:title",
|
|
||||||
theme: "native:theme",
|
|
||||||
titleplaceholder: "native:titleplaceholder",
|
|
||||||
logger: "native:logger",
|
|
||||||
status: "native:status",
|
|
||||||
keyboardShown: "native:keyboardShown",
|
|
||||||
attachmentData: "native:attachment-data",
|
|
||||||
resolve: "native:resolve"
|
|
||||||
};
|
|
||||||
|
|
||||||
export function randId(prefix: string) {
|
export function randId(prefix: string) {
|
||||||
return Math.random()
|
return Math.random()
|
||||||
.toString(36)
|
.toString(36)
|
||||||
@@ -72,15 +61,15 @@ export function makeSessionId(id?: string) {
|
|||||||
export async function isEditorLoaded(
|
export async function isEditorLoaded(
|
||||||
ref: RefObject<WebView>,
|
ref: RefObject<WebView>,
|
||||||
sessionId: string,
|
sessionId: string,
|
||||||
tabId: number
|
tabId: string
|
||||||
) {
|
) {
|
||||||
return await post(ref, sessionId, tabId, EditorEvents.status);
|
return await post(ref, sessionId, tabId, NativeEvents.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function post<T>(
|
export async function post<T>(
|
||||||
ref: RefObject<WebView>,
|
ref: RefObject<WebView>,
|
||||||
sessionId: string,
|
sessionId: string,
|
||||||
tabId: number,
|
tabId: string,
|
||||||
type: string,
|
type: string,
|
||||||
value: T | null = null,
|
value: T | null = null,
|
||||||
waitFor = 300
|
waitFor = 300
|
||||||
@@ -135,7 +124,6 @@ export const waitForEvent = async (
|
|||||||
};
|
};
|
||||||
eSubscribeEvent(type, callback);
|
eSubscribeEvent(type, callback);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log("return..");
|
|
||||||
eUnSubscribeEvent(type, callback);
|
eUnSubscribeEvent(type, callback);
|
||||||
resolve(false);
|
resolve(false);
|
||||||
}, waitFor);
|
}, waitFor);
|
||||||
@@ -158,6 +146,9 @@ const canRestoreAppState = (appState: AppState) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let appState: AppState | undefined;
|
let appState: AppState | undefined;
|
||||||
|
export function setAppState(state: AppState) {
|
||||||
|
appState = state;
|
||||||
|
}
|
||||||
export function getAppState() {
|
export function getAppState() {
|
||||||
if (appState && canRestoreAppState(appState)) return appState as AppState;
|
if (appState && canRestoreAppState(appState)) return appState as AppState;
|
||||||
const json = NotesnookModule.getAppState();
|
const json = NotesnookModule.getAppState();
|
||||||
@@ -183,7 +174,7 @@ export async function openInternalLink(url: string) {
|
|||||||
if (!data?.id) return false;
|
if (!data?.id) return false;
|
||||||
if (
|
if (
|
||||||
data.id ===
|
data.id ===
|
||||||
useTabStore.getState().getNoteIdForTab(useTabStore.getState().currentTab)
|
useTabStore.getState().getNoteIdForTab(useTabStore.getState().currentTab!)
|
||||||
) {
|
) {
|
||||||
if (data.params?.blockId) {
|
if (data.params?.blockId) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
169
apps/mobile/app/screens/note-preview-configure/index.tsx
Normal file
169
apps/mobile/app/screens/note-preview-configure/index.tsx
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
/*
|
||||||
|
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 { Note, VirtualizedGrouping } from "@notesnook/core";
|
||||||
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { FlatList, TouchableOpacity, View } from "react-native";
|
||||||
|
import { db } from "../../common/database";
|
||||||
|
import { Header } from "../../components/header";
|
||||||
|
import Input from "../../components/ui/input";
|
||||||
|
import Paragraph from "../../components/ui/typography/paragraph";
|
||||||
|
import { useDBItem } from "../../hooks/use-db-item";
|
||||||
|
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||||
|
import { useSettingStore } from "../../stores/use-setting-store";
|
||||||
|
import { NotesnookModule } from "../../utils/notesnook-module";
|
||||||
|
|
||||||
|
const NoteItem = (props: {
|
||||||
|
id: string | number;
|
||||||
|
items?: VirtualizedGrouping<Note>;
|
||||||
|
}) => {
|
||||||
|
const { colors } = useThemeColors();
|
||||||
|
const [item] = useDBItem(props.id, "note", props.items);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
activeOpacity={0.7}
|
||||||
|
onPress={() => {
|
||||||
|
const widgetId = NotesnookModule.getWidgetId();
|
||||||
|
NotesnookModule.setString(
|
||||||
|
"appPreview",
|
||||||
|
String(widgetId),
|
||||||
|
JSON.stringify(item)
|
||||||
|
);
|
||||||
|
setTimeout(() => {
|
||||||
|
NotesnookModule.saveAndFinish();
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
flexDirection: "column",
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: colors.primary.border,
|
||||||
|
justifyContent: "center",
|
||||||
|
paddingVertical: 12,
|
||||||
|
minHeight: 45
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{!item ? null : (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: "row",
|
||||||
|
paddingHorizontal: 12
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: "column"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Paragraph
|
||||||
|
numberOfLines={1}
|
||||||
|
style={{
|
||||||
|
color: colors.primary.paragraph,
|
||||||
|
fontSize: 15
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.title}
|
||||||
|
</Paragraph>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NotePreviewConfigure = () => {
|
||||||
|
const [items, setItems] = useState<VirtualizedGrouping<Note>>();
|
||||||
|
const loading = useSettingStore((state) => state.isAppLoading);
|
||||||
|
const bounceRef = React.useRef<NodeJS.Timeout>();
|
||||||
|
const { colors } = useThemeColors();
|
||||||
|
const insets = useGlobalSafeAreaInsets();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
useSettingStore.getState().setDeviceMode("mobile");
|
||||||
|
if (loading) return;
|
||||||
|
db.notes.all.sorted(db.settings.getGroupOptions("notes")).then((notes) => {
|
||||||
|
setItems(notes);
|
||||||
|
});
|
||||||
|
}, [loading]);
|
||||||
|
|
||||||
|
const renderItem = React.useCallback(
|
||||||
|
({ index }: { item: boolean; index: number }) => {
|
||||||
|
return <NoteItem id={index} items={items} />;
|
||||||
|
},
|
||||||
|
[items]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
backgroundColor: colors.primary.background,
|
||||||
|
flex: 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Header
|
||||||
|
canGoBack
|
||||||
|
title="Select a note"
|
||||||
|
onLeftMenuButtonPress={() => {
|
||||||
|
NotesnookModule.cancelAndFinish();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
paddingTop: 6
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
placeholder="Search for notes"
|
||||||
|
containerStyle={{
|
||||||
|
height: 50
|
||||||
|
}}
|
||||||
|
onChangeText={(value) => {
|
||||||
|
bounceRef.current = setTimeout(() => {
|
||||||
|
if (!value) {
|
||||||
|
db.notes.all
|
||||||
|
.sorted(db.settings.getGroupOptions("notes"))
|
||||||
|
.then((notes) => {
|
||||||
|
setItems(notes);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
db.lookup
|
||||||
|
.notes(value)
|
||||||
|
.sorted()
|
||||||
|
.then((notes) => {
|
||||||
|
setItems(notes);
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FlatList
|
||||||
|
data={items?.placeholders}
|
||||||
|
keyboardShouldPersistTaps="handled"
|
||||||
|
keyboardDismissMode="interactive"
|
||||||
|
renderItem={renderItem}
|
||||||
|
windowSize={1}
|
||||||
|
ListFooterComponent={<View style={{ height: 200 }} />}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -284,14 +284,13 @@ NotebookScreen.navigate = async (item: Notebook, canGoBack?: boolean) => {
|
|||||||
focusedRouteId == item?.id
|
focusedRouteId == item?.id
|
||||||
) {
|
) {
|
||||||
// Update the route in place instead
|
// Update the route in place instead
|
||||||
console.log("Updating existing route in place");
|
|
||||||
eSendEvent(eUpdateNotebookRoute, {
|
eSendEvent(eUpdateNotebookRoute, {
|
||||||
item: item,
|
item: item,
|
||||||
title: item.title,
|
title: item.title,
|
||||||
canGoBack: canGoBack
|
canGoBack: canGoBack
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log("Pushing new notebook route");
|
|
||||||
// Push a new route
|
// Push a new route
|
||||||
Navigation.push("Notebook", {
|
Navigation.push("Notebook", {
|
||||||
title: item.title,
|
title: item.title,
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ ColoredNotes.navigate = (item: Color, canGoBack: boolean) => {
|
|||||||
const { focusedRouteId } = useNavigationStore.getState();
|
const { focusedRouteId } = useNavigationStore.getState();
|
||||||
|
|
||||||
if (focusedRouteId === item.id) {
|
if (focusedRouteId === item.id) {
|
||||||
console.log("ColoredNotes.navigate: route already focused for color");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -171,7 +171,6 @@ const NotesPage = ({
|
|||||||
setLoadingNotes(false);
|
setLoadingNotes(false);
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.log("Error loading notes", params.current?.title, e, e.stack);
|
|
||||||
setLoadingNotes(false);
|
setLoadingNotes(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ TaggedNotes.navigate = (item: Tag, canGoBack?: boolean) => {
|
|||||||
const { focusedRouteId } = useNavigationStore.getState();
|
const { focusedRouteId } = useNavigationStore.getState();
|
||||||
|
|
||||||
if (focusedRouteId === item.id) {
|
if (focusedRouteId === item.id) {
|
||||||
console.log("TaggedNotes.navigate: route already focused for tag");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -160,9 +160,7 @@ export default function DebugLogs() {
|
|||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
}, [currentLog?.logs]);
|
}, [currentLog?.logs]);
|
||||||
|
|
||||||
const copyLogs = React.useCallback(() => {
|
const copyLogs = React.useCallback(() => {
|
||||||
|
|||||||
@@ -54,7 +54,9 @@ export const Licenses = () => {
|
|||||||
}}
|
}}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
if (!item.link) return;
|
if (!item.link) return;
|
||||||
Linking.openURL(item.link).catch(console.log);
|
Linking.openURL(item.link).catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Heading size={SIZE.sm}>{item.name}</Heading>
|
<Heading size={SIZE.sm}>{item.name}</Heading>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user