Compare commits

...

399 Commits

Author SHA1 Message Date
Ammar Ahmed
94f2a216ad intl: fix strings.ts 2025-09-16 10:11:43 +05:00
Ammar Ahmed
78e3ee5fd2 Merge pull request #8572 from streetwriters/fix-ipad-split-view
Fix keyboard in iPad split view
2025-09-16 10:10:05 +05:00
Ammar Ahmed
1ddcf44eb0 Merge pull request #8551 from streetwriters/mobile-editor-ui-update
mobile: editor ui overhaul
2025-09-16 10:08:28 +05:00
Ammar Ahmed
5c5fb2ba2c Merge branch 'master' into mobile-editor-ui-update
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2025-09-16 10:08:10 +05:00
Ammar Ahmed
aa4995d871 Merge pull request #8525 from streetwriters/mobile-reminder-fixes
Fix reminders on mobile
2025-09-16 10:06:54 +05:00
Ammar Ahmed
f818c41a02 Merge pull request #8524 from streetwriters/mobile-fix-add-notebook-btn
Fix add notebook button in Notebooks sheet
2025-09-16 10:06:30 +05:00
Ammar Ahmed
3f9d954e2f Merge pull request #8508 from streetwriters/mobile-fix-notebook-tree
Fix Notebook Tree when Searching for Notebooks
2025-09-16 10:06:05 +05:00
Ammar Ahmed
f1fcd227e7 Merge pull request #8535 from streetwriters/mobile-fix-reset-navigation
mobile: fix resetting navigation
2025-09-16 10:04:54 +05:00
Ammar Ahmed
44d910121c Merge pull request #8507 from streetwriters/mobile-fix-string-email
Fix email confirmed description
2025-09-16 10:03:40 +05:00
Abdullah Atta
1258a95aad Merge pull request #8562 from streetwriters/fix/refresh-note-on-attachment-delete
Refresh note on attachment permanent delete
2025-09-16 09:43:15 +05:00
Ammar Ahmed
1379193065 mobile: fix keyboard in ipad split view 2025-09-16 09:11:34 +05:00
Abdullah Atta
a7fa42e944 web: refresh note on attachment permanent delete 2025-09-13 13:07:02 +05:00
Ammar Ahmed
02ab8e3938 mobile: editor ui improvements 2025-09-12 10:57:18 +05:00
Abdullah Atta
5c120676e1 Merge pull request #8550 from streetwriters/release/web/v3.2.4
Release v3.2.4
2025-09-12 08:47:48 +05:00
Abdullah Atta
3821dc29ef web: fix some editor tests on macos 2025-09-12 08:28:39 +05:00
Abdullah Atta
3c51f73427 web: fix cannot destructure property checkout of He as it is undefined 2025-09-12 08:28:39 +05:00
Abdullah Atta
8db373ee34 web: bump version to 3.2.4 2025-09-12 08:28:39 +05:00
Abdullah Atta
adcb73a2fc Merge pull request #8388 from streetwriters/fix/theme-issues-2
Fix theme related issues
2025-09-12 08:27:44 +05:00
Ammar Ahmed
4c97b3481d mobile: editor ui overhaul 2025-09-11 15:49:00 +05:00
Abdullah Atta
4785544e18 Merge pull request #8549 from 01zulfi/web/fix-toc-crash
web: fix crash when shift+click on toc item
2025-09-11 13:48:33 +05:00
01zulfi
7812c15b48 web: fix crash when shift+click on toc item
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-09-11 09:39:00 +05:00
Abdullah Atta
b841c6e0eb Merge pull request #8395 from 01zulfi/editor/image-resizing-dimensions
editor: show image dimensions during drag resizing
2025-09-09 14:51:44 +05:00
Abdullah Atta
55b6be0f15 Merge pull request #8542 from 01zulfi/editor/improve-insert-link-dialog
editor: if selected text is link, autofill url field in insert link popup
2025-09-09 13:07:18 +05:00
Abdullah Atta
dfe6a73ac1 Merge pull request #8546 from streetwriters/release/monograph/1.2.5
Release monographs v1.2.5
2025-09-09 12:53:20 +05:00
Abdullah Atta
afcb8835d2 ci: do not build for armv7 & armv8 platforms 2025-09-09 12:49:33 +05:00
Abdullah Atta
9dc9f186e1 monograph: use oven/bun:1.2.21-slim 2025-09-09 12:45:15 +05:00
Ammar Ahmed
875125cfe4 mobile: clear logs 2025-09-09 12:34:06 +05:00
Abdullah Atta
32d9cdd265 monograph: bump version to 1.2.5 2025-09-09 12:00:28 +05:00
Abdullah Atta
09deae9dbf Merge pull request #8545 from streetwriters/fix-monograph-report-not-working
Add report endpoint to connect-src content policy
2025-09-09 11:59:02 +05:00
Abdullah Atta
337582ac70 monograph: add report endpoint to connect-src content policy 2025-09-09 11:58:29 +05:00
01zulfi
820c78ce81 editor: if selected text is link, autofill url field in insert link popup
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-09-08 11:28:16 +05:00
Ammar Ahmed
565e692af8 mobile: fix crash during navigation 2025-09-05 12:40:20 +05:00
Ammar Ahmed
94e4721663 mobile: fix resetting navigation 2025-09-05 12:11:39 +05:00
Ammar Ahmed
8157c0812a mobile: reminder fixes
1. Allow editing reminder without changing date
2. Fix colors of buttons
3. Allow picking date and time in date picker dialog
4. Fix auto focus of title and description inputs
2025-09-05 11:30:18 +05:00
Ammar Ahmed
bc36837cd0 mobile: add missing buttons in reminder sheet 2025-09-04 14:26:47 +05:00
Ammar Ahmed
33540b000c mobile: show reminder description in properties 2025-09-04 14:26:33 +05:00
Ammar Ahmed
5a63a45d00 mobile: fix add notebook button 2025-09-04 11:43:36 +05:00
01zulfi
8fce6b5eb6 editor: show image dimensions during drag resizing
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-09-02 10:18:36 +05:00
Abdullah Atta
4e44661e1b Merge pull request #8487 from 01zulfi/web/reminder-search-delete
web: fix deleting a reminder not removing from search list
2025-08-29 11:43:15 +05:00
Abdullah Atta
81fd1c097e Merge pull request #8509 from 01zulfi/editor/handle-empty-link-hrefs
editor: don't show open & copy tools for empty links
2025-08-29 11:39:39 +05:00
01zulfi
aaf82f359b editor: don't show open & copy tools for empty links
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-08-28 11:45:12 +05:00
Ammar Ahmed
aa7f14ae79 mobile: fix email confirmed description 2025-08-28 10:12:32 +05:00
Ammar Ahmed
0ad63da4a2 mobile: fix tree items in search 2025-08-25 14:51:56 +05:00
Ammar Ahmed
2885b9ac3e mobile: fix notebook tree when searching notebooks 2025-08-25 13:08:29 +05:00
01zulfi
a1d8b34fb4 web: fix deleting a reminder not removing from search list
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-08-20 09:02:44 +05:00
Ammar Ahmed
874352e753 Merge pull request #8400 from streetwriters/mobile-fix-open-note-notification
Fix opening pinned note from notification on android
2025-08-20 08:49:55 +05:00
Ammar Ahmed
49d1ab4be5 Merge pull request #8401 from streetwriters/mobile-fix-notebook-tag-sorting-filter
Fix filtered tags and notebooks not sorted correctly
2025-08-20 08:49:21 +05:00
Ammar Ahmed
65a80dbd46 Merge pull request #8402 from streetwriters/mobile-fix-back-navigation
Fix back navigation on first app launch by resetting routes
2025-08-20 08:48:40 +05:00
Ammar Ahmed
e55456f492 Merge pull request #8467 from streetwriters/fix-cache-clear-error
mobile: fix error when clearing cache on android
2025-08-20 08:48:01 +05:00
Ammar Ahmed
3f9fce0d34 Merge pull request #8482 from streetwriters/fix-open-share-exported-notes-ios
mobile: fix sharing and opening exported notes on iOS
2025-08-20 08:47:21 +05:00
Ammar Ahmed
2ae094cf1f mobile: fix sharing and opening exported notes 2025-08-19 12:51:16 +05:00
Abdullah Atta
bc224f1674 Merge pull request #8466 from 01zulfi/web/list-pane-max-size
web: increase list pane max size to 1000 for large screens
2025-08-19 09:52:28 +05:00
01zulfi
8bb1582cca web: increase list pane max size to 1000
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-08-18 15:48:59 +05:00
Ammar Ahmed
d02125a93c mobile: fix error when clearing cache on android 2025-08-18 13:15:45 +05:00
Abdullah Atta
d91ce4e936 Merge pull request #8389 from 01zulfi/common/update-keybindings
common: update keybindings for editor search, link, internal link, and command palette
2025-08-13 13:30:39 +05:00
Abdullah Atta
deb902549b Merge pull request #8392 from 01zulfi/web/fix-open-settings-shortcut
web: fix open settings shortcut interfering with editor shortcut
2025-08-13 13:29:34 +05:00
Abdullah Atta
e41ed07805 Merge pull request #8385 from Scott-will/master
Handle pasting urls in html
2025-08-13 13:27:14 +05:00
Scott-will
f6e8dc0a88 editor: fix pasting html links with titles that look like links
Signed-off-by: Scott Williams scottwill1999@gmail.com

editor: update tests for html handling

Signed-off-by: Scott Williams scottwill1999@gmail.com

editor: add logic for markdown links

Signed-off-by: Scott Williams scottwill1999@gmail.com

editor: remove plugin in favour of markPasteRule

Signed-off-by: Scott Williams scottwill1999@gmail.com
2025-08-13 13:26:01 +05:00
Abdullah Atta
044dd5ce59 Merge pull request #8391 from 01zulfi/docs/update-desktop-instructions
docs: add missing instruction in desktop app dev setup
2025-08-13 12:21:12 +05:00
Ammar Ahmed
b24830666c Merge pull request #8409 from streetwriters/fix-fdroid-build
mobile: fix fdroid build
2025-07-31 09:10:13 +05:00
Ammar Ahmed
470802c3e2 mobile: fix fdroid build 2025-07-31 09:09:38 +05:00
Ammar Ahmed
7e0e1eac93 Merge pull request #8406 from streetwriters/mobile-fix-fdroid-build
mobile: compile lazysodium from source
2025-07-29 15:05:39 +05:00
Ammar Ahmed
58cbf653b2 mobile: compile lazysodium from source 2025-07-29 15:04:49 +05:00
Ammar Ahmed
4b35f2ecd2 mobile: fix back navigation on first app launch 2025-07-28 14:20:11 +05:00
Ammar Ahmed
48d9a863ec mobile: fix filtered tags and notebooks not sorted correctly 2025-07-28 13:42:46 +05:00
Ammar Ahmed
1d616cf68f mobile: fix opening note from notification on android 2025-07-28 11:56:54 +05:00
Ammar Ahmed
cdf6a4bb5b Merge pull request #8398 from streetwriters/mobile-release-3.2.10-fix
mobile: release v3.2.10
2025-07-26 12:48:28 +05:00
Ammar Ahmed
1b3735e2b2 mobile: release v3.2.10 2025-07-26 12:19:13 +05:00
Ammar Ahmed
850fc6b066 Merge pull request #8397 from streetwriters/mobile-release-3.2.9
mobile: release v3.2.9
2025-07-26 11:58:20 +05:00
Ammar Ahmed
8a62070c8e Merge pull request #8394 from streetwriters/mobile-editor-ux-improvement
Mobile editor ux improvement
2025-07-26 10:52:01 +05:00
Ammar Ahmed
8aa31b8380 mobile: fix exiting table not working 2025-07-25 14:24:14 +05:00
Ammar Ahmed
f445a963f9 mobile: improve ux when clicking empty area in editor 2025-07-25 12:44:55 +05:00
Ammar Ahmed
d6e3f27612 mobile: add scroll to top and bottom in editor 2025-07-25 12:44:34 +05:00
Ammar Ahmed
4688d74edb mobile: improvements to editor ux
1. Tapping on empty in editor opens the keyboard
2. Swiping to open the editor when editor is empty opens the keyboard automatically
2025-07-25 11:57:44 +05:00
Ammar Ahmed
1985007039 Merge pull request #8393 from streetwriters/mobile-fix-edit-notebook
mobile: fix edit notebook button causes app freeze on iOS
2025-07-25 11:01:03 +05:00
Ammar Ahmed
039c8faabe mobile: fix edit notebook button causes app freeze 2025-07-25 10:57:14 +05:00
01zulfi
fb1eb234b6 web: fix open settings shortcut interfering with editor shortcut
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-07-25 08:25:01 +05:00
01zulfi
7e70cd96b9 docs: add missing instruction in desktop app dev setup
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-07-24 15:56:17 +05:00
01zulfi
f9a89783ed common: update keybindings for editor search, link, internal link, and command palette
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-07-23 09:45:12 +05:00
Abdullah Atta
7f3c74b55b editor: fix theme related issues 2025-07-23 08:32:35 +05:00
Abdullah Atta
301ac781d6 Merge pull request #8387 from 01zulfi/docs/keyboard-shortcuts-heading
docs: add keyboard shortcuts heading
2025-07-23 08:31:41 +05:00
01zulfi
ce3c98ae9b docs: add keyboard shortcuts heading
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-07-22 16:28:07 +05:00
Abdullah Atta
2447b3bea7 Merge pull request #7655 from 01zulfi/global/keyboard
global: keyboard shortcuts documentation && web dialog
2025-07-22 14:21:30 +05:00
Abdullah Atta
63888ff70e web: improve keyboard shortcuts dialog UI 2025-07-22 14:20:15 +05:00
Abdullah Atta
c6e3d1bd47 common: New note should be in the General category 2025-07-22 14:20:15 +05:00
Abdullah Atta
e03e02d702 common: add license header to keybindings.ts 2025-07-22 14:20:15 +05:00
Abdullah Atta
1abf51ae2b common: getGroupedKeybindings should return an array
this is so the groups are always ordered. It also makes it easier to iterate
over the groups.
2025-07-22 14:20:15 +05:00
Abdullah Atta
c41dfa3185 common: letter keys should be upper cased
this is common in a lot of places and it provides better readability as well.
2025-07-22 14:20:15 +05:00
01zulfi
52a07da230 editor: remove unneeded code
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-07-22 14:20:15 +05:00
01zulfi
f572bb1637 editor: fix insertParagraph keys
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-07-22 14:20:15 +05:00
01zulfi
773ede4b6c common: improve key formatting
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-07-22 14:20:15 +05:00
01zulfi
e4d42c8bac web: add command to show keyboard shortcuts in command palette
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-07-22 14:20:01 +05:00
01zulfi
5b85067e15 web: improve contrast of keys text in keyboard shortcuts dialog
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-07-22 14:20:01 +05:00
01zulfi
0d111f3a79 web: use keys from common/keybindings in command palette dialog
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-07-22 14:20:01 +05:00
01zulfi
f6b6f2b4b7 web: support mac key formatting in keyboard shortcuts dialog
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-07-22 14:20:01 +05:00
01zulfi
d55a2aa04d common: support mac keyboard formatting
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-07-22 14:20:01 +05:00
01zulfi
0185eea740 editor: remove hardcoded keybind definitions
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-07-22 14:20:01 +05:00
01zulfi
d44e8874b8 docs: move keyboard shortcuts script to docs/help
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-07-22 14:20:01 +05:00
01zulfi
e34d33dd61 web: update key maps registeration
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-07-22 14:20:01 +05:00
01zulfi
4b2fb80c23 global: add keyboard shortcuts
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

global: (wip) move keyboard shortcuts metadata into common package
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

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

global: (wip) more refactoring
* minor refactor dialog
* refactor logic out from script file to keybindings file

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

common: filter keybindings based on platform && minor ui changes

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

editor: use keyboard keys from common package
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

web: remove unrelated title-box change
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

