Compare commits

...

253 Commits

Author SHA1 Message Date
Ammar Ahmed
d2bbccf19e editor: fix paddings 2025-03-06 15:40:31 +05:00
Ammar Ahmed
375dd18618 editor: apply margin-top only to direct children of the editor 2025-03-06 15:18:23 +05:00
Ammar Ahmed
82f635305e editor: fix task item padding 2025-03-06 15:08:05 +05:00
Abdullah Atta
00345176f7 web: make editor note properties work like toc 2025-03-04 12:45:36 +05:00
Abdullah Atta
9032e3b4bd web: slightly improve add note button ui 2025-03-04 11:10:20 +05:00
Abdullah Atta
432a9c4e84 web: bump version to 3.1.0-beta.1 2025-03-03 14:24:09 +05:00
Abdullah Atta
aaaa9ebbc5 desktop: set publish channel to beta for beta builds 2025-03-03 14:23:43 +05:00
Abdullah Atta
1ac4dca6b5 web: minor ui fixes 2025-03-03 14:18:39 +05:00
Abdullah Atta
9dc549d0d3 theme: fix button hover colors 2025-03-03 14:07:39 +05:00
Abdullah Atta
775570fc13 web: make notesnook branding a bit smaller 2025-03-03 13:53:35 +05:00
Abdullah Atta
997f0bc93e web: minor ui changes and fixes 2025-03-03 12:48:50 +05:00
Abdullah Atta
a2016e9406 web: fix theme not persisting 2025-03-03 12:35:32 +05:00
Abdullah Atta
87623aced1 web: collapse sidebar on navigate if sidebar is temporarily expanded 2025-03-03 12:24:02 +05:00
Abdullah Atta
24977277d0 desktop: explicitly deny geolocation permission to electron process 2025-03-03 11:55:48 +05:00
Abdullah Atta
2f890fd86b web: fix scrollbar styling in sidebar 2025-03-03 11:51:45 +05:00
Abdullah Atta
0220bb4040 web: reset search on navigate 2025-03-03 11:23:46 +05:00
Abdullah Atta
09a8a0ba6d web: fix expand sidebar button not working 2025-03-03 10:30:30 +05:00
Abdullah Atta
808670f6e1 web: always show "X" button if there is query in search input 2025-03-03 10:24:03 +05:00
Abdullah Atta
62ab799a4a ci: build in beta mode when release track is beta 2025-03-01 15:32:06 +05:00
Abdullah Atta
ecee87dac9 ci: run apt-get update before installing flatpak etc 2025-03-01 15:28:54 +05:00
Abdullah Atta
a5d52ded77 desktop: hide release track selector for flatpak/snap builds 2025-03-01 14:47:18 +05:00
Abdullah Atta
4e786a83f9 web: bump version to 3.1.0-beta.0 2025-03-01 13:20:05 +05:00
Abdullah Atta
bf48ce788d web: use virtualized tree for rendering toc 2025-03-01 13:20:05 +05:00
Abdullah Atta
f6637da274 editor: fix toc level mapping 2025-03-01 13:20:05 +05:00
Abdullah Atta
6cd4a4a041 web: add support for pre-expanding tree nodes on creation 2025-03-01 13:20:05 +05:00
Abdullah Atta
02ece7139e web: do not collapse child nodes when collapsing parent 2025-03-01 13:20:05 +05:00
Abdullah Atta
95203d723b web: fix all tests 2025-03-01 13:20:05 +05:00
Abdullah Atta
b423cef2e6 global: update ui 2025-03-01 13:20:05 +05:00
01zulfi
e46b3ea85f web: fix faulty useEffect
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-03-01 13:20:05 +05:00
01zulfi
0019c1f7af web: refactor props for Tags component
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-03-01 13:20:05 +05:00
01zulfi
ffc4e2d263 web: separate component for tab items in sidebar
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-03-01 13:20:05 +05:00
01zulfi
c01ad2e49c web: new sidebar ui
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-03-01 13:20:05 +05:00
Abdullah Atta
9ff235e767 ci: change working-directory to ./apps/web before deployment 2025-03-01 13:19:18 +05:00
Abdullah Atta
9b6f7ff225 web: fix release track switching 2025-03-01 13:13:25 +05:00
Abdullah Atta
99f97ac565 web: fix release-track cookie not getting set 2025-03-01 11:41:37 +05:00
Abdullah Atta
7e598098fe ci: add support for publishing web app beta 2025-03-01 11:15:03 +05:00
Abdullah Atta
02afbf6c90 web: only redirect to beta if env.BETA_BASE_URL is present 2025-03-01 11:07:40 +05:00
Abdullah Atta
b464fafc18 web: bump version to 3.0.29 2025-03-01 11:05:26 +05:00
Abdullah Atta
e06320108b web: fix gap between title and editor on entering focus mode 2025-03-01 10:50:38 +05:00
Abdullah Atta
a377b13e2b editor: fix single & double spaced paragraph margins 2025-03-01 10:40:27 +05:00
Abdullah Atta
4728212531 core: fix tags & notebooks search 2025-03-01 10:23:58 +05:00
01zulfi
71fc2a3cac web: add keyboard shortcut for opening settings (#7701)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-03-01 09:31:23 +05:00
01zulfi
303282624e editor: hide hover popup if right click occurs before popup is shown (#7687)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-03-01 09:23:26 +05:00
01zulfi
2ca02937df editor: open internal links in new tab for middle mouse click (#7688)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-03-01 09:22:47 +05:00
Abdullah Atta
00b62df2e8 desktop: fix tests on windows 2025-02-28 18:03:05 +05:00
Abdullah Atta
6d377ded9a desktop: set APPIMAGE to true when testing linux to allow auto updater to work 2025-02-28 18:03:05 +05:00
Abdullah Atta
087c404225 desktop: fix tests on linux 2025-02-28 18:03:05 +05:00
Abdullah Atta
4932763468 desktop: extend test timeout to 2 minutes 2025-02-28 18:03:05 +05:00
Abdullah Atta
abcc19d5da desktop: skip tsc build during tests 2025-02-28 18:03:05 +05:00
Abdullah Atta
f1ce1e9b60 desktop: enable staging mode during tests to disable code signing 2025-02-28 18:03:05 +05:00
Abdullah Atta
5a6f99a41b desktop: double test timeout on CI 2025-02-28 18:03:05 +05:00
Abdullah Atta
1862802052 desktop: pass --config arg to electron-builder everywhere 2025-02-28 18:03:05 +05:00
Abdullah Atta
1adddbdb4c desktop: add more tests for auto updater 2025-02-28 18:03:05 +05:00
Abdullah Atta
15f4f2accb desktop: add support for switching release tracks
this also includes relevant tests for the auto updater
2025-02-28 18:03:05 +05:00
Abdullah Atta
725f3c3522 web: add support for switching release tracks
we make use of Cloudflare Pages Functions to redirect to the beta site
if a certain cookie is present. This ensures seamless switching between
release tracks.
2025-02-28 18:03:05 +05:00
01zulfi
efa73a1c32 web: update toast position (#7665)
* current position would overlap slightly with the status bar, this commit moves the toast a little higher so it doesn't overlap
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-02-25 13:21:57 +05:00
Abdullah Atta
00250c6669 web: bump version to v3.0.28 2025-02-25 11:41:46 +05:00
01zulfi
d11afda1a8 web: fix attachment table height for few items (#7662)
* assign min-height to container
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-02-25 11:39:44 +05:00
Abdullah Atta
1350784c33 editor: allow other permissions to iframe embeds 2025-02-25 11:00:52 +05:00
Abdullah Atta
1852a216a2 editor: fix Applying mismatched transaction errors on editor load
fixes #7656
2025-02-25 11:00:31 +05:00
Abdullah Atta
422c2492c4 web: fix react error on feature dialog 2025-02-25 10:10:06 +05:00
Abdullah Atta
c9c72741b3 desktop: fix app icon size on macos 2025-02-24 12:58:44 +05:00
Abdullah Atta
99dbcaff7d web: make tabs tests more resilient 2025-02-24 12:19:21 +05:00
Abdullah Atta
8d83f5474d web: fix editor tests 2025-02-24 11:53:51 +05:00
Abdullah Atta
a912a1a22e web: fix invalid usage of sessionId as noteId 2025-02-24 11:53:41 +05:00
Abdullah Atta
ea7afbe93e web: fix invalid usage of closeSessions 2025-02-24 11:53:18 +05:00
Abdullah Atta
7b2a8c3e20 web: fix executing recent command does nothing
fixes #7650
2025-02-24 11:16:38 +05:00
Abdullah Atta
911adfc151 web: fix reloading app loses the expanded size of a collapsed pane 2025-02-24 11:11:03 +05:00
Abdullah Atta
23f710856e web: fix collapsed pane cannot be resized 2025-02-24 11:10:37 +05:00
Abdullah Atta
7f6274821c web: fix title auto resizing on firefox 2025-02-24 10:48:11 +05:00
Abdullah Atta
4700843517 web: fix note content loading in editor
this broke when we migrated to the new tabs logic and was fixed by
using `editor.updateContent` which is significantly slower than
creating a new editor/editor view. This commit fixes that by
depending on the `getContent` function which changes whenever the
content is changed in a session handling all kinds of different
cases.
2025-02-24 10:38:37 +05:00
Abdullah Atta
8823054227 editor: disable suspiciousAttributes check in prosemirror
this check causes a significant slowdown when loading notes with huge
amount of nodes as it recursively scans the attributes of all the
nodes. Disabling this speeds up loading significantly but might
cause issues in some special cases. Fortunately, this error has never
been triggered by anyone in Notesnook. I wouldn't recommend anyone to
disable this without first testing it properly as it might expose you
to cross site scripting attacks.
2025-02-24 10:36:18 +05:00
Abdullah Atta
7c63a3e004 editor: optimize @tiptap/core
this optimizes the following functions in @tiptap/core:

- `mergeAttributes`
- `getRenderedAttributes`
- `injectExtensionAttributesToParseRule`

These functions stood out the most in the profiler.
2025-02-24 10:33:13 +05:00
Abdullah Atta
2a59154268 editor: improve note loading performance
this commit gets rid of the custom logic when loading a new note in
the same editor. Instead, we just create a new view in the same
editor which seems to be much faster then just replacing the state
of the old view.
2025-02-24 10:31:35 +05:00
Abdullah Atta
ed15d53484 editor: fix block-id extension slowing down pasting
we were using `tr.setNodeMarkup` which created a new step for each
node. If there were a lot of nodes, it'd cause an exponential slow
down. As an alternative, we are directly updating the node's blockId
attribute which is against the spec but it works and it's fast.
2025-02-22 09:42:33 +05:00
Abdullah Atta
fc8a4bb55d ci: run desktop build in ubuntu-24.04 2025-02-21 15:07:53 +05:00
Abdullah Atta
dd1c1bb11d ci: use apt-get with --quiet 2025-02-21 15:03:10 +05:00
Abdullah Atta
3ffbad5727 theme-builder: make theme builder compile again 2025-02-21 14:52:38 +05:00
Abdullah Atta
207ae39d13 ci: run web tests on 5 shards (instead of 4) 2025-02-21 14:34:02 +05:00
Abdullah Atta
ae141ac9c5 web: fix control + alt + right/left arrow tests 2025-02-21 14:26:50 +05:00
Abdullah Atta
40f058b14b web: bump version to v3.0.27 2025-02-21 13:13:53 +05:00
Abdullah Atta
865c9f3e32 web: remove check that compares note title with session title 2025-02-21 13:10:27 +05:00
Abdullah Atta
26df9bfdd8 web: fix editing a note and switching immediately to another note and editing the title shouldn't overlap both notes test 2025-02-21 13:04:53 +05:00
Abdullah Atta
1e52e01fb8 web: fix debug strings showing in production builds 2025-02-21 12:39:44 +05:00
01zulfi
5fece5faaa web: fix note title sync if note is opened in multiple tabs (#7636)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-02-21 12:36:07 +05:00
01zulfi
d7fdaf6c46 ui: fix submenu positioning (#7632)
position.top wasn't calculated correctly for the out of screen height case for non-mouse target

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-02-21 12:34:10 +05:00
Abdullah Atta
8e1faf9f60 web: command palette improvements
This includes:

- Full refactoring of the command palette
- Remove command transformation logic for dynamic
commands
- Replace note, notebook & tag specific commands
with a simple menu item -> command conversion logic
- Cached filtering (with notes, notebooks etc.
refetching on sync)
- Add support for fuzzy searching on command group
- Modernize UI to look more like a command palette and less like a dialog
2025-02-21 12:29:25 +05:00
Abdullah Atta
9dce39b32d core: reuse fuzzy search logic 2025-02-21 12:29:25 +05:00
Abdullah Atta
80a0b75887 core: remove matchOnly logic from fuzzy searcher
Search should always filter out the results, not return everything.
2025-02-21 12:29:25 +05:00
Abdullah Atta
c9704e1031 core: remove titleOnly lookup tests 2025-02-21 12:29:25 +05:00
Abdullah Atta
31e32cd154 core: fix query transformer tests 2025-02-21 12:29:25 +05:00
Abdullah Atta
61c296fc37 core: improve fuzzy filter lookup api to be more robust
- add ability to wrap matching text in prefix/suffix
- return match score for further sorting etc.
- fetch everything eagerly (instead of iterating on matches) for perf
- add support for ignoring fields (i.e. ignore id field)
2025-02-21 12:29:25 +05:00
Abdullah Atta
c8d38235b2 web: close command palette if already open on Ctrl/Cmd+K/P 2025-02-21 12:29:25 +05:00
Abdullah Atta
b717a64607 web: add support for closing dialogs imperatively 2025-02-21 12:29:25 +05:00
01zulfi
0f2b1d3e14 web: fix tags not adding/removing in note header bar (#7623)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-02-20 13:02:49 +05:00
Ammar Ahmed
4abefe63db core: fix error can't resolve './fuzzy' 2025-02-20 10:49:03 +05:00
Abdullah Atta
fc31b2a8d7 web: pressing escape key shouldn't exit fullscreen mode in firefox 2025-02-19 16:04:21 +05:00
01zulfi
2708bac4fb web: wrap & remove auto-resize of note title (#7290)
* web: wrap & remove auto-resize of note title
* wrap note title if it overflows
* remove auto-resizing of note title based on its length
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: remove newlines in title textarea

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2025-02-19 15:56:36 +05:00
01zulfi
a47967dd53 web: add command palette (#7314)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-02-19 15:48:44 +05:00
01zulfi
53ba5562d3 intl: fix account casing (#7624)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-02-19 14:13:49 +05:00
three-b0dy
347df32943 monograph: fix docker build for aarch64 (#7622)
--platform=$BUILDPLATFORM will overwrite buildx settings

Signed-off-by: three-b0dy <120573565+three-b0dy@users.noreply.github.com>
2025-02-19 14:03:52 +05:00
01zulfi
ac8c63526b editor: add scroll margin & threshold props (#7292)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-02-18 12:27:55 +05:00
01zulfi
a48bd360ca editor: improve hr & p styling (#7489)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-02-17 12:10:10 +05:00
Ammar Ahmed
f476969e9e mobile: pinned tab changes 2025-02-17 11:58:26 +05:00
Abdullah Atta
c45539dea6 web: add tests for pinned tabs 2025-02-17 11:58:26 +05:00
Abdullah Atta
d8f5815b84 web: disable opening new note in pinned tab 2025-02-17 11:58:26 +05:00
Abdullah Atta
2eaf07118a web: open note history in new tab if active tab is pinned 2025-02-17 11:58:26 +05:00
Abdullah Atta
b636b09a96 web: disable all kinds of navigation in pinned tabs 2025-02-17 11:58:26 +05:00
Abdullah Atta
c16bbcaad5 editor: add support for pasting as markdown 2025-02-17 11:57:24 +05:00
Abdullah Atta
49ffcbea03 editor: fix youtube embeds 2025-02-17 10:30:12 +05:00
Waqar Ahmed
f772cf0f14 editor: fix search in complex elements like tables (#7605)
The current search algorithm while accumulating text assumes that the
text nodes are always followed by a paragraph node with endPos ==
textNode.endPos + 1. This is not true for complex text elements like
tables where the endPos can be higher. Thus the start,end offsets of
TextNodesWithPosition start getting off and results in incorrectly
highlighted search results.

To fix this, simplify the TextNodesWithPosition to only hold start
position and offset since startPos is always correct. We can calculate
the end position on the fly by simply adding text length to startPos.

Issue #7431

Signed-off-by: Waqar Ahmed <waqar.17a@gmail.com>
2025-02-17 10:16:55 +05:00
Abdullah Atta
3f1406a05b monograph: bump version to v1.2.4 2025-02-15 23:39:37 +05:00
Abdullah Atta
21760b2b74 monograph: add a strict csp 2025-02-15 23:38:52 +05:00
Ammar Ahmed
d8011f0239 mobile: release v3.0.31 2025-02-14 15:07:12 +05:00
Ammar Ahmed
b9a8b054e2 Fix sharing screenshot on iOS does not work (#7589) 2025-02-14 15:05:18 +05:00
Abdullah Atta
2ab321869e web: bump version to v3.0.26 2025-02-14 10:08:43 +05:00
Abdullah Atta
fcb45a24d6 monograph: use latest version of bun in prod 2025-02-14 09:57:07 +05:00
Abdullah Atta
7cdb13def6 Merge pull request #7588 from streetwriters/fix/tighten-iframe-security
This fixes a potential security vulnerability where pasting unknown content into the editor could create an RCE risk.

This PR fixes two issues:

1. Potential RCE when pasting/inserting an `iframe` containing a `javascript` link.
2. Potential RCE when pasting/inserting an `svg` containing JavaScript (why do SVGs allow JS in the first place?).

Mitigations include disallowing all execution of JS inside an SVG by rendering it in a sandboxed `iframe`. While we cannot disallow JS execution in embeds (that would break all embeds like YouTube videos), we have disallowed access to the parent window to all `iframe`s, again, by using a sandboxed `iframe` and by disallowing embedding of `javascript:` links.

To be clear, both of these issues can only be triggered when pasting/importing untrusted content (which you shouldn't be doing anyway).

**These cannot be used to steal or access your notes or any other data. They could be used to access what's shown in the window or do automated clicks etc. but since everything is stored and access from an encrypted SQLite database, your data would be 100% safe and isolated from such an attack.**
2025-02-14 09:50:43 +05:00
Abdullah Atta
2ab58f9203 editor: use sandboxed iframe to render SVGs 2025-02-14 09:31:06 +05:00
Abdullah Atta
df74448e17 editor: disallow embedding javascript code in iframes 2025-02-13 20:27:21 +05:00
01zulfi
ada98fca93 web: hide undo & redo buttons for readonly notes (#7541)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-02-13 10:13:44 +05:00
Yash Kumar
1d3650659f editor: select language by pressing enter (#7484)
Signed-off-by: Yash Kumar <kyash03@student.ubc.ca>
2025-02-13 10:12:37 +05:00
Ammar Ahmed
9b43af475b mobile: release v3.0.31 2025-02-11 14:01:11 +05:00
Ammar Ahmed
d9c592c7fb common: always remove the last session from history (#7555) 2025-02-11 13:31:39 +05:00
Ammar Ahmed
f10ac9fbb5 Merge pull request #7556 from streetwriters/fix-tabs-mobile
Fix tabs on mobile
2025-02-11 13:12:12 +05:00
Ammar Ahmed
b7334c09d4 Merge branch 'master' into fix-tabs-mobile
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2025-02-11 13:11:55 +05:00
Ammar Ahmed
0cd3bd5739 mobile: fix dialog calling onClose after pressing positive button 2025-02-11 13:11:13 +05:00
Ammar Ahmed
33d69480eb mobile: fix ts errors 2025-02-11 13:11:02 +05:00
Ammar Ahmed
5cae4eb0d9 mobile: add null checks before setting items on storage 2025-02-11 12:18:28 +05:00
Ammar Ahmed
840c7fda5e mobile: fix loading placeholder 2025-02-11 12:17:45 +05:00
Ammar Ahmed
4ce24ac8fd mobile: do not show sheet in uncaught errors in editor 2025-02-11 12:17:11 +05:00
Ammar Ahmed
ae9e80e12e mobile: fix stuck at loading in editor 2025-02-11 12:16:51 +05:00
luis-411
43356e5b6a core: escape special characters in SQL search query (#7418)
Signed-off-by: Luis Kriner <luis@kriner.info>
2025-02-11 11:00:51 +05:00
xa4hf8
7b13f35424 desktop: replace corrupt Mac icons bundle (#7542)
There is a long-standing bug in Electron builder that generates corrupt Macintosh icon bundles. The issue is visible in Finder "list" view.  This file is correct and was created on Mac with https://github.com/alptugan/icns-creator/

Signed-off-by: xa4hf8 <174972649+xa4hf8@users.noreply.github.com>
2025-02-11 10:54:50 +05:00
luis-411
0517c6ce41 web: fix status-bar not visible on medium sized screens (#7493)
Signed-off-by: Luis Kriner <luis@kriner.info>
2025-02-08 19:31:37 +05:00
01zulfi
54d06328c1 web: add tab keyboard shortcuts (#7322)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-02-08 19:30:20 +05:00
Ammar Ahmed
c74677acd4 mobile: restore settings on logout (#7423) 2025-02-05 09:30:00 +05:00
01zulfi
7689e1a7c5 web: keep some configs in local storage on logout (#7436)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-02-05 09:29:36 +05:00
luzpaz
5ffae2a08b global: fix various typos (#7463)
Found via `codespell -q 3 -D ../dictionary.txt -S "*.patch,*.po" -L actualy,childs,modifer,ontext,ontop,ony,recieved,reciever,se,seperator,te`

Signed-off-by: Luz Paz <luzpaz@pm.me>
2025-02-05 09:28:06 +05:00
Ammar Ahmed
5fa80abdc8 mobile: update changelog 2025-02-04 19:25:16 +05:00
Ammar Ahmed
a30598fedf mobile: release v3.0.30 2025-02-04 19:23:49 +05:00
Ammar Ahmed
48ffecfb5f mobile: fix e2e tests 2025-02-04 19:23:11 +05:00
Ammar Ahmed
89fe712104 mobile: fix file size is 0 errors when downloading attachments 2025-02-04 17:36:46 +05:00
Ammar Ahmed
5dd298ee86 mobile: fix unlocking note with biometrics 2025-02-04 17:36:32 +05:00
Ammar Ahmed
275b285a8b mobile: release v3.0.29 2025-02-04 12:39:04 +05:00
Ammar Ahmed
96310a46e9 mobile: fix configure note activity not showing biometric unlock prompt 2025-02-04 12:33:05 +05:00
Ammar Ahmed
42f9bf1593 mobile: fix opening widget note on app launch 2025-02-04 12:33:05 +05:00
Ammar Ahmed
25aab6913a mobile: show progress when taking backup and allow to hide backup dialog 2025-02-04 12:32:51 +05:00
Ammar Ahmed
3bd7da68be mobile: fix crash on app launch with new tabs 2025-02-04 12:32:30 +05:00
Abdullah Atta
a218738eff web: improve unfocused window ui 2025-02-04 11:11:51 +05:00
Abdullah Atta
8bf283b3bc config: add *.patch files to .gitignore 2025-02-04 10:14:59 +05:00
luis-411
cb09430ff4 web: fix subnotebook title not updating on navigate (#7286)
Signed-off-by: Luis Kriner <luis@kriner.info>
2025-02-04 10:06:59 +05:00
luis-411
66d75492bb editor: fix task list stats 0/0 on app reload (#7327)
Signed-off-by: Luis Kriner <luis@kriner.info>
2025-02-04 10:04:16 +05:00
Abdullah Atta
d772d38dc7 web: fix collapsed pane expanding on app reload 2025-02-04 09:35:04 +05:00
Ammar Ahmed
46a74f9512 mobile: release v3.0.28 2025-02-03 15:45:54 +05:00
Ammar Ahmed
7df1037e3f mobile: fix realtime sync editor updates in tabs 2025-02-03 15:38:32 +05:00
Abdullah Atta
5c3136d07d ci: run actions on ubuntu-22.04 2025-02-03 14:57:30 +05:00
Abdullah Atta
6a9ddb770e web: bump version to 3.0.25 2025-02-03 14:39:44 +05:00
Abdullah Atta
f4c54fd27f web: fix crash on setting a reminder 2025-02-03 14:22:58 +05:00
Ammar Ahmed
05b12df7d7 mobile: cache password when restoring backup 2025-02-03 14:16:46 +05:00
Ammar Ahmed
dcf053eede mobile: call onClose when closing dialog 2025-02-03 14:16:46 +05:00
Abdullah Atta
5430966b02 web: fix tabs are not clickable everywhere 2025-02-03 13:25:17 +05:00
Ammar Ahmed
7271140627 mobile: fix release notes rendering 2025-02-03 13:02:06 +05:00
01zulfi
eb5ae0773b editor: fix hover styling in toolbar color buttons
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-02-03 12:36:06 +05:00
Ammar Ahmed
9661dd0c5e mobile: fix android widget bugs 2025-02-03 12:19:53 +05:00
Ammar Ahmed
8e70860918 mobile: fix attaching a file in new note editor 2025-02-03 12:19:34 +05:00
Ammar Ahmed
3540ecf4ab mobile: close sheet on backup completed 2025-02-03 12:19:17 +05:00
Ammar Ahmed
f1dba9ffc2 mobile: cleanup 2025-02-03 12:17:57 +05:00
Abdullah Atta
3f1761a540 core: fix empty note cannot be exported 2025-02-03 12:17:57 +05:00
Abdullah Atta
a25d21038a core: fix Object is not iterable error on some platforms
This happened due to [Symbol.asyncIterable] not getting
transformed when it was a method of a class. Moving it inside
another method fixes the issue.
2025-02-03 12:17:57 +05:00
Abdullah Atta
d55a6a6fc0 web: fix pinned tab not moving to the beginning 2025-02-03 10:46:24 +05:00
Abdullah Atta
e5cf902780 web: fix unnecessary rerenders of the tab strip 2025-02-03 10:01:52 +05:00
Abdullah Atta
a99996fcf6 web: fix tab drag/drop 2025-02-03 09:52:00 +05:00
Abdullah Atta
0cc9c31bc3 editor: fix Failed to execute 'collapse' on 'Selection' 2025-02-01 15:14:34 +05:00
Abdullah Atta
62da8484ad web: ignore notes in other tabs when navigating back 2025-02-01 14:26:12 +05:00
Ammar Ahmed
821b8eebaa mobile: fix tab issues 2025-02-01 13:47:27 +05:00
Ammar Ahmed
269941c297 mobile: keep all datatype classes 2025-02-01 13:47:27 +05:00
Abdullah Atta
e37ff88f1f web: fix flicker when opening a note in new tab 2025-02-01 13:38:48 +05:00
Abdullah Atta
1a1b4bc43c web: fix note already opened in another tab is not focused 2025-02-01 13:18:31 +05:00
Abdullah Atta
2c59df9fb7 web: remove unused code from action-bar 2025-02-01 12:58:53 +05:00
Abdullah Atta
4fc30cc224 web: move focus mode and editor margins to status bar 2025-02-01 12:57:55 +05:00
Abdullah Atta
9bd7d392af web: fix zoom buttons ui 2025-02-01 12:27:51 +05:00
Abdullah Atta
3bcd288bc7 web: more compact and unified action bar 2025-02-01 12:18:47 +05:00
Ammar Ahmed
3b86e51c49 mobile: fix commands 2025-01-31 15:17:45 +05:00
Abdullah Atta
c286b1d595 web: use new storage key for storing editor sessions 2025-01-31 15:15:48 +05:00
Abdullah Atta
2f6b1e6bcc web: fix rapidly clicking on back/forward buttons opened new tab 2025-01-31 15:15:48 +05:00
Abdullah Atta
abb918e179 web: fix tests 2025-01-31 15:15:48 +05:00
Abdullah Atta
dd0dc09977 web: fix note version can't be restored 2025-01-31 15:15:48 +05:00
Abdullah Atta
211ab47165 web: save selection state before switching note 2025-01-31 15:15:48 +05:00
Abdullah Atta
bc44e28e1e web: remove more usages of session.id where noteId was expected 2025-01-31 15:15:48 +05:00
Abdullah Atta
ade60e4272 web: move tab navigation keyboard shortcuts to key-map.ts 2025-01-31 15:15:48 +05:00
Abdullah Atta
3f5bfcb499 web: focus tab if note is already opened in one & reuse sessions for note 2025-01-31 15:15:48 +05:00
Abdullah Atta
782a979397 web: handle case where a new note session is in the tab history 2025-01-31 15:15:48 +05:00
Abdullah Atta
e7f1e2d533 web: add support for opening a session without focusing the tab 2025-01-31 15:15:48 +05:00
Abdullah Atta
12e5f2acc1 web: fix focus next/prev tab not working 2025-01-31 15:15:48 +05:00
Abdullah Atta
dccc79ace3 web: remove usage of session.id in places where note id is expected 2025-01-31 15:15:48 +05:00
Abdullah Atta
a9a5ba09ff web: activate a new note session on tab rehydration 2025-01-31 15:15:48 +05:00
Abdullah Atta
a6c8bbbf0a web: reactivate same session if user tries to create consecutive new notes 2025-01-31 15:15:48 +05:00
Abdullah Atta
e19e9eb96f web: get fresh note context when saving a new note 2025-01-31 15:15:48 +05:00
Ammar Ahmed
9208140b97 mobile: improve tab button 2025-01-31 15:15:48 +05:00
Ammar Ahmed
8241bb8ae5 mobile: fix opening tab for the first time 2025-01-31 15:15:48 +05:00
Ammar Ahmed
329c2e220f mobile: fix tabs 2025-01-31 15:15:48 +05:00
Abdullah Atta
140e343289 common: allow using custom session id in tab session history 2025-01-31 15:15:48 +05:00
Abdullah Atta
0d99367a93 web: fix title not updating in real time sync 2025-01-31 15:15:48 +05:00
Abdullah Atta
b802b3e165 web: fix diff session not opening in editor 2025-01-31 15:15:48 +05:00
Ammar Ahmed
46583e12d9 mobile: update tabs 2025-01-31 15:15:48 +05:00
Abdullah Atta
c9a3f31e8f web: add more tests 2025-01-31 15:15:48 +05:00
Abdullah Atta
53f68aaf81 web: add tests for new tab button 2025-01-31 15:15:48 +05:00
Abdullah Atta
f31df9a6cc web: add tests for new tabs 2025-01-31 15:15:48 +05:00
Abdullah Atta
c812581bb9 web: add support for new tab navigation 2025-01-31 15:15:48 +05:00
Abdullah Atta
1e6e940f17 common: string tab ids, get rid of currentTab taking tab id as a parameter 2025-01-31 15:15:48 +05:00
Ammar Ahmed
9ab670d933 mobile: fix deps 2025-01-31 15:15:48 +05:00
Ammar Ahmed
c46e57418b mobile: remove unused properties 2025-01-31 15:15:48 +05:00
Ammar Ahmed
83557401f5 mobile: update tabs 2025-01-31 15:15:48 +05:00
Ammar Ahmed
2277644e89 mobile: cleanup 2025-01-31 15:15:48 +05:00
Ammar Ahmed
5eb09a1d3e mobile: cleanup 2025-01-31 15:15:48 +05:00
Ammar Ahmed
d957be7a7b mobile: fix editor header ui 2025-01-31 15:15:48 +05:00
Ammar Ahmed
44ddb49d0e editor: fix imports 2025-01-31 15:15:48 +05:00
Ammar Ahmed
24fcd5cd0c common: fix missing exports 2025-01-31 15:15:48 +05:00
Ammar Ahmed
2a34225d49 global: update deps 2025-01-31 15:15:48 +05:00
Ammar Ahmed
e81c825fc7 mobile: multi-tab support 2025-01-31 15:15:48 +05:00
Ammar Ahmed
c80286b587 mobile: tab history 2025-01-31 15:15:48 +05:00
luis-411
e85f8b60b0 editor: add field labels to add a link popup (#7097)
Signed-off-by: Luis Kriner <luis@kriner.info>
2025-01-31 10:11:59 +05:00
luis-411
5daa99d969 web: fix nav menu collapse (#7377)
Signed-off-by: Luis Kriner <luis@kriner.info>
2025-01-30 09:09:28 +05:00
01zulfi
073bb576b5 editor: fix search not resetting when closed (#7415)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-01-29 11:23:26 +05:00
Abdullah Atta
eaeac130df Revert "intl: update lockfile"
This reverts commit 95a20b3740.
2025-01-29 10:22:18 +05:00
Abdullah Atta
b21519c24e ci: include bundled electron app in desktop build 2025-01-29 10:22:18 +05:00
Abdullah Atta
4f9fe9b1e0 Revert "intl: update lockfile"
This reverts commit f657380ec7.
2025-01-28 14:49:12 +05:00
Abdullah Atta
f657380ec7 intl: update lockfile 2025-01-28 14:20:41 +05:00
Abdullah Atta
05b2e559d8 setup: downgrade tsup version 2025-01-28 13:33:29 +05:00
Abdullah Atta
ab21f02c2d ci: include intl, theme-builder & monograph in npm cache 2025-01-28 13:07:50 +05:00
Abdullah Atta
12b51ed72a intl: update lockfiles 2025-01-28 13:04:19 +05:00
Abdullah Atta
b672fd540c setup: generate sources for intl package 2025-01-28 12:50:42 +05:00
Abdullah Atta
95a20b3740 intl: update lockfile 2025-01-28 11:25:38 +05:00
Abdullah Atta
6f5a7a4923 global: use same version for @lingui deps everywhere 2025-01-28 10:45:04 +05:00
Ammar Ahmed
1b4d98ac0e mobile: fix strings not rendering correctly in editor 2025-01-22 15:23:56 +05:00
Ammar Ahmed
ef2ed2bba0 mobile: show toast when changing some settings 2025-01-22 14:03:52 +05:00
Ammar Ahmed
abf4612977 core: fix esm imports 2025-01-21 14:32:48 +05:00
Ammar Ahmed
9c6637908d ci: update workflow
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2025-01-21 13:58:26 +05:00
01zulfi
e1006ed501 core: add nowz and timestampz formats (#7270)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-01-21 13:17:08 +05:00
Ammar Ahmed
7d034f7f16 mobile: fix build errors 2025-01-21 13:01:40 +05:00
Abdullah Atta
76eec5f8bb web: do not show email confirm notice for loggedout users 2025-01-21 12:47:51 +05:00
Ammar Ahmed
698866f53d mobile: release v3.0.27 2025-01-21 12:31:37 +05:00
Abdullah Atta
21e1e73bee desktop: fix ui when native titlebar is enabled 2025-01-21 11:35:47 +05:00
Abdullah Atta
52e89c7842 web: update snapshots 2025-01-21 10:41:05 +05:00
Abdullah Atta
8b357559fa web: fix keyboard-navigation-list tests 2025-01-21 10:30:17 +05:00
Abdullah Atta
2be35fadff editor: fix tests 2025-01-21 10:14:42 +05:00
Abdullah Atta
5fe366f4f3 global: use same versions for dependencies everywhere (#7365)
* global: use same versions for dependencies everywhere

* intl: fix `Cannot find module '@lingui/macro'`

* web: fix `I18n' is not assignable to parameter` type error

* setup: log post install cmd

* setup: more logging

* web: update lockfile
2025-01-21 09:35:38 +05:00
Ammar Ahmed
a13cbadd44 feat: new widgets on android (#7311)
- Added a new widget on android that allows you to put a note on your home screen.
- Added a new widget on android that allows you to see upcoming reminders on home screen
- Fixed new note widget freezing
2025-01-20 17:17:01 +05:00
Abdullah Atta
aa993a2fce web: bump version to 3.0.24 2025-01-20 13:26:50 +05:00
01zulfi
975af5701b web: fix reveal in list (#7297)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-01-20 12:08:13 +05:00
luis-411
3add56cfe4 global: fix search strings (#7302)
Signed-off-by: Luis Kriner <luis@kriner.info>
2025-01-20 11:47:48 +05:00
Abdullah Atta
522ea1520d web: fix tests failing on macos 2025-01-20 11:28:26 +05:00
467 changed files with 29115 additions and 67376 deletions

View File

@@ -14,6 +14,8 @@ runs:
apps/mobile/package-lock.json
apps/desktop/package-lock.json
apps/web/package-lock.json
apps/monograph/package-lock.json
apps/theme-builder/package-lock.json
extensions/web-clipper/package-lock.json
packages/core/package-lock.json
packages/crypto/package-lock.json
@@ -24,3 +26,4 @@ runs:
packages/logger/package-lock.json
packages/streamable-fs/package-lock.json
packages/theme/package-lock.json
packages/intl/package-lock.json

View File

@@ -4,7 +4,7 @@ on: workflow_dispatch
jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
timeout-minutes: 40
env:
CMAKE_C_COMPILER_LAUNCHER: ccache

View File

@@ -18,7 +18,7 @@ on:
jobs:
test:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4

View File

@@ -33,11 +33,19 @@ on:
required: true
default: true
description: "Build for macOS?"
release-track:
type: choice
required: true
default: stable
description: "Select the release track"
options:
- stable
- beta
jobs:
build:
name: Build
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -49,14 +57,30 @@ jobs:
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: Setup environment
run: |
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
- name: Generate desktop build
- name: Generate desktop build (stable)
if: ${{ inputs.release-track == 'stable' }}
run: npx nx build:desktop @notesnook/web
- name: Generate desktop build (beta)
if: ${{ inputs.release-track == 'beta' }}
run: BETA=true npx nx build:desktop @notesnook/web
- name: Build desktop bundle
working-directory: ./apps/desktop
run: npm run bundle
- name: Archive build artifact
uses: actions/upload-artifact@v4
with:
@@ -71,7 +95,8 @@ jobs:
- name: Package desktop build
run: |
zip -r ./notesnook_build_v${{ steps.app_metadata.outputs.app_version }}.zip ./apps/web/build/
cp -r ./apps/web/build ./apps/desktop/
zip -r ./notesnook_build_v${{ steps.app_metadata.outputs.app_version }}.zip ./apps/desktop/build/
echo "Build folder archived to ./notesnook_build_v${{ steps.app_metadata.outputs.app_version }}.zip"
- name: Upload desktop build
@@ -87,7 +112,8 @@ jobs:
- name: Generate flatpak sources
if: inputs.publish-github && inputs.build-linux
run: |
sudo apt-get install -y flatpak flatpak-builder pipx
sudo apt-get update
sudo apt-get install -y --quiet flatpak flatpak-builder pipx
git clone https://github.com/flatpak/flatpak-builder-tools.git /tmp/flatpak-builder-tools
cd /tmp/flatpak-builder-tools/node
pipx install .
@@ -173,7 +199,7 @@ jobs:
CSC_KEY_PASSWORD: ${{ secrets.mac_certs_password }}
run: |
npx nx run release --project @notesnook/desktop -- --variant=mas
yarn electron-builder --mac mas --universal -p never
yarn electron-builder --config=electron-builder.config.js --mac mas --universal -p never
working-directory: ./apps/desktop
- name: Build zip and dmg
@@ -187,9 +213,9 @@ jobs:
run: |
npx nx run release --project @notesnook/desktop
if [ ${{ inputs.publish-github }} == true ]; then
yarn electron-builder --mac zip dmg --arm64 --x64 -p always
yarn electron-builder --config=electron-builder.config.js --mac zip dmg --arm64 --x64 -p always
else
yarn electron-builder --mac zip dmg --arm64 --x64 -p never
yarn electron-builder --config=electron-builder.config.js --mac zip dmg --arm64 --x64 -p never
fi
working-directory: ./apps/desktop
@@ -251,7 +277,7 @@ jobs:
- name: Build snap
if: inputs.publish-snap
run: |
yarn electron-builder --linux snap:x64 -p never
yarn electron-builder --config=electron-builder.config.js --linux snap:x64 -p never
working-directory: ./apps/desktop
- name: Build AppImage
@@ -259,9 +285,9 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ ${{ inputs.publish-github }} == true ]; then
yarn electron-builder --linux AppImage:x64 AppImage:arm64 -p always
yarn electron-builder --config=electron-builder.config.js --linux AppImage:x64 AppImage:arm64 -p always
else
yarn electron-builder --linux AppImage:x64 AppImage:arm64 -p never
yarn electron-builder --config=electron-builder.config.js --linux AppImage:x64 AppImage:arm64 -p never
fi
working-directory: ./apps/desktop
@@ -321,8 +347,8 @@ jobs:
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
run: |
if ($${{ inputs.publish-github }} -eq $true) {
yarn electron-builder --win --publish always
yarn electron-builder --config=electron-builder.config.js --win --publish always
} else {
yarn electron-builder --win --publish never
yarn electron-builder --config=electron-builder.config.js --win --publish never
}
working-directory: ./apps/desktop

View File

@@ -14,7 +14,7 @@ on:
jobs:
build:
name: Build
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -69,12 +69,11 @@ jobs:
- name: Build app
run: |
yarn electron-builder --mac --dir --x64
yarn electron-builder --config=electron-builder.config.js --mac --dir --x64
working-directory: ./apps/desktop
- name: Run tests x64
run: |
EXECUTABLE_PATH=output/mac/Notesnook.app/Contents/MacOS/Notesnook npm run test
run: npm run test
working-directory: ./apps/desktop
- name: Upload test results
@@ -114,12 +113,11 @@ jobs:
- name: Build app
run: |
yarn electron-builder --mac --dir --arm64
yarn electron-builder --config=electron-builder.config.js --mac --dir --arm64
working-directory: ./apps/desktop
- name: Run tests arm64
run: |
EXECUTABLE_PATH=output/mac-arm64/Notesnook.app/Contents/MacOS/Notesnook npm run test
run: npm run test
working-directory: ./apps/desktop
- name: Upload test results
@@ -133,7 +131,7 @@ jobs:
test-linux:
name: Test for Linux
needs: build
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Check out Git repository
@@ -165,12 +163,11 @@ jobs:
- name: Build app
run: |
yarn electron-builder --linux --dir --arm64 --x64
yarn electron-builder --config=electron-builder.config.js --linux --dir --arm64 --x64
working-directory: ./apps/desktop
- name: Run tests
run: |
EXECUTABLE_PATH=output/linux-unpacked/notesnook xvfb-run --auto-servernum --server-args="-screen 0 1920x1080x24" -- npm run test
run: xvfb-run --auto-servernum --server-args="-screen 0 1920x1080x24" -- npm run test
working-directory: ./apps/desktop
- name: Upload test results
@@ -216,12 +213,11 @@ jobs:
- name: Build app
run: |
npx cross-env NOTESNOOK_STAGING=true yarn electron-builder --win --dir --arm64 --x64
npx cross-env NOTESNOOK_STAGING=true yarn electron-builder --config=electron-builder.config.js --win --dir --arm64 --x64
working-directory: ./apps/desktop
- name: Run tests
run: |
npx cross-env EXECUTABLE_PATH=output/win-unpacked/Notesnook.exe npm run test
run: npm run test
working-directory: ./apps/desktop
- name: Upload test results

View File

@@ -19,7 +19,7 @@ on:
jobs:
build:
name: Build
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4

View File

@@ -11,7 +11,7 @@ on:
jobs:
build:
name: Build
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4

View File

@@ -14,7 +14,7 @@ on: workflow_dispatch
jobs:
push_to_registry:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
permissions:
packages: write
contents: read

View File

@@ -5,7 +5,7 @@ on: workflow_dispatch
jobs:
build:
name: Build
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4

View File

@@ -5,7 +5,7 @@ on: workflow_dispatch
jobs:
build:
name: Build
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4

View File

@@ -14,7 +14,7 @@ on:
jobs:
bench:
name: Bench
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4

View File

@@ -1,11 +1,21 @@
name: Publish @notesnook/web
on: workflow_dispatch
on:
workflow_dispatch:
inputs:
release-track:
type: choice
required: true
default: stable
description: "Select the release track"
options:
- stable
- beta
jobs:
build:
name: Build
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -24,8 +34,20 @@ jobs:
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV
- name: Generate build
- name: Generate build (stable)
if: ${{ inputs.release-track == 'stable' }}
run: npm run build:web
- name: Publish to Cloudflare Pages
run: npx --yes wrangler pages deploy --project-name=notesnook-app ./apps/web/build/
- name: Publish to Cloudflare Pages (stable)
if: ${{ inputs.release-track == 'stable' }}
working-directory: ./apps/web
run: npx --yes wrangler pages deploy --project-name=notesnook-app ./build/
- name: Generate build (beta)
if: ${{ inputs.release-track == 'beta' }}
run: npm run build:beta:web
- name: Publish to Cloudflare Pages (beta)
if: ${{ inputs.release-track == 'beta' }}
working-directory: ./apps/web
run: npx --yes wrangler pages deploy --branch=beta --project-name=notesnook-app-beta ./build/

View File

@@ -19,7 +19,7 @@ on:
jobs:
build:
name: Build
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -49,7 +49,7 @@ jobs:
name: 🧪 Test (${{ matrix.shard }}/${{ strategy.job-total }})
strategy:
matrix:
shard: [1, 2, 3, 4]
shard: [1, 2, 3, 4, 5]
runs-on: ubuntu-latest
steps:
- name: Checkout

1
.gitignore vendored
View File

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

View File

@@ -2,4 +2,6 @@ node_modules
build
output
dist
_catalog
_catalog
test-results
test-artifacts

View File

@@ -58,13 +58,13 @@ This will compile and run the app in production mode but it won't generate any p
npm run release -- --rebuild
# For macOS
npx electron-builder --mac dmg --arm64 --x64 -publish never
npx electron-builder --config=electron-builder.config.js --mac dmg --arm64 --x64 -publish never
# For Linux (AppImage)
npx electron-builder --linux AppImage:x64 AppImage:arm64 -publish never
npx electron-builder --config=electron-builder.config.js --linux AppImage:x64 AppImage:arm64 -publish never
# For Windows
npx electron-builder --win --publish never
npx electron-builder --config=electron-builder.config.js --win --publish never
```
Feel free to play around with the `electron-builder` command to get the packages you need. `npx electron-builder --help` is a great resource to learn different commands & platforms supported by `electron-builder`.

View File

@@ -0,0 +1,246 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { test } from "vitest";
import { harness } from "./utils.js";
import { writeFile } from "fs/promises";
import { Page } from "playwright";
import { gt, lt } from "semver";
test("update starts downloading if version is outdated", async (t) => {
await harness(
t,
async ({ page }) => {
await page.waitForSelector("#authForm");
t.expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
await skipDialog(page);
await page.waitForSelector(".ProseMirror");
await page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /updating/i })
.waitFor({ state: "attached" });
},
{ version: "3.0.0" }
);
});
test("update is only shown if version is outdated and auto updates are disabled", async (t) => {
await harness(
t,
async (ctx) => {
await ctx.app.close();
await writeFile(
ctx.configPath,
JSON.stringify({
automaticUpdates: false
})
);
await ctx.relaunch();
const { page } = ctx;
await page.waitForSelector("#authForm");
t.expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
await skipDialog(page);
await page.waitForSelector(".ProseMirror");
await page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /available/i })
.waitFor({ state: "attached" });
},
{ version: "3.0.0" }
);
});
test("update to stable if it is newer", async (t) => {
await harness(
t,
async (ctx) => {
await ctx.app.close();
await writeFile(
ctx.configPath,
JSON.stringify({
automaticUpdates: false,
releaseTrack: "beta"
})
);
await ctx.relaunch();
const { page } = ctx;
await page.waitForSelector("#authForm");
t.expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
await skipDialog(page);
await page.waitForSelector(".ProseMirror");
const updateButton = page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /available/i });
await updateButton.waitFor({ state: "visible" });
const content = await updateButton.textContent();
const version = content?.split(" ")?.[0] || "";
t.expect(gt(version, "3.0.0-beta.0")).toBe(true);
},
{ version: "3.0.0-beta.0" }
);
});
test("update is not available if it latest stable version is older", async (t) => {
await harness(
t,
async (ctx) => {
await ctx.app.close();
await writeFile(
ctx.configPath,
JSON.stringify({
automaticUpdates: false,
releaseTrack: "beta"
})
);
await ctx.relaunch();
const { page } = ctx;
await page.waitForSelector("#authForm");
t.expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
await skipDialog(page);
await page.waitForSelector(".ProseMirror");
await page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /checking for updates/i })
.waitFor({ state: "hidden" });
t.expect(
await page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /available/i })
.isHidden()
).toBe(true);
},
{ version: "99.0.0-beta.0" }
);
});
test("downgrade to stable on switching to stable release track", async (t) => {
await harness(
t,
async (ctx) => {
await ctx.app.close();
await writeFile(
ctx.configPath,
JSON.stringify({
automaticUpdates: false,
releaseTrack: "stable"
})
);
await ctx.relaunch();
const { page } = ctx;
await page.waitForSelector("#authForm");
t.expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
await skipDialog(page);
await page.waitForSelector(".ProseMirror");
await page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /checking for updates/i })
.waitFor({ state: "hidden" });
const updateButton = page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /available/i });
await updateButton.waitFor({ state: "visible" });
const content = await updateButton.textContent();
const version = content?.split(" ")?.[0] || "";
t.expect(lt(version, "99.0.0-beta.0")).toBe(true);
},
{ version: "99.0.0-beta.0" }
);
});
async function skipDialog(page: Page) {
await page
.waitForSelector(".ReactModal__Content", {
timeout: 1000
})
.catch(() => {})
.then(async () => {
const positiveButton = page.locator(
"button[data-role='positive-button']"
);
const negativeButton = page.locator(
"button[data-role='negative-button']"
);
if (await positiveButton.isVisible()) await positiveButton.click();
else if (await negativeButton.isVisible()) await negativeButton.click();
});
}

View File

@@ -17,16 +17,12 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import test from "node:test";
import { launchApp } from "./utils.mjs";
import { test } from "vitest";
import { harness } from "./utils.js";
import assert from "assert";
import slugify from "slugify";
import path from "path";
import { mkdir } from "fs/promises";
test("make sure app loads", async (t) => {
const { app, page } = await launchApp();
try {
await harness(t, async ({ page }) => {
await page.waitForSelector("#authForm");
assert.ok(
@@ -38,16 +34,5 @@ test("make sure app loads", async (t) => {
.click();
await page.waitForSelector(".ProseMirror");
} catch (e) {
await mkdir("test-results", { recursive: true });
await page.screenshot({
path: path.join(
"test-results",
`${slugify(t.name)}-${process.platform}-${process.arch}-error.png`
)
});
throw e;
} finally {
await app.close();
}
});
});

View File

@@ -0,0 +1,172 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { execSync } from "child_process";
import { mkdir } from "fs/promises";
import { fileURLToPath } from "node:url";
import path from "path";
import { _electron as electron } from "playwright";
import slugify from "slugify";
import { TaskContext } from "vitest";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const IS_DEBUG = process.env.NN_DEBUG === "true" || process.env.CI === "true";
interface AppContext {
app: import("playwright").ElectronApplication;
page: import("playwright").Page;
configPath: string;
relaunch: () => Promise<void>;
}
interface TestOptions {
version: string;
}
export async function harness(
t: TaskContext,
cb: (ctx: AppContext) => Promise<void>,
options?: TestOptions
) {
const ctx = await buildAndLaunchApp(options);
t.onTestFinished(async (result) => {
if (result.state === "fail") {
await mkdir("test-results", { recursive: true });
await ctx.page.screenshot({
path: path.join(
"test-results",
`${slugify(t.task.name)}-${process.platform}-${
process.arch
}-error.png`
)
});
}
await ctx.app.close();
});
await cb(ctx);
}
async function buildAndLaunchApp(options?: TestOptions): Promise<AppContext> {
const productName = makeid(10);
const executablePath = await buildApp({ ...options, productName });
const { app, page, configPath } = await launchApp(executablePath);
const ctx: AppContext = {
app,
page,
configPath,
relaunch: async () => {
const { app, page, configPath } = await launchApp(executablePath);
ctx.app = app;
ctx.page = page;
ctx.configPath = configPath;
}
};
return ctx;
}
async function launchApp(executablePath: string) {
const app = await electron.launch({
executablePath,
args: IS_DEBUG ? [] : ["--hidden"],
env:
process.platform === "linux"
? {
...(process.env as Record<string, string>),
APPIMAGE: "true"
}
: (process.env as Record<string, string>)
});
const page = await app.firstWindow();
const userDataDirectory = await app.evaluate((a) => {
return a.app.getPath("userData");
});
const configPath = path.join(userDataDirectory, "config.json");
return {
app,
page,
configPath
};
}
async function buildApp({
version,
productName
}: {
version?: string;
productName: string;
}) {
const buildRoot = path.join("test-artifacts", `${productName}-build`);
const output = path.join("test-artifacts", `${productName}-output`);
execSync(`npm run release -- --root ${buildRoot} --skip-tsc-build`, {
stdio: IS_DEBUG ? "inherit" : "ignore"
});
const args = [
`--config electron-builder.config.js`,
`--c.extraMetadata.productName=${productName}`,
"--publish=never"
];
if (version) args.push(`--c.extraMetadata.version=${version}`);
execSync(`npx electron-builder --dir --${process.arch} ${args.join(" ")}`, {
stdio: IS_DEBUG ? "inherit" : "ignore",
env: {
...process.env,
NOTESNOOK_STAGING: "true",
NN_BUILD_ROOT: buildRoot,
NN_PRODUCT_NAME: productName,
NN_APP_ID: `com.notesnook.test.${productName}`,
NN_OUTPUT_DIR: output
}
});
return path.join(
__dirname,
"..",
output,
process.platform === "linux"
? process.arch === "arm64"
? "linux-arm64-unpacked"
: "linux-unpacked"
: process.platform === "darwin"
? process.arch === "arm64"
? `mac-arm64/${productName}.app/Contents/MacOS/`
: `mac/${productName}.app/Contents/MacOS/`
: "win-unpacked",
process.platform === "win32" ? `${productName}.exe` : productName
);
}
function makeid(length: number) {
let result = "";
const characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const charactersLength = characters.length;
let counter = 0;
while (counter < length) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
counter += 1;
}
return result.toLowerCase();
}

Binary file not shown.

View File

@@ -0,0 +1,203 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
const path = require("path");
const pkg = require("./package.json");
const buildRoot = process.env.NN_BUILD_ROOT || ".";
const buildFiles = [
`${buildRoot}/build/`,
`!${buildRoot}/build/screenshots\${/*}`,
`!${buildRoot}/build/banner.jpg`,
`!${buildRoot}/build/*.ico`,
`!${buildRoot}/build/*.png`
];
const productName = process.env.NN_PRODUCT_NAME || "Notesnook";
const appId = process.env.NN_APP_ID || "org.streetwriters.notesnook";
const outputDir = process.env.NN_OUTPUT_DIR || "output";
const linuxExecutableName = process.env.NN_PRODUCT_NAME
? process.env.NN_PRODUCT_NAME.toLowerCase().replace(/\s+/g, "-")
: "notesnook";
const year = new Date().getFullYear();
const isBeta = pkg.version.includes("-beta");
module.exports = {
appId: appId,
productName: productName,
copyright: `Copyright © ${year} Streetwriters (Private) Limited`,
artifactName: "notesnook_${os}_${arch}.${ext}",
generateUpdatesFilesForAllChannels: true,
asar: false,
files: [
"!*.chunk.js.map",
"!*.chunk.js.LICENSE.txt",
...buildFiles,
"!node_modules${/*}",
"node_modules/better-sqlite3-multiple-ciphers/build/Release/better_sqlite3.node",
"node_modules/better-sqlite3-multiple-ciphers/lib",
"node_modules/better-sqlite3-multiple-ciphers/package.json",
"node_modules/file-uri-to-path",
"node_modules/bindings",
"node_modules/node-gyp-build",
"node_modules/sqlite-better-trigram",
"node_modules/sodium-native/prebuilds/${platform}-${arch}",
{
from: "node_modules/sqlite-better-trigram-linux-${arch}",
to: "node_modules/sqlite-better-trigram-linux-${arch}"
},
{
from: "node_modules/sqlite-better-trigram-darwin-${arch}",
to: "node_modules/sqlite-better-trigram-darwin-${arch}"
},
{
from: "node_modules/sqlite-better-trigram-windows-${arch}",
to: "node_modules/sqlite-better-trigram-windows-${arch}"
},
"node_modules/sodium-native/index.js",
"node_modules/sodium-native/package.json"
],
afterPack: "./scripts/removeLocales.js",
mac: {
bundleVersion: "240",
minimumSystemVersion: "10.12.0",
target: [
{
target: "dmg",
arch: ["arm64", "x64"]
},
{
target: "zip",
arch: ["arm64", "x64"]
}
],
category: "public.app-category.productivity",
darkModeSupport: true,
type: "distribution",
hardenedRuntime: true,
entitlements: "assets/entitlements.mac.plist",
entitlementsInherit: "assets/entitlements.mac.plist",
gatekeeperAssess: false,
icon: "assets/icons/app.icns",
notarize: true
},
dmg: {
contents: [
{
x: 130,
y: 220
},
{
x: 410,
y: 220,
type: "link",
path: "/Applications"
}
],
icon: "assets/icons/app.icns",
title: "Install Notesnook"
},
mas: {
entitlements: "assets/entitlements.mas.plist",
entitlementsInherit: "assets/entitlements.mas.inherit.plist",
entitlementsLoginHelper: "assets/entitlements.mas.loginhelper.plist",
hardenedRuntime: true
},
win: {
target: [
{
target: "nsis",
arch: ["x64", "arm64"]
},
{
target: "portable",
arch: ["x64", "arm64"]
}
],
signtoolOptions: {
signingHashAlgorithms: ["sha256"],
sign: "./scripts/sign.js"
},
icon: "assets/icons/app.ico"
},
portable: {
artifactName: "notesnook_${os}_${arch}_portable.${ext}"
},
nsis: {
oneClick: true,
createDesktopShortcut: "always",
deleteAppDataOnUninstall: true
},
linux: {
target: [
{
target: "AppImage",
arch: ["x64", "arm64"]
},
{
target: "snap",
arch: ["x64", "arm64"]
}
],
category: "Office",
icon: "assets/icons/app.icns",
description: "Your private note taking space",
executableName: linuxExecutableName,
desktop: {
actions: [
{
id: "new-note",
name: "New note",
args: "new note"
},
{
id: "new-notebook",
name: "New notebook",
args: "new notebook"
},
{
id: "new-reminder",
name: "New reminder",
args: "new reminder"
}
]
}
},
snap: {
autoStart: false,
confinement: "strict",
allowNativeWayland: true
},
extraResources: ["app-update.yml", "./assets/**"],
extraMetadata: {
main: path.join(buildRoot, "build", "electron.js")
},
directories: {
buildResources: "assets",
output: outputDir
},
publish: [
{
provider: "github",
repo: "notesnook",
owner: "streetwriters",
channel: isBeta ? "beta" : "latest"
}
]
};

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "3.0.23",
"version": "3.1.0-beta.1",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/cjs/index.js",
@@ -25,18 +25,18 @@
"repository": "https://github.com/streetwriters/notesnook",
"license": "GPL-3.0-or-later",
"dependencies": {
"@lingui/core": "4.11.4",
"@lingui/core": "5.1.2",
"@notesnook/intl": "file:../../packages/intl",
"@trpc/client": "10.45.2",
"@trpc/server": "10.45.2",
"better-sqlite3-multiple-ciphers": "11.5.0",
"electron-trpc": "0.6.1",
"electron-trpc": "0.7.1",
"electron-updater": "^6.3.4",
"icojs": "^0.19.4",
"sqlite-better-trigram": "^0.0.2",
"sqlite-better-trigram": "0.0.2",
"typed-emitter": "^2.1.0",
"yargs": "^17.7.2",
"zod": "^3.23.8"
"zod": "3.23.8"
},
"devDependencies": {
"@streetwriters/kysely": "^0.27.4",
@@ -45,15 +45,15 @@
"chokidar": "^4.0.1",
"electron": "^31.7.4",
"electron-builder": "^25.1.8",
"esbuild": "^0.24.0",
"esbuild": "0.21.5",
"node-abi": "^3.68.0",
"node-gyp-build": "^4.8.2",
"playwright": "^1.48.2",
"prebuildify": "^6.0.1",
"slugify": "^1.6.6",
"slugify": "1.6.6",
"tree-kill": "^1.2.2",
"undici": "^6.19.8",
"vitest": "^2.1.5"
"vitest": "^2.1.8"
},
"optionalDependencies": {
"dmg-license": "^1.0.11"
@@ -63,203 +63,14 @@
"staging": "node scripts/build.mjs --run",
"release": "node scripts/build.mjs",
"build": "node ../../scripts/build.mjs",
"bundle": "esbuild electron=./src/main.ts ./src/preload.ts --external:electron --external:fsevents --external:better-sqlite3-multiple-ciphers --external:sodium-native --minify --bundle --outdir=./build --platform=node --tsconfig=tsconfig.json --define:MAC_APP_STORE=false --define:RELEASE=true",
"bundle": "esbuild electron=./src/main.ts ./src/preload.ts --external:electron --external:fsevents --external:better-sqlite3-multiple-ciphers --external:sodium-native --bundle --outdir=./build --platform=node --tsconfig=tsconfig.json --define:MAC_APP_STORE=false --define:RELEASE=true",
"bundle:mas": "esbuild electron=./src/main.ts ./src/preload.ts --minify --external:electron --external:fsevents --bundle --outdir=./build --platform=node --tsconfig=tsconfig.json --define:MAC_APP_STORE=true --define:RELEASE=true",
"postinstall": "patch-package",
"test": "node --test --test-timeout 30000 --test-force-exit"
"test": "vitest run"
},
"author": {
"name": "Streetwriters (Private) Limited",
"email": "support@streetwriters.co",
"url": "https://streetwriters.co"
},
"build": {
"appId": "org.streetwriters.notesnook",
"productName": "Notesnook",
"copyright": "Copyright © 2023 Streetwriters (Private) Limited",
"artifactName": "notesnook_${os}_${arch}.${ext}",
"generateUpdatesFilesForAllChannels": true,
"asar": false,
"files": [
"!*.chunk.js.map",
"!*.chunk.js.LICENSE.txt",
"build/",
"!build/screenshots${/*}",
"!build/banner.jpg",
"!build/*.ico",
"!build/*.png",
"!node_modules${/*}",
"node_modules/better-sqlite3-multiple-ciphers/build/Release/better_sqlite3.node",
"node_modules/better-sqlite3-multiple-ciphers/lib",
"node_modules/better-sqlite3-multiple-ciphers/package.json",
"node_modules/file-uri-to-path",
"node_modules/bindings",
"node_modules/node-gyp-build",
"node_modules/sqlite-better-trigram",
"node_modules/sodium-native/prebuilds/${platform}-${arch}",
{
"from": "node_modules/sqlite-better-trigram-linux-${arch}",
"to": "node_modules/sqlite-better-trigram-linux-${arch}"
},
{
"from": "node_modules/sqlite-better-trigram-darwin-${arch}",
"to": "node_modules/sqlite-better-trigram-darwin-${arch}"
},
{
"from": "node_modules/sqlite-better-trigram-windows-${arch}",
"to": "node_modules/sqlite-better-trigram-windows-${arch}"
},
"node_modules/sodium-native/index.js",
"node_modules/sodium-native/package.json"
],
"afterPack": "./scripts/removeLocales.js",
"mac": {
"bundleVersion": "240",
"minimumSystemVersion": "10.12.0",
"target": [
{
"target": "dmg",
"arch": [
"arm64",
"x64"
]
},
{
"target": "zip",
"arch": [
"arm64",
"x64"
]
}
],
"category": "public.app-category.productivity",
"darkModeSupport": true,
"type": "distribution",
"hardenedRuntime": true,
"entitlements": "assets/entitlements.mac.plist",
"entitlementsInherit": "assets/entitlements.mac.plist",
"gatekeeperAssess": false,
"icon": "assets/icons/app.icns",
"notarize": true
},
"dmg": {
"contents": [
{
"x": 130,
"y": 220
},
{
"x": 410,
"y": 220,
"type": "link",
"path": "/Applications"
}
],
"icon": "assets/icons/app.icns",
"title": "Install Notesnook"
},
"mas": {
"entitlements": "assets/entitlements.mas.plist",
"entitlementsInherit": "assets/entitlements.mas.inherit.plist",
"entitlementsLoginHelper": "assets/entitlements.mas.loginhelper.plist",
"hardenedRuntime": true
},
"win": {
"target": [
{
"target": "nsis",
"arch": [
"x64",
"arm64"
]
},
{
"target": "portable",
"arch": [
"x64",
"arm64"
]
}
],
"signtoolOptions": {
"signingHashAlgorithms": [
"sha256"
],
"sign": "./scripts/sign.js"
},
"icon": "assets/icons/app.ico"
},
"portable": {
"artifactName": "notesnook_${os}_${arch}_portable.${ext}"
},
"nsis": {
"oneClick": true,
"createDesktopShortcut": "always",
"deleteAppDataOnUninstall": true
},
"linux": {
"target": [
{
"target": "AppImage",
"arch": [
"x64",
"arm64"
]
},
{
"target": "snap",
"arch": [
"x64",
"arm64"
]
}
],
"category": "Office",
"icon": "assets/icons/app.icns",
"description": "Your private note taking space",
"executableName": "notesnook",
"desktop": {
"actions": [
{
"id": "new-note",
"name": "New note",
"args": "new note"
},
{
"id": "new-notebook",
"name": "New notebook",
"args": "new notebook"
},
{
"id": "new-reminder",
"name": "New reminder",
"args": "new reminder"
}
]
}
},
"snap": {
"autoStart": false,
"confinement": "strict",
"allowNativeWayland": true
},
"extraResources": [
"app-update.yml",
"./assets/**"
],
"extraMetadata": {
"main": "./build/electron.js"
},
"directories": {
"buildResources": "assets",
"output": "./output/"
},
"publish": [
{
"provider": "github",
"repo": "notesnook",
"owner": "streetwriters"
}
]
}
}

View File

@@ -29,10 +29,12 @@ import { patchBetterSQLite3 } from "./patch-better-sqlite3.mjs";
const args = yargs(process.argv);
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const root = args.root || path.join(__dirname, "..");
const skipTscBuild = args.skipTscBuild || false;
const webAppPath = path.resolve(path.join(__dirname, "..", "..", "web"));
await fs.rm("./build/", { force: true, recursive: true });
await fs.rm(path.join(root, "build"), { force: true, recursive: true });
if (args.rebuild || !existsSync(path.join(webAppPath, "build"))) {
await exec(
@@ -44,21 +46,25 @@ if (args.rebuild || !existsSync(path.join(webAppPath, "build"))) {
// temporary until there's support for prebuilt binaries for linux ARM
if (os.platform() === "linux") await patchBetterSQLite3();
await fs.cp(path.join(webAppPath, "build"), "build", {
await fs.cp(path.join(webAppPath, "build"), path.join(root, "build"), {
recursive: true,
force: true
});
if (args.variant === "mas") {
await exec(`yarn run bundle:mas`);
await exec(`yarn run bundle:mas --outdir=${path.join(root, "build")}`);
} else {
await exec(`yarn run bundle`);
await exec(`yarn run bundle --outdir=${path.join(root, "build")}`);
}
await exec(`yarn run build`);
if (!skipTscBuild) {
await exec(`yarn run build`);
}
if (args.run) {
await exec(`yarn electron-builder --dir --${process.arch}`);
await exec(
`yarn electron-builder --dir --${process.arch} --config=electron-builder.config.js`
);
if (process.platform === "win32") {
await exec(`.\\output\\win-unpacked\\Notesnook.exe`);
} else if (process.platform === "darwin") {

View File

@@ -29,7 +29,7 @@ import { dirname } from "path";
import { resolvePath } from "../utils/resolve-path";
import { observable } from "@trpc/server/observable";
import { AssetManager } from "../utils/asset-manager";
import { isFlatpak } from "../utils";
import { isFlatpak, isSnap } from "../utils";
import { setupDesktopIntegration } from "../utils/desktop-integration";
import { rm } from "fs/promises";
import { disableCustomDns, enableCustomDns } from "../utils/custom-dns";
@@ -49,6 +49,7 @@ const NotificationOptions = z.object({
export const osIntegrationRouter = t.router({
isFlatpak: t.procedure.query(() => isFlatpak()),
isSnap: t.procedure.query(() => isSnap()),
zoomFactor: t.procedure.query(() => config.zoomFactor),
setZoomFactor: t.procedure.input(z.number()).mutation(({ input: factor }) => {

View File

@@ -23,6 +23,7 @@ import { CancellationToken, autoUpdater } from "electron-updater";
import type { AppUpdaterEvents } from "electron-updater/out/AppUpdater";
import { z } from "zod";
import { config } from "../utils/config";
import { app } from "electron";
type UpdateInfo = { version: string };
type Progress = { percent: number };
@@ -33,6 +34,7 @@ let downloadTimeout: NodeJS.Timeout | undefined = undefined;
export const updaterRouter = t.router({
autoUpdates: t.procedure.query(() => config.automaticUpdates),
releaseTrack: t.procedure.query(() => config.releaseTrack),
install: t.procedure.query(() => autoUpdater.quitAndInstall()),
download: t.procedure.query(async () => {
if (cancellationToken) return;
@@ -67,7 +69,13 @@ export const updaterRouter = t.router({
.mutation(({ input: { enabled } }) => {
config.automaticUpdates = enabled;
}),
changeReleaseTrack: t.procedure
.input(z.object({ track: z.string() }))
.mutation(({ input: { track } }) => {
config.releaseTrack = track;
app.relaunch();
app.exit();
}),
onChecking: createSubscription("checking-for-update"),
onDownloaded: createSubscription<"update-downloaded", UpdateInfo>(
"update-downloaded"

View File

@@ -85,6 +85,8 @@ async function createWindow() {
const mainWindowState = new WindowState({});
const mainWindow = new BrowserWindow({
show: !cliOptions.hidden,
paintWhenInitiallyHidden: cliOptions.hidden,
skipTaskbar: cliOptions.hidden,
x: mainWindowState.x,
y: mainWindowState.y,
width: mainWindowState.width,
@@ -101,7 +103,10 @@ async function createWindow() {
...(config.desktopSettings.nativeTitlebar
? {}
: {
titleBarStyle: process.platform === "win32" || process.platform === "darwin" ? "hidden" : "default",
titleBarStyle:
process.platform === "win32" || process.platform === "darwin"
? "hidden"
: "default",
frame: process.platform === "win32" || process.platform === "darwin",
titleBarOverlay: {
height: 37,
@@ -142,6 +147,11 @@ async function createWindow() {
await AssetManager.loadIcons();
setupDesktopIntegration(config.desktopSettings);
mainWindow.webContents.session.setPermissionRequestHandler(
(webContents, permission, callback) => {
callback(permission === "geolocation" ? false : true);
}
);
mainWindow.webContents.session.setSpellCheckerDictionaryDownloadURL(
"http://dictionaries.notesnook.com/"
);

View File

@@ -20,19 +20,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { autoUpdater } from "electron-updater";
import { config } from "./config";
const CHANNEL = autoUpdater.currentVersion.raw.endsWith("-beta")
? "beta"
: "latest";
async function configureAutoUpdater() {
const releaseTrack =
config.releaseTrack === "stable" ? "latest" : config.releaseTrack;
autoUpdater.setFeedURL({
provider: "generic",
url: `https://notesnook.com/api/v1/releases/${process.platform}/${CHANNEL}`,
url: `https://notesnook.com/api/v1/releases/${process.platform}/${releaseTrack}`,
useMultipleRangeRequest: false,
channel: CHANNEL
channel: releaseTrack
});
autoUpdater.autoDownload = config.automaticUpdates;
autoUpdater.allowDowngrade = false;
autoUpdater.allowDowngrade =
// only allow downgrade if the current version is a prerelease
// and the user has changed the release track to stable
config.releaseTrack === "stable" &&
autoUpdater.currentVersion.prerelease.length > 0;
autoUpdater.allowPrerelease = false;
autoUpdater.autoInstallOnAppQuit = true;
autoUpdater.disableWebInstaller = true;

View File

@@ -20,6 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { nativeTheme } from "electron";
import { JSONStorage } from "./json-storage";
import { z } from "zod";
import { autoUpdater } from "electron-updater";
export const DesktopIntegration = z.object({
autoStart: z.boolean().optional(),
@@ -46,6 +47,9 @@ export const config = {
automaticUpdates: true,
proxyRules: "",
customDns: true,
releaseTrack: autoUpdater.currentVersion.raw.includes("-beta")
? "beta"
: "stable",
backgroundColor: nativeTheme.themeSource === "dark" ? "#0f0f0f" : "#ffffff",
windowControlsIconColor:

View File

@@ -29,3 +29,7 @@ export function isDevelopment() {
export function isFlatpak() {
return existsSync("/.flatpak-info");
}
export function isSnap() {
return process.env.SNAP !== undefined;
}

View File

@@ -0,0 +1,40 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
testTimeout: process.env.CI ? 120 * 1000 : 120 * 1000,
sequence: {
concurrent: true,
shuffle: true
},
dir: "./__tests__/",
exclude: [
"**/node_modules/**",
"**/dist/**",
"**/cypress/**",
"**/.{idea,git,cache,output,temp}/**",
"**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build,eslint,prettier}.config.*",
"**/test-results/**",
"**/test-artifacts/**"
]
}
});

View File

@@ -32,6 +32,7 @@ import { withErrorBoundry } from "./components/exception-handler";
import GlobalSafeAreaProvider from "./components/globalsafearea";
import { useAppEvents } from "./hooks/use-app-events";
import { ApplicationHolder } from "./navigation";
import { NotePreviewConfigure } from "./screens/note-preview-configure";
import { themeTrpcClient } from "./screens/settings/theme-selector";
import Notifications from "./services/notifications";
import SettingsService from "./services/settings";
@@ -47,7 +48,7 @@ if (appLockEnabled || appLockMode !== "none") {
useUserStore.getState().lockApp(true);
}
const App = () => {
const App = (props: { configureMode: "note-preview" }) => {
useAppEvents();
//@ts-ignore
globalThis["IS_MAIN_APP_RUNNING"] = true;
@@ -61,6 +62,7 @@ const App = () => {
TipManager.init();
}, 100);
}, []);
return (
<View
style={{
@@ -90,7 +92,11 @@ const App = () => {
width: "100%"
}}
>
<ApplicationHolder />
{props.configureMode === "note-preview" ? (
<NotePreviewConfigure />
) : (
<ApplicationHolder />
)}
</GestureHandlerRootView>
<AppLockedOverlay />
</View>
@@ -103,8 +109,8 @@ let currTheme =
: SettingsService.getProperty("lighTheme");
useThemeEngineStore.getState().setTheme(currTheme);
export const withTheme = (Element: () => JSX.Element) => {
return function AppWithThemeProvider() {
export const withTheme = (Element: (props: any) => JSX.Element) => {
return function AppWithThemeProvider(props: any) {
const [colorScheme, darkTheme, lightTheme] = useThemeStore((state) => [
state.colorScheme,
state.darkTheme,
@@ -123,13 +129,14 @@ export const withTheme = (Element: () => JSX.Element) => {
})
.then((theme) => {
if (theme) {
console.log(theme.version, "theme updated");
theme.colorScheme === "dark"
? useThemeStore.getState().setDarkTheme(theme)
: useThemeStore.getState().setLightTheme(theme);
}
})
.catch(console.log);
.catch(() => {
/* empty */
});
}, 1000);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -144,7 +151,7 @@ export const withTheme = (Element: () => JSX.Element) => {
return (
<I18nProvider i18n={i18n}>
<Element />
<Element {...props} />
</I18nProvider>
);
};

View File

@@ -126,7 +126,6 @@ export async function setAppLockVerificationCipher(appLockPassword: string) {
DatabaseLogger.info("setAppLockVerificationCipher");
} catch (e) {
DatabaseLogger.error(e);
console.log(e);
}
}
@@ -310,7 +309,6 @@ export async function getCryptoKey() {
return key;
} catch (e) {
console.log("getCryptoKey", e);
DatabaseLogger.error(e);
}
}

View File

@@ -39,8 +39,6 @@ export async function setupDatabase(password?: string) {
const key = await getDatabaseKey(password);
if (!key) throw new Error(strings.databaseSetupFailed());
console.log("Opening database with key:", !!key);
database.host({
API_HOST: "https://api.notesnook.com",
AUTH_HOST: "https://auth.streetwriters.co",

View File

@@ -27,13 +27,9 @@ import RNFetchBlob from "react-native-blob-util";
*/
export async function compressToBase64(path, type) {
const { width: screenWidth, scale } = Dimensions.get("window");
console.log("COMPRESSING TO BASE64...");
return new Promise((resolve) => {
console.log(path, "image path...");
Image.getSize(path, async (width) => {
console.log("image width", width);
const response = await ImageResizer.createResizedImage(
path,
screenWidth * scale,
@@ -55,10 +51,12 @@ export async function compressToBase64(path, type) {
: response.uri,
"base64"
);
RNFetchBlob.fs.unlink(path.replace("file://", "")).catch(console.log);
RNFetchBlob.fs
.unlink(response.uri.replace("file://", ""))
.catch(console.log);
RNFetchBlob.fs.unlink(path.replace("file://", "")).catch(() => {
/* empty */
});
RNFetchBlob.fs.unlink(response.uri.replace("file://", "")).catch(() => {
/* empty */
});
resolve(base64);
});
@@ -80,6 +78,8 @@ export async function compressToFile(path, type) {
onlyScaleDown: true
}
);
RNFetchBlob.fs.unlink(path.replace("file://", "")).catch(console.log);
RNFetchBlob.fs.unlink(path.replace("file://", "")).catch(() => {
/* empty */
});
return response.uri;
}

View File

@@ -83,7 +83,9 @@ export async function downloadAttachments(attachmentIds: string[]) {
const isCancelled = () => {
if (useAttachmentStore.getState().downloading?.[groupId]?.canceled) {
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
RNFetchBlob.fs.unlink(zipSourceFolder).catch(() => {
/* empty */
});
useAttachmentStore.getState().setDownloading({
groupId,
current: 0,
@@ -188,9 +190,13 @@ export async function downloadAttachments(attachmentIds: string[]) {
ToastManager.error(e as Error, "Error zipping attachments");
}
// Remove source & zip file from cache.
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
RNFetchBlob.fs.unlink(zipSourceFolder).catch(() => {
/* empty */
});
if (Platform.OS === "android") {
RNFetchBlob.fs.unlink(zipOutputFile).catch(console.log);
RNFetchBlob.fs.unlink(zipOutputFile).catch(() => {
/* empty */
});
}
}
@@ -295,8 +301,6 @@ export default async function downloadAttachment(
options?.cache ? "cache" : "file"
);
console.log("file URI", fileUri, options?.cache);
if (!options?.silent) {
ToastManager.show({
heading: strings.network.downloadSuccess(),
@@ -323,10 +327,14 @@ export default async function downloadAttachment(
if (attachment.dateUploaded) {
RNFetchBlob.fs
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}`)
.catch(console.log);
.catch(() => {
/* empty */
});
RNFetchBlob.fs
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}_dcache`)
.catch(console.log);
.catch(() => {
/* empty */
});
}
useAttachmentStore.getState().setDownloading({

View File

@@ -123,7 +123,9 @@ export async function downloadFile(
cancelToken.cancel = async (reason) => {
useAttachmentStore.getState().remove(filename);
request.cancel();
RNFetchBlob.fs.unlink(tempFilePath).catch(console.log);
RNFetchBlob.fs.unlink(tempFilePath).catch(() => {
/* empty */
});
DatabaseLogger.log(`Download cancelled: ${reason} ${filename}`);
};
@@ -142,7 +144,9 @@ export async function downloadFile(
useAttachmentStore.getState().remove(filename);
if (await exists(originalFilePath)) {
await RNFetchBlob.fs.unlink(originalFilePath).catch(console.log);
await RNFetchBlob.fs.unlink(originalFilePath).catch(() => {
/* empty */
});
}
await RNFetchBlob.fs.mv(tempFilePath, originalFilePath);
@@ -169,8 +173,12 @@ export async function downloadFile(
}
useAttachmentStore.getState().remove(filename);
RNFetchBlob.fs.unlink(tempFilePath).catch(console.log);
RNFetchBlob.fs.unlink(originalFilePath).catch(console.log);
RNFetchBlob.fs.unlink(tempFilePath).catch(() => {
/* empty */
});
RNFetchBlob.fs.unlink(originalFilePath).catch(() => {
/* empty */
});
DatabaseLogger.error(e, "Download failed: ", {
url
});
@@ -188,7 +196,7 @@ export async function checkAttachment(hash: string) {
try {
const size = await getUploadedFileSize(hash);
console.log("File Size", size);
if (size === -1) return { success: true };
if (size === 0)

View File

@@ -60,7 +60,9 @@ export async function readEncrypted<TOutputFormat extends DataFormat>(
return output as Output<TOutputFormat>;
} catch (e) {
RNFetchBlob.fs.unlink(path).catch(console.log);
RNFetchBlob.fs.unlink(path).catch(() => {
/* empty */
});
DatabaseLogger.error(e);
}
}
@@ -90,8 +92,9 @@ export async function writeEncryptedBase64(
type: "url"
});
RNFetchBlob.fs.unlink(filepath).catch(console.log);
console.log("encrypted file output: ", output);
RNFetchBlob.fs.unlink(filepath).catch(() => {
/* empty */
});
return {
...output,
@@ -106,7 +109,9 @@ export async function deleteFile(
await createCacheDir();
const localFilePath = cacheDir + `/${filename}`;
if (!requestOptions) {
RNFetchBlob.fs.unlink(localFilePath).catch(console.log);
RNFetchBlob.fs.unlink(localFilePath).catch(() => {
/* empty */
});
return true;
}
@@ -117,7 +122,9 @@ export async function deleteFile(
const status = response.info().status;
const ok = status >= 200 && status < 300;
if (ok) {
RNFetchBlob.fs.unlink(localFilePath).catch(console.log);
RNFetchBlob.fs.unlink(localFilePath).catch(() => {
/* empty */
});
}
return ok;
} catch (e) {
@@ -134,10 +141,14 @@ export async function clearFileStorage() {
const oldCache = await RNFetchBlob.fs.ls(cacheDirOld);
for (const file of files) {
await RNFetchBlob.fs.unlink(cacheDir + `/${file}`).catch(console.log);
await RNFetchBlob.fs.unlink(cacheDir + `/${file}`).catch(() => {
/* empty */
});
}
for (const file of oldCache) {
await RNFetchBlob.fs.unlink(cacheDirOld + `/${file}`).catch(console.log);
await RNFetchBlob.fs.unlink(cacheDirOld + `/${file}`).catch(() => {
/* empty */
});
}
} catch (e) {
DatabaseLogger.error(e, "clearFileStorage");
@@ -161,20 +172,19 @@ export async function migrateFilesFromCache() {
const migratedFilesPath = cacheDir + "/.migrated_1";
const migrated = await RNFetchBlob.fs.exists(migratedFilesPath);
if (migrated) {
console.log("Files migrated already");
return;
}
const files = await RNFetchBlob.fs.ls(cacheDir);
console.log("Files to migrate:", files.join(","));
const oldCache = await RNFetchBlob.fs.ls(cacheDirOld);
for (const file of oldCache) {
if (file.startsWith("org.") || file.startsWith("com.")) continue;
RNFetchBlob.fs
.mv(cacheDirOld + `/${file}`, cacheDir + `/${file}`)
.catch(console.log);
console.log("Moved", file);
.catch(() => {
/* empty */
});
}
await RNFetchBlob.fs.createFile(migratedFilesPath, "1", "utf8");
} catch (e) {
@@ -183,13 +193,17 @@ export async function migrateFilesFromCache() {
}
export async function clearCache() {
await RNFetchBlob.fs.unlink(cacheDir).catch(console.log);
await RNFetchBlob.fs.unlink(cacheDir).catch(() => {
/* empty */
});
await createCacheDir();
eSendEvent("cache-cleared");
}
export async function deleteCacheFileByPath(path: string) {
await RNFetchBlob.fs.unlink(path).catch(console.log);
await RNFetchBlob.fs.unlink(path).catch(() => {
/* empty */
});
}
export async function deleteCacheFileByName(name: string) {
@@ -198,15 +212,21 @@ export async function deleteCacheFileByName(name: string) {
? await (RNFetchBlob.fs as any).pathForAppGroup(IOS_APPGROUPID)
: null;
const appGroupPath = `${iosAppGroup}/${name}`;
await RNFetchBlob.fs.unlink(appGroupPath).catch(console.log);
await RNFetchBlob.fs.unlink(`${cacheDir}/${name}`).catch(console.log);
await RNFetchBlob.fs.unlink(appGroupPath).catch(() => {
/* empty */
});
await RNFetchBlob.fs.unlink(`${cacheDir}/${name}`).catch(() => {
/* empty */
});
}
export async function deleteDCacheFiles() {
const files = await RNFetchBlob.fs.ls(cacheDir);
for (const file of files) {
if (file.includes("_dcache") || file.startsWith("NN_")) {
await RNFetchBlob.fs.unlink(file).catch(console.log);
await RNFetchBlob.fs.unlink(file).catch(() => {
/* empty */
});
}
}
}
@@ -245,7 +265,9 @@ export async function exists(filename: string) {
);
RNFetchBlob.fs
.unlink(existsInAppGroup ? appGroupPath : path)
.catch(console.log);
.catch(() => {
/* empty */
});
return false;
}
@@ -281,7 +303,7 @@ export async function getCacheSize() {
await createCacheDir();
const stat = await RNFetchBlob.fs.lstat(`file://` + cacheDir);
let total = 0;
console.log("Total files", stat.length);
stat.forEach((file) => {
total += parseInt(file.size as unknown as string);
});

View File

@@ -115,7 +115,7 @@ export const FileSizeResult = {
Error: -1
};
export async function getUploadedFileSize(hash: string) {
export async function getUploadedFileSize(hash: string, retry = 0) {
try {
const url = `${hosts.API_HOST}/s3?name=${hash}`;
const token = await db.tokenManager.getAccessToken();
@@ -123,8 +123,24 @@ export async function getUploadedFileSize(hash: string) {
method: "HEAD",
headers: { Authorization: `Bearer ${token}` }
});
if (
!attachmentInfo.ok ||
attachmentInfo.headers?.get("content-length") === null
) {
if (retry < 3) {
DatabaseLogger.log(`Retrying file size check: ${hash}, ${retry}`);
return getUploadedFileSize(hash, retry + 1);
}
throw new Error(
`File size check failed: ${hash}, ${
attachmentInfo.status
}, ${attachmentInfo.headers?.get("content-length")}`
);
}
const contentLength = parseInt(
attachmentInfo.headers?.get("content-length") || "0"
attachmentInfo.headers?.get("content-length") as string
);
return isNaN(contentLength) ? FileSizeResult.Empty : contentLength;
} catch (e) {

View File

@@ -17,9 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
function info(context: string, ...logs: unknown[]) {
console.log(`${new Date().toLocaleDateString()}::info::${context}:`, ...logs);
}
function info(context: string, ...logs: unknown[]) {}
function error(context: string, ...logs: unknown[]) {
console.log(

View File

@@ -41,7 +41,9 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
await sleep(500);
}
if (item.type === "link") {
Linking.openURL(item.data).catch(console.log);
Linking.openURL(item.data).catch(() => {
/* empty */
});
} else if (item.type === "promo") {
presentSheet({
component: (

View File

@@ -112,14 +112,19 @@ const AppLockedOverlay = () => {
if (
!biometricsAuthEnabled ||
!(await BiometricService.isBiometryAvailable())
)
) {
return;
}
biometricUnlockAwaitingUserInput.current = true;
if (Platform.OS === "android") {
const activityName = await NotesnookModule.getActivityName();
if (activityName !== "MainActivity") return;
if (
activityName !== "MainActivity" &&
activityName !== "NotePreviewConfigureActivity"
)
return;
}
useSettingStore.getState().setRequestBiometrics(true);
@@ -180,7 +185,6 @@ const AppLockedOverlay = () => {
DatabaseLogger.info("Locking app on entering foreground");
useUserStore.getState().lockApp(true);
}
if (
!(
biometricUnlockAwaitingUserInput.current ||

View File

@@ -182,18 +182,17 @@ const Actions = ({
relations
.map((relation) => relation.fromId)
.forEach(async (id) => {
const tab = useTabStore.getState().getTabForNote(id);
if (tab !== undefined) {
const isFocused = useTabStore.getState().currentTab === tab;
useTabStore.getState().forEachNoteTab(id, async (tab) => {
const isFocused = useTabStore.getState().currentTab === tab.id;
if (isFocused) {
eSendEvent(eOnLoadNote, {
item: await db.notes.note(id),
forced: true
});
} else {
editorController.current.commands.setLoading(true, tab);
editorController.current.commands.setLoading(true, tab.id);
}
}
});
});
close?.();
},

View File

@@ -528,7 +528,9 @@ export const AttachmentDialog = ({
setAttachments(results);
setLoading(false);
})
.catch(console.log);
.catch(() => {
/* empty */
});
}}
/>
)

View File

@@ -88,7 +88,6 @@ export const ChangePassword = () => {
await sleep(300);
eSendEvent(eOpenRecoveryKeyDialog);
} catch (e) {
console.log(e.stack);
setLoading(false);
ToastManager.show({
heading: strings.passwordChangeFailed(),

View File

@@ -70,7 +70,7 @@ export const useLogin = (onFinishLogin, sessionExpired = false) => {
switch (step) {
case LoginSteps.emailAuth: {
const mfaInfo = await db.user.authenticateEmail(email.current);
console.log("email auth", mfaInfo);
if (mfaInfo) {
TwoFactorVerification.present(
async (mfa, callback) => {

View File

@@ -80,8 +80,9 @@ export const Dialog = ({ context = "global" }) => {
return;
}
}
hide();
setChecked(false);
values.current.inputValue = undefined;
setVisible(false);
};
const show = useCallback(
@@ -104,13 +105,14 @@ export const Dialog = ({ context = "global" }) => {
eUnSubscribeEvent(eOpenSimpleDialog, show);
eUnSubscribeEvent(eCloseSimpleDialog, hide);
};
}, [show]);
}, [hide, show]);
const hide = () => {
const hide = React.useCallback(() => {
setChecked(false);
values.current.inputValue = undefined;
setVisible(false);
};
dialogInfo.onClose?.();
}, [dialogInfo]);
const onNegativePress = async () => {
if (dialogInfo.onClose) {

View File

@@ -180,7 +180,7 @@ AttachImage.present = (response: ImageType[], context?: string) => {
close={close}
onAttach={(result) => {
resolved = true;
console.log("closing");
resolve(result);
close?.();
}}

View File

@@ -307,9 +307,7 @@ const PDFPreview = () => {
password={password}
maxScale={6}
onError={onError}
onPressLink={(uri) => {
console.log(`Link pressed: ${uri}`);
}}
onPressLink={(uri) => {}}
style={{
flex: 1,
width: width,

View File

@@ -62,7 +62,7 @@ export default function Progress() {
eSubscribeEvent(PROGRESS_EVENTS.start, (options: ProgressOptions) => {
setProgress(options.progress);
cancelCallback.current = options.cancelCallback;
console.log("options", options.fillBackground);
setData({
title: options.title,
paragraph: options.paragraph,

View File

@@ -336,7 +336,7 @@ export class VaultDialog extends Component {
);
});
}
console.log("VAULT UPDATED EVENT");
eSendEvent("vaultUpdated");
this.setState({
loading: false
@@ -406,7 +406,6 @@ export class VaultDialog extends Component {
} else {
await db.vault.add(this.state.note.id);
console.log("update note event...");
eSendEvent(eUpdateNoteInEditor, this.state.note, true);
this.close();
@@ -543,7 +542,6 @@ export class VaultDialog extends Component {
this.close();
})
.catch((e) => {
console.log("Error", e);
this._takeErrorAction(e);
});
}

View File

@@ -78,10 +78,10 @@ class ExceptionHandler extends React.Component<{
}
export const withErrorBoundry = (Element: React.ElementType, name: string) => {
return function ErrorBoundary() {
return function ErrorBoundary(props: any) {
return (
<ExceptionHandler component={name}>
<Element />
<Element {...props} />
</ExceptionHandler>
);
};

View File

@@ -54,7 +54,7 @@ export const Header = ({
onSearch
}: {
onLeftMenuButtonPress?: () => void;
renderedInRoute: RouteName;
renderedInRoute?: RouteName;
id?: string;
title: string;
headerRightButtons?: HeaderRightButton[];

View File

@@ -41,7 +41,7 @@ export const RightMenus = ({
onSearch
}: {
headerRightButtons?: HeaderRightButton[];
renderedInRoute: RouteName;
renderedInRoute?: RouteName;
id?: string;
onPressDefaultRightButton?: () => void;
search?: boolean;

View File

@@ -41,7 +41,7 @@ export const Title = ({
isHiddenOnRender?: boolean;
accentColor?: string;
isBeta?: boolean;
renderedInRoute: string;
renderedInRoute?: string;
id?: string;
}) => {
const { colors } = useThemeColors();

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Sodium from "@ammarahmed/react-native-sodium";
import { DataURL } from "@notesnook/core";
import { DataURL, getFileNameWithExtension } from "@notesnook/core";
import type { ImageAttributes } from "@notesnook/editor";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef, useState } from "react";
@@ -86,8 +86,12 @@ const ImagePreview = () => {
return;
}
const attachment = await db.attachments.attachment(hash);
const path = `${cacheDir}/${"NN_" + attachment?.filename}`;
await RNFetchBlob.fs.mv(`${cacheDir}/${uri}`, path).catch(console.log);
const path = `${cacheDir}/${
"NN_" + (await getFileNameWithExtension(hash, attachment?.mimeType))
}`;
await RNFetchBlob.fs.mv(`${cacheDir}/${uri}`, path).catch(() => {
/* empty */
});
setImage("file://" + path);
setLoading(false);
}, 100);
@@ -95,7 +99,9 @@ const ImagePreview = () => {
const close = React.useCallback(() => {
image &&
RNFetchBlob.fs.unlink(image.replace("file://", "")).catch(console.log);
RNFetchBlob.fs.unlink(image.replace("file://", "")).catch(() => {
/* empty */
});
setImage(undefined);
setVisible(false);
}, [image]);
@@ -125,13 +131,18 @@ const ImagePreview = () => {
<IconButton
name="share"
color="white"
style={{
borderWidth: 0
}}
onPress={async () => {
useSettingStore
.getState()
.setAppDidEnterBackgroundForAction(true);
await Share.open({
url: image
}).catch(console.log);
}).catch(() => {
/* empty */
});
useSettingStore
.getState()
.setAppDidEnterBackgroundForAction(false);
@@ -147,7 +158,7 @@ const ImagePreview = () => {
</View>
</View>
),
[close, image, showHeader]
[close, image, insets.top, showHeader]
);
return (

View File

@@ -76,7 +76,8 @@ const NoteItem = ({
}: NoteItemProps) => {
const isEditingNote = useTabStore(
(state) =>
state.tabs.find((t) => t.id === state.currentTab)?.noteId === item.id
state.tabs.find((t) => t.id === state.currentTab)?.session?.noteId ===
item.id
);
const { colors } = useThemeColors();
const compactMode = useIsCompactModeEnabled(

View File

@@ -28,7 +28,8 @@ export const Filler = ({ item, color }: { item: Item; color?: string }) => {
const { colors } = useThemeColors();
const isEditingNote = useTabStore(
(state) =>
state.tabs.find((t) => t.id === state.currentTab)?.noteId === item.id
state.tabs.find((t) => t.id === state.currentTab)?.session?.noteId ===
item.id
);
const [selected] = useIsSelected(item);

View File

@@ -153,9 +153,7 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
},
items?.cacheItem(index) ? 100 : 0
);
} catch (e) {
console.log("Error", e);
}
} catch (e) {}
})();
}, [index, items, refreshItem]);

View File

@@ -119,7 +119,6 @@ export const PricingPlans = ({
setLoading(false);
} catch (e) {
setLoading(false);
console.log("error getting sku", e);
}
}, [promo?.promoCode]);
@@ -171,7 +170,6 @@ export const PricingPlans = ({
});
return true;
} catch (e) {
console.log("PROMOCODE ERROR:", code, e);
return false;
}
};
@@ -231,7 +229,6 @@ export const PricingPlans = ({
});
} catch (e) {
setBuying(false);
console.log(e);
}
};

View File

@@ -141,7 +141,7 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
close();
eSendEvent(eOnLoadNote, {
item: item,
presistTab: true
newTab: true
});
if (!DDS.isTab) {
tabBarRef.current?.goToPage(1);

View File

@@ -100,7 +100,6 @@ export const AddNotebookSheet = ({
eSendEvent(eOnNotebookUpdated, parent);
if (notebook) {
setImmediate(() => {
console.log(parent, notebook.id);
eSendEvent(eOnNotebookUpdated, notebook.id);
});
}

View File

@@ -90,13 +90,12 @@ export const NotebookItem = ({
for (const key in keys) {
nextState[key] = !state.initialState[key] ? undefined : "deselected";
}
console.log("Single item selection");
state.setSelection({
[item.id]: "selected",
...nextState
});
} else {
console.log("Multi item selection");
state.markAs(item, "selected");
}
};

View File

@@ -16,8 +16,8 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Note } from "@notesnook/core";
import { EVENTS } from "@notesnook/core";
import { EVENTS, Note } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect } from "react";
import { View } from "react-native";
@@ -25,27 +25,18 @@ import { FlatList } from "react-native-actions-sheet";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../../common/database";
import { useDBItem } from "../../../hooks/use-db-item";
import { useTabStore } from "../../../screens/editor/tiptap/use-tab-store";
import {
TabItem,
useTabStore
} from "../../../screens/editor/tiptap/use-tab-store";
import { editorController } from "../../../screens/editor/tiptap/utils";
import { eSendEvent, presentSheet } from "../../../services/event-manager";
import { eUnlockNote } from "../../../utils/events";
import { SIZE } from "../../../utils/size";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import { Pressable } from "../../ui/pressable";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
type TabItem = {
id: number;
noteId?: string;
previewTab?: boolean;
locked?: boolean;
noteLocked?: boolean;
readonly?: boolean;
pinned?: boolean;
};
const TabItemComponent = (props: {
tab: TabItem;
@@ -53,7 +44,7 @@ const TabItemComponent = (props: {
close?: (ctx?: string | undefined) => void;
}) => {
const { colors } = useThemeColors();
const [item, update] = useDBItem(props.tab.noteId, "note");
const [item, update] = useDBItem(props.tab.session?.noteId, "note");
useEffect(() => {
const syncCompletedSubscription = db.eventManager?.subscribe(
@@ -85,11 +76,11 @@ const TabItemComponent = (props: {
onPress={() => {
if (!props.isFocused) {
useTabStore.getState().focusTab(props.tab.id);
if (props.tab.locked) {
if (props.tab.session?.locked) {
eSendEvent(eUnlockNote);
}
if (!props.tab.noteId) {
if (!props.tab.session?.noteId) {
setTimeout(() => {
editorController?.current?.commands?.focus(props.tab.id);
}, 300);
@@ -97,11 +88,6 @@ const TabItemComponent = (props: {
}
props.close?.();
}}
onLongPress={() => {
useTabStore.getState().updateTab(props.tab.id, {
previewTab: false
});
}}
>
<View
style={{
@@ -112,9 +98,9 @@ const TabItemComponent = (props: {
flexShrink: 1
}}
>
{props.tab.noteLocked ? (
{props.tab.session?.noteLocked ? (
<>
{props.tab.locked ? (
{props.tab.session?.locked ? (
<Icon size={SIZE.md} name="lock" />
) : (
<Icon size={SIZE.md} name="lock-open-outline" />
@@ -122,7 +108,9 @@ const TabItemComponent = (props: {
</>
) : null}
{props.tab.readonly ? <Icon size={SIZE.md} name="pencil-lock" /> : null}
{props.tab.session?.readonly ? (
<Icon size={SIZE.md} name="pencil-lock" />
) : null}
<Paragraph
color={
@@ -130,15 +118,10 @@ const TabItemComponent = (props: {
? colors.selected.paragraph
: colors.primary.paragraph
}
style={{
fontFamily: props.tab.previewTab
? "OpenSans-Italic"
: "OpenSans-Regular"
}}
numberOfLines={1}
size={SIZE.md}
>
{props.tab.noteId
{props.tab.session?.noteId
? item?.title || strings.untitledNote()
: strings.newNote()}
</Paragraph>
@@ -157,8 +140,7 @@ const TabItemComponent = (props: {
color={props.tab.pinned ? colors.primary.accent : colors.primary.icon}
onPress={() => {
useTabStore.getState().updateTab(props.tab.id, {
pinned: !props.tab.pinned,
previewTab: false
pinned: !props.tab.pinned
});
}}
top={0}
@@ -197,6 +179,7 @@ export default function EditorTabs({
}: {
close?: (ctx?: string | undefined) => void;
}) {
const { colors } = useThemeColors();
const [tabs, currentTab] = useTabStore((state) => [
state.tabs,
state.currentTab
@@ -233,25 +216,13 @@ export default function EditorTabs({
}}
>
<Heading size={SIZE.lg}>{strings.tabs()}</Heading>
<Button
<IconButton
onPress={() => {
useTabStore.getState().newTab();
setTimeout(() => {
editorController?.current?.commands?.focus(
useTabStore.getState().currentTab
);
}, 500);
close?.();
}}
title={strings.newTab()}
icon="plus"
style={{
flexDirection: "row",
justifyContent: "flex-start",
borderRadius: 100,
height: 35
}}
iconSize={SIZE.lg}
name="plus"
color={colors.primary.accent}
/>
</View>

View File

@@ -333,13 +333,17 @@ const ExportNotesSheet = ({
if (Platform.OS === "ios") {
Share.open({
url: result?.fileDir + result.fileName
}).catch(console.log);
}).catch(() => {
/* empty */
});
} else {
FileViewer.open(result.filePath, {
showOpenWithDialog: true,
showAppsSuggestions: true,
shareFile: true
} as any).catch(console.log);
} as any).catch(() => {
/* empty */
});
}
}}
/>

View File

@@ -62,7 +62,9 @@ export const ShareComponent = ({ uri, name, padding }) => {
showOpenWithDialog: true,
showAppsSuggestions: true,
shareFile: true
}).catch(console.log);
}).catch(() => {
/* empty */
});
}}
/>
</View>

View File

@@ -22,24 +22,22 @@ import {
VirtualizedGrouping,
createInternalLink
} from "@notesnook/core";
import type { LinkAttributes } from "@notesnook/editor";
import { NativeEvents } from "@notesnook/editor-mobile/src/utils/native-events";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef, useState } from "react";
import { TextInput, View } from "react-native";
import { FlatList } from "react-native-actions-sheet";
import { db } from "../../../common/database";
import { useDBItem } from "../../../hooks/use-db-item";
import { editorController } from "../../../screens/editor/tiptap/utils";
import { presentSheet } from "../../../services/event-manager";
import { SIZE } from "../../../utils/size";
import { Button } from "../../ui/button";
import Input from "../../ui/input";
import { Pressable } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph";
import type { LinkAttributes } from "@notesnook/editor";
import {
EditorEvents,
editorController
} from "../../../screens/editor/tiptap/utils";
import { strings } from "@notesnook/intl";
const ListNoteItem = ({
id,
@@ -194,7 +192,7 @@ export default function LinkNote(props: {
}
: undefined
);
editorController.current?.postMessage(EditorEvents.resolve, {
editorController.current?.postMessage(NativeEvents.resolve, {
data: {
href: link,
title: selectedNote.title

View File

@@ -113,11 +113,9 @@ const ManageTagsSheet = (props: {
.sorted()
.then((items) => {
setTags(items);
console.log("searched tags");
});
} else {
db.tags.all.sorted(db.settings.getGroupOptions("tags")).then((items) => {
console.log("items loaded tags", items.placeholders.length);
setTags(items);
});
}

View File

@@ -191,13 +191,17 @@ class RecoveryKeySheet extends React.Component {
Share.open({
url: path,
failOnCancel: false
}).catch(console.log);
}).catch(() => {
/* empty */
});
} else {
FileViewer.open(path, {
showOpenWithDialog: true,
showAppsSuggestions: true,
shareFile: true
}).catch(console.log);
}).catch(() => {
/* empty */
});
}
} catch (e) {
console.error(e);

View File

@@ -273,7 +273,7 @@ const ListNoteItem = ({
listType,
noteInternalLinks.length
]);
console.log(noteInternalLinks);
const renderBlock = React.useCallback(
(block: ContentBlock) => (
<ListBlockItem
@@ -419,7 +419,6 @@ export const ReferencesList = ({ item, close }: ReferencesListProps) => {
id={index}
items={items}
onSelect={(note, blockId) => {
console.log(note.id, blockId);
eSendEvent(eOnLoadNote, {
item: note,
blockId: blockId

View File

@@ -38,7 +38,7 @@ const Sort = ({ type, screen }) => {
);
const updateGroupOptions = async (_groupOptions) => {
const groupType = screen === "Notes" ? "home" : type + "s";
console.log("updateGroupOptions for group", groupType, "in", screen);
await db.settings.setGroupOptions(groupType, _groupOptions);
setGroupOptions(_groupOptions);
setTimeout(() => {

View File

@@ -43,7 +43,7 @@ export const Update = ({ version: appVersion, fwdRef }) => {
let notes = version?.notes
? version.notes.replace("Thank you for using Notesnook!", "").split("- ")
: ["Bug fixes and performance improvements"];
notes = notes?.map((n) => n.replace(/\n/g, ""));
notes = notes?.map((n) => n.replace(/\n|<br>/g, ""));
const isGithubRelease = Config.GITHUB_RELEASE === "true";
const getSupportedAbi = () => {
@@ -178,18 +178,20 @@ export const Update = ({ version: appVersion, fwdRef }) => {
{version.body}
</Paragraph>
) : null}
{notes.map((item) => (
<Paragraph
key={item}
color={colors.secondary.paragraph}
style={{
marginBottom: 5
}}
selectable
>
{item}
</Paragraph>
))}
{notes.map((item) =>
item && item !== "" ? (
<Paragraph
key={item}
color={colors.secondary.paragraph}
style={{
marginBottom: 5
}}
selectable
>
{item}
</Paragraph>
) : null
)}
</ScrollView>
<Seperator />
<Button
@@ -215,7 +217,9 @@ export const Update = ({ version: appVersion, fwdRef }) => {
marginTop: 10
}}
onPress={() => {
Linking.openURL(GITHUB_PAGE_URL).catch(console.log);
Linking.openURL(GITHUB_PAGE_URL).catch(() => {
/* empty */
});
}}
>
{strings.readReleaseNotes()}

View File

@@ -263,20 +263,22 @@ export const Pressable = ({
const getStyle = useCallback(
({ pressed }: PressableStateCallbackType): ViewStyle | ViewStyle[] => [
{
backgroundColor: pressed
? RGB_Linear_Shade(alpha, hexToRGBA(selectedColor, opacity || 1))
: hexToRGBA(primaryColor, opacity || 1),
backgroundColor:
pressed && !disabled
? RGB_Linear_Shade(alpha, hexToRGBA(selectedColor, opacity || 1))
: hexToRGBA(primaryColor, opacity || 1),
width: "100%",
alignSelf: "center",
borderRadius: noborder ? 0 : br,
justifyContent: "center",
alignItems: "center",
marginBottom: 0,
borderColor: pressed
? customSelectedColor
? getColorLinearShade(customSelectedColor, 0.3, false)
: borderSelectedColor || borderColor
: borderColor || "transparent",
borderColor:
pressed && !disabled
? customSelectedColor
? getColorLinearShade(customSelectedColor, 0.3, false)
: borderSelectedColor || borderColor
: borderColor || "transparent",
borderWidth: borderWidth
},
style,
@@ -298,7 +300,8 @@ export const Pressable = ({
borderColor,
borderWidth,
style,
growFactor
growFactor,
disabled
]
);

View File

@@ -315,7 +315,9 @@ const trialstarted: { id: string; steps: TStep[] } = {
actionButton: {
text: strings.joinDiscord(),
action: () => {
Linking.openURL("https://discord.gg/zQBK97EE22").catch(console.log);
Linking.openURL("https://discord.gg/zQBK97EE22").catch(() => {
/* empty */
});
}
}
}
@@ -366,7 +368,9 @@ const Support = () => {
width: "90%"
}}
onPress={() => {
Linking.openURL("https://discord.gg/zQBK97EE22").catch(console.log);
Linking.openURL("https://discord.gg/zQBK97EE22").catch(() => {
/* empty */
});
}}
icon="discord"
type="secondary"
@@ -380,7 +384,9 @@ const Support = () => {
width: "90%"
}}
onPress={() => {
Linking.openURL("https://t.me/notesnook").catch(console.log);
Linking.openURL("https://t.me/notesnook").catch(() => {
/* empty */
});
}}
icon="telegram"
type="secondary"

View File

@@ -19,14 +19,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* eslint-disable no-inner-declarations */
import {
Color,
createInternalLink,
ItemReference,
Note,
Notebook,
Reminder,
Tag,
TrashItem,
VAULT_ERRORS,
createInternalLink
VAULT_ERRORS
} from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { DisplayedNotification } from "@notifee/react-native";
@@ -50,11 +50,11 @@ import ReminderSheet from "../components/sheets/reminder";
import { useSideBarDraggingStore } from "../components/side-menu/dragging-store";
import { useTabStore } from "../screens/editor/tiptap/use-tab-store";
import {
ToastManager,
eSendEvent,
eSubscribeEvent,
openVault,
presentSheet
presentSheet,
ToastManager
} from "../services/event-manager";
import Navigation from "../services/navigation";
import Notifications from "../services/notifications";
@@ -544,14 +544,13 @@ export const useActions = ({
const toggleReadyOnlyMode = async () => {
const currentReadOnly = (item as Note).readonly;
await db.notes.readonly(!currentReadOnly, item?.id);
if (useTabStore.getState().hasTabForNote(item.id)) {
const tabId = useTabStore.getState().getTabForNote(item.id);
if (!tabId) return;
useTabStore.getState().updateTab(tabId, {
readonly: !currentReadOnly
useTabStore.getState().forEachNoteTab(item.id, (tab) => {
useTabStore.getState().updateTab(tab.id, {
session: {
readonly: !currentReadOnly
}
});
}
});
Navigation.queueRoutesForUpdate();
close();
};

View File

@@ -50,12 +50,17 @@ import { MMKV } from "../common/database/mmkv";
import { endProgress, startProgress } from "../components/dialogs/progress";
import Migrate from "../components/sheets/migrate";
import NewFeature from "../components/sheets/new-feature";
import ReminderSheet from "../components/sheets/reminder";
import { Walkthrough } from "../components/walkthroughs";
import { useTabStore } from "../screens/editor/tiptap/use-tab-store";
import {
resetTabStore,
useTabStore
} from "../screens/editor/tiptap/use-tab-store";
import {
clearAppState,
editorController,
editorState
editorState,
setAppState
} from "../screens/editor/tiptap/utils";
import { useDragState } from "../screens/settings/editor/state";
import BackupService from "../services/backup";
@@ -94,7 +99,6 @@ import {
eLoginSessionExpired,
eOnLoadNote,
eOpenAnnouncementDialog,
eUnlockNote,
eUserLoggedIn,
refreshNotesPage
} from "../utils/events";
@@ -148,7 +152,6 @@ const onUploadedAttachmentProgress = (data: any) => {
};
const onUserSessionExpired = async () => {
console.log("LOGGED OUT USER....");
SettingsService.set({
sessionExpired: true
});
@@ -157,6 +160,7 @@ const onUserSessionExpired = async () => {
const onAppOpenedFromURL = async (event: { url: string }) => {
const url = event.url;
try {
if (url.startsWith("https://app.notesnook.com/account/verified")) {
await onUserEmailVerified();
@@ -166,6 +170,25 @@ const onAppOpenedFromURL = async (event: { url: string }) => {
eSendEvent(eOnLoadNote, { newNote: true });
tabBarRef.current?.goToPage(1, false);
return;
} else if (url.startsWith("https://notesnook.com/open_note")) {
const id = new URL(url).searchParams.get("id");
if (id) {
const note = await db.notes.note(id);
if (note) {
eSendEvent(eOnLoadNote, {
item: note
});
tabBarRef.current?.goToPage(1, false);
}
}
} else if (url.startsWith("https://notesnook.com/open_reminder")) {
const id = new URL(url).searchParams.get("id");
if (id) {
const reminder = await db.reminders.reminder(id);
if (reminder) ReminderSheet.present(reminder);
}
} else if (url.startsWith("https://notesnook.com/new_reminder")) {
ReminderSheet.present();
}
} catch (e) {
console.error(e);
@@ -222,6 +245,7 @@ const onLogout = async (reason: string) => {
await PremiumService.setPremiumStatus();
await BiometricService.resetCredentials();
MMKV.clearStore();
resetTabStore();
clearAllStores();
setImmediate(() => {
refreshAllStores();
@@ -254,9 +278,7 @@ async function checkForShareExtensionLaunchedInBackground() {
if (note) setTimeout(() => eSendEvent("loadingNote", note), 1);
MMKV.removeItem("shareExtensionOpened");
}
} catch (e) {
console.log(e);
}
} catch (e) {}
}
async function saveEditorState() {
@@ -502,7 +524,9 @@ export const useAppEvents = () => {
}
if (SettingsService.getProperty("offlineMode")) {
db.attachments.cacheAttachments().catch(console.log);
db.attachments.cacheAttachments().catch(() => {
/* empty */
});
}
}
}, []);
@@ -550,7 +574,6 @@ export const useAppEvents = () => {
});
}
} catch (e) {
console.log(e);
ToastManager.error(e as Error, "Error updating user", "global");
}
@@ -621,22 +644,17 @@ export const useAppEvents = () => {
EV.subscribe(EVENTS.vaultLocked, async () => {
// Lock all notes in all tabs...
for (const tab of useTabStore.getState().tabs) {
const noteId = useTabStore.getState().getTab(tab.id)?.noteId;
const noteId = useTabStore.getState().getTab(tab.id)?.session?.noteId;
if (!noteId) continue;
const note = await db.notes.note(noteId);
const locked = note && (await db.vaults.itemExists(note));
if (locked) {
useTabStore.getState().updateTab(tab.id, {
locked: true
session: {
locked: true,
noteLocked: true
}
});
if (
tab.id === useTabStore.getState().currentTab &&
locked &&
!editorState().movedAway
) {
// Show unlock note screen.
eSendEvent(eUnlockNote);
}
}
}
}),
@@ -645,6 +663,7 @@ export const useAppEvents = () => {
const emitterSubscriptions = [
Linking.addEventListener("url", onAppOpenedFromURL),
SodiumEventEmitter.addListener(
"onSodiumProgress",
onFileEncryptionProgress
@@ -730,13 +749,15 @@ export const useAppEvents = () => {
setTimeout(() => {
sub = AppState.addEventListener("change", onAppStateChanged);
if (
refValues.current.initialUrl?.startsWith(
"https://app.notesnook.com/account/verified"
)
refValues.current.initialUrl &&
!refValues.current.initialUrl?.includes("open_note")
) {
onUserEmailVerified();
onAppOpenedFromURL({
url: refValues.current.initialUrl!
});
}
}, 1000);
refValues.current.removeInternetStateListener = NetInfo.addEventListener(
onInternetStateChanged
);
@@ -779,9 +800,7 @@ export const useAppEvents = () => {
useEffect(() => {
if (!appLocked && isAppLoading) {
initializeLogger()
.catch((e) => {
console.log(e);
})
.catch((e) => {})
.finally(() => {
//@ts-ignore
initializeDatabase();

View File

@@ -196,7 +196,6 @@ export const useTotalNotes = (type: "notebook" | "tag" | "color") => {
}
setTotalNotesById(totalNotesById);
});
console.log("useTotalNotes.getTotalNotes");
},
[type]
);

View File

@@ -31,7 +31,7 @@ export function useGroupOptions(type: any) {
const onUpdate = (groupType: string) => {
if (groupType !== type) return;
const options = db.settings?.getGroupOptions(type) as any;
console.log("useGroupOptions.onUpdate", type, options);
if (
groupOptions?.groupBy !== options.groupBy ||
groupOptions?.sortBy !== options.sortBy ||

View File

@@ -74,10 +74,8 @@ export const useNotebook = (
};
const onUpdate = (type: string) => {
console.log("event", type);
if (type !== "notebooks") return;
onRequestUpdate();
console.log("useNotebook.onUpdate", item?.id, Date.now());
};
eSubscribeEvent(eGroupOptionsUpdated, onUpdate);

View File

@@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
@@ -44,7 +45,6 @@ import useNavigationStore from "../stores/use-navigation-store";
import { useSelectionStore } from "../stores/use-selection-store";
import { useSettingStore } from "../stores/use-setting-store";
import { rootNavigatorRef } from "../utils/global-refs";
import { strings } from "@notesnook/intl";
const NativeStack = createNativeStackNavigator();
const IntroStack = createNativeStackNavigator();
@@ -76,11 +76,12 @@ const _Tabs = () => {
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
const height = useSettingStore((state) => state.dimensions.height);
const insets = useGlobalSafeAreaInsets();
const screenHeight = height - (50 + insets.top + insets.bottom);
React.useEffect(() => {
setTimeout(() => {
setTimeout(async () => {
useNavigationStore.getState().update(homepage);
}, 1000);
}, [homepage]);

View File

@@ -509,7 +509,8 @@ const onChangeTab = async (event) => {
});
} else {
if (
useTabStore.getState().getTab(useTabStore.getState().currentTab).locked
useTabStore.getState().getTab(useTabStore.getState().currentTab).session
?.locked
) {
eSendEvent(eUnlockNote);
}
@@ -523,13 +524,15 @@ const onChangeTab = async (event) => {
// Lock all tabs with locked notes...
for (const tab of useTabStore.getState().tabs) {
const noteId = useTabStore.getState().getTab(tab.id)?.noteId;
const noteId = useTabStore.getState().getTab(tab.id)?.session?.noteId;
if (!noteId) continue;
const note = await db.notes.note(noteId);
const locked = note && (await db.vaults.itemExists(note));
if (locked) {
useTabStore.getState().updateTab(tab.id, {
locked: true
session: {
locked: true
}
});
}
}

View File

@@ -9,13 +9,13 @@
"@streetwriters/showdown": "^3.0.1-alpha.2",
"absolutify": "^0.1.0",
"buffer": "^6.0.3",
"dayjs": "^1.10.4",
"deprecated-react-native-prop-types": "^4.1.0",
"entities": "^3.0.1",
"fflate": "^0.7.3",
"html-to-text": "9.0.5",
"phone": "^3.1.14",
"qclone": "^1.2.0",
"dayjs": "^1.11.13",
"react-native-actions-sheet": "0.9.7",
"react-native-drax": "^0.10.2",
"react-native-image-zoom-viewer": "^3.0.1",
@@ -36,10 +36,15 @@
"@streetwriters/kysely": "^0.27.4",
"pathe": "1.1.2",
"react-native-format-currency": "0.0.5",
"@lingui/react": "4.11.2",
"@lingui/core": "4.11.2",
"@lingui/core": "5.1.2",
"@lingui/react": "5.1.2",
"react-native-check-version": "^1.3.0",
"react-native-material-menu": "^2.0.0"
"react-native-material-menu": "^2.0.0",
"@trpc/client": "^10.45.2",
"@trpc/react-query": "^10.45.2",
"@trpc/server": "^10.45.2",
"@tanstack/react-query": "^4.36.1",
"async-mutex": "0.5.0"
},
"sideEffects": false
}

View File

@@ -19,6 +19,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* eslint-disable @typescript-eslint/no-var-requires */
import { i18n } from "@lingui/core";
import { strings } from "@notesnook/intl";
import React, {
forwardRef,
useCallback,
@@ -46,6 +48,7 @@ import {
eUnlockWithPassword
} from "../../utils/events";
import { openLinkInBrowser } from "../../utils/functions";
import { tabBarRef } from "../../utils/global-refs";
import EditorOverlay from "./loading";
import { EDITOR_URI } from "./source";
import { EditorProps, useEditorType } from "./tiptap/types";
@@ -58,9 +61,6 @@ import {
openInternalLink,
randId
} from "./tiptap/utils";
import { tabBarRef } from "../../utils/global-refs";
import { strings } from "@notesnook/intl";
import { i18n } from "@lingui/core";
const style: ViewStyle = {
height: "100%",
@@ -203,11 +203,13 @@ const useLockedNoteHandler = () => {
useEffect(() => {
for (const tab of useTabStore.getState().tabs) {
const noteId = useTabStore.getState().getTab(tab.id)?.noteId;
const noteId = useTabStore.getState().getTab(tab.id)?.session?.noteId;
if (!noteId) continue;
if (tabRef.current && tabRef.current.noteLocked) {
if (tabRef.current && tabRef.current.session?.noteLocked) {
useTabStore.getState().updateTab(tabRef.current.id, {
locked: true
session: {
locked: true
}
});
}
}
@@ -221,33 +223,38 @@ const useLockedNoteHandler = () => {
biometryAvailable: !!biometry,
biometryEnrolled: !!fingerprint
});
syncTabs();
syncTabs("biometry");
})();
}, [tab?.id]);
useEffect(() => {
const unlockWithBiometrics = async () => {
try {
if (!tabRef.current?.noteLocked || !tabRef.current) return;
if (!tabRef.current?.session?.noteLocked || !tabRef.current) return;
console.log("Trying to unlock with biometrics...");
const credentials = await BiometricService.getCredentials(
"Unlock note",
"Unlock note to open it in editor."
);
if (credentials && credentials?.password && tabRef.current.noteId) {
if (
credentials &&
credentials?.password &&
tabRef.current.session?.noteId
) {
const note = await db.vault.open(
tabRef.current.noteId,
tabRef.current.session?.noteId,
credentials?.password
);
eSendEvent(eOnLoadNote, {
item: note
});
useTabStore.getState().updateTab(tabRef.current.id, {
locked: false
item: note,
refresh: true
});
} else {
if (tabRef.current && tabRef.current.session?.locked) {
editorController.current?.commands.focusPassInput();
}
}
} catch (e) {
console.error(e);
@@ -261,7 +268,7 @@ const useLockedNoteHandler = () => {
password: string;
biometrics?: boolean;
}) => {
if (!tabRef.current?.noteId || !tabRef.current) return;
if (!tabRef.current?.session?.noteId || !tabRef.current) return;
if (!password || password.trim().length === 0) {
ToastManager.show({
heading: strings.passwordNotEntered(),
@@ -271,7 +278,10 @@ const useLockedNoteHandler = () => {
}
try {
const note = await db.vault.open(tabRef.current?.noteId, password);
const note = await db.vault.open(
tabRef.current?.session?.noteId,
password
);
if (enrollBiometrics && note) {
try {
const unlocked = await db.vault.unlock(password);
@@ -299,13 +309,10 @@ const useLockedNoteHandler = () => {
}
}
eSendEvent(eOnLoadNote, {
item: note
});
useTabStore.getState().updateTab(tabRef.current.id, {
locked: false
item: note,
refresh: true
});
} catch (e) {
console.log(e);
ToastManager.show({
heading: strings.passwordIncorrect(),
type: "error"
@@ -315,7 +322,7 @@ const useLockedNoteHandler = () => {
const unlock = () => {
if (
(tabRef.current?.locked,
(tabRef.current?.session?.locked,
useTabStore.getState().biometryAvailable &&
useTabStore.getState().biometryEnrolled &&
!editorState().movedAway)
@@ -324,10 +331,9 @@ const useLockedNoteHandler = () => {
unlockWithBiometrics();
}, 150);
} else {
console.log("Biometrics unavailable.", editorState().movedAway);
if (!editorState().movedAway) {
setTimeout(() => {
if (tabRef.current && tabRef.current?.locked) {
if (tabRef.current && tabRef.current?.session?.locked) {
editorController.current?.commands.focus(tabRef.current?.id);
}
}, 100);
@@ -342,13 +348,13 @@ const useLockedNoteHandler = () => {
}),
eSubscribeEvent(eUnlockWithPassword, onSubmit)
];
if (tabRef.current?.locked && tabBarRef.current?.page() === 2) {
if (tabRef.current?.session?.locked && tabBarRef.current?.page() === 2) {
unlock();
}
return () => {
subs.map((s) => s?.unsubscribe());
};
}, [tab?.id, tab?.locked]);
}, [tab?.id, tab?.session?.locked]);
return null;
};

View File

@@ -27,10 +27,10 @@ import WebView from "react-native-webview";
import { useRef } from "react";
import { EDITOR_URI } from "./source";
import { EditorMessage } from "./tiptap/types";
import { EventTypes } from "./tiptap/editor-events";
import { EditorEvents } from "@notesnook/editor-mobile/src/utils/editor-events";
import { Attachment } from "@notesnook/editor";
import downloadAttachment from "../../common/filesystem/download-attachment";
import { EditorEvents } from "./tiptap/utils";
import { NativeEvents } from "@notesnook/editor-mobile/src/utils/native-events";
import { useThemeColors } from "@notesnook/theme";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { db } from "../../common/database";
@@ -69,12 +69,11 @@ export function ReadonlyEditor(props: {
const data = event.nativeEvent.data;
const editorMessage = JSON.parse(data) as EditorMessage<any>;
if (editorMessage.type === EventTypes.logger) {
if (editorMessage.type === EditorEvents.logger) {
logger.info("[READONLY EDITOR LOG]", editorMessage.value);
}
if (editorMessage.type === EventTypes.readonlyEditorLoaded) {
console.log("Readonly editor loaded.");
if (editorMessage.type === EditorEvents.readonlyEditorLoaded) {
props.onLoad?.((content: { data: string; id: string }) => {
setTimeout(() => {
noteId.current = content.id;
@@ -87,10 +86,9 @@ export function ReadonlyEditor(props: {
setLoading(false);
}, 300);
});
} else if (editorMessage.type === EventTypes.getAttachmentData) {
} else if (editorMessage.type === EditorEvents.getAttachmentData) {
const attachment = (editorMessage.value as any).attachment as Attachment;
console.log("Getting attachment data:", attachment.hash, attachment.type);
downloadAttachment(attachment.hash, true, {
base64: attachment.type === "image",
text: attachment.type === "web-clip",
@@ -106,7 +104,7 @@ export function ReadonlyEditor(props: {
);
editorRef.current?.postMessage(
JSON.stringify({
type: EditorEvents.attachmentData,
type: NativeEvents.attachmentData,
value: {
resolverId: (editorMessage.value as any).resolverId,
data
@@ -115,10 +113,9 @@ export function ReadonlyEditor(props: {
);
})
.catch(() => {
console.log("Error downloading attachment data");
editorRef.current?.postMessage(
JSON.stringify({
type: EditorEvents.attachmentData,
type: NativeEvents.attachmentData,
data: {
resolverId: (editorMessage.value as any).resolverId,
data: undefined

View File

@@ -18,9 +18,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Note } from "@notesnook/core";
import type { Attachment } from "@notesnook/editor";
import type { ImageAttributes } from "@notesnook/editor";
import type { LinkAttributes } from "@notesnook/editor";
import type {
Attachment,
ImageAttributes,
LinkAttributes
} from "@notesnook/editor";
import { createRef, RefObject } from "react";
import { Platform } from "react-native";
import { EdgeInsets } from "react-native-safe-area-context";
@@ -37,9 +39,6 @@ async function call(webview: RefObject<WebView | undefined>, action?: Action) {
if (!webview.current || !action) return;
setImmediate(() => webview.current?.injectJavaScript(action.job));
const response = await getResponse(action.id);
// if (!response) {
// console.warn("webview job failed", action.id);
// }
return response ? response.value : response;
}
@@ -75,110 +74,71 @@ class Commands {
return call(this.ref, fn(job, name)) as Promise<T>;
}
focus = async (tabId: number) => {
async sendCommand<T>(command: string, ...args: any[]) {
return this.doAsync(
`response = globalThis.commands.${command}(${args
.map((arg) =>
typeof arg === "string" ? `"${arg}"` : JSON.stringify(arg)
)
.join(",")})`,
command
);
}
focus = async (tabId: string) => {
if (!this.ref.current) return;
const locked = useTabStore.getState().getTab(tabId)?.session?.locked;
if (Platform.OS === "android") {
//this.ref.current?.requestFocus();
setTimeout(async () => {
if (
locked &&
useTabStore.getState().biometryAvailable &&
useTabStore.getState().biometryEnrolled
)
return;
if (!this.ref) return;
textInput.current?.focus();
const locked = useTabStore.getState().getTab(tabId)?.locked;
await this.doAsync(
locked
? `editorControllers[${tabId}]?.focusPassInput();`
: `editors[${tabId}]?.commands.focus()`,
"focus"
);
await this.sendCommand("focus", tabId, locked);
this.ref?.current?.requestFocus();
}, 1);
} else {
await sleep(400);
await this.doAsync(`editors[${tabId}]?.commands.focus()`, "focus");
await this.sendCommand("focus", tabId, locked);
}
};
blur = async (tabId: number) =>
await this.doAsync(
`
const editor = editors[${tabId}];
const editorTitle = editorTitles[${tabId}];
typeof editor !== "undefined" && editor.commands.blur();
typeof editorTitle !== "undefined" && editorTitle.current && editorTitle.current.blur();
editorControllers[${tabId}]?.blurPassInput();
blur = async (tabId: string) => this.sendCommand("blur", tabId);
`,
"blur"
);
clearContent = async (tabId: number) => {
clearContent = async (tabId: string) => {
this.previousSettings = null;
await this.doAsync(
`
const editor = editors[${tabId}];
const editorController = editorControllers[${tabId}];
const editorTitle = editorTitles[${tabId}];
const statusBar = statusBars[${tabId}];
if (typeof editor !== "undefined") {
editor.commands.blur();
editor.commands.clearContent(false);
}
typeof editorTitle !== "undefined" && editorTitle.current && editorTitle.current?.blur();
if (typeof editorController.content !== undefined) editorController.content.current = '';
editorController.onUpdate();
editorController.setTitle('');
if (typeof statusBar !== "undefined") {
statusBar.current.resetWords();
statusBar.current.set({date:"",saved:""});
}`,
"clearContent"
);
await this.sendCommand("clearContent", tabId);
};
setSessionId = async (id: string | null) =>
await this.doAsync(`globalThis.sessionId = "${id}";`);
await this.sendCommand("setSessionId", id);
setStatus = async (
date: string | undefined,
saved: string,
tabId: number
tabId: string
) => {
await this.doAsync(
`
const statusBar = statusBars[${tabId}];
typeof statusBar !== "undefined" && statusBar.current.set({date:"${date}",saved:"${saved}"})`,
"setStatus"
this.sendCommand("setStatus", date, saved, tabId);
};
setPlaceholder = async (placeholder: string) => {};
setLoading = async (loading?: boolean, tabId?: string) => {
this.sendCommand(
"setLoading",
loading,
tabId === undefined ? useTabStore.getState().currentTab : tabId
);
};
setPlaceholder = async (placeholder: string) => {
// await this.doAsync(`
// const element = document.querySelector(".is-editor-empty");
// if (element) {
// element.setAttribute("data-placeholder","${placeholder}");
// }
// `);
};
setLoading = async (loading?: boolean, tabId?: number) => {
await this.doAsync(`
const editorController = editorControllers[${
tabId || useTabStore.getState().currentTab
}];
editorController.setLoading(${loading})
`);
};
setInsets = async (insets: EdgeInsets) => {
await this.doAsync(`
if (typeof safeAreaController !== "undefined") {
safeAreaController.update(${JSON.stringify(insets)})
}
`);
this.sendCommand("setInsets", insets);
};
updateSettings = async (settings?: Partial<Settings>) => {
@@ -187,13 +147,7 @@ if (typeof statusBar !== "undefined") {
...this.previousSettings,
...settings
};
await this.doAsync(`
if (typeof globalThis.settingsController !== "undefined") {
globalThis.settingsController.update(${JSON.stringify(
this.previousSettings
)})
}
`);
this.sendCommand("updateSettings", settings);
};
setSettings = async (settings?: Partial<Settings>) => {
@@ -206,123 +160,63 @@ if (typeof statusBar !== "undefined") {
return;
}
}
await this.doAsync(`
if (typeof globalThis.settingsController !== "undefined") {
globalThis.settingsController.update(${JSON.stringify(settings)})
}
`);
this.sendCommand("setSettings", settings);
};
setTags = async (note: Note | null | undefined) => {
if (!note) return;
const tabId = useTabStore.getState().getTabForNote(note.id);
const tags = await db.relations.to(note, "tag").resolve();
await this.doAsync(
`
const tags = editorTags[${tabId}];
if (tags && tags.current) {
tags.current.setTags(${JSON.stringify(
tags.map((tag) => ({
title: tag.title,
alias: tag.title,
id: tag.id,
type: tag.type
}))
)});
}
`,
"setTags"
);
useTabStore.getState().forEachNoteTab(note.id, async (tab) => {
const tabId = tab.id;
const tags = await db.relations.to(note, "tag").resolve();
await this.sendCommand("setTags", tabId, tags);
});
};
clearTags = async (tabId: number) => {
await this.doAsync(
`
const tags = editorTags[${tabId}];
logger("info", Object.keys(editorTags), typeof editorTags[0]);
if (tags && tags.current) {
tags.current.setTags([]);
}
`,
"clearTags"
);
clearTags = async (tabId: string) => {
await this.sendCommand("clearTags", tabId);
};
insertAttachment = async (attachment: Attachment, tabId: number) => {
await this.doAsync(
`const editor = editors[${tabId}];
editor && editor.commands.insertAttachment(${JSON.stringify(attachment)})`
);
insertAttachment = async (attachment: Attachment, tabId: string) => {
await this.sendCommand("insertAttachment", attachment, tabId);
};
setAttachmentProgress = async (
attachmentProgress: Partial<Attachment>,
tabId: number
tabId: string
) => {
await this.doAsync(
`const editor = editors[${tabId}];
editor && editor.commands.updateAttachment(${JSON.stringify(
attachmentProgress
)}, {
preventUpdate: true,
query: (attachment) => {
return attachment.hash === "${attachmentProgress.hash}";
}
})`
);
await this.sendCommand("setAttachmentProgress", attachmentProgress, tabId);
};
insertImage = async (
image: Omit<ImageAttributes, "bloburl"> & {
dataurl: string;
},
tabId: number
tabId: string
) => {
await this.doAsync(
`const editor = editors[${tabId}];
const image = toBlobURL("${image.dataurl}", "${image.hash}");
editor && editor.commands.insertImage({
...${JSON.stringify({
...image,
dataurl: undefined
})},
bloburl: image
})`
);
await this.sendCommand("insertImage", image, tabId);
};
handleBack = async () => {
return this.doAsync<boolean>(
'response = window.dispatchEvent(new Event("handleBackPress",{cancelable:true}));'
);
return this.sendCommand("handleBack");
};
keyboardShown = async (keyboardShown: boolean) => {
return this.doAsync(`globalThis['keyboardShown']=${keyboardShown};`);
return this.sendCommand("keyboardShown", keyboardShown);
};
getTableOfContents = async () => {
const tabId = useTabStore.getState().currentTab;
return this.doAsync(`
response = editorControllers[${tabId}]?.getTableOfContents() || [];
`);
return this.sendCommand("getTableOfContents", tabId);
};
focusPassInput = async () => {
const tabId = useTabStore.getState().currentTab;
return this.doAsync(`
response = editorControllers[${tabId}]?.focusPassInput() || [];
`);
return this.sendCommand("focusPassInput", tabId);
};
blurPassInput = async () => {
const tabId = useTabStore.getState().currentTab;
return this.doAsync(`
response = editorControllers[${tabId}]?.blurPassInput() || [];
`);
return this.sendCommand("blurPassInput", tabId);
};
createInternalLink = async (
@@ -330,30 +224,18 @@ editor && editor.commands.insertImage({
resolverId: string
) => {
if (!resolverId) return;
return this.doAsync(`
if (globalThis.pendingResolvers["${resolverId}"]) {
globalThis.pendingResolvers["${resolverId}"](${JSON.stringify(
attributes
)});
}`);
return this.sendCommand("createInternalLink", attributes, resolverId);
};
dismissCreateInternalLinkRequest = async (resolverId: string) => {
if (!resolverId) return;
return this.doAsync(`
if (globalThis.pendingResolvers["${resolverId}"]) {
globalThis.pendingResolvers["${resolverId}"](undefined);
}
`);
return this.sendCommand("dismissCreateInternalLinkRequest", resolverId);
};
scrollIntoViewById = async (id: string) => {
const tabId = useTabStore.getState().currentTab;
return this.doAsync(`
response = editorControllers[${tabId}]?.scrollIntoView("${id}") || [];
`);
return this.sendCommand("scrollIntoViewById", id, tabId);
};
//todo add replace image function
}
export default Commands;

View File

@@ -44,7 +44,6 @@ import { useTabStore } from "./use-tab-store";
import { editorController, editorState } from "./utils";
import { strings } from "@notesnook/intl";
import { useUserStore } from "../../../stores/use-user-store";
import { sleep } from "../../../utils/time";
const showEncryptionSheet = (file: DocumentPickerResponse) => {
presentSheet({
@@ -62,7 +61,7 @@ const santizeUri = (uri: string) => {
type PickerOptions = {
noteId?: string;
tabId?: number;
tabId?: string;
type: "image" | "camera" | "file";
reupload: boolean;
hash?: string;
@@ -133,7 +132,6 @@ const file = async (fileOptions: PickerOptions) => {
if (
fileOptions.tabId !== undefined &&
fileOptions.noteId &&
useTabStore.getState().getNoteIdForTab(fileOptions.tabId) ===
fileOptions.noteId
) {
@@ -196,9 +194,7 @@ const camera = async (options: PickerOptions) => {
options
);
})
.catch((e) => {
console.log("camera error: ", e);
});
.catch((e) => {});
} catch (e) {
ToastManager.show({
heading: (e as Error).message,
@@ -226,9 +222,7 @@ const gallery = async (options: PickerOptions) => {
options
)
)
.catch((e) => {
console.log("gallery error: ", e);
});
.catch((e) => {});
} catch (e) {
useSettingStore.getState().setAppDidEnterBackgroundForAction(false);
ToastManager.show({
@@ -236,7 +230,6 @@ const gallery = async (options: PickerOptions) => {
type: "error",
context: "global"
});
console.log("attachment error:", e);
}
};
@@ -321,18 +314,14 @@ const handleImageResponse = async (
? fileName.replace(/HEIC|HEIF/, "jpeg")
: fileName;
console.log("attaching image...", fileName);
console.log("attaching file...");
if (!(await attachFile(uri, hash, image.mime, fileName, options))) return;
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
console.log("attaching image to note...");
if (
options.tabId !== undefined &&
useTabStore.getState().getNoteIdForTab(options.tabId) === options.noteId
) {
console.log("attaching image to note...");
editorController.current?.commands.insertImage(
{
hash: hash,
@@ -407,7 +396,7 @@ export async function attachFile(
} else {
encryptionInfo = { hash: hash };
}
console.log("FILE ENCRYPTED....", encryptionInfo);
await db.attachments.add(encryptionInfo);
return true;
} catch (e) {

View File

@@ -74,7 +74,7 @@ export type EditorMessage<T> = {
value: T;
type: string;
noteId: string;
tabId: number;
tabId: string;
resolverId?: string;
hasTimeout?: boolean;
};
@@ -86,7 +86,7 @@ export type SavePayload = {
type?: "tiptap";
sessionHistoryId?: number;
ignoreEdit: boolean;
tabId: number;
tabId: string;
pendingChanges?: boolean;
};
@@ -94,4 +94,5 @@ export type AppState = {
editing: boolean;
movedAway: boolean;
timestamp: number;
noteId?: string;
};

View File

@@ -21,7 +21,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* eslint-disable @typescript-eslint/no-var-requires */
import { ItemReference } from "@notesnook/core";
import type { Attachment } from "@notesnook/editor";
import { EditorEvents } from "@notesnook/editor-mobile/src/utils/editor-events";
import { NativeEvents } from "@notesnook/editor-mobile/src/utils/native-events";
import { getDefaultPresets } from "@notesnook/editor/dist/cjs/toolbar/tool-definitions";
import { strings } from "@notesnook/intl";
import Clipboard from "@react-native-clipboard/clipboard";
import React, { useCallback, useEffect, useRef } from "react";
import {
@@ -72,11 +75,9 @@ import {
import { openLinkInBrowser } from "../../../utils/functions";
import { tabBarRef } from "../../../utils/global-refs";
import { useDragState } from "../../settings/editor/state";
import { EventTypes } from "./editor-events";
import { EditorMessage, EditorProps, useEditorType } from "./types";
import { useTabStore } from "./use-tab-store";
import { EditorEvents, editorState, openInternalLink } from "./utils";
import { strings } from "@notesnook/intl";
import { editorState, openInternalLink } from "./utils";
const publishNote = async () => {
const user = useUserStore.getState().user;
@@ -101,7 +102,7 @@ const publishNote = async () => {
}
const noteId = useTabStore
.getState()
.getNoteIdForTab(useTabStore.getState().currentTab);
.getNoteIdForTab(useTabStore.getState().currentTab!);
if (noteId) {
const note = await db.notes?.note(noteId);
@@ -124,7 +125,7 @@ const publishNote = async () => {
const showActionsheet = async () => {
const noteId = useTabStore
.getState()
.getNoteIdForTab(useTabStore.getState().currentTab);
.getNoteIdForTab(useTabStore.getState().currentTab!);
if (noteId) {
const note = await db.notes?.note(noteId);
if (editorState().isFocused || editorState().isFocused) {
@@ -177,7 +178,7 @@ export const useEditorEvents = (
useEffect(() => {
const handleKeyboardDidShow: KeyboardEventListener = () => {
editor.commands.keyboardShown(true);
editor.postMessage(EditorEvents.keyboardShown, undefined);
editor.postMessage(NativeEvents.keyboardShown, undefined);
};
const handleKeyboardDidHide: KeyboardEventListener = () => {
editor.commands.keyboardShown(false);
@@ -247,7 +248,7 @@ export const useEditorEvents = (
}
editorState().currentlyEditing = false;
// editor.reset(); Notes remain open.
editor.commands?.blur(useTabStore.getState().currentTab);
editor.commands?.blur(useTabStore.getState().currentTab!);
setTimeout(async () => {
if (deviceMode !== "mobile" && fullscreen) {
if (fullscreen) {
@@ -268,7 +269,6 @@ export const useEditorEvents = (
}, [editor, deviceMode, fullscreen]);
const onHardwareBackPress = useCallback(() => {
console.log(tabBarRef.current?.page());
if (tabBarRef.current?.page() === 2) {
onBackPress();
return true;
@@ -353,25 +353,25 @@ export const useEditorEvents = (
const editorMessage = JSON.parse(data) as EditorMessage<any>;
if (editorMessage.hasTimeout && editorMessage.resolverId) {
editor.postMessage(EditorEvents.resolve, {
editor.postMessage(NativeEvents.resolve, {
data: true,
resolverId: editorMessage.resolverId
});
}
if (editorMessage.type === EventTypes.load) {
if (editorMessage.type === EditorEvents.load) {
DatabaseLogger.log("Editor is ready");
editor.onLoad();
return;
}
if (editorMessage.type === EventTypes.back) {
if (editorMessage.type === EditorEvents.back) {
return onBackPress();
}
if (
editorMessage.sessionId !== editor.sessionId.current &&
editorMessage.type !== EditorEvents.status
editorMessage.type !== NativeEvents.status
) {
return;
}
@@ -381,8 +381,8 @@ export const useEditorEvents = (
.getNoteIdForTab(editorMessage.tabId);
switch (editorMessage.type) {
case EventTypes.content:
DatabaseLogger.log("EventTypes.content");
case EditorEvents.content:
DatabaseLogger.log("EditorEvents.content");
editor.saveContent({
type: editorMessage.type,
content: editorMessage.value.html as string,
@@ -392,8 +392,8 @@ export const useEditorEvents = (
pendingChanges: editorMessage.value?.pendingChanges
});
break;
case EventTypes.title:
DatabaseLogger.log("EventTypes.title");
case EditorEvents.title:
DatabaseLogger.log("EditorEvents.title");
editor.saveContent({
type: editorMessage.type,
title: editorMessage.value?.title as string,
@@ -403,10 +403,10 @@ export const useEditorEvents = (
pendingChanges: editorMessage.value?.pendingChanges
});
break;
case EventTypes.logger:
case EditorEvents.logger:
logger.info("[EDITOR LOG]", editorMessage.value);
break;
case EventTypes.dbLogger:
case EditorEvents.dbLogger:
if (editorMessage.value.error) {
DatabaseLogger.error(
editorMessage.value.error,
@@ -419,12 +419,12 @@ export const useEditorEvents = (
DatabaseLogger.info("[EDITOR_LOG]" + editorMessage.value.message);
}
break;
case EventTypes.contentchange:
case EditorEvents.contentchange:
editor.onContentChanged(editorMessage.noteId);
break;
case EventTypes.selection:
case EditorEvents.selection:
break;
case EventTypes.reminders:
case EditorEvents.reminders:
if (!noteId) {
ToastManager.show({
heading: strings.createNoteFirst(),
@@ -442,7 +442,7 @@ export const useEditorEvents = (
onAdd: () => ReminderSheet.present(undefined, note, true)
});
break;
case EventTypes.newtag:
case EditorEvents.newtag:
if (!noteId) {
ToastManager.show({
heading: strings.createNoteFirst(),
@@ -452,7 +452,7 @@ export const useEditorEvents = (
}
ManageTagsSheet.present([noteId]);
break;
case EventTypes.tag:
case EditorEvents.tag:
if (editorMessage.value) {
if (!noteId) return;
const note = await db.notes.note(noteId);
@@ -468,7 +468,7 @@ export const useEditorEvents = (
});
}
break;
case EventTypes.filepicker:
case EditorEvents.filepicker:
editorState().isAwaitingResult = true;
const { pick } = require("./picker").default;
pick({
@@ -480,14 +480,14 @@ export const useEditorEvents = (
editorState().isAwaitingResult = false;
}, 1000);
break;
case EventTypes.download: {
case EditorEvents.download: {
const downloadAttachment =
require("../../../common/filesystem/download-attachment").default;
downloadAttachment((editorMessage.value as Attachment)?.hash, true);
break;
}
case EventTypes.getAttachmentData: {
case EditorEvents.getAttachmentData: {
const attachment = (editorMessage.value as any)
?.attachment as Attachment;
@@ -507,14 +507,14 @@ export const useEditorEvents = (
!!data,
editorMessage.resolverId
);
editor.postMessage(EditorEvents.resolve, {
editor.postMessage(NativeEvents.resolve, {
resolverId: editorMessage.resolverId,
data
});
})
.catch((e) => {
DatabaseLogger.error(e);
editor.postMessage(EditorEvents.resolve, {
editor.postMessage(NativeEvents.resolve, {
resolverId: editorMessage.resolverId,
data: undefined
});
@@ -523,26 +523,26 @@ export const useEditorEvents = (
break;
}
case EventTypes.pro:
case EditorEvents.pro:
if (editor.state.current?.isFocused) {
editor.state.current.isFocused = true;
}
eSendEvent(eOpenPremiumDialog);
break;
case EventTypes.monograph:
case EditorEvents.monograph:
publishNote();
break;
case EventTypes.properties:
case EditorEvents.properties:
showActionsheet();
break;
case EventTypes.scroll:
case EditorEvents.scroll:
editorState().scrollPosition = editorMessage.value;
break;
case EventTypes.fullscreen:
case EditorEvents.fullscreen:
editorState().isFullscreen = true;
eSendEvent(eOpenFullscreenEditor);
break;
case EventTypes.link:
case EditorEvents.link:
if (editorMessage.value.startsWith("nn://")) {
openInternalLink(editorMessage.value);
console.log(
@@ -554,7 +554,7 @@ export const useEditorEvents = (
}
break;
case EventTypes.previewAttachment: {
case EditorEvents.previewAttachment: {
const hash = (editorMessage.value as Attachment)?.hash;
const attachment = await db.attachments?.attachment(hash);
if (!attachment) return;
@@ -565,26 +565,41 @@ export const useEditorEvents = (
}
break;
}
case EventTypes.copyToClipboard: {
case EditorEvents.copyToClipboard: {
Clipboard.setString(editorMessage.value as string);
break;
}
case EventTypes.tabsChanged: {
case EditorEvents.saveScroll: {
useTabStore.getState().updateTab(editorMessage.tabId, {
session: {
...editorMessage.value
}
});
break;
}
case EditorEvents.newNote: {
eSendEvent(eOnLoadNote, {
tabId: editorMessage.tabId,
newNote: true
});
break;
}
case EditorEvents.tabsChanged: {
// useTabStore.setState({
// tabs: (editorMessage.value as any)?.tabs,
// currentTab: (editorMessage.value as any)?.currentTab
// });
// console.log("Tabs updated");
//
break;
}
case EventTypes.toc:
case EditorEvents.toc:
TableOfContents.present(editorMessage.value);
break;
case EventTypes.showTabs: {
case EditorEvents.showTabs: {
EditorTabs.present();
break;
}
case EventTypes.error: {
case EditorEvents.error: {
presentSheet({
component: (
<Issue
@@ -596,27 +611,15 @@ export const useEditorEvents = (
});
break;
}
case EventTypes.tabFocused: {
console.log(
"Focused tab",
editorMessage.tabId,
editorMessage.noteId,
"Content:",
editorMessage.value
);
case EditorEvents.tabFocused: {
eSendEvent(eEditorTabFocused, editorMessage.tabId);
if (
(!editorMessage.value || editor.currentLoadingNoteId.current) &&
editorMessage.noteId
) {
if (!editor.state.current.initialLoadCalled) break;
if (editorMessage.noteId) {
if (!useSettingStore.getState().isAppLoading) {
const note = await db.notes.note(editorMessage.noteId);
if (note) {
eSendEvent(eOnLoadNote, {
item: note,
forced: true,
tabId: editorMessage.tabId
});
}
@@ -628,7 +631,6 @@ export const useEditorEvents = (
if (note) {
eSendEvent(eOnLoadNote, {
item: note,
forced: true,
tabId: editorMessage.tabId
});
}
@@ -639,7 +641,7 @@ export const useEditorEvents = (
break;
}
case EventTypes.createInternalLink: {
case EditorEvents.createInternalLink: {
LinkNote.present(
editorMessage.value.attributes,
editorMessage.resolverId as string
@@ -647,25 +649,37 @@ export const useEditorEvents = (
break;
}
case EventTypes.unlock: {
case EditorEvents.unlock: {
eSendEvent(eUnlockWithPassword, editorMessage.value);
break;
}
case EventTypes.unlockWithBiometrics: {
case EditorEvents.goBack: {
useTabStore.getState().goBack();
break;
}
case EditorEvents.goForward: {
useTabStore.getState().goForward();
break;
}
case EditorEvents.unlockWithBiometrics: {
eSendEvent(eUnlockWithBiometrics);
break;
}
case EventTypes.disableReadonlyMode: {
case EditorEvents.disableReadonlyMode: {
const noteId = editorMessage.value;
if (noteId) {
await db.notes.readonly(false, noteId);
editor.note.current[noteId] = await db.notes?.note(noteId);
useTabStore
.getState()
.updateTab(useTabStore.getState().currentTab, {
readonly: false
.updateTab(useTabStore.getState().currentTab!, {
session: {
readonly: false
}
});
setTimeout(() => {
Navigation.queueRoutesForUpdate();

View File

@@ -32,8 +32,11 @@ import {
isEncryptedContent,
isTrashItem
} from "@notesnook/core";
import { EditorEvents } from "@notesnook/editor-mobile/src/utils/editor-events";
import { NativeEvents } from "@notesnook/editor-mobile/src/utils/native-events";
import { strings } from "@notesnook/intl";
import { useThemeEngineStore } from "@notesnook/theme";
import { Mutex } from "async-mutex";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import WebView from "react-native-webview";
import { DatabaseLogger, db } from "../../../common/database";
@@ -46,6 +49,7 @@ import {
eUnSubscribeEvent
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { NotePreviewWidget } from "../../../services/note-preview-widget";
import Notifications from "../../../services/notifications";
import SettingsService from "../../../services/settings";
import { useSettingStore } from "../../../stores/use-setting-store";
@@ -62,12 +66,10 @@ import { sleep } from "../../../utils/time";
import { unlockVault } from "../../../utils/unlock-vault";
import { onNoteCreated } from "../../notes/common";
import Commands from "./commands";
import { EventTypes } from "./editor-events";
import { SessionHistory } from "./session-history";
import { EditorState, SavePayload } from "./types";
import { syncTabs, useTabStore } from "./use-tab-store";
import { TabSessionItem, syncTabs, useTabStore } from "./use-tab-store";
import {
EditorEvents,
clearAppState,
defaultState,
getAppState,
@@ -75,11 +77,63 @@ import {
isEditorLoaded,
post
} from "./utils";
import { Linking } from "react-native";
const loadNoteMutex = new Mutex();
type NoteWithContent = Note & {
content?: NoteContent<false>;
};
type LocalTabStateT = {
editedAt: number;
lastFocusedAt: number;
};
class LocalTabState {
state: Record<string, LocalTabStateT> = {};
noteEditedTime: Record<string, number> = {};
setEditTime(noteId: string, time: number) {
this.noteEditedTime[noteId] = time;
}
get(tabId: string) {
return this.state[tabId] || {};
}
set(tabId: string, state: Partial<LocalTabStateT>) {
this.state[tabId] = {
...this.state[tabId],
...state
};
}
clear(tabId: string) {
delete this.state[tabId];
}
reset() {
this.state = {};
}
needsRefresh(tabId: string, locked: boolean, readonly: boolean) {
const state = this.get(tabId);
const tabSession = useTabStore.getState().getTab(tabId)?.session;
const noteId = useTabStore.getState().getNoteIdForTab(tabId);
if (
tabSession?.locked !== locked ||
tabSession?.readonly !== readonly ||
!noteId
) {
return true;
}
return !state.editedAt || state.editedAt < this.noteEditedTime[noteId];
}
}
export const useEditor = (
editorId = "",
readonly?: boolean,
@@ -98,7 +152,6 @@ export const useEditor = (
isPreview?: boolean;
};
})
| null
| undefined
>
>({});
@@ -118,15 +171,17 @@ export const useEditor = (
const lastContentChangeTime = useRef<Record<string, number>>({});
const lock = useRef(false);
const currentLoadingNoteId = useRef<string>();
const loadingState = useRef<string>();
const lastTabFocused = useRef(0);
const lastTabFocused = useRef<string>();
const localTabState = useRef<LocalTabState>(new LocalTabState());
const blockIdRef = useRef<string>();
const postMessage = useCallback(
async <T>(type: string, data: T, tabId?: number, waitFor = 300) =>
async <T>(type: string, data: T, tabId?: string, waitFor = 300) =>
await post(
editorRef,
sessionIdRef.current,
typeof tabId !== "number" ? useTabStore.getState().currentTab : tabId,
tabId || useTabStore.getState().currentTab!,
type,
data,
waitFor
@@ -141,7 +196,7 @@ export const useEditor = (
}, [commands, insets, isDefaultEditor]);
useEffect(() => {
postMessage(EditorEvents.theme, theme);
postMessage(NativeEvents.theme, theme);
}, [theme, postMessage]);
useEffect(() => {
@@ -151,15 +206,14 @@ export const useEditor = (
}, [commands, tags]);
useEffect(() => {
const event = eSubscribeEvent(eEditorTabFocused, (tabId) => {
console.log("Editot tab focus changed", lastTabFocused.current, tabId);
const event = eSubscribeEvent(eEditorTabFocused, (tabId: string) => {
if (lastTabFocused.current !== tabId) lock.current = false;
lastTabFocused.current = tabId as number;
lastTabFocused.current = tabId;
});
return () => {
event?.unsubscribe();
};
});
}, []);
const overlay = useCallback(
(show: boolean, data = { type: "new" }) => {
@@ -188,12 +242,11 @@ export const useEditor = (
);
const reset = useCallback(
async (tabId: number, resetState = true, resetContent = true) => {
console.log("Resetting tab:", tabId);
async (tabId: string, resetState = true, resetContent = true) => {
const noteId = useTabStore.getState().getNoteIdForTab(tabId);
if (noteId) {
currentNotes.current?.id && db.fs().cancel(noteId);
currentNotes.current[noteId] = null;
currentNotes.current[noteId] = undefined;
currentContents.current[noteId] = null;
editorSessionHistory.clearSession(noteId);
lastContentChangeTime.current[noteId] = 0;
@@ -201,18 +254,11 @@ export const useEditor = (
}
saveCount.current = 0;
loadingState.current = undefined;
currentLoadingNoteId.current = undefined;
lock.current = false;
resetContent && postMessage(EditorEvents.title, "", tabId);
resetContent && postMessage(NativeEvents.title, "", tabId);
resetContent && (await commands.clearContent(tabId));
resetContent && (await commands.clearTags(tabId));
useTabStore.getState().updateTab(tabId, {
noteId: undefined,
locked: false,
noteLocked: false,
readonly: false
});
},
[commands, editorSessionHistory, postMessage]
);
@@ -232,6 +278,16 @@ export const useEditor = (
try {
if (id && !(await db.notes?.note(id))) {
await reset(tabId);
useTabStore.getState().updateTab(tabId, {
session: {
noteId: undefined,
noteLocked: undefined,
locked: undefined,
readonly: undefined,
scrollTop: undefined,
selection: undefined
}
});
return;
}
let note = id ? await db.notes?.note(id) : undefined;
@@ -270,13 +326,6 @@ export const useEditor = (
};
}
// If note is edited, the tab becomes a persistent tab automatically.
if (useTabStore.getState().getTab(tabId)?.previewTab) {
useTabStore.getState().updateTab(tabId, {
previewTab: false
});
}
let saved = false;
setTimeout(() => {
if (saved) return;
@@ -311,7 +360,9 @@ export const useEditor = (
}
useTabStore.getState().updateTab(tabId, {
noteId: id
session: {
noteId: id
}
});
const defaultNotebook = db.settings.getDefaultNotebook();
@@ -326,7 +377,7 @@ export const useEditor = (
if (!noteData.title) {
postMessage(
EditorEvents.title,
NativeEvents.title,
currentNotes.current[id]?.title,
tabId
);
@@ -386,12 +437,19 @@ export const useEditor = (
id === useTabStore.getState().getCurrentNoteId() &&
pendingChanges
) {
postMessage(EditorEvents.title, title || note?.title, tabId);
postMessage(EditorEvents.html, data, tabId);
postMessage(NativeEvents.title, title || note?.title, tabId);
postMessage(NativeEvents.html, data, tabId);
currentNotes.current[id] = note;
}
saveCount.current++;
clearTimeout(timers.current.onsave);
timers.current.onsave = setTimeout(async () => {
if (!id || !note) return;
NotePreviewWidget.updateNote(id, note);
}, 500);
return id;
} catch (e) {
console.error(e);
@@ -429,183 +487,222 @@ export const useEditor = (
);
const loadNote = useCallback(
async (event: {
(event: {
item?: Note;
forced?: boolean;
newNote?: boolean;
tabId?: number;
tabId?: string;
blockId?: string;
presistTab?: boolean;
session?: TabSessionItem;
newTab?: boolean;
refresh?: boolean;
}) => {
if (!event) return;
console.log(event.item?.id, event?.item?.title, "loading note...");
if (event.blockId) {
blockIdRef.current = event.blockId;
}
state.current.currentlyEditing = true;
if (
!state.current.ready &&
(await isEditorLoaded(
editorRef,
sessionIdRef.current,
useTabStore.getState().currentTab
))
) {
state.current.ready = true;
}
if (event.newNote) {
useTabStore.getState().focusEmptyTab();
const tabId = useTabStore.getState().currentTab;
currentNotes.current && (await reset(tabId));
setTimeout(() => {
if (state.current?.ready && !state.current.movedAway)
commands.focus(tabId);
});
} else {
if (!event.item) {
overlay(false);
return;
loadNoteMutex.runExclusive(async () => {
if (!event) return;
if (event.blockId) {
blockIdRef.current = event.blockId;
}
console.log("LOADING NOTE", event.item.id);
const item = event.item;
const currentTab = useTabStore
.getState()
.getTab(useTabStore.getState().currentTab);
if (currentTab?.previewTab && item.id !== currentTab.noteId) {
await commands.setLoading(true, useTabStore.getState().currentTab);
}
const isLockedNote = await db.vaults.itemExists(
event.item as ItemReference
);
const tabLocked =
isLockedNote && !(event.item as NoteWithContent).content;
// If note was already opened in a tab, focus that tab.
if (typeof event.tabId !== "number") {
if (useTabStore.getState().hasTabForNote(event.item.id)) {
const tabId = useTabStore.getState().getTabForNote(event.item.id);
if (typeof tabId === "number") {
useTabStore.getState().updateTab(tabId, {
readonly: event.item.readonly || readonly,
locked: tabLocked,
noteLocked: isLockedNote
});
useTabStore.getState().focusTab(tabId);
setTimeout(() => {
if (blockIdRef.current) {
commands.scrollIntoViewById(blockIdRef.current);
blockIdRef.current = undefined;
}
}, 150);
}
console.log("Note already loaded, focusing the tab");
} else {
if (event.presistTab) {
// Open note in new tab.
useTabStore.getState().newTab({
readonly: event.item.readonly || readonly,
locked: tabLocked,
noteLocked: isLockedNote,
noteId: event.item.id,
previewTab: false
});
console.log("Opening note in new tab");
} else {
console.log("Opening note in preview tab");
// Otherwise we focus the preview tab or create one to open the note in.
useTabStore.getState().focusPreviewTab(event.item.id, {
readonly: event.item.readonly || readonly,
locked: tabLocked,
noteLocked: isLockedNote
});
}
}
} else {
if (lastTabFocused.current !== event.tabId) {
useTabStore.getState().focusTab(event.tabId);
}
}
const tabId = event.tabId || useTabStore.getState().currentTab;
if (lastTabFocused.current !== tabId) {
// if ((await waitForEvent(eEditorTabFocused, 1000)) !== tabId) {
// console.log("tab id did not match after focus in 1000ms");
// return;
// }
currentLoadingNoteId.current = item.id;
console.log("Waiting for tab to focus");
return;
}
state.current.movedAway = false;
state.current.currentlyEditing = true;
if (!tabLocked) {
await loadContent(item);
}
if (
currentNotes.current[item.id] &&
loadingState.current &&
currentContents.current[item.id]?.data &&
loadingState.current === currentContents.current[item.id]?.data
!state.current.ready &&
(await isEditorLoaded(
editorRef,
sessionIdRef.current,
useTabStore.getState().currentTab!
))
) {
// If note is already loading, return.
console.log("Note is already loading...");
return;
state.current.ready = true;
}
if (event.newNote && !currentLoadingNoteId.current) {
let tabId;
if (!state.current.ready) {
const currentTab = useTabStore
.getState()
.getTab(useTabStore.getState().currentTab as string);
if (useTabStore.getState().tabs.length === 0 || currentTab?.pinned) {
tabId = useTabStore.getState().newTab();
} else {
tabId = useTabStore.getState().currentTab;
await reset(tabId!, true, true);
if (
event.session?.noteId ||
useTabStore.getState().getTab(tabId!)?.session?.noteId
) {
useTabStore.getState().newTabSession(tabId!, {});
}
}
setTimeout(() => {
if (state.current?.ready && !state.current.movedAway)
commands.focus(tabId!);
});
} else {
if (!event.item) {
overlay(false);
return;
}
const item = event.item;
const currentTab = useTabStore
.getState()
.getTab(useTabStore.getState().currentTab as string);
// If note is already open in a tab, focus that tab.
if (useTabStore.getState().hasTabForNote(item.id) && !event.newTab) {
const tabId = useTabStore.getState().getTabForNote(item.id);
if (
currentTab?.session?.noteId !== item.id &&
tabId !== useTabStore.getState().currentTab
) {
useTabStore.getState().focusTab(tabId as string);
return;
}
}
const isLockedNote = await db.vaults.itemExists(
event.item as ItemReference
);
const tabLocked =
isLockedNote && !(event.item as NoteWithContent).content;
const tabId = event.tabId
? event.tabId
: useTabStore.getState().currentTab;
// Check if tab needs to be refreshed.
if (!event.newTab) {
if (
!event.refresh &&
tabId &&
event.item.id === useTabStore.getState().getNoteIdForTab(tabId) &&
!localTabState.current?.needsRefresh(
tabId,
isLockedNote,
item.readonly
)
) {
commands.setLoading(false, tabId);
return;
} else {
localTabState.current?.setEditTime(
item.id,
localTabState.current?.noteEditedTime[item.id] ||
item.dateEdited
);
localTabState.current?.set(tabId!, {
editedAt:
localTabState.current?.noteEditedTime[item.id] ||
item.dateEdited
});
}
}
currentLoadingNoteId.current = item.id;
// Show loading overlay if note is not already loaded.
if (
tabId &&
(event.item?.id !== useTabStore.getState().getNoteIdForTab(tabId) ||
!currentContents.current[event.item.id]?.data)
) {
await commands.setLoading(true, tabId);
}
const session: Partial<TabSessionItem> = event.session || {
noteId: event.item.id
};
session.noteLocked = isLockedNote;
session.locked = tabLocked;
session.readonly = item.readonly;
const tab = useTabStore.getState().getTab(tabId!);
if (
useTabStore.getState().tabs.length === 0 ||
event.newTab ||
(currentTab?.pinned &&
event.item.id !== currentTab?.session?.noteId)
) {
useTabStore.getState().newTab({
session: session
});
return;
} else {
// A new session is created if the note is changed.
// If the note is already opened, the session is updated.
if (
!tab?.session ||
(event.item.id !== tab?.session?.noteId && tab?.session?.noteId)
) {
useTabStore.getState().newTabSession(tabId!, session);
} else {
useTabStore.getState().updateTab(tabId!, {
session: session
});
}
}
if (lastTabFocused.current !== tabId) return;
if (tabBarRef.current?.page() === 2) {
state.current.movedAway = false;
}
state.current.currentlyEditing = true;
if (!tabLocked) {
await loadContent(item);
} else {
commands.focus(tabId!);
}
lastContentChangeTime.current[item.id] = item.dateEdited;
currentNotes.current[item.id] = item;
return;
if (!currentNotes.current[item.id]) return;
editorSessionHistory.newSession(item.id);
await commands.setStatus(
getFormattedDate(item.dateEdited, "date-time"),
"Saved",
tabId!
);
await postMessage(NativeEvents.title, item.title, tabId);
overlay(false);
await postMessage(
NativeEvents.html,
{
data: currentContents.current[item.id]?.data || "",
scrollTop: tab?.session?.scrollTop,
selection: tab?.session?.selection
},
tabId,
10000
);
setTimeout(() => {
if (blockIdRef.current) {
commands.scrollIntoViewById(blockIdRef.current);
blockIdRef.current = undefined;
}
}, 300);
await commands.setTags(item);
commands.setSettings();
setTimeout(() => {
if (currentLoadingNoteId.current === event.item?.id) {
currentLoadingNoteId.current = undefined;
}
}, 300);
}
lastContentChangeTime.current[item.id] = 0;
currentLoadingNoteId.current = item.id;
currentNotes.current[item.id] = item;
if (!currentNotes.current[item.id]) return;
editorSessionHistory.newSession(item.id);
await commands.setStatus(
getFormattedDate(item.dateEdited, "date-time"),
strings.saved(),
tabId
);
await postMessage(EditorEvents.title, item.title, tabId);
overlay(false);
loadingState.current = currentContents.current[item.id]?.data;
await postMessage(
EditorEvents.html,
currentContents.current[item.id]?.data || "",
tabId,
10000
);
setTimeout(() => {
if (blockIdRef.current) {
commands.scrollIntoViewById(blockIdRef.current);
blockIdRef.current = undefined;
}
}, 300);
loadingState.current = undefined;
await commands.setTags(item);
commands.setSettings();
setTimeout(() => {
if (currentLoadingNoteId.current === event.item?.id) {
currentLoadingNoteId.current = undefined;
}
}, 300);
}
postMessage(EditorEvents.theme, theme);
postMessage(NativeEvents.theme, theme);
});
},
[
commands,
@@ -613,7 +710,6 @@ export const useEditor = (
loadContent,
overlay,
postMessage,
readonly,
reset,
theme
]
@@ -625,97 +721,116 @@ export const useEditor = (
isLocal?: boolean
) => {
try {
await (async () => {
if (SettingsService.get().disableRealtimeSync && !isLocal) return;
if (!data) return;
if (SettingsService.get().disableRealtimeSync && !isLocal) return;
if (!data) return;
if (isDeleted(data) || isTrashItem(data)) {
const tabId = useTabStore.getState().getTabForNote(data.id);
if (tabId !== undefined) {
console.log("Removing tab");
await commands.clearContent(tabId);
useTabStore.getState().removeTab(tabId);
}
return;
if (isDeleted(data) || isTrashItem(data)) {
const tabId = useTabStore.getState().getTabForNote(data.id);
if (tabId !== undefined) {
await commands.clearContent(tabId);
useTabStore.getState().removeTab(tabId);
}
return;
}
const noteId =
(data as ContentItem).type === "tiptap"
? (data as ContentItem).noteId
: data.id;
const noteId =
(data as ContentItem).type === "tiptap"
? (data as ContentItem).noteId
: data.id;
if (!useTabStore.getState().hasTabForNote(noteId)) return;
const tabId = useTabStore.getState().getTabForNote(noteId) as number;
const note = data.type === "note" ? data : await db.notes?.note(noteId);
const tab = useTabStore.getState().getTab(tabId);
lock.current = true;
const note =
data.type === "note" ? data : await db.notes?.note(noteId);
// Handle this case where note was locked on another device and synced.
let locked = note
? await db.vaults.itemExists(note as ItemReference)
: false;
lock.current = true;
if (data.type === "tiptap") {
locked = data.locked;
}
// Handle this case where note was locked on another device and synced.
const locked = note
? await db.vaults.itemExists(note as ItemReference)
: false;
useTabStore.getState().forEachNoteTab(noteId, async (tab) => {
const tabId = tab.id;
let didUnlock = false;
if (note) {
if (!locked && tab?.noteLocked) {
// Note lock removed.
if (tab.locked) {
if (useTabStore.getState().currentTab === tabId) {
eSendEvent(eOnLoadNote, {
item: note,
forced: true
if (!locked && tab?.session?.noteLocked) {
if (tab?.session?.noteLocked || tab?.session?.locked) {
if (useTabStore.getState().currentTab !== tabId) {
localTabState.current?.set(tabId, {
editedAt: 0
});
} else {
useTabStore.getState().updateTab(tabId, {
}
didUnlock = true;
useTabStore.getState().updateTab(tabId, {
session: {
locked: false,
noteLocked: false
});
commands.setLoading(true, tabId);
}
}
});
}
} else if (!tab?.noteLocked && locked) {
// Note lock added.
} else if (!tab?.session?.noteLocked && locked) {
useTabStore.getState().updateTab(tabId, {
locked: true,
noteLocked: true
session: {
locked: true,
noteLocked: true
}
});
commands.clearContent(tabId);
if (useTabStore.getState().currentTab !== tabId) {
commands.clearContent(tabId);
commands.setLoading(true, tabId);
}
localTabState.current?.set(tabId, {
editedAt: 0
});
}
if (currentNotes.current[noteId]?.title !== note.title) {
postMessage(EditorEvents.title, note.title, tabId);
if (
currentNotes.current[noteId]?.title !== note.title ||
didUnlock
) {
postMessage(NativeEvents.title, note.title, tabId);
}
commands.setTags(note);
if (currentNotes.current[noteId]?.dateEdited !== note.dateEdited) {
if (
currentNotes.current[noteId]?.dateEdited !== note.dateEdited ||
didUnlock
) {
commands.setStatus(
getFormattedDate(note.dateEdited, "date-time"),
strings.saved(),
tabId as number
tabId as string
);
}
useTabStore.getState().updateTab(tabId, {
readonly: note.readonly
});
if (tab.session?.readonly !== note.readonly || didUnlock) {
useTabStore.getState().updateTab(tabId, {
session: {
readonly: note.readonly
}
});
}
}
if (data.type === "tiptap" && note && !isLocal) {
if (lastContentChangeTime.current[noteId] >= data.dateModified) {
if (
lastContentChangeTime.current[noteId] >= data.dateModified &&
!didUnlock
) {
return;
}
if (locked && isEncryptedContent(data)) {
const decryptedContent = await db.vault?.decryptContent(data);
if (!decryptedContent) {
useTabStore.getState().updateTab(tabId, {
locked: true,
noteLocked: true
useTabStore.getState().updateTab(tab.id, {
session: {
locked: true,
noteLocked: true
}
});
if (useTabStore.getState().currentTab !== tabId) {
commands.clearContent(tabId);
@@ -723,26 +838,43 @@ export const useEditor = (
}
} else {
await postMessage(
EditorEvents.updatehtml,
decryptedContent.data,
NativeEvents.updatehtml,
{
data: decryptedContent.data,
selection: tab.session?.selection,
scrollTop: tab.session?.scrollTop
},
tabId
);
currentContents.current[note.id] = decryptedContent;
}
} else {
const _nextContent = data.data;
if (_nextContent === currentContents.current[note.id]?.data) {
if (
_nextContent === currentContents.current[note.id]?.data &&
!didUnlock
) {
return;
}
lastContentChangeTime.current[note.id] = note.dateEdited;
await postMessage(EditorEvents.updatehtml, _nextContent, tabId);
console.log(tab.session?.selection);
await postMessage(
NativeEvents.updatehtml,
{
data: _nextContent,
selection: tab.session?.selection,
scrollTop: tab.session?.scrollTop
},
tabId
);
if (!isEncryptedContent(data)) {
currentContents.current[note.id] =
data as UnencryptedContentItem;
}
}
}
})();
});
} catch (e) {
DatabaseLogger.error(e as Error, "Error when applying sync changes");
} finally {
@@ -778,7 +910,7 @@ export const useEditor = (
content?: string;
type: string;
ignoreEdit: boolean;
tabId: number;
tabId: string;
pendingChanges?: boolean;
}) => {
DatabaseLogger.log(
@@ -807,9 +939,13 @@ export const useEditor = (
if (noteId) {
lastContentChangeTime.current[noteId] = Date.now();
localTabState.current?.setEditTime(noteId, Date.now());
localTabState?.current?.set(tabId, {
editedAt: Date.now()
});
}
if (type === EventTypes.content && noteId) {
if (type === EditorEvents.content && noteId) {
currentContents.current[noteId as string] = {
data: content,
type: "tiptap",
@@ -859,7 +995,9 @@ export const useEditor = (
if (!appState) return;
state.current.isRestoringState = true;
state.current.currentlyEditing = true;
state.current.movedAway = false;
if (tabBarRef.current?.page() === 2) {
state.current.movedAway = false;
}
if (!state.current.editorStateRestored) {
state.current.isRestoringState = true;
@@ -890,9 +1028,12 @@ export const useEditor = (
!(await isEditorLoaded(
editorRef,
sessionIdRef.current,
useTabStore.getState().currentTab
useTabStore.getState().currentTab!
))
) {
localTabState.current?.set(useTabStore.getState().currentTab!, {
editedAt: 0
});
eSendEvent(eEditorReset, "onReady");
return false;
} else {
@@ -903,36 +1044,63 @@ export const useEditor = (
}, [isDefaultEditor, restoreEditorState]);
const onLoad = useCallback(async () => {
const isAppLoading = useSettingStore.getState().isAppLoading;
if (isAppLoading) {
const sub = useSettingStore.subscribe((state) => {
if (!state.isAppLoading) {
sub();
onLoad();
}
});
return;
}
setTimeout(() => {
postMessage(EditorEvents.theme, theme);
postMessage(NativeEvents.theme, theme);
});
commands.setInsets(
isDefaultEditor ? insets : { top: 0, left: 0, right: 0, bottom: 0 }
);
await commands.setSettings();
localTabState.current?.set(useTabStore.getState().currentTab!, {
editedAt: 0
});
if (!state.current.ready) {
state.current.ready = true;
}
const noteId = useTabStore.getState().getCurrentNoteId();
if (!noteId) {
loadNote({ newNote: true });
if (tabBarRef.current?.page() === 1) {
state.current.currentlyEditing = false;
}
} else if (state.current?.initialLoadCalled) {
const note = currentNotes.current[noteId];
if (note) {
loadNote({
item: note
});
}
}
if (!state.current?.initialLoadCalled) {
const url = await Linking.getInitialURL();
let noteId = url && new URL(url).searchParams.get("id");
if (noteId) {
const note = await db.notes?.note(noteId);
if (note) {
loadNote({
item: note
});
}
tabBarRef.current?.goToPage(1);
} else {
noteId = useTabStore.getState().getCurrentNoteId() || null;
if (!noteId) {
loadNote({ newNote: true });
if (tabBarRef.current?.page() === 1) {
state.current.currentlyEditing = false;
}
} else {
const note = await db.notes.note(noteId);
if (note) {
loadNote({
item: note
});
}
}
}
state.current.initialLoadCalled = true;
overlay(false);
}
overlay(false);
}, [
postMessage,
theme,

View File

@@ -16,18 +16,27 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {
TabHistory as TabHistoryType,
TabSessionHistory
} from "@notesnook/common";
import { getId } from "@notesnook/core";
import { MMKVLoader } from "react-native-mmkv-storage";
import create from "zustand";
import { persist, StateStorage } from "zustand/middleware";
import { db } from "../../../common/database";
import { MMKV } from "../../../common/database/mmkv";
import { eSendEvent } from "../../../services/event-manager";
import { eOnLoadNote } from "../../../utils/events";
import { editorController } from "./utils";
class History {
history: number[];
class TabHistory {
history: string[];
constructor() {
this.history = [0];
this.history = [];
}
add(item: number) {
add(item: string) {
const index = this.history.findIndex((id) => item === id);
if (index !== -1) {
// Item already exists, move it to the top
@@ -36,19 +45,19 @@ class History {
this.history.unshift(item); // Add item to the beginning of the array
useTabStore.setState({
tabHistory: this.history.slice()
historyNew: this.history.slice()
});
return true; // Item added successfully
}
remove(id: number) {
remove(id: string) {
const index = this.history.findIndex((item) => item === id);
if (index >= -1 && index < this.history.length) {
const removedItem = this.history.splice(index, 1)[0];
return removedItem;
}
useTabStore.setState({
tabHistory: this.history.slice()
historyNew: this.history.slice()
});
return null; // Invalid index
}
@@ -59,7 +68,7 @@ class History {
return restoredItem;
}
useTabStore.setState({
tabHistory: this.history.slice()
historyNew: this.history.slice()
});
return null; // History is empty
}
@@ -69,149 +78,366 @@ class History {
}
}
export type TabItem = {
id: number;
export type TabSessionItem = {
id: string;
noteId?: string;
previewTab?: boolean;
readonly?: boolean;
locked?: boolean;
scrollTop?: number;
selection?: { to: number; from: number };
noteLocked?: boolean;
pinned?: boolean;
locked?: boolean;
readonly?: boolean;
};
const history = new History();
const TabSessionStorageKV = new MMKVLoader()
.withInstanceID("tab-session-storage")
.disableIndexing()
.initialize();
class TabSessionStorage {
static storage: typeof TabSessionStorageKV = TabSessionStorageKV;
static get(id: string): TabSessionItem | null {
if (!id) return null;
return TabSessionStorage.storage.getMap(id);
}
static set(id: string, session: TabSessionItem): void {
if (!id) return;
TabSessionStorage.storage.setMap(id, session);
}
static update(id: string, session: Partial<TabSessionItem>) {
const currentSession = TabSessionStorage.get(id);
const newSession = {
...currentSession,
...session,
id: currentSession?.id || session.id || id
};
TabSessionStorage.set(id, newSession as TabSessionItem);
return newSession;
}
static remove(id: string) {
if (!id) return;
TabSessionStorageKV.removeItem(id);
}
}
export function syncTabs(
type: "tabs" | "history" | "biometry" | "all" = "all"
) {
const data: Partial<TabStore> = {};
if (type === "tabs" || type === "all") {
data.tabs = useTabStore.getState().tabs;
data.currentTab = useTabStore.getState().currentTab;
}
if (type === "history" || type === "all") {
data.canGoBack = useTabStore.getState().canGoBack;
data.canGoForward = useTabStore.getState().canGoForward;
data.sessionId = useTabStore.getState().sessionId;
}
if (type === "biometry" || type === "all") {
data.biometryAvailable = useTabStore.getState().biometryAvailable;
data.biometryEnrolled = useTabStore.getState().biometryEnrolled;
}
editorController.current?.commands.doAsync(`
globalThis.tabStore?.setState(${JSON.stringify(data)});
`);
}
export const tabSessionHistory = new TabSessionHistory({
get() {
return useTabStore.getState();
},
set(state) {
useTabStore.setState({
...state
});
}
});
export type TabItem = {
id: string;
pinned?: boolean;
needsRefresh?: boolean;
session?: Partial<TabSessionItem>;
};
const history = new TabHistory();
export type TabStore = {
tabs: TabItem[];
currentTab: number;
updateTab: (id: number, options: Omit<Partial<TabItem>, "id">) => void;
currentTab: string;
updateTab: (id: string, options: Omit<Partial<TabItem>, "id">) => void;
focusPreviewTab: (
noteId: string,
options: Omit<Partial<TabItem>, "id">
) => void;
removeTab: (index: number) => void;
removeTab: (index: string) => void;
moveTab: (index: number, toIndex: number) => void;
newTab: (options?: Omit<Partial<TabItem>, "id">) => void;
focusTab: (id: number) => void;
getNoteIdForTab: (id: number) => string | undefined;
getTabForNote: (noteId: string) => number | undefined;
newTab: (options?: Omit<Partial<TabItem>, "id">) => string;
focusTab: (id: string) => void;
getNoteIdForTab: (id: string) => string | undefined;
getTabForNote: (noteId: string) => string | undefined;
getTabsForNote: (noteId: string) => TabItem[];
forEachNoteTab: (noteId: string, cb: (tab: TabItem) => void) => void;
hasTabForNote: (noteId: string) => boolean;
focusEmptyTab: () => void;
getCurrentNoteId: () => string | undefined;
getTab: (tabId: number) => TabItem | undefined;
tabHistory: number[];
getTab: (tabId: string) => TabItem | undefined;
newTabSession: (
id: string,
options?: Omit<Partial<TabSessionItem>, "id">
) => void;
historyNew: string[];
biometryAvailable?: boolean;
biometryEnrolled?: boolean;
tabSessionHistory: TabHistoryType;
goBack(): void;
goForward(): void;
loadSession: (id: string) => Promise<boolean>;
canGoBack?: boolean;
canGoForward?: boolean;
sessionId?: string;
};
function getId(id: number, tabs: TabItem[]): number {
const exists = tabs.find((t) => t.id === id);
if (exists) {
return getId(id + 1, tabs);
}
return id;
}
const DEFAULT_TABS = {
tabs: [
{
id: "679da59a3924d4bd56d16d3f",
session: {
id: "679da5a5667a16db2353a062"
}
}
],
tabSessionHistory: {
"679da59a3924d4bd56d16d3f": {
backStack: ["679da5a5667a16db2353a062"],
forwardStack: [] as string[]
}
} as TabHistoryType,
historyNew: ["679da59a3924d4bd56d16d3f"],
currentTab: "679da59a3924d4bd56d16d3f"
} as TabStore;
export function syncTabs() {
editorController.current?.commands.doAsync(`
globalThis.tabStore?.setState({
tabs: ${JSON.stringify(useTabStore.getState().tabs)},
currentTab: ${useTabStore.getState().currentTab},
biometryAvailable: ${useTabStore.getState().biometryAvailable},
biometryEnrolled: ${useTabStore.getState().biometryEnrolled}
});
`);
export function resetTabStore() {
useTabStore.setState({
...DEFAULT_TABS
});
TabSessionStorage.storage.clearStore();
}
export const useTabStore = create<TabStore>(
persist(
(set, get) => ({
tabs: [
{
id: 0
...DEFAULT_TABS,
newTabSession: (
_id?: string,
options?: Omit<Partial<TabSessionItem>, "id">
) => {
const tabId = _id || (get().currentTab as string);
const sessionHistory = get().tabSessionHistory[tabId];
let oldSessionId: string | undefined = undefined;
if (sessionHistory) {
const allSessions = sessionHistory.backStack.concat(
sessionHistory.forwardStack
);
allSessions.forEach((id) => {
if (TabSessionStorage.get(id)?.noteId === options?.noteId) {
oldSessionId = id;
}
});
}
],
tabHistory: [0],
history: new History(),
currentTab: 0,
updateTab: (id: number, options: Omit<Partial<TabItem>, "id">) => {
if (!options) return;
const index = get().tabs.findIndex((t) => t.id === id);
const sessionId =
oldSessionId &&
tabSessionHistory.currentSessionId(tabId) === oldSessionId
? oldSessionId
: tabSessionHistory.add(tabId, oldSessionId);
let session: Partial<TabSessionItem>;
if (!oldSessionId) {
session = {
id: sessionId,
...options
};
TabSessionStorage.set(sessionId, session as TabSessionItem);
} else {
session = {
id: sessionId,
...TabSessionStorage.get(oldSessionId),
...options
};
}
const index = get().tabs.findIndex((t) => t.id === tabId);
if (index == -1) return;
const tabs = [...get().tabs];
tabs[index] = {
...tabs[index],
...options
};
session: session
} as TabItem;
set({
tabs: tabs
});
syncTabs();
},
focusPreviewTab: (
noteId: string,
options: Omit<Partial<TabItem>, "id" | "noteId">
) => {
const index = get().tabs.findIndex((t) => t.previewTab);
if (index === -1)
return get().newTab({
noteId,
previewTab: true,
...options
});
updateTab: (id: string, options: Omit<Partial<TabItem>, "id">) => {
if (!options) return;
const index = get().tabs.findIndex((t) => t.id === id);
if (index == -1) return;
const tabs = [...get().tabs];
const sessionId =
options.session?.id || (tabs[index].session?.id as string);
const updatedSession = !options.session
? tabs[index].session
: TabSessionStorage.update(sessionId, options.session);
tabs[index] = {
...tabs[index],
...options,
previewTab: true,
noteId: noteId
};
console.log("focus preview", noteId);
session: updatedSession
} as TabItem;
set({
tabs: tabs
});
get().focusTab(tabs[index].id);
syncTabs();
},
removeTab: (id: number) => {
const index = get().tabs.findIndex((t) => t.id === id);
goBack: async () => {
const currentTab = get().currentTab;
if (!currentTab) return;
if (!tabSessionHistory.canGoBack(currentTab)) return;
const id = tabSessionHistory.back(currentTab) as string;
const sessionLoaded = await get().loadSession(id);
if (!sessionLoaded) {
const canGoBack = tabSessionHistory.canGoBack(currentTab);
if (!canGoBack) {
tabSessionHistory.forward(currentTab);
syncTabs();
} else {
get().goBack();
tabSessionHistory.remove(currentTab, id);
TabSessionStorage.remove(id);
return;
}
} else {
syncTabs();
}
},
goForward: async () => {
const currentTab = get().currentTab;
if (!currentTab) return;
if (!tabSessionHistory.canGoForward(currentTab)) return;
const id = tabSessionHistory.forward(currentTab) as string;
if (!(await get().loadSession(id))) {
const canGoForward = tabSessionHistory.canGoForward(currentTab);
if (!canGoForward) {
tabSessionHistory.back(currentTab);
syncTabs();
} else {
get().goForward();
tabSessionHistory.remove(currentTab, id);
TabSessionStorage.remove(id);
return;
}
} else {
syncTabs();
}
},
loadSession: async (id: string) => {
const session = TabSessionStorage.get(id);
if (!session) return false;
const note = session?.noteId
? await db.notes.note(session?.noteId)
: undefined;
if (note) {
const isLocked = await db.vaults.itemExists(note);
session.noteLocked = isLocked;
session.locked = isLocked;
session.readonly = note.readonly;
} else if (session.noteId) {
console.log("Failed to load session...");
return false;
}
eSendEvent(eOnLoadNote, {
item: note,
newNote: !note,
tabId: get().currentTab,
session: session
});
return true;
},
focusPreviewTab: (
noteId: string,
options: Omit<Partial<TabItem>, "id" | "noteId">
) => {},
removeTab: (id: string) => {
const index = get().tabs.findIndex((t) => t.id === id);
if (index > -1) {
const isFocused = id === get().currentTab;
const nextTabs = get().tabs.slice();
nextTabs.splice(index, 1);
history.remove(id);
const tabSessions = tabSessionHistory.getTabHistory(id);
tabSessions.back.forEach((id) => TabSessionStorage.remove(id));
tabSessions.forward.forEach((id) => TabSessionStorage.remove(id));
tabSessionHistory.clearStackForTab(id);
if (nextTabs.length === 0) {
nextTabs.push({
id: 0
const id = getId();
set({
tabs: [{ id: id }]
});
get().newTabSession(id);
get().focusTab(id);
} else {
set({
tabs: nextTabs
});
if (isFocused) {
const lastTab = history.restoreLast();
if (lastTab) get().focusTab(lastTab);
}
}
set({
tabs: nextTabs
});
get().focusTab(
isFocused ? history.restoreLast() || 0 : get().currentTab
);
syncTabs();
}
},
newTab: (options) => {
const id = getId(get().tabs.length, get().tabs);
const nextTabs = [
...get().tabs,
{
id: id,
...options
}
];
const id = getId();
set({
tabs: nextTabs
tabs: [
...get().tabs,
{
id: id,
...options
}
]
});
get().newTabSession(id, options?.session || {});
get().focusTab(id);
return id;
},
focusEmptyTab: () => {
const index = get().tabs.findIndex((t) => !t.noteId);
const index = get().tabs.findIndex((t) => !t.session?.noteId);
if (index === -1) return get().newTab();
console.log("focus empty tab", get().tabs[index]);
get().focusTab(get().tabs[index].id);
},
@@ -224,38 +450,46 @@ export const useTabStore = create<TabStore>(
syncTabs();
},
focusTab: (id: number) => {
console.log(history.getHistory(), id);
focusTab: (id: string) => {
history.add(id);
set({
currentTab: id
currentTab: id,
canGoBack: tabSessionHistory.canGoBack(id),
canGoForward: tabSessionHistory.canGoForward(id),
sessionId: tabSessionHistory.currentSessionId(id)
});
syncTabs();
},
getNoteIdForTab: (id: number) => {
return get().tabs.find((t) => t.id === id)?.noteId;
getNoteIdForTab: (id: string) => {
return get().tabs.find((t) => t.id === id)?.session?.noteId;
},
hasTabForNote: (noteId: string) => {
return (
typeof get().tabs.find((t) => t.noteId === noteId)?.id === "number"
);
return !!get().tabs.find((t) => t.session?.noteId === noteId);
},
getTabForNote: (noteId: string) => {
return get().tabs.find((t) => t.noteId === noteId)?.id;
return get().tabs.find((t) => t.session?.noteId === noteId)?.id;
},
getTabsForNote(noteId: string) {
return get().tabs.filter((t) => t.session?.noteId === noteId);
},
forEachNoteTab: (noteId: string, cb: (tab: TabItem) => void) => {
const tabs = get().tabs.filter((t) => t.session?.noteId === noteId);
tabs.forEach(cb);
},
getCurrentNoteId: () => {
return get().tabs.find((t) => t.id === get().currentTab)?.noteId;
return get().tabs.find((t) => t.id === get().currentTab)?.session
?.noteId;
},
getTab: (tabId) => {
return get().tabs.find((t) => t.id === tabId);
}
}),
{
name: "tabs-storage",
name: "tabs-storage-v5",
getStorage: () => MMKV as unknown as StateStorage,
onRehydrateStorage: () => {
return (state) => {
history.history = state?.tabHistory.slice() || [];
history.history = state?.historyNew || [];
};
}
}

View File

@@ -31,6 +31,8 @@ import { eOnLoadNote } from "../../../utils/events";
import { NotesnookModule } from "../../../utils/notesnook-module";
import { AppState, EditorState, useEditorType } from "./types";
import { useTabStore } from "./use-tab-store";
import { NativeEvents } from "@notesnook/editor-mobile/src/utils/native-events";
export const textInput = createRef<TextInput>();
export const editorController =
createRef<useEditorType>() as MutableRefObject<useEditorType>;
@@ -46,19 +48,6 @@ export function editorState() {
return editorController.current?.state.current || defaultState;
}
export const EditorEvents = {
html: "native:html",
updatehtml: "native:updatehtml",
title: "native:title",
theme: "native:theme",
titleplaceholder: "native:titleplaceholder",
logger: "native:logger",
status: "native:status",
keyboardShown: "native:keyboardShown",
attachmentData: "native:attachment-data",
resolve: "native:resolve"
};
export function randId(prefix: string) {
return Math.random()
.toString(36)
@@ -72,15 +61,15 @@ export function makeSessionId(id?: string) {
export async function isEditorLoaded(
ref: RefObject<WebView>,
sessionId: string,
tabId: number
tabId: string
) {
return await post(ref, sessionId, tabId, EditorEvents.status);
return await post(ref, sessionId, tabId, NativeEvents.status);
}
export async function post<T>(
ref: RefObject<WebView>,
sessionId: string,
tabId: number,
tabId: string,
type: string,
value: T | null = null,
waitFor = 300
@@ -135,7 +124,6 @@ export const waitForEvent = async (
};
eSubscribeEvent(type, callback);
setTimeout(() => {
console.log("return..");
eUnSubscribeEvent(type, callback);
resolve(false);
}, waitFor);
@@ -158,6 +146,9 @@ const canRestoreAppState = (appState: AppState) => {
};
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();
@@ -183,7 +174,7 @@ export async function openInternalLink(url: string) {
if (!data?.id) return false;
if (
data.id ===
useTabStore.getState().getNoteIdForTab(useTabStore.getState().currentTab)
useTabStore.getState().getNoteIdForTab(useTabStore.getState().currentTab!)
) {
if (data.params?.blockId) {
setTimeout(() => {

View File

@@ -0,0 +1,169 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Note, VirtualizedGrouping } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useState } from "react";
import { FlatList, TouchableOpacity, View } from "react-native";
import { db } from "../../common/database";
import { Header } from "../../components/header";
import Input from "../../components/ui/input";
import Paragraph from "../../components/ui/typography/paragraph";
import { useDBItem } from "../../hooks/use-db-item";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { useSettingStore } from "../../stores/use-setting-store";
import { NotesnookModule } from "../../utils/notesnook-module";
const NoteItem = (props: {
id: string | number;
items?: VirtualizedGrouping<Note>;
}) => {
const { colors } = useThemeColors();
const [item] = useDBItem(props.id, "note", props.items);
return (
<TouchableOpacity
activeOpacity={0.7}
onPress={() => {
const widgetId = NotesnookModule.getWidgetId();
NotesnookModule.setString(
"appPreview",
String(widgetId),
JSON.stringify(item)
);
setTimeout(() => {
NotesnookModule.saveAndFinish();
});
}}
style={{
flexDirection: "column",
borderBottomWidth: 1,
borderBottomColor: colors.primary.border,
justifyContent: "center",
paddingVertical: 12,
minHeight: 45
}}
>
{!item ? null : (
<View
style={{
flexDirection: "row",
paddingHorizontal: 12
}}
>
<View
style={{
flexDirection: "column"
}}
>
<Paragraph
numberOfLines={1}
style={{
color: colors.primary.paragraph,
fontSize: 15
}}
>
{item.title}
</Paragraph>
</View>
</View>
)}
</TouchableOpacity>
);
};
export const NotePreviewConfigure = () => {
const [items, setItems] = useState<VirtualizedGrouping<Note>>();
const loading = useSettingStore((state) => state.isAppLoading);
const bounceRef = React.useRef<NodeJS.Timeout>();
const { colors } = useThemeColors();
const insets = useGlobalSafeAreaInsets();
useEffect(() => {
useSettingStore.getState().setDeviceMode("mobile");
if (loading) return;
db.notes.all.sorted(db.settings.getGroupOptions("notes")).then((notes) => {
setItems(notes);
});
}, [loading]);
const renderItem = React.useCallback(
({ index }: { item: boolean; index: number }) => {
return <NoteItem id={index} items={items} />;
},
[items]
);
return (
<View
style={{
backgroundColor: colors.primary.background,
flex: 1
}}
>
<Header
canGoBack
title="Select a note"
onLeftMenuButtonPress={() => {
NotesnookModule.cancelAndFinish();
}}
/>
<View
style={{
paddingHorizontal: 12,
paddingTop: 6
}}
>
<Input
placeholder="Search for notes"
containerStyle={{
height: 50
}}
onChangeText={(value) => {
bounceRef.current = setTimeout(() => {
if (!value) {
db.notes.all
.sorted(db.settings.getGroupOptions("notes"))
.then((notes) => {
setItems(notes);
});
return;
}
db.lookup
.notes(value)
.sorted()
.then((notes) => {
setItems(notes);
});
}, 500);
}}
/>
<FlatList
data={items?.placeholders}
keyboardShouldPersistTaps="handled"
keyboardDismissMode="interactive"
renderItem={renderItem}
windowSize={1}
ListFooterComponent={<View style={{ height: 200 }} />}
/>
</View>
</View>
);
};

View File

@@ -284,14 +284,13 @@ NotebookScreen.navigate = async (item: Notebook, canGoBack?: boolean) => {
focusedRouteId == item?.id
) {
// Update the route in place instead
console.log("Updating existing route in place");
eSendEvent(eUpdateNotebookRoute, {
item: item,
title: item.title,
canGoBack: canGoBack
});
} else {
console.log("Pushing new notebook route");
// Push a new route
Navigation.push("Notebook", {
title: item.title,

View File

@@ -59,7 +59,6 @@ ColoredNotes.navigate = (item: Color, canGoBack: boolean) => {
const { focusedRouteId } = useNavigationStore.getState();
if (focusedRouteId === item.id) {
console.log("ColoredNotes.navigate: route already focused for color");
return;
}

View File

@@ -171,7 +171,6 @@ const NotesPage = ({
setLoadingNotes(false);
})
.catch((e) => {
console.log("Error loading notes", params.current?.title, e, e.stack);
setLoadingNotes(false);
});
}

View File

@@ -60,7 +60,6 @@ TaggedNotes.navigate = (item: Tag, canGoBack?: boolean) => {
const { focusedRouteId } = useNavigationStore.getState();
if (focusedRouteId === item.id) {
console.log("TaggedNotes.navigate: route already focused for tag");
return;
}

View File

@@ -160,9 +160,7 @@ export default function DebugLogs() {
type: "success"
});
}
} catch (e) {
console.log(e);
}
} catch (e) {}
}, [currentLog?.logs]);
const copyLogs = React.useCallback(() => {

View File

@@ -54,7 +54,9 @@ export const Licenses = () => {
}}
onPress={() => {
if (!item.link) return;
Linking.openURL(item.link).catch(console.log);
Linking.openURL(item.link).catch(() => {
/* empty */
});
}}
>
<Heading size={SIZE.sm}>{item.name}</Heading>

View File

@@ -33,7 +33,6 @@ import { strings } from "@notesnook/intl";
export const FontPicker = createSettingsPicker({
getValue: () => useSettingStore.getState().settings.defaultFontFamily,
updateValue: (item) => {
console.log(item.id);
SettingsService.set({
defaultFontFamily: item.id
});

View File

@@ -148,7 +148,7 @@ const restoreBackup = async (options: {
let count = 0;
await db.transaction(async () => {
let passwordOrKey: PasswordOrKey;
let passwordOrKey: PasswordOrKey | undefined = undefined;
for (const path of extractedBackupFiles) {
if (path === ".nnbackup" || path === "attachments") continue;
@@ -166,7 +166,7 @@ const restoreBackup = async (options: {
passwordOrKey = !isEncryptedBackup
? ({} as PasswordOrKey)
: await withPassword();
: passwordOrKey || (await withPassword());
if (
isEncryptedBackup &&
@@ -197,7 +197,6 @@ const restoreBackup = async (options: {
const attachment = await db.attachments.attachment(hash as string);
if (!attachment) continue;
console.log("Saving attachment file", hash);
await deleteCacheFileByName(hash);
await RNFetchBlob.fs.cp(
`${zipOutputFolder}/attachments/${hash}`,
@@ -208,9 +207,13 @@ const restoreBackup = async (options: {
progress: strings.cleaningUp()
});
// Remove files from cache
RNFetchBlob.fs.unlink(zipOutputFolder).catch(console.log);
RNFetchBlob.fs.unlink(zipOutputFolder).catch(() => {
/* empty */
});
if (Platform.OS === "android" || deleteBackupFile) {
RNFetchBlob.fs.unlink(filePath).catch(console.log);
RNFetchBlob.fs.unlink(filePath).catch(() => {
/* empty */
});
}
} else {
updateProgress({

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