mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-31 11:08:47 +02:00
Compare commits
35 Commits
3.0.24-and
...
fix/intl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f545216dc3 | ||
|
|
989de21e44 | ||
|
|
2c051f9d21 | ||
|
|
4845d40c0b | ||
|
|
b4615fa9aa | ||
|
|
f24ff63c06 | ||
|
|
7518e6badc | ||
|
|
0fcbdbcdc3 | ||
|
|
999856311f | ||
|
|
98f4e08c62 | ||
|
|
f3140cbd8f | ||
|
|
029cadedc4 | ||
|
|
6cdeb48e56 | ||
|
|
2c8abc4d00 | ||
|
|
04820f6500 | ||
|
|
fc6a843a1a | ||
|
|
129dad06c2 | ||
|
|
063b387807 | ||
|
|
e0058fe30b | ||
|
|
a3c297636e | ||
|
|
afc534806d | ||
|
|
4456cc2184 | ||
|
|
2610db90d2 | ||
|
|
f16c9804ee | ||
|
|
7a3a1aa9c2 | ||
|
|
57d0904bee | ||
|
|
5ca6f98c6c | ||
|
|
dbe32e8af6 | ||
|
|
e0758bf353 | ||
|
|
3c1b5d1ee8 | ||
|
|
074008ac67 | ||
|
|
7c56a6beb3 | ||
|
|
3e9d7a70d0 | ||
|
|
e2ca1a0df6 | ||
|
|
392456a0d9 |
@@ -10,7 +10,7 @@ const authors = readFileSync("AUTHORS", "utf-8");
|
||||
const isAuthor = authors.includes(`<${authorEmail}>`);
|
||||
|
||||
const SCOPES = [
|
||||
// for full list of scopes + details see: https://github.com/streetwriters/notesnook/blob/master/CONTRIBUTING.md#commit-guidelines
|
||||
// for full list of scopes + details see: https://github.com/streetwriters/notesnook-private/blob/master/CONTRIBUTING.md#commit-guidelines
|
||||
|
||||
"mobile",
|
||||
"web",
|
||||
@@ -36,8 +36,7 @@ const SCOPES = [
|
||||
"global",
|
||||
"docs",
|
||||
"themebuilder",
|
||||
"intl",
|
||||
"webclipper"
|
||||
"intl"
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
|
||||
38
.github/workflows/android.e2e.yml
vendored
38
.github/workflows/android.e2e.yml
vendored
@@ -1,3 +1,4 @@
|
||||
|
||||
name: e2e-android
|
||||
on: workflow_dispatch
|
||||
|
||||
@@ -18,7 +19,7 @@ jobs:
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
|
||||
- name: Free Disk Space
|
||||
- name: Free Disk Space
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
tool-cache: false
|
||||
@@ -26,7 +27,7 @@ jobs:
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
|
||||
- name: Install node modules
|
||||
run: |
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
@@ -45,20 +46,38 @@ jobs:
|
||||
architecture: "x64"
|
||||
|
||||
- name: Install Detox CLI
|
||||
run: npm install detox-cli --global
|
||||
run: npm install detox-cli --global
|
||||
|
||||
- name: Generate debug.keystore
|
||||
working-directory: apps/mobile/native/android/app
|
||||
run: |
|
||||
keytool -genkey -v -keystore debug.keystore -storepass android -alias androiddebugkey -keypass android -keyalg RSA -keysize 2048 -validity 10000 -dname "C=US, O=Android, CN=Android Debug"
|
||||
|
||||
- name: Detox build
|
||||
run: |
|
||||
yarn build:android
|
||||
ls apps/mobile/native/android/app/build/outputs/apk
|
||||
ls apps/mobile/native/android/app/build/outputs/apk/release
|
||||
|
||||
- name: Free Disk Space
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
tool-cache: false
|
||||
android: false
|
||||
|
||||
- name: Free Disk Space After Build
|
||||
run: |
|
||||
echo "Disk space before cleanup:"
|
||||
df -h
|
||||
sudo rm -rf /usr/local/lib/android/sdk/ndk
|
||||
sudo apt-get clean
|
||||
echo "Disk space after cleanup:"
|
||||
df -h
|
||||
|
||||
- name: Get device name
|
||||
id: device
|
||||
run: |
|
||||
AVD_NAME=$(node -p "require('./apps/mobile/native/.detoxrc.js').devices.emulator.device.avdName")
|
||||
echo "AVD_NAME=$AVD_NAME" >> $GITHUB_OUTPUT
|
||||
|
||||
|
||||
- name: AVD cache
|
||||
uses: actions/cache@v4
|
||||
id: avd-cache
|
||||
@@ -89,12 +108,13 @@ jobs:
|
||||
disable-animations: true
|
||||
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
|
||||
force-avd-creation: false
|
||||
script: yarn test:android --headless --record-logs failing --record-videos failing --take-screenshots failing
|
||||
|
||||
script: |
|
||||
yarn test:android --headless --record-logs failing --record-videos failing --take-screenshots failing --artifacts-location apps/mobile/native/artifacts
|
||||
|
||||
- name: Upload artifacts
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: detox-artifacts
|
||||
path: artifacts
|
||||
path: apps/mobile/native/artifacts
|
||||
retention-days: 14
|
||||
|
||||
2
.github/workflows/core.tests.yml
vendored
2
.github/workflows/core.tests.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
46
.github/workflows/desktop.publish.yml
vendored
46
.github/workflows/desktop.publish.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
@@ -84,26 +84,6 @@ jobs:
|
||||
files: ./notesnook_build_v${{ steps.app_metadata.outputs.app_version }}.zip
|
||||
prerelease: ${{ endsWith(steps.app_metadata.outputs.app_version, '-beta') }}
|
||||
|
||||
- name: Generate flatpak sources
|
||||
if: inputs.publish-github && inputs.build-linux
|
||||
run: |
|
||||
sudo apt-get install -y flatpak flatpak-builder pipx
|
||||
git clone https://github.com/flatpak/flatpak-builder-tools.git /tmp/flatpak-builder-tools
|
||||
cd /tmp/flatpak-builder-tools/node
|
||||
pipx install .
|
||||
cd -
|
||||
node scripts/generate-sources.mjs
|
||||
|
||||
- name: Upload flatpak sources
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: inputs.publish-github && inputs.build-linux
|
||||
with:
|
||||
draft: true
|
||||
name: Notesnook Desktop v${{ steps.app_metadata.outputs.app_version }}
|
||||
tag_name: v${{ steps.app_metadata.outputs.app_version }}
|
||||
files: ./generated-sources.json
|
||||
prerelease: ${{ endsWith(steps.app_metadata.outputs.app_version, '-beta') }}
|
||||
|
||||
build-macos:
|
||||
name: Build for macOS
|
||||
needs: build
|
||||
@@ -112,7 +92,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
@@ -156,12 +136,6 @@ jobs:
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
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: Install provisioning profile
|
||||
run: echo "${{ secrets.MAC_PROVISIONING_PROFILE }}" | base64 --decode > embedded.provisionprofile
|
||||
working-directory: ./apps/desktop
|
||||
@@ -214,7 +188,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
@@ -238,12 +212,6 @@ jobs:
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
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: Build Electron wrapper
|
||||
run: npx nx run release --project @notesnook/desktop
|
||||
working-directory: ./apps/desktop
|
||||
@@ -279,7 +247,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup .NET Core SDK
|
||||
uses: actions/setup-dotnet@v2
|
||||
@@ -304,12 +272,6 @@ jobs:
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
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: Build
|
||||
run: npx nx run release --project @notesnook/desktop
|
||||
|
||||
|
||||
233
.github/workflows/desktop.tests.yml
vendored
233
.github/workflows/desktop.tests.yml
vendored
@@ -1,233 +0,0 @@
|
||||
name: Test @notesnook/desktop
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
paths:
|
||||
- "app/desktop/**"
|
||||
# re-run workflow if workflow file changes
|
||||
- ".github/workflows/desktop.tests.yml"
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=web
|
||||
|
||||
- name: Setup environment
|
||||
run: |
|
||||
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Generate desktop build
|
||||
run: npx nx build:desktop @notesnook/web
|
||||
|
||||
- name: Archive build artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build
|
||||
path: apps/web/build/**/*
|
||||
|
||||
test-macos-x64:
|
||||
name: Test macOS x64
|
||||
needs: build
|
||||
runs-on: macos-13
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
- name: Download build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build
|
||||
path: ./apps/web/build
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=desktop
|
||||
|
||||
- name: Build Electron wrapper
|
||||
run: npx nx run release --project @notesnook/desktop
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Build app
|
||||
run: |
|
||||
yarn electron-builder --mac --dir --x64
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Run tests x64
|
||||
run: |
|
||||
EXECUTABLE_PATH=output/mac/Notesnook.app/Contents/MacOS/Notesnook npm run test
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-macos-x64
|
||||
path: apps/desktop/test-results
|
||||
retention-days: 5
|
||||
|
||||
test-macos:
|
||||
name: Test macOS
|
||||
needs: build
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
- name: Download build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build
|
||||
path: ./apps/web/build
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=desktop
|
||||
|
||||
- name: Build Electron wrapper
|
||||
run: npx nx run release --project @notesnook/desktop
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Build app
|
||||
run: |
|
||||
yarn electron-builder --mac --dir --arm64
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Run tests arm64
|
||||
run: |
|
||||
EXECUTABLE_PATH=output/mac-arm64/Notesnook.app/Contents/MacOS/Notesnook npm run test
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-macos
|
||||
path: apps/desktop/test-results
|
||||
retention-days: 5
|
||||
|
||||
test-linux:
|
||||
name: Test for Linux
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
- name: Download build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build
|
||||
path: ./apps/web/build
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
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: Build Electron wrapper
|
||||
run: npx nx run release --project @notesnook/desktop
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Build app
|
||||
run: |
|
||||
yarn electron-builder --linux --dir --arm64 --x64
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
EXECUTABLE_PATH=output/linux-unpacked/notesnook xvfb-run --auto-servernum --server-args="-screen 0 1920x1080x24" -- npm run test
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-linux
|
||||
path: apps/desktop/test-results
|
||||
retention-days: 5
|
||||
|
||||
test-windows:
|
||||
name: Test for Windows
|
||||
needs: build
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
- name: Download build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build
|
||||
path: ./apps/web/build
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
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: Build Electron wrapper
|
||||
run: npx nx run release --project @notesnook/desktop
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Build app
|
||||
run: |
|
||||
npx cross-env NOTESNOOK_STAGING=true yarn electron-builder --win --dir --arm64 --x64
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
npx cross-env EXECUTABLE_PATH=output/win-unpacked/Notesnook.exe npm run test
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-win
|
||||
path: apps/desktop/test-results
|
||||
retention-days: 5
|
||||
2
.github/workflows/editor.tests.yml
vendored
2
.github/workflows/editor.tests.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
4
.github/workflows/help.publish.yml
vendored
4
.github/workflows/help.publish.yml
vendored
@@ -1,6 +1,6 @@
|
||||
name: Publish Notesnook Help
|
||||
|
||||
on:
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
2
.github/workflows/ios.publish.yml
vendored
2
.github/workflows/ios.publish.yml
vendored
@@ -4,7 +4,7 @@ on: workflow_dispatch
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-13
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
|
||||
2
.github/workflows/theme-builder.publish.yml
vendored
2
.github/workflows/theme-builder.publish.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
2
.github/workflows/vericrypt.publish.yml
vendored
2
.github/workflows/vericrypt.publish.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
44
.github/workflows/web.benchmarks.yml
vendored
44
.github/workflows/web.benchmarks.yml
vendored
@@ -1,44 +0,0 @@
|
||||
name: Benchmark @notesnook/web
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
paths:
|
||||
- "apps/web/**"
|
||||
# re-run workflow if workflow file changes
|
||||
- ".github/workflows/web.benchmarks.yml"
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
bench:
|
||||
name: Bench
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=web
|
||||
|
||||
- name: Setup environment
|
||||
run: |
|
||||
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build
|
||||
run: npm run build:web
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install chromium --with-deps
|
||||
working-directory: apps/web
|
||||
|
||||
- name: Run benchmarks
|
||||
id: benchmark
|
||||
run: node __bench__/app.bench.mjs
|
||||
working-directory: apps/web
|
||||
2
.github/workflows/web.publish.yml
vendored
2
.github/workflows/web.publish.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
4
.github/workflows/web.tests.yml
vendored
4
.github/workflows/web.tests.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Download build
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import test from "node:test";
|
||||
import { launchApp } from "./utils.mjs";
|
||||
import assert from "assert";
|
||||
import slugify from "slugify";
|
||||
import path from "path";
|
||||
import { mkdir } from "fs/promises";
|
||||
|
||||
test("make sure app loads", async (t) => {
|
||||
const { app, page } = await launchApp();
|
||||
try {
|
||||
await page.waitForSelector("#authForm");
|
||||
|
||||
assert.ok(
|
||||
await page.getByRole("button", { name: "Create account" }).isVisible()
|
||||
);
|
||||
|
||||
await page
|
||||
.getByRole("button", { name: "Skip & go directly to the app" })
|
||||
.click();
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { _electron as electron } from "playwright";
|
||||
|
||||
const executablePath = process.env.EXECUTABLE_PATH;
|
||||
|
||||
// process.platform === "linux"
|
||||
// ? "output/linux-unpacked/Notesnook"
|
||||
// : process.platform === "darwin"
|
||||
// ? "output/mac/Notesnook.app/Contents/MacOS/Notesnook"
|
||||
// : "output/win-unpacked/Notesnook.exe";
|
||||
|
||||
export async function launchApp() {
|
||||
const app = await electron.launch({
|
||||
executablePath
|
||||
});
|
||||
|
||||
const page = await app.firstWindow();
|
||||
|
||||
return { app, page };
|
||||
}
|
||||
2187
apps/desktop/package-lock.json
generated
2187
apps/desktop/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -2,22 +2,21 @@
|
||||
"name": "@notesnook/desktop",
|
||||
"productName": "Notesnook",
|
||||
"description": "Your private note taking space",
|
||||
"version": "3.0.23",
|
||||
"version": "3.0.18",
|
||||
"appAppleId": "1544027013",
|
||||
"private": true,
|
||||
"main": "./dist/cjs/index.js",
|
||||
"module": "./dist/esm/index.js",
|
||||
"types": "./dist/types/index.d.ts",
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"require": {
|
||||
"types": "./dist/types/index.d.ts",
|
||||
"default": "./dist/cjs/index.js"
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.js"
|
||||
},
|
||||
"import": {
|
||||
"types": "./dist/types/index.d.ts",
|
||||
"default": "./dist/esm/index.js"
|
||||
"types": "./dist/index.d.mts",
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -29,11 +28,10 @@
|
||||
"@notesnook/intl": "file:../../packages/intl",
|
||||
"@trpc/client": "10.45.2",
|
||||
"@trpc/server": "10.45.2",
|
||||
"better-sqlite3-multiple-ciphers": "11.5.0",
|
||||
"better-sqlite3-multiple-ciphers": "11.2.1",
|
||||
"electron-trpc": "0.6.1",
|
||||
"electron-updater": "^6.3.4",
|
||||
"icojs": "^0.19.4",
|
||||
"sqlite-better-trigram": "^0.0.2",
|
||||
"typed-emitter": "^2.1.0",
|
||||
"yargs": "^17.7.2",
|
||||
"zod": "^3.23.8"
|
||||
@@ -43,17 +41,14 @@
|
||||
"@types/node": "22.7.0",
|
||||
"@types/yargs": "^17.0.33",
|
||||
"chokidar": "^4.0.1",
|
||||
"electron": "^31.7.4",
|
||||
"electron-builder": "^25.1.8",
|
||||
"electron": "^30.5.1",
|
||||
"electron-builder": "^25.1.7",
|
||||
"esbuild": "^0.24.0",
|
||||
"node-abi": "^3.68.0",
|
||||
"node-gyp-build": "^4.8.2",
|
||||
"playwright": "^1.48.2",
|
||||
"prebuildify": "^6.0.1",
|
||||
"slugify": "^1.6.6",
|
||||
"tree-kill": "^1.2.2",
|
||||
"undici": "^6.19.8",
|
||||
"vitest": "^2.1.5"
|
||||
"undici": "^6.19.8"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"dmg-license": "^1.0.11"
|
||||
@@ -62,11 +57,10 @@
|
||||
"start": "node scripts/dev.mjs",
|
||||
"staging": "node scripts/build.mjs --run",
|
||||
"release": "node scripts/build.mjs",
|
||||
"build": "node ../../scripts/build.mjs",
|
||||
"build": "tsup-node src/index.ts",
|
||||
"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: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",
|
||||
"test": "node --test --test-timeout 30000 --test-force-exit"
|
||||
"postinstall": "patch-package"
|
||||
},
|
||||
"author": {
|
||||
"name": "Streetwriters (Private) Limited",
|
||||
@@ -95,20 +89,7 @@
|
||||
"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"
|
||||
],
|
||||
@@ -181,12 +162,10 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"signtoolOptions": {
|
||||
"signingHashAlgorithms": [
|
||||
"sha256"
|
||||
],
|
||||
"sign": "./scripts/sign.js"
|
||||
},
|
||||
"signingHashAlgorithms": [
|
||||
"sha256"
|
||||
],
|
||||
"sign": "./scripts/sign.js",
|
||||
"icon": "assets/icons/app.ico"
|
||||
},
|
||||
"portable": {
|
||||
|
||||
@@ -18,17 +18,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import path from "path";
|
||||
import fs from "fs/promises";
|
||||
import { existsSync } from "fs";
|
||||
import fs, { readFile, writeFile } from "fs/promises";
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import yargs from "yargs-parser";
|
||||
import os from "os";
|
||||
import * as childProcess from "child_process";
|
||||
import { fileURLToPath } from "url";
|
||||
import { patchBetterSQLite3 } from "./patch-better-sqlite3.mjs";
|
||||
|
||||
const args = yargs(process.argv);
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const packageJson = JSON.parse(
|
||||
readFileSync(path.join(__dirname, "..", "package.json"), "utf-8")
|
||||
);
|
||||
|
||||
const webAppPath = path.resolve(path.join(__dirname, "..", "..", "web"));
|
||||
|
||||
@@ -44,6 +46,12 @@ if (args.rebuild || !existsSync(path.join(webAppPath, "build"))) {
|
||||
// temporary until there's support for prebuilt binaries for linux ARM
|
||||
if (os.platform() === "linux") await patchBetterSQLite3();
|
||||
|
||||
// if (os.platform() === "win32")
|
||||
// await exec(
|
||||
// `npx prebuildify --arch=arm64 --strip -t electron@${packageJson.devDependencies.electron}`,
|
||||
// path.join(__dirname, "..", "node_modules", "sodium-native")
|
||||
// );
|
||||
|
||||
await fs.cp(path.join(webAppPath, "build"), "build", {
|
||||
recursive: true,
|
||||
force: true
|
||||
@@ -75,3 +83,21 @@ async function exec(cmd, cwd) {
|
||||
cwd: cwd || process.cwd()
|
||||
});
|
||||
}
|
||||
|
||||
async function patchBetterSQLite3() {
|
||||
const jsonPath = path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"node_modules",
|
||||
"better-sqlite3-multiple-ciphers",
|
||||
"package.json"
|
||||
);
|
||||
const json = JSON.parse(await readFile(jsonPath, "utf-8"));
|
||||
|
||||
json.version = "11.2.2";
|
||||
json.homepage = "https://github.com/thecodrr/better-sqlite3-multiple-ciphers";
|
||||
json.repository.url =
|
||||
"git://github.com/thecodrr/better-sqlite3-multiple-ciphers.git";
|
||||
|
||||
await writeFile(jsonPath, JSON.stringify(json));
|
||||
}
|
||||
|
||||
@@ -36,8 +36,6 @@ const ENV = {
|
||||
FORCE_COLOR: "false",
|
||||
COLOR: "0"
|
||||
};
|
||||
process.chdir(path.join(__dirname, ".."));
|
||||
|
||||
await onChange(true);
|
||||
|
||||
console.log("Watching...");
|
||||
@@ -55,8 +53,6 @@ async function onChange(first) {
|
||||
if (first) {
|
||||
await fs.rm("./build/", { force: true, recursive: true });
|
||||
|
||||
await exec("npm rebuild electron --verbose --foreground-scripts");
|
||||
|
||||
await exec("yarn electron-builder install-app-deps");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { readFile, writeFile } from "fs/promises";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
export async function patchBetterSQLite3() {
|
||||
const jsonPath = path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"node_modules",
|
||||
"better-sqlite3-multiple-ciphers",
|
||||
"package.json"
|
||||
);
|
||||
const json = JSON.parse(await readFile(jsonPath, "utf-8"));
|
||||
|
||||
json.version = "11.5.1";
|
||||
json.homepage = "https://github.com/thecodrr/better-sqlite3-multiple-ciphers";
|
||||
json.repository.url =
|
||||
"git://github.com/thecodrr/better-sqlite3-multiple-ciphers.git";
|
||||
|
||||
await writeFile(jsonPath, JSON.stringify(json));
|
||||
}
|
||||
|
||||
if (process.argv[1] === __filename) {
|
||||
console.log("Patching better-sqlite3");
|
||||
patchBetterSQLite3();
|
||||
}
|
||||
@@ -46,8 +46,6 @@ export class SQLite {
|
||||
this.sqlite = require("better-sqlite3-multiple-ciphers")(
|
||||
filePath
|
||||
).unsafeMode(true);
|
||||
const betterTrigram = require("sqlite-better-trigram");
|
||||
betterTrigram.load(this.sqlite);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,6 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { NativeImage, nativeImage } from "electron";
|
||||
import path from "path";
|
||||
import { isDevelopment } from "./index";
|
||||
// @ts-expect-error stupid icojs
|
||||
import { ParsedImage, parseICO } from "icojs";
|
||||
import { getSystemTheme } from "./theme";
|
||||
import { readFile } from "fs/promises";
|
||||
|
||||
@@ -57,8 +57,7 @@ function getSystemTheme() {
|
||||
nativeTheme.themeSource = oldThemeSource;
|
||||
|
||||
setTimeout(
|
||||
() =>
|
||||
listeners.forEach((a) => nativeTheme.addListener("updated", () => a())),
|
||||
() => listeners.forEach((a) => nativeTheme.addListener("updated", a)),
|
||||
1000
|
||||
);
|
||||
return currentTheme;
|
||||
@@ -73,8 +72,7 @@ function changeTheme(theme: Theme) {
|
||||
nativeTheme.themeSource = theme;
|
||||
|
||||
setTimeout(
|
||||
() =>
|
||||
listeners.forEach((a) => nativeTheme.addListener("updated", () => a())),
|
||||
() => listeners.forEach((a) => nativeTheme.addListener("updated", a)),
|
||||
1000
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist/",
|
||||
"lib": ["ESNext"],
|
||||
"moduleResolution": "Bundler"
|
||||
"moduleResolution": "nodenext",
|
||||
"module": "nodenext"
|
||||
},
|
||||
"include": ["src", "global.d.ts"]
|
||||
}
|
||||
|
||||
@@ -17,16 +17,15 @@ 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 Sodium, { Cipher, Password } from "@ammarahmed/react-native-sodium";
|
||||
import { SerializedKey } from "@notesnook/crypto";
|
||||
import Sodium from "@ammarahmed/react-native-sodium";
|
||||
import { Platform } from "react-native";
|
||||
import "react-native-get-random-values";
|
||||
import * as Keychain from "react-native-keychain";
|
||||
import { MMKVLoader, ProcessingModes } from "react-native-mmkv-storage";
|
||||
import { generateSecureRandom } from "react-native-securerandom";
|
||||
import { DatabaseLogger } from ".";
|
||||
import { ToastManager } from "../../services/event-manager";
|
||||
import { MMKV } from "./mmkv";
|
||||
import { ToastManager } from "../../services/event-manager";
|
||||
|
||||
// Database key cipher is persisted across different user sessions hence it has
|
||||
// it's independent storage which we will never clear. This is only used when application has
|
||||
@@ -63,7 +62,6 @@ const KEYSTORE_CONFIG = Platform.select({
|
||||
|
||||
function generatePassword() {
|
||||
const length = 80;
|
||||
//@ts-ignore
|
||||
const crypto = window.crypto || window.msCrypto;
|
||||
if (typeof crypto === "undefined") {
|
||||
throw new Error(
|
||||
@@ -80,27 +78,27 @@ function generatePassword() {
|
||||
return secret;
|
||||
}
|
||||
|
||||
export async function encryptDatabaseKeyWithPassword(appLockPassword: string) {
|
||||
const key = (await getDatabaseKey()) as string;
|
||||
export async function encryptDatabaseKeyWithPassword(appLockPassword) {
|
||||
const key = getDatabaseKey();
|
||||
const appLockCredentials = await Sodium.deriveKey(
|
||||
appLockPassword,
|
||||
NOTESNOOK_APPLOCK_KEY_SALT
|
||||
);
|
||||
const databaseKeyCipher = (await encrypt(appLockCredentials, key)) as Cipher;
|
||||
const databaseKeyCipher = await encrypt(appLockCredentials, key);
|
||||
MMKV.setMap(DB_KEY_CIPHER, databaseKeyCipher);
|
||||
// We reset the database key from keychain once app lock password is set.
|
||||
await Keychain.resetInternetCredentials(KEYCHAIN_SERVER_DBKEY);
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function restoreDatabaseKeyToKeyChain(appLockPassword: string) {
|
||||
const databaseKeyCipher: Cipher = CipherStorage.getMap(DB_KEY_CIPHER);
|
||||
const databaseKey = (await decrypt(
|
||||
export async function restoreDatabaseKeyToKeyChain(appLockPassword) {
|
||||
const databaseKeyCipher = CipherStorage.getMap(DB_KEY_CIPHER);
|
||||
const databaseKey = await decrypt(
|
||||
{
|
||||
password: appLockPassword
|
||||
},
|
||||
databaseKeyCipher
|
||||
)) as string;
|
||||
);
|
||||
|
||||
await Keychain.setInternetCredentials(
|
||||
KEYCHAIN_SERVER_DBKEY,
|
||||
@@ -112,16 +110,13 @@ export async function restoreDatabaseKeyToKeyChain(appLockPassword: string) {
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function setAppLockVerificationCipher(appLockPassword: string) {
|
||||
export async function setAppLockVerificationCipher(appLockPassword) {
|
||||
try {
|
||||
const appLockCredentials = await Sodium.deriveKey(
|
||||
appLockPassword,
|
||||
NOTESNOOK_APPLOCK_KEY_SALT
|
||||
);
|
||||
const encrypted = (await encrypt(
|
||||
appLockCredentials,
|
||||
generatePassword()
|
||||
)) as Cipher;
|
||||
const encrypted = await encrypt(appLockCredentials, generatePassword());
|
||||
CipherStorage.setMap(APPLOCK_CIPHER, encrypted);
|
||||
DatabaseLogger.info("setAppLockVerificationCipher");
|
||||
} catch (e) {
|
||||
@@ -134,9 +129,9 @@ export async function clearAppLockVerificationCipher() {
|
||||
CipherStorage.removeItem(APPLOCK_CIPHER);
|
||||
}
|
||||
|
||||
export async function validateAppLockPassword(appLockPassword: string) {
|
||||
export async function validateAppLockPassword(appLockPassword) {
|
||||
try {
|
||||
const appLockCipher: Cipher = CipherStorage.getMap(APPLOCK_CIPHER);
|
||||
const appLockCipher = CipherStorage.getMap(APPLOCK_CIPHER);
|
||||
if (!appLockCipher) return true;
|
||||
const key = await Sodium.deriveKey(appLockPassword, appLockCipher.salt);
|
||||
const decrypted = await decrypt(key, appLockCipher);
|
||||
@@ -151,18 +146,17 @@ export async function validateAppLockPassword(appLockPassword: string) {
|
||||
}
|
||||
}
|
||||
|
||||
let DB_KEY: string | undefined;
|
||||
let DB_KEY;
|
||||
export function clearDatabaseKey() {
|
||||
DB_KEY = undefined;
|
||||
DatabaseLogger.info("Cleared database key");
|
||||
}
|
||||
|
||||
export async function getDatabaseKey(appLockPassword?: string) {
|
||||
export async function getDatabaseKey(appLockPassword) {
|
||||
if (DB_KEY) return DB_KEY;
|
||||
try {
|
||||
if (appLockPassword) {
|
||||
const databaseKeyCipher: Cipher =
|
||||
CipherStorage.getMap("databaseKeyCipher");
|
||||
const databaseKeyCipher = CipherStorage.getMap("databaseKeyCipher");
|
||||
const databaseKey = await decrypt(
|
||||
{
|
||||
password: appLockPassword
|
||||
@@ -178,12 +172,13 @@ export async function getDatabaseKey(appLockPassword?: string) {
|
||||
KEYCHAIN_SERVER_DBKEY
|
||||
);
|
||||
if (hasKey) {
|
||||
const credentials = await Keychain.getInternetCredentials(
|
||||
KEYCHAIN_SERVER_DBKEY
|
||||
let credentials = await Keychain.getInternetCredentials(
|
||||
KEYCHAIN_SERVER_DBKEY,
|
||||
KEYSTORE_CONFIG
|
||||
);
|
||||
|
||||
DatabaseLogger.info("Getting database key from Keychain");
|
||||
DB_KEY = (credentials as Keychain.UserCredentials).password;
|
||||
DB_KEY = credentials.password;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +190,7 @@ export async function getDatabaseKey(appLockPassword?: string) {
|
||||
NOTESNOOK_DB_KEY_SALT
|
||||
);
|
||||
|
||||
DB_KEY = derivedDatabaseKey.key as string;
|
||||
DB_KEY = derivedDatabaseKey.key;
|
||||
|
||||
await Keychain.setInternetCredentials(
|
||||
KEYCHAIN_SERVER_DBKEY,
|
||||
@@ -207,17 +202,18 @@ export async function getDatabaseKey(appLockPassword?: string) {
|
||||
|
||||
if (await Keychain.hasInternetCredentials("notesnook")) {
|
||||
const userKeyCredentials = await Keychain.getInternetCredentials(
|
||||
"notesnook"
|
||||
"notesnook",
|
||||
KEYSTORE_CONFIG
|
||||
);
|
||||
|
||||
if (userKeyCredentials) {
|
||||
const userKeyCipher: Cipher = (await encrypt(
|
||||
const userKeyCipher = await encrypt(
|
||||
{
|
||||
key: DB_KEY,
|
||||
salt: NOTESNOOK_DB_KEY_SALT
|
||||
},
|
||||
userKeyCredentials.password
|
||||
)) as Cipher;
|
||||
);
|
||||
// Store encrypted user key in MMKV
|
||||
MMKV.setMap(USER_KEY_CIPHER, userKeyCipher);
|
||||
await Keychain.resetInternetCredentials("notesnook");
|
||||
@@ -227,84 +223,45 @@ export async function getDatabaseKey(appLockPassword?: string) {
|
||||
|
||||
return DB_KEY;
|
||||
} catch (e) {
|
||||
ToastManager.error(e as Error, "Error getting database key");
|
||||
ToastManager.error(e, "Error getting database key");
|
||||
console.log(e, "error");
|
||||
DatabaseLogger.error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function deriveCryptoKeyFallback(data: SerializedKey) {
|
||||
if (Platform.OS !== "ios") return;
|
||||
export async function deriveCryptoKey(data) {
|
||||
try {
|
||||
if (!data.password || !data.salt)
|
||||
throw new Error(
|
||||
"Invalid password and salt provided to deriveCryptoKeyFallback"
|
||||
);
|
||||
|
||||
const credentials = await Sodium.deriveKeyFallback?.(
|
||||
data.password,
|
||||
data.salt
|
||||
let credentials = await Sodium.deriveKey(data.password, data.salt);
|
||||
const userKeyCipher = await encrypt(
|
||||
{
|
||||
key: await getDatabaseKey(),
|
||||
salt: NOTESNOOK_DB_KEY_SALT
|
||||
},
|
||||
credentials.key
|
||||
);
|
||||
|
||||
if (!credentials) return;
|
||||
|
||||
const userKeyCipher = (await encrypt(
|
||||
{
|
||||
key: (await getDatabaseKey()) as string,
|
||||
salt: NOTESNOOK_DB_KEY_SALT
|
||||
},
|
||||
credentials.key as string
|
||||
)) as Cipher<"base64">;
|
||||
DatabaseLogger.info("User key fallback stored: ", {
|
||||
userKeyCipher: !!userKeyCipher
|
||||
});
|
||||
DatabaseLogger.info("User key stored: ", !!userKeyCipher);
|
||||
|
||||
// Store encrypted user key in MMKV
|
||||
MMKV.setMap(USER_KEY_CIPHER, userKeyCipher);
|
||||
return credentials.key;
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function deriveCryptoKey(data: SerializedKey) {
|
||||
export async function getCryptoKey(_name) {
|
||||
try {
|
||||
if (!data.password || !data.salt)
|
||||
throw new Error("Invalid password and salt provided to deriveCryptoKey");
|
||||
const keyCipher = MMKV.getMap(USER_KEY_CIPHER);
|
||||
|
||||
const credentials = (await Sodium.deriveKey(
|
||||
data.password,
|
||||
data.salt
|
||||
)) as Password;
|
||||
const userKeyCipher = (await encrypt(
|
||||
{
|
||||
key: (await getDatabaseKey()) as string,
|
||||
salt: NOTESNOOK_DB_KEY_SALT
|
||||
},
|
||||
credentials.key as string
|
||||
)) as Cipher<"base64">;
|
||||
DatabaseLogger.info("User key stored: ", {
|
||||
userKeyCipher: !!userKeyCipher
|
||||
});
|
||||
|
||||
// Store encrypted user key in MMKV
|
||||
MMKV.setMap(USER_KEY_CIPHER, userKeyCipher);
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCryptoKey() {
|
||||
try {
|
||||
const keyCipher: Cipher = MMKV.getMap(USER_KEY_CIPHER);
|
||||
if (!keyCipher) {
|
||||
DatabaseLogger.info("User key cipher is null");
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
|
||||
const key = await decrypt(
|
||||
{
|
||||
key: (await getDatabaseKey()) as string,
|
||||
key: await getDatabaseKey(),
|
||||
salt: keyCipher.salt
|
||||
},
|
||||
keyCipher
|
||||
@@ -317,7 +274,7 @@ export async function getCryptoKey() {
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeCryptoKey() {
|
||||
export async function removeCryptoKey(_name) {
|
||||
try {
|
||||
MMKV.removeItem(USER_KEY_CIPHER);
|
||||
await Keychain.resetInternetCredentials("notesnook");
|
||||
@@ -327,79 +284,44 @@ export async function removeCryptoKey() {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getRandomBytes(length: number) {
|
||||
export async function getRandomBytes(length) {
|
||||
return await generateSecureRandom(length);
|
||||
}
|
||||
|
||||
export async function hash(
|
||||
password: string,
|
||||
email: string,
|
||||
options?: { usesFallback?: boolean }
|
||||
) {
|
||||
DatabaseLogger.log(`Hashing password: fallback: ${options?.usesFallback}`);
|
||||
export async function hash(password, email) {
|
||||
let result = await Sodium.hashPassword(password, email);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (options?.usesFallback && Platform.OS !== "ios") {
|
||||
return "";
|
||||
export async function generateCryptoKey(password, salt) {
|
||||
try {
|
||||
let credentials = await Sodium.deriveKey(password, salt || null);
|
||||
return credentials;
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e);
|
||||
}
|
||||
|
||||
return (
|
||||
options?.usesFallback
|
||||
? await Sodium.hashPasswordFallback?.(password, email)
|
||||
: await Sodium.hashPassword(password, email)
|
||||
) as string;
|
||||
}
|
||||
|
||||
export async function generateCryptoKey(password: string, salt?: string) {
|
||||
return Sodium.deriveKey(password, salt) as Promise<SerializedKey>;
|
||||
}
|
||||
|
||||
export async function generateCryptoKeyFallback(
|
||||
password: string,
|
||||
salt?: string
|
||||
): Promise<SerializedKey> {
|
||||
return Sodium.deriveKeyFallback?.(
|
||||
password,
|
||||
salt as string
|
||||
) as Promise<SerializedKey>;
|
||||
}
|
||||
|
||||
export function getAlgorithm(base64Variant: number) {
|
||||
export function getAlgorithm(base64Variant) {
|
||||
return `xcha-argon2i13-${base64Variant}`;
|
||||
}
|
||||
|
||||
export async function decrypt(password: SerializedKey, data: Cipher<"base64">) {
|
||||
const _data = { ...data };
|
||||
export async function decrypt(password, data) {
|
||||
if (!password.password && !password.key) return undefined;
|
||||
if (password.password && password.password === "" && !password.key)
|
||||
return undefined;
|
||||
let _data = { ...data };
|
||||
_data.output = "plain";
|
||||
|
||||
if (!password.salt) password.salt = data.salt;
|
||||
|
||||
if (Platform.OS === "ios" && !password.key && password.password) {
|
||||
const key = await Sodium.deriveKey(password.password, password.salt);
|
||||
try {
|
||||
return await Sodium.decrypt(key, _data);
|
||||
} catch (e) {
|
||||
const fallbackKey = await Sodium.deriveKeyFallback?.(
|
||||
password.password,
|
||||
password.salt
|
||||
);
|
||||
if (Platform.OS === "ios" && fallbackKey) {
|
||||
DatabaseLogger.info("Using fallback key for decryption");
|
||||
}
|
||||
if (fallbackKey) {
|
||||
return await Sodium.decrypt(fallbackKey, _data);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return await Sodium.decrypt(password, _data);
|
||||
}
|
||||
|
||||
export async function decryptMulti(
|
||||
password: Password,
|
||||
data: Cipher<"base64">[]
|
||||
) {
|
||||
export async function decryptMulti(password, data) {
|
||||
if (!password.password && !password.key) return undefined;
|
||||
if (password.password && password.password === "" && !password.key)
|
||||
return undefined;
|
||||
|
||||
data = data.map((d) => {
|
||||
d.output = "plain";
|
||||
return d;
|
||||
@@ -409,30 +331,10 @@ export async function decryptMulti(
|
||||
password.salt = data[0].salt;
|
||||
}
|
||||
|
||||
if (Platform.OS === "ios" && !password.key && password.password) {
|
||||
const key = await Sodium.deriveKey(password.password, password.salt);
|
||||
try {
|
||||
return await Sodium.decryptMulti(key, data);
|
||||
} catch (e) {
|
||||
const fallbackKey = await Sodium.deriveKeyFallback?.(
|
||||
password.password,
|
||||
password.salt as string
|
||||
);
|
||||
if (Platform.OS === "ios" && fallbackKey) {
|
||||
DatabaseLogger.info("Using fallback key for decryption");
|
||||
}
|
||||
if (fallbackKey) {
|
||||
return await Sodium.decryptMulti(fallbackKey, data);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return await Sodium.decryptMulti(password, data);
|
||||
}
|
||||
|
||||
export function parseAlgorithm(alg: string) {
|
||||
export function parseAlgorithm(alg) {
|
||||
if (!alg) return {};
|
||||
const [enc, kdf, compressed, compressionAlg, base64variant] = alg.split("-");
|
||||
return {
|
||||
@@ -444,11 +346,16 @@ export function parseAlgorithm(alg: string) {
|
||||
};
|
||||
}
|
||||
|
||||
export async function encrypt(password: SerializedKey, plainText: string) {
|
||||
const result = await Sodium.encrypt<"base64">(password, {
|
||||
export async function encrypt(password, data) {
|
||||
if (!password.password && !password.key) return undefined;
|
||||
if (password.password && password.password === "" && !password.key)
|
||||
return undefined;
|
||||
|
||||
let message = {
|
||||
type: "plain",
|
||||
data: plainText
|
||||
});
|
||||
data: data
|
||||
};
|
||||
let result = await Sodium.encrypt(password, message);
|
||||
|
||||
return {
|
||||
...result,
|
||||
@@ -456,13 +363,14 @@ export async function encrypt(password: SerializedKey, plainText: string) {
|
||||
};
|
||||
}
|
||||
|
||||
export async function encryptMulti(
|
||||
password: SerializedKey,
|
||||
plainText: string[]
|
||||
) {
|
||||
const results = await Sodium.encryptMulti<"base64">(
|
||||
export async function encryptMulti(password, data) {
|
||||
if (!password.password && !password.key) return undefined;
|
||||
if (password.password && password.password === "" && !password.key)
|
||||
return undefined;
|
||||
|
||||
let results = await Sodium.encryptMulti(
|
||||
password,
|
||||
plainText.map((item) => ({
|
||||
data.map((item) => ({
|
||||
type: "plain",
|
||||
data: item
|
||||
}))
|
||||
@@ -16,26 +16,26 @@ 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 "./logger";
|
||||
import { database } from "@notesnook/common";
|
||||
import { logger as dbLogger, ICompressor } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { logger as dbLogger } from "@notesnook/core";
|
||||
import { Platform } from "react-native";
|
||||
import * as Gzip from "react-native-gzip";
|
||||
import EventSource from "../../utils/sse/even-source-ios";
|
||||
import AndroidEventSource from "../../utils/sse/event-source";
|
||||
import {
|
||||
SqliteAdapter,
|
||||
SqliteIntrospector,
|
||||
SqliteQueryCompiler
|
||||
} from "@streetwriters/kysely";
|
||||
import { Platform } from "react-native";
|
||||
import * as Gzip from "react-native-gzip";
|
||||
import SettingsService from "../../services/settings";
|
||||
import EventSource from "../../utils/sse/even-source-ios";
|
||||
import AndroidEventSource from "../../utils/sse/event-source";
|
||||
import { FileStorage } from "../filesystem";
|
||||
import { getDatabaseKey } from "./encryption";
|
||||
import "./logger";
|
||||
import filesystem from "../filesystem";
|
||||
import Storage from "./storage";
|
||||
import { RNSqliteDriver } from "./sqlite.kysely";
|
||||
import { Storage } from "./storage";
|
||||
import { getDatabaseKey } from "./encryption";
|
||||
import SettingsService from "../../services/settings";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
export async function setupDatabase(password?: string) {
|
||||
export async function setupDatabase(password) {
|
||||
const key = await getDatabaseKey(password);
|
||||
if (!key) throw new Error(strings.databaseSetupFailed());
|
||||
|
||||
@@ -47,21 +47,17 @@ export async function setupDatabase(password?: string) {
|
||||
SSE_HOST: "https://events.streetwriters.co",
|
||||
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
|
||||
ISSUES_HOST: "https://issues.streetwriters.co",
|
||||
MONOGRAPH_HOST: "https://monogr.ph",
|
||||
...(SettingsService.getProperty("serverUrls") || {})
|
||||
});
|
||||
|
||||
database.setup({
|
||||
storage: Storage,
|
||||
eventsource: (Platform.OS === "ios"
|
||||
? EventSource
|
||||
: AndroidEventSource) as any,
|
||||
fs: FileStorage,
|
||||
compressor: async () =>
|
||||
({
|
||||
compress: Gzip.deflate,
|
||||
decompress: Gzip.inflate
|
||||
} as ICompressor),
|
||||
eventsource: Platform.OS === "ios" ? EventSource : AndroidEventSource,
|
||||
fs: filesystem,
|
||||
compressor: {
|
||||
compress: Gzip.deflate,
|
||||
decompress: Gzip.inflate
|
||||
},
|
||||
batchSize: 100,
|
||||
sqliteOptions: {
|
||||
dialect: (name) => ({
|
||||
@@ -26,7 +26,7 @@ import { CompiledQuery } from "@streetwriters/kysely";
|
||||
import { QuickSQLiteConnection, open } from "react-native-quick-sqlite";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type Config = { dbName: string; async: boolean; location?: string };
|
||||
type Config = { dbName: string; async: boolean; location: string };
|
||||
|
||||
export class RNSqliteDriver implements Driver {
|
||||
private connection?: DatabaseConnection;
|
||||
|
||||
@@ -16,52 +16,58 @@ 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 { IStorage } from "@notesnook/core";
|
||||
import { MMKVInstance } from "react-native-mmkv-storage";
|
||||
|
||||
import { Platform } from "react-native";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import {
|
||||
decrypt,
|
||||
decryptMulti,
|
||||
deriveCryptoKey,
|
||||
deriveCryptoKeyFallback,
|
||||
encrypt,
|
||||
encryptMulti,
|
||||
generateCryptoKey,
|
||||
getCryptoKey,
|
||||
getRandomBytes,
|
||||
hash,
|
||||
generateCryptoKeyFallback
|
||||
removeCryptoKey
|
||||
} from "./encryption";
|
||||
import { MMKV } from "./mmkv";
|
||||
|
||||
export class KV {
|
||||
storage: MMKVInstance;
|
||||
constructor(storage: MMKVInstance) {
|
||||
/**
|
||||
* @type {typeof MMKV}
|
||||
*/
|
||||
storage = null;
|
||||
constructor(storage) {
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
async read<T>(key: string, isArray?: boolean) {
|
||||
if (!key) return undefined;
|
||||
const data = this.storage.getString(key);
|
||||
if (!data) return undefined;
|
||||
async read(key) {
|
||||
if (!key) return null;
|
||||
let data = this.storage.getString(key);
|
||||
if (!data) return null;
|
||||
try {
|
||||
return JSON.parse(data) as T;
|
||||
let parse = JSON.parse(data);
|
||||
return parse;
|
||||
} catch (e) {
|
||||
return data as T;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
async write<T>(key: string, data: T) {
|
||||
async write(key, data) {
|
||||
this.storage.setString(
|
||||
key,
|
||||
typeof data === "string" ? data : JSON.stringify(data)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async readMulti<T>(keys: string[]) {
|
||||
async readMulti(keys) {
|
||||
if (keys.length <= 0) {
|
||||
return [];
|
||||
} else {
|
||||
try {
|
||||
const data = await this.storage.getMultipleItemsAsync<any>(
|
||||
let data = await this.storage.getMultipleItemsAsync(
|
||||
keys.slice(),
|
||||
"string"
|
||||
);
|
||||
@@ -73,24 +79,24 @@ export class KV {
|
||||
obj = value;
|
||||
}
|
||||
return [key, obj];
|
||||
}) as [string, T][];
|
||||
});
|
||||
} catch (e) {
|
||||
return [];
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async remove(key: string) {
|
||||
this.storage.removeItem(key);
|
||||
async remove(key) {
|
||||
return this.storage.removeItem(key);
|
||||
}
|
||||
|
||||
async removeMulti(keys: string[]) {
|
||||
if (!keys) return;
|
||||
this.storage.removeItems(keys);
|
||||
async removeMulti(keys) {
|
||||
if (!keys) return true;
|
||||
return this.storage.removeItems(keys);
|
||||
}
|
||||
|
||||
async clear() {
|
||||
this.storage.clearStore();
|
||||
return this.storage.clearStore();
|
||||
}
|
||||
|
||||
async getAllKeys() {
|
||||
@@ -107,46 +113,54 @@ export class KV {
|
||||
return keys;
|
||||
}
|
||||
|
||||
async writeMulti(items: [string, any][]) {
|
||||
await this.storage.setMultipleItemsAsync(items, "object");
|
||||
async writeMulti(items) {
|
||||
return this.storage.setMultipleItemsAsync(items, "object");
|
||||
}
|
||||
}
|
||||
|
||||
const DefaultStorage = new KV(MMKV);
|
||||
|
||||
export const Storage: IStorage = {
|
||||
write<T>(key: string, data: T): Promise<void> {
|
||||
return DefaultStorage.write(key, data);
|
||||
},
|
||||
writeMulti<T>(entries: [string, T][]): Promise<void> {
|
||||
return DefaultStorage.writeMulti(entries);
|
||||
},
|
||||
readMulti<T>(keys: string[]): Promise<[string, T][]> {
|
||||
return DefaultStorage.readMulti(keys);
|
||||
},
|
||||
read<T>(key: string, isArray?: boolean): Promise<T | undefined> {
|
||||
return DefaultStorage.read(key, isArray);
|
||||
},
|
||||
remove(key: string): Promise<void> {
|
||||
return DefaultStorage.remove(key);
|
||||
},
|
||||
removeMulti(keys: string[]): Promise<void> {
|
||||
return DefaultStorage.removeMulti(keys);
|
||||
},
|
||||
clear(): Promise<void> {
|
||||
return DefaultStorage.clear();
|
||||
},
|
||||
getAllKeys(): Promise<string[]> {
|
||||
return DefaultStorage.getAllKeys();
|
||||
},
|
||||
hash,
|
||||
getCryptoKey,
|
||||
async function requestPermission() {
|
||||
if (Platform.OS === "ios") return true;
|
||||
return true;
|
||||
}
|
||||
async function checkAndCreateDir(path) {
|
||||
let dir =
|
||||
Platform.OS === "ios"
|
||||
? RNFetchBlob.fs.dirs.DocumentDir + path
|
||||
: RNFetchBlob.fs.dirs.SDCardDir + "/Notesnook/" + path;
|
||||
|
||||
try {
|
||||
let exists = await RNFetchBlob.fs.exists(dir);
|
||||
let isDir = await RNFetchBlob.fs.isDir(dir);
|
||||
if (!exists || !isDir) {
|
||||
await RNFetchBlob.fs.mkdir(dir);
|
||||
}
|
||||
} catch (e) {
|
||||
await RNFetchBlob.fs.mkdir(dir);
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
export default {
|
||||
read: (key) => DefaultStorage.read(key),
|
||||
write: (key, value) => DefaultStorage.write(key, value),
|
||||
readMulti: (keys) => DefaultStorage.readMulti(keys),
|
||||
remove: (key) => DefaultStorage.remove(key),
|
||||
clear: () => DefaultStorage.clear(),
|
||||
getAllKeys: () => DefaultStorage.getAllKeys(),
|
||||
writeMulti: (items) => DefaultStorage.writeMulti(items),
|
||||
removeMulti: (keys) => DefaultStorage.removeMulti(keys),
|
||||
encrypt,
|
||||
encryptMulti,
|
||||
decrypt,
|
||||
decryptMulti,
|
||||
getRandomBytes,
|
||||
checkAndCreateDir,
|
||||
requestPermission,
|
||||
deriveCryptoKey,
|
||||
getCryptoKey,
|
||||
removeCryptoKey,
|
||||
hash,
|
||||
generateCryptoKey,
|
||||
generateCryptoKeyFallback,
|
||||
deriveCryptoKeyFallback
|
||||
encryptMulti
|
||||
};
|
||||
@@ -16,6 +16,7 @@ GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Sodium from "@ammarahmed/react-native-sodium";
|
||||
import { getFileNameWithExtension } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
@@ -24,12 +25,12 @@ import { Platform } from "react-native";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import { subscribe, zip } from "react-native-zip-archive";
|
||||
import filesystem from ".";
|
||||
import { ShareComponent } from "../../components/sheets/export-notes/share";
|
||||
import { presentSheet, ToastManager } from "../../services/event-manager";
|
||||
import { ToastManager, presentSheet } from "../../services/event-manager";
|
||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { IOS_APPGROUPID } from "../../utils/constants";
|
||||
import { DatabaseLogger, db } from "../database";
|
||||
import Storage from "../database/storage";
|
||||
import { createCacheDir, exists } from "./io";
|
||||
import { cacheDir, copyFileAsync, releasePermissions } from "./utils";
|
||||
|
||||
@@ -50,23 +51,23 @@ export async function downloadAllAttachments() {
|
||||
/**
|
||||
* Downloads provided attachments to a .zip file
|
||||
* on user's device.
|
||||
* @param {string[]} attachmentIds
|
||||
* @param {string[]} attachments
|
||||
* @param onProgress
|
||||
* @returns
|
||||
*/
|
||||
export async function downloadAttachments(attachmentIds: string[]) {
|
||||
export async function downloadAttachments(attachments) {
|
||||
await createCacheDir();
|
||||
if (!attachmentIds || !attachmentIds.length) return;
|
||||
if (!attachments || !attachments.length) return;
|
||||
const groupId = `download-all-${Date.now()}`;
|
||||
|
||||
let outputFolder;
|
||||
if (Platform.OS === "android") {
|
||||
// Ask the user to select a directory to store the file
|
||||
const file = await ScopedStorage.openDocumentTree(true);
|
||||
let file = await ScopedStorage.openDocumentTree(true);
|
||||
outputFolder = file.uri;
|
||||
if (!outputFolder) return;
|
||||
} else {
|
||||
outputFolder = await filesystem.checkAndCreateDir("/downloads/");
|
||||
outputFolder = await Storage.checkAndCreateDir("/downloads/");
|
||||
}
|
||||
|
||||
// Create the folder to zip;
|
||||
@@ -82,7 +83,7 @@ export async function downloadAttachments(attachmentIds: string[]) {
|
||||
await RNFetchBlob.fs.mkdir(zipSourceFolder);
|
||||
|
||||
const isCancelled = () => {
|
||||
if (useAttachmentStore.getState().downloading?.[groupId]?.canceled) {
|
||||
if (useAttachmentStore.getState().downloading[groupId]?.canceled) {
|
||||
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
|
||||
useAttachmentStore.getState().setDownloading({
|
||||
groupId,
|
||||
@@ -96,21 +97,19 @@ export async function downloadAttachments(attachmentIds: string[]) {
|
||||
}
|
||||
};
|
||||
|
||||
for (let i = 0; i < attachmentIds.length; i++) {
|
||||
for (let i = 0; i < attachments.length; i++) {
|
||||
if (isCancelled()) return;
|
||||
const attachment = await db.attachments.attachment(attachmentIds[i]);
|
||||
if (!attachment) continue;
|
||||
|
||||
let attachment = await db.attachments.attachment(attachments[i]);
|
||||
const hash = attachment.hash;
|
||||
try {
|
||||
useAttachmentStore.getState().setDownloading({
|
||||
groupId: groupId,
|
||||
current: i + 1,
|
||||
total: attachmentIds.length,
|
||||
total: attachments.length,
|
||||
filename: attachment.hash
|
||||
});
|
||||
// Download to cache
|
||||
const uri = await downloadAttachment(hash, false, {
|
||||
let uri = await downloadAttachment(hash, false, {
|
||||
silent: true,
|
||||
cache: true,
|
||||
groupId: groupId
|
||||
@@ -185,7 +184,7 @@ export async function downloadAttachments(attachmentIds: string[]) {
|
||||
});
|
||||
releasePermissions(outputFolder);
|
||||
sub?.remove();
|
||||
ToastManager.error(e as Error, "Error zipping attachments");
|
||||
ToastManager.error(e, "Error zipping attachments");
|
||||
}
|
||||
// Remove source & zip file from cache.
|
||||
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
|
||||
@@ -195,71 +194,49 @@ export async function downloadAttachments(attachmentIds: string[]) {
|
||||
}
|
||||
|
||||
export default async function downloadAttachment(
|
||||
hashOrId: string,
|
||||
hash,
|
||||
global = true,
|
||||
options?: {
|
||||
silent?: boolean;
|
||||
cache?: boolean;
|
||||
throwError?: boolean;
|
||||
groupId?: string;
|
||||
base64?: boolean;
|
||||
text?: boolean;
|
||||
options = {
|
||||
silent: false,
|
||||
cache: false,
|
||||
throwError: false,
|
||||
groupId: undefined,
|
||||
base64: false,
|
||||
text: false
|
||||
}
|
||||
) {
|
||||
await createCacheDir();
|
||||
|
||||
const attachment = await db.attachments.attachment(hashOrId);
|
||||
let attachment = await db.attachments.attachment(hash);
|
||||
if (!attachment) {
|
||||
DatabaseLogger.log("Attachment not found", {
|
||||
hash: hashOrId
|
||||
});
|
||||
DatabaseLogger.log("Attachment not found");
|
||||
return;
|
||||
}
|
||||
|
||||
let folder: {
|
||||
uri: string;
|
||||
} | null = null;
|
||||
if (!options?.cache) {
|
||||
let folder = {};
|
||||
if (!options.cache) {
|
||||
if (Platform.OS === "android") {
|
||||
folder = await ScopedStorage.openDocumentTree();
|
||||
if (!folder) return;
|
||||
} else {
|
||||
folder = {
|
||||
uri: await filesystem.checkAndCreateDir("/downloads/")
|
||||
};
|
||||
folder.uri = await Storage.checkAndCreateDir("/downloads/");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
useAttachmentStore.getState().setDownloading({
|
||||
groupId: options?.groupId || attachment.hash,
|
||||
current: 0,
|
||||
total: 1,
|
||||
filename: attachment.filename
|
||||
});
|
||||
|
||||
await db
|
||||
.fs()
|
||||
.downloadFile(
|
||||
options?.groupId || attachment.hash,
|
||||
options.groupId || attachment.hash,
|
||||
attachment.hash,
|
||||
attachment.chunkSize
|
||||
);
|
||||
|
||||
useAttachmentStore.getState().setDownloading({
|
||||
groupId: options?.groupId || attachment.hash,
|
||||
current: 1,
|
||||
total: 1,
|
||||
filename: attachment.filename,
|
||||
success: true
|
||||
});
|
||||
|
||||
if (!(await exists(attachment.hash))) {
|
||||
DatabaseLogger.log("Attachment does not exist after download.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (options?.base64 || options?.text) {
|
||||
if (options.base64 || options.text) {
|
||||
DatabaseLogger.log(`Starting to decrypt... hash: ${attachment.hash}`);
|
||||
return await db.attachments.read(
|
||||
attachment.hash,
|
||||
@@ -267,16 +244,14 @@ export default async function downloadAttachment(
|
||||
);
|
||||
}
|
||||
|
||||
const filename = await getFileNameWithExtension(
|
||||
let filename = getFileNameWithExtension(
|
||||
attachment.filename,
|
||||
attachment.mimeType
|
||||
);
|
||||
|
||||
const key = await db.attachments.decryptKey(attachment.key);
|
||||
let key = await db.attachments.decryptKey(attachment.key);
|
||||
|
||||
if (!key) return;
|
||||
|
||||
const info = {
|
||||
let info = {
|
||||
iv: attachment.iv,
|
||||
salt: attachment.salt,
|
||||
length: attachment.size,
|
||||
@@ -284,18 +259,18 @@ export default async function downloadAttachment(
|
||||
hash: attachment.hash,
|
||||
hashType: attachment.hashType,
|
||||
mime: attachment.mimeType,
|
||||
fileName: options?.cache ? undefined : filename,
|
||||
uri: options?.cache ? undefined : folder?.uri,
|
||||
fileName: options.cache ? undefined : filename,
|
||||
uri: options.cache ? undefined : folder.uri,
|
||||
chunkSize: attachment.chunkSize,
|
||||
appGroupId: IOS_APPGROUPID
|
||||
};
|
||||
let fileUri = await Sodium.decryptFile(
|
||||
key,
|
||||
info,
|
||||
options?.cache ? "cache" : "file"
|
||||
options.cache ? "cache" : "file"
|
||||
);
|
||||
|
||||
if (!options?.silent) {
|
||||
if (!options.silent) {
|
||||
ToastManager.show({
|
||||
heading: strings.network.downloadSuccess(),
|
||||
message: strings.network.fileDownloaded(filename),
|
||||
@@ -303,15 +278,15 @@ export default async function downloadAttachment(
|
||||
});
|
||||
}
|
||||
|
||||
if (Platform.OS === "ios" && !options?.cache) {
|
||||
fileUri = folder?.uri + `/${filename}`;
|
||||
if (Platform.OS === "ios" && !options.cache) {
|
||||
fileUri = folder.uri + `/${filename}`;
|
||||
}
|
||||
if (!options?.silent) {
|
||||
if (!options.silent) {
|
||||
presentSheet({
|
||||
title: strings.network.fileDownloaded(),
|
||||
paragraph: strings.fileSaved(filename, Platform.OS),
|
||||
icon: "download",
|
||||
context: global ? "global" : attachment.hash,
|
||||
context: global ? null : attachment.hash,
|
||||
component: <ShareComponent uri={fileUri} name={filename} padding={12} />
|
||||
});
|
||||
}
|
||||
@@ -326,17 +301,9 @@ export default async function downloadAttachment(
|
||||
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}_dcache`)
|
||||
.catch(console.log);
|
||||
}
|
||||
|
||||
useAttachmentStore.getState().setDownloading({
|
||||
groupId: options?.groupId || attachment.hash,
|
||||
current: 0,
|
||||
total: 0,
|
||||
filename: attachment.filename,
|
||||
success: false
|
||||
});
|
||||
DatabaseLogger.error(e);
|
||||
useAttachmentStore.getState().remove(attachment.hash);
|
||||
if (options?.throwError) {
|
||||
if (options.throwError) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { RequestOptions } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import NetInfo from "@react-native-community/netinfo";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
@@ -27,13 +26,7 @@ import { DatabaseLogger, db } from "../database";
|
||||
import { createCacheDir, exists } from "./io";
|
||||
import { ABYTES, cacheDir, getUploadedFileSize, parseS3Error } from "./utils";
|
||||
|
||||
export async function downloadFile(
|
||||
filename: string,
|
||||
requestOptions: RequestOptions,
|
||||
cancelToken: {
|
||||
cancel: (reason?: string) => Promise<void>;
|
||||
}
|
||||
) {
|
||||
export async function downloadFile(filename, requestOptions, cancelToken) {
|
||||
if (!requestOptions) {
|
||||
DatabaseLogger.log(
|
||||
`Error downloading file: ${filename}, reason: No requestOptions`
|
||||
@@ -43,11 +36,9 @@ export async function downloadFile(
|
||||
|
||||
DatabaseLogger.log(`Downloading ${filename}`);
|
||||
await createCacheDir();
|
||||
|
||||
const { url, headers, chunkSize } = requestOptions;
|
||||
const tempFilePath = `${cacheDir}/${filename}_temp`;
|
||||
const originalFilePath = `${cacheDir}/${filename}`;
|
||||
|
||||
let { url, headers, chunkSize } = requestOptions;
|
||||
let tempFilePath = `${cacheDir}/${filename}_temp`;
|
||||
let originalFilePath = `${cacheDir}/${filename}`;
|
||||
try {
|
||||
if (await exists(filename)) {
|
||||
DatabaseLogger.log(`File Exists already: ${filename}`);
|
||||
@@ -55,8 +46,6 @@ export async function downloadFile(
|
||||
}
|
||||
|
||||
const attachment = await db.attachments.attachment(filename);
|
||||
if (!attachment) return false;
|
||||
|
||||
const size = await getUploadedFileSize(filename);
|
||||
|
||||
if (size === -1) {
|
||||
@@ -82,21 +71,21 @@ export async function downloadFile(
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
const resolveUrlResponse = await fetch(url, {
|
||||
let res = await fetch(url, {
|
||||
method: "GET",
|
||||
headers
|
||||
});
|
||||
|
||||
if (!resolveUrlResponse.ok) {
|
||||
if (!res.ok) {
|
||||
DatabaseLogger.log(
|
||||
`Error downloading file: ${filename}, ${resolveUrlResponse.status}, ${resolveUrlResponse.statusText}, reason: Unable to resolve download url`
|
||||
`Error downloading file: ${filename}, ${res.status}, ${res.statusText}, reason: Unable to resolve download url`
|
||||
);
|
||||
throw new Error(
|
||||
`${resolveUrlResponse.status}: ${strings.failedToResolvedDownloadUrl()}`
|
||||
`${res.status}: ${strings.failedToResolvedDownloadUrl()}`
|
||||
);
|
||||
}
|
||||
|
||||
const downloadUrl = await resolveUrlResponse.text();
|
||||
const downloadUrl = await res.text();
|
||||
|
||||
if (!downloadUrl) {
|
||||
DatabaseLogger.log(
|
||||
@@ -106,12 +95,12 @@ export async function downloadFile(
|
||||
}
|
||||
|
||||
DatabaseLogger.log(`Download starting: ${filename}`);
|
||||
const request = RNFetchBlob.config({
|
||||
let request = RNFetchBlob.config({
|
||||
path: tempFilePath,
|
||||
IOSBackgroundTask: true,
|
||||
overwrite: true
|
||||
})
|
||||
.fetch("GET", downloadUrl)
|
||||
.fetch("GET", downloadUrl, null)
|
||||
.progress(async (recieved, total) => {
|
||||
useAttachmentStore
|
||||
.getState()
|
||||
@@ -120,14 +109,15 @@ export async function downloadFile(
|
||||
DatabaseLogger.log(`Downloading: ${filename}, ${recieved}/${total}`);
|
||||
});
|
||||
|
||||
cancelToken.cancel = async (reason) => {
|
||||
cancelToken.cancel = () => {
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
request.cancel();
|
||||
RNFetchBlob.fs.unlink(tempFilePath).catch(console.log);
|
||||
DatabaseLogger.log(`Download cancelled: ${reason} ${filename}`);
|
||||
DatabaseLogger.log(`Download cancelled: ${filename}`);
|
||||
};
|
||||
|
||||
const response = await request;
|
||||
let response = await request;
|
||||
console.log(response.info().headers);
|
||||
|
||||
const contentType =
|
||||
response.info().headers?.["content-type"] ||
|
||||
@@ -138,10 +128,10 @@ export async function downloadFile(
|
||||
throw new Error(`[${error.Code}] ${error.Message}`);
|
||||
}
|
||||
|
||||
const status = response.info().status;
|
||||
let status = response.info().status;
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
|
||||
if (await exists(originalFilePath)) {
|
||||
if (exists(originalFilePath)) {
|
||||
await RNFetchBlob.fs.unlink(originalFilePath).catch(console.log);
|
||||
}
|
||||
|
||||
@@ -153,14 +143,11 @@ export async function downloadFile(
|
||||
|
||||
return status >= 200 && status < 300;
|
||||
} catch (e) {
|
||||
if (
|
||||
(e as Error).message !== "canceled" &&
|
||||
!(e as Error).message.includes("NoSuchKey")
|
||||
) {
|
||||
if (e.message !== "canceled" && !e.message.includes("NoSuchKey")) {
|
||||
const toast = {
|
||||
heading: strings.downloadError((e as Error).message),
|
||||
message: (e as Error).message,
|
||||
type: "error" as const,
|
||||
heading: strings.downloadError(),
|
||||
message: e.message,
|
||||
type: "error",
|
||||
context: "global"
|
||||
};
|
||||
ToastManager.show(toast);
|
||||
@@ -171,14 +158,15 @@ export async function downloadFile(
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
RNFetchBlob.fs.unlink(tempFilePath).catch(console.log);
|
||||
RNFetchBlob.fs.unlink(originalFilePath).catch(console.log);
|
||||
DatabaseLogger.error(e, "Download failed: ", {
|
||||
url
|
||||
DatabaseLogger.error(e, {
|
||||
url,
|
||||
headers
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkAttachment(hash: string) {
|
||||
export async function checkAttachment(hash) {
|
||||
const internetState = await NetInfo.fetch();
|
||||
const isInternetReachable =
|
||||
internetState.isConnected && internetState.isInternetReachable;
|
||||
@@ -196,7 +184,7 @@ export async function checkAttachment(hash: string) {
|
||||
failed: `File length is 0. Please upload this file again from the attachment manager. (File hash: ${hash})`
|
||||
};
|
||||
} catch (e) {
|
||||
return { failed: (e as Error)?.message };
|
||||
return { failed: e?.message };
|
||||
}
|
||||
return { success: true };
|
||||
}
|
||||
@@ -17,40 +17,24 @@ 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 { IFileStorage } from "@notesnook/core";
|
||||
import { checkAttachment, downloadFile } from "./download";
|
||||
import {
|
||||
bulkExists,
|
||||
clearCache,
|
||||
clearFileStorage,
|
||||
deleteCacheFileByName,
|
||||
deleteCacheFileByPath,
|
||||
deleteFile,
|
||||
exists,
|
||||
getCacheSize,
|
||||
hashBase64,
|
||||
readEncrypted,
|
||||
writeEncryptedBase64
|
||||
} from "./io";
|
||||
import { uploadFile } from "./upload";
|
||||
import {
|
||||
cancelable,
|
||||
checkAndCreateDir,
|
||||
getUploadedFileSize,
|
||||
requestPermission
|
||||
} from "./utils";
|
||||
|
||||
export default {
|
||||
checkAttachment,
|
||||
writeEncryptedBase64,
|
||||
hashBase64,
|
||||
clearCache,
|
||||
deleteCacheFileByName,
|
||||
deleteCacheFileByPath,
|
||||
getCacheSize,
|
||||
requestPermission,
|
||||
checkAndCreateDir
|
||||
};
|
||||
bulkExists,
|
||||
getCacheSize
|
||||
} from "./io";
|
||||
import { uploadFile } from "./upload";
|
||||
import { cancelable, getUploadedFileSize } from "./utils";
|
||||
|
||||
export const FileStorage: IFileStorage = {
|
||||
export default {
|
||||
readEncrypted,
|
||||
writeEncryptedBase64,
|
||||
hashBase64,
|
||||
@@ -60,5 +44,10 @@ export const FileStorage: IFileStorage = {
|
||||
exists,
|
||||
clearFileStorage,
|
||||
getUploadedFileSize,
|
||||
bulkExists
|
||||
checkAttachment,
|
||||
clearCache,
|
||||
deleteCacheFileByName,
|
||||
deleteCacheFileByPath,
|
||||
bulkExists,
|
||||
getCacheSize
|
||||
};
|
||||
@@ -18,13 +18,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Sodium from "@ammarahmed/react-native-sodium";
|
||||
import {
|
||||
FileEncryptionMetadataWithHash,
|
||||
FileEncryptionMetadataWithOutputType,
|
||||
Output,
|
||||
RequestOptions
|
||||
} from "@notesnook/core";
|
||||
import { DataFormat, SerializedKey } from "@notesnook/crypto";
|
||||
import { Platform } from "react-native";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import { eSendEvent } from "../../services/event-manager";
|
||||
@@ -32,21 +25,17 @@ import { IOS_APPGROUPID } from "../../utils/constants";
|
||||
import { DatabaseLogger, db } from "../database";
|
||||
import { ABYTES, cacheDir, cacheDirOld, getRandomId } from "./utils";
|
||||
|
||||
export async function readEncrypted<TOutputFormat extends DataFormat>(
|
||||
filename: string,
|
||||
key: SerializedKey,
|
||||
cipherData: FileEncryptionMetadataWithOutputType<TOutputFormat>
|
||||
) {
|
||||
export async function readEncrypted(filename, key, cipherData) {
|
||||
await migrateFilesFromCache();
|
||||
DatabaseLogger.log("Read encrypted file...");
|
||||
const path = `${cacheDir}/${filename}`;
|
||||
let path = `${cacheDir}/${filename}`;
|
||||
|
||||
try {
|
||||
if (!(await exists(filename))) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const output = await Sodium.decryptFile(
|
||||
let output = await Sodium.decryptFile(
|
||||
key,
|
||||
{
|
||||
...cipherData,
|
||||
@@ -58,14 +47,15 @@ export async function readEncrypted<TOutputFormat extends DataFormat>(
|
||||
|
||||
DatabaseLogger.log("File decrypted...");
|
||||
|
||||
return output as Output<TOutputFormat>;
|
||||
return output;
|
||||
} catch (e) {
|
||||
RNFetchBlob.fs.unlink(path).catch(console.log);
|
||||
DatabaseLogger.error(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function hashBase64(data: string) {
|
||||
export async function hashBase64(data) {
|
||||
const hash = await Sodium.hashFile({
|
||||
type: "base64",
|
||||
data,
|
||||
@@ -77,70 +67,61 @@ export async function hashBase64(data: string) {
|
||||
};
|
||||
}
|
||||
|
||||
export async function writeEncryptedBase64(
|
||||
data: string,
|
||||
encryptionKey: SerializedKey,
|
||||
mimeType: string
|
||||
): Promise<FileEncryptionMetadataWithHash> {
|
||||
export async function writeEncryptedBase64(data, key) {
|
||||
await createCacheDir();
|
||||
const filepath = cacheDir + `/${getRandomId("imagecache_")}`;
|
||||
let filepath = cacheDir + `/${getRandomId("imagecache_")}`;
|
||||
await RNFetchBlob.fs.writeFile(filepath, data, "base64");
|
||||
const output = await Sodium.encryptFile(encryptionKey, {
|
||||
let output = await Sodium.encryptFile(key, {
|
||||
uri: Platform.OS === "ios" ? filepath : "file://" + filepath,
|
||||
type: "url"
|
||||
});
|
||||
|
||||
RNFetchBlob.fs.unlink(filepath).catch(console.log);
|
||||
console.log("encrypted file output: ", output);
|
||||
|
||||
output.size = output.length;
|
||||
delete output.length;
|
||||
return {
|
||||
...output,
|
||||
alg: "xcha-stream"
|
||||
};
|
||||
}
|
||||
|
||||
export async function deleteFile(
|
||||
filename: string,
|
||||
requestOptions?: RequestOptions
|
||||
): Promise<boolean> {
|
||||
export async function deleteFile(filename, data) {
|
||||
await createCacheDir();
|
||||
const localFilePath = cacheDir + `/${filename}`;
|
||||
if (!requestOptions) {
|
||||
RNFetchBlob.fs.unlink(localFilePath).catch(console.log);
|
||||
let delFilePath = cacheDir + `/${filename}`;
|
||||
if (!data) {
|
||||
if (!filename) return;
|
||||
RNFetchBlob.fs.unlink(delFilePath).catch(console.log);
|
||||
return true;
|
||||
}
|
||||
|
||||
const { url, headers } = requestOptions;
|
||||
|
||||
let { url, headers } = data;
|
||||
try {
|
||||
const response = await RNFetchBlob.fetch("DELETE", url, headers);
|
||||
const status = response.info().status;
|
||||
const ok = status >= 200 && status < 300;
|
||||
let response = await RNFetchBlob.fetch("DELETE", url, headers);
|
||||
let status = response.info().status;
|
||||
let ok = status >= 200 && status < 300;
|
||||
if (ok) {
|
||||
RNFetchBlob.fs.unlink(localFilePath).catch(console.log);
|
||||
RNFetchBlob.fs.unlink(delFilePath).catch(console.log);
|
||||
}
|
||||
return ok;
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e, "Delete file", {
|
||||
url: url
|
||||
});
|
||||
console.log("delete file: ", e, url, headers);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function clearFileStorage() {
|
||||
try {
|
||||
const files = await RNFetchBlob.fs.ls(cacheDir);
|
||||
const oldCache = await RNFetchBlob.fs.ls(cacheDirOld);
|
||||
let files = await RNFetchBlob.fs.ls(cacheDir);
|
||||
let oldCache = await RNFetchBlob.fs.ls(cacheDirOld);
|
||||
|
||||
for (const file of files) {
|
||||
for (let file of files) {
|
||||
await RNFetchBlob.fs.unlink(cacheDir + `/${file}`).catch(console.log);
|
||||
}
|
||||
for (const file of oldCache) {
|
||||
for (let file of oldCache) {
|
||||
await RNFetchBlob.fs.unlink(cacheDirOld + `/${file}`).catch(console.log);
|
||||
}
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e, "clearFileStorage");
|
||||
console.log("clearFileStorage", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,11 +146,11 @@ export async function migrateFilesFromCache() {
|
||||
return;
|
||||
}
|
||||
|
||||
const files = await RNFetchBlob.fs.ls(cacheDir);
|
||||
let files = await RNFetchBlob.fs.ls(cacheDir);
|
||||
console.log("Files to migrate:", files.join(","));
|
||||
|
||||
const oldCache = await RNFetchBlob.fs.ls(cacheDirOld);
|
||||
for (const file of oldCache) {
|
||||
let oldCache = await RNFetchBlob.fs.ls(cacheDirOld);
|
||||
for (let file of oldCache) {
|
||||
if (file.startsWith("org.") || file.startsWith("com.")) continue;
|
||||
RNFetchBlob.fs
|
||||
.mv(cacheDirOld + `/${file}`, cacheDir + `/${file}`)
|
||||
@@ -178,7 +159,7 @@ export async function migrateFilesFromCache() {
|
||||
}
|
||||
await RNFetchBlob.fs.createFile(migratedFilesPath, "1", "utf8");
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e, "migrateFilesFromCache");
|
||||
console.log("migrateFilesFromCache", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,14 +169,14 @@ export async function clearCache() {
|
||||
eSendEvent("cache-cleared");
|
||||
}
|
||||
|
||||
export async function deleteCacheFileByPath(path: string) {
|
||||
export async function deleteCacheFileByPath(path) {
|
||||
await RNFetchBlob.fs.unlink(path).catch(console.log);
|
||||
}
|
||||
|
||||
export async function deleteCacheFileByName(name: string) {
|
||||
export async function deleteCacheFileByName(name) {
|
||||
const iosAppGroup =
|
||||
Platform.OS === "ios"
|
||||
? await (RNFetchBlob.fs as any).pathForAppGroup(IOS_APPGROUPID)
|
||||
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
|
||||
: null;
|
||||
const appGroupPath = `${iosAppGroup}/${name}`;
|
||||
await RNFetchBlob.fs.unlink(appGroupPath).catch(console.log);
|
||||
@@ -211,12 +192,12 @@ export async function deleteDCacheFiles() {
|
||||
}
|
||||
}
|
||||
|
||||
export async function exists(filename: string) {
|
||||
const path = `${cacheDir}/${filename}`;
|
||||
export async function exists(filename) {
|
||||
let path = `${cacheDir}/${filename}`;
|
||||
|
||||
const iosAppGroup =
|
||||
Platform.OS === "ios"
|
||||
? await (RNFetchBlob.fs as any).pathForAppGroup(IOS_APPGROUPID)
|
||||
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
|
||||
: null;
|
||||
const appGroupPath = `${iosAppGroup}/${filename}`;
|
||||
|
||||
@@ -230,7 +211,6 @@ export async function exists(filename: string) {
|
||||
|
||||
if (exists || existsInAppGroup) {
|
||||
const attachment = await db.attachments.attachment(filename);
|
||||
if (!attachment) return false;
|
||||
const totalChunks = Math.ceil(attachment.size / attachment.chunkSize);
|
||||
const totalAbytes = totalChunks * ABYTES;
|
||||
const expectedFileSize = attachment.size + totalAbytes;
|
||||
@@ -254,7 +234,7 @@ export async function exists(filename: string) {
|
||||
return exists;
|
||||
}
|
||||
|
||||
export async function bulkExists(files: string[]) {
|
||||
export async function bulkExists(files) {
|
||||
try {
|
||||
await createCacheDir();
|
||||
const cacheFiles = await RNFetchBlob.fs.ls(cacheDir);
|
||||
@@ -263,7 +243,7 @@ export async function bulkExists(files: string[]) {
|
||||
if (Platform.OS === "ios") {
|
||||
const iosAppGroup =
|
||||
Platform.OS === "ios"
|
||||
? await (RNFetchBlob.fs as any).pathForAppGroup(IOS_APPGROUPID)
|
||||
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
|
||||
: null;
|
||||
const appGroupFiles = await RNFetchBlob.fs.ls(iosAppGroup);
|
||||
missingFiles = missingFiles.filter(
|
||||
@@ -282,8 +262,8 @@ export async function getCacheSize() {
|
||||
const stat = await RNFetchBlob.fs.lstat(`file://` + cacheDir);
|
||||
let total = 0;
|
||||
console.log("Total files", stat.length);
|
||||
stat.forEach((file) => {
|
||||
total += parseInt(file.size as unknown as string);
|
||||
stat.forEach((s) => {
|
||||
total += parseInt(s.size);
|
||||
});
|
||||
return total;
|
||||
}
|
||||
@@ -17,7 +17,6 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { RequestOptions } from "@notesnook/core";
|
||||
import { Platform } from "react-native";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import { ToastManager } from "../../services/event-manager";
|
||||
@@ -25,22 +24,11 @@ import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { IOS_APPGROUPID } from "../../utils/constants";
|
||||
import { DatabaseLogger, db } from "../database";
|
||||
import { createCacheDir } from "./io";
|
||||
import {
|
||||
cacheDir,
|
||||
checkUpload,
|
||||
FileSizeResult,
|
||||
getUploadedFileSize
|
||||
} from "./utils";
|
||||
import { cacheDir, checkUpload, getUploadedFileSize } from "./utils";
|
||||
|
||||
export async function uploadFile(
|
||||
filename: string,
|
||||
requestOptions: RequestOptions,
|
||||
cancelToken: {
|
||||
cancel: (reason?: string) => Promise<void>;
|
||||
}
|
||||
) {
|
||||
export async function uploadFile(filename, requestOptions, cancelToken) {
|
||||
if (!requestOptions) return false;
|
||||
const { url, headers } = requestOptions;
|
||||
let { url, headers } = requestOptions;
|
||||
await createCacheDir();
|
||||
DatabaseLogger.info(`Preparing to upload file: ${filename}`);
|
||||
|
||||
@@ -51,7 +39,7 @@ export async function uploadFile(
|
||||
if (!exists && Platform.OS === "ios") {
|
||||
const iosAppGroup =
|
||||
Platform.OS === "ios"
|
||||
? await (RNFetchBlob.fs as any).pathForAppGroup(IOS_APPGROUPID)
|
||||
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
|
||||
: null;
|
||||
const appGroupPath = `${iosAppGroup}/${filename}`;
|
||||
filePath = appGroupPath;
|
||||
@@ -66,15 +54,14 @@ export async function uploadFile(
|
||||
|
||||
const fileSize = (await RNFetchBlob.fs.stat(filePath)).size;
|
||||
|
||||
const remoteFileSize = await getUploadedFileSize(filename);
|
||||
if (remoteFileSize === FileSizeResult.Error) return false;
|
||||
|
||||
if (remoteFileSize > FileSizeResult.Empty && remoteFileSize === fileSize) {
|
||||
let remoteFileSize = await getUploadedFileSize(filename);
|
||||
if (remoteFileSize === -1) return false;
|
||||
if (remoteFileSize > 0 && remoteFileSize === fileSize) {
|
||||
DatabaseLogger.log(`File ${filename} is already uploaded.`);
|
||||
return true;
|
||||
}
|
||||
|
||||
const uploadUrlResponse = await fetch(url, {
|
||||
let uploadUrlResponse = await fetch(url, {
|
||||
method: "PUT",
|
||||
headers
|
||||
});
|
||||
@@ -91,8 +78,7 @@ export async function uploadFile(
|
||||
|
||||
DatabaseLogger.info(`Starting upload: ${filename}`);
|
||||
|
||||
const uploadRequest = RNFetchBlob.config({
|
||||
//@ts-ignore
|
||||
let uploadRequest = RNFetchBlob.config({
|
||||
IOSBackgroundTask: !globalThis["IS_SHARE_EXTENSION"]
|
||||
})
|
||||
.fetch(
|
||||
@@ -112,14 +98,14 @@ export async function uploadFile(
|
||||
);
|
||||
});
|
||||
|
||||
cancelToken.cancel = async () => {
|
||||
cancelToken.cancel = () => {
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
uploadRequest.cancel();
|
||||
};
|
||||
|
||||
const uploadResponse = await uploadRequest;
|
||||
const status = uploadResponse.info().status;
|
||||
const uploaded = status >= 200 && status < 300;
|
||||
let uploadResponse = await uploadRequest;
|
||||
let status = uploadResponse.info().status;
|
||||
let uploaded = status >= 200 && status < 300;
|
||||
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
|
||||
@@ -132,13 +118,12 @@ export async function uploadFile(
|
||||
);
|
||||
}
|
||||
const attachment = await db.attachments.attachment(filename);
|
||||
if (!attachment) return false;
|
||||
await checkUpload(filename, requestOptions.chunkSize, attachment.size);
|
||||
DatabaseLogger.info(`File upload status: ${filename}, ${status}`);
|
||||
return uploaded;
|
||||
} catch (e) {
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
ToastManager.error(e as Error, "File upload failed");
|
||||
ToastManager.error(e, "File upload failed");
|
||||
DatabaseLogger.error(e, "File upload failed", {
|
||||
filename
|
||||
});
|
||||
@@ -17,11 +17,11 @@ 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 { hosts, RequestOptions } from "@notesnook/core";
|
||||
import { Platform } from "react-native";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import { DatabaseLogger, db } from "../database";
|
||||
import { hosts } from "@notesnook/core";
|
||||
|
||||
export const ABYTES = 17;
|
||||
export const cacheDirOld = RNFetchBlob.fs.dirs.CacheDir;
|
||||
@@ -31,13 +31,18 @@ export const cacheDir =
|
||||
? RNFetchBlob.fs.dirs.LibraryDir + "/.cache"
|
||||
: RNFetchBlob.fs.dirs.DocumentDir + "/.cache";
|
||||
|
||||
export function getRandomId(prefix: string) {
|
||||
export function getRandomId(prefix) {
|
||||
return Math.random()
|
||||
.toString(36)
|
||||
.replace("0.", prefix || "");
|
||||
}
|
||||
|
||||
export function parseS3Error(data?: string) {
|
||||
/**
|
||||
*
|
||||
* @param {string | undefined} data
|
||||
* @returns
|
||||
*/
|
||||
export function parseS3Error(data) {
|
||||
const xml = typeof data === "string" ? data : null;
|
||||
|
||||
const error = {
|
||||
@@ -65,19 +70,11 @@ export function parseS3Error(data?: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export function cancelable(
|
||||
operation: (
|
||||
filename: string,
|
||||
requestOptions: RequestOptions,
|
||||
cancelToken: {
|
||||
cancel: (reason?: string) => Promise<void>;
|
||||
}
|
||||
) => Promise<boolean>
|
||||
) {
|
||||
export function cancelable(operation) {
|
||||
const cancelToken = {
|
||||
cancel: async (reason?: string) => {}
|
||||
cancel: () => {}
|
||||
};
|
||||
return (filename: string, requestOptions: RequestOptions) => {
|
||||
return (filename, requestOptions) => {
|
||||
return {
|
||||
execute: () => operation(filename, requestOptions, cancelToken),
|
||||
cancel: async () => {
|
||||
@@ -87,35 +84,29 @@ export function cancelable(
|
||||
};
|
||||
}
|
||||
|
||||
export function copyFileAsync(source: string, dest: string) {
|
||||
export function copyFileAsync(source, dest) {
|
||||
return new Promise((resolve, reject) => {
|
||||
//@ts-ignore
|
||||
ScopedStorage.copyFile(source, dest, (e: any, r: any) => {
|
||||
ScopedStorage.copyFile(source, dest, (e, r) => {
|
||||
if (e) {
|
||||
reject(e);
|
||||
return;
|
||||
}
|
||||
resolve(true);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function releasePermissions(path: string) {
|
||||
export async function releasePermissions(path) {
|
||||
if (Platform.OS === "ios") return;
|
||||
const uris = await ScopedStorage.getPersistedUriPermissions();
|
||||
for (const uri of uris) {
|
||||
for (let uri of uris) {
|
||||
if (path.startsWith(uri)) {
|
||||
await ScopedStorage.releasePersistableUriPermission(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const FileSizeResult = {
|
||||
Empty: 0,
|
||||
Error: -1
|
||||
};
|
||||
|
||||
export async function getUploadedFileSize(hash: string) {
|
||||
export async function getUploadedFileSize(hash) {
|
||||
try {
|
||||
const url = `${hosts.API_HOST}/s3?name=${hash}`;
|
||||
const token = await db.tokenManager.getAccessToken();
|
||||
@@ -124,20 +115,16 @@ export async function getUploadedFileSize(hash: string) {
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
});
|
||||
const contentLength = parseInt(
|
||||
attachmentInfo.headers?.get("content-length") || "0"
|
||||
attachmentInfo.headers?.get("content-length")
|
||||
);
|
||||
return isNaN(contentLength) ? FileSizeResult.Empty : contentLength;
|
||||
return isNaN(contentLength) ? 0 : contentLength;
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e);
|
||||
return FileSizeResult.Error;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkUpload(
|
||||
filename: string,
|
||||
chunkSize: number,
|
||||
expectedSize: number
|
||||
) {
|
||||
export async function checkUpload(filename, chunkSize, expectedSize) {
|
||||
const size = await getUploadedFileSize(filename);
|
||||
const totalChunks = Math.ceil(size / chunkSize);
|
||||
const decryptedLength = size - totalChunks * ABYTES;
|
||||
@@ -151,25 +138,3 @@ export async function checkUpload(
|
||||
: undefined;
|
||||
if (error) throw new Error(error);
|
||||
}
|
||||
|
||||
export async function requestPermission() {
|
||||
if (Platform.OS === "ios") return true;
|
||||
return true;
|
||||
}
|
||||
export async function checkAndCreateDir(path: string) {
|
||||
const dir =
|
||||
Platform.OS === "ios"
|
||||
? RNFetchBlob.fs.dirs.DocumentDir + path
|
||||
: RNFetchBlob.fs.dirs.SDCardDir + "/Notesnook/" + path;
|
||||
|
||||
try {
|
||||
const exists = await RNFetchBlob.fs.exists(dir);
|
||||
const isDir = await RNFetchBlob.fs.isDir(dir);
|
||||
if (!exists || !isDir) {
|
||||
await RNFetchBlob.fs.mkdir(dir);
|
||||
}
|
||||
} catch (e) {
|
||||
await RNFetchBlob.fs.mkdir(dir);
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
@@ -63,11 +63,9 @@ const Actions = ({
|
||||
attachment,
|
||||
close,
|
||||
setAttachments,
|
||||
fwdRef,
|
||||
context
|
||||
fwdRef
|
||||
}: {
|
||||
attachment: Attachment;
|
||||
context: string;
|
||||
setAttachments: (attachments?: VirtualizedGrouping<Attachment>) => void;
|
||||
close?: () => void;
|
||||
fwdRef: RefObject<ActionSheetRef>;
|
||||
@@ -81,6 +79,7 @@ const Actions = ({
|
||||
const [loading, setLoading] = useState<{
|
||||
name?: string;
|
||||
}>({});
|
||||
|
||||
const actions = [
|
||||
{
|
||||
name: strings.network.download(),
|
||||
@@ -89,7 +88,7 @@ const Actions = ({
|
||||
await db.fs().cancel(attachment.hash);
|
||||
useAttachmentStore.getState().remove(attachment.hash);
|
||||
}
|
||||
downloadAttachment(attachment.hash, context === "global");
|
||||
downloadAttachment(attachment.hash, false);
|
||||
fwdRef.current?.hide();
|
||||
},
|
||||
icon: "download"
|
||||
@@ -376,7 +375,6 @@ Actions.present = (
|
||||
setAttachments={set}
|
||||
close={close}
|
||||
attachment={attachment}
|
||||
context={context || "global"}
|
||||
/>
|
||||
)
|
||||
});
|
||||
|
||||
@@ -31,7 +31,6 @@ import { ProgressCircleComponent } from "../ui/svg/lazy";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import Actions from "./actions";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { Pressable } from "../ui/pressable";
|
||||
|
||||
function getFileExtension(filename: string) {
|
||||
const ext = /^.+\.([^.]+)$/.exec(filename);
|
||||
@@ -70,7 +69,8 @@ export const AttachmentItem = ({
|
||||
};
|
||||
|
||||
return errorOnly && attachment && !attachment?.failed ? null : (
|
||||
<Pressable
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.9}
|
||||
onPress={onPress}
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
@@ -186,6 +186,6 @@ export const AttachmentItem = ({
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Pressable>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -206,7 +206,7 @@ export const AttachmentDialog = ({
|
||||
errorOnly={currentFilter === "errors"}
|
||||
attachments={attachments}
|
||||
id={index}
|
||||
context={!isSheet ? "global" : "attachments-list"}
|
||||
context="global"
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -68,15 +68,11 @@ export const ChangePassword = () => {
|
||||
}
|
||||
setLoading(true);
|
||||
try {
|
||||
const result = await BackupService.run(
|
||||
false,
|
||||
"change-password-dialog",
|
||||
"partial"
|
||||
);
|
||||
if (result.error) {
|
||||
const result = await BackupService.run(false, "change-password-dialog");
|
||||
if (!result.error)
|
||||
throw new Error(strings.backupFailed() + `: ${result.error}`);
|
||||
}
|
||||
|
||||
await db.user.clearSessions();
|
||||
await db.user.changePassword(oldPassword.current, password.current);
|
||||
ToastManager.show({
|
||||
heading: strings.passwordChangedSuccessfully(),
|
||||
@@ -88,7 +84,6 @@ export const ChangePassword = () => {
|
||||
await sleep(300);
|
||||
eSendEvent(eOpenRecoveryKeyDialog);
|
||||
} catch (e) {
|
||||
console.log(e.stack);
|
||||
setLoading(false);
|
||||
ToastManager.show({
|
||||
heading: strings.passwordChangeFailed(),
|
||||
|
||||
@@ -162,7 +162,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo, onCancel }) => {
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
{strings["2faCodeHelpText"][currentMethod.method]?.() ||
|
||||
{strings["2faCodeHelpText"][currentMethod.method]() ||
|
||||
strings.select2faCodeHelpText()}
|
||||
</Paragraph>
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
View
|
||||
} from "react-native";
|
||||
import { Image as ImageType } from "react-native-image-crop-picker";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { useThemeColors } from "../../../../../../packages/theme/dist";
|
||||
import { presentSheet } from "../../../services/event-manager";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { Button } from "../../ui/button";
|
||||
|
||||
@@ -188,7 +188,7 @@ const PDFPreview = () => {
|
||||
}}
|
||||
color={colors.static.white}
|
||||
>
|
||||
{strings.loadingWithProgress(progress?.percent)}
|
||||
{strings.loadingWithProgress(progress.percent)}
|
||||
</Paragraph>
|
||||
</Animated.View>
|
||||
) : (
|
||||
|
||||
@@ -164,7 +164,7 @@ export default function Progress() {
|
||||
/>
|
||||
</View>
|
||||
|
||||
{!data?.canHideProgress ? null : (
|
||||
{data?.canHideProgress ? null : (
|
||||
<Button
|
||||
title={cancelCallback.current ? "Cancel" : "Hide"}
|
||||
type="secondaryAccented"
|
||||
|
||||
@@ -460,7 +460,7 @@ export class VaultDialog extends Component {
|
||||
async _deleteNote() {
|
||||
try {
|
||||
await db.vault.remove(this.state.note.id, this.password);
|
||||
await deleteItems("note", [this.state.note.id]);
|
||||
await deleteItems([this.state.note.id], "note");
|
||||
this.close();
|
||||
} catch (e) {
|
||||
this._takeErrorAction(e);
|
||||
|
||||
@@ -19,8 +19,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useRef } from "react";
|
||||
import { Platform, StyleSheet, View } from "react-native";
|
||||
//@ts-ignore
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { Menu } from "react-native-material-menu";
|
||||
import Menu from "react-native-reanimated-material-menu";
|
||||
import { notesnook } from "../../../e2e/test.ids";
|
||||
import {
|
||||
HeaderRightButton,
|
||||
@@ -96,16 +97,14 @@ export const RightMenus = ({
|
||||
style={{
|
||||
borderRadius: 5,
|
||||
backgroundColor: contextMenuColors.primary.background,
|
||||
marginTop: 35
|
||||
marginTop: -40
|
||||
}}
|
||||
onRequestClose={() => {
|
||||
//@ts-ignore
|
||||
menuRef.current?.hide();
|
||||
}}
|
||||
anchor={
|
||||
<IconButton
|
||||
onPress={() => {
|
||||
//@ts-ignore
|
||||
menuRef.current?.show();
|
||||
}}
|
||||
name="dots-vertical"
|
||||
@@ -117,10 +116,9 @@ export const RightMenus = ({
|
||||
{headerRightButtons.map((item) => (
|
||||
<Button
|
||||
style={{
|
||||
width: 150,
|
||||
justifyContent: "flex-start",
|
||||
borderRadius: 0,
|
||||
alignSelf: "flex-start",
|
||||
width: "100%"
|
||||
borderRadius: 0
|
||||
}}
|
||||
type="plain"
|
||||
buttonType={{
|
||||
@@ -129,7 +127,6 @@ export const RightMenus = ({
|
||||
key={item.title}
|
||||
title={item.title}
|
||||
onPress={async () => {
|
||||
//@ts-ignore
|
||||
menuRef.current?.hide();
|
||||
if (Platform.OS === "ios") await sleep(300);
|
||||
item.onPress();
|
||||
|
||||
@@ -83,7 +83,7 @@ const Intro = ({ navigation }) => {
|
||||
>
|
||||
{item.headings?.map((heading) => (
|
||||
<Heading
|
||||
key={heading()}
|
||||
key={heading}
|
||||
style={{
|
||||
marginBottom: 5
|
||||
}}
|
||||
@@ -128,6 +128,7 @@ const Intro = ({ navigation }) => {
|
||||
style={{
|
||||
width: deviceMode !== "mobile" ? width / 2 : "100%",
|
||||
backgroundColor: colors.secondary.background,
|
||||
marginBottom: 20,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.primary.border,
|
||||
alignSelf: deviceMode !== "mobile" ? "center" : undefined,
|
||||
@@ -137,7 +138,7 @@ const Intro = ({ navigation }) => {
|
||||
marginTop: deviceMode !== "mobile" ? 50 : null,
|
||||
paddingTop: insets.top + 10,
|
||||
paddingBottom: insets.top + 10,
|
||||
minHeight: height * 0.7 - (insets.top + insets.bottom)
|
||||
minHeight: height * 0.7
|
||||
}}
|
||||
>
|
||||
<SwiperFlatList
|
||||
|
||||
@@ -57,7 +57,10 @@ export const SectionHeader = React.memo<
|
||||
}: SectionHeaderProps) {
|
||||
const { colors } = useThemeColors();
|
||||
const { fontScale } = useWindowDimensions();
|
||||
const groupBy = strings.groupByStrings[groupOptions.groupBy]();
|
||||
const groupBy =
|
||||
strings.groupByStrings[
|
||||
groupOptions.groupBy as keyof typeof strings.groupByStrings
|
||||
]?.();
|
||||
const isCompactModeEnabled = useIsCompactModeEnabled(
|
||||
dataType as "note" | "notebook"
|
||||
);
|
||||
|
||||
@@ -81,8 +81,7 @@ const ReminderItem = React.memo(
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
flexWrap: "wrap",
|
||||
marginTop: 5
|
||||
flexWrap: "wrap"
|
||||
}}
|
||||
>
|
||||
{item.disabled ? (
|
||||
@@ -150,8 +149,10 @@ const ReminderItem = React.memo(
|
||||
fontSize={SIZE.xs}
|
||||
style={{
|
||||
justifyContent: "flex-start",
|
||||
height: 25,
|
||||
alignSelf: "flex-start"
|
||||
borderWidth: 0,
|
||||
height: 30,
|
||||
alignSelf: "flex-start",
|
||||
marginTop: 5
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -70,7 +70,6 @@ const SelectionWrapper = ({
|
||||
}
|
||||
|
||||
const onLongPress = () => {
|
||||
if (isSheet) return;
|
||||
if (useSelectionStore.getState().selectionMode !== item.type) {
|
||||
useSelectionStore.getState().setSelectionMode(item.type);
|
||||
}
|
||||
@@ -79,7 +78,7 @@ const SelectionWrapper = ({
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
customColor={isSheet ? colors.secondary.background : "transparent"}
|
||||
customColor={isSheet ? colors.primary.hover : "transparent"}
|
||||
testID={testID}
|
||||
onLongPress={onLongPress}
|
||||
onPress={onPress}
|
||||
|
||||
@@ -137,13 +137,14 @@ export const Items = ({ item, buttons, close }) => {
|
||||
style={{
|
||||
alignSelf: "flex-start",
|
||||
paddingHorizontal: 0,
|
||||
flex: 1
|
||||
width: topBarItemWidth
|
||||
}}
|
||||
>
|
||||
<View
|
||||
onPress={item.func}
|
||||
style={{
|
||||
height: topBarItemWidth,
|
||||
width: topBarItemWidth,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
textAlign: "center",
|
||||
@@ -168,7 +169,7 @@ export const Items = ({ item, buttons, close }) => {
|
||||
|
||||
<Paragraph
|
||||
textBreakStrategy="simple"
|
||||
size={SIZE.xxs}
|
||||
size={SIZE.xxs + 1}
|
||||
style={{ textAlign: "center" }}
|
||||
>
|
||||
{item.title}
|
||||
@@ -241,7 +242,7 @@ export const Items = ({ item, buttons, close }) => {
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={{
|
||||
paddingRight: 25,
|
||||
gap: 15
|
||||
gap: 5
|
||||
}}
|
||||
>
|
||||
{topBarItems.map(renderTopBarItem)}
|
||||
|
||||
@@ -18,7 +18,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Item, ItemType, VirtualizedGrouping } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import {
|
||||
@@ -27,7 +26,7 @@ import {
|
||||
Platform,
|
||||
View
|
||||
} from "react-native";
|
||||
import { Menu } from "react-native-material-menu";
|
||||
import Menu from "react-native-reanimated-material-menu/src/Menu";
|
||||
import { db } from "../../common/database";
|
||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||
import { ToastManager } from "../../services/event-manager";
|
||||
@@ -47,6 +46,7 @@ import { MoveNotebookSheet } from "../sheets/move-notebook";
|
||||
import { Button } from "../ui/button";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
export const SelectionHeader = React.memo(
|
||||
({
|
||||
@@ -101,11 +101,9 @@ export const SelectionHeader = React.memo(
|
||||
};
|
||||
|
||||
const deleteItem = async () => {
|
||||
if (!type) return;
|
||||
presentDialog({
|
||||
title: strings.doActions.delete.unknown(type, selectedItemsList.length),
|
||||
paragraph: strings.actionConfirmations.delete.unknown(
|
||||
type,
|
||||
title: strings.doActions.delete[type](selectedItemsList.length),
|
||||
paragraph: strings.actionConfirmations.delete[type](
|
||||
selectedItemsList.length
|
||||
),
|
||||
positiveText: strings.delete(),
|
||||
@@ -220,16 +218,14 @@ export const SelectionHeader = React.memo(
|
||||
style={{
|
||||
borderRadius: 5,
|
||||
backgroundColor: contextMenuColors.primary.background,
|
||||
marginTop: 35
|
||||
marginTop: -20
|
||||
}}
|
||||
onRequestClose={() => {
|
||||
//@ts-ignore
|
||||
menuRef.current?.hide();
|
||||
}}
|
||||
anchor={
|
||||
<IconButton
|
||||
onPress={() => {
|
||||
//@ts-ignore
|
||||
menuRef.current?.show();
|
||||
}}
|
||||
name="dots-vertical"
|
||||
@@ -296,35 +292,15 @@ export const SelectionHeader = React.memo(
|
||||
{
|
||||
title: strings.moveToTrash(),
|
||||
onPress: async () => {
|
||||
const selection = useSelectionStore.getState();
|
||||
if (!selection.selectionMode) return;
|
||||
await deleteItems(
|
||||
selection.selectionMode as ItemType,
|
||||
selection.selectedItemsList
|
||||
);
|
||||
selection.clearSelection();
|
||||
selection.setSelectionMode(undefined);
|
||||
deleteItems(
|
||||
undefined,
|
||||
useSelectionStore.getState().selectionMode
|
||||
).then(() => {
|
||||
useSelectionStore.getState().clearSelection();
|
||||
useSelectionStore.getState().setSelectionMode(undefined);
|
||||
});
|
||||
},
|
||||
visible: type === "note" || type === "notebook",
|
||||
icon: "delete"
|
||||
},
|
||||
{
|
||||
title: strings.doActions.delete.unknown(
|
||||
type!,
|
||||
selectedItemsList.length
|
||||
),
|
||||
onPress: async () => {
|
||||
const selection = useSelectionStore.getState();
|
||||
if (!selection.selectionMode) return;
|
||||
await deleteItems(
|
||||
selection.selectionMode as ItemType,
|
||||
selection.selectedItemsList
|
||||
);
|
||||
selection.clearSelection();
|
||||
selection.setSelectionMode(undefined);
|
||||
},
|
||||
visible:
|
||||
type !== "trash" && type !== "note" && type !== "notebook",
|
||||
visible: type !== "trash",
|
||||
icon: "delete"
|
||||
},
|
||||
{
|
||||
@@ -343,10 +319,9 @@ export const SelectionHeader = React.memo(
|
||||
!item.visible ? null : (
|
||||
<Button
|
||||
style={{
|
||||
width: 150,
|
||||
justifyContent: "flex-start",
|
||||
borderRadius: 0,
|
||||
alignSelf: "flex-start",
|
||||
width: "100%"
|
||||
borderRadius: 0
|
||||
}}
|
||||
type="plain"
|
||||
buttonType={{
|
||||
@@ -356,7 +331,6 @@ export const SelectionHeader = React.memo(
|
||||
key={item.title}
|
||||
title={item.title}
|
||||
onPress={async () => {
|
||||
//@ts-ignore
|
||||
menuRef.current?.hide();
|
||||
if (Platform.OS === "ios") await sleep(300);
|
||||
item.onPress();
|
||||
|
||||
@@ -134,10 +134,8 @@ export const ChangeEmail = ({ close }: ChangeEmailProps) => {
|
||||
) : (
|
||||
<>
|
||||
<Input
|
||||
key="code-input"
|
||||
fwdRef={codeInputRef}
|
||||
placeholder={strings.code()}
|
||||
defaultValue=""
|
||||
placeholder={strings.verifyNewEmail()}
|
||||
onChangeText={(code) => {
|
||||
emailChangeData.current.code = code;
|
||||
}}
|
||||
|
||||
@@ -301,8 +301,8 @@ export const NotebookSheet = () => {
|
||||
}}
|
||||
onPress={async () => {
|
||||
await deleteItems(
|
||||
"notebook",
|
||||
useItemSelectionStore.getState().getSelectedItemIds()
|
||||
useItemSelectionStore.getState().getSelectedItemIds(),
|
||||
"notebook"
|
||||
);
|
||||
useSelectionStore.getState().clearSelection();
|
||||
useItemSelectionStore.setState({
|
||||
|
||||
@@ -17,17 +17,12 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { sanitizeFilename } from "@notesnook/common";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import Clipboard from "@react-native-clipboard/clipboard";
|
||||
import React, { createRef } from "react";
|
||||
import { Platform, View } from "react-native";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import FileViewer from "react-native-file-viewer";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import Share from "react-native-share";
|
||||
import { db } from "../../../common/database";
|
||||
import filesystem from "../../../common/filesystem";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent,
|
||||
@@ -35,6 +30,8 @@ import {
|
||||
} from "../../../services/event-manager";
|
||||
import { clearMessage } from "../../../services/message";
|
||||
import SettingsService from "../../../services/settings";
|
||||
import { db } from "../../../common/database";
|
||||
import Storage from "../../../common/database/storage";
|
||||
import { eOpenRecoveryKeyDialog } from "../../../utils/events";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { sleep } from "../../../utils/time";
|
||||
@@ -44,6 +41,9 @@ import Seperator from "../../ui/seperator";
|
||||
import SheetWrapper from "../../ui/sheet";
|
||||
import { QRCode } from "../../ui/svg/lazy";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import { sanitizeFilename } from "@notesnook/common";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
class RecoveryKeySheet extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -126,7 +126,7 @@ class RecoveryKeySheet extends React.Component {
|
||||
"base64"
|
||||
);
|
||||
} else {
|
||||
path = await filesystem.checkAndCreateDir("/");
|
||||
path = await Storage.checkAndCreateDir("/");
|
||||
await RNFetchBlob.fs.writeFile(path + fileName, data, "base64");
|
||||
}
|
||||
ToastManager.show({
|
||||
@@ -157,7 +157,7 @@ class RecoveryKeySheet extends React.Component {
|
||||
if (!file) return;
|
||||
path = file.uri;
|
||||
} else {
|
||||
path = await filesystem.checkAndCreateDir("/");
|
||||
path = await Storage.checkAndCreateDir("/");
|
||||
await RNFetchBlob.fs.writeFile(path + fileName, this.state.key, "utf8");
|
||||
path = path + fileName;
|
||||
}
|
||||
@@ -88,14 +88,14 @@ export const RelationsList = ({
|
||||
sortDirection: "desc"
|
||||
})
|
||||
.then((grouped) => {
|
||||
setTimeout(() => {
|
||||
setItems(grouped);
|
||||
}, 300);
|
||||
setItems(grouped);
|
||||
});
|
||||
}, [relationType, referenceType, item?.id, item?.type, updater]);
|
||||
}, [relationType, referenceType, item?.id, item?.type]);
|
||||
|
||||
return (
|
||||
<View style={{ paddingHorizontal: 12, height: "100%" }}>
|
||||
<View
|
||||
style={{ paddingHorizontal: 12, height: hasNoRelations ? 300 : "100%" }}
|
||||
>
|
||||
<SheetProvider context="local" />
|
||||
<DialogHeader
|
||||
title={title}
|
||||
|
||||
@@ -68,19 +68,19 @@ export default function ReminderNotify({
|
||||
|
||||
const QuickActions = [
|
||||
{
|
||||
title: `5 ${strings.timeShort.minute()}`,
|
||||
title: `5 ${strings.timeShort.minute}`,
|
||||
time: 5
|
||||
},
|
||||
{
|
||||
title: `15 ${strings.timeShort.minute()}`,
|
||||
title: `15 ${strings.timeShort.minute}`,
|
||||
time: 15
|
||||
},
|
||||
{
|
||||
title: `30 ${strings.timeShort.minute()}`,
|
||||
title: `30 ${strings.timeShort.minute}`,
|
||||
time: 30
|
||||
},
|
||||
{
|
||||
title: `1 ${strings.timeShort.hour()}`,
|
||||
title: `1 ${strings.timeShort.hour}`,
|
||||
time: 60
|
||||
}
|
||||
];
|
||||
|
||||
@@ -124,11 +124,9 @@ export default function ReminderSheet({
|
||||
const referencedItem = reference ? (reference as Note) : null;
|
||||
|
||||
const title = useRef<string | undefined>(
|
||||
!reminder ? referencedItem?.title : reminder?.title
|
||||
);
|
||||
const details = useRef<string | undefined>(
|
||||
!reminder ? referencedItem?.headline : reminder?.description
|
||||
reminder?.title || referencedItem?.title
|
||||
);
|
||||
const details = useRef<string | undefined>(reminder?.description);
|
||||
const titleRef = useRef<TextInput>(null);
|
||||
const timer = useRef<NodeJS.Timeout>();
|
||||
|
||||
@@ -545,7 +543,7 @@ export default function ReminderSheet({
|
||||
/>
|
||||
|
||||
{reminderMode === ReminderModes.Permanent ? null : (
|
||||
<RNScrollView
|
||||
<ScrollView
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
marginTop: 12,
|
||||
@@ -557,7 +555,7 @@ export default function ReminderSheet({
|
||||
<Button
|
||||
key={mode}
|
||||
title={strings.reminderNotificationModes[
|
||||
mode as keyof typeof ReminderNotificationModes
|
||||
mode as keyof typeof strings.reminderNotificationModes
|
||||
]()}
|
||||
style={{
|
||||
marginRight: 12,
|
||||
@@ -590,7 +588,7 @@ export default function ReminderSheet({
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</RNScrollView>
|
||||
</ScrollView>
|
||||
)}
|
||||
</ScrollView>
|
||||
|
||||
|
||||
@@ -154,9 +154,7 @@ export const Toast = ({ context = "global" }) => {
|
||||
>
|
||||
<Icon
|
||||
name={
|
||||
toastOptions.icon
|
||||
? toastOptions.icon
|
||||
: toastOptions.type === "success"
|
||||
toastOptions.type === "success"
|
||||
? "check"
|
||||
: toastOptions.type === "info"
|
||||
? "information"
|
||||
|
||||
@@ -51,6 +51,7 @@ export const ReminderTime = ({
|
||||
<Button
|
||||
title={time}
|
||||
key={reminder.id}
|
||||
height={20}
|
||||
icon="bell"
|
||||
fontSize={SIZE.xs}
|
||||
iconSize={SIZE.sm}
|
||||
@@ -66,10 +67,12 @@ export const ReminderTime = ({
|
||||
marginRight: 0
|
||||
}}
|
||||
style={{
|
||||
height: "auto",
|
||||
borderRadius: 5,
|
||||
marginRight: 5,
|
||||
borderWidth: 0.5,
|
||||
borderColor: colors.primary.border,
|
||||
paddingHorizontal: 6,
|
||||
marginBottom: 5,
|
||||
...(style as ViewStyle)
|
||||
}}
|
||||
{...props}
|
||||
|
||||
@@ -254,8 +254,8 @@ export const useActions = ({
|
||||
item.type === "color"
|
||||
) {
|
||||
presentDialog({
|
||||
title: strings.doActions.delete.unknown(item.type, 1),
|
||||
paragraph: strings.actionConfirmations.delete.unknown(item.type, 1),
|
||||
title: strings.doActions.delete[item.type](1),
|
||||
paragraph: strings.actionConfirmations.delete[item.type](1),
|
||||
positivePress: async () => {
|
||||
if (item.type === "reminder") {
|
||||
await db.reminders.remove(item.id);
|
||||
@@ -290,7 +290,7 @@ export const useActions = ({
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
await deleteItems(item.type, [item.id]);
|
||||
await deleteItems([item.id], item.type);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
@@ -303,8 +303,8 @@ export const useActions = ({
|
||||
close();
|
||||
await sleep(300);
|
||||
presentDialog({
|
||||
title: strings.doActions.delete.unknown(item.itemType, 1),
|
||||
paragraph: strings.actionConfirmations.delete.unknown(item.itemType, 1),
|
||||
title: strings.doActions.delete[item.itemType](1),
|
||||
paragraph: strings.actionConfirmations.delete[item.itemType](1),
|
||||
positiveText: strings.delete(),
|
||||
negativeText: strings.cancel(),
|
||||
positivePress: async () => {
|
||||
@@ -313,7 +313,10 @@ export const useActions = ({
|
||||
Navigation.queueRoutesForUpdate();
|
||||
useSelectionStore.getState().setSelectionMode(undefined);
|
||||
ToastManager.show({
|
||||
heading: strings.actions.deleted.unknown(item.itemType, 1),
|
||||
heading:
|
||||
strings.actions.deleted[
|
||||
item.itemType as keyof typeof strings.actions.deleted
|
||||
](1),
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
@@ -954,10 +957,11 @@ export const useActions = ({
|
||||
id: "trash",
|
||||
title:
|
||||
item.type !== "notebook" && item.type !== "note"
|
||||
? strings.doActions.delete.unknown(
|
||||
item.type === "trash" ? item.itemType : item.type,
|
||||
1
|
||||
)
|
||||
? strings.doActions.delete[
|
||||
item.type === "trash"
|
||||
? item.itemType
|
||||
: (item.type as keyof typeof strings.doActions.delete)
|
||||
](1)
|
||||
: strings.moveToTrash(),
|
||||
icon: "delete-outline",
|
||||
type: "error",
|
||||
|
||||
@@ -17,15 +17,9 @@ 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 {
|
||||
EV,
|
||||
EVENTS,
|
||||
EventManagerSubscription,
|
||||
SYNC_CHECK_IDS,
|
||||
SyncStatusEvent,
|
||||
User
|
||||
} from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { User } from "@notesnook/core";
|
||||
import { EV, EVENTS, SYNC_CHECK_IDS, SyncStatusEvent } from "@notesnook/core";
|
||||
import { EventManagerSubscription } from "@notesnook/core";
|
||||
import notifee from "@notifee/react-native";
|
||||
import NetInfo, { NetInfoSubscription } from "@react-native-community/netinfo";
|
||||
import React, { useCallback, useEffect, useRef } from "react";
|
||||
@@ -47,7 +41,6 @@ import * as RNIap from "react-native-iap";
|
||||
import { DatabaseLogger, db, setupDatabase } from "../common/database";
|
||||
import { initializeLogger } from "../common/database/logger";
|
||||
import { MMKV } from "../common/database/mmkv";
|
||||
import { endProgress, startProgress } from "../components/dialogs/progress";
|
||||
import Migrate from "../components/sheets/migrate";
|
||||
import NewFeature from "../components/sheets/new-feature";
|
||||
import { Walkthrough } from "../components/walkthroughs";
|
||||
@@ -59,7 +52,6 @@ import {
|
||||
} from "../screens/editor/tiptap/utils";
|
||||
import { useDragState } from "../screens/settings/editor/state";
|
||||
import BackupService from "../services/backup";
|
||||
import BiometricService from "../services/biometrics";
|
||||
import {
|
||||
ToastManager,
|
||||
eSendEvent,
|
||||
@@ -74,17 +66,14 @@ import {
|
||||
setRecoveryKeyMessage,
|
||||
setUpdateAvailableMessage
|
||||
} from "../services/message";
|
||||
import Navigation from "../services/navigation";
|
||||
import Notifications from "../services/notifications";
|
||||
import PremiumService from "../services/premium";
|
||||
import SettingsService from "../services/settings";
|
||||
import Sync from "../services/sync";
|
||||
import { clearAllStores, initAfterSync } from "../stores";
|
||||
import { refreshAllStores } from "../stores/create-db-collection-store";
|
||||
import { initAfterSync } from "../stores";
|
||||
import { useAttachmentStore } from "../stores/use-attachment-store";
|
||||
import { useMessageStore } from "../stores/use-message-store";
|
||||
import { useSettingStore } from "../stores/use-setting-store";
|
||||
import { changeSystemBarColors } from "../stores/use-theme-store";
|
||||
import { SyncStatus, useUserStore } from "../stores/use-user-store";
|
||||
import { updateStatusBarColor } from "../utils/colors";
|
||||
import { BETA } from "../utils/constants";
|
||||
@@ -100,8 +89,10 @@ import {
|
||||
} from "../utils/events";
|
||||
import { getGithubVersion } from "../utils/github-version";
|
||||
import { tabBarRef } from "../utils/global-refs";
|
||||
import { NotesnookModule } from "../utils/notesnook-module";
|
||||
import { sleep } from "../utils/time";
|
||||
import { NotesnookModule } from "../utils/notesnook-module";
|
||||
import { changeSystemBarColors } from "../stores/use-theme-store";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
const onCheckSyncStatus = async (type: SyncStatusEvent) => {
|
||||
const { disableSync, disableAutoSync } = SettingsService.get();
|
||||
@@ -148,7 +139,6 @@ const onUploadedAttachmentProgress = (data: any) => {
|
||||
};
|
||||
|
||||
const onUserSessionExpired = async () => {
|
||||
console.log("LOGGED OUT USER....");
|
||||
SettingsService.set({
|
||||
sessionExpired: true
|
||||
});
|
||||
@@ -217,19 +207,11 @@ const onRequestPartialSync = async (
|
||||
};
|
||||
|
||||
const onLogout = async (reason: string) => {
|
||||
DatabaseLogger.log("User Logged Out " + reason);
|
||||
setLoginMessage();
|
||||
await PremiumService.setPremiumStatus();
|
||||
await BiometricService.resetCredentials();
|
||||
MMKV.clearStore();
|
||||
clearAllStores();
|
||||
setImmediate(() => {
|
||||
refreshAllStores();
|
||||
DatabaseLogger.log("User Logged Out" + reason);
|
||||
Notifications.setupReminders(true);
|
||||
SettingsService.set({
|
||||
introCompleted: true
|
||||
});
|
||||
Navigation.queueRoutesForUpdate();
|
||||
SettingsService.resetSettings();
|
||||
useUserStore.getState().setUser(null);
|
||||
useUserStore.getState().setSyncing(false);
|
||||
};
|
||||
|
||||
async function checkForShareExtensionLaunchedInBackground() {
|
||||
@@ -595,29 +577,6 @@ export const useAppEvents = () => {
|
||||
EV.subscribe(EVENTS.uploadCanceled, (data) => {
|
||||
useAttachmentStore.getState().setUploading(data);
|
||||
}),
|
||||
EV.subscribe(EVENTS.migrationStarted, (name) => {
|
||||
if (
|
||||
name !== "notesnook" ||
|
||||
!SettingsService.getProperty("introCompleted") ||
|
||||
Config.isTesting === "true"
|
||||
)
|
||||
return;
|
||||
startProgress({
|
||||
title: "Migrating Data",
|
||||
paragraph: "Please wait while we migrate your data",
|
||||
canHideProgress: false,
|
||||
fillBackground: true
|
||||
});
|
||||
}),
|
||||
EV.subscribe(EVENTS.migrationFinished, (name) => {
|
||||
if (
|
||||
name !== "notesnook" ||
|
||||
!SettingsService.getProperty("introCompleted") ||
|
||||
Config.isTesting === "true"
|
||||
)
|
||||
return;
|
||||
endProgress();
|
||||
}),
|
||||
EV.subscribe(EVENTS.vaultLocked, async () => {
|
||||
// Lock all notes in all tabs...
|
||||
for (const tab of useTabStore.getState().tabs) {
|
||||
|
||||
@@ -97,7 +97,7 @@ const _TabsHolder = () => {
|
||||
tabBarRef.current?.goToPage(1, false);
|
||||
return;
|
||||
}
|
||||
if (item && item.type === "notesnook.action.newnote") {
|
||||
if (item.type === "notesnook.action.newnote") {
|
||||
clearAppState();
|
||||
if (!tabBarRef.current) {
|
||||
await sleep(3000);
|
||||
|
||||
@@ -17,11 +17,13 @@
|
||||
"phone": "^3.1.14",
|
||||
"qclone": "^1.2.0",
|
||||
"react-native-actions-sheet": "0.9.7",
|
||||
"react-native-check-version": "https://github.com/flexible-agency/react-native-check-version",
|
||||
"react-native-drax": "^0.10.2",
|
||||
"react-native-image-zoom-viewer": "^3.0.1",
|
||||
"react-native-keyboard-aware-scroll-view": "^0.9.5",
|
||||
"react-native-progress": "^5.0.0",
|
||||
"react-native-qrcode-svg": "^6.0.6",
|
||||
"react-native-reanimated-material-menu": "github:ammarahm-ed/react-native-reanimated-material-menu",
|
||||
"react-native-reanimated-progress-bar": "1.0.1",
|
||||
"react-native-swiper-flatlist": "3.2.2",
|
||||
"timeago.js": "4.0.2",
|
||||
@@ -32,14 +34,17 @@
|
||||
"katex": "0.16.2",
|
||||
"@readme/data-urls": "3.0.0",
|
||||
"react-native-wheel-color-picker": "^1.3.1",
|
||||
"tinycolor2": "1.6.0",
|
||||
"@azure/core-asynciterator-polyfill": "^1.0.2",
|
||||
"@tanstack/react-query": "^4.36.1",
|
||||
"@trpc/client": "10.45.2",
|
||||
"@trpc/react-query": "10.45.2",
|
||||
"@trpc/server": "10.45.2",
|
||||
"@streetwriters/kysely": "^0.27.4",
|
||||
"pathe": "1.1.2",
|
||||
"react-native-format-currency": "0.0.5",
|
||||
"@lingui/react": "4.11.2",
|
||||
"@lingui/core": "4.11.2",
|
||||
"react-native-check-version": "^1.3.0",
|
||||
"react-native-material-menu": "^2.0.0"
|
||||
"@lingui/core": "4.11.2"
|
||||
},
|
||||
"sideEffects": false
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ import { EditorEvents } from "./tiptap/utils";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||
import { db } from "../../common/database";
|
||||
import { i18n } from "@lingui/core";
|
||||
|
||||
const onShouldStartLoadWithRequest = (request: ShouldStartLoadRequest) => {
|
||||
if (request.url.includes("https")) {
|
||||
@@ -144,17 +143,10 @@ export function ReadonlyEditor(props: {
|
||||
ref={editorRef}
|
||||
key={"readonly-editor:" + props.editorId}
|
||||
nestedScrollEnabled
|
||||
injectedJavaScript={`
|
||||
globalThis.__DEV__ = ${__DEV__}
|
||||
globalThis.readonlyEditor=true;
|
||||
globalThis.LINGUI_LOCALE = "${i18n.locale}";
|
||||
globalThis.LINGUI_LOCALE_DATA = ${JSON.stringify({
|
||||
[i18n.locale]: i18n.messages
|
||||
})};
|
||||
globalThis.loadApp();`}
|
||||
injectedJavaScriptBeforeContentLoaded={`globalThis.readonlyEditor=true;`}
|
||||
injectedJavaScript="globalThis.readonlyEditor=true;"
|
||||
useSharedProcessPool={false}
|
||||
javaScriptEnabled={true}
|
||||
webviewDebuggingEnabled={__DEV__}
|
||||
focusable={true}
|
||||
setSupportMultipleWindows={false}
|
||||
overScrollMode="never"
|
||||
|
||||
@@ -37,7 +37,6 @@ export type EditorState = {
|
||||
scrollPosition: number;
|
||||
overlay?: boolean;
|
||||
initialLoadCalled?: boolean;
|
||||
editorStateRestored?: boolean;
|
||||
};
|
||||
|
||||
export type Settings = {
|
||||
|
||||
@@ -21,7 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
import { ItemReference } from "@notesnook/core";
|
||||
import type { Attachment } from "@notesnook/editor";
|
||||
import { getDefaultPresets } from "@notesnook/editor/dist/cjs/toolbar/tool-definitions";
|
||||
import { getDefaultPresets } from "@notesnook/editor/dist/toolbar/tool-definitions";
|
||||
import Clipboard from "@react-native-clipboard/clipboard";
|
||||
import React, { useCallback, useEffect, useRef } from "react";
|
||||
import {
|
||||
|
||||
@@ -861,13 +861,9 @@ export const useEditor = (
|
||||
state.current.currentlyEditing = true;
|
||||
state.current.movedAway = false;
|
||||
|
||||
if (!state.current.editorStateRestored) {
|
||||
state.current.isRestoringState = true;
|
||||
if (!DDS.isTab) {
|
||||
tabBarRef.current?.goToPage(1, false);
|
||||
}
|
||||
if (!DDS.isTab) {
|
||||
tabBarRef.current?.goToPage(1, false);
|
||||
}
|
||||
|
||||
clearAppState();
|
||||
state.current.isRestoringState = false;
|
||||
}, []);
|
||||
|
||||
@@ -52,7 +52,7 @@ export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
|
||||
<SelectionHeader id={route.name} items={notes} type="note" />
|
||||
<Header
|
||||
renderedInRoute={route.name}
|
||||
title={strings.routes[route.name]()}
|
||||
title={strings.routes[route.name as keyof typeof strings.routes]()}
|
||||
canGoBack={false}
|
||||
hasSearch={true}
|
||||
onSearch={() => {
|
||||
@@ -72,11 +72,13 @@ export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
|
||||
dataType="note"
|
||||
renderedInRoute={route.name}
|
||||
loading={loading || !isFocused}
|
||||
headerTitle={strings.routes[route.name]()}
|
||||
headerTitle={strings.routes[
|
||||
route.name as keyof typeof strings.routes
|
||||
]?.()}
|
||||
placeholder={{
|
||||
title: route.name?.toLowerCase(),
|
||||
paragraph: strings.notesEmpty(),
|
||||
button: strings.createNewNote(),
|
||||
button: strings.newNote(),
|
||||
action: openEditor,
|
||||
loading: strings.loadingNotes()
|
||||
}}
|
||||
|
||||
@@ -196,7 +196,8 @@ const NotesPage = ({
|
||||
<Header
|
||||
renderedInRoute={route.name}
|
||||
title={
|
||||
route.name === "Monographs" ? strings.routes[route.name]() : title
|
||||
title ||
|
||||
strings.routes[route.name as unknown as keyof typeof strings.routes]()
|
||||
}
|
||||
canGoBack={params?.current?.canGoBack}
|
||||
hasSearch={true}
|
||||
|
||||
@@ -91,6 +91,7 @@ export const Reminders = ({
|
||||
/>
|
||||
|
||||
<FloatingButton
|
||||
title={strings.setReminder()}
|
||||
onPress={() => {
|
||||
ReminderSheet.present();
|
||||
}}
|
||||
|
||||
@@ -17,9 +17,6 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { sanitizeFilename } from "@notesnook/common";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useThemeColors, VariantsWithStaticColors } from "@notesnook/theme";
|
||||
import Clipboard from "@react-native-clipboard/clipboard";
|
||||
import React, {
|
||||
Dispatch,
|
||||
@@ -29,11 +26,11 @@ import React, {
|
||||
useState
|
||||
} from "react";
|
||||
import { ActivityIndicator, Linking, Platform, View } from "react-native";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import { FlatList } from "react-native-gesture-handler";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import { db } from "../../common/database";
|
||||
import filesystem from "../../common/filesystem";
|
||||
import Storage from "../../common/database/storage";
|
||||
import DialogHeader from "../../components/dialog/dialog-header";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { IconButton } from "../../components/ui/icon-button";
|
||||
@@ -49,10 +46,13 @@ import {
|
||||
presentSheet,
|
||||
ToastManager
|
||||
} from "../../services/event-manager";
|
||||
import { useThemeColors, VariantsWithStaticColors } from "@notesnook/theme";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { eCloseSheet } from "../../utils/events";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
import { sanitizeFilename } from "@notesnook/common";
|
||||
import { strings } from "@notesnook/intl";
|
||||
const mfaMethods: MFAMethod[] = [
|
||||
{
|
||||
id: "app",
|
||||
@@ -525,7 +525,7 @@ export const MFARecoveryCodes = ({
|
||||
if (!file) return;
|
||||
path = file.uri;
|
||||
} else {
|
||||
path = await filesystem.checkAndCreateDir("/");
|
||||
path = await Storage.checkAndCreateDir("/");
|
||||
await RNFetchBlob.fs.writeFile(
|
||||
path + fileName,
|
||||
codeString,
|
||||
|
||||
@@ -29,7 +29,6 @@ import { useAttachmentProgress } from "../../hooks/use-attachment-progress";
|
||||
import { useDBItem } from "../../hooks/use-db-item";
|
||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
export const AttachmentGroupProgress = (props: { groupId?: string }) => {
|
||||
const { colors } = useThemeColors();
|
||||
@@ -91,12 +90,11 @@ export const AttachmentGroupProgress = (props: { groupId?: string }) => {
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{strings.downloading()} {file?.filename}{" "}
|
||||
{formatBytes(file?.size || 0)}{" "}
|
||||
Downloading {file?.filename} {formatBytes(file?.size || 0)}{" "}
|
||||
{fileProgress?.percent ? `(${fileProgress.percent})` : ""}
|
||||
</Paragraph>
|
||||
<Paragraph size={10} color={colors.secondary.paragraph}>
|
||||
{strings.group()}: {props.groupId}
|
||||
Group: {props.groupId}
|
||||
</Paragraph>
|
||||
</View>
|
||||
{props.groupId === "offline-mode" ? null : (
|
||||
|
||||
@@ -17,24 +17,24 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { sanitizeFilename } from "@notesnook/common";
|
||||
import { format, LogLevel, logManager } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { LogMessage } from "@notesnook/logger";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import Clipboard from "@react-native-clipboard/clipboard";
|
||||
import { LogMessage } from "@notesnook/logger";
|
||||
import { format, LogLevel, logManager } from "@notesnook/core";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { FlatList, Platform, TouchableOpacity, View } from "react-native";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import filesystem from "../../common/filesystem";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import Storage from "../../common/database/storage";
|
||||
import { presentDialog } from "../../components/dialog/functions";
|
||||
import { IconButton } from "../../components/ui/icon-button";
|
||||
import { Notice } from "../../components/ui/notice";
|
||||
import Paragraph from "../../components/ui/typography/paragraph";
|
||||
import useTimer from "../../hooks/use-timer";
|
||||
import { ToastManager } from "../../services/event-manager";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { hexToRGBA } from "../../utils/colors";
|
||||
import { sanitizeFilename } from "@notesnook/common";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
export default function DebugLogs() {
|
||||
const { colors } = useThemeColors();
|
||||
@@ -148,7 +148,7 @@ export default function DebugLogs() {
|
||||
if (!file) return;
|
||||
path = file.uri;
|
||||
} else {
|
||||
path = await filesystem.checkAndCreateDir("/");
|
||||
path = await Storage.checkAndCreateDir("/");
|
||||
await RNFetchBlob.fs.writeFile(path + fileName + ".txt", data, "utf8");
|
||||
path = path + fileName;
|
||||
}
|
||||
|
||||
@@ -17,18 +17,18 @@ 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 { Icons } from "@notesnook/editor/dist/cjs/toolbar/icons";
|
||||
import { Icons } from "@notesnook/editor/dist/toolbar/icons";
|
||||
import { ToolbarGroupDefinition } from "@notesnook/editor";
|
||||
import {
|
||||
getAllTools,
|
||||
getDefaultPresets
|
||||
} from "@notesnook/editor/dist/cjs/toolbar/tool-definitions";
|
||||
} from "@notesnook/editor/dist/toolbar/tool-definitions";
|
||||
import { ToolId } from "@notesnook/editor";
|
||||
|
||||
export const tools = getAllTools() as any;
|
||||
export const tools = getAllTools();
|
||||
export const presets: { [name: string]: ToolbarGroupDefinition[] } = {
|
||||
default: getDefaultPresets().default as any,
|
||||
minimal: getDefaultPresets().minimal as any,
|
||||
default: getDefaultPresets().default,
|
||||
minimal: getDefaultPresets().minimal,
|
||||
custom: []
|
||||
};
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ const Home = ({
|
||||
});
|
||||
|
||||
const renderItem = ({ item }: { item: SettingSection; index: number }) =>
|
||||
item.id === "account" ? (
|
||||
item.name === "account" ? (
|
||||
<SettingsUserSection item={item} />
|
||||
) : (
|
||||
<SectionGroup item={item} />
|
||||
|
||||
@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { Menu, MenuItem } from "react-native-material-menu";
|
||||
import Menu, { MenuItem } from "react-native-reanimated-material-menu";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { Dialog } from "../../../components/dialog";
|
||||
import { Pressable } from "../../../components/ui/pressable";
|
||||
@@ -146,7 +146,7 @@ export function SettingsPicker<T>({
|
||||
onChange(item);
|
||||
}
|
||||
}}
|
||||
pressColor={colors.primary.hover}
|
||||
underlayColor={colors.primary.hover}
|
||||
style={{
|
||||
backgroundColor: compareValue(currentValue, item)
|
||||
? colors.selected.background
|
||||
|
||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { DATE_FORMATS, TIME_FORMATS } from "@notesnook/core";
|
||||
import { getFontById, getFonts } from "@notesnook/editor/dist/cjs/utils/font";
|
||||
import { getFontById, getFonts } from "@notesnook/editor/dist/utils/font";
|
||||
import dayjs from "dayjs";
|
||||
import { createSettingsPicker } from ".";
|
||||
import { db } from "../../../common/database";
|
||||
@@ -57,7 +57,7 @@ export const HomePicker = createSettingsPicker({
|
||||
});
|
||||
},
|
||||
formatValue: (item) => {
|
||||
return strings.routes[typeof item === "object" ? item.name : item]?.();
|
||||
return strings.routes[typeof item === "object" ? item.name : item]();
|
||||
},
|
||||
getItemKey: (item) => item.name,
|
||||
options: MenuItemsList.slice(0, MenuItemsList.length - 1),
|
||||
|
||||
@@ -28,7 +28,7 @@ import DocumentPicker from "react-native-document-picker";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import { unzip } from "react-native-zip-archive";
|
||||
import { DatabaseLogger, db } from "../../../common/database";
|
||||
import filesystem from "../../../common/filesystem";
|
||||
import storage from "../../../common/database/storage";
|
||||
import { deleteCacheFileByName } from "../../../common/filesystem/io";
|
||||
import { cacheDir, copyFileAsync } from "../../../common/filesystem/utils";
|
||||
import { presentDialog } from "../../../components/dialog/functions";
|
||||
@@ -300,7 +300,7 @@ export const RestoreBackup = () => {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const path = await filesystem.checkAndCreateDir("/backups/");
|
||||
const path = await storage.checkAndCreateDir("/backups/");
|
||||
files = await RNFetchBlob.fs.lstat(path);
|
||||
}
|
||||
files = files
|
||||
|
||||
@@ -203,15 +203,19 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
|
||||
<Input
|
||||
{...item.inputProperties}
|
||||
onSubmit={(e) => {
|
||||
SettingsService.set({
|
||||
[item.property as string]: e.nativeEvent.text
|
||||
});
|
||||
if (e.nativeEvent.text) {
|
||||
SettingsService.set({
|
||||
[item.property as string]: e.nativeEvent.text
|
||||
});
|
||||
}
|
||||
item.inputProperties?.onSubmitEditing?.(e);
|
||||
}}
|
||||
onChangeText={(text) => {
|
||||
SettingsService.set({
|
||||
[item.property as string]: text
|
||||
});
|
||||
if (text) {
|
||||
SettingsService.set({
|
||||
[item.property as string]: text
|
||||
});
|
||||
}
|
||||
item.inputProperties?.onSubmitEditing?.(text as any);
|
||||
}}
|
||||
containerStyle={{ marginTop: 12 }}
|
||||
|
||||
@@ -18,10 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { formatBytes } from "@notesnook/common";
|
||||
import { User } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import notifee from "@notifee/react-native";
|
||||
import Clipboard from "@react-native-clipboard/clipboard";
|
||||
import dayjs from "dayjs";
|
||||
import React from "react";
|
||||
import { Appearance, Linking, Platform } from "react-native";
|
||||
@@ -30,6 +28,7 @@ import * as RNIap from "react-native-iap";
|
||||
import { enabled } from "react-native-privacy-snapshot";
|
||||
import ScreenGuardModule from "react-native-screenguard";
|
||||
import { DatabaseLogger, db } from "../../common/database";
|
||||
import { MMKV } from "../../common/database/mmkv";
|
||||
import filesystem from "../../common/filesystem";
|
||||
import { ChangePassword } from "../../components/auth/change-password";
|
||||
import { presentDialog } from "../../components/dialog/functions";
|
||||
@@ -55,11 +54,14 @@ import {
|
||||
openVault,
|
||||
presentSheet
|
||||
} from "../../services/event-manager";
|
||||
import { setLoginMessage } from "../../services/message";
|
||||
import Navigation from "../../services/navigation";
|
||||
import Notifications from "../../services/notifications";
|
||||
import PremiumService from "../../services/premium";
|
||||
import SettingsService from "../../services/settings";
|
||||
import Sync from "../../services/sync";
|
||||
import { clearAllStores } from "../../stores";
|
||||
import { refreshAllStores } from "../../stores/create-db-collection-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { SUBSCRIPTION_STATUS } from "../../utils/constants";
|
||||
@@ -76,6 +78,8 @@ import { verifyUser, verifyUserWithApplock } from "./functions";
|
||||
import { SettingSection } from "./types";
|
||||
import { getTimeLeft } from "./user-section";
|
||||
|
||||
type User = any;
|
||||
|
||||
export const settingsGroups: SettingSection[] = [
|
||||
{
|
||||
id: "account",
|
||||
@@ -114,10 +118,6 @@ export const settingsGroups: SettingSection[] = [
|
||||
"MMMM D, YYYY"
|
||||
);
|
||||
|
||||
if (user.subscription.provider === 4) {
|
||||
return strings.subEndsOn(expiryDate);
|
||||
}
|
||||
|
||||
return user.subscription?.type === 2
|
||||
? strings.signedUpOn(startDate)
|
||||
: user.subscription?.type === 1
|
||||
@@ -133,39 +133,6 @@ export const settingsGroups: SettingSection[] = [
|
||||
: strings.neverHesitate();
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "redeem-gift-code",
|
||||
name: strings.redeemGiftCode(),
|
||||
description: strings.redeemGiftCodeDesc(),
|
||||
hidden: (current) => {
|
||||
return !current as boolean;
|
||||
},
|
||||
useHook: () =>
|
||||
useUserStore(
|
||||
(state) =>
|
||||
state.user?.subscription.type == SUBSCRIPTION_STATUS.TRIAL ||
|
||||
state.user?.subscription.type == SUBSCRIPTION_STATUS.BASIC
|
||||
),
|
||||
icon: "gift",
|
||||
modifer: () => {
|
||||
presentDialog({
|
||||
title: strings.redeemGiftCode(),
|
||||
paragraph: strings.redeemGiftCodeDesc(),
|
||||
input: true,
|
||||
inputPlaceholder: strings.code(),
|
||||
positiveText: strings.redeem(),
|
||||
positivePress: async (value) => {
|
||||
db.subscriptions.redeemCode(value).catch((e) => {
|
||||
ToastManager.show({
|
||||
heading: "Error redeeming code",
|
||||
message: (e as Error).message,
|
||||
type: "error"
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "account-settings",
|
||||
type: "screen",
|
||||
@@ -479,6 +446,18 @@ export const settingsGroups: SettingSection[] = [
|
||||
});
|
||||
|
||||
await db.user?.logout();
|
||||
setLoginMessage();
|
||||
await PremiumService.setPremiumStatus();
|
||||
await BiometricService.resetCredentials();
|
||||
MMKV.clearStore();
|
||||
clearAllStores();
|
||||
setImmediate(() => {
|
||||
refreshAllStores();
|
||||
});
|
||||
Navigation.queueRoutesForUpdate();
|
||||
SettingsService.resetSettings();
|
||||
useUserStore.getState().setUser(null);
|
||||
useUserStore.getState().setSyncing(false);
|
||||
endProgress();
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e);
|
||||
@@ -1186,20 +1165,13 @@ export const settingsGroups: SettingSection[] = [
|
||||
type: "component",
|
||||
hidden: () => !useUserStore.getState().user,
|
||||
name: strings.automaticBackupsWithAttachments(),
|
||||
description: [
|
||||
...strings.automaticBackupsWithAttachmentsDesc()
|
||||
].join("\n"),
|
||||
description: strings.automaticBackupsWithAttachmentsDesc(),
|
||||
component: "autobackupsattachments"
|
||||
},
|
||||
{
|
||||
id: "select-backup-dir",
|
||||
name: strings.selectBackupDir(),
|
||||
description: () => {
|
||||
const desc = strings.selectBackupDirDesc(
|
||||
SettingsService.get().backupDirectoryAndroid?.path || ""
|
||||
);
|
||||
return desc[0] + " " + desc[1];
|
||||
},
|
||||
description: strings.selectBackupDirDesc(),
|
||||
icon: "folder",
|
||||
hidden: () =>
|
||||
!!SettingsService.get().backupDirectoryAndroid ||
|
||||
@@ -1411,15 +1383,7 @@ export const settingsGroups: SettingSection[] = [
|
||||
name: strings.emailSupport(),
|
||||
icon: "mail",
|
||||
modifer: () => {
|
||||
Clipboard.setString("support@streetwriters.co");
|
||||
ToastManager.show({
|
||||
heading: strings.emailCopied(),
|
||||
type: "success",
|
||||
icon: "content-copy"
|
||||
});
|
||||
setTimeout(() => {
|
||||
Linking.openURL("mailto:support@streetwriters.co");
|
||||
}, 1000);
|
||||
Linking.openURL("mailto:support@streetwriters.co");
|
||||
},
|
||||
description: strings.emailSupportDesc()
|
||||
},
|
||||
|
||||
@@ -126,19 +126,18 @@ export const Subscription = () => {
|
||||
user.subscription?.type !== SUBSCRIPTION_STATUS.PREMIUM_EXPIRED &&
|
||||
user.subscription?.type !== SUBSCRIPTION_STATUS.BASIC ? (
|
||||
<Button
|
||||
title={subscriptionProviderInfo?.title()}
|
||||
title={subscriptionProviderInfo?.title}
|
||||
onPress={() => {
|
||||
presentSheet({
|
||||
title: subscriptionProviderInfo.title(),
|
||||
paragraph: subscriptionProviderInfo.desc()
|
||||
title: subscriptionProviderInfo.title,
|
||||
paragraph: subscriptionProviderInfo.desc
|
||||
});
|
||||
}}
|
||||
style={{
|
||||
alignSelf: "flex-start",
|
||||
width: "100%",
|
||||
paddingHorizontal: 0
|
||||
borderRadius: 100
|
||||
}}
|
||||
fontSize={SIZE.xs}
|
||||
fontSize={SIZE.sm}
|
||||
height={30}
|
||||
type="secondaryAccented"
|
||||
/>
|
||||
|
||||
@@ -30,12 +30,24 @@ import SettingsService from "./settings";
|
||||
|
||||
async function doInBackground(callback: () => Promise<void>) {
|
||||
if (Platform.OS === "ios") {
|
||||
const bgTaskId = await beginBackgroundTask();
|
||||
const result = await callback();
|
||||
await endBackgroundTask(bgTaskId);
|
||||
return result;
|
||||
try {
|
||||
const bgTaskId = await beginBackgroundTask();
|
||||
const result = await callback();
|
||||
await endBackgroundTask(bgTaskId);
|
||||
return result;
|
||||
} catch (e) {
|
||||
return (e as Error).message;
|
||||
}
|
||||
} else {
|
||||
return await callback();
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
return new Promise(async (resolve) => {
|
||||
try {
|
||||
const result = await callback();
|
||||
resolve(result);
|
||||
} catch (e) {
|
||||
resolve((e as Error).message);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { sanitizeFilename } from "@notesnook/common";
|
||||
import { formatDate } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { Platform } from "react-native";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import FileViewer from "react-native-file-viewer";
|
||||
@@ -27,14 +26,20 @@ import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import Share from "react-native-share";
|
||||
import { zip } from "react-native-zip-archive";
|
||||
import { DatabaseLogger, db } from "../common/database";
|
||||
import filesystem, { FileStorage } from "../common/filesystem";
|
||||
import storage from "../common/database/storage";
|
||||
import filesystem from "../common/filesystem";
|
||||
import { cacheDir, copyFileAsync } from "../common/filesystem/utils";
|
||||
import { presentDialog } from "../components/dialog/functions";
|
||||
import { endProgress, updateProgress } from "../components/dialogs/progress";
|
||||
import {
|
||||
endProgress,
|
||||
startProgress,
|
||||
updateProgress
|
||||
} from "../components/dialogs/progress";
|
||||
import { eCloseSheet } from "../utils/events";
|
||||
import { sleep } from "../utils/time";
|
||||
import { ToastManager, eSendEvent, presentSheet } from "./event-manager";
|
||||
import SettingsService from "./settings";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
const MS_DAY = 86400000;
|
||||
const MS_WEEK = MS_DAY * 7;
|
||||
@@ -85,13 +90,9 @@ async function checkBackupDirExists(reset = false, context = "global") {
|
||||
resolve(await getDirectoryAndroid());
|
||||
return;
|
||||
}
|
||||
const desc = strings.selectBackupDirDesc(
|
||||
SettingsService.get().backupDirectoryAndroid?.path || ""
|
||||
);
|
||||
|
||||
presentDialog({
|
||||
title: strings.selectBackupDir(),
|
||||
paragraph: desc[0] + " " + desc,
|
||||
paragraph: strings.selectBackupDirDesc(),
|
||||
positivePress: async () => {
|
||||
resolve(await getDirectoryAndroid());
|
||||
},
|
||||
@@ -177,7 +178,7 @@ async function run(
|
||||
let path;
|
||||
|
||||
if (Platform.OS === "ios") {
|
||||
path = await filesystem.checkAndCreateDir("/backups");
|
||||
path = await storage.checkAndCreateDir("/backups");
|
||||
}
|
||||
|
||||
const backupFileName = sanitizeFilename(
|
||||
@@ -231,7 +232,7 @@ async function run(
|
||||
updateProgress({
|
||||
progress: `Saving attachments in backup... ${file.hash}`
|
||||
});
|
||||
if (await FileStorage.exists(file.hash)) {
|
||||
if (await filesystem.exists(file.hash)) {
|
||||
await RNFetchBlob.fs.cp(
|
||||
`${cacheDir}/${file.hash}`,
|
||||
`${attachmentsDir}/${file.hash}`
|
||||
@@ -298,7 +299,7 @@ async function run(
|
||||
path: path
|
||||
};
|
||||
} catch (e) {
|
||||
ToastManager.error(e as Error, strings.backupFailed(), context || "global");
|
||||
ToastManager.error(e, strings.backupFailed(), context || "global");
|
||||
|
||||
if (
|
||||
(e as Error)?.message?.includes("android.net.Uri") &&
|
||||
|
||||
@@ -20,10 +20,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { Platform } from "react-native";
|
||||
import FingerprintScanner, {
|
||||
AuthenticateIOS
|
||||
} from "@ammarahmed/react-native-fingerprint-scanner";
|
||||
} from "react-native-fingerprint-scanner";
|
||||
import * as Keychain from "react-native-keychain";
|
||||
import { MMKV } from "../common/database/mmkv";
|
||||
import { Storage } from "../common/database/storage";
|
||||
import Storage from "../common/database/storage";
|
||||
import { useSettingStore } from "../stores/use-setting-store";
|
||||
import { ToastOptions, ToastManager } from "./event-manager";
|
||||
import { useUserStore } from "../stores/use-user-store";
|
||||
|
||||
@@ -123,7 +123,6 @@ export type PresentSheetOptions = {
|
||||
};
|
||||
|
||||
export function presentSheet(data: Partial<PresentSheetOptions>) {
|
||||
console.log("PRESENTING...");
|
||||
eSendEvent(eOpenSheet, data);
|
||||
}
|
||||
|
||||
@@ -149,31 +148,28 @@ export const ToastManager = {
|
||||
type = "error",
|
||||
context = "global",
|
||||
func,
|
||||
actionText,
|
||||
icon,
|
||||
duration = 3000
|
||||
actionText
|
||||
}: ToastOptions) => {
|
||||
if (Config.isTesting) return;
|
||||
eSendEvent(eShowToast, {
|
||||
heading,
|
||||
message,
|
||||
type,
|
||||
context,
|
||||
func,
|
||||
actionText,
|
||||
icon,
|
||||
duration
|
||||
heading: heading,
|
||||
message: message,
|
||||
type: type,
|
||||
context: context,
|
||||
duration: 3000,
|
||||
func: func,
|
||||
actionText: actionText
|
||||
});
|
||||
},
|
||||
hide: () => eSendEvent(eHideToast),
|
||||
error: (e: Error, title?: string, context?: any, duration = 6000) => {
|
||||
error: (e: Error, title?: string, context?: any, duration = 5000) => {
|
||||
ToastManager.show({
|
||||
heading: title,
|
||||
message: e?.message || "",
|
||||
type: "error",
|
||||
context: context || "global",
|
||||
actionText: "Copy logs",
|
||||
duration: duration,
|
||||
duration: 6000,
|
||||
func: () => {
|
||||
Clipboard.setString(e?.stack || "");
|
||||
ToastManager.show({
|
||||
|
||||
@@ -23,6 +23,7 @@ import RNHTMLtoPDF from "react-native-html-to-pdf-lite";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import { zip } from "react-native-zip-archive";
|
||||
import { DatabaseLogger } from "../common/database/index";
|
||||
import Storage from "../common/database/storage";
|
||||
|
||||
import {
|
||||
exportNote as _exportNote,
|
||||
@@ -30,13 +31,13 @@ import {
|
||||
ExportableNote,
|
||||
exportNotes
|
||||
} from "@notesnook/common";
|
||||
import { FilteredSelector, Note } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { basename, dirname, extname, join } from "pathe";
|
||||
import filesystem from "../common/filesystem";
|
||||
import { Note } from "@notesnook/core";
|
||||
import { FilteredSelector } from "@notesnook/core";
|
||||
import { basename, dirname, join, extname } from "pathe";
|
||||
import downloadAttachment from "../common/filesystem/download-attachment";
|
||||
import { cacheDir } from "../common/filesystem/utils";
|
||||
import { unlockVault } from "../utils/unlock-vault";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
const FolderNames: { [name: string]: string } = {
|
||||
txt: "Text",
|
||||
@@ -48,7 +49,7 @@ const FolderNames: { [name: string]: string } = {
|
||||
async function getPath(type: string) {
|
||||
let path =
|
||||
Platform.OS === "ios" &&
|
||||
(await filesystem.checkAndCreateDir(`/exported/${type}/`));
|
||||
(await Storage.checkAndCreateDir(`/exported/${type}/`));
|
||||
|
||||
if (Platform.OS === "android") {
|
||||
const file = await ScopedStorage.openDocumentTree(true);
|
||||
|
||||
@@ -32,7 +32,7 @@ export const STORE_LINK =
|
||||
|
||||
export const GROUP = {
|
||||
default: "default",
|
||||
none: "none",
|
||||
None: "none",
|
||||
abc: "abc",
|
||||
year: "year",
|
||||
week: "week",
|
||||
|
||||
@@ -17,8 +17,6 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ItemType } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { Linking } from "react-native";
|
||||
import { db } from "../common/database";
|
||||
import { presentDialog } from "../components/dialog/functions";
|
||||
@@ -26,18 +24,16 @@ import { eSendEvent, ToastManager } from "../services/event-manager";
|
||||
import Navigation from "../services/navigation";
|
||||
import { useMenuStore } from "../stores/use-menu-store";
|
||||
import { useRelationStore } from "../stores/use-relation-store";
|
||||
import { useTagStore } from "../stores/use-tag-store";
|
||||
import { useSelectionStore } from "../stores/use-selection-store";
|
||||
import { eOnNotebookUpdated, eUpdateNoteInEditor } from "./events";
|
||||
import { getParentNotebookId } from "./notebooks";
|
||||
import { useTagStore } from "../stores/use-tag-store";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
function confirmDeleteAllNotes(
|
||||
items: string[],
|
||||
type: "notebook",
|
||||
context?: string
|
||||
) {
|
||||
return new Promise<{ delete: boolean; deleteNotes: boolean }>((resolve) => {
|
||||
function confirmDeleteAllNotes(items, type, context) {
|
||||
return new Promise((resolve) => {
|
||||
presentDialog({
|
||||
title: strings.doActions.delete.notebook(items.length),
|
||||
title: strings.doActions.delete[type](items.length),
|
||||
positiveText: strings.delete(),
|
||||
negativeText: strings.cancel(),
|
||||
positivePress: (_inputValue, value) => {
|
||||
@@ -47,21 +43,22 @@ function confirmDeleteAllNotes(
|
||||
},
|
||||
onClose: () => {
|
||||
setTimeout(() => {
|
||||
resolve({ delete: false, deleteNotes: false });
|
||||
resolve({ delete: false });
|
||||
});
|
||||
},
|
||||
context: context,
|
||||
check: {
|
||||
info: strings.deleteContainingNotes(items.length),
|
||||
info: `Move all notes in ${
|
||||
items.length > 1 ? `these ${type}s` : `this ${type}`
|
||||
} to trash`,
|
||||
type: "transparent"
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteNotebook(id: string, deleteNotes: boolean) {
|
||||
async function deleteNotebook(id, deleteNotes) {
|
||||
const notebook = await db.notebooks.notebook(id);
|
||||
if (!notebook) return;
|
||||
const parentId = getParentNotebookId(id);
|
||||
if (deleteNotes) {
|
||||
const noteRelations = await db.relations.from(notebook, "note").get();
|
||||
@@ -77,16 +74,14 @@ async function deleteNotebook(id: string, deleteNotes: boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteItems = async (
|
||||
type: ItemType,
|
||||
itemIds: string[],
|
||||
context?: string
|
||||
) => {
|
||||
export const deleteItems = async (items, type, context) => {
|
||||
const ids = items ? items : useSelectionStore.getState().selectedItemsList;
|
||||
|
||||
if (type === "reminder") {
|
||||
await db.reminders.remove(...itemIds);
|
||||
await db.reminders.remove(...ids);
|
||||
useRelationStore.getState().update();
|
||||
} else if (type === "note") {
|
||||
for (const id of itemIds) {
|
||||
for (const id of ids) {
|
||||
if (db.monographs.isPublished(id)) {
|
||||
ToastManager.show({
|
||||
heading: strings.someNotesPublished(),
|
||||
@@ -109,20 +104,20 @@ export const deleteItems = async (
|
||||
);
|
||||
}
|
||||
} else if (type === "notebook") {
|
||||
const result = await confirmDeleteAllNotes(itemIds, "notebook", context);
|
||||
const result = await confirmDeleteAllNotes(ids, "notebook", context);
|
||||
if (!result.delete) return;
|
||||
for (const id of itemIds) {
|
||||
for (const id of ids) {
|
||||
await deleteNotebook(id, result.deleteNotes);
|
||||
eSendEvent(eOnNotebookUpdated, await getParentNotebookId(id));
|
||||
}
|
||||
} else if (type === "tag") {
|
||||
presentDialog({
|
||||
title: strings.doActions.delete.tag(itemIds.length),
|
||||
title: strings.doActions.delete.tag(ids.length),
|
||||
positiveText: strings.delete(),
|
||||
negativeText: strings.cancel(),
|
||||
paragraph: strings.actionConfirmations.delete.tag(2),
|
||||
positivePress: async () => {
|
||||
await db.tags.remove(...itemIds);
|
||||
await db.tags.remove(...ids);
|
||||
useTagStore.getState().refresh();
|
||||
useRelationStore.getState().update();
|
||||
},
|
||||
@@ -131,9 +126,9 @@ export const deleteItems = async (
|
||||
return;
|
||||
}
|
||||
|
||||
const deletedIds = [...itemIds];
|
||||
let deletedIds = [...ids];
|
||||
if (type === "notebook" || type === "note") {
|
||||
const message = strings.actions.movedToTrash[type](itemIds.length);
|
||||
let message = strings.actions.movedToTrash[type](ids.length);
|
||||
ToastManager.show({
|
||||
heading: message,
|
||||
type: "success",
|
||||
@@ -153,21 +148,28 @@ export const deleteItems = async (
|
||||
});
|
||||
} else {
|
||||
ToastManager.show({
|
||||
heading: strings.actions.deleted.unknown(type, itemIds.length),
|
||||
heading: strings.deleted(type, ids.length),
|
||||
type: "success"
|
||||
});
|
||||
}
|
||||
|
||||
Navigation.queueRoutesForUpdate();
|
||||
if (!items) {
|
||||
useSelectionStore.getState().clearSelection();
|
||||
}
|
||||
useMenuStore.getState().setColorNotes();
|
||||
if (type === "notebook") {
|
||||
itemIds.forEach(async (id) => {
|
||||
ids.forEach(async (id) => {
|
||||
eSendEvent(eOnNotebookUpdated, await getParentNotebookId(id));
|
||||
});
|
||||
useMenuStore.getState().setMenuPins();
|
||||
}
|
||||
};
|
||||
|
||||
export const openLinkInBrowser = async (link: string) => {
|
||||
Linking.openURL(link);
|
||||
export const openLinkInBrowser = async (link) => {
|
||||
try {
|
||||
Linking.openURL(link);
|
||||
} catch (error) {
|
||||
console.log(error.message);
|
||||
}
|
||||
};
|
||||
@@ -66,7 +66,7 @@ describe("NOTE TESTS", () => {
|
||||
await Tests.fromText("icon-pinned").isNotVisible();
|
||||
});
|
||||
|
||||
it.skip("Pin a note in notifications", async () => {
|
||||
it("Pin a note in notifications", async () => {
|
||||
await Tests.prepare();
|
||||
await Tests.createNote();
|
||||
await Tests.fromId(notesnook.listitem.menu).waitAndTap();
|
||||
@@ -100,14 +100,14 @@ describe("NOTE TESTS", () => {
|
||||
await Tests.fromText(note.body).isVisible();
|
||||
});
|
||||
|
||||
it("Delete & restore a note", async () => {
|
||||
it.only("Delete & restore a note", async () => {
|
||||
await Tests.prepare();
|
||||
await Tests.createNote();
|
||||
await Tests.fromId(notesnook.listitem.menu).waitAndTap();
|
||||
await Tests.fromId("icon-trash").waitAndTap();
|
||||
await Tests.navigate("Trash");
|
||||
await Tests.fromId(notesnook.listitem.menu).waitAndTap();
|
||||
await Tests.fromText("Restore").waitAndTap();
|
||||
await Tests.fromText("Restore note").waitAndTap();
|
||||
await device.pressBack();
|
||||
await Tests.fromText(
|
||||
"Test note description that is very long and should not fit in text."
|
||||
|
||||
@@ -57,7 +57,6 @@ async function openLockedNote(pwd?: string) {
|
||||
|
||||
async function goToPrivacySecuritySettings() {
|
||||
await Tests.navigate("Settings");
|
||||
await Tests.sleep(300);
|
||||
await Tests.fromText("Vault").waitAndTap();
|
||||
}
|
||||
|
||||
@@ -105,7 +104,6 @@ describe("VAULT", () => {
|
||||
"1234"
|
||||
);
|
||||
await Tests.fromText("Delete").waitAndTap();
|
||||
await Tests.sleep(300);
|
||||
await Tests.fromText("Create vault").isVisible();
|
||||
await device.pressBack();
|
||||
await device.pressBack();
|
||||
@@ -123,7 +121,6 @@ describe("VAULT", () => {
|
||||
);
|
||||
await Tests.fromText("Delete notes in this vault").waitAndTap();
|
||||
await Tests.fromText("Delete").waitAndTap();
|
||||
await Tests.sleep(300);
|
||||
await Tests.fromText("Create vault").isVisible();
|
||||
await device.pressBack();
|
||||
await device.pressBack();
|
||||
@@ -137,7 +134,7 @@ describe("VAULT", () => {
|
||||
await openLockedNote();
|
||||
});
|
||||
|
||||
it("Remove note from vault", async () => {
|
||||
it.only("Remove note from vault", async () => {
|
||||
await Tests.prepare();
|
||||
await Tests.createNote();
|
||||
await lockNote();
|
||||
|
||||
@@ -116,7 +116,7 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
multiDexEnabled true
|
||||
versionCode 3034
|
||||
versionCode 3028
|
||||
versionName getNpmVersion()
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
@@ -164,7 +164,10 @@ android {
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
pickFirst '**/libc++_shared.so'
|
||||
pickFirst 'lib/x86/libc++_shared.so'
|
||||
pickFirst 'lib/x86_64/libc++_shared.so'
|
||||
pickFirst 'lib/armeabi-v7a/libc++_shared.so'
|
||||
pickFirst 'lib/arm64-v8a/libc++_shared.so'
|
||||
jniLibs {
|
||||
useLegacyPackaging isBuildingAAB == false
|
||||
}
|
||||
|
||||
@@ -129,16 +129,6 @@
|
||||
<data android:mimeType="application/*" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:label="Make Note">
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="text/*" />
|
||||
<data android:mimeType="image/*" />
|
||||
<data android:mimeType="video/*" />
|
||||
<data android:mimeType="image/*" />
|
||||
<data android:mimeType="application/*" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:label="Make Note">
|
||||
<action android:name="android.intent.action.PROCESS_TEXT" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
@@ -16,7 +16,7 @@ org.gradle.jvmargs=-Xmx4096m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
# org.gradle.configuration-cache=true
|
||||
org.gradle.configuration-cache=true
|
||||
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
- You can now share multiple files to Notesnook
|
||||
- Fix file and image sharing not working
|
||||
- Fix random crashes on system startup and when app enter background
|
||||
- Attachments manager now shows progress for uploading/downloading/checking attachments
|
||||
- Attachments manager has two new tabs, Errors and Orphaned, to help you manage your attachments
|
||||
- New and improved toast message design
|
||||
- Fix statusbar flicker on app launch
|
||||
- Many other bug fixes and small improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
Binary file not shown.
@@ -5,11 +5,11 @@
|
||||
<key>provisioningProfiles</key>
|
||||
<dict>
|
||||
<key>org.streetwriters.notesnook</key>
|
||||
<string>Notesnook App Distribution 2025</string>
|
||||
<string>Notesnook App Distribution 2024</string>
|
||||
<key>org.streetwriters.notesnook.notewidget</key>
|
||||
<string>Notesnook Widget Distribution 2025</string>
|
||||
<string>Notesnook Widget Distribution 2024</string>
|
||||
<key>org.streetwriters.notesnook.share</key>
|
||||
<string>Notesnook Share Distribution 2025</string>
|
||||
<string>Notesnook Share Distribution 2024</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -112,7 +112,7 @@
|
||||
2D02E4901E0B4A5D006451C7 /* Notesnook-tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Notesnook-tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
41D57E9831C724F59F18FBAE /* libPods-Notesnook-Add to Notes.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Notesnook-Add to Notes.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
47AE097A087A651BD60CAD7B /* Pods-Notesnook-Make Note.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Notesnook-Make Note.release.xcconfig"; path = "Target Support Files/Pods-Notesnook-Make Note/Pods-Notesnook-Make Note.release.xcconfig"; sourceTree = "<group>"; };
|
||||
4B679E87FDFB1312CAD6CF45 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = Notesnook/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
4B679E87FDFB1312CAD6CF45 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Notesnook/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
4BD34AE0C0FD2416E48803B8 /* libPods-Notesnook.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Notesnook.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
4C4AF691C324D95337F2FB0A /* libPods-Make Note.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Make Note.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
6510E6D62877215700DACAA9 /* build.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; name = build.bundle; path = "../../../../packages/editor-mobile/build.bundle"; sourceTree = "<group>"; };
|
||||
@@ -124,7 +124,6 @@
|
||||
6529D2B0257B4A2900B49BC3 /* NotesnookDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = NotesnookDebug.entitlements; path = Notesnook/NotesnookDebug.entitlements; sourceTree = "<group>"; };
|
||||
656835802BB29A8300144BAB /* OpenSans-Italic.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "OpenSans-Italic.ttf"; path = "../../../../packages/editor-mobile/public/fonts/OpenSans-Italic.ttf"; sourceTree = "<group>"; };
|
||||
6593E4A2281C345400492C50 /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = Notesnook/AppDelegate.mm; sourceTree = "<group>"; };
|
||||
6594DDF22CD8BFE8007F5EC2 /* BetterTrigram.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = BetterTrigram.xcframework; sourceTree = "<group>"; };
|
||||
659BE46625E11A5100E05671 /* notesnook-text.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "notesnook-text.png"; path = "Notesnook/Images.xcassets/notesnook-text.png"; sourceTree = "<group>"; };
|
||||
65A7F34F255689AD00699170 /* Notesnook.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Notesnook.entitlements; path = Notesnook/Notesnook.entitlements; sourceTree = "<group>"; };
|
||||
65AA857725E6DDEC00772A01 /* NotesWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotesWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -153,7 +152,7 @@
|
||||
88211800FA8BF60638327ADE /* Pods-Make Note.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Make Note.debug.xcconfig"; path = "Target Support Files/Pods-Make Note/Pods-Make Note.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
A03E3F5851D27269540E147C /* libPods-Notesnook-tvOSTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Notesnook-tvOSTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
A84F3F0D50641D1CD4569B0E /* Pods-Make Note.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Make Note.release.xcconfig"; path = "Target Support Files/Pods-Make Note/Pods-Make Note.release.xcconfig"; sourceTree = "<group>"; };
|
||||
B5216B66C0B4A91C0A69128B /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = Notesnook/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
B5216B66C0B4A91C0A69128B /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Notesnook/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
B9989E5C777BB11343A62D09 /* Pods-Notesnook-NotesnookTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Notesnook-NotesnookTests.debug.xcconfig"; path = "Target Support Files/Pods-Notesnook-NotesnookTests/Pods-Notesnook-NotesnookTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
D1710CA05FED221CC56D2BE1 /* Pods-Notesnook-tvOSTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Notesnook-tvOSTests.release.xcconfig"; path = "Target Support Files/Pods-Notesnook-tvOSTests/Pods-Notesnook-tvOSTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
|
||||
@@ -256,7 +255,6 @@
|
||||
2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6594DDF22CD8BFE8007F5EC2 /* BetterTrigram.xcframework */,
|
||||
6515C42E2580AA2F00E83E39 /* StoreKit.framework */,
|
||||
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
|
||||
ED2971642150620600B7C4FE /* JavaScriptCore.framework */,
|
||||
@@ -1063,7 +1061,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2121;
|
||||
CURRENT_PROJECT_VERSION = 2115;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
ENABLE_BITCODE = NO;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1137,7 +1135,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.24;
|
||||
MARKETING_VERSION = 3.0.18;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -1168,7 +1166,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 2121;
|
||||
CURRENT_PROJECT_VERSION = 2115;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1242,7 +1240,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.24;
|
||||
MARKETING_VERSION = 3.0.18;
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
@@ -1252,7 +1250,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook;
|
||||
PRODUCT_NAME = Notesnook;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook App Distribution 2025";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook App Distribution 2024";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Notesnook-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
@@ -1401,7 +1399,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2121;
|
||||
CURRENT_PROJECT_VERSION = 2115;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1413,7 +1411,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.24;
|
||||
MARKETING_VERSION = 3.0.18;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
@@ -1444,7 +1442,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2121;
|
||||
CURRENT_PROJECT_VERSION = 2115;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
@@ -1457,12 +1455,12 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.24;
|
||||
MARKETING_VERSION = 3.0.18;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook Widget Distribution 2025";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook Widget Distribution 2024";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
@@ -1487,7 +1485,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2121;
|
||||
CURRENT_PROJECT_VERSION = 2115;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1561,7 +1559,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.24;
|
||||
MARKETING_VERSION = 3.0.18;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
@@ -1592,7 +1590,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2121;
|
||||
CURRENT_PROJECT_VERSION = 2115;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
@@ -1667,12 +1665,12 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.24;
|
||||
MARKETING_VERSION = 3.0.18;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook Share Distribution 2025";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook Share Distribution 2024";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Make Note/Make Note-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -1744,7 +1742,10 @@
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_CFLAGS = "$(inherited)";
|
||||
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "$(inherited) ";
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
" ",
|
||||
);
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
USE_HERMES = true;
|
||||
@@ -1808,7 +1809,10 @@
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
OTHER_CFLAGS = "$(inherited)";
|
||||
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "$(inherited) ";
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
" ",
|
||||
);
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
USE_HERMES = true;
|
||||
|
||||
@@ -113,7 +113,7 @@ post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
if target.name == "react-native-quick-sqlite" then
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = 'SQLITE_CORE=1 SQLITE_ENABLE_FTS5=1 SQLITE3MC_OMIT_AES_HARDWARE_SUPPORT=1 HAVE_CIPHER_AES_128_CBC=0 HAVE_CIPHER_AES_256_CBC=0 HAVE_CIPHER_SQLCIPHER=0 HAVE_CIPHER_RC4=0 HAVE_CIPHER_CHACHA20=1 SQLITE_OMIT_PROGRESS_CALLBACK=1 SQLITE_MAX_EXPR_DEPTH=0 SQLITE_OMIT_DEPRECATED=1 SQLITE_LIKE_DOESNT_MATCH_BLOBS=1 SQLITE_DQS=0 SQLITE_DEFAULT_MEMSTATUS=0 SQLITE_USE_ALLOCA=1'
|
||||
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = 'SQLITE_ENABLE_FTS5=1 SQLITE3MC_OMIT_AES_HARDWARE_SUPPORT=1 HAVE_CIPHER_AES_128_CBC=0 HAVE_CIPHER_AES_256_CBC=0 HAVE_CIPHER_SQLCIPHER=0 HAVE_CIPHER_RC4=0 HAVE_CIPHER_CHACHA20=1 SQLITE_OMIT_PROGRESS_CALLBACK=1 SQLITE_MAX_EXPR_DEPTH=0 SQLITE_OMIT_DEPRECATED=1 SQLITE_LIKE_DOESNT_MATCH_BLOBS=1 SQLITE_DQS=0 SQLITE_DEFAULT_MEMSTATUS=0 SQLITE_USE_ALLOCA=1'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -970,8 +970,6 @@ PODS:
|
||||
- React
|
||||
- react-native-background-actions (2.6.7):
|
||||
- React-Core
|
||||
- react-native-begin-background-task (0.1.0):
|
||||
- React
|
||||
- react-native-blob-util (0.18.3):
|
||||
- React-Core
|
||||
- react-native-config (1.5.1):
|
||||
@@ -983,7 +981,7 @@ PODS:
|
||||
- react-native-document-picker (7.1.3):
|
||||
- React-Core
|
||||
- react-native-fingerprint-scanner (5.0.0):
|
||||
- React-Core
|
||||
- React
|
||||
- react-native-get-random-values (1.9.0):
|
||||
- React-Core
|
||||
- react-native-gzip (1.1.0):
|
||||
@@ -1019,9 +1017,9 @@ PODS:
|
||||
- react-native-screenguard (1.0.0):
|
||||
- React-Core
|
||||
- SDWebImage (~> 5.11.1)
|
||||
- react-native-share-extension (2.7.0):
|
||||
- react-native-share-extension (3.0.0):
|
||||
- React
|
||||
- react-native-sodium (1.6.1):
|
||||
- react-native-sodium (1.5.4):
|
||||
- React
|
||||
- react-native-theme-switch-animation (0.6.0):
|
||||
- DoubleConversion
|
||||
@@ -1305,7 +1303,7 @@ PODS:
|
||||
- React-Core
|
||||
- RNCMaskedView (0.2.9):
|
||||
- React-Core
|
||||
- RNDateTimePicker (8.2.0):
|
||||
- RNDateTimePicker (6.6.0):
|
||||
- React-Core
|
||||
- RNDeviceInfo (8.7.1):
|
||||
- React-Core
|
||||
@@ -1488,12 +1486,11 @@ DEPENDENCIES:
|
||||
- React-Mapbuffer (from `../../node_modules/react-native/ReactCommon`)
|
||||
- react-native-actions-shortcuts (from `../../node_modules/react-native-actions-shortcuts`)
|
||||
- react-native-background-actions (from `../../node_modules/react-native-background-actions`)
|
||||
- react-native-begin-background-task (from `../../node_modules/react-native-begin-background-task`)
|
||||
- react-native-blob-util (from `../../node_modules/react-native-blob-util`)
|
||||
- react-native-config (from `../../node_modules/react-native-config`)
|
||||
- react-native-date-picker (from `../../node_modules/react-native-date-picker`)
|
||||
- react-native-document-picker (from `../../node_modules/react-native-document-picker`)
|
||||
- "react-native-fingerprint-scanner (from `../../node_modules/@ammarahmed/react-native-fingerprint-scanner`)"
|
||||
- react-native-fingerprint-scanner (from `../../node_modules/react-native-fingerprint-scanner`)
|
||||
- react-native-get-random-values (from `../../node_modules/react-native-get-random-values`)
|
||||
- react-native-gzip (from `../../node_modules/react-native-gzip`)
|
||||
- react-native-html-to-pdf-lite (from `../../node_modules/react-native-html-to-pdf-lite`)
|
||||
@@ -1645,8 +1642,6 @@ EXTERNAL SOURCES:
|
||||
:path: "../../node_modules/react-native-actions-shortcuts"
|
||||
react-native-background-actions:
|
||||
:path: "../../node_modules/react-native-background-actions"
|
||||
react-native-begin-background-task:
|
||||
:path: "../../node_modules/react-native-begin-background-task"
|
||||
react-native-blob-util:
|
||||
:path: "../../node_modules/react-native-blob-util"
|
||||
react-native-config:
|
||||
@@ -1656,7 +1651,7 @@ EXTERNAL SOURCES:
|
||||
react-native-document-picker:
|
||||
:path: "../../node_modules/react-native-document-picker"
|
||||
react-native-fingerprint-scanner:
|
||||
:path: "../../node_modules/@ammarahmed/react-native-fingerprint-scanner"
|
||||
:path: "../../node_modules/react-native-fingerprint-scanner"
|
||||
react-native-get-random-values:
|
||||
:path: "../../node_modules/react-native-get-random-values"
|
||||
react-native-gzip:
|
||||
@@ -1838,12 +1833,11 @@ SPEC CHECKSUMS:
|
||||
React-Mapbuffer: 6c1cacdbf40b531f549eba249e531a7d0bfd8e7f
|
||||
react-native-actions-shortcuts: 5d9cf0c9c308333dfcc1e05c3f9afa8c428e2533
|
||||
react-native-background-actions: 2c251c986f23347f9c1722f05fd296938f60edb1
|
||||
react-native-begin-background-task: 3b889e07458afc5822a7277cf9cbc7cd077e39ee
|
||||
react-native-blob-util: 2d36383bb52c15c5451be81cb7ddf22bc34a12a6
|
||||
react-native-config: 86038147314e2e6d10ea9972022aa171e6b1d4d8
|
||||
react-native-date-picker: 93e43b3084cea595b4d68b1405d6d99849663bd6
|
||||
react-native-document-picker: ec07866a30707f23660c0f3ae591d669d3e89096
|
||||
react-native-fingerprint-scanner: be63e626b31fb951780a5fac5328b065a61a3d6e
|
||||
react-native-fingerprint-scanner: 3e8c46641b5751a31f45e66789d4b6f342be5e6d
|
||||
react-native-get-random-values: dee677497c6a740b71e5612e8dbd83e7539ed5bb
|
||||
react-native-gzip: c5e87ee9e359f02350e3a2ee52eb35eddc398868
|
||||
react-native-html-to-pdf-lite: 21bfb169bf4cbcd7bec9f736975ee1b3f5292d4a
|
||||
@@ -1855,11 +1849,11 @@ SPEC CHECKSUMS:
|
||||
react-native-notification-sounds: da78c828fe1bcbb92d8b505d5261890ed315ff39
|
||||
react-native-orientation: f1caf84d65f1a4fd4511a18f2b924e634ad7a628
|
||||
react-native-pdf: 33c622cbdf776a649929e8b9d1ce2d313347c4fa
|
||||
react-native-quick-sqlite: 18e1367c34faac90e37f6eb3e78c196e9b674b5d
|
||||
react-native-quick-sqlite: e0e23b749382a85e4b57146f753de737a6c3a9e1
|
||||
react-native-safe-area-context: b7daa1a8df36095a032dff095a1ea8963cb48371
|
||||
react-native-screenguard: 8b36a3df84c76cd2b82c477f71c26fa1c8cc14a0
|
||||
react-native-share-extension: 17e42444d0d9fbfeb0a7392899def70f9534c9c4
|
||||
react-native-sodium: 4cb76086943a7f60c42b40ebca866695b360a196
|
||||
react-native-share-extension: faed334b1ddf165f1e576fcabd3dc1c9e748bfa9
|
||||
react-native-sodium: 955bb0dc3ea05f8ea06d5e96cb89d1be7b5d7681
|
||||
react-native-theme-switch-animation: d3eb50365a3829ce5572628888fa514752703f61
|
||||
react-native-webview: 553abd09f58e340fdc7746c9e2ae096839e99911
|
||||
React-nativeconfig: ba9a2e54e2f0882cf7882698825052793ed4c851
|
||||
@@ -1890,7 +1884,7 @@ SPEC CHECKSUMS:
|
||||
RNCCheckbox: 43bcc6493611468af0e19f19f029dab3da8561c4
|
||||
RNCClipboard: 3f0451a8100393908bea5c5c5b16f96d45f30bfc
|
||||
RNCMaskedView: 949696f25ec596bfc697fc88e6f95cf0c79669b6
|
||||
RNDateTimePicker: 40ffda97d071a98a10fdca4fa97e3977102ccd14
|
||||
RNDateTimePicker: 818672afa85519722533d017b832ed09539d9ddb
|
||||
RNDeviceInfo: aad3c663b25752a52bf8fce93f2354001dd185aa
|
||||
RNExitApp: c4e052df2568b43bec8a37c7cd61194d4cfee2c3
|
||||
RNFileViewer: ce7ca3ac370e18554d35d6355cffd7c30437c592
|
||||
@@ -1907,7 +1901,7 @@ SPEC CHECKSUMS:
|
||||
RNShare: a5dc3b9c53ddc73e155b8cd9a94c70c91913c43c
|
||||
RNSVG: d7d7bc8229af3842c9cfc3a723c815a52cdd1105
|
||||
RNTooltips: 5424d4bf0b3d441104127943b1115cc7f0616b1f
|
||||
RNZipArchive: 9d90367b1573903b5561a8c0b911e5cc12626e74
|
||||
RNZipArchive: 68a0c6db4b1c103f846f1559622050df254a3ade
|
||||
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
|
||||
SexyTooltip: 5c9b4dec52bfb317938cb0488efd9da3717bb6fd
|
||||
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d
|
||||
@@ -1917,6 +1911,6 @@ SPEC CHECKSUMS:
|
||||
toolbar-android: 2a73856e98b750d7e71ce4644d3f41cc98211719
|
||||
Yoga: 2246eea72aaf1b816a68a35e6e4b74563653ae09
|
||||
|
||||
PODFILE CHECKSUM: aa07aca16c1d92394d8ce12f2c5fcaa8a3ae4997
|
||||
PODFILE CHECKSUM: 98f600b3236e314680588e77819982573fb4d9f7
|
||||
|
||||
COCOAPODS: 1.15.2
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user