editor: add TODO:remove comments
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-07-22 14:19:46 +05:00
Abdullah Atta
3c908ac079 Merge pull request #8384 from streetwriters/themebuilder/release/1.4.0
Update theme builder to new UI
2025-07-22 09:08:02 +05:00
Abdullah Atta
563f068948 theme-builder: bump version to 1.4.0 2025-07-22 09:03:09 +05:00
Abdullah Atta
1344b958cb Merge pull request #8382 from 01zulfi/web/note-linking-dialog
web: migrate note linking dialog lists from tanstack virtual to virtuoso
2025-07-22 08:41:38 +05:00
01zulfi
32d7cab70c web: migrate note linking dialog lists from tanstack virtual to virtuoso
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-07-21 14:49:41 +05:00
Abdullah Atta
1293fa2dec Merge pull request #8365 from 01zulfi/web/fix-nav-pane-on-small-screens
web: fix nav pane on small screens
2025-07-21 12:23:50 +05:00
Abdullah Atta
8542cbf37e Merge pull request #8381 from streetwriters/fix/email-should-not-be-required-session-expiry
Email should not be required on session expiry
2025-07-21 12:19:42 +05:00
Abdullah Atta
62c6fba9a0 web: email should not be required on session expiry 2025-07-21 12:10:43 +05:00
Abdullah Atta
2d84154226 web: show hamburger menu button on mobile 2025-07-21 12:09:58 +05:00
01zulfi
c4e306d569 web: fix nav pane on small screens
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-07-21 11:56:45 +05:00
Abdullah Atta
e86f7491ef Merge pull request #8380 from streetwriters/ci/run-tests-on-pr-ref
Run tests on pr ref instead of base branch
2025-07-21 11:47:27 +05:00
Abdullah Atta
5151d83af9 ci: run tests on pr ref instead of base branch 2025-07-21 11:38:30 +05:00
Abdullah Atta
e462cb0596 Merge pull request #8379 from streetwriters/docs/remove-onenote-sn-docs
Remove standard notes and one note importer docs
2025-07-21 11:30:52 +05:00
Abdullah Atta
a30c0e4608 docs(help): remove standard notes and one note importer docs 2025-07-21 11:30:32 +05:00
Abdullah Atta
f90e7982f7 Merge pull request #8378 from streetwriters/ci/migrate-to-pr-target
Fix tests from PRs opened from external forks
2025-07-21 11:28:11 +05:00
Abdullah Atta
40c69d7cba ci: use pull_request_target with authorization hook where required 2025-07-21 11:26:03 +05:00
Abdullah Atta
23ee50f688 Merge pull request #8372 from streetwriters/fix/web-tests
Fix web E2E tests
2025-07-21 09:53:05 +05:00
Abdullah Atta
a7c857fb71 desktop: increase hookTimeout ton 120 seconds 2025-07-21 09:35:39 +05:00
Abdullah Atta
0f6622360c desktop: ignore error on failure to remove test artifacts 2025-07-21 09:28:26 +05:00
Abdullah Atta
578bc6e546 desktop: build app only once during tests 2025-07-21 09:19:53 +05:00
Abdullah Atta
70fd038207 desktop: add build retrying when running tests 2025-07-21 08:57:16 +05:00
Abdullah Atta
b30110a48a desktop: add support for running tests in parallel
we do this by copying the build directory instead of building it over and over
again for each test. This also makes the tests super fast.
2025-07-19 15:05:24 +05:00
Abdullah Atta
21978c4c1d desktop: patch better-sqlite3-multiple-ciphers on each test build 2025-07-19 12:32:22 +05:00
Abdullah Atta
ec48f2350d desktop: run tests sequentially
this is required since each test builds a new app and on linux & windows that
can cause issues if done in parallel
2025-07-19 11:02:32 +05:00
Abdullah Atta
fe1e3bb847 desktop: migrate tests to vitest 3 2025-07-19 11:01:57 +05:00
Abdullah Atta
69776f26d8 desktop: upgrade to vitest 3 2025-07-19 11:01:42 +05:00
Abdullah Atta
ab4092c088 desktop: fix dialog skipping in tests 2025-07-19 09:43:39 +05:00
Abdullah Atta
4948a8d7de desktop: add test artifacts cleanup on test finish 2025-07-19 09:43:23 +05:00
Abdullah Atta
0a607bce18 web: increase app lock test timeouts 2025-07-19 08:51:29 +05:00
Abdullah Atta
e59820e3d6 web: fix adding a one-time reminder before current time should not be possible 2025-07-19 08:50:22 +05:00
Abdullah Atta
85a2d25c1b web: wait for reminder dialog to close before proceeding in tests 2025-07-18 15:06:59 +05:00
Abdullah Atta
3bc7fc84f9 web: fix `if note is active in multiple tabs, moving the note to trash should
close those tabs` test
2025-07-18 14:45:06 +05:00
Abdullah Atta
249ce1ecbb web: remove new line at eof in test snapshots 2025-07-18 13:02:03 +05:00
Abdullah Atta
be7f8d9916 ci: run tests based on affected files on pr open 2025-07-18 12:59:04 +05:00
Abdullah Atta
0a0495f1a7 config: include editor/src/extensions/code-block/languages to build output 2025-07-18 12:51:11 +05:00
Abdullah Atta
2f7afa8556 web: update readonly edited note snapshots for linux & windows 2025-07-18 12:44:45 +05:00
Abdullah Atta
a6d0177153 Merge pull request #8366 from Scott-will/master
editor: maintain relative cursor location when list is sorted
2025-07-18 12:29:13 +05:00
Scott-will
b9e24888e7 editor: maintain relative cursor location when list is sorted
Signed-off-by: Scott Williams scottwill1999@gmail.com
2025-07-16 14:01:40 -07:00
Abdullah Atta
9017dae664 Merge pull request #8364 from 01zulfi/editor/fix-readonly-links
editor: fix internal links opening in new tab in readonly notes
2025-07-16 11:50:58 +05:00
01zulfi
94acc241d9 editor: fix internal links opening in new tab in readonly notes
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-07-16 11:47:09 +05:00
Ammar Ahmed
765f3a476d mobile: release v3.2.9 2025-07-16 11:26:19 +05:00
Ammar Ahmed
2cdb916233 Merge pull request #8363 from streetwriters/fix-app-lauch-crash-pixel-device
mobile: fix crash on app launch on pixel devices
2025-07-16 10:47:16 +05:00
Ammar Ahmed
5f14261aba mobile: fix crash on app launch on pixel devices 2025-07-16 10:11:05 +05:00
Ammar Ahmed
20138505c4 Merge pull request #8361 from streetwriters/mobile-release-3.2.8
mobile: release v3.2.8
2025-07-16 09:37:35 +05:00
Ammar Ahmed
49c09bfd85 mobile: release v3.2.8 2025-07-15 11:07:33 +05:00
Ammar Ahmed
475edf6732 Merge pull request #8356 from streetwriters/mobile-fix-sidebar-items-update
mobile: Fix sidebar items do not update after sync
2025-07-15 10:56:47 +05:00
Ammar Ahmed
76ea89b668 Merge pull request #8351 from streetwriters/mobile-fix-color-tag-long-press-crash
mobile: fix crash on long press color tag
2025-07-15 10:56:34 +05:00
Ammar Ahmed
56b3a8df6b Merge pull request #8349 from streetwriters/mobile-fix-android-nav-bar
mobile: fix nav bar handling on android 15 and above
2025-07-15 10:56:15 +05:00
Ammar Ahmed
e02de0c0f6 Merge pull request #8336 from streetwriters/mobile-fix-crash-on-move-notebook
Fix crash on moving notebook when no notebooks selected
2025-07-15 10:55:58 +05:00
Ammar Ahmed
84bfab2a72 mobile: fix sidebar items do not update after sync 2025-07-15 09:36:00 +05:00
Ammar Ahmed
a6825a2df2 mobile: fix bottom insets 2025-07-14 15:19:15 +05:00
Abdullah Atta
9ba58d1a46 Merge pull request #8350 from 01zulfi/web/hide-spell-checker-languages-on-mac
web: hide spell checker language selector on mac os
2025-07-14 12:57:33 +05:00
Ammar Ahmed
29a99d3f97 mobile: fix crash on long press color tag 2025-07-14 12:48:58 +05:00
01zulfi
a8b6fe78ba web: hide spell checker language selector on mac os
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-07-14 12:42:49 +05:00
Ammar Ahmed
4abd69b460 mobile: fix nav bar handling on android 15 and above 2025-07-14 12:21:43 +05:00
Abdullah Atta
5c55b49976 Merge pull request #8348 from 01zulfi/web/fix-link-in-importer
web: fix github link in importer setting
2025-07-14 11:53:39 +05:00
01zulfi
c932961dd4 web: fix github link in importer setting
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-07-14 11:31:30 +05:00
Abdullah Atta
47357c3e25 Merge pull request #8346 from 01zulfi/web/middle-click-new-note-button
web: create new tab on create note button's middle click
2025-07-14 10:52:12 +05:00
Abdullah Atta
3ec5a1cf2c Merge pull request #8345 from 01zulfi/web/fix-toolbar-bug
web: fix toolbar crash
2025-07-14 10:49:54 +05:00
01zulfi
13d8a7baf7 web: create new tab on create note button's middle click
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-07-14 09:21:19 +05:00
01zulfi
19b1e74d06 web: fix toolbar crash
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-07-14 09:12:16 +05:00
Ammar Ahmed
82de9b9b94 mobile: fix crash on moving notebook when no notebooks selected 2025-07-12 14:31:00 +05:00
Ammar Ahmed
867af58695 Merge pull request #8335 from streetwriters/mobile-release-3.2.7
mobile: release v3.2.7
2025-07-12 14:29:22 +05:00
Ammar Ahmed
cdd50f0258 mobile: release v3.2.7 2025-07-11 14:01:22 +05:00
Ammar Ahmed
8c8b92cc4e Merge pull request #8333 from streetwriters/mobile-fix-empty-note-bug
Fix notes open empty from notes list randomly
2025-07-11 13:57:47 +05:00
Ammar Ahmed
31dd1619f6 mobile: fix notes open empty from notes list 2025-07-11 12:54:39 +05:00
Ammar Ahmed
afb010e3ae Merge pull request #8331 from streetwriters/release-mobile-3.2.6
mobile: release v3.2.6
2025-07-11 11:15:19 +05:00
Abdullah Atta
ddebdcda43 Merge pull request #8330 from 01zulfi/monograph/fix-api-host-on-client 2025-07-11 10:51:35 +05:00
01zulfi
29c69418e9 monograph: fix API_HOST on client
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-07-11 09:44:09 +05:00
Ammar Ahmed
3e565e5195 mobile: release v3.2.6 2025-07-08 14:42:46 +05:00
Ammar Ahmed
1b352d6162 Merge pull request #8320 from streetwriters/mobile-fix-crash-on-boot-android
Fix boot crash on android
2025-07-08 14:40:08 +05:00
Ammar Ahmed
258aa77cdd mobile: fix boot crash on android 2025-07-08 14:23:25 +05:00
Ammar Ahmed
7789bd145c Merge pull request #8319 from streetwriters/mobile-fix-duplicate-body-notification
Fix reminder description duplicated in notification on iOS
2025-07-08 12:24:52 +05:00
Ammar Ahmed
c1457cc83f mobile: show description in notification body only 2025-07-08 12:20:40 +05:00
Ammar Ahmed
8b316d0abf mobile: release v3.2.5 2025-06-30 13:54:35 +05:00
Ammar Ahmed
def5059ff1 mobile: fix app stuck on logout on ios 2025-06-30 13:44:30 +05:00
Ammar Ahmed
e1febfae31 mobile: fix loading pdf screen not showing 2025-06-30 13:37:13 +05:00
Ammar Ahmed
82aa506902 mobile: fix pdf view gets stuck 2025-06-30 12:40:18 +05:00
Ammar Ahmed
de1aa40ecf mobile: fix sqlite3-fts5-html lib loading error on ios 2025-06-30 12:39:12 +05:00
Ammar Ahmed
53724b437e mobile: fix app store error invalid swift support 2025-06-28 14:24:46 +05:00
Ammar Ahmed
1592620141 mobile: release v3.2.4 2025-06-28 13:35:21 +05:00
Ammar Ahmed
28ef87a95b mobile: fix ts error 2025-06-28 13:33:24 +05:00
Ammar Ahmed
d4019eea99 mobile: fix sync taking too long to complete 2025-06-28 13:33:24 +05:00
Ammar Ahmed
d13b46cbba mobile: do not embed framework in appex on ios 2025-06-28 08:50:31 +05:00
Ammar Ahmed
f1eb215bb9 mobile: release v3.2.3 2025-06-27 14:11:28 +05:00
Ammar Ahmed
92c7e1a0c1 mobile: wrap add-reminder screen in keyboard avoiding view on ios 2025-06-27 13:54:50 +05:00
Ammar Ahmed
b81cc178b5 mobile: make add-reminder sheet a screen 2025-06-27 12:56:50 +05:00
Abdullah Atta
dd42b82dbf web: bump version to v3.2.3 2025-06-27 09:49:40 +05:00
Abdullah Atta
ca6b50081a web: remove navigating to Monographs test 2025-06-27 09:48:44 +05:00
Abdullah Atta
4fa27046bb global: update lockfiles 2025-06-27 09:44:46 +05:00
01zulfi
8cadc6eaa4 web: fix scrolling toolbar also scrolling editor (#8256)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-27 09:43:36 +05:00
Abdullah Atta
ccae629833 web: update sqlite3-fts5-html to 0.0.4 2025-06-27 09:43:36 +05:00
Abdullah Atta
4a1cbe632c desktop: update sqlite3-fts5-html to 0.0.4 2025-06-27 09:43:36 +05:00
Abdullah Atta
579813d93a core: update sqlite3-fts5-html to 0.0.4 2025-06-27 09:43:36 +05:00
01zulfi
00d00c02ea editor: don't clear nodes in clear formatting option (#8260)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-27 09:43:36 +05:00
Abdullah Atta
765949cc40 web: only subscribe to note title if its generated 2025-06-27 09:43:36 +05:00
01zulfi
87cea6a120 web: hide monograph list for not logged in users (#8258)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-27 09:43:36 +05:00
Ammar Ahmed
83526d4d46 mobile: fix android toolbar 2025-06-27 09:37:17 +05:00
Ammar Ahmed
fca4faa114 mobile: convert sheets with input to screens or simple modals 2025-06-27 09:36:26 +05:00
Ammar Ahmed
171628c7b9 mobile: fix sqlite fts5 2025-06-27 09:34:16 +05:00
Ammar Ahmed
d456132dd6 mobile: release v3.2.2 2025-06-21 13:24:24 +05:00
Abdullah Atta
4ac7941b6c web: bump version to v3.2.2 2025-06-21 13:03:04 +05:00
Abdullah Atta
e7f5463917 web: make trash actions multi selectable 2025-06-21 13:00:46 +05:00
Abdullah Atta
6e41ce459d common: bulk resolve notebooks & tags 2025-06-21 13:00:46 +05:00
Abdullah Atta
f5095fa643 core: fix hang when resolving recursive nested notebooks 2025-06-21 13:00:46 +05:00
Abdullah Atta
e6a5b85011 web: register fts5 extensions after db decryption 2025-06-21 13:00:46 +05:00
Abdullah Atta
409c5a2517 web: update sqlite to v3.49.2 2025-06-21 13:00:46 +05:00
Abdullah Atta
35cd6b101d desktop: update better-sqlite3-multiple-ciphers to v11.10.0 2025-06-21 13:00:46 +05:00
Ammar Ahmed
2440e28168 mobile: fix android toolbar hidden behind keyboard on android 15 2025-06-20 14:49:15 +05:00
Ammar Ahmed
0078d509c5 mobile: release v3.2.1 2025-06-18 11:25:33 +05:00
Ammar Ahmed
0c2d57716d mobile: fix app crash on android 15 & 16 2025-06-18 11:16:07 +05:00
Abdullah Atta
678aeb98c3 ci: release snap to stable track (instead of release track) 2025-06-18 10:35:56 +05:00
Abdullah Atta
5f422f4eb7 web: bump version to v3.2.1 2025-06-18 10:27:50 +05:00
Abdullah Atta
2b26524fcd web: bump version to v3.2.1-beta.0 2025-06-18 10:27:13 +05:00
Abdullah Atta
7326533cc1 ci: publish snap beta 2025-06-18 10:27:13 +05:00
Abdullah Atta
c8aae5be61 desktop: change snap base to core22 2025-06-18 10:27:13 +05:00
Abdullah Atta
be1a8bf849 docs: remove mention of importer.notesnook.com (#8218) 2025-06-17 13:59:38 +05:00
Ammar Ahmed
2ffc2a27a5 mobile: fix android build 2025-06-17 12:49:38 +05:00
Ammar Ahmed
b0a9f3320b mobile: fix ios workflow
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2025-06-17 12:17:36 +05:00
Ammar Ahmed
0ef844f08c mobile: update pods 2025-06-17 11:58:48 +05:00
Abdullah Atta
bbe00b0f9a ci: disable snap on arm64 2025-06-17 11:56:42 +05:00
Ammar Ahmed
4dcaf70346 mobile: release v3.2.0 2025-06-17 11:45:39 +05:00
Abdullah Atta
76e5e3758a desktop: fix `'environment/DISABLE_WAYLAND' property does not match the
required schema`
2025-06-17 11:42:58 +05:00
Abdullah Atta
f460772d75 web: bump version to v3.2.0 2025-06-17 11:01:02 +05:00
Abdullah Atta
1cf1605564 core: fix small tokens ignored from query 2025-06-17 10:49:42 +05:00
01zulfi
0abbd41ff2 web: fix archived notes showing in search results (#8216)
* web: fix archived notes showing in search results
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* core: make notes param required in lookup.notesWithHighlighting
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-17 10:26:15 +05:00
Abdullah Atta
f16818ea93 web: fix tag navigation from editor 2025-06-17 10:26:15 +05:00
Abdullah Atta
719ab6dff3 web: fix ui glitch in note linking dialog 2025-06-17 10:26:15 +05:00
01zulfi
ed50d8041e editor: fix failing snapshot tests (#8215)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-17 10:26:15 +05:00
01zulfi
9a0de8dff8 web: fix navigating to notebooks & tags from note item (#8211)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-17 10:26:15 +05:00
01zulfi
caba714798 core: fix notebooks parentId() tests (#8213)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-17 10:26:15 +05:00
01zulfi
154cf897b3 web: fix move notebook dialog (#8214)
* also use db.notebooks.parentId()

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-17 10:26:15 +05:00
Abdullah Atta
323e53a1ef global: regenerate package-lock files to fix CI (#8212) 2025-06-17 09:44:35 +05:00
Ammar Ahmed
9b097cf233 mobile: update deps 2025-06-16 14:52:06 +05:00
Ammar Ahmed
75cb2fe194 mobile: fix pin and unpin from notifications 2025-06-16 14:09:45 +05:00
Ammar Ahmed
7aa975457c mobile: support android 16 2025-06-16 14:09:45 +05:00
Abdullah Atta
1932d27f1c core: fix group header for search results hidden 2025-06-16 14:09:45 +05:00
01zulfi
f226bdbff1 docs: add monograph size limit (#8206)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-16 14:09:45 +05:00
Abdullah Atta
7843943130 web: bump version to v3.2.0-beta.9 2025-06-16 14:09:45 +05:00
Abdullah Atta
a92afab3fc core: remove search results count
it was inaccurate and confused users
2025-06-16 14:09:45 +05:00
Abdullah Atta
1354ee683f web: reduce trial2pro discount to 30% 2025-06-16 14:09:45 +05:00
Abdullah Atta
891e311374 desktop: do not use native menu for tag suggestions on macos 2025-06-16 14:09:45 +05:00
Ammar Ahmed
881099c2c9 mobile: release beta 4 2025-06-16 14:09:45 +05:00
Ammar Ahmed
2dde6b6150 mobile: make adding notebook a dialog
* make manage tags sheet a screen
2025-06-16 14:09:45 +05:00
Abdullah Atta
a1f75dfed3 editor: fix search popup removed on link hover 2025-06-16 14:09:45 +05:00
Abdullah Atta
493ee81c42 core: fix search results sorting not working 2025-06-16 14:09:45 +05:00
Abdullah Atta
7109a36006 web: bump version to v3.2.0-beta.8 2025-06-16 14:09:45 +05:00
Abdullah Atta
6fccac9aea core: take into account boolean parameters in query 2025-06-16 14:09:45 +05:00
Ammar Ahmed
fa43f97bb4 mobile: default tag 2025-06-16 14:09:45 +05:00
01zulfi
e3a973d61e web: fix navigation commands in command palette (#8070)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-16 14:09:45 +05:00
01zulfi
542a628435 web: add move notebook dialog (#8099)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2025-06-16 14:09:45 +05:00
Ammar Ahmed
fdd25a11e7 mobile: fix ci build 2025-06-16 14:09:45 +05:00
Abdullah Atta
449ae78700 web: bump version to v3.2.0-beta.7 2025-06-16 14:09:45 +05:00
Ammar Ahmed
6bda4a9a73 mobile: fix mmkv patch 2025-06-16 14:09:27 +05:00
Ammar Ahmed
2d316ed1ec mobile: beta release 2025-06-16 14:09:27 +05:00
Abdullah Atta
570ae00453 core: fix false positives appearing in search 2025-06-16 14:09:27 +05:00
Ammar Ahmed
c845f7c1e1 mobile: update deps 2025-06-16 14:09:27 +05:00
Abdullah Atta
659e963b39 core: remove porter tokenizer for stemming
it didn't work very well. It was stemming things like "one" into "on"
2025-06-16 14:09:27 +05:00
Ammar Ahmed
63b8f7ef91 mobile: fix key missing warning in search-result item 2025-06-16 14:09:27 +05:00
Abdullah Atta
841e229866 core: fix lookup tests 2025-06-16 14:09:27 +05:00
Abdullah Atta
4098a27df0 core: update htmlparser2 2025-06-16 14:09:27 +05:00
Ammar Ahmed
918824f26b core: skip decodine entities when parsing html 2025-06-16 14:09:27 +05:00
Ammar Ahmed
83735705d2 mobile: update lockfiles 2025-06-16 14:09:27 +05:00
Ammar Ahmed
8ac6ccb701 mobile: fix android build 2025-06-16 14:09:27 +05:00
Ammar Ahmed
9ae00db676 mobile: fix ios build on rn 77 2025-06-16 14:09:27 +05:00
Ammar Ahmed
c389a1ce4d mobile: update fts5-html patch 2025-06-16 14:09:27 +05:00
Ammar Ahmed
817a58611e mobile: remove patch 2025-06-16 14:09:27 +05:00
Ammar Ahmed
bfa0f09fa0 mobile: update patches 2025-06-16 14:09:27 +05:00
Ammar Ahmed
0e461c19ed mobile: upgrade to RN 0.77.2 2025-06-16 14:09:27 +05:00
Abdullah Atta
7b289f8e58 core: sort content matches 2025-06-16 14:09:27 +05:00
Abdullah Atta
071052ce37 core: only expand matching callout & outline list items 2025-06-16 14:09:27 +05:00
Abdullah Atta
ec6ad79586 ci: fix prerelease not setting correctly on gh release 2025-06-16 14:09:27 +05:00
Abdullah Atta
96205fd5b7 web: bump version to 3.2.0-beta.6 2025-06-16 14:09:27 +05:00
01zulfi
4028a29bdb web: add support for setting a tag as default (#8060)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-16 14:09:27 +05:00
01zulfi
d812024c25 editor: remove styling for sibling elements of double spaced paragraphs (#8144)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-16 14:09:27 +05:00
Ammar Ahmed
92f4e33ac7 mobile: release new beta 2025-06-16 14:09:27 +05:00
Ammar Ahmed
ad056e0e3b mobile: skip rendering if query changed during search 2025-06-16 14:09:27 +05:00
Abdullah Atta
dc40601fb5 web: discard results if query changed during search 2025-06-16 14:09:27 +05:00
Abdullah Atta
65f3338df4 core: auto expand outline & callout in search result 2025-06-16 14:09:27 +05:00
Abdullah Atta
4f9a5cb074 core: fix phrase queries not getting highlighted 2025-06-16 14:09:27 +05:00
Abdullah Atta
eb550e95dc web: fix click on match no opening note at desired location 2025-06-16 14:09:27 +05:00
Ammar Ahmed
84dbab5bdf mobile: enable regexp in sqlite 2025-06-16 14:09:27 +05:00
Ammar Ahmed
c2aff26dfc mobile: fix tab gets focused in loop 2025-06-16 14:09:27 +05:00
Abdullah Atta
5dd9ce9f25 desktop: add SQLITE_ENABLE_REGEXP define to better-sqlite3 2025-06-16 14:09:27 +05:00
Abdullah Atta
e5998f51e9 desktop: do not gitignore *.patch 2025-06-16 14:09:27 +05:00
Abdullah Atta
538f1f1910 web: bump version to 3.2.0-beta.5 2025-06-16 14:09:27 +05:00
Abdullah Atta
9c2fa4eb30 core: use intl for showing search results count 2025-06-16 14:09:26 +05:00
Abdullah Atta
ab04b13582 core: install @notesnook/intl 2025-06-16 14:09:26 +05:00
Abdullah Atta
a6ef69905e web: use plain list instead of tree for showing search results 2025-06-16 14:09:26 +05:00
Abdullah Atta
565a2396f1 web: enable regexp in sqlite 2025-06-16 14:09:26 +05:00
Abdullah Atta
5a395aca3c desktop: enable regexp in sqlite 2025-06-16 14:09:26 +05:00
Abdullah Atta
f99ee88499 core: speed up search 2025-06-16 14:09:26 +05:00
Ammar Ahmed
dc7e5dfa50 core: fix stop-words import 2025-06-16 14:09:26 +05:00
Abdullah Atta
4f1bb994f2 core: filter out stop words in search query 2025-06-16 14:09:26 +05:00
Abdullah Atta
6357a8f3a1 web: bump version to v3.2.0-beta.4 2025-06-16 14:09:26 +05:00
Abdullah Atta
6c083bd76d ci: build desktop app on ubuntu-22-04
this fixes `Error: /lib/x86_64-linux-gnu/libm.so.6: version `GLIBC_2.38'
not found` error
2025-06-16 14:09:26 +05:00
Abdullah Atta
c53fbb7441 web: fix editor not jumping to search result 2025-06-16 14:09:26 +05:00
Abdullah Atta
6c28075070 web: fix various type errors causing crashes 2025-06-16 14:09:26 +05:00
Abdullah Atta
bb027c6f25 core: bring back limit in lookup 2025-06-16 14:09:12 +05:00
Ammar Ahmed
7dc5daffe8 mobile: release v3.2.0-beta.1 2025-06-16 14:09:12 +05:00
Ammar Ahmed
3e4862d768 mobile: fix note search 2025-06-16 14:09:12 +05:00
Abdullah Atta
29b0c2eeda web: use notesWithHighlighting for full notes search 2025-06-16 14:09:12 +05:00
Abdullah Atta
08e5f889f8 core: bring back old notes search 2025-06-16 14:09:12 +05:00
Ammar Ahmed
bc198b4cd7 mobile: release v3.2.0-beta.0 2025-06-16 14:09:12 +05:00
Abdullah Atta
5c71b77b98 web: bump version to 3.2.0-beta.3 2025-06-16 14:09:12 +05:00
Ammar Ahmed
5660178760 mobile: search and sorting 2025-06-16 14:08:56 +05:00
Abdullah Atta
8dac907ead web: localize search results count 2025-06-16 14:08:56 +05:00
Abdullah Atta
5b7c8eaa70 web: hide search results on editor focus & search cancel 2025-06-16 14:08:55 +05:00
Abdullah Atta
7ab4a2210a editor: make search results style toggleable 2025-06-16 14:08:55 +05:00
Abdullah Atta
c861b11c5f web: fix note not scrolling to selected match 2025-06-16 14:08:55 +05:00
Abdullah Atta
4f540dfdcb web: fix note not opening from search results if its already opened 2025-06-16 14:08:55 +05:00
Abdullah Atta
0e0add2f71 web: use same search ux for notes everywhere 2025-06-16 14:08:55 +05:00
Abdullah Atta
29edb3a3c3 web: improve search results ui 2025-06-16 14:08:55 +05:00
Abdullah Atta
ff723e5c78 core: improve prefix/suffix generation for matches 2025-06-16 14:08:55 +05:00
Abdullah Atta
ea76929c80 web: add full support for highlighting results in notes search 2025-06-16 14:08:55 +05:00
Abdullah Atta
5d4d84bded editor: add support for nn-search-result node 2025-06-16 14:08:55 +05:00
Abdullah Atta
0faf267735 core: add raw html content with highlighting in results 2025-06-16 14:08:55 +05:00
Abdullah Atta
197e95e66f web: add support for highlighting in search results 2025-06-16 14:08:55 +05:00
Abdullah Atta
9d4ae71bfe core: add console.time tracing for notes search 2025-06-16 14:08:55 +05:00
Abdullah Atta
422869c3d2 core: improve search results highlighting 2025-06-16 14:08:55 +05:00
Abdullah Atta
b7d478088b core: fix title matches not merged when query contains short tokens 2025-06-16 14:08:55 +05:00
Abdullah Atta
1827cbeb93 core: bring back search result sorting for notes 2025-06-16 14:08:55 +05:00
Abdullah Atta
66041a45f6 core: add support for highlighting search results 2025-06-16 14:08:55 +05:00
Ammar Ahmed
ccb0c858a2 mobile: fix archive count not shown 2025-06-16 14:08:55 +05:00
Ammar Ahmed
659b937852 mobile: update fts5-html lib 2025-06-16 14:08:55 +05:00
Ammar Ahmed
de48cea9eb mobile: support sorting search results 2025-06-16 14:08:55 +05:00
Abdullah Atta
404e3147b0 core: add a new migration to reindex fts5 2025-06-16 14:08:55 +05:00
Abdullah Atta
c0d64ff674 web: update sqlite3-fts5-html to v0.0.3 2025-06-16 14:08:55 +05:00
Abdullah Atta
bbcce64549 web: show date created on search results if sort by is set to date created 2025-06-16 14:08:55 +05:00
Abdullah Atta
1ca30b8a5b web: add support for sorting in search 2025-06-16 14:08:55 +05:00
Abdullah Atta
22b037ea1d core: add support for sortable search 2025-06-16 14:08:55 +05:00
01zulfi
2d74e3d4c0 editor: support ctrl/cmd+click to open internal links in new tab (#8131)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-16 14:08:55 +05:00
01zulfi
1be0e4790e web: add 'move to top' for sub-notebooks (#8066)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-16 14:08:55 +05:00
Ammar Ahmed
3770091b58 mobile: handle crash in background on device boot 2025-06-16 14:08:55 +05:00
Ammar Ahmed
0c86f11e21 mobile: fix new reminder title/headline is not autofill 2025-06-16 14:08:55 +05:00
Ammar Ahmed
73bf283fd7 mobile: add line break when appending to note 2025-06-16 14:08:55 +05:00
Ammar Ahmed
208bd13cdd mobile: fix crash in settings -> user subscription 2025-06-16 14:08:55 +05:00
01zulfi
42887a1dd5 web: persist double spaced lines config on logout (#8097)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-16 14:08:55 +05:00
01zulfi
0ec2f07be0 web: fix readonly note not locking when vault is locked (#8092)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-16 14:08:55 +05:00
01zulfi
82b70d2951 web: add support for drag/dropping notes on tab strip (#8090)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-16 14:08:55 +05:00
Abdullah Atta
8da961f300 web: bump version to 3.2.0-beta.2 2025-06-16 14:08:55 +05:00
Abdullah Atta
4fba380f2d core: fix `expected previously executed migration 2 to be at index 1 but
10 was found in its place.`
2025-06-16 14:08:54 +05:00
01zulfi
e1b046ec0c core: fix db.settings.getTrashCleanupInterval return type (#8076)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-16 14:08:54 +05:00
01zulfi
5d01159e20 web: add support for removing recents in command palette using delete key (#8069)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-16 14:08:54 +05:00
01zulfi
aeef53df55 web: press shift+enter to create note in quick open (#8068)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-16 14:08:54 +05:00
Ammar Ahmed
29433c4149 editor: fix alignment items in callout heading (#8101)
* editor: fix alignment items in callout heading

* Update packages/editor/styles/styles.css

Signed-off-by: Abdullah Atta <thecodrr@protonmail.com>

---------

Signed-off-by: Abdullah Atta <thecodrr@protonmail.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2025-06-16 14:08:54 +05:00
Abdullah Atta
0edac2ee9d desktop: update patched better-sqlite3-multiple-ciphers to 11.6.1 2025-06-16 14:08:54 +05:00
Abdullah Atta
4607dc3084 global: regenerate package-lock.json for theme & ui packages 2025-06-16 14:08:54 +05:00
Ammar Ahmed
41babda9b7 mobile: fix error in build 2025-06-16 14:08:54 +05:00
Ammar Ahmed
0fec38610c mobile: add option to disable auto update check 2025-06-16 14:08:54 +05:00
Abdullah Atta
3ecc43d976 misc: include @notesnook/theme package when generating flatpak sources 2025-06-16 14:08:54 +05:00
Abdullah Atta
9730560fb7 desktop: downgrade playwright 2025-06-16 14:08:54 +05:00
Abdullah Atta
6c0e51dc9d desktop: fix flatpak sources generation 2025-06-16 14:08:54 +05:00
Abdullah Atta
2a313a0026 desktop: regenerate package-lock.json 2025-06-16 14:08:54 +05:00
Abdullah Atta
5d4e60b829 web: update package-lock.json 2025-06-16 14:08:54 +05:00
Abdullah Atta
c0666d8547 global: fix dependencies version differences 2025-06-16 14:08:24 +05:00
Abdullah Atta
a5fa261e3b global: update package-lock.json 2025-06-16 14:08:24 +05:00
Ammar Ahmed
fa8aa4d88f mobile: archive notes 2025-06-16 14:08:24 +05:00
Abdullah Atta
b63be2de15 web: bump version to 3.2.0-beta.1 2025-06-16 14:08:24 +05:00
Abdullah Atta
173caf41d0 core: fix query-transformer tests 2025-06-16 14:08:24 +05:00
Ammar Ahmed
30e4f55218 mobile: add sqlite3-fts5-html module 2025-06-16 14:08:24 +05:00
Abdullah Atta
f3895f534e desktop: install sqlite3-fts5-html 2025-06-16 14:08:24 +05:00
Abdullah Atta
ea14db77de web: update wa-sqlite to use sqlite3-fts5-html for stripping html 2025-06-16 14:08:24 +05:00
Abdullah Atta
9bb48f4896 core: fix short queries search 2025-06-16 14:08:24 +05:00
Abdullah Atta
62b5cc853b core: remove @orama/stopwords 2025-06-16 14:08:24 +05:00
Abdullah Atta
90900628a2 core: strip html when searching notes
this commit also properly fixes search queries containing
words less than 3 characters in length
2025-06-16 14:08:24 +05:00
Abdullah Atta
ebf98bc652 core: fix warnings in tests 2025-06-16 14:08:24 +05:00
Ammar Ahmed
ddd3fe6ac5 mobile: update better trigram 2025-06-16 14:08:24 +05:00
Abdullah Atta
2964e7a8f0 core: fix db migration 2025-06-16 14:08:24 +05:00
Abdullah Atta
f6d1f5890a core: wrap query with invalid symbols in quotes 2025-06-16 14:08:24 +05:00
Abdullah Atta
1cc0cf19c8 core: update sqlite-better-trigram to v0.0.3 2025-06-16 14:08:24 +05:00
01zulfi
efc4f3566a core: fix trash cleanup interval setting retrieval (#8045)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-16 14:08:06 +05:00
01zulfi
85b7760e1e web: add more archive test cases (#8040)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-16 14:08:06 +05:00
Abdullah Atta
40ee0ecf47 desktop: no need to externalize @resvg/resvg-js 2025-06-16 14:08:06 +05:00
Abdullah Atta
513c9ee8a7 desktop: remove icon from native menu 2025-06-16 14:08:06 +05:00
Abdullah Atta
2aacadc24d desktop: only show native menus on macos 2025-06-16 14:08:06 +05:00
Abdullah Atta
716bb67b96 desktop: show native menus where possible 2025-06-16 14:08:06 +05:00
01zulfi
916b6a17aa web: assign accent color to links (#8041)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-16 14:08:06 +05:00
Abdullah Atta
813401ea91 desktop: update packages 2025-06-16 14:08:06 +05:00
01zulfi
70e5efbcea desktop: upgrade electron to v34.0.0 (#8038)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-16 14:07:44 +05:00
01zulfi
6c8dc223f2 web: fix automatic backup reminder setting reverting to never (#8037)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-16 14:07:44 +05:00
01zulfi
5b09528e7e web: fix focus last active tab method (#8034)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-16 14:07:44 +05:00
01zulfi
5de5ffe068 editor: improve hover link popup position (#8011)
* move it down so it doesn't block the link

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-16 14:07:44 +05:00
Abdullah Atta
1b511c8ddd web: bump version to 3.2.0-beta.0 2025-06-16 14:07:44 +05:00
Abdullah Atta
68d1a3f50e web: update @notesnook-imoporter/core to 2.2.2 2025-06-16 14:06:37 +05:00
01zulfi
74edd466d4 web: fix notestore is not defined error (#8012)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-16 14:05:54 +05:00
Abdullah Atta
34aa21790c web: update @notesnook-importer/core to 2.2.1 2025-06-16 14:05:54 +05:00
01zulfi
e7ec9e2aa3 web: add support for archiving notes (#7810)
* web: archive notes
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* global: arhives -> archived
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-06-16 14:05:54 +05:00
tarynlucia
20bbb6dd5b ui: correct grammar in vault creation message (issue #7497) (#8188)
Signed-off-by: Taryn Eng <taryn.eng@gmail.com>
2025-06-10 11:18:11 +05:00
Abdullah Atta
f937b164e9 web: update lockfile 2025-06-06 11:16:34 +05:00
Abdullah Atta
5b4e888ddd web: fix infinite rerenders on checkout screen 2025-06-06 11:01:14 +05:00
Abdullah Atta
410d983842 web: chore bump version to v3.1.1 2025-05-22 11:27:10 +05:00
Abdullah Atta
16cd92f03a web: fix flicker when programatically updating title 2025-05-22 11:20:58 +05:00
Abdullah Atta
c6ee1e6303 core: do not allow empty titles 2025-05-22 11:20:44 +05:00
SB
0f00d0e508 docs: add missing '-' in build-steps for MacOS and Linux (AppImage) 2025-05-16 10:12:24 +05:00
01zulfi
21fdc39db7 web: fix list pane not expanding (#7963)
* web: fix list pane not expanding
* fix list pane not expanding to saved width on reload
* fix list pane not expanding to saved width on collapse

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

* web: collapse pane and set size to 0 when applicable
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

---------

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-04-29 12:51:19 +05:00
01zulfi
bb5d5a5731 core: segregate logic for note headline and note headline title generation (#7968)
* core: segregate logic for note headline and note headline title generation
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* core: minor refactor note headline title
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-04-29 09:27:35 +05:00
Ammar Ahmed
bf5e68a82e ci: fix build ios
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2025-04-28 14:03:48 +05:00
Ammar Ahmed
cc7f479627 ci: fix build ios
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2025-04-28 13:53:21 +05:00
Ammar Ahmed
9eb183f6b5 ci: fix build ios
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2025-04-28 13:42:39 +05:00
375 changed files with 768606 additions and 128275 deletions

View File

@@ -69,8 +69,6 @@ jobs:
run: |
sed -i -e 's/arguments/arguments "-DCMAKE_CXX_COMPILER_LAUNCHER=ccache",/g' apps/mobile/node_modules/react-native-mmkv-storage/android/build.gradle
sed -i -e 's/arguments/arguments "-DCMAKE_CXX_COMPILER_LAUNCHER=ccache",/g' apps/mobile/node_modules/react-native-reanimated/android/build.gradle
sed -i -e 's/defaultConfig {/ndkVersion safeExtGet('ndkVersion', "25.2.9519653")\n defaultConfig {/g' apps/mobile/node_modules/react-native-reanimated//android/build.gradle
sed -i -e 's/defaultConfig {/ndkVersion safeExtGet('ndkVersion', "25.2.9519653")\n defaultConfig {/g' apps/mobile/node_modules/react-native-mmkv-storage/android/build.gradle
- name: CCache Stats Before Build
run: ccache -sv

View File

@@ -69,8 +69,6 @@ jobs:
run: |
sed -i -e 's/arguments/arguments "-DCMAKE_CXX_COMPILER_LAUNCHER=ccache",/g' apps/mobile/node_modules/react-native-mmkv-storage/android/build.gradle
sed -i -e 's/arguments/arguments "-DCMAKE_CXX_COMPILER_LAUNCHER=ccache",/g' apps/mobile/node_modules/react-native-reanimated/android/build.gradle
sed -i -e 's/defaultConfig {/ndkVersion safeExtGet('ndkVersion', "25.2.9519653")\n defaultConfig {/g' apps/mobile/node_modules/react-native-reanimated//android/build.gradle
sed -i -e 's/defaultConfig {/ndkVersion safeExtGet('ndkVersion', "25.2.9519653")\n defaultConfig {/g' apps/mobile/node_modules/react-native-mmkv-storage/android/build.gradle
- name: CCache Stats Before Build
run: ccache -sv

View File

@@ -9,7 +9,13 @@ on:
- "packages/core/**"
# re-run workflow if workflow file changes
- ".github/workflows/core.tests.yml"
pull_request:
pull_request_target:
branches:
- "master"
paths:
- "packages/core/**"
# re-run workflow if workflow file changes
- ".github/workflows/core.tests.yml"
types:
- "ready_for_review"
- "opened"
@@ -17,11 +23,21 @@ on:
- "reopened"
jobs:
test:
runs-on: ubuntu-22.04
authorize:
environment: ${{ github.event_name == 'pull_request_target' &&
github.event.pull_request.head.repo.full_name != github.repository &&
'external' || 'internal' }}
runs-on: ubuntu-latest
steps:
- run: echo true
test:
needs: authorize
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.ref }}
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache

View File

@@ -45,7 +45,7 @@ on:
jobs:
build:
name: Build
runs-on: ubuntu-24.04
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -107,7 +107,8 @@ jobs:
name: Notesnook Desktop v${{ steps.app_metadata.outputs.app_version }}
tag_name: v${{ steps.app_metadata.outputs.app_version }}
files: ./notesnook_build_v${{ steps.app_metadata.outputs.app_version }}.zip
prerelease: ${{ endsWith(steps.app_metadata.outputs.app_version, '-beta') }}
prerelease: ${{ inputs.release-track == 'beta' }}
target_commitish: ${{ github.ref }}
- name: Generate flatpak sources
if: inputs.publish-github && inputs.build-linux
@@ -232,8 +233,8 @@ jobs:
xcrun altool --upload-package $package -t osx --apiKey $API_KEY_ID --apiIssuer $API_KEY_ISSUER_ID --apple-id ${{ steps.app_metadata.outputs.apple_app_id }} --bundle-id ${{ steps.app_metadata.outputs.app_bundle_id }} --bundle-short-version-string ${{ steps.app_metadata.outputs.app_version }} --bundle-version ${{ steps.app_metadata.outputs.bundle_version }}
working-directory: ./apps/desktop
build-linux:
name: Build for Linux
build-linux-x64:
name: Build for Linux x64
needs: build
if: inputs.build-linux
runs-on: ubuntu-22.04
@@ -250,7 +251,7 @@ jobs:
run: echo "SNAPCRAFT_STORE_CREDENTIALS=${{ secrets.snapcraft_token }}" >> $GITHUB_ENV
- name: Install Snapcraft
uses: samuelmeuli/action-snapcraft@v1
uses: samuelmeuli/action-snapcraft@v3
if: inputs.publish-snap
- name: Download build
@@ -266,7 +267,6 @@ jobs:
- name: Install sqlite-better-trigram for all arch
run: |
npm i --cpu arm64 sqlite-better-trigram
npm i --cpu x64 sqlite-better-trigram
working-directory: ./apps/desktop
@@ -285,18 +285,86 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ ${{ inputs.publish-github }} == true ]; then
yarn electron-builder --config=electron-builder.config.js --linux AppImage:x64 AppImage:arm64 -p always
yarn electron-builder --config=electron-builder.config.js --linux AppImage:x64 -p always
else
yarn electron-builder --config=electron-builder.config.js --linux AppImage:x64 AppImage:arm64 -p never
yarn electron-builder --config=electron-builder.config.js --linux AppImage:x64 -p never
fi
working-directory: ./apps/desktop
- name: Publish on Snapcraft
if: inputs.publish-snap
run: |
snapcraft upload --release=stable ./output/notesnook_linux_amd64.snap
if [ ${{ inputs.release-track }} == 'beta' ]; then
snapcraft upload --release=beta ./output/notesnook_linux_amd64.snap
else
snapcraft upload --release=stable ./output/notesnook_linux_amd64.snap
fi
working-directory: ./apps/desktop
build-linux-arm64:
name: Build for Linux arm64
needs: build
if: inputs.build-linux
runs-on: ubuntu-22.04-arm
steps:
- name: Check out Git repository
uses: actions/checkout@v4
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
# - name: Setup Snapcraft Auth
# if: inputs.publish-snap
# run: echo "SNAPCRAFT_STORE_CREDENTIALS=${{ secrets.snapcraft_token }}" >> $GITHUB_ENV
# - name: Install Snapcraft
# uses: samuelmeuli/action-snapcraft@v1
# if: inputs.publish-snap
- name: Download build
uses: actions/download-artifact@v4
with:
name: build
path: ./apps/web/build
- name: Install packages
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=desktop
- name: Install sqlite-better-trigram for all arch
run: |
npm i --cpu arm64 sqlite-better-trigram
working-directory: ./apps/desktop
- name: Build Electron wrapper
run: npx nx run release --project @notesnook/desktop
working-directory: ./apps/desktop
# - name: Build snap
# if: inputs.publish-snap
# run: |
# yarn electron-builder --config=electron-builder.config.js --linux snap:arm64 -p never
# working-directory: ./apps/desktop
- name: Build AppImage
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ ${{ inputs.publish-github }} == true ]; then
yarn electron-builder --config=electron-builder.config.js --linux AppImage:arm64 -p always
else
yarn electron-builder --config=electron-builder.config.js --linux AppImage:arm64 -p never
fi
working-directory: ./apps/desktop
# - name: Publish on Snapcraft
# if: inputs.publish-snap
# run: |
# snapcraft upload --release=stable ./output/notesnook_linux_arm64.snap
# working-directory: ./apps/desktop
build-windows:
name: Build for Windows
needs: build

View File

@@ -10,6 +10,12 @@ on:
# re-run workflow if workflow file changes
- ".github/workflows/desktop.tests.yml"
pull_request:
branches:
- "master"
paths:
- "app/desktop/**"
# re-run workflow if workflow file changes
- ".github/workflows/desktop.tests.yml"
jobs:
build:

View File

@@ -10,6 +10,12 @@ on:
# re-run workflow if workflow file changes
- ".github/workflows/editor.tests.yml"
pull_request:
branches:
- "master"
paths:
- "packages/editor/**"
# re-run workflow if workflow file changes
- ".github/workflows/editor.tests.yml"
types:
- "ready_for_review"
- "opened"

View File

@@ -4,7 +4,7 @@ on: workflow_dispatch
jobs:
build:
runs-on: macos-latest
runs-on: macos-14
timeout-minutes: 60
steps:
@@ -14,6 +14,11 @@ jobs:
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "16.1"
- name: Install node modules
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
@@ -54,7 +59,10 @@ jobs:
key: ${{ runner.os }}-pods-${{ hashFiles('apps/mobile/native/ios/Podfile.lock') }}
- name: Install Pods
run: npm run prepare:ios
run: |
cd apps/mobile/native/ios
bundle install
RCT_NEW_ARCH_ENABLED=0 bundle exec pod install
- name: CCache Stats Before Build
run: ccache -sv

View File

@@ -45,7 +45,7 @@ jobs:
- name: Docker Setup Buildx
uses: docker/setup-buildx-action@v3
with:
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v8
platforms: linux/amd64,linux/arm64
- name: Log in to Docker Hub
uses: docker/login-action@v3
@@ -77,7 +77,7 @@ jobs:
context: apps/monograph
file: apps/monograph/Dockerfile
push: true
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v8
platforms: linux/amd64,linux/arm64
tags: streetwriters/monograph:${{ steps.package_metadata.outputs.app_version }},streetwriters/monograph:latest
cache-from: streetwriters/monograph:latest

View File

@@ -9,7 +9,13 @@ on:
- "apps/web/**"
# re-run workflow if workflow file changes
- ".github/workflows/web.tests.yml"
pull_request:
pull_request_target:
branches:
- "master"
paths:
- "apps/web/**"
# re-run workflow if workflow file changes
- ".github/workflows/web.tests.yml"
types:
- "ready_for_review"
- "opened"
@@ -17,12 +23,23 @@ on:
- "reopened"
jobs:
authorize:
environment: ${{ github.event_name == 'pull_request_target' &&
github.event.pull_request.head.repo.full_name != github.repository &&
'external' || 'internal' }}
runs-on: ubuntu-latest
steps:
- run: echo true
build:
needs: authorize
name: Build
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.ref }}
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
@@ -54,6 +71,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.ref }}
- name: Download build
uses: actions/download-artifact@v4

2
.gitignore vendored
View File

@@ -11,4 +11,4 @@ nx-cloud.env
site
node_modules.backup
.nx
*.patch
/*.patch

View File

@@ -66,7 +66,7 @@ We take all queries, issues and bug reports that you might have. Feel free to as
## Additional Resources
- [Migrating & Importing your data from other apps — Importer](https://importer.notesnook.com/)
- [Migrating & Importing your data from other apps — Importer](https://help.notesnook.com/importing-notes)
- [Privacy policy](https://notesnook.com/privacy) & [Terms of service](https://notesnook.com/terms)
- [Verify Notesnook encryption claims yourself — Vericrypt](https://vericrypt.notesnook.com/)
- [Why Notesnook requires an email address?](https://blog.notesnook.com/why-notesnook-requires-an-email-address/)

View File

@@ -24,7 +24,7 @@ Requirements:
Before you can do anything, you'll need to [install Node.js](https://nodejs.org/en/download/) v16 or later on your system.
Once you have completed the setup, the first step is to `clone` the monorepo:
1. `clone` the monorepo:
```bash
git clone https://github.com/streetwriters/notesnook.git
@@ -33,19 +33,28 @@ git clone https://github.com/streetwriters/notesnook.git
cd notesnook
```
Once you are inside the `./notesnook` directory, run the preparation step:
2. Install dependencies:
```bash
# this might take a while to complete
npm install
```
Now you can finally start the desktop app for development:
3. Run the webapp for desktop environment:
```bash
cd apps/web
npm run start:desktop
```
4. In a separate terminal session, run the desktop app from the root of the project:
```bash
npm run start:desktop
```
### Release mode
To run the app in release mode:
```bash
@@ -58,10 +67,10 @@ This will compile and run the app in production mode but it won't generate any p
npm run release -- --rebuild
# For macOS
npx electron-builder --config=electron-builder.config.js --mac dmg --arm64 --x64 -publish never
npx electron-builder --config=electron-builder.config.js --mac dmg --arm64 --x64 --publish never
# For Linux (AppImage)
npx electron-builder --config=electron-builder.config.js --linux AppImage:x64 AppImage:arm64 -publish never
npx electron-builder --config=electron-builder.config.js --linux AppImage:x64 AppImage:arm64 --publish never
# For Windows
npx electron-builder --config=electron-builder.config.js --win --publish never

View File

@@ -17,230 +17,226 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { test } from "vitest";
import { harness } from "./utils.js";
import { testCleanup, test } from "./test-override.js";
import { writeFile } from "fs/promises";
import { Page } from "playwright";
import { gt, lt } from "semver";
import { describe } from "vitest";
test("update starts downloading if version is outdated", async (t) => {
await harness(
t,
async ({ page }) => {
await page.waitForSelector("#authForm");
test("update starts downloading if version is outdated", async ({
ctx: { page },
expect,
onTestFinished
}) => {
onTestFinished(testCleanup);
t.expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page.waitForSelector("#authForm");
expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
await skipDialog(page);
await skipDialog(page);
await page.waitForSelector(".ProseMirror");
await page.waitForSelector(".ProseMirror");
await page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /updating/i })
.waitFor({ state: "attached" });
},
{ version: "3.0.0" }
);
await page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /updating/i })
.waitFor({ state: "attached" });
});
test("update is only shown if version is outdated and auto updates are disabled", async (t) => {
await harness(
t,
async (ctx) => {
await ctx.app.close();
await writeFile(
ctx.configPath,
JSON.stringify({
automaticUpdates: false
})
);
test("update is only shown if version is outdated and auto updates are disabled", async ({
ctx,
expect,
onTestFinished
}) => {
onTestFinished(testCleanup);
await ctx.relaunch();
await ctx.app.close();
await writeFile(
ctx.configPath,
JSON.stringify({
automaticUpdates: false
})
);
const { page } = ctx;
await ctx.relaunch();
await page.waitForSelector("#authForm");
const { page } = ctx;
t.expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page.waitForSelector("#authForm");
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await skipDialog(page);
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
await page.waitForSelector(".ProseMirror");
await skipDialog(page);
await page.waitForSelector(".ProseMirror");
await page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /available/i })
.waitFor({ state: "attached" });
});
describe("update to stable if it is newer", () => {
test.scoped({ options: { version: "3.0.0-beta.0" } });
test("test", async ({ ctx, expect, onTestFinished }) => {
onTestFinished(testCleanup);
await ctx.app.close();
await writeFile(
ctx.configPath,
JSON.stringify({
automaticUpdates: false,
releaseTrack: "beta"
})
);
await ctx.relaunch();
const { page } = ctx;
await page.waitForSelector("#authForm");
expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
await skipDialog(page);
await page.waitForSelector(".ProseMirror");
const updateButton = page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /available/i });
await updateButton.waitFor({ state: "visible" });
const content = await updateButton.textContent();
const version = content?.split(" ")?.[0] || "";
expect(gt(version, "3.0.0-beta.0")).toBe(true);
});
});
describe("update is not available if it latest stable version is older", () => {
test.scoped({ options: { version: "99.0.0-beta.0" } });
test("test", async ({ ctx, expect, onTestFinished }) => {
onTestFinished(testCleanup);
await ctx.app.close();
await writeFile(
ctx.configPath,
JSON.stringify({
automaticUpdates: false,
releaseTrack: "beta"
})
);
await ctx.relaunch();
const { page } = ctx;
await page.waitForSelector("#authForm");
expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
await skipDialog(page);
await page.waitForSelector(".ProseMirror");
await page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /checking for updates/i })
.waitFor({ state: "hidden" });
expect(
await page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /available/i })
.waitFor({ state: "attached" });
},
{ version: "3.0.0" }
);
.isHidden()
).toBe(true);
});
});
test("update to stable if it is newer", async (t) => {
await harness(
t,
async (ctx) => {
await ctx.app.close();
await writeFile(
ctx.configPath,
JSON.stringify({
automaticUpdates: false,
releaseTrack: "beta"
})
);
describe("downgrade to stable on switching to stable release track", () => {
test.scoped({ options: { version: "99.0.0-beta.0" } });
test("test", async ({ ctx, expect, onTestFinished }) => {
onTestFinished(testCleanup);
await ctx.relaunch();
await ctx.app.close();
await writeFile(
ctx.configPath,
JSON.stringify({
automaticUpdates: false,
releaseTrack: "stable"
})
);
const { page } = ctx;
await ctx.relaunch();
await page.waitForSelector("#authForm");
const { page } = ctx;
t.expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page.waitForSelector("#authForm");
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await skipDialog(page);
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
await page.waitForSelector(".ProseMirror");
await skipDialog(page);
const updateButton = page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /available/i });
await updateButton.waitFor({ state: "visible" });
const content = await updateButton.textContent();
const version = content?.split(" ")?.[0] || "";
t.expect(gt(version, "3.0.0-beta.0")).toBe(true);
},
{ version: "3.0.0-beta.0" }
);
});
await page.waitForSelector(".ProseMirror");
test("update is not available if it latest stable version is older", async (t) => {
await harness(
t,
async (ctx) => {
await ctx.app.close();
await writeFile(
ctx.configPath,
JSON.stringify({
automaticUpdates: false,
releaseTrack: "beta"
})
);
await page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /checking for updates/i })
.waitFor({ state: "hidden" });
await ctx.relaunch();
const { page } = ctx;
await page.waitForSelector("#authForm");
t.expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
await skipDialog(page);
await page.waitForSelector(".ProseMirror");
await page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /checking for updates/i })
.waitFor({ state: "hidden" });
t.expect(
await page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /available/i })
.isHidden()
).toBe(true);
},
{ version: "99.0.0-beta.0" }
);
});
test("downgrade to stable on switching to stable release track", async (t) => {
await harness(
t,
async (ctx) => {
await ctx.app.close();
await writeFile(
ctx.configPath,
JSON.stringify({
automaticUpdates: false,
releaseTrack: "stable"
})
);
await ctx.relaunch();
const { page } = ctx;
await page.waitForSelector("#authForm");
t.expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
await skipDialog(page);
await page.waitForSelector(".ProseMirror");
await page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /checking for updates/i })
.waitFor({ state: "hidden" });
const updateButton = page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /available/i });
await updateButton.waitFor({ state: "visible" });
const content = await updateButton.textContent();
const version = content?.split(" ")?.[0] || "";
t.expect(lt(version, "99.0.0-beta.0")).toBe(true);
},
{ version: "99.0.0-beta.0" }
);
const updateButton = page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /available/i });
await updateButton.waitFor({ state: "visible" });
const content = await updateButton.textContent();
const version = content?.split(" ")?.[0] || "";
expect(lt(version, "99.0.0-beta.0")).toBe(true);
});
});
async function skipDialog(page: Page) {
await page
.waitForSelector(".ReactModal__Content", {
timeout: 1000
})
.catch(() => {})
.then(async () => {
const positiveButton = page.locator(
"button[data-role='positive-button']"
);
const negativeButton = page.locator(
"button[data-role='negative-button']"
);
if (await positiveButton.isVisible()) await positiveButton.click();
else if (await negativeButton.isVisible()) await negativeButton.click();
});
try {
const dialog = page.locator(".ReactModal__Content");
const positiveButton = dialog.locator(
"button[data-role='positive-button']"
);
const negativeButton = dialog.locator(
"button[data-role='negative-button']"
);
if (await positiveButton.isVisible())
await positiveButton.click({ timeout: 1000 });
else if (await negativeButton.isVisible())
await negativeButton.click({ timeout: 1000 });
} catch (e) {
// ignore error
}
}

