Compare commits

...

160 Commits

Author SHA1 Message Date
01zulfi
04b9023315 editor: add keybinding to collapse/expand headings & callouts
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-01-28 13:02:13 +05:00
Chloe Oletto
12687dae7b core: Fix reminder sorting. (#9242)
Signed off by: Chloe Oletto <NeedsChloesure@riseup.net>
2026-01-22 09:26:27 +05:00
Chloe Oletto
c53c3167d8 web: Fix reminder dialog subtext formatting. (#9241)
Signed-off by: Chloe Oletto <NeedsChloesure@riseup.net>
2026-01-22 09:25:20 +05:00
01zulfi
60df472d53 editor: fix search scroll not working on switching editors or opening new editor (#8061)
* editor: fix search scroll not working on switching editors or opening new editor
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* editor: initialize search right after editor is created
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-01-19 11:59:48 +05:00
01zulfi
67fe25bc8b common: make command palette keybinding same across web & desktop (#9235)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-01-17 21:18:52 +05:00
01zulfi
44a7aed308 intl: fix strings with '<<< HEAD' in pseudo-LOCALE.po (#9236)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-01-17 12:32:48 +05:00
Ammar Ahmed
f22937e1eb ci: update build tools version 2026-01-17 10:54:44 +05:00
Ammar Ahmed
c816f8b15b mobile: fix ts errors 2026-01-17 09:39:20 +05:00
01zulfi
fa4f4e7bea web: add command palette button in status bar (#7977)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-01-16 19:24:21 +05:00
01zulfi
076cea6d6d editor: add scrollbar to color picker (#8065)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-01-16 19:22:08 +05:00
01zulfi
d951b97bed web: fix getting active note commands in command palette (#9234)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-01-16 19:20:38 +05:00
Ammar Ahmed
bd888e57e5 mobile: release 3.3.13-beta.1 2026-01-16 14:46:15 +05:00
Abdullah Atta
3ba1234056 core: fix note expiry date index not created 2026-01-16 14:44:53 +05:00
Ammar Ahmed
4c5a668114 global: update deps 2026-01-16 14:31:53 +05:00
Abdullah Atta
06d1891389 web: bump version to 3.3.8-beta.1 2026-01-16 14:27:31 +05:00
Ammar Ahmed
c97a228ec8 Merge branch 'master' into beta 2026-01-16 14:26:17 +05:00
Ammar Ahmed
d11a200ebb Merge pull request #9233 from streetwriters/mobile/fix-web-clip
Fix web clip on mobile does not include url & date clipped
2026-01-16 14:24:46 +05:00
01zulfi
bfedc1f43e editor: replace simplebar with simple css styling (#9085)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-01-16 14:24:11 +05:00
Ammar Ahmed
b148083750 Merge pull request #9232 from streetwriters/mobile/fix-shortcuts-update
Fix shortcuts do not update in side menu
2026-01-16 14:24:01 +05:00
Ammar Ahmed
cdfd7333cd Merge pull request #9231 from streetwriters/mobile/fix-share-padding
Fix share sheet bottom padding
2026-01-16 14:23:44 +05:00
Ammar Ahmed
2ed32efefb Merge pull request #9210 from streetwriters/fix-editor-features-lock
mobile: fix editor features not updated on login or user change
2026-01-16 14:23:23 +05:00
01zulfi
e3331e6dee web: temporarily disable change&forgot password
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-01-16 14:21:01 +05:00
Ammar Ahmed
b3406be1ad mobile: disable password changing and forgot password 2026-01-16 14:21:01 +05:00
01zulfi
751d0ca861 editor: fix hidden collapse/expand icon when callout title is empty (#9220)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-01-16 13:06:43 +05:00
Ammar Ahmed
a614693dfa mobile: fix web clip does not include url & date clipped 2026-01-16 13:00:53 +05:00
01zulfi
c4bbceef67 web: add option to preview attachments in attachment manager (#9186)
* web: add option to preview attachments in attachment manager
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: open pdf preview dialog in full width&height
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: minor ui adjustment for pdf preview dialog

* web: fix pdf text selection not visible

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-01-16 13:00:14 +05:00
Abdullah Atta
d39e7c990d Merge pull request #9192 from streetwriters/core/note-history-title
Add support for saving title in note history sessions
2026-01-16 12:41:49 +05:00
Abdullah Atta
e248dbfa5b intl: update po files 2026-01-16 12:41:19 +05:00
Abdullah Atta
29f789493e web: fix previewing note history where only title has changed 2026-01-16 12:41:12 +05:00
Abdullah Atta
2b0e5c41c8 core: remove note content if history item has no content 2026-01-16 12:40:42 +05:00
Ammar Ahmed
e241b2fe72 mobile: fix showing properties from shortcuts 2026-01-16 11:49:08 +05:00
01zulfi
a91983340c web: support note title from session history in diff viewer
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-01-16 11:21:52 +05:00
Ammar Ahmed
2aa3aaf2f8 mobile: cleanup 2026-01-16 11:21:52 +05:00
Ammar Ahmed
6b2bf369ff core: ensure session content remains consistent when new session is created
New sessions can be created with title or content only, hence we need to pull the missing data from the note itself to keep session history consistent with previous sessions
2026-01-16 11:21:52 +05:00
Ammar Ahmed
fea5818951 core: update note history tests 2026-01-16 11:21:52 +05:00
Ammar Ahmed
e6eb71a484 core: restoring note history with title 2026-01-16 11:21:52 +05:00
Ammar Ahmed
1e023500be mobile: add support for setting note history session title 2026-01-16 11:21:34 +05:00
Ammar Ahmed
2c078de2c0 core: save note title in note session history item 2026-01-16 11:21:34 +05:00
Abdullah Atta
b373881b8b Merge pull request #9169 from streetwriters/editor/table-export-import-csv
editor: add options to export/import table <> csv
2026-01-16 11:19:10 +05:00
Abdullah Atta
6b37cf0dda intl: update locales 2026-01-16 11:18:02 +05:00
Abdullah Atta
1025c4ec6e editor: minor refactors 2026-01-16 11:17:49 +05:00
Abdullah Atta
c44f9f685d editor: parse csv directly to prosemirror nodes 2026-01-16 11:17:33 +05:00
Ammar Ahmed
5633d4504f mobile: fix shortcuts not updating 2026-01-16 11:10:45 +05:00
Abdullah Atta
c7aa49e541 common: move import/export csv to Pro plan 2026-01-16 10:58:17 +05:00
Ammar Ahmed
ee139463d2 mobile: export table to csv 2026-01-16 10:58:17 +05:00
01zulfi
2cf7f0cc27 editor: add options to export/import table <> csv
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-01-16 10:58:17 +05:00
Ammar Ahmed
210549445e global: add support for expiring notes (#9167)
* core: expiring notes

* core: add tests

* mobile: support setting not expiry date

* web: support note expiry dates
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* core: add tests for expiring notes

* core: fix tests

* mobile: delete expiring notes at startup

* core: create index on expiry date

* core: minor refactor

* core: remove `.only` sync test

* web: refactors

* mobile: set limit on expiring notes

* web: improve expiry date menu option && note item ui
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: add premium check for setting expiry for notes
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: move note expiry date dialog into its own file && minor changes
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: delete expired notes on startup

* web: minor refactors

---------

Co-authored-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-01-16 10:55:40 +05:00
Ammar Ahmed
5b85158a7c mobile: fix share sheet bottom padding 2026-01-16 10:38:55 +05:00
01zulfi
09cba1a1df editor: fix youtube embeds erroring with code 153 (#9180)
* web: add referer header with content origin
* youtube embeds aren't working without adding this header

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* editor: use corsHost for youtube embeds

* editor: make youtube embed detection more robust

* web: remove referrer meta tag

---------

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
Co-authored-by: Ammar Ahmed <ammarahmed6506@gmail.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-01-15 19:22:46 +05:00
01zulfi
d75e2e85c9 desktop: fix start minimized on autostartup (#8492)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-01-15 10:41:36 +05:00
01zulfi
966673dfd1 editor: add clear current node keyboard shortcut (#7272)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-01-15 10:38:33 +05:00
Abdullah Atta
b8ad9265b9 Merge pull request #5726 from streetwriters/feature/conflict-on-locked-notes
Add support for showing conflicts on locked notes
2026-01-15 10:36:07 +05:00
Ammar Ahmed
b2f1fda641 mobile: show merge conflict on locked notes 2026-01-15 10:35:36 +05:00
Abdullah Atta
1544b656e0 web: fix conflict on locked note auto resolving 2026-01-15 10:34:39 +05:00
Abdullah Atta
d97314c484 core: enable conflicts on locked notes 2026-01-15 10:34:39 +05:00
Ammar Ahmed
1432553897 mobile: fix editor features not updated on login 2026-01-15 09:29:48 +05:00
Abdullah Atta
f585fb0381 Merge pull request #9159 from streetwriters/web/change-note-created-date
web: allow editing note's created date from properties panel
2026-01-14 22:06:15 +05:00
01zulfi
98557be52d web: replace note creation date edit popover with dialog
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-01-14 14:14:14 +05:00
Ammar Ahmed
e7da0a35d3 mobile: limit changing created date to note's edited date 2026-01-14 14:13:42 +05:00
Ammar Ahmed
965a7f91ed mobile: support changing date-created 2026-01-14 14:13:42 +05:00
01zulfi
2b1c973177 web: allow editing note's created date from properties panel
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-01-14 14:13:42 +05:00
Abdullah Atta
7e87cf7fdc Merge pull request #9170 from streetwriters/editor/fix-line-height-css-issues
editor: fix icons not aligning in outline,task,check lists & callouts because of custom line height
2026-01-14 13:52:36 +05:00
Ammar Ahmed
c8cd5a4dc8 mobile: fix line height not applied to paragraphs 2026-01-14 13:36:37 +05:00
Abdullah Atta
d00f7f6255 Merge pull request #9081 from streetwriters/fix/improve-sync-reliability
Improve sync reliability
2026-01-14 11:48:34 +05:00
01zulfi
e8690e3419 web: fix toc headings alignment (#9143)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-01-14 11:48:04 +05:00
Abdullah Atta
72ff45c722 Merge pull request #9182 from streetwriters/web/week-format
web: add setting to change week format
2026-01-14 11:40:26 +05:00
01zulfi
22a6271423 editor: fix duplicate block-id on splitting node (#9198)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-01-14 10:56:31 +05:00
01zulfi
a6032ffd72 web: fix empty command palette/quick open when locked note active (#9200)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-01-14 10:53:00 +05:00
01zulfi
69ea9992ce web: enable toc toggle on new notes (#9201)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-01-14 10:52:24 +05:00
Ammar Ahmed
36b4d69c2d mobile: week format settings 2026-01-09 11:22:03 +05:00
01zulfi
2129533aa9 web: add setting to change week format
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-01-08 15:49:13 +05:00
Abdullah Atta
e635ace338 Merge pull request #9174 from streetwriters/fix/theme-server-crashing
Fix theme server crashing on fetching total installs
2026-01-08 01:03:18 +05:00
Abdullah Atta
e729086015 server(themes): bump version to 1.0.4 2026-01-08 01:02:25 +05:00
Abdullah Atta
3fbb7ce548 server(themes): fix crash when fetching install count 2026-01-08 01:01:59 +05:00
01zulfi
e0c31d6fdd editor: fix icons not aligning in outline,task,check lists & callouts because of custom line height
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-01-07 14:05:57 +05:00
Ammar Ahmed
d19c5b9775 mobile: release 3.3.13-beta.0 2026-01-07 10:00:58 +05:00
Abdullah Atta
f01b88083f Merge branch 'master' into beta 2026-01-07 09:40:38 +05:00
Abdullah Atta
d3ab1d1d2a web: bump version to 3.3.8-beta.0 2026-01-07 09:39:11 +05:00
Ammar Ahmed
7855442d33 Merge pull request #9161 from streetwriters/mobile-multipart-uploads
mobile: add support for multipart uploads
2026-01-06 13:56:42 +05:00
Ammar Ahmed
bbaa6c76b4 mobile: add support for multipart uploads
Files >100MB fail currently using simple file upload methods.
2026-01-06 13:08:53 +05:00
Abdullah Atta
cf71809f6c Merge pull request #9132 from streetwriters/fix/themes-not-instaling
Fix themes not installing
2026-01-03 14:40:18 +05:00
Abdullah Atta
3b7d0ddb36 server(themes): remove _test key suffix 2026-01-03 14:40:06 +05:00
Abdullah Atta
482fea91f3 server(themes): bump version to 1.0.3 2026-01-03 14:39:20 +05:00
Abdullah Atta
4bace10bab server(themes): make counter increment faster 2026-01-03 14:38:45 +05:00
Abdullah Atta
603b7da9e6 server(theme): update theme's install count immediately on install 2026-01-03 14:38:00 +05:00
Abdullah Atta
bf4d168c96 server(theme): sort themes by total installs 2026-01-03 14:37:19 +05:00
Abdullah Atta
756bb82fb7 server(themes): sync themes every 1 hour (roughly) 2026-01-03 14:36:41 +05:00
Abdullah Atta
81b1005b02 Merge pull request #9128 from streetwriters/fix/themes-server-fetch-perf
Fix themes server fetch perf
2026-01-02 13:04:40 +05:00
Abdullah Atta
7ba52d1358 server(themes): bump version to 1.0.2 2026-01-02 13:03:36 +05:00
Abdullah Atta
fcfb2c7e01 server(themes): fetch installs once on sync instead of on every fetch 2026-01-02 13:03:19 +05:00
Abdullah Atta
fa0a5afa63 Merge pull request #9113 from streetwriters/editor/collapsible-headings-perf
editor: improve collapsible heading performance
2026-01-02 09:43:06 +05:00
01zulfi
b881b236a0 editor: export & use AttributeUpdate interface
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-01-01 15:23:27 +05:00
Ammar Ahmed
ec9c40bd64 Merge pull request #9125 from streetwriters/fix-subscription-wording
mobile: fix wrong trial end date shown in settings
2026-01-01 12:53:04 +05:00
Ammar Ahmed
f0f7857355 mobile: fix wrong trial end date shown in settings 2026-01-01 12:51:05 +05:00
Abdullah Atta
9c59cbbc40 Merge pull request #8944 from streetwriters/web/fix-resizing-editor
web: fix editor & note title resizing when window resizes
2026-01-01 11:19:40 +05:00
Abdullah Atta
6dce41a070 web: fix editor & note title resizing when window resizes
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-01-01 11:19:23 +05:00
Abdullah Atta
dffeb05f86 Merge pull request #8488 from 01zulfi/global/day-format-setting
global: support day format options & $day$ title format & /day command
2026-01-01 10:51:53 +05:00
Abdullah Atta
ecfbfd83b9 Merge branch 'master' into beta 2026-01-01 10:44:45 +05:00
Ammar Ahmed
0aea1deea0 Merge pull request #9124 from streetwriters/fix-invalid-file-crash-on-restore
mobile: fix app crash when trying to restore an invalid file
2026-01-01 10:26:28 +05:00
Ammar Ahmed
f041b92360 mobile: fix app crash when trying to restore an invalid file 2026-01-01 10:15:54 +05:00
Abdullah Atta
64074030dd Merge pull request #9123 from streetwriters/fix/make-themes-host-configurable
Make themes server host configurable
2026-01-01 09:48:49 +05:00
Abdullah Atta
3387dda22b server(themes): bump version to 1.0.1 2026-01-01 09:46:50 +05:00
Abdullah Atta
534b810350 server(themes): make themes server port configurable 2026-01-01 09:46:25 +05:00
01zulfi
50666c1307 editor: add a collapsible heading test case
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-31 15:33:21 +05:00
01zulfi
200c9bd9eb editor: improve checks to not unecessary call toggle fn in heading update plugin
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-31 15:01:14 +05:00
Ammar Ahmed
2f93c6fdc8 mobile: support day format options 2025-12-31 14:57:16 +05:00
01zulfi
5c21c0a498 global: support day format options & $day$ title format & /day command
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-31 14:54:44 +05:00
Abdullah Atta
93c07d6842 Merge pull request #9122 from streetwriters/fix/themes-server-ci
Fix themes server docker image build & publish
2025-12-31 12:09:19 +05:00
Abdullah Atta
5d9f18ed77 ci: set build context to root for themes server 2025-12-31 12:08:11 +05:00
Abdullah Atta
313d47d9ff Merge pull request #8108 from StLyn4/docker-themes-server
docker: theme server
2025-12-31 11:57:11 +05:00
Abdullah Atta
304a9f3218 ci: add workflow for publishing theme server 2025-12-31 11:56:15 +05:00
Abdullah Atta
a160725acb server(themes): use bun in production 2025-12-31 11:53:22 +05:00
Abdullah Atta
05535b50da server(themes): no need for default env 2025-12-31 11:38:57 +05:00
Abdullah Atta
70fdcf2b44 server(theme): add support for cloudflare kv for counter persistance 2025-12-31 11:08:27 +05:00
Ammar Ahmed
67d47c283e mobile: fix default type homepage not loading 2025-12-31 09:42:53 +05:00
Ammar Ahmed
dd59aab328 Merge branch 'master' into beta 2025-12-31 09:34:23 +05:00
Ammar Ahmed
509b308419 Merge pull request #9119 from streetwriters/fix-navigation-unresponsive-ios
mobile: add simple tab bar implementation
2025-12-31 09:23:45 +05:00
01zulfi
7a12fd701e editor: batch update hidden attribute when toggling headings
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-30 16:53:02 +05:00
01zulfi
9396a06a89 core: support customizable monograph title (#8491)
* core: support customizable monograph title
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* mobile: set custom title for monograph

* web: minor ui changes

---------

Co-authored-by: Ammar Ahmed <ammarahmed6506@gmail.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2025-12-30 14:29:01 +05:00
Ammar Ahmed
9d9c3b7db5 mobile: add simple tab bar implementation
This fixes most bugs faced by users in side-menu on ios like swipe gesture working when it is disabled and navigation screen stuck on older ios versions.
2025-12-30 14:23:39 +05:00
Abdullah Atta
4cfb10db7c Merge pull request #8284 from 01zulfi/web/editor-line-height-setting
web: add editor line height setting
2025-12-30 14:09:26 +05:00
Abdullah Atta
50024b62cb intl: update locales 2025-12-30 14:08:30 +05:00
Ammar Ahmed
f31084baa7 mobile: configurable default line height setting 2025-12-30 14:07:45 +05:00
01zulfi
2688becb05 web: add editor line height setting
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-30 14:07:44 +05:00
01zulfi
8298900f76 editor: support ctrl+c for images (#8386)
* editor: support ctrl+c for images
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* editor: add test for copy image functionality
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-30 13:46:24 +05:00
01zulfi
b0c18a8ece web: allow saving note from status icon click & tab menu (#8316)
* web: allow saving note from status icon click & tab menu
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: directly save content instead of using events

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2025-12-30 13:27:10 +05:00
Abdullah Atta
9659a12d70 Merge pull request #9117 from streetwriters/fix/monograph-ui
Fix monograph ui
2025-12-30 10:06:31 +05:00
Abdullah Atta
6abc25f58a monograph: bump version to 1.2.7 2025-12-30 10:05:51 +05:00
Abdullah Atta
a164ac67ce monograph: fix monograph ui 2025-12-30 10:04:27 +05:00
01zulfi
255677d98e editor: improve headingUpdatePlugin performance
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-29 14:23:16 +05:00
Ammar Ahmed
2a92ea4efd mobile: update export options 2025-12-29 10:25:04 +05:00
Ammar Ahmed
608b4f3009 mobile: update ios provisioning 2025-12-29 10:25:04 +05:00
Ammar Ahmed
a69cb1bf9c mobile: release 3.3.12 2025-12-29 10:25:04 +05:00
Abdullah Atta
be40b3d160 monograph: fix build 2025-12-29 10:25:04 +05:00
Abdullah Atta
51147b17cd monograph: bump version to 1.2.6 2025-12-29 10:25:04 +05:00
Abdullah Atta
5d0c2ae0b9 monograph: add support for reading secrets from file 2025-12-29 10:25:04 +05:00
Ammar Ahmed
8435e69cb5 Merge pull request #9110 from streetwriters/mobile-release/3.3.12
Mobile release/3.3.12
2025-12-29 10:24:05 +05:00
Ammar Ahmed
1d6a33e3be mobile: update export options 2025-12-27 11:47:31 +05:00
Ammar Ahmed
3f4d78af7d mobile: update ios provisioning 2025-12-27 10:19:31 +05:00
Abdullah Atta
f94012d184 Merge pull request #9103 from streetwriters/fix/monograph-build
monograph: fix build
2025-12-27 00:40:06 +05:00
Abdullah Atta
ca73c0c2ce monograph: fix build 2025-12-27 00:39:13 +05:00
Abdullah Atta
742bf31038 Merge pull request #9102 from streetwriters/feat/monograph/secrets-support
Add support for reading secrets from file
2025-12-27 00:28:45 +05:00
Abdullah Atta
1369b64cb1 monograph: bump version to 1.2.6 2025-12-27 00:27:15 +05:00
Abdullah Atta
bc3488512f monograph: add support for reading secrets from file 2025-12-27 00:26:51 +05:00
Ammar Ahmed
51f87911e1 mobile: release 3.3.12 2025-12-26 14:37:49 +05:00
Ammar Ahmed
4d4af5c278 Merge branch 'master' into beta 2025-12-26 11:24:47 +05:00
Ammar Ahmed
a6fcff4d86 Merge pull request #9099 from streetwriters/fix-file-errors
Fix bugs in file picker, pdf preview, backup restoration and vault unlock with biometrics
2025-12-26 11:23:39 +05:00
Ammar Ahmed
9982d7b7b4 Merge pull request #9088 from streetwriters/feat-home-shortcuts
Feat Android home launcher shortcuts
2025-12-25 11:40:27 +05:00
Ammar Ahmed
22497199c5 mobile: catch errors 2025-12-25 11:40:05 +05:00
Ammar Ahmed
873b180dab mobile: enable feature for pro users 2025-12-25 11:37:27 +05:00
Ammar Ahmed
ca8e505cd0 mobile: add support for color shortcuts on homescreen 2025-12-24 14:17:52 +05:00
Ammar Ahmed
465e1af838 mobile: keep shortcuts synced with the app 2025-12-24 13:11:08 +05:00
01zulfi
41094c80d5 editor: add move line up/down keybinding (#7078)
* editor: add move line up/down keybinding
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* docs: update keyboard shortcuts

* editor: add missing changes

* editor: add move parent node up/down keyboard shortcut
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* editor: refactor moveNode fn && add a test case
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* editor: improve move node logic
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2025-12-23 12:39:30 +05:00
Abdullah Atta
583a68cabe core: fix merger tests 2025-12-23 11:53:43 +05:00
Abdullah Atta
9c1a02dbd5 mobile: use db's event manager instead of global 2025-12-23 11:04:06 +05:00
Abdullah Atta
637a19ee21 web: use db's event manager instead of global 2025-12-23 11:03:59 +05:00
Abdullah Atta
896420064f core: improve sync reliability 2025-12-23 11:03:36 +05:00
Ammar Ahmed
b59a3fb5de mobile: improved navigation for deep links 2025-12-23 10:54:46 +05:00
Ammar Ahmed
0a7f8631b6 mobile: add home shortcuts
Add ability to create shortcuts on launcher homescreen for notes, notebooks and tags.
2025-12-22 18:59:36 +05:00
Vsevolod Volkov
c6d471cec9 docker: ability to build container with theme server 2025-11-26 19:24:04 +01:00
255 changed files with 16799 additions and 68628 deletions

View File

@@ -113,7 +113,7 @@ jobs:
keyStorePassword: ${{ secrets.KEY_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: "33.0.0"
BUILD_TOOLS_VERSION: "36.0.0"
- name: Build apks for Github release
run: yarn release:android
@@ -128,7 +128,7 @@ jobs:
keyStorePassword: ${{ secrets.PUBLIC_KEY_PASSWORD }}
keyPassword: ${{ secrets.PUBLIC_KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: "33.0.0"
BUILD_TOOLS_VERSION: "36.0.0"
- name: Rename apk files
run: |

78
.github/workflows/themes.publish.yml vendored Normal file
View File

@@ -0,0 +1,78 @@
# 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/themes-server
on: workflow_dispatch
jobs:
push_to_registry:
name: Push Docker image to Docker Hub
runs-on: ubuntu-22.04
permissions:
packages: write
contents: read
attestations: write
id-token: write
steps:
- name: Check out the repo
uses: actions/checkout@v4
- name: Collect package metadata
id: package_metadata
working-directory: ./servers/themes
run: |
echo ::set-output name=app_version::$(cat package.json | jq -r .version)
# Setup Buildx
- name: Docker Setup Buildx
uses: docker/setup-buildx-action@v3
with:
platforms: linux/amd64,linux/arm64
- 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/themes-server: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/themes-server
- name: Build and push Docker image
id: push
uses: docker/build-push-action@v6
with:
context: .
file: servers/themes/Dockerfile
push: true
platforms: linux/amd64,linux/arm64
tags: streetwriters/themes-server:${{ steps.package_metadata.outputs.app_version }},streetwriters/themes-server:latest
cache-from: streetwriters/themes-server:latest
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v1
with:
subject-name: index.docker.io/streetwriters/themes-server
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.3.7",
"version": "3.3.8-beta.1",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/cjs/index.js",

View File

@@ -39,7 +39,7 @@ const LINUX_AUTOSTART_DIRECTORY_PATH = path.join(
"autostart"
);
const STARTUP_ARGS = ["--hidden"];
const HIDDEN_ARG = "--hidden";
export class AutoLaunch {
static enable(hidden: boolean) {
@@ -54,13 +54,13 @@ export class AutoLaunch {
);
} else {
const loginItemSettings = app.getLoginItemSettings({
args: STARTUP_ARGS
args: hidden ? [HIDDEN_ARG] : undefined
});
if (loginItemSettings.openAtLogin) return;
app.setLoginItemSettings({
openAtLogin: true,
openAsHidden: hidden,
args: STARTUP_ARGS
args: hidden ? [HIDDEN_ARG] : undefined
});
}
}

View File

@@ -125,7 +125,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled true
versionCode 3087
versionCode 3090
versionName getNpmVersion()
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'

View File

@@ -6,7 +6,14 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.Icon;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
@@ -29,6 +36,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
public class RCTNNativeModule extends ReactContextBaseJavaModule {
@@ -237,6 +245,221 @@ public class RCTNNativeModule extends ReactContextBaseJavaModule {
}
}
@ReactMethod
public void addShortcut(final String id, final String type, final String title, final String description, final String color, Promise promise) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
promise.reject("UNSUPPORTED", "Pinned launcher shortcuts require Android 8.0 or higher");
return;
}
try {
ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
if (shortcutManager == null) {
promise.reject("ERROR", "ShortcutManager not available");
return;
}
String uri = "https://app.notesnook.com/open_" + type + "?id=" + id;
Intent intent = new Intent(Intent.ACTION_VIEW, android.net.Uri.parse(uri));
intent.setPackage(mContext.getPackageName());
Icon icon = createLetterIcon(type, title, color);
ShortcutInfo shortcut = new ShortcutInfo.Builder(mContext, id)
.setShortLabel(title)
.setLongLabel(description != null && !description.isEmpty() ? description : title)
.setIcon(icon)
.setCategories(Set.of(type))
.setIntent(intent)
.build();
shortcutManager.requestPinShortcut(shortcut, null);
promise.resolve(true);
} catch (Exception e) {
promise.reject("ERROR", e.getMessage());
}
}
@ReactMethod
public void removeShortcut(final String id, Promise promise) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
promise.reject("UNSUPPORTED", "Pinned launcher shortcuts require Android 8.0 or higher");
return;
}
try {
ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
if (shortcutManager == null) {
promise.reject("ERROR", "ShortcutManager not available");
return;
}
shortcutManager.disableShortcuts(java.util.Collections.singletonList(id));
promise.resolve(true);
} catch (Exception e) {
promise.reject("ERROR", e.getMessage());
}
}
@ReactMethod
public void updateShortcut(final String id, final String type, final String title, final String description,final String color, Promise promise) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
promise.reject("UNSUPPORTED", "Pinned launcher shortcuts require Android 8.0 or higher");
return;
}
try {
ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
if (shortcutManager == null) {
promise.reject("ERROR", "ShortcutManager not available");
return;
}
// Get existing shortcut to preserve icon and intent
List<ShortcutInfo> shortcuts = shortcutManager.getPinnedShortcuts();
ShortcutInfo existingShortcut = null;
for (ShortcutInfo s : shortcuts) {
if (s.getId().equals(id)) {
existingShortcut = s;
break;
}
}
if (existingShortcut == null) {
return;
}
Icon icon = createLetterIcon(type, title, color);
ShortcutInfo updatedShortcut = new ShortcutInfo.Builder(mContext, id)
.setShortLabel(title)
.setIcon(icon)
.setLongLabel(description != null && !description.isEmpty() ? description : title)
.setIntent(existingShortcut.getIntent())
.build();
shortcutManager.updateShortcuts(java.util.Collections.singletonList(updatedShortcut));
promise.resolve(true);
} catch (Exception e) {
promise.reject("ERROR", e.getMessage());
}
}
@ReactMethod
public void removeAllShortcuts(Promise promise) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
promise.reject("UNSUPPORTED", "Pinned launcher shortcuts require Android 8.0 or higher");
return;
}
try {
ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
if (shortcutManager == null) {
promise.reject("ERROR", "ShortcutManager not available");
return;
}
List<ShortcutInfo> pinnedShortcuts = shortcutManager.getPinnedShortcuts();
if (!pinnedShortcuts.isEmpty()) {
List<String> ids = new ArrayList<>();
for (ShortcutInfo shortcut : pinnedShortcuts) {
ids.add(shortcut.getId());
}
shortcutManager.disableShortcuts(ids);
}
promise.resolve(true);
} catch (Exception e) {
promise.reject("ERROR", e.getMessage());
}
}
@ReactMethod
public void getAllShortcuts(Promise promise) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}
ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
WritableArray shortcuts = Arguments.createArray();
List<ShortcutInfo> infos = shortcutManager.getPinnedShortcuts();
for (ShortcutInfo info: infos) {
WritableMap data = Arguments.createMap();
data.putString("id", info.getId());
if (info.getShortLabel() != null) {
data.putString("title", info.getShortLabel().toString());
}
if (info.getLongLabel() != null) {
data.putString("description", info.getLongLabel().toString());
}
if (!Objects.requireNonNull(info.getCategories()).isEmpty()) {
if (info.getCategories().contains("note")) {
data.putString("type", "note");
} else if (info.getCategories().contains("notebook")) {
data.putString("type", "notebook");
} else if (info.getCategories().contains("tag")) {
data.putString("type", "tag");
} else if (info.getCategories().contains("color")) {
data.putString("type", "color");
}
}
shortcuts.pushMap(data);
}
promise.resolve(shortcuts);
}
private Icon createLetterIcon(String type, String title, String colorCode) {
String letter = type.contains("tag") ? "#" : title != null && !title.isEmpty()
? title.substring(0, 1).toUpperCase()
: "?";
int color = type.equals("color") ? Color.parseColor(colorCode) : getColorForLetter(letter);
if (type.equals("notebook")) return Icon.createWithResource(mContext, R.drawable.ic_notebook);
// Use a larger canvas and fill it completely to avoid white borders from launcher masking.
int iconSize = 256;
Bitmap bitmap = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
if (type.equals("color")) {
Paint backgroundPaint = new Paint();
backgroundPaint.setColor(color);
backgroundPaint.setAntiAlias(true);
canvas.drawCircle(iconSize / 2, iconSize / 2, iconSize/2, backgroundPaint);
} else {
Paint textPaint = new Paint();
textPaint.setColor(color);
textPaint.setTextSize(130);
textPaint.setAntiAlias(true);
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setTypeface(android.graphics.Typeface.create(android.graphics.Typeface.DEFAULT, android.graphics.Typeface.BOLD));
float x = iconSize / 2f;
float y = (iconSize / 2f) - ((textPaint.descent() + textPaint.ascent()) / 2f);
canvas.drawText(letter, x, y, textPaint);
}
return Icon.createWithBitmap(bitmap);
}
private int getColorForLetter(String letter) {
int[] colors = {
0xFF1976D2, // Blue
0xFFD32F2F, // Red
0xFF388E3C, // Green
0xFFF57C00, // Orange
0xFF7B1FA2, // Purple
0xFF0097A7, // Cyan
0xFFC2185B, // Pink
0xFF455A64, // Blue Grey
0xFF6A1B9A, // Deep Purple
0xFF00796B, // Teal
0xFF512DA8, // Indigo
0xFF1565C0 // Dark Blue
};
int hash = Math.abs(letter.hashCode());
return colors[hash % colors.length];
}
}

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="#008837"
android:alpha="1">
<path
android:fillColor="@android:color/white"
android:pathData="M240,880Q207,880 183.5,856.5Q160,833 160,800L160,160Q160,127 183.5,103.5Q207,80 240,80L720,80Q753,80 776.5,103.5Q800,127 800,160L800,800Q800,833 776.5,856.5Q753,880 720,880L240,880ZM240,800L720,800Q720,800 720,800Q720,800 720,800L720,160Q720,160 720,160Q720,160 720,160L640,160L640,440L540,380L440,440L440,160L240,160Q240,160 240,160Q240,160 240,160L240,800Q240,800 240,800Q240,800 240,800ZM240,800Q240,800 240,800Q240,800 240,800L240,160Q240,160 240,160Q240,160 240,160L240,160Q240,160 240,160Q240,160 240,160L240,800Q240,800 240,800Q240,800 240,800ZM440,440L540,380L640,440L640,440L540,380L440,440Z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 B

View File

@@ -1,6 +1,11 @@
- Get an overview of your year with Notesnook with Wrapped 2025
- Cell selection and column resizing in tables
- Added support for uploading large files in background
- Bug fixes and minor improvements
- Add title and date clipped to web clips
- Configure Week to start from Sunday or Monday
- Change Note's creation date from properties
- Set expiry date for notes for auto-deletion
- Fix tag and notebook properties cannot be accessed from shortcuts
- Fix editor features remain locked after login
- Temporarily disable password changing and forgot password features
- Note history now includes note title changes
- Other minor bug fixes and improvements
Thank you for using Notesnook!

View File

@@ -24,7 +24,7 @@ import {
useThemeEngineStore
} from "@notesnook/theme";
import React, { useEffect } from "react";
import { Appearance, I18nManager, StatusBar } from "react-native";
import { Appearance, I18nManager, Linking, StatusBar } from "react-native";
import "react-native-gesture-handler";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { SafeAreaProvider } from "react-native-safe-area-context";
@@ -43,7 +43,7 @@ import { changeSystemBarColors, useThemeStore } from "./stores/use-theme-store";
import { useUserStore } from "./stores/use-user-store";
import RNBootSplash from "react-native-bootsplash";
import AppLocked from "./components/app-lock";
import { useSettingStore } from "./stores/use-setting-store";
I18nManager.allowRTL(false);
I18nManager.forceRTL(false);
I18nManager.swapLeftAndRightInRTL(false);
@@ -54,13 +54,18 @@ if (appLockEnabled || appLockMode !== "none") {
RNBootSplash.hide({
fade: true
});
Linking.getInitialURL().then((url) => {
useSettingStore.setState({
initialUrl: url
});
});
const App = (props: { configureMode: "note-preview" }) => {
useAppEvents();
//@ts-ignore
globalThis["IS_MAIN_APP_RUNNING"] = true;
useEffect(() => {
changeSystemBarColors();
SettingsService.onFirstLaunch();
changeSystemBarColors();
setTimeout(async () => {
await Notifications.get();
if (SettingsService.get().notifNotes) {

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 { isImage, RequestOptions } from "@notesnook/core";
import { isImage, RequestOptions, hosts } from "@notesnook/core";
import { PermissionsAndroid, Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import { ToastManager } from "../../services/event-manager";
@@ -32,6 +32,133 @@ import {
getUploadedFileSize
} from "./utils";
import Upload from "@ammarahmed/react-native-upload";
import { CloudUploader } from "react-native-nitro-cloud-uploader";
// Upload constants
const CHUNK_SIZE = 10 * 1024 * 1024; // 10 MB
const MINIMUM_MULTIPART_FILE_SIZE = 25 * 1024 * 1024; // 25MB
interface InitiateMultipartResponse {
uploadId: string;
parts: string[];
error?: string;
}
async function initiateMultipartUpload(
filename: string,
fileSize: number,
headers: Record<string, string>
): Promise<InitiateMultipartResponse> {
const totalParts = Math.ceil(fileSize / CHUNK_SIZE);
const url = `${hosts.API_HOST}/s3/multipart?name=${filename}&parts=${totalParts}&uploadId=`;
const response = await fetch(url, { headers });
if (!response.ok) {
throw new Error(
`Failed to initiate multipart upload: ${response.statusText}`
);
}
const data = await response.json();
if (data.error) {
throw new Error(data.error);
}
if (!data.uploadId || !data.parts) {
throw new Error("Failed to initiate multipart upload: invalid response.");
}
DatabaseLogger.info(
`Initiated multipart upload for ${filename} with upload ID: ${data.uploadId}`
);
return data;
}
async function multipartUploadFile(
filename: string,
filePath: string,
fileSize: number,
requestOptions: RequestOptions,
cancelToken: { cancel: (reason?: string) => Promise<void> }
): Promise<Response> {
const { headers } = requestOptions;
try {
const uploadData = await initiateMultipartUpload(
filename,
fileSize,
headers
);
const { uploadId, parts } = uploadData;
DatabaseLogger.info(
`Starting upload for ${filename} with ${parts.length} parts`
);
cancelToken.cancel = async () => {
useAttachmentStore.getState().remove(filename);
await CloudUploader.cancelUpload(uploadId);
};
CloudUploader.addListener("upload-progress", (event) => {
useAttachmentStore
.getState()
.setProgress(
event.bytesUploaded || 0,
event.totalBytes || fileSize,
filename,
0,
"upload"
);
DatabaseLogger.info(
`File upload progress: ${filename}, ${event.bytesUploaded}/${
event.totalBytes || fileSize
}, chunk: ${event.chunkIndex}, progress: ${event.progress}`
);
});
// CloudUploader handles chunking and uploading all parts internally
const result = await CloudUploader.startUpload(
filename,
filePath,
parts,
3, // maxParallel
true // showNotification
);
CloudUploader.removeListener("upload-progress");
if (!result.success) {
throw new Error("Failed to upload multipart file");
}
DatabaseLogger.info(
`Multipart upload completed for ${filename} with upload ID: ${uploadId}`
);
const response = await fetch(`${hosts.API_HOST}/s3/multipart`, {
method: "POST",
body: JSON.stringify({
Key: filename,
UploadId: uploadId,
PartETags: result.etags.map((etag, index) => ({
partNumber: index + 1,
etag: etag
}))
}),
headers: { ...headers, "Content-Type": "application/json" }
});
return response;
} catch (error) {
DatabaseLogger.error(error, "Multipart upload failed", { filename });
CloudUploader.removeListener("upload-progress");
useAttachmentStore.getState().remove(filename);
throw error;
}
}
export async function uploadFile(
filename: string,
@@ -96,72 +223,111 @@ export async function uploadFile(
}
}
const upload = Upload.create({
customUploadId: filename,
path: Platform.OS === "ios" ? "file://" + fileInfo.path : fileInfo.path,
url: url,
method: "PUT",
headers: {
...headers,
"content-type": "application/octet-stream"
},
appGroup: IOS_APPGROUPID,
notification: {
filename:
attachmentInfo && isImage(attachmentInfo?.mimeType)
? "image"
: attachmentInfo?.filename || "file",
enabled: true,
enableRingTone: true,
autoClear: true
}
}).onChange((event) => {
switch (event.status) {
case "running":
case "pending":
useAttachmentStore
.getState()
.setProgress(
event.uploadedBytes || 0,
event.totalBytes || fileInfo.size,
filename,
0,
"upload"
);
DatabaseLogger.info(
`File upload progress: ${filename}, ${event.uploadedBytes}/${
event.totalBytes || fileInfo.size
}`
);
break;
case "completed":
console.log("Upload completed");
break;
}
});
const result = await upload.start();
cancelToken.cancel = async () => {
useAttachmentStore.getState().remove(filename);
upload.cancel();
};
let uploaded = false;
const status = result.responseCode || 0;
const uploaded = status >= 200 && status < 300;
// Use multipart upload for files larger than MINIMUM_MULTIPART_FILE_SIZE
if (fileInfo.size >= MINIMUM_MULTIPART_FILE_SIZE) {
DatabaseLogger.info(
`Using multipart upload for large file: ${filename} (${fileInfo.size} bytes)`
);
const result = await multipartUploadFile(
filename,
filePath,
fileInfo.size,
requestOptions,
cancelToken
);
const status = result.status || 0;
uploaded = status >= 200 && status < 300;
if (!uploaded) {
const fileInfo = await RNFetchBlob.fs.stat(filePath);
throw new Error(
`${status}, name: ${fileInfo.filename}, length: ${
fileInfo.size
}, info: ${JSON.stringify(await result.text())}`
);
}
} else {
// Use single-part upload for smaller files
DatabaseLogger.info(
`Using single-part upload for file: ${filename} (${fileInfo.size} bytes)`
);
const upload = Upload.create({
customUploadId: filename,
path: Platform.OS === "ios" ? "file://" + fileInfo.path : fileInfo.path,
url: url,
method: "PUT",
headers: {
...headers,
"content-type": "application/octet-stream"
},
appGroup: IOS_APPGROUPID,
notification: {
filename:
attachmentInfo && isImage(attachmentInfo?.mimeType)
? "image"
: attachmentInfo?.filename || "file",
enabled: true,
enableRingTone: true,
autoClear: true
}
}).onChange((event) => {
switch (event.status) {
case "running":
case "pending":
useAttachmentStore
.getState()
.setProgress(
event.uploadedBytes || 0,
event.totalBytes || fileInfo.size,
filename,
0,
"upload"
);
DatabaseLogger.info(
`File upload progress: ${filename}, ${event.uploadedBytes}/${
event.totalBytes || fileInfo.size
}`
);
break;
case "completed":
DatabaseLogger.info("Upload completed");
break;
}
});
const result = await upload.start();
cancelToken.cancel = async () => {
useAttachmentStore.getState().remove(filename);
upload.cancel();
};
const status = result.responseCode || 0;
uploaded = status >= 200 && status < 300;
if (!uploaded) {
const fileInfo = await RNFetchBlob.fs.stat(filePath);
throw new Error(
`${status}, name: ${fileInfo.filename}, length: ${
fileInfo.size
}, info: ${JSON.stringify(result.error)}`
);
}
}
useAttachmentStore.getState().remove(filename);
if (!uploaded) {
const fileInfo = await RNFetchBlob.fs.stat(filePath);
throw new Error(
`${status}, name: ${fileInfo.filename}, length: ${
fileInfo.size
}, info: ${JSON.stringify(result.error)}`
if (uploaded) {
attachmentInfo = await db.attachments.attachment(filename);
if (!attachmentInfo) return false;
await checkUpload(
filename,
requestOptions.chunkSize,
attachmentInfo.size
);
DatabaseLogger.info(`File upload status: ${filename}, success`);
}
attachmentInfo = await db.attachments.attachment(filename);
if (!attachmentInfo) return false;
await checkUpload(filename, requestOptions.chunkSize, attachmentInfo.size);
DatabaseLogger.info(`File upload status: ${filename}, ${status}`);
return uploaded;
} catch (e) {
useAttachmentStore.getState().remove(filename);

View File

@@ -25,7 +25,7 @@ import { TouchableOpacity, View, useWindowDimensions } from "react-native";
import { SheetManager } from "react-native-actions-sheet";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
import { DDS } from "../../services/device-detection";
import { eSendEvent } from "../../services/event-manager";
import { eSendEvent, ToastManager } from "../../services/event-manager";
import Navigation from "../../services/navigation";
import PremiumService from "../../services/premium";
import SettingsService from "../../services/settings";
@@ -257,8 +257,13 @@ export const Login = ({
paddingHorizontal: 0
}}
onPress={() => {
if (loading || !email.current) return;
SheetManager.show("forgotpassword_sheet");
ToastManager.show({
type: "info",
message:
"Password changing has been disabled temporarily to address some issues faced by users. It will be enabled again once the issues have resolved."
});
// if (loading || !email.current) return;
// SheetManager.show("forgotpassword_sheet");
}}
textStyle={{
textDecorationLine: "underline"

View File

@@ -0,0 +1,66 @@
import { useThemeColors } from "@notesnook/theme";
import { useRef } from "react";
import { View } from "react-native";
import { defaultBorderRadius } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import DatePicker from "react-native-date-picker";
import dayjs from "dayjs";
import { strings } from "@notesnook/intl";
import { Button } from "../ui/button";
export default function DatePickerComponent(props: {
onConfirm: (date: Date) => void;
onCancel: () => void;
}) {
const { colors, isDark } = useThemeColors();
const dateRef = useRef<Date>(dayjs().add(1, "day").toDate());
return (
<View
style={{
backgroundColor: colors.primary.background,
borderRadius: defaultBorderRadius,
padding: DefaultAppStyles.GAP,
borderWidth: 0.5,
borderColor: colors.primary.border,
width: "80%",
gap: DefaultAppStyles.GAP_VERTICAL
}}
>
<DatePicker
theme={isDark ? "dark" : "light"}
mode="date"
minimumDate={dayjs().add(1, "day").toDate()}
onCancel={() => {
close?.();
}}
date={dateRef.current}
onDateChange={(date) => {
dateRef.current = date;
}}
/>
<Button
title={strings.setExpiry()}
type="accent"
style={{
width: "100%"
}}
onPress={async () => {
if (!dateRef.current) return;
props.onConfirm(dateRef.current);
}}
/>
<Button
title={strings.cancel()}
type="secondary"
style={{
width: "100%"
}}
onPress={async () => {
props.onCancel();
}}
/>
</View>
);
}

View File

@@ -17,7 +17,6 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { TextInput, View, ViewStyle } from "react-native";

View File

@@ -1,900 +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 Clipboard from "@react-native-clipboard/clipboard";
import React, { Component, createRef } from "react";
import { InteractionManager, View } from "react-native";
import Share from "react-native-share";
import { notesnook } from "../../../../e2e/test.ids";
import { db } from "../../../common/database";
import BiometricService from "../../../services/biometrics";
import { DDS } from "../../../services/device-detection";
import {
ToastManager,
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { getElevationStyle } from "../../../utils/elevation";
import {
eCloseActionSheet,
eCloseVaultDialog,
eOnLoadNote,
eOpenVaultDialog,
eUpdateNoteInEditor
} from "../../../utils/events";
import { deleteItems } from "../../../utils/functions";
import { fluidTabsRef } from "../../../utils/global-refs";
import { convertNoteToText } from "../../../utils/note-to-text";
import { sleep } from "../../../utils/time";
import BaseDialog from "../../dialog/base-dialog";
import DialogButtons from "../../dialog/dialog-buttons";
import DialogHeader from "../../dialog/dialog-header";
import { Toast } from "../../toast";
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";
import { DefaultAppStyles } from "../../../utils/styles";
export class VaultDialog extends Component {
constructor(props) {
super(props);
this.state = {
visible: false,
wrongPassword: false,
loading: false,
note: {},
vault: false,
locked: true,
permanant: false,
goToEditor: false,
share: false,
passwordsDontMatch: false,
deleteNote: false,
focusIndex: null,
biometricUnlock: false,
isBiometryEnrolled: false,
isBiometryAvailable: false,
fingerprintAccess: false,
changePassword: false,
copyNote: false,
revokeFingerprintAccess: false,
title: strings.goToEditor(),
description: null,
clearVault: false,
deleteVault: false,
deleteAll: false,
noteLocked: false
};
this.passInputRef = createRef();
this.confirmPassRef = createRef();
this.changePassInputRef = createRef();
this.password = null;
this.confirmPassword = null;
this.newPassword = null;
this.title = !this.state.novault
? strings.createVault()
: this.state.fingerprintAccess
? strings.vaultFingerprintUnlock()
: this.state.revokeFingerprintAccess
? strings.revokeVaultFingerprintUnlock()
: this.state.changePassword
? strings.changeVaultPassword()
: this.state.noteLocked
? this.state.deleteNote
? strings.deleteNote()
: this.state.share
? strings.shareNote()
: this.state.copyNote
? strings.copyNote()
: this.state.goToEditor
? strings.goToEditor()
: strings.goToEditor()
: strings.lockNote();
}
componentDidMount() {
eSubscribeEvent(eOpenVaultDialog, this.open);
eSubscribeEvent(eCloseVaultDialog, this.close);
}
componentWillUnmount() {
eUnSubscribeEvent(eOpenVaultDialog, this.open);
eUnSubscribeEvent(eCloseVaultDialog, this.close);
}
/**
*
* @param {import('../../../services/event-manager').vaultType} data
*/
open = async (data) => {
let biometry = await BiometricService.isBiometryAvailable();
let available = false;
let fingerprint = await BiometricService.hasInternetCredentials("nn_vault");
if (biometry) {
available = true;
}
this.setState({
note: data.item,
novault: data.novault,
locked: data.locked,
permanant: data.permanant,
goToEditor: data.goToEditor,
share: data.share,
deleteNote: data.deleteNote,
copyNote: data.copyNote,
isBiometryAvailable: available,
biometricUnlock: fingerprint,
isBiometryEnrolled: fingerprint,
fingerprintAccess: data.fingerprintAccess,
changePassword: data.changePassword,
revokeFingerprintAccess: data.revokeFingerprintAccess,
title: data.title,
description: data.description,
clearVault: data.clearVault,
deleteVault: data.deleteVault,
noteLocked: data.item && (await db.vaults.itemExists(data.item))
});
if (
fingerprint &&
data.novault &&
!data.fingerprintAccess &&
!data.revokeFingerprintAccess &&
!data.changePassword &&
!data.clearVault &&
!data.deleteVault
) {
await this._onPressFingerprintAuth(data.title, data.description);
} else {
this.setState({
visible: true
});
}
};
close = () => {
if (this.state.loading) {
ToastManager.show({
heading: this.state.title,
message: strings.pleaseWait() + "...",
type: "success",
context: "local"
});
return;
}
Navigation.queueRoutesForUpdate();
this.password = null;
this.confirmPassword = null;
this.setState({
visible: false,
note: {},
locked: false,
permanant: false,
goToEditor: false,
share: false,
novault: false,
deleteNote: false,
passwordsDontMatch: false
});
};
onPress = async () => {
if (this.state.revokeFingerprintAccess) {
await this._revokeFingerprintAccess();
this.close();
return;
}
if (this.state.loading) return;
if (!this.password) {
ToastManager.show({
heading: strings.passwordNotEntered(),
type: "error",
context: "local"
});
return;
}
if (!this.state.novault) {
if (this.password !== this.confirmPassword) {
ToastManager.show({
heading: strings.passwordNotMatched(),
type: "error",
context: "local"
});
this.setState({
passwordsDontMatch: true
});
return;
}
this._createVault();
} else if (this.state.changePassword) {
this.setState({
loading: true
});
db.vault
.changePassword(this.password, this.newPassword)
.then(() => {
this.setState({
loading: false
});
if (this.state.biometricUnlock) {
this._enrollFingerprint(this.newPassword);
}
ToastManager.show({
heading: strings.passwordUpdated(),
type: "success",
context: "global"
});
this.close();
})
.catch((e) => {
this.setState({
loading: false
});
if (e.message === db.vault.ERRORS.wrongPassword) {
ToastManager.show({
heading: strings.passwordIncorrect(),
type: "error",
context: "local"
});
} else {
ToastManager.error(e);
}
});
} else if (this.state.locked) {
if (!this.password || this.password.trim() === 0) {
ToastManager.show({
heading: strings.passwordIncorrect(),
type: "error",
context: "local"
});
this.setState({
wrongPassword: true
});
return;
}
if (this.state.noteLocked) {
await this._unlockNote();
} else {
db.vault
.unlock(this.password)
.then(async () => {
this.setState({
wrongPassword: false
});
await this._lockNote();
})
.catch((e) => {
this._takeErrorAction(e);
});
}
} else if (this.state.fingerprintAccess) {
this._enrollFingerprint(this.password);
} else if (this.state.clearVault) {
await this.clearVault();
} else if (this.state.deleteVault) {
await this.deleteVault();
}
};
deleteVault = async () => {
this.setState({
loading: true
});
try {
let verified = true;
if (await db.user.getUser()) {
verified = await db.user.verifyPassword(this.password);
}
if (verified) {
let noteIds = [];
if (this.state.deleteAll) {
const vault = await db.vaults.default();
const relations = await db.relations.from(vault, "note").get();
noteIds = relations.map((item) => item.toId);
}
await db.vault.delete(this.state.deleteAll);
if (this.state.deleteAll) {
noteIds.forEach((id) => {
eSendEvent(
eUpdateNoteInEditor,
{
id: id,
deleted: true
},
true
);
});
}
eSendEvent("vaultUpdated");
this.setState({
loading: false
});
setTimeout(() => {
this.close();
}, 100);
} else {
ToastManager.show({
heading: strings.passwordIncorrect(),
type: "error",
context: "local"
});
}
} catch (e) {
console.error(e);
}
};
clearVault = async () => {
this.setState({
loading: true
});
try {
const vault = await db.vaults.default();
const relations = await db.relations.from(vault, "note").get();
const noteIds = relations.map((item) => item.toId);
await db.vault.clear(this.password);
noteIds.forEach((id) => {
eSendEvent(
eUpdateNoteInEditor,
{
id: id,
deleted: true
},
true
);
});
this.setState({
loading: false
});
this.close();
eSendEvent("vaultUpdated");
} catch (e) {
ToastManager.show({
heading: strings.passwordIncorrect(),
type: "error",
context: "local"
});
}
this.setState({
loading: false
});
};
async _lockNote() {
if (!this.password || this.password.trim() === 0) {
ToastManager.show({
heading: strings.passwordIncorrect(),
type: "error",
context: "local"
});
return;
} else {
await db.vault.add(this.state.note.id);
eSendEvent(eUpdateNoteInEditor, this.state.note, true);
this.close();
ToastManager.show({
message: strings.noteLocked(),
type: "error",
context: "local"
});
this.setState({
loading: false
});
}
}
async _unlockNote() {
if (!this.password || this.password.trim() === 0) {
ToastManager.show({
heading: strings.passwordIncorrect(),
type: "error",
context: "local"
});
return;
}
if (this.state.permanant) {
this._permanantUnlock();
} else {
await this._openNote();
}
}
_openNote = async () => {
try {
let note = await db.vault.open(this.state.note.id, this.password);
if (this.state.biometricUnlock && !this.state.isBiometryEnrolled) {
await this._enrollFingerprint(this.password);
}
if (this.state.goToEditor) {
this._openInEditor(note);
} else if (this.state.share) {
await this._shareNote(note);
} else if (this.state.deleteNote) {
await this._deleteNote();
} else if (this.state.copyNote) {
await this._copyNote(note);
}
} catch (e) {
this._takeErrorAction(e);
}
};
async _deleteNote() {
try {
await db.vault.remove(this.state.note.id, this.password);
await deleteItems("note", [this.state.note.id]);
this.close();
} catch (e) {
this._takeErrorAction(e);
}
}
async _enrollFingerprint(password) {
this.setState(
{
loading: true
},
async () => {
try {
await db.vault.unlock(password);
await BiometricService.storeCredentials(password);
this.setState({
loading: false
});
eSendEvent("vaultUpdated");
ToastManager.show({
heading: strings.biometricUnlockEnabled(),
type: "success",
context: "global"
});
this.close();
} catch (e) {
this.close();
ToastManager.show({
heading: strings.passwordIncorrect(),
type: "error",
context: "local"
});
this.setState({
loading: false
});
}
}
);
}
async _createVault() {
await db.vault.create(this.password);
if (this.state.biometricUnlock) {
await this._enrollFingerprint(this.password);
}
if (this.state.note?.id) {
await db.vault.add(this.state.note.id);
eSendEvent(eUpdateNoteInEditor, this.state.note, true);
this.setState({
loading: false
});
ToastManager.show({
heading: strings.noteLocked(),
type: "success",
context: "global"
});
this.close();
} else {
ToastManager.show({
heading: strings.vaultCreated(),
type: "success",
context: "global"
});
this.close();
}
eSendEvent("vaultUpdated");
}
_permanantUnlock() {
db.vault
.remove(this.state.note.id, this.password)
.then(() => {
ToastManager.show({
heading: strings.noteUnlocked(),
type: "success",
context: "global"
});
eSendEvent(eUpdateNoteInEditor, this.state.note, true);
this.close();
})
.catch((e) => {
this._takeErrorAction(e);
});
}
_openInEditor(note) {
this.close();
InteractionManager.runAfterInteractions(async () => {
eSendEvent(eOnLoadNote, {
item: note
});
if (!DDS.isTab) {
fluidTabsRef.current?.goToPage("editor");
}
});
}
async _copyNote(note) {
Clipboard.setString((await convertNoteToText(note, true)) || "");
ToastManager.show({
heading: strings.noteCopied(),
type: "success",
context: "global"
});
this.close();
}
async _shareNote(note) {
this.close();
try {
await Share.open({
heading: note.title,
failOnCancel: false,
message: (await convertNoteToText(note)) || ""
});
} catch (e) {
console.error(e);
}
}
_takeErrorAction(e) {
this.setState({
wrongPassword: true,
visible: true
});
setTimeout(() => {
ToastManager.show({
heading: strings.passwordIncorrect(),
type: "error",
context: "local"
});
}, 500);
}
_revokeFingerprintAccess = async () => {
try {
await BiometricService.resetCredentials();
eSendEvent("vaultUpdated");
ToastManager.show({
heading: strings.biometricUnlockDisabled(),
type: "success",
context: "global"
});
} catch (e) {
ToastManager.show({
heading: e.message,
type: "success",
context: "global"
});
}
};
_onPressFingerprintAuth = async (title, description) => {
try {
let credentials = await BiometricService.getCredentials(
title || this.state.title,
description || this.state.description
);
if (credentials?.password) {
this.password = credentials.password;
this.onPress();
} else {
eSendEvent(eCloseActionSheet);
await sleep(300);
this.setState({
visible: true
});
}
} catch (e) {
console.error(e);
}
};
render() {
const { colors } = this.props;
const {
note,
visible,
novault,
deleteNote,
share,
goToEditor,
fingerprintAccess,
changePassword,
loading,
deleteVault,
clearVault
} = this.state;
if (!visible) return null;
return (
<BaseDialog
onShow={async () => {
await sleep(100);
this.passInputRef.current?.focus();
}}
statusBarTranslucent={false}
onRequestClose={this.close}
visible={true}
>
<View
style={{
...getElevationStyle(5),
width: DDS.isTab ? 350 : "85%",
borderRadius: 10,
backgroundColor: colors.primary.background,
paddingTop: 12
}}
>
<DialogHeader title={this.state.title} icon="shield" padding={12} />
<Seperator half />
<View
style={{
paddingHorizontal: DefaultAppStyles.GAP
}}
>
{(novault ||
changePassword ||
this.state.clearVault ||
this.state.deleteVault) &&
!this.state.revokeFingerprintAccess ? (
<>
<Input
fwdRef={this.passInputRef}
editable={!loading}
autoCapitalize="none"
testID={notesnook.ids.dialogs.vault.pwd}
onChangeText={(value) => {
this.password = value;
}}
marginBottom={
!this.state.biometricUnlock ||
!this.state.isBiometryEnrolled ||
!novault ||
changePassword
? 0
: 10
}
onSubmit={() => {
changePassword
? this.confirmPassRef.current?.focus()
: this.onPress;
}}
autoComplete="password"
returnKeyLabel={
changePassword ? strings.next() : this.state.title
}
returnKeyType={changePassword ? "next" : "done"}
secureTextEntry
placeholder={
changePassword
? strings.currentPassword()
: strings.password()
}
/>
{!this.state.biometricUnlock ||
!this.state.isBiometryEnrolled ||
!novault ||
changePassword ? null : (
<Button
onPress={() =>
this._onPressFingerprintAuth(strings.unlockNote(), "")
}
icon="fingerprint"
width="100%"
title={strings.unlockWithBiometrics()}
type="transparent"
/>
)}
</>
) : null}
{this.state.deleteVault && (
<Button
onPress={() =>
this.setState({
deleteAll: !this.state.deleteAll
})
}
icon={
this.state.deleteAll
? "check-circle-outline"
: "checkbox-blank-circle-outline"
}
style={{
marginTop: DefaultAppStyles.GAP_VERTICAL
}}
width="100%"
title={strings.deleteAllNotes()}
type="errorShade"
/>
)}
{changePassword ? (
<>
<Seperator half />
<Input
ref={this.confirmPassRef}
editable={!loading}
testID={notesnook.ids.dialogs.vault.changePwd}
autoCapitalize="none"
onChangeText={(value) => {
this.newPassword = value;
}}
autoComplete="password"
onSubmit={this.onPress}
returnKeyLabel="Change"
returnKeyType="done"
secureTextEntry
placeholder={strings.newPassword()}
/>
</>
) : null}
{!novault ? (
<View>
<Input
fwdRef={this.passInputRef}
autoCapitalize="none"
testID={notesnook.ids.dialogs.vault.pwd}
onChangeText={(value) => {
this.password = value;
}}
autoComplete="password"
returnKeyLabel={strings.next()}
returnKeyType="next"
secureTextEntry
onSubmit={() => {
this.confirmPassRef.current?.focus();
}}
placeholder={strings.password()}
/>
<Input
fwdRef={this.confirmPassRef}
autoCapitalize="none"
testID={notesnook.ids.dialogs.vault.pwdAlt}
secureTextEntry
validationType="confirmPassword"
customValidator={() => this.password}
errorMessage="Passwords do not match."
onErrorCheck={() => null}
marginBottom={0}
autoComplete="password"
returnKeyLabel="Create"
returnKeyType="done"
onChangeText={(value) => {
this.confirmPassword = value;
if (value !== this.password) {
this.setState({
passwordsDontMatch: true
});
} else {
this.setState({
passwordsDontMatch: false
});
}
}}
onSubmit={this.onPress}
placeholder={strings.confirmPassword()}
/>
</View>
) : null}
{this.state.biometricUnlock &&
!this.state.isBiometryEnrolled &&
novault ? (
<Paragraph>{strings.vaultEnableBiometrics()}</Paragraph>
) : null}
{this.state.isBiometryAvailable &&
!this.state.fingerprintAccess &&
!this.state.clearVault &&
!this.state.deleteVault &&
((!this.state.biometricUnlock && !changePassword) || !novault) ? (
<Button
onPress={() => {
this.setState({
biometricUnlock: !this.state.biometricUnlock
});
}}
style={{
marginTop: DefaultAppStyles.GAP_VERTICAL
}}
icon="fingerprint"
width="100%"
title={strings.unlockWithBiometrics()}
iconColor={
this.state.biometricUnlock
? colors.selected.accent
: colors.primary.icon
}
type={this.state.biometricUnlock ? "transparent" : "plain"}
/>
) : null}
</View>
<DialogButtons
onPressNegative={this.close}
onPressPositive={this.onPress}
loading={loading}
positiveType={
deleteVault || clearVault ? "errorShade" : "transparent"
}
positiveTitle={
deleteVault
? strings.delete()
: clearVault
? strings.clear()
: fingerprintAccess
? strings.enable()
: this.state.revokeFingerprintAccess
? strings.revoke()
: changePassword
? strings.change()
: this.state.noteLocked
? deleteNote
? strings.delete()
: share
? strings.share()
: goToEditor
? strings.open()
: strings.unlock()
: !note.id
? strings.create()
: strings.lock()
}
/>
</View>
<Toast context="local" />
</BaseDialog>
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -50,7 +50,7 @@ export const Header = ({
onLeftMenuButtonPress?: () => void;
renderedInRoute?: RouteName;
id?: string;
title: string;
title?: string;
canGoBack?: boolean;
onPressDefaultRightButton?: () => void;
hasSearch?: boolean;
@@ -114,7 +114,16 @@ export const Header = ({
onLeftButtonPress={onLeftMenuButtonPress}
/>
{hasSearch ? (
{!title ? (
<View
style={{
width: 100,
backgroundColor: colors.primary.hover,
height: 10,
borderRadius: 100
}}
/>
) : hasSearch ? (
<Paragraph>
{selectionMode
? `${selectedItemsList.length} selected`

View File

@@ -131,7 +131,7 @@ function ReorderableList<T extends { id: string }>({
]
);
function getOrderedItems() {
const getOrderedItems = React.useCallback(() => {
if (!customizableSidebarFeature?.isAllowed) return data;
const items: T[] = [];
itemOrderState.forEach((id) => {
@@ -142,7 +142,7 @@ function ReorderableList<T extends { id: string }>({
items.push(...data.filter((i) => !itemOrderState.includes(i.id)));
return items;
}
}, [data, customizableSidebarFeature?.isAllowed]);
return (
<View style={styles.container}>

View File

@@ -18,8 +18,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { getFormattedDate } from "@notesnook/common";
import {
EncryptedContentItem,
isEncryptedContent,
Note,
UnencryptedContentItem
} from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import KeepAwake from "@sayem314/react-native-keep-awake";
import { diff } from "diffblazer";
import React, { useEffect, useRef, useState } from "react";
import { SafeAreaView, Text, View } from "react-native";
import Animated from "react-native-reanimated";
@@ -32,13 +40,16 @@ import { DDS } from "../../services/device-detection";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent
eUnSubscribeEvent,
openVault
} from "../../services/event-manager";
import Navigation from "../../services/navigation";
import Sync from "../../services/sync";
import { useSettingStore } from "../../stores/use-setting-store";
import { eOnLoadNote, eShowMergeDialog } from "../../utils/events";
import { AppFontSize } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { Dialog } from "../dialog";
import BaseDialog from "../dialog/base-dialog";
import DialogButtons from "../dialog/dialog-buttons";
import DialogContainer from "../dialog/dialog-container";
@@ -47,46 +58,55 @@ import { Button } from "../ui/button";
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";
import { DefaultAppStyles } from "../../utils/styles";
import { presentDialog } from "../dialog/functions";
const MergeConflicts = () => {
const { colors } = useThemeColors();
const [visible, setVisible] = useState(false);
const [keep, setKeep] = useState(null);
const [copy, setCopy] = useState(null);
const [selectedContent, setSelectedContent] =
useState<UnencryptedContentItem>();
const [copyOfDiscardedContent, setCopyOfDiscardedContent] =
useState<UnencryptedContentItem>();
const [dialogVisible, setDialogVisible] = useState(false);
const insets = useGlobalSafeAreaInsets();
const content = useRef(null);
const isKeepingConflicted = !keep?.conflicted;
const isKeeping = !!keep;
const content = useRef<UnencryptedContentItem>(null);
const { height } = useSettingStore((state) => state.dimensions);
const applyChanges = async () => {
let _content = keep;
let note = await db.notes.note(_content.noteId);
let contentToSave = selectedContent;
if (!contentToSave) return;
let note = await db.notes.note(contentToSave.noteId);
if (!note) return;
await db.notes.add({
id: note.id,
conflicted: false,
dateEdited: _content.dateEdited
dateEdited: contentToSave.dateEdited
});
await db.content.add({
id: note.contentId,
data: _content.data,
type: _content.type,
dateResolved: content.current?.conflicted?.dateModified || Date.now(),
sessionId: Date.now(),
conflicted: false
});
const noteLocked = await db.vaults.itemExists(note);
if (copy) {
if (noteLocked) {
await db.vault.save({
...contentToSave,
sessionId: `${Date.now()}`
});
} else {
await db.content.add({
id: note.contentId,
data: contentToSave.data,
type: contentToSave.type,
dateResolved: content.current?.conflicted?.dateModified || Date.now(),
sessionId: `${Date.now()}`,
conflicted: undefined
});
}
if (copyOfDiscardedContent) {
await db.notes.add({
title: note.title + " (Copy)",
content: {
data: copy.data,
type: copy.type
data: copyOfDiscardedContent.data,
type: copyOfDiscardedContent.type
}
});
}
@@ -103,15 +123,86 @@ const MergeConflicts = () => {
Sync.run();
};
const show = async (item) => {
let noteContent = await db.content.get(item.contentId);
content.current = { ...noteContent };
if (__DEV__) {
if (!noteContent.conflicted) {
content.current.conflicted = { ...noteContent };
const show = async (item: Note) => {
const isLocked = await db.vaults.itemExists(item);
let noteContent: UnencryptedContentItem;
if (isLocked) {
openVault({
item: item,
novault: true,
customActionTitle: "Unlock note",
customActionParagraph: "Unlock note to merge conflicts",
onUnlock: async (item, password) => {
if (!item || !password) return;
const currentContent = await db.content.get(item.contentId!);
try {
noteContent = {
...(await db.content.get(item.contentId!)),
...item.content,
conflicted: currentContent?.conflicted
? await db.vault.decryptContent(
currentContent?.conflicted as EncryptedContentItem,
password
)
: undefined
} as UnencryptedContentItem;
content.current = noteContent;
if (__DEV__) {
if (!noteContent?.conflicted) {
content.current.conflicted = noteContent;
}
}
setVisible(true);
} catch (e) {
presentDialog({
input: true,
inputPlaceholder: strings.enterPassword(),
title: strings.unlockIncomingNote(),
paragraph: strings.unlockIncomingNoteDesc(),
positiveText: "Unlock",
positivePress: async (password) => {
try {
noteContent = {
...(await db.content.get(item.contentId!)),
...item.content,
conflicted: currentContent?.conflicted
? await db.vault.decryptContent(
currentContent?.conflicted as EncryptedContentItem,
password
)
: undefined
} as UnencryptedContentItem;
content.current = noteContent;
if (__DEV__) {
if (!noteContent?.conflicted) {
content.current.conflicted = noteContent;
}
}
setVisible(true);
return true;
} catch (e) {
return false;
}
}
});
}
}
});
} else {
noteContent = (await db.content.get(
item.contentId!
)) as UnencryptedContentItem;
content.current = noteContent;
if (__DEV__) {
if (!noteContent?.conflicted) {
content.current.conflicted = noteContent;
}
}
setVisible(true);
}
setVisible(true);
};
useEffect(() => {
@@ -123,8 +214,8 @@ const MergeConflicts = () => {
const close = () => {
setVisible(false);
setCopy(null);
setKeep(null);
setCopyOfDiscardedContent(undefined);
setSelectedContent(undefined);
setDialogVisible(false);
};
@@ -134,6 +225,12 @@ const MergeConflicts = () => {
back,
isCurrent,
contentToKeep
}: {
isDiscarded: boolean;
keeping: boolean;
back: boolean;
isCurrent: boolean;
contentToKeep: UnencryptedContentItem;
}) => {
return (
<View
@@ -194,7 +291,7 @@ const MergeConflicts = () => {
{isDiscarded ? (
<Button
onPress={() => {
setCopy(contentToKeep);
setCopyOfDiscardedContent(contentToKeep);
setDialogVisible(true);
}}
title={strings.saveACopy()}
@@ -222,7 +319,6 @@ const MergeConflicts = () => {
paddingHorizontal: DefaultAppStyles.GAP
}}
fontSize={AppFontSize.xs}
color={colors.error.paragraph}
onPress={() => {
setDialogVisible(true);
}}
@@ -244,7 +340,9 @@ const MergeConflicts = () => {
keeping && !isDiscarded ? strings.undo() : strings.keep()
}
onPress={() => {
setKeep(keeping && !isDiscarded ? null : contentToKeep);
setSelectedContent(
keeping && !isDiscarded ? undefined : contentToKeep
);
}}
/>
</>
@@ -258,7 +356,6 @@ const MergeConflicts = () => {
<BaseDialog
statusBarTranslucent
transparent={false}
animationType="slide"
animated={false}
bounce={false}
onRequestClose={() => {
@@ -266,15 +363,9 @@ const MergeConflicts = () => {
}}
centered={false}
background={colors?.primary.background}
supportedOrientations={[
"portrait",
"portrait-upside-down",
"landscape",
"landscape-left",
"landscape-right"
]}
visible={true}
>
<Dialog context="merge-conflicts" />
<SafeAreaView
style={{
backgroundColor: colors.primary.background,
@@ -301,15 +392,15 @@ const MergeConflicts = () => {
style={{
height: "100%",
width: "100%",
backgroundColor: DDS.isLargeTablet() ? "rgba(0,0,0,0.3)" : null
backgroundColor: DDS.isLargeTablet() ? "rgba(0,0,0,0.3)" : undefined
}}
>
<ConfigBar
back={true}
isCurrent={true}
isDiscarded={isKeeping && isKeepingConflicted}
keeping={isKeeping}
contentToKeep={content.current}
isDiscarded={!!selectedContent && !selectedContent.conflicted}
keeping={!!selectedContent}
contentToKeep={content.current!}
/>
<Animated.View
@@ -323,15 +414,18 @@ const MergeConflicts = () => {
<ReadonlyEditor
editorId="conflictPrimary"
onLoad={async (loadContent) => {
const note = await db.notes.note(content.current?.noteId);
const note = await db.notes.note(content.current!.noteId);
if (!note) return;
loadContent({
id: note.id,
data: diff(
content.current.conflicted.data,
content.current.data
)
});
if (content.current && content.current.conflicted) {
loadContent({
id: note.id,
data: diff(
(content.current.conflicted as UnencryptedContentItem)
.data,
content.current.data
)
});
}
}}
/>
</Animated.View>
@@ -339,9 +433,11 @@ const MergeConflicts = () => {
<ConfigBar
back={false}
isCurrent={false}
isDiscarded={isKeeping && !isKeepingConflicted}
keeping={isKeeping}
contentToKeep={content.current.conflicted}
isDiscarded={!!selectedContent && !!selectedContent.conflicted}
keeping={!!selectedContent}
contentToKeep={
content.current!.conflicted! as UnencryptedContentItem
}
/>
<Animated.View
@@ -354,11 +450,12 @@ const MergeConflicts = () => {
<ReadonlyEditor
editorId="conflictSecondary"
onLoad={async (loadContent) => {
const note = await db.notes.note(content.current?.noteId);
const note = await db.notes.note(content.current?.noteId!);
if (!note) return;
loadContent({
id: note.id,
data: content.current.conflicted.data
data: (content.current!.conflicted as UnencryptedContentItem)
.data
});
}}
/>

View File

@@ -47,7 +47,7 @@ const HistoryItem = ({
}: {
index: number;
items?: VirtualizedGrouping<HistorySession>;
note?: Note;
note: Note;
}) => {
const [item] = useDBItem(index, "noteHistory", items);
const { colors } = useThemeColors();
@@ -73,6 +73,7 @@ const HistoryItem = ({
session: getDate(item.dateCreated, item.dateModified)
}}
content={content}
note={note}
/>
),
context: "note_history"
@@ -135,7 +136,7 @@ export default function NoteHistory({
const renderItem = useCallback(
({ index }: { index: number }) => (
<HistoryItem index={index} items={history} />
<HistoryItem index={index} items={history} note={note} />
),
[history]
);

View File

@@ -37,21 +37,43 @@ import Paragraph from "../ui/typography/paragraph";
import { diff } from "diffblazer";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../utils/styles";
import {
HistorySession,
isEncryptedContent,
Note,
NoteContent,
SessionContentItem,
TrashOrItem
} from "@notesnook/core";
/**
*
* @param {any} param0
* @returns
*/
export default function NotePreview({ session, content, note }) {
export default function NotePreview({
session,
content,
note
}: {
session?: HistorySession & { session: string };
content:
| Partial<
NoteContent<boolean> & {
title: string;
}
>
| undefined;
note: TrashOrItem<Note>;
}) {
const { colors } = useThemeColors();
const [locked, setLocked] = useState(false);
async function restore() {
if (note && note.type === "trash") {
if ((await db.trash.restore(note.id)) === false) return;
await db.trash.restore(note.id);
Navigation.queueRoutesForUpdate();
useSelectionStore.getState().setSelectionMode(false);
useSelectionStore.getState().setSelectionMode();
ToastManager.show({
heading: strings.noteRestored(),
type: "success"
@@ -59,24 +81,26 @@ export default function NotePreview({ session, content, note }) {
eSendEvent(eCloseSheet);
return;
}
await db.noteHistory.restore(session.id);
if (useTabStore.getState().hasTabForNote(session?.noteId)) {
const note = editorController.current.note.current[session?.noteId];
if (note) {
eSendEvent(eOnLoadNote, {
item: note,
forced: true
});
if (session) {
await db.noteHistory.restore(session.id);
if (useTabStore.getState().hasTabForNote(session?.noteId)) {
const note = editorController.current.note.current[session?.noteId];
if (note) {
eSendEvent(eOnLoadNote, {
item: note,
forced: true
});
}
}
}
eSendEvent(eCloseSheet, "note_history");
eSendEvent(eCloseSheet);
Navigation.queueRoutesForUpdate();
eSendEvent(eCloseSheet, "note_history");
eSendEvent(eCloseSheet);
Navigation.queueRoutesForUpdate();
ToastManager.show({
heading: strings.noteRestoredFromHistory(),
type: "success"
});
ToastManager.show({
heading: strings.noteRestoredFromHistory(),
type: "success"
});
}
}
useEffect(() => {
@@ -91,15 +115,17 @@ export default function NotePreview({ session, content, note }) {
negativeText: strings.cancel(),
context: "local",
positivePress: async () => {
await db.trash.delete(note.id);
useTrashStore.getState().refresh();
useSelectionStore.getState().setSelectionMode(false);
ToastManager.show({
heading: strings.noteDeleted(),
type: "success",
context: "local"
});
eSendEvent(eCloseSheet);
if (note) {
await db.trash.delete(note.id);
useTrashStore.getState().refresh();
useSelectionStore.getState().setSelectionMode();
ToastManager.show({
heading: strings.noteDeleted(),
type: "success",
context: "local"
});
eSendEvent(eCloseSheet);
}
},
positiveType: "error"
});
@@ -113,8 +139,11 @@ export default function NotePreview({ session, content, note }) {
}}
>
<Dialog context="local" />
<DialogHeader padding={12} title={note?.title || session?.session} />
{!session?.locked && !locked ? (
<DialogHeader
padding={12}
title={content?.title || note.title || session?.session}
/>
{!session?.locked && !locked && content?.data ? (
<View
style={{
flex: 1,
@@ -125,17 +154,27 @@ export default function NotePreview({ session, content, note }) {
editorId="historyPreview"
onLoad={async (loadContent) => {
try {
if (content.data) {
const _note = note || (await db.notes.note(session?.noteId));
const currentContent = await db.content.get(_note.contentId);
loadContent({
data: diff(currentContent.data, content.data),
id: _note.id
});
if (content?.data) {
const currentContent = note?.contentId
? await db.content.get(note.contentId)
: undefined;
if (
currentContent?.data &&
!isEncryptedContent(currentContent)
) {
loadContent({
data: diff(
currentContent?.data || "<p></p>",
content.data as string
),
id: session?.noteId || note.id
});
}
}
} catch (e) {
ToastManager.error(
e,
e as Error,
"Failed to load history preview",
"local"
);
@@ -153,7 +192,9 @@ export default function NotePreview({ session, content, note }) {
}}
>
<Paragraph color={colors.secondary.paragraph}>
{strings.encryptedNoteHistoryNotice()}
{!content?.data
? strings.noContent()
: strings.encryptedNoteHistoryNotice()}
</Paragraph>
</View>
)}

View File

@@ -1,72 +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 from "react";
import { View } from "react-native";
import { useThemeColors } from "@notesnook/theme";
import { AppFontSize } from "../../utils/size";
import Paragraph from "../ui/typography/paragraph";
import { getFormattedDate } from "@notesnook/common";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../utils/styles";
export const DateMeta = ({ item }) => {
const { colors } = useThemeColors();
function getDateMeta() {
let keys = Object.keys(item);
if (keys.includes("dateEdited"))
keys.splice(
keys.findIndex((k) => k === "dateModified"),
1
);
return keys.filter((key) => key.startsWith("date") && key !== "date");
}
const renderItem = (key) =>
!item[key] ? null : (
<View
key={key}
style={{
flexDirection: "row",
justifyContent: "space-between",
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL / 2
}}
>
<Paragraph size={AppFontSize.xs} color={colors.secondary.paragraph}>
{strings.dateDescFromKey(key)}
</Paragraph>
<Paragraph size={AppFontSize.xs} color={colors.secondary.paragraph}>
{getFormattedDate(item[key], "date-time")}
</Paragraph>
</View>
);
return (
<View
style={{
borderTopWidth: 1,
borderTopColor: colors.primary.border,
paddingHorizontal: DefaultAppStyles.GAP,
paddingTop: DefaultAppStyles.GAP_VERTICAL_SMALL
}}
>
{getDateMeta().map(renderItem)}
</View>
);
};

View File

@@ -0,0 +1,130 @@
/*
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, { useState } from "react";
import { View } from "react-native";
import { useThemeColors } from "@notesnook/theme";
import { AppFontSize } from "../../utils/size";
import Paragraph from "../ui/typography/paragraph";
import { getFormattedDate } from "@notesnook/common";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../utils/styles";
import DateTimePickerModal from "react-native-modal-datetime-picker";
import { db } from "../../common/database";
import { Item, Note } from "@notesnook/core";
import AppIcon from "../ui/AppIcon";
export const DateMeta = ({ item }: { item: Item }) => {
const { colors, isDark } = useThemeColors();
const [isDatePickerVisible, setIsDatePickerVisible] = useState(false);
const [dateCreated, setDateCreated] = useState(item.dateCreated);
function getDateMeta() {
let keys = Object.keys(item);
if (keys.includes("dateEdited"))
keys.splice(
keys.findIndex((k) => k === "dateModified"),
1
);
return keys.filter((key) => key.startsWith("date") && key !== "date");
}
const renderItem = (key: string) =>
!item[key as keyof Item] ? null : (
<View
key={key}
style={{
flexDirection: "row",
justifyContent: "space-between",
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL / 2
}}
>
<Paragraph size={AppFontSize.xs} color={colors.secondary.paragraph}>
{strings.dateDescFromKey(
key as
| "dateDeleted"
| "dateEdited"
| "dateModified"
| "dateCreated"
| "dateUploaded"
)}
</Paragraph>
<Paragraph
size={AppFontSize.xs}
color={colors.secondary.paragraph}
onPress={
item.type !== "note"
? undefined
: () => {
setIsDatePickerVisible(true);
}
}
>
{getFormattedDate(
key === "dateCreated"
? dateCreated
: (item[key as keyof Item] as string),
"date-time"
)}
{key === "dateCreated" && item.type === "note" ? (
<>
{" "}
<AppIcon name="pencil" size={AppFontSize.md} />
</>
) : null}
</Paragraph>
</View>
);
return (
<>
{item.type === "note" ? (
<DateTimePickerModal
isVisible={isDatePickerVisible}
mode="datetime"
onConfirm={async (date: Date) => {
await db.notes.add({
id: item.id,
dateCreated: date.getTime()
});
setDateCreated(date.getTime());
setIsDatePickerVisible(false);
}}
onCancel={() => {
setIsDatePickerVisible(false);
}}
maximumDate={new Date((item as Note).dateEdited)}
isDarkModeEnabled={isDark}
is24Hour={db.settings.getTimeFormat() === "24-hour"}
date={new Date(dateCreated)}
/>
) : null}
<View
style={{
borderTopWidth: 1,
borderTopColor: colors.primary.border,
paddingHorizontal: DefaultAppStyles.GAP,
paddingTop: DefaultAppStyles.GAP_VERTICAL_SMALL
}}
>
{getDateMeta().map(renderItem)}
</View>
</>
);
};

View File

@@ -39,6 +39,7 @@ import { DateMeta } from "./date-meta";
import { Items } from "./items";
import Notebooks from "./notebooks";
import { TagStrip, Tags } from "./tags";
import { Dialog } from "../dialog";
const Line = ({ top = 6, bottom = 6 }) => {
const { colors } = useThemeColors();
@@ -203,6 +204,7 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
/>
) : null}
<SheetProvider context="properties" />
<Dialog context="properties" />
</View>
)}
/>

View File

@@ -33,6 +33,7 @@ import AppIcon from "../ui/AppIcon";
import { Button } from "../ui/button";
import { Pressable } from "../ui/pressable";
import Paragraph from "../ui/typography/paragraph";
import { Dialog } from "../dialog";
const TOP_BAR_ITEMS: ActionId[] = [
"pin",
@@ -57,6 +58,8 @@ const BOTTOM_BAR_ITEMS: ActionId[] = [
"export",
"copy-link",
"duplicate",
"launcher-shortcut",
"expiry-date",
"trash"
];
@@ -76,6 +79,7 @@ const COLUMN_BAR_ITEMS: ActionId[] = [
"reorder",
"rename-color",
"rename-tag",
"launcher-shortcut",
"restore",
"trash",
"delete"
@@ -170,15 +174,15 @@ export const Items = ({
DDS.isTab
? AppFontSize.xxl
: shouldShrink
? AppFontSize.xxl
: AppFontSize.lg
? AppFontSize.xxl
: AppFontSize.lg
}
color={
item.checked
? item.activeColor || colors.primary.accent
: item.id.match(/(delete|trash)/g)
? colors.error.icon
: colors.secondary.icon
? colors.error.icon
: colors.secondary.icon
}
/>
</Pressable>
@@ -209,8 +213,8 @@ export const Items = ({
text: item.checked
? item.activeColor || colors.primary.accent
: item.id === "delete" || item.id === "trash"
? colors.error.paragraph
: colors.primary.paragraph
? colors.error.paragraph
: colors.primary.paragraph
}}
testID={"icon-" + item.id}
onPress={item.onPress}
@@ -274,8 +278,8 @@ export const Items = ({
item.checked
? item.activeColor || colors.primary.accent
: item.id === "delete" || item.id === "trash"
? colors.error.icon
: colors.secondary.icon
? colors.error.icon
: colors.secondary.icon
}
/>

View File

@@ -78,12 +78,15 @@ const PublishNoteSheet = ({
const isFeatureAvailable = useIsFeatureAvailable("monographAnalytics");
const [isLocked, setIsLocked] = useState(false);
const [publishing, setPublishing] = useState(false);
const customTitle = useRef<string>("");
const pwdInput = useRef<TextInput>(null);
const titleInput = useRef<TextInput>(null);
const passwordValue = useRef<string>(undefined);
const monographData = useAsync(async () => {
return fetchMonographData(note?.id);
}, []);
const monograph = monographData.result?.monograph;
customTitle.current = monograph?.title || note.title || "";
const publishUrl = monograph && `${hosts.MONOGRAPH_HOST}/${monograph?.id}`;
const isPublished = db.monographs.monograph(note?.id);
@@ -108,7 +111,7 @@ const PublishNoteSheet = ({
try {
if (note?.id) {
if (isLocked && !passwordValue.current) return;
await db.monographs.publish(note.id, {
await db.monographs.publish(note.id, customTitle.current, {
selfDestruct: selfDestruct,
password: isLocked ? passwordValue.current : undefined
});
@@ -129,7 +132,6 @@ const PublishNoteSheet = ({
setPublishLoading(false);
};
const setPublishLoading = (value: boolean) => {
setPublishing(value);
};
@@ -245,6 +247,13 @@ const PublishNoteSheet = ({
</TouchableOpacity>
) : null}
<Input
fwdRef={titleInput}
onChangeText={(value) => (customTitle.current = value)}
defaultValue={customTitle.current}
placeholder={strings.noteTitle()}
/>
<TouchableOpacity
onPress={() => {
if (publishing) return;

View File

@@ -30,6 +30,7 @@ import ReorderableList from "../list/reorderable-list";
import { Properties } from "../properties";
import { useSideBarDraggingStore } from "./dragging-store";
import { MenuItem } from "./menu-item";
import { Default_Drag_Action } from "../../hooks/use-actions";
export const ColorSection = React.memo(
function ColorSection() {
@@ -59,7 +60,7 @@ export const ColorSection = React.memo(
const onLongPress = React.useCallback((item: SideMenuItem) => {
if (useSideBarDraggingStore.getState().dragging) return;
Properties.present(item.data as Color);
Properties.present(item.data as Color, false, [Default_Drag_Action]);
}, []);
const renderIcon = React.useCallback((item: SideMenuItem, size: number) => {

View File

@@ -21,15 +21,6 @@ import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { View } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import {
NavigationState,
Route,
SceneMap,
SceneRendererProps,
TabDescriptor,
TabView
} from "react-native-tab-view";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import { useGroupOptions } from "../../hooks/use-group-options";
@@ -59,7 +50,104 @@ import SettingsService from "../../services/settings";
import { isFeatureAvailable } from "@notesnook/common";
import PaywallSheet from "../sheets/paywall";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
const renderScene = SceneMap({
/**
* Simple Tab View Implementation for the Side bar
*/
type SimpleRoute = {
key: string;
title?: string;
};
type SimpleNavigationState = {
index: number;
routes: SimpleRoute[];
};
type SimpleTabBarProps = {
navigationState: SimpleNavigationState;
jumpTo: (key: string) => void;
};
type SimpleTabViewProps = {
navigationState: SimpleNavigationState;
renderScene: ({ route }: { route: SimpleRoute }) => React.ReactNode;
renderTabBar?: (props: SimpleTabBarProps) => React.ReactNode;
onIndexChange?: (index: number) => void;
};
const createSceneMap = (
scenes: Record<string, React.ComponentType<any>>
): ((props: { route: SimpleRoute }) => React.ReactNode) => {
return ({ route }: { route: SimpleRoute }) => {
const SceneComponent = scenes[route.key];
if (!SceneComponent) return null;
return <SceneComponent />;
};
};
const SimpleTabView = ({
navigationState,
renderScene,
renderTabBar,
onIndexChange
}: SimpleTabViewProps) => {
const loadedKeysRef = React.useRef(new Set<string>());
const scenesRef = React.useRef(new Map<string, React.ReactNode>());
const jumpTo = React.useCallback(
(key: string) => {
const nextIndex = navigationState.routes.findIndex(
(route) => route.key === key
);
if (nextIndex !== -1 && nextIndex !== navigationState.index) {
onIndexChange?.(nextIndex);
}
},
[navigationState.index, navigationState.routes, onIndexChange]
);
const activeKey = navigationState.routes[navigationState.index]?.key;
if (activeKey && !loadedKeysRef.current.has(activeKey)) {
loadedKeysRef.current.add(activeKey);
}
const getSceneForRoute = React.useCallback(
(route: SimpleRoute) => {
const cached = scenesRef.current.get(route.key);
if (cached) return cached;
const created = renderScene({ route });
scenesRef.current.set(route.key, created);
return created;
},
[renderScene]
);
return (
<View style={{ flex: 1 }}>
<View style={{ flex: 1 }}>
{navigationState.routes.map((route, routeIndex) => (
<View
key={route.key}
style={{
flex: 1,
display: navigationState.index === routeIndex ? "flex" : "none"
}}
>
{loadedKeysRef.current.has(route.key)
? getSceneForRoute(route)
: navigationState.index === routeIndex
? getSceneForRoute(route)
: null}
</View>
))}
</View>
{renderTabBar ? renderTabBar({ navigationState, jumpTo }) : null}
</View>
);
};
const renderScene = createSceneMap({
home: SideMenuHome,
notebooks: SideMenuNotebooks,
tags: SideMenuTags,
@@ -73,7 +161,7 @@ export const SideMenu = React.memo(
const [index, setIndex] = React.useState(
SettingsService.getProperty("defaultSidebarTab")
);
const [routes] = React.useState<Route[]>([
const [routes] = React.useState<SimpleRoute[]>([
{
key: "home",
title: "Home"
@@ -98,15 +186,11 @@ export const SideMenu = React.memo(
paddingLeft: insets.left
}}
>
<TabView
<SimpleTabView
navigationState={{ index, routes }}
renderTabBar={(props) => <TabBar {...props} />}
tabBarPosition="bottom"
renderScene={renderScene}
onIndexChange={setIndex}
swipeEnabled={false}
animationEnabled={false}
lazy
/>
</View>
);
@@ -114,12 +198,7 @@ export const SideMenu = React.memo(
() => true
);
const TabBar = (
props: SceneRendererProps & {
navigationState: NavigationState<Route>;
options: Record<string, TabDescriptor<Route>> | undefined;
}
) => {
const TabBar = (props: SimpleTabBarProps) => {
const dragging = useSideBarDraggingStore((state) => state.dragging);
const { colors, isDark } = useThemeColors();
const groupOptions = useGroupOptions(

View File

@@ -37,7 +37,7 @@ import { Pressable } from "../ui/pressable";
import Paragraph from "../ui/typography/paragraph";
import { useSideBarDraggingStore } from "./dragging-store";
function _MenuItem({
export function MenuItem({
item,
index,
testID,
@@ -179,12 +179,3 @@ function _MenuItem({
</Pressable>
);
}
export const MenuItem = React.memo(_MenuItem, (prev, next) => {
if (
prev.item.id !== next.item.id &&
prev.item.data?.dateModified !== next.item.data?.dateModified
)
return false;
return true;
});

View File

@@ -31,6 +31,9 @@ import ReorderableList from "../list/reorderable-list";
import { MenuItem } from "./menu-item";
import { useThemeColors } from "@notesnook/theme";
import { DefaultAppStyles } from "../../utils/styles";
import { useSideBarDraggingStore } from "./dragging-store";
import { Properties } from "../properties";
import { Default_Drag_Action } from "../../hooks/use-actions";
export const PinnedSection = React.memo(
function PinnedSection() {
@@ -57,6 +60,11 @@ export const PinnedSection = React.memo(
});
}, []);
const onLongPress = React.useCallback((item: SideMenuItem) => {
if (useSideBarDraggingStore.getState().dragging) return;
Properties.present(item.data as Notebook, false, [Default_Drag_Action]);
}, []);
const menuItems = useMemo(
() =>
menuPins.map((item) => ({
@@ -65,7 +73,8 @@ export const PinnedSection = React.memo(
icon: item.type === "notebook" ? "notebook-outline" : "pound",
dataType: item.type,
data: item,
onPress: onPress
onPress: onPress,
onLongPress: onLongPress
})) as SideMenuItem[],
[menuPins, onPress]
);
@@ -98,6 +107,7 @@ export const PinnedSection = React.memo(
flexGrow: 1,
width: "100%"
}}
disableDefaultDrag
contentContainerStyle={{
flexGrow: 1
}}

View File

@@ -85,6 +85,7 @@ export function SideMenuHome() {
keyboardDismissMode="interactive"
keyboardShouldPersistTaps="handled"
keyExtractor={() => "scroll-items"}
bounces={false}
renderItem={() => (
<>
<ReorderableList

View File

@@ -17,12 +17,14 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* eslint-disable no-inner-declarations */
import { useAreFeaturesAvailable } from "@notesnook/common";
import { isFeatureAvailable, useAreFeaturesAvailable } from "@notesnook/common";
import {
Color,
createInternalLink,
Item,
ItemReference,
Note,
Notebook,
VAULT_ERRORS
} from "@notesnook/core";
import { strings } from "@notesnook/intl";
@@ -30,7 +32,7 @@ import { useThemeColors } from "@notesnook/theme";
import { DisplayedNotification } from "@notifee/react-native";
import Clipboard from "@react-native-clipboard/clipboard";
import React, { useEffect, useRef, useState } from "react";
import { InteractionManager, Platform } from "react-native";
import { InteractionManager, Platform, View } from "react-native";
import Share from "react-native-share";
import { DatabaseLogger, db } from "../common/database";
import { AttachmentDialog } from "../components/attachments";
@@ -65,11 +67,13 @@ import { useSelectionStore } from "../stores/use-selection-store";
import { useSettingStore } from "../stores/use-setting-store";
import { useTagStore } from "../stores/use-tag-store";
import { useUserStore } from "../stores/use-user-store";
import { eUpdateNoteInEditor } from "../utils/events";
import { eCloseSheet, eUpdateNoteInEditor } from "../utils/events";
import { deleteItems } from "../utils/functions";
import { convertNoteToText } from "../utils/note-to-text";
import { sleep } from "../utils/time";
import { resetStoredState } from "./use-stored-state";
import { NotesnookModule } from "../utils/notesnook-module";
import DatePickerComponent from "../components/date-picker";
export type ActionId =
| "select"
@@ -114,7 +118,9 @@ export type ActionId =
| "remove-from-notebook"
| "trash"
| "default-homepage"
| "default-tag";
| "default-tag"
| "launcher-shortcut"
| "expiry-date";
export type Action = {
id: ActionId;
@@ -130,6 +136,31 @@ export type Action = {
locked?: boolean;
};
export const Default_Drag_Action: Action = {
id: "reorder",
title: strings.reorder(),
icon: "sort-ascending",
onPress: async () => {
const feature = await isFeatureAvailable("customizableSidebar");
if (feature && !feature.isAllowed) {
ToastManager.show({
message: feature.error,
type: "info",
context: "local",
actionText: strings.upgrade(),
func: () => {
PaywallSheet.present(feature);
}
});
return;
}
useSideBarDraggingStore.setState({
dragging: true
});
eSendEvent(eCloseSheet);
}
};
function isNotePinnedInNotifications(item: Item) {
const pinned = Notifications.getPinnedNotes();
if (!pinned || pinned.length === 0) {
@@ -158,10 +189,12 @@ export const useActions = ({
"shortcuts",
"notebooks",
"customizableSidebar",
"customHomepage"
"customHomepage",
"androidLauncherShortcuts",
"expiringNotes"
]);
const [item, setItem] = useState(propItem);
const { colors } = useThemeColors();
const { colors, isDark } = useThemeColors();
const setMenuPins = useMenuStore((state) => state.setMenuPins);
const [isPinnedToMenu, setIsPinnedToMenu] = useState(
db.shortcuts.exists(item.id)
@@ -297,7 +330,6 @@ export const useActions = ({
id: item.id,
title: value
});
eSendEvent(Navigation.routeNames.TaggedNotes);
InteractionManager.runAfterInteractions(() => {
useTagStore.getState().refresh();
@@ -435,31 +467,6 @@ export const useActions = ({
icon: "square-edit-outline",
onPress: renameColor
});
actions.push({
id: "reorder",
title: strings.reorder(),
icon: "sort-ascending",
onPress: async () => {
if (features && !features.customizableSidebar.isAllowed) {
ToastManager.show({
message: features.customizableSidebar.error,
type: "info",
context: "local",
actionText: strings.upgrade(),
func: () => {
PaywallSheet.present(features.customizableSidebar);
}
});
return;
}
useSideBarDraggingStore.setState({
dragging: true
});
close();
},
locked: !features?.customizableSidebar.isAllowed
});
}
if (item.type === "reminder") {
@@ -706,26 +713,6 @@ export const useActions = ({
type: item.type
}
);
resetStoredState(
"app-home-navigtion-key",
isHomepage
? undefined
: {
name:
item.type === "notebook"
? "Notebook"
: item.type === "tag"
? "TaggedNotes"
: item.type === "color"
? "ColorNotes"
: undefined,
params: {
item: item,
id: item.id
}
}
);
}
});
}
@@ -965,7 +952,7 @@ export const useActions = ({
copyNote: true,
novault: true,
locked: true,
item: item,
item: item as Note,
title: strings.copyNote()
});
} else {
@@ -1177,6 +1164,45 @@ export const useActions = ({
},
checked: item.archived,
isToggle: true
},
{
id: "expiry-date",
title: item.expiryDate ? strings.unsetExpiry() : strings.setExpiry(),
icon: item.expiryDate ? "bomb-off" : "bomb",
locked: features?.expiringNotes?.isAllowed,
onPress: async () => {
if (item.expiryDate) {
await db.notes.setExpiryDate(null, item.id);
setItem((await db.notes.note(item.id)) as Item);
} else {
if (features && !features?.expiringNotes.isAllowed) {
ToastManager.show({
message: features?.expiringNotes.error,
type: "info",
actionText: strings.upgrade(),
context: "local",
func: () => {
PaywallSheet.present(features?.expiringNotes);
}
});
return;
}
presentDialog({
context: "properties",
component: (close) => (
<DatePickerComponent
onCancel={() => close?.()}
onConfirm={async (date) => {
close?.();
await db.notes.setExpiryDate(date.getTime(), item.id);
setItem((await db.notes.note(item.id)) as Item);
}}
/>
)
});
}
}
}
);
@@ -1207,5 +1233,44 @@ export const useActions = ({
});
}
if (
Platform.OS === "android" &&
(item.type === "tag" ||
item.type === "note" ||
item.type === "notebook" ||
item.type === "color")
) {
actions.push({
id: "launcher-shortcut",
title: strings.addToHome(),
icon: "cellphone-arrow-down",
locked: !features?.androidLauncherShortcuts.isAllowed,
onPress: async () => {
if (features && !features?.androidLauncherShortcuts.isAllowed) {
ToastManager.show({
message: features?.androidLauncherShortcuts.error,
type: "info",
actionText: strings.upgrade(),
context: "local",
func: () => {
PaywallSheet.present(features?.androidLauncherShortcuts);
}
});
return;
}
try {
await NotesnookModule.addShortcut(
item.id,
item.type,
item.title,
(item as Note).headline || (item as Notebook).description || "",
(item as Color).colorCode
);
} catch (e) {}
}
});
}
return actions;
};

View File

@@ -85,7 +85,7 @@ import Notifications from "../services/notifications";
import PremiumService from "../services/premium";
import SettingsService from "../services/settings";
import Sync from "../services/sync";
import { clearAllStores, initAfterSync, initialize } from "../stores";
import { clearAllStores, initAfterSync } from "../stores";
import { refreshAllStores } from "../stores/create-db-collection-store";
import { useAttachmentStore } from "../stores/use-attachment-store";
import { useMessageStore } from "../stores/use-message-store";
@@ -108,6 +108,8 @@ import { NotesnookModule } from "../utils/notesnook-module";
import { sleep } from "../utils/time";
import useFeatureManager from "./use-feature-manager";
import { deleteDCacheFiles } from "../common/filesystem/io";
import dayjs from "dayjs";
import { useRelationStore } from "../stores/use-relation-store";
const onCheckSyncStatus = async (type: SyncStatusEvent) => {
const { disableSync, disableAutoSync } = SettingsService.get();
@@ -160,7 +162,10 @@ const onUserSessionExpired = async () => {
eSendEvent(eLoginSessionExpired);
};
const onAppOpenedFromURL = async (event: { url: string }) => {
const onAppOpenedFromURL = async (event: {
url: string;
isInitialUrl?: boolean;
}) => {
const url = event.url;
try {
@@ -172,7 +177,7 @@ const onAppOpenedFromURL = async (event: { url: string }) => {
eSendEvent(eOnLoadNote, { newNote: true });
fluidTabsRef.current?.goToPage("editor", false);
return;
} else if (url.startsWith("https://app.notesnook.com/open_note")) {
} else if (url.startsWith("https://app.notesnook.com/open_note?")) {
const id = new URL(url).searchParams.get("id");
if (id) {
const note = await db.notes.note(id);
@@ -184,6 +189,53 @@ const onAppOpenedFromURL = async (event: { url: string }) => {
fluidTabsRef.current?.goToPage("editor", false);
}
}
} else if (
url.startsWith("https://app.notesnook.com/open_notebook?") &&
!event.isInitialUrl
) {
const id = new URL(url).searchParams.get("id");
if (id) {
const notebook = await db.notebooks.notebook(id);
if (notebook) {
Navigation.navigate("Notebook", {
id: notebook.id,
canGoBack: true,
item: notebook
});
}
}
} else if (
url.startsWith("https://app.notesnook.com/open_tag?") &&
!event.isInitialUrl
) {
const id = new URL(url).searchParams.get("id");
if (id) {
const tag = await db.tags.tag(id);
if (tag) {
Navigation.navigate("TaggedNotes", {
type: "tag",
id: tag.id,
item: tag,
canGoBack: true
});
}
}
} else if (
url.startsWith("https://app.notesnook.com/open_color?") &&
!event.isInitialUrl
) {
const id = new URL(url).searchParams.get("id");
if (id) {
const color = await db.colors.color(id);
if (color) {
Navigation.navigate("ColoredNotes", {
type: "color",
id: color.id,
item: color,
canGoBack: true
});
}
}
} else if (url.startsWith("https://app.notesnook.com/open_reminder")) {
const id = new URL(url).searchParams.get("id");
if (id) {
@@ -436,6 +488,28 @@ const IsDatabaseMigrationRequired = () => {
return true;
};
let timer: NodeJS.Timeout | null = null;
let initialDate = -1;
async function expiringNotesTimer() {
if (timer != null) clearInterval(timer);
if (initialDate === -1) {
DatabaseLogger.info("Deleting expired notes at startup");
db.notes.deleteExpiredNotes();
initialDate = dayjs().date();
}
timer = setInterval(() => {
if (dayjs().date() != initialDate) {
DatabaseLogger.info("Deleting expired notes");
db.notes.deleteExpiredNotes();
Navigation.queueRoutesForUpdate();
useRelationStore.getState().update();
initialDate = dayjs().date();
}
}, 1000 * 60);
}
const initializeDatabase = async (password?: string) => {
if (useUserStore.getState().appLocked) return;
if (!db.isInitialized) {
@@ -455,16 +529,21 @@ const initializeDatabase = async (password?: string) => {
if (db.isInitialized) {
await setAppMessage();
useSettingStore.getState().setAppLoading(false);
useSettingStore.getState().refresh();
Notifications.setupReminders(true);
DatabaseLogger.info("Database initialized");
Notifications.restorePinnedNotes();
expiringNotesTimer();
deleteDCacheFiles();
}
Walkthrough.init();
};
export const useAppEvents = () => {
const isAppLoading = useSettingStore((state) => state.isAppLoading);
const [isAppLoading, initialUrl] = useSettingStore((state) => [
state.isAppLoading,
state.initialUrl
]);
const [setLastSynced, setUser, appLocked, syncing] = useUserStore((state) => [
state.setLastSynced,
state.setUser,
@@ -509,6 +588,15 @@ export const useAppEvents = () => {
};
}, [isAppLoading, onSyncComplete]);
useEffect(() => {
if (initialUrl) {
onAppOpenedFromURL({
url: initialUrl!,
isInitialUrl: true
});
}
}, [initialUrl]);
const subscribeToPurchaseListeners = useCallback(async () => {
if (Platform.OS === "android") {
try {
@@ -637,10 +725,10 @@ export const useAppEvents = () => {
EV.subscribe(EVENTS.syncCheckStatus, onCheckSyncStatus),
EV.subscribe(EVENTS.syncAborted, onSyncAborted),
EV.subscribe(EVENTS.appRefreshRequested, onSyncComplete),
EV.subscribe(EVENTS.userLoggedOut, onLogout),
EV.subscribe(EVENTS.userEmailConfirmed, onUserEmailVerified),
db.eventManager.subscribe(EVENTS.userLoggedOut, onLogout),
db.eventManager.subscribe(EVENTS.userEmailConfirmed, onUserEmailVerified),
EV.subscribe(EVENTS.userSessionExpired, onUserSessionExpired),
EV.subscribe(
db.eventManager.subscribe(
EVENTS.userSubscriptionUpdated,
onUserSubscriptionStatusChanged
),
@@ -724,6 +812,7 @@ export const useAppEvents = () => {
if (state === "active") {
notifee.setBadgeCount(0);
updateStatusBarColor();
expiringNotesTimer();
checkAutoBackup();
Sync.run("global", false, "full");
reconnectSSE();
@@ -774,23 +863,10 @@ export const useAppEvents = () => {
}
};
if (!refValues.current.initialUrl) {
Linking.getInitialURL().then((url) => {
if (url) {
refValues.current.initialUrl = url;
}
});
}
let sub: NativeEventSubscription;
if (!isAppLoading && !appLocked) {
setTimeout(() => {
sub = AppState.addEventListener("change", onAppStateChanged);
if (refValues.current.initialUrl) {
onAppOpenedFromURL({
url: refValues.current.initialUrl!
});
refValues.current.initialUrl = undefined;
}
}, 1000);
refValues.current.removeInternetStateListener = NetInfo.addEventListener(

View File

@@ -31,8 +31,6 @@ import { useSettingStore } from "../stores/use-setting-store";
import { rootNavigatorRef } from "../utils/global-refs";
import Navigation from "../services/navigation";
import { isFeatureAvailable } from "@notesnook/common";
import { useStoredValue } from "../hooks/use-stored-state";
import { db } from "../common/database";
const RootStack = createNativeStackNavigator();
const AppStack = createNativeStackNavigator();
@@ -58,105 +56,117 @@ const AppNavigation = React.memo(
() => {
const { colors } = useThemeColors();
const homepageV2 = useSettingStore((state) => state.settings.homepageV2);
const home = useStoredValue(
"app-home-navigtion-key",
!homepageV2 || homepageV2.id === DEFAULT_HOME.name
? DEFAULT_HOME
: undefined
);
const loading = useSettingStore((state) => state.isAppLoading);
const [home, setHome] = React.useState<
{ name: string; params: any } | undefined
>(undefined);
if (!home.value && !homepageV2) {
home.value = DEFAULT_HOME;
}
React.useEffect(() => {
if (!home) {
if (useSettingStore.getState().initialUrl) {
const url = useSettingStore.getState().initialUrl;
if (url?.startsWith("https://app.notesnook.com/open_notebook?")) {
const id = new URL(url).searchParams.get("id");
if (id) {
setHome({
name: "Notebook",
params: {
id: id
}
});
return;
}
} else if (url?.startsWith("https://app.notesnook.com/open_tag?")) {
const id = new URL(url).searchParams.get("id");
if (id) {
setHome({
name: "TaggedNotes",
params: {
type: "tag",
id: id
}
});
return;
}
} else if (url?.startsWith("https://app.notesnook.com/open_color?")) {
const id = new URL(url).searchParams.get("id");
if (id) {
setHome({
name: "ColoredNotes",
params: {
type: "color",
id: id
}
});
return;
}
}
}
if (homepageV2) {
switch (homepageV2.type) {
case "notebook": {
setHome({
name: "Notebook",
params: {
id: homepageV2.id
}
});
return;
}
case "color": {
setHome({
name: "ColoredNotes",
params: {
type: "color",
id: homepageV2.id
}
});
return;
}
case "tag": {
setHome({
name: "TaggedNotes",
params: {
type: "tag",
id: homepageV2.id
}
});
return;
}
case "default":
setHome({
name: homepageV2.id,
params: undefined
});
return;
}
} else {
setHome(DEFAULT_HOME);
}
}
}, []);
React.useEffect(() => {
if (!homepageV2 || loading) return;
(async () => {
isFeatureAvailable("customHomepage").then((value) => {
if (!value.isAllowed) {
home.value = DEFAULT_HOME;
SettingsService.setProperty("homepageV2", undefined);
}
});
if (!home.value) {
switch (homepageV2.type) {
case "notebook":
{
const notebook = await db.notebooks.notebook(homepageV2.id);
if (notebook) {
home.value = {
name: "Notebook",
params: {
item: notebook,
id: notebook.id,
title: notebook.title
}
};
return;
}
}
break;
case "color": {
const color = await db.colors.color(homepageV2.id);
if (color) {
home.value = {
name: "ColoredNotes",
params: {
item: color,
id: color.id,
title: color.title
}
};
return;
}
break;
}
case "tag": {
const tag = await db.tags.tag(homepageV2.id);
if (tag) {
home.value = {
name: "TaggedNotes",
params: {
item: tag,
id: tag.id,
title: tag.title
}
};
return;
}
break;
}
case "default":
{
home.value = DEFAULT_HOME;
}
return;
}
home.value = undefined;
setTimeout(() => {
home.value = DEFAULT_HOME;
});
isFeatureAvailable("customHomepage").then((value) => {
if (!value.isAllowed) {
SettingsService.setProperty("homepageV2", undefined);
}
})();
}, [homepageV2, loading, home.value]);
});
}, [homepageV2, loading]);
React.useEffect(() => {
if (!home) return;
useNavigationStore.getState().update(home?.name as keyof RouteParams);
useNavigationStore
.getState()
.update(home.value?.name as keyof RouteParams);
useNavigationStore
.getState()
.setFocusedRouteId(home.value?.params?.id || home.value?.name);
.setFocusedRouteId(home?.params?.id || home?.name);
}, [home]);
return !home.value ? null : (
return !home ? null : (
<AppStack.Navigator
initialRouteName={home.value?.name}
initialRouteName={home?.name}
screenOptions={{
headerShown: false,
animation: "none",
@@ -197,7 +207,7 @@ const AppNavigation = React.memo(
return TaggedNotes;
}}
initialParams={
home.value?.name === "TaggedNotes" ? home.value?.params : undefined
home?.name === "TaggedNotes" ? home?.params : undefined
}
/>
@@ -209,7 +219,7 @@ const AppNavigation = React.memo(
return ColoredNotes;
}}
initialParams={
home.value?.name === "ColoredNotes" ? home.value?.params : undefined
home?.name === "ColoredNotes" ? home?.params : undefined
}
/>
@@ -244,9 +254,7 @@ const AppNavigation = React.memo(
Notebook = Notebook || require("../screens/notebook").default;
return Notebook;
}}
initialParams={
home.value?.name === "Notebook" ? home.value?.params : undefined
}
initialParams={home?.name === "Notebook" ? home?.params : undefined}
/>
<AppStack.Screen

View File

@@ -42,9 +42,9 @@ import { DDS } from "../../services/device-detection";
import { ToastManager } from "../../services/event-manager";
import Navigation, { NavigationProps } from "../../services/navigation";
import Notifications from "../../services/notifications";
import PremiumService from "../../services/premium";
import SettingsService from "../../services/settings";
import { useRelationStore } from "../../stores/use-relation-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { AppFontSize, defaultBorderRadius } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { getFormattedDate, useIsFeatureAvailable } from "@notesnook/common";
@@ -70,7 +70,8 @@ const RecurringModes = {
Year: "year"
};
const WeekDays = new Array(7).fill(true);
const WeekDays = [0, 1, 2, 3, 4, 5, 6];
const WeekDaysMon = [1, 2, 3, 4, 5, 6, 0];
const MonthDays = new Array(31).fill(true);
const WeekDayNames = {
0: "Sunday",
@@ -92,6 +93,7 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
const { reminder, reference } = props.route.params;
useNavigationFocus(props.navigation, { focusOnInit: true });
const { colors, isDark } = useThemeColors();
const weekFormat = useSettingStore((state) => state.weekFormat);
const [reminderMode, setReminderMode] = useState<Reminder["mode"]>(
reminder?.mode || "once"
);
@@ -151,7 +153,7 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
const isSecondLast = index === selectedDays.length - 2;
const joinWith = isSecondLast ? " & " : isLast ? "" : ", ";
return recurringMode === RecurringModes.Week
? WeekDayNames[day as keyof typeof WeekDayNames] + joinWith
? strings.weekDayNames[day as keyof typeof WeekDayNames]() + joinWith
: `${day}${nth(day)} ${joinWith}`;
})
.join("");
@@ -390,37 +392,39 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
recurringMode === RecurringModes.Year
? null
: recurringMode === RecurringModes.Week
? WeekDays.map((item, index) => (
<Button
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"
}
fontSize={AppFontSize.xs}
style={{
height: 40,
borderRadius: 100,
marginRight: 10
}}
onPress={() => {
setSelectedDays((days) => {
if (days.indexOf(index) > -1) {
days.splice(days.indexOf(index), 1);
? (weekFormat === "Mon" ? WeekDaysMon : WeekDays).map(
(item) => (
<Button
key={strings.weekDayNamesShort[
item as keyof typeof strings.weekDayNamesShort
]()}
title={strings.weekDayNamesShort[
item as keyof typeof strings.weekDayNamesShort
]()}
type={
selectedDays.indexOf(item) > -1
? "selected"
: "plain"
}
fontSize={AppFontSize.xs}
style={{
height: 40,
borderRadius: 100,
marginRight: 10
}}
onPress={() => {
setSelectedDays((days) => {
if (days.indexOf(item) > -1) {
days.splice(days.indexOf(item), 1);
return [...days];
}
days.push(item);
return [...days];
}
days.push(index);
return [...days];
});
}}
/>
))
});
}}
/>
)
)
: MonthDays.map((item, index) => (
<Button
key={index + "monthday"}
@@ -468,6 +472,7 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
onConfirm={handleConfirm}
onCancel={hideDatePicker}
isDarkModeEnabled={isDark}
firstDayOfWeek={weekFormat === "Mon" ? 1 : 0}
is24Hour={db.settings.getTimeFormat() === "24-hour"}
date={date || new Date(Date.now())}
/>

View File

@@ -20,6 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import type { ToolbarGroupDefinition } from "@notesnook/editor";
import { useEditor } from "./use-editor";
import { FeatureId, FeatureResult } from "@notesnook/common";
import { DayFormat } from "@notesnook/core";
export type useEditorType = ReturnType<typeof useEditor>;
export type EditorState = {
@@ -60,6 +61,8 @@ export type Settings = {
markdownShortcuts: boolean;
features: Record<any, any>;
loggedIn: boolean;
defaultLineHeight: number;
dayFormat: DayFormat;
};
export type EditorProps = {

View File

@@ -28,11 +28,13 @@ import { getDefaultPresets } from "@notesnook/editor/dist/cjs/toolbar/tool-defin
import { strings } from "@notesnook/intl";
import Clipboard from "@react-native-clipboard/clipboard";
import React, { useCallback, useEffect, useRef } from "react";
import * as ScopedStorage from "react-native-scoped-storage";
import {
BackHandler,
Keyboard,
KeyboardEventListener,
NativeEventSubscription,
Platform,
useWindowDimensions
} from "react-native";
import { WebViewMessageEvent } from "react-native-webview";
@@ -82,6 +84,9 @@ import { useDragState } from "../../settings/editor/state";
import { EditorMessage, EditorProps, useEditorType } from "./types";
import { useTabStore } from "./use-tab-store";
import { editorState, openInternalLink } from "./utils";
import filesystem from "../../../common/filesystem";
import ReactNativeBlobUtil from "react-native-blob-util";
import { ShareComponent } from "../../../components/sheets/export-notes/share";
const publishNote = async () => {
const user = useUserStore.getState().user;
@@ -153,16 +158,22 @@ export const useEditorEvents = (
const features = useAreFeaturesAvailable([
"callout",
"outlineList",
"taskList"
"taskList",
"importCsvToTable",
"exportTableAsCsv"
]);
const deviceMode = useSettingStore((state) => state.deviceMode);
const fullscreen = useSettingStore((state) => state.fullscreen);
const corsProxy = useSettingStore((state) => state.settings.corsProxy);
const loading = useSettingStore((state) => state.isAppLoading);
const [dateFormat, timeFormat] = useSettingStore((state) => [
state.dateFormat,
state.timeFormat
]);
const [dateFormat, timeFormat, defaultLineHeight, dayFormat] =
useSettingStore((state) => [
state.dateFormat,
state.timeFormat,
state.settings.defaultLineHeight,
state.dayFormat
]);
const handleBack = useRef<NativeEventSubscription>(undefined);
const loggedIn = useUserStore((state) => !!state.user);
const { fontScale } = useWindowDimensions();
@@ -222,10 +233,12 @@ export const useEditorEvents = (
fontFamily: SettingsService.get().defaultFontFamily,
dateFormat: db.settings?.getDateFormat(),
timeFormat: db.settings?.getTimeFormat(),
dayFormat: db.settings?.getDayFormat(),
fontScale,
markdownShortcuts,
features,
loggedIn
loggedIn,
defaultLineHeight
});
}, [
fullscreen,
@@ -241,10 +254,13 @@ export const useEditorEvents = (
defaultFontFamily,
dateFormat,
timeFormat,
dayFormat,
loading,
fontScale,
markdownShortcuts,
loggedIn
loggedIn,
defaultLineHeight,
features
]);
const onBackPress = useCallback(async () => {
@@ -733,6 +749,62 @@ export const useEditorEvents = (
}
break;
}
case EditorEvents.downloadCsv: {
try {
const csv = editorMessage.value;
let filePath: string;
let fileName: string;
if (Platform.OS === "android") {
let file = await ScopedStorage.createDocument(
"table.csv",
"text/csv",
csv,
"utf8"
);
if (!file) return;
filePath = file.path || file.uri;
fileName = file.name;
} else {
const path = await filesystem.checkAndCreateDir("/");
let possibleFileName = "table.csv";
let counter = 1;
// Check if file exists and find available filename
while (
await ReactNativeBlobUtil.fs.exists(path + possibleFileName)
) {
possibleFileName = `table (${counter}).csv`;
counter++;
}
await ReactNativeBlobUtil.fs.writeFile(
path + possibleFileName,
csv,
"utf8"
);
filePath = path + possibleFileName;
fileName = possibleFileName;
}
await sleep(500);
presentSheet({
title: "Table saved to csv",
paragraph: strings.fileSaved(fileName, Platform.OS),
icon: "download",
context: "global",
component: (
<ShareComponent uri={filePath} name={fileName} padding={12} />
)
});
} catch (e) {
ToastManager.show({
type: "info",
message: "Could not save table to csv"
});
DatabaseLogger.error(e as Error);
}
break;
}
default:
break;

View File

@@ -298,7 +298,7 @@ export const useEditor = (
return;
}
if (isContentInvalid(data) && id) {
if (!title && isContentInvalid(data) && id) {
// Create a new history session if recieved empty or invalid content
// To ensure that history is preserved for correct content.
currentSessionHistoryId = editorSessionHistory.newSession(id);
@@ -1097,7 +1097,10 @@ export const useEditor = (
if (!state.current?.initialLoadCalled) {
const url = await Linking.getInitialURL();
let noteId = url && new URL(url).searchParams.get("id");
let noteId =
url &&
url.startsWith("https://app.notesnook.com/open_note?") &&
new URL(url).searchParams.get("id");
if (noteId) {
const note = await db.notes?.note(noteId);
fluidTabsRef.current?.goToPage("editor");

View File

@@ -38,7 +38,6 @@ import { eUpdateNotebookRoute } from "../../utils/events";
import { findRootNotebookId } from "../../utils/notebooks";
import { openEditor, setOnFirstSave } from "../notes/common";
import { View } from "react-native";
import { DefaultAppStyles } from "../../utils/styles";
import { Notebooks } from "../../components/sheets/notebooks";
import { useSettingStore } from "../../stores/use-setting-store";
import { rootNavigatorRef } from "../../utils/global-refs";
@@ -47,6 +46,9 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
const params = useRef<NotebookScreenParams>(route?.params);
const isAppLoading = useSettingStore((state) => state.isAppLoading);
const [notebook, setNotebook] = useState<Notebook | undefined>(
params.current.item
);
const [loading, setLoading] = useState(true);
const updateOnFocus = useRef(false);
const [breadcrumbs, setBreadcrumbs] = useState<
@@ -75,10 +77,10 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
});
const syncWithNavigation = React.useCallback(() => {
useNavigationStore.getState().setFocusedRouteId(params?.current?.item?.id);
useNavigationStore.getState().setFocusedRouteId(params?.current?.id);
setOnFirstSave({
type: "notebook",
id: params.current.item.id
id: params.current.id
});
}, []);
@@ -86,23 +88,20 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
async (data?: NotebookScreenParams) => {
if (useSettingStore.getState().isAppLoading) return;
if (
useNavigationStore.getState().focusedRouteId !==
params.current.item.id &&
useNavigationStore.getState().focusedRouteId !== params.current.id &&
!data
) {
updateOnFocus.current = true;
return;
}
if (data?.item?.id && params.current.item?.id !== data?.item?.id) {
const nextRootNotebookId = await findRootNotebookId(data?.item?.id);
const currentNotebookRoot = await findRootNotebookId(
params.current.item.id
);
if (data?.id && params.current?.id !== data?.id) {
const nextRootNotebookId = await findRootNotebookId(data?.id);
const currentNotebookRoot = await findRootNotebookId(params.current.id);
if (
nextRootNotebookId !== currentNotebookRoot ||
nextRootNotebookId === params.current?.item?.id
nextRootNotebookId === params.current?.id
) {
// Never update notebook in route if root is different or if the root is current notebook.
return;
@@ -112,14 +111,13 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
if (data) params.current = data;
try {
const notebook = await db.notebooks?.notebook(
params?.current?.item?.id
);
const notebook = await db.notebooks?.notebook(params?.current?.id);
setNotebook(notebook);
params.current.item = notebook;
if (notebook) {
const breadcrumbs = await db.notebooks.breadcrumbs(notebook.id);
setBreadcrumbs(breadcrumbs.slice(0, breadcrumbs.length - 1));
params.current.item = notebook;
params.current.id = notebook.id;
const notes = await db.relations
.from(notebook, "note")
.selector.grouped(db.settings.getGroupOptions("notes"));
@@ -160,29 +158,27 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
<>
<Header
renderedInRoute={route.name}
title={params.current.item?.title}
title={notebook?.title}
canGoBack={params?.current?.canGoBack}
rightButton={{
name: "dots-vertical",
onPress: () => {
Properties.present(params.current.item);
Properties.present(notebook);
}
}}
hasSearch={true}
onSearch={() => {
const selector = db.relations.from(
params.current.item,
"note"
).selector;
if (!notebook) return;
const selector = db.relations.from(notebook, "note").selector;
Navigation.push("Search", {
placeholder: strings.searchInRoute(params.current.item?.title),
placeholder: strings.searchInRoute(notebook?.title),
type: "note",
title: params.current.item?.title,
title: notebook?.title,
route: route.name,
items: selector
});
}}
id={params.current.item?.id}
id={notebook?.id}
/>
<DelayLayout wait={loading}>
@@ -192,19 +188,19 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
onRefresh={() => {
onRequestUpdate();
}}
id={params.current.item?.id}
id={params.current?.id}
renderedInRoute="Notebook"
headerTitle={params.current.item.title}
headerTitle={notebook?.title}
loading={loading}
CustomLisHeader={
<NotebookHeader
breadcrumbs={breadcrumbs}
notebook={params.current.item}
notebook={notebook!}
totalNotes={notes?.placeholders.length || 0}
/>
}
placeholder={{
title: params.current.item?.title,
title: notebook?.title!,
paragraph: strings.notesEmpty(),
button: strings.addFirstNote(),
action: openEditor,
@@ -225,7 +221,8 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
testID="notebookTreeSheet"
size="small"
onPress={() => {
Notebooks.present(params.current.item);
if (!notebook) return;
Notebooks.present(notebook);
}}
style={{
position: "relative",
@@ -246,7 +243,7 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
/>
</View>
<SelectionHeader
id={route.params?.item?.id}
id={route.params?.id}
items={notes}
type="note"
renderedInRoute="Notebook"
@@ -260,6 +257,7 @@ NotebookScreen.navigate = async (item: Notebook, canGoBack?: boolean) => {
const { currentRoute, focusedRouteId } = useNavigationStore.getState();
if (currentRoute === "Notebooks") {
Navigation.push("Notebook", {
id: item.id,
item: item,
canGoBack
});
@@ -276,22 +274,24 @@ NotebookScreen.navigate = async (item: Notebook, canGoBack?: boolean) => {
// Update the route in place instead
eSendEvent(eUpdateNotebookRoute, {
item: item,
title: item.title,
canGoBack: canGoBack
id: item.id,
canGoBack: canGoBack,
item: item
});
} else {
// Push a new route
Navigation.push("Notebook", {
item: item,
canGoBack
id: item.id,
canGoBack,
item: item
});
}
} else {
// Push a new route anyways
Navigation.push("Notebook", {
item: item,
canGoBack
id: item.id,
canGoBack,
item: item
});
}
};

View File

@@ -25,7 +25,7 @@ import Navigation, { NavigationProps } from "../../services/navigation";
import useNavigationStore, {
NotesScreenParams
} from "../../stores/use-navigation-store";
import { PLACEHOLDER_DATA, openEditor, toCamelCase } from "./common";
import { PLACEHOLDER_DATA, openEditor } from "./common";
export const ColoredNotes = ({
navigation,
route
@@ -45,11 +45,13 @@ export const ColoredNotes = ({
ColoredNotes.get = async (params: NotesScreenParams, grouped = true) => {
if (!grouped) {
return await db.relations.from(params.item, "note").resolve();
return await db.relations
.from({ id: params.id, type: "color" }, "note")
.resolve();
}
return await db.relations
.from(params.item, "note")
.from({ id: params.id, type: "color" }, "note")
.selector.grouped(db.settings.getGroupOptions("notes"));
};
@@ -63,8 +65,10 @@ ColoredNotes.navigate = (item: Color, canGoBack: boolean) => {
}
Navigation.push<"ColoredNotes">("ColoredNotes", {
item: item,
canGoBack
type: "color",
id: item.id,
canGoBack,
item: item
});
};

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { resolveItems } from "@notesnook/common";
import { VirtualizedGrouping } from "@notesnook/core";
import { Tag, VirtualizedGrouping } from "@notesnook/core";
import { Color, Note } from "@notesnook/core";
import React, { useEffect, useRef, useState } from "react";
import { db } from "../../common/database";
@@ -76,14 +76,12 @@ const NotesPage = ({
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
const [loadingNotes, setLoadingNotes] = useState(true);
const isMonograph = route.name === "Monographs";
const title =
params.current?.item.type === "tag"
? "#" + params.current?.item.title
: params.current?.item.title;
const [item, setItem] = useState<Tag | Color | undefined>(
params.current.item
);
const title = item?.type === "tag" ? "#" + item?.title : item?.title;
const accentColor =
route.name === "ColoredNotes"
? (params.current?.item as Color)?.colorCode
: undefined;
route.name === "ColoredNotes" ? (item as Color)?.colorCode : undefined;
const updateOnFocus = useRef(false);
const isAppLoading = useSettingStore((state) => state.isAppLoading);
const isFocused = useNavigationFocus(navigation, {
@@ -107,31 +105,30 @@ const NotesPage = ({
});
const syncWithNavigation = React.useCallback(() => {
const { item } = params.current;
useNavigationStore
.getState()
.setFocusedRouteId(params?.current?.item?.id || route.name);
const { id } = params.current;
useNavigationStore.getState().setFocusedRouteId(id || route.name);
!isMonograph &&
setOnFirstSave({
type: getItemType(route.name),
id: item.id
id: id
});
}, [isMonograph, route.name]);
const onRequestUpdate = React.useCallback(
async (data?: NotesScreenParams) => {
if (useSettingStore.getState().isAppLoading) return;
if (
params.current.item.id &&
useNavigationStore.getState().focusedRouteId !==
params.current.item.id &&
params.current.id &&
useNavigationStore.getState().focusedRouteId !== params.current.id &&
!data
) {
updateOnFocus.current = false;
return;
}
const isNew = data && data?.item?.id !== params.current?.item?.id;
const isNew = data && data?.id !== params.current?.id;
if (data) params.current = data;
try {
@@ -142,9 +139,9 @@ const NotesPage = ({
)) as VirtualizedGrouping<Note>;
if (route.name === "TaggedNotes" || route.name === "ColoredNotes") {
const item = await (db as any)[params.current.item.type + "s"][
params.current.item.type
](params.current.item.id);
const item = await (db as any)[params.current.type + "s"][
params.current.type
](params.current.id);
if (!item) {
if (rootNavigatorRef.canGoBack()) {
@@ -154,7 +151,7 @@ const NotesPage = ({
}
return;
}
setItem(item);
params.current.item = item;
}
@@ -173,15 +170,7 @@ const NotesPage = ({
useEffect(() => {
if (isAppLoading) return;
if (loadingNotes) {
get(params.current, true)
.then(async (items) => {
setNotes(items as VirtualizedGrouping<Note>);
await (items as VirtualizedGrouping<Note>).item(0, resolveItems);
setLoadingNotes(false);
})
.catch((e) => {
setLoadingNotes(false);
});
onRequestUpdate(params.current);
}
}, [loadingNotes, get, isAppLoading]);
@@ -202,19 +191,18 @@ const NotesPage = ({
}
canGoBack={params?.current?.canGoBack}
hasSearch={true}
id={
route.name === "Monographs" ? "Monographs" : params?.current.item?.id
}
id={route.name === "Monographs" ? "Monographs" : params?.current?.id}
onSearch={() => {
if (!item) return;
const selector =
route.name === "Monographs"
? db.monographs.all
: db.relations.from(params.current.item, "note").selector;
: db.relations.from(item, "note").selector;
Navigation.push("Search", {
placeholder: strings.searchInRoute(title || route.name),
type: "note",
title: title,
title: title!,
route: route.name,
items: selector
});
@@ -229,7 +217,7 @@ const NotesPage = ({
onRefresh={onRequestUpdate}
loading={false}
renderedInRoute={route.name}
id={params.current.item?.id}
id={params.current?.id}
headerTitle={title || "Monographs"}
customAccentColor={accentColor}
placeholder={placeholder}
@@ -246,7 +234,7 @@ const NotesPage = ({
) : null}
</DelayLayout>
<SelectionHeader
id={route.params?.item?.id || route.name}
id={route.params?.id || route.name}
items={notes}
type="note"
renderedInRoute={route.name}

View File

@@ -62,7 +62,8 @@ Monographs.get = async (params?: NotesScreenParams, grouped = true) => {
Monographs.navigate = (canGoBack?: boolean) => {
Navigation.navigate<"Monographs">("Monographs", {
item: { type: "monograph" } as any,
type: "monograph",
id: "monograph",
canGoBack: canGoBack as boolean
});
};

View File

@@ -46,11 +46,13 @@ export const TaggedNotes = ({
TaggedNotes.get = async (params: NotesScreenParams, grouped = true) => {
if (!grouped) {
return await db.relations.from(params.item, "note").resolve();
return await db.relations
.from({ id: params.id, type: "tag" }, "note")
.resolve();
}
return await db.relations
.from(params.item, "note")
.from({ id: params.id, type: "tag" }, "note")
.selector.grouped(db.settings.getGroupOptions("notes"));
};
@@ -64,8 +66,10 @@ TaggedNotes.navigate = (item: Tag, canGoBack?: boolean) => {
}
Navigation.push<"TaggedNotes">("TaggedNotes", {
item: item,
canGoBack
id: item.id,
type: "tag",
canGoBack,
item: item
});
};

View File

@@ -32,6 +32,8 @@ import {
BackupReminderPicker,
BackupWithAttachmentsReminderPicker,
DateFormatPicker,
DayFormatPicker,
WeekFormatPicker,
FontPicker,
HomePicker,
SidebarTabPicker,
@@ -57,6 +59,8 @@ export const components: { [name: string]: ReactElement } = {
"title-format": <TitleFormat />,
"date-format-selector": <DateFormatPicker />,
"time-format-selector": <TimeFormatPicker />,
"day-format-selector": <DayFormatPicker />,
"week-format-selector": <WeekFormatPicker />,
"theme-selector": <ThemeSelector />,
"applock-timer": <ApplockTimerPicker />,
autobackupsattachments: <BackupWithAttachmentsReminderPicker />,

View File

@@ -32,6 +32,18 @@ import { strings } from "@notesnook/intl";
import { isFeatureAvailable } from "@notesnook/common";
import PaywallSheet from "../../../components/sheets/paywall";
const DAY_FORMATS = ["short", "long"];
const DayFormatFormats = {
short: "ddd",
long: "dddd"
};
const WEEK_FORMATS = ["Sun", "Mon"];
const WeekFormatNames = {
Sun: "Sunday",
Mon: "Monday"
};
export const FontPicker = createSettingsPicker({
getValue: () => useSettingStore.getState().settings.defaultFontFamily,
updateValue: (item) => {
@@ -111,8 +123,8 @@ export const TrashIntervalPicker = createSettingsPicker({
return item === -1
? strings.never()
: item === 1
? strings.reminderRecurringMode.day()
: strings.days(item);
? strings.reminderRecurringMode.day()
: strings.days(item);
},
getItemKey: (item) => item.toString(),
options: [-1, 1, 7, 30, 365],
@@ -152,6 +164,42 @@ export const DateFormatPicker = createSettingsPicker({
isOptionAvailable: () => true
});
export const DayFormatPicker = createSettingsPicker({
getValue: () => db.settings.getDayFormat(),
updateValue: (item) => {
db.settings.setDayFormat(item);
useSettingStore.setState({
dayFormat: item
});
},
formatValue: (item) => {
return `${strings.dayFormat()} (${dayjs().format(DayFormatFormats[item])})`;
},
getItemKey: (item) => item,
options: DAY_FORMATS,
compareValue: (current, item) => current === item,
isFeatureAvailable: () => true,
isOptionAvailable: () => true
});
export const WeekFormatPicker = createSettingsPicker({
getValue: () => db.settings.getWeekFormat(),
updateValue: (item) => {
db.settings.setWeekFormat(item);
useSettingStore.setState({
weekFormat: item
});
},
formatValue: (item) => {
return `${WeekFormatNames[item]}`;
},
getItemKey: (item) => item,
options: WEEK_FORMATS,
compareValue: (current, item) => current === item,
isFeatureAvailable: () => true,
isOptionAvailable: () => true
});
const TimeFormats = {
"12-hour": "hh:mm A",
"24-hour": "HH:mm"
@@ -228,10 +276,10 @@ export const ApplockTimerPicker = createSettingsPicker({
return item === -1
? strings.never()
: item === 0 || item === undefined
? strings.immediately()
: item === 1
? strings.minutes(1)
: strings.minutes(item);
? strings.immediately()
: item === 1
? strings.minutes(1)
: strings.minutes(item);
},
getItemKey: (item) => item.toString(),
options: [-1, 0, 1, 5, 15, 30],

View File

@@ -95,6 +95,15 @@ const restoreBackup = async (options: {
deleteFile?: boolean;
}) => {
try {
if (
!options.uri.endsWith(".nnbackup") &&
!options.uri.endsWith(".nnbackupz")
) {
throw new Error(
`Invalid backup file selected. Only .nnbackup and .nnbackupz files can be restored.`
);
}
const isLegacyBackup = options.uri.endsWith(".nnbackup");
startProgress({

View File

@@ -71,6 +71,7 @@ import { verifyUser, verifyUserWithApplock } from "./functions";
import { logoutUser } from "./logout";
import { SettingSection } from "./types";
import { getTimeLeft } from "./user-section";
import { EDITOR_LINE_HEIGHT } from "../../utils/constants";
export const settingsGroups: SettingSection[] = [
{
@@ -143,21 +144,20 @@ export const settingsGroups: SettingSection[] = [
"dddd, MMMM D, YYYY h:mm A"
);
const trialEndDate = dayjs(user?.subscription?.start)
.add(
user?.subscription?.productId?.includes("monthly") ? 7 : 14,
"day"
)
.format("dddd, MMMM D, YYYY h:mm A");
if (
user.subscription?.plan !== SubscriptionPlan.FREE &&
user.subscription?.productId
) {
const status = user.subscription?.status;
return status === SubscriptionStatus.TRIAL
? strings.trialEndsOn(
dayjs(user?.subscription?.start)
.add(
user?.subscription?.productId?.includes("monthly")
? 7
: 14
)
.format("dddd, MMMM D, YYYY h:mm A")
)
? strings.trialOnGoing(trialEndDate)
: status === SubscriptionStatus.ACTIVE
? strings.subRenewOn(expiryDate)
: status === SubscriptionStatus.CANCELED ||
@@ -286,10 +286,17 @@ export const settingsGroups: SettingSection[] = [
{
id: "change-password",
name: strings.changePassword(),
type: "screen",
// type: "screen",
description: strings.changePasswordDesc(),
component: "change-password",
icon: "form-textbox-password"
// component: "change-password",
icon: "form-textbox-password",
modifer: () => {
ToastManager.show({
type: "info",
message:
"Password changing has been disabled temporarily to address some issues faced by users. It will be enabled again once the issues have resolved."
});
}
},
{
id: "change-email",
@@ -716,6 +723,22 @@ export const settingsGroups: SettingSection[] = [
component: "date-format-selector",
icon: "calendar-blank"
},
{
id: "day-format",
name: strings.dayFormat(),
description: strings.dayFormatDesc(),
type: "component",
component: "day-format-selector",
icon: "calendar-today"
},
{
id: "week-format",
name: strings.weekFormat(),
description: strings.weekFormatDesc(),
type: "component",
component: "week-format-selector",
icon: "calendar"
},
{
id: "time-format",
name: strings.timeFormat(),
@@ -815,6 +838,16 @@ export const settingsGroups: SettingSection[] = [
property: "defaultFontFamily",
component: "font-selector"
},
{
id: "default-line-height",
name: strings.lineHeight(),
description: strings.lineHeightDesc(),
type: "input-selector",
property: "defaultLineHeight",
icon: "format-line-spacing",
minInputValue: EDITOR_LINE_HEIGHT.MIN,
maxInputValue: EDITOR_LINE_HEIGHT.MAX
},
{
id: "title-format",
name: strings.titleFormat(),
@@ -896,7 +929,6 @@ export const settingsGroups: SettingSection[] = [
hidden: (current) => (current as VaultStatusType)?.exists,
modifer: () => {
openVault({
item: {},
novault: false,
title: strings.createVault()
});
@@ -910,7 +942,6 @@ export const settingsGroups: SettingSection[] = [
hidden: (current) => !(current as VaultStatusType)?.exists,
modifer: () =>
openVault({
item: {},
changePassword: true,
novault: true,
title: strings.changeVaultPassword()
@@ -924,7 +955,6 @@ export const settingsGroups: SettingSection[] = [
hidden: (current) => !(current as VaultStatusType)?.exists,
modifer: () => {
openVault({
item: {},
clearVault: true,
novault: true,
title: strings.clearVault() + "?"
@@ -939,7 +969,6 @@ export const settingsGroups: SettingSection[] = [
hidden: (current) => !(current as VaultStatusType)?.exists,
modifer: () => {
openVault({
item: {},
deleteVault: true,
novault: true,
title: strings.deleteVault() + "?"
@@ -961,7 +990,6 @@ export const settingsGroups: SettingSection[] = [
modifer: (current) => {
const _current = current as VaultStatusType;
openVault({
item: {},
fingerprintAccess: !_current.biometryEnrolled,
revokeFingerprintAccess: _current.biometryEnrolled,
novault: true,

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 { EventHandler, EventManager } from "@notesnook/core";
import { EventHandler, EventManager, Note, NoteContent } from "@notesnook/core";
import Clipboard from "@react-native-clipboard/clipboard";
import { RefObject } from "react";
import { ActionSheetRef } from "react-native-actions-sheet";
@@ -32,8 +32,8 @@ import {
} from "../utils/events";
import { strings } from "@notesnook/intl";
type Vault = {
item: unknown;
export type Vault = {
item: Note;
novault: boolean;
title: string;
description: string;
@@ -48,6 +48,14 @@ type Vault = {
clearVault: boolean;
deleteVault: boolean;
copyNote: boolean;
customActionTitle: string;
customActionParagraph: string;
onUnlock: (
item: Note & {
content?: NoteContent<false>;
},
password: string
) => void;
};
type NoteEdit = {

View File

@@ -463,9 +463,11 @@ async function loadNote(id: string, jump: boolean) {
const tab = useTabStore.getState().getTabForNote(id);
if (useTabStore.getState().currentTab !== tab) {
eSendEvent(eOnLoadNote, {
item: note
});
setTimeout(() => {
eSendEvent(eOnLoadNote, {
item: note
});
}, 300);
}
}

View File

@@ -23,6 +23,7 @@ import ShareExtension, {
import { getPreviewData } from "@flyerhq/react-native-link-preview";
import {
formatBytes,
getFormattedDate,
isFeatureAvailable,
useIsFeatureAvailable
} from "@notesnook/common";
@@ -47,7 +48,6 @@ import {
import RNFetchBlob from "react-native-blob-util";
import {
SafeAreaProvider,
SafeAreaView,
useSafeAreaInsets
} from "react-native-safe-area-context";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
@@ -70,14 +70,20 @@ import { Search } from "./search";
import { initDatabase, useShareStore } from "./store";
import { isTablet } from "react-native-device-info";
import { NotesnookModule } from "../utils/notesnook-module";
import { DefaultAppStyles } from "../utils/styles";
const getLinkPreview = (url: string) => {
return getPreviewData(url, 5000);
};
async function sanitizeHtml(site: string) {
async function sanitizeHtml(site: string, title?: string) {
try {
let html = await fetchHandle.current?.processUrl(site);
return html;
return (
html +
"<hr/>" +
`<p>Clipped from <a href="${site}">${title || site}</a></p>
<p>Date clipped ${getFormattedDate(Date.now())}</p>`
);
} catch (e) {
return "";
}
@@ -145,6 +151,7 @@ const ShareView = () => {
const noteContent = useRef<string>(undefined);
const noteTitle = useRef<string>(undefined);
const [loading, setLoading] = useState(false);
const [loadingData, setLoadingData] = useState(true);
const [loadingExtension, setLoadingExtension] = useState(true);
const fullQualityImages = useIsFeatureAvailable("fullQualityImages");
const [rawData, setRawData] = useState<{
@@ -167,6 +174,11 @@ const ShareView = () => {
globalThis["IS_SHARE_EXTENSION"] = true;
const onKeyboardDidShow = (event: KeyboardEvent) => {
let height = Dimensions.get("screen").height - event.endCoordinates.screenY;
if (height < 100) {
setKeyboardHeight(0);
keyboardHeightRef.current = 0;
return;
}
keyboardHeightRef.current = height;
setKeyboardHeight(height);
};
@@ -210,7 +222,6 @@ const ShareView = () => {
};
const onLoad = useCallback(() => {
console.log(noteContent.current, "current...");
eSendEvent(eOnLoadNote + "shareEditor", {
id: null,
content: {
@@ -226,6 +237,7 @@ const ShareView = () => {
try {
if (noteContent.current) {
onLoad();
setLoadingData(false);
return;
}
defaultNote.content.data = undefined;
@@ -242,6 +254,7 @@ const ShareView = () => {
editorRef.current?.focus();
}, 300);
}
setLoadingData(false);
return;
}
@@ -298,7 +311,9 @@ const ShareView = () => {
});
onLoad();
setNote({ ...note });
setLoadingData(false);
} catch (e) {
setLoadingData(false);
console.error(e);
}
},
@@ -311,6 +326,7 @@ const ShareView = () => {
await initDatabase();
setLoadingExtension(false);
useShareStore.getState().restore();
loadData(false);
} catch (e) {
DatabaseLogger.error(e);
}
@@ -389,7 +405,7 @@ const ShareView = () => {
if (value === 2) {
setLoadingPage(true);
setTimeout(async () => {
let html = await sanitizeHtml(rawData?.value || "");
let html = await sanitizeHtml(rawData?.value || "", note.title);
noteContent.current = html || "";
setLoadingPage(false);
onLoad();
@@ -448,11 +464,7 @@ const ShareView = () => {
overflow: "hidden",
paddingTop: insets.top,
paddingLeft: insets.left,
paddingRight: insets.right,
paddingBottom:
Platform.OS === "android" && keyboardHeight
? keyboardHeight - (insets.bottom || (gesturesEnabled ? 25 : 50))
: 0
paddingRight: insets.right
}}
>
{loadingPage ? <HtmlLoadingWebViewAgent /> : null}
@@ -500,394 +512,414 @@ const ShareView = () => {
display: searchMode ? "none" : "flex",
borderTopRightRadius: Platform.OS === "ios" ? 10 : 15,
borderTopLeftRadius: Platform.OS === "ios" ? 10 : 15,
maxHeight: Platform.OS === "android" ? undefined : "100%"
maxHeight: Platform.OS === "android" ? undefined : "100%",
paddingBottom:
Platform.OS === "android" && keyboardHeight
? keyboardHeight
: insets.bottom
}}
>
<ScrollView>
{loadingData ? (
<View
style={{
maxHeight: "100%"
justifyContent: "center",
alignItems: "center",
height: 300,
gap: DefaultAppStyles.GAP_VERTICAL
}}
>
<ActivityIndicator color={colors.primary.accent} />
<Paragraph>Loading clip...</Paragraph>
</View>
) : (
<ScrollView>
<View
style={{
width: "100%"
maxHeight: "100%"
}}
>
<View
style={{
minHeight: 100,
backgroundColor: colors.primary.background,
overflow: "hidden"
width: "100%"
}}
>
<View
style={{
justifyContent: "space-between",
flexDirection: "row",
alignItems: "center",
borderBottomWidth: 1,
paddingBottom: 12,
borderBottomColor: colors.secondary.background,
paddingHorizontal: 12,
gap: 10
minHeight: 100,
backgroundColor: colors.primary.background,
overflow: "hidden"
}}
>
{appendNoteId ? (
<Heading
style={{
flexShrink: 1,
flexGrow: 1,
fontFamily: "Inter-SemiBold",
fontSize: AppFontSize.lg,
paddingBottom: 0,
paddingTop: 0
}}
allowFontScaling={false}
>
Save note
</Heading>
) : (
<TextInput
placeholder="Enter note title"
ref={inputRef}
allowFontScaling={false}
style={{
flexShrink: 1,
flexGrow: 1,
fontFamily: "Inter-SemiBold",
fontSize: AppFontSize.lg,
paddingBottom: 0,
paddingTop: 0,
color: colors.primary.heading
}}
onChangeText={(value) => {
noteTitle.current = value;
}}
defaultValue={noteTitle.current}
blurOnSubmit={false}
onSubmitEditing={() => {
editorRef.current?.focus();
}}
/>
)}
<Button
title="Done"
type="accent"
loading={loading}
onPress={onPress}
allowFontScaling={false}
/>
</View>
{rawFiles?.length > 0 ? (
<View
style={{
justifyContent: "space-between",
flexDirection: "row",
alignItems: "center",
borderBottomWidth: 1,
paddingBottom: 12,
borderBottomColor: colors.secondary.background,
paddingHorizontal: 12,
paddingVertical: 12,
backgroundColor: colors.secondary.background
gap: 10
}}
>
<Paragraph
style={{ marginBottom: 6 }}
color={colors.primary.paragraph}
{appendNoteId ? (
<Heading
style={{
flexShrink: 1,
flexGrow: 1,
fontFamily: "Inter-SemiBold",
fontSize: AppFontSize.lg,
paddingBottom: 0,
paddingTop: 0
}}
allowFontScaling={false}
>
Save note
</Heading>
) : (
<TextInput
placeholder="Enter note title"
ref={inputRef}
allowFontScaling={false}
style={{
flexShrink: 1,
flexGrow: 1,
fontFamily: "Inter-SemiBold",
fontSize: AppFontSize.lg,
paddingBottom: 0,
paddingTop: 0,
color: colors.primary.heading
}}
onChangeText={(value) => {
noteTitle.current = value;
}}
defaultValue={noteTitle.current}
blurOnSubmit={false}
onSubmitEditing={() => {
editorRef.current?.focus();
}}
/>
)}
<Button
title="Done"
type="accent"
loading={loading}
onPress={onPress}
allowFontScaling={false}
>
Attaching {rawFiles.length} file(s):
</Paragraph>
<ScrollView horizontal>
{rawFiles.map((item) =>
isImage(item.type) || item.value?.endsWith(".png") ? (
<TouchableOpacity
onPress={() => onRemoveFile(item)}
key={item.name}
activeOpacity={0.9}
>
<Image
source={{
uri:
Platform.OS === "android"
? `file://${item.value}`
: item.value
}}
style={{
width: 100,
height: 100,
borderRadius: defaultBorderRadius,
backgroundColor: "black",
marginRight: 6
}}
resizeMode="cover"
/>
</TouchableOpacity>
) : (
<TouchableOpacity
activeOpacity={0.9}
key={item.name}
onPress={() => onRemoveFile(item)}
style={{
borderRadius: defaultBorderRadius,
backgroundColor: colors.secondary.background,
flexDirection: "row",
borderWidth: 1,
borderColor: colors.primary.border,
alignItems: "center",
paddingVertical: 5,
paddingHorizontal: 8,
marginRight: 6
}}
>
<Icon
color={colors.primary.icon}
size={15}
name="file"
/>
/>
</View>
<Paragraph
size={AppFontSize.xs}
color={colors.primary.paragraph}
allowFontScaling={false}
style={{
marginLeft: 4,
paddingRight: 8
}}
>
{item.name || item.value.split("/").pop()}
{item.size ? `(${formatBytes(item.size)})` : ""}
</Paragraph>
</TouchableOpacity>
)
)}
</ScrollView>
<Paragraph
color={colors.secondary.paragraph}
size={AppFontSize.xs}
allowFontScaling={false}
{rawFiles?.length > 0 ? (
<View
style={{
marginTop: 6
paddingHorizontal: 12,
paddingVertical: 12,
backgroundColor: colors.secondary.background
}}
>
Tap to remove an attachment.
</Paragraph>
{rawFiles.some((item) => isImage(item.type)) ? (
<TouchableOpacity
activeOpacity={1}
<Paragraph
style={{ marginBottom: 6 }}
color={colors.primary.paragraph}
allowFontScaling={false}
>
Attaching {rawFiles.length} file(s):
</Paragraph>
<ScrollView horizontal>
{rawFiles.map((item) =>
isImage(item.type) || item.value?.endsWith(".png") ? (
<TouchableOpacity
onPress={() => onRemoveFile(item)}
key={item.name}
activeOpacity={0.9}
>
<Image
source={{
uri:
Platform.OS === "android"
? `file://${item.value}`
: item.value
}}
style={{
width: 100,
height: 100,
borderRadius: defaultBorderRadius,
backgroundColor: "black",
marginRight: 6
}}
resizeMode="cover"
/>
</TouchableOpacity>
) : (
<TouchableOpacity
activeOpacity={0.9}
key={item.name}
onPress={() => onRemoveFile(item)}
style={{
borderRadius: defaultBorderRadius,
backgroundColor: colors.secondary.background,
flexDirection: "row",
borderWidth: 1,
borderColor: colors.primary.border,
alignItems: "center",
paddingVertical: 5,
paddingHorizontal: 8,
marginRight: 6
}}
>
<Icon
color={colors.primary.icon}
size={15}
name="file"
/>
<Paragraph
size={AppFontSize.xs}
color={colors.primary.paragraph}
allowFontScaling={false}
style={{
marginLeft: 4,
paddingRight: 8
}}
>
{item.name || item.value.split("/").pop()}
{item.size ? `(${formatBytes(item.size)})` : ""}
</Paragraph>
</TouchableOpacity>
)
)}
</ScrollView>
<Paragraph
color={colors.secondary.paragraph}
size={AppFontSize.xs}
allowFontScaling={false}
style={{
flexDirection: "row",
alignSelf: "center",
alignItems: "center",
width: "100%",
marginTop: 6
}}
onPress={() => {
if (fullQualityImages?.isAllowed) {
setCompress(!compress);
}
}}
>
<Icon
size={20}
name={
compress
? "checkbox-marked"
: "checkbox-blank-outline"
}
allowFontScaling={false}
color={
compress && fullQualityImages?.isAllowed
? colors.primary.accent
: colors.primary.icon
}
/>
<Text
Tap to remove an attachment.
</Paragraph>
{rawFiles.some((item) => isImage(item.type)) ? (
<TouchableOpacity
activeOpacity={1}
style={{
flexShrink: 1,
marginLeft: 3,
fontSize: 12
flexDirection: "row",
alignSelf: "center",
alignItems: "center",
width: "100%",
marginTop: 6
}}
onPress={() => {
if (fullQualityImages?.isAllowed) {
setCompress(!compress);
}
}}
allowFontScaling={false}
>
Compress image(s) (recommended)
</Text>
</TouchableOpacity>
) : null}
<Icon
size={20}
name={
compress
? "checkbox-marked"
: "checkbox-blank-outline"
}
allowFontScaling={false}
color={
compress && fullQualityImages?.isAllowed
? colors.primary.accent
: colors.primary.icon
}
/>
<Text
style={{
flexShrink: 1,
marginLeft: 3,
fontSize: 12
}}
allowFontScaling={false}
>
Compress image(s) (recommended)
</Text>
</TouchableOpacity>
) : null}
</View>
) : null}
<View
style={{
width: "100%",
height: rawFiles.length > 0 ? 100 : 200,
paddingBottom: 15,
marginBottom: 10,
borderBottomColor: colors.secondary.background,
borderBottomWidth: 1
}}
>
<SafeAreaProvider
style={{
flex: 1,
paddingTop: 10,
justifyContent: loadingPage ? "center" : undefined,
alignItems: loadingPage ? "center" : undefined
}}
>
{!loadingExtension && !loadingPage ? (
<>
<Editor
editorRef={editorRef}
onLoad={onLoadEditor}
onChange={(html) => {
noteContent.current = html;
}}
/>
</>
) : (
<>
{loadingPage ? (
<>
<ActivityIndicator
color={colors.primary.accent}
/>
<Paragraph>Preparing web clip...</Paragraph>
</>
) : null}
</>
)}
</SafeAreaProvider>
</View>
) : null}
<View
style={{
width: "100%",
height: rawFiles.length > 0 ? 100 : 200,
paddingBottom: 15,
marginBottom: 10,
borderBottomColor: colors.secondary.background,
borderBottomWidth: 1
}}
>
<SafeAreaProvider
{appendNoteId ? (
<AppendNote
id={appendNoteId}
onLoad={(title) => {
if (!noteTitle.current) {
noteTitle.current = title;
inputRef.current?.setNativeProps?.({
text: noteTitle.current
});
}
}}
/>
) : null}
<View
style={{
flex: 1,
paddingTop: 10,
justifyContent: loadingPage ? "center" : undefined,
alignItems: loadingPage ? "center" : undefined
flexDirection: "row",
paddingHorizontal: 12,
width: "100%",
alignItems: "center"
}}
>
{!loadingExtension && !loadingPage ? (
<>
<Editor
editorRef={editorRef}
onLoad={onLoadEditor}
onChange={(html) => {
noteContent.current = html;
}}
/>
</>
) : (
<>
{loadingPage ? (
<>
<ActivityIndicator color={colors.primary.accent} />
<Paragraph>Preparing web clip...</Paragraph>
</>
) : null}
</>
)}
</SafeAreaProvider>
</View>
{appendNoteId ? (
<AppendNote
id={appendNoteId}
onLoad={(title) => {
if (!noteTitle.current) {
noteTitle.current = title;
inputRef.current?.setNativeProps?.({
text: noteTitle.current
});
}
}}
/>
) : null}
<View
style={{
flexDirection: "row",
paddingHorizontal: 12,
width: "100%",
alignItems: "center"
}}
>
<Paragraph
style={{
marginRight: 10
}}
allowFontScaling={false}
>
Clip Mode:
</Paragraph>
{rawData.value && isURL(rawData.value) ? (
<Button
type={mode === 2 ? "inverted" : "plain"}
icon={mode === 2 ? "radiobox-marked" : "radiobox-blank"}
onPress={() => changeMode(2)}
title={modes[2].title}
height={30}
<Paragraph
style={{
marginRight: 10
}}
allowFontScaling={false}
>
Clip Mode:
</Paragraph>
{rawData.value && isURL(rawData.value) ? (
<Button
type={mode === 2 ? "inverted" : "plain"}
icon={mode === 2 ? "radiobox-marked" : "radiobox-blank"}
onPress={() => changeMode(2)}
title={modes[2].title}
height={30}
allowFontScaling={false}
style={{
paddingHorizontal: 6
}}
/>
) : null}
<Button
type={mode === 1 ? "inverted" : "plain"}
icon={mode === 1 ? "radiobox-marked" : "radiobox-blank"}
onPress={() => changeMode(1)}
allowFontScaling={false}
title={modes[1].title}
height={30}
style={{
paddingHorizontal: 6
}}
/>
) : null}
<Button
type={mode === 1 ? "inverted" : "plain"}
icon={mode === 1 ? "radiobox-marked" : "radiobox-blank"}
onPress={() => changeMode(1)}
allowFontScaling={false}
title={modes[1].title}
height={30}
style={{
paddingHorizontal: 6
}}
/>
</View>
</View>
</View>
</View>
<View
style={{
width: "100%",
borderRadius: 10,
flexDirection: "column",
marginTop: 10,
alignSelf: "center",
alignItems: "center",
paddingHorizontal: 12,
gap: 10
}}
>
<Button
icon="plus"
onPress={() => {
useShareStore.getState().setAppendNote(null);
}}
type={!appendNoteId ? "transparent" : "plain"}
title="New note"
allowFontScaling={false}
<View
style={{
paddingHorizontal: 12,
height: 45,
width: "100%",
marginRight: 0,
borderWidth: 1,
borderColor: colors.secondary.background,
justifyContent: "flex-start"
}}
/>
<Button
icon="text-short"
onPress={() => {
setSearchMode("appendNote");
}}
allowFontScaling={false}
type={appendNoteId ? "transparent" : "plain"}
title={`Append to a note`}
style={{
borderRadius: 10,
flexDirection: "column",
marginTop: 10,
alignSelf: "center",
alignItems: "center",
paddingHorizontal: 12,
height: 45,
width: "100%",
marginRight: 0,
borderWidth: 1,
borderColor: colors.secondary.background,
justifyContent: "flex-start"
gap: 10
}}
/>
{!appendNoteId ? (
<AddTags
>
<Button
icon="plus"
onPress={() => {
setSearchMode("selectTags");
useShareStore.getState().setAppendNote(null);
}}
type={!appendNoteId ? "transparent" : "plain"}
title="New note"
allowFontScaling={false}
style={{
paddingHorizontal: 12,
height: 45,
width: "100%",
marginRight: 0,
borderWidth: 1,
borderColor: colors.secondary.background,
justifyContent: "flex-start"
}}
/>
) : null}
{!appendNoteId ? (
<AddNotebooks
<Button
icon="text-short"
onPress={() => {
setSearchMode("selectNotebooks");
setSearchMode("appendNote");
}}
allowFontScaling={false}
type={appendNoteId ? "transparent" : "plain"}
title={`Append to a note`}
style={{
paddingHorizontal: 12,
height: 45,
width: "100%",
marginRight: 0,
borderWidth: 1,
borderColor: colors.secondary.background,
justifyContent: "flex-start"
}}
/>
) : null}
</View>
<View
style={{
height: isTablet() ? 150 : Platform.OS === "ios" ? 110 : 0
}}
/>
</View>
</ScrollView>
{!appendNoteId ? (
<AddTags
onPress={() => {
setSearchMode("selectTags");
}}
/>
) : null}
{!appendNoteId ? (
<AddNotebooks
onPress={() => {
setSearchMode("selectNotebooks");
}}
/>
) : null}
</View>
<View
style={{
height: isTablet() ? 150 : Platform.OS === "ios" ? 110 : 0
}}
/>
</View>
</ScrollView>
)}
</WrapperView>
</View>
);

View File

@@ -17,12 +17,13 @@ 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 { db } from "../common/database";
import { DatabaseLogger, db } from "../common/database";
import { eSendEvent } from "../services/event-manager";
import Navigation from "../services/navigation";
import { NotePreviewWidget } from "../services/note-preview-widget";
import Notifications from "../services/notifications";
import { eAfterSync } from "../utils/events";
import { NotesnookModule, ShortcutInfo } from "../utils/notesnook-module";
import { useFavoriteStore } from "./use-favorite-store";
import { useMenuStore } from "./use-menu-store";
import { useMonographStore } from "./use-monograph-store";
@@ -34,6 +35,81 @@ import { useTagStore } from "./use-tag-store";
import { useTrashStore } from "./use-trash-store";
import { useUserStore } from "./use-user-store";
async function syncShortcuts(result: ShortcutInfo[]) {
try {
for (let shortcut of result) {
switch (shortcut.type) {
case "note":
{
const note = await db.notes.note(shortcut.id);
if (!note) {
NotesnookModule.removeShortcut(shortcut.id);
} else if (note.title !== shortcut.title) {
NotesnookModule.updateShortcut(
shortcut.id,
"note",
note.title,
note.headline
);
}
}
break;
case "notebook":
{
const notebook = await db.notebooks.notebook(shortcut.id);
if (!notebook) {
NotesnookModule.removeShortcut(shortcut.id);
} else if (notebook.title !== shortcut.title) {
NotesnookModule.updateShortcut(
shortcut.id,
"notebook",
notebook.title,
notebook.description
);
}
}
break;
case "tag":
{
const tag = await db.tags.tag(shortcut.id);
if (!tag) {
NotesnookModule.removeShortcut(shortcut.id);
} else if (tag.title !== shortcut.title) {
NotesnookModule.updateShortcut(
shortcut.id,
"tag",
tag.title,
tag.title
);
}
}
break;
case "color":
{
const color = await db.colors.color(shortcut.id);
if (!color) {
NotesnookModule.removeShortcut(shortcut.id);
} else if (color.title !== shortcut.title) {
NotesnookModule.updateShortcut(
shortcut.id,
"color",
color.title,
color.title,
color.colorCode
);
}
}
break;
}
}
} catch (e) {
DatabaseLogger.error(
e as Error,
"Error while syncing homescreen shortcuts"
);
}
}
export function initAfterSync(type: "full" | "send" = "send") {
if (type === "full") {
Navigation.queueRoutesForUpdate();
@@ -46,9 +122,16 @@ export function initAfterSync(type: "full" | "send" = "send") {
profile: db.settings.getProfile()
});
}
Notifications.setupReminders(true);
NotePreviewWidget.updateNotes();
eSendEvent(eAfterSync);
NotesnookModule.getAllShortcuts()
.then(syncShortcuts)
.catch((e) => {
DatabaseLogger.log(e);
});
}
export async function initialize() {}

View File

@@ -25,8 +25,7 @@ import {
Note,
Notebook,
Reminder,
Tag,
TrashItem
Tag
} from "@notesnook/core";
import { ParamListBase } from "@react-navigation/core";
import { create } from "zustand";
@@ -36,12 +35,15 @@ export type GenericRouteParam = {
};
export type NotebookScreenParams = {
item: Notebook;
id: string;
item?: Notebook;
canGoBack?: boolean;
};
export type NotesScreenParams = {
item: Note | Notebook | Tag | Color | TrashItem | Reminder;
type: "tag" | "color" | "monograph";
id: string;
item?: Tag | Color;
canGoBack?: boolean;
};

View File

@@ -24,7 +24,9 @@ import { initialWindowMetrics } from "react-native-safe-area-context";
import { FileType } from "react-native-scoped-storage";
import { create } from "zustand";
import { ThemeDark, ThemeLight, ThemeDefinition } from "@notesnook/theme";
import { Reminder } from "@notesnook/core";
import { DayFormat, WeekFormat, Reminder } from "@notesnook/core";
import { db } from "../common/database";
import { EDITOR_LINE_HEIGHT } from "../utils/constants";
export const HostIds = [
"API_HOST",
"AUTH_HOST",
@@ -99,6 +101,7 @@ export type Settings = {
serverUrls?: Record<HostId, string>;
defaultSidebarTab: number;
checkForUpdates?: boolean;
defaultLineHeight: number;
};
type DimensionsType = {
@@ -134,8 +137,12 @@ export interface SettingStore {
setInsets: (insets: Insets) => void;
timeFormat: string;
dateFormat: string;
dayFormat: DayFormat;
weekFormat: WeekFormat;
dbPassword?: string;
isOldAppLock: () => boolean;
initialUrl: string | null;
refresh: () => void;
}
const { width, height } = Dimensions.get("window");
@@ -198,7 +205,8 @@ export const defaultSettings: SettingStore["settings"] = {
backupType: "partial",
fullBackupReminder: "never",
lastFullBackupDate: 0,
checkForUpdates: true
checkForUpdates: true,
defaultLineHeight: EDITOR_LINE_HEIGHT.DEFAULT
};
export const useSettingStore = create<SettingStore>((set, get) => ({
@@ -221,6 +229,8 @@ export const useSettingStore = create<SettingStore>((set, get) => ({
setInsets: (insets) => set({ insets }),
timeFormat: "12-hour",
dateFormat: "DD-MM-YYYY",
dayFormat: "short",
weekFormat: "Sun",
setAppDidEnterBackgroundForAction: (value: boolean) => {
set({
appDidEnterBackgroundForAction: value
@@ -236,5 +246,14 @@ export const useSettingStore = create<SettingStore>((set, get) => ({
},
insets: initialWindowMetrics?.insets
? initialWindowMetrics.insets
: { top: 0, right: 0, left: 0, bottom: 0 }
: { top: 0, right: 0, left: 0, bottom: 0 },
initialUrl: null,
refresh: () => {
set({
dayFormat: db.settings.getDayFormat(),
timeFormat: db.settings.getTimeFormat(),
dateFormat: db.settings?.getTimeFormat(),
weekFormat: db.settings.getWeekFormat()
});
}
}));

View File

@@ -122,3 +122,9 @@ export const SUBSCRIPTION_PROVIDER = {
icon: "web"
}
};
export const EDITOR_LINE_HEIGHT = {
DEFAULT: 1.2,
MAX: 10,
MIN: 1
};

View File

@@ -19,6 +19,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { NativeModules, Platform } from "react-native";
export type ShortcutInfo = {
id: string;
title: string;
description: string;
type: "note" | "notebook" | "tag" | "color";
};
interface NotesnookModuleInterface {
getActivityName: () => Promise<string>;
setBackgroundColor: (color: string) => void;
@@ -41,6 +48,23 @@ interface NotesnookModuleInterface {
updateWidgetNote: (noteId: string, data: string) => void;
updateReminderWidget: () => void;
isGestureNavigationEnabled: () => boolean;
addShortcut: (
id: string,
type: "note" | "notebook" | "tag" | "color",
title: string,
description?: string,
color?: string
) => Promise<boolean>;
removeShortcut: (id: string) => Promise<boolean>;
updateShortcut: (
id: string,
type: "note" | "notebook" | "tag" | "color",
title: string,
description?: string,
color?: string
) => Promise<boolean>;
removeAllShortcuts: () => Promise<boolean>;
getAllShortcuts: () => Promise<ShortcutInfo[]>;
}
export const NotesnookModule: NotesnookModuleInterface = Platform.select({
@@ -61,7 +85,12 @@ export const NotesnookModule: NotesnookModuleInterface = Platform.select({
hasWidgetNote: () => {},
updateWidgetNote: () => {},
updateReminderWidget: () => {},
isGestureNavigationEnabled: () => true
isGestureNavigationEnabled: () => true,
addShortcut: () => Promise.resolve(false),
removeShortcut: () => Promise.resolve(false),
updateShortcut: () => Promise.resolve(false),
removeAllShortcuts: () => Promise.resolve(false),
getAllShortcuts: () => Promise.resolve([])
},
android: NativeModules.NNativeModule
});

View File

@@ -94,7 +94,7 @@ describe("VAULT", () => {
.typeTextById(notesnook.ids.dialogs.vault.pwd, "1234")
.typeTextById(notesnook.ids.dialogs.vault.changePwd, "2362")
.waitAndTapByText("Change")
.pressBack(4)
.pressBack(3)
.addStep(async () => await openLockedNote("2362"))
.run();
});

View File

@@ -5,11 +5,11 @@
<key>provisioningProfiles</key>
<dict>
<key>org.streetwriters.notesnook</key>
<string>Notesnook App Distribution 2025</string>
<string>Notesnook 2026</string>
<key>org.streetwriters.notesnook.notewidget</key>
<string>Notesnook Widget Distribution 2025</string>
<string>Notesnook Note Widget 2026</string>
<key>org.streetwriters.notesnook.share</key>
<string>Notesnook Share Distribution 2025</string>
<string>Notesnook Share 2026</string>
</dict>
</dict>
</plist>

View File

@@ -1029,7 +1029,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2167;
CURRENT_PROJECT_VERSION = 2170;
DEVELOPMENT_TEAM = 53CWBG3QUC;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1104,7 +1104,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.3.11;
MARKETING_VERSION = 3.3.13;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -1135,7 +1135,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2167;
CURRENT_PROJECT_VERSION = 2170;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1210,7 +1210,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.3.11;
MARKETING_VERSION = 3.3.13;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = (
"$(inherited)",
@@ -1220,7 +1220,7 @@
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook;
PRODUCT_NAME = Notesnook;
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook App Distribution 2025";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook 2026";
SWIFT_OBJC_BRIDGING_HEADER = "Notesnook-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -1367,7 +1367,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2167;
CURRENT_PROJECT_VERSION = 2170;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1379,7 +1379,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.3.11;
MARKETING_VERSION = 3.3.13;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
@@ -1410,7 +1410,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2167;
CURRENT_PROJECT_VERSION = 2170;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1423,12 +1423,12 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.3.11;
MARKETING_VERSION = 3.3.13;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook Widget Distribution 2025";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook Note Widget 2026";
SKIP_INSTALL = YES;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
@@ -1453,7 +1453,7 @@
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2167;
CURRENT_PROJECT_VERSION = 2170;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1534,7 +1534,7 @@
"@executable_path/../../Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)";
MARKETING_VERSION = 3.3.11;
MARKETING_VERSION = 3.3.13;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
@@ -1565,7 +1565,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2167;
CURRENT_PROJECT_VERSION = 2170;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1647,12 +1647,12 @@
"@executable_path/../../Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)";
MARKETING_VERSION = 3.3.11;
MARKETING_VERSION = 3.3.13;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook Share Distribution 2025";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook Share 2026";
SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = "Make Note/Make Note-Bridging-Header.h";
SWIFT_VERSION = 5.0;

View File

@@ -45,6 +45,65 @@ PODS:
- MMKV (1.3.14):
- MMKVCore (~> 1.3.14)
- MMKVCore (1.3.14)
- NitroCloudUploader (1.0.9):
- boost
- DoubleConversion
- fast_float
- fmt
- glog
- hermes-engine
- NitroModules
- RCT-Folly
- RCT-Folly/Fabric
- RCTRequired
- RCTTypeSafety
- React-callinvoker
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-jsi
- React-NativeModulesApple
- React-RCTFabric
- React-renderercss
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- SocketRocket
- Yoga
- NitroModules (0.32.0):
- boost
- DoubleConversion
- fast_float
- fmt
- glog
- hermes-engine
- RCT-Folly
- RCT-Folly/Fabric
- RCTRequired
- RCTTypeSafety
- React-callinvoker
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-jsi
- React-NativeModulesApple
- React-RCTFabric
- React-renderercss
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- SocketRocket
- Yoga
- pop (1.0.12)
- RCT-Folly (2024.11.18.00):
- boost
@@ -2004,35 +2063,6 @@ PODS:
- React
- react-native-orientation-locker (1.7.0):
- React-Core
- react-native-pager-view (8.0.0):
- boost
- DoubleConversion
- fast_float
- fmt
- glog
- hermes-engine
- RCT-Folly
- RCT-Folly/Fabric
- RCTRequired
- RCTTypeSafety
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-jsi
- React-NativeModulesApple
- React-RCTFabric
- React-renderercss
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- SocketRocket
- SwiftUIIntrospect (~> 1.0)
- Yoga
- react-native-pdf (7.0.3):
- boost
- DoubleConversion
@@ -2245,7 +2275,7 @@ PODS:
- ReactCommon/turbomodule/core
- SocketRocket
- Yoga
- react-native-upload (6.29.0):
- react-native-upload (6.31.0):
- React
- react-native-view-shot (4.0.3):
- boost
@@ -3062,10 +3092,10 @@ PODS:
- Yoga
- RNKeychain (4.0.5):
- React
- RNNotifee (7.4.10):
- RNNotifee (7.4.11):
- React-Core
- RNNotifee/NotifeeCore (= 7.4.10)
- RNNotifee/NotifeeCore (7.4.10):
- RNNotifee/NotifeeCore (= 7.4.11)
- RNNotifee/NotifeeCore (7.4.11):
- React-Core
- RNPrivacySnapshot (1.0.0):
- React-Core
@@ -3161,7 +3191,7 @@ PODS:
- RNWorklets
- SocketRocket
- Yoga
- RNScreens (4.18.0):
- RNScreens (4.19.0):
- boost
- DoubleConversion
- fast_float
@@ -3188,10 +3218,10 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- RNScreens/common (= 4.18.0)
- RNScreens/common (= 4.19.0)
- SocketRocket
- Yoga
- RNScreens/common (4.18.0):
- RNScreens/common (4.19.0):
- boost
- DoubleConversion
- fast_float
@@ -3414,7 +3444,6 @@ PODS:
- pop (~> 1.0)
- SocketRocket (0.7.1)
- SSZipArchive (2.4.3)
- SwiftUIIntrospect (1.3.0)
- SwiftyRSA (1.7.0):
- SwiftyRSA/ObjC (= 1.7.0)
- SwiftyRSA/ObjC (1.7.0)
@@ -3434,6 +3463,8 @@ DEPENDENCIES:
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
- MMKV
- NitroCloudUploader (from `../node_modules/react-native-nitro-cloud-uploader`)
- NitroModules (from `../node_modules/react-native-nitro-modules`)
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`)
- RCTRequired (from `../node_modules/react-native/Libraries/Required`)
@@ -3486,7 +3517,6 @@ DEPENDENCIES:
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
- react-native-notification-sounds (from `../node_modules/react-native-notification-sounds`)
- react-native-orientation-locker (from `../node_modules/react-native-orientation-locker`)
- react-native-pager-view (from `../node_modules/react-native-pager-view`)
- react-native-pdf (from `../node_modules/react-native-pdf`)
- react-native-quick-sqlite (from `../node_modules/react-native-quick-sqlite`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
@@ -3570,7 +3600,6 @@ SPEC REPOS:
- SDWebImage
- SocketRocket
- SSZipArchive
- SwiftUIIntrospect
- SwiftyRSA
- TOCropViewController
@@ -3592,6 +3621,10 @@ EXTERNAL SOURCES:
hermes-engine:
:podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec"
:tag: hermes-2025-09-01-RNv0.82.0-265ef62ff3eb7289d17e366664ac0da82303e101
NitroCloudUploader:
:path: "../node_modules/react-native-nitro-cloud-uploader"
NitroModules:
:path: "../node_modules/react-native-nitro-modules"
RCT-Folly:
:podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec"
RCTDeprecation:
@@ -3694,8 +3727,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-notification-sounds"
react-native-orientation-locker:
:path: "../node_modules/react-native-orientation-locker"
react-native-pager-view:
:path: "../node_modules/react-native-pager-view"
react-native-pdf:
:path: "../node_modules/react-native-pdf"
react-native-quick-sqlite:
@@ -3853,6 +3884,8 @@ SPEC CHECKSUMS:
JWTDecode: 2eed97c2fa46ccaf3049a787004eedf0be474a87
MMKV: 7b5df6a8bf785c6705cc490c541b9d8a957c4a64
MMKVCore: 3f40b896e9ab522452df9df3ce983471aa2449ba
NitroCloudUploader: 62489381ed6b472d87c8c6b62629136ebd2342d1
NitroModules: f964d2f3f5b392d0c0303737085cc30e375dc43f
pop: d582054913807fd11fd50bfe6a539d91c7e1a55a
RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669
RCTDeprecation: f17e2ebc07876ca9ab8eb6e4b0a4e4647497ae3a
@@ -3906,7 +3939,6 @@ SPEC CHECKSUMS:
react-native-netinfo: cec9c4e86083cb5b6aba0e0711f563e2fbbff187
react-native-notification-sounds: ce106d58df0dd384bccbd2e84fb53accab7cc068
react-native-orientation-locker: cc6f357b289a2e0dd2210fea0c52cb8e0727fdaa
react-native-pager-view: d7d2aa47f54343bf55fdcee3973503dd27c2bd37
react-native-pdf: edc236298f13f1609e42d41e45b8b6ea88ed10f9
react-native-quick-sqlite: 1ed8d3db1e22a8604d006be69f06053382e93bb0
react-native-safe-area-context: c6e2edd1c1da07bdce287fa9d9e60c5f7b514616
@@ -3914,7 +3946,7 @@ SPEC CHECKSUMS:
react-native-share-extension: fdc6aaab51591a2d445df239c446aaa3a99658ec
react-native-sodium: 066f76e46c9be13e9260521e3fa994937c4cdab4
react-native-theme-switch-animation: 449d6db7a760f55740505e7403ae8061debc9a7e
react-native-upload: 1459a4be625dda39e4364d9e7d859477a3000f2a
react-native-upload: 6285965d20879bcfcf7953f05eb785ba03a60e40
react-native-view-shot: 6c008e58f4720de58370848201c5d4a082c6d4ca
react-native-webview: 654f794a7686b47491cf43aa67f7f428bea00eed
React-NativeModulesApple: 46690a0fe94ec28fc6fc686ec797b911d251ded0
@@ -3962,10 +3994,10 @@ SPEC CHECKSUMS:
RNIap: 61f183ac917792fae42b0326b1bef33598c1adf6
RNImageCropPicker: 5fd4ceaead64d8c53c787e4e559004f97bc76df7
RNKeychain: ffd0513e676445c637410b47249460cbf56bc9cb
RNNotifee: 3840cc81add9954a5dec040f96442498f3f95d7b
RNNotifee: c4827fa2cec60ab634d62bafdb5cf57cd2999d54
RNPrivacySnapshot: ccad3a548338c2f526bb7b1789af3fb0618b7d1d
RNReanimated: f1868b36f4b2b52a0ed00062cfda69506f75eaee
RNScreens: d821082c6dd1cb397cc0c98b026eeafaa68be479
RNScreens: ffbb0296608eb3560de641a711bbdb663ed1f6b4
RNSecureRandom: b64d263529492a6897e236a22a2c4249aa1b53dc
RNShare: 5d39a36f2d3d9a6c9344bfb3232c8ed607924a35
RNSVG: 8c0bbfa480a24b24468f1c76bd852a4aac3178e6
@@ -3976,7 +4008,6 @@ SPEC CHECKSUMS:
SexyTooltip: 5c9b4dec52bfb317938cb0488efd9da3717bb6fd
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef
SwiftUIIntrospect: fee9aa07293ee280373a591e1824e8ddc869ba5d
SwiftyRSA: 8c6dd1ea7db1b8dc4fb517a202f88bb1354bc2c6
TOCropViewController: 797deaf39c90e6e9ddd848d88817f6b9a8a09888
toolbar-android: c426ed5bd3dcccfed20fd79533efc0d1ae0ef018

View File

@@ -1,22 +1,22 @@
{
"name": "@notesnook/mobile",
"version": "3.3.11",
"version": "3.3.13-beta.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/mobile",
"version": "3.3.11",
"version": "3.3.13-beta.0",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
"@ammarahmed/notifee-react-native": "^7.4.10",
"@ammarahmed/notifee-react-native": "^7.4.11",
"@ammarahmed/react-native-background-fetch": "^4.2.2",
"@ammarahmed/react-native-eventsource": "1.1.0",
"@ammarahmed/react-native-fingerprint-scanner": "^5.0.1",
"@ammarahmed/react-native-share-extension": "^2.9.5",
"@ammarahmed/react-native-sodium": "^1.6.8",
"@ammarahmed/react-native-upload": "^6.29.0",
"@ammarahmed/react-native-upload": "^6.31.0",
"@azure/core-asynciterator-polyfill": "^1.0.2",
"@bam.tech/react-native-image-resizer": "3.0.11",
"@callstack/repack": "~5.2.1",
@@ -100,9 +100,10 @@
"react-native-mmkv-storage": "^12.0.1",
"react-native-modal-datetime-picker": "14.0.0",
"react-native-navigation-bar-color": "2.0.2",
"react-native-nitro-cloud-uploader": "^1.0.9",
"react-native-nitro-modules": "^0.32.0",
"react-native-notification-sounds": "0.5.5",
"react-native-orientation-locker": "^1.7.0",
"react-native-pager-view": "^8.0.0",
"react-native-pdf": "^7.0.3",
"react-native-privacy-snapshot": "github:standardnotes/react-native-privacy-snapshot",
"react-native-progress": "5.0.0",
@@ -113,12 +114,11 @@
"react-native-safe-area-context": "^5.6.1",
"react-native-scoped-storage": "^1.9.5",
"react-native-screenguard": "1.0.0",
"react-native-screens": "^4.16.0",
"react-native-screens": "^4.19.0",
"react-native-securerandom": "^1.0.1",
"react-native-share": "^12.0.3",
"react-native-svg": "^15.12.0",
"react-native-swiper-flatlist": "3.2.2",
"react-native-tab-view": "^4.2.2",
"react-native-theme-switch-animation": "^0.6.0",
"react-native-tooltips": "^1.0.3",
"react-native-url-polyfill": "^2.0.0",
@@ -331,9 +331,11 @@
"colord": "^2.9.3",
"detect-indent": "^7.0.1",
"entities": "5.0.0",
"file-saver": "^2.0.5",
"katex": "0.16.11",
"linkifyjs": "^4.1.3",
"nanoid": "5.0.7",
"papaparse": "^5.5.3",
"prism-themes": "^1.9.0",
"prosemirror-codemark": "^0.4.2",
"prosemirror-view": "1.34.2",
@@ -341,7 +343,6 @@
"react-colorful": "^5.6.1",
"redent": "^4.0.0",
"refractor": "^4.8.1",
"simplebar-react": "^3.3.2",
"strip-indent": "^4.0.0",
"unfurl.js": "^6.4.0"
},
@@ -350,7 +351,9 @@
"@mdi/js": "7.4.47",
"@theme-ui/components": "0.16.1",
"@theme-ui/core": "0.16.1",
"@types/file-saver": "^2.0.7",
"@types/katex": "0.16.7",
"@types/papaparse": "^5.5.2",
"@types/prismjs": "1.26.4",
"@types/react": "18.3.5",
"@types/react-color": "^3.0.12",
@@ -467,27 +470,29 @@
},
"../../servers/themes": {
"name": "@notesnook/themes-server",
"version": "1.0.0",
"version": "1.0.4",
"license": "ISC",
"dependencies": {
"@notesnook/theme": "file:../../packages/theme",
"@orama/orama": "^1.0.8",
"@trpc/server": "10.45.2",
"async-mutex": "0.5.0",
"cloudflare": "^5.2.0",
"cors": "^2.8.5",
"react": "18.3.1",
"util": "^0.12.5",
"zod": "3.23.8"
},
"devDependencies": {
"@notesnook/theme": "file:../../packages/theme",
"@types/cors": "^2.8.13",
"react": "18.3.1",
"esbuild": "0.21.5",
"vite-node": "2.1.8"
}
},
"node_modules/@ammarahmed/notifee-react-native": {
"version": "7.4.10",
"resolved": "https://registry.npmjs.org/@ammarahmed/notifee-react-native/-/notifee-react-native-7.4.10.tgz",
"integrity": "sha512-19t1vhIddFneOI7JVmXghunEt5mEmDBY/G1YVMuRNNT/HFQ1EcArJWuAaFAJrhrl/2CBS7xoPGLusKbkbT/JjA==",
"version": "7.4.11",
"resolved": "https://registry.npmjs.org/@ammarahmed/notifee-react-native/-/notifee-react-native-7.4.11.tgz",
"integrity": "sha512-9zKhONgir2FN/sMj/rsKAhDwfxadDuxB6LJILKxpGwzqvcIuG70oCKWmBBG9LJJpSfcVpNTdxBoH9RtNZTZSsQ==",
"license": "Apache-2.0",
"peerDependencies": {
"react-native": "*"
@@ -533,9 +538,9 @@
"license": "ISC"
},
"node_modules/@ammarahmed/react-native-upload": {
"version": "6.29.0",
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-upload/-/react-native-upload-6.29.0.tgz",
"integrity": "sha512-k2FJAVR4Nohx4VhfgHuXzLXQpgEnco/PAv9R080X7oY3O12DTgY0ruDvOFwvQGFW0PDIHlXfLfUiMq2kZc/k1A==",
"version": "6.31.0",
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-upload/-/react-native-upload-6.31.0.tgz",
"integrity": "sha512-GwMDd2IND3nuS4l13pQPP8zA/GJoxQKz0pxCGUoWj65fbPuZPnuUA2Rpf4jZzLlgY62vdjI/TqmX+rPiwwCceQ==",
"license": "BSD-3-Clause",
"peerDependencies": {
"react": "*",
@@ -17716,6 +17721,30 @@
"integrity": "sha512-ZmpLWRocyme1au11e5ZuecMS/UCi57nlzgnioi03Q6ERMbeUOqqbWgNBaNB7SsCeqBV6fZPjo3+A64zEIpzw4w==",
"license": "MIT"
},
"node_modules/react-native-nitro-cloud-uploader": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/react-native-nitro-cloud-uploader/-/react-native-nitro-cloud-uploader-1.0.9.tgz",
"integrity": "sha512-J96JC/yp0DWeyS512zQFYbw2KGiengWs0IAGGoV6NCAystluBJlVO4WqgiCiy/nd3bFidy/+uteC+P5h28j0vw==",
"license": "MIT",
"workspaces": [
"example"
],
"peerDependencies": {
"react": "*",
"react-native": "*",
"react-native-nitro-modules": "*"
}
},
"node_modules/react-native-nitro-modules": {
"version": "0.32.0",
"resolved": "https://registry.npmjs.org/react-native-nitro-modules/-/react-native-nitro-modules-0.32.0.tgz",
"integrity": "sha512-nLDsmi/H9/cLcYxArYxcTDL641JiWnAH3u2LmsFETgyqSEHvukswDTdmX6AHBRIAwXu/iUDppZyjsaRpTG6WMg==",
"license": "MIT",
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-notification-sounds": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/react-native-notification-sounds/-/react-native-notification-sounds-0.5.5.tgz",
@@ -17742,16 +17771,6 @@
}
}
},
"node_modules/react-native-pager-view": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/react-native-pager-view/-/react-native-pager-view-8.0.0.tgz",
"integrity": "sha512-oAwlWT1lhTkIs9HhODnjNNl/owxzn9DP1MbP+az6OTUdgbmzA16Up83sBH8NRKwrH8rNm7iuWnX1qMqiiWOLhg==",
"license": "MIT",
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-pdf": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-7.0.3.tgz",
@@ -17900,9 +17919,9 @@
}
},
"node_modules/react-native-screens": {
"version": "4.18.0",
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.18.0.tgz",
"integrity": "sha512-mRTLWL7Uc1p/RFNveEIIrhP22oxHduC2ZnLr/2iHwBeYpGXR0rJZ7Bgc0ktxQSHRjWTPT70qc/7yd4r9960PBQ==",
"version": "4.19.0",
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.19.0.tgz",
"integrity": "sha512-qSDAO3AL5bti0Ri7KZRSVmWlhDr8MV86N5GruiKVQfEL7Zx2nUi3Dl62lqHUAD/LnDvOPuDDsMHCfIpYSv3hPQ==",
"license": "MIT",
"dependencies": {
"react-freeze": "^1.0.0",
@@ -17958,20 +17977,6 @@
"react-native": ">=0.59.0"
}
},
"node_modules/react-native-tab-view": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/react-native-tab-view/-/react-native-tab-view-4.2.2.tgz",
"integrity": "sha512-NXtrG6OchvbGjsvbySJGVocXxo4Y2vA17ph4rAaWtA2jh+AasD8OyikKBRg2SmllEfeQ+GEhcKe8kulHv8BhTg==",
"license": "MIT",
"dependencies": {
"use-latest-callback": "^0.2.4"
},
"peerDependencies": {
"react": ">= 18.2.0",
"react-native": "*",
"react-native-pager-view": ">= 6.0.0"
}
},
"node_modules/react-native-theme-switch-animation": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/react-native-theme-switch-animation/-/react-native-theme-switch-animation-0.6.0.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "@notesnook/mobile",
"version": "3.3.11",
"version": "3.3.13-beta.1",
"private": true,
"license": "GPL-3.0-or-later",
"scripts": {
@@ -26,13 +26,13 @@
"react": "19.1.1"
},
"dependencies": {
"@ammarahmed/notifee-react-native": "^7.4.10",
"@ammarahmed/notifee-react-native": "^7.4.11",
"@ammarahmed/react-native-background-fetch": "^4.2.2",
"@ammarahmed/react-native-eventsource": "1.1.0",
"@ammarahmed/react-native-fingerprint-scanner": "^5.0.1",
"@ammarahmed/react-native-share-extension": "^2.9.5",
"@ammarahmed/react-native-sodium": "^1.6.8",
"@ammarahmed/react-native-upload": "^6.29.0",
"@ammarahmed/react-native-upload": "^6.31.0",
"@azure/core-asynciterator-polyfill": "^1.0.2",
"@bam.tech/react-native-image-resizer": "3.0.11",
"@callstack/repack": "~5.2.1",
@@ -116,9 +116,10 @@
"react-native-mmkv-storage": "^12.0.1",
"react-native-modal-datetime-picker": "14.0.0",
"react-native-navigation-bar-color": "2.0.2",
"react-native-nitro-cloud-uploader": "^1.0.9",
"react-native-nitro-modules": "^0.32.0",
"react-native-notification-sounds": "0.5.5",
"react-native-orientation-locker": "^1.7.0",
"react-native-pager-view": "^8.0.0",
"react-native-pdf": "^7.0.3",
"react-native-privacy-snapshot": "github:standardnotes/react-native-privacy-snapshot",
"react-native-progress": "5.0.0",
@@ -129,12 +130,11 @@
"react-native-safe-area-context": "^5.6.1",
"react-native-scoped-storage": "^1.9.5",
"react-native-screenguard": "1.0.0",
"react-native-screens": "^4.16.0",
"react-native-screens": "^4.19.0",
"react-native-securerandom": "^1.0.1",
"react-native-share": "^12.0.3",
"react-native-svg": "^15.12.0",
"react-native-swiper-flatlist": "3.2.2",
"react-native-tab-view": "^4.2.2",
"react-native-theme-switch-animation": "^0.6.0",
"react-native-tooltips": "^1.0.3",
"react-native-url-polyfill": "^2.0.0",

View File

@@ -0,0 +1,83 @@
diff --git a/node_modules/react-native-nitro-cloud-uploader/android/src/main/AndroidManifest.xml b/node_modules/react-native-nitro-cloud-uploader/android/src/main/AndroidManifest.xml
index 9ea903a..e023649 100644
--- a/node_modules/react-native-nitro-cloud-uploader/android/src/main/AndroidManifest.xml
+++ b/node_modules/react-native-nitro-cloud-uploader/android/src/main/AndroidManifest.xml
@@ -11,7 +11,7 @@
<!-- Required for background uploads -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<!-- Keep CPU awake during uploads -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
diff --git a/node_modules/react-native-nitro-cloud-uploader/android/src/main/java/com/margelo/nitro/nitroclouduploader/NitroCloudUploader.kt b/node_modules/react-native-nitro-cloud-uploader/android/src/main/java/com/margelo/nitro/nitroclouduploader/NitroCloudUploader.kt
index f93d4dc..f95a537 100644
--- a/node_modules/react-native-nitro-cloud-uploader/android/src/main/java/com/margelo/nitro/nitroclouduploader/NitroCloudUploader.kt
+++ b/node_modules/react-native-nitro-cloud-uploader/android/src/main/java/com/margelo/nitro/nitroclouduploader/NitroCloudUploader.kt
@@ -388,7 +388,7 @@ class NitroCloudUploader(
)
if (shouldNotify) {
- showNotification(uploadId, 100, "Upload complete!", isComplete = true)
+// showNotification(uploadId, 100, "Upload complete!", isComplete = true)
}
// ✅ Stop foreground service on success
@@ -414,7 +414,7 @@ class NitroCloudUploader(
println("❌ Upload failed: ${e.message}")
if (shouldNotify) {
- showNotification(uploadId, -1, "Upload failed", isComplete = true)
+// showNotification(uploadId, -1, "Upload failed", isComplete = true)
}
// ✅ Stop foreground service on failure
@@ -458,7 +458,7 @@ class NitroCloudUploader(
)
if (shouldNotify) {
- showNotification(uploadId, 0, "Starting upload...")
+// showNotification(uploadId, 0, "Starting upload...")
}
} catch (e: Exception) {
@@ -672,7 +672,7 @@ class NitroCloudUploader(
if (showNotification) {
val progressPercent = (progress * 100).toInt()
- showNotification(uploadId, progressPercent, "Uploading... ${progressPercent}%")
+// showNotification(uploadId, progressPercent, "Uploading... ${progressPercent}%")
// ✅ Update foreground service notification
val ctx = appContext // Cache to avoid smart cast issues
diff --git a/node_modules/react-native-nitro-cloud-uploader/android/src/main/java/com/margelo/nitro/nitroclouduploader/UploadForegroundService.kt b/node_modules/react-native-nitro-cloud-uploader/android/src/main/java/com/margelo/nitro/nitroclouduploader/UploadForegroundService.kt
index 466c212..e261329 100644
--- a/node_modules/react-native-nitro-cloud-uploader/android/src/main/java/com/margelo/nitro/nitroclouduploader/UploadForegroundService.kt
+++ b/node_modules/react-native-nitro-cloud-uploader/android/src/main/java/com/margelo/nitro/nitroclouduploader/UploadForegroundService.kt
@@ -134,7 +134,7 @@ class UploadForegroundService : Service() {
startForeground(
NOTIFICATION_ID,
notification,
- ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
)
} else {
startForeground(NOTIFICATION_ID, notification)
@@ -165,6 +165,7 @@ class UploadForegroundService : Service() {
isServiceStarted = false
stopForeground(STOP_FOREGROUND_REMOVE)
+ notificationManager.cancel(NOTIFICATION_ID);
stopSelf()
println("✅ Foreground service stopped")
}
@@ -201,7 +202,7 @@ class UploadForegroundService : Service() {
}
val builder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
- .setContentTitle("Cloud Uploader")
+ .setContentTitle("Uploading file")
.setContentText(message)
.setSmallIcon(android.R.drawable.stat_sys_upload)
.setPriority(NotificationCompat.PRIORITY_LOW)

View File

@@ -1,72 +0,0 @@
diff --git a/node_modules/react-native-pdf/android/.gradle/5.6.1/fileChanges/last-build.bin b/node_modules/react-native-pdf/android/.gradle/5.6.1/fileChanges/last-build.bin
deleted file mode 100644
index f76dd23..0000000
Binary files a/node_modules/react-native-pdf/android/.gradle/5.6.1/fileChanges/last-build.bin and /dev/null differ
diff --git a/node_modules/react-native-pdf/android/.gradle/5.6.1/fileHashes/fileHashes.lock b/node_modules/react-native-pdf/android/.gradle/5.6.1/fileHashes/fileHashes.lock
deleted file mode 100644
index 52d8866..0000000
Binary files a/node_modules/react-native-pdf/android/.gradle/5.6.1/fileHashes/fileHashes.lock and /dev/null differ
diff --git a/node_modules/react-native-pdf/android/.gradle/5.6.1/gc.properties b/node_modules/react-native-pdf/android/.gradle/5.6.1/gc.properties
deleted file mode 100644
index e69de29..0000000
diff --git a/node_modules/react-native-pdf/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/node_modules/react-native-pdf/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock
deleted file mode 100644
index 1508dc9..0000000
Binary files a/node_modules/react-native-pdf/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock and /dev/null differ
diff --git a/node_modules/react-native-pdf/android/.gradle/buildOutputCleanup/cache.properties b/node_modules/react-native-pdf/android/.gradle/buildOutputCleanup/cache.properties
deleted file mode 100644
index f4a32a7..0000000
--- a/node_modules/react-native-pdf/android/.gradle/buildOutputCleanup/cache.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-#Sat Oct 12 21:44:37 CST 2019
-gradle.version=5.6.1
diff --git a/node_modules/react-native-pdf/android/.gradle/vcs-1/gc.properties b/node_modules/react-native-pdf/android/.gradle/vcs-1/gc.properties
deleted file mode 100644
index e69de29..0000000
diff --git a/node_modules/react-native-pdf/android/build.gradle b/node_modules/react-native-pdf/android/build.gradle
index 3dfe1dc..70ef9da 100644
--- a/node_modules/react-native-pdf/android/build.gradle
+++ b/node_modules/react-native-pdf/android/build.gradle
@@ -126,6 +126,6 @@ dependencies {
// The repo from zacharee is based on PdfiumAndroidKt, a much newer fork of PdfiumAndroid, with better maintenance and updated native libraries.
implementation 'com.github.zacharee:AndroidPdfViewer:4.0.1'
// Depend on PdfiumAndroidKt directly so this can be updated independently of AndroidPdfViewer as updates are provided.
- implementation 'io.legere:pdfiumandroid:1.0.24'
+ implementation 'io.legere:pdfiumandroid:1.0.32'
implementation 'com.google.code.gson:gson:2.8.5'
}
diff --git a/node_modules/react-native-pdf/android/src/main/java/org/wonday/pdf/PdfView.java b/node_modules/react-native-pdf/android/src/main/java/org/wonday/pdf/PdfView.java
index 7b7a125..361924b 100644
--- a/node_modules/react-native-pdf/android/src/main/java/org/wonday/pdf/PdfView.java
+++ b/node_modules/react-native-pdf/android/src/main/java/org/wonday/pdf/PdfView.java
@@ -12,6 +12,8 @@ import java.io.File;
import android.content.ContentResolver;
import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
import android.util.SizeF;
import android.view.View;
import android.view.ViewGroup;
@@ -105,7 +107,7 @@ public class PdfView extends PDFView implements OnPageChangeListener,OnLoadCompl
TopChangeEvent tce = new TopChangeEvent(surfaceId, getId(), event);
if (dispatcher != null) {
- dispatcher.dispatchEvent(tce);
+ new Handler(Looper.getMainLooper()).postDelayed(() -> dispatcher.dispatchEvent(tce), 10);
}
// ReactContext reactContext = (ReactContext)this.getContext();
diff --git a/node_modules/react-native-pdf/index.js b/node_modules/react-native-pdf/index.js
index 56df005..dd14412 100644
--- a/node_modules/react-native-pdf/index.js
+++ b/node_modules/react-native-pdf/index.js
@@ -364,7 +364,6 @@ export default class Pdf extends Component {
}
_onChange = (event) => {
-
let message = event.nativeEvent.message.split('|');
//__DEV__ && console.log("onChange: " + message);
if (message.length > 0) {

View File

@@ -115,7 +115,13 @@ const EXTRA_ICON_NAMES = [
"update",
"notebook-minus",
"calendar-blank",
"email-newsletter"
"email-newsletter",
"cellphone-arrow-down",
"format-line-spacing",
"calendar-today",
"bomb",
"bomb-off",
"cancel"
];
const __filename = fileURLToPath(import.meta.url);

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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