Compare commits

...

175 Commits

Author SHA1 Message Date
Ammar Ahmed
db7b3635e2 mobile: set sticky state if not set already 2024-11-06 13:12:29 +05:00
Ammar Ahmed
72e256a949 mobile: fix setting startingOffset 2024-11-06 13:08:30 +05:00
Ammar Ahmed
a81a788c74 mobile: fix editor status flickers too much 2024-11-06 13:06:09 +05:00
Ammar Ahmed
3eaba77c38 mobile: fix keep-in-view overscrolling 2024-11-06 13:05:53 +05:00
Ammar Ahmed
3cf00cb0f5 mobile: allow user to cancel 2fa auth 2024-11-06 11:04:08 +05:00
Ammar Ahmed
5659576ce3 mobile: fix vault does not show error toast on incorrect password 2024-11-06 10:33:49 +05:00
Abdullah Atta
55842d44ea chore: set type to Feature for feature requests
Signed-off-by: Abdullah Atta <abdullahatta@streetwriters.co>
2024-11-06 08:39:53 +05:00
Abdullah Atta
43238692e4 chore: remove thecodrr as default issue assignee
Signed-off-by: Abdullah Atta <abdullahatta@streetwriters.co>
2024-11-06 08:39:02 +05:00
01zulfi
fa1f64f505 web: toc improvements (#4977) (#6818)
1. ToC open/close state is persisted
2. ToC is resizeable
3. ToC no longer covers note contents
4. Increase ToC indentations

Signed-off-by: Zulfiqar Ali <01zulfiqarali@gmail.com>
2024-11-05 16:21:40 +05:00
Abdullah Atta
25e3d56435 intl: restore vaultLocked 2024-11-05 15:43:00 +05:00
01zulfi
8722524c98 web: show vault unlock indication in status bar (#6830)
* web: add vault in status bar
* add vault lock functionality for user
* add new vault EVENTS

Signed-off-by: Zulfiqar Ali <85733202+01zulfi@users.noreply.github.com>
2024-11-05 15:35:02 +05:00
Abdullah Atta
c9b1b42461 desktop: fix Could not resolve "@notesnook/intl/dist/locales/$en.json" error 2024-11-05 13:07:31 +05:00
Ammar Ahmed
fa54f7e7f7 intl: fix localization 2024-11-05 10:14:00 +05:00
Ammar Ahmed
26d1de0620 intl: fix localization 2024-11-04 16:13:02 +05:00
01zulfi
b21548780a web: fix note title not focusing until clicked twice (#6767)
Signed-off-by: Zulfiqar Ali <85733202+01zulfi@users.noreply.github.com>
2024-11-04 11:39:08 +05:00
Ammar Ahmed
c8ba9266a7 intl: fix localization 2024-11-04 10:01:45 +05:00
Abdullah Atta
9e9ef15737 web: add quick checkout (#6811)
* web: add quick checkout

* web: allow changing checkout locale during testing
2024-11-02 12:44:50 +05:00
Muhammad Ali
4d7d2fbf31 web: add confirm password when changing app lock pin/password (#6808) 2024-11-02 12:14:39 +05:00
Abdullah Atta
b7ddae6950 core: fix invalid object-ids generated on desktop 2024-10-28 15:58:45 +05:00
Abdullah Atta
1d60f2132d docs: update analytics script 2024-10-28 08:27:09 +05:00
Abdullah Atta
e9e4a76ac2 ci: publish versioned monograph docker image 2024-10-26 15:47:24 +05:00
Abdullah Atta
4d3a2cc574 monograph: bump version to 1.1.0 2024-10-26 15:44:10 +05:00
Abdullah Atta
c516c8a0ba monograph: fix theme not loading in dev 2024-10-26 15:43:31 +05:00
Abdullah Atta
24d5a0746b monograph: cache storage keys for 5 minutes 2024-10-26 15:43:20 +05:00
Abdullah Atta
dee6c86d15 monograph: add table of contents on mobile 2024-10-26 15:43:03 +05:00
Abdullah Atta
33e64416f3 monograph: fix process is not defined 2024-10-26 15:42:42 +05:00
Abdullah Atta
a74637e9c0 monograph: load i18n for editor 2024-10-26 15:41:55 +05:00
Muhammad Ali
ccd1b6d69b editor: fix editing note link replaces link text with note name (#6768) 2024-10-25 09:27:53 +05:00
Muhammad Ali
3750dd38dc editor: fix some math glyps not rendering correctly (#6762) 2024-10-24 11:47:13 +05:00
Ammar Ahmed
1506cc257d intl: update locale 2024-10-19 14:05:18 +05:00
Ammar Ahmed
fe7c7889e8 mobile: fix file upload checks 2024-10-19 14:05:04 +05:00
Ammar Ahmed
3f54eee11d mobile: fix localization 2024-10-19 14:04:16 +05:00
Ammar Ahmed
11b5922c2c mobile: use pseudo-LOCALE in debug 2024-10-19 11:49:53 +05:00
Ammar Ahmed
71b85d2a4d intl: fix strings 2024-10-19 11:46:50 +05:00
Ammar Ahmed
289832d166 mobile: wrap script manager in try/catch 2024-10-19 11:08:53 +05:00
Ammar Ahmed
a88e612c35 editor: fix table popup ui on mobile 2024-10-17 13:44:16 +05:00
Ammar Ahmed
bda456362b mobile: fix cache dir does not exist error on bulkExist check 2024-10-17 12:02:00 +05:00
Abdullah Atta
f6f644c55d editor: fix queueMicrotask causing hang 2024-10-17 11:13:05 +05:00
Ammar Ahmed
08c92333d6 global: fix discord community url in github issue templates
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2024-10-17 11:11:01 +05:00
Muhammad Ali
4d72b77f29 web: fix right side panel of settings is not scrollable (#6716) 2024-10-17 11:08:46 +05:00
Ammar Ahmed
4f9d06737f mobile: fix repack not loading async chunks 2024-10-16 16:03:47 +05:00
Ammar Ahmed
bfc494a3d4 mobile: fix copy/share buttons get stuck sometimes with no error thrown (#6708)
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2024-10-16 16:03:08 +05:00
Muhammad Ali
e3b004d35f docs: add help for app lock (#6703) 2024-10-16 11:07:01 +05:00
Ammar Ahmed
bd05ad6548 mobile: fix single file export on ios 2024-10-14 12:59:45 +05:00
Ammar Ahmed
ffe068dbd5 global: fix localization 2024-10-14 12:55:08 +05:00
Abdullah Atta
b1f21c1abe desktop: fix type errors importing @notesnook/intl 2024-10-14 11:35:05 +05:00
Abdullah Atta
b494efb8ce web: fix cannot read property instance of undefined 2024-10-14 11:35:05 +05:00
Ammar Ahmed
99ec45ec2a web: fix google-keep importer link 2024-10-14 09:46:11 +05:00
Abdullah Atta
9cea027506 monograph: use top-level await to initialize svg2png-wasm 2024-10-11 09:54:12 +05:00
Abdullah Atta
8bfc158c51 monograph: fix Already initialized. The initialize function can be used only once. 2024-10-11 09:42:52 +05:00
Abdullah Atta
d6ed99282d monograph: only check for success if an object in kv 2024-10-10 23:27:02 +05:00
Abdullah Atta
ac92f3685f monograph: add /api/og endpoint redirection for old og links 2024-10-10 23:16:43 +05:00
Abdullah Atta
05fea49baf monograph: fix kv read not working 2024-10-10 22:55:30 +05:00
Abdullah Atta
ed16143a2c monograph: fix Use yyyy instead of YYYY`` 2024-10-10 16:00:11 +05:00
Abdullah Atta
6618a21a63 monograph: add more meta tags and social image 2024-10-10 15:54:27 +05:00
Abdullah Atta
5b472748a1 monograph: fix uncolored text on initial load 2024-10-10 14:25:11 +05:00
Abdullah Atta
a117aab558 monograph: add missing links in buttons on landing page 2024-10-10 14:14:33 +05:00
Abdullah Atta
ad2ab03be7 monograph: fix og image url on landing page 2024-10-10 14:08:56 +05:00
Abdullah Atta
514112c077 ci: use latest tag when publishing monograph 2024-10-10 13:10:07 +05:00
Abdullah Atta
6dd939af5b monograph: add health check endpoint 2024-10-10 13:09:53 +05:00
Abdullah Atta
82f9c907be ci: disable qemu for monograph build 2024-10-10 12:50:28 +05:00
Abdullah Atta
3cd6c6a371 intl: use pseudo locale in development 2024-10-10 12:35:58 +05:00
Abdullah Atta
8437394891 intl: add more strings 2024-10-10 12:35:58 +05:00
Abdullah Atta
29cb444746 web: fix ui for longer language strings 2024-10-10 12:35:58 +05:00
Abdullah Atta
41eb22b5ca desktop: use strings from @notesnook/intl 2024-10-10 12:35:58 +05:00
Abdullah Atta
8577c32c1f editor: use strings from @notesnook/intl 2024-10-10 12:35:58 +05:00
Abdullah Atta
3e55492318 web: use strings from @notesnook/intl in more places 2024-10-10 12:35:58 +05:00
Abdullah Atta
bb53b0167b mobile: fix typo 2024-10-10 12:35:58 +05:00
Abdullah Atta
ec07c402c9 intl: add new strings 2024-10-10 12:35:58 +05:00
Abdullah Atta
cb50bccbeb intl: use vite for building 2024-10-10 12:35:58 +05:00
Ammar Ahmed
bf7c7ebc82 mobile: fix build 2024-10-10 12:35:58 +05:00
Ammar Ahmed
1184c9f506 web: localize 2024-10-10 12:35:58 +05:00
Ammar Ahmed
f781aaa6f5 web: localize 2024-10-10 12:35:58 +05:00
Ammar Ahmed
e78b245eea web: localize 2024-10-10 12:35:58 +05:00
Ammar Ahmed
bb1ec22197 intl: update strings 2024-10-10 12:35:58 +05:00
Ammar Ahmed
1fff0f6a1c web: localize 2024-10-10 12:35:58 +05:00
Ammar Ahmed
d28d34fad1 mobile: add missing localizations 2024-10-10 12:35:58 +05:00
Ammar Ahmed
40ae83dcad mobile: localize 2024-10-10 12:35:58 +05:00
Ammar Ahmed
0a18f43d95 mobile: localize new strings 2024-10-10 12:35:58 +05:00
Ammar Ahmed
c639bfb227 mobile: fix localize errors 2024-10-10 12:35:58 +05:00
Ammar Ahmed
014866c330 mobile: localize 2024-10-10 12:35:58 +05:00
Ammar Ahmed
dfe7b2e4a6 mobile: localize 2024-10-10 12:35:58 +05:00
Ammar Ahmed
f371c05446 mobile: localize 2024-10-10 12:35:58 +05:00
Ammar Ahmed
d31f6688f8 mobile: migrate more strings 2024-10-10 12:35:58 +05:00
Ammar Ahmed
3e5b740ef0 mobile: migrate app to intl 2024-10-10 12:35:58 +05:00
Ammar Ahmed
f282265fbb intl: initial setup 2024-10-10 12:35:58 +05:00
Abdullah Atta
05df9b77ed ci: add monograph publishing workflow 2024-10-02 15:27:24 +05:00
Abdullah Atta
69ba20009c monograph: add support for self hosting 2024-10-02 15:27:24 +05:00
Abdullah Atta
9bf490bcb4 monograph: use public url 2024-10-02 15:27:24 +05:00
Abdullah Atta
5a40080347 monograph: publish source code 2024-10-02 15:27:24 +05:00
Abdullah Atta
f42155ca5d core: use math.random instead of crypto random in object-id 2024-10-01 16:17:36 +05:00
Abdullah Atta
3b06450113 editor: fix @emotion/react version 2024-10-01 16:16:43 +05:00
Abdullah Atta
2fb3c3a02b ui: get rid of @mdi/react 2024-10-01 16:15:53 +05:00
Abdullah Atta
c8648659c7 theme: add support for setting default theme via global variable 2024-10-01 16:13:35 +05:00
Abdullah Atta
83a663da2b editor: render react widgets inside queueMicrotask 2024-10-01 16:13:06 +05:00
Abdullah Atta
5f0d14f01c web: fix motion.create is not a function 2024-09-25 14:41:25 +05:00
Ammar Ahmed
069ec39de8 mobile: fix foreground service permission type 2024-09-25 14:20:02 +05:00
Ammar Ahmed
bef06a2a20 mobile: change foreground service type to dataSync 2024-09-25 12:44:17 +05:00
Ammar Ahmed
1a98fb8996 mobile: release v3.0.18 2024-09-25 11:02:09 +05:00
Abdullah Atta
dc15bf75c6 editor: fix framer-motion deprecation warning 2024-09-25 10:55:55 +05:00
Abdullah Atta
7357422053 web: bump version to 3.0.18 2024-09-25 10:53:58 +05:00
Abdullah Atta
7918c089d3 desktop: update packages and electron 2024-09-25 10:53:30 +05:00
Ammar Ahmed
ba50b75c54 mobile: fix download file overwrite error 2024-09-25 10:43:26 +05:00
Ammar Ahmed
0d7f32bdd0 mobile: fix attachment recheck ui 2024-09-25 10:39:51 +05:00
Ammar Ahmed
8251e9437b mobile: scope recheck state to current note 2024-09-25 10:39:51 +05:00
Ammar Ahmed
416d5aef90 mobile: progress for note attachments 2024-09-25 10:39:51 +05:00
Abdullah Atta
edb2e3a0ed web: add a couple more tests for basic plan 2024-09-25 10:00:37 +05:00
Abdullah Atta
64031a7814 web: handle errors during tag/color edit 2024-09-24 14:00:32 +05:00
Abdullah Atta
1bd3c3d0f4 web: change module resolution to Bundler 2024-09-24 13:44:44 +05:00
Abdullah Atta
21b6fc4982 editor: export tool definitions and other utils independently 2024-09-24 13:44:31 +05:00
Abdullah Atta
b881023e7b core: update @streetwriters/showdown 2024-09-24 13:25:37 +05:00
Abdullah Atta
6387156596 common: retain original error stack during export 2024-09-24 13:25:37 +05:00
Abdullah Atta
2b2cfe9d33 web: do not zip note without attachments on export 2024-09-24 13:25:37 +05:00
Ammar Ahmed
e81580024e mobile: fix build failing 2024-09-24 13:14:35 +05:00
Ammar Ahmed
8b207b47dd mobile: fix app state 2024-09-24 13:14:34 +05:00
Ammar Ahmed
15aab4ec29 mobile: fix attachments progress 2024-09-24 13:14:34 +05:00
Ammar Ahmed
bdb0474645 editor: fix paragraph not inserted at the end of some nodes 2024-09-24 13:14:34 +05:00
Abdullah Atta
8d25d03390 web: update note export test snapshots 2024-09-24 12:28:02 +05:00
Abdullah Atta
ea2c7d2ea9 web: make markdown rendered container a div instead of span 2024-09-24 12:26:28 +05:00
theProton
0ff38c7ca3 web: fix text rendering in bug report confirmation dialog (#6573)
Signed-off-by: parthiv-m <parthivmenon.dev@gmail.com>
Co-authored-by: Abdullah Atta <thecodrr@protonmail.com>
2024-09-24 12:25:23 +05:00
Abdullah Atta
595f2e9ba7 web: do not ask for confirmation when deleting a single tag 2024-09-24 12:19:39 +05:00
Abdullah Atta
b02bf3d4ad web: update failing tests snapshots 2024-09-24 12:19:25 +05:00
Abdullah Atta
1e295d8562 web: fix reminder cron expression validation 2024-09-24 12:19:12 +05:00
Abdullah Atta
27a0aeb170 web: fix found 0 byte sized chunk error on attaching files 2024-09-23 19:30:40 +05:00
Abdullah Atta
7e355c3ecb crypto: @notesnook/sodium no longer requires top-level await 2024-09-23 19:30:18 +05:00
Abdullah Atta
69468487c2 core: note title reset on updating a note 2024-09-23 17:26:50 +05:00
Abdullah Atta
0809b5b701 web: fix rotate direction of update check icon 2024-09-23 16:12:12 +05:00
Muhammad Ali
171f28f7c0 web: fix markdown shortcuts toggle reset on app restart (#6555) 2024-09-23 16:09:32 +05:00
Abdullah Atta
0a2af0ec7d global: publish packages 2024-09-23 15:47:09 +05:00
Abdullah Atta
c53354c368 editor: create languages directory if it doesn't exist 2024-09-23 15:46:22 +05:00
Abdullah Atta
be414f869c common: use local @notesnook/core package 2024-09-23 15:42:22 +05:00
Abdullah Atta
de40285e21 core: create languages directory if it doesn't exist 2024-09-23 15:37:58 +05:00
Abdullah Atta
a7fa0bde00 global: publish packages 2024-09-23 15:32:14 +05:00
Abdullah Atta
19c2d2007f core: fix imports in tests 2024-09-23 15:32:13 +05:00
Abdullah Atta
ecc9736b90 global: use tsup for bundling packages 2024-09-23 15:32:13 +05:00
Abdullah Atta
ffd022cfa6 mobile: fix imports 2024-09-23 15:32:13 +05:00
Abdullah Atta
89aba00aac web: fix imports 2024-09-23 15:32:13 +05:00
Abdullah Atta
bcc739aa57 setup: improve publish script 2024-09-23 15:32:13 +05:00
Abdullah Atta
fc1015b3fb setup: increase cleanup script concurrency 2024-09-23 15:32:13 +05:00
Abdullah Atta
f1fa32c7f5 ui: fix element type is invalid error when using icon 2024-09-23 15:32:13 +05:00
Abdullah Atta
a1e794c583 editor(mobile): fix imports and make build cross platform 2024-09-23 15:32:13 +05:00
Abdullah Atta
c787ef29d6 editor: include esm & cjs variants in build 2024-09-23 15:32:13 +05:00
Abdullah Atta
7c257cfbd2 editor: import & use language grammars from refractor directly 2024-09-23 15:32:13 +05:00
Abdullah Atta
99c77e6c5f editor: fix crash when inserting a code block 2024-09-23 15:32:12 +05:00
Abdullah Atta
f060c65bd5 editor: make imports esm compatible 2024-09-23 15:32:12 +05:00
Abdullah Atta
22559cdf42 common: include esm and cjs variants in build 2024-09-23 15:32:12 +05:00
Abdullah Atta
1fb86cb27b common: make imports esm compatible 2024-09-23 15:32:12 +05:00
Abdullah Atta
7f31066e8f core: include cjs and esm variants in build 2024-09-23 15:32:12 +05:00
Abdullah Atta
50e418dc53 core: use language grammars directly from refractor 2024-09-23 15:32:12 +05:00
Abdullah Atta
59739067b6 core: make imports compatible with esm 2024-09-23 15:32:12 +05:00
Abdullah Atta
f100e157d3 server(themes): upgrade @trpc/server 2024-09-23 15:32:11 +05:00
Abdullah Atta
870ce259f0 theme: include both esm and cjs variants in build 2024-09-23 15:32:11 +05:00
Abdullah Atta
95b50eb5ca theme: make imports esm compatible 2024-09-23 15:32:11 +05:00
Abdullah Atta
f243d13b81 ui: include both esm and cjs variants in build 2024-09-23 15:32:11 +05:00
Abdullah Atta
749a13efbc ui: make imports esm compatible 2024-09-23 15:32:11 +05:00
Abdullah Atta
275d5d7f04 fs: include both esm and cjs variants in build 2024-09-23 15:32:11 +05:00
Abdullah Atta
54de071e85 fs: make imports esm compatible 2024-09-23 15:32:11 +05:00
Abdullah Atta
9647e2513b crypto(sodium): include both esm and cjs variants in build 2024-09-23 15:32:11 +05:00
Abdullah Atta
239781eabd crypto: make imports esm compatible 2024-09-23 15:32:11 +05:00
Abdullah Atta
21573d6cc8 logger: include both esm and cjs variants in build 2024-09-23 15:32:11 +05:00
Abdullah Atta
9b091f0bb0 logger: make imports esm compatible 2024-09-23 15:32:11 +05:00
Abdullah Atta
e0beef745e crypto: include both esm & cjs variants in build 2024-09-23 15:32:11 +05:00
Abdullah Atta
2f2653d223 crypto: make imports esm compatible 2024-09-23 15:32:11 +05:00
Abdullah Atta
7458aa53ae clipper: make imports esm compatible 2024-09-23 15:32:11 +05:00
Ammar Ahmed
f7db306c26 mobile: fix showing toast at bottom always 2024-09-23 13:47:58 +05:00
Ammar Ahmed
7e8d6f2041 mobile: make sure toast moves with keyboard 2024-09-23 13:47:58 +05:00
Ammar Ahmed
d424a6c48f mobile: fix size of text in toast 2024-09-23 13:47:58 +05:00
Ammar Ahmed
7293224514 mobile: fix toast ui in diff cond 2024-09-23 13:47:58 +05:00
Ammar Ahmed
7706cfed9a mobile: new toast 2024-09-23 13:47:58 +05:00
Ammar Ahmed
bbfe0b237d editor: fix duplicate block-id when pasting content from note 2024-09-23 12:49:08 +05:00
Ammar Ahmed
2c7cddd853 mobile: do not zip note without attachments 2024-09-23 12:48:17 +05:00
Ammar Ahmed
37c1d39c41 mobile: fix statusbar flicker on launch app 2024-09-23 12:47:39 +05:00
Ammar Ahmed
5d4960a26f mobile: fix foregroundServiceType 2024-09-23 12:12:09 +05:00
Ammar Ahmed
78f07b23df mobile: fix boot service crash on boot 2024-09-23 12:12:09 +05:00
Ammar Ahmed
7aeb047b50 mobile: improve attachments manager 2024-09-23 12:11:26 +05:00
833 changed files with 56164 additions and 12828 deletions

View File

@@ -15,6 +15,7 @@ const SCOPES = [
"mobile",
"web",
"vericrypt",
"monograph",
"desktop",
"crypto",
"editor",
@@ -34,7 +35,8 @@ const SCOPES = [
"common",
"global",
"docs",
"themebuilder"
"themebuilder",
"intl"
];
module.exports = {

View File

@@ -1,8 +1,6 @@
name: Bug Report
description: Are you facing a bug or a crash in Notesnook?
labels: ["Type: Bug", "Status: Pending"]
assignees:
- thecodrr
type: Bug
body:
- type: markdown
attributes:

View File

@@ -1,5 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: Notesnook Discord Community
url: https://discord.gg/6mHHyncHJE
url: https://go.notesnook.com/discord
about: Reach out to us directly & discuss your issues, suggestions & other feedback!

View File

@@ -1,6 +1,6 @@
name: Feature request
description: Suggest a feature you are missing in Notesnook
labels: ["Type: Feature Request", "Status: Pending"]
type: Feature
body:
- type: markdown
attributes:

89
.github/workflows/monograph.publish.yml vendored Normal file
View File

@@ -0,0 +1,89 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# GitHub recommends pinning actions to a commit SHA.
# To get a newer version, you will need to update the SHA.
# You can also reference a tag or branch, but the action may change without warning.
name: Publish @notesnook/monograph
on: workflow_dispatch
jobs:
push_to_registry:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
attestations: write
id-token: write
steps:
- name: Check out the repo
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=monograph
- name: Collect package metadata
id: package_metadata
working-directory: ./apps/monograph
run: |
echo ::set-output name=app_version::$(cat package.json | jq -r .version)
- name: Generate build
run: npx nx build @notesnook/monograph
# Setup Buildx
- name: Docker Setup Buildx
uses: docker/setup-buildx-action@v3
with:
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v8
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
ecr: auto
logout: true
# Pull previous image from docker hub to use it as cache to improve the image build time.
- name: docker pull cache image
continue-on-error: true
run: docker pull streetwriters/monograph:latest
# Setup QEMU
# - name: Set up QEMU
# uses: docker/setup-qemu-action@v2
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: streetwriters/monograph
- name: Build and push Docker image
id: push
uses: docker/build-push-action@v6
with:
context: apps/monograph
file: apps/monograph/Dockerfile
push: true
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v8
tags: streetwriters/monograph:${{ steps.package_metadata.outputs.app_version }},streetwriters/monograph:latest
cache-from: streetwriters/monograph:latest
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v1
with:
subject-name: index.docker.io/streetwriters/monograph
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "3.0.17",
"version": "3.0.18",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/index.js",
@@ -11,29 +11,31 @@
"repository": "https://github.com/streetwriters/notesnook",
"license": "GPL-3.0-or-later",
"dependencies": {
"@lingui/core": "4.11.4",
"@notesnook/intl": "file:../../packages/intl",
"@trpc/client": "10.45.2",
"@trpc/server": "10.45.2",
"better-sqlite3-multiple-ciphers": "^11.2.1",
"better-sqlite3-multiple-ciphers": "11.2.1",
"electron-trpc": "0.6.1",
"electron-updater": "^6.2.1",
"electron-updater": "^6.3.4",
"icojs": "^0.19.4",
"typed-emitter": "^2.1.0",
"yargs": "^17.7.2",
"zod": "^3.21.4"
"zod": "^3.23.8"
},
"devDependencies": {
"@streetwriters/kysely": "^0.27.4",
"@types/node": "20.14.8",
"@types/node": "22.7.0",
"@types/yargs": "^17.0.33",
"chokidar": "^3.5.3",
"electron": "^30.4.0",
"electron-builder": "^24.13.3",
"esbuild": "^0.23.0",
"node-abi": "^3.67.0",
"node-gyp-build": "^4.8.0",
"chokidar": "^4.0.1",
"electron": "^30.5.1",
"electron-builder": "^25.1.7",
"esbuild": "^0.24.0",
"node-abi": "^3.68.0",
"node-gyp-build": "^4.8.2",
"prebuildify": "^6.0.1",
"tree-kill": "^1.2.2",
"undici": "^6.14.1"
"undici": "^6.19.8"
},
"optionalDependencies": {
"dmg-license": "^1.0.11"

View File

@@ -1,8 +1,8 @@
diff --git a/node_modules/app-builder-lib/out/targets/LinuxTargetHelper.js b/node_modules/app-builder-lib/out/targets/LinuxTargetHelper.js
index fcb7f54..cb1c7f7 100644
index 739a48c..7a7b4af 100644
--- a/node_modules/app-builder-lib/out/targets/LinuxTargetHelper.js
+++ b/node_modules/app-builder-lib/out/targets/LinuxTargetHelper.js
@@ -116,6 +116,7 @@ class LinuxTargetHelper {
@@ -117,6 +117,7 @@ class LinuxTargetHelper {
StartupWMClass: appInfo.productName,
...extra,
...targetSpecificOptions.desktop,
@@ -10,7 +10,7 @@ index fcb7f54..cb1c7f7 100644
};
const description = this.getDescription(targetSpecificOptions);
if (!(0, builder_util_1.isEmptyOrSpaces)(description)) {
@@ -159,6 +160,23 @@ class LinuxTargetHelper {
@@ -160,6 +161,23 @@ class LinuxTargetHelper {
data += `\n${name}=${desktopMeta[name]}`;
}
data += "\n";

View File

@@ -1,23 +0,0 @@
diff --git a/node_modules/electron-trpc/dist/main.mjs b/node_modules/electron-trpc/dist/main.mjs
index 379cf3b..2644e5d 100644
--- a/node_modules/electron-trpc/dist/main.mjs
+++ b/node_modules/electron-trpc/dist/main.mjs
@@ -221,9 +221,16 @@ class G {
i(this, c).includes(r) || (i(this, c).push(r), I(this, T, W).call(this, r));
}
detachWindow(r) {
+
y(this, c, i(this, c).filter((n) => n !== r));
- for (const [n, t] of i(this, u).entries())
- n.startsWith(`${r.webContents.id}-`) && (t.unsubscribe(), i(this, u).delete(n));
+ for (const [n, t] of i(this, u).entries()) {
+ try {
+ n.startsWith(`${r.webContents.id}-`) && (t.unsubscribe(), i(this, u).delete(n));
+ } catch(e) {
+ console.error(e);
+ // ignore
+ }
+ }
}
}
c = new WeakMap(), u = new WeakMap(), T = new WeakSet(), W = function(r) {

View File

@@ -0,0 +1,39 @@
diff --git a/node_modules/electron-trpc/dist/main.mjs b/node_modules/electron-trpc/dist/main.mjs
index 07971db..b67a3cf 100644
--- a/node_modules/electron-trpc/dist/main.mjs
+++ b/node_modules/electron-trpc/dist/main.mjs
@@ -537,8 +537,15 @@ class be {
attachWindow(r) {
F(this, w).includes(r) || (y("Attaching window", r.id), F(this, w).push(r), v(this, N, Z).call(this, r));
}
- detachWindow(r) {
- y("Detaching window", r.id), L(this, w, F(this, w).filter((e) => e !== r)), v(this, R, M).call(this, { webContentsId: r.webContents.id });
+ detachWindow(r, webContentsId) {
+ y("Detaching window", r.id);
+
+ if (r.isDestroyed() && webContentsId === undefined) {
+ throw new Error('webContentsId is required when calling detachWindow on a destroyed window');
+ }
+
+ L(this, w, F(this, w).filter((e) => e !== r));
+ v(this, R, M).call(this, { webContentsId: webContentsId ?? r.webContents.id });
}
}
w = new WeakMap(), E = new WeakMap(), R = new WeakSet(), M = function({
@@ -548,6 +555,7 @@ w = new WeakMap(), E = new WeakMap(), R = new WeakSet(), M = function({
for (const [i, a] of F(this, E).entries())
i.startsWith(`${r}-${e ?? ""}`) && (y("Closing subscription", i), a.unsubscribe(), F(this, E).delete(i));
}, N = new WeakSet(), Z = function(r) {
+ const webContentsId = r.webContents.id;
r.webContents.on("did-start-navigation", ({ frame: e }) => {
y(
"Handling webContents `did-start-navigation` event",
@@ -558,7 +566,7 @@ w = new WeakMap(), E = new WeakMap(), R = new WeakSet(), M = function({
frameRoutingId: e.routingId
});
}), r.webContents.on("destroyed", () => {
- y("Handling webContents `destroyed` event"), this.detachWindow(r);
+ y("Handling webContents `destroyed` event"), this.detachWindow(r, webContentsId);
});
};
const me = ({

View File

@@ -1,46 +0,0 @@
diff --git a/node_modules/node-gyp-build/bin.js b/node_modules/node-gyp-build/bin.js
index 3fbcdf0..7ca3ab5 100644
--- a/node_modules/node-gyp-build/bin.js
+++ b/node_modules/node-gyp-build/bin.js
@@ -16,7 +16,8 @@ if (!buildFromSource()) {
}
function build () {
- var args = [os.platform() === 'win32' ? 'node-gyp.cmd' : 'node-gyp', 'rebuild']
+ var win32 = os.platform() === 'win32'
+ var args = [win32 ? 'node-gyp.cmd' : 'node-gyp', 'rebuild']
try {
var pkg = require('node-gyp/package.json')
@@ -27,7 +28,7 @@ function build () {
]
} catch (_) {}
- proc.spawn(args[0], args.slice(1), { stdio: 'inherit' }).on('exit', function (code) {
+ proc.spawn(args[0], args.slice(1), { stdio: 'inherit', shell: win32, windowsHide: true }).on('exit', function (code) {
if (code || !process.argv[3]) process.exit(code)
exec(process.argv[3]).on('exit', function (code) {
process.exit(code)
@@ -45,15 +46,18 @@ function preinstall () {
function exec (cmd) {
if (process.platform !== 'win32') {
- var shell = os.platform() === 'android' ? 'sh' : '/bin/sh'
- return proc.spawn(shell, ['-c', '--', cmd], {
+ var shell = os.platform() === 'android' ? 'sh' : true
+ return proc.spawn(cmd, [], {
+ shell,
stdio: 'inherit'
})
}
- return proc.spawn(process.env.comspec || 'cmd.exe', ['/s', '/c', '"' + cmd + '"'], {
+ return proc.spawn(cmd, [], {
windowsVerbatimArguments: true,
- stdio: 'inherit'
+ stdio: 'inherit',
+ shell: true,
+ windowsHide: true
})
}

View File

@@ -36,6 +36,20 @@ import { bringToFront } from "./utils/bring-to-front";
import { bridge } from "./api/bridge";
import { setupDesktopIntegration } from "./utils/desktop-integration";
import { disableCustomDns, enableCustomDns } from "./utils/custom-dns";
import { Messages, setI18nGlobal } from "@notesnook/intl";
import { i18n } from "@lingui/core";
const locale =
process.env.NODE_ENV === "development"
? import("@notesnook/intl/locales/$pseudo-LOCALE.json")
: import("@notesnook/intl/locales/$en.json");
locale.then(({ default: locale }) => {
i18n.load({
en: locale.messages as unknown as Messages
});
i18n.activate("en");
});
setI18nGlobal(i18n);
// only run a single instance
if (!MAC_APP_STORE && !app.requestSingleInstanceLock()) {

View File

@@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { strings } from "@notesnook/intl";
import { Menu, MenuItem, clipboard, shell } from "electron";
function setupMenu() {
@@ -40,7 +41,7 @@ function setupMenu() {
if (params.misspelledWord) {
menu.append(
new MenuItem({
label: "Add to dictionary",
label: strings.addToDictionary(),
click: () =>
globalThis.window?.webContents.session.addWordToSpellCheckerDictionary(
params.misspelledWord
@@ -59,7 +60,7 @@ function setupMenu() {
if (params.linkURL.length) {
menu.append(
new MenuItem({
label: "Open in browser",
label: strings.openInBrowser(),
click: () => shell.openExternal(params.linkURL)
})
);
@@ -68,7 +69,7 @@ function setupMenu() {
if (params.isEditable) {
menu.append(
new MenuItem({
label: "Undo",
label: strings.undo(),
role: "undo",
enabled: params.isEditable,
accelerator: "CommandOrControl+Z"
@@ -77,7 +78,7 @@ function setupMenu() {
menu.append(
new MenuItem({
label: "Redo",
label: strings.redo(),
role: "redo",
enabled: params.isEditable,
accelerator: "CommandOrControl+Y"
@@ -94,7 +95,7 @@ function setupMenu() {
if (params.isEditable)
menu.append(
new MenuItem({
label: "Cut",
label: strings.cut(),
role: "cut",
enabled: params.selectionText.length > 0,
accelerator: "CommandOrControl+X"
@@ -104,7 +105,7 @@ function setupMenu() {
if (params.linkURL?.length) {
menu.append(
new MenuItem({
label: "Copy link",
label: strings.copyLink(),
click() {
clipboard.writeText(params.linkURL);
}
@@ -113,7 +114,7 @@ function setupMenu() {
menu.append(
new MenuItem({
label: "Copy link text",
label: strings.copyLinkText(),
click() {
clipboard.writeText(params.linkText);
}
@@ -124,7 +125,7 @@ function setupMenu() {
if (params.selectionText.length) {
menu.append(
new MenuItem({
label: "Copy",
label: strings.copy(),
role: "copy",
accelerator: "CommandOrControl+C"
})
@@ -135,7 +136,7 @@ function setupMenu() {
menu.append(
new MenuItem({
id: "copy-image",
label: "Copy Image",
label: strings.copyImage(),
click() {
globalThis.window?.webContents.copyImageAt(params.x, params.y);
}
@@ -145,7 +146,7 @@ function setupMenu() {
if (params.isEditable)
menu.append(
new MenuItem({
label: "Paste",
label: strings.paste(),
role: "pasteAndMatchStyle",
enabled: clipboard.readText("clipboard").length > 0,
accelerator: "CommandOrControl+V"

View File

@@ -16,6 +16,8 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { i18n } from "@lingui/core";
import { I18nProvider } from "@lingui/react";
import {
THEME_COMPATIBILITY_VERSION,
useThemeEngineStore
@@ -63,7 +65,9 @@ const App = () => {
<View
style={{
height: "100%",
width: "100%"
width: "100%",
justifyContent: "center",
alignItems: "center"
}}
>
<View
@@ -138,7 +142,11 @@ export const withTheme = (Element: () => JSX.Element) => {
currTheme = nextTheme;
}
return <Element />;
return (
<I18nProvider i18n={i18n}>
<Element />
</I18nProvider>
);
};
};

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import "./logger";
import { database } from "@notesnook/common";
import { logger as dbLogger } from "@notesnook/core/dist/logger";
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";
@@ -33,11 +33,11 @@ import Storage from "./storage";
import { RNSqliteDriver } from "./sqlite.kysely";
import { getDatabaseKey } from "./encryption";
import SettingsService from "../../services/settings";
import { strings } from "@notesnook/intl";
export async function setupDatabase(password) {
const key = await getDatabaseKey(password);
if (!key)
throw new Error("Database setup failed, could not get database key");
if (!key) throw new Error(strings.databaseSetupFailed());
console.log("Opening database with key:", !!key);

View File

@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { initialize } from "@notesnook/core/dist/logger";
import { initialize } from "@notesnook/core";
import {
SqliteAdapter,
SqliteIntrospector,

View File

@@ -24,6 +24,7 @@ import type {
} from "@streetwriters/kysely";
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 };
@@ -97,7 +98,7 @@ class RNSqliteConnection implements DatabaseConnection {
constructor(private readonly db: QuickSQLiteConnection) {}
streamQuery<R>(): AsyncIterableIterator<QueryResult<R>> {
throw new Error("wasqlite driver doesn't support streaming");
throw new Error(strings.streamingNotSupported());
}
async executeQuery<R>(

View File

@@ -18,7 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Sodium from "@ammarahmed/react-native-sodium";
import { getFileNameWithExtension } from "@notesnook/core/dist/utils/filename";
import { getFileNameWithExtension } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import React from "react";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
@@ -54,15 +55,10 @@ export async function downloadAllAttachments() {
* @param onProgress
* @returns
*/
export async function downloadAttachments(
attachments,
onProgress,
canceled,
groupId
) {
export async function downloadAttachments(attachments) {
await createCacheDir();
if (!attachments || !attachments.length) return;
const result = new Map();
const groupId = `download-all-${Date.now()}`;
let outputFolder;
if (Platform.OS === "android") {
@@ -86,92 +82,117 @@ export async function downloadAttachments(
await RNFetchBlob.fs.mkdir(zipSourceFolder);
const isCancelled = () => {
if (useAttachmentStore.getState().downloading[groupId]?.canceled) {
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
useAttachmentStore.getState().setDownloading({
groupId,
current: 0,
total: 0,
success: false,
message: strings.network.dowloadCancelled(),
canceled: true
});
return true;
}
};
for (let i = 0; i < attachments.length; i++) {
if (isCancelled()) return;
let attachment = await db.attachments.attachment(attachments[i]);
const hash = attachment.hash;
try {
if (canceled.current) {
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
return;
}
onProgress?.(
i + 1 / attachments.length,
`Downloading attachments (${i + 1}/${
attachments.length
})... Please wait`
);
useAttachmentStore.getState().setDownloading({
groupId: groupId,
current: i + 1,
total: attachments.length,
filename: attachment.hash
});
// Download to cache
let uri = await downloadAttachment(hash, false, {
silent: true,
cache: true,
groupId: groupId
});
if (canceled.current) {
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
return;
}
if (!uri) throw new Error("Failed to download file");
if (isCancelled()) return;
if (!uri) throw new Error(strings.failedToDownloadFile());
// Move file to the source folder we will zip eventually and rename the file to it's actual name.
const filePath = `${zipSourceFolder}/${attachment.filename}`;
await RNFetchBlob.fs.mv(`${cacheDir}/${uri}`, filePath);
result.set(hash, {
filename: attachment.filename,
status: FileDownloadStatus.Success,
attachment: attachment
});
} catch (e) {
result.set(hash, {
filename: attachment.filename,
status: FileDownloadStatus.Fail,
reason: e
});
ToastManager.error(e, "Error downloading attachment");
DatabaseLogger.error(e);
}
}
if (canceled.current) {
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
return;
}
if (result?.size) {
let sub;
try {
onProgress?.(0, `Zipping... Please wait`);
// If all goes well, zip the notesnook-attachments folder in cache.
sub = subscribe(({ progress }) => {
onProgress(
progress,
`Saving zip file (${(progress * 100).toFixed(1)}%)... Please wait`
);
useAttachmentStore.getState().setDownloading({
groupId: groupId,
current: 0,
total: 0,
success: true
});
if (isCancelled()) return;
let sub;
try {
useAttachmentStore.getState().setDownloading({
current: 0,
total: 1,
message: `${strings.savingZipFile()}... ${strings.pleaseWait()}`,
groupId
});
// If all goes well, zip the notesnook-attachments folder in cache.
sub = subscribe(({ progress }) => {
useAttachmentStore.getState().setDownloading({
groupId,
current: progress,
total: 1,
message: `${strings.savingZipFile()} (${(progress * 100).toFixed(
1
)}%)... ${strings.pleaseWait()}`
});
await zip(zipSourceFolder, zipOutputFile);
sub?.remove();
onProgress(1, `Saving zip file... Please wait`);
if (Platform.OS === "android") {
// Move the zip to user selected directory.
const file = await ScopedStorage.createFile(
outputFolder,
`notesnook-attachments-${Date.now()}.zip`,
"application/zip"
);
await copyFileAsync(`file://${zipOutputFile}`, file.uri);
}
});
await zip(zipSourceFolder, zipOutputFile);
sub?.remove();
onProgress?.(1, `Done`);
releasePermissions(outputFolder);
} catch (e) {
releasePermissions(outputFolder);
sub?.remove();
ToastManager.error(e, "Error zipping attachments");
}
// Remove source & zip file from cache.
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
if (Platform.OS === "android") {
RNFetchBlob.fs.unlink(zipOutputFile).catch(console.log);
// Move the zip to user selected directory.
const file = await ScopedStorage.createFile(
outputFolder,
`notesnook-attachments-${Date.now()}.zip`,
"application/zip"
);
await copyFileAsync(`file://${zipOutputFile}`, file.uri);
}
}
return result;
useAttachmentStore.getState().setDownloading({
current: 0,
total: 0,
message: undefined,
success: true,
groupId
});
releasePermissions(outputFolder);
} catch (e) {
useAttachmentStore.getState().setDownloading({
current: 0,
total: 0,
message: undefined,
success: false,
groupId
});
releasePermissions(outputFolder);
sub?.remove();
ToastManager.error(e, "Error zipping attachments");
}
// Remove source & zip file from cache.
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
if (Platform.OS === "android") {
RNFetchBlob.fs.unlink(zipOutputFile).catch(console.log);
}
}
export default async function downloadAttachment(
@@ -253,8 +274,8 @@ export default async function downloadAttachment(
if (!options.silent) {
ToastManager.show({
heading: "Download successful",
message: filename + " downloaded",
heading: strings.network.downloadSuccess(),
message: strings.network.fileDownloaded(filename),
type: "success"
});
}
@@ -264,12 +285,8 @@ export default async function downloadAttachment(
}
if (!options.silent) {
presentSheet({
title: "File downloaded",
paragraph: `${filename} saved to ${
Platform.OS === "android"
? "selected path"
: "File Manager/Notesnook/downloads"
}`,
title: strings.network.fileDownloaded(),
paragraph: strings.fileSaved(filename, Platform.OS),
icon: "download",
context: global ? null : attachment.hash,
component: <ShareComponent uri={fileUri} name={filename} padding={12} />

View File

@@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { strings } from "@notesnook/intl";
import NetInfo from "@react-native-community/netinfo";
import RNFetchBlob from "react-native-blob-util";
import { ToastManager } from "../../services/event-manager";
@@ -43,16 +44,17 @@ export async function downloadFile(filename, requestOptions, cancelToken) {
DatabaseLogger.log(`File Exists already: ${filename}`);
return true;
}
const attachment = await db.attachments.attachment(filename);
const size = await getUploadedFileSize(filename);
if (size === -1) {
const error = `Uploaded file verification failed. (File hash: ${filename})`;
const error = `${strings.fileVerificationFailed()} (File hash: ${filename})`;
throw new Error(error);
}
if (size === 0) {
const error = `File length is 0. Please upload this file again from the attachment manager. (File hash: ${filename})`;
const error = `${strings.fileLengthError()} (File hash: ${filename})`;
await db.attachments.markAsFailed(attachment.id, error);
throw new Error(error);
}
@@ -61,7 +63,10 @@ export async function downloadFile(filename, requestOptions, cancelToken) {
const decryptedLength = size - totalChunks * ABYTES;
if (attachment && attachment.size !== decryptedLength) {
const error = `File length mismatch. Expected ${attachment.size} but got ${decryptedLength} bytes. Please upload this file again from the attachment manager. (File hash: ${filename})`;
const error = `${strings.fileLengthMismatch(
attachment.size,
decryptedLength
)} (File hash: ${filename})`;
await db.attachments.markAsFailed(attachment.id, error);
throw new Error(error);
}
@@ -75,7 +80,9 @@ export async function downloadFile(filename, requestOptions, cancelToken) {
DatabaseLogger.log(
`Error downloading file: ${filename}, ${res.status}, ${res.statusText}, reason: Unable to resolve download url`
);
throw new Error(`${res.status}: Unable to resolve download url`);
throw new Error(
`${res.status}: ${strings.failedToResolvedDownloadUrl()}`
);
}
const downloadUrl = await res.text();
@@ -84,13 +91,14 @@ export async function downloadFile(filename, requestOptions, cancelToken) {
DatabaseLogger.log(
`Error downloading file: ${filename}, reason: Unable to resolve download url`
);
throw new Error("Unable to resolve download url");
throw new Error(strings.failedToResolvedDownloadUrl());
}
DatabaseLogger.log(`Download starting: ${filename}`);
let request = RNFetchBlob.config({
path: tempFilePath,
IOSBackgroundTask: true
IOSBackgroundTask: true,
overwrite: true
})
.fetch("GET", downloadUrl, null)
.progress(async (recieved, total) => {
@@ -122,6 +130,11 @@ export async function downloadFile(filename, requestOptions, cancelToken) {
let status = response.info().status;
useAttachmentStore.getState().remove(filename);
if (exists(originalFilePath)) {
await RNFetchBlob.fs.unlink(originalFilePath).catch(console.log);
}
await RNFetchBlob.fs.mv(tempFilePath, originalFilePath);
if (!(await exists(filename))) {
@@ -131,18 +144,15 @@ export async function downloadFile(filename, requestOptions, cancelToken) {
return status >= 200 && status < 300;
} catch (e) {
if (e.message !== "canceled" && !e.message.includes("NoSuchKey")) {
ToastManager.show({
heading: "Error downloading file",
const toast = {
heading: strings.downloadError(),
message: e.message,
type: "error",
context: "global"
});
ToastManager.show({
heading: "Error downloading file",
message: e.message,
type: "error",
context: "local"
});
};
ToastManager.show(toast);
toast.context = "local";
ToastManager.show(toast);
}
useAttachmentStore.getState().remove(filename);

View File

@@ -126,9 +126,13 @@ export async function clearFileStorage() {
}
export async function createCacheDir() {
if (!(await RNFetchBlob.fs.exists(cacheDir))) {
await RNFetchBlob.fs.mkdir(cacheDir);
DatabaseLogger.log("Cache directory created");
try {
if (!(await RNFetchBlob.fs.exists(cacheDir))) {
await RNFetchBlob.fs.mkdir(cacheDir);
DatabaseLogger.log("Cache directory created");
}
} catch (e) {
DatabaseLogger.error(e);
}
}
@@ -231,22 +235,30 @@ export async function exists(filename) {
}
export async function bulkExists(files) {
const cacheFiles = await RNFetchBlob.fs.ls(cacheDir);
let missingFiles = files.filter((file) => !cacheFiles.includes(file));
try {
await createCacheDir();
const cacheFiles = await RNFetchBlob.fs.ls(cacheDir);
let missingFiles = files.filter((file) => !cacheFiles.includes(file));
if (Platform.OS === "ios") {
const iosAppGroup =
Platform.OS === "ios"
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
: null;
const appGroupFiles = await RNFetchBlob.fs.ls(iosAppGroup);
missingFiles = missingFiles.filter((file) => !appGroupFiles.includes(file));
if (Platform.OS === "ios") {
const iosAppGroup =
Platform.OS === "ios"
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
: null;
const appGroupFiles = await RNFetchBlob.fs.ls(iosAppGroup);
missingFiles = missingFiles.filter(
(file) => !appGroupFiles.includes(file)
);
}
return missingFiles;
} catch (e) {
DatabaseLogger.error(e);
return [];
}
return missingFiles;
}
export async function getCacheSize() {
await createCacheDir();
const stat = await RNFetchBlob.fs.lstat(`file://` + cacheDir);
let total = 0;
console.log("Total files", stat.length);

View File

@@ -24,7 +24,7 @@ import { useAttachmentStore } from "../../stores/use-attachment-store";
import { IOS_APPGROUPID } from "../../utils/constants";
import { DatabaseLogger, db } from "../database";
import { createCacheDir } from "./io";
import { cacheDir, getUploadedFileSize } from "./utils";
import { cacheDir, checkUpload, getUploadedFileSize } from "./utils";
export async function uploadFile(filename, requestOptions, cancelToken) {
if (!requestOptions) return false;
@@ -33,24 +33,42 @@ export async function uploadFile(filename, requestOptions, cancelToken) {
DatabaseLogger.info(`Preparing to upload file: ${filename}`);
try {
const uploadedFileSize = await getUploadedFileSize(filename);
if (uploadedFileSize === -1) {
const error = `Uploaded file verification failed. (File hash: ${filename})`;
throw new Error(error);
let filePath = `${cacheDir}/${filename}`;
let exists = await RNFetchBlob.fs.exists(filePath);
// Check for file in appGroupPath if it doesn't exist in cacheDir
if (!exists && Platform.OS === "ios") {
const iosAppGroup =
Platform.OS === "ios"
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
: null;
const appGroupPath = `${iosAppGroup}/${filename}`;
filePath = appGroupPath;
exists = await RNFetchBlob.fs.exists(filePath);
}
if (uploadedFileSize !== 0) {
if (!exists) {
throw new Error(
`Trying to upload file at path ${filePath} that doest not exist.`
);
}
const fileSize = (await RNFetchBlob.fs.stat(filePath)).size;
let remoteFileSize = await getUploadedFileSize(filename);
if (remoteFileSize === -1) return false;
if (remoteFileSize > 0 && remoteFileSize === fileSize) {
DatabaseLogger.log(`File ${filename} is already uploaded.`);
return true;
}
let res = await fetch(url, {
let uploadUrlResponse = await fetch(url, {
method: "PUT",
headers
});
const uploadUrl = res.ok ? await res.text() : await res.json();
const uploadUrl = uploadUrlResponse.ok
? await uploadUrlResponse.text()
: await uploadUrlResponse.json();
if (typeof uploadUrl !== "string") {
throw new Error(
@@ -58,28 +76,9 @@ export async function uploadFile(filename, requestOptions, cancelToken) {
);
}
let uploadFilePath = `${cacheDir}/${filename}`;
const iosAppGroup =
Platform.OS === "ios"
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
: null;
const appGroupPath = `${iosAppGroup}/${filename}`;
let exists = await RNFetchBlob.fs.exists(uploadFilePath);
if (!exists && Platform.OS === "ios") {
uploadFilePath = appGroupPath;
exists = await RNFetchBlob.fs.exists(uploadFilePath);
}
if (!exists) {
throw new Error(
`Trying to upload file at path ${uploadFilePath} that doest not exist.`
);
}
DatabaseLogger.info(`Starting upload: ${filename}`);
let request = RNFetchBlob.config({
let uploadRequest = RNFetchBlob.config({
IOSBackgroundTask: !globalThis["IS_SHARE_EXTENSION"]
})
.fetch(
@@ -88,7 +87,7 @@ export async function uploadFile(filename, requestOptions, cancelToken) {
{
"content-type": ""
},
RNFetchBlob.wrap(uploadFilePath)
RNFetchBlob.wrap(filePath)
)
.uploadProgress((sent, total) => {
useAttachmentStore
@@ -101,30 +100,27 @@ export async function uploadFile(filename, requestOptions, cancelToken) {
cancelToken.cancel = () => {
useAttachmentStore.getState().remove(filename);
request.cancel();
uploadRequest.cancel();
};
let response = await request;
let status = response.info().status;
let text = await response.text();
let result = status >= 200 && status < 300 && text.length === 0;
let uploadResponse = await uploadRequest;
let status = uploadResponse.info().status;
let uploaded = status >= 200 && status < 300;
useAttachmentStore.getState().remove(filename);
if (result) {
DatabaseLogger.info(
`File upload status: ${filename}, ${status}, ${text}`
);
let attachment = await db.attachments.attachment(filename);
if (!attachment) return result;
} else {
const fileInfo = await RNFetchBlob.fs.stat(uploadFilePath);
if (!uploaded) {
const fileInfo = await RNFetchBlob.fs.stat(filePath);
throw new Error(
`${status}, ${text}, name: ${fileInfo.filename}, length: ${
`${status}, name: ${fileInfo.filename}, length: ${
fileInfo.size
}, info: ${JSON.stringify(response.info())}`
}, info: ${JSON.stringify(uploadResponse.info())}`
);
}
return result;
const attachment = await db.attachments.attachment(filename);
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, "File upload failed");

View File

@@ -21,7 +21,7 @@ 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/dist/utils/constants";
import { hosts } from "@notesnook/core";
export const ABYTES = 17;
export const cacheDirOld = RNFetchBlob.fs.dirs.CacheDir;
@@ -123,3 +123,18 @@ export async function getUploadedFileSize(hash) {
return -1;
}
}
export async function checkUpload(filename, chunkSize, expectedSize) {
const size = await getUploadedFileSize(filename);
const totalChunks = Math.ceil(size / chunkSize);
const decryptedLength = size - totalChunks * ABYTES;
const error =
size === 0
? `File size is 0.`
: size === -1
? `File verification check failed.`
: expectedSize !== decryptedLength
? `File size mismatch. Expected ${size} bytes but got ${decryptedLength} bytes.`
: undefined;
if (error) throw new Error(error);
}

View File

@@ -53,6 +53,7 @@ import Seperator from "../ui/seperator";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
import { strings } from "@notesnook/intl";
const getUser = () => {
const user = MMKV.getString("user");
@@ -157,7 +158,7 @@ const AppLockedOverlay = () => {
password.current = undefined;
} else {
ToastManager.show({
heading: `Invalid ${keyboardType === "numeric" ? "pin" : "password"}`,
heading: strings.invalid(keyboardType),
type: "error",
context: "local"
});
@@ -250,7 +251,7 @@ const AppLockedOverlay = () => {
textAlign: "center"
}}
>
Unlock your notes
{strings.unlockNotes()}
</Heading>
<Paragraph
@@ -260,7 +261,7 @@ const AppLockedOverlay = () => {
maxWidth: "90%"
}}
>
{"Please verify it's you"}
{strings.verifyItsYou()}
</Paragraph>
<Seperator />
<View
@@ -278,13 +279,13 @@ const AppLockedOverlay = () => {
keyboardType={
appLockHasPasswordSecurity ? keyboardType : "default"
}
placeholder={`Enter ${
placeholder={
appLockHasPasswordSecurity
? `app lock ${
keyboardType === "numeric" ? "pin" : "password"
}`
: "account password"
}`}
? keyboardType === "numeric"
? strings.enterApplockPassword()
: strings.enterApplockPin()
: strings.enterAccountPassword()
}
onChangeText={(v) => (password.current = v)}
onSubmit={() => {
onSubmit();
@@ -301,7 +302,7 @@ const AppLockedOverlay = () => {
{user || appLockHasPasswordSecurity ? (
<>
<Button
title="Continue"
title={strings.continue()}
type="accent"
onPress={onSubmit}
width={250}
@@ -317,7 +318,7 @@ const AppLockedOverlay = () => {
{biometricsAuthEnabled ? (
<Button
title="Unlock with Biometrics"
title={strings.unlockWithBiometrics()}
width={250}
onPress={onUnlockAppRequested}
icon={"fingerprint"}

View File

@@ -30,6 +30,8 @@ import filesystem from "../../common/filesystem";
import downloadAttachment from "../../common/filesystem/download-attachment";
import { useAttachmentProgress } from "../../hooks/use-attachment-progress";
import picker from "../../screens/editor/tiptap/picker";
import { useTabStore } from "../../screens/editor/tiptap/use-tab-store";
import { editorController } from "../../screens/editor/tiptap/utils";
import {
ToastManager,
eSendEvent,
@@ -49,14 +51,13 @@ import { Dialog } from "../dialog";
import { presentDialog } from "../dialog/functions";
import { openNote } from "../list-items/note/wrapper";
import { DateMeta } from "../properties/date-meta";
import SheetProvider from "../sheet-provider";
import { Button } from "../ui/button";
import { Notice } from "../ui/notice";
import { Pressable } from "../ui/pressable";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { useTabStore } from "../../screens/editor/tiptap/use-tab-store";
import { editorController } from "../../screens/editor/tiptap/utils";
import SheetProvider from "../sheet-provider";
import { strings } from "@notesnook/intl";
const Actions = ({
attachment,
@@ -81,23 +82,23 @@ const Actions = ({
const actions = [
{
name: "Download",
name: strings.network.download(),
onPress: async () => {
if (currentProgress) {
await db.fs().cancel(attachment.hash);
useAttachmentStore.getState().remove(attachment.hash);
}
downloadAttachment(attachment.hash, false);
eSendEvent(eCloseSheet, contextId);
fwdRef.current?.hide();
},
icon: "download"
},
{
name: "Reupload",
name: strings.network.reupload(),
onPress: async () => {
if (!PremiumService.get()) {
ToastManager.show({
heading: "Upgrade to pro",
heading: strings.upgradeToPro(),
type: "error",
context: "local"
});
@@ -113,10 +114,10 @@ const Actions = ({
icon: "upload"
},
{
name: "Run file check",
name: strings.fileCheck(),
onPress: async () => {
setLoading({
name: "Run file check"
name: strings.fileCheck()
});
const result = await filesystem.checkAttachment(attachment.hash);
if (!result) return;
@@ -125,7 +126,7 @@ const Actions = ({
db.attachments.markAsFailed(attachment.id, result.failed);
setFailed(result.failed);
ToastManager.show({
heading: "File check failed with error: " + result.failed,
heading: strings.fileCheckFailed(result.failed),
type: "error",
context: "local"
});
@@ -134,7 +135,7 @@ const Actions = ({
db.attachments.markAsFailed(attachment.id);
eSendEvent(eDBItemUpdate, attachment.id);
ToastManager.show({
heading: "File check passed",
heading: strings.fileCheckPassed(),
type: "success",
context: "local"
});
@@ -148,13 +149,11 @@ const Actions = ({
icon: "file-check"
},
{
name: "Rename",
name: strings.rename(),
onPress: () => {
presentDialog({
context: contextId as any,
input: true,
title: "Rename file",
paragraph: "Enter a new name for the file",
title: strings.renameFile(),
defaultValue: attachment.filename,
positivePress: async (value) => {
if (value && value.length > 0) {
@@ -167,13 +166,13 @@ const Actions = ({
eSendEvent(eDBItemUpdate, attachment.id);
}
},
positiveText: "Rename"
positiveText: strings.rename()
});
},
icon: "form-textbox"
},
{
name: "Delete",
name: strings.delete(),
onPress: async () => {
const relations = await db.relations.to(attachment, "note").get();
await db.attachments.remove(attachment.hash, false);
@@ -219,6 +218,7 @@ const Actions = ({
style={{
maxHeight: "100%"
}}
keyboardShouldPersistTaps="never"
>
<Dialog context={contextId} />
<SheetProvider context={contextId} />
@@ -256,8 +256,7 @@ const Actions = ({
{notes.length ? (
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
{notes.length} note
{notes.length > 1 ? "s" : ""}
{strings.notes(notes.length)}
</Paragraph>
) : null}
<Paragraph
@@ -265,7 +264,7 @@ const Actions = ({
Clipboard.setString(attachment.hash);
ToastManager.show({
type: "success",
heading: "Attachment hash copied",
heading: strings.hashCopied(),
context: "local"
});
}}
@@ -295,7 +294,7 @@ const Actions = ({
}}
size={SIZE.sm}
>
List of notes:
{strings.listOf()} {strings.dataTypesPlural.note()}:
</Heading>
{notes.map((item) => (
@@ -354,7 +353,7 @@ const Actions = ({
{failed ? (
<Notice
type="alert"
text={`File check failed: ${failed} Try reuploading the file to fix the issue.`}
text={strings.fileCheckFailed(failed)}
size="small"
/>
) : null}

View File

@@ -22,7 +22,6 @@ import { Attachment, VirtualizedGrouping } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { TouchableOpacity, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import { useAttachmentProgress } from "../../hooks/use-attachment-progress";
import { useDBItem } from "../../hooks/use-db-item";
@@ -31,6 +30,7 @@ import { IconButton } from "../ui/icon-button";
import { ProgressCircleComponent } from "../ui/svg/lazy";
import Paragraph from "../ui/typography/paragraph";
import Actions from "./actions";
import { strings } from "@notesnook/intl";
function getFileExtension(filename: string) {
const ext = /^.+\.([^.]+)$/.exec(filename);
@@ -44,7 +44,8 @@ export const AttachmentItem = ({
setAttachments,
pressable = true,
hideWhenNotDownloading,
context
context,
errorOnly
}: {
id: string | number;
attachments?: VirtualizedGrouping<Attachment>;
@@ -53,9 +54,9 @@ export const AttachmentItem = ({
pressable?: boolean;
hideWhenNotDownloading?: boolean;
context?: string;
errorOnly?: boolean;
}) => {
const [attachment] = useDBItem(id, "attachment", attachments);
const { colors } = useThemeColors();
const [currentProgress, setCurrentProgress] = useAttachmentProgress(
attachment,
@@ -67,8 +68,7 @@ export const AttachmentItem = ({
Actions.present(attachment, setAttachments, context);
};
return hideWhenNotDownloading &&
(!currentProgress || !currentProgress.value) ? null : (
return errorOnly && attachment && !attachment?.failed ? null : (
<TouchableOpacity
activeOpacity={0.9}
onPress={onPress}
@@ -78,8 +78,6 @@ export const AttachmentItem = ({
justifyContent: "space-between",
padding: 12,
paddingVertical: 6,
borderRadius: 5,
backgroundColor: colors.secondary.background,
minHeight: 45
}}
>
@@ -89,41 +87,46 @@ export const AttachmentItem = ({
style={{
flexShrink: 1,
flexDirection: "row",
alignItems: "center"
alignItems: "center",
gap: 10
}}
>
<View
style={{
justifyContent: "center",
alignItems: "center",
marginLeft: -5
marginLeft: -5,
borderWidth: 1,
borderColor: colors.secondary.border,
paddingHorizontal: 2,
width: 30,
height: 30,
borderRadius: 5
}}
>
<Icon name="file" size={SIZE.xxxl} color={colors.primary.icon} />
<Paragraph
adjustsFontSizeToFit
size={6}
color={colors.static.white}
size={8}
color={colors.secondary.paragraph}
style={{
position: "absolute"
maxWidth: 30
}}
numberOfLines={1}
>
{getFileExtension(attachment.filename).toUpperCase()}
{getFileExtension(attachment.filename).toUpperCase() ||
attachment.mimeType.split("/")?.[1]?.toUpperCase()}
</Paragraph>
</View>
<View
style={{
flexShrink: 1,
marginLeft: 10
flexShrink: 1
}}
>
<Paragraph
size={SIZE.sm - 1}
size={SIZE.sm}
style={{
flexWrap: "wrap",
marginBottom: 2.5
flexWrap: "wrap"
}}
numberOfLines={1}
lineBreakMode="middle"
@@ -133,11 +136,8 @@ export const AttachmentItem = ({
</Paragraph>
{!hideWhenNotDownloading ? (
<Paragraph color={colors.secondary.paragraph} size={SIZE.xs}>
{formatBytes(attachment.size)}{" "}
{currentProgress?.type
? "(" + currentProgress.type + "ing - tap to cancel)"
: ""}
<Paragraph color={colors.secondary.paragraph} size={SIZE.xxs}>
{strings.fileSize()}: {formatBytes(attachment.size)}
</Paragraph>
) : null}
</View>

View File

@@ -1,297 +0,0 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useRef, useState } from "react";
import { Platform, View } from "react-native";
import { db } from "../../common/database";
import { downloadAttachments } from "../../common/filesystem/download-attachment";
import {
PresentSheetOptions,
presentSheet
} from "../../services/event-manager";
import { Button } from "../ui/button";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { ProgressBarComponent } from "../ui/svg/lazy";
import { useThemeColors } from "@notesnook/theme";
import { FlatList } from "react-native-actions-sheet";
import { AttachmentItem } from "./attachment-item";
import { Attachment, VirtualizedGrouping } from "@notesnook/core";
const DownloadAttachments = ({
close,
attachments,
isNote,
update
}: {
attachments: VirtualizedGrouping<Attachment>;
close?: ((ctx?: string | undefined) => void) | undefined;
isNote?: boolean;
update?: (props: PresentSheetOptions) => void;
}) => {
const { colors } = useThemeColors();
const [downloading, setDownloading] = useState(false);
const [progress, setProgress] = useState({
value: 0,
statusText: "Download started... Please wait"
});
const [result, setResult] = useState(new Map());
const canceled = useRef(false);
const groupId = useRef<string>();
const onDownload = async () => {
update?.({
disableClosing: true
} as PresentSheetOptions);
setDownloading(true);
canceled.current = false;
groupId.current = Date.now().toString();
const result = await downloadAttachments(
await attachments.ids(),
(progress: number, statusText: string) =>
setProgress({ value: progress, statusText }),
canceled,
groupId.current
);
if (canceled.current) return;
setResult(result || new Map());
setDownloading(false);
update?.({
disableClosing: false
} as PresentSheetOptions);
};
const cancel = async () => {
update?.({
disableClosing: false
} as PresentSheetOptions);
canceled.current = true;
if (!groupId.current) return;
console.log(groupId.current, "canceling groupId downloads");
await db.fs().cancel(groupId.current);
setDownloading(false);
setResult(new Map());
groupId.current = undefined;
};
const failedResults = () => {
const results = [];
for (const value of result.values()) {
if (value.status === 0) results.push(value.attachment);
}
return results;
};
function getResultText() {
const downloadedAttachmentsCount =
attachments?.placeholders?.length - failedResults().length;
if (downloadedAttachmentsCount === 0)
return "Failed to download all attachments";
return `Successfully downloaded ${downloadedAttachmentsCount}/${
attachments?.placeholders.length
} attachments as a zip file at ${
Platform.OS === "android" ? "the selected folder" : "Notesnook/downloads"
}`;
}
return (
<View
style={{
alignItems: "center",
width: "100%",
paddingVertical: 12,
paddingHorizontal: 12
}}
>
<Heading>
{downloading
? "Downloading attachments"
: result?.size
? "Downloaded attachments"
: "Download attachments"}
</Heading>
{downloading ? (
<Paragraph
style={{
textAlign: "center"
}}
>
{progress.statusText}
</Paragraph>
) : result?.size ? (
<Paragraph
style={{
textAlign: "center"
}}
>
{getResultText()}
</Paragraph>
) : (
<Paragraph
style={{
textAlign: "center"
}}
>
Are you sure you want to download all attachments
{isNote ? " of this note?" : "?"}
</Paragraph>
)}
{downloading ? (
<View
style={{
width: 200,
marginTop: 10
}}
>
<ProgressBarComponent
height={5}
width={null}
animated={true}
useNativeDriver
progress={
progress.value
? progress.value / attachments.placeholders?.length
: 0
}
unfilledColor={colors.secondary.background}
color={colors.primary.accent}
borderWidth={0}
/>
</View>
) : null}
<FlatList
style={{
maxHeight: 300,
width: "100%",
minHeight: 60,
backgroundColor: colors.secondary.background,
borderRadius: 5,
marginVertical: 12
}}
data={downloading ? attachments.placeholders : undefined}
ListEmptyComponent={
<View
style={{
width: "100%",
justifyContent: "center",
alignItems: "center",
height: 60
}}
>
<Paragraph color={colors.secondary.paragraph}>
No downloads in progress.
</Paragraph>
</View>
}
keyExtractor={(index) => "attachment_download" + index}
renderItem={({ index }) => {
return (
<AttachmentItem
id={index}
setAttachments={() => {}}
pressable={false}
hideWhenNotDownloading={true}
attachments={attachments}
/>
);
}}
/>
{result?.size ? (
<Button
style={{
width: 250,
borderRadius: 100,
marginTop: 20
}}
onPress={() => {
close?.();
}}
type="accent"
title="Done"
/>
) : !downloading ? (
<View
style={{
flexDirection: "row",
width: "100%",
marginTop: 20
}}
>
<Button
style={{
flex: 1,
borderRadius: 100,
marginRight: 5
}}
onPress={() => {
close?.();
}}
type="secondary"
title="No"
/>
<Button
style={{
flex: 1,
borderRadius: 100,
marginLeft: 5
}}
onPress={onDownload}
type="accent"
title="Yes"
/>
</View>
) : (
<Button
style={{
width: 250,
borderRadius: 100,
marginTop: 20
}}
onPress={cancel}
type="error"
title="Cancel"
/>
)}
</View>
);
};
DownloadAttachments.present = (
context: string,
attachments: VirtualizedGrouping<Attachment>,
isNote?: boolean
) => {
presentSheet({
context: context,
component: (ref, close, update) => (
<DownloadAttachments
close={close}
attachments={attachments}
isNote={isNote}
update={update}
/>
)
});
};
export default DownloadAttachments;

View File

@@ -19,21 +19,29 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import {
Attachment,
FilteredSelector,
Note,
SortOptions,
VirtualizedGrouping
} from "@notesnook/core";
import { FilteredSelector } from "@notesnook/core/dist/database/sql-collection";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef, useState } from "react";
import { ActivityIndicator, ScrollView, View } from "react-native";
import { ActivityIndicator, View } from "react-native";
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
import { ScrollView } from "react-native-gesture-handler";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import create, { State } from "zustand";
import { db } from "../../common/database";
import filesystem from "../../common/filesystem";
import { presentSheet } from "../../services/event-manager";
import { useSettingStore } from "../../stores/use-setting-store";
import { downloadAttachments } from "../../common/filesystem/download-attachment";
import { AttachmentGroupProgress } from "../../screens/settings/attachment-group-progress";
import { presentSheet, ToastManager } from "../../services/event-manager";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { SIZE } from "../../utils/size";
import { Dialog } from "../dialog";
import { presentDialog } from "../dialog/functions";
import { Header } from "../header";
import SheetProvider from "../sheet-provider";
import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
@@ -42,22 +50,105 @@ import Seperator from "../ui/seperator";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { AttachmentItem } from "./attachment-item";
import DownloadAttachments from "./download-attachments";
const DEFAULT_SORTING: SortOptions = {
sortBy: "dateEdited",
sortDirection: "desc"
};
export const AttachmentDialog = ({ note }: { note?: Note }) => {
type RecheckerProgress = {
failed: number;
passed: number;
isWorking: boolean;
shown: boolean;
filter: string;
};
interface RecheckerState extends State {
progress: {
[key: string]: RecheckerProgress;
};
setProgress: (key: string, progress: RecheckerProgress) => void;
currentFilter: string;
}
const useRechecker = create<RecheckerState>((set) => ({
progress: {
all: {
failed: 0,
passed: 0,
isWorking: false,
shown: false,
filter: "all"
}
},
currentFilter: "all",
setProgress: (key: string, progress: RecheckerProgress) => {
set((state) => ({
progress: {
...state.progress,
[key]: progress
}
}));
}
}));
const attachmentTypes = [
{
title: strings.mediaTypes.all(),
filterBy: "all"
},
{
title: strings.mediaTypes.image(),
filterBy: "images"
},
{
title: strings.mediaTypes.audio(),
filterBy: "audio"
},
{
title: strings.mediaTypes.video(),
filterBy: "video"
},
{
title: strings.mediaTypes.document(),
filterBy: "documents"
},
{
title: strings.mediaTypes.orphaned(),
filterBy: "orphaned"
},
{
title: strings.mediaTypes.errors(),
filterBy: "errors"
}
];
export const AttachmentDialog = ({
note,
isSheet
}: {
note?: Note;
isSheet: boolean;
}) => {
const { colors } = useThemeColors();
const { height } = useSettingStore((state) => state.dimensions);
const [attachments, setAttachments] =
useState<VirtualizedGrouping<Attachment>>();
const attachmentSearchValue = useRef<string>();
const [loading, setLoading] = useState(true);
const searchTimer = useRef<NodeJS.Timeout>();
const [loading, setLoading] = useState(false);
const [currentFilter, setCurrentFilter] = useState("all");
const rechecker = useRechecker((state) =>
note ? state.progress[note.id] || {} : state.progress.all
);
const currentFilterRef = useRef(currentFilter);
currentFilterRef.current = currentFilter;
useEffect(() => {
useRechecker.setState({
currentFilter: currentFilter
});
}, [currentFilter]);
const refresh = React.useCallback(() => {
if (note) {
@@ -68,6 +159,7 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
sortBy: "dateModified"
})
.then((attachments) => {
setLoading(false);
setAttachments(attachments);
});
} else {
@@ -76,7 +168,10 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
...DEFAULT_SORTING,
sortBy: "dateModified"
})
.then((attachments) => setAttachments(attachments));
.then((attachments) => {
setAttachments(attachments);
setLoading(false);
});
}
}, [note]);
@@ -108,53 +203,70 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
setAttachments={async () => {
setAttachments(await filterAttachments(currentFilter));
}}
errorOnly={currentFilter === "errors"}
attachments={attachments}
id={index}
context="attachments-list"
context="global"
/>
);
const onCheck = async () => {
if (!attachments) return;
setLoading(true);
const getState = () =>
useRechecker.getState().progress[note?.id || "all"] || {};
const setState = (state: Partial<RecheckerProgress>) =>
useRechecker.getState().setProgress(note?.id || "all", {
...getState(),
...state
});
for (let i = 0; i < attachments.placeholders.length; i++) {
if (!attachments || getState().isWorking) return;
setState({
isWorking: true,
failed: 0,
passed: 0,
shown: true,
filter: currentFilterRef.current
});
const filter = currentFilterRef.current;
const filteredAttachments = await filterAttachments(
currentFilterRef.current
);
for (let i = 0; i < filteredAttachments.placeholders.length; i++) {
if (!getState().isWorking) {
ToastManager.show({
message: "Attachment recheck cancelled",
type: "info",
context: isSheet ? "local" : "global"
});
return;
}
const attachment = (await attachments.item(i))?.item;
if (currentFilter == "errors" && !attachment?.failed) continue;
if (!attachment) continue;
const result = await filesystem.checkAttachment(attachment.hash);
if (!result) return;
if (result.failed) {
setState({
failed: getState().failed + 1
});
await db.attachments.markAsFailed(attachment.hash, result.failed);
} else {
setState({
passed: getState().passed + 1
});
await db.attachments.markAsFailed(attachment.id);
}
}
refresh();
setLoading(false);
};
const attachmentTypes = [
{
title: "All",
filterBy: "all"
},
{
title: "Images",
filterBy: "images"
},
{
title: "Documents",
filterBy: "documents"
},
{
title: "Video",
filterBy: "video"
},
{
title: "Audio",
filterBy: "audio"
if (filter === useRechecker.getState().currentFilter) {
setAttachments(await filterAttachments(currentFilter));
}
];
setState({
isWorking: false
});
};
const filterAttachments = async (type: string) => {
let items: FilteredSelector<Attachment> = db.attachments.all;
@@ -184,6 +296,14 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
items = note
? db.attachments.ofNote(note.id, "documents")
: db.attachments.documents;
break;
case "orphaned":
items = db.attachments.orphaned;
break;
case "errors":
items = items = note
? db.attachments.ofNote(note.id, "all")
: db.attachments.all;
}
return await items.sorted({
@@ -193,39 +313,59 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
};
return (
<View
style={{
width: "100%",
alignSelf: "center",
paddingHorizontal: 12,
height: height * 0.85
}}
>
<SheetProvider context="attachments-list" />
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center"
}}
>
<Heading>Attachments</Heading>
<>
{isSheet ? (
<>
<SheetProvider context="attachments-list" />
<Dialog context="local" />
</>
) : null}
{!isSheet ? (
<Header
title="Manage attachments"
renderedInRoute="SettingsGroup"
canGoBack
headerRightButtons={[
{
onPress() {
onCheck();
},
title: "Recheck all"
},
{
onPress() {
if (!attachments) return;
presentDialog({
title: `Download ${attachments.placeholders.length} attachments`,
paragraph:
"Are you sure you want to download all attachments?",
positiveText: "Download",
positivePress: async () => {
downloadAttachments(await attachments.ids());
},
negativeText: "Cancel"
});
},
title: "Download all"
}
]}
/>
) : (
<View
style={{
flexDirection: "row"
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: 12
}}
>
{loading ? (
<ActivityIndicator
style={{
height: 40,
width: 40,
marginRight: 10
}}
size={SIZE.lg}
/>
) : (
<Heading>{strings.dataTypesPluralCamelCase.attachment()}</Heading>
<View
style={{
flexDirection: "row"
}}
>
<IconButton
name="check-all"
style={{
@@ -237,126 +377,241 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
size={SIZE.lg}
onPress={onCheck}
/>
)}
<IconButton
name="download"
style={{
height: 40,
width: 40
}}
color={colors.primary.paragraph}
onPress={() => {
if (!attachments) return;
DownloadAttachments.present(
"attachments-list",
attachments,
!!note
);
}}
size={SIZE.lg}
/>
</View>
</View>
<Seperator />
<Input
placeholder="Filter attachments by filename, type or hash"
onChangeText={onChangeText}
onSubmit={() => {
onChangeText(attachmentSearchValue.current as string);
}}
/>
<View>
<ScrollView
style={{
width: "100%",
height: 50,
flexDirection: "row",
backgroundColor: colors.primary.background
}}
contentContainerStyle={{
minWidth: "100%",
height: 50
}}
horizontal
>
{attachmentTypes.map((item) => (
<Button
type={
currentFilter === item.filterBy ? "secondaryAccented" : "plain"
}
key={item.title}
title={item.title}
<IconButton
name="download"
style={{
borderRadius: 0,
borderBottomWidth: 1,
flexGrow: 1,
borderBottomColor:
currentFilter !== item.filterBy
? "transparent"
: colors.primary.accent
height: 40,
width: 40
}}
onPress={async () => {
setCurrentFilter(item.filterBy);
setAttachments(await filterAttachments(item.filterBy));
color={colors.primary.paragraph}
onPress={() => {
if (!attachments) return;
presentDialog({
title: `Download ${attachments.placeholders.length} attachments`,
paragraph:
"Are you sure you want to download all attachments?",
context: "local",
positiveText: "Download",
positivePress: async () => {
downloadAttachments(await attachments.ids());
},
negativeText: "Cancel"
});
}}
size={SIZE.lg}
/>
))}
</ScrollView>
</View>
<FlashList
keyboardDismissMode="none"
keyboardShouldPersistTaps="always"
ListEmptyComponent={
<View
style={{
height: 150,
justifyContent: "center",
alignItems: "center"
}}
>
<Icon name="attachment" size={60} color={colors.secondary.icon} />
<Paragraph>
{note ? "No attachments on this note" : "No attachments"}
</Paragraph>
</View>
}
ListFooterComponent={
<View
style={{
height: 350
}}
/>
}
estimatedItemSize={50}
data={attachments?.placeholders}
renderItem={renderItem}
/>
</View>
)}
<Paragraph
color={colors.secondary.paragraph}
size={SIZE.xs}
<View
style={{
textAlign: "center",
marginTop: 10
width: "100%",
alignSelf: "center",
paddingHorizontal: 12,
height: "100%"
}}
>
<Icon
name="shield-key-outline"
size={SIZE.xs}
color={colors.primary.icon}
<Seperator />
<Input
placeholder={strings.filterAttachments()}
onChangeText={onChangeText}
onSubmit={() => {
onChangeText(attachmentSearchValue.current as string);
}}
/>
{" "}All attachments are end-to-end encrypted.
</Paragraph>
{rechecker.shown ? (
<View
style={{
flexDirection: "row",
alignItems: "center",
width: "100%",
borderRadius: 10,
padding: 10,
borderWidth: 1,
borderColor: colors.primary.border,
gap: 12,
justifyContent: "space-between"
}}
>
<View
style={{
flexDirection: "row",
gap: 12,
alignItems: "center"
}}
>
{rechecker.isWorking ? (
<ActivityIndicator color={colors.primary.accent} />
) : (
<Icon
name="file-check"
size={30}
color={colors.primary.accent}
/>
)}
<View>
<Paragraph>
{rechecker.isWorking
? note
? `Checking ${rechecker.filter.toLowerCase()} note attachments`
: `Checking ${rechecker.filter.toLowerCase()} attachments`
: "Attachments recheck complete"}
</Paragraph>
<Paragraph>
{`${rechecker.isWorking ? "Please wait... " : ""}Passed: ${
rechecker.passed
}, Failed: ${rechecker.failed}`}
</Paragraph>
</View>
</View>
<IconButton
type={rechecker.isWorking ? "errorShade" : "plain"}
name={rechecker.isWorking ? "close" : "check"}
size={SIZE.lg}
color={
rechecker.isWorking ? colors.error.icon : colors.primary.accent
}
onPress={() => {
useRechecker.getState().setProgress(note?.id || "all", {
...useRechecker.getState().progress[note?.id || "all"],
shown: false,
isWorking: false
});
}}
/>
</View>
) : null}
<View>
<ScrollView
style={{
backgroundColor: colors.primary.background,
flexWrap: "wrap",
flexDirection: "row",
paddingVertical: 12
}}
contentContainerStyle={{
alignItems: "center"
}}
horizontal
>
{attachmentTypes.map((item) =>
item.filterBy === "orphaned" && note ? null : (
<Button
type={
currentFilter === item.filterBy
? "secondaryAccented"
: "plain"
}
key={item.title}
title={item.title}
fontSize={SIZE.sm}
style={{
borderRadius: 100,
paddingHorizontal: 12,
height: 40,
minWidth: 80
}}
onPress={async () => {
const filterBy = item.filterBy;
setCurrentFilter(filterBy);
setLoading(true);
filterAttachments(filterBy)
.then((results) => {
if (filterBy !== currentFilterRef.current) return;
setAttachments(results);
setLoading(false);
})
.catch(console.log);
}}
/>
)
)}
</ScrollView>
</View>
<FlashList
keyboardDismissMode="none"
keyboardShouldPersistTaps="always"
ListEmptyComponent={
<View
style={{
height: "100%",
justifyContent: "center",
alignItems: "center"
}}
>
{loading ? (
<ActivityIndicator size={40} color={colors.primary.accent} />
) : (
<>
<Icon
name="attachment"
size={60}
color={colors.secondary.icon}
/>
<Paragraph>{strings.noAttachments()}</Paragraph>
</>
)}
</View>
}
ListHeaderComponent={<AllProgress note={note} />}
ListFooterComponent={
<View
style={{
height: 350
}}
/>
}
estimatedItemSize={50}
data={loading ? [] : attachments?.placeholders}
renderItem={renderItem}
/>
</View>
</>
);
};
const AllProgress = ({ note }: { note?: Note }) => {
const progress = useAttachmentStore((state) =>
note
? {
downloading: {
[note.id]: state.downloading?.[note.id]
},
uploading: {
[note.id]: state.uploading?.[note.id]
}
}
: state
);
return (
<View
style={{
gap: 10,
width: "100%"
}}
>
{Object.keys(progress.downloading || {}).map((groupId) => (
<AttachmentGroupProgress key={groupId} groupId={groupId} />
))}
{Object.keys(progress.uploading || {}).map((groupId) => (
<AttachmentGroupProgress key={groupId} groupId={groupId} />
))}
</View>
);
};
AttachmentDialog.present = (note?: Note) => {
presentSheet({
component: () => <AttachmentDialog note={note} />,
component: () => <AttachmentDialog note={note} isSheet={true} />,
keyboardHandlerDisabled: true
});
};

View File

@@ -36,6 +36,7 @@ import { IconButton } from "../ui/icon-button";
import { hideAuth, initialAuthMode } from "./common";
import { Login } from "./login";
import { Signup } from "./signup";
import { strings } from "@notesnook/intl";
export const AuthMode = {
login: 0,
@@ -153,7 +154,7 @@ const AuthModal = () => {
{initialAuthMode.current !== AuthMode.welcomeSignup ? null : (
<Button
title="Skip"
title={strings.skip()}
onPress={() => {
hideAuth();
}}

View File

@@ -35,6 +35,7 @@ import Seperator from "../ui/seperator";
import { Dialog } from "../dialog";
import BackupService from "../../services/backup";
import { sleep } from "../../utils/time";
import { strings } from "@notesnook/intl";
export const ChangePassword = () => {
const passwordInputRef = useRef();
@@ -49,8 +50,8 @@ export const ChangePassword = () => {
const changePassword = async () => {
if (!user?.isEmailConfirmed) {
ToastManager.show({
heading: "Email not confirmed",
message: "Please confirm your email to change account password",
heading: strings.emailNotConfirmed(),
message: strings.emailNotConfirmedDesc(),
type: "error",
context: "local"
});
@@ -58,8 +59,8 @@ export const ChangePassword = () => {
}
if (error || !oldPassword.current || !password.current) {
ToastManager.show({
heading: "All fields required",
message: "Fill all the fields and try again.",
heading: strings.allFieldsRequired(),
message: strings.allFieldsRequiredDesc(),
type: "error",
context: "local"
});
@@ -68,13 +69,13 @@ export const ChangePassword = () => {
setLoading(true);
try {
const result = await BackupService.run(false, "change-password-dialog");
if (result.error)
throw new Error(`Failed to create backup: ${result.error}`);
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: "Account password updated",
heading: strings.passwordChangedSuccessfully(),
type: "success",
context: "global"
});
@@ -85,7 +86,7 @@ export const ChangePassword = () => {
} catch (e) {
setLoading(false);
ToastManager.show({
heading: "Failed to change password",
heading: strings.passwordChangeFailed(),
message: e.message,
type: "error",
context: "local"
@@ -102,10 +103,7 @@ export const ChangePassword = () => {
}}
>
<Dialog context="change-password-dialog" />
<DialogHeader
title="Change password"
paragraph="Enter your old and new passwords"
/>
<DialogHeader title={strings.changePassword()} />
<Seperator />
<Input
@@ -119,7 +117,7 @@ export const ChangePassword = () => {
autoComplete="password"
autoCapitalize="none"
autoCorrect={false}
placeholder="Old Password"
placeholder={strings.oldPassword()}
/>
<Input
@@ -128,27 +126,21 @@ export const ChangePassword = () => {
password.current = value;
}}
onErrorCheck={(e) => setError(e)}
returnKeyLabel="Next"
returnKeyLabel={strings.next()}
returnKeyType="next"
secureTextEntry
validationType="password"
autoComplete="password"
autoCapitalize="none"
autoCorrect={false}
placeholder="New password"
placeholder={strings.newPassword()}
/>
<Notice
text={`Changing password is an irreversible process. You will be logged out from all your devices. Please make sure you do not close the app while your password is changing and have good internet connection.`}
type="alert"
/>
<Notice text={strings.changePasswordNotice()} type="alert" />
<View style={{ height: 10 }} />
<Notice
text={`Once your password is changed, please make sure to save the new account recovery key.`}
type="alert"
/>
<Notice text={strings.changePasswordNotice2()} type="alert" />
<Button
style={{
@@ -158,7 +150,7 @@ export const ChangePassword = () => {
loading={loading}
onPress={changePassword}
type="accent"
title={loading ? null : "I understand, change my password"}
title={loading ? null : strings.changePasswordConfirm()}
/>
</View>
);

View File

@@ -32,6 +32,7 @@ import Input from "../ui/input";
import Seperator from "../ui/seperator";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
export const ForgotPassword = () => {
const { colors } = useThemeColors("sheet");
@@ -44,7 +45,7 @@ export const ForgotPassword = () => {
const sendRecoveryEmail = async () => {
if (!email.current || error) {
ToastManager.show({
heading: "Account email is required.",
heading: strings.emailRequired(),
type: "error",
context: "local"
});
@@ -57,15 +58,15 @@ export const ForgotPassword = () => {
lastRecoveryEmailTime &&
Date.now() - JSON.parse(lastRecoveryEmailTime) < 60000 * 3
) {
throw new Error("Please wait before requesting another email");
throw new Error(strings.pleaseWaitBeforeSendEmail());
}
await db.user.recoverAccount(email.current.toLowerCase());
SettingsService.set({
lastRecoveryEmailTime: Date.now()
});
ToastManager.show({
heading: "Check your email to reset password",
message: `Recovery email has been sent to ${email.current.toLowerCase()}`,
heading: strings.recoveryEmailSent(),
message: strings.recoveryEmailSentDesc(),
type: "success",
context: "local",
duration: 7000
@@ -75,7 +76,7 @@ export const ForgotPassword = () => {
} catch (e) {
setLoading(false);
ToastManager.show({
heading: "Recovery email not sent",
heading: strings.recoveryEmailFailed(),
message: e.message,
type: "error",
context: "local"
@@ -121,13 +122,13 @@ export const ForgotPassword = () => {
name="email"
size={50}
/>
<Heading>Recovery email sent!</Heading>
<Heading>{strings.recoveryEmailSent()}</Heading>
<Paragraph
style={{
textAlign: "center"
}}
>
Please follow the link in the email to recover your account.
{strings.recoveryEmailSentDesc()}
</Paragraph>
</View>
) : (
@@ -140,10 +141,7 @@ export const ForgotPassword = () => {
padding: 12
}}
>
<DialogHeader
title="Account recovery"
paragraph="We will send you an email with steps on how to reset your password."
/>
<DialogHeader title={strings.accountRecovery()} />
<Seperator />
<Input
@@ -153,14 +151,14 @@ export const ForgotPassword = () => {
}}
defaultValue={email.current}
onErrorCheck={(e) => setError(e)}
returnKeyLabel="Next"
returnKeyLabel={strings.next()}
returnKeyType="next"
autoComplete="email"
validationType="email"
autoCorrect={false}
autoCapitalize="none"
errorMessage="Email is invalid"
placeholder="Email"
errorMessage={strings.emailInvalid()}
placeholder={strings.email()}
onSubmit={() => {}}
/>
@@ -172,7 +170,7 @@ export const ForgotPassword = () => {
loading={loading}
onPress={sendRecoveryEmail}
type="accent"
title={loading ? null : "Next"}
title={loading ? null : strings.next()}
/>
</View>
)}

View File

@@ -38,6 +38,7 @@ import Paragraph from "../ui/typography/paragraph";
import { hideAuth } from "./common";
import { ForgotPassword } from "./forgot-password";
import { useLogin } from "./use-login";
import { strings } from "@notesnook/intl";
const LoginSteps = {
emailAuth: 1,
@@ -147,7 +148,7 @@ export const Login = ({ changeMode }) => {
extraBold
size={SIZE.xxl}
>
Login to your {"\n"}account
{strings.loginToYourAccount()}
</Heading>
</View>
@@ -178,8 +179,8 @@ export const Login = ({ changeMode }) => {
validationType="email"
autoCorrect={false}
autoCapitalize="none"
errorMessage="Email is invalid"
placeholder="Enter your email"
errorMessage={strings.emailInvalid()}
placeholder={strings.email()}
defaultValue={email.current}
editable={step === LoginSteps.emailAuth && !loading}
onSubmit={() => {
@@ -199,20 +200,20 @@ export const Login = ({ changeMode }) => {
password.current = value;
}}
testID="input.password"
returnKeyLabel="Done"
returnKeyLabel={strings.done()}
returnKeyType="done"
secureTextEntry
autoComplete="password"
autoCapitalize="none"
autoCorrect={false}
placeholder="Password"
placeholder={strings.password()}
marginBottom={0}
editable={!loading}
defaultValue={password.current}
onSubmit={() => login()}
/>
<Button
title="Forgot your password?"
title={strings.forgotPassword()}
style={{
alignSelf: "flex-end",
height: 30,
@@ -249,12 +250,12 @@ export const Login = ({ changeMode }) => {
height={50}
fontSize={SIZE.md}
type="accent"
title={!loading ? "Continue" : null}
title={!loading ? strings.continue() : null}
/>
{step === LoginSteps.passwordAuth && (
<Button
title="Cancel logging in"
title={strings.cancelLogin()}
style={{
alignSelf: "center",
height: 30,
@@ -290,12 +291,12 @@ export const Login = ({ changeMode }) => {
size={SIZE.xs + 1}
color={colors.secondary.paragraph}
>
Don't have an account?{" "}
{strings.dontHaveAccount()}{" "}
<Paragraph
size={SIZE.xs + 1}
style={{ color: colors.primary.accent }}
>
Sign up
{strings.signUp()}
</Paragraph>
</Paragraph>
</TouchableOpacity>

View File

@@ -47,6 +47,7 @@ import Input from "../ui/input";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { LoginSteps, useLogin } from "./use-login";
import { strings } from "@notesnook/intl";
function getObfuscatedEmail(email) {
if (!email) return "";
@@ -193,15 +194,14 @@ export const SessionExpired = () => {
size={50}
/>
<Heading size={SIZE.xxxl} color={colors.primary.heading}>
Session expired
{strings.sessionExpired()}
</Heading>
<Paragraph
style={{
textAlign: "center"
}}
>
Your session on this device has expired. Please enter password for{" "}
{getObfuscatedEmail(email.current)} to continue.
{strings.sessionExpiredDesc(getObfuscatedEmail(email.current))}
</Paragraph>
</View>
@@ -211,13 +211,13 @@ export const SessionExpired = () => {
onChangeText={(value) => {
password.current = value;
}}
returnKeyLabel="Next"
returnKeyLabel={strings.done()}
returnKeyType="next"
secureTextEntry
autoComplete="password"
autoCapitalize="none"
autoCorrect={false}
placeholder="Password"
placeholder={strings.password()}
onSubmit={() => login()}
/>
) : null}
@@ -231,7 +231,7 @@ export const SessionExpired = () => {
loading={loading}
onPress={() => login()}
type="accent"
title={loading ? null : "Login"}
title={loading ? null : strings.login()}
/>
<Button
@@ -242,16 +242,15 @@ export const SessionExpired = () => {
onPress={() => {
presentDialog({
context: "session_expiry",
title: "Logout",
paragraph:
"Are you sure you want to logout from this device? Any unsynced changes will be lost.",
positiveText: "Logout",
title: strings.logoutFromDevice(),
paragraph: strings.logoutDesc(),
positiveText: strings.logout(),
positiveType: "errorShade",
positivePress: logout
});
}}
type="errorShade"
title={loading ? null : "Logout from this device"}
title={loading ? null : strings.logoutFromDevice()}
/>
</View>
<Toast context="local" />

View File

@@ -36,6 +36,7 @@ import Paragraph from "../ui/typography/paragraph";
import { hideAuth } from "./common";
import { useSettingStore } from "../../stores/use-setting-store";
import SettingsService from "../../services/settings";
import { strings } from "@notesnook/intl";
export const Signup = ({ changeMode, trial }) => {
const { colors } = useThemeColors();
@@ -54,8 +55,8 @@ export const Signup = ({ changeMode, trial }) => {
const validateInfo = () => {
if (!password.current || !email.current || !confirmPassword.current) {
ToastManager.show({
heading: "All fields required",
message: "Fill all the fields and try again",
heading: strings.allFieldsRequired(),
message: strings.allFieldsRequiredDesc(),
type: "error",
context: "local"
});
@@ -88,7 +89,7 @@ export const Signup = ({ changeMode, trial }) => {
} catch (e) {
setLoading(false);
ToastManager.show({
heading: "Signup failed",
heading: strings.signupFailed(),
message: e.message,
type: "error",
context: "local"
@@ -156,7 +157,7 @@ export const Signup = ({ changeMode, trial }) => {
}}
size={SIZE.xxl}
>
Create your {"\n"}account
{strings.createYourAccount()}
</Heading>
</View>
@@ -175,14 +176,14 @@ export const Signup = ({ changeMode, trial }) => {
}}
testID="input.email"
onErrorCheck={(e) => setError(e)}
returnKeyLabel="Next"
returnKeyLabel={strings.next()}
returnKeyType="next"
autoComplete="email"
validationType="email"
autoCorrect={false}
autoCapitalize="none"
errorMessage="Email is invalid"
placeholder="Email"
errorMessage={strings.email()}
placeholder={strings.email()}
onSubmit={() => {
passwordInputRef.current?.focus();
}}
@@ -195,14 +196,14 @@ export const Signup = ({ changeMode, trial }) => {
}}
testID="input.password"
onErrorCheck={(e) => setError(e)}
returnKeyLabel="Next"
returnKeyLabel={strings.next()}
returnKeyType="next"
secureTextEntry
autoComplete="password"
autoCapitalize="none"
validationType="password"
autoCorrect={false}
placeholder="Password"
placeholder={strings.password()}
onSubmit={() => {
confirmPasswordInputRef.current?.focus();
}}
@@ -215,7 +216,7 @@ export const Signup = ({ changeMode, trial }) => {
}}
testID="input.confirmPassword"
onErrorCheck={(e) => setError(e)}
returnKeyLabel="Signup"
returnKeyLabel={strings.done()}
returnKeyType="done"
secureTextEntry
autoComplete="password"
@@ -223,7 +224,7 @@ export const Signup = ({ changeMode, trial }) => {
autoCorrect={false}
validationType="confirmPassword"
customValidator={() => password.current}
placeholder="Confirm password"
placeholder={strings.confirmPassword()}
marginBottom={12}
onSubmit={signup}
/>
@@ -235,7 +236,7 @@ export const Signup = ({ changeMode, trial }) => {
size={SIZE.xs}
color={colors.secondary.paragraph}
>
By signing up, you agree to our{" "}
{strings.signupAgreement[0]()}
<Paragraph
size={SIZE.xs}
onPress={() => {
@@ -246,9 +247,9 @@ export const Signup = ({ changeMode, trial }) => {
}}
color={colors.primary.accent}
>
Terms of Service{" "}
{strings.signupAgreement[1]()}
</Paragraph>
and{" "}
{strings.signupAgreement[2]()}
<Paragraph
size={SIZE.xs}
onPress={() => {
@@ -259,14 +260,13 @@ export const Signup = ({ changeMode, trial }) => {
}}
color={colors.primary.accent}
>
Privacy Policy.
</Paragraph>{" "}
You also agree to recieve marketing emails from us which you can
opt-out of from app settings.
{strings.signupAgreement[3]()}
</Paragraph>
{strings.signupAgreement[4]()}
</Paragraph>
<Button
title={!loading ? "Continue" : null}
title={!loading ? strings.continue() : null}
type="accent"
loading={loading}
onPress={signup}
@@ -291,12 +291,12 @@ export const Signup = ({ changeMode, trial }) => {
}}
>
<Paragraph size={SIZE.xs + 1} color={colors.secondary.paragraph}>
Already have an account?{" "}
{strings.alreadyHaveAccount()}{" "}
<Paragraph
size={SIZE.xs + 1}
style={{ color: colors.primary.accent }}
>
Login
{strings.login()}
</Paragraph>
</Paragraph>
</TouchableOpacity>

View File

@@ -38,8 +38,9 @@ import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { useCallback } from "react";
import { ScrollView } from "react-native-actions-sheet";
import { strings } from "@notesnook/intl";
const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
const TwoFactorVerification = ({ onMfaLogin, mfaInfo, onCancel }) => {
const { colors } = useThemeColors();
const code = useRef();
const [currentMethod, setCurrentMethod] = useState({
@@ -51,20 +52,6 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
const inputRef = useRef();
const [sending, setSending] = useState(false);
const codeHelpText = {
app: "Enter the 6 digit code from your authenticator app to continue logging in",
sms: "Enter the 6 digit code sent to your phone number to continue logging in",
email: "Enter the 6 digit code sent to your email to continue logging in",
recoveryCode: "Enter the recovery code to continue logging in"
};
const secondaryMethodsText = {
app: "I don't have access to authenticator app",
sms: "I don't have access to my phone",
email: "I don't have access to email",
recoveryCode: "I don't have recovery codes"
};
const onNext = async () => {
if (!code.current || code.current.length < 6) return;
setLoading(true);
@@ -94,22 +81,22 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
const methods = [
{
id: "sms",
title: "Send code via SMS",
title: strings.sendCodeSms(),
icon: "message-plus-outline"
},
{
id: "email",
title: "Send code via email",
title: strings.sendCodeEmail(),
icon: "email-outline"
},
{
id: "app",
title: "Enter code from authenticator app",
title: strings.authAppCode(),
icon: "cellphone-key"
},
{
id: "recoveryCode",
title: "I have a recovery code",
title: strings.recoveryCode(),
icon: "key"
}
];
@@ -151,7 +138,8 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
<View
style={{
alignItems: "center",
paddingHorizontal: currentMethod.method ? 12 : 0
paddingHorizontal: currentMethod.method ? 12 : 0,
gap: 12
}}
>
<IconButton
@@ -168,9 +156,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
textAlign: "center"
}}
>
{currentMethod.method
? "Two factor authentication"
: "Select methods for two-factor authentication"}
{currentMethod.method ? strings["2fa"]() : strings.select2faMethod()}
</Heading>
<Paragraph
style={{
@@ -178,12 +164,10 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
textAlign: "center"
}}
>
{codeHelpText[currentMethod.method] ||
"Select how you would like to recieve the code"}
{strings["2faCodeHelpText"][currentMethod.method]() ||
strings.select2faCodeHelpText()}
</Paragraph>
<Seperator />
{currentMethod.method === "sms" || currentMethod.method === "email" ? (
<Button
onPress={onSendCode}
@@ -191,15 +175,17 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
title={
sending
? ""
: `${seconds ? `Resend code in (${seconds})` : "Send code"}`
: `${
seconds
? strings.resend2faCode(seconds)
: strings.sendCode()
}`
}
loading={sending}
height={30}
/>
) : null}
<Seperator />
{currentMethod.method ? (
<>
<Input
@@ -238,25 +224,33 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
minWidth: "50%"
}}
/>
<Seperator />
<Button
title={loading ? null : "Next"}
title={loading ? null : strings.next()}
type="accent"
width={250}
loading={loading}
onPress={onNext}
style={{
borderRadius: 100,
marginBottom: 10
borderRadius: 100
}}
/>
<Button
title={secondaryMethodsText[currentMethod.method]}
title={strings["2faCodeSecondaryMethodText"][
currentMethod.method
]()}
type="plain"
onPress={onRequestSecondaryMethod}
height={30}
/>
<Button
title={strings.cancel()}
type="plain"
onPress={onCancel}
height={30}
/>
</>
) : (
<>
@@ -307,15 +301,16 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
);
};
TwoFactorVerification.present = (onMfaLogin, data, context) => {
TwoFactorVerification.present = (onMfaLogin, data, onCancel, context) => {
presentSheet({
component: () => (
<TwoFactorVerification onMfaLogin={onMfaLogin} mfaInfo={data} />
<TwoFactorVerification
onMfaLogin={onMfaLogin}
mfaInfo={data}
onCancel={onCancel}
/>
),
context: context || "two_factor_verify",
onClose: () => {
onMfaLogin();
},
disableClosing: true
});
};

View File

@@ -26,6 +26,7 @@ import SettingsService from "../../services/settings";
import { useUserStore } from "../../stores/use-user-store";
import { eCloseSheet } from "../../utils/events";
import TwoFactorVerification from "./two-factor";
import { strings } from "@notesnook/intl";
export const LoginSteps = {
emailAuth: 1,
@@ -49,8 +50,8 @@ export const useLogin = (onFinishLogin, sessionExpired = false) => {
(!email.current && step === LoginSteps.emailAuth)
) {
ToastManager.show({
heading: "All fields required",
message: "Fill all the fields and try again",
heading: strings.allFieldsRequired(),
message: strings.allFieldsRequiredDesc(),
type: "error",
context: "local"
});
@@ -71,33 +72,41 @@ export const useLogin = (onFinishLogin, sessionExpired = false) => {
const mfaInfo = await db.user.authenticateEmail(email.current);
console.log("email auth", mfaInfo);
if (mfaInfo) {
TwoFactorVerification.present(async (mfa, callback) => {
try {
const success = await db.user.authenticateMultiFactorCode(
mfa.code,
mfa.method
);
TwoFactorVerification.present(
async (mfa, callback) => {
try {
const success = await db.user.authenticateMultiFactorCode(
mfa.code,
mfa.method
);
if (success) {
setStep(LoginSteps.passwordAuth);
setLoading(false);
setTimeout(() => {
passwordInputRef.current?.focus();
}, 500);
callback && callback(true);
}
callback && callback(false);
} catch (e) {
callback && callback(false);
if (e.message === "invalid_grant") {
eSendEvent(eCloseSheet, "two_factor_verify");
setLoading(false);
setStep(LoginSteps.emailAuth);
if (success) {
setStep(LoginSteps.passwordAuth);
setLoading(false);
setTimeout(() => {
passwordInputRef.current?.focus();
}, 500);
callback && callback(true);
}
callback && callback(false);
} catch (e) {
callback && callback(false);
if (e.message === "invalid_grant") {
eSendEvent(eCloseSheet, "two_factor_verify");
setLoading(false);
setStep(LoginSteps.emailAuth);
}
}
},
mfaInfo,
() => {
eSendEvent(eCloseSheet, "two_factor_verify");
setLoading(false);
setStep(LoginSteps.emailAuth);
}
}, mfaInfo);
);
} else {
finishWithError(new Error("Unable to send 2FA code"));
finishWithError(new Error(strings.unableToSend2faCode()));
}
break;
}
@@ -122,7 +131,7 @@ export const useLogin = (onFinishLogin, sessionExpired = false) => {
if (e.message === "invalid_grant") setStep(LoginSteps.emailAuth);
setLoading(false);
ToastManager.show({
heading: "Login failed",
heading: strings.loginFailed(),
message: e.message,
type: "error",
context: "local"
@@ -131,13 +140,13 @@ export const useLogin = (onFinishLogin, sessionExpired = false) => {
const finishLogin = async () => {
const user = await db.user.getUser();
if (!user) throw new Error("Email or password incorrect!");
if (!user) throw new Error(strings.emailOrPasswordIncorrect());
PremiumService.setPremiumStatus();
setUser(user);
clearMessage();
ToastManager.show({
heading: "Login successful",
message: `Logged in as ${user.email}`,
heading: strings.loginSuccess(),
message: strings.loginSuccessDesc(),
type: "success",
context: "global"
});

View File

@@ -34,11 +34,9 @@ import { useSelectionStore } from "../../stores/use-selection-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { getElevationStyle } from "../../utils/elevation";
import { SIZE, normalize } from "../../utils/size";
import NativeTooltip from "../../utils/tooltip";
import { Pressable } from "../ui/pressable";
interface FloatingButtonProps {
title: string;
onPress: () => void;
color?: string;
shouldShow?: boolean;
@@ -46,7 +44,6 @@ interface FloatingButtonProps {
}
const FloatingButton = ({
title,
onPress,
color,
alwaysVisible = false
@@ -126,11 +123,6 @@ const FloatingButton = ({
...getElevationStyle(5),
borderRadius: 100
}}
onLongPress={(event) => {
if (title) {
NativeTooltip.show(event, title, NativeTooltip.POSITIONS.LEFT);
}
}}
onPress={onPress}
>
<View

View File

@@ -26,12 +26,13 @@ import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
import Paragraph from "../ui/typography/paragraph";
import { getColorLinearShade } from "../../utils/colors";
import { strings } from "@notesnook/intl";
const DialogButtons = ({
onPressPositive,
onPressNegative,
positiveTitle,
negativeTitle = "Cancel",
negativeTitle = strings.cancel(),
loading,
doneText,
positiveType

View File

@@ -38,6 +38,7 @@ import { useCallback } from "react";
import { Button } from "../ui/button";
import { getContainerBorder } from "../../utils/colors";
import { Notice } from "../ui/notice";
import { strings } from "@notesnook/intl";
export const Dialog = ({ context = "global" }) => {
const { colors } = useThemeColors();
@@ -50,8 +51,8 @@ export const Dialog = ({ context = "global" }) => {
const [dialogInfo, setDialogInfo] = useState({
title: "",
paragraph: "",
positiveText: "Done",
negativeText: "Cancel",
positiveText: strings.done(),
negativeText: strings.cancel(),
positivePress: () => {},
onClose: () => {},
positiveType: "transparent",

View File

@@ -49,6 +49,7 @@ import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import Input from "../../ui/input";
import Seperator from "../../ui/seperator";
import { strings } from "@notesnook/intl";
export const AppLockPassword = () => {
const { colors } = useThemeColors();
@@ -119,22 +120,7 @@ export const AppLockPassword = () => {
}}
>
<DialogHeader
title={
mode === "change"
? `Change app lock ${keyboardType}`
: mode === "remove"
? `Remove app lock ${keyboardType}`
: `Set up app lock ${keyboardType}`
}
paragraph={
mode === "change"
? `Change app lock ${keyboardType}`
: mode === "remove"
? `Enter ${
accountPass ? "account password" : `app lock ${keyboardType}`
} to remove ${keyboardType}`
: `Set up a custom app lock ${keyboardType} to unlock the app`
}
title={strings.changeAppLockCredentials(mode, keyboardType)}
icon="shield"
padding={12}
/>
@@ -157,11 +143,15 @@ export const AppLockPassword = () => {
}}
defaultValue={values.current.currentPassword}
autoComplete="password"
returnKeyLabel="Next"
returnKeyLabel={strings.next()}
keyboardType={keyboardType === "pin" ? "number-pad" : "default"}
returnKeyType="next"
secureTextEntry={secureTextEntry}
placeholder={`Current ${keyboardType}`}
placeholder={
keyboardType === "pin"
? strings.currentPin()
: strings.currentPassword()
}
/>
) : null}
@@ -179,7 +169,9 @@ export const AppLockPassword = () => {
keyboardType === "pin" && !accountPass ? "number-pad" : "default"
}
autoComplete="password"
returnKeyLabel={mode !== "remove" ? "Next" : "Remove"}
returnKeyLabel={
mode !== "remove" ? strings.next() : strings.remove()
}
returnKeyType={mode !== "remove" ? "next" : "done"}
secureTextEntry={secureTextEntry}
buttonLeft={
@@ -206,10 +198,14 @@ export const AppLockPassword = () => {
}
placeholder={
accountPass
? "Account password"
? strings.enterAccountPassword()
: mode === "change"
? `New ${keyboardType}`
: `${keyboardType === "pin" ? "Pin" : "Password"}`
? keyboardType === "pin"
? strings.newPin()
: strings.newPassword()
: `${
keyboardType === "pin" ? strings.pin() : strings.password()
}`
}
/>
@@ -228,10 +224,14 @@ export const AppLockPassword = () => {
customValidator={() => values.current.password || ""}
validationType="confirmPassword"
autoComplete="password"
returnKeyLabel="Done"
returnKeyLabel={strings.done()}
returnKeyType="done"
secureTextEntry={secureTextEntry}
placeholder={`Confirm ${keyboardType}`}
placeholder={
keyboardType === "pin"
? strings.confirmPin()
: strings.confirmPassword()
}
/>
) : null}
@@ -253,7 +253,7 @@ export const AppLockPassword = () => {
iconColor={
accountPass ? colors.primary.accent : colors.primary.icon
}
title="Use account password instead"
title={strings.useAccountPassword()}
style={{
width: "100%",
alignSelf: "flex-start",
@@ -272,7 +272,7 @@ export const AppLockPassword = () => {
if (mode === "create") {
if (!values.current.password || !values.current.confirmPassword) {
ToastManager.error(
new Error("All inputs are required"),
new Error(strings.allFieldsRequired()),
undefined,
"local"
);
@@ -281,11 +281,7 @@ export const AppLockPassword = () => {
if (values.current.password !== values.current.confirmPassword) {
ToastManager.error(
new Error(
`${
keyboardType === "pin" ? "Pin" : "Password"
} does not match`
),
new Error(strings.mismatch(keyboardType)),
undefined,
"local"
);
@@ -301,7 +297,7 @@ export const AppLockPassword = () => {
!values.current.confirmPassword
) {
ToastManager.error(
new Error("All inputs are required"),
new Error(strings.allFieldsRequired()),
undefined,
"local"
);
@@ -310,11 +306,7 @@ export const AppLockPassword = () => {
if (values.current.password !== values.current.confirmPassword) {
ToastManager.error(
new Error(
`${
keyboardType === "pin" ? "Pin" : "Password"
} does not match`
),
new Error(strings.mismatch(keyboardType)),
undefined,
"local"
);
@@ -326,9 +318,7 @@ export const AppLockPassword = () => {
if (!isCurrentPasswordCorrect) {
ToastManager.error(
new Error(
`${keyboardType === "pin" ? "Pin" : "Password"} incorrect`
),
new Error(strings.incorrect(keyboardType)),
undefined,
"local"
);
@@ -342,7 +332,7 @@ export const AppLockPassword = () => {
} else if (mode === "remove") {
if (!values.current.password) {
ToastManager.error(
new Error("All inputs are required"),
new Error(strings.allFieldsRequired()),
undefined,
"local"
);
@@ -357,10 +347,8 @@ export const AppLockPassword = () => {
ToastManager.error(
new Error(
accountPass
? "Account password incorrect"
: `${
keyboardType === "pin" ? "Pin" : "Password"
} incorrect`
? strings.passwordIncorrect()
: strings.incorrect(keyboardType)
),
undefined,
"local"
@@ -377,7 +365,7 @@ export const AppLockPassword = () => {
SettingsService.setProperty("appLockEnabled", false);
SettingsService.setPrivacyScreen(SettingsService.get());
ToastManager.show({
message: "App lock disabled",
message: strings.applockDisabled(),
type: "success"
});
}
@@ -391,9 +379,13 @@ export const AppLockPassword = () => {
close();
}}
positiveTitle={
mode === "remove" ? "Remove" : mode === "change" ? "Change" : "Save"
mode === "remove"
? strings.remove()
: mode === "change"
? strings.change()
: strings.save()
}
negativeTitle="Cancel"
negativeTitle={strings.cancel()}
positiveType="transparent"
loading={false}
doneText=""

View File

@@ -32,6 +32,7 @@ import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import { Notice } from "../../ui/notice";
import Paragraph from "../../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
export default function AttachImage({
response,
@@ -64,7 +65,7 @@ export default function AttachImage({
}}
>
<Paragraph style={{ color: colors.primary.paragraph, marginBottom: 6 }}>
Attaching {response?.length} image(s):
{strings.attachImageHeading(response?.length || 1)}
</Paragraph>
<ScrollView horizontal>
{response?.map?.((item) => (
@@ -123,14 +124,14 @@ export default function AttachImage({
}}
size={SIZE.sm}
>
Compress (recommended)
{strings.compress()} ({strings.recommended().toLowerCase()})
</Paragraph>
</TouchableOpacity>
{!compress ? (
<Notice
type="alert"
text="Images uploaded without compression are slow to load and take more bandwidth. We recommend compressing images unless you need image in original quality."
text={strings.compressionOffNotice()}
size="small"
style={{
width: "100%",
@@ -140,7 +141,7 @@ export default function AttachImage({
) : (
<Notice
type="information"
text="Compressed images are uploaded in Full HD resolution and usually are good enough for most use cases."
text={strings.compressionOnNotice()}
size="small"
style={{
width: "100%",
@@ -150,9 +151,7 @@ export default function AttachImage({
)}
<Button
title={`${
(response?.length || 0) > 1 ? "Attach Images" : "Attach Image"
}`}
title={strings.attachImageHeading(response?.length || 1)}
type="accent"
width="100%"
onPress={() => {

View File

@@ -33,6 +33,7 @@ import { Toast } from "../../toast";
import { Button } from "../../ui/button";
import Input from "../../ui/input";
import { Pressable } from "../../ui/pressable";
import { strings } from "@notesnook/intl";
const HEX_COLOR_REGEX_ALPHA =
/^#(?:(?:[\da-fA-F]{3}){1,2}|(?:[\da-fA-F]{4}){1,2})$/;
@@ -148,31 +149,31 @@ const ColorPicker = ({
}}
testID="color-title-input"
defaultValue={title.current}
placeholder={title.current || "Color title"}
placeholder={title.current || strings.colorTitle()}
/>
<Button
title="Add color"
title={strings.addColor()}
style={{
marginBottom: 10
}}
onPress={async () => {
if (!selectedColor)
return ToastManager.error(
new Error("Select a color"),
new Error(strings.noColorSelected()),
undefined,
"color-picker"
);
if (!title.current)
return ToastManager.error(
new Error("Enter a title for the color")
new Error(strings.allFieldsRequired())
);
const exists = await db.colors.all.find((v) =>
v.and([v(`colorCode`, "==", selectedColor)])
);
if (exists)
return ToastManager.error(
new Error(`Color #${selectedColor} already exists`)
new Error(strings.colorExists(selectedColor))
);
const id = await db.colors.add({
title: title.current,

View File

@@ -45,6 +45,7 @@ import Paragraph from "../../ui/typography/paragraph";
import { sleep } from "../../../utils/time";
import { MMKV } from "../../../common/database/mmkv";
import { deleteCacheFileByPath } from "../../../common/filesystem/io";
import { strings } from "@notesnook/intl";
const WIN_WIDTH = Dimensions.get("window").width;
const WIN_HEIGHT = Dimensions.get("window").height;
@@ -137,10 +138,10 @@ const PDFPreview = () => {
presentDialog({
context: attachment?.hash,
input: true,
inputPlaceholder: "Enter password",
positiveText: "Unlock",
title: "Decrypt",
paragraph: "Please input password to view pdf.",
inputPlaceholder: strings.enterPassword(),
positiveText: strings.open(),
title: strings.pdfLocked(),
paragraph: strings.pdfLockedDesc(),
positivePress: (value) => {
setTimeout(() => {
setPassword(value);
@@ -187,8 +188,7 @@ const PDFPreview = () => {
}}
color={colors.static.white}
>
Loading {`${progress?.percent ? `(${progress?.percent})` : ""}`}
... Please wait
{strings.loadingWithProgress(progress.percent)}
</Paragraph>
</Animated.View>
) : (

View File

@@ -39,10 +39,9 @@ const ResultDialog = () => {
const { colors } = useThemeColors();
const [visible, setVisible] = useState(false);
const [dialogData, setDialogData] = useState({
title: "Thank you for signing up!",
paragraph:
"Try out all features of Notesnook free for 7 days. No limitations. No commitments.",
button: "Start taking notes"
title: "",
paragraph: "",
button: ""
});
useEffect(() => {
eSubscribeEvent(eOpenResultDialog, open);

View File

@@ -52,6 +52,7 @@ import { Button } from "../../ui/button";
import Input from "../../ui/input";
import Seperator from "../../ui/seperator";
import Paragraph from "../../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
export class VaultDialog extends Component {
constructor(props) {
@@ -76,7 +77,7 @@ export class VaultDialog extends Component {
changePassword: false,
copyNote: false,
revokeFingerprintAccess: false,
title: "Unlock Note",
title: strings.goToEditor(),
description: null,
clearVault: false,
deleteVault: false,
@@ -91,46 +92,25 @@ export class VaultDialog extends Component {
this.password = null;
this.confirmPassword = null;
this.newPassword = null;
(this.title = !this.state.novault
? "Create Vault"
this.title = !this.state.novault
? strings.createVault()
: this.state.fingerprintAccess
? "Vault Fingerprint Unlock"
? strings.vaultFingerprintUnlock()
: this.state.revokeFingerprintAccess
? "Revoke Vault Fingerprint Unlock"
? strings.revokeVaultFingerprintUnlock()
: this.state.changePassword
? "Change Vault Password"
? strings.changeVaultPassword()
: this.state.noteLocked
? this.state.deleteNote
? "Delete note"
? strings.deleteNote()
: this.state.share
? "Share note"
? strings.shareNote()
: this.state.copyNote
? "Copy note"
? strings.copyNote()
: this.state.goToEditor
? "Unlock note"
: "Unlock note"
: "Lock note"),
(this.description = !this.state.novault
? "Set a password to create vault"
: this.state.fingerprintAccess
? "Enter vault password to enable fingerprint unlocking."
: this.state.revokeFingerprintAccess
? "Disable vault fingerprint unlock "
: this.state.changePassword
? "Setup a new password for the vault."
: this.state.permanant
? "Enter password to remove note from vault."
: this.state.noteLocked
? this.state.deleteNote
? "Unlock note to delete it. If biometrics are not working, you can enter device pin to unlock vault."
: this.state.share
? "Unlock note to share it. If biometrics are not working, you can enter device pin to unlock vault."
: this.state.copyNote
? "Unlock note to copy it. If biometrics are not working, you can enter device pin to unlock vault."
: this.state.goToEditor
? "Unlock note to open it in editor."
: "Enter vault password to unlock note. If biometrics are not working, you can enter device pin to unlock vault."
: "Enter vault password to lock note. If biometrics are not working, you can enter device pin to lock note.");
? strings.goToEditor()
: strings.goToEditor()
: strings.lockNote();
}
componentDidMount() {
@@ -199,7 +179,7 @@ export class VaultDialog extends Component {
if (this.state.loading) {
ToastManager.show({
heading: this.state.title,
message: "Please wait and do not close the app.",
message: strings.pleaseWait() + "...",
type: "success",
context: "local"
});
@@ -234,8 +214,7 @@ export class VaultDialog extends Component {
if (!this.password) {
ToastManager.show({
heading: "Password not entered",
message: "Enter a password for the vault and try again.",
heading: strings.passwordNotEntered(),
type: "error",
context: "local"
});
@@ -245,7 +224,7 @@ export class VaultDialog extends Component {
if (!this.state.novault) {
if (this.password !== this.confirmPassword) {
ToastManager.show({
heading: "Passwords do not match",
heading: strings.passwordNotMatched(),
type: "error",
context: "local"
});
@@ -271,7 +250,7 @@ export class VaultDialog extends Component {
this._enrollFingerprint(this.newPassword);
}
ToastManager.show({
heading: "Vault password updated successfully",
heading: strings.passwordUpdated(),
type: "success",
context: "global"
});
@@ -283,8 +262,7 @@ export class VaultDialog extends Component {
});
if (e.message === db.vault.ERRORS.wrongPassword) {
ToastManager.show({
heading: "Incorrect password",
message: "Please enter the correct password and try again",
heading: strings.passwordIncorrect(),
type: "error",
context: "local"
});
@@ -295,8 +273,7 @@ export class VaultDialog extends Component {
} else if (this.state.locked) {
if (!this.password || this.password.trim() === 0) {
ToastManager.show({
heading: "Incorrect password",
message: "Please enter the correct password and try again",
heading: strings.passwordIncorrect(),
type: "error",
context: "local"
});
@@ -364,8 +341,7 @@ export class VaultDialog extends Component {
this.close();
} else {
ToastManager.show({
heading: "Account password incorrect",
message: "Please enter correct password for your account.",
heading: strings.passwordIncorrect(),
type: "error",
context: "local"
});
@@ -406,8 +382,7 @@ export class VaultDialog extends Component {
eSendEvent("vaultUpdated");
} catch (e) {
ToastManager.show({
heading: "Vault password incorrect",
message: "Please enter correct password to clear vault.",
heading: strings.passwordIncorrect(),
type: "error",
context: "local"
});
@@ -420,7 +395,7 @@ export class VaultDialog extends Component {
async _lockNote() {
if (!this.password || this.password.trim() === 0) {
ToastManager.show({
heading: "Incorrect password",
heading: strings.passwordIncorrect(),
type: "error",
context: "local"
});
@@ -433,7 +408,7 @@ export class VaultDialog extends Component {
this.close();
ToastManager.show({
message: "Note locked successfully",
message: strings.noteLocked(),
type: "error",
context: "local"
});
@@ -446,8 +421,7 @@ export class VaultDialog extends Component {
async _unlockNote() {
if (!this.password || this.password.trim() === 0) {
ToastManager.show({
heading: "Incorrect password",
message: "Please enter the correct password and try again",
heading: strings.passwordIncorrect(),
type: "error",
context: "local"
});
@@ -504,8 +478,7 @@ export class VaultDialog extends Component {
});
eSendEvent("vaultUpdated");
ToastManager.show({
heading: "Biometric unlocking enabled!",
message: "Now you can unlock notes in vault with biometrics.",
heading: strings.biometricUnlockEnabled(),
type: "success",
context: "global"
});
@@ -513,9 +486,7 @@ export class VaultDialog extends Component {
} catch (e) {
this.close();
ToastManager.show({
heading: "Incorrect password",
message:
"Please enter the correct vault password to enable biometrics.",
heading: strings.passwordIncorrect(),
type: "error",
context: "local"
});
@@ -540,7 +511,7 @@ export class VaultDialog extends Component {
loading: false
});
ToastManager.show({
heading: "Note added to vault",
heading: strings.noteLocked(),
type: "success",
context: "global"
});
@@ -548,7 +519,7 @@ export class VaultDialog extends Component {
} else {
eSendEvent("vaultUpdated");
ToastManager.show({
heading: "Vault created successfully",
heading: strings.vaultCreated(),
type: "success",
context: "global"
});
@@ -561,7 +532,7 @@ export class VaultDialog extends Component {
.remove(this.state.note.id, this.password)
.then(() => {
ToastManager.show({
heading: "Note permanently unlocked.",
heading: strings.noteUnlocked(),
type: "success",
context: "global"
});
@@ -569,6 +540,7 @@ export class VaultDialog extends Component {
this.close();
})
.catch((e) => {
console.log("Error", e);
this._takeErrorAction(e);
});
}
@@ -588,9 +560,8 @@ export class VaultDialog extends Component {
async _copyNote(note) {
Clipboard.setString((await convertNoteToText(note, true)) || "");
ToastManager.show({
heading: "Note copied",
heading: strings.noteCopied(),
type: "success",
message: "Note has been copied to clipboard!",
context: "global"
});
this.close();
@@ -600,7 +571,7 @@ export class VaultDialog extends Component {
this.close();
try {
await Share.open({
heading: "Share note",
heading: note.title,
failOnCancel: false,
message: (await convertNoteToText(note)) || ""
});
@@ -610,24 +581,17 @@ export class VaultDialog extends Component {
}
_takeErrorAction(e) {
if (
e.message === db.vault.ERRORS.wrongPassword ||
e.message === "FAILURE"
) {
this.setState({
wrongPassword: true,
visible: true
this.setState({
wrongPassword: true,
visible: true
});
setTimeout(() => {
ToastManager.show({
heading: strings.passwordIncorrect(),
type: "error",
context: "local"
});
setTimeout(() => {
ToastManager.show({
heading: "Incorrect password",
type: "error",
context: "local"
});
}, 500);
return;
}
}, 500);
}
_revokeFingerprintAccess = async () => {
@@ -635,14 +599,13 @@ export class VaultDialog extends Component {
await BiometricService.resetCredentials();
eSendEvent("vaultUpdated");
ToastManager.show({
heading: "Biometric unlocking disabled!",
heading: strings.biometricUnlockDisabled(),
type: "success",
context: "global"
});
} catch (e) {
ToastManager.show({
heading: "Failed to disable Biometric unlocking.",
message: e.message,
heading: e.message,
type: "success",
context: "global"
});
@@ -708,12 +671,7 @@ export class VaultDialog extends Component {
paddingTop: 12
}}
>
<DialogHeader
title={this.state.title}
paragraph={this.state.description}
icon="shield"
padding={12}
/>
<DialogHeader title={this.state.title} icon="shield" padding={12} />
<Seperator half />
<View
@@ -749,10 +707,16 @@ export class VaultDialog extends Component {
: this.onPress;
}}
autoComplete="password"
returnKeyLabel={changePassword ? "Next" : this.state.title}
returnKeyLabel={
changePassword ? strings.next() : this.state.title
}
returnKeyType={changePassword ? "next" : "done"}
secureTextEntry
placeholder={changePassword ? "Current password" : "Password"}
placeholder={
changePassword
? strings.currentPassword()
: strings.password()
}
/>
{!this.state.biometricUnlock ||
@@ -761,11 +725,11 @@ export class VaultDialog extends Component {
changePassword ? null : (
<Button
onPress={() =>
this._onPressFingerprintAuth("Unlock note", "")
this._onPressFingerprintAuth(strings.unlockNote(), "")
}
icon="fingerprint"
width="100%"
title={"Biometric unlock"}
title={strings.unlockWithBiometrics()}
type="transparent"
/>
)}
@@ -788,7 +752,7 @@ export class VaultDialog extends Component {
marginTop: 10
}}
width="100%"
title={"Delete all notes"}
title={strings.deleteAllNotes()}
type="errorShade"
/>
)}
@@ -809,7 +773,7 @@ export class VaultDialog extends Component {
returnKeyLabel="Change"
returnKeyType="done"
secureTextEntry
placeholder={"New password"}
placeholder={strings.newPassword()}
/>
</>
) : null}
@@ -824,13 +788,13 @@ export class VaultDialog extends Component {
this.password = value;
}}
autoComplete="password"
returnKeyLabel="Next"
returnKeyLabel={strings.next()}
returnKeyType="next"
secureTextEntry
onSubmit={() => {
this.confirmPassRef.current?.focus();
}}
placeholder="Password"
placeholder={strings.password()}
/>
<Input
@@ -859,7 +823,7 @@ export class VaultDialog extends Component {
}
}}
onSubmit={this.onPress}
placeholder="Confirm password"
placeholder={strings.confirmPassword()}
/>
</View>
) : null}
@@ -867,9 +831,7 @@ export class VaultDialog extends Component {
{this.state.biometricUnlock &&
!this.state.isBiometryEnrolled &&
novault ? (
<Paragraph>
Unlock with password once to enable biometric access.
</Paragraph>
<Paragraph>{strings.vaultEnableBiometrics()}</Paragraph>
) : null}
{this.state.isBiometryAvailable &&
@@ -888,7 +850,7 @@ export class VaultDialog extends Component {
}}
icon="fingerprint"
width="100%"
title="Biometric unlocking"
title={strings.unlockWithBiometrics()}
iconColor={
this.state.biometricUnlock
? colors.selected.accent
@@ -908,26 +870,26 @@ export class VaultDialog extends Component {
}
positiveTitle={
deleteVault
? "Delete"
? strings.delete()
: clearVault
? "Clear"
? strings.clear()
: fingerprintAccess
? "Enable"
? strings.enable()
: this.state.revokeFingerprintAccess
? "Revoke"
? strings.revoke()
: changePassword
? "Change"
? strings.change()
: this.state.noteLocked
? deleteNote
? "Delete"
? strings.delete()
: share
? "Share "
? strings.share()
: goToEditor
? "Open"
: "Unlock"
? strings.open()
: strings.unlock()
: !note.id
? "Create"
: "Lock"
? strings.create()
: strings.lock()
}
/>
</View>

View File

@@ -67,8 +67,6 @@ export const Header = ({
hasSearch?: boolean;
onSearch?: () => void;
}) => {
const navigation = useNavigation();
const { colors } = useThemeColors();
const insets = useGlobalSafeAreaInsets();
const [borderHidden, setBorderHidden] = useState(true);

View File

@@ -96,7 +96,8 @@ export const RightMenus = ({
animationDuration={200}
style={{
borderRadius: 5,
backgroundColor: contextMenuColors.primary.background
backgroundColor: contextMenuColors.primary.background,
marginTop: -40
}}
onRequestClose={() => {
menuRef.current?.hide();

View File

@@ -18,8 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Sodium from "@ammarahmed/react-native-sodium";
import dataurl from "@notesnook/core/dist/utils/dataurl";
import type { ImageAttributes } from "@notesnook/editor/dist/extensions/image/index";
import { DataURL } from "@notesnook/core";
import type { ImageAttributes } from "@notesnook/editor";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef, useState } from "react";
import { Platform, View } from "react-native";
@@ -55,8 +55,8 @@ const ImagePreview = () => {
setLoading(true);
setTimeout(async () => {
let hash = image.hash;
if (!hash && image.src && dataurl.toObject(image.src)) {
const data = dataurl.toObject(image.src);
if (!hash && image.src && DataURL.toObject(image.src)) {
const data = DataURL.toObject(image.src);
if (!data) return;
hash = await Sodium.hashFile({
data: data.data,

View File

@@ -40,6 +40,7 @@ import Navigation from "../../services/navigation";
import { eOpenLoginDialog } from "../../utils/events";
import { AuthMode } from "../auth";
import { eSendEvent } from "../../services/event-manager";
import { strings } from "@notesnook/intl";
const Intro = ({ navigation }) => {
const { colors } = useThemeColors();
@@ -96,11 +97,13 @@ const Intro = ({ navigation }) => {
extraBold
size={SIZE.xxl}
>
{heading}
{heading()}
</Heading>
))}
{item.body ? <Paragraph size={SIZE.sm}>{item.body}</Paragraph> : null}
{item.body ? (
<Paragraph size={SIZE.sm}>{item.body()}</Paragraph>
) : null}
{item.tesimonial ? (
<Paragraph
@@ -112,7 +115,7 @@ const Intro = ({ navigation }) => {
Linking.openURL(item.link);
}}
>
{item.tesimonial} {item.user}
{item.tesimonial()} {item.user}
</Paragraph>
) : null}
</View>
@@ -152,26 +155,7 @@ const Intro = ({ navigation }) => {
index={0}
useReactNativeGestureHandler={true}
showPagination
data={[
{
headings: ["Open source.", "End to end encrypted.", "Private."],
body: "Write notes with freedom, no spying, no tracking."
},
{
headings: [
"Privacy for everyone",
"— not just the",
"privileged few"
],
body: "Your privacy matters to us, no matter who you are. In a world where everyone is trying to spy on you, Notesnook encrypts all your data before it leaves your device. With Notesnook no one can ever sell your data again."
},
{
tesimonial:
"You simply cannot get any better of a note taking app than @notesnook. The UI is clean and slick, it is feature rich, encrypted, reasonably priced (esp. for students & educators) & open source",
link: "https://twitter.com/andrewsayer/status/1637817220113002503",
user: "@andrewsayer on Twitter"
}
]}
data={strings.introData}
paginationActiveColor={colors.primary.accent}
paginationStyleItem={{
width: 10,
@@ -212,7 +196,7 @@ const Intro = ({ navigation }) => {
}}
fontSize={SIZE.md}
type="accent"
title="Get started"
title={strings.getStarted()}
/>
</View>
</ScrollView>

View File

@@ -29,6 +29,7 @@ import { IconButton } from "../../ui/icon-button";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { getFormattedDate } from "@notesnook/common";
import { strings } from "@notesnook/intl";
export const NotebookHeader = ({
notebook,
@@ -57,7 +58,7 @@ export const NotebookHeader = ({
}
});
ToastManager.show({
heading: "Shortcut created",
heading: strings.shortcutCreated(),
type: "success"
});
}
@@ -148,11 +149,7 @@ export const NotebookHeader = ({
size={SIZE.xs}
color={colors.secondary.paragraph}
>
{notebook && totalNotes > 1
? totalNotes + " notes"
: totalNotes === 1
? totalNotes + " note"
: "0 notes"}
{strings.notes(totalNotes || 0)}
</Paragraph>
</View>
);

View File

@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { GroupHeader, GroupOptions, ItemType } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { TouchableOpacity, View, useWindowDimensions } from "react-native";
@@ -26,7 +27,6 @@ import { presentSheet } from "../../../services/event-manager";
import SettingsService from "../../../services/settings";
import { RouteName } from "../../../stores/use-navigation-store";
import { getContainerBorder } from "../../../utils/colors";
import { GROUP } from "../../../utils/constants";
import { SIZE } from "../../../utils/size";
import Sort from "../../sheets/sort";
import { Button } from "../../ui/button";
@@ -57,17 +57,14 @@ export const SectionHeader = React.memo<
}: SectionHeaderProps) {
const { colors } = useThemeColors();
const { fontScale } = useWindowDimensions();
let groupBy = Object.keys(GROUP).find(
(key) => GROUP[key as keyof typeof GROUP] === groupOptions.groupBy
);
const groupBy =
strings.groupByStrings[
groupOptions.groupBy as keyof typeof strings.groupByStrings
]?.();
const isCompactModeEnabled = useIsCompactModeEnabled(
dataType as "note" | "notebook"
);
groupBy = !groupBy
? "Default"
: groupBy.slice(0, 1).toUpperCase() + groupBy.slice(1, groupBy.length);
return (
<View
style={{
@@ -103,7 +100,7 @@ export const SectionHeader = React.memo<
textAlignVertical: "center"
}}
>
{!item.title || item.title === "" ? "Pinned" : item.title}
{!item.title || item.title === "" ? strings.pinned() : item.title}
</Heading>
</TouchableOpacity>
@@ -117,12 +114,10 @@ export const SectionHeader = React.memo<
<>
<Button
onPress={() => {
console.log("Opening Sort sheet", screen, dataType);
presentSheet({
component: <Sort screen={screen} type={dataType} />
});
}}
tooltipText="Change sorting of items in list"
title={groupBy}
icon={
groupOptions.sortDirection === "asc"
@@ -156,11 +151,6 @@ export const SectionHeader = React.memo<
screen !== "Notes"
}
testID="icon-compact-mode"
tooltipText={
isCompactModeEnabled
? "Switch to normal mode"
: "Switch to compact mode"
}
color={colors.secondary.icon}
name={isCompactModeEnabled ? "view-list" : "view-list-outline"}
onPress={() => {

View File

@@ -46,6 +46,7 @@ import { ReminderTime } from "../../ui/reminder-time";
import { TimeSince } from "../../ui/time-since";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
type NoteItemProps = {
item: Note | BaseTrashItem<Note>;
@@ -341,9 +342,10 @@ const NoteItem = ({
marginRight: 6
}}
>
Deleted on{" "}
{item && item.dateDeleted
? new Date(item.dateDeleted).toISOString().slice(0, 10)
? strings.deletedOn(
new Date(item.dateDeleted).toISOString().slice(0, 10)
)
: null}
</Paragraph>
@@ -354,8 +356,7 @@ const NoteItem = ({
marginRight: 6
}}
>
{(item as TrashItem).itemType[0].toUpperCase() +
(item as TrashItem).itemType.slice(1)}
{strings.note()}
</Paragraph>
</>
)}

View File

@@ -30,6 +30,7 @@ import { Properties } from "../../properties";
import { IconButton } from "../../ui/icon-button";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
type NotebookItemProps = {
item: Notebook | BaseTrashItem<Notebook>;
@@ -112,10 +113,11 @@ export const NotebookItem = ({
marginRight: 6
}}
>
{"Deleted on " +
{strings.deletedOn(
new Date((item as TrashItem).dateDeleted)
.toISOString()
.slice(0, 10)}
.slice(0, 10)
)}
</Paragraph>
<Paragraph
color={colors.primary.accent}
@@ -147,11 +149,7 @@ export const NotebookItem = ({
marginRight: 6
}}
>
{item && totalNotes > 1
? totalNotes + " notes"
: totalNotes === 1
? totalNotes + " note"
: "0 notes"}
{strings.notes(totalNotes)}
</Paragraph>
{item.pinned ? (
@@ -183,11 +181,7 @@ export const NotebookItem = ({
marginRight: 6
}}
>
{item && totalNotes > 1
? totalNotes + " notes"
: totalNotes === 1
? totalNotes + " note"
: "0 notes"}
{strings.notes(totalNotes)}
</Paragraph>
</>
) : null}

View File

@@ -28,6 +28,7 @@ import { useSelectionStore } from "../../../stores/use-selection-store";
import { useTrashStore } from "../../../stores/use-trash-store";
import { presentDialog } from "../../dialog/functions";
import SelectionWrapper, { selectItem } from "../selection-wrapper";
import { strings } from "@notesnook/intl";
export const openNotebook = (item: Notebook | BaseTrashItem<Notebook>) => {
const isTrash = item.type === "trash";
@@ -36,16 +37,15 @@ export const openNotebook = (item: Notebook | BaseTrashItem<Notebook>) => {
if (isTrash) {
presentDialog({
title: `Restore notebook`,
paragraph: `Restore or delete notebook forever`,
positiveText: "Restore",
negativeText: "Delete",
title: strings.restoreNotebook(),
positiveText: strings.restore(),
negativeText: strings.delete(),
positivePress: async () => {
await db.trash.restore(item.id);
Navigation.queueRoutesForUpdate();
useSelectionStore.getState().setSelectionMode(undefined);
ToastManager.show({
heading: "Restore successful",
heading: strings.notebookRestored(),
type: "success"
});
},
@@ -54,7 +54,7 @@ export const openNotebook = (item: Notebook | BaseTrashItem<Notebook>) => {
useTrashStore.getState().refresh();
useSelectionStore.getState().setSelectionMode(undefined);
ToastManager.show({
heading: "Permanently deleted items",
heading: strings.permanentlyDeletedNotebook(),
type: "success",
context: "local"
});

View File

@@ -30,6 +30,7 @@ import { ReminderTime } from "../../ui/reminder-time";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import SelectionWrapper, { selectItem } from "../selection-wrapper";
import { strings } from "@notesnook/intl";
const ReminderItem = React.memo(
({
@@ -108,7 +109,7 @@ const ReminderItem = React.memo(
color={colors.secondary.paragraph}
style={{ marginLeft: 5 }}
>
Disabled
{strings.disabled()}
</Paragraph>
</View>
) : null}
@@ -137,9 +138,7 @@ const ReminderItem = React.memo(
color={colors.secondary.paragraph}
style={{ marginLeft: 5 }}
>
{item.recurringMode.slice(0, 1).toUpperCase() +
item.recurringMode.replace("y", "i").slice(1) +
"ly"}
{strings.reminderRecurringMode[item.recurringMode]()}
</Paragraph>
</View>
) : null}

View File

@@ -29,6 +29,7 @@ import { IconButton } from "../../ui/icon-button";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import SelectionWrapper, { selectItem } from "../selection-wrapper";
import { strings } from "@notesnook/intl";
const TagItem = React.memo(
({
@@ -77,11 +78,7 @@ const TagItem = React.memo(
marginTop: 5
}}
>
{totalNotes > 1
? totalNotes + " notes"
: totalNotes === 1
? totalNotes + " note"
: null}
{strings.notes(totalNotes)}
</Paragraph>
</View>

View File

@@ -87,8 +87,8 @@ export const Empty = React.memo(function Empty({
color={color}
tip={
screen !== "Search"
? tip || ({ text: placeholder?.paragraph } as TTip)
: ({ text: placeholder?.paragraph } as TTip)
? tip || ({ text: () => placeholder?.paragraph } as TTip)
: ({ text: () => placeholder?.paragraph } as TTip)
}
style={{
backgroundColor: "transparent",

View File

@@ -36,7 +36,7 @@ import {
TrashItem,
VirtualizedGrouping
} from "@notesnook/core";
import { getSortValue } from "@notesnook/core/dist/utils/grouping";
import { getSortValue } from "@notesnook/core";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { View } from "react-native";
import { db } from "../../common/database";

View File

@@ -48,6 +48,7 @@ import { IconButton } from "../ui/icon-button";
import Seperator from "../ui/seperator";
import Paragraph from "../ui/typography/paragraph";
import { diff } from "diffblazer";
import { strings } from "@notesnook/intl";
const MergeConflicts = () => {
const { colors } = useThemeColors();
@@ -171,7 +172,11 @@ const MergeConflicts = () => {
fontWeight: "bold"
}}
>
{isCurrent ? "(This Device)" : "(Incoming)"}
(
{isCurrent
? strings.mergeConflict.thisDevice()
: strings.mergeConflict.otherDevice()}
)
</Text>
{"\n"}
{getFormattedDate(contentToKeep?.dateEdited)}
@@ -191,7 +196,7 @@ const MergeConflicts = () => {
setCopy(contentToKeep);
setDialogVisible(true);
}}
title="Save a copy"
title={strings.saveACopy()}
type="secondary"
height={30}
style={{
@@ -204,7 +209,7 @@ const MergeConflicts = () => {
<View style={{ width: 10 }} />
{isDiscarded ? (
<Button
title="Discard"
title={strings.discard()}
type="accent"
buttonType={{
color: colors.static.red,
@@ -234,7 +239,9 @@ const MergeConflicts = () => {
}}
type="accent"
fontSize={SIZE.xs}
title={keeping && !isDiscarded ? "Undo" : "Keep"}
title={
keeping && !isDiscarded ? strings.undo() : strings.keep()
}
onPress={() => {
setKeep(keeping && !isDiscarded ? null : contentToKeep);
}}
@@ -277,11 +284,7 @@ const MergeConflicts = () => {
{dialogVisible && (
<BaseDialog visible={true}>
<DialogContainer>
<DialogHeader
title="Apply Changes"
paragraph="Apply selected changes to note?"
padding={12}
/>
<DialogHeader title={strings.applyChanges()} padding={12} />
<Seperator />
<DialogButtons
positiveTitle="Apply"

View File

@@ -37,6 +37,7 @@ import { Pressable } from "../ui/pressable";
import Seperator from "../ui/seperator";
import Paragraph from "../ui/typography/paragraph";
import NotePreview from "./preview";
import { strings } from "@notesnook/intl";
const HistoryItem = ({
index,
@@ -141,11 +142,7 @@ export default function NoteHistory({
return (
<View>
<SheetProvider context="note_history" />
<DialogHeader
title="Note history"
paragraph="Revert back to an older version of this note"
padding={12}
/>
<DialogHeader title={strings.noteHistory()} padding={12} />
<Seperator />
@@ -180,7 +177,7 @@ export default function NoteHistory({
<>
<Icon name="history" size={50} color={colors.primary.icon} />
<Paragraph color={colors.secondary.paragraph}>
No note history found on this device.
{strings.noteHistoryPlaceholder()}
</Paragraph>
</>
)}
@@ -196,7 +193,7 @@ export default function NoteHistory({
alignSelf: "center"
}}
>
Note version history is local only.{" "}
{strings.noteHistoryNotice[0]()}{" "}
<Text
onPress={() => {
openLinkInBrowser(
@@ -208,7 +205,7 @@ export default function NoteHistory({
textDecorationLine: "underline"
}}
>
Learn how this works.
{strings.noteHistoryNotice[1]()}
</Text>
</Paragraph>
</View>

View File

@@ -35,6 +35,7 @@ import { presentDialog } from "../dialog/functions";
import { Button } from "../ui/button";
import Paragraph from "../ui/typography/paragraph";
import { diff } from "diffblazer";
import { strings } from "@notesnook/intl";
/**
*
@@ -51,7 +52,7 @@ export default function NotePreview({ session, content, note }) {
Navigation.queueRoutesForUpdate();
useSelectionStore.getState().setSelectionMode(false);
ToastManager.show({
heading: "Restore successful",
heading: strings.noteRestored(),
type: "success"
});
eSendEvent(eCloseSheet);
@@ -72,7 +73,7 @@ export default function NotePreview({ session, content, note }) {
Navigation.queueRoutesForUpdate();
ToastManager.show({
heading: "Note restored successfully",
heading: strings.noteRestoredFromHistory(),
type: "success"
});
}
@@ -83,17 +84,17 @@ export default function NotePreview({ session, content, note }) {
const deleteNote = async () => {
presentDialog({
title: `Delete note permanently`,
paragraph: `Are you sure you want to delete this note from trash permanentaly`,
positiveText: "Delete",
negativeText: "Cancel",
title: strings.deleteNote(),
paragraph: strings.deleteNoteConfirmation(),
positiveText: strings.delete(),
negativeText: strings.cancel(),
context: "local",
positivePress: async () => {
await db.trash.delete(note.id);
useTrashStore.getState().refresh();
useSelectionStore.getState().setSelectionMode(false);
ToastManager.show({
heading: "Permanently deleted items",
heading: strings.noteDeleted(),
type: "success",
context: "local"
});
@@ -150,7 +151,7 @@ export default function NotePreview({ session, content, note }) {
}}
>
<Paragraph color={colors.secondary.paragraph}>
Preview not available, content is encrypted.
{strings.encryptedNoteHistoryNotice()}
</Paragraph>
</View>
)}
@@ -160,10 +161,15 @@ export default function NotePreview({ session, content, note }) {
paddingHorizontal: 12
}}
>
<Button onPress={restore} title="Restore" type="accent" width="100%" />
<Button
onPress={restore}
title={strings.restore()}
type="accent"
width="100%"
/>
<Button
onPress={deleteNote}
title="Delete permanently"
title={strings.deletePermanently()}
type="error"
width="100%"
style={{

View File

@@ -48,6 +48,7 @@ import Paragraph from "../ui/typography/paragraph";
import { Walkthrough } from "../walkthroughs";
import { PricingItem } from "./pricing-item";
import { useSettingStore } from "../../stores/use-setting-store";
import { strings } from "@notesnook/intl";
const UUID_PREFIX = "0bdaea";
const UUID_VERSION = "4";
@@ -516,7 +517,7 @@ export const PricingPlans = ({
setBuying(true);
try {
if (!(await getPromo(value as string)))
throw new Error("Error applying promo code");
throw new Error(strings.errorApplyingPromoCode());
ToastManager.show({
heading: "Discount applied!",
type: "success",

View File

@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Color, Note } from "@notesnook/core/dist/types";
import { Color, Note } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useState } from "react";
import { View } from "react-native";
@@ -36,6 +36,7 @@ import ColorPicker from "../dialogs/color-picker";
import { Button } from "../ui/button";
import NativeTooltip from "../../utils/tooltip";
import { Pressable } from "../ui/pressable";
import { strings } from "@notesnook/intl";
const ColorItem = ({ item, note }: { item: Color; note: Note }) => {
const { colors } = useThemeColors();
@@ -145,7 +146,7 @@ export const ColorTags = ({ item }: { item: Note }) => {
buttonType={{
text: colors.primary.accent
}}
title="Add color"
title={strings.addColor()}
type="secondary"
icon="plus"
iconPosition="right"

View File

@@ -23,26 +23,10 @@ import { useThemeColors } from "@notesnook/theme";
import { SIZE } from "../../utils/size";
import Paragraph from "../ui/typography/paragraph";
import { getFormattedDate } from "@notesnook/common";
import { strings } from "@notesnook/intl";
export const DateMeta = ({ item }) => {
const { colors } = useThemeColors();
const getNameFromKey = (key) => {
switch (key) {
case "dateCreated":
return "Created at:";
case "dateEdited":
return "Last edited at:";
case "dateModified":
return "Last modified at:";
case "dateDeleted":
return "Deleted at:";
case "dateUploaded":
return "Uploaded at:";
default:
return key;
}
};
function getDateMeta() {
let keys = Object.keys(item);
if (keys.includes("dateEdited"))
@@ -64,7 +48,7 @@ export const DateMeta = ({ item }) => {
}}
>
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
{getNameFromKey(key)}
{strings.dateDescFromKey(key)}
</Paragraph>
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
{getFormattedDate(item[key], "date-time")}

View File

@@ -39,6 +39,7 @@ import Notebooks from "./notebooks";
import { Synced } from "./synced";
import { TagStrip, Tags } from "./tags";
import { tabBarRef } from "../../utils/global-refs";
import { strings } from "@notesnook/intl";
const Line = ({ top = 6, bottom = 6 }) => {
const { colors } = useThemeColors();
@@ -61,7 +62,7 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
if (!item || !item.id) {
return (
<Paragraph style={{ marginVertical: 10, alignSelf: "center" }}>
Start writing to save your note.
{strings.noNotePropertiesNotice()}
</Paragraph>
);
}

View File

@@ -29,6 +29,7 @@ import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
import Heading from "../ui/typography/heading";
import { Pressable } from "../ui/pressable";
import { strings } from "@notesnook/intl";
export default function Notebooks({ note, close, full }) {
const { colors } = useThemeColors();
@@ -102,7 +103,7 @@ export default function Notebooks({ note, close, full }) {
{noteNotebooks.length > 1 && !full ? (
<Button
title={`View all linked notebooks`}
title={strings.viewAllLinkedNotebooks()}
fontSize={SIZE.xs}
style={{
alignSelf: "flex-end",

View File

@@ -28,6 +28,7 @@ import { sleep } from "../../utils/time";
import { Button } from "../ui/button";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
export const Synced = ({ item, close }) => {
const { colors } = useThemeColors();
const user = useUserStore((state) => state.user);
@@ -71,7 +72,7 @@ export const Synced = ({ item, close }) => {
flexWrap: "wrap"
}}
>
Encrypted and synced
{strings.noteSyncedNoticeHeading()}
</Heading>
{shouldShrink ? null : (
<Paragraph
@@ -81,7 +82,7 @@ export const Synced = ({ item, close }) => {
size={SIZE.xs}
color={colors.primary.paragraph}
>
No one can view this {item.itemType || item.type} except you.
{strings.noteSyncedNoticeDesc(item.itemType || item.type)}
</Paragraph>
)}
</View>
@@ -99,7 +100,7 @@ export const Synced = ({ item, close }) => {
console.error(e);
}
}}
title="Learn more"
title={strings.learnMore()}
fontSize={SIZE.xs}
height={30}
type="secondaryAccented"

View File

@@ -27,6 +27,7 @@ import { sleep } from "../../utils/time";
import ManageTagsSheet from "../sheets/manage-tags";
import { Button } from "../ui/button";
import { ColorTags } from "./color-tags";
import { strings } from "@notesnook/intl";
export const Tags = ({ item, close }) => {
const { colors } = useThemeColors();
@@ -51,7 +52,7 @@ export const Tags = ({ item, close }) => {
buttonType={{
text: colors.primary.accent
}}
title="Add tag"
title={strings.addTag()}
type="secondary"
icon="plus"
iconPosition="right"

View File

@@ -46,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(
({
@@ -94,21 +95,20 @@ export const SelectionHeader = React.memo(
clearSelection();
ToastManager.show({
heading: "Restore successful",
heading: strings.restored(),
type: "success"
});
};
const deleteItem = async () => {
presentDialog({
title: `Delete ${selectedItemsList.length > 1 ? "items" : "item"}`,
paragraph: `Are you sure you want to delete ${
selectedItemsList.length > 1
? "these items permanently?"
: "this item permanently?"
}`,
positiveText: "Delete",
negativeText: "Cancel",
title: strings.deleteItems(type as string, selectedItemsList.length),
paragraph: strings.deleteItemsConfirmation(
type as string,
selectedItemsList.length
),
positiveText: strings.delete(),
negativeText: strings.cancel(),
positivePress: async () => {
if (!selectedItemsList.length) return;
await db.trash.delete(...selectedItemsList);
@@ -236,10 +236,7 @@ export const SelectionHeader = React.memo(
>
{[
{
title:
selectedItemsList.length > 1
? "Move notebooks"
: "Move notebook",
title: strings.move(),
onPress: async () => {
const ids = selectedItemsList;
const notebooks = await db.notebooks.all.items(ids);
@@ -249,7 +246,7 @@ export const SelectionHeader = React.memo(
icon: "arrow-right-bold-box-outline"
},
{
title: "Manage tags",
title: strings.manageTags(),
onPress: async () => {
await sleep(100);
ManageTagsSheet.present(selectedItemsList);
@@ -258,7 +255,7 @@ export const SelectionHeader = React.memo(
icon: "pound"
},
{
title: "Export",
title: strings.export(),
onPress: async () => {
await sleep(100);
ExportNotesSheet.present(selectedItemsList);
@@ -267,7 +264,7 @@ export const SelectionHeader = React.memo(
icon: "export"
},
{
title: "Link notebook",
title: strings.linkNotebook(),
onPress: async () => {
await sleep(100);
MoveNoteSheet.present();
@@ -276,7 +273,7 @@ export const SelectionHeader = React.memo(
icon: "plus"
},
{
title: "Unlink notebook",
title: strings.unlinkNotebook(),
onPress: async () => {
if (!id) return;
await db.notes.removeFromNotebook(id, ...selectedItemsList);
@@ -288,13 +285,13 @@ export const SelectionHeader = React.memo(
icon: "minus"
},
{
title: "Unfavorite",
title: strings.unfavorite(),
onPress: addToFavorite,
visible: focusedRouteId === "Favorites",
icon: "star-off"
},
{
title: `Move to trash`,
title: strings.moveToTrash(),
onPress: async () => {
deleteItems(
undefined,
@@ -308,13 +305,13 @@ export const SelectionHeader = React.memo(
icon: "delete"
},
{
title: `Restore`,
title: strings.restore(),
onPress: restoreItem,
visible: type === "trash",
icon: "delete-restore"
},
{
title: `Delete`,
title: strings.delete(),
onPress: deleteItem,
visible: type === "trash",
icon: "delete"

View File

@@ -40,6 +40,7 @@ import { MoveNotes } from "../move-notes/movenote";
import { eOnNotebookUpdated } from "../../../utils/events";
import { getParentNotebookId } from "../../../utils/notebooks";
import { useNotebookStore } from "../../../stores/use-notebook-store";
import { strings } from "@notesnook/intl";
export const AddNotebookSheet = ({
notebook,
@@ -62,18 +63,18 @@ export const AddNotebookSheet = ({
const onSaveChanges = async () => {
if (loading) return;
setLoading(true);
if (!title.current || title?.current.trim().length === 0) {
ToastManager.show({
heading: "Notebook title is required",
heading: strings.enterNotebookTitle(),
type: "error",
context: "local"
});
setLoading(false);
return;
}
setLoading(true);
const id = await db.notebooks.add({
title: title.current,
description: description.current,
@@ -119,7 +120,7 @@ export const AddNotebookSheet = ({
}}
>
<Heading size={SIZE.lg}>
{notebook ? "Edit Notebook" : "New Notebook"}
{notebook ? strings.editNotebook() : strings.newNotebook()}
</Heading>
<Seperator />
@@ -135,7 +136,7 @@ export const AddNotebookSheet = ({
titleInput?.current?.focus();
});
}}
placeholder="Enter notebook title"
placeholder={strings.enterNotebookTitle()}
onSubmit={() => {
descriptionInput.current?.focus();
}}
@@ -150,14 +151,14 @@ export const AddNotebookSheet = ({
onChangeText={(value) => {
description.current = value;
}}
placeholder="Enter notebook description"
returnKeyLabel="Next"
placeholder={strings.enterNotebookDescription()}
returnKeyLabel={strings.next()}
returnKeyType="next"
defaultValue={notebook ? notebook.description : ""}
/>
<Button
title={notebook ? "Save" : "Add"}
title={notebook ? strings.save() : strings.add()}
type="accent"
height={45}
fontSize={SIZE.md}

View File

@@ -55,6 +55,7 @@ import { useNotebookItemSelectionStore } from "./store";
import { AddNotebookSheet } from "../add-notebook";
import Input from "../../ui/input";
import { presentDialog } from "../../dialog/functions";
import { strings } from "@notesnook/intl";
async function updateInitialSelectionState(items: string[]) {
const relations = await db.relations
@@ -200,11 +201,11 @@ const MoveNoteSheet = ({
minHeight: 10,
flexShrink: 1
}}
title="Select notebooks"
title={strings.selectNotebooks()}
paragraph={
!multiSelect
? "Long press to enable multi-select."
: "Select notebooks you want to add note(s) to."
? strings.enableMultiSelect()
: strings.selectNotebooksDesc()
}
/>
@@ -236,7 +237,7 @@ const MoveNoteSheet = ({
paddingHorizontal: 24,
alignSelf: "flex-start"
}}
title="Save"
title={strings.save()}
type={"accent"}
onPress={onSave}
/>
@@ -266,7 +267,7 @@ const MoveNoteSheet = ({
}}
>
<Input
placeholder="Search notebooks...."
placeholder={strings.searchNotebooks()}
button={{
icon: "plus",
onPress: () => {
@@ -319,7 +320,7 @@ const MoveNoteSheet = ({
/>
) : (
<Paragraph color={colors.primary.icon}>
No notebooks
{strings.emptyPlaceholders("notebook")}
</Paragraph>
)}
</View>

View File

@@ -30,6 +30,7 @@ import DialogHeader from "../../dialog/dialog-header";
import { Button } from "../../ui/button";
import Input from "../../ui/input";
import { eUserLoggedIn } from "../../../utils/events";
import { strings } from "@notesnook/intl";
type ChangeEmailProps = {
actionSheetRef: RefObject<ActionSheetRef>;
@@ -68,7 +69,7 @@ export const ChangeEmail = ({ close }: ChangeEmailProps) => {
const verified = await db.user?.verifyPassword(
emailChangeData.current.password
);
if (!verified) throw new Error("Password is incorrect");
if (!verified) throw new Error(strings.passwordIncorrect());
await db.user?.sendVerificationEmail(emailChangeData.current.email);
setStep(EmailChangeSteps.changeEmail);
setLoading(false);
@@ -88,8 +89,7 @@ export const ChangeEmail = ({ close }: ChangeEmailProps) => {
eSendEvent(eUserLoggedIn);
close?.();
ToastManager.show({
heading: `Email changed`,
message: `Your account email has been updated to ${emailChangeData.current.email}`,
heading: strings.emailUpdated(emailChangeData.current.email),
type: "success",
context: "global"
});
@@ -103,8 +103,8 @@ export const ChangeEmail = ({ close }: ChangeEmailProps) => {
return (
<View style={{ paddingHorizontal: 12 }}>
<DialogHeader
title="Change email"
paragraph="Your account email will be changed without affecting your subscription or any other settings."
title={strings.changeEmail()}
paragraph={strings.changeEmailDesc()}
/>
<View
style={{
@@ -115,7 +115,7 @@ export const ChangeEmail = ({ close }: ChangeEmailProps) => {
<>
<Input
fwdRef={emailInputRef}
placeholder="Enter your new email"
placeholder={strings.enterNewEmail()}
validationType="email"
onErrorCheck={(e) => setError(e)}
onChangeText={(email) => {
@@ -124,7 +124,7 @@ export const ChangeEmail = ({ close }: ChangeEmailProps) => {
/>
<Input
fwdRef={passInputRef}
placeholder="Enter your account password"
placeholder={strings.enterAccountPassword()}
secureTextEntry
onChangeText={(pass) => {
emailChangeData.current.password = pass;
@@ -135,7 +135,7 @@ export const ChangeEmail = ({ close }: ChangeEmailProps) => {
<>
<Input
fwdRef={codeInputRef}
placeholder="Enter verification code sent to your new email"
placeholder={strings.verifyNewEmail()}
onChangeText={(code) => {
emailChangeData.current.code = code;
}}
@@ -149,8 +149,8 @@ export const ChangeEmail = ({ close }: ChangeEmailProps) => {
loading
? undefined
: step === EmailChangeSteps.verify
? "Verify"
: "Change email"
? strings.verify()
: strings.changeEmail()
}
type="accent"
width={250}

View File

@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Note } from "@notesnook/core";
import { EVENTS } from "@notesnook/core/dist/common";
import { EVENTS } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect } from "react";
import { View } from "react-native";
@@ -35,6 +35,7 @@ import { IconButton } from "../../ui/icon-button";
import { Pressable } from "../../ui/pressable";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
type TabItem = {
id: number;
@@ -137,7 +138,9 @@ const TabItemComponent = (props: {
numberOfLines={1}
size={SIZE.md}
>
{props.tab.noteId ? item?.title || "Untitled note" : "New note"}
{props.tab.noteId
? item?.title || strings.untitledNote()
: strings.newNote()}
</Paragraph>
</View>
@@ -229,7 +232,7 @@ export default function EditorTabs({
alignItems: "center"
}}
>
<Heading size={SIZE.lg}>Tabs</Heading>
<Heading size={SIZE.lg}>{strings.tabs()}</Heading>
<Button
onPress={() => {
useTabStore.getState().newTab();
@@ -240,7 +243,7 @@ export default function EditorTabs({
}, 500);
close?.();
}}
title="New tab"
title={strings.newTab()}
icon="plus"
style={{
flexDirection: "row",

View File

@@ -52,6 +52,7 @@ import { Pressable } from "../../ui/pressable";
import Seperator from "../../ui/seperator";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
const ExportNotesSheet = ({
ids,
@@ -118,7 +119,6 @@ const ExportNotesSheet = ({
await exportNoteAs("pdf");
},
icon: "file-pdf-box",
desc: "View in any pdf reader app",
id: notesnook.ids.dialogs.export.pdf,
pro: premium
},
@@ -128,7 +128,6 @@ const ExportNotesSheet = ({
await exportNoteAs("md");
},
icon: "language-markdown",
desc: "View in any text or markdown editor",
id: notesnook.ids.dialogs.export.md,
pro: premium
},
@@ -138,7 +137,6 @@ const ExportNotesSheet = ({
await exportNoteAs("md-frontmatter");
},
icon: "language-markdown",
desc: "View in any text or markdown editor",
id: notesnook.ids.dialogs.export.md,
pro: premium
},
@@ -148,7 +146,6 @@ const ExportNotesSheet = ({
await exportNoteAs("txt");
},
icon: "card-text",
desc: "View in any text editor",
id: notesnook.ids.dialogs.export.text,
pro: true
},
@@ -158,7 +155,6 @@ const ExportNotesSheet = ({
await exportNoteAs("html");
},
icon: "language-html5",
desc: "View in any web browser & html reader",
id: notesnook.ids.dialogs.export.html,
pro: premium
}
@@ -175,14 +171,7 @@ const ExportNotesSheet = ({
>
<DialogHeader
icon="export"
title={
ids.length > 1 ? `Export ${ids.length} Notes` : "Export Note"
}
paragraph={`All exports are saved in ${
Platform.OS === "android"
? "the selected"
: "Notesnook/exported"
} folder in phone storage`}
title={`${strings.export()} ${strings.notes(ids.length)}`}
/>
</View>
@@ -238,13 +227,13 @@ const ExportNotesSheet = ({
<Heading style={{ marginLeft: 10 }} size={SIZE.md}>
{item.title}
</Heading>
<Paragraph
{/* <Paragraph
style={{ marginLeft: 10 }}
size={SIZE.sm}
color={colors.secondary.paragraph}
>
{item.desc}
</Paragraph>
</Paragraph> */}
</View>
</Pressable>
</Fragment>
@@ -264,9 +253,10 @@ const ExportNotesSheet = ({
<>
<ActivityIndicator />
<Paragraph>
{`${
status ? `${status})` : `Exporting notes`
}... Please wait`}
{strings.exportingNotes(status) +
"..." +
" " +
strings.pleaseWait()}
</Paragraph>
</>
) : (
@@ -287,21 +277,20 @@ const ExportNotesSheet = ({
}}
color={colors.secondary.heading}
>
{ids.length > 1
? `${ids.length} Notes exported`
: "Note exported"}
{strings.exportSuccessHeading(ids.length)}
</Heading>
<Paragraph
style={{
textAlign: "center"
}}
>
Your {ids.length > 1 ? "notes are" : "note is"} exported
successfully as {result?.fileName}
{strings.exportSuccessDesc(result?.fileName as string)}
</Paragraph>
<Button
title={
Platform.OS === "android" ? "Open file location" : "Open"
Platform.OS === "android"
? strings.openFileLocation()
: strings.open()
}
type="accent"
width={250}
@@ -322,8 +311,7 @@ const ExportNotesSheet = ({
showAppsSuggestions: true
}).catch((e) => {
ToastManager.show({
heading: "Cannot open",
message: `No application found to open ${result.name} file.`,
heading: strings.noApplicationFound(result.name),
type: "success",
context: "local"
});
@@ -332,7 +320,7 @@ const ExportNotesSheet = ({
}}
/>
<Button
title="Share"
title={strings.share()}
type="secondaryAccented"
width={250}
fontSize={SIZE.md}
@@ -356,7 +344,7 @@ const ExportNotesSheet = ({
}}
/>
<Button
title="Export in another format"
title={strings.exportAgain()}
type="inverted"
width={250}
fontSize={SIZE.md}

View File

@@ -23,6 +23,7 @@ import FileViewer from "react-native-file-viewer";
import { ToastManager } from "../../../services/event-manager";
import { SIZE } from "../../../utils/size";
import { Button } from "../../ui/button";
import { strings } from "@notesnook/intl";
export const ShareComponent = ({ uri, name, padding }) => {
return (
<View
@@ -31,7 +32,7 @@ export const ShareComponent = ({ uri, name, padding }) => {
}}
>
<Button
title="Open"
title={strings.open()}
type="accent"
width="100%"
fontSize={SIZE.md}
@@ -41,8 +42,7 @@ export const ShareComponent = ({ uri, name, padding }) => {
showAppsSuggestions: true
}).catch(() => {
ToastManager.show({
heading: "Cannot open",
message: `No application found to open ${name} file.`,
heading: strings.noApplicationFound(name),
type: "success",
context: "local"
});
@@ -50,7 +50,7 @@ export const ShareComponent = ({ uri, name, padding }) => {
}}
/>
<Button
title="Share"
title={strings.share()}
type="shade"
width="100%"
fontSize={SIZE.md}

View File

@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Debug } from "@notesnook/core/dist/api/debug";
import { Debug } from "@notesnook/core";
import { getModel, getBrand, getSystemVersion } from "react-native-device-info";
import { useThemeColors } from "@notesnook/theme";
import React, { useRef, useState } from "react";
@@ -34,6 +34,7 @@ import { Button } from "../../ui/button";
import Seperator from "../../ui/seperator";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
export const Issue = ({ defaultTitle, defaultBody, issueTitle }) => {
const { colors } = useThemeColors();
@@ -75,8 +76,7 @@ Logged in: ${user ? "yes" : "no"}`,
} catch (e) {
setLoading(false);
ToastManager.show({
heading: "An error occured",
message: e.message,
heading: e.message,
type: "error"
});
}
@@ -99,14 +99,14 @@ Logged in: ${user ? "yes" : "no"}`,
gap: 10
}}
>
<Heading>Issue submitted</Heading>
<Heading>{strings.issueCreatedHeading()}</Heading>
<Paragraph
style={{
textAlign: "center"
}}
selectable={true}
>
You can track your issue at{" "}
{strings.issueCreatedDesc[0]()}
<Paragraph
style={{
textDecorationLine: "underline",
@@ -118,12 +118,11 @@ Logged in: ${user ? "yes" : "no"}`,
>
{issueUrl.current}
</Paragraph>
. Please note that we will respond to your issue on the given
link. We recommend that you save it.
. {strings.issueCreatedDesc[1]()}
</Paragraph>
<Button
title="Open issue"
title={strings.openIssue()}
onPress={() => {
Linking.openURL(issueUrl.current);
}}
@@ -135,18 +134,14 @@ Logged in: ${user ? "yes" : "no"}`,
) : (
<>
<DialogHeader
title={issueTitle || "Report issue"}
paragraph={
issueTitle
? "We are sorry, it seems that the app crashed due to an error. You can submit a bug report below so we can fix this asap."
: "Let us know if you have faced any issue/bug while using Notesnook."
}
title={issueTitle || strings.issueTitle()}
paragraph={issueTitle ? strings.issueDesc() : strings.issueDesc2()}
/>
<Seperator half />
<TextInput
placeholder="Title"
placeholder={strings.title()}
onChangeText={(v) => (title.current = v)}
defaultValue={title.current}
style={{
@@ -165,13 +160,7 @@ Logged in: ${user ? "yes" : "no"}`,
<TextInput
ref={bodyRef}
multiline
placeholder={`Tell us more about the issue you are facing.
For example:
- What were you trying to do in the app?
- What did you expect to happen?
- Steps to reproduce the issue
- Things you have tried etc.`}
placeholder={strings.issuePlaceholder()}
numberOfLines={5}
textAlignVertical="top"
onChangeText={(v) => (body.current = v)}
@@ -211,7 +200,7 @@ For example:
<Seperator />
<Button
onPress={onPress}
title={loading ? null : "Submit"}
title={loading ? null : strings.submit()}
loading={loading}
width="100%"
type="accent"
@@ -225,10 +214,10 @@ For example:
textAlign: "center"
}}
>
The information above will be publically available at{" "}
{strings.issueNotice[0]()}{" "}
<Text
onPress={() => {
Linking.openURL("https://github.com/streetwriters/notesnook");
Linking.openURL("https://github.com/streetwriters/notesnook/issues");
}}
style={{
textDecorationLine: "underline",
@@ -237,8 +226,7 @@ For example:
>
github.com/streetwriters/notesnook.
</Text>{" "}
If you want to ask something in general or need some assistance, we
would suggest that you{" "}
{strings.issueNotice[1]()}{" "}
<Text
style={{
textDecorationLine: "underline",
@@ -255,7 +243,7 @@ For example:
}
}}
>
join our community on Discord.
{strings.issueNotice[2]()}
</Text>
</Paragraph>
</>

View File

@@ -34,11 +34,12 @@ import { Button } from "../../ui/button";
import Input from "../../ui/input";
import { Pressable } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph";
import type { LinkAttributes } from "@notesnook/editor/dist/extensions/link";
import type { LinkAttributes } from "@notesnook/editor";
import {
EditorEvents,
editorController
} from "../../../screens/editor/tiptap/utils";
import { strings } from "@notesnook/intl";
const ListNoteItem = ({
id,
@@ -117,7 +118,7 @@ const ListBlockItem = ({
{item?.content.length > 200
? item?.content.slice(0, 200) + "..."
: !item.content || item.content.trim() === ""
? "(Empty block)"
? strings.linkNoteEmptyBlock()
: item.content}
</Paragraph>
@@ -237,8 +238,8 @@ export default function LinkNote(props: {
<Input
placeholder={
selectedNote
? "Search a section of a note to link to"
: "Search a note to link to"
? strings.searchSectionToLinkPlaceholder()
: strings.searchNoteToLinkPlaceholder()
}
containerStyle={{
width: "100%"
@@ -256,7 +257,7 @@ export default function LinkNote(props: {
}}
>
<Paragraph color={colors.secondary.paragraph} size={SIZE.xs}>
SELECTED NOTE
{strings.linkNoteSelectedNote()}
</Paragraph>
<Pressable
onPress={() => {
@@ -286,7 +287,7 @@ export default function LinkNote(props: {
<Paragraph numberOfLines={1}>{selectedNote?.title}</Paragraph>
<Paragraph color={colors.secondary.paragraph} size={SIZE.xs}>
Tap to deselect
{strings.tapToDeselect()}
</Paragraph>
</View>
</Pressable>
@@ -299,7 +300,7 @@ export default function LinkNote(props: {
color={colors.secondary.paragraph}
size={SIZE.xs}
>
LINK TO A SECTION
{strings.linkNoteToSection()}
</Paragraph>
) : null}
</View>
@@ -342,7 +343,7 @@ export default function LinkNote(props: {
style={{
marginTop: 10
}}
title="Create link"
title={strings.createLink()}
type="accent"
width="100%"
onPress={() => {

View File

@@ -18,8 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { VirtualizedGrouping } from "@notesnook/core";
import { Tags } from "@notesnook/core/dist/collections/tags";
import { Tag } from "@notesnook/core/dist/types";
import { sanitizeTag } from "@notesnook/core";
import { Tag } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, {
RefObject,
@@ -48,6 +48,7 @@ import Input from "../../ui/input";
import { Pressable } from "../../ui/pressable";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
async function updateInitialSelectionState(items: string[]) {
const relations = await db.relations
@@ -138,11 +139,6 @@ const ManageTagsSheet = (props: {
const onSubmit = async () => {
if (!query || query === "" || query.trimStart().length == 0) {
ToastManager.show({
heading: "Tag field is empty",
type: "error",
context: "local"
});
return;
}
@@ -181,12 +177,7 @@ const ManageTagsSheet = (props: {
useTagStore.getState().refresh();
setQuery(undefined);
} catch (e) {
ToastManager.show({
heading: "Cannot add tag",
type: "error",
message: (e as Error).message,
context: "local"
});
ToastManager.error(e as Error);
}
Navigation.queueRoutesForUpdate();
@@ -268,8 +259,8 @@ const ManageTagsSheet = (props: {
fwdRef={inputRef}
autoCapitalize="none"
onChangeText={(v) => {
setQuery(Tags.sanitize(v));
checkQueryExists(Tags.sanitize(v));
setQuery(sanitizeTag(v));
checkQueryExists(sanitizeTag(v));
}}
onFocusInput={() => {
setFocus(true);
@@ -280,7 +271,7 @@ const ManageTagsSheet = (props: {
onSubmit={() => {
onSubmit();
}}
placeholder="Search or add a tag"
placeholder={strings.searchForTags()}
/>
{query && !queryExists ? (
@@ -296,7 +287,7 @@ const ManageTagsSheet = (props: {
type="selected"
>
<Heading size={SIZE.sm} color={colors.selected.heading}>
Add {'"' + "#" + query + '"'}
{strings.add()} {'"' + "#" + query + '"'}
</Heading>
<Icon name="plus" color={colors.selected.icon} size={SIZE.lg} />
</Pressable>
@@ -331,7 +322,7 @@ const ManageTagsSheet = (props: {
textBreakStrategy="balanced"
color={colors.secondary.paragraph}
>
You do not have any tags.
{strings.emptyPlaceholders("tag")}
</Paragraph>
</View>
}

View File

@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { EVENTS } from "@notesnook/core/dist/common";
import { EVENTS } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useState } from "react";
import { Platform, View } from "react-native";
@@ -41,6 +41,7 @@ import Seperator from "../../ui/seperator";
import { ProgressBarComponent } from "../../ui/svg/lazy";
import Paragraph from "../../ui/typography/paragraph";
import { Issue } from "../github/issue";
import { strings } from "@notesnook/intl";
export const makeError = (stack: string, component: string) => `
@@ -133,11 +134,9 @@ export default function Migrate() {
>
{!loading && !error ? (
<DialogHeader
title="Save a backup of your notes"
title={strings.migrationSaveBackup()}
centered
paragraph={
"Thank you for updating Notesnook! We will be applying some minor changess for a better note taking experience."
}
paragraph={strings.migrationSaveBackupDesc()}
/>
) : null}
<Seperator />
@@ -158,9 +157,7 @@ export default function Migrate() {
textAlign: "center"
}}
>
Migrating database{progress ? `(${progress?.collection})` : null}
{progress ? `(${progress.current}/${progress.total}) ` : null}
... please wait
{strings.migrationProgress(progress)}
</Paragraph>
<View
@@ -190,9 +187,7 @@ export default function Migrate() {
textAlign: "center"
}}
>
An error occurred while migrating your data. You can logout of your
account and try to relogin. However this is not recommended as it
may result in some data loss if your data was not synced.
{strings.migrationError()}
</Paragraph>
{reset ? (
@@ -202,11 +197,11 @@ export default function Migrate() {
textAlign: "center"
}}
>
App data has been cleared. Kindly relaunch the app to login again.
{strings.migrationAppReset()}
</Paragraph>
) : (
<Button
title="Logout & clear app data"
title={strings.logoutAndClearData()}
type="error"
width={250}
onPress={async () => {
@@ -224,7 +219,7 @@ export default function Migrate() {
</>
) : (
<Button
title="Save & continue"
title={strings.saveAndContinue()}
type="accent"
width={250}
onPress={startMigration}

View File

@@ -45,6 +45,7 @@ import { Pressable } from "../../ui/pressable";
import Seperator from "../../ui/seperator";
import Paragraph from "../../ui/typography/paragraph";
import { AddNotebookSheet } from "../add-notebook";
import { strings } from "@notesnook/intl";
const useNotebookExpandedStore = create<{
expanded: {
@@ -96,17 +97,12 @@ export const MoveNotebookSheet = ({
selectedNotebooks={selectedNotebooks}
onPress={async (selectedNotebook) => {
presentDialog({
title: `Move ${
selectedNotebooks.length > 1
? selectedNotebooks.length + " notebooks"
: selectedNotebooks[0].title
}`,
paragraph: `Are you sure you want to move ${
selectedNotebooks.length > 1
? selectedNotebooks.length + " notebooks"
: selectedNotebooks[0].title
} to ${selectedNotebook.title}?`,
positiveText: "Move",
title: strings.moveNotebooks(selectedNotebooks.length),
paragraph: strings.moveNotebooksConfirm(
selectedNotebooks.length,
selectedNotebook.title
),
positiveText: strings.move(),
context: "move-notebook",
positivePress: async () => {
for (const notebook of selectedNotebooks) {
@@ -152,12 +148,10 @@ export const MoveNotebookSheet = ({
}}
>
<DialogHeader
title={`Moving ${
selectedNotebooks.length > 1
? selectedNotebooks.length + " notebooks"
: selectedNotebooks[0].title
}`}
paragraph={"Select the notebook you want to move this notebook to."}
title={strings.moveNotebook(
selectedNotebooks.length,
selectedNotebooks[0].title
)}
/>
</View>
<Seperator />
@@ -174,7 +168,7 @@ export const MoveNotebookSheet = ({
>
{moveToTop ? (
<Button
title="Move to top"
title={strings.moveToTop()}
style={{
alignSelf: "flex-start",
width: "100%",

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { VirtualizedGrouping } from "@notesnook/core";
import { Note, Notebook } from "@notesnook/core/dist/types";
import { Note, Notebook } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { RefObject, useEffect, useRef, useState } from "react";
import { ActivityIndicator, TextInput, View } from "react-native";
@@ -39,6 +39,7 @@ import Input from "../../ui/input";
import { Pressable } from "../../ui/pressable";
import Seperator from "../../ui/seperator";
import Paragraph from "../../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
const useItemSelectionStore = createItemSelectionStore(true);
@@ -123,10 +124,7 @@ export const MoveNotes = ({
>
<Dialog context="local" />
<DialogHeader
title={`Add notes to ${currentNotebook.title}`}
paragraph={"Select the notes you want to move to this Notebook."}
/>
<DialogHeader title={strings.addNotesToNotebook(currentNotebook.title)} />
<Seperator />
<Input
@@ -142,7 +140,7 @@ export const MoveNotes = ({
onChangeText={(v) => {
loadNotes(v && v.trim() === "" ? undefined : v.trim());
}}
placeholder="Search a note"
placeholder={strings.searchANote()}
/>
<FlashList
@@ -158,7 +156,7 @@ export const MoveNotes = ({
<ActivityIndicator size="large" color={colors.primary.accent} />
) : (
<Paragraph color={colors.secondary.paragraph}>
No notes to show
{strings.emptyPlaceholders("note")}
</Paragraph>
)}
</View>
@@ -185,7 +183,7 @@ export const MoveNotes = ({
Navigation.queueRoutesForUpdate();
fwdRef?.current?.hide();
}}
title="Move selected notes"
title={strings.moveSelectedNotes()}
type="accent"
width="100%"
/>

View File

@@ -31,6 +31,7 @@ import { Button } from "../../ui/button";
import Seperator from "../../ui/seperator";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
export type FeatureType = {
title: string;
body: string;
@@ -56,7 +57,7 @@ const NewFeature = ({
}}
>
<Heading color={colors.secondary.heading} size={SIZE.md}>
{!version ? "New version " : `v${version} `}Highlights 🎉
{!version ? `${strings.newVersion()} ` : `v${version} `}Highlights 🎉
</Heading>
<Seperator />
@@ -81,7 +82,7 @@ const NewFeature = ({
<Seperator />
<Button
title="Got it"
title={strings.gotIt()}
type="accent"
width={250}
style={{

View File

@@ -51,6 +51,7 @@ import Paragraph from "../../ui/typography/paragraph";
import { AddNotebookSheet } from "../add-notebook";
import { MoveNotebookSheet } from "../move-notebook";
import Sort from "../sort";
import { strings } from "@notesnook/intl";
const useItemSelectionStore = createItemSelectionStore(true, false);
@@ -289,7 +290,7 @@ export const NotebookSheet = () => {
}}
>
<Paragraph size={SIZE.xs} color={colors.primary.icon}>
NOTEBOOKS
{strings.notebooks()}
</Paragraph>
<View
style={{
@@ -433,7 +434,9 @@ export const NotebookSheet = () => {
height: 200
}}
>
<Paragraph color={colors.primary.icon}>No notebooks</Paragraph>
<Paragraph color={colors.primary.icon}>
{strings.emptyPlaceholders("notebook")}
</Paragraph>
</View>
}
/>

View File

@@ -27,6 +27,7 @@ import Seperator from "../../ui/seperator";
import { ProgressBarComponent } from "../../ui/svg/lazy";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
export const Progress = () => {
const { colors } = useThemeColors();
const { progress } = useSyncProgress();
@@ -51,8 +52,8 @@ export const Progress = () => {
paddingBottom: 15
}}
>
<Heading size={SIZE.lg}>Syncing your data</Heading>
<Paragraph>Please wait while we sync all your data.</Paragraph>
<Heading size={SIZE.lg}>{strings.syncingHeading()}</Heading>
<Paragraph>{strings.syncingDesc()}</Paragraph>
<Seperator />
<View
style={{
@@ -73,8 +74,7 @@ export const Progress = () => {
{progress ? (
<Paragraph color={colors.secondary.paragraph}>
{progress.type?.slice(0, 1).toUpperCase() + progress.type?.slice(1)}
ing {progress?.current}
{strings.networkProgress(progress.type)} {progress?.current}
</Paragraph>
) : null}
</View>

View File

@@ -37,6 +37,7 @@ import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { requestInAppReview } from "../../../services/app-review";
import { Note } from "@notesnook/core";
import { strings } from "@notesnook/intl";
const PublishNoteSheet = ({
note: item
@@ -76,7 +77,7 @@ const PublishNoteSheet = ({
requestInAppReview();
} catch (e) {
ToastManager.show({
heading: "Could not publish note",
heading: strings.failedToPublish(),
message: (e as Error).message,
type: "error",
context: "local"
@@ -102,7 +103,7 @@ const PublishNoteSheet = ({
}
} catch (e) {
ToastManager.show({
heading: "Could not unpublish note",
heading: strings.failedToUnpublish(),
message: (e as Error).message,
type: "error",
context: "local"
@@ -120,10 +121,8 @@ const PublishNoteSheet = ({
}}
>
<DialogHeader
title={note?.title}
paragraph={`Anyone with the link${
isLocked ? " and password" : ""
} of the published note can view it.`}
title={`${strings.publish()} ${strings.note()}`}
paragraph={strings.publishDesc()}
/>
{publishing ? (
@@ -141,9 +140,9 @@ const PublishNoteSheet = ({
textAlign: "center"
}}
>
Please wait...
{strings.pleaseWait()}...
{downloading && downloading.current && downloading.total
? `\nDownloading attachments (${
? `\n${strings.downloadingAttachments()} (${
downloading?.current / downloading?.total
})`
: ""}
@@ -168,7 +167,7 @@ const PublishNoteSheet = ({
flexShrink: 1
}}
>
<Heading size={SIZE.md}>Published at:</Heading>
<Heading size={SIZE.md}>{strings.publishedAt()}:</Heading>
<Paragraph size={SIZE.sm} numberOfLines={1}>
{publishUrl}
</Paragraph>
@@ -186,8 +185,8 @@ const PublishNoteSheet = ({
color: colors.primary.paragraph
}}
>
<Icon color={colors.primary.accent} name="open-in-new" /> Open
in browser
<Icon color={colors.primary.accent} name="open-in-new" />{" "}
{strings.openInBrowser()}
</Paragraph>
</View>
@@ -195,7 +194,7 @@ const PublishNoteSheet = ({
onPress={() => {
Clipboard.setString(publishUrl as string);
ToastManager.show({
heading: "Note publish url copied",
heading: strings.monographUrlCopied(),
type: "success",
context: "local"
});
@@ -243,10 +242,8 @@ const PublishNoteSheet = ({
flexShrink: 1
}}
>
<Heading size={SIZE.md}>Password protection</Heading>
<Paragraph>
Published note can only be viewed by someone with the password.
</Paragraph>
<Heading size={SIZE.md}>{strings.monographPassHeading()}</Heading>
<Paragraph>{strings.monographPassDesc()}</Paragraph>
</View>
</TouchableOpacity>
@@ -282,11 +279,10 @@ const PublishNoteSheet = ({
flexShrink: 1
}}
>
<Heading size={SIZE.md}>Self destruct</Heading>
<Paragraph>
Published note link will be automatically deleted once it is
viewed by someone.
</Paragraph>
<Heading size={SIZE.md}>
{strings.monographSelfDestructHeading()}
</Heading>
<Paragraph>{strings.monographSelfDestructDesc()}</Paragraph>
</View>
</TouchableOpacity>
@@ -305,7 +301,7 @@ const PublishNoteSheet = ({
blurOnSubmit
secureTextEntry
defaultValue={passwordValue.current}
placeholder="Enter Password"
placeholder={strings.enterPassword()}
/>
<Seperator half />
</>
@@ -324,7 +320,7 @@ const PublishNoteSheet = ({
onPress={deletePublishedNote}
fontSize={SIZE.md}
type="error"
title="Unpublish"
title={strings.unpublish()}
style={{
width: "49%"
}}
@@ -340,7 +336,7 @@ const PublishNoteSheet = ({
borderRadius: isPublished ? 5 : 100
}}
type="accent"
title={isPublished ? "Update" : "Publish"}
title={isPublished ? strings.update() : strings.publish()}
/>
</View>
</View>
@@ -365,7 +361,7 @@ const PublishNoteSheet = ({
}
}}
>
Learn more about Notesnook Monograph
{strings.monographLearnMore()}
</Paragraph>
</View>
);

View File

@@ -33,6 +33,7 @@ import Seperator from "../../ui/seperator";
import SheetWrapper from "../../ui/sheet";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
const RateAppSheet = () => {
const [visible, setVisible] = useState(false);
const actionSheetRef = useRef();
@@ -85,11 +86,8 @@ const RateAppSheet = () => {
paddingHorizontal: 12
}}
>
<Heading>Do you enjoy using Notesnook?</Heading>
<Paragraph size={SIZE.md}>
It took us a year to bring Notesnook to life. Share your experience
and suggestions to help us improve it.
</Paragraph>
<Heading>{strings.rateAppHeading()}</Heading>
<Paragraph size={SIZE.md}>{strings.rateAppDesc()}</Paragraph>
<Seperator half />
<Button
@@ -97,7 +95,7 @@ const RateAppSheet = () => {
fontSize={SIZE.md}
width="100%"
type="accent"
title="Rate now (It takes only a second)"
title={strings.rateApp()}
/>
<View
style={{
@@ -120,14 +118,14 @@ const RateAppSheet = () => {
fontSize={SIZE.md}
type="error"
width="48%"
title="Never"
title={strings.never()}
/>
<Button
onPress={onClose}
fontSize={SIZE.md}
width="48%"
type="secondary"
title="Later"
title={strings.later()}
/>
</View>
</View>

View File

@@ -43,6 +43,7 @@ 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) {
@@ -75,8 +76,8 @@ class RecoveryKeySheet extends React.Component {
close = () => {
if (this.tapCount === 0) {
ToastManager.show({
heading: "Did you save recovery key?",
message: "Tap one more time to confirm.",
heading: strings.recoveryKeySaved(),
message: strings.recoveryKeySavedDesc(),
type: "success",
context: "local"
});
@@ -129,9 +130,7 @@ class RecoveryKeySheet extends React.Component {
await RNFetchBlob.fs.writeFile(path + fileName, data, "base64");
}
ToastManager.show({
heading: "Recovery key QR-Code saved",
message:
"QR-Code image has been saved to Gallery at " + path + fileName,
heading: strings.recoveryKeyQRCodeSaved(),
type: "success",
context: "local"
});
@@ -164,8 +163,7 @@ class RecoveryKeySheet extends React.Component {
}
ToastManager.show({
heading: "Recovery key text file saved",
message: "Recovery key saved in text file.",
heading: strings.recoveryKeyTextFileSaved(),
type: "success",
context: "local"
});
@@ -227,9 +225,8 @@ class RecoveryKeySheet extends React.Component {
}}
>
<DialogHeader
title="Save account recovery key"
paragraph="If you forget your password, you can recover your
data and reset your password only using this recovery key."
title={strings.saveRecoveryKey()}
paragraph={strings.saveRecoveryKeyDesc()}
/>
<View
@@ -284,20 +281,20 @@ class RecoveryKeySheet extends React.Component {
onPress={() => {
Clipboard.setString(this.state.key);
ToastManager.show({
heading: "Recovery key copied!",
heading: strings.recoveryKeyCopied(),
type: "success",
context: "local"
});
}}
icon="content-copy"
title="Copy to clipboard"
title={strings.copyToClipboard()}
width="100%"
type="secondaryAccented"
fontSize={SIZE.md}
/>
<Seperator />
<Button
title="Save QR-Code to gallery"
title={strings.saveQRCode()}
onPress={this.saveQRCODE}
width="100%"
type="secondaryAccented"
@@ -307,7 +304,7 @@ class RecoveryKeySheet extends React.Component {
<Seperator />
<Button
onPress={this.saveToTextFile}
title="Save to text file"
title={strings.saveAsText()}
width="100%"
type="secondaryAccented"
icon="text"
@@ -317,7 +314,7 @@ class RecoveryKeySheet extends React.Component {
<Button
onPress={this.shareFile}
title="Share to Cloud"
title={strings.shareToCloud()}
width="100%"
type="secondaryAccented"
icon="cloud"
@@ -336,10 +333,10 @@ class RecoveryKeySheet extends React.Component {
textAlign: "center"
}}
>
Tap twice to confirm you have saved the recovery key.
{strings.recoveryKeySavedConfirmation()}
</Paragraph>
<Button
title="I have saved the key."
title={strings.done()}
width="100%"
type="error"
fontSize={SIZE.md}

View File

@@ -23,7 +23,7 @@ import {
createInternalLink,
highlightInternalLinks
} from "@notesnook/core";
import { ContentBlock, ItemReference, Note } from "@notesnook/core/dist/types";
import { ContentBlock, ItemReference, Note } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { MutableRefObject, useEffect, useRef, useState } from "react";
import { ActivityIndicator, View } from "react-native";
@@ -41,6 +41,7 @@ import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import { Pressable } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
export const useExpandedStore = create<{
expanded: {
@@ -99,7 +100,7 @@ const ListBlockItem = ({
{item?.content.length > 200
? item?.content.slice(0, 200) + "..."
: !item.content || item.content.trim() === ""
? "(Empty block)"
? strings.linkNoteEmptyBlock()
: item.content}
</Paragraph>
@@ -360,7 +361,7 @@ const ListNoteItem = ({
<>
{linkedBlocks.length === 0 ? (
<Paragraph color={colors.secondary.paragraph}>
No blocks linked
{strings.noBlocksLinked()}
</Paragraph>
) : (
linkedBlocks.map(renderBlock)
@@ -370,7 +371,7 @@ const ListNoteItem = ({
<>
{noteInternalLinks.length === 0 ? (
<Paragraph color={colors.secondary.paragraph}>
No references found of this note
{strings.noReferencesFound()}
</Paragraph>
) : (
noteInternalLinks.map(renderInternalLink)
@@ -447,7 +448,7 @@ export const ReferencesList = ({ item, close }: ReferencesListProps) => {
>
<Button
type={"plain"}
title="Linked notes"
title={strings.linkedNotes()}
style={{
borderRadius: 0,
borderWidth: 0,
@@ -462,7 +463,7 @@ export const ReferencesList = ({ item, close }: ReferencesListProps) => {
/>
<Button
type={"plain"}
title="Referenced in"
title={strings.referencedIn()}
style={{
width: "50%",
borderWidth: 0,
@@ -486,9 +487,7 @@ export const ReferencesList = ({ item, close }: ReferencesListProps) => {
}}
>
<Paragraph color={colors.secondary.paragraph}>
{tab === 1
? "This note is not referenced in other notes."
: "This note does not link to other notes."}
{tab === 1 ? strings.notReferenced() : strings.notLinked()}
</Paragraph>
</View>
) : (

View File

@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { VirtualizedGrouping } from "@notesnook/core";
import { Item, ItemReference } from "@notesnook/core/dist/types";
import { Item, ItemReference } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { RefObject, useEffect, useState } from "react";
import { View } from "react-native";
@@ -37,6 +37,8 @@ import SheetProvider from "../../sheet-provider";
import { Button } from "../../ui/button";
import { PressableProps } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph";
import { isStringLiteralOrJsxExpression } from "typescript";
import { strings } from "@notesnook/intl";
type RelationsListProps = {
actionSheetRef: RefObject<ActionSheetRef>;
@@ -114,9 +116,7 @@ export const RelationsList = ({
size={60}
color={colors.primary.icon}
/>
<Paragraph>
No {referenceType}s linked to this {item.type}.
</Paragraph>
<Paragraph>{strings.noLinksFound()}</Paragraph>
<Button
onPress={() => {
onAdd?.();
@@ -125,7 +125,7 @@ export const RelationsList = ({
// width="100%"
type="inverted"
icon="plus"
title={`Add a ${referenceType}`}
title={strings.addItem(referenceType)}
/>
</View>
) : (

View File

@@ -40,6 +40,7 @@ import {
VirtualizedGrouping,
Note
} from "@notesnook/core";
import { strings } from "@notesnook/intl";
type ReminderSheetProps = {
actionSheetRef: RefObject<ActionSheetRef>;
@@ -67,19 +68,19 @@ export default function ReminderNotify({
const QuickActions = [
{
title: "5 min",
title: `5 ${strings.timeShort.minute}`,
time: 5
},
{
title: "15 min",
title: `15 ${strings.timeShort.minute}`,
time: 15
},
{
title: "30 min",
title: `30 ${strings.timeShort.minute}`,
time: 30
},
{
title: "1 hour",
title: `1 ${strings.timeShort.hour}`,
time: 60
}
];
@@ -134,7 +135,7 @@ export default function ReminderNotify({
marginTop: 10
}}
>
<Paragraph size={SIZE.xs}>Remind me in:</Paragraph>
<Paragraph size={SIZE.xs}>{strings.remindMeIn()}:</Paragraph>
{QuickActions.map((item) => {
return (
<Button
@@ -171,7 +172,7 @@ export default function ReminderNotify({
marginBottom: 10
}}
>
REFERENCED IN
{strings.referencedIn()}
</Paragraph>
<List
data={references}

View File

@@ -49,6 +49,7 @@ import { ReminderTime } from "../../ui/reminder-time";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { ItemReference, Note, Reminder } from "@notesnook/core";
import { strings } from "@notesnook/intl";
type ReminderSheetProps = {
actionSheetRef: RefObject<ActionSheetRef>;
@@ -168,9 +169,7 @@ export default function ReminderSheet({
async function saveReminder() {
try {
if (!(await Notifications.checkAndRequestPermissions(true)))
throw new Error(
"App does not have permission to schedule notifications"
);
throw new Error(strings.noNotificationPermission());
if (!date && reminderMode !== ReminderModes.Permanent) return;
if (
reminderMode === ReminderModes.Repeat &&
@@ -178,12 +177,12 @@ export default function ReminderSheet({
recurringMode !== "year" &&
selectedDays.length === 0
)
throw new Error("Please select the day to repeat the reminder on");
throw new Error(strings.selectDayError());
if (!title.current) throw new Error("Please set title of the reminder");
if (!title.current) throw new Error(strings.setTitleError());
if (date.getTime() < Date.now() && reminderMode === "once") {
titleRef?.current?.focus();
throw new Error("Reminder date must be set in future");
throw new Error(strings.dateError());
}
date.setSeconds(0, 0);
@@ -205,12 +204,6 @@ export default function ReminderSheet({
if (!reminderId) return;
const _reminder = await db.reminders?.reminder(reminderId);
if (!_reminder) {
ToastManager.show({
heading: "Failed to add a new reminder",
context: "local"
});
}
if (reference && _reminder) {
await db.relations?.add(reference, {
id: _reminder?.id as string,
@@ -234,7 +227,7 @@ export default function ReminderSheet({
}}
>
<Heading size={SIZE.lg}>
{reminder ? "Edit reminder" : "New reminder"}
{reminder ? strings.editReminder() : strings.newReminder()}
</Heading>
<Dialog context="local" />
@@ -247,7 +240,7 @@ export default function ReminderSheet({
<Input
fwdRef={titleRef}
defaultValue={reminder?.title || referencedItem?.title}
placeholder="Remind me of..."
placeholder={strings.remindeMeOf()}
onChangeText={(text) => (title.current = text)}
wrapperStyle={{
marginTop: 10
@@ -258,7 +251,7 @@ export default function ReminderSheet({
defaultValue={
reminder ? reminder?.description : referencedItem?.headline
}
placeholder="Add a short note"
placeholder={strings.addShortNote()}
onChangeText={(text) => (details.current = text)}
containerStyle={{
maxHeight: 80
@@ -286,7 +279,9 @@ export default function ReminderSheet({
{Object.keys(ReminderModes).map((mode) => (
<Button
key={mode}
title={mode}
title={strings.reminderModes(
ReminderModes[mode as keyof typeof ReminderModes] as string
)}
style={{
marginRight: 12,
borderRadius: 100,
@@ -342,9 +337,9 @@ export default function ReminderSheet({
{Object.keys(RecurringModes).map((mode) => (
<Button
key={mode}
title={
!repeatFrequency || repeatFrequency <= 1 ? mode : mode + "s"
}
title={strings.recurringModes(
RecurringModes[mode as keyof typeof RecurringModes]
)}
style={{
marginRight: 6,
borderRadius: 100
@@ -376,10 +371,12 @@ export default function ReminderSheet({
: recurringMode === RecurringModes.Week
? WeekDays.map((item, index) => (
<Button
key={WeekDayNames[index as keyof typeof WeekDayNames]}
title={WeekDayNames[
index as keyof typeof WeekDayNames
].slice(0, 1)}
key={strings.weekDayNamesShort[
index as keyof typeof strings.weekDayNamesShort
]()}
title={strings.weekDayNamesShort[
index as keyof typeof strings.weekDayNamesShort
]()}
type={
selectedDays.indexOf(index) > -1 ? "selected" : "plain"
}
@@ -478,7 +475,7 @@ export default function ReminderSheet({
style={{
width: "100%"
}}
title={date ? date.toLocaleDateString() : "Select date"}
title={date ? date.toLocaleDateString() : strings.selectDate()}
type={date ? "secondaryAccented" : "secondary"}
icon="calendar"
fontSize={SIZE.md}
@@ -507,27 +504,28 @@ export default function ReminderSheet({
<>
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
{recurringMode === RecurringModes.Daily
? "Repeats daily " + `at ${dayjs(date).format("hh:mm A")}.`
? strings.reminderRepeatStrings.day(
dayjs(date).format("hh:mm A")
)
: recurringMode === RecurringModes.Year
? `The reminder will repeat every year on ${dayjs(
date
).format("dddd, MMMM D, h:mm A")}.`
? strings.reminderRepeatStrings.year(
dayjs(date).format("dddd, MMMM D, h:mm A")
)
: selectedDays.length === 7 &&
recurringMode === RecurringModes.Week
? `The reminder will repeat daily at ${dayjs(date).format(
"hh:mm A"
)}.`
? strings.reminderRepeatStrings.week.daily(
dayjs(date).format("hh:mm A")
)
: selectedDays.length === 0
? recurringMode === RecurringModes.Week
? "Select day of the week to repeat the reminder."
: "Select nth day of the month to repeat the reminder."
: `Repeats every${
repeatFrequency > 1 ? " " + repeatFrequency : ""
} ${
repeatFrequency > 1 ? recurringMode + "s" : recurringMode
} on ${getSelectedDaysText(selectedDays)} at ${dayjs(
date
).format("hh:mm A")}.`}
? strings.reminderRepeatStrings[
recurringMode as "week" | "month"
].selectDays()
: strings.reminderRepeatStrings.repeats(
repeatFrequency,
recurringMode as string,
getSelectedDaysText(selectedDays),
dayjs(date).format("hh:mm A")
)}
</Paragraph>
</>
</View>
@@ -556,7 +554,9 @@ export default function ReminderSheet({
{Object.keys(ReminderNotificationModes).map((mode) => (
<Button
key={mode}
title={mode}
title={strings.reminderNotificationModes[
mode as keyof typeof strings.reminderNotificationModes
]()}
style={{
marginRight: 12,
borderRadius: 100
@@ -593,7 +593,7 @@ export default function ReminderSheet({
</ScrollView>
<Button
title="Save"
title={strings.save()}
type="accent"
height={45}
fontSize={SIZE.md}

View File

@@ -29,6 +29,7 @@ import { SIZE } from "../../../utils/size";
import { Button } from "../../ui/button";
import Seperator from "../../ui/seperator";
import Heading from "../../ui/typography/heading";
import { strings } from "@notesnook/intl";
const Sort = ({ type, screen }) => {
const isTopicSheet = screen === "TopicSheet";
const { colors } = useThemeColors();
@@ -80,7 +81,7 @@ const Sort = ({ type, screen }) => {
alignSelf: "center"
}}
>
Sort by
{strings.sortBy()}
</Heading>
<Button
@@ -88,16 +89,16 @@ const Sort = ({ type, screen }) => {
groupOptions?.sortDirection === "asc"
? groupOptions?.groupBy === "abc" ||
groupOptions?.sortBy === "title"
? "A - Z"
? strings.aToZ()
: groupOptions?.sortBy === "dueDate"
? "Earliest first"
: "Old - New"
? strings.earliestFirst()
: strings.oldNew()
: groupOptions?.groupBy === "abc" ||
groupOptions?.sortBy === "title"
? "Z - A"
? strings.zToA()
: groupOptions?.sortBy === "dueDate"
? "Latest first"
: "New - Old"
? strings.latestFirst()
: strings.newOld()
}
icon={
groupOptions?.sortDirection === "asc"
@@ -137,7 +138,7 @@ const Sort = ({ type, screen }) => {
{groupOptions?.groupBy === "abc" ? (
<Button
type="secondary"
title="Title"
title={strings.title()}
height={40}
iconPosition="left"
icon={"check"}
@@ -155,7 +156,7 @@ const Sort = ({ type, screen }) => {
<Button
key={item}
type={groupOptions?.sortBy === item ? "selected" : "plain"}
title={SORT[item]}
title={strings.sortByStrings[item]()}
height={40}
iconPosition="left"
icon={groupOptions?.sortBy === item ? "check" : null}
@@ -195,7 +196,7 @@ const Sort = ({ type, screen }) => {
}}
size={SIZE.lg}
>
Group by
{strings.groupBy()}
</Heading>
<Seperator />
@@ -236,9 +237,7 @@ const Sort = ({ type, screen }) => {
}}
height={40}
icon={groupOptions?.groupBy === GROUP[item] ? "check" : null}
title={
item.slice(0, 1).toUpperCase() + item.slice(1, item.length)
}
title={strings.groupByStrings[item]()}
style={{
paddingHorizontal: 8,
marginBottom: 10,

View File

@@ -30,6 +30,7 @@ import {
editorState
} from "../../../screens/editor/tiptap/utils";
import { FlatList } from "react-native-actions-sheet";
import { strings } from "@notesnook/intl";
type TableOfContentsItem = {
level: number;
@@ -87,7 +88,7 @@ const TableOfContentsItem: React.FC<{
}
size={SIZE.md}
>
{item?.title || "New note"}
{item?.title || strings.newNote()}
</Paragraph>
</View>
</Pressable>
@@ -111,7 +112,7 @@ const TableOfContents = ({ toc, close }: TableOfContentsProps) => {
alignItems: "center"
}}
>
<Heading size={SIZE.lg}>Table of contents</Heading>
<Heading size={SIZE.lg}>{strings.toc()}</Heading>
</View>
<FlatList

File diff suppressed because one or more lines are too long

View File

@@ -37,6 +37,7 @@ import { useSideBarDraggingStore } from "./dragging-store";
import { MenuItem } from "./menu-item";
import { PinnedSection } from "./pinned-section";
import { UserStatus } from "./user-status";
import { strings } from "@notesnook/intl";
export const SideMenu = React.memo(
function SideMenu() {
@@ -86,7 +87,13 @@ export const SideMenu = React.memo(
return (
<MenuItem
key={item.name}
item={item}
item={{
...item,
title:
strings.routes[
item.name as keyof typeof strings.routes
]?.() || item.name
}}
testID={item.name}
index={index}
/>
@@ -153,7 +160,7 @@ export const SideMenu = React.memo(
width: "100%"
}}
icon="close"
title="Tap to stop reordering"
title={strings.stopReordering()}
onPress={() => {
useSideBarDraggingStore.setState({
dragging: false

View File

@@ -29,6 +29,7 @@ import { Button } from "../ui/button";
import { Pressable } from "../ui/pressable";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
function _MenuItem({
item,
@@ -112,10 +113,10 @@ function _MenuItem({
/>
{isFocused ? (
<Heading color={colors.selected.heading} size={SIZE.md}>
{item.name}
{item.title || item.name}
</Heading>
) : (
<Paragraph size={SIZE.md}>{item.name}</Paragraph>
<Paragraph size={SIZE.md}>{item.title || item.name}</Paragraph>
)}
{item.isBeta ? (
@@ -129,7 +130,7 @@ function _MenuItem({
}}
>
<Paragraph color={primaryColors.accentForeground} size={SIZE.xxs}>
BETA
{strings.beta()}
</Paragraph>
</View>
) : null}

View File

@@ -38,6 +38,7 @@ import Seperator from "../ui/seperator";
import SheetWrapper from "../ui/sheet";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
export const PinnedSection = React.memo(
function PinnedSection() {
@@ -100,7 +101,7 @@ export const PinnedSection = React.memo(
<Notice
size="small"
type="information"
text="Add shortcuts for notebooks and tags here."
text={strings.sideMenuNotice()}
style={{
marginHorizontal: 12
}}
@@ -152,7 +153,7 @@ export const PinItem = React.memo(
>
<Seperator />
<Button
title="Remove Shortcut"
title={strings.removeShortcut()}
type="error"
onPress={async () => {
await db.shortcuts.remove(item.id);

View File

@@ -36,6 +36,7 @@ import { IconButton } from "../ui/icon-button";
import { Pressable } from "../ui/pressable";
import { TimeSince } from "../ui/time-since";
import Paragraph from "../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
export const UserStatus = () => {
const { colors, isDark } = useThemeColors();
@@ -116,7 +117,7 @@ export const UserStatus = () => {
color={colors.primary.heading}
>
{!user || !userProfile?.fullName
? "Settings"
? strings.settings()
: userProfile.fullName}
</Paragraph>
@@ -128,14 +129,16 @@ export const UserStatus = () => {
color={colors.secondary.heading}
>
{!user ? (
"Not logged in"
strings.notLoggedIn()
) : lastSynced && lastSynced !== "Never" ? (
<>
{syncing
? `Syncing ${progress ? `(${progress.current})` : ""}`
? `${strings.syncing()} ${
progress ? `(${progress.current})` : ""
}`
: lastSyncStatus === SyncStatus.Failed
? "Sync failed"
: "Synced"}{" "}
? strings.syncFailed()
: strings.synced()}{" "}
{!syncing ? (
<TimeSince
style={{
@@ -145,10 +148,10 @@ export const UserStatus = () => {
time={lastSynced}
/>
) : null}
{isOffline ? " (offline)" : ""}
{isOffline ? ` (${strings.offline()})` : ""}
</>
) : (
"never"
strings.never()
)}{" "}
<Icon
name="checkbox-blank-circle"

View File

@@ -42,6 +42,7 @@ import { eSendEvent } from "../../services/event-manager";
import { useSettingStore } from "../../stores/use-setting-store";
import { eClearEditor } from "../../utils/events";
import { useSideBarDraggingStore } from "../side-menu/dragging-store";
import { useTabStore } from "../../screens/editor/tiptap/use-tab-store";
interface TabProps extends ViewProps {
dimensions: { width: number; height: number };
@@ -85,7 +86,9 @@ export const FluidTabs = forwardRef<TabsRef, TabProps>(function FluidTabs(
);
const translateX = useSharedValue(
widths
? appState && !appState?.movedAway
? appState &&
appState?.movedAway === false &&
useTabStore.getState().getCurrentNoteId()
? widths.sidebar + widths.list
: widths.sidebar
: 0
@@ -121,9 +124,9 @@ export const FluidTabs = forwardRef<TabsRef, TabProps>(function FluidTabs(
} else {
if (prevWidths.current?.sidebar !== widths.sidebar) {
translateX.value =
!appState || (appState && appState?.movedAway)
? widths.sidebar
: editorPosition;
appState && appState?.movedAway === false
? editorPosition
: widths.sidebar;
if (translateX.value === editorPosition) {
onChangeTab?.({ i: 2, from: 1 });
}

View File

@@ -28,6 +28,7 @@ import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
import Seperator from "../ui/seperator";
import Paragraph from "../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
export const Tip = ({
tip,
@@ -67,7 +68,7 @@ export const Tip = ({
}}
>
<Button
title="TIP"
title={strings.tip()}
icon="information"
fontSize={SIZE.xs}
iconSize={SIZE.xs}
@@ -87,7 +88,7 @@ export const Tip = ({
{neverShowAgain && (
<Button
title="Never show again"
title={strings.neverShowAgain()}
type="secondary"
icon="close"
fontSize={SIZE.xs}
@@ -115,7 +116,7 @@ export const Tip = ({
color={colors.primary.paragraph}
size={SIZE.md}
>
{tip.text}
{tip.text()}
</Paragraph>
{tip.image && !noImage && (
<View

View File

@@ -1,235 +0,0 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useCallback, useEffect, useRef, useState } from "react";
import { TouchableOpacity, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../e2e/test.ids";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { DDS } from "../../services/device-detection";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import { useThemeColors } from "@notesnook/theme";
import { getElevationStyle } from "../../utils/elevation";
import { eHideToast, eShowToast } from "../../utils/events";
import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
export const Toast = ({ context = "global" }) => {
const { colors } = useThemeColors();
const [data, setData] = useState({});
const insets = useGlobalSafeAreaInsets();
const hideTimeout = useRef();
const [visible, setVisible] = useState(false);
const toastMessages = useRef([]);
const show = useCallback(
async (data) => {
if (!data) return;
if (data.context !== context) return;
if (
toastMessages.current.findIndex((m) => m.message === data.message) >= 0
) {
return;
}
toastMessages.current.push(data);
if (toastMessages.current?.length > 1) return;
setData(data);
setVisible(true);
if (hideTimeout.current) {
clearTimeout(hideTimeout.current);
}
hideTimeout.current = setTimeout(() => {
hide();
}, data.duration);
},
[context, hide]
);
const next = useCallback(
(data) => {
if (!data) {
hide();
return;
}
setData(data);
if (hideTimeout.current) {
clearTimeout(hideTimeout.current);
}
hideTimeout.current = setTimeout(() => {
hide();
}, data?.duration);
},
[hide]
);
const hide = useCallback(() => {
if (hideTimeout.current) {
clearTimeout(hideTimeout.current);
}
let msg =
toastMessages.current.length > 1 ? toastMessages.current.shift() : null;
if (msg) {
setVisible(false);
next(msg);
setTimeout(() => {
setVisible(true);
}, 300);
} else {
setVisible(false);
toastMessages.current.shift();
setTimeout(() => {
setData({});
if (hideTimeout.current) {
clearTimeout(hideTimeout.current);
}
}, 100);
}
}, [next]);
useEffect(() => {
eSubscribeEvent(eShowToast, show);
eSubscribeEvent(eHideToast, hide);
return () => {
toastMessages.current = [];
eUnSubscribeEvent(eShowToast, show);
eUnSubscribeEvent(eHideToast, hide);
};
}, [hide, show]);
return (
visible && (
<TouchableOpacity
onPress={hide}
activeOpacity={1}
style={{
width: DDS.isTab ? 400 : "100%",
alignItems: "center",
alignSelf: "center",
minHeight: 30,
top: insets.top + 10,
position: "absolute",
zIndex: 999,
elevation: 15
}}
>
<View
style={{
...getElevationStyle(5),
maxWidth: "95%",
backgroundColor: colors.secondary.background,
minWidth: data?.func ? "95%" : "50%",
alignSelf: "center",
borderRadius: 5,
minHeight: 30,
paddingVertical: 10,
paddingLeft: 12,
paddingRight: 5,
justifyContent: "space-between",
flexDirection: "row",
alignItems: "center",
width: "95%"
}}
>
<View
style={{
flexDirection: "row",
alignItems: "center",
flexGrow: 1,
flex: 1
}}
>
<View
style={{
height: 30,
borderRadius: 100,
justifyContent: "center",
alignItems: "center",
marginRight: 10
}}
>
<Icon
name={data?.type === "success" ? "check" : "close"}
size={SIZE.lg}
color={
data?.type === "error"
? colors.error.icon
: colors.primary.accent
}
/>
</View>
<View
style={{
flexGrow: 1,
paddingRight: 25
}}
>
{data?.heading ? (
<Heading
color={colors.primary.paragraph}
size={SIZE.md}
onPress={() => {
hide();
}}
>
{data.heading}
</Heading>
) : null}
{data?.message ? (
<Paragraph
color={colors.primary.paragraph}
style={{
maxWidth: "100%",
paddingRight: 10
}}
onPress={() => {
hide();
}}
>
{data.message}
</Paragraph>
) : null}
</View>
</View>
{data.func ? (
<Button
testID={notesnook.toast.button}
fontSize={SIZE.md}
type={data.type === "error" ? "errorShade" : "transparent"}
onPress={data.func}
title={data.actionText}
height={30}
style={{
zIndex: 10
}}
/>
) : null}
</View>
</TouchableOpacity>
)
);
};

Some files were not shown because too many files have changed in this diff Show More