View File

@@ -0,0 +1,24 @@
/*
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 { buildApp } from "./utils";
export default async function setup() {
await buildApp();
}

View File

@@ -17,22 +17,24 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { test } from "vitest";
import { harness } from "./utils.js";
import assert from "assert";
import { testCleanup, test } from "./test-override.js";
test("make sure app loads", async (t) => {
await harness(t, async ({ page }) => {
await page.waitForSelector("#authForm");
test("make sure app loads", async ({
ctx: { page },
expect,
onTestFinished
}) => {
onTestFinished(testCleanup);
assert.ok(
await page.getByRole("button", { name: "Create account" }).isVisible()
);
await page.waitForSelector("#authForm");
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page.waitForSelector(".ProseMirror");
});
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
await page.waitForSelector(".ProseMirror");
});

View File

@@ -0,0 +1,64 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { test as vitestTest, TestContext } from "vitest";
import { buildAndLaunchApp, Fixtures, TestOptions } from "./utils";
import { mkdir, rm } from "fs/promises";
import path from "path";
import slugify from "slugify";
export const test = vitestTest.extend<Fixtures>({
options: { version: "3.0.0" } as TestOptions,
ctx: async ({ options }, use) => {
const ctx = await buildAndLaunchApp(options);
await use(ctx);
}
});
export async function testCleanup(context: TestContext) {
const ctx = (context.task.context as unknown as Fixtures).ctx;
if (context.task.result?.state === "fail") {
await mkdir("test-results", { recursive: true });
await ctx.page.screenshot({
path: path.join(
"test-results",
`${slugify(context.task.name)}-${process.platform}-${
process.arch
}-error.png`
)
});
}
await ctx.app.close();
await rm(ctx.userDataDir, {
force: true,
recursive: true,
maxRetries: 3,
retryDelay: 5000
}).catch(() => {
/*ignore */
});
await rm(ctx.outputDir, {
force: true,
recursive: true,
maxRetries: 3,
retryDelay: 5000
}).catch(() => {
/*ignore */
});
}

View File

