Compare commits

...

149 Commits

Author SHA1 Message Date
Ammar Ahmed
21f931d3ea mobile: fix empty toast message shows on removing tools from group 2026-04-14 08:50:11 +05:00
Ammar Ahmed
58253f11b7 mobile: fix tools and groups do not re-render properly on changing preset or adding tools 2026-04-10 14:03:22 +05:00
Ammar Ahmed
0535de9ae9 editor: make editor tools object a function
Required for string localization to work properly
2026-04-10 12:27:54 +05:00
Ammar Ahmed
74921ae1b3 mobile: release 3.3.19 (#9683)
* mobile: release 3.3.19

* mobile: fix ios production xcconfig
2026-04-08 09:37:56 +05:00
01zulfi
40f2c88ead Merge pull request #9682 from streetwriters/release/3.3.13
web: bump version to 3.3.13
2026-04-07 15:49:17 +05:00
01zulfi
7fe410ae89 web: bump version to 3.3.13
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-07 14:00:37 +05:00
Ammar Ahmed
4cbdd085d6 mobile: fix editor stops updating notes after first two notes edited (#9681) 2026-04-07 13:18:50 +05:00
01zulfi
24556dab83 web: show better error message on invalid recovery key (#9680)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-07 13:00:02 +05:00
Abdullah Atta
dd8116eba8 Merge pull request #9677 from streetwriters/fix/table-cell-selection-mobile
Fix table cell selection on mobile
2026-04-06 23:55:18 +05:00
Abdullah Atta
ae82a8c0a0 editor: only allow resizing column from resize handle 2026-04-06 23:49:04 +05:00
Chloe Oletto
af4dc5e9ea web: fix all tabs closed on sync when any note is deleted from another device (#9252)
* web: All tabs closed when any note is deleted.

Signed-off by: Chloe Oletto <NeedsChloesure@riseup.net>

* web: remove redundant noteID check, fix check to ensure locked notes are reopened.

Signed-off By: Chloe Oletto <NeedsChloesure@riseup.net>
2026-04-06 23:02:12 +05:00
Abdullah Atta
71b03a90dd editor: disable scroll during column resize 2026-04-06 21:01:25 +05:00
Abdullah Atta
728e50cd13 editor: fix awkward selection behaviour in table cells
this includes fixes to multiple UX bugs:

1. scroll now stops when user starts cell selection
2. moving cell selection to either edge auto scrolls the table
3. text selection does not trigger cell selection
2026-04-06 21:00:23 +05:00
01zulfi
c8e8935b34 web: add back button in recovery forms (#9676)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-06 16:27:33 +05:00
01zulfi
21f4b906d7 web: fix html entities shown in match result (#9645)
* web: fix non-breaking space shown as encoded in match result
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* core: decode matches in db.lookup.notesWithHighlighting
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* core: decode entities at parse time

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-04-06 14:20:10 +05:00
01zulfi
d49d42b736 common: guard against undefined backStack/forwardStack in TabSessionHistory (#9674)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-06 12:42:00 +05:00
SahilAslam001
267fe4e453 mobile: remove restoring app state on app launch #6294 (#9586)
Signed-off-by: Sahil Aslam <sahil.aslam011@gmail.com>
2026-04-06 11:20:30 +05:00
01zulfi
413ca61ef5 core: $headline$ format should get 60 chars from first p or h tag (#9658)
* core: note title headline format should get content from first p or h tag
* also decrease character limit to 60

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

* Apply suggestion from @thecodrr

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

---------

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
Signed-off-by: Abdullah Atta <thecodrr@protonmail.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-04-06 09:20:21 +05:00
Ammar Ahmed
02b96470bc Merge pull request #9631 from streetwriters/fix/file-size-check-fail
mobile: fix file size check shows error but continues uploading the file
2026-04-02 13:13:56 +05:00
Ammar Ahmed
7234128f78 docs(help): add reminders help page (#9637)
* docs: add remindershelp page

* Apply suggestions from code review

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

---------

Signed-off-by: Abdullah Atta <thecodrr@protonmail.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
Co-authored-by: Abdullah Atta <thecodrr@protonmail.com>
2026-04-02 12:38:28 +05:00
01zulfi
27e070e555 web: redirect automatically after final step in reset password flow (#9642)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-02 12:29:16 +05:00
01zulfi
539950bdc5 core: perform fuzzy search again if query contains non-alphanumeric chars (#9634)
* core: simplify fuzzy separator regex
* remove lookahead and lookbehind

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

* core: do 2 pass fuzzy search if query contains non-alphanumeric chars

---------

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-04-02 12:13:06 +05:00
Ammar Ahmed
83280e7cee docs: clarify what happens on account deletion (#9636)
* docs: clarify what happens on account deletion

* intl: clarify what happens when account is deleted
2026-04-01 15:23:57 +05:00
01zulfi
cee0275565 web: handle crash on preview attachment (#9626)
* web: handle crash on preview attachment
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: better error message on attachment preview failure

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-04-01 13:07:17 +05:00
Ammar Ahmed
87bddf0037 mobile: fix file size check shows error but continues uploading the file 2026-04-01 11:06:33 +05:00
SahilAslam001
e736e3d810 mobile: do not auto fill auto generated title when user clears title field in editor (#9623)
Signed-off-by: Sahil Aslam <sahil.aslam011@gmail.com>
2026-04-01 09:31:21 +05:00
01zulfi
092f15406c editor: fix table column toolbar not showing in correct position (#9583)
* editor: fix table column toolbar not showing in correct position
* also scroll to the newly created column if it is not visible

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

* editor: use tr.scrollIntoView instead of manually adjusting position on new column
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* editor: add tr.scrollIntoView() for table's selectRow()
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

---------

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-31 13:28:03 +05:00
01zulfi
befcd0f112 web: refresh user on change email & run timer on first email sent (#9620)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-31 09:53:15 +05:00
Ammar Ahmed
8b92936d05 Add workflow to build android/ios previews on each PR (#9604)
Builds and publishes iOS and Android Preview apps that are instantly available to registered testers and quality assurance teams. We are using Firebase App Distribution for this purpose that works seamlessly on both android and iOS.

Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2026-03-31 09:42:24 +05:00
01zulfi
aec135de83 web: fix plans page being cut-off from top (#9594)
* web: add top padding to plans page
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: fix plans page being cut-off from top
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-31 09:41:28 +05:00
01zulfi
f9738168e5 editor: fix callout first heading being collapsed/expanded if clicked towards right of it (#9547)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-30 20:15:03 +05:00
Ammar Ahmed
e873b88dbd Merge pull request #9618 from streetwriters/mobile-release/3.3.18
mobile: release 3.3.18
2026-03-30 09:43:53 +05:00
Abdullah Atta
214f5b8a98 ci: fix desktop pr preview workflow (#9612)
* ci: fix desktop pr preview workflow

Removed dependency on build-linux-arm64 for posting PR comments.

Signed-off-by: Abdullah Atta <abdullahatta@streetwriters.co>

* ci: do not sign windows staging build

* clipper(web): regen package-lock.json

* ci: fix `Unknown target: x64`

---------

Signed-off-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-03-28 14:05:43 +05:00
Abdullah Atta
70cf17fdd5 ci: add workflow for desktop pr preview (#9607)
* ci: add workflow for desktop pr preview

* ci: workflow didn't run

* ci: limit workflow to specific paths
2026-03-28 13:25:55 +05:00
Abdullah Atta
7d557810cb ci: add workflow for notesnook web pr preview (#9593)
* ci: add workflow for notesnook web pr preview

* ci: remove benchmarks workflow

* ci: use `setup-node-with-cache`

* ci: only publish pr preview when packages/** or apps/web/** changes
2026-03-28 12:47:17 +05:00
Abdullah Atta
ed24282703 web: bump version to 3.3.12 (#9602) 2026-03-28 11:09:19 +05:00
Ammar Ahmed
e2cf3e81af mobile: release 3.3.18 2026-03-28 10:12:23 +05:00
Ammar Ahmed
28fa133b6d Merge pull request #9597 from SahilAslam001/fix/undefined-id-issue
mobile: resolve undefined id issue in subscription product items
2026-03-28 10:04:03 +05:00
Ammar Ahmed
37893ced80 mobile: resolve undefine id issue in subscription product items
Signed-off-by: Sahil Aslam <sahil.aslam011@gmail.com>
2026-03-28 10:03:03 +05:00
Ammar Ahmed
900c48292c Merge pull request #9601 from streetwriters/chore/fix-prettier
mobile: format all files
2026-03-28 09:58:39 +05:00
Ammar Ahmed
302ae5ddb4 mobile: format all files
It seems prettier was not working properly. Pin prettier version to 2.8.8 in apps/mobile and ran prettier on all files to fix formatting issues.
2026-03-28 09:57:51 +05:00
Abdullah Atta
24fca08fdc core: generate all user keys on sign up (#9600) 2026-03-28 09:20:53 +05:00
01zulfi
91077dc132 core: fix ciphertext cannot be decrypted... error on attachment upload after sign up (#9596)
* core: fix attachment upload on new user signup
* multiple attachment keys were being created because null cache was set in the key manager, a check is added so only defined key values are set in cache

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

* core: use `cache.get` instead of `cache.has` for checking key existence

---------

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-03-28 00:21:02 +05:00
01zulfi
273bbea938 web: don't collapse nav bar if a menu is still open (#9588)
* web: don't uncollapse nav bar if a menu is still open
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: handle native menu case

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-03-26 23:44:52 +05:00
01zulfi
36c44ad201 web: add toasts in email verification dialog (#9584)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-26 23:22:59 +05:00
01zulfi
46f5a2d7a7 editor: check for permission before pasting images (#9587)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-26 23:19:31 +05:00
Ammar Ahmed
9b359a91cc Merge pull request #9582 from streetwriters/mobile-release/3.3.17
Mobile release/3.3.17
2026-03-26 12:16:02 +05:00
Ammar Ahmed
0af831d50b mobile: release 3.3.17 2026-03-25 13:08:31 +05:00
Ammar Ahmed
3efaeaeeb9 mobile: fix failing tests 2026-03-25 12:31:54 +05:00
01zulfi
00a20dda48 Merge pull request #9569 from streetwriters/release/v3.3.11
web: bump version to 3.3.11
2026-03-25 11:48:02 +05:00
01zulfi
ec45160b27 web: add content in feature dialog for v3.3.11
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-25 11:29:34 +05:00
Ammar Ahmed
eda284ca25 Merge pull request #9568 from streetwriters/fix/disable-app-lock-permission-request
mobile: fix app gets locked when requesting notification permission for upload progress
2026-03-25 10:41:23 +05:00
Ammar Ahmed
1ed331b4bb Merge pull request #9567 from streetwriters/fix/ca-certificates-self-hosted-mobile
mobile: fix app does not use user defined CA certificates
2026-03-25 10:40:43 +05:00
01zulfi
ee74bb4fbf web: fix edit note creation date test
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-24 16:30:18 +05:00
01zulfi
df104669a3 web: bump version to 3.3.11
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-24 14:28:55 +05:00
01zulfi
9621b43bf2 web: fix command palette highlighting being escaped (#9565)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-24 14:22:49 +05:00
Ammar Ahmed
503c1a5c8d mobile: fix app gets locked when requesting notification permission for file upload progress 2026-03-24 13:17:01 +05:00
Ammar Ahmed
a2fa1d4d78 mobile: fix app does not use user defined CA certificates 2026-03-24 12:45:58 +05:00
Ammar Ahmed
6c6ad9a766 Merge pull request #9516 from streetwriters/fix/9496
Fix iOS app remains stuck when restoring backup from list
2026-03-24 09:59:02 +05:00
Ammar Ahmed
fc8807f74d Merge pull request #9519 from streetwriters/fix/GHSA-f27j-fqc6-v7pm
mobile: escape title when creating web-clip for shared metadata
2026-03-24 09:57:41 +05:00
Ammar Ahmed
edec48f9d4 Merge pull request #9523 from streetwriters/fix/mobile-merge-conflicts
mobile: fix merge conflicts for locked notes
2026-03-24 09:56:58 +05:00
Ammar Ahmed
ce98551057 Merge pull request #9532 from streetwriters/fix/note-open-glitch
mobile: fix glitch when opening note, flickery behaviour
2026-03-24 09:56:10 +05:00
Ammar Ahmed
74a2cc5b19 Merge pull request #9550 from streetwriters/fix/attachment-nav-flow
mobile: fix linked note not opening from attachment properties
2026-03-24 09:54:43 +05:00
01zulfi
9628a940e9 core: normalize special separators in fuzzy search (#9541)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-24 09:50:27 +05:00
01zulfi
d48643cbb9 web: fix editor title temporary flicker on tab switch (#9548)
* also remove debounce on editor's resize observer

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-24 09:46:02 +05:00
Abdullah Atta
558beaf216 desktop: fix error 153 in youtube embeds (#9510)
* desktop: fix error 153 in youtube embeds

* editor: update @social-embed/lib
2026-03-20 16:47:44 +05:00
01zulfi
6cc5d2452b web: fix typo in import result text (#9513)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-20 12:46:21 +05:00
Ammar Ahmed
e934ef7ffd mobile: allow lock-unlock a note even if it's not in any vault (#9508) 2026-03-20 12:43:40 +05:00
Ammar Ahmed
e2d8032ef4 Merge pull request #9484 from streetwriters/fix/9459
mobile: fix top bar item page width and count
2026-03-20 12:21:31 +05:00
Ammar Ahmed
81e6101fb5 mobile: add missing telegram icon in settings (#9507) 2026-03-20 12:17:57 +05:00
01zulfi
070354920b web: fix feature title margin in feature dialog (#9551)
* also fix typo

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-20 12:06:18 +05:00
Ammar Ahmed
404168fb86 mobile: fix linked note not opening from attachment properties 2026-03-19 13:38:30 +05:00
01zulfi
d5fa9fd02b editor: remove vertical offset on link hover popup (#9487)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-19 09:47:51 +05:00
01zulfi
6cc66d91be docs: clarify internal note links limitation in Evernote importer (#9509)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-19 09:42:59 +05:00
01zulfi
20a3b3b95e core: fix diacritics not getting ignored in search (#9479)
* core: fix diacritics search in db.lookup.notesWithHighlighting
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* core: remove diacritic in transformTokens
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* core: remove redundant `textContainsTokens` check

* core: fix wrong edge case handling

* core: create & use highlightRegexMatches
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-03-18 11:24:13 +05:00
01zulfi
9145ea4234 web: escape command title in command palette (#9539)
* Because we use dangerouslySetInnerHTML

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-17 11:24:16 +05:00
Abdullah Atta
0fa79433bd clipper(web): remove node-sass and sass-loader (#9536) 2026-03-17 09:16:27 +05:00
01zulfi
5185273b38 editor: sandbox clip iframe (#9521)
* editor: sandbox clip iframe
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* editor: render webclips via bloburl with strict sandboxing

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-03-17 09:15:57 +05:00
01zulfi
eadd23de4d web: escape html in note title in diff viewer (#9518)
* web: escape html in note title in diff viewer
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: use entities package for escaping note title in diff viewer
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-16 13:07:56 +05:00
Ammar Ahmed
71d9738980 mobile: fix glitch when opening note, flickery behaviour 2026-03-16 09:07:18 +05:00
Ammar Ahmed
80328af888 mobile: fix merge conflicts for locked notes 2026-03-13 12:48:29 +05:00
Ammar Ahmed
5e83da5575 mobile: escape title when creating web-clip for shared metadata
fixes https://github.com/streetwriters/notesnook/security/advisories/GHSA-f27j-fqc6-v7pm
2026-03-13 10:54:07 +05:00
Ammar Ahmed
521afe90ca mobile: show backup complete sheet after 500ms 2026-03-13 09:39:03 +05:00
Ammar Ahmed
ee3b7b5d7d mobile: fix ios app remains stuck when restoring backup from list 2026-03-13 09:21:49 +05:00
01zulfi
95a9775c26 core: fix crash because of bracket in note search query (#9477)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-13 09:18:53 +05:00
01zulfi
93d1319b25 desktop: disable update checker if auto updates off (#9302)
* web: disable desktop update checker if auto updates off
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: use setting store listener instead of directly using autoUpdates value
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-13 09:16:57 +05:00
Abdullah Atta
b2bac3808b Merge pull request #9505 from streetwriters/release/vnext
Release vNext
2026-03-12 10:19:47 +05:00
Abdullah Atta
ebf58e68cf desktop: disable db connecting closing 2026-03-12 10:00:50 +05:00
Ammar Ahmed
dc3e909dfe mobile: release v3.3.16 2026-03-12 09:50:21 +05:00
Abdullah Atta
bf09e9615a web: bump version to 3.3.10 2026-03-12 09:48:22 +05:00
Abdullah Atta
b2d30d47d6 Merge pull request #9504 from streetwriters/fix/crash-on-app-close
Fix sql crash on app close
2026-03-12 09:43:34 +05:00
Abdullah Atta
b1296fd24f Merge pull request #9503 from streetwriters/fix/disable-auto-orphaned-attachment-deletion
Disable automatic orphaned attachment deletion
2026-03-12 09:43:09 +05:00
Abdullah Atta
62d8c89300 desktop: fix sql crash on app close 2026-03-12 09:40:39 +05:00
Abdullah Atta
80c44eee89 mobile: disable automatic orphaned attachment removal 2026-03-12 09:34:15 +05:00
Abdullah Atta
153eb126fc web: disable automatic orphaned attachment removal 2026-03-12 09:34:04 +05:00
Ammar Ahmed
f1fc166713 mobile: fix top bar item page width and count 2026-03-11 12:00:36 +05:00
Abdullah Atta
dda36bb036 Merge pull request #9478 from streetwriters/add-pr-template
Add pull request template for contributions
2026-03-10 10:31:28 +05:00
Ammar Ahmed
843ad08813 global: add platform selection to pull request template
Added a section to specify related platforms for the PR.

Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2026-03-10 10:23:40 +05:00
Abdullah Atta
a0e3c923e4 Apply suggestion from @thecodrr
Signed-off-by: Abdullah Atta <thecodrr@protonmail.com>
2026-03-10 10:14:57 +05:00
Abdullah Atta
3095b03b09 Update .github/pull_request_template.md
Signed-off-by: Abdullah Atta <thecodrr@protonmail.com>
2026-03-10 10:00:11 +05:00
Ammar Ahmed
ea5a0e0a7c Add pull request template for contributions
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2026-03-10 09:29:38 +05:00
Abdullah Atta
2b4ec5b03b Merge pull request #9472 from NeedsChloesure/feat/update-docs
docs: hotfix: Fix copilot's document formatting error that I didn't notice.
2026-03-09 14:14:51 +05:00
Chloe Oletto
59f7add065 docs: hotfix: Fix copilot's document formatting error that I didn't notice.
Signed-off-by: Chloe Oletto <needschloesure@riseup.net>
2026-03-09 01:39:25 -07:00
Abdullah Atta
9c7bfc2347 Merge pull request #9450 from streetwriters/release/vnext
Release v3.3.9
2026-03-09 13:10:05 +05:00
Abdullah Atta
ef90f40be4 web: add version highlights 2026-03-09 12:39:42 +05:00
Ammar Ahmed
bca6a4f22c mobile: add feature highlights 2026-03-09 11:47:34 +05:00
Ammar Ahmed
1d3e2b56b2 mobile: fix icon colors not rendering correctly 2026-03-09 11:30:17 +05:00
Ammar Ahmed
a93a20ed5b mobile: fix color of icon note move-notebook 2026-03-09 11:30:17 +05:00
Ammar Ahmed
971a80c183 mobile: release 3.3.15 2026-03-09 11:30:17 +05:00
Abdullah Atta
125b60facc core: disable sync stress tests 2026-03-09 09:50:29 +05:00
Abdullah Atta
ef99b47178 web: fix edit creation date test 2026-03-09 08:57:53 +05:00
Abdullah Atta
d65f870213 core: fix tests 2026-03-09 08:48:08 +05:00
Abdullah Atta
5b3f1c9de2 web: bump version to 3.3.9 2026-03-08 20:55:25 +05:00
Abdullah Atta
4afb090cb1 web: update snapshots 2026-03-08 13:40:20 +05:00
Abdullah Atta
8cf6e65ed8 web: upgrade otplib 2026-03-08 13:11:49 +05:00
Abdullah Atta
c85c51ec08 web: improve tests reliability 2026-03-07 12:47:44 +05:00
Abdullah Atta
dbeabea6c5 web: always use prod urls for testing 2026-03-07 12:47:44 +05:00
Abdullah Atta
dfbe2d0f0f web: don't crash on web extension errors 2026-03-07 12:47:44 +05:00
Abdullah Atta
ea2277ea4e web: fix characters missing sometimes when creating a new note 2026-03-07 12:47:44 +05:00
Abdullah Atta
ad78105a6e web: update playwright 2026-03-07 12:47:44 +05:00
Abdullah Atta
36584175e2 web: add support for test throttling 2026-03-07 12:47:44 +05:00
Ammar Ahmed
084b1fdfd2 mobile: fix blank space under sheet 2026-03-07 09:22:22 +05:00
Ammar Ahmed
bed091e20f mobile: fix properties topbar pagination 2026-03-07 09:22:10 +05:00
Ammar Ahmed
4420663687 mobile: fix use-db-item hook 2026-03-06 13:07:57 +05:00
Ammar Ahmed
677ae82ffa Merge pull request #9435 from streetwriters/fix/crash-theme-switch-animation
Fix native crashes on android found on google developer console android vitals
2026-03-05 12:34:30 +05:00
Ammar Ahmed
48a3fac9e0 Merge pull request #9434 from streetwriters/fix/reachabilty-url
mobile: fix reachability url for checking network access
2026-03-05 12:33:24 +05:00
Ammar Ahmed
e797d37b01 Merge pull request #9418 from streetwriters/fix/read-only-title
mobile: fix title is editable when note has readonly mode enabled
2026-03-05 12:32:24 +05:00
Abdullah Atta
ffafd2e787 editor: fix marks lost on node split 2026-03-05 12:26:59 +05:00
Abdullah Atta
8500c971eb editor: fix font family not reflected in toolbar 2026-03-05 12:26:59 +05:00
Ammar Ahmed
46a67a56fb mobile: fix reachability url 2026-03-03 08:45:51 +05:00
Chloe Oletto
5dc62e6df2 docs: Update all docs to be current. (#9430)
Signed-off-by: Chloe Oletto <NeedsChloesure@riseup.net>
2026-03-02 12:34:20 +05:00
Abdullah Atta
4479d6cf56 Merge branch 'master' into beta 2026-03-02 12:30:52 +05:00
01zulfi
1faaf1f6b1 web: fix vault not marked as created on login if already exists for user (#9407)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-02 10:37:49 +05:00
Copilot
1f33ef191a docs(help): add Archive help documentation (#9412)
* Initial plan

* Add help documentation for Archive feature

Co-authored-by: thecodrr <7473959+thecodrr@users.noreply.github.com>

* Fix Desktop/Web instructions to use right-click only, not three-dot button

Co-authored-by: thecodrr <7473959+thecodrr@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: thecodrr <7473959+thecodrr@users.noreply.github.com>
2026-03-02 10:31:58 +05:00
Copilot
d5bf8a6e3c editor: fix backticks create inline code even with Markdown Shortcuts disabled (#9415) 2026-03-02 10:29:21 +05:00
Copilot
5c252c7843 desktop: fix external links opening in-app instead of default browser on desktop (#9417)
* Initial plan

* fix: open external links in default browser on Linux desktop

Co-authored-by: thecodrr <7473959+thecodrr@users.noreply.github.com>

* desktop: fix sql db closure on app close

---------

Signed-off-by: Abdullah Atta <abdullahatta@streetwriters.co>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: thecodrr <7473959+thecodrr@users.noreply.github.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-03-02 10:05:29 +05:00
Abdullah Atta
d2e10ccaf6 Merge pull request #9414 from streetwriters/copilot/add-opensans-font
monograph: Use Inter font instead of missing OpenSans in OG image generation
2026-03-02 09:37:39 +05:00
Abdullah Atta
89d2b6870f monograph: bump version to 1.2.8 2026-03-02 09:37:24 +05:00
Copilot
26a9086d96 editor: fix TOC active heading detection (#9413)
* Initial plan

* fix: compute heading offsets relative to editor root for correct TOC highlighting

Headings inside callout blocks had incorrect `offsetTop` values because
`offsetTop` is relative to the nearest positioned ancestor (the callout),
not the editor root. This caused the TOC to highlight wrong sections.

Fix: walk the `offsetParent` chain from the heading up to the editor
content element, accumulating `offsetTop` values to get the correct
absolute position within the editor.

Co-authored-by: thecodrr <7473959+thecodrr@users.noreply.github.com>

* editor: ignore callouts from toc

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: thecodrr <7473959+thecodrr@users.noreply.github.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-03-02 09:34:53 +05:00
01zulfi
9fc8e6b1e5 web: reduce note title size & improve padding (#9420)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-02-27 22:11:59 +05:00
Ammar Ahmed
6718650619 mobile: update libraries
react-native-background-fetch
notifee-react-native
2026-02-27 12:03:02 +05:00
Ammar Ahmed
a622949f23 mobile: fix react-native-tooltips crash 2026-02-27 12:02:34 +05:00
Ammar Ahmed
5a2df5b2c1 mobile: fix crash when switching theme animation 2026-02-27 12:01:18 +05:00
Ammar Ahmed
ae751cdd65 mobile: fix readonly title editable 2026-02-27 09:24:00 +05:00
Ammar Ahmed
0d00bb2866 mobile: update @ammarahmed/react-native-upload dep 2026-02-27 08:54:35 +05:00
Ammar Ahmed
4299965064 mobile: update README.md 2026-02-27 08:47:23 +05:00
copilot-swe-agent[bot]
6f3fc8db3d Use Inter font instead of OpenSans in monograph OG image generation
Co-authored-by: thecodrr <7473959+thecodrr@users.noreply.github.com>
2026-02-26 18:26:26 +00:00
copilot-swe-agent[bot]
db731856f8 Fix missing OpenSans fonts in monograph Docker image
Co-authored-by: thecodrr <7473959+thecodrr@users.noreply.github.com>
2026-02-26 17:21:36 +00:00
copilot-swe-agent[bot]
217f1dd6d4 Initial plan 2026-02-26 17:15:33 +00:00
251 changed files with 17046 additions and 15134 deletions

30
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,30 @@
## Description
<!-- Add a detailed summary of what this feature/bugfix does -->
## Type of Change
- [ ] Bug fix
- [ ] Feature
## Visuals
- [ ] Attached relevant screenshots / screen recording / GIF
- [ ] N/A (not a feature or no UI changes)
## Testing
- [ ] Ran all E2E tests
- [ ] Ran all integration tests
- [ ] Added/updated tests for this change (if needed)
- [ ] N/A (tests not needed — explanation provided below)
### If tests were not added, explain why
<!-- explanation -->
## Platform
<!-- Describe which platforms this PR is related to -->
- [ ] Web
- [ ] Mobile
- [ ] Desktop
## Sign-off
- [ ] QA passed
- [ ] UI/UX passed

View File

@@ -0,0 +1,135 @@
name: Notesnook Android Preview
on:
pull_request_target:
types: [opened, reopened, synchronize]
branches: [master, beta]
paths:
- "apps/mobile/**"
- "packages/**"
- ".github/workflows/android.preview.firebase.yml"
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
runs-on: ubuntu-22.04
env:
STAGING_BUILD: true
steps:
- name: Checkout
uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.sha || github.ref }}
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Use specific Java version for the builds
uses: joschi/setup-jdk@v2
with:
java-version: "17"
architecture: "x64"
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
# this might remove tools that are actually needed,
# if set to "true" but frees about 6 GB
tool-cache: false
# all of these default to true, but feel free to set to
# "false" if necessary for your workflow
android: false
dotnet: true
haskell: true
large-packages: true
docker-images: true
swap-storage: true
- name: Install node modules
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=mobile
- name: Make Gradlew Executable
run: cd apps/mobile/android && chmod +x ./gradlew
- name: Get build number
id: build-number
run: echo "timestamp=$(($(date +%s) - 1774851180 ))" >> $GITHUB_OUTPUT
- name: Check for typescript errors
run: |
npm run tx mobile:build
cd apps/mobile
npx tsc --noEmit
- name: Build arm64-v8a apk
run: |
cd apps/mobile/android
./gradlew assembleRelease -PreactNativeArchitectures=arm64-v8a -PstagingReleaseBuild=true -PprBuildNumber=${{ steps.build-number.outputs.timestamp }}
- name: Get app version
id: package-version
uses: saionaro/extract-package-version@master
with:
path: apps/mobile
- name: Publish to firebase CLI
id: firebase-output
uses: wzieba/Firebase-Distribution-Github-Action@v1
with:
appId: ${{secrets.FIREBASE_APP_ID}}
serviceCredentialsFileContent: ${{ secrets.QA_SERVICE_ACCOUNT }}
groups: testers
file: apps/mobile/android/app/build/outputs/apk/release/app-arm64-v8a-release.apk
releaseNotes: Preview for https://github.com/streetwriters/notesnook/pull/${{github.event.number}}
- name: Post or update PR comment
uses: actions/github-script@v6
env:
preview_url: ${{ steps.firebase-output.outputs.TESTING_URI }}
with:
script: |
const marker = '<!-- android-preview-comment -->';
const prNumber = context.issue.number;
const previewUrl = process.env.preview_url || '';
const body = `${marker}\n**Android App Preview**\n\n${previewUrl || 'Preview URL unavailable — check workflow logs.'}\n\nCommit: ${process.env.GITHUB_SHA}\n`;
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
});
const existing = comments.find(c => c.body && c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body,
});
}
- name: Upload sourcemaps
uses: actions/upload-artifact@v4
with:
name: sourcemaps
path: |
apps/mobile/android/app/build/generated/sourcemaps/**/*.map

257
.github/workflows/desktop.preview.yml vendored Normal file
View File

@@ -0,0 +1,257 @@
name: Preview @notesnook/desktop
on:
pull_request:
types: [opened, reopened, synchronize]
branches: [master, beta]
paths:
- "apps/desktop/**"
- "apps/web/**"
- "packages/**"
# re-run workflow if workflow file changes
- ".github/workflows/desktop.preview.yml"
jobs:
build:
if: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
name: Build
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Install packages
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=web
npm run bootstrap -- --scope=desktop
- name: Install sqlite-better-trigram for all arch
run: |
npm i --cpu arm64 sqlite-better-trigram
npm i --cpu x64 sqlite-better-trigram
working-directory: ./apps/desktop
- name: Generate desktop build (stable)
run: npm run tx @notesnook/web:build:desktop
- name: Build desktop bundle
working-directory: ./apps/desktop
run: npm run bundle
- name: Archive build artifact
uses: actions/upload-artifact@v4
with:
name: build
path: apps/web/build/**/*
build-macos:
name: Build for macOS
needs: build
runs-on: macos-14
outputs:
macos-artifact-url: ${{ steps.artifact-upload-step.outputs.artifact-url }}
steps:
- name: Check out Git repository
uses: actions/checkout@v4
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Install setuptools
run: brew install python-setuptools
- name: Setup notarization
run: |
mkdir -p ~/private_keys/
echo '${{ secrets.api_key }}' > ~/private_keys/AuthKey_${{ secrets.api_key_id }}.p8
- name: Collect app metadata
id: app_metadata
working-directory: ./apps/desktop
run: |
echo ::set-output name=apple_app_id::$(cat package.json | jq -r .appAppleId)
echo ::set-output name=app_bundle_id::$(cat package.json | jq -r .build.appId)
echo ::set-output name=app_version::$(cat package.json | jq -r .version)
echo ::set-output name=bundle_version::$(cat package.json | jq -r .build.mac.bundleVersion)
- name: Download build
uses: actions/download-artifact@v4
with:
name: build
path: ./apps/web/build
- name: Install packages
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=desktop
- name: Install sqlite-better-trigram for all arch
run: |
npm i --cpu arm64 sqlite-better-trigram
npm i --cpu x64 sqlite-better-trigram
working-directory: ./apps/desktop
- name: Install provisioning profile
run: echo "${{ secrets.MAC_PROVISIONING_PROFILE }}" | base64 --decode > embedded.provisionprofile
working-directory: ./apps/desktop
- name: Build dmg
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CSC_LINK: ${{ secrets.mac_certs }}
CSC_KEY_PASSWORD: ${{ secrets.mac_certs_password }}
APPLE_API_KEY: ~/private_keys/AuthKey_${{ secrets.api_key_id }}.p8
APPLE_API_KEY_ID: ${{ secrets.api_key_id }}
APPLE_API_ISSUER: ${{ secrets.api_key_issuer_id }}
run: |
npm run tx @notesnook/desktop:release
cd apps/desktop
yarn electron-builder --config=electron-builder.config.js --mac dmg --arm64 -p never
- name: Upload dmg artifact
id: artifact-upload-step
uses: actions/upload-artifact@v4
with:
name: macos-build
path: apps/desktop/output/*.dmg
build-linux-x64:
name: Build for Linux x64
needs: build
runs-on: ubuntu-22.04
outputs:
linux-x64-artifact-url: ${{ steps.artifact-upload-step.outputs.artifact-url }}
steps:
- name: Check out Git repository
uses: actions/checkout@v4
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Download build
uses: actions/download-artifact@v4
with:
name: build
path: ./apps/web/build
- name: Install packages
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=desktop
- name: Install sqlite-better-trigram for all arch
run: |
npm i --cpu x64 sqlite-better-trigram
working-directory: ./apps/desktop
- name: Build Electron wrapper
run: npm run tx @notesnook/desktop:release
- name: Build AppImage
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: yarn electron-builder --config=electron-builder.config.js --linux AppImage:x64 -p never
working-directory: ./apps/desktop
- name: Upload AppImage artifact
id: artifact-upload-step
uses: actions/upload-artifact@v4
with:
name: linux-x64-build
path: apps/desktop/output/*.AppImage
build-windows:
name: Build for Windows
needs: build
runs-on: windows-latest
outputs:
windows-artifact-url: ${{ steps.artifact-upload-step.outputs.artifact-url }}
steps:
- name: Check out Git repository
uses: actions/checkout@v4
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Download build
uses: actions/download-artifact@v4
with:
name: build
path: ./apps/web/build
- name: Install packages
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=desktop
- name: Install sqlite-better-trigram for all arch
run: |
npm i --cpu arm64 sqlite-better-trigram
npm i --cpu x64 sqlite-better-trigram
npm i --cpu arm64 sqlite3-fts5-html
npm i --cpu x64 sqlite3-fts5-html
working-directory: ./apps/desktop
- name: Build
run: node scripts/execute.mjs @notesnook/desktop:release
- name: Publish
env:
NOTESNOOK_STAGING: true
run: yarn electron-builder --config=electron-builder.config.js --win nsis:x64 --publish never
working-directory: ./apps/desktop
- name: Upload exe artifact
id: artifact-upload-step
uses: actions/upload-artifact@v4
with:
name: windows-build
path: apps/desktop/output/*.exe
post-pr-comment:
name: Post PR comment with preview URLs
needs: [build, build-macos, build-linux-x64, build-windows]
runs-on: ubuntu-latest
steps:
- name: Post or update PR comment
uses: actions/github-script@v6
env:
macos_artifact_url: ${{ needs.build-macos.outputs.macos-artifact-url }}
linux_x64_artifact_url: ${{ needs.build-linux-x64.outputs.linux-x64-artifact-url }}
windows_artifact_url: ${{ needs.build-windows.outputs.windows-artifact-url }}
with:
script: |
const marker = '<!-- desktop-preview-comment -->';
const prNumber = context.issue.number;
const previewUrl = [
{ platform: 'macOS', url: process.env.macos_artifact_url },
{ platform: 'Linux x64', url: process.env.linux_x64_artifact_url },
{ platform: 'Windows x64', url: process.env.windows_artifact_url },
].filter(u => u.url).map(u => `- [${u.platform}](${u.url})`).join('\n');
const body = `${marker}\n**Desktop Previews**\n\n${previewUrl || 'Preview URL unavailable — check workflow logs.'}\n\nCommit: ${process.env.GITHUB_SHA}\n`;
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
});
const existing = comments.find(c => c.body && c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body,
});
}

View File

@@ -0,0 +1,168 @@
name: Notesnook iOS Preview
on:
pull_request_target:
types: [opened, reopened, synchronize]
branches: [master, beta]
paths:
- "apps/mobile/**"
- "packages/**"
- ".github/workflows/ios.preview.firebase.yml"
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
runs-on: macos-26
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.ref }}
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "26.1.1"
- name: Install node modules
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=mobile
- name: Build packages
run: npm run tx @notesnook/mobile:build
- name: Cache Pods
uses: actions/cache@v3
id: pods-cache
with:
path: apps/mobile/ios/Pods
key: ${{ runner.os }}-pods-${{ hashFiles('apps/mobile/ios/Podfile.lock') }}
- name: Install Pods
run: |
cd apps/mobile/ios
pod install
- name: Check for typescript errors
run: |
npm run tx mobile:build
cd apps/mobile
npx tsc --noEmit
- name: Get marketing version
id: marketing-version
run: echo "version=$(grep "IOS_MARKETING_VERSION" apps/mobile/ios/build-configs/ios-build.staging.xcconfig | awk -F'=' '{print $2}' | xargs)" >> $GITHUB_OUTPUT
- name: Get build number
id: build-number
run: echo "timestamp=$(($(date +%s) - 1774851180 ))" >> $GITHUB_OUTPUT
- name: Make staging xcconfig active
run: |
cd apps/mobile/ios/build-configs
./use-ios-build-config.sh staging --marketing-version ${{steps.marketing-version.outputs.version}} --build-number ${{steps.build-number.outputs.timestamp}}
- name: Build iOS App
uses: ammarahm-ed/ios-build-action@master
with:
bundle-identifier: org.streetwriters.notesnook
scheme: Notesnook
configuration: "Release"
export-options: apps/mobile/ios/ExportOptionsStaging.plist
export-method: "ad-hoc"
project-path: apps/mobile/ios/Notesnook.xcodeproj
workspace-path: apps/mobile/ios/Notesnook.xcworkspace
update-targets: |
Notesnook
Make Note
NotesWidgetExtension
disable-targets: Notesnook-tvOS,Notesnook-tvOSTests,NotesnookTests
code-signing-identity: Apple Distribution
team-id: ${{ secrets.APPLE_TEAM_ID }}
p12-base64: ${{ secrets.APPLE_CERTIFICATE_P12 }}
certificate-password: ${{ secrets.APPLE_CERTIFICATE_P12_PASSWORD }}
app-store-connect-api-key-issuer-id: ${{ secrets.API_KEY_ISSUER_ID }}
app-store-connect-api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
app-store-connect-api-key-base64: ${{ secrets.APPSTORE_CONNECT_API_KEY_BASE64 }}
output-path: Notesnook.ipa
mobileprovision-base64: |
${{ secrets.APPLE_MOBILE_PROVISION_ADHOC_APP }}
${{ secrets.APPLE_MOBILE_PROVISION_ADHOC_SHARE }}
${{ secrets.APPLE_MOBILE_PROVISION_ADHOC_WIDGET }}
- name: Upload IPA artifact
uses: actions/upload-artifact@v4
with:
name: ios-preview-ipa
path: Notesnook.ipa
if-no-files-found: error
retention-days: 1
publish:
runs-on: ubuntu-latest
needs: build
timeout-minutes: 20
steps:
- name: Download IPA artifact
uses: actions/download-artifact@v4
with:
name: ios-preview-ipa
path: .
- name: Publish to firebase CLI
id: firebase-output
uses: wzieba/Firebase-Distribution-Github-Action@v1
with:
appId: ${{secrets.FIREBASE_IOS_APP_ID}}
serviceCredentialsFileContent: ${{ secrets.QA_SERVICE_ACCOUNT }}
groups: testers
file: Notesnook.ipa
releaseNotes: Preview for https://github.com/streetwriters/notesnook/pull/${{github.event.number}}
- name: Post or update PR comment
uses: actions/github-script@v6
env:
preview_url: ${{ steps.firebase-output.outputs.TESTING_URI }}
with:
script: |
const marker = '<!-- ios-preview-comment -->';
const prNumber = context.issue.number;
const previewUrl = process.env.preview_url || '';
const body = `${marker}\n**iOS App Preview**\n\n${previewUrl || 'Preview URL unavailable — check workflow logs.'}\n\nCommit: ${process.env.GITHUB_SHA}\n`;
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
});
const existing = comments.find(c => c.body && c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body,
});
}

View File

@@ -1,40 +0,0 @@
name: Benchmark @notesnook/web
on:
workflow_dispatch:
push:
branches:
- "master"
paths:
- "apps/web/**"
# re-run workflow if workflow file changes
- ".github/workflows/web.benchmarks.yml"
pull_request:
jobs:
bench:
name: Bench
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Install packages
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=web
- name: Build
run: npm run build:web
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
working-directory: apps/web
- name: Run benchmarks
id: benchmark
run: node __bench__/app.bench.mjs
working-directory: apps/web

80
.github/workflows/web.preview.yml vendored Normal file
View File

@@ -0,0 +1,80 @@
name: Notesnook Web PR Preview
on:
pull_request:
types: [opened, reopened, synchronize]
branches: [master, beta]
paths:
- "apps/web/**"
- "packages/**"
# re-run workflow if workflow file changes
- ".github/workflows/web.preview.yml"
jobs:
build-and-deploy:
if: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
runs-on: ubuntu-latest
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Install dependencies
run: npm ci
- name: Build web
run: npm run build:web
- name: Deploy to Cloudflare Pages
id: deploy
working-directory: ./apps/web
run: |
set -euo pipefail
BRANCH=pr-${{ github.event.number }}-$(echo "${{ github.sha }}" | cut -c1-7)
echo "Deploying branch: $BRANCH"
DEPLOY_OUT=$(npx --yes wrangler pages deploy --project-name=notesnook-app --branch="$BRANCH" ./build 2>&1) || { echo "$DEPLOY_OUT"; exit 1; }
echo "$DEPLOY_OUT"
PREVIEW_URL=$(printf "%s" "$DEPLOY_OUT" | grep -Eo 'https?://[^ ]+' | head -1 || true)
if [ -z "$PREVIEW_URL" ]; then
echo "WARNING: could not parse preview URL from wrangler output"
fi
echo "preview_url=$PREVIEW_URL" >> $GITHUB_ENV
- name: Post or update PR comment
uses: actions/github-script@v6
env:
preview_url: ${{ env.preview_url }}
with:
script: |
const marker = '<!-- pages-preview-comment -->';
const prNumber = context.issue.number;
const previewUrl = process.env.preview_url || '';
const body = `${marker}\n**Cloudflare Pages Preview**\n\n${previewUrl || 'Preview URL unavailable — check workflow logs.'}\n\nCommit: ${process.env.GITHUB_SHA}\n`;
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
});
const existing = comments.find(c => c.body && c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body,
});
}

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/desktop",
"version": "3.3.9-beta.2",
"version": "3.3.13",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/desktop",
"version": "3.3.9-beta.2",
"version": "3.3.13",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "3.3.9-beta.2",
"version": "3.3.13",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/cjs/index.js",

View File

@@ -252,6 +252,7 @@ export const osIntegrationRouter = t.router({
if (menuItem) menu.append(menuItem);
}
if (menu.items.length > 0) menu.popup();
menu.on("menu-will-close", () => emit.next([]));
return () => {
menu.removeAllListeners();
menu.closePopup();

View File

@@ -34,6 +34,17 @@ export const windowRouter = t.router({
}),
maximized: t.procedure.query(() => globalThis.window?.isMaximized()),
fullscreen: t.procedure.query(() => globalThis.window?.isFullScreen()),
onClose: t.procedure.subscription(() => {
return observable<void>((emit) => {
function listener() {
emit.next();
}
globalThis.window?.addListener("close", listener);
return () => {
globalThis.window?.removeListener("close", listener);
};
});
}),
onWindowStateChanged: t.procedure.subscription(() => {
return observable<{ maximized: boolean; fullscreen: boolean }>((emit) => {
function listener() {

View File

@@ -52,6 +52,9 @@ locale.then(({ default: locale }) => {
});
setI18nGlobal(i18n);
const appHostnames = isDevelopment()
? ["localhost", "127.0.0.1"]
: ["app.notesnook.com"];
// only run a single instance
if (!MAC_APP_STORE && !app.requestSingleInstanceLock()) {
console.log("Another instance is already running!");
@@ -173,6 +176,19 @@ async function createWindow() {
return { action: "deny" };
});
mainWindow.webContents.on("will-navigate", (event, url) => {
try {
const parsedUrl = new URL(url);
if (!appHostnames.includes(parsedUrl.hostname)) {
event.preventDefault();
shell.openExternal(url);
}
} catch (e) {
console.error("will-navigate: failed to parse URL", url, e);
event.preventDefault();
}
});
nativeTheme.on("updated", () => {
setupTray();
setupJumplist();

View File

@@ -62,7 +62,17 @@ function registerProtocol() {
headers: { "Content-Type": extensionToMimeType[fileExtension] }
});
} else {
return net.fetch(request, { bypassCustomProtocolHandlers: true });
const requestHeaders = new Headers(request.headers);
if (!requestHeaders.has("referer"))
requestHeaders.set("referer", PROTOCOL_URL);
return await net.fetch(
new Request(request, {
headers: requestHeaders
}),
{
bypassCustomProtocolHandlers: true
}
);
}
});
console.info(`${SCHEME} protocol inteception "successful"`);

View File

@@ -3,10 +3,8 @@
</p>
<h1 align="center">Notesnook Mobile</h1>
<h3 align="center">The mobile app is built using React Native, Typescript & Javascript for both iOS & Android.</h3>
<p align="center">
<a href="#developer-guide">Developer guide</a> | <a href="#build-instructions">How to build?</a>
</p>
<h3 align="center">The mobile app is built with React Native for both iOS and Android.</h3>
<p align="center"><a href="#build-instructions">Build instructions</a> | <a href="#developer-guide">Developer guide</a> | <a href="#running-e2e-tests-detox">E2E tests</a></p>
<p align="center">
<a href="https://play.google.com/store/apps/details?id=com.streetwriters.notesnook">
@@ -25,21 +23,21 @@
Requirements:
1. [Node.js](https://nodejs.org/en/download/)
1. [Node.js](https://nodejs.org/en/download/) 20+ (the repo is pinned to Node `22.20.0` via Volta)
2. [git](https://git-scm.com/downloads)
3. NPM (not yarn or pnpm)
4. [React Native](https://reactnative.dev/docs/environment-setup)
3. `npm`
4. [React Native environment setup](https://reactnative.dev/docs/set-up-your-environment)
To run the app locally, you will need to setup React Native on your system:
To run the app locally, first complete React Native native tooling setup:
1. Open the official [environment setup guide here](https://reactnative.dev/docs/environment-setup)
1. Open [React Native environment setup](https://reactnative.dev/docs/set-up-your-environment)
2. Select `React Native CLI Quickstart`
3. Select your OS & the platform to run the app on (iOS or Android)
3. Select your OS and target platform(s): iOS and/or Android
4. Follow the steps listed.
> Please keep in mind that **Expo is not supported**.
> Expo is not used in this project.
Once you have completed the setup, the first step is to `clone` the monorepo:
Clone the monorepo:
```bash
git clone https://github.com/streetwriters/notesnook.git
@@ -48,7 +46,7 @@ git clone https://github.com/streetwriters/notesnook.git
cd notesnook
```
Once you are inside the `./notesnook` directory, run the preparation step:
Install dependencies and bootstrap the mobile workspace:
```bash
# this might take a while to complete
@@ -58,17 +56,17 @@ npm run bootstrap -- --scope=mobile
### Running the app on Android
[Setup an Android emulator from Android Studio](https://developer.android.com/studio/run/managing-avds) if you haven't already, and then run the following command to start the app in the Emulator:
[Set up an Android emulator from Android Studio](https://developer.android.com/studio/run/managing-avds) (or connect a physical device), then run:
```bash
npm run start:android
```
If you want to run the app on your phone, make sure to [enable USB debugging](https://developer.android.com/studio/debug/dev-options).
If you are using a physical device, enable [USB debugging](https://developer.android.com/studio/debug/dev-options).
### Running the app on iOS
To run the app on iOS:
Install CocoaPods dependencies first, then run the iOS app:
```bash
# this might take a while to complete
@@ -77,90 +75,87 @@ npm run prepare:ios
npm run start:ios
```
### Useful development commands
```bash
# start Metro only
npm run start:metro
# start Re.Pack bundler
npm run start:repack
```
## Developer guide
> This project is in a transition state between Javascript & Typescript. We are gradually porting everything over to Typescript, so if you can help with that, it'd be great!
> The mobile app is a mixed TypeScript/JavaScript codebase.
### The tech stack
We try to keep the stack as lean as possible:
1. React Native
2. Typescript/Javascript
3. Zustand: State management
4. Detox: Runs all our e2e tests
5. React Native MMKV: Database & persistence
6. libsodium: Encryption
1. React Native `0.82`
2. React `19`
3. TypeScript + JavaScript
4. Zustand (state management)
5. Detox (end-to-end testing)
6. libsodium (encryption)
### Project structure
The app codebase is distributed over two primary directories. `native/` and `app/`.
Top-level directories in `apps/mobile/`:
- `android`: Android native code
- `ios`: iOS native code
- `app/`: Includes all the app code other than the native part. All JS-only dependencies are installed here.
- `components/`: Each component serves a specific purpose in the app UI. For example, the `Paragraph` component is used to render paragraphs in the app, and a `Header` component is used to render a `header` on all screens.
- `common/`: Features that are integral to the app's functionality. For example, the notesnook core is initialized here.
- `hooks/`: Hooks for different app logic
- `navigation/`: Includes app navigation-specific code. Here the app navigation, editor & side menu are rendered side by side in fluid tabs.
- `screens`: Navigator screens.
- `share`: Code for the iOS Share Extension and Android widget.
- `services`: Parts of code that do a specific function. For example, the `sync` service runs Sync from anywhere in the app.
- `stores`: We use `zustand` for global state management in the app. Multiple stores provide the state for different parts of the app.
- `utils`: General purpose stuff such as constant values, utility functions, etc.
- `app/`: Main React Native app source (`components`, `common`, `hooks`, `navigation`, `screens`, `services`, `stores`, `utils`, etc.)
- `android/`: Android native project
- `ios/`: iOS native project
- `e2e/`: Detox test suite and config
- `patches/`: `patch-package` patches
- `scripts/`: Mobile-specific scripts
There are several other folders at the root:
## Running E2E tests (Detox)
- `e2e/`: Detox End to end tests
- `patches/`: Patches for various react native dependencies.
Detox device defaults in this repo:
### Running the tests
When you are done making the required changes, you must run the tests to ensure you didn't break anything. We use Detox as the testing framework & the tests can be started as follows:
- Android emulator: `Pixel_5_API_36`
- iOS simulator: `iPhone 17 Pro Max`
### Android
To run the tests on Android, you will need to create an emulator device on your system:
Build and run Android Detox tests:
```
$ANDROID_HOME/tools/bin/avdmanager create avd -n Pixel_5_API_31 -d pixel --package "system-images;android-31;default;x86_64"
```
If you face problems, follow the detailed guide in [Detox documentation](https://wix.github.io/Detox/docs/introduction/android-dev-env). Keep the emulator name set to `Pixel_5_API_31`.
Once you have created an emulator device, build the Android apks:
```
```bash
npm run build:android
```
Finally, run the tests:
```
npm run test:android
```
For debug configuration:
```bash
npm run build:android:debug
npm run start:metro
npm run test:android:debug
```
### iOS
To run e2e tests on the iOS simulator, you must be on a Mac with XCode installed.
First, install [AppleSimulatorUtils](https://github.com/wix/AppleSimulatorUtils):
Build and run iOS Detox tests:
```bash
npm run build:ios
npm run test:ios
```
If simulator tooling is missing, install [AppleSimulatorUtils](https://github.com/wix/AppleSimulatorUtils):
```bash
brew tap wix/brew
brew install applesimutils
```
Now build the iOS app for testing:
## Release commands
```
npm run build:ios
```
Finally, run the tests:
Android release helpers:
```bash
npm run release:android
npm run release:android:bundle
```
npm run test:ios
```
All tests on iOS are configured to run on `iPhone 8` simulator.

View File

@@ -5,6 +5,7 @@ apply plugin: 'kotlin-parcelize'
apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"
import com.android.build.OutputFile
import groovy.json.JsonSlurper
import org.apache.tools.ant.taskdefs.condition.Os
@@ -94,6 +95,18 @@ def fdroidBuild() {
return project.hasProperty("fdroidBuild") && project.fdroidBuild == "true"
}
def prBuildNumber() {
def value = project.getProperties().get("prBuildNumber")
return value ? value : "1000"
}
def stagingReleaseBuild() {
// Enable with STAGING_BUILDtrue or -PstagingReleaseBuild=true
def fromEnv = System.getenv("STAGING_BUILD") == "true"
def fromProp = project.hasProperty("stagingReleaseBuild") && project.stagingReleaseBuild == "true"
return fromEnv || fromProp
}
def getNpmVersion() {
def inputFile = file("$rootDir/../package.json")
def jsonPackage = new JsonSlurper().parseText(inputFile.text)
@@ -118,14 +131,17 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
namespace "com.streetwriters.notesnook"
defaultConfig {
applicationId "com.streetwriters.notesnook"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled true
versionCode 3094
if (project.hasProperty("prBuildNumber")) {
versionCode Integer.parseInt(prBuildNumber())
} else {
versionCode 3099
}
versionName getNpmVersion()
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
@@ -192,6 +208,8 @@ android {
output.versionCodeOverride =
defaultConfig.versionCode * 5 + versionCodes.get(abi)
println("Fdroid Version code: ${output.versionCodeOverride} for abi ${versionCodes.get(abi)}");
} else if (stagingReleaseBuild()) {
output.versionCodeOverride = defaultConfig.versionCode + versionCodes.get(abi)
} else {
if (isBuildingAAB) {
output.versionCodeOverride = 4 * 1048576 + defaultConfig.versionCode
@@ -233,7 +251,6 @@ dependencies {
} else {
implementation jscFlavor
}
}

View File

@@ -199,11 +199,7 @@
</intent-filter>
</activity>
<service
android:name=".OnClearFromRecentService"
android:enabled="true"
android:exported="true"
android:stopWithTask="false" />
<service android:name="com.asterinet.react.bgactions.RNBackgroundActionsTask" />
<service

View File

@@ -39,28 +39,32 @@ public class BootTaskService extends HeadlessJsTaskService {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel("com.streetwriters.notesnook",
"Default",
NotificationManager.IMPORTANCE_DEFAULT);
try {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel("com.streetwriters.notesnook",
"Default",
NotificationManager.IMPORTANCE_DEFAULT);
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
notification = new NotificationCompat.Builder(this, channel.getId())
.setContentTitle("Sync on boot")
.setContentText("")
.setSmallIcon(R.drawable.ic_stat_name)
.build();
if (android.os.Build.VERSION.SDK_INT >= 34) {
this.startForeground(
1,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE);
} else {
this.startForeground(
1,
notification);
}
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
notification = new NotificationCompat.Builder(this, channel.getId())
.setContentTitle("Sync on boot")
.setContentText("")
.setSmallIcon(R.drawable.ic_stat_name)
.build();
if (android.os.Build.VERSION.SDK_INT >= 34) {
this.startForeground(
1,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE);
} else {
this.startForeground(
1,
notification);
}
} catch (Exception ignored) {
stopSelf(startId);
}
return super.onStartCommand(intent, flags, startId);

View File

@@ -28,10 +28,6 @@ public class MainActivity extends ReactActivity {
WebView.setWebContentsDebuggingEnabled(true);
}
try {
startService(new Intent(getBaseContext(), OnClearFromRecentService.class));
} catch (Exception ignored) {}
}
/**

View File

@@ -1,36 +0,0 @@
package com.streetwriters.notesnook;
import android.app.Service;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.IBinder;
import android.util.Log;
public class OnClearFromRecentService extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public void onTaskRemoved(Intent rootIntent) {
try {
SharedPreferences appStateDetails = getApplicationContext().getSharedPreferences("appStateDetails", MODE_PRIVATE);
SharedPreferences.Editor edit = appStateDetails.edit();
edit.remove("appState");
edit.apply();
stopSelf();
} catch (UnsatisfiedLinkError | Exception e) {
}
}
}

View File

@@ -93,21 +93,6 @@ public class RCTNNativeModule extends ReactContextBaseJavaModule {
}
}
@ReactMethod
public void setAppState(final String appState) {
SharedPreferences appStateDetails = getReactApplicationContext().getSharedPreferences("appStateDetails", Context.MODE_PRIVATE);
SharedPreferences.Editor edit = appStateDetails.edit();
edit.putString("appState", appState);
edit.apply();
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String getAppState() {
SharedPreferences appStateDetails = getReactApplicationContext().getSharedPreferences("appStateDetails", Context.MODE_PRIVATE);
String appStateValue = appStateDetails.getString("appState", "");
return appStateValue.isEmpty() ? null : appStateValue;
}
@ReactMethod(isBlockingSynchronousMethod = true)
public int getWidgetId() {
return NotePreviewConfigureActivity.appWidgetId;

View File

@@ -4,4 +4,10 @@
<domain includeSubdomains="true">10.0.2.2</domain>
<domain includeSubdomains="true">localhost</domain>
</domain-config>
<base-config>
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>

View File

@@ -36,6 +36,8 @@ allprojects {
url "$rootDir/../node_modules/detox/Detox-android"
}
google()
mavenCentral()
maven { url 'https://www.jitpack.io' }
}
}

View File

@@ -1,3 +1,8 @@
- Bug fixes and minor improvements
- Fix clearing note title adds generated title instead of staying empty
- Improved table cell selection, table scrolling, and table cell editing on mobile
- Fixed app on launch randomly focuses editor instead of notes list
- Fixed file size check shows error but continues adding the file to editor
- Note title headline format should get content from first paragraph
- Minor bug fixes and performance improvements
Thank you for using Notesnook!

View File

@@ -1,4 +1,4 @@
{
"name": "Notesnook",
"displayName": "Notesnook"
}
}

View File

@@ -127,8 +127,12 @@ export const withTheme = (Element: (props: any) => JSX.Element) => {
.then((theme) => {
if (theme) {
theme.colorScheme === "dark"
? useThemeStore.getState().setDarkTheme(theme)
: useThemeStore.getState().setLightTheme(theme);
? useThemeStore.setState({
darkTheme: theme
})
: useThemeStore.setState({
lightTheme: theme
});
}
})
.catch(() => {
@@ -138,9 +142,9 @@ export const withTheme = (Element: (props: any) => JSX.Element) => {
const listener = Appearance.addChangeListener(({ colorScheme }) => {
if (colorScheme && SettingsService.getProperty("useSystemTheme")) {
useThemeStore
.getState()
.setColorScheme(colorScheme as "light" | "dark");
useThemeStore.setState({
colorScheme: colorScheme as "light" | "dark"
});
}
});
return () => {

View File

@@ -205,8 +205,9 @@ export async function getDatabaseKey(appLockPassword?: string) {
}
if (await Keychain.hasInternetCredentials("notesnook")) {
const userKeyCredentials =
await Keychain.getInternetCredentials("notesnook");
const userKeyCredentials = await Keychain.getInternetCredentials(
"notesnook"
);
if (userKeyCredentials) {
const userKeyCipher: Cipher = (await encrypt(

View File

@@ -72,7 +72,7 @@ export async function setupDatabase(password?: string) {
({
compress: Gzip.deflate,
decompress: Gzip.inflate
}) as ICompressor,
} as ICompressor),
batchSize: 50,
sqliteOptions: {
dialect: (name) => ({

View File

@@ -33,6 +33,10 @@ import {
} from "./utils";
import Upload from "@ammarahmed/react-native-upload";
import { CloudUploader } from "react-native-nitro-cloud-uploader";
import { useUserStore } from "../../stores/use-user-store";
import { sleep } from "../../utils/time";
import { isFeatureAvailable } from "@notesnook/common";
import { strings } from "@notesnook/intl";
// Upload constants
const CHUNK_SIZE = 10 * 1024 * 1024; // 10 MB
@@ -197,6 +201,20 @@ export async function uploadFile(
const remoteFileSize = await getUploadedFileSize(filename);
if (remoteFileSize === FileSizeResult.Error) return false;
const featureResult = await isFeatureAvailable(
"fileSize",
fileInfo.size || 0
);
if (!featureResult.isAllowed) {
ToastManager.show({
heading: strings.fileTooLarge(),
message: featureResult.error,
type: "error"
});
return false;
}
if (
remoteFileSize > FileSizeResult.Empty &&
remoteFileSize === fileInfo.size
@@ -212,6 +230,9 @@ export async function uploadFile(
);
if (Platform.OS === "android") {
useUserStore.setState({
disableAppLockRequests: true
});
const status = await PermissionsAndroid.request(
"android.permission.POST_NOTIFICATIONS"
);
@@ -221,6 +242,10 @@ export async function uploadFile(
type: "info"
});
}
await sleep(500);
useUserStore.setState({
disableAppLockRequests: false
});
}
let uploaded = false;

View File

@@ -161,10 +161,10 @@ export async function checkUpload(
size === 0
? `File size is 0.`
: size === -1
? `File verification check failed.`
: expectedSize !== decryptedLength
? `File size mismatch. Expected ${size} bytes but got ${decryptedLength} bytes.`
: undefined;
? `File verification check failed.`
: expectedSize !== decryptedLength
? `File size mismatch. Expected ${size} bytes but got ${decryptedLength} bytes.`
: undefined;
if (error) throw new Error(error);
}

View File

@@ -239,8 +239,8 @@ const AppLocked = () => {
deviceMode !== "mobile"
? "50%"
: Platform.OS == "ios"
? "95%"
: "100%",
? "95%"
: "100%",
paddingHorizontal: 12,
marginBottom: 30,
marginTop: 15,

View File

@@ -59,6 +59,7 @@ import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../utils/styles";
import Navigation from "../../services/navigation";
const Actions = ({
attachment,
@@ -304,9 +305,9 @@ const Actions = ({
<Pressable
onPress={async () => {
eSendEvent(eCloseSheet, contextId);
await sleep(150);
close?.();
eSendEvent(eCloseAttachmentDialog);
await sleep(300);
Navigation.navigate("FluidPanelsView");
openNote(item, (item as any).type === "trash");
}}
style={{

View File

@@ -195,8 +195,8 @@ export const Login = ({
? "50%"
: "49.99%"
: focused
? "100%"
: "99.9%",
? "100%"
: "99.9%",
backgroundColor: colors.primary.background,
alignSelf: "center",
paddingHorizontal: DDS.isTab ? 0 : DefaultAppStyles.GAP,

View File

@@ -161,10 +161,10 @@ const FloatingButton = ({
icon
? icon
: route.name === "Notebooks"
? "notebook-plus"
: route.name === "Trash"
? "delete"
: "plus"
? "notebook-plus"
: route.name === "Trash"
? "delete"
: "plus"
}
color={color || colors.primary.accent}
size={size === "small" ? AppFontSize.xl : AppFontSize.xxxl}

View File

@@ -136,8 +136,8 @@ const BaseDialog = ({
backgroundColor: background
? background
: transparent
? "transparent"
: "rgba(0,0,0,0.3)"
? "transparent"
: "rgba(0,0,0,0.3)"
}}
>
<KeyboardAvoidingView
@@ -155,8 +155,8 @@ const BaseDialog = ({
justifyContent: centered
? "center"
: bottom
? "flex-end"
: "flex-start"
? "flex-end"
: "flex-start"
}
]}
>

View File

@@ -201,14 +201,12 @@ export const AppLockPassword = () => {
accountPass
? strings.enterAccountPassword()
: mode === "change"
? keyboardType === "pin"
? strings.newPin()
: strings.newPassword()
: `${
keyboardType === "pin"
? strings.pin()
: strings.password()
}`
? keyboardType === "pin"
? strings.newPin()
: strings.newPassword()
: `${
keyboardType === "pin" ? strings.pin() : strings.password()
}`
}
/>
@@ -385,8 +383,8 @@ export const AppLockPassword = () => {
mode === "remove"
? strings.remove()
: mode === "change"
? strings.change()
: strings.save()
? strings.change()
: strings.save()
}
negativeTitle={strings.cancel()}
positiveType="transparent"

View File

@@ -56,7 +56,12 @@ import Seperator from "../../ui/seperator";
import Paragraph from "../../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../../utils/styles";
import { Note, NoteContent, VAULT_ERRORS } from "@notesnook/core";
import {
isEncryptedContent,
Note,
NoteContent,
VAULT_ERRORS
} from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
export const VaultDialog: React.FC = () => {
@@ -109,9 +114,17 @@ export const VaultDialog: React.FC = () => {
const biometry = await BiometricService.isBiometryAvailable();
const available = !!biometry;
const fingerprint = await BiometricService.hasInternetCredentials();
const noteLocked = data.item
? await db.vaults.itemExists(data.item)
: false;
if (data.item) {
const locked = await db.vaults.itemExists(data.item);
noteLockedRef.current = locked;
if (!locked) {
const content = await db.content.findByNoteId(data.item!.id);
if (content && isEncryptedContent(content)) {
noteLockedRef.current = true;
}
}
}
// Set refs
noteRef.current = data.item;
@@ -122,7 +135,6 @@ export const VaultDialog: React.FC = () => {
positiveButtonTypeRef.current = data.positiveButtonType || "transparent";
customActionTitleRef.current = data.customActionTitle || null;
customActionParagraphRef.current = data.customActionParagraph || null;
noteLockedRef.current = noteLocked;
onUnlockRef.current = data.onUnlock;
requestTypeRef.current = data.requestType;
@@ -417,9 +429,10 @@ export const VaultDialog: React.FC = () => {
onUnlockRef.current
) {
const password = passwordRef.current;
const unlock = onUnlockRef.current;
close();
await sleep(300);
onUnlockRef.current(note, password);
await sleep(500);
unlock(note, password);
}
} catch (e) {
takeErrorAction();

View File

@@ -40,7 +40,6 @@ import Animated, {
withTiming
} from "react-native-reanimated";
import { useTabStore } from "../../screens/editor/tiptap/use-tab-store";
import { getAppState } from "../../screens/editor/tiptap/utils";
import { eSendEvent } from "../../services/event-manager";
import { useSettingStore } from "../../stores/use-setting-store";
import { eClearEditor } from "../../utils/events";
@@ -84,18 +83,9 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
}: TabProps,
ref
) {
const appState = useMemo(() => getAppState(), []);
const deviceMode = useSettingStore((state) => state.deviceMode);
const fullscreen = useSettingStore((state) => state.fullscreen);
const translateX = useSharedValue(
widths
? appState &&
appState?.movedAway === false &&
useTabStore.getState().getCurrentNoteId()
? widths.sidebar + widths.list
: widths.sidebar
: 0
);
const translateX = useSharedValue(widths ? widths.sidebar : 0);
const startX = useSharedValue(0);
const currentTab = useSharedValue(1);
const previousTab = useSharedValue(1);
@@ -125,10 +115,7 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
translateX.value = 0;
} else {
if (prevWidths.current?.sidebar !== widths.sidebar) {
translateX.value =
appState && appState?.movedAway === false
? editorPosition
: widths.sidebar;
translateX.value = widths.sidebar;
if (translateX.value === editorPosition) {
onChangeTab?.({ i: 2, from: 1 });
}
@@ -136,15 +123,7 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
}
isLoaded.current = true;
prevWidths.current = widths;
}, [
deviceMode,
widths,
fullscreen,
translateX,
editorPosition,
appState,
onChangeTab
]);
}, [deviceMode, widths, fullscreen, translateX, editorPosition, onChangeTab]);
useEffect(() => {
const sub = BackHandler.addEventListener("hardwareBackPress", () => {

View File

@@ -32,9 +32,11 @@ import { eOnLoadNote, eShowMergeDialog } from "../../../utils/events";
import { fluidTabsRef } from "../../../utils/global-refs";
import { NotebooksWithDateEdited, TagsWithDateEdited } from "@notesnook/common";
import { useTabStore } from "../../../screens/editor/tiptap/use-tab-store";
import { editorController } from "../../../screens/editor/tiptap/utils";
import { RouteParams } from "../../../stores/use-navigation-store";
import NotePreview from "../../note-history/preview";
import SelectionWrapper, { selectItem } from "../selection-wrapper";
import { RouteParams } from "../../../stores/use-navigation-store";
export const openNote = async (
item: Note,
@@ -63,11 +65,19 @@ export const openNote = async (
component: <NotePreview note={item} content={content} />
});
} else {
if (!useTabStore.getState().hasTabForNote(note.id!)) {
editorController.current.commands.setLoading(
true,
useTabStore.getState().currentTab
);
}
eSendEvent(eOnLoadNote, {
item: note
});
if (!DDS.isTab) {
fluidTabsRef.current?.goToPage("editor");
setTimeout(() => {
fluidTabsRef.current?.goToPage("editor");
}, 32);
}
}
};

View File

@@ -77,10 +77,10 @@ export default function List(props: ListProps) {
props.renderedInRoute === "Notes"
? "home"
: props.renderedInRoute === "Favorites"
? "favorites"
: props.renderedInRoute === "Trash" || props.dataType === "trash"
? "trash"
: `${props.dataType}s`;
? "favorites"
: props.renderedInRoute === "Trash" || props.dataType === "trash"
? "trash"
: `${props.dataType}s`;
const groupOptions = useGroupOptions(groupType);

View File

@@ -60,6 +60,7 @@ import { IconButton } from "../ui/icon-button";
import Seperator from "../ui/seperator";
import Paragraph from "../ui/typography/paragraph";
import { presentDialog } from "../dialog/functions";
import { Cipher } from "@notesnook/crypto";
const MergeConflicts = () => {
const { colors } = useThemeColors();
@@ -84,12 +85,21 @@ const MergeConflicts = () => {
dateEdited: contentToSave.dateEdited
});
const noteLocked = await db.vaults.itemExists(note);
const noteContent = await db.content.findByNoteId(note.id);
if (noteLocked) {
await db.vault.save({
...contentToSave,
sessionId: `${Date.now()}`
if (noteContent?.locked) {
const selectedContent = contentToSave.conflicted
? noteContent
: noteContent.conflicted;
await db.content.add({
id: note.contentId,
dateResolved: noteContent?.conflicted?.dateModified || Date.now(),
sessionId: `${Date.now()}`,
data: selectedContent?.data as Cipher<"base64">,
type: selectedContent?.type,
locked: true,
conflicted: undefined
});
} else {
await db.content.add({
@@ -138,7 +148,6 @@ const MergeConflicts = () => {
onUnlock: async (item, password) => {
if (!item || !password) return;
const currentContent = await db.content.get(item.contentId!);
try {
noteContent = {
...(await db.content.get(item.contentId!)),

View File

@@ -33,7 +33,6 @@ import AppIcon from "../ui/AppIcon";
import { Button } from "../ui/button";
import { Pressable } from "../ui/pressable";
import Paragraph from "../ui/typography/paragraph";
import { Dialog } from "../dialog";
const TOP_BAR_ITEMS: ActionId[] = [
"pin",
@@ -174,15 +173,15 @@ export const Items = ({
DDS.isTab
? AppFontSize.xxl
: shouldShrink
? AppFontSize.xxl
: AppFontSize.lg
? AppFontSize.xxl
: AppFontSize.lg
}
color={
item.checked
? item.activeColor || colors.primary.accent
: item.id.match(/(delete|trash)/g)
? colors.error.icon
: colors.secondary.icon
? colors.error.icon
: colors.secondary.icon
}
/>
</Pressable>
@@ -213,8 +212,8 @@ export const Items = ({
text: item.checked
? item.activeColor || colors.primary.accent
: item.id === "delete" || item.id === "trash"
? colors.error.paragraph
: colors.primary.paragraph
? colors.error.paragraph
: colors.primary.paragraph
}}
testID={"icon-" + item.id}
onPress={item.onPress}
@@ -278,8 +277,8 @@ export const Items = ({
item.checked
? item.activeColor || colors.primary.accent
: item.id === "delete" || item.id === "trash"
? colors.error.icon
: colors.secondary.icon
? colors.error.icon
: colors.secondary.icon
}
/>
@@ -327,8 +326,9 @@ export const Items = ({
const getTopBarItemChunksOfFour = () => {
const chunks = [];
for (let i = 0; i < topBarItems.length; i += 5) {
chunks.push(topBarItems.slice(i, i + 5));
const itemCount = shouldShrink ? 4 : 5;
for (let i = 0; i < topBarItems.length; i += itemCount) {
chunks.push(topBarItems.slice(i, i + itemCount));
}
return chunks;
};
@@ -375,7 +375,8 @@ export const Items = ({
style={{
flexDirection: "row",
paddingHorizontal: DefaultAppStyles.GAP,
gap: 5
gap: 5,
width: width
}}
>
{item.map(renderTopBarItem)}

View File

@@ -356,7 +356,7 @@ const ProductItem = (props: {
(product as RNIap.Subscription)?.productId
) ||
props.pricingPlans.user?.subscription?.productId ===
(product as Plan).id);
(product as Plan)?.id);
useEffect(() => {
props.pricingPlans

View File

@@ -102,9 +102,17 @@ const TabItemComponent = (props: {
{props.tab.session?.noteLocked ? (
<>
{props.tab.session?.locked ? (
<Icon size={AppFontSize.md} name="lock" />
<Icon
size={AppFontSize.md}
name="lock"
color={colors.primary.icon}
/>
) : (
<Icon size={AppFontSize.md} name="lock-open-outline" />
<Icon
size={AppFontSize.md}
name="lock-open-outline"
color={colors.primary.icon}
/>
)}
</>
) : null}

View File

@@ -118,8 +118,8 @@ const ListBlockItem = ({
{item?.content.length > 200
? item?.content.slice(0, 200) + "..."
: !item.content || item.content.trim() === ""
? strings.linkNoteEmptyBlock()
: item.content}
? strings.linkNoteEmptyBlock()
: item.content}
</Paragraph>
<View

View File

@@ -53,15 +53,9 @@ async function fetchMonographData(noteId: string) {
const monograph = monographId
? await db.monographs.get(monographId)
: undefined;
const analyticsFeature = await isFeatureAvailable("monographAnalytics");
const analytics =
monographId && analyticsFeature
? await db.monographs.analytics(monographId)
: undefined;
return {
monograph,
monographId,
analytics
};
}
@@ -381,9 +375,6 @@ const PublishNoteSheet = ({
}}
>
<Paragraph size={AppFontSize.sm}>{strings.views()}</Paragraph>
<Paragraph>
{monographData?.result?.analytics?.totalViews || 0}
</Paragraph>
</View>
</View>
</View>

View File

@@ -104,8 +104,8 @@ const ListBlockItem = ({
{item?.content.length > 200
? item?.content.slice(0, 200) + "..."
: !item.content || item.content.trim() === ""
? strings.linkNoteEmptyBlock()
: item.content}
? strings.linkNoteEmptyBlock()
: item.content}
</Paragraph>
<View

View File

@@ -114,8 +114,8 @@ export const UserSheet = () => {
progress ? `(${progress.current})` : ""
}`
: lastSyncStatus === SyncStatus.Failed
? strings.syncFailed()
: strings.synced()}{" "}
? strings.syncFailed()
: strings.synced()}{" "}
{!syncing ? (
<TimeSince
style={{
@@ -139,8 +139,8 @@ export const UserSheet = () => {
!user || lastSyncStatus === SyncStatus.Failed
? colors.error.icon
: isOffline
? colors.static.orange
: colors.success.icon
? colors.static.orange
: colors.success.icon
}
/>
</Paragraph>

View File

@@ -136,8 +136,8 @@ const SimpleTabView = ({
{loadedKeysRef.current.has(route.key)
? getSceneForRoute(route)
: navigationState.index === routeIndex
? getSceneForRoute(route)
: null}
? getSceneForRoute(route)
: null}
</View>
))}
</View>
@@ -435,8 +435,9 @@ const TabBar = (props: SimpleTabBarProps) => {
color={colors.primary.icon}
onPress={async () => {
if (props.navigationState.index === 1) {
const notebooksFeature =
await isFeatureAvailable("notebooks");
const notebooksFeature = await isFeatureAvailable(
"notebooks"
);
if (!notebooksFeature.isAllowed) {
PaywallSheet.present(notebooksFeature);
return;

View File

@@ -151,8 +151,8 @@ export function MenuItem({
item.icon === "crown"
? colors.static.yellow
: isFocused
? colors.selected.icon
: colors.secondary.icon
? colors.selected.icon
: colors.secondary.icon
}
size={AppFontSize.md}
/>

View File

@@ -98,8 +98,8 @@ export const NotebookItem = ({
item.depth === 0
? undefined
: item.depth < 6
? 15 * item.depth
: 15 * 5,
? 15 * item.depth
: 15 * 5,
width: "100%",
marginTop: 2
}}
@@ -187,8 +187,8 @@ export const NotebookItem = ({
!item.hasChildren || disableExpand
? "book-outline"
: expanded
? "chevron-down"
: "chevron-right"
? "chevron-down"
: "chevron-right"
}
/>
@@ -241,6 +241,7 @@ export const NotebookItem = ({
name="plus"
size={AppFontSize.md}
testID={`add-notebook-${index}`}
color={colors.primary.icon}
top={0}
left={0}
bottom={0}

View File

@@ -303,7 +303,7 @@ export const SideMenuTags = () => {
} catch (e) {
DatabaseLogger.error(e);
}
}, 500);
}, 100);
}}
placeholderTextColor={colors.primary.placeholder}
/>

View File

@@ -158,22 +158,22 @@ export const Toast = ({ context = "global" }) => {
toastOptions.icon
? toastOptions.icon
: toastOptions.type === "success"
? "check"
: toastOptions.type === "info"
? "information"
: "close"
? "check"
: toastOptions.type === "info"
? "information"
: "close"
}
size={isFullToastMessage ? AppFontSize.xxxl : AppFontSize.xl}
color={
toastOptions?.icon
? toastOptions?.icon
: toastOptions.type === "error"
? colors.error.icon
: toastOptions.type === "info"
? isDark
? colors.static.white
: colors.static.black
: colors.success.icon
? colors.error.icon
: toastOptions.type === "info"
? isDark
? colors.static.white
: colors.static.black
: colors.success.icon
}
/>

View File

@@ -20,6 +20,7 @@ import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { ColorValue, TextProps } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import EvilIcon from "react-native-vector-icons/EvilIcons";
import { AppFontSize } from "../../../utils/size";
export interface IconProps extends TextProps {
@@ -43,11 +44,22 @@ export interface IconProps extends TextProps {
*
*/
color?: ColorValue | number | undefined;
iconFamily?: "evilicons" | "material";
}
export default function AppIcon(props: IconProps) {
export default function AppIcon({
iconFamily = "material",
...props
}: IconProps) {
const { colors } = useThemeColors();
return (
return iconFamily === "evilicons" ? (
<EvilIcon
size={AppFontSize.md}
color={colors.primary.icon}
{...(props as any)}
/>
) : (
<Icon
size={AppFontSize.md}
color={colors.primary.icon}

View File

@@ -107,7 +107,7 @@ export const IconButton = ({
color={
restProps.disabled
? RGB_Linear_Shade(-0.05, hexToRGBA(colors.secondary.background))
: colors.static[color as never] || color
: colors.static[color as never] || color || colors.primary.icon
}
size={size}
/>

View File

@@ -121,8 +121,8 @@ const Input = ({
const color = error
? colors.error.border
: focus
? customColor || colors.selected.border
: colors.primary.border;
? customColor || colors.selected.border
: colors.primary.border;
const validate = async (value: string) => {
if (!validationType) return;

View File

@@ -268,8 +268,8 @@ export const Pressable = ({
const opacity = customOpacity
? customOpacity
: type === "accent"
? 1
: colorOpacity;
? 1
: colorOpacity;
const alpha = customAlpha ? customAlpha : isDark ? 0.03 : -0.03;
const { fontScale } = useWindowDimensions();
const growFactor = 1 + (fontScale - 1) / 8;

View File

@@ -159,7 +159,7 @@ const SheetWrapper = ({
{bottomPadding ? (
<View
style={{
height: bottomInsets
height: 10
}}
/>
) : null}

View File

@@ -21,6 +21,7 @@ import { isFeatureAvailable, useAreFeaturesAvailable } from "@notesnook/common";
import {
Color,
createInternalLink,
isEncryptedContent,
Item,
ItemReference,
Note,
@@ -221,7 +222,15 @@ export const useActions = ({
useEffect(() => {
if (item.type === "note") {
db.vaults.itemExists(item).then((locked) => setLocked(locked));
db.vaults.itemExists(item).then(async (locked) => {
setLocked(locked);
if (!locked) {
const content = await db.content.findByNoteId(item.id);
if (content && isEncryptedContent(content)) {
setLocked(true);
}
}
});
}
}, [item]);

View File

@@ -30,6 +30,7 @@ import {
import { strings } from "@notesnook/intl";
import notifee from "@notifee/react-native";
import NetInfo, { NetInfoSubscription } from "@react-native-community/netinfo";
import dayjs from "dayjs";
import React, { useCallback, useEffect, useRef } from "react";
import {
AppState,
@@ -48,6 +49,7 @@ import * as RNIap from "react-native-iap";
import { DatabaseLogger, db, setupDatabase } from "../common/database";
import { initializeLogger } from "../common/database/logger";
import { MMKV } from "../common/database/mmkv";
import { deleteDCacheFiles } from "../common/filesystem/io";
import { endProgress, startProgress } from "../components/dialogs/progress";
import Migrate from "../components/sheets/migrate";
import NewFeature from "../components/sheets/new-feature";
@@ -58,11 +60,7 @@ import {
resetTabStore,
useTabStore
} from "../screens/editor/tiptap/use-tab-store";
import {
clearAppState,
editorController,
editorState
} from "../screens/editor/tiptap/utils";
import { editorController, editorState } from "../screens/editor/tiptap/utils";
import { useDragState } from "../screens/settings/editor/state";
import BackupService from "../services/backup";
import BiometricService from "../services/biometrics";
@@ -89,6 +87,7 @@ import { clearAllStores, initAfterSync } from "../stores";
import { refreshAllStores } from "../stores/create-db-collection-store";
import { useAttachmentStore } from "../stores/use-attachment-store";
import { useMessageStore } from "../stores/use-message-store";
import { useRelationStore } from "../stores/use-relation-store";
import { useSettingStore } from "../stores/use-setting-store";
import { SyncStatus, useUserStore } from "../stores/use-user-store";
import { updateStatusBarColor } from "../utils/colors";
@@ -105,12 +104,8 @@ import {
} from "../utils/events";
import { getGithubVersion } from "../utils/github-version";
import { fluidTabsRef } from "../utils/global-refs";
import { NotesnookModule } from "../utils/notesnook-module";
import { sleep } from "../utils/time";
import useFeatureManager from "./use-feature-manager";
import { deleteDCacheFiles } from "../common/filesystem/io";
import dayjs from "dayjs";
import { useRelationStore } from "../stores/use-relation-store";
const onCheckSyncStatus = async (type: SyncStatusEvent) => {
const { disableSync, disableAutoSync } = SettingsService.get();
@@ -173,7 +168,6 @@ const onAppOpenedFromURL = async (event: {
if (url.startsWith("https://app.notesnook.com/account/verified")) {
await onUserEmailVerified();
} else if (url.startsWith("ShareMedia://QuickNoteWidget")) {
clearAppState();
editorState().movedAway = false;
eSendEvent(eOnLoadNote, { newNote: true });
fluidTabsRef.current?.goToPage("editor", false);
@@ -361,23 +355,6 @@ async function checkForShareExtensionLaunchedInBackground() {
} catch (e) {}
}
async function saveEditorState() {
if (!editorState().movedAway) {
const id = useTabStore.getState().getCurrentNoteId();
const note = id ? await db.notes.note(id) : undefined;
const locked = note && (await db.vaults.itemExists(note));
if (locked) return;
const state = JSON.stringify({
editing: editorState().currentlyEditing,
movedAway: editorState().movedAway,
timestamp: Date.now()
});
NotesnookModule.setAppState(state);
} else {
NotesnookModule.setAppState("");
}
}
const onSuccessfulSubscription = async (
subscription: RNIap.ProductPurchase | RNIap.SubscriptionPurchase
) => {
@@ -537,7 +514,6 @@ const initializeDatabase = async (password?: string) => {
Notifications.restorePinnedNotes();
expiringNotesTimer();
deleteDCacheFiles();
db.attachments.removeOrphaned();
}
Walkthrough.init();
};
@@ -593,13 +569,13 @@ export const useAppEvents = () => {
}, [isAppLoading, onSyncComplete]);
useEffect(() => {
if (initialUrl) {
if (initialUrl && !isAppLoading) {
onAppOpenedFromURL({
url: initialUrl!,
isInitialUrl: true
});
}
}, [initialUrl]);
}, [initialUrl, isAppLoading]);
const subscribeToPurchaseListeners = useCallback(async () => {
if (Platform.OS === "android") {
@@ -839,7 +815,6 @@ export const useAppEvents = () => {
Sync.run("global", false, "full");
reconnectSSE();
await checkForShareExtensionLaunchedInBackground();
NotesnookModule.setAppState("");
let user = await db.user.getUser();
if (user && !user?.isEmailConfirmed) {
try {
@@ -862,7 +837,6 @@ export const useAppEvents = () => {
eSendEvent(eEditorReset);
}
} else {
await saveEditorState();
if (
SettingsService.canLockAppInBackground() &&
!useSettingStore.getState().requestBiometrics &&

View File

@@ -60,23 +60,17 @@ export const useDBItem = <T extends keyof ItemTypeKey>(
): [ItemTypeKey[T] | undefined, () => void] => {
const [item, setItem] = useState<ItemTypeKey[T]>();
const itemIdRef = useRef<string>(undefined);
const prevIdOrIndexRef = useRef<string | number>(undefined);
if (prevIdOrIndexRef.current !== idOrIndex) {
itemIdRef.current = undefined;
prevIdOrIndexRef.current = idOrIndex;
}
const itemsRef = useRef(item);
itemsRef.current = item;
useEffect(() => {
const onUpdateItem = async (itemId?: string) => {
if (typeof itemId === "string" && itemId !== itemIdRef.current) return;
if (!isValidIdOrIndex(idOrIndex)) return;
let item: ItemTypeKey[T] | undefined = undefined;
if (items && typeof idOrIndex === "number") {
const item = (await items.item(idOrIndex))?.item;
setItem(item);
itemIdRef.current = item?.id;
onItemUpdated?.(item);
item = (await items.item(idOrIndex))?.item;
} else {
if (!(db as any)[type + "s"][type]) {
console.warn(
@@ -84,24 +78,30 @@ export const useDBItem = <T extends keyof ItemTypeKey>(
`db.${type}s.${type}(id: string)`
);
} else {
const item = await (db as any)[type + "s"]?.[type]?.(
idOrIndex as string
);
setItem(item);
itemIdRef.current = item.id;
onItemUpdated?.(item);
item = await (db as any)[type + "s"]?.[type]?.(idOrIndex as string);
}
}
};
if (
itemsRef.current === item ||
itemsRef.current?.dateModified === item?.dateModified
)
return;
setItem(item);
itemIdRef.current = item?.id;
onItemUpdated?.(item);
};
let unsub: (() => void) | undefined;
if (
useSettingStore.getState().isAppLoading &&
//@ts-ignore
!globalThis["IS_SHARE_EXTENSION"]
) {
useSettingStore.subscribe((state) => {
unsub = useSettingStore.subscribe((state) => {
if (!state.isAppLoading) {
onUpdateItem();
unsub?.();
unsub = undefined;
}
});
} else {
@@ -109,9 +109,10 @@ export const useDBItem = <T extends keyof ItemTypeKey>(
}
eSubscribeEvent(eDBItemUpdate, onUpdateItem);
return () => {
unsub?.();
eUnSubscribeEvent(eDBItemUpdate, onUpdateItem);
};
}, [idOrIndex, type, items, onItemUpdated]);
}, [idOrIndex, type, items]);
return [
isValidIdOrIndex(idOrIndex) ? (item as ItemTypeKey[T]) : undefined,
@@ -134,8 +135,9 @@ export const useNoteLocked = (noteId: string | undefined) => {
//@ts-ignore
!globalThis["IS_SHARE_EXTENSION"]
) {
useSettingStore.subscribe((state) => {
let unsub = useSettingStore.subscribe((state) => {
if (!state.isAppLoading) {
unsub();
db.vaults
.itemExists({
type: "note",
@@ -181,25 +183,27 @@ export const useTotalNotes = (type: "notebook" | "tag" | "color") => {
const [totalNotesById, setTotalNotesById] = useState<{
[id: string]: number;
}>({});
const totalNotesRef = useRef(0);
const getTotalNotes = React.useCallback(
(ids: string[]) => {
if (!ids || !ids.length || !type) return;
db.relations
.from({ type: type, ids: ids as string[] }, ["note"])
.get()
.then((relations) => {
const totalNotesById: any = {};
for (const id of ids) {
totalNotesById[id] = relations.filter(
(relation) => relation.fromId === id && relation.toType === "note"
)?.length;
}
setTotalNotesById(totalNotesById);
});
},
[type]
);
const getTotalNotes = React.useCallback((ids: string[]) => {
if (!ids || !ids.length || !type) return;
db.relations
.from({ type: type, ids: ids as string[] }, ["note"])
.get()
.then((relations) => {
const totalNotesById: any = {};
for (const id of ids) {
totalNotesById[id] = relations.filter(
(relation) => relation.fromId === id && relation.toType === "note"
)?.length;
}
if (totalNotesById === totalNotesRef.current) return;
totalNotesRef.current = totalNotesById;
setTotalNotesById(totalNotesById);
});
}, []);
return {
totalNotes: (id: string) => {

View File

@@ -276,8 +276,8 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
(product as Plan).period === "yearly"
? (product as Plan).price.gross
: (product as Plan).period === "5-year"
? (product as Plan).price.gross
: (product as Plan).price.gross
? (product as Plan).price.gross
: (product as Plan).price.gross
}`;
}
@@ -432,8 +432,8 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
return period.endsWith("W")
? "week"
: period.endsWith("M")
? "month"
: "year";
? "month"
: "year";
} else {
const unit = (product as RNIap.SubscriptionIOS)
?.subscriptionPeriodUnitIOS;
@@ -475,8 +475,8 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
type: phase.billingPeriod.endsWith("W")
? "week"
: phase.billingPeriod.endsWith("M")
? "month"
: "year"
? "month"
: "year"
};
} else {
const productIos = product as RNIap.SubscriptionIOS;
@@ -607,8 +607,8 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
(product as Plan).period === "yearly"
? ((product as Plan).price.gross / 12).toFixed(2)
: (product as Plan).period === "5-year"
? ((product as Plan).price.gross / (12 * 5)).toFixed(2)
: (product as Plan).price.gross
? ((product as Plan).price.gross / (12 * 5)).toFixed(2)
: (product as Plan).price.gross
}`;
}

View File

@@ -21,7 +21,6 @@ import { useEffect } from "react";
import { NativeEventEmitter, NativeModule } from "react-native";
import { useRef } from "react";
import { Platform } from "react-native";
import { Linking } from "react-native";
import deviceInfoModule from "react-native-device-info";
import { strings } from "@notesnook/intl";
const ShortcutsEmitter = new NativeEventEmitter(
@@ -54,14 +53,9 @@ export const useShortcutManager = ({
}, [shortcuts]);
useEffect(() => {
Linking.getInitialURL().then((url) => {
if (url?.startsWith("ShareMedia://QuickNoteWidget")) {
onShortcutPressed(defaultShortcuts[0]);
}
});
if (!isSupported()) return;
Shortcuts.getInitialShortcut().then((shortcut) => {
if (initialShortcutRecieved.current) return;
if (initialShortcutRecieved.current || !shortcut) return;
onShortcutPressed(shortcut);
initialShortcutRecieved.current = true;
});

View File

@@ -47,12 +47,7 @@ import useGlobalSafeAreaInsets from "../hooks/use-global-safe-area-insets";
import { useShortcutManager } from "../hooks/use-shortcut-manager";
import { hideAllTooltips } from "../hooks/use-tooltip";
import { useTabStore } from "../screens/editor/tiptap/use-tab-store";
import {
clearAppState,
editorController,
editorState,
getAppState
} from "../screens/editor/tiptap/utils";
import { editorController, editorState } from "../screens/editor/tiptap/utils";
import { DDS } from "../services/device-detection";
import {
eSendEvent,
@@ -118,13 +113,9 @@ export const FluidPanelsView = React.memo(
useShortcutManager({
onShortcutPressed: async (item) => {
if (!item && getAppState()) {
editorState().movedAway = false;
fluidTabsRef.current?.goToPage("editor", false);
return;
}
if (!item) return;
if (item?.type === "notesnook.action.newnote") {
clearAppState();
if (!fluidTabsRef.current) {
setTimeout(() => {
eSendEvent(eOnLoadNote, { newNote: true });
@@ -205,7 +196,6 @@ export const FluidPanelsView = React.memo(
eSendEvent(eCloseFullscreenEditor, current);
}
const state = getAppState();
setTimeout(() => {
switch (current) {
case "tablet":
@@ -217,18 +207,10 @@ export const FluidPanelsView = React.memo(
}
break;
case "mobile":
if (
state &&
editorState().movedAway === false &&
useTabStore.getState().getCurrentNoteId()
) {
fluidTabsRef.current?.goToPage("editor", false);
} else {
fluidTabsRef.current?.goToPage(
fluidTabsRef.current?.page(),
false
);
}
fluidTabsRef.current?.goToPage(
fluidTabsRef.current?.page(),
false
);
break;
}
}, 0);

View File

@@ -400,45 +400,17 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
recurringMode === RecurringModes.Year
? null
: recurringMode === RecurringModes.Week
? (weekFormat === "Mon" ? WeekDaysMon : WeekDays).map(
(item) => (
<Button
key={strings.weekDayNamesShort[
item as keyof typeof strings.weekDayNamesShort
]()}
title={strings.weekDayNamesShort[
item as keyof typeof strings.weekDayNamesShort
]()}
type={
selectedDays.indexOf(item) > -1
? "selected"
: "plain"
}
fontSize={AppFontSize.xs}
style={{
height: 40,
borderRadius: 100,
marginRight: 10
}}
onPress={() => {
setSelectedDays((days) => {
if (days.indexOf(item) > -1) {
days.splice(days.indexOf(item), 1);
return [...days];
}
days.push(item);
return [...days];
});
}}
/>
)
)
: MonthDays.map((item, index) => (
? (weekFormat === "Mon" ? WeekDaysMon : WeekDays).map(
(item) => (
<Button
key={index + "monthday"}
title={index + 1 + ""}
key={strings.weekDayNamesShort[
item as keyof typeof strings.weekDayNamesShort
]()}
title={strings.weekDayNamesShort[
item as keyof typeof strings.weekDayNamesShort
]()}
type={
selectedDays.indexOf(index + 1) > -1
selectedDays.indexOf(item) > -1
? "selected"
: "plain"
}
@@ -450,16 +422,44 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
}}
onPress={() => {
setSelectedDays((days) => {
if (days.indexOf(index + 1) > -1) {
days.splice(days.indexOf(index + 1), 1);
if (days.indexOf(item) > -1) {
days.splice(days.indexOf(item), 1);
return [...days];
}
days.push(index + 1);
days.push(item);
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}
@@ -542,24 +542,24 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
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")
)}
? 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>
@@ -587,8 +587,8 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
mode === "Silent"
? "minus-circle"
: mode === "Vibrate"
? "vibrate"
: "volume-high"
? "vibrate"
: "volume-high"
}
fontSize={AppFontSize.xs}
height={35}

View File

@@ -90,6 +90,7 @@ const file = async (fileOptions: PickerOptions) => {
message: featureResult.error,
type: "error"
});
return;
}
if (fileCopyUri.status === "error") {
@@ -272,6 +273,7 @@ const handleImageResponse = async (
message: featureResult.error,
type: "error"
});
return;
}
const b64 = `data:${image.mime};base64, ` + image.data;

View File

@@ -559,7 +559,7 @@ export const useEditorEvents = (
.then((data: any) => {
console.log(
"Got attachment data:",
data,
!!data,
editorMessage.resolverId
);
editor.postMessage(NativeEvents.resolve, {

View File

@@ -69,14 +69,7 @@ import Commands from "./commands";
import { SessionHistory } from "./session-history";
import { EditorState, SavePayload } from "./types";
import { TabSessionItem, syncTabs, useTabStore } from "./use-tab-store";
import {
clearAppState,
defaultState,
getAppState,
isContentInvalid,
isEditorLoaded,
post
} from "./utils";
import { defaultState, isContentInvalid, isEditorLoaded, post } from "./utils";
import { Linking } from "react-native";
const loadNoteMutex = new Mutex();
@@ -167,7 +160,7 @@ export const useEditor = (
const tags = useTagStore((state) => state.items);
const insets = useGlobalSafeAreaInsets();
const isDefaultEditor = editorId === "";
const saveCount = useRef(0);
const saveCount = useRef<Record<string, number>>({});
const lastContentChangeTime = useRef<Record<string, number>>({});
const lock = useRef(false);
const currentLoadingNoteId = useRef<string>(undefined);
@@ -253,9 +246,9 @@ export const useEditor = (
clearTimeout(timers.current["loading-images" + noteId]);
}
saveCount.current = 0;
currentLoadingNoteId.current = undefined;
lock.current = false;
saveCount.current[tabId] = 0;
resetContent && postMessage(NativeEvents.title, "", tabId);
resetContent && (await commands.clearContent(tabId));
resetContent && (await commands.clearTags(tabId));
@@ -388,7 +381,7 @@ export const useEditor = (
if (
oldNote?.title !== currentNotes.current[id]?.title &&
!noteData.title
typeof noteData.title === "undefined"
) {
postMessage(
NativeEvents.title,
@@ -436,10 +429,9 @@ export const useEditor = (
lastContentChangeTime.current[id] = note.dateEdited;
if (
saveCount.current < 2 ||
saveCount.current[tabId] < 2 ||
currentNotes.current[id]?.title !== note.title ||
currentNotes.current[id]?.headline?.slice(0, 200) !==
note.headline?.slice(0, 200)
currentNotes.current[id]?.headline !== note.headline
) {
Navigation.queueRoutesForUpdate();
}
@@ -455,7 +447,11 @@ export const useEditor = (
currentNotes.current[id] = note;
}
saveCount.current++;
if (!saveCount.current[tabId]) {
saveCount.current[tabId] = 1;
} else {
saveCount.current[tabId]++;
}
clearTimeout(timers.current.onsave);
timers.current.onsave = setTimeout(async () => {
@@ -487,6 +483,17 @@ export const useEditor = (
};
} else if (note.contentId) {
const rawContent = await db.content?.get(note.contentId);
if (rawContent && isEncryptedContent(rawContent)) {
useTabStore.getState().updateTab(useTabStore.getState().currentTab, {
session: {
noteLocked: true,
locked: true
}
});
return;
}
if (rawContent && !isDeleted(rawContent) && !rawContent.locked) {
currentContents.current[note.id] = {
data: rawContent.data,
@@ -553,6 +560,7 @@ export const useEditor = (
useTabStore.getState().newTabSession(tabId, {});
}
}
saveCount.current[tabId] = 0;
setTimeout(() => {
if (state.current?.ready && !state.current.movedAway)
@@ -685,6 +693,7 @@ export const useEditor = (
currentNotes.current[item.id] = item;
editorSessionHistory.newSession(item.id);
saveCount.current[tabId] = 0;
await commands.setStatus(
getFormattedDate(item.dateEdited, "date-time"),
@@ -1014,22 +1023,11 @@ export const useEditor = (
);
const restoreEditorState = useCallback(async () => {
const appState = getAppState();
if (!appState) return;
state.current.isRestoringState = true;
state.current.currentlyEditing = true;
if (fluidTabsRef.current?.page() === "editor") {
state.current.movedAway = false;
}
if (!state.current.editorStateRestored) {
state.current.isRestoringState = true;
if (!DDS.isTab) {
fluidTabsRef.current?.goToPage("editor", false);
}
}
clearAppState();
state.current.isRestoringState = false;
}, []);
const onContentChanged = (noteId?: string) => {

View File

@@ -141,34 +141,6 @@ export function isContentInvalid(content: string | undefined) {
);
}
const canRestoreAppState = (appState: AppState) => {
return appState.editing && Date.now() < appState.timestamp + 3600000;
};
let appState: AppState | undefined;
export function setAppState(state: AppState) {
appState = state;
}
export function getAppState() {
if (appState && canRestoreAppState(appState)) return appState as AppState;
const json = NotesnookModule.getAppState();
if (json) {
appState = JSON.parse(json) as AppState;
if (canRestoreAppState(appState)) {
return appState;
} else {
clearAppState();
return null;
}
}
return null;
}
export function clearAppState() {
appState = undefined;
NotesnookModule.setAppState("");
}
export async function openInternalLink(url: string) {
const data = parseInternalLink(url);
if (!data?.id) return false;

View File

@@ -237,8 +237,9 @@ const LinkNotebooks = (props: NavigationProps<"LinkNotebooks">) => {
button={{
icon: "plus",
onPress: async () => {
const notebooksFeature =
await isFeatureAvailable("notebooks");
const notebooksFeature = await isFeatureAvailable(
"notebooks"
);
if (!notebooksFeature.isAllowed) {
ToastManager.show({
message: notebooksFeature.error,
@@ -387,8 +388,8 @@ const NotebookItemWrapper = React.memo(
!selected
? "selected"
: !state.initialState[item.notebook.id]
? undefined
: "deselected"
? undefined
: "deselected"
);
}}
canDisableSelectionMode={false}

View File

@@ -417,15 +417,15 @@ const TagItem = ({
selection === "selected"
? "check-circle-outline"
: selection === "intermediate"
? "minus-circle-outline"
: "checkbox-blank-circle-outline"
? "minus-circle-outline"
: "checkbox-blank-circle-outline"
}
name={
selection === "selected"
? "check-circle-outline"
: selection === "intermediate"
? "minus-circle-outline"
: "checkbox-blank-circle-outline"
? "minus-circle-outline"
: "checkbox-blank-circle-outline"
}
/>
)}

View File

@@ -242,8 +242,9 @@ export const MoveNotebook = (props: NavigationProps<"MoveNotebook">) => {
button={{
icon: "plus",
onPress: async () => {
const notebooksFeature =
await isFeatureAvailable("notebooks");
const notebooksFeature = await isFeatureAvailable(
"notebooks"
);
if (!notebooksFeature.isAllowed) {
ToastManager.show({
message: notebooksFeature.error,

View File

@@ -322,8 +322,8 @@ export const MFASetup = ({
method?.id === "email"
? user?.email
: method?.id === "app"
? authenticatorDetails?.sharedKey || ""
: undefined
? authenticatorDetails?.sharedKey || ""
: undefined
}
multiline={method.id === "app"}
onChangeText={(value) => {
@@ -356,12 +356,12 @@ export const MFASetup = ({
sending
? null
: method.id === "app"
? strings.copy()
: `${
seconds
? strings.resendCode(seconds as number)
: strings.sendCode()
}`
? strings.copy()
: `${
seconds
? strings.resendCode(seconds as number)
: strings.sendCode()
}`
}
/>
)

View File

@@ -75,15 +75,15 @@ export default function DebugLogs() {
item.level === LogLevel.Error || item.level === LogLevel.Fatal
? hexToRGBA(colors.error.paragraph, 0.2)
: item.level === LogLevel.Warn
? hexToRGBA(colors.static.orange, 0.2)
: "transparent";
? hexToRGBA(colors.static.orange, 0.2)
: "transparent";
const color =
item.level === LogLevel.Error || item.level === LogLevel.Fatal
? colors.error.paragraph
: item.level === LogLevel.Warn
? colors.static.black
: colors.primary.paragraph;
? colors.static.black
: colors.primary.paragraph;
return !item ? null : (
<TouchableOpacity

View File

@@ -24,7 +24,6 @@ import Animated, { FadeInDown, FadeOutDown } from "react-native-reanimated";
import { Button } from "../../../components/ui/button";
import { Notice } from "../../../components/ui/notice";
import Paragraph from "../../../components/ui/typography/paragraph";
import PremiumService from "../../../services/premium";
import { useThemeColors } from "@notesnook/theme";
import { AppFontSize } from "../../../utils/size";
import { Group } from "./group";
@@ -41,7 +40,7 @@ export const ConfigureToolbar = () => {
const renderGroups = () => {
return data?.map((item, index) => (
<Group key={`group-${index}`} item={item} index={index} />
<Group key={`group-${index}-${item.length}`} item={item} index={index} />
));
};
@@ -131,6 +130,7 @@ export const ConfigureToolbar = () => {
style={{
flex: 1
}}
key={preset}
scrollEventThrottle={13}
showsVerticalScrollIndicator={false}
>

View File

@@ -31,7 +31,7 @@ import { renderTool } from "./common";
import { DraggableItem, useDragState } from "./state";
import ToolSheet from "./tool-sheet";
import { useIsFeatureAvailable } from "@notesnook/common";
import { isFeatureAvailable, useIsFeatureAvailable } from "@notesnook/common";
import type { ToolId } from "@notesnook/editor";
import { strings } from "@notesnook/intl";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
@@ -64,78 +64,87 @@ export const Group = ({
const { colors } = useThemeColors();
const featureAvailable = useIsFeatureAvailable("customToolbarPreset");
const onDrop = (data: DraxDragWithReceiverEventData) => {
if (!featureAvailable?.isAllowed) {
ToastManager.show({
type: "info",
message: featureAvailable?.error
});
return;
}
const isDroppedAbove = data.receiver.receiveOffsetRatio.y < 0.5;
const dragged = data.dragged.payload;
const reciever = data.receiver.payload;
const _data = useDragState.getState().data.slice();
if (dragged.type === "group") {
const fromIndex = dragged.index;
const toIndex = isDroppedAbove
? Math.max(0, reciever.index)
: reciever.index + 1;
_data.splice(
toIndex > fromIndex ? toIndex - 1 : toIndex,
0,
_data.splice(fromIndex, 1)[0]
);
}
// Always insert sub group at the end of the group.
if (dragged.type === "subgroup") {
const fromIndex = dragged.index;
const insertAt = _data[reciever.index] as string[];
const insertFrom = _data[dragged.groupIndex] as string[];
if (typeof insertAt[insertAt.length - 1] !== "string") {
setRecieving(false);
return data.dragAbsolutePosition;
const onDrop = React.useCallback(
(data: DraxDragWithReceiverEventData) => {
if (!featureAvailable?.isAllowed) {
ToastManager.show({
type: "info",
message: featureAvailable?.error || strings.featureNotAvailable()
});
return;
}
insertAt.push(insertFrom.splice(fromIndex, 1)[0]);
}
const isDroppedAbove = data.receiver.receiveOffsetRatio.y < 0.5;
const dragged = data.dragged.payload;
const reciever = data.receiver.payload;
const _data = useDragState.getState().data.slice();
if (dragged.type === "tool") {
const insertFrom =
typeof dragged.parentIndex === "number"
? (_data[dragged.parentIndex][dragged.groupIndex] as string[])
: (_data[dragged.groupIndex] as string[]);
_data[groupIndex].push(insertFrom.splice(dragged.index, 1)[0] as ToolId);
}
if (dragged.type === "group") {
const fromIndex = dragged.index;
const toIndex = isDroppedAbove
? Math.max(0, reciever.index)
: reciever.index + 1;
setData(_data);
setRecieving(false);
return data.dragAbsolutePosition;
};
_data.splice(
toIndex > fromIndex ? toIndex - 1 : toIndex,
0,
_data.splice(fromIndex, 1)[0]
);
}
const onRecieveData = (data: DraxDragWithReceiverEventData) => {
setRecieving(true);
if (data.dragged.payload.type !== "group")
return setRecievePosition("below");
if (data.receiver.receiveOffsetRatio.y < 0.5) {
setRecievePosition("above");
} else {
setRecievePosition("below");
}
};
// Always insert sub group at the end of the group.
if (dragged.type === "subgroup") {
const fromIndex = dragged.index;
const insertAt = _data[reciever.index] as string[];
const insertFrom = _data[dragged.groupIndex] as string[];
if (typeof insertAt[insertAt.length - 1] !== "string") {
setRecieving(false);
return data.dragAbsolutePosition;
}
insertAt.push(insertFrom.splice(fromIndex, 1)[0]);
}
if (dragged.type === "tool") {
const insertFrom =
typeof dragged.parentIndex === "number"
? (_data[dragged.parentIndex][dragged.groupIndex] as string[])
: (_data[dragged.groupIndex] as string[]);
_data[groupIndex].push(
insertFrom.splice(dragged.index, 1)[0] as ToolId
);
}
setData(_data);
setRecieving(false);
return data.dragAbsolutePosition;
},
[featureAvailable]
);
const onRecieveData = React.useCallback(
(data: DraxDragWithReceiverEventData) => {
setRecieving(true);
if (data.dragged.payload.type !== "group")
return setRecievePosition("below");
if (data.receiver.receiveOffsetRatio.y < 0.5) {
setRecievePosition("above");
} else {
setRecievePosition("below");
}
},
[]
);
const buttons = [
{
name: "minus",
onPress: () => {
if (!featureAvailable?.isAllowed) {
onPress: async () => {
const feature = await isFeatureAvailable("customToolbarPreset");
if (!feature.isAllowed) {
ToastManager.show({
type: "info",
message: featureAvailable?.error
message: feature?.error
});
return;
}
@@ -157,11 +166,12 @@ export const Group = ({
},
{
name: "plus",
onPress: () => {
if (!featureAvailable?.isAllowed) {
onPress: async () => {
const feature = await isFeatureAvailable("customToolbarPreset");
if (!feature.isAllowed) {
ToastManager.show({
type: "info",
message: featureAvailable?.error
message: feature?.error
});
return;
}

View File

@@ -44,8 +44,8 @@ export default function ToolSheet({
fwdRef: RefObject<ActionSheetRef>;
}) {
const { colors } = useThemeColors();
const data = useDragState((state) => state.data);
const ungrouped = getUngroupedTools(data) as ToolId[];
const [data] = useDragState((state) => [state.data]);
const ungrouped = getUngroupedTools( data) as ToolId[];
const renderTool = React.useCallback(
(item: ToolId) => {
@@ -114,7 +114,7 @@ export default function ToolSheet({
return (
<View
style={{
maxHeight: "100%",
maxHeight: 400,
padding: DefaultAppStyles.GAP
}}
>

View File

@@ -33,7 +33,7 @@ import { DraggableItem, useDragState } from "./state";
import ToolSheet from "./tool-sheet";
import { findToolById, getToolIcon } from "./toolbar-definition";
import { useIsFeatureAvailable } from "@notesnook/common";
import { isFeatureAvailable, useIsFeatureAvailable } from "@notesnook/common";
import type { ToolId } from "@notesnook/editor";
import { strings } from "@notesnook/intl";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
@@ -78,11 +78,12 @@ export const Tool = ({
? [
{
name: "minus",
onPress: () => {
if (!featureAvailable?.isAllowed) {
onPress: async () => {
const feature = await isFeatureAvailable("customToolbarPreset");
if (!feature.isAllowed) {
ToastManager.show({
type: "info",
message: featureAvailable?.error
message: feature?.error
});
return;
}
@@ -102,11 +103,12 @@ export const Tool = ({
},
{
name: "plus",
onPress: () => {
if (!featureAvailable?.isAllowed) {
onPress: async () => {
const feature = await isFeatureAvailable("customToolbarPreset");
if (!feature.isAllowed) {
ToastManager.show({
type: "info",
message: featureAvailable?.error
message: feature?.error
});
return;
}
@@ -122,11 +124,12 @@ export const Tool = ({
: [
{
name: "minus",
onPress: () => {
if (!featureAvailable?.isAllowed) {
onPress: async () => {
const feature = await isFeatureAvailable("customToolbarPreset");
if (!feature.isAllowed) {
ToastManager.show({
type: "info",
message: featureAvailable?.error
message: feature?.error
});
return;
}
@@ -151,7 +154,7 @@ export const Tool = ({
if (parentIndex === undefined && !isSubgroup) {
btns.unshift({
name: "unfold-less-horizontal",
onPress: () => {
onPress: async () => {
if (groupIndex === undefined) return;
const _data = useDragState.getState().data.slice();
const hasSubGroup = Array.isArray(
@@ -267,6 +270,7 @@ export const Tool = ({
style={{
paddingLeft: 30
}}
key={`subgroup-${item.length}-${groupIndex}-${index}-${parentIndex}`}
>
{renderGroup({ index, item, parentIndex: groupIndex })}
</View>
@@ -290,63 +294,69 @@ export const Tool = ({
]
);
const onDrop = (data: DraxDragWithReceiverEventData) => {
if (!featureAvailable?.isAllowed) {
ToastManager.show({
type: "info",
message: featureAvailable?.error
});
return;
}
const isDroppedAbove = data.receiver.receiveOffsetRatio.y < 0.5;
const dragged = data.dragged.payload;
const reciever = data.receiver.payload;
const _data = useDragState.getState().data?.slice();
if (!_data) return;
const isFromSubgroup = typeof dragged?.parentIndex === "number";
const isDroppedAtSubgroup = typeof reciever?.parentIndex === "number";
if (dragged.type === "tool") {
const fromIndex = dragged.index;
const toIndex = isDroppedAbove
? Math.max(0, reciever.index)
: reciever.index + 1;
const insertAt = isDroppedAtSubgroup
? (_data[reciever.parentIndex][reciever.groupIndex] as string[])
: (_data[reciever.groupIndex] as string[]);
const insertFrom = isFromSubgroup
? (_data[dragged.parentIndex][dragged.groupIndex] as string[])
: (_data[dragged.groupIndex] as string[]);
insertAt.splice(
toIndex > fromIndex ? toIndex - 1 : toIndex,
0,
insertFrom.splice(fromIndex, 1)[0]
);
// Remove the group or subgroup if it is empty.
if (insertFrom.length === 0) {
isFromSubgroup
? _data[dragged.parentIndex].splice(
_data[dragged.parentIndex].length - 1,
1
)
: _data.splice(dragged.groupIndex, 1);
const onDrop = React.useCallback(
(data: DraxDragWithReceiverEventData) => {
if (!featureAvailable?.isAllowed) {
ToastManager.show({
type: "info",
message: featureAvailable?.error || strings.featureNotAvailable()
});
return;
}
}
setData(_data);
setRecieving(false);
return data.dragAbsolutePosition;
};
const isDroppedAbove = data.receiver.receiveOffsetRatio.y < 0.5;
const dragged = data.dragged.payload;
const reciever = data.receiver.payload;
const _data = useDragState.getState().data?.slice();
if (!_data) return;
const isFromSubgroup = typeof dragged?.parentIndex === "number";
const isDroppedAtSubgroup = typeof reciever?.parentIndex === "number";
const onRecieveData = (data: DraxDragWithReceiverEventData) => {
setRecieving(true);
if (data.receiver.receiveOffsetRatio.y < 0.5) {
setRecievePosition("above");
} else {
setRecievePosition("below");
}
};
if (dragged.type === "tool") {
const fromIndex = dragged.index;
const toIndex = isDroppedAbove
? Math.max(0, reciever.index)
: reciever.index + 1;
const insertAt = isDroppedAtSubgroup
? (_data[reciever.parentIndex][reciever.groupIndex] as string[])
: (_data[reciever.groupIndex] as string[]);
const insertFrom = isFromSubgroup
? (_data[dragged.parentIndex][dragged.groupIndex] as string[])
: (_data[dragged.groupIndex] as string[]);
insertAt.splice(
toIndex > fromIndex ? toIndex - 1 : toIndex,
0,
insertFrom.splice(fromIndex, 1)[0]
);
// Remove the group or subgroup if it is empty.
if (insertFrom.length === 0) {
isFromSubgroup
? _data[dragged.parentIndex].splice(
_data[dragged.parentIndex].length - 1,
1
)
: _data.splice(dragged.groupIndex, 1);
}
}
setData(_data);
setRecieving(false);
return data.dragAbsolutePosition;
},
[featureAvailable]
);
const onRecieveData = React.useCallback(
(data: DraxDragWithReceiverEventData) => {
setRecieving(true);
if (data.receiver.receiveOffsetRatio.y < 0.5) {
setRecievePosition("above");
} else {
setRecievePosition("below");
}
},
[]
);
return (
<Animated.View layout={Layout}>

View File

@@ -25,18 +25,18 @@ import {
} from "@notesnook/editor/dist/cjs/toolbar/tool-definitions";
import { ToolId } from "@notesnook/editor";
export const tools = getAllTools() as any;
export const tools = () => getAllTools();
export const presets: { [name: string]: ToolbarGroupDefinition[] } = {
default: getDefaultPresets().default as any,
minimal: getDefaultPresets().minimal as any,
custom: []
};
export function findToolById(id: keyof typeof tools): {
export function findToolById(id: keyof ReturnType<typeof tools> ): {
title: string;
icon: string;
} {
return tools[id];
return tools()[id];
}
export function getToolIcon(id: ToolId, color: string) {
@@ -51,13 +51,14 @@ export function getToolIcon(id: ToolId, color: string) {
export function getUngroupedTools(
toolDefinition: (string | string[])[][]
): string[] {
const keys = Object.keys(tools);
): string[] {
const allTools = tools();
const keys = Object.keys(allTools);
const ungrouped = [];
const toolString = JSON.stringify(toolDefinition);
for (const key of keys) {
if (tools[key as ToolId].conditional) continue;
if ((allTools[key as ToolId] as any).conditional) continue;
if (!toolString.includes(key)) ungrouped.push(key);
}

View File

@@ -123,8 +123,8 @@ export const TrashIntervalPicker = createSettingsPicker({
return item === -1
? strings.never()
: item === 1
? strings.reminderRecurringMode.day()
: strings.days(item);
? strings.reminderRecurringMode.day()
: strings.days(item);
},
getItemKey: (item) => item.toString(),
options: [-1, 1, 7, 30, 365],
@@ -276,10 +276,10 @@ export const ApplockTimerPicker = createSettingsPicker({
return item === -1
? strings.never()
: item === 0 || item === undefined
? strings.immediately()
: item === 1
? strings.minutes(1)
: strings.minutes(item);
? strings.immediately()
: item === 1
? strings.minutes(1)
: strings.minutes(item);
},
getItemKey: (item) => item.toString(),
options: [-1, 0, 1, 5, 15, 30],

View File

@@ -570,13 +570,17 @@ const BackupItem = ({
paragraph: strings.restoreBackupConfirm(),
positiveText: strings.restore(),
negativeText: strings.cancel(),
context: "global",
positivePress: async () => {
restoreBackup({
uri:
Platform.OS === "android"
? (item as ScopedStorage.FileType).uri
: (item as ReactNativeBlobUtilStat).path
});
setTimeout(() => {
restoreBackup({
uri:
Platform.OS === "android"
? (item as ScopedStorage.FileType).uri
: (item as ReactNativeBlobUtilStat).path
});
}, 500);
return true;
}
});
}}

View File

@@ -25,7 +25,6 @@ import {
} from "@react-navigation/native";
import React, { useEffect, useRef, useState } from "react";
import { ActivityIndicator, TextInput, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { FeatureResult, useIsFeatureAvailable } from "@notesnook/common";
//@ts-ignore
import ToggleSwitch from "toggle-switch-react-native";
@@ -218,14 +217,15 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
}}
>
{!!item.icon && (
<Icon
<AppIcon
color={
item.type === "danger"
? colors.error.icon
: colors.secondary.icon
}
iconFamily={item.iconFamily}
name={item.icon}
size={30}
size={item.iconSize || 30}
/>
)}
</View>

View File

@@ -92,6 +92,7 @@ export const settingsGroups: SettingSection[] = [
{
id: "delete-data",
name: strings.deleteData(),
icon: "delete",
description: strings.deleteAccountDesc(),
modifer: () => {
presentDialog({
@@ -205,15 +206,15 @@ export const settingsGroups: SettingSection[] = [
return status === SubscriptionStatus.TRIAL
? strings.trialOnGoing(trialEndDate)
: status === SubscriptionStatus.ACTIVE
? strings.subRenewOn(expiryDate)
: status === SubscriptionStatus.CANCELED ||
status === SubscriptionStatus.PAUSED
? strings.subEndsOn(expiryDate)
: status === SubscriptionStatus.EXPIRED
? subscriptionDaysLeft.time < -3
? strings.subEnded()
: strings.accountDowngradedIn(3)
: strings.neverHesitate();
? strings.subRenewOn(expiryDate)
: status === SubscriptionStatus.CANCELED ||
status === SubscriptionStatus.PAUSED
? strings.subEndsOn(expiryDate)
: status === SubscriptionStatus.EXPIRED
? subscriptionDaysLeft.time < -3
? strings.subEnded()
: strings.accountDowngradedIn(3)
: strings.neverHesitate();
}
return strings.neverHesitate();
@@ -1243,7 +1244,7 @@ export const settingsGroups: SettingSection[] = [
}
},
{
id: "backup-now",
id: "backup-now-with-attachments",
name: strings.backupNowWithAttachments(),
description: strings.backupNowWithAttachmentsDesc(),
hidden: () => !useUserStore.getState().user,
@@ -1545,6 +1546,9 @@ export const settingsGroups: SettingSection[] = [
id: "join-telegram",
name: strings.joinTelegram(),
description: strings.joinTelegramDesc(),
icon: "sc-telegram",
iconFamily: "evilicons",
iconSize: 35,
modifer: () => {
Linking.openURL("https://t.me/notesnook").catch(() => {
/* empty */

View File

@@ -20,6 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { TextInput } from "react-native";
import { Settings } from "../../stores/use-setting-store";
import { FeatureId } from "@notesnook/common";
import { IconProps } from "../../components/ui/AppIcon";
export type SettingSection = {
id: string;
@@ -34,6 +35,8 @@ export type SettingSection = {
name?: string | ((current?: unknown) => string);
description?: string | ((current: unknown) => string);
icon?: string;
iconFamily?: IconProps["iconFamily"];
iconSize?: number;
property?: keyof Settings;
sections?: SettingSection[];
component?: string;

View File

@@ -319,9 +319,8 @@ async function run(
};
}
await sleep(300);
if (canShowCompletionStatus) {
await sleep(500);
presentBackupCompleteSheet(path);
}

View File

@@ -18,7 +18,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Platform } from "react-native";
import { db } from "../common/database";
import { setAppState } from "../screens/editor/tiptap/utils";
import { eOnLoadNote } from "../utils/events";
import { NotesnookModule } from "../utils/notesnook-module";
import { eSendEvent } from "./event-manager";
@@ -37,14 +36,6 @@ export const IntentService = {
onLaunch() {
if (launched || Platform.OS === "ios") return;
launched = true;
if (launchIntent["com.streetwriters.notesnook.OpenNoteId"]) {
setAppState({
movedAway: false,
editing: true,
timestamp: Date.now(),
noteId: launchIntent["com.streetwriters.notesnook.OpenNoteId"]
});
}
},
async onAppStateChanged() {
if (Platform.OS === "ios") return;

View File

@@ -146,7 +146,7 @@ const updateAvailableMessage = (
icon: "update",
type: "normal",
id: "app-update"
}) as Message;
} as Message);
export function setUpdateAvailableMessage(
version: GithubVersionInfo | CheckVersionResponse

View File

@@ -138,7 +138,6 @@ const onEvent = async ({ type, detail }: Event) => {
if (type === EventType.PRESS) {
notifee.decrementBadgeCount();
if (notification?.data?.type === "quickNote") return;
NotesnookModule.setAppState("");
if (notification?.data?.type === "reminder" && notification?.id) {
const reminder = await db.reminders?.reminder(
notification.id?.split("_")[0]
@@ -453,13 +452,6 @@ async function loadNote(id: string, jump: boolean) {
if (!DDS.isTab && jump) {
fluidTabsRef.current?.goToPage("editor");
}
NotesnookModule.setAppState(
JSON.stringify({
editing: true,
movedAway: false,
timestamp: Date.now()
})
);
const tab = useTabStore.getState().getTabForNote(id);
if (useTabStore.getState().currentTab !== tab) {

View File

@@ -195,8 +195,8 @@ const NotebookItem = ({
!nestedNotebooks?.placeholders.length
? "book-outline"
: isExpanded
? "chevron-down"
: "chevron-right"
? "chevron-down"
: "chevron-right"
}
/>
</TouchableOpacity>
@@ -538,10 +538,10 @@ export const Search = ({
{searchKeyword
? `No results found for "${searchKeyword}"`
: mode === "appendNote"
? "No notes"
: mode === "selectTags"
? "No tags"
: "No notebooks"}
? "No notes"
: mode === "selectTags"
? "No tags"
: "No notebooks"}
</Text>
</View>
}

View File

@@ -71,6 +71,7 @@ import { initDatabase, useShareStore } from "./store";
import { isTablet } from "react-native-device-info";
import { NotesnookModule } from "../utils/notesnook-module";
import { DefaultAppStyles } from "../utils/styles";
import { encodeHTML5 } from "entities";
const getLinkPreview = (url: string) => {
return getPreviewData(url, 5000);
@@ -81,7 +82,7 @@ async function sanitizeHtml(site: string, title?: string) {
return (
html +
"<hr/>" +
`<p>Clipped from <a href="${site}">${title || site}</a></p>
`<p>Clipped from <a href="${site}">${encodeHTML5(title || site)}</a></p>
<p>Date clipped ${getFormattedDate(Date.now())}</p>`
);
} catch (e) {
@@ -96,7 +97,7 @@ function makeHtmlFromUrl(url: string) {
function makeHtmlFromPlainText(text: string) {
if (!text) return "";
return `<p>${text
return `<p>${encodeHTML5(text)
.replace(/[\n]+/g, "\n")
.replace(/(?:\r\n|\r|\n)/g, "</p><p>")}</p>`;
}
@@ -419,8 +420,8 @@ const ShareView = () => {
let html = !rawData.value
? ""
: isURL(rawData?.value)
? makeHtmlFromUrl(rawData?.value)
: makeHtmlFromPlainText(rawData?.value);
? makeHtmlFromUrl(rawData?.value)
: makeHtmlFromPlainText(rawData?.value);
setNote((note) => {
note.content.data = html;
noteContent.current = html;

View File

@@ -76,18 +76,14 @@ export const useThemeStore = create<ThemeStore>((set, get) => ({
? (Appearance.getColorScheme() as "dark" | "light")
: SettingsService.get().colorScheme,
setDarkTheme: (darkTheme) => {
switchThemeWithAnimation(() => {
set({ darkTheme });
changeSystemBarColors();
SettingsService.setProperty("darkTheme", darkTheme);
});
set({ darkTheme });
changeSystemBarColors();
SettingsService.setProperty("darkTheme", darkTheme);
},
setLightTheme: (lightTheme) => {
switchThemeWithAnimation(() => {
set({ lightTheme });
changeSystemBarColors();
SettingsService.setProperty("lighTheme", lightTheme);
});
set({ lightTheme });
changeSystemBarColors();
SettingsService.setProperty("lighTheme", lightTheme);
},
setColorScheme: (colorScheme) => {
switchThemeWithAnimation(() => {

View File

@@ -46,7 +46,7 @@ export interface UserStore {
appLocked: boolean;
lockApp: (verified: boolean) => void;
disableAppLockRequests: boolean;
setDisableAppLockRequests: (shouldBlockVerifyUser: boolean) => void;
setDisableAppLockRequests: (disableAppLockRequests: boolean) => void;
profile?: Partial<Profile>;
}

View File

@@ -30,8 +30,6 @@ interface NotesnookModuleInterface {
getActivityName: () => Promise<string>;
setBackgroundColor: (color: string) => void;
setSecureMode: (enabled: boolean) => void;
setAppState: (appState: string) => void;
getAppState: () => string;
saveAndFinish: () => void;
setString: (storeName: string, key: string, value: string) => void;
getString: (storeName: string, key: string) => Promise<string>;
@@ -72,8 +70,6 @@ export const NotesnookModule: NotesnookModuleInterface = Platform.select({
getActivityName: () => {},
setBackgroundColor: () => {},
setSecureMode: () => {},
setAppState: () => {},
getAppState: () => {},
saveAndFinish: () => {},
getString: () => {},
setString: () => {},

View File

@@ -159,7 +159,7 @@ function show(event: any, text: string, position = 2) {
duration: 2000,
autoHide: true,
clickToHide: true,
shadow: true
shadow: false
});
}

View File

@@ -21,7 +21,7 @@ import { notesnook } from "../test.ids";
import { TestBuilder } from "./utils";
describe("Sort & filter", () => {
it.only("Sort by date-edited/date-created", async () => {
it("Sort by date-edited/date-created", async () => {
await TestBuilder.create()
.prepare()
.createNote("Note 1", "Note 1")
@@ -34,7 +34,7 @@ describe("Sort & filter", () => {
.element(by.web.className("ProseMirror"))
.typeText("Edited ", true);
})
.pressBack(1)
.pressBack(2)
.waitAndTapById("icon-sort")
.wait(500)
.waitAndTapByText("Date created")

View File

@@ -46,9 +46,10 @@ describe("Tags", () => {
.waitAndTapByText('Add "#testtag"')
.isVisibleByText("#testtag")
.pressBack(2)
.wait(1000)
.openSideMenu()
.waitAndTapById("tab-tags")
.waitAndTapByText("#testtag")
.waitAndTapByText("testtag")
.processResult(async (note) => {
await TestBuilder.create().isVisibleByText(note.body).run();
})

View File

@@ -32,10 +32,7 @@ const testvars = {
class Element {
element: Detox.NativeElement;
constructor(
public type: "id" | "text",
public value: string
) {
constructor(public type: "id" | "text", public value: string) {
if (type == "id") {
this.element = element(by.id(value)).atIndex(0);
} else {

Binary file not shown.

View File

@@ -17,9 +17,10 @@ Notifications.init();
enableFreeze(true);
NetInfo.configure({
reachabilityUrl: "https://notesnook.com",
reachabilityUrl: "https://api.notesnook.com/health",
reachabilityTest: (response) => {
if (!response) return false;
console.log("reachabilty test", response.status);
return response?.status >= 200 && response?.status < 300;
}
});

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>provisioningProfiles</key>
<dict>
<key>org.streetwriters.notesnook</key>
<string>Notesnook Adhoc 2026</string>
<key>org.streetwriters.notesnook.notewidget</key>
<string>Notesnook Note Widget Adhoc 2026</string>
<key>org.streetwriters.notesnook.share</key>
<string>Notesnook Share Adhoc 2026</string>
</dict>
</dict>
</plist>

View File

@@ -34,6 +34,8 @@
65AA857E25E6DDEC00772A01 /* NotesWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65AA857D25E6DDEC00772A01 /* NotesWidget.swift */; };
65AA858025E6DDEE00772A01 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 65AA857F25E6DDEE00772A01 /* Assets.xcassets */; };
65AA858425E6DDEE00772A01 /* NotesWidgetExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 65AA857725E6DDEC00772A01 /* NotesWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
65AD1F4A2F6289110033176F /* EvilIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 65AD1F492F6289110033176F /* EvilIcons.ttf */; };
65AD1F4B2F6289110033176F /* EvilIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 65AD1F492F6289110033176F /* EvilIcons.ttf */; };
65B5014425A672B200E2D264 /* ShareViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 65B5014325A672B200E2D264 /* ShareViewController.m */; };
65B5014725A672B200E2D264 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 65B5014525A672B200E2D264 /* MainInterface.storyboard */; };
65B5014B25A672B200E2D264 /* Make Note.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 65B5014025A672B200E2D264 /* Make Note.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
@@ -129,6 +131,7 @@
65AA857D25E6DDEC00772A01 /* NotesWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotesWidget.swift; sourceTree = "<group>"; };
65AA857F25E6DDEE00772A01 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
65AA858125E6DDEE00772A01 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
65AD1F492F6289110033176F /* EvilIcons.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = EvilIcons.ttf; path = ../fonts/EvilIcons.ttf; sourceTree = SOURCE_ROOT; };
65B5014025A672B200E2D264 /* Make Note.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Make Note.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
65B5014325A672B200E2D264 /* ShareViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ShareViewController.m; sourceTree = "<group>"; };
65B5014625A672B200E2D264 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
@@ -141,6 +144,7 @@
65E03409257B9FF100793428 /* Notesnook-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Notesnook-Bridging-Header.h"; sourceTree = "<group>"; };
65E0340A257B9FF100793428 /* File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = "<group>"; };
65EC5B71272A7EE200FB3748 /* NotesWidgetExtensionDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotesWidgetExtensionDebug.entitlements; sourceTree = "<group>"; };
684AA0A1B1BE6091A02D42FF /* ios-build.active.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "ios-build.active.xcconfig"; path = "build-configs/ios-build.active.xcconfig"; sourceTree = SOURCE_ROOT; };
77124A1C1FB538ED63FDEF82 /* Pods-Notesnook.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Notesnook.release.xcconfig"; path = "Target Support Files/Pods-Notesnook/Pods-Notesnook.release.xcconfig"; sourceTree = "<group>"; };
7D3F6A50D104CE391243515F /* Pods-Notesnook-Add to Notes.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Notesnook-Add to Notes.release.xcconfig"; path = "Target Support Files/Pods-Notesnook-Add to Notes/Pods-Notesnook-Add to Notes.release.xcconfig"; sourceTree = "<group>"; };
88211800FA8BF60638327ADE /* Pods-Make Note.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Make Note.debug.xcconfig"; path = "Target Support Files/Pods-Make Note/Pods-Make Note.debug.xcconfig"; sourceTree = "<group>"; };
@@ -291,6 +295,7 @@
83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup;
children = (
65AD1F492F6289110033176F /* EvilIcons.ttf */,
653F4E042EA3887C009A42A2 /* Inter-Bold.ttf */,
653F4E052EA3887C009A42A2 /* Inter-Italic.ttf */,
653F4E062EA3887C009A42A2 /* Inter-Regular.ttf */,
@@ -308,6 +313,7 @@
2D16E6871FA4F8E400B85C8A /* Frameworks */,
DA84645424CFBF19A44D64B3 /* Pods */,
B5216B66C0B4A91C0A69128B /* PrivacyInfo.xcprivacy */,
684AA0A1B1BE6091A02D42FF /* ios-build.active.xcconfig */,
);
indentWidth = 2;
sourceTree = "<group>";
@@ -536,6 +542,7 @@
buildActionMask = 2147483647;
files = (
659201D82EA78265006B16DB /* Inter-Bold.ttf in Resources */,
65AD1F4B2F6289110033176F /* EvilIcons.ttf in Resources */,
659201D92EA78265006B16DB /* Inter-Italic.ttf in Resources */,
659201DA2EA78265006B16DB /* Inter-Regular.ttf in Resources */,
659201DB2EA78265006B16DB /* Inter-SemiBold.ttf in Resources */,
@@ -581,6 +588,7 @@
659201DE2EA7826D006B16DB /* Inter-Regular.ttf in Resources */,
659201DF2EA7826D006B16DB /* Inter-SemiBold.ttf in Resources */,
65C400DF2A80B6B600AA3DF5 /* MaterialCommunityIcons.ttf in Resources */,
65AD1F4A2F6289110033176F /* EvilIcons.ttf in Resources */,
65C149872A61151B005C40F1 /* extension.bundle in Resources */,
65B5014725A672B200E2D264 /* MainInterface.storyboard in Resources */,
);
@@ -1029,7 +1037,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2174;
CURRENT_PROJECT_VERSION = "$(IOS_CURRENT_PROJECT_VERSION)";
DEVELOPMENT_TEAM = 53CWBG3QUC;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1104,13 +1112,13 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.3.15;
MARKETING_VERSION = "$(IOS_MARKETING_VERSION)";
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook;
PRODUCT_BUNDLE_IDENTIFIER = "$(IOS_MAIN_BUNDLE_ID)";
PRODUCT_NAME = Notesnook;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Notesnook-Bridging-Header.h";
@@ -1135,7 +1143,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2174;
CURRENT_PROJECT_VERSION = "$(IOS_CURRENT_PROJECT_VERSION)";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1210,17 +1218,17 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.3.15;
MARKETING_VERSION = "$(IOS_MARKETING_VERSION)";
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook;
PRODUCT_BUNDLE_IDENTIFIER = "$(IOS_MAIN_BUNDLE_ID)";
PRODUCT_NAME = Notesnook;
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook 2026";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "$(IOS_MAIN_PROFILE)";
SWIFT_OBJC_BRIDGING_HEADER = "Notesnook-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -1367,7 +1375,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2174;
CURRENT_PROJECT_VERSION = "$(IOS_CURRENT_PROJECT_VERSION)";
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1379,10 +1387,10 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.3.15;
MARKETING_VERSION = "$(IOS_MARKETING_VERSION)";
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
PRODUCT_BUNDLE_IDENTIFIER = "$(IOS_WIDGET_BUNDLE_ID)";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
@@ -1410,7 +1418,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2174;
CURRENT_PROJECT_VERSION = "$(IOS_CURRENT_PROJECT_VERSION)";
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1423,12 +1431,12 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.3.15;
MARKETING_VERSION = "$(IOS_MARKETING_VERSION)";
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
PRODUCT_BUNDLE_IDENTIFIER = "$(IOS_WIDGET_BUNDLE_ID)";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook Note Widget 2026";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "$(IOS_WIDGET_PROFILE)";
SKIP_INSTALL = YES;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
@@ -1453,7 +1461,7 @@
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2174;
CURRENT_PROJECT_VERSION = "$(IOS_CURRENT_PROJECT_VERSION)";
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1534,10 +1542,10 @@
"@executable_path/../../Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)";
MARKETING_VERSION = 3.3.15;
MARKETING_VERSION = "$(IOS_MARKETING_VERSION)";
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
PRODUCT_BUNDLE_IDENTIFIER = "$(IOS_SHARE_BUNDLE_ID)";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = "Make Note/Make Note-Bridging-Header.h";
@@ -1565,7 +1573,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2174;
CURRENT_PROJECT_VERSION = "$(IOS_CURRENT_PROJECT_VERSION)";
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1647,12 +1655,12 @@
"@executable_path/../../Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)";
MARKETING_VERSION = 3.3.15;
MARKETING_VERSION = "$(IOS_MARKETING_VERSION)";
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
PRODUCT_BUNDLE_IDENTIFIER = "$(IOS_SHARE_BUNDLE_ID)";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook Share 2026";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "$(IOS_SHARE_PROFILE)";
SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = "Make Note/Make Note-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@@ -1662,6 +1670,7 @@
};
83CBBA201A601CBA00E9B192 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 684AA0A1B1BE6091A02D42FF /* ios-build.active.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APPLICATION_EXTENSION_API_ONLY = YES;
@@ -1737,6 +1746,7 @@
};
83CBBA211A601CBA00E9B192 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 684AA0A1B1BE6091A02D42FF /* ios-build.active.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APPLICATION_EXTENSION_API_ONLY = YES;

View File

@@ -12,7 +12,7 @@
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>Notesnook</string>
<string>$(APP_DISPLAY_NAME)</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
@@ -97,6 +97,7 @@
<string>Inter-Regular.ttf</string>
<string>Inter-Bold.ttf</string>
<string>MaterialCommunityIcons.ttf</string>
<string>EvilIcons.ttf</string>
</array>
<key>UIApplicationSceneManifest</key>
<dict>

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