@@ -18,146 +18,235 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { execSync } from "child_process";
import { mkdir } from "fs/promises";
import { cp, readFile, writeFile } from "fs/promises";
import { fileURLToPath } from "node:url";
import path from "path";
import path, { join, resolve } from "path";
import { _electron as electron } from "playwright";
import slugify from "slugify";
import { TaskContext } from "vitest";
import { existsSync } from "fs";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const IS_DEBUG = process.env.NN_DEBUG === "true" || process.env.CI === "true";
const productName = `NotesnookTestHarness`;
const SOURCE_DIR = resolve("output", productName);
interface AppContext {
export interface AppContext {
app: import("playwright").ElectronApplication;
page: import("playwright").Page;
configPath: string;
userDataDir: string;
outputDir: string;
relaunch: () => Promise<void>;
}
interface TestOptions {
export interface TestOptions {
version: string;
}
export async function harness(
t: TaskContext,
cb: (ctx: AppContext) => Promise<void>,
options?: TestOptions
) {
const ctx = await buildAndLaunchApp(options);
t.onTestFinished(async (result) => {
if (result.state === "fail") {
await mkdir("test-results", { recursive: true });
await ctx.page.screenshot({
path: path.join(
"test-results",
`${slugify(t.task.name)}-${process.platform}-${
process.arch
}-error.png`
)
});
}
await ctx.app.close();
});
await cb(ctx);
export interface Fixtures {
options: TestOptions;
ctx: AppContext;
}
async function buildAndLaunchApp(options?: TestOptions): Promise<AppContext> {
const productName = makeid(10);
const executablePath = await buildApp({ ...options, productName });
const { app, page, configPath } = await launchApp(executablePath);
export async function buildAndLaunchApp(
options?: TestOptions
): Promise<AppContext> {
const productName = `notesnooktest${makeid(10)}`;
const outputDir = path.join("test-artifacts", `${productName}-output`);
const executablePath = await copyBuild({
...options,
outputDir
});
const { app, page, configPath, userDataDir } = await launchApp(
executablePath,
productName
);
const ctx: AppContext = {
app,
page,
configPath,
userDataDir,
outputDir,
relaunch: async () => {
const { app, page, configPath } = await launchApp(executablePath);
const { app, page, configPath, userDataDir } = await launchApp(
executablePath,
productName
);
ctx.app = app;
ctx.page = page;
ctx.userDataDir = userDataDir;
ctx.configPath = configPath;
}
};
return ctx;
}
async function launchApp(executablePath: string) {
async function launchApp(executablePath: string, packageName: string) {
const userDataDir = resolve(
__dirname,
"..",
"test-artifacts",
"user_data_dirs",
packageName
);
const app = await electron.launch({
executablePath,
args: IS_DEBUG ? [] : ["--hidden"],
env:
process.platform === "linux"
env: {
...(process.platform === "linux"
? {
...(process.env as Record<string, string>),
APPIMAGE: "true"
}
: (process.env as Record<string, string>)
: (process.env as Record<string, string>)),
CUSTOM_USER_DATA_DIR: userDataDir
}
});
const page = await app.firstWindow();
const userDataDirectory = await app.evaluate((a) => {
return a.app.getPath("userData");
});
const configPath = path.join(userDataDirectory, "config.json");
const configPath = path.join(userDataDir, "UserData", "config.json");
return {
app,
page,
configPath
configPath,
userDataDir
};
}
async function buildApp({
let MAX_RETRIES = 3;
export async function buildApp(version?: string) {
if (!existsSync(SOURCE_DIR)) {
const args = [
"electron-builder",
"--dir",
`--${process.arch}`,
`--config electron-builder.config.js`,
`--c.extraMetadata.productName=${productName}`,
`--c.compression=store`,
"--publish=never"
];
if (version) args.push(`--c.extraMetadata.version=${version}`);
try {
execSync(`npx ${args.join(" ")}`, {
stdio: IS_DEBUG ? "inherit" : "ignore",
env: {
...process.env,
NOTESNOOK_STAGING: "true",
NN_PRODUCT_NAME: productName,
NN_APP_ID: `com.notesnook.test.${productName}`,
NN_OUTPUT_DIR: SOURCE_DIR
}
});
} catch (e) {
if (--MAX_RETRIES) {
console.log("retrying...");
return await buildApp(version);
} else throw e;
}
}
}
async function copyBuild({
version,
productName
outputDir
}: {
version?: string;
productName: string;
outputDir: string;
}) {
const buildRoot = path.join("test-artifacts", `${productName}-build`);
const output = path.join("test-artifacts", `${productName}-output`);
execSync(`npm run release -- --root ${buildRoot} --skip-tsc-build`, {
stdio: IS_DEBUG ? "inherit" : "ignore"
});
return process.platform === "win32"
? await makeBuildCopyWindows(outputDir, productName, version)
: process.platform === "darwin"
? await makeBuildCopyMacOS(outputDir, productName, version)
: await makeBuildCopyLinux(outputDir, productName, version);
}
const args = [
`--config electron-builder.config.js`,
`--c.extraMetadata.productName=${productName}`,
"--publish=never"
];
if (version) args.push(`--c.extraMetadata.version=${version}`);
execSync(`npx electron-builder --dir --${process.arch} ${args.join(" ")}`, {
stdio: IS_DEBUG ? "inherit" : "ignore",
env: {
...process.env,
NOTESNOOK_STAGING: "true",
NN_BUILD_ROOT: buildRoot,
NN_PRODUCT_NAME: productName,
NN_APP_ID: `com.notesnook.test.${productName}`,
NN_OUTPUT_DIR: output
}
});
return path.join(
async function makeBuildCopyLinux(
outputDir: string,
productName: string,
version?: string
) {
const platformDir =
process.arch === "arm64" ? "linux-arm64-unpacked" : "linux-unpacked";
const appDir = await makeBuildCopy(
outputDir,
platformDir,
"resources",
version
);
return resolve(
__dirname,
"..",
output,
process.platform === "linux"
? process.arch === "arm64"
? "linux-arm64-unpacked"
: "linux-unpacked"
: process.platform === "darwin"
? process.arch === "arm64"
? `mac-arm64/${productName}.app/Contents/MacOS/`
: `mac/${productName}.app/Contents/MacOS/`
: "win-unpacked",
process.platform === "win32" ? `${productName}.exe` : productName
appDir,
productName.toLowerCase().replace(/\s+/g, "-")
);
}
async function makeBuildCopyWindows(
outputDir: string,
productName: string,
version?: string
) {
const platformDir =
process.arch === "arm64" ? "win-arm64-unpacked" : "win-unpacked";
const appDir = await makeBuildCopy(
outputDir,
platformDir,
"resources",
version
);
return resolve(__dirname, "..", appDir, `${productName}.exe`);
}
async function makeBuildCopyMacOS(
outputDir: string,
productName: string,
version?: string
) {
const platformDir = process.arch === "arm64" ? "mac-arm64" : "mac";
const appDir = await makeBuildCopy(
outputDir,
platformDir,
join(`${productName}.app`, "Contents", "Resources"),
version
);
return resolve(
__dirname,
"..",
appDir,
`${productName}.app`,
"Contents",
"MacOS",
productName
);
}
async function makeBuildCopy(
outputDir: string,
platformDir: string,
resourcesDir: string,
version?: string
) {
const appDir = outputDir;
await cp(join(SOURCE_DIR, platformDir), outputDir, {
recursive: true,
preserveTimestamps: true,
verbatimSymlinks: true,
dereference: false,
force: true
});
const packageJsonPath = join(appDir, resourcesDir, "app", "package.json");
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf-8"));
if (version) {
packageJson.version = version;
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
}
return appDir;
}
function makeid(length: number) {
let result = "";
const characters =

View File

@@ -38,6 +38,9 @@ const linuxExecutableName = process.env.NN_PRODUCT_NAME
const year = new Date().getFullYear();
const isBeta = pkg.version.includes("-beta");
/**
* @type {import("app-builder-lib").Configuration}
*/
module.exports = {
appId: appId,
productName: productName,
@@ -57,6 +60,7 @@ module.exports = {
"node_modules/bindings",
"node_modules/node-gyp-build",
"node_modules/sqlite-better-trigram",
"node_modules/sqlite3-fts5-html",
"node_modules/sodium-native/prebuilds/${platform}-${arch}",
{
from: "node_modules/sqlite-better-trigram-linux-${arch}",
@@ -70,6 +74,20 @@ module.exports = {
from: "node_modules/sqlite-better-trigram-windows-${arch}",
to: "node_modules/sqlite-better-trigram-windows-${arch}"
},
{
from: "node_modules/sqlite3-fts5-html-linux-${arch}",
to: "node_modules/sqlite3-fts5-html-linux-${arch}"
},
{
from: "node_modules/sqlite3-fts5-html-darwin-${arch}",
to: "node_modules/sqlite3-fts5-html-darwin-${arch}"
},
{
from: "node_modules/sqlite3-fts5-html-windows-${arch}",
to: "node_modules/sqlite3-fts5-html-windows-${arch}"
},
"node_modules/sodium-native/index.js",
"node_modules/sodium-native/package.json"
],
@@ -160,29 +178,27 @@ module.exports = {
description: "Your private note taking space",
executableName: linuxExecutableName,
desktop: {
actions: [
{
id: "new-note",
name: "New note",
args: "new note"
desktopActions: {
"new-note": {
Name: "New note",
Exec: `${linuxExecutableName} new note`
},
{
id: "new-notebook",
name: "New notebook",
args: "new notebook"
"new-notebook": {
Name: "New notebook",
Exec: `${linuxExecutableName} new notebook`
},
{
id: "new-reminder",
name: "New reminder",
args: "new reminder"
"new-reminder": {
Name: "New reminder",
Exec: `${linuxExecutableName} new reminder`
}
]
}
}
},
snap: {
autoStart: false,
confinement: "strict",
allowNativeWayland: true
allowNativeWayland: true,
base: "core22"
},
extraResources: ["app-update.yml", "./assets/**"],
extraMetadata: {

File diff suppressed because it is too large Load Diff

View File

@@ -2,13 +2,15 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "3.1.0",
"version": "3.2.4",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
"types": "./dist/types/index.d.ts",
"sideEffects": false,
"sideEffects": [
"src/overrides.ts"
],
"exports": {
".": {
"require": {
@@ -27,37 +29,42 @@
"dependencies": {
"@lingui/core": "5.1.2",
"@notesnook/intl": "file:../../packages/intl",
"@notesnook/ui": "file:../../packages/ui",
"@trpc/client": "10.45.2",
"@trpc/server": "10.45.2",
"better-sqlite3-multiple-ciphers": "11.5.0",
"better-sqlite3-multiple-ciphers": "^11.10.0",
"electron-trpc": "0.7.1",
"electron-updater": "^6.3.4",
"icojs": "^0.19.4",
"sqlite-better-trigram": "0.0.2",
"electron-updater": "^6.6.2",
"icojs": "^0.19.5",
"sqlite-better-trigram": "0.0.3",
"sqlite3-fts5-html": "^0.0.4",
"typed-emitter": "^2.1.0",
"yargs": "^17.7.2",
"zod": "3.23.8"
"zod": "3.24.3"
},
"devDependencies": {
"@streetwriters/kysely": "^0.27.4",
"@types/node": "22.7.0",
"@types/node": "22.15.3",
"@types/yargs": "^17.0.33",
"chokidar": "^4.0.1",
"electron": "^31.7.4",
"electron-builder": "^25.1.8",
"chokidar": "^4.0.3",
"electron": "^34.0.0",
"electron-builder": "^26.0.12",
"esbuild": "0.21.5",
"node-abi": "^3.68.0",
"node-gyp-build": "^4.8.2",
"playwright": "^1.48.2",
"node-abi": "^4.5.0",
"node-gyp-build": "^4.8.4",
"playwright": "1.48.2",
"prebuildify": "^6.0.1",
"slugify": "1.6.6",
"tree-kill": "^1.2.2",
"undici": "^6.19.8",
"vitest": "^2.1.8"
"undici": "^7.8.0",
"vitest": "^3.2.4"
},
"optionalDependencies": {
"dmg-license": "^1.0.11"
},
"overrides": {
"@electron/node-gyp": "^10.2.0-electron.2"
},
"scripts": {
"start": "node scripts/dev.mjs",
"staging": "node scripts/build.mjs --run",

View File

@@ -1,36 +0,0 @@
diff --git a/node_modules/app-builder-lib/out/targets/LinuxTargetHelper.js b/node_modules/app-builder-lib/out/targets/LinuxTargetHelper.js
index 739a48c..7a7b4af 100644
--- a/node_modules/app-builder-lib/out/targets/LinuxTargetHelper.js
+++ b/node_modules/app-builder-lib/out/targets/LinuxTargetHelper.js
@@ -117,6 +117,7 @@ class LinuxTargetHelper {
StartupWMClass: appInfo.productName,
...extra,
...targetSpecificOptions.desktop,
+ actions: undefined
};
const description = this.getDescription(targetSpecificOptions);
if (!(0, builder_util_1.isEmptyOrSpaces)(description)) {
@@ -160,6 +161,23 @@ class LinuxTargetHelper {
data += `\n${name}=${desktopMeta[name]}`;
}
data += "\n";
+
+ if (targetSpecificOptions.desktop.actions) {
+ let actionsData = "";
+ const validActions = [];
+ for (const action of targetSpecificOptions.desktop.actions) {
+ if (!action.id || !action.name || !action.args) continue;
+ actionsData += "\n";
+ actionsData += `[Desktop Action ${action.id}]
+Name=${action.name}
+Exec=${desktopMeta.Exec} ${action.args}`;
+ actionsData += "\n";
+
+ validActions.push(action.id);
+ }
+
+ data += `Actions=${validActions.join(";")};\n${actionsData}`
+ }
return Promise.resolve(data);
}
}

View File

@@ -0,0 +1,13 @@
diff --git a/node_modules/app-builder-lib/out/targets/snap.js b/node_modules/app-builder-lib/out/targets/snap.js
index 0b5405e..753f6c6 100644
--- a/node_modules/app-builder-lib/out/targets/snap.js
+++ b/node_modules/app-builder-lib/out/targets/snap.js
@@ -115,7 +115,7 @@ class SnapTarget extends core_1.Target {
else {
const archTriplet = archNameToTriplet(arch);
appDescriptor.environment = {
- DISABLE_WAYLAND: options.allowNativeWayland ? "" : "1",
+ DISABLE_WAYLAND: options.allowNativeWayland ? "0" : "1",
PATH: "$SNAP/usr/sbin:$SNAP/usr/bin:$SNAP/sbin:$SNAP/bin:$PATH",
SNAP_DESKTOP_RUNTIME: "$SNAP/gnome-platform",
LD_LIBRARY_PATH: [

View File

@@ -0,0 +1,11 @@
diff --git a/node_modules/better-sqlite3-multiple-ciphers/deps/defines.gypi b/node_modules/better-sqlite3-multiple-ciphers/deps/defines.gypi
index 1a14ecd..ff938f1 100644
--- a/node_modules/better-sqlite3-multiple-ciphers/deps/defines.gypi
+++ b/node_modules/better-sqlite3-multiple-ciphers/deps/defines.gypi
@@ -38,5 +38,6 @@
'SQLITE_TRACE_SIZE_LIMIT=32',
'SQLITE_USER_AUTHENTICATION=0',
'SQLITE_USE_URI=0',
+ 'SQLITE_ENABLE_REGEXP',
],
}

View File

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

View File

@@ -43,8 +43,7 @@ if (args.rebuild || !existsSync(path.join(webAppPath, "build"))) {
);
}
// temporary until there's support for prebuilt binaries for linux ARM
if (os.platform() === "linux") await patchBetterSQLite3();
await patchBetterSQLite3();
await fs.cp(path.join(webAppPath, "build"), path.join(root, "build"), {
recursive: 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 { readFile, writeFile } from "fs/promises";
import { readFile, rm, writeFile } from "fs/promises";
import path from "path";
import { fileURLToPath } from "url";
@@ -25,6 +25,8 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export async function patchBetterSQLite3() {
console.log("Patching better-sqlite3");
const jsonPath = path.join(
__dirname,
"..",
@@ -34,15 +36,23 @@ export async function patchBetterSQLite3() {
);
const json = JSON.parse(await readFile(jsonPath, "utf-8"));
json.version = "11.5.1";
json.homepage = "https://github.com/thecodrr/better-sqlite3-multiple-ciphers";
json.repository.url =
"git://github.com/thecodrr/better-sqlite3-multiple-ciphers.git";
delete json.homepage;
delete json.repository;
await writeFile(jsonPath, JSON.stringify(json));
await rm(
path.join(
__dirname,
"..",
"node_modules",
"better-sqlite3-multiple-ciphers",
"build"
),
{ force: true, recursive: true }
);
}
if (process.argv[1] === __filename) {
console.log("Patching better-sqlite3");
patchBetterSQLite3();
}

View File

@@ -19,7 +19,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { initTRPC } from "@trpc/server";
import { z } from "zod";
import { app, dialog, nativeTheme, Notification, shell } from "electron";
import {
app,
dialog,
Menu,
MenuItem,
nativeImage,
nativeTheme,
Notification,
shell
} from "electron";
import { AutoLaunch } from "../utils/autolaunch";
import { config, DesktopIntegration } from "../utils/config";
import { bringToFront } from "../utils/bring-to-front";
@@ -33,6 +42,7 @@ import { isFlatpak, isSnap } from "../utils";
import { setupDesktopIntegration } from "../utils/desktop-integration";
import { rm } from "fs/promises";
import { disableCustomDns, enableCustomDns } from "../utils/custom-dns";
import type { MenuItem as NNMenuItem } from "@notesnook/ui";
const t = initTRPC.create();
@@ -225,5 +235,63 @@ export const osIntegrationRouter = t.router({
nativeTheme.off("updated", updated);
};
})
)
),
showMenu: t.procedure
.input(
z.object({
menuItems: z.array(z.any())
})
)
.subscription(({ input: { menuItems } }) =>
observable<string[]>((emit) => {
const items = menuItems as NNMenuItem[];
const menu = new Menu();
for (const item of items) {
const menuItem = toMenuItem(item, (id) => emit.next(id));
if (menuItem) menu.append(menuItem);
}
if (menu.items.length > 0) menu.popup();
return () => {
menu.removeAllListeners();
menu.closePopup();
};
})
)
});
function toMenuItem(
item: NNMenuItem,
onClick: (id: string[]) => void,
parentKey?: string
): MenuItem | undefined {
switch (item.type) {
case "lazy-loader":
return undefined;
case "separator":
return new MenuItem({ type: "separator" });
case "button": {
const submenu = item.menu ? new Menu() : undefined;
if (submenu && item.menu) {
for (const subitem of item.menu.items) {
const subMenuItem = toMenuItem(subitem, onClick, item.key);
if (subMenuItem) submenu.append(subMenuItem);
}
}
return new MenuItem({
label: item.title,
enabled: !item.isDisabled,
visible: !item.isHidden,
toolTip: item.tooltip,
sublabel: item.tooltip,
checked: item.isChecked,
type: submenu ? "submenu" : item.isChecked ? "checkbox" : "normal",
id: item.key,
submenu,
click: () => onClick(parentKey ? [parentKey, item.key] : [item.key]),
accelerator: item.modifier?.replace("Mod", "CommandOrControl")
});
}
}
}

View File

@@ -33,6 +33,8 @@ export class SQLite {
initialized = false;
preparedStatements: Map<string, Statement<unknown[]>> = new Map();
retryCounter: Record<string, number> = {};
extensionsLoaded = false;
constructor() {
console.log("new sqlite worker");
}
@@ -46,8 +48,6 @@ export class SQLite {
this.sqlite = require("better-sqlite3-multiple-ciphers")(
filePath
).unsafeMode(true);
const betterTrigram = require("sqlite-better-trigram");
betterTrigram.load(this.sqlite);
}
/**
@@ -115,6 +115,20 @@ export class SQLite {
} catch (e) {
if (e instanceof Error) e.message += ` (query: ${sql})`;
throw e;
} finally {
// Since SQLite 3.48.0 (SQLite3MC v2.0.2) it's not possible to load fts5
// extensions before database has been decrypting. This is because
// executing a `SELECT` now accesses the underlying databases resulting in
// an error. Since FTS5 extensions depend on `SELECT fts5` to load the
// fts5 API, we must wait decrypt the database before we can load
// the extensions.
if (!this.extensionsLoaded && (await this.isDatabaseReady())) {
const betterTrigram = require("sqlite-better-trigram");
const fts5Html = require("sqlite3-fts5-html");
betterTrigram.load(this.sqlite);
fts5Html.load(this.sqlite);
this.extensionsLoaded = true;
}
}
}
@@ -142,4 +156,22 @@ export class SQLite {
retryDelay: 500
});
}
/**
* This just executes `SELECT 1` on the database to make sure its ready.
* On an encrypted database, this will fail until `PRAGMA key` has been
* called.
*/
private async isDatabaseReady() {
// return this.exec(`SELECT 1;`)
// .then(() => true)
// .catch(() => false);
if (!this.sqlite) return false;
try {
this.sqlite.prepare(`SELECT 1;`).run();
return true;
} catch {
return false;
}
}
}

View File

@@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import "./overrides";
import { app, BrowserWindow, nativeTheme, shell } from "electron";
import { isDevelopment } from "./utils";
import { registerProtocol, PROTOCOL_URL } from "./utils/protocol";

View File

@@ -0,0 +1,35 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { app } from "electron";
import path from "path";
if (process.env.CUSTOM_USER_DATA_DIR) {
app.setPath(
"appData",
path.join(process.env.CUSTOM_USER_DATA_DIR, "AppData")
);
app.setPath(
"userData",
path.join(process.env.CUSTOM_USER_DATA_DIR, "UserData")
);
app.setPath(
"documents",
path.join(process.env.CUSTOM_USER_DATA_DIR, "Documents")
);
}

View File

@@ -143,16 +143,44 @@ function setupMenu() {
})
);
if (params.isEditable)
if (params.isEditable) {
menu.append(
new MenuItem({
label: strings.paste(),
role: "pasteAndMatchStyle",
role: "paste",
enabled: clipboard.readText("clipboard").length > 0,
accelerator: "CommandOrControl+V"
})
);
menu.append(
new MenuItem({
label:
process.platform === "darwin"
? strings.pasteAndMatchStyle()
: strings.pasteWithoutFormatting(),
role: "pasteAndMatchStyle",
enabled: clipboard.readText("clipboard").length > 0,
accelerator:
process.platform === "darwin"
? "Option+Shift+Command+V"
: "Shift+CommandOrControl+V"
})
);
menu.append(
new MenuItem({
type: "separator"
})
);
menu.append(
new MenuItem({
label: strings.spellCheck(),
role: "toggleSpellChecker"
})
);
}
if (menu.items.length > 0) menu.popup();
});
}

View File

@@ -21,11 +21,13 @@ import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
testTimeout: process.env.CI ? 120 * 1000 : 120 * 1000,
testTimeout: 120 * 1000,
hookTimeout: 120 * 1000,
sequence: {
concurrent: true,
shuffle: true
},
globalSetup: "./__tests__/global-setup.ts",
dir: "./__tests__/",
exclude: [
"**/node_modules/**",

View File

@@ -69,6 +69,7 @@ buck-out/
*/fastlane/report.xml
*/fastlane/Preview.html
*/fastlane/screenshots
native/vendor
# Bundle artifact
*.jsbundle

View File

@@ -137,6 +137,7 @@ export async function deleteFile(
export async function clearFileStorage() {
try {
await createCacheDir();
const files = await RNFetchBlob.fs.ls(cacheDir);
const oldCache = await RNFetchBlob.fs.ls(cacheDirOld);
@@ -221,6 +222,7 @@ export async function deleteCacheFileByName(name: string) {
}
export async function deleteDCacheFiles() {
await createCacheDir();
const files = await RNFetchBlob.fs.ls(cacheDir);
for (const file of files) {
if (file.includes("_dcache") || file.startsWith("NN_")) {

View File

@@ -167,6 +167,7 @@ const Actions = ({
setAttachments();
eSendEvent(eDBItemUpdate, attachment.id);
}
return true;
},
positiveText: strings.rename()
});

View File

@@ -17,26 +17,20 @@ 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 React, { useRef, useState } from "react";
import { View } from "react-native";
import { db } from "../../common/database";
import {
eSendEvent,
presentSheet,
ToastManager
} from "../../services/event-manager";
import BackupService from "../../services/backup";
import { eSendEvent, ToastManager } from "../../services/event-manager";
import Navigation from "../../services/navigation";
import { useUserStore } from "../../stores/use-user-store";
import { eCloseSheet, eOpenRecoveryKeyDialog } from "../../utils/events";
import DialogHeader from "../dialog/dialog-header";
import { eOpenRecoveryKeyDialog } from "../../utils/events";
import { DefaultAppStyles } from "../../utils/styles";
import { Dialog } from "../dialog";
import { Button } from "../ui/button";
import Input from "../ui/input";
import { Notice } from "../ui/notice";
import Seperator from "../ui/seperator";
import { Dialog } from "../dialog";
import BackupService from "../../services/backup";
import { sleep } from "../../utils/time";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../utils/styles";
export const ChangePassword = () => {
const passwordInputRef = useRef();
@@ -85,8 +79,7 @@ export const ChangePassword = () => {
context: "global"
});
setLoading(false);
eSendEvent(eCloseSheet);
await sleep(300);
Navigation.goBack();
eSendEvent(eOpenRecoveryKeyDialog);
} catch (e) {
setLoading(false);
@@ -108,9 +101,6 @@ export const ChangePassword = () => {
}}
>
<Dialog context="change-password-dialog" />
<DialogHeader title={strings.changePassword()} />
<Seperator />
<Input
fwdRef={oldPasswordInputRef}
onChangeText={(value) => {
@@ -160,9 +150,3 @@ export const ChangePassword = () => {
</View>
);
};
ChangePassword.present = () => {
presentSheet({
component: <ChangePassword />
});
};

View File

@@ -39,6 +39,7 @@ import { hideAuth } from "./common";
import { ForgotPassword } from "./forgot-password";
import { useLogin } from "./use-login";
import { DefaultAppStyles } from "../../utils/styles";
import { Dialog } from "../dialog";
const LoginSteps = {
emailAuth: 1,
@@ -89,7 +90,7 @@ export const Login = ({ changeMode }) => {
return (
<>
<ForgotPassword />
<SheetProvider context="two_factor_verify" />
<Dialog context="two_factor_verify" />
<View
style={{
borderRadius: DDS.isTab ? 5 : 0,

View File

@@ -24,12 +24,8 @@ import { View } from "react-native";
import { ScrollView } from "react-native-actions-sheet";
import { db } from "../../common/database/index";
import useTimer from "../../hooks/use-timer";
import {
eSendEvent,
presentSheet,
ToastManager
} from "../../services/event-manager";
import { eCloseSheet } from "../../utils/events";
import { eSendEvent, ToastManager } from "../../services/event-manager";
import { eCloseSimpleDialog } from "../../utils/events";
import { AppFontSize } from "../../utils/size";
import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
@@ -38,6 +34,7 @@ import { Pressable } from "../ui/pressable";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { DefaultAppStyles } from "../../utils/styles";
import { presentDialog } from "../dialog/functions";
const TwoFactorVerification = ({ onMfaLogin, mfaInfo, onCancel }) => {
const { colors } = useThemeColors();
@@ -46,7 +43,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo, onCancel }) => {
method: mfaInfo?.primaryMethod,
isPrimary: true
});
const { seconds, start } = useTimer(currentMethod.method);
const { seconds, start, reset } = useTimer(currentMethod.method);
const [loading, setLoading] = useState(false);
const inputRef = useRef();
const [sending, setSending] = useState(false);
@@ -62,7 +59,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo, onCancel }) => {
},
(result) => {
if (result) {
eSendEvent(eCloseSheet, "two_factor_verify");
eSendEvent(eCloseSimpleDialog, "two_factor_verify");
}
setLoading(false);
}
@@ -133,6 +130,12 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo, onCancel }) => {
<ScrollView
keyboardShouldPersistTaps="handled"
keyboardDismissMode="interactive"
style={{
width: "100%",
height: "100%",
backgroundColor: colors.primary.background,
paddingTop: 60
}}
>
<View
style={{
@@ -236,7 +239,10 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo, onCancel }) => {
<Button
title={strings.cancel()}
type="secondaryAccented"
onPress={onCancel}
onPress={() => {
reset();
onCancel();
}}
width={250}
/>
@@ -297,7 +303,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo, onCancel }) => {
};
TwoFactorVerification.present = (onMfaLogin, data, onCancel, context) => {
presentSheet({
presentDialog({
component: () => (
<TwoFactorVerification
onMfaLogin={onMfaLogin}
@@ -306,7 +312,9 @@ TwoFactorVerification.present = (onMfaLogin, data, onCancel, context) => {
/>
),
context: context || "two_factor_verify",
disableClosing: true
disableClosing: true,
transparent: false,
statusBarTranslucent: true
});
};

View File

@@ -24,7 +24,7 @@ import { clearMessage } from "../../services/message";
import PremiumService from "../../services/premium";
import SettingsService from "../../services/settings";
import { useUserStore } from "../../stores/use-user-store";
import { eCloseSheet } from "../../utils/events";
import { eCloseSimpleDialog } from "../../utils/events";
import TwoFactorVerification from "./two-factor";
import { strings } from "@notesnook/intl";
@@ -92,7 +92,7 @@ export const useLogin = (onFinishLogin, sessionExpired = false) => {
} catch (e) {
callback && callback(false);
if (e.message === "invalid_grant") {
eSendEvent(eCloseSheet, "two_factor_verify");
eSendEvent(eCloseSimpleDialog, "two_factor_verify");
setLoading(false);
setStep(LoginSteps.emailAuth);
}
@@ -100,7 +100,7 @@ export const useLogin = (onFinishLogin, sessionExpired = false) => {
},
mfaInfo,
() => {
eSendEvent(eCloseSheet, "two_factor_verify");
eSendEvent(eCloseSimpleDialog, "two_factor_verify");
setLoading(false);
setStep(LoginSteps.emailAuth);
}

View File

@@ -36,6 +36,7 @@ import { getElevationStyle } from "../../utils/elevation";
import { AppFontSize, normalize } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { hexToRGBA, RGB_Linear_Shade } from "../../utils/colors";
import useKeyboard from "../../hooks/use-keyboard";
interface FloatingButtonProps {
onPress: () => void;

View File

@@ -26,7 +26,7 @@ import { notesnook } from "../../../e2e/test.ids";
import { getColorLinearShade } from "../../utils/colors";
import { AppFontSize } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { Button } from "../ui/button";
import { Button, ButtonProps } from "../ui/button";
import Paragraph from "../ui/typography/paragraph";
const DialogButtons = ({
@@ -37,6 +37,14 @@ const DialogButtons = ({
loading,
doneText,
positiveType
}: {
onPressPositive?: () => void;
onPressNegative: () => void;
positiveTitle: string;
negativeTitle?: string;
loading?: boolean;
doneText?: string;
positiveType?: ButtonProps["type"];
}) => {
const { colors, isDark } = useThemeColors();

View File

@@ -21,7 +21,7 @@ import React from "react";
import { Text, View, ViewStyle } from "react-native";
import { useThemeColors } from "@notesnook/theme";
import { AppFontSize } from "../../utils/size";
import { Button } from "../ui/button";
import { Button, ButtonProps } from "../ui/button";
import { PressableProps } from "../ui/pressable";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
@@ -31,13 +31,7 @@ type DialogHeaderProps = {
icon?: string;
title?: string;
paragraph?: string;
button?: {
onPress?: () => void;
loading?: boolean;
title?: string;
type?: PressableProps["type"];
icon?: string;
};
button?: ButtonProps;
paragraphColor?: string;
padding?: number;
centered?: boolean;
@@ -95,17 +89,14 @@ const DialogHeader = ({
{button ? (
<Button
onPress={button.onPress}
style={{
borderRadius: 100,
paddingHorizontal: DefaultAppStyles.GAP
}}
loading={button.loading}
fontSize={13}
title={button.title}
icon={button.icon}
type={button.type || "secondary"}
height={30}
{...button}
/>
) : null}
</View>

View File

@@ -17,15 +17,20 @@ 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 { KeyboardTypeOptions } from "react-native";
import { eSendEvent } from "../../services/event-manager";
import { eCloseSimpleDialog, eOpenSimpleDialog } from "../../utils/events";
import { ButtonProps } from "../ui/button";
type DialogInfo = {
export type DialogInfo = {
title?: string;
paragraph?: string;
positiveText?: string;
negativeText?: string;
positivePress?: (...args: any[]) => void;
positiveText: string;
negativeText: string;
background?: string;
transparent?: boolean;
statusBarTranslucent?: boolean;
positivePress?: (...args: any[]) => Promise<any>;
onClose?: () => void;
positiveType?:
| "transparent"
@@ -44,16 +49,18 @@ type DialogInfo = {
// eslint-disable-next-line @typescript-eslint/ban-types
context: "global" | "local" | (string & {});
secureTextEntry?: boolean;
keyboardType?: string;
keyboardType?: KeyboardTypeOptions;
check?: {
info: string;
type?: string;
type?: ButtonProps["type"];
defaultValue?: boolean;
};
notice: {
text: string;
type: "alert" | "information";
};
disableBackdropClosing: boolean;
component: JSX.Element | ((close?: () => void) => JSX.Element);
};
export function presentDialog(data: Partial<DialogInfo>): void {

View File

@@ -20,7 +20,7 @@ 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 { View } from "react-native";
import { TextInput, View, ViewStyle } from "react-native";
import { DDS } from "../../services/device-detection";
import {
eSubscribeEvent,
@@ -40,42 +40,26 @@ import Seperator from "../ui/seperator";
import BaseDialog from "./base-dialog";
import DialogButtons from "./dialog-buttons";
import DialogHeader from "./dialog-header";
import { DialogInfo } from "./functions";
export const Dialog = ({ context = "global" }) => {
export const Dialog = ({ context = "global" }: { context?: string }) => {
const { colors } = useThemeColors();
const [visible, setVisible] = useState(false);
const [checked, setChecked] = useState(false);
const [loading, setLoading] = useState(false);
const values = useRef({
const [visible, setVisible] = useState<boolean>();
const [checked, setChecked] = useState<boolean>();
const [loading, setLoading] = useState<boolean>();
const values = useRef<{
inputValue?: string;
}>({
inputValue: undefined
});
const inputRef = useRef();
const [dialogInfo, setDialogInfo] = useState({
title: "",
paragraph: "",
positiveText: strings.done(),
negativeText: strings.cancel(),
positivePress: () => {},
onClose: () => {},
positiveType: "transparent",
icon: null,
paragraphColor: colors.primary.paragraph,
input: false,
inputPlaceholder: "Enter some text",
defaultValue: "",
disableBackdropClosing: false,
check: {
info: "Check",
type: "transparent",
defaultValue: false
}
});
const inputRef = useRef<TextInput>(null);
const [dialogInfo, setDialogInfo] = useState<DialogInfo>();
const onPressPositive = async () => {
if (dialogInfo.positivePress) {
if (dialogInfo?.positivePress) {
inputRef.current?.blur();
setLoading(true);
let result;
let result = false;
try {
result = await dialogInfo.positivePress(
values.current.inputValue || dialogInfo.defaultValue,
@@ -97,7 +81,7 @@ export const Dialog = ({ context = "global" }) => {
};
const show = useCallback(
(data) => {
(data: DialogInfo) => {
if (!data.context) data.context = "global";
if (data.context !== context) return;
setDialogInfo(data);
@@ -108,6 +92,14 @@ export const Dialog = ({ context = "global" }) => {
[context]
);
const hide = React.useCallback(() => {
setChecked(false);
values.current.inputValue = undefined;
setVisible(false);
setDialogInfo(undefined);
dialogInfo?.onClose?.();
}, [dialogInfo]);
useEffect(() => {
eSubscribeEvent(eOpenSimpleDialog, show);
eSubscribeEvent(eCloseSimpleDialog, hide);
@@ -118,21 +110,14 @@ export const Dialog = ({ context = "global" }) => {
};
}, [hide, show]);
const hide = React.useCallback(() => {
setChecked(false);
values.current.inputValue = undefined;
setVisible(false);
dialogInfo.onClose?.();
}, [dialogInfo]);
const onNegativePress = async () => {
if (dialogInfo.onClose) {
if (dialogInfo?.onClose) {
await dialogInfo.onClose();
}
hide();
};
const style = {
const style: ViewStyle = {
...getElevationStyle(5),
width: DDS.isTab ? 400 : "85%",
maxHeight: 450,
@@ -143,11 +128,19 @@ export const Dialog = ({ context = "global" }) => {
overflow: "hidden"
};
return visible ? (
return visible && dialogInfo ? (
<BaseDialog
statusBarTranslucent={false}
statusBarTranslucent={
dialogInfo.statusBarTranslucent === undefined
? false
: dialogInfo.statusBarTranslucent
}
bounce={!dialogInfo.input}
closeOnTouch={!dialogInfo.disableBackdropClosing}
background={dialogInfo.background}
transparent={
dialogInfo.transparent === undefined ? true : dialogInfo.transparent
}
onShow={async () => {
if (dialogInfo.input) {
inputRef.current?.setNativeProps({
@@ -160,89 +153,99 @@ export const Dialog = ({ context = "global" }) => {
visible={true}
onRequestClose={hide}
>
<View style={style}>
<DialogHeader
title={dialogInfo.title}
icon={dialogInfo.icon}
paragraph={dialogInfo.paragraph}
paragraphColor={dialogInfo.paragraphColor}
padding={12}
style={{
minHeight: 0
}}
/>
<Seperator half />
{typeof dialogInfo.component === "function"
? dialogInfo.component(() => hide())
: dialogInfo.component}
{dialogInfo.input ? (
<View
{dialogInfo.component ? null : (
<View style={style}>
<DialogHeader
title={dialogInfo.title}
icon={dialogInfo.icon}
paragraph={dialogInfo.paragraph}
paragraphColor={dialogInfo.paragraphColor}
padding={12}
style={{
paddingHorizontal: DefaultAppStyles.GAP
minHeight: 0
}}
>
<Input
fwdRef={inputRef}
autoCapitalize="none"
onChangeText={(value) => {
values.current.inputValue = value;
}}
testID="input-value"
secureTextEntry={dialogInfo.secureTextEntry}
//defaultValue={dialogInfo.defaultValue}
onSubmit={onPressPositive}
returnKeyLabel="Done"
returnKeyType="done"
keyboardType={dialogInfo.keyboardType || "default"}
placeholder={dialogInfo.inputPlaceholder}
/>
</View>
) : null}
/>
<Seperator half />
{dialogInfo?.notice ? (
<View
style={{
paddingHorizontal: DefaultAppStyles.GAP
}}
>
<Notice
type={dialogInfo.notice.type || "information"}
text={dialogInfo.notice.text}
/>
</View>
) : null}
{dialogInfo.check ? (
<>
<Button
onPress={() => {
setChecked(!checked);
}}
icon={
checked
? "check-circle-outline"
: "checkbox-blank-circle-outline"
}
iconColor={checked ? colors.secondary.icon : colors.primary.icon}
{dialogInfo.input ? (
<View
style={{
justifyContent: "flex-start"
paddingHorizontal: DefaultAppStyles.GAP
}}
height={35}
iconSize={20}
width="100%"
title={dialogInfo.check.info}
type={checked ? dialogInfo.check.type || "plain" : "plain"}
/>
</>
) : null}
>
<Input
fwdRef={inputRef}
autoCapitalize="none"
onChangeText={(value) => {
values.current.inputValue = value;
}}
testID="input-value"
secureTextEntry={dialogInfo.secureTextEntry}
//defaultValue={dialogInfo.defaultValue}
onSubmit={() => {
onPressPositive();
}}
returnKeyLabel="Done"
returnKeyType="done"
keyboardType={dialogInfo.keyboardType || "default"}
placeholder={dialogInfo.inputPlaceholder}
/>
</View>
) : null}
<DialogButtons
onPressNegative={onNegativePress}
onPressPositive={dialogInfo.positivePress && onPressPositive}
loading={loading}
positiveTitle={dialogInfo.positiveText}
negativeTitle={dialogInfo.negativeText}
positiveType={dialogInfo.positiveType}
/>
</View>
{dialogInfo?.notice ? (
<View
style={{
paddingHorizontal: DefaultAppStyles.GAP
}}
>
<Notice
type={dialogInfo.notice.type || "information"}
text={dialogInfo.notice.text}
/>
</View>
) : null}
{dialogInfo.check ? (
<>
<Button
onPress={() => {
setChecked(!checked);
}}
icon={
checked
? "check-circle-outline"
: "checkbox-blank-circle-outline"
}
iconColor={
checked ? colors.secondary.icon : colors.primary.icon
}
style={{
justifyContent: "flex-start"
}}
height={35}
iconSize={20}
width="100%"
title={dialogInfo.check.info}
type={checked ? dialogInfo.check.type || "plain" : "plain"}
/>
</>
) : null}
<DialogButtons
onPressNegative={onNegativePress}
onPressPositive={dialogInfo.positivePress && onPressPositive}
loading={loading}
positiveTitle={dialogInfo.positiveText}
negativeTitle={dialogInfo.negativeText}
positiveType={dialogInfo.positiveType}
/>
</View>
)}
<Toast context="local" />
</BaseDialog>
) : null;

View File

@@ -17,12 +17,15 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { Dimensions, TextInput, View } from "react-native";
import Orientation from "react-native-orientation-locker";
import Pdf from "react-native-pdf";
import Animated, { FadeIn, FadeOut } from "react-native-reanimated";
import { MMKV } from "../../../common/database/mmkv";
import downloadAttachment from "../../../common/filesystem/download-attachment";
import { deleteCacheFileByPath, exists } from "../../../common/filesystem/io";
import { cacheDir } from "../../../common/filesystem/utils";
import { useAttachmentProgress } from "../../../hooks/use-attachment-progress";
import useGlobalSafeAreaInsets from "../../../hooks/use-global-safe-area-insets";
@@ -30,8 +33,9 @@ import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../../services/event-manager";
import { useThemeColors } from "@notesnook/theme";
import { AppFontSize } from "../../../utils/size";
import { DefaultAppStyles } from "../../../utils/styles";
import { sleep } from "../../../utils/time";
import { Dialog } from "../../dialog";
import BaseDialog from "../../dialog/base-dialog";
import { presentDialog } from "../../dialog/functions";
@@ -39,11 +43,6 @@ import SheetProvider from "../../sheet-provider";
import { IconButton } from "../../ui/icon-button";
import { ProgressBarComponent } from "../../ui/svg/lazy";
import Paragraph from "../../ui/typography/paragraph";
import { sleep } from "../../../utils/time";
import { MMKV } from "../../../common/database/mmkv";
import { deleteCacheFileByPath } from "../../../common/filesystem/io";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../../utils/styles";
const WIN_WIDTH = Dimensions.get("window").width;
const WIN_HEIGHT = Dimensions.get("window").height;
@@ -105,15 +104,19 @@ const PDFPreview = () => {
const open = useCallback(
async (attachment) => {
setVisible(true);
setLoading(true);
setTimeout(async () => {
setAttachment(attachment);
let hash = attachment.hash;
if (!hash) return;
if (!(await exists(hash))) setLoading(true);
const uri = await downloadAttachment(hash, false, {
silent: true,
cache: true
});
if (!(await exists(hash))) {
setVisible(false);
return;
}
const path = `${cacheDir}/${uri}`;
snapshotValue.current = snapshot.current;
setPDFSource("file://" + path);
@@ -166,8 +169,7 @@ const PDFPreview = () => {
}}
>
{loading ? (
<Animated.View
exiting={FadeOut}
<View
style={{
flex: 1,
justifyContent: "center",
@@ -188,7 +190,7 @@ const PDFPreview = () => {
>
{strings.loadingWithProgress(progress?.percent)}
</Paragraph>
</Animated.View>
</View>
) : (
<>
<View
@@ -270,49 +272,42 @@ const PDFPreview = () => {
</View>
</View>
{pdfSource ? (
<Animated.View
style={{
flex: 1
<Pdf
source={{
uri: pdfSource
}}
entering={FadeIn}
>
<Pdf
source={{
uri: pdfSource
}}
ref={pdfRef}
onLoadComplete={(numberOfPages) => {
setNumPages(numberOfPages);
}}
onPageChanged={(page) => {
setCurrentPage(page);
inputRef.current?.setNativeProps({
text: page + ""
});
saveSnapshot({
currentPage: page,
scale: snapshot?.current?.scale
});
}}
// scale={snapshotValue.current?.scale}
// onScaleChanged={(scale) => {
// saveSnapshot({
// currentPage: snapshot?.current?.currentPage,
// scale: scale
// });
// }}
page={snapshotValue?.current?.currentPage}
password={password}
maxScale={6}
onError={onError}
onPressLink={(uri) => {}}
style={{
flex: 1,
width: width,
height: Dimensions.get("window").height
}}
/>
</Animated.View>
ref={pdfRef}
onLoadComplete={(numberOfPages) => {
setNumPages(numberOfPages);
}}
onPageChanged={(page) => {
setCurrentPage(page);
inputRef.current?.setNativeProps({
text: page + ""
});
saveSnapshot({
currentPage: page,
scale: snapshot?.current?.scale
});
}}
// scale={snapshotValue.current?.scale}
// onScaleChanged={(scale) => {
// saveSnapshot({
// currentPage: snapshot?.current?.currentPage,
// scale: scale
// });
// }}
page={snapshotValue?.current?.currentPage}
password={password}
maxScale={6}
onError={onError}
onPressLink={(uri) => {}}
style={{
flex: 1,
width: width,
height: Dimensions.get("window").height
}}
/>
) : null}
</>
)}

View File

@@ -30,6 +30,7 @@ import { BackHandler, Platform, ViewProps } from "react-native";
import { Gesture, GestureDetector } from "react-native-gesture-handler";
import Animated, {
runOnJS,
SharedValue,
useAnimatedReaction,
useAnimatedStyle,
useSharedValue,
@@ -67,6 +68,7 @@ export interface TabsRef {
setScrollEnabled: () => true;
isDrawerOpen: () => boolean;
node: RefObject<Animated.View>;
tabChangedFromSwipeAction: SharedValue<boolean>;
}
export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
@@ -116,6 +118,7 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
const isLoaded = useRef(false);
const prevWidths = useRef(widths);
const isIPhone = Platform.OS === "ios";
const tabChangedFromSwipeAction = useSharedValue(false);
useEffect(() => {
if (deviceMode === "tablet" || fullscreen) {
@@ -191,6 +194,7 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
: withTiming(editorPosition);
currentTab.value = 2;
}
tabChangedFromSwipeAction.value = false;
},
goToIndex: (index: number, animated = true) => {
if (deviceMode === "tablet") {
@@ -210,6 +214,7 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
: editorPosition;
currentTab.value = 2;
}
tabChangedFromSwipeAction.value = false;
},
unlock: () => {
forcedLock.value = false;
@@ -252,7 +257,8 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
},
page: () => (currentTab.value === 1 ? "home" : "editor"),
setScrollEnabled: () => true,
node: node
node: node,
tabChangedFromSwipeAction: tabChangedFromSwipeAction
}),
[
currentTab,
@@ -262,7 +268,8 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
homePosition,
editorPosition,
forcedLock,
isDrawerOpen
isDrawerOpen,
tabChangedFromSwipeAction
]
);
@@ -362,18 +369,20 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
isDrawerOpen.value = true;
currentTab.value = 1;
runOnJS(onDrawerStateChange)(true);
tabChangedFromSwipeAction.value = true;
return;
} else if (!isSwipeLeft && finalValue > 100) {
translateX.value = withSpring(homePosition, animationConfig);
isDrawerOpen.value = false;
currentTab.value = 1;
runOnJS(onDrawerStateChange)(false);
tabChangedFromSwipeAction.value = true;
return;
} else if (!isSwipeLeft && finalValue < 100) {
translateX.value = withSpring(0, animationConfig);
isDrawerOpen.value = true;
currentTab.value = 1;
tabChangedFromSwipeAction.value = true;
return;
}
}
@@ -384,6 +393,8 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
translateX.value = withSpring(editorPosition, animationConfig);
currentTab.value = 2;
isDrawerOpen.value = false;
tabChangedFromSwipeAction.value = true;
return;
}
}
@@ -398,6 +409,8 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
translateX.value = withSpring(editorPosition, animationConfig);
currentTab.value = 2;
}
tabChangedFromSwipeAction.value = true;
return;
}

View File

@@ -97,7 +97,6 @@ export const Header = ({
style={{
flexDirection: "row",
justifyContent: "space-between",
marginTop: DefaultAppStyles.GAP_SMALL,
borderRadius: 10,
paddingVertical: 3,
borderWidth: hasSearch ? 1 : 0,

View File

@@ -133,7 +133,9 @@ export const SectionHeader = React.memo<
<Sort
screen={screen}
type={dataType}
hideGroupOptions={screen === "Reminders"}
hideGroupOptions={
screen === "Reminders" || screen === "Search"
}
/>
)
});

View File

@@ -17,24 +17,26 @@ 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 { Reminder } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../../e2e/test.ids";
import { defaultBorderRadius, AppFontSize } from "../../../utils/size";
import useIsSelected from "../../../hooks/use-selected";
import AddReminder from "../../../screens/add-reminder";
import { eSendEvent } from "../../../services/event-manager";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { eCloseSheet } from "../../../utils/events";
import { AppFontSize, defaultBorderRadius } from "../../../utils/size";
import { DefaultAppStyles } from "../../../utils/styles";
import { Properties } from "../../properties";
import ReminderSheet from "../../sheets/reminder";
import AppIcon from "../../ui/AppIcon";
import { IconButton } from "../../ui/icon-button";
import { ReminderTime } from "../../ui/reminder-time";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import SelectionWrapper, { selectItem } from "../selection-wrapper";
import { strings } from "@notesnook/intl";
import { useSelectionStore } from "../../../stores/use-selection-store";
import useIsSelected from "../../../hooks/use-selected";
import AppIcon from "../../ui/AppIcon";
import { DefaultAppStyles } from "../../../utils/styles";
const ReminderItem = React.memo(
({
@@ -49,8 +51,10 @@ const ReminderItem = React.memo(
const { colors } = useThemeColors();
const openReminder = () => {
if (selectItem(item)) return;
ReminderSheet.present(item, undefined, isSheet);
AddReminder.present(item, undefined);
if (isSheet) {
eSendEvent(eCloseSheet);
}
};
const selectionMode = useSelectionStore((state) => state.selectionMode);
const [selected] = useIsSelected(item);

View File

@@ -0,0 +1,180 @@
/*
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 { HighlightedResult } from "@notesnook/core";
import { View } from "react-native";
import { Pressable } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph";
import { useThemeColors } from "@notesnook/theme";
import { DefaultAppStyles } from "../../../utils/styles";
import Heading from "../../ui/typography/heading";
import { AppFontSize } from "../../../utils/size";
import { Properties } from "../../properties";
import { db } from "../../../common/database";
import { eSendEvent } from "../../../services/event-manager";
import { eOnLoadNote } from "../../../utils/events";
import { IconButton } from "../../ui/icon-button";
import { fluidTabsRef } from "../../../utils/global-refs";
type SearchResultProps = {
item: HighlightedResult;
};
export const SearchResult = (props: SearchResultProps) => {
const [expanded, setExpanded] = React.useState(true);
const { colors } = useThemeColors();
const openNote = async (index?: number) => {
const note = await db.notes.note(props.item.id);
eSendEvent(eOnLoadNote, {
item: {
...note,
content: props.item.rawContent
? {
data: props.item.rawContent || "",
type: "tiptap"
}
: undefined
},
searchResultIndex: index
});
fluidTabsRef.current?.goToPage("editor");
};
return (
<Pressable
style={{
alignSelf: "flex-start",
alignItems: "flex-start",
paddingHorizontal: DefaultAppStyles.GAP,
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL,
borderRadius: 0
}}
onLongPress={async () => {
const note = await db.notes.note(props.item.id);
Properties.present(note);
}}
onPress={async () => openNote()}
>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
width: "100%"
}}
>
<View
style={{
flexDirection: "row",
alignItems: "center",
gap: DefaultAppStyles.GAP_SMALL / 2,
flexShrink: 1
}}
>
{props.item.content?.length ? (
<IconButton
name={!expanded ? "chevron-right" : "chevron-down"}
onPress={() => setExpanded((prev) => !prev)}
size={AppFontSize.md + 2}
color={colors.secondary.icon}
style={{
width: 23,
height: 23
}}
/>
) : null}
<Heading
size={AppFontSize.sm}
style={{
flexShrink: 1
}}
>
{props.item.title.map((title, index) => (
<React.Fragment key={props.item.id + index + "-srt"}>
{title.prefix}
<Heading
size={AppFontSize.sm}
style={{
backgroundColor: colors.secondary.accent,
color: colors.secondary.accentForeground
}}
>
{title.match}
</Heading>
{title.suffix}
</React.Fragment>
))}
</Heading>
</View>
{props.item.content?.length ? (
<Paragraph size={AppFontSize.xxs} color={colors.secondary.paragraph}>
{props.item.content.length}
</Paragraph>
) : null}
</View>
{expanded &&
props.item.content.map((content, index) => (
<Pressable
key={props.item.id + index}
style={{
alignSelf: "flex-start",
alignItems: "flex-start",
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL,
borderColor: colors.primary.border,
borderWidth: 0,
borderRadius: 0,
borderBottomWidth: 1,
borderBottomColor: colors.primary.border,
width: "100%"
}}
onLongPress={async () => {
const note = await db.notes.note(props.item.id);
Properties.present(note);
}}
onPress={() => {
let activeIndex = 0;
for (let i = 0; i <= index; i++) {
activeIndex += props.item.content[i].length;
}
openNote(activeIndex);
}}
>
<Paragraph>
{content.map((match, index) => (
<React.Fragment key={props.item.id + index + "src"}>
{match.prefix}
<Paragraph
size={AppFontSize.sm}
style={{
backgroundColor: colors.secondary.accent,
color: colors.secondary.accentForeground
}}
>
{match.match}
</Paragraph>
{match.suffix}
</React.Fragment>
))}
</Paragraph>
</Pressable>
))}
</Pressable>
);
};

View File

@@ -150,7 +150,7 @@ export default function List(props: ListProps) {
flex: 1
}}
>
{props.data?.placeholders?.length === 0 ? (
{props.data?.placeholders?.length === 0 || !props.data ? (
<>
{props.CustomLisHeader ? (
props.CustomLisHeader

View File

@@ -27,6 +27,7 @@ import {
GroupHeader,
GroupOptions,
GroupingKey,
HighlightedResult,
Item,
ItemType,
Note,
@@ -49,6 +50,7 @@ import { NoteWrapper } from "../list-items/note/wrapper";
import { NotebookWrapper } from "../list-items/notebook/wrapper";
import ReminderItem from "../list-items/reminder";
import TagItem from "../list-items/tag";
import { SearchResult } from "../list-items/search-result";
type ListItemWrapperProps<TItem = Item> = {
group?: GroupingKey;
@@ -153,7 +155,9 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
},
items?.cacheItem(index) ? 100 : 0
);
} catch (e) {}
} catch (e) {
/** empty */
}
})();
}, [index, items, refreshItem]);
@@ -196,7 +200,7 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
notebooks={notebooks.current}
reminder={reminder.current}
attachmentsCount={attachmentsCount.current}
date={getDate(item, group)}
date={getDate(item as Note, group)}
isRenderedInActionSheet={isSheet}
index={index}
locked={locked.current}
@@ -227,7 +231,7 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
<NotebookWrapper
item={item as Notebook}
totalNotes={totalNotes.current}
date={getDate(item, group)}
date={getDate(item as Notebook, group)}
index={index}
/>
</>
@@ -285,12 +289,34 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
/>
</>
);
case "searchResult":
return (
<>
{groupHeader && previousIndex.current === index && !isSheet ? (
<SectionHeader
screen={props.renderedInRoute}
item={groupHeader}
index={index}
dataType={item.type}
color={props.customAccentColor}
groupOptions={groupOptions}
onOpenJumpToDialog={() => {
eSendEvent(eOpenJumpToDialog, {
ref: props.scrollRef,
data: items
});
}}
/>
) : null}
<SearchResult item={item as HighlightedResult} />
</>
);
default:
return null;
}
}
function getDate(item: Item, groupType?: GroupingKey): number {
function getDate(item: Notebook | Note, groupType?: GroupingKey): number {
return (
getSortValue(
groupType

View File

@@ -73,9 +73,6 @@ const ColorItem = ({ item, note }: { item: Color; note: Note }) => {
testID={notesnook.ids.dialogs.actionsheet.color(item.colorCode)}
key={item.id}
onPress={toggleColor}
onLongPress={(event) => {
NativeTooltip.show(event, item.title, NativeTooltip.POSITIONS.TOP);
}}
style={{
width: 35,
height: 35,

View File

@@ -21,13 +21,14 @@ import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { View } from "react-native";
import { FlatList } from "react-native-actions-sheet";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import { DDS } from "../../services/device-detection";
import { eSendEvent, presentSheet } from "../../services/event-manager";
import { ColorValues } from "../../utils/colors";
import { eOnLoadNote } from "../../utils/events";
import { fluidTabsRef } from "../../utils/global-refs";
import { AppFontSize } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import SheetProvider from "../sheet-provider";
import { IconButton } from "../ui/icon-button";
import { Pressable } from "../ui/pressable";
@@ -38,8 +39,6 @@ import { DateMeta } from "./date-meta";
import { Items } from "./items";
import Notebooks from "./notebooks";
import { TagStrip, Tags } from "./tags";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { DefaultAppStyles } from "../../utils/styles";
const Line = ({ top = 6, bottom = 6 }) => {
const { colors } = useThemeColors();
@@ -151,7 +150,8 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
) : null}
</View>
{item.type === "notebook" && item.description ? (
{(item.type === "notebook" || item.type === "reminder") &&
item.description ? (
<Paragraph>{item.description}</Paragraph>
) : null}

View File

@@ -36,6 +36,7 @@ import Paragraph from "../ui/typography/paragraph";
const TOP_BAR_ITEMS: ActionId[] = [
"pin",
"favorite",
"archive",
"lock-unlock",
"publish",
"local-only",
@@ -64,8 +65,11 @@ const COLUMN_BAR_ITEMS: ActionId[] = [
"edit-notebook",
"move-notes",
"move-notebook",
"edit-reminder",
"pin",
"disable-reminder",
"default-notebook",
"default-tag",
"default-homepage",
"add-shortcut",
"reorder",

View File

@@ -24,11 +24,15 @@ import { TaggedNotes } from "../../screens/notes/tagged";
import { useThemeColors } from "@notesnook/theme";
import { AppFontSize } from "../../utils/size";
import { sleep } from "../../utils/time";
import ManageTagsSheet from "../sheets/manage-tags";
import { Button } from "../ui/button";
import { ColorTags } from "./color-tags";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../utils/styles";
import ManageTags from "../../screens/manage-tags";
import {
paddingTop,
paddingVertical
} from "deprecated-react-native-prop-types/DeprecatedLayoutPropTypes";
export const Tags = ({ item, close }) => {
const { colors } = useThemeColors();
@@ -47,7 +51,8 @@ export const Tags = ({ item, close }) => {
>
<Button
onPress={async () => {
ManageTagsSheet.present([item.id]);
ManageTags.present([item.id]);
close();
}}
buttonType={{
text: colors.primary.accent
@@ -85,7 +90,6 @@ export const TagStrip = ({ item, close }) => {
flexDirection: "row",
flexWrap: "wrap",
alignItems: "center",
marginTop: DefaultAppStyles.GAP_VERTICAL,
gap: 5
}}
>
@@ -106,16 +110,13 @@ const TagItem = ({ tag, close }) => {
const style = {
paddingHorizontal: 0,
borderRadius: 100,
marginTop: 0,
backgroundColor: "transparent"
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL
};
return (
<Button
onPress={onPress}
title={"#" + tag.title}
type="plain"
height={20}
fontSize={AppFontSize.xs}
style={style}
textStyle={{

View File

@@ -42,10 +42,11 @@ import { DefaultAppStyles } from "../../utils/styles";
import { sleep } from "../../utils/time";
import { presentDialog } from "../dialog/functions";
import ExportNotesSheet from "../sheets/export-notes";
import ManageTagsSheet from "../sheets/manage-tags";
import { MoveNotebook } from "../../screens/move-notebook";
import { IconButton } from "../ui/icon-button";
import NativeTooltip from "../../utils/tooltip";
import ManageTags from "../../screens/manage-tags";
export const SelectionHeader = React.memo(
({
@@ -196,20 +197,10 @@ export const SelectionHeader = React.memo(
}
]
: [
{
title: strings.move(),
onPress: async () => {
const ids = selectedItemsList;
const notebooks = await db.notebooks.all.items(ids);
MoveNotebook.present(notebooks);
},
visible: renderedInRoute === "Notebooks",
icon: "arrow-right-bold-box-outline"
},
{
title: strings.manageTags(),
onPress: async () => {
ManageTagsSheet.present(selectedItemsList);
ManageTags.present(selectedItemsList);
},
visible: type === "note",
icon: "pound"

View File

@@ -95,8 +95,6 @@ const SheetProvider = ({ context = "global" }) => {
[context]
);
console.log(data?.keyboardHandlerDisabled);
return !visible || !data ? null : (
<SheetWrapper
fwdRef={actionSheetRef}

View File

@@ -24,23 +24,22 @@ import { TextInput, View } from "react-native";
import { notesnook } from "../../../../e2e/test.ids";
import { db } from "../../../common/database";
import { DDS } from "../../../services/device-detection";
import {
ToastManager,
eSendEvent,
presentSheet
} from "../../../services/event-manager";
import { ToastManager, eSendEvent } from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { useMenuStore } from "../../../stores/use-menu-store";
import { useNotebookStore } from "../../../stores/use-notebook-store";
import { useRelationStore } from "../../../stores/use-relation-store";
import { eOnNotebookUpdated } from "../../../utils/events";
import { getParentNotebookId } from "../../../utils/notebooks";
import { AppFontSize } from "../../../utils/size";
import { Button } from "../../ui/button";
import Input from "../../ui/input";
import Seperator from "../../ui/seperator";
import Heading from "../../ui/typography/heading";
import { DefaultAppStyles } from "../../../utils/styles";
import { useSettingStore } from "../../../stores/use-setting-store";
import DialogHeader from "../../dialog/dialog-header";
import DialogButtons from "../../dialog/dialog-buttons";
import { presentDialog } from "../../dialog/functions";
import { getElevationStyle } from "../../../utils/elevation";
import { defaultBorderRadius } from "../../../utils/size";
import { useThemeColors } from "@notesnook/theme";
import { getContainerBorder } from "../../../utils/colors";
export const AddNotebookSheet = ({
notebook,
@@ -55,6 +54,7 @@ export const AddNotebookSheet = ({
showMoveNotesOnComplete: boolean;
defaultTitle?: string;
}) => {
const { colors } = useThemeColors();
const title = useRef(notebook?.title || defaultTitle);
const description = useRef(notebook?.description);
const titleInput = useRef<TextInput>(null);
@@ -124,57 +124,66 @@ export const AddNotebookSheet = ({
return (
<View
style={{
maxHeight: DDS.isTab ? "90%" : "97%",
borderRadius: DDS.isTab ? 5 : 0,
paddingHorizontal: DefaultAppStyles.GAP
...getElevationStyle(5),
width: DDS.isTab ? 400 : "85%",
maxHeight: 450,
borderRadius: defaultBorderRadius,
backgroundColor: colors.primary.background,
paddingTop: 12,
...getContainerBorder(colors.primary.border, 0.5),
overflow: "hidden"
}}
>
<Heading size={AppFontSize.lg}>
{notebook ? strings.editNotebook() : strings.newNotebook()}
</Heading>
<Seperator />
<Input
fwdRef={titleInput}
testID={notesnook.ids.dialogs.notebook.inputs.title}
onChangeText={(value) => {
title.current = value;
}}
onLayout={() => {
setImmediate(() => {
titleInput?.current?.focus();
});
}}
placeholder={strings.enterNotebookTitle()}
onSubmit={() => {
descriptionInput.current?.focus();
}}
returnKeyLabel="Next"
returnKeyType="next"
defaultValue={notebook ? notebook.title : title.current}
/>
<Input
fwdRef={descriptionInput}
testID={notesnook.ids.dialogs.notebook.inputs.description}
onChangeText={(value) => {
description.current = value;
}}
placeholder={strings.enterNotebookDescription()}
returnKeyLabel={strings.next()}
returnKeyType="next"
defaultValue={notebook ? notebook.description : ""}
/>
<Button
title={notebook ? strings.save() : strings.add()}
type="accent"
<View
style={{
paddingHorizontal: DefaultAppStyles.GAP * 2,
width: "100%"
paddingHorizontal: 12
}}
onPress={onSaveChanges}
>
<DialogHeader
title={notebook ? strings.editNotebook() : strings.newNotebook()}
/>
<Input
fwdRef={titleInput}
testID={notesnook.ids.dialogs.notebook.inputs.title}
onChangeText={(value) => {
title.current = value;
}}
onLayout={() => {
setTimeout(() => {
titleInput?.current?.focus();
}, 300);
}}
placeholder={strings.enterNotebookTitle()}
onSubmit={() => {
descriptionInput.current?.focus();
}}
returnKeyLabel="Next"
returnKeyType="next"
defaultValue={notebook ? notebook.title : title.current}
/>
<Input
fwdRef={descriptionInput}
testID={notesnook.ids.dialogs.notebook.inputs.description}
onChangeText={(value) => {
description.current = value;
}}
placeholder={strings.enterNotebookDescription()}
returnKeyLabel={strings.next()}
returnKeyType="next"
defaultValue={notebook ? notebook.description : ""}
/>
</View>
<DialogButtons
onPressNegative={() => {
title.current = undefined;
close?.(false);
useSettingStore.getState().setSheetKeyboardHandler(true);
}}
positiveTitle={notebook ? strings.save() : strings.add()}
negativeTitle={strings.cancel()}
onPressPositive={onSaveChanges}
/>
</View>
);
@@ -188,9 +197,9 @@ AddNotebookSheet.present = (
showMoveNotesOnComplete = true,
defaultTitle?: string
) => {
presentSheet({
presentDialog({
context: context,
component: (ref, close) => (
component: (close) => (
<AddNotebookSheet
notebook={notebook}
parentNotebook={parentNotebook}

View File

@@ -310,6 +310,7 @@ const ExportNotesSheet = ({
ToastManager.error(e as Error);
});
} else {
await sleep(500);
FileViewer.open(result?.filePath, {
showOpenWithDialog: true,
showAppsSuggestions: true
@@ -338,6 +339,7 @@ const ExportNotesSheet = ({
.getState()
.setAppDidEnterBackgroundForAction(true);
if (Platform.OS === "ios") {
await sleep(500);
Share.open({
url: result?.fileDir + result.fileName
}).catch(() => {

View File

@@ -283,7 +283,14 @@ export default function LinkNote(props: {
width: "100%"
}}
>
<Paragraph numberOfLines={1}>{selectedNote?.title}</Paragraph>
<Paragraph
style={{
flexShrink: 1
}}
numberOfLines={1}
>
{selectedNote?.title}
</Paragraph>
<Paragraph
color={colors.secondary.paragraph}

View File

@@ -38,8 +38,8 @@ import { eCloseSheet, eOnNotebookUpdated } from "../../../utils/events";
import { AppFontSize } from "../../../utils/size";
import { DefaultAppStyles } from "../../../utils/styles";
import { sleep } from "../../../utils/time";
import { Dialog } from "../../dialog";
import { Properties } from "../../properties";
import SheetProvider from "../../sheet-provider";
import { NotebookItem } from "../../side-menu/notebook-item";
import { useSideMenuNotebookTreeStore } from "../../side-menu/stores";
import { IconButton } from "../../ui/icon-button";
@@ -151,7 +151,7 @@ export const Notebooks = (props: {
height: 400
}}
>
<SheetProvider context="local" />
<Dialog context="local" />
<View
style={{
@@ -175,7 +175,7 @@ export const Notebooks = (props: {
}}
name="plus"
onPress={() => {
AddNotebookSheet.present(props.rootNotebook, undefined, "local");
AddNotebookSheet.present(undefined, props.rootNotebook, "local");
}}
/>
</View>

View File

@@ -34,7 +34,7 @@ import { AppFontSize } from "../../../utils/size";
import DialogHeader from "../../dialog/dialog-header";
import List from "../../list";
import SheetProvider from "../../sheet-provider";
import { Button } from "../../ui/button";
import { Button, ButtonProps } from "../../ui/button";
import { PressableProps } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph";
import { DefaultAppStyles } from "../../../utils/styles";
@@ -47,18 +47,10 @@ type RelationsListProps = {
referenceType: string;
relationType: "to" | "from";
title: string;
button?: Button;
button?: ButtonProps;
onAdd: () => void;
};
type Button = {
onPress?: (() => void) | undefined;
loading?: boolean | undefined;
title?: string | undefined;
type?: PressableProps["type"];
icon?: string;
};
const IconsByType = {
reminder: "bell"
};
@@ -154,7 +146,7 @@ RelationsList.present = ({
referenceType: string;
relationType: "to" | "from";
title: string;
button?: Button;
button?: ButtonProps;
onAdd: () => void;
}) => {
presentSheet({

View File

@@ -1,630 +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 { useThemeColors } from "@notesnook/theme";
import React, { RefObject, useRef, useState } from "react";
import {
Platform,
TextInput,
View,
ScrollView as RNScrollView
} from "react-native";
import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
import DateTimePickerModal from "react-native-modal-datetime-picker";
import {
PresentSheetOptions,
ToastManager,
presentSheet
} from "../../../services/event-manager";
import { defaultBorderRadius, AppFontSize } from "../../../utils/size";
import { Button } from "../../ui/button";
import Input from "../../ui/input";
import dayjs from "dayjs";
import DatePicker from "react-native-date-picker";
import { db } from "../../../common/database";
import { DDS } from "../../../services/device-detection";
import Navigation 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 { Dialog } from "../../dialog";
import { ReminderTime } from "../../ui/reminder-time";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { ItemReference, Note, Reminder } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../../utils/styles";
type ReminderSheetProps = {
actionSheetRef: RefObject<ActionSheetRef>;
close?: (ctx?: string) => void;
update?: (options: PresentSheetOptions) => void;
reminder?: Reminder;
reference?: ItemReference;
};
const ReminderModes =
Platform.OS === "ios"
? {
Once: "once",
Repeat: "repeat"
}
: {
Once: "once",
Repeat: "repeat",
Permanent: "permanent"
};
const RecurringModes = {
Daily: "day",
Week: "week",
Month: "month",
Year: "year"
};
const WeekDays = new Array(7).fill(true);
const MonthDays = new Array(31).fill(true);
const WeekDayNames = {
0: "Sunday",
1: "Monday",
2: "Tuesday",
3: "Wednesday",
4: "Thursday",
5: "Friday",
6: "Saturday"
};
const ReminderNotificationModes = {
Silent: "silent",
Vibrate: "vibrate",
Urgent: "urgent"
};
export default function ReminderSheet({
actionSheetRef,
close,
update,
reminder,
reference
}: ReminderSheetProps) {
const { colors, isDark } = useThemeColors();
const [reminderMode, setReminderMode] = useState<Reminder["mode"]>(
reminder?.mode || "once"
);
const [recurringMode, setRecurringMode] = useState<Reminder["recurringMode"]>(
reminder?.recurringMode || "week"
);
const [selectedDays, setSelectedDays] = useState<number[]>(
reminder?.selectedDays || []
);
const [date, setDate] = useState<Date>(
new Date(reminder?.date || Date.now())
);
const [reminderNotificationMode, setReminderNotificatioMode] = useState<
Reminder["priority"]
>(reminder?.priority || SettingsService.get().reminderNotificationMode);
const [isDatePickerVisible, setDatePickerVisibility] = useState(false);
const [repeatFrequency, setRepeatFrequency] = useState(1);
const referencedItem = reference ? (reference as Note) : null;
const title = useRef<string | undefined>(
!reminder ? referencedItem?.title : reminder?.title
);
const details = useRef<string | undefined>(
!reminder ? referencedItem?.headline : reminder?.description
);
const titleRef = useRef<TextInput>(null);
const timer = useRef<NodeJS.Timeout>();
const showDatePicker = () => {
setDatePickerVisibility(true);
};
const hideDatePicker = () => {
setDatePickerVisibility(false);
};
const handleConfirm = (date: Date) => {
timer.current = setTimeout(() => {
hideDatePicker();
setDate(date);
}, 50);
};
function nth(n: number) {
return (
["st", "nd", "rd"][(((((n < 0 ? -n : n) + 90) % 100) - 10) % 10) - 1] ||
"th"
);
}
function getSelectedDaysText(selectedDays: number[]) {
const text = selectedDays
.sort((a, b) => a - b)
.map((day, index) => {
const isLast = index === selectedDays.length - 1;
const isSecondLast = index === selectedDays.length - 2;
const joinWith = isSecondLast ? " & " : isLast ? "" : ", ";
return recurringMode === RecurringModes.Week
? WeekDayNames[day as keyof typeof WeekDayNames] + joinWith
: `${day}${nth(day)} ${joinWith}`;
})
.join("");
return text;
}
async function saveReminder() {
try {
if (!(await Notifications.checkAndRequestPermissions(true)))
throw new Error(strings.noNotificationPermission());
if (!date && reminderMode !== ReminderModes.Permanent) return;
if (
reminderMode === ReminderModes.Repeat &&
recurringMode !== "day" &&
recurringMode !== "year" &&
selectedDays.length === 0
)
throw new Error(strings.selectDayError());
if (!title.current) throw new Error(strings.setTitleError());
if (date.getTime() < Date.now() && reminderMode === "once") {
titleRef?.current?.focus();
throw new Error(strings.dateError());
}
date.setSeconds(0, 0);
const reminderId = await db.reminders?.add({
id: reminder?.id,
date: date?.getTime(),
priority: reminderNotificationMode,
title: title.current,
description: details.current,
recurringMode: recurringMode,
selectedDays: selectedDays,
mode: reminderMode,
localOnly: reminderMode === "permanent",
snoozeUntil:
date?.getTime() > Date.now() ? undefined : reminder?.snoozeUntil,
disabled: false
});
if (!reminderId) return;
const _reminder = await db.reminders?.reminder(reminderId);
if (reference && _reminder) {
await db.relations?.add(reference, {
id: _reminder?.id as string,
type: _reminder?.type
});
}
Notifications.scheduleNotification(_reminder as Reminder);
Navigation.queueRoutesForUpdate();
useRelationStore.getState().update();
close?.();
} catch (e) {
ToastManager.error(e as Error, undefined, "local");
}
}
return (
<View
style={{
paddingHorizontal: DefaultAppStyles.GAP,
maxHeight: "100%"
}}
>
<Heading size={AppFontSize.lg}>
{reminder ? strings.editReminder() : strings.newReminder()}
</Heading>
<Dialog context="local" />
<ScrollView
bounces={false}
style={{
marginBottom: DDS.isTab ? 25 : undefined
}}
>
<Input
fwdRef={titleRef}
defaultValue={reminder?.title || referencedItem?.title}
placeholder={strings.remindeMeOf()}
onChangeText={(text) => (title.current = text)}
wrapperStyle={{
marginTop: DefaultAppStyles.GAP_VERTICAL
}}
/>
<Input
defaultValue={
reminder ? reminder?.description : referencedItem?.headline
}
placeholder={strings.addShortNote()}
onChangeText={(text) => (details.current = text)}
containerStyle={{
maxHeight: 80
}}
multiline
textAlignVertical="top"
inputStyle={{
minHeight: 80,
paddingVertical: DefaultAppStyles.GAP_VERTICAL
}}
height={80}
wrapperStyle={{
marginBottom: DefaultAppStyles.GAP_VERTICAL
}}
/>
<ScrollView
style={{
flexDirection: "row",
marginBottom: DefaultAppStyles.GAP_VERTICAL,
height: 50
}}
horizontal
>
{Object.keys(ReminderModes).map((mode) => (
<Button
key={mode}
title={strings.reminderModes(
ReminderModes[mode as keyof typeof ReminderModes] as string
)}
style={{
marginRight: 12,
borderRadius: 100,
minWidth: 70,
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL
}}
proTag={mode === "Repeat"}
height={35}
type={
reminderMode ===
ReminderModes[mode as keyof typeof ReminderModes]
? "selected"
: "plain"
}
onPress={() => {
if (mode === "Repeat" && !PremiumService.get()) return;
setReminderMode(
ReminderModes[
mode as keyof typeof ReminderModes
] as Reminder["mode"]
);
if (mode === "Repeat") {
setSelectedDays((days) => {
if (days.length > 0) return days;
if (days.indexOf(date.getDay()) > -1) {
return days;
}
days.push(date.getDay());
return [...days];
});
}
}}
/>
))}
</ScrollView>
{reminderMode === ReminderModes.Repeat ? (
<View
style={{
backgroundColor: colors.secondary.background,
padding: DefaultAppStyles.GAP,
borderRadius: defaultBorderRadius,
marginBottom: DefaultAppStyles.GAP_VERTICAL
}}
>
<View
style={{
flexDirection: "row",
marginBottom:
recurringMode === "day" || recurringMode === "year" ? 0 : 12,
alignItems: "center"
}}
>
{Object.keys(RecurringModes).map((mode) => (
<Button
key={mode}
title={strings.recurringModes(
RecurringModes[mode as keyof typeof RecurringModes]
)}
style={{
marginRight: 6,
borderRadius: 100,
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL
}}
type={
recurringMode ===
RecurringModes[mode as keyof typeof RecurringModes]
? "selected"
: "plain"
}
onPress={() => {
setRecurringMode(
RecurringModes[
mode as keyof typeof RecurringModes
] as Reminder["recurringMode"]
);
setSelectedDays([]);
setRepeatFrequency(1);
}}
/>
))}
</View>
<RNScrollView showsHorizontalScrollIndicator={false} horizontal>
{recurringMode === RecurringModes.Daily ||
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);
return [...days];
}
days.push(index);
return [...days];
});
}}
/>
))
: MonthDays.map((item, index) => (
<Button
key={index + "monthday"}
title={index + 1 + ""}
type={
selectedDays.indexOf(index + 1) > -1
? "selected"
: "plain"
}
fontSize={AppFontSize.xs}
style={{
height: 40,
borderRadius: 100,
marginRight: 10
}}
onPress={() => {
setSelectedDays((days) => {
if (days.indexOf(index + 1) > -1) {
days.splice(days.indexOf(index + 1), 1);
return [...days];
}
days.push(index + 1);
return [...days];
});
}}
/>
))}
</RNScrollView>
</View>
) : null}
{reminderMode === ReminderModes.Permanent ? null : (
<View
style={{
width: "100%",
flexDirection: "column",
justifyContent: "center",
marginBottom: DefaultAppStyles.GAP_VERTICAL,
alignItems: "center"
}}
>
<DateTimePickerModal
isVisible={isDatePickerVisible}
mode="date"
onConfirm={handleConfirm}
onCancel={hideDatePicker}
is24Hour={db.settings.getTimeFormat() === "24-hour"}
date={date || new Date(Date.now())}
/>
<DatePicker
date={date}
maximumDate={dayjs(date).add(3, "months").toDate()}
onDateChange={handleConfirm}
textColor={isDark ? colors.static.white : colors.static.black}
fadeToColor={colors.primary.background}
theme={isDark ? "dark" : "light"}
androidVariant="nativeAndroid"
is24hourSource="locale"
locale={
db.settings?.getTimeFormat() === "24-hour" ? "en_GB" : "en_US"
}
mode={
reminderMode === ReminderModes.Repeat &&
recurringMode !== "year"
? "time"
: "datetime"
}
/>
{reminderMode === ReminderModes.Repeat ? null : (
<Button
style={{
width: "100%"
}}
title={date ? date.toLocaleDateString() : strings.selectDate()}
type={date ? "secondaryAccented" : "secondary"}
icon="calendar"
fontSize={AppFontSize.sm}
onPress={() => {
showDatePicker();
}}
/>
)}
</View>
)}
{reminderMode === ReminderModes.Once ||
reminderMode === ReminderModes.Permanent ? null : (
<View
style={{
borderRadius: defaultBorderRadius,
flexDirection: "row",
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL,
alignItems: "center",
justifyContent: "flex-start",
marginBottom: DefaultAppStyles.GAP_VERTICAL
}}
>
<>
<Paragraph
size={AppFontSize.xxs}
color={colors.secondary.paragraph}
>
{recurringMode === RecurringModes.Daily
? strings.reminderRepeatStrings.day(
dayjs(date).format("hh:mm A")
)
: recurringMode === RecurringModes.Year
? strings.reminderRepeatStrings.year(
dayjs(date).format("dddd, MMMM D, h:mm A")
)
: selectedDays.length === 7 &&
recurringMode === RecurringModes.Week
? strings.reminderRepeatStrings.week.daily(
dayjs(date).format("hh:mm A")
)
: selectedDays.length === 0
? strings.reminderRepeatStrings[
recurringMode as "week" | "month"
].selectDays()
: strings.reminderRepeatStrings.repeats(
repeatFrequency,
recurringMode as string,
getSelectedDaysText(selectedDays),
dayjs(date).format("hh:mm A")
)}
</Paragraph>
</>
</View>
)}
<ReminderTime
reminder={reminder}
style={{
width: "100%",
justifyContent: "flex-start",
borderWidth: 0,
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL,
alignSelf: "flex-start"
}}
/>
{reminderMode === ReminderModes.Permanent ? null : (
<RNScrollView
style={{
flexDirection: "row",
marginTop: DefaultAppStyles.GAP_VERTICAL,
height: 50
}}
horizontal
>
{Object.keys(ReminderNotificationModes).map((mode) => (
<Button
key={mode}
title={strings.reminderNotificationModes(
mode as keyof typeof ReminderNotificationModes
)}
style={{
marginRight: 12,
borderRadius: 100,
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL
}}
icon={
mode === "Silent"
? "minus-circle"
: mode === "Vibrate"
? "vibrate"
: "volume-high"
}
height={35}
type={
reminderNotificationMode ===
ReminderNotificationModes[
mode as keyof typeof ReminderNotificationModes
]
? "selected"
: "plain"
}
onPress={() => {
const _mode = ReminderNotificationModes[
mode as keyof typeof ReminderNotificationModes
] as Reminder["priority"];
SettingsService.set({
reminderNotificationMode: _mode
});
setReminderNotificatioMode(_mode);
}}
/>
))}
</RNScrollView>
)}
</ScrollView>
<Button
title={strings.save()}
type="accent"
style={{
paddingHorizontal: DefaultAppStyles.GAP * 2,
marginTop: DefaultAppStyles.GAP_VERTICAL,
width: "100%"
}}
onPress={saveReminder}
/>
</View>
);
}
ReminderSheet.present = (
reminder?: Reminder,
reference?: ItemReference,
isSheet?: boolean
) => {
presentSheet({
context: isSheet ? "local" : undefined,
component: (ref, close, update) => (
<ReminderSheet
actionSheetRef={ref}
close={close}
update={update}
reminder={reminder}
reference={reference}
/>
)
});
};

View File

@@ -52,24 +52,42 @@ const Sort = ({
hideGroupOptions?: boolean;
}) => {
const { colors } = useThemeColors();
const groupType =
screen === "Archive"
? "archive"
: screen === "Search"
? "search"
: screen === "Notes"
? "home"
: screen === "Trash" || type === "trash"
? "trash"
: ((type + "s") as GroupingKey);
const [groupOptions, setGroupOptions] = useState(
db.settings.getGroupOptions(
screen === "Notes"
? "home"
: screen === "Trash" || type === "trash"
? "trash"
: ((type + "s") as GroupingKey)
)
db.settings.getGroupOptions(groupType)
);
const getSortButtonTitle = () => {
const { sortDirection, groupBy, sortBy } = groupOptions || {};
const isAlphabetical = groupBy === "abc" || sortBy === "title";
const isDueDate = sortBy === "dueDate";
const isRelevance = sortBy === "relevance";
if (sortDirection === "asc") {
if (isAlphabetical) return strings.aToZ();
if (isDueDate) return strings.earliestFirst();
if (isRelevance) return strings.leastRelevantFirst();
return strings.oldNew();
} else {
if (isAlphabetical) return strings.zToA();
if (isDueDate) return strings.latestFirst();
if (isRelevance) return strings.mostRelevantFirst();
return strings.newOld();
}
};
const updateGroupOptions = async (_groupOptions: GroupOptions) => {
const groupType =
screen === "Notes"
? "home"
: screen === "Trash" || type === "trash"
? "trash"
: screen === "Favorites"
? "favorites"
: ((type + "s") as GroupingKey);
await db.settings.setGroupOptions(groupType, _groupOptions);
setGroupOptions(_groupOptions);
setTimeout(() => {
@@ -110,31 +128,10 @@ const Sort = ({
paddingHorizontal: DefaultAppStyles.GAP
}}
>
<Heading
size={AppFontSize.lg}
style={{
alignSelf: "center"
}}
>
{strings.sortBy()}
</Heading>
<Heading size={AppFontSize.lg}>{strings.sortBy()}</Heading>
<Button
title={
groupOptions?.sortDirection === "asc"
? groupOptions?.groupBy === "abc" ||
groupOptions?.sortBy === "title"
? strings.aToZ()
: groupOptions?.sortBy === "dueDate"
? strings.earliestFirst()
: strings.oldNew()
: groupOptions?.groupBy === "abc" ||
groupOptions?.sortBy === "title"
? strings.zToA()
: groupOptions?.sortBy === "dueDate"
? strings.latestFirst()
: strings.newOld()
}
title={getSortButtonTitle()}
icon={
groupOptions?.sortDirection === "asc"
? "sort-ascending"
@@ -158,13 +155,24 @@ const Sort = ({
borderBottomColor: colors.primary.border
}}
>
{Object.keys(SORT).map((item) =>
(item === "dueDate" && screen !== "Reminders") ||
(screen !== "Tags" &&
screen !== "Reminders" &&
item === "dateModified") ||
((screen === "Tags" || screen === "Reminders") &&
item === "dateEdited") ? null : (
{Object.keys(SORT).map((item) => {
const sortOptionVisibility = {
relevance: screen === "Search",
dueDate: screen === "Reminders",
dateModified: screen === "Tags" || screen === "Reminders",
dateEdited: screen !== "Tags" && screen !== "Reminders"
};
// Check if this sort option should be skipped for the current screen
const shouldSkip =
item in sortOptionVisibility &&
!sortOptionVisibility[item as keyof typeof sortOptionVisibility];
if (shouldSkip) {
return null;
}
return (
<Pressable
key={item}
type={groupOptions?.sortBy === item ? "selected" : "plain"}
@@ -202,8 +210,8 @@ const Sort = ({
/>
) : null}
</Pressable>
)
)}
);
})}
</View>
{!hideGroupOptions ? (

View File

@@ -40,6 +40,7 @@ import Sync from "../../../services/sync";
import Clipboard from "@react-native-clipboard/clipboard";
import { logoutUser } from "../../../screens/settings/logout";
import { sleep } from "../../../utils/time";
export const UserSheet = () => {
const ref = useSheetRef();
const { colors } = useThemeColors();
@@ -310,10 +311,10 @@ export const UserSheet = () => {
{
icon: "logout",
title: strings.logout(),
onPress: () => {
console.log("logout");
logoutUser();
onPress: async () => {
ref.current?.hide();
await sleep(300);
logoutUser();
},
hidden: !user
}

View File

@@ -211,6 +211,14 @@ const TabBar = (
const ids = useSideMenuNotebookSelectionStore
.getState()
.getSelectedItemIds();
if (!ids.length) {
ToastManager.show({
context: "local",
type: "error",
message: strings.noNotebooksSelectedToMove()
});
return;
}
const notebooks = await db.notebooks.all.items(ids);
Navigation.navigate("MoveNotebook", {
selectedNotebooks: notebooks

View File

@@ -80,6 +80,9 @@ function _MenuItem({
case "Monographs":
setItemCount(await db.monographs.all.count());
break;
case "Archive":
setItemCount(await db.notes.archived.count());
break;
case "Trash":
setItemCount((await db.trash.all()).length);
break;

View File

@@ -49,7 +49,8 @@ export const NotebookItem = ({
onPress,
onLongPress,
onAddNotebook,
canDisableSelectionMode
canDisableSelectionMode,
disableExpand
}: {
index: number;
item: TreeItem;
@@ -64,6 +65,7 @@ export const NotebookItem = ({
onLongPress?: () => void;
onAddNotebook?: () => void;
canDisableSelectionMode?: boolean;
disableExpand?: boolean;
}) => {
const notebook = item.notebook;
const isFocused = focused;
@@ -93,7 +95,11 @@ export const NotebookItem = ({
<View
style={{
paddingLeft:
item.depth > 0 && item.depth < 6 ? 15 * item.depth : undefined,
item.depth === 0
? undefined
: item.depth < 6
? 15 * item.depth
: 15 * 5,
width: "100%",
marginTop: 2
}}
@@ -162,7 +168,7 @@ export const NotebookItem = ({
}
testID={item.hasChildren ? `expand-notebook-${index}` : ""}
onPress={() => {
if (item.hasChildren) {
if (item.hasChildren && !disableExpand) {
onToggleExpanded?.();
} else {
onPress?.();
@@ -178,7 +184,7 @@ export const NotebookItem = ({
borderRadius: defaultBorderRadius
}}
name={
!item.hasChildren
!item.hasChildren || disableExpand
? "book-outline"
: expanded
? "chevron-down"

View File

@@ -20,8 +20,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { Notebook } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import { FlashList } from "@shopify/flash-list";
import React, { useEffect, useState } from "react";
import { FlatList, TextInput, View } from "react-native";
import { TextInput, View } from "react-native";
import { db } from "../../common/database";
import NotebookScreen from "../../screens/notebook";
import Navigation from "../../services/navigation";
@@ -42,6 +43,7 @@ import {
useSideMenuNotebookSelectionStore.setState({
multiSelect: true
});
export const SideMenuNotebooks = () => {
const tree = useSideMenuNotebookTreeStore((state) => state.tree);
const [notebooks, loading] = useNotebooks();
@@ -64,13 +66,19 @@ export const SideMenuNotebooks = () => {
const updateNotebooks = React.useCallback(() => {
if (lastQuery.current) {
// useSideMenuNotebookTreeStore.setState({
// isSearching: true
// });
db.lookup
.notebooks(lastQuery.current)
.sorted()
.sorted(db.settings.getGroupOptions("notebooks"))
.then((filtered) => {
setFilteredNotebooks(filtered);
});
} else {
// useSideMenuNotebookTreeStore.setState({
// isSearching: false
// });
setFilteredNotebooks(notebooks);
}
}, [notebooks]);
@@ -137,13 +145,13 @@ export const SideMenuNotebooks = () => {
/>
) : (
<>
<FlatList
<FlashList
data={tree}
bounces={false}
bouncesZoom={false}
overScrollMode="never"
keyExtractor={(item) => item.notebook.id}
windowSize={3}
// keyExtractor={(item,) => item.notebook.id}
estimatedItemSize={30}
ListHeaderComponent={
<View
style={{
@@ -196,7 +204,9 @@ const NotebookItemWrapper = React.memo(
const expanded = useSideMenuNotebookExpandedStore(
(state) => state.expanded[item.notebook.id]
);
const disableExpand = useSideMenuNotebookTreeStore(
(state) => state.isSearching
);
const selectionEnabled = useSideMenuNotebookSelectionStore(
(state) => state.enabled
);
@@ -256,6 +266,7 @@ const NotebookItemWrapper = React.memo(
.removeChildren(item.notebook.id);
}
}}
disableExpand={disableExpand}
selected={selected}
selectionEnabled={selectionEnabled}
selectionStore={useSideMenuNotebookSelectionStore}

View File

@@ -220,7 +220,7 @@ export const SideMenuTags = () => {
if (lastQuery.current) {
db.lookup
.tags(lastQuery.current.trim())
.sorted()
.sorted(db.settings.getGroupOptions("tags"))
.then(async (filtered) => {
setFilteredTags(filtered);
});

View File

@@ -321,6 +321,9 @@ const Input = ({
bottom={10}
onPress={button.onPress}
color={button.color}
style={{
marginRight: -8
}}
/>
)}

View File

@@ -54,6 +54,7 @@ type ButtonTypes =
| "accent"
| "shade"
| "secondary"
| "selectedAccent"
| "secondaryAccented"
| "inverted"
| "white"
@@ -126,6 +127,18 @@ const buttonTypes = (
isDark
)
},
selectedAccent: {
primary: colors.selected.accent,
text: colors.selected.accentForeground,
selected: colors.selected.accent,
borderWidth: 0.8,
borderColor: getColorLinearShade(colors.selected.accent, 0.05, isDark),
borderSelectedColor: getColorLinearShade(
colors.selected.accent,
0.05,
isDark
)
},
secondaryAccented: {
primary: colors.secondary.background,
text: colors.primary.accent,

View File

@@ -23,13 +23,11 @@ import { Platform, View } from "react-native";
import ActionSheet from "react-native-actions-sheet";
import useGlobalSafeAreaInsets from "../../../hooks/use-global-safe-area-insets";
import { useSettingStore } from "../../../stores/use-setting-store";
import { PremiumToast } from "../../premium/premium-toast";
import { Toast } from "../../toast";
import { useAppState } from "../../../hooks/use-app-state";
import SettingsService from "../../../services/settings";
import { useUserStore } from "../../../stores/use-user-store";
import { getContainerBorder } from "../../../utils/colors";
import { PremiumToast } from "../../premium/premium-toast";
import { Toast } from "../../toast";
import { NotesnookModule } from "../../../utils/notesnook-module";
/**
*
* @param {any} param0
@@ -63,6 +61,8 @@ const SheetWrapper = ({
const locked = useUserStore((state) => state.appLocked);
let width = dimensions.width > 600 ? 600 : 500;
const isGestureNavigationEnabled =
NotesnookModule.isGestureNavigationEnabled();
const style = React.useMemo(() => {
return {
@@ -77,14 +77,18 @@ const SheetWrapper = ({
borderBottomRightRadius: 0,
borderBottomLeftRadius: 0,
...getContainerBorder(colors.primary.border, 0.5),
borderBottomWidth: 0
borderBottomWidth: 0,
paddingBottom: isGestureNavigationEnabled
? insets.bottom
: insets.bottom || 48
};
}, [
colors.primary.background,
colors.primary.border,
largeTablet,
smallTablet,
width
width,
insets.bottom
]);
const _onOpen = () => {

View File

@@ -336,7 +336,7 @@ const emailconfirmed: { id: string; steps: TStep[] } = {
steps: [
{
title: strings.emailConfirmed(),
text: strings.emailNotConfirmedDesc(),
text: strings.emailConfirmedDesc(),
walkthroughItem: (colors) => (
<SvgView src={WELCOME_SVG(colors.primary.paragraph)} />
),

View File

@@ -41,7 +41,6 @@ import ExportNotesSheet from "../components/sheets/export-notes";
import PublishNoteSheet from "../components/sheets/publish-note";
import { ReferencesList } from "../components/sheets/references";
import { RelationsList } from "../components/sheets/relations-list/index";
import ReminderSheet from "../components/sheets/reminder";
import { useSideBarDraggingStore } from "../components/side-menu/dragging-store";
import { ButtonProps } from "../components/ui/button";
import { useTabStore } from "../screens/editor/tiptap/use-tab-store";
@@ -54,21 +53,24 @@ import {
} from "../services/event-manager";
import Navigation from "../services/navigation";
import Notifications from "../services/notifications";
import SettingsService from "../services/settings";
import { useArchivedStore } from "../stores/use-archived-store";
import { useMenuStore } from "../stores/use-menu-store";
import useNavigationStore from "../stores/use-navigation-store";
import { useRelationStore } from "../stores/use-relation-store";
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 { deleteItems } from "../utils/functions";
import { convertNoteToText } from "../utils/note-to-text";
import { sleep } from "../utils/time";
import SettingsService from "../services/settings";
import { useSettingStore } from "../stores/use-setting-store";
import AddReminder from "../screens/add-reminder";
export type ActionId =
| "select"
| "archive"
| "restore"
| "delete"
| "reorder"
@@ -108,7 +110,8 @@ export type ActionId =
| "favorite"
| "remove-from-notebook"
| "trash"
| "default-homepage";
| "default-homepage"
| "default-tag";
export type Action = {
id: ActionId;
@@ -156,6 +159,8 @@ export const useActions = ({
const [defaultNotebook, setDefaultNotebook] = useState(
db.settings.getDefaultNotebook()
);
const [defaultTag, setDefaultTag] = useState(db.settings.getDefaultTag());
const [noteInCurrentNotebook, setNoteInCurrentNotebook] = useState(false);
const [locked, setLocked] = useState(false);
const isHomepage = useSettingStore(
@@ -443,7 +448,8 @@ export const useActions = ({
title: strings.editReminder(),
icon: "pencil",
onPress: async () => {
ReminderSheet.present(item);
AddReminder.present(item);
close();
}
}
);
@@ -467,15 +473,39 @@ export const useActions = ({
}
if (item.type === "tag" || item.type === "notebook") {
actions.push({
id: "add-shortcut",
title: isPinnedToMenu ? strings.removeShortcut() : strings.addShortcut(),
icon: isPinnedToMenu ? "link-variant-remove" : "link-variant",
onPress: createMenuShortcut,
isToggle: true,
checked: isPinnedToMenu,
activeColor: colors.error.paragraph
});
actions.push(
{
id: "add-shortcut",
title: isPinnedToMenu
? strings.removeShortcut()
: strings.addShortcut(),
icon: isPinnedToMenu ? "link-variant-remove" : "link-variant",
onPress: createMenuShortcut,
isToggle: true,
checked: isPinnedToMenu,
activeColor: colors.error.paragraph
},
{
id: "default-tag",
title:
defaultTag === item.id
? strings.removeAsDefault()
: strings.setAsDefault(),
hidden: item.type !== "tag",
icon: "pound",
onPress: async () => {
if (defaultTag === item.id) {
await db.settings.setDefaultTag(undefined);
setDefaultTag(undefined);
} else {
await db.settings.setDefaultTag(item.id);
setDefaultTag(item.id);
}
close();
},
checked: defaultTag === item.id
}
);
}
if (item.type === "notebook") {
@@ -485,6 +515,8 @@ export const useActions = ({
title: strings.addNotebook(),
icon: "plus",
onPress: async () => {
close();
await sleep(300);
AddNotebookSheet.present(undefined, item);
}
},
@@ -493,6 +525,8 @@ export const useActions = ({
title: strings.editNotebook(),
icon: "square-edit-outline",
onPress: async () => {
close();
await sleep(300);
AddNotebookSheet.present(item);
}
},
@@ -651,7 +685,8 @@ export const useActions = ({
async function pinToNotifications() {
if (notifPinned) {
Notifications.remove(item.id);
await Notifications.remove(item.id);
await sleep(500);
await Notifications.get();
setNotifPinned(isNotePinnedInNotifications(item));
return;
@@ -665,7 +700,8 @@ export const useActions = ({
return;
}
Notifications.pinNote(item.id);
await Notifications.pinNote(item.id);
await sleep(500);
await Notifications.get();
setNotifPinned(isNotePinnedInNotifications(item));
}
@@ -877,12 +913,18 @@ export const useActions = ({
referenceType: "reminder",
relationType: "from",
title: strings.dataTypesPluralCamelCase.reminder(),
onAdd: () => ReminderSheet.present(undefined, item, true),
onAdd: () => {
AddReminder.present(undefined, item);
close();
},
button: {
title: strings.add(),
type: "accent",
onPress: () => ReminderSheet.present(undefined, item, true),
icon: "plus"
type: "plain",
onPress: () => {
AddReminder.present(undefined, item);
close();
},
icon: "plus",
iconSize: 20
}
});
}
@@ -926,7 +968,8 @@ export const useActions = ({
title: strings.remindMe(),
icon: "clock-plus-outline",
onPress: () => {
ReminderSheet.present(undefined, { id: item.id, type: "note" });
close();
AddReminder.present(undefined, item);
}
},
{
@@ -972,6 +1015,19 @@ export const useActions = ({
reference: item as ItemReference
});
}
},
{
id: "archive",
title: !item.archived ? strings.archive() : strings.unarchive(),
icon: "archive",
onPress: async () => {
db.notes.archive(!item.archived, item.id);
setItem((await db.notes.note(item.id)) as Item);
Navigation.queueRoutesForUpdate();
useArchivedStore.getState().refresh();
},
checked: item.archived,
isToggle: true
}
);

View File

@@ -49,7 +49,6 @@ import { MMKV } from "../common/database/mmkv";
import { endProgress, startProgress } from "../components/dialogs/progress";
import Migrate from "../components/sheets/migrate";
import NewFeature from "../components/sheets/new-feature";
import ReminderSheet from "../components/sheets/reminder";
import { Walkthrough } from "../components/walkthroughs";
import {
resetTabStore,
@@ -103,6 +102,7 @@ import { getGithubVersion } from "../utils/github-version";
import { fluidTabsRef } from "../utils/global-refs";
import { NotesnookModule } from "../utils/notesnook-module";
import { sleep } from "../utils/time";
import AddReminder from "../screens/add-reminder";
const onCheckSyncStatus = async (type: SyncStatusEvent) => {
const { disableSync, disableAutoSync } = SettingsService.get();
@@ -182,10 +182,10 @@ const onAppOpenedFromURL = async (event: { url: string }) => {
const id = new URL(url).searchParams.get("id");
if (id) {
const reminder = await db.reminders.reminder(id);
if (reminder) ReminderSheet.present(reminder);
if (reminder) AddReminder.present(reminder);
}
} else if (url.startsWith("https://notesnook.com/new_reminder")) {
ReminderSheet.present();
AddReminder.present();
}
} catch (e) {
console.error(e);
@@ -341,8 +341,15 @@ const doAppLoadActions = async () => {
};
const checkAppUpdateAvailable = async () => {
if (__DEV__ || Config.isTesting === "true" || Config.FDROID_BUILD || BETA)
if (
__DEV__ ||
Config.isTesting === "true" ||
Config.FDROID_BUILD ||
BETA ||
!SettingsService.getProperty("checkForUpdates")
)
return;
try {
const version =
Config.GITHUB_RELEASE === "true"

View File

@@ -26,12 +26,14 @@ import { useCallback } from "react";
* @returns Is keyboard floating or not
*/
const useIsFloatingKeyboard = () => {
const { width } = useWindowDimensions();
const { width, height } = useWindowDimensions();
const [floating, setFloating] = useState<boolean>(false);
const onKeyboardWillChangeFrame = useCallback(
(event: KeyboardEvent) => {
setFloating(event.endCoordinates.width < width);
setFloating(
event.endCoordinates.width === 0 || event.endCoordinates.width < width
);
},
[width]
);
@@ -41,8 +43,13 @@ const useIsFloatingKeyboard = () => {
"keyboardWillChangeFrame",
onKeyboardWillChangeFrame
);
const sub2 = Keyboard.addListener(
"keyboardWillShow",
onKeyboardWillChangeFrame
);
return () => {
sub1?.remove();
sub2?.remove();
};
}, [onKeyboardWillChangeFrame, width]);

View File

@@ -52,7 +52,14 @@ const useTimer = (initialId?: string) => {
};
}, [seconds, id]);
return { seconds, setId, start };
const reset = () => {
if (id) {
timers[id] = 0;
setSeconds(0);
}
};
return { seconds, setId, start, reset };
};
export default useTimer;

View File

@@ -30,6 +30,10 @@ import React, {
useState
} from "react";
import { Dimensions, LayoutChangeEvent, Platform, View } from "react-native";
import Orientation, {
OrientationType,
useDeviceOrientationChange
} from "react-native-orientation-locker";
import Animated, {
useAnimatedStyle,
useSharedValue,
@@ -67,10 +71,6 @@ import {
} from "../utils/events";
import { editorRef, fluidTabsRef } from "../utils/global-refs";
import { AppNavigationStack } from "./navigation-stack";
import Orientation, {
OrientationType,
useDeviceOrientationChange
} from "react-native-orientation-locker";
const MOBILE_SIDEBAR_SIZE = 0.85;
@@ -421,7 +421,6 @@ export const FluidPanelsView = React.memo(
height: "100%",
width: "100%",
backgroundColor: colors.primary.background,
paddingBottom: Platform.OS === "android" ? insets?.bottom : 0,
marginRight:
orientation === "LANDSCAPE-RIGHT" && Platform.OS === "ios"
? insets.right
@@ -530,17 +529,20 @@ const onChangeTab = async (event: { i: number; from: number }) => {
activateKeepAwake();
eSendEvent(eOnEnterEditor);
if (!useTabStore.getState().getCurrentNoteId()) {
eSendEvent(eOnLoadNote, {
newNote: true
});
} else {
if (
useTabStore.getState().getTab(useTabStore.getState().currentTab)
?.session?.locked
) {
eSendEvent(eUnlockNote);
}
if (
useTabStore.getState().getTab(useTabStore.getState().currentTab)?.session
?.locked
) {
eSendEvent(eUnlockNote);
}
if (
fluidTabsRef.current?.tabChangedFromSwipeAction.value &&
!useTabStore.getState().getNoteIdForTab(useTabStore.getState().currentTab)
) {
editorController?.current?.commands?.focus(
useTabStore.getState().currentTab
);
}
} else {
if (event.from === 2) {

View File

@@ -21,6 +21,7 @@ import { useThemeColors } from "@notesnook/theme";
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import * as React from "react";
import { db } from "../common/database";
import { hideAllTooltips } from "../hooks/use-tooltip";
import SettingsService from "../services/settings";
import useNavigationStore, {
@@ -29,7 +30,7 @@ import useNavigationStore, {
import { useSelectionStore } from "../stores/use-selection-store";
import { useSettingStore } from "../stores/use-setting-store";
import { rootNavigatorRef } from "../utils/global-refs";
import { db } from "../common/database";
import Navigation from "../services/navigation";
const RootStack = createNativeStackNavigator();
const AppStack = createNativeStackNavigator();
@@ -43,7 +44,7 @@ let Reminders: any = null;
let Monographs: any = null;
let TaggedNotes: any = null;
let ColoredNotes: any = null;
let Archive: any = null;
const AppNavigation = React.memo(
() => {
const { colors } = useThemeColors();
@@ -192,6 +193,14 @@ const AppNavigation = React.memo(
initialParams={home.name === "ColoredNotes" ? home.params : undefined}
/>
<AppStack.Screen
name="Archive"
getComponent={() => {
Archive = Archive || require("../screens/archive").default;
return Archive;
}}
/>
<AppStack.Screen
name="Reminders"
getComponent={() => {
@@ -239,18 +248,26 @@ let LinkNotebooks: any = null;
let MoveNotebook: any = null;
let MoveNotes: any = null;
let Settings: any = null;
let ManageTags: any = null;
let AddReminder: any = null;
export const RootNavigation = () => {
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
const clearSelection = useSelectionStore((state) => state.clearSelection);
const onStateChange = React.useCallback(() => {
if (useSelectionStore.getState().selectionMode) {
clearSelection();
}
hideAllTooltips();
}, [clearSelection]);
const onStateChange = React.useCallback(
(state: any) => {
if (useSelectionStore.getState().selectionMode) {
clearSelection();
}
setTimeout(() => {
Navigation.resetRootState(state);
}, 1000);
hideAllTooltips();
},
[clearSelection]
);
return (
<NavigationContainer onStateChange={onStateChange} ref={rootNavigatorRef}>
@@ -318,6 +335,24 @@ export const RootNavigation = () => {
return Settings;
}}
/>
<RootStack.Screen
name="ManageTags"
getComponent={() => {
ManageTags =
ManageTags || require("../screens/manage-tags").default;
return ManageTags;
}}
/>
<RootStack.Screen
name="AddReminder"
getComponent={() => {
AddReminder =
AddReminder || require("../screens/add-reminder").default;
return AddReminder;
}}
/>
</RootStack.Navigator>
</NavigationContainer>
);

View File

@@ -0,0 +1,621 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Note, Reminder } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import dayjs from "dayjs";
import React, { useRef, useState } from "react";
import {
KeyboardAvoidingView,
Platform,
ScrollView,
TextInput,
View
} from "react-native";
import DatePicker from "react-native-date-picker";
import DateTimePickerModal from "react-native-modal-datetime-picker";
import { SafeAreaView } from "react-native-safe-area-context";
import { db } from "../../common/database";
import { Dialog } from "../../components/dialog";
import { Header } from "../../components/header";
import { Button } from "../../components/ui/button";
import Input from "../../components/ui/input";
import { ReminderTime } from "../../components/ui/reminder-time";
import Paragraph from "../../components/ui/typography/paragraph";
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 { AppFontSize, defaultBorderRadius } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { getFormattedDate } from "@notesnook/common";
const ReminderModes =
Platform.OS === "ios"
? {
Once: "once",
Repeat: "repeat"
}
: {
Once: "once",
Repeat: "repeat",
Permanent: "permanent"
};
const RecurringModes = {
Daily: "day",
Week: "week",
Month: "month",
Year: "year"
};
const WeekDays = new Array(7).fill(true);
const MonthDays = new Array(31).fill(true);
const WeekDayNames = {
0: "Sunday",
1: "Monday",
2: "Tuesday",
3: "Wednesday",
4: "Thursday",
5: "Friday",
6: "Saturday"
};
const ReminderNotificationModes = {
Silent: "silent",
Vibrate: "vibrate",
Urgent: "urgent"
};
export default function AddReminder(props: NavigationProps<"AddReminder">) {
const { reminder, reference } = props.route.params;
const { colors, isDark } = useThemeColors();
const [reminderMode, setReminderMode] = useState<Reminder["mode"]>(
reminder?.mode || "once"
);
const [recurringMode, setRecurringMode] = useState<Reminder["recurringMode"]>(
reminder?.recurringMode || "week"
);
const [selectedDays, setSelectedDays] = useState<number[]>(
reminder?.selectedDays || []
);
const [date, setDate] = useState<Date>(
new Date(reminder?.date || Date.now())
);
const [reminderNotificationMode, setReminderNotificatioMode] = useState<
Reminder["priority"]
>(reminder?.priority || SettingsService.get().reminderNotificationMode);
const [isDatePickerVisible, setDatePickerVisibility] = useState(false);
const [repeatFrequency, setRepeatFrequency] = useState(1);
const referencedItem = reference ? (reference as Note) : null;
const title = useRef<string | undefined>(
!reminder ? referencedItem?.title : reminder?.title
);
const details = useRef<string | undefined>(
!reminder ? referencedItem?.headline : reminder?.description
);
const titleRef = useRef<TextInput>(null);
const descriptionRef = useRef<TextInput>(null);
const timer = useRef<NodeJS.Timeout>();
const showDatePicker = () => {
setDatePickerVisibility(true);
};
const hideDatePicker = () => {
setDatePickerVisibility(false);
};
const handleConfirm = (date: Date) => {
timer.current = setTimeout(() => {
hideDatePicker();
setDate(date);
}, 10);
};
function nth(n: number) {
return (
["st", "nd", "rd"][(((((n < 0 ? -n : n) + 90) % 100) - 10) % 10) - 1] ||
"th"
);
}
function getSelectedDaysText(selectedDays: number[]) {
const text = selectedDays
.sort((a, b) => a - b)
.map((day, index) => {
const isLast = index === selectedDays.length - 1;
const isSecondLast = index === selectedDays.length - 2;
const joinWith = isSecondLast ? " & " : isLast ? "" : ", ";
return recurringMode === RecurringModes.Week
? WeekDayNames[day as keyof typeof WeekDayNames] + joinWith
: `${day}${nth(day)} ${joinWith}`;
})
.join("");
return text;
}
async function saveReminder() {
try {
if (!(await Notifications.checkAndRequestPermissions(true)))
throw new Error(strings.noNotificationPermission());
if (!date && reminderMode !== ReminderModes.Permanent) return;
if (
reminderMode === ReminderModes.Repeat &&
recurringMode !== "day" &&
recurringMode !== "year" &&
selectedDays.length === 0
)
throw new Error(strings.selectDayError());
if (!title.current) throw new Error(strings.setTitleError());
if (
date.getTime() < Date.now() &&
reminderMode === "once" &&
!props.route.params.reminder
) {
throw new Error(strings.dateError());
}
date.setSeconds(0, 0);
const reminderId = await db.reminders?.add({
id: reminder?.id,
date: date?.getTime(),
priority: reminderNotificationMode,
title: title.current,
description: details.current,
recurringMode: recurringMode,
selectedDays: selectedDays,
mode: reminderMode,
localOnly: reminderMode === "permanent",
snoozeUntil:
date?.getTime() > Date.now() ? undefined : reminder?.snoozeUntil,
disabled: false
});
if (!reminderId) return;
const _reminder = await db.reminders?.reminder(reminderId);
if (reference && _reminder) {
await db.relations?.add(reference, {
id: _reminder?.id as string,
type: _reminder?.type
});
}
Notifications.scheduleNotification(_reminder as Reminder);
Navigation.queueRoutesForUpdate();
useRelationStore.getState().update();
Navigation.goBack();
} catch (e) {
ToastManager.error(e as Error, undefined);
}
}
const KeyboardViewIOS = Platform.OS === "ios" ? KeyboardAvoidingView : View;
return (
<SafeAreaView
style={{
backgroundColor: colors.primary.background,
flex: 1
}}
>
<KeyboardViewIOS
behavior="padding"
style={{
flex: 1
}}
>
<Header
title={reminder ? strings.editReminder() : strings.newReminder()}
canGoBack
rightButton={{
name: "check",
onPress: saveReminder
}}
/>
<Dialog context="local" />
<ScrollView
style={{
marginBottom: DDS.isTab ? 25 : undefined,
paddingHorizontal: DefaultAppStyles.GAP
}}
keyboardDismissMode="interactive"
keyboardShouldPersistTaps="handled"
>
<Input
fwdRef={titleRef}
defaultValue={reminder?.title || referencedItem?.title}
placeholder={strings.remindeMeOf()}
onChangeText={(text) => (title.current = text)}
autoFocus
wrapperStyle={{
marginTop: DefaultAppStyles.GAP_VERTICAL
}}
onSubmit={() => {
descriptionRef.current?.focus();
}}
/>
<Input
defaultValue={
reminder ? reminder?.description : referencedItem?.headline
}
fwdRef={descriptionRef}
placeholder={strings.addShortNote()}
onChangeText={(text) => (details.current = text)}
containerStyle={{
maxHeight: 80
}}
multiline
textAlignVertical="top"
inputStyle={{
minHeight: 80,
paddingVertical: DefaultAppStyles.GAP_VERTICAL
}}
height={80}
wrapperStyle={{
marginBottom: DefaultAppStyles.GAP_VERTICAL
}}
/>
<ScrollView
style={{
flexDirection: "row",
marginBottom: DefaultAppStyles.GAP_VERTICAL
}}
horizontal
>
{Object.keys(ReminderModes).map((mode) => (
<Button
key={mode}
title={strings.reminderModes(
ReminderModes[mode as keyof typeof ReminderModes] as string
)}
style={{
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL,
marginRight: DefaultAppStyles.GAP_SMALL
}}
proTag={mode === "Repeat"}
height={35}
type={
reminderMode ===
ReminderModes[mode as keyof typeof ReminderModes]
? "selectedAccent"
: "plain"
}
onPress={() => {
if (mode === "Repeat" && !PremiumService.get()) return;
setReminderMode(
ReminderModes[
mode as keyof typeof ReminderModes
] as Reminder["mode"]
);
if (mode === "Repeat") {
setSelectedDays((days) => {
if (days.length > 0) return days;
if (days.indexOf(date.getDay()) > -1) {
return days;
}
days.push(date.getDay());
return [...days];
});
}
}}
/>
))}
</ScrollView>
{reminderMode === ReminderModes.Repeat ? (
<View
style={{
backgroundColor: colors.secondary.background,
padding: DefaultAppStyles.GAP,
borderRadius: defaultBorderRadius,
marginBottom: DefaultAppStyles.GAP_VERTICAL
}}
>
<View
style={{
flexDirection: "row",
marginBottom:
recurringMode === "day" || recurringMode === "year"
? 0
: 12,
alignItems: "center"
}}
>
{Object.keys(RecurringModes).map((mode) => (
<Button
key={mode}
title={strings.recurringModes(
RecurringModes[mode as keyof typeof RecurringModes]
)}
style={{
marginRight: 6,
borderRadius: 100,
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL
}}
type={
recurringMode ===
RecurringModes[mode as keyof typeof RecurringModes]
? "selected"
: "plain"
}
onPress={() => {
setRecurringMode(
RecurringModes[
mode as keyof typeof RecurringModes
] as Reminder["recurringMode"]
);
setSelectedDays([]);
setRepeatFrequency(1);
}}
/>
))}
</View>
<ScrollView showsHorizontalScrollIndicator={false} horizontal>
{recurringMode === RecurringModes.Daily ||
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);
return [...days];
}
days.push(index);
return [...days];
});
}}
/>
))
: MonthDays.map((item, index) => (
<Button
key={index + "monthday"}
title={index + 1 + ""}
type={
selectedDays.indexOf(index + 1) > -1
? "selected"
: "plain"
}
fontSize={AppFontSize.xs}
style={{
height: 40,
borderRadius: 100,
marginRight: 10
}}
onPress={() => {
setSelectedDays((days) => {
if (days.indexOf(index + 1) > -1) {
days.splice(days.indexOf(index + 1), 1);
return [...days];
}
days.push(index + 1);
return [...days];
});
}}
/>
))}
</ScrollView>
</View>
) : null}
{reminderMode === ReminderModes.Permanent ? null : (
<View
style={{
width: "100%",
flexDirection: "column",
justifyContent: "center",
marginBottom: DefaultAppStyles.GAP_VERTICAL,
alignItems: "center"
}}
>
<DateTimePickerModal
isVisible={isDatePickerVisible}
mode="datetime"
onConfirm={handleConfirm}
onCancel={hideDatePicker}
isDarkModeEnabled={isDark}
is24Hour={db.settings.getTimeFormat() === "24-hour"}
date={date || new Date(Date.now())}
/>
<DatePicker
date={date}
maximumDate={dayjs(date).add(3, "months").toDate()}
onDateChange={handleConfirm}
textColor={isDark ? colors.static.white : colors.static.black}
fadeToColor={colors.primary.background}
theme={isDark ? "dark" : "light"}
androidVariant="nativeAndroid"
is24hourSource="locale"
locale={
db.settings?.getTimeFormat() === "24-hour" ? "en_GB" : "en_US"
}
mode={
reminderMode === ReminderModes.Repeat &&
recurringMode !== "year"
? "time"
: "datetime"
}
/>
{reminderMode === ReminderModes.Repeat ? null : (
<Button
style={{
width: "100%"
}}
title={
date
? getFormattedDate(date, "date-time")
: strings.selectDate()
}
type={date ? "secondaryAccented" : "secondary"}
icon="calendar"
fontSize={AppFontSize.sm}
onPress={() => {
showDatePicker();
}}
/>
)}
</View>
)}
{reminderMode === ReminderModes.Once ||
reminderMode === ReminderModes.Permanent ? null : (
<View
style={{
borderRadius: defaultBorderRadius,
flexDirection: "row",
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL,
alignItems: "center",
justifyContent: "flex-start",
marginBottom: DefaultAppStyles.GAP_VERTICAL
}}
>
<>
<Paragraph
size={AppFontSize.xxs}
color={colors.secondary.paragraph}
>
{recurringMode === RecurringModes.Daily
? strings.reminderRepeatStrings.day(
dayjs(date).format("hh:mm A")
)
: recurringMode === RecurringModes.Year
? strings.reminderRepeatStrings.year(
dayjs(date).format("dddd, MMMM D, h:mm A")
)
: selectedDays.length === 7 &&
recurringMode === RecurringModes.Week
? strings.reminderRepeatStrings.week.daily(
dayjs(date).format("hh:mm A")
)
: selectedDays.length === 0
? strings.reminderRepeatStrings[
recurringMode as "week" | "month"
].selectDays()
: strings.reminderRepeatStrings.repeats(
repeatFrequency,
recurringMode as string,
getSelectedDaysText(selectedDays),
dayjs(date).format("hh:mm A")
)}
</Paragraph>
</>
</View>
)}
<ReminderTime
reminder={reminder}
style={{
width: "100%",
justifyContent: "flex-start",
borderWidth: 0,
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL,
alignSelf: "flex-start"
}}
/>
{reminderMode === ReminderModes.Permanent ? null : (
<ScrollView
style={{
flexDirection: "row",
marginTop: DefaultAppStyles.GAP_VERTICAL,
height: 50
}}
horizontal
>
{Object.keys(ReminderNotificationModes).map((mode) => (
<Button
key={mode}
title={strings.reminderNotificationModes(
mode as keyof typeof ReminderNotificationModes
)}
style={{
marginRight: 12,
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL
}}
icon={
mode === "Silent"
? "minus-circle"
: mode === "Vibrate"
? "vibrate"
: "volume-high"
}
fontSize={AppFontSize.xs}
height={35}
type={
reminderNotificationMode ===
ReminderNotificationModes[
mode as keyof typeof ReminderNotificationModes
]
? "selectedAccent"
: "plain"
}
onPress={() => {
const _mode = ReminderNotificationModes[
mode as keyof typeof ReminderNotificationModes
] as Reminder["priority"];
SettingsService.set({
reminderNotificationMode: _mode
});
setReminderNotificatioMode(_mode);
}}
/>
))}
</ScrollView>
)}
</ScrollView>
</KeyboardViewIOS>
</SafeAreaView>
);
}
AddReminder.present = (reminder?: Reminder, reference?: Note) => {
Navigation.navigate("AddReminder", {
reminder,
reference
});
};

View File

@@ -0,0 +1,88 @@
/*
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 DelayLayout from "../../components/delay-layout";
import { Header } from "../../components/header";
import List from "../../components/list";
import SelectionHeader from "../../components/selection-header";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import Navigation, { NavigationProps } from "../../services/navigation";
import SettingsService from "../../services/settings";
import useNavigationStore from "../../stores/use-navigation-store";
import { db } from "../../common/database";
import { strings } from "@notesnook/intl";
import { useArchived } from "../../stores/use-archived-store";
export const Archive = ({ navigation, route }: NavigationProps<"Archive">) => {
const [archive, loading, refresh] = useArchived();
const isFocused = useNavigationFocus(navigation, {
onFocus: (prev) => {
Navigation.routeNeedsUpdate(
route.name,
Navigation.routeUpdateFunctions[route.name]
);
useNavigationStore.getState().setFocusedRouteId(route?.name);
return false;
},
onBlur: () => false,
delay: SettingsService.get().homepage === route.name ? 1 : -1
});
return (
<>
<SelectionHeader id={route.name} items={archive} type="note" />
<Header
renderedInRoute={route.name}
title={strings.routes[route.name]()}
canGoBack={false}
hasSearch={true}
id={route.name}
onSearch={() => {
Navigation.push("Search", {
placeholder: strings.searchInRoute(route.name),
type: "note",
title: route.name,
route: route.name,
items: db.notes.archived
});
}}
/>
<DelayLayout wait={loading}>
<List
data={archive}
dataType="note"
onRefresh={() => {
refresh();
}}
renderedInRoute="Archive"
loading={loading}
placeholder={{
title: strings.yourArchive(),
paragraph: strings.yourArchiveIsEmpty(),
loading: strings.loadingArchive()
}}
headerTitle={strings.routes.Archive()}
/>
</DelayLayout>
</>
);
};
export default Archive;

View File

@@ -236,6 +236,11 @@ class Commands {
const tabId = useTabStore.getState().currentTab;
return this.sendCommand("scrollIntoViewById", id, tabId);
};
scrollToSearchResult = (index: number) => {
const tabId = useTabStore.getState().currentTab;
return this.sendCommand("scrollToSearchResult", index, tabId);
};
}
export default Commands;

View File

@@ -38,12 +38,11 @@ import { WebViewMessageEvent } from "react-native-webview";
import { DatabaseLogger, db } from "../../../common/database";
import downloadAttachment from "../../../common/filesystem/download-attachment";
import { AuthMode } from "../../../components/auth/common";
import { Properties } from "../../../components/properties";
import EditorTabs from "../../../components/sheets/editor-tabs";
import { Issue } from "../../../components/sheets/github/issue";
import LinkNote from "../../../components/sheets/link-note";
import ManageTagsSheet from "../../../components/sheets/manage-tags";
import { RelationsList } from "../../../components/sheets/relations-list";
import ReminderSheet from "../../../components/sheets/reminder";
import TableOfContents from "../../../components/sheets/toc";
import { DDS } from "../../../services/device-detection";
import {
@@ -74,11 +73,13 @@ import {
} from "../../../utils/events";
import { openLinkInBrowser } from "../../../utils/functions";
import { fluidTabsRef } from "../../../utils/global-refs";
import { sleep } from "../../../utils/time";
import ManageTags from "../../manage-tags";
import { useDragState } from "../../settings/editor/state";
import { EditorMessage, EditorProps, useEditorType } from "./types";
import { useTabStore } from "./use-tab-store";
import { editorState, openInternalLink } from "./utils";
import { Properties } from "../../../components/properties";
import AddReminder from "../../add-reminder";
const publishNote = async () => {
const user = useUserStore.getState().user;
@@ -130,7 +131,6 @@ const showActionsheet = async () => {
.getState()
.getNoteIdForTab(useTabStore.getState().currentTab!);
if (noteId) {
console.log("OPEN NOTE");
const note = await db.notes?.note(noteId);
Properties.present(note, false);
} else {
@@ -437,7 +437,7 @@ export const useEditorEvents = (
referenceType: "reminder",
relationType: "from",
title: strings.dataTypesPluralCamelCase.reminder(),
onAdd: () => ReminderSheet.present(undefined, note, true)
onAdd: () => AddReminder.present(undefined, note)
});
break;
case EditorEvents.newtag:
@@ -448,7 +448,7 @@ export const useEditorEvents = (
});
return;
}
ManageTagsSheet.present([noteId]);
ManageTags.present([noteId]);
break;
case EditorEvents.tag:
if (editorMessage.value) {
@@ -613,6 +613,10 @@ export const useEditorEvents = (
eSendEvent(eEditorTabFocused, editorMessage.tabId);
if (!editor.state.current.initialLoadCalled) break;
if (editorMessage.noteId) {
// Wait for next tick to ensure the tab is not changed.
await sleep(1);
if (useTabStore.getState().currentTab !== editorMessage.tabId)
return;
if (!useSettingStore.getState().isAppLoading) {
const note = await db.notes.note(editorMessage.noteId);
if (note) {

View File

@@ -365,14 +365,24 @@ export const useEditor = (
}
});
const defaultNotebook = db.settings.getDefaultNotebook();
if (!state.current.onNoteCreated && defaultNotebook) {
onNoteCreated(id, {
type: "notebook",
id: defaultNotebook
});
if (!state.current.onNoteCreated) {
const defaultNotebook = db.settings.getDefaultNotebook();
if (defaultNotebook) {
onNoteCreated(id, {
type: "notebook",
id: defaultNotebook
});
}
const defaultTag = db.settings.getDefaultTag();
if (defaultTag) {
onNoteCreated(id, {
type: "tag",
id: defaultTag
});
}
} else {
state.current?.onNoteCreated && state.current.onNoteCreated(id);
state.current.onNoteCreated(id);
}
}
@@ -469,8 +479,7 @@ export const useEditor = (
}
) => {
currentNotes.current[note.id] = note;
const locked = note && (await db.vaults.itemExists(note));
if ((locked || note.content) && note.content?.data) {
if (note.content && note.content?.data) {
currentContents.current[note.id] = {
data: note.content?.data,
type: note.content?.type || "tiptap",
@@ -498,6 +507,7 @@ export const useEditor = (
session?: TabSessionItem;
newTab?: boolean;
refresh?: boolean;
searchResultIndex?: number;
}) => {
loadNoteMutex.runExclusive(async () => {
if (!event) return;
@@ -683,13 +693,17 @@ export const useEditor = (
{
data: currentContents.current[item.id]?.data || "",
scrollTop: tab?.session?.scrollTop,
selection: tab?.session?.selection
selection: tab?.session?.selection,
searchResultIndex: event.searchResultIndex
},
tabId,
10000
);
setTimeout(() => {
if (event.searchResultIndex !== undefined) {
commands.scrollToSearchResult(event.searchResultIndex);
}
if (blockIdRef.current) {
commands.scrollIntoViewById(blockIdRef.current);
blockIdRef.current = undefined;
@@ -741,6 +755,8 @@ export const useEditor = (
? (data as ContentItem).noteId
: data.id;
if (!useTabStore.getState().hasTabForNote(noteId)) return;
const note = data.type === "note" ? data : await db.notes?.note(noteId);
lock.current = true;
// Handle this case where note was locked on another device and synced.
@@ -859,7 +875,6 @@ export const useEditor = (
}
lastContentChangeTime.current[note.id] = note.dateEdited;
console.log(tab.session?.selection);
await postMessage(
NativeEvents.updatehtml,
{

View File

@@ -98,7 +98,7 @@ class TabSessionStorage {
static get(id: string): TabSessionItem | null {
if (!id) return null;
return TabSessionStorage.storage.getMap(id);
return TabSessionStorage.storage.getMap(id) || null;
}
static set(id: string, session: TabSessionItem): void {

View File

@@ -96,14 +96,16 @@ export const EditorWrapper = ({ widths }: { widths: any }) => {
minHeight: "100%",
backgroundColor: toolBarColors.primary.background,
borderLeftWidth: DDS.isTab ? 1 : 0,
borderLeftColor: DDS.isTab ? colors.secondary.background : "transparent"
borderLeftColor: DDS.isTab
? colors.secondary.background
: "transparent",
paddingBottom: insets.bottom
}}
>
{loading || !introCompleted ? null : (
<KeyboardAvoidingViewIOS
behavior="padding"
style={{
marginBottom: getMarginBottom(),
backgroundColor: colors.primary.background,
flex: 1
}}

View File

@@ -300,6 +300,8 @@ const NotebookItemWrapper = React.memo(
const expanded = useNotebookExpandedStore(
(state) => state.expanded[item.notebook.id]
);
const disableExpand = useNotebookTreeStore((state) => state.isSearching);
const selectionEnabled = useNotebookSelectionStore(
(state) => state.enabled
);
@@ -371,6 +373,7 @@ const NotebookItemWrapper = React.memo(
selectionEnabled={selectionEnabled}
selectionStore={useNotebookSelectionStore}
onItemUpdate={onItemUpdate}
disableExpand={disableExpand}
focused={false}
onPress={onPress}
onAddNotebook={() => {

View File

@@ -29,27 +29,30 @@ import React, {
useRef,
useState
} from "react";
import { SafeAreaView } from "react-native-safe-area-context";
import { TextInput, View, useWindowDimensions } from "react-native";
import { ActionSheetRef } from "react-native-actions-sheet";
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../../common/database";
import { useDBItem } from "../../../hooks/use-db-item";
import { ToastManager, presentSheet } from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { db } from "../../common/database";
import { useDBItem } from "../../hooks/use-db-item";
import { ToastManager } from "../../services/event-manager";
import Navigation, { NavigationProps } from "../../services/navigation";
import {
ItemSelection,
createItemSelectionStore
} from "../../../stores/item-selection-store";
import { useRelationStore } from "../../../stores/use-relation-store";
import { useTagStore } from "../../../stores/use-tag-store";
import { defaultBorderRadius, AppFontSize } from "../../../utils/size";
import Input from "../../ui/input";
import { Pressable } from "../../ui/pressable";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
} from "../../stores/item-selection-store";
import { useRelationStore } from "../../stores/use-relation-store";
import { useTagStore } from "../../stores/use-tag-store";
import { defaultBorderRadius, AppFontSize } from "../../utils/size";
import Input from "../../components/ui/input";
import { Pressable } from "../../components/ui/pressable";
import Heading from "../../components/ui/typography/heading";
import Paragraph from "../../components/ui/typography/paragraph";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../../utils/styles";
import { DefaultAppStyles } from "../../utils/styles";
import { Header } from "../../components/header";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
async function updateInitialSelectionState(items: string[]) {
const relations = await db.relations
@@ -85,16 +88,14 @@ async function updateInitialSelectionState(items: string[]) {
const useTagItemSelection = createItemSelectionStore(true);
const ManageTagsSheet = (props: {
ids?: string[];
actionSheetRef: RefObject<ActionSheetRef>;
}) => {
const ManageTags = (props: NavigationProps<"ManageTags">) => {
const { colors } = useThemeColors();
const ids = useMemo(() => props.ids || [], [props.ids]);
const ids = props.route.params.ids || [];
const [tags, setTags] = useState<VirtualizedGrouping<Tag>>();
const [query, setQuery] = useState<string>();
const inputRef = useRef<TextInput>(null);
const [focus, setFocus] = useState(false);
useNavigationFocus(props.navigation, { focusOnInit: true });
const [queryExists, setQueryExists] = useState(false);
const dimensions = useWindowDimensions();
const refreshSelection = useCallback(() => {
@@ -111,7 +112,7 @@ const ManageTagsSheet = (props: {
if (query && query.trim() !== "") {
db.lookup
.tags(query)
.sorted()
.sorted(db.settings.getGroupOptions("tags"))
.then((items) => {
setTags(items);
});
@@ -239,112 +240,118 @@ const ManageTagsSheet = (props: {
);
return (
<View
<SafeAreaView
style={{
width: "100%",
alignSelf: "center",
paddingHorizontal: DefaultAppStyles.GAP,
maxHeight: dimensions.height * 0.85
backgroundColor: colors.primary.background,
gap: DefaultAppStyles.GAP_VERTICAL
}}
>
<Input
button={{
icon: "magnify",
color: colors.primary.accent,
size: AppFontSize.lg,
onPress: () => {}
}}
testID="tag-input"
fwdRef={inputRef}
autoCapitalize="none"
onChangeText={(v) => {
setQuery(sanitizeTag(v));
checkQueryExists(sanitizeTag(v));
}}
onFocusInput={() => {
setFocus(true);
}}
onBlurInput={() => {
setFocus(false);
}}
onSubmit={() => {
onSubmit();
}}
placeholder={strings.searchForTags()}
/>
{query && !queryExists ? (
<Pressable
key={"query_item"}
style={{
flexDirection: "row",
marginVertical: 5,
justifyContent: "space-between",
padding: DefaultAppStyles.GAP
}}
onPress={onSubmit}
type="selected"
>
<Heading size={AppFontSize.sm} color={colors.selected.heading}>
{strings.add()} {'"' + "#" + query + '"'}
</Heading>
<Icon
name="plus"
color={colors.selected.icon}
size={AppFontSize.lg}
/>
</Pressable>
) : null}
<Header title={strings.manageTags()} canGoBack />
<View
style={{
width: "100%",
flexGrow: 1,
height: "100%"
paddingHorizontal: DefaultAppStyles.GAP
}}
>
<FlashList
data={tags?.placeholders}
keyboardShouldPersistTaps
keyboardDismissMode="interactive"
estimatedItemSize={50}
renderItem={renderTag}
ListEmptyComponent={
<View
style={{
width: "100%",
height: 200,
justifyContent: "center",
alignItems: "center"
}}
>
<Heading size={50} color={colors.secondary.heading}>
#
</Heading>
<Paragraph
textBreakStrategy="balanced"
color={colors.secondary.paragraph}
>
{strings.emptyPlaceholders("tag")}
</Paragraph>
</View>
}
ListFooterComponent={<View style={{ height: 50 }} />}
<Input
button={{
icon: "magnify",
color: colors.primary.accent,
size: AppFontSize.lg,
onPress: () => {}
}}
testID="tag-input"
fwdRef={inputRef}
autoCapitalize="none"
onChangeText={(v) => {
setQuery(sanitizeTag(v));
checkQueryExists(sanitizeTag(v));
}}
onFocusInput={() => {
setFocus(true);
}}
onBlurInput={() => {
setFocus(false);
}}
onSubmit={() => {
onSubmit();
}}
placeholder={strings.searchForTags()}
/>
{query && !queryExists ? (
<Pressable
key={"query_item"}
style={{
flexDirection: "row",
justifyContent: "space-between",
paddingHorizontal: DefaultAppStyles.GAP,
paddingVertical: DefaultAppStyles.GAP_VERTICAL
}}
onPress={onSubmit}
type="selected"
>
<Heading size={AppFontSize.sm} color={colors.selected.heading}>
{strings.add()} {'"' + "#" + query + '"'}
</Heading>
<Icon
name="plus"
color={colors.selected.icon}
size={AppFontSize.lg}
/>
</Pressable>
) : null}
<View
style={{
width: "100%",
flexGrow: 1,
height: "100%"
}}
>
<FlashList
data={tags?.placeholders}
keyboardShouldPersistTaps
keyboardDismissMode="interactive"
estimatedItemSize={50}
renderItem={renderTag}
ListEmptyComponent={
<View
style={{
width: "100%",
height: 200,
justifyContent: "center",
alignItems: "center"
}}
>
<Heading size={50} color={colors.secondary.heading}>
#
</Heading>
<Paragraph
textBreakStrategy="balanced"
color={colors.secondary.paragraph}
>
{strings.emptyPlaceholders("tag")}
</Paragraph>
</View>
}
ListFooterComponent={<View style={{ height: 50 }} />}
/>
</View>
</View>
</View>
</SafeAreaView>
);
};
ManageTagsSheet.present = (ids?: string[]) => {
presentSheet({
component: (ref) => {
return <ManageTagsSheet actionSheetRef={ref} ids={ids} />;
}
ManageTags.present = (ids?: string[]) => {
Navigation.push("ManageTags", {
ids: ids
});
};
export default ManageTagsSheet;
export default ManageTags;
const TagItem = ({
id,

View File

@@ -328,6 +328,8 @@ const NotebookItemWrapper = React.memo(
const expanded = useNotebookExpandedStore(
(state) => state.expanded[item.notebook.id]
);
const disableExpand = useNotebookTreeStore((state) => state.isSearching);
const selectionEnabled = useNotebookSelectionStore(
(state) => state.enabled
);
@@ -378,6 +380,7 @@ const NotebookItemWrapper = React.memo(
useNotebookTreeStore.getState().removeChildren(item.notebook.id);
}
}}
disableExpand={disableExpand}
selected={selected}
selectionEnabled={selectionEnabled}
selectionStore={useNotebookSelectionStore}

View File

@@ -248,7 +248,13 @@ const SelectableNoteItem = React.memo(
flexGrow: 1
}}
>
<Heading size={AppFontSize.sm} numberOfLines={1}>
<Heading
size={AppFontSize.sm}
numberOfLines={1}
style={{
maxWidth: "93%"
}}
>
{item?.title}
</Heading>
{item.type == "note" && item.headline ? (
@@ -256,6 +262,9 @@ const SelectableNoteItem = React.memo(
numberOfLines={1}
color={colors?.secondary.paragraph}
size={AppFontSize.sm}
style={{
maxWidth: "93%"
}}
>
{item.headline}
</Paragraph>

View File

@@ -29,6 +29,7 @@ import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { useSettingStore } from "../../stores/use-setting-store";
import { NotesnookModule } from "../../utils/notesnook-module";
import { DefaultAppStyles } from "../../utils/styles";
import { SafeAreaView } from "react-native-safe-area-context";
const NoteItem = (props: {
id: string | number;
@@ -111,7 +112,7 @@ export const NotePreviewConfigure = () => {
);
return (
<View
<SafeAreaView
style={{
backgroundColor: colors.primary.background,
flex: 1
@@ -165,6 +166,6 @@ export const NotePreviewConfigure = () => {
ListFooterComponent={<View style={{ height: 200 }} />}
/>
</View>
</View>
</SafeAreaView>
);
};

View File

@@ -17,19 +17,19 @@ 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 React from "react";
import { FloatingButton } from "../../components/container/floating-button";
import DelayLayout from "../../components/delay-layout";
import { Header } from "../../components/header";
import List from "../../components/list";
import SelectionHeader from "../../components/selection-header";
import ReminderSheet from "../../components/sheets/reminder";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import Navigation, { NavigationProps } from "../../services/navigation";
import SettingsService from "../../services/settings";
import useNavigationStore from "../../stores/use-navigation-store";
import { useReminders } from "../../stores/use-reminder-store";
import { strings } from "@notesnook/intl";
import AddReminder from "../add-reminder";
export const Reminders = ({
navigation,
@@ -68,7 +68,7 @@ export const Reminders = ({
}}
id={route.name}
onPressDefaultRightButton={() => {
ReminderSheet.present();
AddReminder.present();
}}
/>
@@ -84,7 +84,7 @@ export const Reminders = ({
paragraph: strings.remindersEmpty(),
button: strings.setReminder(),
action: () => {
ReminderSheet.present();
AddReminder.present();
},
loading: strings.loadingReminders()
}}
@@ -92,7 +92,7 @@ export const Reminders = ({
<FloatingButton
onPress={() => {
ReminderSheet.present();
AddReminder.present();
}}
alwaysVisible
/>

View File

@@ -17,35 +17,37 @@ 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 { Item, Note, VirtualizedGrouping } from "@notesnook/core";
import {
FilteredSelector,
Item,
Note,
VirtualizedGrouping
} from "@notesnook/core";
import { strings } from "@notesnook/intl";
import React, { useEffect, useRef, useState } from "react";
import { DatabaseLogger, db } from "../../common/database";
import List from "../../components/list";
import SelectionHeader from "../../components/selection-header";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import {
ToastManager,
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import { ToastManager, eSubscribeEvent } from "../../services/event-manager";
import { NavigationProps } from "../../services/navigation";
import useNavigationStore from "../../stores/use-navigation-store";
import { eOnRefreshSearch } from "../../utils/events";
import { eGroupOptionsUpdated, eOnRefreshSearch } from "../../utils/events";
import { SearchBar } from "./search-bar";
import { FilteredSelector } from "@notesnook/core";
import { strings } from "@notesnook/intl";
export const Search = ({ route, navigation }: NavigationProps<"Search">) => {
const [results, setResults] = useState<VirtualizedGrouping<Item>>();
const [loading, setLoading] = useState(false);
const [searchStatus, setSearchStatus] = useState<string>();
const currentQuery = useRef<string>();
const timer = useRef<NodeJS.Timeout>();
const isFocused = useNavigationFocus(navigation, {
useNavigationFocus(navigation, {
onFocus: (prev) => {
useNavigationStore.getState().setFocusedRouteId(route.name);
return !prev?.current;
},
onBlur: () => false
onBlur: () => {
return false;
}
});
const onSearch = React.useCallback(
@@ -60,32 +62,35 @@ export const Search = ({ route, navigation }: NavigationProps<"Search">) => {
try {
setLoading(true);
let results: VirtualizedGrouping<Item> | undefined;
const groupOptions = db.settings.getGroupOptions("search");
switch (route.params.type) {
case "note":
results = await db.lookup
.notes(query, route.params.items as FilteredSelector<Note>)
.sorted();
results = await db.lookup.notesWithHighlighting(
query,
route.params.items as FilteredSelector<Note>,
groupOptions
);
break;
case "notebook":
results = await db.lookup.notebooks(query).sorted();
results = await db.lookup.notebooks(query).sorted(groupOptions);
break;
case "tag":
results = await db.lookup.tags(query).sorted();
results = await db.lookup.tags(query).sorted(groupOptions);
break;
case "reminder":
results = await db.lookup.reminders(query).sorted();
results = await db.lookup.reminders(query).sorted(groupOptions);
break;
case "trash":
results = await db.lookup.trash(query).sorted();
results = await db.lookup.trash(query).sorted(groupOptions);
break;
case "attachment":
results = await db.lookup.attachments(query).sorted();
results = await db.lookup.attachments(query).sorted(groupOptions);
break;
default:
results = undefined;
}
if (currentQuery.current !== query) return;
if (!results) {
setSearchStatus(strings.noResultsFound(query));
setLoading(false);
@@ -97,6 +102,7 @@ export const Search = ({ route, navigation }: NavigationProps<"Search">) => {
);
if (currentQuery.current !== query) return;
await results.item(0);
if (currentQuery.current !== query) return;
setResults(results);
if (results.placeholders?.length === 0) {
setSearchStatus(strings.noResultsFound(query));
@@ -118,9 +124,18 @@ export const Search = ({ route, navigation }: NavigationProps<"Search">) => {
onSearch(currentQuery.current);
}
};
eSubscribeEvent(eOnRefreshSearch, onRefreshSearch);
const subs = [
eSubscribeEvent(eGroupOptionsUpdated, (groupType) => {
if (groupType === "search") {
onSearch(currentQuery.current);
}
}),
eSubscribeEvent(eOnRefreshSearch, onRefreshSearch)
];
return () => {
eUnSubscribeEvent(eOnRefreshSearch, onRefreshSearch);
subs.forEach((sub) => sub?.unsubscribe());
};
}, [onSearch, route.params?.type]);
@@ -137,7 +152,7 @@ export const Search = ({ route, navigation }: NavigationProps<"Search">) => {
clearTimeout(timer.current);
timer.current = setTimeout(() => {
onSearch(query);
}, 300);
}, 500);
}}
loading={loading}
/>

View File

@@ -16,35 +16,22 @@ 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, { RefObject, useRef, useState } from "react";
import React, { useRef, useState } from "react";
import { TextInput, View } from "react-native";
import { ActionSheetRef } from "react-native-actions-sheet";
import { db } from "../../../common/database";
import {
eSendEvent,
presentSheet,
PresentSheetOptions,
ToastManager
} from "../../../services/event-manager";
import DialogHeader from "../../dialog/dialog-header";
import { Button } from "../../ui/button";
import Input from "../../ui/input";
import { eSendEvent, ToastManager } from "../../../services/event-manager";
import { eUserLoggedIn } from "../../../utils/events";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../../utils/styles";
type ChangeEmailProps = {
actionSheetRef: RefObject<ActionSheetRef>;
close?: () => void;
update?: (options: PresentSheetOptions) => void;
};
import Input from "../../../components/ui/input";
import { Button } from "../../../components/ui/button";
enum EmailChangeSteps {
verify,
changeEmail
}
export const ChangeEmail = ({ close }: ChangeEmailProps) => {
export const ChangeEmail = () => {
const [step, setStep] = useState(EmailChangeSteps.verify);
const emailChangeData = useRef<{
email?: string;
@@ -103,10 +90,6 @@ export const ChangeEmail = ({ close }: ChangeEmailProps) => {
return (
<View style={{ paddingHorizontal: DefaultAppStyles.GAP }}>
<DialogHeader
title={strings.changeEmail()}
paragraph={strings.changeEmailDesc()}
/>
<View
style={{
marginTop: DefaultAppStyles.GAP_VERTICAL
@@ -156,23 +139,12 @@ export const ChangeEmail = ({ close }: ChangeEmailProps) => {
: strings.changeEmail()
}
type="accent"
width={250}
style={{
width: "100%"
}}
loading={loading}
onPress={onSubmit}
style={{
borderRadius: 100,
height: 45,
marginTop: 2
}}
/>
</View>
);
};
ChangeEmail.present = () => {
presentSheet({
component: (ref, close, update) => (
<ChangeEmail actionSheetRef={ref} close={close} update={update} />
)
});
};

View File

@@ -43,6 +43,8 @@ import ThemeSelector from "./theme-selector";
import { TitleFormat } from "./title-format";
import { View } from "react-native";
import { DefaultAppStyles } from "../../utils/styles";
import { ChangePassword } from "../../components/auth/change-password";
import { ChangeEmail } from "./change-email";
export const components: { [name: string]: ReactElement } = {
colorpicker: <AccentColorPicker />,
@@ -69,5 +71,7 @@ export const components: { [name: string]: ReactElement } = {
<AttachmentGroupProgress groupId="offline-mode" />
</View>
),
"sidebar-tab-selector": <SidebarTabPicker />
"sidebar-tab-selector": <SidebarTabPicker />,
"change-password": <ChangePassword />,
"change-email": <ChangeEmail />
};

View File

@@ -32,11 +32,9 @@ import { enabled } from "react-native-privacy-snapshot";
import ScreenGuardModule from "react-native-screenguard";
import { db } from "../../common/database";
import filesystem from "../../common/filesystem";
import { ChangePassword } from "../../components/auth/change-password";
import { presentDialog } from "../../components/dialog/functions";
import { AppLockPassword } from "../../components/dialogs/applock-password";
import { endProgress, startProgress } from "../../components/dialogs/progress";
import { ChangeEmail } from "../../components/sheets/change-email";
import ExportNotesSheet from "../../components/sheets/export-notes";
import { Issue } from "../../components/sheets/github/issue";
import { Progress } from "../../components/sheets/progress";
@@ -82,7 +80,8 @@ export const settingsGroups: SettingSection[] = [
useHook: () => useUserStore((state) => state.user),
hidden: (current) => !current,
name: (current) => {
const user = current as User;
const user = (current as User) || useUserStore.getState().user;
if (!user) return strings.subscribeToPro();
const isBasic = user.subscription?.type === SUBSCRIPTION_STATUS.BASIC;
const isTrial = user.subscription?.type === SUBSCRIPTION_STATUS.TRIAL;
return isBasic || !user.subscription?.type
@@ -96,6 +95,7 @@ export const settingsGroups: SettingSection[] = [
icon: "crown",
description: (current) => {
const user = current as User;
if (!user) return strings.neverHesitate();
const subscriptionDaysLeft =
user &&
getTimeLeft(
@@ -241,17 +241,15 @@ export const settingsGroups: SettingSection[] = [
{
id: "change-password",
name: strings.changePassword(),
modifer: async () => {
ChangePassword.present();
},
description: strings.changePasswordDesc()
type: "screen",
description: strings.changePasswordDesc(),
component: "change-password"
},
{
id: "change-email",
name: strings.changeEmail(),
modifer: async () => {
ChangeEmail.present();
},
type: "screen",
component: "change-email",
description: strings.changeEmailDesc()
},
{
@@ -670,6 +668,13 @@ export const settingsGroups: SettingSection[] = [
});
},
disabled: () => !db.settings.getDefaultNotebook()
},
{
id: "disable-update-check",
type: "switch",
name: strings.autoUpdateCheck(),
description: strings.autoUpdateCheckDesc(),
property: "checkForUpdates"
}
]
},

View File

@@ -113,7 +113,10 @@ async function onBackgroundSyncStarted() {
useUserStore.getState().setSyncing(false);
}
await Notifications.setupReminders();
if (SettingsService.get().notifNotes) {
Notifications.pinQuickNote(false);
}
Notifications.restorePinnedNotes();
NotePreviewWidget.updateNotes();
deleteDCacheFiles();
DatabaseLogger.info("BACKGROUND SYNC COMPLETE");
@@ -142,6 +145,7 @@ const onBoot = async () => {
Notifications.pinQuickNote(false);
}
Notifications.restorePinnedNotes();
NotePreviewWidget.updateNotes();
DatabaseLogger.info("BOOT TASK COMPLETE");
} catch (e) {
DatabaseLogger.error(e as Error);

View File

@@ -18,12 +18,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Platform } from "react-native";
import { db } from "../common/database";
import ReminderSheet from "../components/sheets/reminder";
import { setAppState } from "../screens/editor/tiptap/utils";
import { eOnLoadNote } from "../utils/events";
import { NotesnookModule } from "../utils/notesnook-module";
import { eSendEvent } from "./event-manager";
import { fluidTabsRef } from "../utils/global-refs";
import AddReminder from "../screens/add-reminder";
const launchIntent = Platform.OS === "ios" ? {} : NotesnookModule.getIntent();
let used = false;
@@ -65,9 +65,9 @@ export const IntentService = {
const reminder = await db.reminders.reminder(
intent["com.streetwriters.notesnook.OpenReminderId"]
);
if (reminder) ReminderSheet.present(reminder);
if (reminder) AddReminder.present(reminder);
} else if (intent["com.streetwriters.notesnook.NewReminder"]) {
ReminderSheet.present();
AddReminder.present();
}
} catch (e) {
/* empty */

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