Compare commits

...

1094 Commits

Author SHA1 Message Date
Ammar Ahmed
614f228e6e Merge branch 'master' into beta 2026-08-05 08:42:26 +05:00
Ammar Ahmed
b8a02640d6 Merge pull request #10193 from streetwriters/mobile/release-3.4.8
mobile: release v3.4.8
2026-08-04 19:24:16 +05:00
Ammar Ahmed
31dbbe5e9e mobile: fix rn 82 build on xcode 26.5 2026-08-04 12:11:54 +05:00
Ammar Ahmed
427dcbbb20 mobile: release v3.4.8 2026-08-03 19:30:12 +05:00
Ammar Ahmed
d3d21aa835 Merge pull request #10161 from streetwriters/fix/android-widgets
Fix android widget bugs.
2026-08-03 19:29:33 +05:00
Ammar Ahmed
d60c8be423 Merge pull request #10019 from kashaf-ansari-dev/feat/9694-new-reminder-shortcut
mobile: add app shortcut for creating reminders
2026-08-03 12:28:09 +05:00
Ammar Ahmed
516a598b2b Merge pull request #10166 from kashaf-ansari-dev/fix-10135-image-note-save
mobile: fix images sometimes not saving after insert
2026-08-03 09:58:06 +05:00
Abdullah Atta
f858e3471f desktop: set snap base to core24 (#10180) 2026-07-31 12:41:56 +05:00
kashaf-ansari-dev
57113e0896 mobile: remove AddReminder from stack on navigation away to prevent stale form state
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-07-31 09:49:36 +05:00
kashaf-ansari-dev
28f2ffdbf7 mobile: remove unused ignoreEdit handling
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-07-30 23:29:32 +05:00
kashaf-ansari-dev
02c60d14c7 editor: skip ignoreEdit updates before scheduling save
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-07-30 23:29:00 +05:00
kashaf-ansari-dev
41cdc882c9 editor: fix ignoreEdit flag overwritten during debounced save
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-07-30 23:29:00 +05:00
Suyadi
a1879dae17 mobile: fix error on android's search query (#10106)
* mobile: pass value for items when searching on main screen to match other screens' pattern
Signed-off-by: Suyadi <afsuyadi@gmail.com>

* core: guard notesWithHighlighting against an undefined notes selector.
Signed-off-by: Suyadi <afsuyadi@gmail.com>

* core: add regression test for notesWithHighlighting crash.
Signed-off-by: Suyadi <afsuyadi@gmail.com>

* core: revert changes after feedback
Signed-off-by: Suyadi <afsuyadi@gmail.com>

* mobile: ensure type: "note" will make items become mandatory.
Signed-off-by: Suyadi <afsuyadi@gmail.com>

* core: remove tests after feedback
Signed-off-by: Suyadi <afsuyadi@gmail.com>
2026-07-30 23:01:21 +05:00
Ammar Ahmed
4765657423 mobile: fix editor cross contamination (#10157)
1. Use noteId defined in editorMessage when saving content, never use tabId since it can point to a different note since a tab can load a different note while a note save message is coming across the bridge.
2. If saving fails and editor saves save payloads in localStorage, add the noteId to them so we know which note the content belongs to.
3. Pending content saves must keep noteId and edit time so when we save them later, they save into the correct note and if the content is newer, we skip saving.
4. Fix the debounce key so cross contamination can never occur in new notes between two tabs.
2026-07-30 22:55:06 +05:00
Ammar Ahmed
e5b97abe0d Merge branch 'master' into beta 2026-07-30 12:35:39 +05:00
Ammar Ahmed
eef56916de mobile: Upcoming Reminders -> Reminders in home widgets 2026-07-30 09:40:43 +05:00
Abdullah Atta
f7c755f2d6 editor: sort code block languages by recently used (#10144) 2026-07-30 08:57:38 +05:00
Kashaf Ansari
10a08d5719 mobile: fix add notes to notebook selection behavior (#10104)
* mobile: fix add notes to notebook selection behavior

Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>

* mobile: improve move notes keyboard interactions

Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>

* mobile: fix bottom inset when keyboard is open

---------

Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
Co-authored-by: Ammar Ahmed <ammarahmed6506@gmail.com>
2026-07-30 08:56:18 +05:00
Kashaf Ansari
6e10ce8d8e mobile: add sync status button to side menu (#10112)
* mobile: add sync status button to side menu

Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>

* mobile: minor fixes

* mobile: hide sync status during logout

Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>

* mobile: skipped sync during logout

Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>

---------

Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
Co-authored-by: Ammar Ahmed <ammarahmed6506@gmail.com>
2026-07-30 08:54:05 +05:00
Ammar Ahmed
ae1811cc4a Merge branch 'master' into beta 2026-07-29 12:50:50 +05:00
Ammar Ahmed
7f5215403b mobile: keep reminders showing in widget after they go off for 3 hours 2026-07-29 12:14:15 +05:00
Ammar Ahmed
8e1d59d016 mobile: fix reminder widget title vertical padding 2026-07-29 10:01:29 +05:00
Ammar Ahmed
9833c7636b mobile: reminder widget does not show up on some devices 2026-07-29 09:37:26 +05:00
Ammar Ahmed
71cae07dc0 mobile: update widgets on time changed
When system time changes, reminder widgets keep showing reminders that have passed as active.
2026-07-29 09:13:59 +05:00
Ammar Ahmed
77e56f9da1 mobile: fix widgets do not refresh after app data is cleared, require a restart. 2026-07-29 09:08:03 +05:00
Abdullah Atta
b5140d9947 Merge pull request #10159 from streetwriters/release/3.4.5
web: bump version to 3.4.5
2026-07-28 21:09:08 +05:00
Abdullah Atta
2288b946ad desktop: launch app with --hidden flag for tests 2026-07-28 19:53:37 +05:00
Abdullah Atta
5fd43ae08e desktop: fix auto start with minimized window on macos 2026-07-28 19:53:37 +05:00
Abdullah Atta
729c410602 global: update intl & lockfiles 2026-07-28 19:53:37 +05:00
Abdullah Atta
bb30336f5c desktop: add tests for #9942 2026-07-28 19:52:48 +05:00
01zulfi
0dc89ba945 web: bump version to 3.4.5
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-07-28 19:52:47 +05:00
Abdullah Atta
bb51244fa3 editor: update prosemirror packages (#10158)
* editor: update prosemirror packages

* editor: fix `__serializeForClipboard` usage
2026-07-28 19:46:51 +05:00
Abdullah Atta
b5ac41c29b web: improve "skip & go directly to app" wording (#10167) 2026-07-28 19:46:23 +05:00
01zulfi
aafb6049b2 core: delete all vaults when user triggers their vault deletion (#9939)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-07-28 19:45:30 +05:00
Ammar Ahmed
b066fac692 mobile: drop unused imports from widget source 2026-07-27 12:24:55 +05:00
Ammar Ahmed
8d2e1c65bc mobile: keep note and reminder content off the lock screen
From Android 16 QPR1 widgets are lock screen eligible by default, so the
note widget (title + preview text) and the reminders widget (titles and
descriptions) could render user content on a locked device.

Adds res/xml-v36 overrides declaring not_keyguard for both. The new note
widget is left eligible - it is only a button and leaks nothing.
2026-07-27 12:21:49 +05:00
Ammar Ahmed
93337b6560 mobile: remove dead widget theme overlay, use system corner radius
ThemeOverlay.Notesnook.AppWidgetContainer declared and set
appWidgetBackgroundColor/appWidgetTextColor, but no layout ever read
them - leftovers from the Android Studio widget template, in a palette
unrelated to the app. Removes the overlay, its declare-styleable, the
android:theme references, and the light_blue_* colors that only it used.

Also replaces the hardcoded 10dp widget corner radius with
system_app_widget_background_radius on API 31+ (10dp fallback below),
so the widgets match the rest of the home screen.
2026-07-27 12:14:13 +05:00
Ammar Ahmed
70fd3096d2 mobile: fix invisible empty state in the reminders widget 2026-07-27 12:10:25 +05:00
Ammar Ahmed
1889b13459 mobile: remove stray quote in NotePreviewWidget manifest entry 2026-07-27 12:05:28 +05:00
Ammar Ahmed
314c484ea0 mobile: fix note widget lookups corrupting the reminders list
updateWidgetNote scanned every key in the appPreview preferences and
rewrote any whose value contained the note id. That included the
remindersList key, so a match replaced the entire reminders list with a
note and then threw NumberFormatException parsing "remindersList" as a
widget id. hasWidgetNote had the same unguarded scan.

Match on the parsed note's id instead of on the raw JSON containing it,
and treat only keys that parse as an int as widget entries, so non-widget
keys are excluded structurally rather than by name. Also guards the casts
and gson parses that could throw on a malformed entry.
2026-07-27 12:01:31 +05:00
Ammar Ahmed
6b5e6f62c5 mobile: make NotePreviewWidget respond to resize events and properly resize based on available room. 2026-07-27 11:25:34 +05:00
Ammar Ahmed
55b51b81e3 mobile: add previewLayout for all widgets 2026-07-27 11:17:12 +05:00
Ammar Ahmed
b2c7f732a0 mobile: replace reminders widget service with RemoteCollectionItems
Push the reminder rows into the widget update itself via RemoteViewsCompat
instead of binding a RemoteViewsService adapter, which has been deprecated
since API 31. Removes the bound service entirely, so there is no cached
factory to invalidate and no separate notifyAppWidgetViewDataChanged step
that can race or be missed.

Adds androidx.core:core-remoteviews for the API 24-30 fallback. Row ids are
now derived from the reminder id rather than the list position, and the list
is capped at 50 rows so the update fits in a binder transaction.
2026-07-27 11:10:46 +05:00
Ammar Ahmed
88eb3ab714 mobile: fix NotePreviewWidget empty state has no click handler — make it tap-to-configure so a blank widget can self-heal 2026-07-27 10:49:36 +05:00
Ammar Ahmed
942c9287fe mobile: fix stale reminder times — render-time formatting + valid updatePeriodMillis + self-refresh 2026-07-27 10:39:39 +05:00
Ammar Ahmed
343d2b0131 core: export getUpcomingReminderTime 2026-07-27 10:39:20 +05:00
Abdullah Atta
32070327f9 core: add logs to diagnose why decryption fails after changing password (#10139) 2026-07-27 10:20:25 +05:00
Ammar Ahmed
9b29503b17 mobile: added onRestored() that migrates each stored note in the appPreview prefs from its old widget ID key to the new one. 2026-07-27 10:08:16 +05:00
Ammar Ahmed
0939f0f805 mobile: migrated off the deprecated background-activity-launch mode
API 36+ → MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE
API 34–35 → MODE_BACKGROUND_ACTIVITY_START_ALLOWED (unchanged behaviour on older devices)
< 34 → null
2026-07-27 09:56:18 +05:00
Abdullah Atta
771a8bb4e3 web: fix editor undo history reset on image delete (#10143) 2026-07-25 08:55:58 +05:00
Ammar Ahmed
e4fa0e9f0b mobile: fix ios app does not load if awaited RNBootSplash.hide 2026-07-25 08:54:44 +05:00
kashaf-ansari-dev
97749729ad mobile: improve startup loading shortcuts
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-07-24 18:57:54 +05:00
Ammar Ahmed
ca19fd9230 mobile: fix new note loading from shortcut on cold launch 2026-07-24 18:57:54 +05:00
kashaf-ansari-dev
c435ab46d1 mobile: simplified new editor load logic
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-07-24 18:57:54 +05:00
kashaf-ansari-dev
62ecc5f7fb mobile: resolve app shortcut navigation and add reminder feature gate
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-07-24 18:57:54 +05:00
kashaf-ansari-dev
3e2847f844 mobile: add fluid panels initialRoute to simplified navigation
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-07-24 18:57:54 +05:00
kashaf-ansari-dev
c3c9b1fbf4 mobile: fix app shortcut navigation, flash, and back-nav issues
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-07-24 18:57:53 +05:00
kashaf-ansari-dev
a40814a3ab mobile: revise app shortcut handling implementation
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-07-24 18:57:53 +05:00
kashaf-ansari-dev
180c202c4e mobile: handle app shortcuts correctly during cold and warm app launches
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-07-24 18:57:53 +05:00
kashaf-ansari-dev
64943647f8 mobile: add app shortcut for creating reminders
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-07-24 18:57:53 +05:00
Ammar Ahmed
4f816b47ba ci: fix ios build entitlements (#10146) 2026-07-24 18:55:11 +05:00
01zulfi
1071249baa desktop: fix app not appearing in alt-tab switcher (#9764)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-07-24 13:05:59 +05:00
Ammar Ahmed
d4775ad340 Merge pull request #10133 from streetwriters/chore/update-workflows
ci: publish ios preview app on linux runner
2026-07-24 12:10:35 +05:00
Chloe Oletto
0d8c855cd0 desktop: If --hidden passed via cli and any system tray option enabled, don't show the window. streetwriters/notesnook#9902 flathub/com.Notesnook.notesnook#99 (#9942)
Signed-off-by: Chloe Oletto <NeedsChloesure@riseup.net>
2026-07-24 09:16:17 +05:00
Ammar Ahmed
d63ce5a213 Merge pull request #10126 from streetwriters/mobile-release/3.4.7
mobile: release v3.4.7
2026-07-22 11:12:45 +05:00
Ammar Ahmed
a1d6bb91e5 ci: fix ci workflow 2026-07-22 10:53:18 +05:00
Ammar Ahmed
a20f6960d7 ci: fix workflow 2026-07-22 10:45:49 +05:00
Ammar Ahmed
6b2b224f41 ci: fix ios workflow 2026-07-22 10:37:31 +05:00
Ammar Ahmed
054a023142 ci: fix intermittent ios preview build failure 2026-07-22 10:13:16 +05:00
Ammar Ahmed
441a6396be ci: publish ios preview app on linux runner 2026-07-22 10:02:18 +05:00
Chloe Oletto
987671e59c web: fix web-clipper crash on clipped file. (#10127)
Signed-off-by: Chloe Oletto <NeedsChloesure@riseup.net>
2026-07-21 12:45:13 +05:00
Ammar Ahmed
0fc705ce1e Merge pull request #10130 from streetwriters/chore/update-workflows
ci: update mobile workflows
2026-07-21 12:41:02 +05:00
Ammar Ahmed
b3805534d6 ci: remove authorize step in android/ios preview builds 2026-07-21 12:35:08 +05:00
Ammar Ahmed
77c009e83d mobile: fix toast message shows under the sheet in global context 2026-07-21 12:14:08 +05:00
Ammar Ahmed
c9f1cba702 ci: update mobile workflows 2026-07-21 11:36:23 +05:00
Ammar Ahmed
637cf914ee mobile: fix app gets stuck after deleting history item on iOS 2026-07-21 10:25:12 +05:00
Ammar Ahmed
64877c519b Merge pull request #10128 from streetwriters/chore/update-workflows
ci: update workflows to use actions/checkout@v5
2026-07-21 10:04:20 +05:00
Ammar Ahmed
229b620473 ci: update workflows to use actions/checkout@v5 2026-07-21 09:27:49 +05:00
Ammar Ahmed
658e915589 mobile: release v3.4.7 2026-07-20 13:12:20 +05:00
Ammar Ahmed
d4658aa032 Merge pull request #10095 from kashaf-ansari-dev/fix-reminder-notification-permission
Improve the Android reminder notification permission flow by properly handling notification and exact alarm permissions.
2026-07-17 09:10:57 +05:00
Ammar Ahmed
489c793806 Merge pull request #10041 from kashaf-ansari-dev/feat/9723-clear-note-history
mobile: add option to clear note version history
2026-07-15 09:52:04 +05:00
kashaf-ansari-dev
eeae993736 mobile: add version history deletion strings
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-07-15 09:49:41 +05:00
kashaf-ansari-dev
c8cf4ce52a mobile: fix individual note history version deletion
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-07-15 09:49:41 +05:00
kashaf-ansari-dev
f6c31d19d8 mobile: add missing context prop in dialog
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-07-15 09:49:41 +05:00
kashaf-ansari-dev
a94e8d6852 mobile: render clear-history dialog with local context
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-07-15 09:49:41 +05:00
kashaf-ansari-dev
2edbc3bf2e mobile: remove clear version history action for multiple selected notes
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-07-15 09:49:41 +05:00
kashaf-ansari-dev
e4a7d63e07 mobile: add option to clear note version history
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-07-15 09:49:30 +05:00
Ammar Ahmed
76fd61af4f Merge pull request #10085 from streetwriters/fix/651
mobile: fix unable to change plan on buy-plan screen
2026-07-14 08:43:53 +05:00
Ammar Ahmed
98386a6d71 Merge pull request #10094 from streetwriters/mobile-release/3.4.6
mobile: release v3.4.6
2026-07-13 22:44:13 +05:00
kashaf-ansari-dev
539a630f77 mobile: improve reminder permission settings flow on Android
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-07-13 18:07:40 +05:00
kashaf-ansari-dev
6b65f8031d mobile: fix reminder creation after granting notification permission
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-07-13 14:58:31 +05:00
01zulfi
3c85cddfe7 Merge pull request #10093 from streetwriters/release/3.4.4
web: bump version to 3.4.4
2026-07-13 14:14:25 +05:00
Ammar Ahmed
e426426468 mobile: release v3.4.6 2026-07-13 12:56:49 +05:00
01zulfi
109e46bd06 web: bump version to 3.4.4
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-07-13 12:28:07 +05:00
Ammar Ahmed
281a973c37 mobile: fix unable to change plan on buy-plan screen 2026-07-11 12:00:15 +05:00
Ammar Ahmed
2738207c20 Merge pull request #9998 from kashaf-ansari-dev/fix-9967-linking-note-blocks-loading-state
mobile: prevent upgrade prompt flash in note block linking list
2026-07-10 18:41:04 +05:00
01zulfi
1aa73c1f23 web: update editor's font size dynamically when default font size setting changes (#10047)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-07-10 09:39:21 +05:00
Masatoshi Ogiwara
9ec22724e9 fix: typo in some documents (#10071) 2026-07-09 10:34:09 +05:00
Kashaf Ansari
4a7aa27bd9 mobile: add keep-screen-on preference in settings-behaviour (#10043)
* mobile: add keep-screen-on preference in settings-behaviour

Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>

* mobile: default Keep Screen On to true and clarify description

Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>

---------

Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-07-09 09:49:38 +05:00
kashaf-ansari-dev
6051614d96 mobile: add bottom spacing for create link button
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-07-08 18:23:23 +05:00
kashaf-ansari-dev
295a461dda mobile: wire upgrade plan button to paywall on link-note
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-07-08 17:55:00 +05:00
Chloe Oletto
a4cbe83211 vericrypt: Update to support DEK. (#10005)
* vericrypt: Update to support DEK.

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

* vericrypt: Clear account data on restart.

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

* Update apps/vericrypt/src/components/decrypted-result.tsx

Co-authored-by: Abdullah Atta <thecodrr@protonmail.com>
Signed-off-by: Chloe Oletto <NeedsChloesure@riseup.net>

* docs: Update data encryption documentation to be accurate with the current client versions.

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

* docs: Explain what happens better.

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

* Fix typo in encryption key generation section

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

* Update docs/help/contents/how-is-my-data-encrypted.md

Co-authored-by: Abdullah Atta <thecodrr@protonmail.com>
Signed-off-by: Chloe Oletto <NeedsChloesure@riseup.net>

* docs: update wording

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

---------

Signed-off-by: Chloe Oletto <NeedsChloesure@riseup.net>
Co-authored-by: Abdullah Atta <thecodrr@protonmail.com>
2026-07-08 10:24:17 +05:00
Masatoshi Ogiwara
afd63db043 docs(help): typo in documents (#10064) 2026-07-08 08:41:17 +05:00
Ammar Ahmed
774d612bf2 misc: cleanup 2026-07-07 09:50:36 +05:00
Ammar Ahmed
aadb6e3a01 mobile: prepare v3.4.5 for release (#10060) 2026-07-07 09:30:31 +05:00
Ammar Ahmed
7c3fdab6ee Merge pull request #10053 from kashaf-ansari-dev/fix-7348-unlink-notebook-on-relink
mobile: unlink note from previous notebook in single-select mode
2026-07-07 09:25:56 +05:00
Ammar Ahmed
14f727d6e6 Merge pull request #10016 from kashaf-ansari-dev/fix-9742-duplicate-pinned-note
core: do not preserve pin state when duplicating notes
2026-07-07 09:25:23 +05:00
01zulfi
688a646181 Merge pull request #10058 from streetwriters/release/3.4.3
web: bump version to 3.4.3
2026-07-06 15:10:47 +05:00
Kashaf Ansari
7a7f0db941 mobile: use FileViewer for image sharing on Android (#10018)
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-07-06 13:37:29 +05:00
Kashaf Ansari
96be35c105 mobile: fix long tag names overflow and display in tag items (#9950)
* mobile: fix long tag names overflow and display in tag items

Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>

* mobile: fix long title in side menu

Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>

* mobile: fix long notebook title display

Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>

* mobile: prevent tag list overlap on first render

Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>

---------

Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-07-06 13:36:31 +05:00
01zulfi
36bacce3ac web: bump version to 3.4.3
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-07-06 10:47:13 +05:00
kashaf-ansari-dev
c89e333c11 mobile: unlink note from previous notebook in single-select mode
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-07-03 13:12:10 +05:00
Abdullah Atta
d521eb3bd8 desktop: regenerate package-lock (#10051) 2026-07-03 05:45:48 +05:00
Abdullah Atta
20bf090c7e desktop: fix source generation for electron (#10046)
* desktop: fix source generation for electron

* ci: upload generated-sources as build artifact
2026-07-03 05:14:11 +05:00
01zulfi
6cc9b8d1e1 web: add top margin to unlock view error text (#10030)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-07-02 08:45:49 +05:00
01zulfi
e931423eba web: show success toast when copying note link in context menu (#10027)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-07-02 08:45:41 +05:00
01zulfi
743d47e9e9 web: verify old password before creating backup during password update (#10031)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-07-02 08:45:26 +05:00
Ammar Ahmed
37c96177a7 Mobile release/3.4.4 (#10040)
* mobile: add bump-version script

* mobile: bump version to v3.4.4
2026-07-01 15:54:10 +05:00
Abdullah Atta
834479d095 Release v3.4.2 (#10039)
* web: bump version to 3.4.2

* desktop: fix `Invalid filename` error on backup creation
2026-07-01 12:51:34 +05:00
Abdullah Atta
643515c6c4 desktop: fix Invalid filename error on backup creation 2026-07-01 12:28:45 +05:00
Abdullah Atta
0b0a4f1b6f web: bump version to 3.4.2 2026-07-01 11:52:50 +05:00
Ammar Ahmed
48d88069c0 core: make db migration more resilient and recoverable (#10037)
* core: do not throw error if inboxitemshistory table already exists during migration

* core: add ensure column check to avoid `column already exists` errors

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-07-01 11:51:24 +05:00
Abdullah Atta
9d7754f1e1 ci: fix macos x64 build (#10038) 2026-07-01 08:54:05 +05:00
Abdullah Atta
b2ed115011 Merge pull request #10034 from streetwriters/release/3.4.1
Release v3.4.1
2026-06-30 20:32:34 +05:00
Abdullah Atta
3447e8133c web: bump version to 3.4.1 2026-06-30 20:15:54 +05:00
Abdullah Atta
4473d8ec67 ci: fix linux builds 2026-06-30 20:14:53 +05:00
01zulfi
08cf21b05a web: compress image in realtime for better UX in attach files dialog (#9899)
* web: compress image in realtime for better UX in attach files dialog
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: refactor

* web: fix "done" button is disabled

* theme: improve switch styles

* web: improve bulk attachment processing & ux

* web: fix `attachFile` is not a function

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-06-30 19:31:42 +05:00
01zulfi
10dccba5b6 docs: include ColorNote & UpNote pages in importer section (#10029)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-06-30 15:22:00 +05:00
01zulfi
23c9a98013 docs: remove inbox api beta warning (#10028)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-06-30 13:43:04 +05:00
01zulfi
473fae4a30 Merge pull request #9995 from streetwriters/release/3.4.0
web: bump version to 3.4.0
2026-06-30 13:16:48 +05:00
Ammar Ahmed
e9808c08f0 mobile: bump version 2026-06-30 11:13:02 +05:00
Abdullah Atta
4bb46ea149 ci: fix build for windows 2026-06-30 10:02:44 +05:00
Ammar Ahmed
01811372fc mobile: prepare for release 2026-06-30 09:43:29 +05:00
Abdullah Atta
6d2097259b editor: remove .replaceAll usage to support older Android 9 devices 2026-06-30 09:43:29 +05:00
Abdullah Atta
4c3e065928 core: add comment about why we aren't lowercasing email for old password generation 2026-06-30 09:43:29 +05:00
Abdullah Atta
e45b72a8c4 ci: install sqlite modules in apps/desktop dir 2026-06-30 09:43:29 +05:00
Abdullah Atta
aceb6db8b8 core: always lowercase email address when resetting password 2026-06-30 09:43:29 +05:00
Abdullah Atta
5a0ca0bed9 ci: fix sqlite modules not being found in desktop builds 2026-06-30 09:43:29 +05:00
Abdullah Atta
8dd5779308 ci: fix desktop tests 2026-06-30 09:43:29 +05:00
Abdullah Atta
dc958098ea desktop: fix tests 2026-06-30 09:43:29 +05:00
Ammar Ahmed
bf536eb183 mobile: fix event subscription 2026-06-30 09:43:29 +05:00
Ammar Ahmed
eb3eea031e mobile: add support for copying links as html links with proper title. 2026-06-30 09:43:29 +05:00
Ammar Ahmed
d376e00a1e editor: cleanup nn:// links when pasted
Ensure that we remove any styles added to deep links. This is done especially for iOS where pasted link html get's wrapped into custom div with some hard coded styles making the link not match style with the rest of content.
2026-06-30 09:43:29 +05:00
Ammar Ahmed
aca36b60b7 editor: fix note links not detected on paste/edit 2026-06-30 09:43:29 +05:00
Ammar Ahmed
033666795c mobile: fix screenguard module error 2026-06-30 09:43:29 +05:00
Abdullah Atta
c0872c98a8 desktop: fix deep linking not working 2026-06-30 09:43:29 +05:00
01zulfi
f2173a42d9 web: add confirmation before deleting failed inbox items
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-06-30 09:43:29 +05:00
Ammar Ahmed
cbddea4518 mobile: show toast message on vault locked/unlocked events 2026-06-30 09:43:03 +05:00
Ammar Ahmed
c69304781a mobile: fix password input is not shown in vault dialog 2026-06-30 09:43:03 +05:00
Ammar Ahmed
412631bf67 core: dispatch vaultUnlocked event when password is updated. 2026-06-30 09:43:03 +05:00
Abdullah Atta
8c2be00920 core: do not send PushCompletedV2 if no items sent 2026-06-30 09:43:03 +05:00
01zulfi
553e2060be editor: copy html for internal links in copy link tool
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-06-30 09:43:03 +05:00
Abdullah Atta
7603de25c6 ci: fix Could not find any Visual Studio installation to use 2026-06-30 09:43:03 +05:00
01zulfi
146216949d web: update publish view popup when monographs sync (#10007)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-06-30 09:43:03 +05:00
Abdullah Atta
c4d8abef16 setup: add node-gyp-build to postinstall whitelist 2026-06-30 09:43:03 +05:00
Abdullah Atta
baf823264b core: remove debug tests 2026-06-30 09:43:03 +05:00
Abdullah Atta
7f0a517ee1 config: add better-sqlite3 to postinstall whitelist 2026-06-30 09:43:03 +05:00
01zulfi
da48cf0bd1 web: don't show monograph view count when self destruct enabled
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-06-30 09:43:03 +05:00
Ammar Ahmed
4c41e13c5d mobile: fix ts errors 2026-06-30 09:43:03 +05:00
01zulfi
82e2953bc8 web: fix paragraph color in inbox settings for dark mode
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-06-30 09:43:03 +05:00
01zulfi
fc244b1196 core: delete linked inboxHistoryItem's on permanent note deletion
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-06-30 09:43:03 +05:00
01zulfi
98ecad3450 web: bump version to 3.4.0
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-06-30 09:43:03 +05:00
Abdullah Atta
54dcbd9be6 web: bump importer to 2.4.5 2026-06-30 09:42:42 +05:00
Abdullah Atta
d26c99c487 web: continue importing if an error occurs but highlight the error clearly 2026-06-30 09:42:42 +05:00
01zulfi
0922d9d833 web: support colornote importer (#9985)
* docs: add import from colornote page
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: add support for colornote importer
* add colornote password input field
* add support for importing reminders
* fix archived notes import

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

* docs: update import from colornote page
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: use PromptDialog instead of field in colornote importer's password option
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* docs: code review suggestion
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

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

---------

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-06-30 09:42:42 +05:00
01zulfi
0cdeee80fa docs: add import from upnote page (#9272)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-06-30 09:42:42 +05:00
kashaf-ansari-dev
3aa23d3215 core: do not preserve pin state when duplicating notes
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-06-25 17:33:36 +05:00
Abdullah Atta
936b6cb4aa Merge branch 'master' into beta 2026-06-23 08:34:57 +05:00
kashaf-ansari-dev
e0b9932aba mobile: prevent upgrade prompt flash in note block linking list
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-06-19 22:31:56 +05:00
Abdullah Atta
f9f974c9f0 Merge pull request #9429 from streetwriters/web/fix-vault-deletion-for-not-logged-in-user
web: don't ask for account password for vault deletion for not logged in users
2026-06-19 08:46:18 +05:00
01zulfi
e3816234ff monograph: show error message for empty password on unlock (#9927)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-06-18 14:17:33 +05:00
01zulfi
8b36ae6dee core: fix "table already exists" error in migrations (#9987)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-06-18 09:45:22 +05:00
Abdullah Atta
0bd030dc78 Merge pull request #9984 from streetwriters/fix/disable-ignore-edit-save
Editor transactions with `ignoreEdit` will no longer trigger save
2026-06-18 09:17:32 +05:00
Ammar Ahmed
63eb42eb9e mobile: disable save when ignoreEdit is true 2026-06-18 08:40:52 +05:00
Abdullah Atta
eb75df360f web: editor transactions with ignoreEdit will no longer trigger save 2026-06-17 21:44:34 +05:00
Ammar Ahmed
89f3a7945b mobile: update styling for checkbox 2026-06-17 09:21:05 +05:00
Ammar Ahmed
7af1387691 mobile: fix delete all notes check truncated 2026-06-17 09:18:21 +05:00
Ammar Ahmed
1d292d740c mobile: fix vault asks for fingerprint permission on creating vault 2026-06-17 09:03:20 +05:00
Ammar Ahmed
4f884a8411 mobile: do not ask for password on vault deletion if user not logged in 2026-06-17 09:03:19 +05:00
01zulfi
3122e8a379 web: don't ask for account password for vault deletion for not logged in users
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-06-17 09:02:30 +05:00
Ammar Ahmed
528812d69d Merge branch 'master' into beta 2026-06-16 12:54:30 +05:00
Ammar Ahmed
ab5391fd6e Merge pull request #9971 from streetwriters/fix/331-358
fix date picker modal renders blank if themVariant is not set based on app's theme mode
2026-06-16 12:39:22 +05:00
Ammar Ahmed
770d290af1 Merge pull request #9892 from kashaf-ansari-dev/fix-remove-unset-expiry-date
mobile: fix unset note expiry date
2026-06-16 12:38:22 +05:00
kashaf-ansari-dev
3db6c2b1fd mobile: remove today/tomorrow logic and set default expiry initial date to 1 week
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-06-16 12:35:58 +05:00
kashaf-ansari-dev
19a11dae26 mobile: fix expiry date picker width
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-06-16 12:35:58 +05:00
kashaf-ansari-dev
07d7a537e4 mobile: display expiry date on notes item
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-06-16 12:35:58 +05:00
kashaf-ansari-dev
20f39b82c6 mobile: add success messages on setting or removing expiry date
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-06-16 12:35:58 +05:00
kashaf-ansari-dev
9e5ed67eb3 mobile: prevent crash in note properties when expiryDate is undefined
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-06-16 12:34:53 +05:00
kashaf-ansari-dev
6ddd5078ac mobile: fix unset note expiry date
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-06-16 12:34:53 +05:00
Ammar Ahmed
579ae294b6 Merge pull request #9955 from kashaf-ansari-dev/fix-published-note-updated-title
mobile: reflected published note title in published section
2026-06-16 12:26:25 +05:00
Ammar Ahmed
6418476ed6 Merge pull request #9932 from kashaf-ansari-dev/fix-display-monograph-view-count
mobile: display monograph view count
2026-06-16 12:25:47 +05:00
Ammar Ahmed
dfc01fa558 Merge pull request #9931 from kashaf-ansari-dev/fix-vault-dialog-form-input
mobile: replace Input with FormInput in VaultDialog for inline field …
2026-06-16 12:24:09 +05:00
01zulfi
b4f3593966 web: fix recovery view header overflow on small screens (#9975)
* web: fix recovery view header overflow on small screens
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: fix long emails being truncated in recovery screen
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-06-15 20:16:50 +05:00
Abdullah Atta
c0eff80bd4 webclipper: bump version to 0.4.1 (#9977) 2026-06-13 11:22:53 +05:00
Ammar Ahmed
fd55eddf73 mobile: update version codes 2026-06-13 10:42:46 +05:00
Abdullah Atta
80a964af67 Release 3.4.0-beta.1 (#9885)
* web: bump version to 3.4.0-beta.1

* mobile: bump version

* mobile: hide inbox settings when inbox api is disabled

* mobile: fix failed inbox item text goes outside the view

* mobile: add validation for creating api key name input

* mobile: fix api key shows expired status if set to never expire

* mobile: fix typo Notesbook to Notesnook

* mobile: add missing setup inbox keys screen

* mobile: update linkedom version

* mobile: fix ts errors

* mobile: show error on both inputs in case of invalid keypair

* mobile: fix expired text not visible in light mode

* mobile: show loading in switch item when disabling inbox api

* mobile: enable save button only if keys are edited

* mobile: make failed inbox item error json vertically scrollable

* mobile: fix inbox pgp keys title text mismatch

* mobile: ask user password before showing pgp keys

* mobile: show inbox keys saved success message when inbox api created

* intl: cleanup

* mobile: show generic error below input fields

* mobile: show full date-time on inbox api keys

* mobile: update inbox enabled setting on userFetched event
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* core: add extensive logging around sync

* mobile: fix scrollview in sheet

---------

Co-authored-by: Ammar Ahmed <ammarahmed6506@gmail.com>
Co-authored-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
Co-authored-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2026-06-13 10:28:43 +05:00
Ammar Ahmed
8c3c011ad0 mobile: fix scrollview in sheet 2026-06-13 10:27:49 +05:00
Abdullah Atta
632d923c69 core: add extensive logging around sync 2026-06-13 09:23:38 +05:00
Ammar Ahmed
ce03e01bf2 mobile: fix date picker modal renders blank if themVariant is not set based on app's theme mode 2026-06-11 20:19:16 +05:00
Ammar Ahmed
e2aa57909f Merge pull request #9969 from streetwriters/mobile/live-inbox-enabled
mobile: update inbox enabled setting on userFetched event
2026-06-11 10:27:04 +05:00
01zulfi
6c9eb57f3d mobile: update inbox enabled setting on userFetched event
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-06-11 09:56:04 +05:00
Ammar Ahmed
e68d60588e mobile: show full date-time on inbox api keys 2026-06-11 09:27:06 +05:00
Ammar Ahmed
49aa7860f8 mobile: show generic error below input fields 2026-06-11 09:27:06 +05:00
Ammar Ahmed
9c6cfa0b27 intl: cleanup 2026-06-11 09:27:06 +05:00
Ammar Ahmed
4d75a55f33 mobile: show inbox keys saved success message when inbox api created 2026-06-11 09:26:47 +05:00
Ammar Ahmed
e1e2aec014 mobile: ask user password before showing pgp keys 2026-06-11 09:26:47 +05:00
Ammar Ahmed
e82b141aaf mobile: fix inbox pgp keys title text mismatch 2026-06-11 09:26:47 +05:00
Ammar Ahmed
db53a1e311 mobile: make failed inbox item error json vertically scrollable 2026-06-11 09:26:47 +05:00
Ammar Ahmed
3216c33fe6 mobile: enable save button only if keys are edited 2026-06-11 09:26:47 +05:00
Ammar Ahmed
d2ca64d7c9 mobile: show loading in switch item when disabling inbox api 2026-06-11 09:26:47 +05:00
Ammar Ahmed
91a56058bb mobile: fix expired text not visible in light mode 2026-06-11 09:26:47 +05:00
Ammar Ahmed
4e057fa788 mobile: show error on both inputs in case of invalid keypair 2026-06-11 09:26:47 +05:00
Ammar Ahmed
229edaae55 mobile: fix ts errors 2026-06-11 09:26:47 +05:00
Ammar Ahmed
eb265ea89e mobile: update linkedom version 2026-06-11 09:26:47 +05:00
Ammar Ahmed
16ee34aceb mobile: add missing setup inbox keys screen 2026-06-11 09:26:47 +05:00
Ammar Ahmed
8dd39e6a68 mobile: fix typo Notesbook to Notesnook 2026-06-11 09:25:50 +05:00
Ammar Ahmed
d8bc12287c mobile: fix api key shows expired status if set to never expire 2026-06-11 09:25:50 +05:00
Ammar Ahmed
5ba3233055 mobile: add validation for creating api key name input 2026-06-11 09:25:50 +05:00
Ammar Ahmed
ffecb2f9cb mobile: fix failed inbox item text goes outside the view 2026-06-11 09:25:50 +05:00
Ammar Ahmed
bfd3b0736d mobile: hide inbox settings when inbox api is disabled 2026-06-11 09:25:50 +05:00
Ammar Ahmed
26b9dc1149 mobile: bump version 2026-06-11 09:25:50 +05:00
Abdullah Atta
1a6c752c57 web: bump version to 3.4.0-beta.1 2026-06-11 09:25:33 +05:00
01zulfi
c7e0225535 core: handle triggerSync & inboxUpdated SSEs (#9963)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-06-11 09:23:56 +05:00
kashaf-ansari-dev
0cb527f97b mobile: clear password error when switching locked notes
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-06-10 22:19:46 +05:00
Abdullah Atta
9c103ea0e3 Merge branch 'master' into beta 2026-06-10 17:39:44 +05:00
kashaf-ansari-dev
c89a9fad50 mobile: fix biometric password update flow
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-06-10 09:03:09 +05:00
kashaf-ansari-dev
251e635ccb core: handle note open/unlock using try/catch
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-06-10 09:03:09 +05:00
kashaf-ansari-dev
46d5bc83c8 core: don't recreate vault on note open/remove after vault deletion
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-06-10 09:03:09 +05:00
kashaf-ansari-dev
9c69514a74 mobile: reset crendentials after deleting vault
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-06-10 09:03:09 +05:00
kashaf-ansari-dev
a6a74b924e mobile: show inline password error on note unlock instead of toast
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-06-10 09:03:09 +05:00
kashaf-ansari-dev
8c8f085b16 mobile: remove unused validationType from FormInput
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-06-10 09:03:08 +05:00
kashaf-ansari-dev
f75ce74c6b mobile: replace Input with FormInput in VaultDialog for inline field validation
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-06-10 09:03:08 +05:00
kashaf-ansari-dev
d7e92346e5 mobile: reflected published note title in published section
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-06-10 07:56:09 +05:00
kashaf-ansari-dev
17bbaa2323 mobile: prevent UI jerk on analytics refresh after app resume
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-06-09 20:09:09 +05:00
Ammar Ahmed
2f47146ae2 Merge pull request #9959 from streetwriters/mobile/release-3.3.27
mobile: prepare v3.3.27 for release
2026-06-09 19:25:15 +05:00
Ammar Ahmed
2dd6f94f41 mobile: fix theme loading 2026-06-09 13:02:38 +05:00
01zulfi
5f88375bec Merge pull request #9957 from streetwriters/release/3.3.23
web: bump version to 3.3.23
2026-06-08 15:55:55 +05:00
Ammar Ahmed
3f9bea1d6f mobile: prepare v3.3.27 for release 2026-06-08 13:17:53 +05:00
Ammar Ahmed
97d3162d20 Merge pull request #9958 from streetwriters/mobile/release-3.3.25
Mobile/release 3.3.25
2026-06-08 13:15:26 +05:00
Ammar Ahmed
a12e6ba008 Merge pull request #9900 from streetwriters/mobile/311
Fix saving Recovery Key QR-Code to Gallery saves it to files
2026-06-08 13:11:27 +05:00
Ammar Ahmed
dfb1dc7e57 mobile: do not request any permissions on android 2026-06-08 13:11:00 +05:00
Ammar Ahmed
c27e8916a1 mobile: fix toast message not shown 2026-06-08 13:11:00 +05:00
Ammar Ahmed
5fe6331c1f mobile: fix saving qr-code to gallery 2026-06-08 13:11:00 +05:00
Ammar Ahmed
68d1a1bc34 Merge pull request #9894 from kashaf-ansari-dev/fix-theme-invalid-file-upload-error
mobile: show user-friendly error for invalid theme file upload
2026-06-08 13:09:32 +05:00
kashaf-ansari-dev
7adc5fbf1e mobile: copy actual error message for incomplete theme file uploads
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-06-08 13:09:10 +05:00
kashaf-ansari-dev
46b6888ca3 mobile: add error message for incomplete theme files
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-06-08 13:09:10 +05:00
Ammar Ahmed
9eec90e333 mobile: fix invalid error shown on iOS if no file is picked 2026-06-08 13:09:10 +05:00
kashaf-ansari-dev
e49291c55e mobile: add Learn more action to theme import invalid JSON file error and modified error message
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-06-08 13:09:10 +05:00
kashaf-ansari-dev
70147d4332 mobile: show user-friendly error for invalid theme file upload
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-06-08 13:08:53 +05:00
Ammar Ahmed
f8d2649f4d Merge pull request #9944 from streetwriters/fix/517
mobile: wrap title input into multiple lines for publish note
2026-06-08 13:07:25 +05:00
Ammar Ahmed
44ffe0adf6 mobile: fix version 2026-06-08 11:56:55 +05:00
Ammar Ahmed
14cb815e34 mobile: fix ios app version 2026-06-08 11:56:55 +05:00
01zulfi
ca4368a08e web: bump version to 3.3.23
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-06-08 10:43:42 +05:00
Ammar Ahmed
537c514562 Merge pull request #9953 from streetwriters/mobile/release-3.3.25
Mobile/release 3.3.25
2026-06-08 10:23:15 +05:00
copilot-swe-agent[bot]
d9720ba7b8 ci: make iOS artifact upload non-blocking 2026-06-06 10:11:35 +00:00
Ammar Ahmed
16e968dc57 mobile: release v3.3.25 2026-06-06 10:11:50 +05:00
Ammar Ahmed
03fafcbd15 mobile: fix attachment file-size check 2026-06-06 10:11:25 +05:00
Abdullah Atta
b7bd06c9f3 Release v3.3.22 (#9952)
* web: fix file size is 0 error on attachment upload (#9951)

* web: bump version to 3.3.22
2026-06-06 09:48:03 +05:00
Abdullah Atta
766a8f28d4 web: bump version to 3.3.22 2026-06-06 09:22:20 +05:00
Abdullah Atta
86512b5ce7 web: fix file size is 0 error on attachment upload (#9951) 2026-06-06 09:10:29 +05:00
kashaf-ansari-dev
cdef17eb8d mobile: fix monograpghs analytics fetch and display conditions
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-06-05 12:33:02 +05:00
kashaf-ansari-dev
4256003e1c mobile: refresh monograph analytics on app foreground
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-06-05 12:32:46 +05:00
kashaf-ansari-dev
657f0e428a mobile: display monograph view count
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-06-05 12:29:59 +05:00
Ammar Ahmed
59b003d1f8 mobile: limit input height to 100 2026-06-04 20:24:22 +05:00
Ammar Ahmed
50d21e8787 mobile: wrap title input into multiple lines for publish note 2026-06-04 20:21:10 +05:00
Ammar Ahmed
a820fcde53 editor: allow any width/height value in EmbedSizeOptions when inserting embed (#9925)
* editor: allow any width/height value in EmbedSizeOptions when inserting embeds

* editor: only accept valid url as value

* editor: allow embeding normal website urls

* editor: check url if embedSource=url

* editor: use defaultValue prop for width/height
2026-06-04 13:16:51 +05:00
01zulfi
5ddf1eddf5 web: validate max date when creating reminder (#9937)
* web: validate max date when creating reminder
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: skip max date validation on repeat mode in reminder dialog
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-06-04 13:16:01 +05:00
Asad Ali
4237d2eb4a desktop: fix spell check issue for non-latin languages #9423 (#9443)
* desktop: fix spell check issue for non-latin languages #9423

Signed-off-by: Asad Ali <asadali.wolf@gmail.com>

* Delete apps/desktop/src/api/spell-checker.test.ts

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

* desktop: refactor

---------

Signed-off-by: Asad Ali <asadali.wolf@gmail.com>
Signed-off-by: Abdullah Atta <thecodrr@protonmail.com>
Co-authored-by: Abdullah Atta <thecodrr@protonmail.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-06-04 09:36:34 +05:00
Ammar Ahmed
f67cd503d6 Merge pull request #9922 from streetwriters/fix/400
mobile: hide toc button if no headings in editor
2026-06-04 08:54:08 +05:00
Ammar Ahmed
14039d6a1e Merge pull request #9921 from streetwriters/fix/401
Fix long tag name does not wrap properly in editor
2026-06-04 08:53:50 +05:00
Ammar Ahmed
d1fed9072e Merge pull request #9890 from kashaf-ansari-dev/fix-duplicate-note-message
mobile: add success message after note duplication
2026-06-04 08:52:31 +05:00
kashaf-ansari-dev
b003a3ffed mobile: reduce translation diff noise
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-06-03 14:17:33 +05:00
kashaf-ansari-dev
55e10e4755 mobile: add success message after note duplication
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-06-03 14:14:07 +05:00
Ammar Ahmed
7c1dd0f79a Merge pull request #9915 from streetwriters/fix/359-360
mobile: fix unable to edit/delete reminder when accessing reminders from note properties
2026-06-03 14:03:41 +05:00
Ammar Ahmed
229cf195a8 Merge pull request #9920 from streetwriters/fix/357
Fix screen jerks when opening add reminder screen from note
2026-06-03 14:02:33 +05:00
Ammar Ahmed
38da6ac08b Merge pull request #9918 from streetwriters/fix/318
Fix themes do not load properly on mobile in theme selector
2026-06-03 14:01:58 +05:00
Ammar Ahmed
597dc587cd Merge pull request #9919 from streetwriters/fix/317
Fix subscription not active opens a sheet that keeps loading if there is an error
2026-06-03 14:01:32 +05:00
Ammar Ahmed
5c76b92789 Merge pull request #9893 from kashaf-ansari-dev/fix-publish-password-validation
mobile: validate password field before publishing note
2026-06-03 14:01:09 +05:00
Abdullah Atta
8418d5fa9c Merge pull request #9886 from streetwriters/web/notebook-tag-group-options
web: allow notebook/tag/color level group&sort preferences
2026-06-03 13:35:06 +05:00
01zulfi
cd071f8f58 web: ensure full year range is visible in reminder dialog's day picker (#9928)
* web: ensure full year range is visible in reminder dialog's day picker
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: include boundary years in day picker's fluid year dropdown
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-06-03 11:52:36 +05:00
Abdullah Atta
967374d28e Merge pull request #9526 from streetwriters/fix/desktop-app-security
Improve desktop app security
2026-06-02 15:56:54 +05:00
01zulfi
32ddc3fa40 web: refresh tag & notebook filtered lists on item rename (#9923)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-06-02 15:27:47 +05:00
01zulfi
68d07a7862 web: fix properties panel in focus mode (#9917)
* allow properties panel to open/close when app is in focus mode

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-06-02 13:06:50 +05:00
01zulfi
2aa5162fc9 web: set edited date as max date in note creation date edit's day picker (#9924)
* web: set edited date as max date in note creation date edit's day picker
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: fix creation date not being able to set to same as edited date
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-06-02 13:02:06 +05:00
Abdullah Atta
adaaabe46c ci: install arm64 sqlite3-fts5-html 2026-06-02 12:27:26 +05:00
01zulfi
cf5565d09d web: don't allow note expiry to be set beyond 1 year in the future (#9901)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-06-02 08:56:21 +05:00
Ammar Ahmed
f129e0c411 mobile: hide toc button if no headings in note 2026-06-01 13:18:13 +05:00
01zulfi
bb43d80e05 Merge pull request #9916 from streetwriters/release/3.3.21
web: bump version to 3.3.21
2026-06-01 12:52:57 +05:00
Ammar Ahmed
4e4d3866df mobile: limit tag to single line only 2026-06-01 12:43:41 +05:00
Ammar Ahmed
b2088ef782 mobile: fix long tag name does not wrap properly in editor 2026-06-01 12:27:37 +05:00
Ammar Ahmed
07db14f6bf mobile: fix screen jerks when opening add reminder screen from note 2026-06-01 11:53:00 +05:00
Ammar Ahmed
82d37701c5 mobile: fix gap in buttons 2026-06-01 11:09:29 +05:00
Ammar Ahmed
b48ab3c85d mobile: fix query error on fetching themes in theme-selector 2026-06-01 10:56:35 +05:00
01zulfi
52d7d90b68 web: bump version to 3.3.21
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-06-01 10:21:57 +05:00
Ammar Ahmed
bb7ff4f376 mobile: show error if no active subscription is found 2026-06-01 10:13:31 +05:00
Ammar Ahmed
43b35917ef mobile: fix subscription not active screen gets stuck on loading on iOS 2026-06-01 09:32:05 +05:00
Ammar Ahmed
bcfa34ca05 mobile: fix unable to edit/delete reminder when accessing reminders from note properties 2026-06-01 08:40:30 +05:00
Abdullah Atta
8b2f5901fe web: increase real time sync test timeout 2026-05-30 11:16:10 +05:00
Abdullah Atta
d64d2e44d9 web: make backup tests more reliable 2026-05-30 11:10:47 +05:00
Abdullah Atta
4f8c9eb76c web: fix tests 2026-05-30 11:00:35 +05:00
Abdullah Atta
ba0cfbf44a desktop: move file link confirm dialog to native side 2026-05-30 11:00:35 +05:00
Abdullah Atta
b43374c53f desktop: fix typescript build output 2026-05-30 11:00:35 +05:00
Abdullah Atta
f2c08c89b0 desktop: add support for running web tests via desktop app 2026-05-30 11:00:35 +05:00
Abdullah Atta
bd04427e03 desktop: secure desktop app by disabling nodeIntegration & enabling contextIsolation
this required significant infrastructure changes to how we do things. It is no longer possible to access anything outside of what the app exposes. Requiring node modules has been disabled which should make things A LOT more secure. File system related APIs have also been removed and now everything sensitive is handled on the Node.js side of things. The renderer does not control paths, fs, or anything that could be used to exploit the host system.
2026-05-30 11:00:35 +05:00
Abdullah Atta
27ca64694f web: bring back file:// links opening support 2026-05-30 11:00:35 +05:00
Abdullah Atta
42f229ab03 Merge branch 'master' into beta 2026-05-30 11:00:07 +05:00
Abdullah Atta
aab6a36067 Do not refetch user before decrypting each chunk in sync (#9805)
* core: do not refetch user before decrypting each chunk in sync

* web: fix editor readonly state not updating in realtime
2026-05-25 19:42:03 +05:00
01zulfi
c7e0cd35e7 editor: expand collapsed nodes on search scroll if search text inside callout (#9745)
* editor: expand callout on search scroll if search text inside callout
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* editor: expand outline list && headings on search scroll
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* editor: expand parents & scroll into view on type

* editor: fix parents expanding on every transaction

* editor: add a small debounce to delay search

* editor: fix editor not scrolling to selected search result

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-05-25 19:41:06 +05:00
01zulfi
bd3edc535a web: use getGroupOptionsById & setGroupOptionsById
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-05-25 15:04:17 +05:00
01zulfi
1dff7a121d core: fix group options by id tests
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-05-25 14:50:57 +05:00
Ammar Ahmed
3f3467e3a7 mobile: add support for sorting/grouping items based on notebook/tag/color id 2026-05-25 13:21:48 +05:00
Ammar Ahmed
39bb9afa0e core: add getGroupOptionsById/setGroupOptionsById 2026-05-25 13:21:00 +05:00
kashaf-ansari-dev
886e757715 mobile: removed redundant isLocked check for password field in publish form
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-05-25 11:21:55 +05:00
Ammar Ahmed
f1124c4112 Merge pull request #9896 from streetwriters/ci-fix-ios-workflow
ci: fix ios workflow
2026-05-25 10:18:54 +05:00
Ammar Ahmed
a1575ab5ea ci: fix 2026-05-25 10:18:16 +05:00
Ammar Ahmed
d31eaef6d4 ci: update 2026-05-25 10:17:22 +05:00
Ammar Ahmed
930e31d109 ci: update 2026-05-25 08:44:08 +05:00
Ammar Ahmed
2a6cc90592 ci: fix missing ios platform error 2026-05-25 08:43:46 +05:00
Ammar Ahmed
a13962f8c9 ci: fix ios workflow 2026-05-23 13:49:15 +05:00
kashaf-ansari-dev
5c93645969 mobile: migrate publish inputs to FormInput with validation
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-05-23 11:03:00 +05:00
Abdullah Atta
cad13856af Merge branch 'master' into beta 2026-05-23 10:28:40 +05:00
01zulfi
a4c8259a02 web: bump version to 3.3.20 (#9888)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-05-22 20:28:04 +05:00
kashaf-ansari-dev
07defdfa78 mobile: validate password field before publishing note
Signed-off-by: kashaf-ansari-dev <kashafansari3108@gmail.com>
2026-05-22 17:15:38 +05:00
01zulfi
fc93a39164 web: fix weird scroll when toggling switch in attach files dialog (#9891)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-05-22 15:46:36 +05:00
01zulfi
9b588c50bf web: fix day picker to allow selecting all months within min/max range (#9879)
* web: fix day picker to allow selecting all months within min/max range
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: disable month nav buttons if they exceed min/max range in DayPicker
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-05-22 11:07:21 +05:00
01zulfi
27b00d4ef9 web: allow notebook/tag level group&sort preferences
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-05-22 10:48:27 +05:00
Ammar Ahmed
3248c82c5c Merge branch 'master' into beta 2026-05-21 13:44:51 +05:00
Abdullah Atta
691a0a62ea Merge pull request #9829 from streetwriters/web/failed-inbox-table
web: handle & show failed inbox items
2026-05-21 13:13:27 +05:00
01zulfi
2c578c9695 web: add delete all button in inbox history dialog
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-05-21 12:25:59 +05:00
Ammar Ahmed
ee48990c90 mobile: show delete-all button only when items are in list 2026-05-21 12:15:28 +05:00
Ammar Ahmed
436c635889 mobile: add failed inbox item history 2026-05-21 11:55:44 +05:00
Ammar Ahmed
51b226226e core: fix invalid html results in empty note
When html is invalid we want to return it as-is in a code-block so it gets rendered correctly
2026-05-21 11:54:45 +05:00
01zulfi
7deb095aca web: inbox UI fixes
* use user format preferences for dates in inbox module
* delete all failed inbox items when inbox is disabled
* improve error message for failed validation case

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-05-21 11:54:45 +05:00
01zulfi
b615dfcd87 web: handle & show failed inbox items
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-05-21 11:54:43 +05:00
01zulfi
833368e225 web: fix never expiring inbox api key being shown as expired (#9856)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-05-21 11:48:04 +05:00
Ammar Ahmed
9221e21393 mobile: fix inbox keys validation (#9882) 2026-05-21 11:44:25 +05:00
Ammar Ahmed
91e0437554 Merge pull request #9872 from streetwriters/mobile/release-3.3.24
mobile: release v3.3.24
2026-05-20 09:46:10 +05:00
01zulfi
0476d3652a core: use RequestFetchV4 method (#9873)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-05-19 13:38:52 +05:00
01zulfi
4dcf14fce1 core: handle unknown collection types in sync (#9878)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-05-19 13:38:20 +05:00
01zulfi
8f8ac25d20 desktop: add support for opening file links (#9784)
* desktop: add support for opening file links
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: unescape paths with spaces

* web: wrap message in confirm dialog
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* desktop: show message box if path doesn't exist when opening file link
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* desktop: disable opening file links on flatpak
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* desktop: wrap long file paths in electron dialog
* on linux, long messages in dialog aren't wrapped, so we need to wrap manually

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

* desktop: fix persistent "path not found" error

* intl: update strings

---------

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-05-19 13:27:46 +05:00
01zulfi
b594d58d31 web: improve attachments UX (#9718)
* web: improve attaching attachments UX
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: don't couple editor logic in attach files dialog
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: close attach file dialog after single file && remove countdown
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: show file size exceeded message in attach file dialog
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: add 'processing' status message when file picker is opened
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: show "processing" modal after picking files

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-05-19 13:25:17 +05:00
Abdullah Atta
e4c132a8cc monograph: bump version to 1.3.1 2026-05-18 21:21:02 +05:00
Abdullah Atta
ba9877b3dd monograph: bump bun version to 1.3.14 2026-05-18 21:20:52 +05:00
01zulfi
20921df3f1 Merge pull request #9869 from streetwriters/release/3.3.19
web: bump version to 3.3.19
2026-05-18 15:23:49 +05:00
01zulfi
43d3036525 web: fix subscription settings not showing
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-05-18 13:19:33 +05:00
Ammar Ahmed
f725499039 mobile: release 3.3.24 2026-05-18 12:47:36 +05:00
01zulfi
cd743a92cb web: bump version to 3.3.19
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-05-18 10:31:21 +05:00
01zulfi
a545c9cfac core: require non-empty source field in inbox item (#9868)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-05-18 09:52:25 +05:00
Abdullah Atta
ea0550cb05 desktop: disable updates for portable desktop app (#9855)
* desktop: disable updates for portable desktop app

* web: hide setting group if all settings under it are hidden

* web: hide check for updates button on flatpak/snap/portable apps

* desktop: disable auto updates for flatpak/snap/portable apps
2026-05-15 18:59:01 +05:00
Ammar Ahmed
fa03ec5ebe Merge branch 'master' into beta 2026-05-15 12:53:44 +05:00
01zulfi
79ecbf6752 core: clear key cache & fix private key encoding in saveInboxKeys() (#9851)
* after saving new keys, we didn't clear the cache so getInboxKeys() was still returning old keys
* remove unnecessary JSON.stringify on private key in saveInboxKeys()

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-05-15 11:34:37 +05:00
Ammar Ahmed
d9d5cb2168 Merge pull request #9832 from streetwriters/fix/310
mobile: fix form validation in set your name dialog
2026-05-15 08:42:32 +05:00
Ammar Ahmed
69e509da1c Merge pull request #9857 from streetwriters/fix/crash-on-upgrade
mobile: fix app crashes on upgrade from v2.6.x to v3
2026-05-15 08:41:43 +05:00
01zulfi
2d97445f26 web: show correct message when user provided pgp private key is encrypted (#9850)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-05-15 08:23:16 +05:00
Ammar Ahmed
6ba1fda2f2 mobile: fix app crashes on upgrade from v2.6.x to v3 2026-05-14 13:43:20 +05:00
Ammar Ahmed
93efecd6c3 Merge pull request #9839 from streetwriters/mobile/release-3.3.23
mobile: release 3.3.23
2026-05-14 10:42:25 +05:00
Ammar Ahmed
70d1119cf0 mobile: fix crash on tap set your name 2026-05-14 09:39:01 +05:00
Ammar Ahmed
3f11b76811 mobile: fix form validation in set your name dialog 2026-05-14 09:38:15 +05:00
01zulfi
d912c93f81 web: fix validation error message on add notebook dialog (#9852)
* only title is required field

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-05-14 09:25:21 +05:00
01zulfi
e635bf628a web: fix expiring notes option on free plan (#9853)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-05-14 09:25:13 +05:00
01zulfi
8048c90a89 web: disable months/year menu options where applicable (#9846)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-05-13 11:46:23 +05:00
01zulfi
8093f44a70 web: skip attachments restore from backup if user not logged in (#9778)
* web: skip attachments restore from backup if user not logged in
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: show login message when downloading attachments without login
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: show backup restored success toast
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-05-12 09:08:26 +05:00
Ammar Ahmed
d0bfc21b49 mobile: release 3.3.23 2026-05-11 14:04:02 +05:00
01zulfi
f67f90cecd Merge pull request #9838 from streetwriters/release/3.3.18
web: bump version to 3.3.18
2026-05-11 13:47:06 +05:00
Ammar Ahmed
ff4e2631cb Merge pull request #9828 from streetwriters/fix/313
Add input validation for 2fa method configuration
2026-05-11 13:38:44 +05:00
Ammar Ahmed
9779685102 mobile: fix no success message on copying 2fa codes 2026-05-11 13:38:19 +05:00
Ammar Ahmed
f2925725b8 mobile: add input validation for 2fa method configuration 2026-05-11 13:38:17 +05:00
Ammar Ahmed
9ca9fe3f99 Merge pull request #9827 from streetwriters/fix/312
Fix copy button text is cut in 2fa settings page
2026-05-11 13:36:49 +05:00
Ammar Ahmed
8e5039fa7f Merge pull request #9831 from streetwriters/fix/289
Add form validation and error handling in change email on Mobile
2026-05-11 13:36:15 +05:00
Ammar Ahmed
2f47fe331d Merge pull request #9825 from streetwriters/fix/288
Add form validation in change password screen
2026-05-11 13:35:45 +05:00
Ammar Ahmed
238abee126 mobile: add form validation in change password screen 2026-05-11 13:35:28 +05:00
Ammar Ahmed
a8d0fa3b94 Merge pull request #9820 from streetwriters/fix/286
Fix rename attachment does not work on iOS
2026-05-11 13:33:00 +05:00
Ammar Ahmed
27b3cfcd23 Merge pull request #9819 from streetwriters/fix/285
Fix Reminder allows past time in edit mode without validation error
2026-05-11 13:32:39 +05:00
01zulfi
d3f1690dca web: bump version to 3.3.18
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-05-11 10:19:20 +05:00
Ammar Ahmed
d4d7eac240 mobile: fix loading indicator causes cancel button to jump 2026-05-08 10:44:05 +05:00
Ammar Ahmed
e7fb246c25 mobile: show errors under input in rename dialog 2026-05-08 10:44:04 +05:00
Ammar Ahmed
03defb3a48 mobile: fix defaultValue is used if input is empty in dialog input 2026-05-08 10:42:25 +05:00
Ammar Ahmed
73c44d476b mobile: fix rename attachment does not work on iOS 2026-05-08 10:41:53 +05:00
Ammar Ahmed
61aaaba452 mobile: cleanup 2026-05-08 10:02:57 +05:00
Ammar Ahmed
9992bf8c32 mobile: add form validation in change email 2026-05-08 09:26:11 +05:00
Ammar Ahmed
b8b7d37799 core: remap Invalid token error to Invalid code in changeEmail 2026-05-08 09:24:39 +05:00
Abdullah Atta
0aaabfab62 web: exclude hidden settings from setting search (#9772)
* web: exclude hidden settings from search

* web: add support for searching setting section groups
2026-05-08 08:53:00 +05:00
Ammar Ahmed
14d680c092 mobile: show all errors inline with inputs/data-entry items 2026-05-07 18:42:47 +05:00
Ammar Ahmed
28f95b0061 mobile: fix plans keep loading on paywall screen 2026-05-07 13:01:38 +05:00
Ammar Ahmed
419dd555f4 mobile: fix reminder allows past time in edit mode without validation error 2026-05-07 13:01:37 +05:00
Ammar Ahmed
1312ce7d50 Merge pull request #9818 from streetwriters/fix/284
Trim title before checking if title is valid in add reminder screen
2026-05-07 12:58:46 +05:00
Ammar Ahmed
d16cd510b3 mobile: cleanup 2026-05-07 12:58:05 +05:00
Ammar Ahmed
af08ed5174 mobile: show title is required error under the input 2026-05-07 12:58:05 +05:00
Ammar Ahmed
365e1d7029 mobile: trim title before checking if title is valid 2026-05-07 12:57:25 +05:00
Ammar Ahmed
80b75fc28c Merge pull request #9821 from streetwriters/fix/287
Show confirm dialog before deleting attachment in attachment manager
2026-05-07 12:54:10 +05:00
Ammar Ahmed
6de4b4ac2d mobile: show toast message after attachment deleted 2026-05-07 12:53:44 +05:00
Ammar Ahmed
39bfff6b14 mobile: fix base dialog background is always transparent 2026-05-07 12:53:44 +05:00
Ammar Ahmed
e381b62e5c mobile: show confirm dialog before deleting attachment in attachment manager 2026-05-07 12:53:43 +05:00
Ammar Ahmed
ffef5affce Merge pull request #9822 from streetwriters/fix/309
mobile: fix handling invalid gift code input
2026-05-07 12:28:27 +05:00
Ammar Ahmed
780276b30e mobile: fix pro user walk through makes app get stuck 2026-05-07 11:12:49 +05:00
Ammar Ahmed
e023f11cd9 mobile: fix copy button text is cut in 2fa settings page 2026-05-07 09:22:59 +05:00
Ammar Ahmed
276239dd2b mobile: add form validation support in dialog inputs 2026-05-07 08:49:21 +05:00
Ammar Ahmed
5ef3bdcad4 mobile: fix handling invalid gift code 2026-05-07 08:03:45 +05:00
Ammar Ahmed
63076d37f5 Merge pull request #9808 from streetwriters/fix/270
Improve error handling when signing up on mobile
2026-05-06 13:21:56 +05:00
Ammar Ahmed
724ad58824 mobile: show errors under the email input 2026-05-06 11:28:58 +05:00
Chloe Oletto
4f805910c4 web: Don't show input box in "too many Inbox keys" dialog. (#9817)
Signed-off-by: Chloe Oletto <NeedsChloesure@riseup.net>
2026-05-06 08:20:55 +05:00
Ammar Ahmed
7fcdb3b55d mobile: skip showing toast message 2026-05-06 08:16:35 +05:00
Ammar Ahmed
47ba40d7cd mobile: fix ts errors in build 2026-05-05 21:00:17 +05:00
Ammar Ahmed
e6a75c1f8a Merge branch 'master' into beta 2026-05-05 20:07:31 +05:00
Ammar Ahmed
0801e73242 mobile: fix login and signup flows 2026-05-05 19:06:42 +05:00
Ammar Ahmed
c82487983c Merge pull request #9806 from streetwriters/fix/268
Fix No error message shown for missing or incorrect 2FA code on mobile
2026-05-05 14:13:20 +05:00
Ammar Ahmed
2ad39048da mobile: fix form validation in login, signup and forgot password 2026-05-05 13:47:37 +05:00
Ammar Ahmed
3421627f31 mobile: show error if passwords do not match or fields empty 2026-05-05 10:34:44 +05:00
Ammar Ahmed
cf6262e995 mobile: fix showing 2fa error when field is empty and show error below input field 2026-05-05 10:32:27 +05:00
Ammar Ahmed
8a325bf7c0 mobile: cleanup 2026-05-05 10:11:19 +05:00
Ammar Ahmed
883667b411 mobile: show error message on failed two-factor auth 2026-05-05 10:11:19 +05:00
Ammar Ahmed
06b88e74ae Merge pull request #9807 from streetwriters/fix/269
Remove password length check on signup on mobile
2026-05-05 10:10:03 +05:00
Ammar Ahmed
7800a25775 Merge pull request #9803 from streetwriters/mobile/release-3.3.22
mobile: release v3.3.22
2026-05-05 07:46:27 +05:00
Ammar Ahmed
01d0de3909 mobile: remove password length check on signup 2026-05-04 13:42:05 +05:00
Ammar Ahmed
d61f9472c8 mobile: fix search in archived notes returns no results 2026-05-04 13:08:05 +05:00
Ammar Ahmed
2465337148 mobile: fix show results count in search 2026-05-04 13:05:54 +05:00
01zulfi
af4638ac50 Merge pull request #9802 from streetwriters/release/3.3.17
web: bump version to 3.3.17
2026-05-04 12:33:11 +05:00
Ammar Ahmed
5e6be09aab mobile: release v3.3.22 2026-05-04 10:41:36 +05:00
Ammar Ahmed
f626953e0a Merge pull request #9781 from streetwriters/fix/archive-note-time
Fix Archive screen does not show correct date/time on note based on Sorting Type
2026-05-04 10:30:02 +05:00
01zulfi
4d4bce301d web: bump version to 3.3.17
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-05-04 10:21:03 +05:00
Ammar Ahmed
d45b18afd6 Merge pull request #9783 from streetwriters/feat/compact-view-search
mobile: add compact-view support in search results
2026-05-04 09:51:01 +05:00
Ammar Ahmed
4f212453a6 mobile: fix sort options not working in trash 2026-05-02 11:04:11 +05:00
Ammar Ahmed
2c1b9081bf mobile: fix group type usage
Explicitly pass group type to list and sort sheet.
2026-05-02 10:49:08 +05:00
01zulfi
7c5dc1615c web: remove testimonial with broken link (#9789)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-05-02 08:48:34 +05:00
Ammar Ahmed
3fb07369bb mobile: fix navigating from Monographs to Search screen 2026-05-01 10:09:50 +05:00
Ammar Ahmed
9b480950da mobile: fix typo in properties key 2026-05-01 09:59:44 +05:00
01zulfi
9f20d243fa web: disable expand sidebar button on small screens (#9769)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-05-01 09:07:28 +05:00
01zulfi
13e6573342 web: show copied text when monograph link is copied (#9780)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-30 17:31:08 +05:00
Ammar Ahmed
f37869cb5a mobile: add compact-view support in search results 2026-04-30 13:55:19 +05:00
Ammar Ahmed
46d802548b mobile: fix Archive screen does not show correct date/time on note based on sorting 2026-04-30 13:27:52 +05:00
Ammar Ahmed
2bf42c207d Merge pull request #9776 from streetwriters/mobile/release-3.3.21
mobile: v3.3.21 release
2026-04-30 10:05:04 +05:00
Ammar Ahmed
65e42e5d8b mobile: prepare for v3.3.21 release 2026-04-29 16:36:09 +05:00
Ammar Ahmed
c85007f0dd Merge pull request #9705 from streetwriters/fix/screenguard
mobile: fix screenguard on mobile & privacy mode
2026-04-29 16:20:06 +05:00
Ammar Ahmed
36058145b5 mobile: disable privacy mode on user logout 2026-04-29 16:19:18 +05:00
Ammar Ahmed
a376a6b497 mobile: fix screenguard on mobile & privacy mode 2026-04-29 16:19:18 +05:00
Ammar Ahmed
cd62d51e45 Merge pull request #9752 from streetwriters/mobile/fix-locked-note-merge-conflict
mobile: fix saving incoming note in merge conflict dialog does not work
2026-04-29 16:16:38 +05:00
Abdullah Atta
3f6fcbacbc global: fix dialog on successful bug report (#9750)
* core: handle different issue report responses

* web: better handling for issue report responses

* mobile: update issue report dialog to show correct info of where issue is reported

* mobile: close sheet on clicking "done"

* web: do not include email in issue success dialog

---------

Co-authored-by: Ammar Ahmed <ammarahmed6506@gmail.com>
2026-04-29 14:19:29 +05:00
01zulfi
2a2bf67fea web: use expire verbiage in subscription subtitle when provider is gift card (#9759)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-29 08:45:52 +05:00
01zulfi
b8fca5cd98 Merge pull request #9765 from streetwriters/release/3.3.16
web: bump version to 3.3.16
2026-04-28 15:48:34 +05:00
01zulfi
4d87155fdd web: bump version to 3.3.16
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-28 10:17:27 +05:00
01zulfi
ed38bb6299 editor: fix crash in backspace handler (#9740)
* editor: fix crash in backspace handler
* replace nextNode with .nodeAfter

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

* editor: fix position of node when deleting nextNode

---------

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-04-28 09:08:31 +05:00
Abdullah Atta
7c9e3e79df web: small refactor to make the build pass (#9761) 2026-04-27 12:54:44 +05:00
Chloe Oletto
43b90070a9 core: Limit note subtitle/headline length. (#9732)
Signed-off by: Chloe Oletto <NeedsChloesure@riseup.net>
2026-04-23 22:27:33 +05:00
Ammar Ahmed
04f6e1068b mobile: fix saving incoming note in merge conflict dialog does not work 2026-04-23 10:43:07 +05:00
01zulfi
9bea4adcf9 web: show titlebar on auth pages (#9093)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-22 13:51:21 +05:00
Chloe Oletto
a5790de23c desktop: Stop urgent reminder causes segfault & app freeze on Linux. (#9737)
* desktop: Stop urgent reminder causes segfault & app freeze on Linux.

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

* desktop: Linux segfault
Signed-off-by: Chloe Oletto <NeedsChloesure@riseup.net>

* Apply suggestion from @thecodrr

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

---------

Signed-off-by: Abdullah Atta <thecodrr@protonmail.com>
Co-authored-by: Abdullah Atta <thecodrr@protonmail.com>
2026-04-22 11:56:47 +05:00
Abdullah Atta
6489dda4c8 Merge branch 'master' into beta 2026-04-22 11:15:34 +05:00
Abdullah Atta
eb4edeea8f web: fix crash on non-paddle related iframe event (#9747)
Stack trace:
```
undefined is not an object (evaluating 'w.data')
E@https://app.notesnook.com/assets/paddle-Z43isEce-h-M.js:23:45428
E@https://app.notesnook.com/assets/paddle-Z43isEce-h-M.js:23:45846
```
2026-04-22 09:46:29 +05:00
01zulfi
e3ccd504da ui: fix submenu cutting off when there are a lot of lazy items (#9739)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-22 09:11:28 +05:00
Ammar Ahmed
4627d7d5df Merge pull request #9736 from streetwriters/mobile/release-3.3.20
mobile: release 3.3.20
2026-04-22 08:46:43 +05:00
01zulfi
f216c1b65d web: fix scrollbar blocking date in compact note list item (#9719)
* web: fix scrollbar blocking date in compact note list item
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: adjust notes list scrollbar width, compact item padding, & group header padding
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-21 11:08:30 +05:00
Abdullah Atta
4692ba7183 desktop: use newer appimage bundler (#9744)
this should fix libfuse and libz errors
2026-04-21 10:55:52 +05:00
Ammar Ahmed
543825918c mobile: fix useEffect deps in use-feature-manager hook 2026-04-21 08:46:19 +05:00
Ammar Ahmed
9f995b861b mobile: fix toast position incorrect with keyboard opened 2026-04-21 08:45:45 +05:00
Ammar Ahmed
9963a6c655 mobile: delete vault action keeps loading when entering incorrect password 2026-04-21 08:45:29 +05:00
Ammar Ahmed
1696ec5c72 mobile: fix unable to switch between yearly/monthly plans 2026-04-21 08:14:38 +05:00
Ammar Ahmed
7c95faad21 mobile: fix clear trash option is disabled on login 2026-04-20 15:43:39 +05:00
Ammar Ahmed
ca80441c14 mobile: fix trash count does not update on calling clear trash 2026-04-20 15:30:26 +05:00
01zulfi
9c497fd2bb Merge pull request #9724 from streetwriters/release/3.3.15
web: bump version to 3.3.15
2026-04-20 12:21:46 +05:00
Ammar Ahmed
ef5e5e5b4c mobile: release 3.3.20 2026-04-20 12:03:12 +05:00
Abdullah Atta
ede900fdbd ci: add linux arm64 preview builds (#9735) 2026-04-20 11:56:10 +05:00
01zulfi
1758f03f93 intl: regen locale files (#9734)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-20 11:39:13 +05:00
01zulfi
f18db748e0 web: bump version to 3.3.15
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-20 10:38:18 +05:00
01zulfi
2fba2af126 web: fix pdf viewer's search dialog unreadable UI in dark mode (#9720)
* web: fix pdf viewer's search dialog unreadable UI in dark mode
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: fix pdf search dialog ui

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-04-18 10:46:09 +05:00
Ammar Ahmed
0b1aa3315b Merge branch 'master' into beta 2026-04-18 10:08:55 +05:00
Ammar Ahmed
1ef3d712e1 mobile: fix parent notebook can be moved into it's child notebooks. (#9691)
* mobile: fix parent notebook can be moved into it's child notebooks.

1. Ensure notebook that is being moved and all it's children are not visible in move-notebook dialog
2. Ensure child cannot be moved into it's parent again
3. Ensure child can be moved into it's sibling notebooks

* mobile: add e2e tests

* mobile: fix toast position
2026-04-18 09:28:39 +05:00
Ammar Ahmed
405adff8f4 mobile: fix linting not working in vscode and fix lint errors (#9710) 2026-04-18 08:52:39 +05:00
Ammar Ahmed
9a21ca1cf4 mobile: fix discount value shows as undefined on iOS when buying plan (#9711)
* mobile: fix discount value is undefined

* mobile: fix discount calculation and price formatting

Respect spacing when writing price in a currency.
Fix incorrect discount calculation when comparing products of a plan based on int values, use parseFloat to parse correct values for comparision

* mobile: fix ui inconsistencies

* mobile: conditionally include trial plan conditions based on GitHub release status
2026-04-18 08:40:50 +05:00
01zulfi
49252e66ce web: fix RCE via stored XSS in note html export (#9709)
* sandbox the export iframe
* escape fields when building html template

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-17 18:10:06 +05:00
Abdullah Atta
4ba2e900c5 desktop: disable autoInstallOnAppQuit (#9726) 2026-04-17 17:07:21 +05:00
Chloe Oletto
ebfc554cd7 web: fix Beta release track unset after browser exit (#9721)
Beta cookies are set to expire after the session ends, which means update checks in the future (for example, the user puts their computer to sleep, but leaves the web app open) can erroneously suggest the latest stable on the beta release track.

Signed-off By: Chloe Oletto <NeedsChloesure@riseup.net>
2026-04-17 15:49:17 +05:00
01zulfi
b6714b559e web: make dates text reactive to date format setting (#9707)
the following dates text are now reactive:
* dates in note list item
* date in notebook header
* dates in properties panel
* date in reminder list item

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-16 12:42:10 +05:00
Ammar Ahmed
a6c1757cf6 mobile: fix list-items do not show correct notes count in side menu (#9697)
* mobile: fix list-items do not show correct notes count in side menu

* mobile: fix notebook note count not updated on deleting note

* mobile: fix monograph count does not update on unpublishing note in side-menu

* mobile: fix notebook and tag shortcut count does not update in sidemenu
2026-04-15 21:43:45 +05:00
01zulfi
3d847ea00c web: fix time field not aligning in reminder dialog (#9202)
* also capitalize month string

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-15 12:49:22 +05:00
Abdullah Atta
63453942ee Merge pull request #9686 from streetwriters/web/clear-trash
web: add clear trash option in trash nav item context menu
2026-04-15 12:48:50 +05:00
Ammar Ahmed
cb5a7904d3 mobile: add clear trash option in Trash properties
mobile: show clear trash success message

mobile: add e2e tests

mobile: fix clear trash option remains disabled after moving item to trash after login

mobile: Disable "Move to trash" for deleted notes.
2026-04-15 12:48:24 +05:00
01zulfi
f2c088fb18 web: add clear trash option in trash nav item context menu
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

web: disable clear trash if empty && update confirm message
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-15 12:48:10 +05:00
Abdullah Atta
cf3f2fc3ce monograph: bump version to 1.3.0 2026-04-15 08:45:19 +05:00
Ammar Ahmed
a62fea1a4b mobile: release 3.4.0-beta.0 2026-04-15 08:43:49 +05:00
Abdullah Atta
f83e894758 web: bump version to 3.4.0-beta.0 2026-04-15 08:14:29 +05:00
Abdullah Atta
131f6c6d5f Merge branch 'master' into beta 2026-04-15 08:10:24 +05:00
01zulfi
4e2fd9dde6 web: show error text on settings number input's out of range (#9171)
* web: show error text on settings number input's out of range
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: minor refactor and fixes

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-04-15 08:04:58 +05:00
Abdullah Atta
534cf883be Merge pull request #9395 from streetwriters/web/vault-lock-timer-setting
web: add vault lock timer setting
2026-04-15 08:03:47 +05:00
Ammar Ahmed
92c1ca7b6f mobile: fix configure toolbar do not render properly on adding/removing groups (#9699)
* editor: make editor tools object a function

Required for string localization to work properly

* mobile: fix tools and groups do not re-render properly on changing preset or adding tools

* mobile: fix empty toast message shows on removing tools from group
2026-04-14 22:46:41 +05:00
01zulfi
5fcad35cdc Merge pull request #9712 from streetwriters/release/3.3.14
web: bump version to 3.3.14
2026-04-14 15:14:39 +05:00
01zulfi
8a83a1af23 web: fix vault lock timer not showing after vault is created
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-14 13:44:55 +05:00
01zulfi
2b11fcea41 web: bump version to 3.3.14
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-14 13:14:49 +05:00
01zulfi
690c30eed3 web: show toast when vault is auto-locked or when timer is changed
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-14 12:44:26 +05:00
Chloe Oletto
3f0c24d27b core: duplicating a note should copy all the flags (#9250)
Signed-off by: Chloe Oletto <NeedsChloesure@riseup.net>
2026-04-14 08:23:50 +05:00
01zulfi
71088fca99 editor: handle crash on inserting incorrect syntax in math nodes (#9690)
* editor: handle crash on inserting incorrect syntax in math nodes
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* editor: fix 'math error' text shown on newly empty math node
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-13 20:35:57 +05:00
01zulfi
d6f7f0c0be editor: fix crash when app is unlocked after a relock (#9698)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-13 20:35:16 +05:00
01zulfi
4044b68701 webclipper: fix notebooks getting unselected on tag select (#9706)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-13 14:55:20 +05:00
Abdullah Atta
610ced00af global: whitelist postinstall packages on bootstrap (#9629)
* global: enable npm ignore scripts by default

* config: disable ignore-scripts in .npmrc

* mobile: update lockfiles

---------

Co-authored-by: Ammar Ahmed <ammarahmed6506@gmail.com>
2026-04-13 14:27:32 +05:00
Ammar Ahmed
8cc86284ee mobile: fix ts errors 2026-04-13 12:43:32 +05:00
Ammar Ahmed
e4f1f2bfa7 core: fix import 2026-04-13 11:43:33 +05:00
Ammar Ahmed
1f7a64a42d mobile: add vault lock timer settings 2026-04-13 11:40:34 +05:00
01zulfi
2b1cd537e2 web: add vault lock timer setting
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-13 11:40:34 +05:00
Abdullah Atta
f9fecda1f1 ci: enable CSC_FOR_PULL_REQUEST for desktop preview builds (#9695) 2026-04-10 14:45:24 +05:00
Abdullah Atta
769d92fa0c docs(help): add beta warning for inbox api 2026-04-08 09:51:37 +05:00
01zulfi
f6d5d3cebc web: enable inbox && add inbox api docs (#8872)
* web: enable inbox && add inbox api docs
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* docs(help): improve inbox api docs

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-04-08 09:49:00 +05:00
Ammar Ahmed
74921ae1b3 mobile: release 3.3.19 (#9683)
* mobile: release 3.3.19

* mobile: fix ios production xcconfig
2026-04-08 09:37:56 +05:00
Abdullah Atta
f3ef277a52 Merge pull request #9643 from streetwriters/core/validate-inbox-items
core: validate & sanitize inbox items
2026-04-07 20:41:25 +05:00
Abdullah Atta
d30ec4a19d core: make html validation more resilient 2026-04-07 20:34:39 +05:00
01zulfi
40f2c88ead Merge pull request #9682 from streetwriters/release/3.3.13
web: bump version to 3.3.13
2026-04-07 15:49:17 +05:00
01zulfi
7fe410ae89 web: bump version to 3.3.13
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-07 14:00:37 +05:00
Ammar Ahmed
4cbdd085d6 mobile: fix editor stops updating notes after first two notes edited (#9681) 2026-04-07 13:18:50 +05:00
01zulfi
24556dab83 web: show better error message on invalid recovery key (#9680)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-07 13:00:02 +05:00
Ammar Ahmed
206157b68b core: handle invalid html inputs 2026-04-07 12:02:23 +05:00
Abdullah Atta
dd8116eba8 Merge pull request #9677 from streetwriters/fix/table-cell-selection-mobile
Fix table cell selection on mobile
2026-04-06 23:55:18 +05:00
Abdullah Atta
ae82a8c0a0 editor: only allow resizing column from resize handle 2026-04-06 23:49:04 +05:00
Abdullah Atta
418e4517a5 Merge pull request #8468 from 01zulfi/web/note-spellcheck-toggle
web: add support for toggling spellcheck per note
2026-04-06 23:20:44 +05:00
Abdullah Atta
9bd0f0ceb1 editor: fix minor issues 2026-04-06 23:19:19 +05:00
Chloe Oletto
af4dc5e9ea web: fix all tabs closed on sync when any note is deleted from another device (#9252)
* web: All tabs closed when any note is deleted.

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

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

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

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

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

* core: decode entities at parse time

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-04-06 14:20:10 +05:00
01zulfi
d49d42b736 common: guard against undefined backStack/forwardStack in TabSessionHistory (#9674)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-06 12:42:00 +05:00
Ammar Ahmed
6efd1af0e9 Feat/image compression setting (#9542)
* mobile: migrate pickers to ts

* mobile: add support for image compression default setting in mobile

* mobile: update e2e tests
2026-04-06 11:24:09 +05:00
SahilAslam001
267fe4e453 mobile: remove restoring app state on app launch #6294 (#9586)
Signed-off-by: Sahil Aslam <sahil.aslam011@gmail.com>
2026-04-06 11:20:30 +05:00
01zulfi
8189b2ead8 web: remove spellcheck toggle from note context menu
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-06 09:58:04 +05:00
01zulfi
257ac8962c web: refresh sessions instead of raising event on spellcheck toggle
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-06 09:57:51 +05:00
01zulfi
faea6635c6 core: remove unnecessary note.spellcheck data migration
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-06 09:57:42 +05:00
Ammar Ahmed
537521d568 mobile: add spellcheck toggle in note properties 2026-04-06 09:57:42 +05:00
01zulfi
e3ed75461e web: spellcheck should be enabled by default for new notes && hide properties toggle if disabled on desktop
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-06 09:57:19 +05:00
01zulfi
f6b59eeb99 web: add support for toggling spellcheck per note
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-06 09:57:02 +05:00
Abdullah Atta
b58db58e13 Merge pull request #9506 from streetwriters/editor/internal-link-title-in-popup
editor: show internal link's title in hover popup
2026-04-06 09:37:31 +05:00
Ammar Ahmed
195dc18a09 Merge pull request #9649 from streetwriters/feat/copy-id-mobile
mobile: add copy id button in notebook and tag properties
2026-04-06 09:28:44 +05:00
Ammar Ahmed
187086460d mobile: undo debug change 2026-04-06 09:28:01 +05:00
Abdullah Atta
84655cec40 Merge pull request #9650 from streetwriters/fix-internal-links
Fix internal links
2026-04-06 09:25:29 +05:00
01zulfi
413ca61ef5 core: $headline$ format should get 60 chars from first p or h tag (#9658)
* core: note title headline format should get content from first p or h tag
* also decrease character limit to 60

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

* Apply suggestion from @thecodrr

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

---------

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
Signed-off-by: Abdullah Atta <thecodrr@protonmail.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-04-06 09:20:21 +05:00
01zulfi
49b1dd46d1 core: add support for server controlled monograph urls (#8995)
* core: support monograph.publishUrl field
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* core: support monograph publish-info endpoint
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* core: pubilsh-info -> metadata

* monograph: add support for slug

* mobile: use monograph metadata to get publishUrl (#9628)

Signed-off-by: sahil <sahil.aslam011@gmail.com>

* core: use v2 endpoint for publishing monographs

* monograph: separate route for v2 monographs

* core: fix monograph updates

---------

Signed-off-by: sahil <sahil.aslam011@gmail.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
Co-authored-by: SahilAslam001 <sahil.aslam011@gmail.com>
2026-04-06 09:15:15 +05:00
01zulfi
76e14d4434 web: handle multiple internal link types in internal link properties tabs
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-03 14:40:58 +05:00
Ammar Ahmed
f828ab0314 mobile: safely handle multiple internal link types 2026-04-03 13:01:34 +05:00
Ammar Ahmed
8d3d173762 core: db.notes.internalLinks() should only return note internal links 2026-04-03 13:00:51 +05:00
Ammar Ahmed
12644fce28 mobile: add copy id button in notebook and tag properties 2026-04-03 12:20:44 +05:00
Ammar Ahmed
358b499d32 editor: add support for pasting nn:// internal urls 2026-04-02 14:02:24 +05:00
Ammar Ahmed
17864d3325 mobile: add support in editor to fetch internal link data 2026-04-02 13:32:03 +05:00
01zulfi
7b6165c8f0 core: validate & sanitize inbox items
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-02 13:26:36 +05:00
Ammar Ahmed
02b96470bc Merge pull request #9631 from streetwriters/fix/file-size-check-fail
mobile: fix file size check shows error but continues uploading the file
2026-04-02 13:13:56 +05:00
Ammar Ahmed
c90a2f5179 Merge branch 'master' into beta 2026-04-02 13:07:58 +05:00
Ammar Ahmed
7234128f78 docs(help): add reminders help page (#9637)
* docs: add remindershelp page

* Apply suggestions from code review

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

---------

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

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

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

---------

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

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

* web: better error message on attachment preview failure

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-04-01 13:07:17 +05:00
Ammar Ahmed
87bddf0037 mobile: fix file size check shows error but continues uploading the file 2026-04-01 11:06:33 +05:00
SahilAslam001
e736e3d810 mobile: do not auto fill auto generated title when user clears title field in editor (#9623)
Signed-off-by: Sahil Aslam <sahil.aslam011@gmail.com>
2026-04-01 09:31:21 +05:00
01zulfi
47b2569b29 editor: add loading indicator while fetching internal link title
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-31 16:12:20 +05:00
Abdullah Atta
692975d4a0 Merge pull request #8928 from streetwriters/web/inbox-pgp-keys
[NN-5] web: use pgp keys for inbox
2026-03-31 13:32:30 +05:00
Abdullah Atta
6a843a6457 core: fix getInboxKeys 2026-03-31 13:31:37 +05:00
Abdullah Atta
a139bdad1e intl: update strings 2026-03-31 13:31:37 +05:00
Ammar Ahmed
b5dc298d7d mobile: add inbox api settings 2026-03-31 13:31:37 +05:00
01zulfi
561477fb1a web: use pgp keys for inbox
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-31 13:30:48 +05:00
01zulfi
092f15406c editor: fix table column toolbar not showing in correct position (#9583)
* editor: fix table column toolbar not showing in correct position
* also scroll to the newly created column if it is not visible

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

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

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

---------

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

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

* web: fix plans page being cut-off from top
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-31 09:41:28 +05:00
01zulfi
f9738168e5 editor: fix callout first heading being collapsed/expanded if clicked towards right of it (#9547)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-30 20:15:03 +05:00
01zulfi
9789edad4c editor: show internal link's note title in hover popup
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-30 17:12:51 +05:00
Abdullah Atta
b46e02753a Merge pull request #9378 from streetwriters/desktop/handle-nn-protocol
Add handler for nn:// urls
2026-03-30 12:07:42 +05:00
Ammar Ahmed
5f74fcad0c Merge branch 'master' into beta 2026-03-30 09:44:11 +05:00
Ammar Ahmed
e873b88dbd Merge pull request #9618 from streetwriters/mobile-release/3.3.18
mobile: release 3.3.18
2026-03-30 09:43:53 +05:00
Abdullah Atta
214f5b8a98 ci: fix desktop pr preview workflow (#9612)
* ci: fix desktop pr preview workflow

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

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

* ci: do not sign windows staging build

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

* ci: fix `Unknown target: x64`

---------

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

* ci: workflow didn't run

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

* ci: remove benchmarks workflow

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

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

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

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

---------

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-03-28 00:21:02 +05:00
Ammar Ahmed
fcba5f9c91 mobile: fix editor stays open when opening notebook/tag/color link. 2026-03-27 11:41:47 +05:00
Ammar Ahmed
5909025976 mobile: add support for internal links 2026-03-27 11:38:51 +05:00
01zulfi
273bbea938 web: don't collapse nav bar if a menu is still open (#9588)
* web: don't uncollapse nav bar if a menu is still open
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: handle native menu case

---------

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

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

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

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-20 12:06:18 +05:00
sahil
32c825463f mobile: add close button in editor all tab bottom sheet #6059
Signed-off-by: Sahil Aslam <sahil.aslam011@gmail.com>
2026-03-20 11:34:01 +05:00
Ammar Ahmed
404168fb86 mobile: fix linked note not opening from attachment properties 2026-03-19 13:38:30 +05:00
01zulfi
d5fa9fd02b editor: remove vertical offset on link hover popup (#9487)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-19 09:47:51 +05:00
01zulfi
6cc66d91be docs: clarify internal note links limitation in Evernote importer (#9509)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-03-19 09:42:59 +05:00
Abdullah Atta
1172a8c67b web: fix events missed from desktop side on startup 2026-03-19 09:32:40 +05:00
Abdullah Atta
0191e5be00 desktop: wait for bridge to be ready before sending events 2026-03-19 09:32:07 +05:00
Abdullah Atta
7f367389bb web: support copying notebook/tag/color internal link 2026-03-19 09:24:39 +05:00
Abdullah Atta
e94c757d5b desktop: add handler for nn:// urls 2026-03-18 11:39:26 +05:00
01zulfi
20a3b3b95e core: fix diacritics not getting ignored in search (#9479)
* core: fix diacritics search in db.lookup.notesWithHighlighting
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

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

* core: remove redundant `textContainsTokens` check

* core: fix wrong edge case handling

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

---------

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

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

* editor: render webclips via bloburl with strict sandboxing

---------

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

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

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

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

* Add help documentation for Archive feature

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

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

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

---------

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

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

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

* desktop: fix sql db closure on app close

---------

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

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

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

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

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

* editor: ignore callouts from toc

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: thecodrr <7473959+thecodrr@users.noreply.github.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-03-02 09:34:53 +05:00
01zulfi
9fc8e6b1e5 web: reduce note title size & improve padding (#9420)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-02-27 22:11:59 +05:00
Ammar Ahmed
6718650619 mobile: update libraries
react-native-background-fetch
notifee-react-native
2026-02-27 12:03:02 +05:00
Ammar Ahmed
a622949f23 mobile: fix react-native-tooltips crash 2026-02-27 12:02:34 +05:00
Ammar Ahmed
5a2df5b2c1 mobile: fix crash when switching theme animation 2026-02-27 12:01:18 +05:00
Ammar Ahmed
ae751cdd65 mobile: fix readonly title editable 2026-02-27 09:24:00 +05:00
Ammar Ahmed
0d00bb2866 mobile: update @ammarahmed/react-native-upload dep 2026-02-27 08:54:35 +05:00
Ammar Ahmed
4299965064 mobile: update README.md 2026-02-27 08:47:23 +05:00
copilot-swe-agent[bot]
6f3fc8db3d Use Inter font instead of OpenSans in monograph OG image generation
Co-authored-by: thecodrr <7473959+thecodrr@users.noreply.github.com>
2026-02-26 18:26:26 +00:00
copilot-swe-agent[bot]
db731856f8 Fix missing OpenSans fonts in monograph Docker image
Co-authored-by: thecodrr <7473959+thecodrr@users.noreply.github.com>
2026-02-26 17:21:36 +00:00
copilot-swe-agent[bot]
217f1dd6d4 Initial plan 2026-02-26 17:15:33 +00:00
Abdullah Atta
cf87929476 web: bump version to v3.3.9-beta.2 2026-02-26 12:55:37 +05:00
Ammar Ahmed
d343a0720e mobile: update README.md 2026-02-26 12:46:53 +05:00
Ammar Ahmed
51bd58edb2 mobile: release 3.3.15-beta.1 2026-02-26 12:37:22 +05:00
Ammar Ahmed
f778d83316 editor: prevent audio element request attachment data multiple times 2026-02-26 10:53:32 +05:00
Ammar Ahmed
b169206c68 mobile: fix audio playback in editor 2026-02-26 10:34:28 +05:00
Ammar Ahmed
4cea69e71d core: remove group from queueDownloads when in progress 2026-02-26 10:34:04 +05:00
Ammar Ahmed
04d422b411 core: fix audio attachments not linked to note when attached 2026-02-26 10:32:17 +05:00
Ammar Ahmed
49dec52aa0 mobile: fix multiple unlock with biometrics buttons rendering in vault dialog 2026-02-26 09:44:06 +05:00
Ammar Ahmed
b83a62814d mobile: use home group options when configuring note preview widget 2026-02-26 09:13:34 +05:00
Ammar Ahmed
ce5449f25a intl: fix add tag description 2026-02-26 08:31:02 +05:00
Abdullah Atta
aa769a8921 Merge pull request #9301 from streetwriters/core/orphaned-attachment-cleanup
core: remove orphaned attachments in trash cleanup method
2026-02-25 12:53:17 +05:00
01zulfi
a8d4e6c754 web: fix vault not marked as created when vault is created from locking note (#9394)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-02-25 11:36:32 +05:00
Abdullah Atta
f2402cf086 web: remove orphaned attachments on app launch 2026-02-25 11:24:31 +05:00
Ammar Ahmed
07a481f002 mobile: remove orphaned attachments on app launch 2026-02-24 12:55:31 +05:00
Ammar Ahmed
f41638db6d mobile: attachment items not rendering on switching list 2026-02-24 12:48:34 +05:00
Ammar Ahmed
82baf41d8e mobile: fix bulk delete call 2026-02-24 12:48:10 +05:00
Abdullah Atta
e60370d4dc editor: improve hr styling (#9377) 2026-02-24 11:08:45 +05:00
01zulfi
3e412323ee web: handle crash when drag-n-drop file in flatpak build (#9379)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-02-24 11:08:24 +05:00
Abdullah Atta
d635c1a056 Merge pull request #9364 from streetwriters/editor/audio-playback
editor: support audio attachment playback
2026-02-24 11:06:50 +05:00
01zulfi
f02db2a024 editor: fix checklist & blockquote indent so text starts at same pos as other lists (#9358)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-02-24 11:04:38 +05:00
Abdullah Atta
8c74324ff8 editor: handle attachments based on mime type 2026-02-24 10:29:58 +05:00
01zulfi
f2230eb39b editor: disallow inserting empty embed urls/code (#9392)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-02-24 09:50:38 +05:00
Abdullah Atta
ab900ae5da editor: various fixes and improvements related to audio widget 2026-02-24 09:36:47 +05:00
Ammar Ahmed
1cf28c56c2 mobile: add bulk delete support in file storage 2026-02-24 09:17:29 +05:00
Ammar Ahmed
08cd33a0cf Merge pull request #9371 from streetwriters/fix-sort-tags-by-note
mobile: sort assigned tags on top when viewing note tags
2026-02-24 08:28:10 +05:00
Ammar Ahmed
7b0d2a239d Merge pull request #9367 from streetwriters/fix/vault-biometrics-ux
Fix vault with biometrics enabled does not lock the note
2026-02-24 08:27:54 +05:00
Abdullah Atta
5d922d934c Merge branch 'master' into beta 2026-02-21 10:36:37 +05:00
Abdullah Atta
5bab9ea914 Merge pull request #9385 from streetwriters/fix/url-crash-in-editor
Fix web/desktop app crashes
2026-02-21 10:34:29 +05:00
Abdullah Atta
da06d7e636 misc: add stack trace deobfuscation script 2026-02-21 10:30:56 +05:00
Abdullah Atta
aece140473 web: fix crash on rewriting error message 2026-02-21 10:30:39 +05:00
Abdullah Atta
656f87e3c6 editor: fix crash if embed url is invalid or empty 2026-02-21 10:30:18 +05:00
Ammar Ahmed
36a95616d9 mobile: cleanup imports 2026-02-20 08:57:49 +05:00
Ammar Ahmed
60bdc4715d mobile: sort assigned tags on top when viewing note tags 2026-02-20 08:50:08 +05:00
Ammar Ahmed
7da168ab52 Merge pull request #9307 from streetwriters/fix-offline-mode-cache-attachments
mobile: fix switching app from background to foreground starts a new attachment cache download group
2026-02-20 08:12:57 +05:00
Ammar Ahmed
b20ad48b31 mobile: release 3.3.15-beta.0 2026-02-20 08:04:53 +05:00
Ammar Ahmed
8b17495f0e mobile: fix vault with biometrics enabled does not lock note 2026-02-19 13:16:09 +05:00
Abdullah Atta
e113e1b59c web: bump version to 3.3.9-beta.1 2026-02-19 11:38:30 +05:00
Abdullah Atta
6bd4924c0b web: add logging about persistence type 2026-02-19 11:37:59 +05:00
Abdullah Atta
a2939aa183 web: use in memory db only for account recovery routes 2026-02-19 11:34:17 +05:00
Abdullah Atta
effa726b65 web: bump version to 3.3.9-beta.0 2026-02-19 10:43:37 +05:00
Abdullah Atta
20d9bf1f23 core: ensure user is fetched after sign up 2026-02-19 10:42:55 +05:00
Abdullah Atta
09e5d014df web: fix page state persistence on account recovery pages 2026-02-19 10:42:55 +05:00
01zulfi
add1b8a68a editor: support audio attachment playback
Co-authored-by: Hamish <133548095+Hamster45105@users.noreply.github.com>
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-02-18 11:24:45 +05:00
Ammar Ahmed
fe143f66b7 core: remove log 2026-02-16 15:04:05 +05:00
Ammar Ahmed
75c8ca29ef web: publish vaultLocked event from db eventManager 2026-02-16 14:42:43 +05:00
Ammar Ahmed
c52779df23 Merge branch 'master' into beta 2026-02-16 14:40:45 +05:00
Ammar Ahmed
b7b77646e6 Merge pull request #9357 from streetwriters/mobile/release-3.3.14
mobile: release 3.3.14
2026-02-16 14:36:42 +05:00
01zulfi
1157c6b7cd Merge pull request #9351 from streetwriters/web/server-url-from-environment
web: prioritize environment variables for server urls configuration
2026-02-16 14:28:08 +05:00
Abdullah Atta
ab824d7f66 web: fix server urls 2026-02-16 14:27:50 +05:00
Abdullah Atta
702e28a6e6 Merge pull request #9323 from streetwriters/fix/password-changing
Fix account password changing
2026-02-16 14:15:39 +05:00
Ammar Ahmed
0e0ab58959 mobile: release 3.3.14 2026-02-16 13:39:41 +05:00
Ammar Ahmed
e5cdc9ea3d Merge pull request #9355 from streetwriters/fix/use-db-ev-manager
core: use db event manager
2026-02-16 13:34:31 +05:00
Ammar Ahmed
d3c9744e65 core: use db event manager 2026-02-16 13:11:45 +05:00
Ammar Ahmed
6b02144b30 Merge pull request #9354 from streetwriters/fix-loading-note-bug
mobile: loading note gets stuck
2026-02-16 13:07:27 +05:00
Abdullah Atta
91cbc5ddf0 core: simplify user signup 2026-02-16 13:06:58 +05:00
Ammar Ahmed
93aa1ca5aa Merge pull request #9352 from streetwriters/fix-attachment-upload-error
mobile: fix attachment upload error Failed to delete file
2026-02-16 12:59:15 +05:00
Ammar Ahmed
6ae13ebb2f mobile: fix attachment upload error Failed to delete file 2026-02-16 11:43:55 +05:00
Ammar Ahmed
73cdbf64c7 mobile: fix note loading is slow in editor after login/signup 2026-02-13 14:31:13 +05:00
01zulfi
0aeea965c0 core: add support for bulk delete files
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-02-13 13:01:56 +05:00
Abdullah Atta
a63b15637f core: fix types 2026-02-13 11:33:09 +05:00
Ammar Ahmed
5153b43bc6 mobile: enable password change and forgot password 2026-02-13 11:27:58 +05:00
Ammar Ahmed
9ea7d09722 core: fix imports 2026-02-13 09:45:08 +05:00
01zulfi
b7b474b245 core: fix archived notes not showing in attachment's linked notes (#9001)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-02-12 14:19:59 +05:00
ash
e2184a8bdf web: fix text not selectable from entire height of editor (#8972)
Signed-off-by: Ash Holland <x-hv2j5m@keysmash.solutions>
2026-02-12 14:05:49 +05:00
01zulfi
9ea84ca9ae editor: reduce horizontal rule height for large screens (#9040)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-02-12 14:02:01 +05:00
01zulfi
50136e3689 editor: use last set code language as default (#7086)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-02-12 13:49:52 +05:00
01zulfi
e87f5e5f89 editor: escape html in twitter embed src (#9326)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-02-10 12:33:56 +05:00
01zulfi
d44792d132 web: align check icon inside note's color in properties panel (#9308)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-02-09 12:33:28 +05:00
01zulfi
50b01e0b1b web: fix attachment not marked as uploaded when app locked & unlocked (#9306)
When RouteWrapper was unmounted, it would unsub all events including the ones subbed during db initialization (db.init()). Since db isn't initialized again when app is locked & unlocked, those events weren't subbed again. One of these event subscription (fileUploaded event) is responsible for marking an attachment as uploaded. There is no need to unsubscribe all events on RouteWrapper unmount.

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-02-09 12:32:29 +05:00
Abdullah Atta
122c3a96c7 web: fix account recovery 2026-02-09 12:27:36 +05:00
Abdullah Atta
4d407bd646 web: enable account recovery 2026-02-09 11:10:41 +05:00
Abdullah Atta
16b6b37b94 web: enable password changing 2026-02-09 11:08:00 +05:00
Abdullah Atta
8c766ef4f0 core: add initial DEK support 2026-02-09 11:07:53 +05:00
Ammar Ahmed
b7d2865435 mobile: fix switching app from background to foreground starts
a new attachment cache download group
2026-02-05 13:07:27 +05:00
Ammar Ahmed
8d9df5030c mobile: loading note gets stuck 2026-02-05 11:41:54 +05:00
01zulfi
4a158b2547 core: remove orphaned attachments in trash cleanup method
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-02-04 13:13:56 +05:00
Ammar Ahmed
af014cf8c1 Merge pull request #9297 from streetwriters/fix-toast-context-color-picker
mobile: fix toast context in color picker
2026-02-03 11:48:33 +05:00
Ammar Ahmed
1ddd7b1e6e mobile: fix toast context in color picker 2026-02-03 11:46:32 +05:00
Ammar Ahmed
9ef0179566 Merge pull request #9296 from streetwriters/fix-notebook-header-error
mobile: fix crash when opening notebook (happens randomly)
2026-02-03 11:11:51 +05:00
Ammar Ahmed
38690d18d4 mobile: fix crash when opening notebook (happens randomly) 2026-02-03 11:10:59 +05:00
Arjun Vijay Prakash
e381e54bb7 web: fix toolbar item indentation when moving between groups (#8624)
* web: adjust item depth calculation during move operation

Signed-off-by: Arjun Vijay Prakash <arjunv.prakash12345@gmail.com>

* web: add tests for customize-toolbar.tsx

Signed-off-by: Arjun Vijay Prakash <arjunv.prakash12345@gmail.com>

* refactor(toolbar): export types and functions in customize-toolbar.tsx

Signed-off-by: Arjun Vijay Prakash <arjunv.prakash12345@gmail.com>

* refactor(tests): simplify and import types for customize-toolbar tests

Signed-off-by: Arjun Vijay Prakash <arjunv.prakash12345@gmail.com>

* refactor(toolbar): remove export from internal functions in customize-toolbar.tsx

Signed-off-by: Arjun Vijay Prakash <arjunv.prakash12345@gmail.com>

---------

Signed-off-by: Arjun Vijay Prakash <arjunv.prakash12345@gmail.com>
2026-02-03 10:21:46 +05:00
little Kitchen
7001f98353 fix: unable to change zoom level back to 1.0 (#9283)
Fixed two issues with number input settings:

1. Added onBlur handler to immediately commit values when user
   clicks away, bypassing the 500ms debounce delay. This ensures
   values are saved even if the user navigates away quickly.

2. Added key prop based on current value to force re-render
   when the stored value changes, ensuring the input always
   reflects the actual persisted value.

Fixes #4035
2026-02-02 13:05:24 +05:00
Abdullah Atta
a836642d74 Merge pull request #7901 from 01zulfi/web/delete-data-for-not-logged-in-user
web: add delete data option for not logged in user
2026-02-02 12:31:01 +05:00
Abdullah Atta
a3d3add48f Merge pull request #7303 from 01zulfi/web/reminder-dialog-improvements
web: reminder dialogs improvements
2026-02-02 12:30:40 +05:00
01zulfi
994060cce2 editor: allow attaching image as file (#8752)
* editor: allow attaching image as file
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: fix image previews for regular attachments

* mobile: allow images to be added as attachment without preview

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

---------

Co-authored-by: Ammar Ahmed <ammarahmed6506@gmail.com>
2026-02-02 10:26:03 +05:00
Abdullah Atta
75103dc706 Merge pull request #9237 from streetwriters/web/dont-render-properties-on-locked-note
web: don't render properties panel for locked sessions
2026-02-02 09:56:20 +05:00
Abdullah Atta
cf63c2fdc5 Merge branch 'master' into beta 2026-01-28 12:44:31 +05:00
Abdullah Atta
c07f5393f6 Merge pull request #9266 from streetwriters/release/v3.3.8
Release vNext
2026-01-28 12:42:38 +05:00
Abdullah Atta
406c63721d web: bump version to 3.3.8 2026-01-27 17:24:36 +05:00
Abdullah Atta
97f3e02f59 core: remove .only from note history tests 2026-01-27 12:25:39 +05:00
Abdullah Atta
5c6564f00b core: fix monograph sync tests 2026-01-27 12:20:54 +05:00
Ammar Ahmed
605e75efb3 mobile: fix and update tests 2026-01-27 12:02:20 +05:00
Ammar Ahmed
496f9ba340 ci: update build tools version 2026-01-27 10:59:49 +05:00
Ammar Ahmed
f98283f2dd mobile: update changelog 2026-01-27 10:08:17 +05:00
Ammar Ahmed
2d32e85a64 mobile: refactor 2026-01-27 09:59:43 +05:00
Ammar Ahmed
1a56a22a33 mobile: fix feature not locked 2026-01-26 13:40:51 +05:00
Ammar Ahmed
c85204d0c1 mobile: bump version to 3.3.13 2026-01-26 13:32:28 +05:00
Abdullah Atta
1d8a958eaf editor: update test snapshots 2026-01-26 13:07:35 +05:00
Abdullah Atta
b52f70d57d core: fix reminders tests 2026-01-26 12:49:50 +05:00
Chloe Oletto
12687dae7b core: Fix reminder sorting. (#9242)
Signed off by: Chloe Oletto <NeedsChloesure@riseup.net>
2026-01-22 09:26:27 +05:00
Chloe Oletto
c53c3167d8 web: Fix reminder dialog subtext formatting. (#9241)
Signed-off by: Chloe Oletto <NeedsChloesure@riseup.net>
2026-01-22 09:25:20 +05:00
Ammar Ahmed
f15b4cf634 mobile: show note references in reminder page 2026-01-20 10:17:57 +05:00
01zulfi
6430611e3e web: reminder dialogs improvements
* add note references (if any) in reminder edit dialog
* wrap snooze option buttons in reminder preview dialog

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-01-20 09:29:52 +05:00
01zulfi
afe57e41ce web: add confirmation dialog for delete data option
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-01-19 16:18:29 +05:00
Ammar Ahmed
86f3f90143 mobile: delete all data option in settings 2026-01-19 14:24:21 +05:00
01zulfi
d3ea51cc01 web: add delete data option for not logged in user
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-01-19 12:58:48 +05:00
01zulfi
60df472d53 editor: fix search scroll not working on switching editors or opening new editor (#8061)
* editor: fix search scroll not working on switching editors or opening new editor
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

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

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

* web: minor ui adjustment for pdf preview dialog

* web: fix pdf text selection not visible

---------

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

* core: add tests

* mobile: support setting not expiry date

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

* core: add tests for expiring notes

* core: fix tests

* mobile: delete expiring notes at startup

* core: create index on expiry date

* core: minor refactor

* core: remove `.only` sync test

* web: refactors

* mobile: set limit on expiring notes

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

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

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

* web: delete expired notes on startup

* web: minor refactors

---------

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

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

* editor: use corsHost for youtube embeds

* editor: make youtube embed detection more robust

* web: remove referrer meta tag

---------

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

* mobile: set custom title for monograph

* web: minor ui changes

---------

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

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

* web: directly save content instead of using events

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2025-12-30 13:27:10 +05:00
Abdullah Atta
9659a12d70 Merge pull request #9117 from streetwriters/fix/monograph-ui
Fix monograph ui
2025-12-30 10:06:31 +05:00
Abdullah Atta
6abc25f58a monograph: bump version to 1.2.7 2025-12-30 10:05:51 +05:00
Abdullah Atta
a164ac67ce monograph: fix monograph ui 2025-12-30 10:04:27 +05:00
01zulfi
255677d98e editor: improve headingUpdatePlugin performance
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-29 14:23:16 +05:00
Ammar Ahmed
2a92ea4efd mobile: update export options 2025-12-29 10:25:04 +05:00
Ammar Ahmed
608b4f3009 mobile: update ios provisioning 2025-12-29 10:25:04 +05:00
Ammar Ahmed
a69cb1bf9c mobile: release 3.3.12 2025-12-29 10:25:04 +05:00
Abdullah Atta
be40b3d160 monograph: fix build 2025-12-29 10:25:04 +05:00
Abdullah Atta
51147b17cd monograph: bump version to 1.2.6 2025-12-29 10:25:04 +05:00
Abdullah Atta
5d0c2ae0b9 monograph: add support for reading secrets from file 2025-12-29 10:25:04 +05:00
Ammar Ahmed
8435e69cb5 Merge pull request #9110 from streetwriters/mobile-release/3.3.12
Mobile release/3.3.12
2025-12-29 10:24:05 +05:00
Ammar Ahmed
1d6a33e3be mobile: update export options 2025-12-27 11:47:31 +05:00
Ammar Ahmed
3f4d78af7d mobile: update ios provisioning 2025-12-27 10:19:31 +05:00
Abdullah Atta
f94012d184 Merge pull request #9103 from streetwriters/fix/monograph-build
monograph: fix build
2025-12-27 00:40:06 +05:00
Abdullah Atta
ca73c0c2ce monograph: fix build 2025-12-27 00:39:13 +05:00
Abdullah Atta
742bf31038 Merge pull request #9102 from streetwriters/feat/monograph/secrets-support
Add support for reading secrets from file
2025-12-27 00:28:45 +05:00
Abdullah Atta
1369b64cb1 monograph: bump version to 1.2.6 2025-12-27 00:27:15 +05:00
Abdullah Atta
bc3488512f monograph: add support for reading secrets from file 2025-12-27 00:26:51 +05:00
Ammar Ahmed
51f87911e1 mobile: release 3.3.12 2025-12-26 14:37:49 +05:00
Ammar Ahmed
4d4af5c278 Merge branch 'master' into beta 2025-12-26 11:24:47 +05:00
Ammar Ahmed
a6fcff4d86 Merge pull request #9099 from streetwriters/fix-file-errors
Fix bugs in file picker, pdf preview, backup restoration and vault unlock with biometrics
2025-12-26 11:23:39 +05:00
Ammar Ahmed
50b7f9eb68 mobile: refactor 2025-12-26 11:11:57 +05:00
Ammar Ahmed
a81c534258 mobile: fix ios crash when using biometrics 2025-12-25 22:03:20 +05:00
Ammar Ahmed
67cd2e6504 mobile: fix keyboard opens on app launch if locked note is opened in current tab 2025-12-25 15:25:49 +05:00
Ammar Ahmed
ed5c84b069 mobile: cleanup downloaded cache files on startup 2025-12-25 13:52:52 +05:00
Ammar Ahmed
f6c442ba88 mobile: fix pdf previews not working on ios 2025-12-25 13:51:13 +05:00
Ammar Ahmed
19e59b778d mobile: fix picking files on ios and android 2025-12-25 13:47:35 +05:00
Ammar Ahmed
0643a74ade Merge pull request #9084 from streetwriters/fix-notification-notes
mobile: fix loading note in editor from notifications
2025-12-25 11:41:39 +05:00
Ammar Ahmed
1babbb567c Merge pull request #9086 from streetwriters/fix-sidebar-gesture-close
mobile: fix sidebar close gesture on ios
2025-12-25 11:41:25 +05:00
Ammar Ahmed
11899501f2 Merge pull request #9087 from streetwriters/fix-color-picker-wheel-image
mobile: fix color picker wheel images not loaded
2025-12-25 11:41:02 +05:00
Ammar Ahmed
9982d7b7b4 Merge pull request #9088 from streetwriters/feat-home-shortcuts
Feat Android home launcher shortcuts
2025-12-25 11:40:27 +05:00
Ammar Ahmed
22497199c5 mobile: catch errors 2025-12-25 11:40:05 +05:00
Ammar Ahmed
873b180dab mobile: enable feature for pro users 2025-12-25 11:37:27 +05:00
Ammar Ahmed
ca8e505cd0 mobile: add support for color shortcuts on homescreen 2025-12-24 14:17:52 +05:00
Ammar Ahmed
465e1af838 mobile: keep shortcuts synced with the app 2025-12-24 13:11:08 +05:00
Ammar Ahmed
2f66ee04cc mobile: fix notification notes created on every app launch 2025-12-24 10:51:27 +05:00
Ammar Ahmed
a829594fad mobile: fix color picker wheel images not loaded 2025-12-23 13:57:57 +05:00
Ammar Ahmed
1427518e4b mobile: fix sidebar close gesture on ios 2025-12-23 13:43:15 +05:00
Ammar Ahmed
ecf75a07e4 mobile: fix loading note in editor from notifications
Fix a race condition where the loading note gets replaced by editor's stored state on app launch
2025-12-23 12:48:29 +05:00
01zulfi
41094c80d5 editor: add move line up/down keybinding (#7078)
* editor: add move line up/down keybinding
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* docs: update keyboard shortcuts

* editor: add missing changes

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

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

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

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2025-12-23 12:39:30 +05:00
Abdullah Atta
09b12c6d7e Merge pull request #9083 from streetwriters/release/3.3.7
Release v3.3.7
2025-12-23 12:33:43 +05:00
Abdullah Atta
c560685285 web: bump version to 3.3.7 2025-12-23 12:15:04 +05:00
Abdullah Atta
48d2099308 Merge pull request #9082 from streetwriters/fix/checklist-txt-export 2025-12-23 12:08:21 +05:00
Abdullah Atta
ca3604129d Merge pull request #9082 from streetwriters/fix/checklist-txt-export
Fix check list text export
2025-12-23 12:05:21 +05:00
Abdullah Atta
583a68cabe core: fix merger tests 2025-12-23 11:53:43 +05:00
Abdullah Atta
924eaf8486 core: fix check list text export 2025-12-23 11:33:14 +05:00
Abdullah Atta
9c1a02dbd5 mobile: use db's event manager instead of global 2025-12-23 11:04:06 +05:00
Abdullah Atta
637a19ee21 web: use db's event manager instead of global 2025-12-23 11:03:59 +05:00
Abdullah Atta
896420064f core: improve sync reliability 2025-12-23 11:03:36 +05:00
01zulfi
f6140e8396 editor: fix crash on task list toggle & drag (#9078)
* make sure column resizing's handleMouseDown correctly checks for handle element

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-23 10:55:28 +05:00
Ammar Ahmed
b59a3fb5de mobile: improved navigation for deep links 2025-12-23 10:54:46 +05:00
Ammar Ahmed
0a7f8631b6 mobile: add home shortcuts
Add ability to create shortcuts on launcher homescreen for notes, notebooks and tags.
2025-12-22 18:59:36 +05:00
Ammar Ahmed
de37cb0da1 Merge branch 'master' into beta 2025-12-19 10:48:12 +05:00
Ammar Ahmed
50d0827972 Merge pull request #9071 from streetwriters/mobile-release/3.3.11
mobile: release 3.3.11
2025-12-19 10:47:24 +05:00
Abdullah Atta
a0d4be2379 Merge pull request #9069 from streetwriters/common/command-palette-web-keybinding
common: add ctrl+shift+: web keybinding for command palette
2025-12-18 11:56:06 +05:00
Ammar Ahmed
e4eb5d1f0f mobile: release 3.3.11 2025-12-18 11:46:32 +05:00
Ammar Ahmed
9ae4d3df55 Merge pull request #9067 from streetwriters/fix-wrapped-background-mobile
Fixed wrapped background in screenshots on mobile
2025-12-18 11:35:31 +05:00
Abdullah Atta
95b6b45cc4 Merge pull request #9068 from streetwriters/editor/fix-scroll-mobile-table
editor: fix scroll for tables in mobile
2025-12-18 11:34:29 +05:00
01zulfi
9280d0ef7e common: add ctrl+shift+: web keybinding for command palette
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-18 10:34:39 +05:00
01zulfi
5171b2e5e2 editor: fix scroll for tables in mobile
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-18 10:20:10 +05:00
Ammar Ahmed
8455ff876a mobile: fixed wrapped background in screenshots 2025-12-18 09:48:54 +05:00
Ammar Ahmed
167ce97dd9 Merge branch 'mobile-release/3.3.10' into beta 2025-12-18 09:47:30 +05:00
Ammar Ahmed
0503af6819 Merge pull request #9066 from streetwriters/mobile-release/3.3.10
Mobile release 3.3.10
2025-12-18 09:46:43 +05:00
Abdullah Atta
a218ee1c02 Merge branch 'release/3.3.6' into beta 2025-12-18 00:50:54 +05:00
Abdullah Atta
fbaba8ebe8 Merge pull request #9064 from streetwriters/release/3.3.6
Release v3.3.6
2025-12-18 00:48:11 +05:00
Abdullah Atta
64c70f51c9 web: bump version to 3.3.6 2025-12-17 23:09:20 +05:00
Abdullah Atta
946da6e274 desktop: update sqlite3-fts5-html to 0.0.6
this should fix the `The specified module could not be found` error on Windows
2025-12-17 23:02:46 +05:00
Ammar Ahmed
f42489ecb5 mobile: fix package version is null 2025-12-17 19:06:48 +05:00
Ammar Ahmed
abd5fa00ed mobile: fix ci workflow no space left 2025-12-17 16:28:50 +05:00
Abdullah Atta
9450849b9d web: remove htmlparser2 2025-12-17 16:25:07 +05:00
Ammar Ahmed
d66bda7adc mobile: release 3.3.10 2025-12-17 15:38:37 +05:00
Abdullah Atta
461c4acfb7 ci: use macos-15-intel instead of macos-13 2025-12-17 14:49:46 +05:00
Abdullah Atta
ccb0840771 web: update @playwright/test to 1.57.0 2025-12-17 14:49:22 +05:00
Abdullah Atta
b5a706a6a9 editor: fix TouchEvent is not defined when resizing columns 2025-12-17 14:40:25 +05:00
Abdullah Atta
a45b54dd85 Merge pull request #9041 from streetwriters/fix-subscription-type 2025-12-17 14:22:31 +05:00
01zulfi
a4d29f3e7a editor: hide expand/collapse icon for empty headings (#9063)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-17 14:18:30 +05:00
Ammar Ahmed
2a8ec8245f mobile: fix e2e tests 2025-12-17 13:01:33 +05:00
Ammar Ahmed
d85f32e1f2 mobile: fix babel config for reanimated when using metro bundler 2025-12-17 09:02:27 +05:00
Ammar Ahmed
adc7bb1d9e mobile: fix months ordering bug in wrapped 2025-12-17 09:01:58 +05:00
Ammar Ahmed
2dafde0117 mobile: improve navigation performance 2025-12-13 12:39:38 +05:00
Ammar Ahmed
7582a01b3f mobile: remove polkadot background 2025-12-13 12:38:52 +05:00
Ammar Ahmed
9081012673 mobile: fix drawer open animation velocity 2025-12-13 10:31:41 +05:00
Ammar Ahmed
bf83dfbd28 mobile: do not cache wrapped data 2025-12-13 10:20:12 +05:00
01zulfi
903b96e629 web: show spaces in note,notebook,tag,reminder list item titles (#9027)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-12 19:19:36 +05:00
Ammar Ahmed
3e9007806e mobile: release 3.3.10-beta.6 2025-12-12 12:00:58 +05:00
Abdullah Atta
b6ec0091f0 web: update lockfile (again) 2025-12-12 11:57:54 +05:00
Ammar Ahmed
726b4f5c0d Merge pull request #9037 from streetwriters/fix-clipper-images
Fix loading images in clipped pages
2025-12-12 11:54:11 +05:00
Abdullah Atta
2fdd3a2a85 web: update lockfile 2025-12-12 11:45:36 +05:00
Ammar Ahmed
4fbb39efd0 Merge pull request #9041 from streetwriters/fix-subscription-type
Fix subscription type
2025-12-12 11:05:45 +05:00
Ammar Ahmed
88ecb58153 mobile: fix wrapped ui on tablets 2025-12-12 10:38:05 +05:00
Abdullah Atta
d084da2914 global: update package lockfiles 2025-12-12 10:33:02 +05:00
Abdullah Atta
716de6f9eb web: bump version to 3.3.6-beta.3 2025-12-12 10:06:04 +05:00
Abdullah Atta
dfc4cbe01f Merge pull request #9042 from streetwriters/editor/fix-callout-heading
editor: fix callout collapse/expand on clicking right after its heading
2025-12-12 09:57:56 +05:00
Abdullah Atta
3a5b4c48f5 Merge branch 'master' into beta 2025-12-12 09:47:50 +05:00
Abdullah Atta
08b0fd498e Merge pull request #9049 from streetwriters/chore/disable-rebase-when-sync
config: disable rebase when sync vscode setting
2025-12-12 09:47:05 +05:00
Abdullah Atta
3f365e7ee1 config: disable rebase when sync vscode setting 2025-12-12 09:46:31 +05:00
Abdullah Atta
ebb5c078c9 Merge pull request #9048 from streetwriters/wrapped-2025
Notesnook Wrapped
2025-12-12 09:37:52 +05:00
Ammar Ahmed
f536def5fa mobile: wrapped 2025 2025-12-12 09:37:20 +05:00
01zulfi
2918b8f04b web: wrapped 2025
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

web: refined wrapped ui

web: make wrapped work automatically for future years

web: fix emoji for colors

web: format word count
2025-12-12 09:37:11 +05:00
01zulfi
bb3b05aab0 web: allow closing file drag overlay by click or esc key (#9044)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-11 11:29:28 +05:00
01zulfi
2cdfe2c158 editor: add shortcut to open search and replace (#9043)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-11 11:28:15 +05:00
01zulfi
d1dfafe1da editor: fix callout collapse/expand on clicking right after its heading
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-10 14:36:50 +05:00
01zulfi
144febb3cc web: handle null productId
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-10 13:01:55 +05:00
Ammar Ahmed
22b2d50139 mobile: fix productId usage 2025-12-10 10:11:21 +05:00
01zulfi
121e521eb7 editor: hide horizontal rule if its under a collapsed heading (#9038)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-10 10:01:14 +05:00
Ammar Ahmed
7ada9cacda core: fix productId can be null 2025-12-10 09:40:56 +05:00
Ammar Ahmed
5585d69dcf mobile: fix clipper build path 2025-12-09 12:49:37 +05:00
Ammar Ahmed
f5d73a0c8f clipper: set full src url on image.src 2025-12-09 12:49:02 +05:00
Ammar Ahmed
4563739f6e Merge branch 'mobile-release-3.3.10-beta.4' into beta 2025-12-09 10:49:52 +05:00
Abdullah Atta
79ed081c99 desktop: update sqlite-better-trigram to 0.0.5
this should fix the `The specified module could not be found` error on Windows
2025-12-08 15:14:08 +05:00
Ammar Ahmed
dc1c9a9d37 mobile: fix PrivacyInfo.xcprivacy file 2025-12-06 21:15:51 +05:00
Ammar Ahmed
25c90537c8 mobile: fix version code for iOS 2025-12-06 19:54:57 +05:00
Ammar Ahmed
b7aadf4b17 ci: Update iOS Workflow 2025-12-06 18:51:50 +05:00
Ammar Ahmed
b9587e0fd5 mobile: bump version to v3.3.10-beta.5 2025-12-06 18:46:19 +05:00
Ammar Ahmed
38e529f5bf mobile: update libs 2025-12-06 18:42:06 +05:00
Ammar Ahmed
81e2d7738f mobile: enable fade animation for splash screen 2025-12-06 18:41:53 +05:00
Ammar Ahmed
380a681cc1 mobile: fix splash screen in dark mode 2025-12-06 13:28:18 +05:00
Ammar Ahmed
8c2a557618 mobile: fix switching device mode while in fullscreen mode 2025-12-06 12:36:55 +05:00
Ammar Ahmed
7b98d2ede2 mobile: Update lockfile 2025-12-06 11:21:13 +05:00
Ammar Ahmed
64c544a86e mobile: fix notifee lib version 2025-12-06 11:10:31 +05:00
Ammar Ahmed
a048125a14 mobile: release 3.3.10-beta.4 2025-12-06 11:10:31 +05:00
Ammar Ahmed
4c37e16e92 mobile: remove delay when opening settings 2025-12-06 11:10:31 +05:00
Ammar Ahmed
c448bc151b mobile: remove unused hook 2025-12-06 11:10:31 +05:00
Ammar Ahmed
efcbefe1c8 mobile: fix ui shift on focus search 2025-12-06 11:10:31 +05:00
Ammar Ahmed
339a15803c mobile: fix app exits editor on focus keyboard 2025-12-06 11:10:31 +05:00
Ammar Ahmed
582a06c05a mobile: fix biometric unlock not working on iPad and tablet devices 2025-12-06 11:09:39 +05:00
Ammar Ahmed
852f1ec31a Merge pull request #8949 from streetwriters/mobile-fix-ipad-biometric-unlock
mobile: Fix biometric unlock not working on iPad and tablet devices
2025-12-05 14:43:24 +05:00
Ammar Ahmed
2ca142566c Merge branch 'mobile-release-3.3.10-beta.3' into beta 2025-12-04 14:37:27 +05:00
Ammar Ahmed
9a13d112cb mobile: Update Podfile 2025-12-04 14:36:52 +05:00
Abdullah Atta
bc9cbbc1f6 crypto: lazily import sodium 2025-12-03 19:15:31 +05:00
Ammar Ahmed
129bf49204 mobile: release v3.3.10-beta.3 2025-12-03 14:26:57 +05:00
Ammar Ahmed
a29acb6f30 mobile: update notifee to support new architecture 2025-12-03 14:26:57 +05:00
Ammar Ahmed
c957094b1e mobile: fix orientation changing and fullscreen mode on tablets 2025-12-03 14:26:57 +05:00
Ammar Ahmed
8e5743f417 mobile: fix orientation listening events 2025-12-03 14:26:57 +05:00
Ammar Ahmed
4ff42f1ae4 mobile: fix crash on configuration changed on android 2025-12-03 14:26:57 +05:00
Ammar Ahmed
06c8f5367e mobile: fix picking themes from file 2025-12-03 14:26:57 +05:00
Abdullah Atta
f74d7cf37e web: bump version to 3.3.6-beta.2 2025-12-03 14:06:45 +05:00
Abdullah Atta
b971b4d07a Merge pull request #9002 from streetwriters/web/new-note-properties-pane 2025-12-03 14:05:27 +05:00
Abdullah Atta
09fd5d8642 Merge pull request #9002 from streetwriters/web/new-note-properties-pane
web: release space taken by properties pane for new note/tab
2025-12-03 14:01:34 +05:00
Abdullah Atta
97447e8d6c Merge pull request #9016 from NeedsChloesure/patch/erroneous-filesize-check
Fix file size calculation
2025-12-03 14:01:05 +05:00
Abdullah Atta
c388045c68 web: fix file uploading not working in safari 2025-12-03 13:59:55 +05:00
Abdullah Atta
db4d998705 editor: fix TouchEvent is not defined 2025-12-03 13:59:36 +05:00
Chloe Oletto
011cc992e8 mobile: Incorrect math for determining total number of chunks causes erroneous assertation that there is one more chunk in certain conditions.
Signed-off-by: Chloe Oletto <needschloesure@riseup.net>
2025-12-01 02:45:21 -08:00
Chloe Oletto
2c1d8b0eea web: Incorrect math for determining total number of chunks causes erroneous assertation that there is one more chunk in certain conditions.
Signed-off-by: Chloe Oletto <needschloesure@riseup.net>
2025-12-01 02:43:33 -08:00
Ammar Ahmed
65ae22e70e Merge branch 'mobile-release-3.3.10-beta.2' into beta 2025-12-01 10:05:46 +05:00
01zulfi
81da60333d editor: fade all nodes inside a checklist item except for a nested checklist (#9008)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-11-29 12:37:36 +05:00
01zulfi
aa572febd4 editor: add toggle text color keybinding (#9010)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-11-29 12:36:09 +05:00
Ammar Ahmed
6060cb457d mobile: fix failing build errors on android 2025-11-29 00:01:56 +05:00
Ammar Ahmed
6ac0107274 mobile: bump react-native-upload library version 2025-11-28 18:52:12 +05:00
Ammar Ahmed
95de4855c0 mobile: release 3.3.10-beta.2 2025-11-28 14:36:33 +05:00
Ammar Ahmed
bcfff70e78 mobile: improve file uploads in background 2025-11-28 14:34:18 +05:00
Ammar Ahmed
11e7d34961 mobile: update deps 2025-11-28 12:22:16 +05:00
Ammar Ahmed
e48e523b90 mobile: use KeyboardAvoidingView on android 2025-11-28 12:21:42 +05:00
Ammar Ahmed
4689b4290e mobile: patch rn to fix keyboard hiding on android 2025-11-28 12:21:13 +05:00
Ammar Ahmed
f9af8d116c mobile: fix crash on pick image or file 2025-11-28 11:09:31 +05:00
Ammar Ahmed
1f9339e5ab Merge branch 'release-v3.3.10-beta.1' into beta 2025-11-28 09:29:38 +05:00
Ammar Ahmed
4bc9b718cc mobile: delay layout on navigating to settings 2025-11-27 14:14:19 +05:00
Abdullah Atta
eae6b058fc web: bump version to 3.3.6-beta.1 2025-11-27 14:11:19 +05:00
Ammar Ahmed
a29bcf713f mobile: fix loading data in share extension 2025-11-27 14:09:42 +05:00
Ammar Ahmed
6b171f578d mobile: release 3.3.10-beta.1 2025-11-27 13:31:50 +05:00
Ammar Ahmed
3f45c2ebd4 mobile: update react-native-actions-sheet 2025-11-27 13:31:50 +05:00
Ammar Ahmed
883b1a63b2 mobile: improve animation for fluid panels 2025-11-27 13:31:50 +05:00
Ammar Ahmed
4c0efa8b08 mobile: make gradlew executable 2025-11-27 13:31:50 +05:00
01zulfi
ed2a04d590 editor: improve inline code styling (#8987)
* editor: improve inline code styling
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* editor: remove top&bottom padding from inline code
* it was cutting off a bit of text from top&bottom lines

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

* editor: update text color of inline code
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* editor: adjust font size and padding of inline code

---------

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2025-11-27 12:05:16 +05:00
01zulfi
52a3d1c373 editor: readonly table fixes (#8997)
* editor: readonly table fixes
* fix tables not hiding under collapsed headings in readonly view
* allow column resizing in readonly view
* fix scrollbar not showing in readonly view

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

* editor: remove table view

---------

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2025-11-27 12:04:23 +05:00
Vsevolod Volkov
c6d471cec9 docker: ability to build container with theme server 2025-11-26 19:24:04 +01:00
01zulfi
f7914c57c7 web: release space taken by properties pane for new note/tab
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-11-26 16:49:17 +05:00
Ammar Ahmed
6dd16f6791 mobile: fix biometric unlock not working on iPad and tablet devices 2025-11-12 12:19:57 +05:00
863 changed files with 57160 additions and 35551 deletions

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

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

View File

@@ -10,7 +10,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Enable KVM
run: |
@@ -55,13 +55,13 @@ jobs:
- name: Detox build
run: |
yarn build:android
ls apps/mobile/native/android/app/build/outputs/apk
ls apps/mobile/native/android/app/build/outputs/apk/release
ls apps/mobile/android/app/build/outputs/apk
ls apps/mobile/android/app/build/outputs/apk/release
- name: Get device name
id: device
run: |
AVD_NAME=$(node -p "require('./apps/mobile/native/.detoxrc.js').devices.emulator.device.avdName")
AVD_NAME=$(node -p "require('./apps/mobile/.detoxrc.js').devices.emulator.device.avdName")
echo "AVD_NAME=$AVD_NAME" >> $GITHUB_OUTPUT
- name: AVD cache

View File

@@ -0,0 +1,104 @@
name: Notesnook Android Preview Build
# UNTRUSTED stage. Runs on `pull_request`, so fork code is checked out and
# compiled with a read-only GITHUB_TOKEN and NO repository secrets. The release
# APK is built without any signing secret (the same as before). Firebase
# distribution and the PR comment happen in android.preview.publish.yml, which
# runs in the trusted `workflow_run` context and never executes fork code.
# Because no secrets are exposed here, the build runs automatically for every
# PR (including forks) with no authorization gate.
on:
pull_request:
types: [opened, reopened, synchronize]
branches: [master, beta]
paths:
- "apps/mobile/**"
- "packages/**"
- ".github/workflows/android.preview.build.yml"
- ".github/workflows/android.preview.publish.yml"
concurrency:
group: android-preview-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-22.04
env:
STAGING_BUILD: true
steps:
- name: Checkout PR code
uses: actions/checkout@v5
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Use specific Java version for the builds
uses: joschi/setup-jdk@v2
with:
java-version: "17"
architecture: "x64"
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
tool-cache: false
android: false
dotnet: true
haskell: true
large-packages: true
docker-images: true
swap-storage: true
- name: Install node modules
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=mobile
- name: Make Gradlew Executable
run: cd apps/mobile/android && chmod +x ./gradlew
- name: Get build number
id: build-number
run: echo "timestamp=$(($(date +%s) - 1774851180 ))" >> $GITHUB_OUTPUT
- name: Check for typescript errors
run: |
npm run tx mobile:build
cd apps/mobile
npx tsc --noEmit
- name: Build arm64-v8a apk
run: |
cd apps/mobile/android
./gradlew assembleRelease -PreactNativeArchitectures=arm64-v8a -PstagingReleaseBuild=true -PprBuildNumber=${{ steps.build-number.outputs.timestamp }}
- name: Stage build artifact
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/artifact"
cp apps/mobile/android/app/build/outputs/apk/release/app-arm64-v8a-release.apk \
"$RUNNER_TEMP/artifact/app-preview.apk"
{
echo "PR_NUMBER=${{ github.event.pull_request.number }}"
echo "HEAD_SHA=${{ github.event.pull_request.head.sha }}"
} > "$RUNNER_TEMP/artifact/pr-meta.env"
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: android-preview-build
path: ${{ runner.temp }}/artifact
if-no-files-found: error
retention-days: 1
- name: Upload sourcemaps
uses: actions/upload-artifact@v4
with:
name: sourcemaps
path: |
apps/mobile/android/app/build/generated/sourcemaps/**/*.map

View File

@@ -0,0 +1,72 @@
name: Notesnook Android Preview Publish
# TRUSTED stage. Runs via `workflow_run` after the build workflow finishes, so
# it has the base repo's secrets and a write-scoped token. It downloads the APK
# the build produced and distributes it + posts the PR comment. It never checks
# out or executes fork code.
on:
workflow_run:
workflows: ["Notesnook Android Preview Build"]
types: [completed]
jobs:
publish:
if: github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: android-preview-build
path: ${{ runner.temp }}/artifact
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Load PR metadata
run: cat "$RUNNER_TEMP/artifact/pr-meta.env" >> "$GITHUB_ENV"
- name: Publish to Firebase
id: firebase-output
uses: wzieba/Firebase-Distribution-Github-Action@v1
with:
appId: ${{ secrets.FIREBASE_APP_ID }}
serviceCredentialsFileContent: ${{ secrets.QA_SERVICE_ACCOUNT }}
groups: testers
file: ${{ runner.temp }}/artifact/app-preview.apk
releaseNotes: Preview for https://github.com/${{ github.repository }}/pull/${{ env.PR_NUMBER }}
- name: Post or update PR comment
uses: actions/github-script@v7
env:
preview_url: ${{ steps.firebase-output.outputs.TESTING_URI }}
with:
script: |
const marker = '<!-- android-preview-comment -->';
const prNumber = Number(process.env.PR_NUMBER);
if (!prNumber) return;
const previewUrl = process.env.preview_url || '';
const body = `${marker}\n**Android App Preview**\n\n${previewUrl || 'Preview URL unavailable — check workflow logs.'}\n\nCommit: ${process.env.HEAD_SHA}\n`;
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
});
const existing = comments.find(c => c.body && c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body,
});
}

View File

@@ -13,7 +13,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v5
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
@@ -113,7 +113,7 @@ jobs:
keyStorePassword: ${{ secrets.KEY_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: "33.0.0"
BUILD_TOOLS_VERSION: "36.0.0"
- name: Build apks for Github release
run: yarn release:android
@@ -128,7 +128,7 @@ jobs:
keyStorePassword: ${{ secrets.PUBLIC_KEY_PASSWORD }}
keyPassword: ${{ secrets.PUBLIC_KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: "33.0.0"
BUILD_TOOLS_VERSION: "36.0.0"
- name: Rename apk files
run: |

View File

@@ -13,7 +13,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v5
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
@@ -24,6 +24,22 @@ jobs:
java-version: "17"
architecture: "x64"
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
# this might remove tools that are actually needed,
# if set to "true" but frees about 6 GB
tool-cache: false
# all of these default to true, but feel free to set to
# "false" if necessary for your workflow
android: false
dotnet: true
haskell: true
large-packages: true
docker-images: true
swap-storage: true
- name: Install node modules
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
@@ -97,7 +113,7 @@ jobs:
keyStorePassword: ${{ secrets.KEY_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: "33.0.0"
BUILD_TOOLS_VERSION: "36.0.0"
- name: Build apks for Github release
run: yarn release:android
@@ -112,7 +128,7 @@ jobs:
keyStorePassword: ${{ secrets.PUBLIC_KEY_PASSWORD }}
keyPassword: ${{ secrets.PUBLIC_KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: "33.0.0"
BUILD_TOOLS_VERSION: "36.0.0"
- name: Rename apk files
run: |
@@ -124,7 +140,7 @@ jobs:
- name: Get app version
id: package-version
uses: martinbeentjes/npm-get-version-action@master
uses: saionaro/extract-package-version@master
with:
path: apps/mobile
@@ -143,8 +159,8 @@ jobs:
uses: softprops/action-gh-release@v1
with:
draft: true
tag_name: ${{ steps.package-version.outputs.current-version}}-android
name: Notesnook Android v${{ steps.package-version.outputs.current-version}}
tag_name: ${{ steps.package-version.outputs.version}}-android
name: Notesnook Android v${{ steps.package-version.outputs.version}}
repository: streetwriters/notesnook
token: ${{ secrets.GITHUB_TOKEN }}
files: |

View File

@@ -35,7 +35,7 @@ jobs:
needs: authorize
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
ref: ${{ github.event.pull_request.head.sha || github.ref }}

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

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

View File

@@ -48,7 +48,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
@@ -107,7 +107,7 @@ jobs:
target_commitish: ${{ github.ref }}
- name: Generate flatpak sources
if: inputs.publish-github && inputs.build-linux
if: inputs.build-linux
run: |
sudo apt-get update
sudo apt-get install -y --quiet flatpak flatpak-builder pipx
@@ -127,6 +127,13 @@ jobs:
files: ./generated-sources.json
prerelease: ${{ endsWith(steps.app_metadata.outputs.app_version, '-beta') }}
- name: Upload generated-sources.json as artifact
uses: actions/upload-artifact@v4
if: inputs.build-linux
with:
name: generated-sources
path: ./generated-sources.json
build-macos:
name: Build for macOS
needs: build
@@ -135,7 +142,7 @@ jobs:
steps:
- name: Check out Git repository
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
@@ -182,7 +189,9 @@ jobs:
- name: Install sqlite-better-trigram for all arch
run: |
npm i --cpu arm64 sqlite-better-trigram
npm i --cpu arm64 sqlite3-fts5-html
npm i --cpu x64 sqlite-better-trigram
npm i --cpu x64 sqlite3-fts5-html
working-directory: ./apps/desktop
- name: Install provisioning profile
@@ -237,7 +246,7 @@ jobs:
steps:
- name: Check out Git repository
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
@@ -264,6 +273,7 @@ jobs:
- name: Install sqlite-better-trigram for all arch
run: |
npm i --cpu x64 sqlite-better-trigram
npm i --cpu x64 sqlite3-fts5-html
working-directory: ./apps/desktop
- name: Build Electron wrapper
@@ -304,7 +314,7 @@ jobs:
steps:
- name: Check out Git repository
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
@@ -331,6 +341,7 @@ jobs:
- name: Install sqlite-better-trigram for all arch
run: |
npm i --cpu arm64 sqlite-better-trigram
npm i --cpu arm64 sqlite3-fts5-html
working-directory: ./apps/desktop
- name: Build Electron wrapper
@@ -363,11 +374,11 @@ jobs:
name: Build for Windows
needs: build
if: inputs.build-windows
runs-on: windows-latest
runs-on: windows-2022
steps:
- name: Check out Git repository
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup .NET Core SDK
uses: actions/setup-dotnet@v2

View File

@@ -18,12 +18,13 @@ on:
- ".github/workflows/desktop.tests.yml"
jobs:
build:
name: Build
runs-on: ubuntu-22.04
test-macos-x64:
name: Test macOS x64
runs-on: macos-15-intel
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Check out Git repository
uses: actions/checkout@v5
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
@@ -33,44 +34,10 @@ jobs:
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=web
- name: Generate desktop build
run: npm run tx @notesnook/web:build:desktop
- name: Archive build artifact
uses: actions/upload-artifact@v4
with:
name: build
path: apps/web/build/**/*
test-macos-x64:
name: Test macOS x64
needs: build
runs-on: macos-13
steps:
- name: Check out Git repository
uses: actions/checkout@v4
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Download build
uses: actions/download-artifact@v4
with:
name: build
path: ./apps/web/build
- name: Install packages
- name: Install sqlite-better-trigram for all arch
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=desktop
- name: Build Electron wrapper
run: npm run tx @notesnook/desktop:release
- name: Build app
run: |
yarn electron-builder --config=electron-builder.config.js --mac --dir --x64
npm i --cpu x64 sqlite-better-trigram
npm i --cpu x64 sqlite3-fts5-html
working-directory: ./apps/desktop
- name: Run tests x64
@@ -87,33 +54,24 @@ jobs:
test-macos:
name: Test macOS
needs: build
runs-on: macos-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Download build
uses: actions/download-artifact@v4
with:
name: build
path: ./apps/web/build
- name: Install packages
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=desktop
npm run bootstrap -- --scope=web
- name: Build Electron wrapper
run: npm run tx @notesnook/desktop:release
- name: Build app
- name: Install sqlite-better-trigram for all arch
run: |
yarn electron-builder --config=electron-builder.config.js --mac --dir --arm64
npm i --cpu arm64 sqlite-better-trigram
npm i --cpu arm64 sqlite3-fts5-html
working-directory: ./apps/desktop
- name: Run tests arm64
@@ -130,39 +88,24 @@ jobs:
test-linux:
name: Test for Linux
needs: build
runs-on: ubuntu-22.04
steps:
- name: Check out Git repository
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Download build
uses: actions/download-artifact@v4
with:
name: build
path: ./apps/web/build
- name: Install packages
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=desktop
npm run bootstrap -- --scope=web
- 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: Build Electron wrapper
run: npm run tx @notesnook/desktop:release
- name: Build app
run: |
yarn electron-builder --config=electron-builder.config.js --linux --dir --arm64 --x64
npm i --cpu x64 sqlite3-fts5-html
working-directory: ./apps/desktop
- name: Run tests
@@ -179,44 +122,26 @@ jobs:
test-windows:
name: Test for Windows
needs: build
runs-on: windows-latest
runs-on: windows-2022
steps:
- name: Check out Git repository
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Download build
uses: actions/download-artifact@v4
with:
name: build
path: ./apps/web/build
- name: Install packages
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=desktop
npm run bootstrap -- --scope=web
- name: Install sqlite-better-trigram for all arch
run: |
npm i --cpu arm64 sqlite-better-trigram
npm i --cpu x64 sqlite-better-trigram
npm i --cpu arm64 sqlite3-fts5-html
npm i --cpu x64 sqlite3-fts5-html
working-directory: ./apps/desktop
- name: Build Electron wrapper
run: node scripts/execute.mjs @notesnook/desktop:release
- name: Build app
run: |
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: npm run test
working-directory: ./apps/desktop

View File

@@ -28,7 +28,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- uses: actions-rs/toolchain@v1
with:
toolchain: stable

152
.github/workflows/ios.preview.build.yml vendored Normal file
View File

@@ -0,0 +1,152 @@
name: Notesnook iOS Preview Build
# UNTRUSTED stage. Runs on `pull_request`, so fork code is checked out and
# compiled with a read-only GITHUB_TOKEN and NO repository secrets. It only
# produces an *unsigned* archive. Signing, Firebase distribution and PR
# comments happen in ios.preview.publish.yml, which runs in the trusted
# `workflow_run` context and never executes fork code. Because no secrets are
# exposed here, the build runs automatically for every PR (including forks)
# with no authorization gate.
on:
pull_request:
types: [opened, reopened, synchronize]
branches: [master, beta]
paths:
- "apps/mobile/**"
- "packages/**"
- ".github/workflows/ios.preview.build.yml"
- ".github/workflows/ios.preview.publish.yml"
# A fork that spams pushes shouldn't queue up macOS builds.
concurrency:
group: ios-preview-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
build:
runs-on: macos-26
timeout-minutes: 60
steps:
- name: Checkout PR code
uses: actions/checkout@v5
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "26.1.1"
- name: Setup iOS Platform
run: |
# GitHub runs this as `/bin/bash -e`, so `set +e` is required -- without
# it the first failure aborts the step before any retry can happen.
# Both commands hit "Unable to connect to simulator" (exit 70) when
# CoreSimulatorService is wedged, so retry each and bounce it between.
set +e
DL_DIR="$RUNNER_TEMP/ios-platform"
retry() {
for i in 1 2 3; do
"$@" && return 0
echo "Attempt $i failed ($*); resetting CoreSimulator..."
sudo killall -9 com.apple.CoreSimulator.CoreSimulatorService simdiskimaged 2>/dev/null
sleep 20
xcrun simctl list runtimes >/dev/null 2>&1
done
echo "::error::Failed after 3 attempts: $*"
exit 1
}
xcrun simctl list runtimes >/dev/null 2>&1
retry xcodebuild -downloadPlatform iOS -exportPath "$DL_DIR"
# Find the dmg; its name embeds a build number that changes.
DMG="$(find "$DL_DIR" -name '*.dmg' | head -n1)"
[ -n "$DMG" ] || { echo "::error::No dmg in $DL_DIR"; exit 1; }
retry xcodebuild -importPlatform "$DMG"
- name: Install node modules
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=mobile
- name: Build packages
run: npm run tx @notesnook/mobile:build
- name: Cache Pods
uses: actions/cache@v4
id: pods-cache
with:
path: apps/mobile/ios/Pods
key: ${{ runner.os }}-pods-${{ hashFiles('apps/mobile/ios/Podfile.lock') }}
- name: Install Pods
run: |
cd apps/mobile/ios
pod install
- name: Check for typescript errors
run: |
npm run tx mobile:build
cd apps/mobile
npx tsc --noEmit
- name: Get marketing version
id: marketing-version
run: echo "version=$(grep "IOS_MARKETING_VERSION" apps/mobile/ios/build-configs/ios-build.staging.xcconfig | awk -F'=' '{print $2}' | xargs)" >> $GITHUB_OUTPUT
- name: Get build number
id: build-number
run: echo "timestamp=$(($(date +%s) - 1774851180 ))" >> $GITHUB_OUTPUT
- name: Make staging xcconfig active
run: |
cd apps/mobile/ios/build-configs
./use-ios-build-config.sh staging --marketing-version ${{steps.marketing-version.outputs.version}} --build-number ${{steps.build-number.outputs.timestamp}}
# Archive unsigned: this job has no certs/profiles (secrets), and the iOS
# device SDK forbids ad-hoc signing, so real signing happens in the trusted
# publish workflow (ios.preview.publish.yml). Do NOT pass
# CODE_SIGN_ENTITLEMENTS="" -- each target must keep its own entitlements so
# the archive records them; blanking them dropped the App Group and crashed
# MMKV on launch.
- name: Archive (unsigned)
run: |
set -euo pipefail
xcodebuild \
-workspace apps/mobile/ios/Notesnook.xcworkspace \
-scheme Notesnook \
-configuration Release \
-sdk iphoneos \
-destination 'generic/platform=iOS' \
-archivePath "$RUNNER_TEMP/Notesnook.xcarchive" \
CODE_SIGNING_ALLOWED=NO \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGN_IDENTITY="" \
archive
- name: Stage build artifact
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/artifact"
tar -czf "$RUNNER_TEMP/artifact/Notesnook.xcarchive.tar.gz" \
-C "$RUNNER_TEMP" Notesnook.xcarchive
# Carry the PR context forward; workflow_run cannot see it reliably for forks.
{
echo "PR_NUMBER=${{ github.event.pull_request.number }}"
echo "HEAD_SHA=${{ github.event.pull_request.head.sha }}"
} > "$RUNNER_TEMP/artifact/pr-meta.env"
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: ios-preview-build
path: ${{ runner.temp }}/artifact
if-no-files-found: error
retention-days: 1

View File

@@ -0,0 +1,222 @@
name: Notesnook iOS Preview Publish
# TRUSTED stage. Runs via `workflow_run` after the build workflow finishes, so
# it has the base repo's secrets and a write-scoped token. It checks out the
# BASE repository (never fork code), downloads the unsigned archive the build
# produced, then signs + distributes it and posts the PR comment. Signing is
# done directly with `codesign` on the prebuilt archive -- no app build phases
# run here, so fork code is never executed in this trusted context.
#
# Split into two jobs on purpose: signing needs macOS, but the Firebase
# distribution action is a Docker container action and only runs on Linux.
on:
workflow_run:
workflows: ["Notesnook iOS Preview Build"]
types: [completed]
jobs:
export:
# Only publish previews for builds that actually succeeded.
if: github.event.workflow_run.conclusion == 'success'
runs-on: macos-26
timeout-minutes: 30
outputs:
pr_number: ${{ steps.meta.outputs.PR_NUMBER }}
head_sha: ${{ steps.meta.outputs.HEAD_SHA }}
steps:
- name: Checkout base repo (trusted)
uses: actions/checkout@v5
# No `ref` -> checks out the default branch, i.e. trusted base code.
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "26.1.1"
- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: ios-preview-build
path: ${{ runner.temp }}/artifact
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Load PR metadata & extract archive
id: meta
run: |
set -euo pipefail
cat "$RUNNER_TEMP/artifact/pr-meta.env" >> "$GITHUB_OUTPUT"
tar -xzf "$RUNNER_TEMP/artifact/Notesnook.xcarchive.tar.gz" -C "$RUNNER_TEMP"
- name: Import signing certificate
uses: apple-actions/import-codesign-certs@v3
with:
p12-file-base64: ${{ secrets.APPLE_CERTIFICATE_P12 }}
p12-password: ${{ secrets.APPLE_CERTIFICATE_P12_PASSWORD }}
- name: Install provisioning profiles
env:
PROFILE_APP: ${{ secrets.APPLE_MOBILE_PROVISION_ADHOC_APP }}
PROFILE_SHARE: ${{ secrets.APPLE_MOBILE_PROVISION_ADHOC_SHARE }}
PROFILE_WIDGET: ${{ secrets.APPLE_MOBILE_PROVISION_ADHOC_WIDGET }}
run: |
set -euo pipefail
PROFILE_DIR="$HOME/Library/MobileDevice/Provisioning Profiles"
mkdir -p "$PROFILE_DIR"
i=0
for p in "$PROFILE_APP" "$PROFILE_SHARE" "$PROFILE_WIDGET"; do
echo "$p" | base64 --decode > "$PROFILE_DIR/preview-$i.mobileprovision"
i=$((i + 1))
done
# Sign the unsigned archive by hand. `xcodebuild -exportArchive` re-signs
# from the archive's `archived-expanded-entitlements.xcent`, which an
# unsigned archive does not contain, so it dropped the App Group and the
# app crashed on launch. Instead we sign each bundle directly, taking the
# entitlements from each provisioning profile (exactly what Apple
# provisioned -- App Group, keychain groups with the right team prefix).
# Runs on macOS but executes NO fork build phases, so it stays trusted.
- name: Sign IPA
run: |
set -euo pipefail
PROFILES="$HOME/Library/MobileDevice/Provisioning Profiles"
APP="$(find "$RUNNER_TEMP/Notesnook.xcarchive/Products/Applications" -maxdepth 1 -name '*.app' | head -n1)"
[ -n "$APP" ] || { echo "::error::No .app in archive"; exit 1; }
# Single Apple Distribution identity from the imported keychain.
IDENTITY="$(security find-identity -v -p codesigning | awk '/Apple Distribution/{print $2; exit}')"
[ -n "$IDENTITY" ] || { echo "::error::No Apple Distribution identity found"; exit 1; }
echo "Signing identity: $IDENTITY"
TMP="$RUNNER_TEMP/sign"; mkdir -p "$TMP"
# Decode a profile's bundle id (application-identifier minus team prefix).
profile_bundle_id() {
security cms -D -i "$1" -o "$TMP/_p.plist" 2>/dev/null
local appid; appid="$(plutil -extract Entitlements.application-identifier raw -o - "$TMP/_p.plist")"
echo "${appid#*.}"
}
# Find the profile matching a bundle id; echoes its path.
profile_for() {
local want="$1" p
for p in "$PROFILES"/*.mobileprovision; do
[ -e "$p" ] || continue
[ "$(profile_bundle_id "$p")" = "$want" ] && { echo "$p"; return 0; }
done
return 1
}
# Embed the matching profile and sign a bundle with the profile's entitlements.
sign_bundle() {
local bundle="$1" bid prof ent
bid="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$bundle/Info.plist")"
prof="$(profile_for "$bid")" || { echo "::error::No provisioning profile for $bid"; exit 1; }
ent="$TMP/ent-$bid.plist"
security cms -D -i "$prof" -o "$TMP/_p.plist"
plutil -extract Entitlements xml1 -o "$ent" "$TMP/_p.plist"
cp "$prof" "$bundle/embedded.mobileprovision"
echo "Signing $(basename "$bundle") ($bid)"
codesign --force --sign "$IDENTITY" --entitlements "$ent" "$bundle"
}
# Sign inside-out: nested code first, then extensions, then the app.
if [ -d "$APP/Frameworks" ]; then
find "$APP/Frameworks" -maxdepth 1 \( -name '*.framework' -o -name '*.dylib' \) -print0 \
| while IFS= read -r -d '' f; do codesign --force --sign "$IDENTITY" "$f"; done
fi
if [ -d "$APP/PlugIns" ]; then
for appex in "$APP/PlugIns"/*.appex; do
[ -e "$appex" ] || continue
if [ -d "$appex/Frameworks" ]; then
find "$appex/Frameworks" -maxdepth 1 \( -name '*.framework' -o -name '*.dylib' \) -print0 \
| while IFS= read -r -d '' f; do codesign --force --sign "$IDENTITY" "$f"; done
fi
sign_bundle "$appex"
done
fi
sign_bundle "$APP"
codesign --verify --deep --strict --verbose=2 "$APP"
# Package into an IPA.
rm -rf "$TMP/Payload"; mkdir -p "$TMP/Payload"
cp -R "$APP" "$TMP/Payload/"
( cd "$TMP" && zip -qry "$RUNNER_TEMP/Notesnook.ipa" Payload )
# Fail fast if the App Group didn't make it into the signed app -- its
# absence is the launch crash (MMKV nil group path). Never distribute
# a build without it.
echo "::group::Signed app entitlements"
codesign -d --entitlements :- "$APP" 2>/dev/null || true
echo "::endgroup::"
codesign -d --entitlements :- "$APP" 2>/dev/null | grep -q "group.org.streetwriters.notesnook" \
|| { echo "::error::Signed app is missing the App Group entitlement; it would crash on launch."; exit 1; }
echo "App Group entitlement present."
- name: Upload signed IPA
uses: actions/upload-artifact@v4
with:
name: ios-preview-ipa
path: ${{ runner.temp }}/Notesnook.ipa
if-no-files-found: error
retention-days: 1
# Must be Linux: wzieba/Firebase-Distribution-Github-Action is a Docker
# container action, which GitHub only supports on Linux runners.
distribute:
needs: export
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Download signed IPA
uses: actions/download-artifact@v4
with:
name: ios-preview-ipa
path: ${{ runner.temp }}
- name: Publish to Firebase
id: firebase-output
uses: wzieba/Firebase-Distribution-Github-Action@v1
with:
appId: ${{ secrets.FIREBASE_IOS_APP_ID }}
serviceCredentialsFileContent: ${{ secrets.QA_SERVICE_ACCOUNT }}
groups: testers
file: ${{ runner.temp }}/Notesnook.ipa
releaseNotes: Preview for https://github.com/${{ github.repository }}/pull/${{ needs.export.outputs.pr_number }}
- name: Post or update PR comment
uses: actions/github-script@v7
env:
preview_url: ${{ steps.firebase-output.outputs.TESTING_URI }}
PR_NUMBER: ${{ needs.export.outputs.pr_number }}
HEAD_SHA: ${{ needs.export.outputs.head_sha }}
with:
script: |
const marker = '<!-- ios-preview-comment -->';
const prNumber = Number(process.env.PR_NUMBER);
if (!prNumber) return;
const previewUrl = process.env.preview_url || '';
const body = `${marker}\n**iOS App Preview**\n\n${previewUrl || 'Preview URL unavailable — check workflow logs.'}\n\nCommit: ${process.env.HEAD_SHA}\n`;
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
});
const existing = comments.find(c => c.body && c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body,
});
}

View File

@@ -4,12 +4,12 @@ on: workflow_dispatch
jobs:
build:
runs-on: macos-14
runs-on: macos-26
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v5
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
@@ -17,7 +17,12 @@ jobs:
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "16.1"
xcode-version: "26.1.1"
- name: Setup iOS Platform
run: |
xcodebuild -downloadPlatform iOS -exportPath ~/Downloads
xcodebuild -importPlatform ~/Downloads/iphonesimulator_26.1_23B86.dmg
- name: Install node modules
run: |
@@ -30,7 +35,7 @@ jobs:
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2.11
with:
key: ${{ runner.os }}-ccache-${{ hashFiles(format('apps/mobile/native/ios/Podfile.lock')) }}
key: ${{ runner.os }}-ccache-${{ hashFiles(format('apps/mobile/ios/Podfile.lock')) }}
max-size: 1500M
restore-keys: |
${{ runner.os }}-ccache-
@@ -55,14 +60,13 @@ jobs:
uses: actions/cache@v3
id: pods-cache
with:
path: apps/mobile/native/ios/Pods
key: ${{ runner.os }}-pods-${{ hashFiles('apps/mobile/native/ios/Podfile.lock') }}
path: apps/mobile/ios/Pods
key: ${{ runner.os }}-pods-${{ hashFiles('apps/mobile/ios/Podfile.lock') }}
- name: Install Pods
run: |
cd apps/mobile/native/ios
bundle install
RCT_NEW_ARCH_ENABLED=0 bundle exec pod install
cd apps/mobile/ios
pod install
- name: Check for typescript errors
run: |
@@ -79,9 +83,9 @@ jobs:
bundle-identifier: org.streetwriters.notesnook
scheme: Notesnook
configuration: "Release"
export-options: apps/mobile/native/ios/ExportOptions.plist
project-path: apps/mobile/native/ios/Notesnook.xcodeproj
workspace-path: apps/mobile/native/ios/Notesnook.xcworkspace
export-options: apps/mobile/ios/ExportOptions.plist
project-path: apps/mobile/ios/Notesnook.xcodeproj
workspace-path: apps/mobile/ios/Notesnook.xcworkspace
update-targets: |
Notesnook
Make Note
@@ -112,10 +116,11 @@ jobs:
api-private-key: ${{ secrets.API_KEY }}
- name: Upload Notesnook.ipa to Github
continue-on-error: true
uses: actions/upload-artifact@v4
with:
name: Notesnook.zip
path: |
Notesnook.ipa
apps/mobile/native/ios/**/*.map
apps/mobile/ios/**/*.map
packages/editor-mobile/sourcemaps/*.map

View File

@@ -22,7 +22,7 @@ jobs:
id-token: write
steps:
- name: Check out the repo
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache

View File

@@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache

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

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

View File

@@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache

View File

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

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

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

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache

View File

@@ -37,7 +37,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
ref: ${{ github.event.pull_request.head.sha || github.ref }}
@@ -66,7 +66,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
ref: ${{ github.event.pull_request.head.sha || github.ref }}

2
.npmrc Normal file
View File

@@ -0,0 +1,2 @@
min-release-age=7 # days
# ignore-scripts=true

View File

@@ -11,5 +11,5 @@
"cache": true,
"cacheStrategy": "content"
},
"git.rebaseWhenSync": true
"git.rebaseWhenSync": false
}

View File

@@ -17,155 +17,91 @@ 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 { testCleanup, test } from "./test-override.js";
import { writeFile } from "fs/promises";
import { Page } from "playwright";
import { test, expect } from "@nn/test";
import { gt, lt } from "semver";
import { describe } from "vitest";
import { AppModel } from "../../web/__e2e__/models/app.model.js";
test("update starts downloading if version is outdated", async ({
ctx: { page },
expect,
onTestFinished
}) => {
onTestFinished(testCleanup);
test.extend({ options: { version: "3.0.0" } })(
"update starts downloading if version is outdated",
async ({ page }) => {
await page.waitForSelector(".ProseMirror");
await page.waitForSelector("#authForm");
expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /updating/i })
.waitFor({ state: "attached" });
}
);
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" });
});
test("update is only shown if version is outdated and auto updates are disabled", async ({
ctx,
expect,
onTestFinished
}) => {
onTestFinished(testCleanup);
await ctx.app.close();
await writeFile(
ctx.configPath,
JSON.stringify({
test.extend({
options: {
version: "3.0.0",
config: {
automaticUpdates: false
})
);
}
}
})(
"update is only shown if version is outdated and auto updates are disabled",
async ({ page }) => {
await page.waitForSelector(".ProseMirror");
await ctx.relaunch();
const app = new AppModel(page);
const settings = await app.goToSettings();
const { page } = ctx;
await settings.checkForUpdates();
await settings.close();
await page.waitForSelector("#authForm");
expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
await skipDialog(page);
await page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /available/i })
.waitFor({ state: "attached" });
}
);
test.extend({
options: {
version: "3.0.0-beta.0",
config: {
automaticUpdates: false,
releaseTrack: "beta"
}
}
})("update to stable if it is newer", async ({ page }) => {
await page.waitForSelector(".ProseMirror");
await page
const app = new AppModel(page);
const settings = await app.goToSettings();
await settings.checkForUpdates();
await settings.close();
const updateButton = page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /available/i })
.waitFor({ state: "attached" });
.getByRole("button", { name: /available/i });
await updateButton.waitFor({ state: "visible" });
const content = await updateButton.textContent();
const version = content?.split(" ")?.[0] || "";
expect(gt(version, "3.0.0-beta.0")).toBe(true);
});
describe("update to stable if it is newer", () => {
test.scoped({ options: { version: "3.0.0-beta.0" } });
test("test", async ({ ctx, expect, onTestFinished }) => {
onTestFinished(testCleanup);
await ctx.app.close();
await writeFile(
ctx.configPath,
JSON.stringify({
automaticUpdates: false,
releaseTrack: "beta"
})
);
await ctx.relaunch();
const { page } = ctx;
await page.waitForSelector("#authForm");
expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
await skipDialog(page);
test.extend({
options: {
version: "99.0.0-beta.0",
config: {
automaticUpdates: false,
releaseTrack: "beta"
}
}
})(
"update is not available if it latest stable version is older",
async ({ page }) => {
await page.waitForSelector(".ProseMirror");
const updateButton = page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /available/i });
await updateButton.waitFor({ state: "visible" });
const content = await updateButton.textContent();
const version = content?.split(" ")?.[0] || "";
expect(gt(version, "3.0.0-beta.0")).toBe(true);
});
});
const app = new AppModel(page);
const settings = await app.goToSettings();
describe("update is not available if it latest stable version is older", () => {
test.scoped({ options: { version: "99.0.0-beta.0" } });
test("test", async ({ ctx, expect, onTestFinished }) => {
onTestFinished(testCleanup);
await ctx.app.close();
await writeFile(
ctx.configPath,
JSON.stringify({
automaticUpdates: false,
releaseTrack: "beta"
})
);
await ctx.relaunch();
const { page } = ctx;
await page.waitForSelector("#authForm");
expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
await skipDialog(page);
await page.waitForSelector(".ProseMirror");
await page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /checking for updates/i })
.waitFor({ state: "hidden" });
await settings.checkForUpdates();
await settings.close();
expect(
await page
@@ -173,45 +109,24 @@ describe("update is not available if it latest stable version is older", () => {
.getByRole("button", { name: /available/i })
.isHidden()
).toBe(true);
});
});
describe("downgrade to stable on switching to stable release track", () => {
test.scoped({ options: { version: "99.0.0-beta.0" } });
test("test", async ({ ctx, expect, onTestFinished }) => {
onTestFinished(testCleanup);
await ctx.app.close();
await writeFile(
ctx.configPath,
JSON.stringify({
automaticUpdates: false,
releaseTrack: "stable"
})
);
await ctx.relaunch();
const { page } = ctx;
await page.waitForSelector("#authForm");
expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
await skipDialog(page);
}
);
test.extend({
options: {
version: "99.0.0-beta.0",
config: { automaticUpdates: false, releaseTrack: "stable" }
}
})(
"downgrade to stable on switching to stable release track",
async ({ page }) => {
await page.waitForSelector(".ProseMirror");
await page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /checking for updates/i })
.waitFor({ state: "hidden" });
const app = new AppModel(page);
const settings = await app.goToSettings();
await settings.checkForUpdates();
await settings.close();
const updateButton = page
.locator(".theme-scope-statusBar")
@@ -220,23 +135,5 @@ describe("downgrade to stable on switching to stable release track", () => {
const content = await updateButton.textContent();
const version = content?.split(" ")?.[0] || "";
expect(lt(version, "99.0.0-beta.0")).toBe(true);
});
});
async function skipDialog(page: Page) {
try {
const dialog = page.locator(".ReactModal__Content");
const positiveButton = dialog.locator(
"button[data-role='positive-button']"
);
const negativeButton = dialog.locator(
"button[data-role='negative-button']"
);
if (await positiveButton.isVisible())
await positiveButton.click({ timeout: 1000 });
else if (await negativeButton.isVisible())
await negativeButton.click({ timeout: 1000 });
} catch (e) {
// ignore error
}
}
);

View File

@@ -0,0 +1,63 @@
/*
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 { mergeTests, test } from "@playwright/test";
import type { CommonFixtures, CommonWorkerFixtures } from "./common-fixtures";
import { commonFixtures } from "./common-fixtures";
import { platformTest } from "./platform-fixtures";
import { testModeTest } from "./test-mode-fixtures";
export const base = test;
export const baseTest = mergeTests(base, platformTest, testModeTest).extend<
CommonFixtures,
CommonWorkerFixtures
>(commonFixtures);
export function step<
This extends NonNullable<unknown>,
Args extends any[],
Return
>(
target: (this: This, ...args: Args) => Promise<Return>,
context: ClassMethodDecoratorContext<
This,
(this: This, ...args: Args) => Promise<Return>
>
) {
function replacementMethod(this: This, ...args: Args): Promise<Return> {
const name =
this.constructor.name +
"." +
(context.name as string) +
"(" +
args.map((a) => JSON.stringify(a)).join(",") +
")";
return test.step(name, async () => {
return await target.call(this, ...args);
});
}
return replacementMethod;
}
// declare global {
// interface Window {
// builtins: Builtins;
// }
// }

View File

@@ -0,0 +1,347 @@
/* eslint-disable header/header */
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Fixtures } from "@playwright/test";
import type { ChildProcess } from "child_process";
import { execSync, spawn } from "child_process";
import net from "net";
import fs from "fs";
import { stripAnsi } from "./playwright-utils";
type TestChildParams = {
command: string[];
cwd?: string;
env?: NodeJS.ProcessEnv;
shell?: boolean;
onOutput?: () => void;
};
import childProcess from "child_process";
type ProcessData = {
pid: number; // process ID
pgrp: number; // process group ID
children: Set<ProcessData>; // direct children of the process
};
function readAllProcessesLinux(): {
pid: number;
ppid: number;
pgrp: number;
}[] {
const result: { pid: number; ppid: number; pgrp: number }[] = [];
for (const dir of fs.readdirSync("/proc")) {
const pid = +dir;
if (isNaN(pid)) continue;
try {
const statFile = fs.readFileSync(`/proc/${pid}/stat`, "utf8");
// Format of /proc/*/stat is described https://man7.org/linux/man-pages/man5/proc.5.html
const match = statFile.match(
/^(?<pid>\d+)\s+\((?<comm>.*)\)\s+(?<state>R|S|D|Z|T|t|W|X|x|K|W|P)\s+(?<ppid>\d+)\s+(?<pgrp>\d+)/
);
if (match && match.groups) {
result.push({
pid: +match.groups.pid,
ppid: +match.groups.ppid,
pgrp: +match.groups.pgrp
});
}
} catch (e) {
// We don't have access to some /proc/<pid>/stat file.
}
}
return result;
}
function readAllProcessesMacOS(): {
pid: number;
ppid: number;
pgrp: number;
}[] {
const result: { pid: number; ppid: number; pgrp: number }[] = [];
const processTree = childProcess.spawnSync("ps", ["-eo", "pid,pgid,ppid"]);
const lines = processTree.stdout.toString().trim().split("\n");
for (const line of lines) {
const [pid, pgrp, ppid] = line
.trim()
.split(/\s+/)
.map((token) => +token);
// On linux, the very first line of `ps` is the header with "PID PGID PPID".
if (isNaN(pid) || isNaN(pgrp) || isNaN(ppid)) continue;
result.push({ pid, ppid, pgrp });
}
return result;
}
function buildProcessTreePosix(pid: number): ProcessData | undefined {
// Certain Linux distributions might not have `ps` installed.
const allProcesses =
process.platform === "darwin"
? readAllProcessesMacOS()
: readAllProcessesLinux();
const pidToProcess = new Map<number, ProcessData>();
for (const { pid, pgrp } of allProcesses)
pidToProcess.set(pid, { pid, pgrp, children: new Set() });
for (const { pid, ppid } of allProcesses) {
const parent = pidToProcess.get(ppid);
const child = pidToProcess.get(pid);
// On POSIX, certain processes might not have parent (e.g. PID=1 and occasionally PID=2)
// or we might not have access to it proc info.
if (parent && child) parent.children.add(child);
}
return pidToProcess.get(pid);
}
export class TestChildProcess {
params: TestChildParams;
process: ChildProcess;
output = "";
stdout = "";
stderr = "";
fullOutput = "";
onOutput?: (chunk: string | Buffer) => void;
exited: Promise<{ exitCode: number | null; signal: string | null }>;
exitCode: Promise<number | null>;
private _outputCallbacks = new Set<() => void>();
constructor(params: TestChildParams) {
this.params = params;
// See https://nodejs.org/api/deprecations.html#DEP0190
const command = params.shell ? params.command.join(" ") : params.command[0];
const args = params.shell ? [] : params.command.slice(1);
this.process = spawn(command, args, {
env: {
...process.env,
...params.env
},
cwd: params.cwd,
shell: params.shell,
// On non-windows platforms, `detached: true` makes child process a leader of a new
// process group, making it possible to kill child process tree with `.kill(-pid)` command.
// @see https://nodejs.org/api/child_process.html#child_process_options_detached
detached: process.platform !== "win32"
});
if (process.env.PWTEST_DEBUG)
process.stdout.write(`\n\nLaunching ${params.command.join(" ")}\n`);
this.onOutput = params.onOutput;
const appendChunk = (type: "stdout" | "stderr", chunk: string | Buffer) => {
this.output += String(chunk);
if (type === "stderr") this.stderr += String(chunk);
else this.stdout += String(chunk);
if (process.env.PWTEST_DEBUG) process.stdout.write(String(chunk));
else this.fullOutput += String(chunk);
this.onOutput?.(chunk);
for (const cb of this._outputCallbacks) cb();
this._outputCallbacks.clear();
};
this.process.stderr!.on("data", appendChunk.bind(null, "stderr"));
this.process.stdout!.on("data", appendChunk.bind(null, "stdout"));
const killProcessGroup = this._killProcessTree.bind(this, "SIGKILL");
process.on("exit", killProcessGroup);
this.exited = new Promise((f) => {
this.process.on("exit", (exitCode, signal) => f({ exitCode, signal }));
process.off("exit", killProcessGroup);
});
this.exitCode = this.exited.then((r) => r.exitCode);
}
outputLines(): string[] {
const strippedOutput = stripAnsi(this.output);
return strippedOutput
.split("\n")
.filter((line) => line.startsWith("%%"))
.map((line) => line.substring(2).trim());
}
async kill(signal: "SIGINT" | "SIGKILL" = "SIGKILL") {
this._killProcessTree(signal);
return this.exited;
}
private _killProcessTree(signal: "SIGINT" | "SIGKILL") {
if (!this.process.pid || !this.process.kill(0)) return;
killProcessGroup(this.process.pid, signal);
}
async cleanExit() {
const r = await this.exited;
if (r.exitCode)
throw new Error(
`Process failed with exit code ${r.exitCode}. Output:\n${this.output}`
);
if (r.signal)
throw new Error(
`Process received signal: ${r.signal}. Output:\n${this.output}`
);
}
async waitForOutput(substring: string, count = 1) {
while (countTimes(stripAnsi(this.output), substring) < count)
await new Promise<void>((f) => this._outputCallbacks.add(f));
}
clearOutput() {
this.output = "";
}
write(chars: string) {
this.process.stdin!.write(chars);
}
}
export function killProcessGroup(
pid: number,
signal: "SIGINT" | "SIGKILL" = "SIGKILL"
) {
// On Windows, we always call `taskkill` no matter signal.
if (process.platform === "win32") {
try {
execSync(`taskkill /pid ${pid} /T /F /FI "MEMUSAGE gt 0"`, {
stdio: "ignore"
});
} catch (e) {
// the process might have already stopped
}
return;
}
// In case of POSIX and `SIGINT` signal, send it to the main process group only.
if (signal === "SIGINT") {
try {
process.kill(-pid, "SIGINT");
} catch (e) {
// the process might have already stopped
}
return;
}
// In case of POSIX and `SIGKILL` signal, we should send it to all descendant process groups.
const rootProcess = buildProcessTreePosix(pid);
if (!rootProcess) return;
const descendantProcessGroups = (function flatten(
processData: ProcessData,
result: Set<number> = new Set()
) {
// Process can nullify its own process group with `setpgid`. Use its PID instead.
result.add(processData.pgrp || processData.pid);
processData.children.forEach((child) => flatten(child, result));
return result;
})(rootProcess);
for (const pgrp of descendantProcessGroups) {
try {
process.kill(-pgrp, "SIGKILL");
} catch (e) {
// the process might have already stopped
}
}
}
export type CommonFixtures = {
childProcess: (params: TestChildParams) => TestChildProcess;
waitForPort: (port: number) => Promise<void>;
findFreePort: () => Promise<number>;
};
export type CommonWorkerFixtures = {
daemonProcess: (params: TestChildParams) => TestChildProcess;
};
export const commonFixtures: Fixtures<CommonFixtures, CommonWorkerFixtures> = {
childProcess: async ({}, use, testInfo) => {
const processes: TestChildProcess[] = [];
await use((params) => {
const process = new TestChildProcess(params);
processes.push(process);
return process;
});
await Promise.all(processes.map(async (child) => child.kill()));
if (
testInfo.status !== "passed" &&
testInfo.status !== "skipped" &&
!process.env.PWTEST_DEBUG
) {
for (const process of processes) {
console.log("====== " + process.params.command.join(" "));
console.log(process.fullOutput.replace(/\x1Bc/g, ""));
console.log("=========================================");
}
}
},
daemonProcess: [
async ({}, use) => {
const processes: TestChildProcess[] = [];
await use((params) => {
const process = new TestChildProcess(params);
processes.push(process);
return process;
});
await Promise.all(processes.map((child) => child.kill("SIGINT")));
},
{ scope: "worker" }
],
waitForPort: async ({}, use) => {
const token = { canceled: false };
await use(async (port) => {
while (!token.canceled) {
const promise = new Promise<boolean>((resolve) => {
const conn = net
.connect(port, "127.0.0.1")
.on("error", () => resolve(false))
.on("connect", () => {
conn.end();
resolve(true);
});
});
if (await promise) return;
await new Promise((x) => setTimeout(x, 100));
}
});
token.canceled = true;
},
findFreePort: async ({}, use) => {
await use(async () => {
return new Promise((resolve, reject) => {
const server = net.createServer();
server.listen(0, "127.0.0.1", () => {
const { port } = server.address() as net.AddressInfo;
server.close(() => resolve(port));
});
server.on("error", reject);
});
});
}
};
export function countTimes(s: string, sub: string): number {
let result = 0;
for (let index = 0; index !== -1; ) {
index = s.indexOf(sub, index);
if (index !== -1) {
result++;
index += sub.length;
}
}
return result;
}

View File

@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { buildApp } from "./utils";
export default async function setup() {
export default async function globalSetup() {
console.log("Building the app...");
await buildApp();
}

View File

@@ -0,0 +1,152 @@
/*
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/>.
*/
/* eslint-disable no-empty-pattern */
import { AppContext, buildAndLaunchApp, TestOptions } from "./utils";
import path from "path";
import { version } from "../../package.json";
import type { ElectronApplication, Page } from "@playwright/test";
import type { TraceViewerFixtures } from "./trace-viewer-fixtures";
import { traceViewerFixtures } from "./trace-viewer-fixtures";
import { PageTestFixtures, PageWorkerFixtures } from "./page-test-api";
import fs from "fs";
import { tmpdir } from "os";
import { baseTest } from "./base-test";
export type ElectronTestFixtures = PageTestFixtures & {
electronApp: ElectronApplication;
launchElectronApp: (options?: TestOptions) => Promise<ElectronApplication>;
createUserDataDir: () => Promise<string>;
options: TestOptions;
newPage: () => Promise<Page>;
};
export type { Page, Browser } from "@playwright/test";
export { expect } from "@playwright/test";
export const test = baseTest
.extend<TraceViewerFixtures>(traceViewerFixtures)
.extend<ElectronTestFixtures, PageWorkerFixtures>({
browserVersion: [
({}, use) => use(process.env.ELECTRON_CHROMIUM_VERSION!),
{ scope: "worker" }
],
browserMajorVersion: [
({}, use) =>
use(Number(process.env.ELECTRON_CHROMIUM_VERSION!.split(".")[0])),
{ scope: "worker" }
],
electronMajorVersion: [
({}, use) =>
use(
parseInt(require("electron/package.json").version.split(".")[0], 10)
),
{ scope: "worker" }
],
isBidi: [false, { scope: "worker" }],
isAndroid: [false, { scope: "worker" }],
isElectron: [true, { scope: "worker" }],
isHeadlessShell: [false, { scope: "worker" }],
isFrozenWebkit: [false, { scope: "worker" }],
createUserDataDir: async ({}, run) => {
const dirs: string[] = [];
// We do not put user data dir in testOutputPath,
// because we do not want to upload them as test result artifacts.
await run(async () => {
const dir = await fs.promises.mkdtemp(
path.join(tmpdir(), "playwright-test-")
);
dirs.push(dir);
return dir;
});
await removeFolders(dirs);
},
launchElectronApp: async ({ createUserDataDir }, use) => {
// This env prevents 'Electron Security Policy' console message.
process.env["ELECTRON_DISABLE_SECURITY_WARNINGS"] = "true";
const apps: AppContext[] = [];
await use(async (options?: TestOptions) => {
const userDataDir = await createUserDataDir();
const ctx = await buildAndLaunchApp(
userDataDir,
options || {
version,
config: {}
}
);
apps.push(ctx);
return ctx.app;
});
for (const ctx of apps) {
await ctx.app.close();
}
await removeFolders(apps.map((ctx) => ctx.outputDir));
},
electronApp: async ({ launchElectronApp, options }, use) => {
await use(await launchElectronApp(options));
},
page: async ({ electronApp, viewport }, run) => {
const page = await electronApp.firstWindow();
if (viewport) {
await page.setViewportSize(viewport);
await electronApp.evaluate((p, viewport) => {
const mainWindow = p.BrowserWindow.getAllWindows()[0];
mainWindow.setSize(viewport.width, viewport.height);
}, viewport);
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
page.context().app = electronApp;
await run(page);
},
context: async ({ electronApp }, run) => {
await run(electronApp.context());
},
newPage: async ({ launchElectronApp, options }, use) => {
await use(async () => {
const app = await launchElectronApp(options);
return app.firstWindow();
});
},
options: async ({}, use) => {
await use({
version,
config: {}
});
}
});
async function removeFolders(folders: string[]) {
await Promise.all(
folders.map((folder) =>
fs.promises.rm(folder, {
force: true,
recursive: true,
maxRetries: 3,
retryDelay: 5000
})
)
);
}

View File

@@ -0,0 +1,58 @@
/* eslint-disable header/header */
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Browser, Page, ViewportSize } from "playwright-core";
import type {
PageScreenshotOptions,
ScreenshotMode,
VideoMode
} from "@playwright/test";
export { expect } from "@playwright/test";
// Page test does not guarantee an isolated context, just a new page (because Android).
export type PageTestFixtures = {
page: Page;
};
export type PageWorkerFixtures = {
headless: boolean;
channel: string | undefined;
screenshot:
| ScreenshotMode
| ({ mode: ScreenshotMode } & Pick<
PageScreenshotOptions,
"fullPage" | "omitBackground"
>);
trace:
| "off"
| "on"
| "retain-on-failure"
| "on-first-retry"
| "retain-on-first-failure"
| "on-all-retries"
| /** deprecated */ "retry-with-trace";
video: VideoMode | { mode: VideoMode; size: ViewportSize };
browserName: "chromium" | "firefox" | "webkit";
browserVersion: string;
browserMajorVersion: number;
electronMajorVersion: number;
isBidi: boolean;
isAndroid: boolean;
isElectron: boolean;
isHeadlessShell: boolean;
isFrozenWebkit: boolean;
};

View File

@@ -0,0 +1,47 @@
/* eslint-disable header/header */
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { test } from "@playwright/test";
import os from "os";
export type PlatformWorkerFixtures = {
platform: "win32" | "darwin" | "linux";
isWindows: boolean;
isMac: boolean;
isLinux: boolean;
macVersion: number; // major only, 11 or later, zero if not mac
};
function platform(): "win32" | "darwin" | "linux" {
if (process.env.PLAYWRIGHT_SERVICE_OS === "linux") return "linux";
if (process.env.PLAYWRIGHT_SERVICE_OS === "windows") return "win32";
if (process.env.PLAYWRIGHT_SERVICE_OS === "macos") return "darwin";
return process.platform as "win32" | "darwin" | "linux";
}
function macVersion() {
if (process.platform !== "darwin") return 0;
return +os.release().split(".")[0] - 9;
}
export const platformTest = test.extend<{}, PlatformWorkerFixtures>({
platform: [platform(), { scope: "worker" }],
isWindows: [platform() === "win32", { scope: "worker" }],
isMac: [platform() === "darwin", { scope: "worker" }],
isLinux: [platform() === "linux", { scope: "worker" }],
macVersion: [macVersion(), { scope: "worker" }]
});

View File

@@ -0,0 +1,26 @@
/*
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 ansiRegex = new RegExp(
"[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))",
"g"
);
export function stripAnsi(str: string): string {
return str.replace(ansiRegex, "");
}

View File

@@ -0,0 +1,75 @@
/*
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 "@playwright/test";
import type { TestModeName } from "./test-mode";
import { DefaultTestMode, DriverTestMode } from "./test-mode";
export type TestModeWorkerOptions = {
mode: TestModeName;
};
export type TestModeTestFixtures = {
toImpl: (rpcObject?: any) => any;
};
export type TestModeWorkerFixtures = {
toImplInWorkerScope: (rpcObject?: any) => any;
playwright: typeof import("@playwright/test");
};
export const testModeTest = test.extend<
TestModeTestFixtures,
TestModeWorkerOptions & TestModeWorkerFixtures
>({
mode: ["default", { scope: "worker", option: true }],
playwright: [
async ({ mode }, run) => {
const testMode = {
default: new DefaultTestMode(),
service: new DefaultTestMode(),
service2: new DefaultTestMode(),
"service-grid": new DefaultTestMode(),
wsl: new DefaultTestMode(),
driver: new DriverTestMode()
}[mode];
const playwright = await testMode.setup();
await run(playwright);
await testMode.teardown();
},
{ scope: "worker" }
],
toImplInWorkerScope: [
async ({ playwright }, use) => {
await use((playwright as any)._connection.toImpl);
},
{ scope: "worker" }
],
toImpl: async (
{ toImplInWorkerScope: toImplWorker, mode },
use,
testInfo
) => {
if (mode !== "default" || process.env.PW_TEST_REUSE_CONTEXT)
testInfo.skip();
await use(toImplWorker);
}
});

View File

@@ -0,0 +1,52 @@
/* eslint-disable header/header */
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
/// @ts-ignore
import { oop, client } from "playwright-core/lib/coreBundle";
export type TestModeName = "default" | "driver";
const { start } = oop;
interface TestMode {
setup(): Promise<client.Playwright>;
teardown(): Promise<void>;
}
export class DriverTestMode implements TestMode {
private _impl: { playwright: client.Playwright; stop: () => Promise<void> };
async setup() {
this._impl = await start({
NODE_OPTIONS: undefined // Hide driver process while debugging.
});
return this._impl.playwright;
}
async teardown() {
await this._impl.stop();
}
}
export class DefaultTestMode implements TestMode {
async setup() {
return require("playwright-core");
}
async teardown() {}
}

View File

@@ -0,0 +1,243 @@
/* eslint-disable header/header */
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type {
Fixtures,
FrameLocator,
Locator,
Page,
Browser,
BrowserContext
} from "@playwright/test";
import { step } from "./base-test";
import path from "path";
import { CommonFixtures, TestChildProcess } from "./common-fixtures";
type BaseTestFixtures = CommonFixtures & {
context: BrowserContext;
};
type BaseWorkerFixtures = {
headless: boolean;
browser: Browser;
browserName: "chromium" | "firefox" | "webkit";
playwright: typeof import("@playwright/test");
};
export type TraceViewerFixtures = {
showTraceViewer: (
trace: string | undefined,
options?: { host?: string; port?: number; stdin?: boolean }
) => Promise<TraceViewerPage>;
runAndTrace: (
body: () => Promise<void>,
optsOverrides?: Parameters<BrowserContext["tracing"]["start"]>[0]
) => Promise<TraceViewerPage>;
};
class TraceViewerPage {
actionTitles: Locator;
actionsTree: Locator;
callLines: Locator;
consoleLines: Locator;
logLines: Locator;
errorMessages: Locator;
consoleLineMessages: Locator;
consoleStacks: Locator;
networkRequests: Locator;
metadataTab: Locator;
snapshotContainer: Locator;
sourceCodeTab: Locator;
networkTab: Locator;
settingsDialog: Locator;
themeSetting: Locator;
displayCanvasContentSetting: Locator;
constructor(public page: Page, public process: TestChildProcess) {
this.actionTitles = page.locator(".action-title");
this.actionsTree = page.getByTestId("actions-tree");
this.callLines = page.locator(".call-tab .call-line");
this.logLines = page
.getByRole("list", { name: "Log entries" })
.getByRole("listitem");
this.consoleLines = page
.getByRole("tabpanel", { name: "Console" })
.getByRole("listitem");
this.consoleLineMessages = page.locator(".console-line-message");
this.errorMessages = page.locator(".error-message");
this.consoleStacks = page.locator(".console-stack");
this.networkRequests = page
.getByRole("list", { name: "Network requests" })
.getByRole("listitem");
this.snapshotContainer = page.locator(
".snapshot-container iframe.snapshot-visible[name=snapshot]"
);
this.metadataTab = page.getByRole("tabpanel", { name: "Metadata" });
this.sourceCodeTab = page.getByRole("tabpanel", { name: "Source" });
this.networkTab = page.getByRole("tabpanel", { name: "Network" });
this.settingsDialog = page.getByTestId("settings-toolbar-dialog");
this.themeSetting = this.settingsDialog.getByRole("combobox", {
name: "Theme"
});
this.displayCanvasContentSetting = page
.locator(".setting")
.getByText("Display canvas content");
}
@step
async showAllActions() {
await this.page.getByRole("button", { name: "Filter actions" }).click();
await this.page.locator(".setting").getByText("Network routes").click();
await this.page.locator(".setting").getByText("Getters").click();
await this.page.locator(".setting").getByText("Configuration").click();
await this.page.getByRole("button", { name: "Filter actions" }).click();
}
stackFrames(options: { selected?: boolean } = {}) {
const entry = this.page
.getByRole("list", { name: "Stack trace" })
.getByRole("listitem");
if (options.selected) return entry.locator(":scope.selected");
return entry;
}
actionIconsText(action: string) {
const entry = this.actionsTree.getByRole("treeitem", { name: action });
return entry.locator(".action-icon-value").filter({ visible: true });
}
actionIcons(action: string) {
return this.actionsTree
.getByRole("treeitem", { name: action })
.locator(".action-icons")
.filter({ visible: true });
}
@step
async expandAction(title: string) {
await this.actionsTree
.getByRole("treeitem", { name: title })
.locator(".codicon-chevron-right")
.click();
}
@step
async selectAction(title: string, ordinal: number = 0) {
await this.actionsTree.getByTitle(title).nth(ordinal).click();
}
@step
async hoverAction(title: string, ordinal: number = 0) {
await this.actionsTree
.getByRole("treeitem", { name: title })
.nth(ordinal)
.hover();
}
@step
async selectSnapshot(name: string) {
await this.page.getByRole("tab", { name }).click();
}
async showErrorsTab() {
await this.page.getByRole("tab", { name: "Errors" }).click();
}
async showConsoleTab() {
await this.page.getByRole("tab", { name: "Console" }).click();
}
async showSourceTab() {
await this.page.getByRole("tab", { name: "Source" }).click();
}
async showNetworkTab() {
await this.page.getByRole("tab", { name: "Network" }).click();
}
async showMetadataTab() {
await this.page.getByRole("tab", { name: "Metadata" }).click();
}
async showSettings() {
await this.page.getByRole("button", { name: "Settings" }).click();
}
@step
async snapshotFrame(
actionName: string,
ordinal: number = 0,
hasSubframe: boolean = false
): Promise<FrameLocator> {
await this.selectAction(actionName, ordinal);
while (this.page.frames().length < (hasSubframe ? 4 : 3))
await this.page.waitForEvent("frameattached");
return this.page.frameLocator("iframe.snapshot-visible[name=snapshot]");
}
}
export const traceViewerFixtures: Fixtures<
TraceViewerFixtures,
{},
BaseTestFixtures,
BaseWorkerFixtures
> = {
showTraceViewer: async ({ playwright, childProcess, browserName }, use) => {
const browsers: Browser[] = [];
await use(async (trace: string | undefined, { host, port, stdin } = {}) => {
const command = [
"node",
path.join(__dirname, "../../node_modules/playwright-core/cli.js"),
"show-trace",
"--port",
"" + (port ?? "0")
];
if (host) command.push("--host", host);
if (stdin) command.push("--stdin");
if (trace) command.push(trace);
const cp = childProcess({ command });
await cp.waitForOutput("Listening on");
const browser = await playwright.chromium.launch({
...(browserName === "chromium" ? {} : { channel: "chromium" }),
executablePath: process.env.CRPATH // without this, setting FFPATH makes us launch Firefox with Chromium args
});
browsers.push(browser);
const page = await browser.newPage();
const url = cp.output.match(/Listening on (http:\/\/[^\s]+)/)![1];
await page.goto(url);
return new TraceViewerPage(page, cp);
});
for (const browser of browsers) await browser.close();
},
runAndTrace: async ({ context, showTraceViewer }, use, testInfo) => {
await use(async (body: () => Promise<void>, optsOverrides = {}) => {
const traceFile = testInfo.outputPath("trace.zip");
await context.tracing.start({
snapshots: true,
screenshots: true,
sources: true,
...optsOverrides
});
await body();
await context.tracing.stop({ path: traceFile });
return showTraceViewer(traceFile);
});
}
};

View File

@@ -19,21 +19,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { execSync } from "child_process";
import { cp } from "fs/promises";
import { fileURLToPath } from "node:url";
import path, { join, resolve } from "path";
import { _electron as electron } from "playwright";
import {
_electron as electron,
ElectronApplication,
Page
} from "@playwright/test";
import { existsSync } from "fs";
import { mkdir, writeFile } from "node:fs/promises";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const IS_DEBUG = process.env.NN_DEBUG === "true" || process.env.CI === "true";
const productName = `NotesnookTestHarness`;
const SOURCE_DIR = resolve("output", productName);
const root = path.resolve(__dirname, "..", "..");
const SOURCE_DIR = resolve(root, "output", productName);
export interface AppContext {
app: import("playwright").ElectronApplication;
page: import("playwright").Page;
configPath: string;
app: ElectronApplication;
userDataDir: string;
outputDir: string;
relaunch: () => Promise<void>;
@@ -41,6 +42,8 @@ export interface AppContext {
export interface TestOptions {
version: string;
args?: string[];
config?: Record<string, unknown>;
}
export interface Fixtures {
@@ -49,35 +52,43 @@ export interface Fixtures {
}
export async function buildAndLaunchApp(
userDataDir: string,
options?: TestOptions
): Promise<AppContext> {
await buildApp(options?.version);
const productName = `notesnooktest${makeid(10)}`;
const outputDir = path.join("test-artifacts", `${productName}-output`);
const outputDir = path.join(root, "test-artifacts", `${productName}-output`);
const executablePath = await copyBuild({
...options,
outputDir
});
const { app, page, configPath, userDataDir } = await launchApp(
const configPath = path.join(userDataDir, "UserData", "config.json");
if (options?.config) {
await mkdir(path.dirname(configPath), { recursive: true });
await writeFile(configPath, JSON.stringify(options.config));
}
const { app } = await launchApp(
executablePath,
productName,
options?.version
userDataDir,
options?.version,
options?.args
);
const ctx: AppContext = {
app,
page,
configPath,
userDataDir,
outputDir,
relaunch: async () => {
const { app, page, configPath, userDataDir } = await launchApp(
const { app } = await launchApp(
executablePath,
productName,
options?.version
userDataDir,
options?.version,
options?.args
);
ctx.app = app;
ctx.page = page;
ctx.userDataDir = userDataDir;
ctx.configPath = configPath;
}
};
return ctx;
@@ -85,19 +96,15 @@ export async function buildAndLaunchApp(
async function launchApp(
executablePath: string,
packageName: string,
version?: string
userDataDir: string,
version?: string,
args: string[] = []
) {
const userDataDir = resolve(
__dirname,
"..",
"test-artifacts",
"user_data_dirs",
packageName
);
const app = await electron.launch({
executablePath,
args: IS_DEBUG ? [] : ["--hidden"],
args: IS_DEBUG ? [...args] : ["--hidden", ...args],
baseURL: "https://app.notesnook.com",
acceptDownloads: true,
env: {
...(process.platform === "linux"
? {
@@ -114,20 +121,28 @@ async function launchApp(
}
});
const page = await app.firstWindow();
const configPath = path.join(userDataDir, "UserData", "config.json");
return {
app,
page,
configPath,
userDataDir
};
}
export function getAppFromPage(page: Page) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return page.context().app as ElectronApplication;
}
let MAX_RETRIES = 3;
export async function buildApp(version?: string) {
if (!existsSync(SOURCE_DIR)) {
execSync(
`node ${path.join(root, "scripts", "build.mjs")} --test --rebuild`,
{
stdio: IS_DEBUG ? "inherit" : "ignore"
}
);
const args = [
"electron-builder",
"--dir",
@@ -143,6 +158,7 @@ export async function buildApp(version?: string) {
stdio: IS_DEBUG ? "inherit" : "ignore",
env: {
...process.env,
CSC_IDENTITY_AUTO_DISCOVERY: "false",
NOTESNOOK_STAGING: "true",
NN_PRODUCT_NAME: productName,
NN_APP_ID: `com.notesnook.test.${productName}`,
@@ -151,7 +167,7 @@ export async function buildApp(version?: string) {
});
} catch (e) {
if (--MAX_RETRIES) {
console.log("retrying...");
console.log("retrying...", e);
return await buildApp(version);
} else throw e;
}
@@ -189,8 +205,7 @@ async function makeBuildCopyMacOS(outputDir: string, productName: string) {
const platformDir = process.arch === "arm64" ? "mac-arm64" : "mac";
const appDir = await makeBuildCopy(outputDir, platformDir);
return resolve(
__dirname,
"..",
root,
appDir,
`${productName}.app`,
"Contents",

View File

@@ -17,24 +17,65 @@ 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 { testCleanup, test } from "./test-override.js";
import { test, expect } from "@nn/test";
import type { ElectronApplication } from "@playwright/test";
test("make sure app loads", async ({
ctx: { page },
expect,
onTestFinished
}) => {
onTestFinished(testCleanup);
await page.waitForSelector("#authForm");
expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
async function getMainWindowState(app: ElectronApplication) {
return await app.evaluate((window) => {
const { BrowserWindow } = window;
const mainWindow = BrowserWindow.getAllWindows()[0];
if (!mainWindow) throw new Error("Main window not found");
return {
isMinimized: mainWindow.isMinimized(),
isVisible: mainWindow.isVisible()
};
});
}
test("make sure app loads", async ({ page }) => {
await page.waitForSelector(".ProseMirror");
});
test("hidden launch minimizes when tray is disabled", async ({
launchElectronApp,
options
}) => {
const app = await launchElectronApp({
version: options.version,
args: ["--hidden"],
config: {
desktopSettings: {
minimizeToSystemTray: false,
closeToSystemTray: false
}
}
});
const page = await app.firstWindow();
await page.waitForSelector(".ProseMirror");
const state = await getMainWindowState(app);
expect(state.isMinimized).toBe(true);
});
test("hidden launch does not minimize when close-to-tray is enabled", async ({
launchElectronApp,
options
}) => {
const app = await launchElectronApp({
version: options.version,
args: ["--hidden"],
config: {
desktopSettings: {
minimizeToSystemTray: false,
closeToSystemTray: true
}
}
});
const page = await app.firstWindow();
await page.waitForSelector(".ProseMirror");
const state = await getMainWindowState(app);
expect(state.isMinimized).toBe(false);
});

View File

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

View File

@@ -0,0 +1,11 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"noEmit": true,
"lib": ["ESNext"],
"moduleResolution": "Bundler",
"paths": {
"@nn/test": ["./electron-test/index.ts"]
}
}
}

View File

@@ -96,6 +96,7 @@ module.exports = {
"node_modules/sodium-native/package.json"
],
afterPack: "./scripts/removeLocales.js",
protocols: [{ name: "Notesnook", schemes: ["nn"] }],
mac: {
bundleVersion: "240",
minimumSystemVersion: "10.12.0",
@@ -181,6 +182,7 @@ module.exports = {
icon: "assets/icons/app.icns",
description: "Your private note taking space",
executableName: linuxExecutableName,
mimeTypes: ["x-scheme-handler/nn"],
desktop: {
desktopActions: {
"new-note": {
@@ -198,11 +200,15 @@ module.exports = {
}
}
},
snap: {
autoStart: false,
confinement: "strict",
allowNativeWayland: true,
base: "core22"
toolsets: {
appimage: "1.0.2"
},
snapcraft: {
base: "core24",
core24: {
confinement: "strict",
autoStart: false
}
},
extraResources: ["app-update.yml", "./assets/**"],
extraMetadata: {

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "3.3.6-beta.0",
"version": "3.4.5",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/cjs/index.js",
@@ -21,6 +21,17 @@
"types": "./dist/types/index.d.ts",
"default": "./dist/esm/index.js"
}
},
"./testutils": "./__tests__/test-override.ts",
"./*": {
"require": {
"types": "./dist/types/*",
"default": "./dist/cjs/*"
},
"import": {
"types": "./dist/types/*",
"default": "./dist/esm/*"
}
}
},
"homepage": "https://notesnook.com/",
@@ -36,23 +47,25 @@
"electron-trpc": "0.7.1",
"electron-updater": "^6.6.2",
"icojs": "^0.19.5",
"sqlite-better-trigram": "0.0.3",
"sqlite3-fts5-html": "^0.0.4",
"sqlite-better-trigram": "0.0.5",
"sqlite3-fts5-html": "^0.0.6",
"typed-emitter": "^2.1.0",
"yargs": "^17.7.2",
"zod": "3.24.3"
},
"devDependencies": {
"@playwright/test": "1.61.1",
"@streetwriters/kysely": "^0.27.4",
"@types/node": "22.15.3",
"@types/yargs": "^17.0.33",
"chokidar": "^4.0.3",
"electron": "^37.0.0",
"electron-builder": "^26.0.12",
"electron": "^37.10.3",
"electron-builder": "^26.8.1",
"esbuild": "0.21.5",
"fkill": "^10.0.3",
"node-abi": "^4.5.0",
"node-gyp-build": "^4.8.4",
"playwright": "1.48.2",
"playwright-core": "1.61.1",
"prebuildify": "^6.0.1",
"slugify": "1.6.6",
"tree-kill": "^1.2.2",
@@ -73,7 +86,7 @@
"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": "vitest run"
"test": "playwright test --tsconfig __tests__/tsconfig.json --project notesnook-desktop"
},
"author": {
"name": "Streetwriters (Private) Limited",

View File

@@ -0,0 +1,24 @@
diff --git a/node_modules/@playwright/test/node_modules/playwright-core/lib/coreBundle.js b/node_modules/@playwright/test/node_modules/playwright-core/lib/coreBundle.js
index 7d8468f..ab252ef 100644
--- a/node_modules/@playwright/test/node_modules/playwright-core/lib/coreBundle.js
+++ b/node_modules/@playwright/test/node_modules/playwright-core/lib/coreBundle.js
@@ -16010,6 +16010,7 @@ var init_validator = __esm({
args: tOptional(tArray(tString)),
chromiumSandbox: tOptional(tBoolean),
cwd: tOptional(tString),
+ baseURL: tOptional(tString),
env: tOptional(tArray(tType("NameValue"))),
timeout: tFloat,
acceptDownloads: tOptional(tEnum(["accept", "deny", "internal-browser-default"])),
diff --git a/node_modules/@playwright/test/node_modules/playwright-core/types/types.d.ts b/node_modules/@playwright/test/node_modules/playwright-core/types/types.d.ts
index 0bff9d3..d195697 100644
--- a/node_modules/@playwright/test/node_modules/playwright-core/types/types.d.ts
+++ b/node_modules/@playwright/test/node_modules/playwright-core/types/types.d.ts
@@ -21813,6 +21813,7 @@ export interface Electron {
* Additional arguments to pass to the application when launching. You typically pass the main script name here.
*/
args?: Array<string>;
+ baseURL?: string;
/**
* If specified, artifacts (traces, videos, downloads, HAR files, etc.) are saved into this directory. The directory

View File

@@ -0,0 +1,92 @@
/* eslint-disable header/header */
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type {
Config,
PlaywrightTestOptions,
PlaywrightWorkerOptions
} from "@playwright/test";
import * as path from "path";
process.env.PWPAGE_IMPL = "electron";
process.env.TEST_DESKTOP = "true";
const outputDir = path.join(__dirname, "test-results");
const testDir = path.join(__dirname, "__tests__");
const config: Config<PlaywrightWorkerOptions & PlaywrightTestOptions> = {
testDir,
outputDir,
expect: {
timeout: 10000
},
use: {
acceptDownloads: true,
trace: "retain-on-failure",
screenshot: "only-on-failure",
video: "retry-with-video",
viewport: {
width: 1920,
height: 1080
}
},
timeout: 60000,
globalTimeout: 5400000,
workers: process.env.CI ? 1 : undefined,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 1 : 0,
reporter: process.env.CI
? [["dot"], ["json", { outputFile: path.join(outputDir, "report.json") }]]
: "line",
projects: [],
globalSetup: "./__tests__/electron-test/global-setup.ts"
};
const metadata = {
platform: process.platform,
headless: "headed",
browserName: "electron",
channel: undefined,
mode: "default",
video: false
};
config.projects?.push({
name: "notesnook-desktop",
// Share screenshots with chromium.
snapshotPathTemplate:
"{testDir}/{testFileDir}/{testFileName}-snapshots/{arg}-electron{ext}",
use: {
browserName: "chromium",
headless: false
},
testDir: "__tests__",
metadata
});
config.projects?.push({
name: "notesnook-web",
// Share screenshots with chromium.
snapshotPathTemplate:
"{testDir}/{testFileDir}/{testFileName}-snapshots/{arg}-electron{ext}",
use: {
browserName: "chromium",
headless: false
},
testDir: path.resolve(__dirname, "../web/__e2e__"),
metadata
});
export default config;

View File

@@ -33,6 +33,7 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const root = args.root || path.join(__dirname, "..");
const skipTscBuild = args.skipTscBuild || false;
const buildForTesting = args.test || false;
const webAppPath = path.resolve(path.join(__dirname, "..", "..", "web"));
@@ -51,7 +52,11 @@ console.log("removed build folder");
if (args.rebuild || !existsSync(path.join(webAppPath, "build"))) {
console.log("rebuilding...");
await exec(
"node scripts/execute.mjs @notesnook/web:build:desktop",
`node scripts/execute.mjs ${
buildForTesting
? "@notesnook/web:build:test:desktop"
: "@notesnook/web:build:desktop"
}`,
path.join(__dirname, "..", "..", "..")
);
}

View File

@@ -27,6 +27,7 @@ import crypto from "crypto";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const root = path.join(__dirname, "..");
const RUNNING_PROCESSES = [];
const RESTARTABLE_PROCESSES = [];
let lastBundleHash = null;
@@ -36,7 +37,7 @@ const ENV = {
FORCE_COLOR: "false",
COLOR: "0"
};
process.chdir(path.join(__dirname, ".."));
process.chdir(root);
await onChange(true);
@@ -55,13 +56,16 @@ async function onChange(first) {
if (first) {
await fs.rm("./build/", { force: true, recursive: true });
await exec("npm rebuild electron --verbose --foreground-scripts");
await exec(
"npm run postinstall --verbose",
path.join(root, "node_modules", "electron")
);
await exec("yarn electron-builder install-app-deps");
await exec("yarn electron-builder install-app-deps", root);
}
await exec(`yarn run bundle`);
await exec(`yarn run build`);
await exec(`yarn run bundle`, root);
await exec(`yarn run build`, root);
if (await isBundleSame()) {
console.log("Bundle is same. Doing nothing.");
@@ -111,7 +115,7 @@ function spawnAndWaitUntil(cmd, cwd, predicate) {
async function exec(cmd, cwd) {
try {
console.log(">", cmd);
console.log(">", cmd, cwd);
return execSync(cmd, {
env: ENV,

View File

@@ -0,0 +1,108 @@
/*
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 { initTRPC } from "@trpc/server";
import { createWriteStream, mkdirSync } from "node:fs";
import path from "node:path";
import { z } from "zod";
import { config } from "../utils/config";
import { resolvePath } from "../utils/resolve-path";
import { app } from "electron";
const t = initTRPC.create();
const activeStreams = new Map<string, NodeJS.WritableStream>();
function generateId() {
return Math.random().toString(36).slice(2);
}
function getStreamOrThrow(id: string, operation: "write" | "close") {
const stream = activeStreams.get(id);
if (!stream) {
throw new Error(
`Backup stream not found during ${operation}. The stream may have already been closed or was never opened. Stream id: ${id}`
);
}
return stream;
}
export const backupsRouter = t.router({
open: t.procedure
.input(z.object({ filename: z.string() }))
.mutation(({ input }) => {
const { filename } = input;
if (!filename.trim()) {
throw new Error("Invalid backup filename: filename cannot be empty.");
}
if (filename.includes(path.sep) || filename.includes("\\"))
throw new Error(
`Invalid backup filename: expected a plain file name without path separators, received "${filename}".`
);
const resolvedBackupDir = resolvePath(config.backupDirectory);
const backupPath = path.resolve(resolvedBackupDir, filename);
const relativeBackupPath = path.relative(resolvedBackupDir, backupPath);
if (
relativeBackupPath.startsWith("..") ||
path.isAbsolute(relativeBackupPath)
)
throw new Error(
`Invalid backup filename: resolved path "${backupPath}" is outside the configured backup directory "${resolvedBackupDir}". The configured backup directory may be invalid.`
);
mkdirSync(resolvedBackupDir, { recursive: true });
const stream = createWriteStream(backupPath, { encoding: "utf-8" });
const id = generateId();
activeStreams.set(id, stream);
return id;
}),
write: t.procedure
.input(z.object({ id: z.string(), chunk: z.string() }))
.mutation(({ input }) => {
const stream = getStreamOrThrow(input.id, "write");
return new Promise<void>((resolve, reject) => {
stream.write(Buffer.from(input.chunk, "base64"), (err) =>
err ? reject(err) : resolve()
);
});
}),
close: t.procedure
.input(z.object({ id: z.string() }))
.mutation(({ input }) => {
const stream = getStreamOrThrow(input.id, "close");
return new Promise<void>((resolve) => {
stream.end(() => {
activeStreams.delete(input.id);
resolve();
});
});
})
});
app.on("before-quit", () => {
try {
for (const stream of activeStreams.values()) {
stream.end();
}
} catch {
// ignore
}
});

View File

@@ -24,21 +24,43 @@ import TypedEventEmitter from "typed-emitter";
export type AppEvents = {
onCreateItem(name: "note" | "notebook" | "reminder"): void;
onOpenLink(url: string): void;
bridgeReady(): void;
};
const emitter = new EventEmitter();
const typedEmitter = emitter as TypedEventEmitter<AppEvents>;
let isBridgeReady = false;
const pendingEvents: { name: string; args: unknown[] }[] = [];
const _emitter = new EventEmitter();
const emitter = _emitter as TypedEventEmitter<AppEvents>;
const t = initTRPC.create();
export const bridgeRouter = t.router({
onCreateItem: createSubscription("onCreateItem")
onCreateItem: createSubscription("onCreateItem"),
onOpenLink: createSubscription("onOpenLink"),
ready: t.procedure.query(() => {
isBridgeReady = true;
if (pendingEvents.length > 0) {
console.log(
"Emitting pending events",
pendingEvents.map((e) => e.name)
);
pendingEvents.forEach((event) => {
emitter.emit(event.name as any, ...(event.args as any[]));
});
pendingEvents.length = 0;
}
return true;
})
});
export const bridge: AppEvents = new Proxy({} as AppEvents, {
get(_t, name) {
if (typeof name === "symbol") return;
return (...args: unknown[]) => {
emitter.emit(name, ...args);
if (!isBridgeReady) {
pendingEvents.push({ name, args });
return;
}
_emitter.emit(name, ...args);
};
}
});
@@ -49,9 +71,9 @@ function createSubscription<TName extends keyof AppEvents>(eventName: TName) {
const listener: AppEvents[TName] = (...args: any[]) => {
emit.next(args[0]);
};
typedEmitter.addListener(eventName, listener);
emitter.addListener(eventName, listener);
return () => {
typedEmitter.removeListener(eventName, listener);
emitter.removeListener(eventName, listener);
};
});
});

View File

@@ -25,6 +25,8 @@ import { updaterRouter } from "./updater";
import { bridgeRouter } from "./bridge";
import { safeStorageRouter } from "./safe-storage";
import { windowRouter } from "./window";
import { sqliteRouter } from "./sqlite-kysely";
import { backupsRouter } from "./backups";
const t = initTRPC.create();
@@ -35,10 +37,13 @@ export const router = t.router({
updater: updaterRouter,
bridge: bridgeRouter,
safeStorage: safeStorageRouter,
window: windowRouter
window: windowRouter,
sqlite: sqliteRouter,
backups: backupsRouter
});
export const api = router.createCaller({});
const createCaller = t.createCallerFactory(router);
export const api = createCaller({});
// Export type router type signature,
// NOT the router itself.

View File

@@ -24,7 +24,6 @@ import {
dialog,
Menu,
MenuItem,
nativeImage,
nativeTheme,
Notification,
shell
@@ -33,16 +32,16 @@ import { AutoLaunch } from "../utils/autolaunch";
import { config, DesktopIntegration } from "../utils/config";
import { bringToFront } from "../utils/bring-to-front";
import { getTheme, setTheme, Theme } from "../utils/theme";
import { mkdirSync, writeFileSync } from "fs";
import { dirname } from "path";
import { resolvePath } from "../utils/resolve-path";
import { existsSync } from "fs";
import { normalizePathString, resolvePath } from "../utils/resolve-path";
import { observable } from "@trpc/server/observable";
import { AssetManager } from "../utils/asset-manager";
import { isFlatpak, isSnap } from "../utils";
import { isFlatpak, isPortable, isSnap } from "../utils";
import { setupDesktopIntegration } from "../utils/desktop-integration";
import { rm } from "fs/promises";
import { disableCustomDns, enableCustomDns } from "../utils/custom-dns";
import type { MenuItem as NNMenuItem } from "@notesnook/ui";
import { platform } from "os";
import { strings } from "@notesnook/intl";
const t = initTRPC.create();
@@ -60,6 +59,14 @@ const NotificationOptions = z.object({
export const osIntegrationRouter = t.router({
isFlatpak: t.procedure.query(() => isFlatpak()),
isSnap: t.procedure.query(() => isSnap()),
isPortable: t.procedure.query(() => isPortable()),
backupDirectory: t.procedure.query(() => {
const backupDirectory = normalizePathString(config.backupDirectory);
if (backupDirectory !== config.backupDirectory) {
config.backupDirectory = backupDirectory;
}
return backupDirectory;
}),
zoomFactor: t.procedure.query(() => config.zoomFactor),
setZoomFactor: t.procedure.input(z.number()).mutation(({ input: factor }) => {
@@ -116,53 +123,19 @@ export const osIntegrationRouter = t.router({
setupDesktopIntegration(settings);
}),
selectDirectory: t.procedure
.input(
z.object({
title: z.string().optional(),
buttonLabel: z.string().optional(),
defaultPath: z.string().optional()
})
)
.query(async ({ input }) => {
if (!globalThis.window) return undefined;
selectBackupDirectory: t.procedure.input(z.undefined()).query(async () => {
if (!globalThis.window) return undefined;
const { title, buttonLabel, defaultPath } = input;
const result = await dialog.showOpenDialog(globalThis.window, {
title: strings.selectBackupDir(),
buttonLabel: strings.select(),
properties: ["openDirectory"],
defaultPath: config.backupDirectory && resolvePath(config.backupDirectory)
});
if (result.canceled) return undefined;
const result = await dialog.showOpenDialog(globalThis.window, {
title,
buttonLabel,
properties: ["openDirectory"],
defaultPath: defaultPath && resolvePath(defaultPath)
});
if (result.canceled) return undefined;
return result.filePaths[0];
}),
saveFile: t.procedure
.input(z.object({ data: z.string(), filePath: z.string() }))
.query(({ input }) => {
const { data, filePath } = input;
if (!data || !filePath) return;
const resolvedPath = resolvePath(filePath);
mkdirSync(dirname(resolvedPath), { recursive: true });
writeFileSync(resolvedPath, data);
}),
resolvePath: t.procedure
.input(z.object({ filePath: z.string() }))
.query(({ input }) => {
const { filePath } = input;
return resolvePath(filePath);
}),
deleteFile: t.procedure.input(z.string()).query(async ({ input }) => {
await rm(input);
config.backupDirectory = result.filePaths[0];
}),
restart: t.procedure.query(() => {
app.relaunch();
app.exit();
@@ -178,7 +151,9 @@ export const osIntegrationRouter = t.router({
})
});
notification.show();
if (input.urgency === "critical") {
if (input.urgency === "critical" && process.platform !== "linux") {
// due to an Electron bug in versions below 40.x, shell.beep() causes a segfault on Linux.
// TODO: Remove when migrating to Electron 40.x or newer.
shell.beep();
}
@@ -189,9 +164,38 @@ export const osIntegrationRouter = t.router({
}),
openPath: t.procedure
.input(z.object({ type: z.literal("path"), link: z.string() }))
.query(({ input }) => {
.query(async ({ input }) => {
if (isFlatpak()) return;
const { type, link } = input;
if (type === "path") return shell.openPath(resolvePath(link));
if (type !== "path") return;
const path = decodeURIComponent(new URL(link).pathname);
const resolvedPath = resolvePath(
// remove leading slash from path on windows
platform() === "win32" ? path.slice(1) : path
);
if (!existsSync(resolvedPath)) {
if (globalThis.window) {
await dialog.showMessageBox(globalThis.window, {
type: "error",
title: "Path not found",
message: `The path does not exist:\n${wrapPath(resolvedPath)}`
});
}
return;
}
const result = await dialog.showMessageBox(globalThis.window!, {
message: strings.openingLocalFileDesc(resolvedPath),
title: strings.openingLocalFile(),
buttons: [strings.cancel(), strings.open()],
defaultId: 1,
cancelId: 0,
type: "question"
});
result.response === 1 && (await shell.openPath(resolvedPath));
}),
bringToFront: t.procedure.query(() => bringToFront()),
changeTheme: t.procedure
@@ -252,6 +256,7 @@ export const osIntegrationRouter = t.router({
if (menuItem) menu.append(menuItem);
}
if (menu.items.length > 0) menu.popup();
menu.on("menu-will-close", () => emit.next([]));
return () => {
menu.removeAllListeners();
menu.closePopup();
@@ -295,3 +300,7 @@ function toMenuItem(
}
}
}
function wrapPath(path: string, maxLineLength = 100): string {
return path.replace(new RegExp(`(.{${maxLineLength}})`, "g"), "$1\n");
}

View File

@@ -84,35 +84,55 @@ const LANGUAGES: Record<string, string> = {
type Language = { code: string; name: string };
const LANGUAGE_REDIRECT_MAP: Record<string, string> = {
es: "es-MX",
"es-419": "es-MX",
"es-ES": "es-AR"
};
export const spellCheckerRouter = t.router({
isEnabled: t.procedure.query(() => config.isSpellCheckerEnabled),
languages: t.procedure.query(
() =>
<Language[]>(
globalThis.window?.webContents.session.availableSpellCheckerLanguages.map(
(code) => ({
code,
name: LANGUAGES[code]
})
)
)
),
enabledLanguages: t.procedure.query(
() =>
<Language[]>(
globalThis.window?.webContents.session
.getSpellCheckerLanguages()
.map((code) => ({
code,
name: LANGUAGES[code]
}))
)
),
setLanguages: t.procedure
.input(z.array(z.string()))
.mutation(({ input: languages }) =>
globalThis.window?.webContents.session.setSpellCheckerLanguages(languages)
),
languages: t.procedure.query(() => {
const available =
globalThis.window?.webContents.session.availableSpellCheckerLanguages ||
[];
return <Language[]>available
.map((code) => ({
code,
name: LANGUAGES[code] || code
}))
.sort((a, b) => a.name.localeCompare(b.name));
}),
enabledLanguages: t.procedure.query(() => {
const enabled =
globalThis.window?.webContents.session.getSpellCheckerLanguages() || [];
const available =
globalThis.window?.webContents.session.availableSpellCheckerLanguages ||
[];
const resolved = enabled
.map((code) => resolveLanguage(code, available))
.filter(Boolean) as string[];
return <Language[]>resolved.map((code) => ({
code,
name: LANGUAGES[code] || code
}));
}),
setLanguages: t.procedure.input(z.array(z.string())).mutation(({ input }) => {
const available =
globalThis.window?.webContents.session.availableSpellCheckerLanguages ||
[];
const resolved = input
.map((code) => resolveLanguage(code, available))
.filter(Boolean) as string[];
globalThis.window?.webContents.session.setSpellCheckerLanguages(resolved);
}),
toggle: t.procedure
.input(z.object({ enabled: z.boolean() }))
.mutation(({ input: { enabled } }) => {
@@ -128,3 +148,16 @@ export const spellCheckerRouter = t.router({
);
})
});
function resolveLanguage(code: string, available: string[]) {
if (LANGUAGE_REDIRECT_MAP[code]) {
const working = LANGUAGE_REDIRECT_MAP[code];
return available.includes(working) ? working : code;
}
const fallback = code.split("-")[0];
return available.includes(code)
? code
: available.includes(fallback)
? fallback
: undefined;
}

View File

@@ -19,6 +19,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import type { Database, Statement } from "better-sqlite3-multiple-ciphers";
import type { QueryResult } from "@streetwriters/kysely";
import { app } from "electron";
import path from "node:path";
import { initTRPC } from "@trpc/server";
type SQLiteCompatibleType =
| number
@@ -28,25 +31,32 @@ type SQLiteCompatibleType =
| bigint
| null;
export class SQLite {
class SQLite {
sqlite?: Database;
initialized = false;
preparedStatements: Map<string, Statement<unknown[]>> = new Map();
retryCounter: Record<string, number> = {};
extensionsLoaded = false;
private filePath?: string;
constructor() {
console.log("new sqlite worker");
}
async open(filePath: string) {
async open(filename: string) {
if (this.sqlite) {
console.error("Database is already initialized");
return;
}
this.filePath =
filename === ":memory:"
? filename
: path.join(app.getPath("userData"), filename) + ".sql";
if (!isPathAllowed(this.filePath))
throw new Error("Database path is not allowed: " + this.filePath);
this.sqlite = require("better-sqlite3-multiple-ciphers")(
filePath
this.filePath
).unsafeMode(true);
}
@@ -113,7 +123,8 @@ export class SQLite {
};
}
} catch (e) {
if (e instanceof Error) e.message += ` (query: ${sql})`;
if (e instanceof Error)
throw rewriteError(e, `${e.message} (query: ${sql})`);
throw e;
} finally {
// Since SQLite 3.48.0 (SQLite3MC v2.0.2) it's not possible to load fts5
@@ -154,9 +165,11 @@ export class SQLite {
this.sqlite = undefined;
}
async delete(filePath: string) {
async delete() {
if (!this.filePath) return;
await this.close();
await require("fs/promises").rm(filePath, {
await require("node:fs/promises").rm(this.filePath, {
force: true,
maxRetries: 5,
retryDelay: 500
@@ -212,3 +225,75 @@ function getExtensionPath(extensionName: string, entryPoint: string) {
}
return loadablePath;
}
function rewriteError(e: Error, message: string) {
const error = new Error(message);
error.stack = e.stack;
error.name = e.name;
error.cause = e.cause;
return error;
}
function isPathAllowed(databasePath: string) {
if (databasePath === ":memory:") return true;
const base = app.getPath("userData");
const resolved = path.resolve(databasePath);
return resolved.startsWith(base + path.sep);
}
const t = initTRPC.create();
const databases: Record<string, SQLite> = {};
export const sqliteRouter = t.router({
open: t.procedure
.input((v) => v)
.mutation(async ({ input }) => {
const { filePath } = input as { filePath: string };
if (databases[filePath]) return filePath;
const sqlite = new SQLite();
await sqlite.open(filePath);
databases[filePath] = sqlite;
return filePath;
}),
run: t.procedure
.input((v) => v)
.mutation(async ({ input }) => {
const { id, sql, parameters } = input as {
id: string;
sql: string;
parameters?: SQLiteCompatibleType[];
};
const sqlite = databases[id];
if (!sqlite) throw new Error("Database not found for id: " + id);
return await sqlite.run(sql, parameters);
}),
close: t.procedure
.input((v) => v)
.mutation(async ({ input }) => {
const { id } = input as { id: string };
const sqlite = databases[id];
if (!sqlite) throw new Error("Database not found for id: " + id);
await sqlite.close();
delete databases[id];
}),
delete: t.procedure
.input((v) => v)
.mutation(async ({ input }) => {
const { id } = input as { id: string };
const sqlite = databases[id];
if (!sqlite) throw new Error("Database not found for id: " + id);
await sqlite.delete();
delete databases[id];
})
});
app.on("before-quit", async () => {
for (const db of Object.values(databases)) {
try {
await db.close();
} catch (e) {
console.error("Error closing database:", e);
}
}
});

View File

@@ -24,6 +24,7 @@ import type { AppUpdaterEvents } from "electron-updater/out/AppUpdater";
import { z } from "zod";
import { config } from "../utils/config";
import { app } from "electron";
import { isFlatpak, isPortable, isSnap } from "../utils";
type UpdateInfo = { version: string };
type Progress = { percent: number };
@@ -31,13 +32,15 @@ type Progress = { percent: number };
const t = initTRPC.create();
let cancellationToken: CancellationToken | undefined = undefined;
let downloadTimeout: NodeJS.Timeout | undefined = undefined;
const updatesSupported = !isFlatpak() && !isSnap() && !isPortable();
export const updaterRouter = t.router({
autoUpdates: t.procedure.query(() => config.automaticUpdates),
autoUpdates: t.procedure.query(
() => updatesSupported && config.automaticUpdates
),
releaseTrack: t.procedure.query(() => config.releaseTrack),
install: t.procedure.query(() => autoUpdater.quitAndInstall()),
download: t.procedure.query(async () => {
if (cancellationToken) return;
if (!updatesSupported || cancellationToken) return;
clearTimeout(downloadTimeout);
await new Promise<string[]>((resolve, reject) => {
downloadTimeout = setTimeout(async () => {
@@ -52,7 +55,7 @@ export const updaterRouter = t.router({
});
}),
check: t.procedure.query(async () => {
if (cancellationToken) return;
if (!updatesSupported || cancellationToken) return;
clearTimeout(downloadTimeout);
await new Promise<void>((resolve) => {
downloadTimeout = setTimeout(async () => {

View File

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

View File

@@ -21,4 +21,3 @@ export { PATHS } from "./constants";
export type { AppRouter } from "./api";
export { type UpdateInfo } from "builder-util-runtime";
export { type DesktopIntegration } from "./utils/config";
export { SQLite } from "./api/sqlite-kysely.js";

View File

@@ -39,6 +39,8 @@ import { setupDesktopIntegration } from "./utils/desktop-integration";
import { disableCustomDns, enableCustomDns } from "./utils/custom-dns";
import { Messages, setI18nGlobal } from "@notesnook/intl";
import { i18n } from "@lingui/core";
import { PATHS } from "./constants";
import { normalizePathString } from "./utils/resolve-path";
const locale =
process.env.NODE_ENV === "development"
@@ -52,6 +54,13 @@ locale.then(({ default: locale }) => {
});
setI18nGlobal(i18n);
const appHostnames = isDevelopment()
? ["localhost", "127.0.0.1"]
: ["app.notesnook.com"];
// Pending nn:// link to open once the window is ready (used on Windows/Linux
// when the app is launched via the nn:// protocol for the first time).
let pendingNNLink: string | undefined = findNNLink(process.argv);
// only run a single instance
if (!MAC_APP_STORE && !app.requestSingleInstanceLock()) {
console.log("Another instance is already running!");
@@ -83,6 +92,17 @@ async function createWindow() {
const cliOptions = await parseArguments(process.argv);
setTheme(getTheme());
// this workaround is necessary because macos doesn't support
// the --hidden flag when launching the app on startup
if (
process.platform === "darwin" &&
app.getLoginItemSettings().wasOpenedAtLogin &&
config.desktopSettings.autoStart &&
config.desktopSettings.startMinimized
) {
cliOptions.hidden = true;
}
const mainWindowState = new WindowState({});
const mainWindow = new BrowserWindow({
show: !cliOptions.hidden,
@@ -122,9 +142,6 @@ async function createWindow() {
webPreferences: {
zoomFactor: config.zoomFactor,
nodeIntegration: true,
contextIsolation: false,
nodeIntegrationInWorker: true,
spellcheck: config.isSpellCheckerEnabled,
preload: __dirname + "/preload.js"
}
@@ -135,7 +152,13 @@ async function createWindow() {
mainWindow.setMenuBarVisibility(false);
mainWindowState.manage(mainWindow);
if (cliOptions.hidden && !config.desktopSettings.minimizeToSystemTray)
if (
cliOptions.hidden &&
!(
config.desktopSettings.minimizeToSystemTray ||
config.desktopSettings.closeToSystemTray
)
)
mainWindow.minimize();
await mainWindow.webContents.loadURL(`${createURL(cliOptions, "/")}`);
@@ -158,6 +181,14 @@ async function createWindow() {
);
mainWindow.webContents.session.setProxy({ proxyRules: config.proxyRules });
mainWindow.on("show", () =>
/**
* We may set `skipTaskbar` to true at startup.
* This also removes the window from the Alt-Tab switcher.
* To fix that, whenever the app is shown, we set `skipTaskbar` to false.
*/
mainWindow.setSkipTaskbar(false)
);
mainWindow.once("closed", () => {
globalThis.window = null;
});
@@ -173,10 +204,28 @@ async function createWindow() {
return { action: "deny" };
});
mainWindow.webContents.on("will-navigate", (event, url) => {
try {
const parsedUrl = new URL(url);
if (!appHostnames.includes(parsedUrl.hostname)) {
event.preventDefault();
shell.openExternal(url);
}
} catch (e) {
console.error("will-navigate: failed to parse URL", url, e);
event.preventDefault();
}
});
nativeTheme.on("updated", () => {
setupTray();
setupJumplist();
});
if (pendingNNLink) {
bridge.onOpenLink(pendingNNLink);
pendingNNLink = undefined;
}
}
app.once("ready", async () => {
@@ -196,9 +245,12 @@ app.once("ready", async () => {
if (config.customDns) enableCustomDns();
else disableCustomDns();
if (!MAC_APP_STORE) app.setAsDefaultProtocolClient("nn");
if (!isDevelopment()) registerProtocol();
await createWindow();
configureAutoUpdater();
await migrateBackupDirectory();
await configureAutoUpdater();
});
app.once("window-all-closed", () => {
@@ -209,6 +261,12 @@ app.once("window-all-closed", () => {
app.on("second-instance", async (_ev, argv) => {
if (!globalThis.window) return;
const nnLink = findNNLink(argv);
if (nnLink) {
bridge.onOpenLink(nnLink);
bringToFront();
return;
}
const cliOptions = await parseArguments(argv);
if (cliOptions.note) bridge.onCreateItem("note");
if (cliOptions.notebook) bridge.onCreateItem("notebook");
@@ -216,12 +274,29 @@ app.on("second-instance", async (_ev, argv) => {
bringToFront();
});
// macOS opens URLs via this event. The app may or may not be fully loaded yet.
app.on("open-url", (event, url) => {
event.preventDefault();
if (!url.startsWith("nn://")) return;
if (globalThis.window) {
bridge.onOpenLink(url);
bringToFront();
} else {
// Window not ready yet — store for when createWindow finishes loading.
pendingNNLink = url;
}
});
app.on("activate", () => {
if (globalThis.window === null) {
createWindow();
}
});
function findNNLink(argv: string[]): string | undefined {
return argv.find((arg) => arg.startsWith("nn://"));
}
function createURL(options: CLIOptions, path = "/") {
const url = new URL(isDevelopment() ? "http://localhost:3000" : PROTOCOL_URL);
@@ -232,7 +307,31 @@ function createURL(options: CLIOptions, path = "/") {
else if (typeof options.note === "string")
url.hash = `/notes/${options.note}/edit`;
else if (typeof options.notebook === "string")
url.hash = `/notebooks/${options.notebook}`;
url.pathname = `/notebooks/${options.notebook}`;
return url;
}
async function migrateBackupDirectory() {
if (!globalThis.window) return;
try {
if (config.backupDirectory !== PATHS.backupsDirectory) return;
const oldPath = await globalThis.window?.webContents.executeJavaScript(
`localStorage.getItem("backupStorageLocation")`
);
if (!oldPath || oldPath === PATHS.backupsDirectory) return;
config.backupDirectory = normalizePathString(oldPath);
} catch (e) {
console.error("Failed to migrate backup directory", e);
const pressedButton = dialog.showMessageBoxSync(globalThis.window, {
message:
"Failed to migrate backup directory. It has been reset to default.",
title: "Backup Directory Migration Failed",
type: "error",
buttons: ["Set backup directory", "Ignore"]
});
if (pressedButton === 0) {
await api.integration.selectBackupDirectory();
}
}
}

View File

@@ -19,25 +19,21 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* eslint-disable no-var */
import { ELECTRON_TRPC_CHANNEL } from "electron-trpc/main";
// import type { NNCrypto } from "@notesnook/crypto";
import { ipcRenderer } from "electron";
import { platform } from "os";
import { ipcRenderer, contextBridge } from "electron";
declare global {
var os: () => "mas" | ReturnType<typeof platform>;
var os: () => "mas" | typeof process.platform;
var electronTRPC: any;
// var NativeNNCrypto: (new () => NNCrypto) | undefined;
}
process.once("loaded", async () => {
const electronTRPC = {
sendMessage: (operation: any) =>
ipcRenderer.send(ELECTRON_TRPC_CHANNEL, operation),
onMessage: (callback: any) =>
ipcRenderer.on(ELECTRON_TRPC_CHANNEL, (_event, args) => callback(args))
};
globalThis.electronTRPC = electronTRPC;
});
const electronTRPC = {
sendMessage: (operation: any) =>
ipcRenderer.send(ELECTRON_TRPC_CHANNEL, operation),
onMessage: (callback: any) =>
ipcRenderer.on(ELECTRON_TRPC_CHANNEL, (_event, args) => callback(args))
};
// globalThis.NativeNNCrypto = require("@notesnook/crypto").NNCrypto;
globalThis.os = () => (MAC_APP_STORE ? "mas" : platform());
const os = () => (MAC_APP_STORE ? "mas" : process.platform);
contextBridge.exposeInMainWorld("electronTRPC", electronTRPC);
contextBridge.exposeInMainWorld("os", os);

View File

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

View File

@@ -37,7 +37,11 @@ async function configureAutoUpdater() {
config.releaseTrack === "stable" &&
autoUpdater.currentVersion.prerelease.length > 0;
autoUpdater.allowPrerelease = false;
autoUpdater.autoInstallOnAppQuit = true;
// Do NOT auto-install on quit. On Windows, if the system shuts down while
// the NSIS installer is running, it first removes all old files and then
// gets killed before copying new ones — leaving an empty install directory.
// Updates should only be installed when the user explicitly triggers it.
autoUpdater.autoInstallOnAppQuit = false;
autoUpdater.disableWebInstaller = true;
}

View File

@@ -21,6 +21,7 @@ import { nativeTheme } from "electron";
import { JSONStorage } from "./json-storage";
import { z } from "zod";
import { autoUpdater } from "electron-updater";
import { PATHS } from "../constants";
export const DesktopIntegration = z.object({
autoStart: z.boolean().optional(),
@@ -53,7 +54,8 @@ export const config = {
backgroundColor: nativeTheme.themeSource === "dark" ? "#0f0f0f" : "#ffffff",
windowControlsIconColor:
nativeTheme.themeSource === "dark" ? "#ffffff" : "#000000"
nativeTheme.themeSource === "dark" ? "#ffffff" : "#000000",
backupDirectory: PATHS.backupsDirectory
};
type ConfigKey = keyof typeof config;

View File

@@ -33,3 +33,7 @@ export function isFlatpak() {
export function isSnap() {
return process.env.SNAP !== undefined;
}
export function isPortable() {
return process.env.PORTABLE_EXECUTABLE_DIR !== undefined;
}

View File

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

View File

@@ -20,11 +20,30 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { app } from "electron";
import { isAbsolute, join } from "path";
export function normalizePathString(_path: string) {
let normalizedPath = _path.trim();
try {
const parsedPath = JSON.parse(normalizedPath);
if (typeof parsedPath === "string") normalizedPath = parsedPath;
} catch {
// ignore invalid JSON and keep the original path string
}
if (normalizedPath === "~") return app.getPath("home");
if (/^~[\\/]/.test(normalizedPath)) {
return join(app.getPath("home"), normalizedPath.slice(2));
}
return normalizedPath;
}
export function resolvePath(_path: string) {
if (isAbsolute(_path)) return _path;
const normalizedPath = normalizePathString(_path);
if (isAbsolute(normalizedPath)) return normalizedPath;
return join(
..._path.split("/").map((segment) => {
...normalizedPath.split("/").map((segment) => {
let resolved = segment;
try {
resolved = app.getPath(resolved as any);

View File

@@ -1,9 +1,11 @@
{
"extends": "../../tsconfig",
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist/",
"lib": ["ESNext"],
"moduleResolution": "Bundler"
},
"include": ["src", "global.d.ts"]
"files": ["global.d.ts"],
"include": ["src"],
"exclude": ["__tests__"]
}

View File

@@ -30,7 +30,7 @@ module.exports = {
testBinaryPath:
"android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk",
build:
"cd android ; ENVFILE=.env.test ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ..",
"cd android ; ENVFILE=.env.test ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug -PreactNativeArchitectures=arm64-v8a && cd ..",
reversePorts: [8081]
},
"android.release": {
@@ -47,7 +47,7 @@ module.exports = {
simulator: {
type: "ios.simulator",
device: {
type: "iPhone 12"
type: "iPhone 17 Pro Max"
}
},
attached: {
@@ -59,7 +59,7 @@ module.exports = {
emulator: {
type: "android.emulator",
device: {
avdName: "Pixel_5_API_34"
avdName: "Pixel_5_API_36"
}
}
},

View File

@@ -13,7 +13,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v5
with:
persist-credentials: false

View File

@@ -13,7 +13,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v5
with:
persist-credentials: false

View File

@@ -1,4 +1,5 @@
.yarnrc.yml
tsconfig.tsbuildinfo
artifacts/
# OSX
#

View File

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

View File

@@ -1,9 +1,11 @@
apply plugin: "com.android.application"
apply plugin: "org.jetbrains.kotlin.android"
apply plugin: "com.facebook.react"
apply plugin: 'kotlin-parcelize'
apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"
import com.android.build.OutputFile
import groovy.json.JsonSlurper
import org.apache.tools.ant.taskdefs.condition.Os
@@ -93,6 +95,18 @@ def fdroidBuild() {
return project.hasProperty("fdroidBuild") && project.fdroidBuild == "true"
}
def prBuildNumber() {
def value = project.getProperties().get("prBuildNumber")
return value ? value : "1000"
}
def stagingReleaseBuild() {
// Enable with STAGING_BUILDtrue or -PstagingReleaseBuild=true
def fromEnv = System.getenv("STAGING_BUILD") == "true"
def fromProp = project.hasProperty("stagingReleaseBuild") && project.stagingReleaseBuild == "true"
return fromEnv || fromProp
}
def getNpmVersion() {
def inputFile = file("$rootDir/../package.json")
def jsonPackage = new JsonSlurper().parseText(inputFile.text)
@@ -117,14 +131,17 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
namespace "com.streetwriters.notesnook"
defaultConfig {
applicationId "com.streetwriters.notesnook"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled true
versionCode 3079
if (project.hasProperty("prBuildNumber")) {
versionCode Integer.parseInt(prBuildNumber())
} else {
versionCode 3114
}
versionName getNpmVersion()
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
@@ -191,6 +208,8 @@ android {
output.versionCodeOverride =
defaultConfig.versionCode * 5 + versionCodes.get(abi)
println("Fdroid Version code: ${output.versionCodeOverride} for abi ${versionCodes.get(abi)}");
} else if (stagingReleaseBuild()) {
output.versionCodeOverride = defaultConfig.versionCode + versionCodes.get(abi)
} else {
if (isBuildingAAB) {
output.versionCodeOverride = 4 * 1048576 + defaultConfig.versionCode
@@ -214,6 +233,9 @@ dependencies {
// The version of react-native is set by the React Native Gradle Plugin
implementation("com.facebook.react:react-android")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.0.0")
// Lets the widgets push their list data straight into the RemoteViews on every API level,
// instead of the deprecated RemoteViewsService adapter (which needs API 31 to do natively).
implementation("androidx.core:core-remoteviews:1.0.0")
implementation("androidx.core:core-splashscreen:1.0.0")
implementation 'androidx.multidex:multidex:2.0.1'
@@ -232,7 +254,6 @@ dependencies {
} else {
implementation jscFlavor
}
}

View File

@@ -63,4 +63,7 @@
-keepattributes Signature
-keep class com.google.gson.reflect.TypeToken { *; }
-keep class * extends com.google.gson.reflect.TypeToken
-keep class com.streetwriters.notesnook.datatypes.* { *; }
-keep class com.streetwriters.notesnook.datatypes.* { *; }
-keep class net.gotev.uploadservice.* { *; }
-keep class kotlinx.parcelize.* { *; }

View File

@@ -86,8 +86,9 @@
android:name=".NotePreviewWidget"
android:exported="false"
android:label="@string/note">
<intent-filter>"
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_RESTORED" />
</intent-filter>
<meta-data
@@ -95,6 +96,15 @@
android:resource="@xml/note_widget_info" />
</receiver>
<receiver
android:name=".WidgetTimeChangeReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.TIME_SET" />
<action android:name="android.intent.action.TIMEZONE_CHANGED" />
</intent-filter>
</receiver>
<receiver
android:name=".ReminderWidgetProvider"
android:exported="false"
@@ -155,6 +165,15 @@
<data android:scheme="notesnook" />
</intent-filter>
<intent-filter android:label="Notesnook">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="nn" />
</intent-filter>
</activity>
<activity
android:name="com.facebook.react.devsupport.DevSettingsActivity"
@@ -199,11 +218,7 @@
</intent-filter>
</activity>
<service
android:name=".OnClearFromRecentService"
android:enabled="true"
android:exported="true"
android:stopWithTask="false" />
<service android:name="com.asterinet.react.bgactions.RNBackgroundActionsTask" />
<service
@@ -225,11 +240,6 @@
</intent-filter>
</service>
<service
android:name=".ReminderViewsService"
android:exported="true"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"

View File

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

View File

@@ -20,7 +20,7 @@ import com.zoontek.rnbootsplash.RNBootSplash;
public class MainActivity extends ReactActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
protected void onCreate(Bundle savedInstanceState) {
RNBootSplash.init(this, R.style.BootTheme);
super.onCreate(null);
@@ -28,23 +28,6 @@ public class MainActivity extends ReactActivity {
WebView.setWebContentsDebuggingEnabled(true);
}
try {
startService(new Intent(getBaseContext(), OnClearFromRecentService.class));
} catch (Exception ignored) {}
if (Build.VERSION.SDK_INT >= 35) {
final View rootView = findViewById(android.R.id.content);
ViewCompat.setOnApplyWindowInsetsListener(rootView, (v, insets) -> {
Insets innerPadding = insets.getInsets(WindowInsetsCompat.Type.ime());
rootView.setPadding(
innerPadding.left,
innerPadding.top,
innerPadding.right,
innerPadding.bottom
);
return insets;
});
}
}
/**
@@ -60,20 +43,20 @@ public class MainActivity extends ReactActivity {
DefaultNewArchitectureEntryPoint.getFabricEnabled());
}
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
}
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
getReactInstanceManager().onConfigurationChanged(this, newConfig);
Intent intent = new Intent("onConfigurationChanged");
intent.putExtra("newConfig", newConfig);
this.sendBroadcast(intent);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
getReactHost().onConfigurationChanged(this);
Intent intent = new Intent("onConfigurationChanged");
intent.putExtra("newConfig", newConfig);
this.sendBroadcast(intent);
}
@Override
protected String getMainComponentName() {

View File

@@ -6,7 +6,7 @@ import com.facebook.react.ReactApplication
import com.facebook.react.ReactHost
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
import org.wonday.orientation.OrientationActivityLifecycle;
class MainApplication : Application(), ReactApplication {
override val reactHost: ReactHost by lazy {
getDefaultReactHost(
@@ -22,6 +22,7 @@ class MainApplication : Application(), ReactApplication {
override fun onCreate() {
super.onCreate()
registerActivityLifecycleCallbacks(OrientationActivityLifecycle.getInstance());
loadReactNative(this)
}
}

View File

@@ -5,15 +5,11 @@ import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.widget.RemoteViews;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactActivityDelegate;
import com.google.gson.Gson;
import com.streetwriters.notesnook.datatypes.Note;
public class NotePreviewConfigureActivity extends ReactActivity {
@@ -40,18 +36,32 @@ public class NotePreviewConfigureActivity extends ReactActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
Intent intent = getIntent();
Bundle extras = intent.getExtras();
int appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
if (extras != null) {
appWidgetId = extras.getInt(
AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
NotePreviewConfigureActivity.appWidgetId = appWidgetId;
}
activity = this;
readAppWidgetId(getIntent());
}
/**
* We launch as singleTask, so configuring a second widget while this screen is still alive
* arrives here rather than in onCreate(). Without this the activity would keep writing to
* whichever widget it happened to be opened for first.
*/
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
activity = this;
readAppWidgetId(intent);
}
private void readAppWidgetId(Intent intent) {
Bundle extras = intent != null ? intent.getExtras() : null;
int appWidgetId = extras != null
? extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID)
: AppWidgetManager.INVALID_APPWIDGET_ID;
NotePreviewConfigureActivity.appWidgetId = appWidgetId;
Intent resultValue = new Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
setResult(Activity.RESULT_CANCELED, resultValue);
activity = this;
}
public static void saveAndFinish(Context context) {

View File

@@ -1,6 +1,5 @@
package com.streetwriters.notesnook;
import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
@@ -8,51 +7,125 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.widget.RemoteViews;
import com.google.gson.Gson;
import com.streetwriters.notesnook.datatypes.Note;
import java.util.HashSet;
import java.util.Set;
public class NotePreviewWidget extends AppWidgetProvider {
static String OpenNoteId = "com.streetwriters.notesnook.OpenNoteId";
static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
int appWidgetId) {
String data = context.getSharedPreferences("appPreview", Context.MODE_PRIVATE).getString(String.valueOf(appWidgetId), "");
if (data.isEmpty()) {
String data = context.getSharedPreferences(WidgetUtils.PREFERENCES, Context.MODE_PRIVATE).getString(String.valueOf(appWidgetId), "");
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.note_widget);
Note note = WidgetUtils.parseNote(data);
if (note == null) {
// Either the widget was never configured, or we lost the note it pointed at (ids
// reassigned, data cleared). Point it back at the picker rather than leaving the user
// with an inert widget they can only fix by deleting and re-adding it.
views.setTextViewText(R.id.widget_title, context.getString(R.string.widget_note_unconfigured_title));
views.setTextViewText(R.id.widget_body, context.getString(R.string.widget_note_unconfigured_body));
views.setOnClickPendingIntent(R.id.open_note, getConfigurePendingIntent(context, appWidgetId));
appWidgetManager.updateAppWidget(appWidgetId, views);
return;
}
Gson gson = new Gson();
Note note = gson.fromJson(data, Note.class);
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.note_widget);
views.setTextViewText(R.id.widget_title, note.getTitle());
views.setTextViewText(R.id.widget_body, note.getHeadline());
// Once the user shrinks the widget down to a single row there is no room for the preview
// text, and a clipped half-line of it looks like a rendering glitch.
views.setViewVisibility(R.id.widget_body,
hasRoomForBody(appWidgetManager, appWidgetId) ? View.VISIBLE : View.GONE);
Intent intent = new Intent(context, MainActivity.class);
intent.putExtra(OpenNoteId, note.getId());
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(RCTNNativeModule.IntentType, "OpenNote");
intent.setData(Uri.parse("https://app.notesnook.com/open_note?id=" + note.getId()));
PendingIntent pendingIntent = PendingIntent.getActivity(context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE, getActivityOptionsBundle());
intent.setData(Uri.parse("nn://note/" + note.getId()));
PendingIntent pendingIntent = PendingIntent.getActivity(context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE, WidgetUtils.getActivityOptionsBundle());
views.setOnClickPendingIntent(R.id.open_note, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetId, views);
}
/**
* Reopens the configure screen for this widget. The launcher's own "reconfigure" gesture is
* hard to discover and not offered by every launcher, so an unconfigured widget needs its own
* way back in.
*/
private static PendingIntent getConfigurePendingIntent(Context context, int appWidgetId) {
Intent intent = new Intent(context, NotePreviewConfigureActivity.class);
intent.setAction(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
// PendingIntent equality ignores extras, so the widget id has to be the request code for
// each widget to get its own.
return PendingIntent.getActivity(context, appWidgetId, intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE,
WidgetUtils.getActivityOptionsBundle());
}
/**
* Height below which the note preview text is dropped, leaving just the title.
*/
private static final int MIN_HEIGHT_FOR_BODY_DP = 70;
private static boolean hasRoomForBody(AppWidgetManager appWidgetManager, int appWidgetId) {
Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId);
if (options == null) return true;
int minHeight = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT);
// Not reported yet (the widget has just been placed): assume there is room.
return minHeight <= 0 || minHeight >= MIN_HEIGHT_FOR_BODY_DP;
}
@Override
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
// This used to do nothing at all, so resizing the widget left it rendered for its old size.
updateAppWidget(context, appWidgetManager, appWidgetId);
}
private static Bundle getActivityOptionsBundle() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
ActivityOptions activityOptions = ActivityOptions.makeBasic();
activityOptions.setPendingIntentCreatorBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
return activityOptions.toBundle();
} else
return null;
/**
* The note shown by each widget is stored in the "appPreview" preferences under its widget id.
* When the system restores our widgets it hands out fresh ids, so unless we move the stored
* notes over to the new ids the widgets are left permanently blank with no way to recover
* other than removing and re-adding them.
*
* AppWidgetProvider calls onUpdate() with the new ids right after this, which re-renders them.
*/
@Override
public void onRestored(Context context, int[] oldWidgetIds, int[] newWidgetIds) {
super.onRestored(context, oldWidgetIds, newWidgetIds);
if (oldWidgetIds == null || newWidgetIds == null) return;
int count = Math.min(oldWidgetIds.length, newWidgetIds.length);
SharedPreferences preferences = context.getSharedPreferences("appPreview", Context.MODE_PRIVATE);
// Read everything up front: an old id can collide with the new id of another widget.
String[] notes = new String[count];
Set<String> newKeys = new HashSet<>();
for (int i = 0; i < count; i++) {
notes[i] = preferences.getString(String.valueOf(oldWidgetIds[i]), "");
newKeys.add(String.valueOf(newWidgetIds[i]));
}
SharedPreferences.Editor edit = preferences.edit();
for (int i = 0; i < count; i++) {
String oldKey = String.valueOf(oldWidgetIds[i]);
if (!newKeys.contains(oldKey)) {
edit.remove(oldKey);
}
}
for (int i = 0; i < count; i++) {
if (notes[i].isEmpty()) continue;
edit.putString(String.valueOf(newWidgetIds[i]), notes[i]);
}
edit.apply();
}
@Override

View File

@@ -1,12 +1,10 @@
package com.streetwriters.notesnook;
import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.widget.RemoteViews;
@@ -24,20 +22,10 @@ public class NoteWidget extends AppWidgetProvider {
static void setClickIntent(Context context, RemoteViews views) {
Intent intent = new Intent(context, ShareActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE, getActivityOptionsBundle());
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE, WidgetUtils.getActivityOptionsBundle());
views.setOnClickPendingIntent(R.id.new_note, pendingIntent);
}
private static Bundle getActivityOptionsBundle() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
ActivityOptions activityOptions = ActivityOptions.makeBasic();
activityOptions.setPendingIntentCreatorBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
return activityOptions.toBundle();
} else
return null;
}
@Override
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);

View File

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

View File

@@ -6,7 +6,13 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.Icon;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
@@ -22,13 +28,13 @@ import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.google.gson.Gson;
import com.streetwriters.notesnook.datatypes.Note;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
public class RCTNNativeModule extends ReactContextBaseJavaModule {
@@ -85,21 +91,6 @@ public class RCTNNativeModule extends ReactContextBaseJavaModule {
}
}
@ReactMethod
public void setAppState(final String appState) {
SharedPreferences appStateDetails = getReactApplicationContext().getSharedPreferences("appStateDetails", Context.MODE_PRIVATE);
SharedPreferences.Editor edit = appStateDetails.edit();
edit.putString("appState", appState);
edit.apply();
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String getAppState() {
SharedPreferences appStateDetails = getReactApplicationContext().getSharedPreferences("appStateDetails", Context.MODE_PRIVATE);
String appStateValue = appStateDetails.getString("appState", "");
return appStateValue.isEmpty() ? null : appStateValue;
}
@ReactMethod(isBlockingSynchronousMethod = true)
public int getWidgetId() {
return NotePreviewConfigureActivity.appWidgetId;
@@ -145,7 +136,7 @@ public class RCTNNativeModule extends ReactContextBaseJavaModule {
if (Objects.equals(extras.getString(IntentType), "NewReminder")) {
map.putString(ReminderWidgetProvider.NewReminder, extras.getString(ReminderWidgetProvider.NewReminder));
} else if (Objects.equals(extras.getString(IntentType), "OpenReminder")) {
map.putString(ReminderViewsService.OpenReminderId, extras.getString(ReminderViewsService.OpenReminderId));
map.putString(ReminderWidgetProvider.OpenReminderId, extras.getString(ReminderWidgetProvider.OpenReminderId));
} else if (Objects.equals(extras.getString(IntentType), "OpenNote")) {
map.putString(NotePreviewWidget.OpenNoteId, extras.getString(NotePreviewWidget.OpenNoteId));
}
@@ -163,14 +154,8 @@ public class RCTNNativeModule extends ReactContextBaseJavaModule {
@ReactMethod
public void getWidgetNotes(Promise promise) {
SharedPreferences pref = getReactApplicationContext().getSharedPreferences("appPreview", Context.MODE_PRIVATE);
Map<String, ?> map = pref.getAll();
WritableArray arr = Arguments.createArray();
for(Map.Entry<String,?> entry : map.entrySet()){
if (entry.getKey().equals("remindersList")) continue;
String value = (String) entry.getValue();
Gson gson = new Gson();
Note note = gson.fromJson(value, Note.class);
for (Note note : WidgetUtils.getWidgetNotes(getReactApplicationContext()).values()) {
arr.pushString(note.getId());
}
promise.resolve(arr);
@@ -178,36 +163,46 @@ public class RCTNNativeModule extends ReactContextBaseJavaModule {
@ReactMethod
public void hasWidgetNote(final String noteId, Promise promise) {
SharedPreferences pref = getReactApplicationContext().getSharedPreferences("appPreview", Context.MODE_PRIVATE);
Map<String, ?> map = pref.getAll();
boolean found = false;
for(Map.Entry<String,?> entry : map.entrySet()){
String value = (String) entry.getValue();
if (value.contains(noteId)) {
for (Note note : WidgetUtils.getWidgetNotes(getReactApplicationContext()).values()) {
if (note.getId().equals(noteId)) {
found = true;
break;
}
}
promise.resolve(found);
}
@ReactMethod
public void updateWidgetNote(final String noteId, final String data) {
SharedPreferences pref = getReactApplicationContext().getSharedPreferences("appPreview", Context.MODE_PRIVATE);
Map<String, ?> map = pref.getAll();
SharedPreferences pref = getReactApplicationContext().getSharedPreferences(WidgetUtils.PREFERENCES, Context.MODE_PRIVATE);
SharedPreferences.Editor edit = pref.edit();
ArrayList<String> ids = new ArrayList<>();
for(Map.Entry<String,?> entry : map.entrySet()) {
String value = (String) entry.getValue();
if (value.contains(noteId)) {
edit.putString(entry.getKey(), data);
ids.add(entry.getKey());
}
List<Integer> ids = new ArrayList<>();
// Match on the note's id, not on the raw JSON containing it somewhere: a note whose body
// happens to mention another note's id is not the same note.
for (Map.Entry<Integer, Note> entry : WidgetUtils.getWidgetNotes(getReactApplicationContext()).entrySet()) {
if (!noteId.equals(entry.getValue().getId())) continue;
edit.putString(String.valueOf(entry.getKey()), data);
ids.add(entry.getKey());
}
edit.apply();
for (String id: ids) {
NotePreviewWidget.updateAppWidget(mContext, AppWidgetManager.getInstance(mContext), Integer.parseInt(id));
for (int id : ids) {
NotePreviewWidget.updateAppWidget(mContext, AppWidgetManager.getInstance(mContext), id);
}
}
/**
* Redraws every widget from scratch. Needed because the app can be stopped while its widgets
* stay on the home screen: clearing app data empties the store without the widgets ever being
* told, so they keep showing content that is gone until something forces a redraw.
*/
@ReactMethod
public void refreshWidgets() {
WidgetUtils.refreshAll(mContext);
}
@ReactMethod
public void updateReminderWidget() {
AppWidgetManager wm = AppWidgetManager.getInstance(mContext);
@@ -215,8 +210,8 @@ public class RCTNNativeModule extends ReactContextBaseJavaModule {
for (int id: ids) {
Log.d("Reminders", "Updating" + id);
RemoteViews views = new RemoteViews(mContext.getPackageName(), R.layout.widget_reminders);
// The rows are part of this update, so there is nothing left to invalidate afterwards.
ReminderWidgetProvider.updateAppWidget(mContext, wm, id, views);
wm.notifyAppWidgetViewDataChanged(id, R.id.widget_list_view);
}
}
@@ -237,6 +232,221 @@ public class RCTNNativeModule extends ReactContextBaseJavaModule {
}
}
@ReactMethod
public void addShortcut(final String id, final String type, final String title, final String description, final String color, Promise promise) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
promise.reject("UNSUPPORTED", "Pinned launcher shortcuts require Android 8.0 or higher");
return;
}
try {
ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
if (shortcutManager == null) {
promise.reject("ERROR", "ShortcutManager not available");
return;
}
String uri = "nn://" + type + "/" + id;
Intent intent = new Intent(Intent.ACTION_VIEW, android.net.Uri.parse(uri));
intent.setPackage(mContext.getPackageName());
Icon icon = createLetterIcon(type, title, color);
ShortcutInfo shortcut = new ShortcutInfo.Builder(mContext, id)
.setShortLabel(title)
.setLongLabel(description != null && !description.isEmpty() ? description : title)
.setIcon(icon)
.setCategories(Set.of(type))
.setIntent(intent)
.build();
shortcutManager.requestPinShortcut(shortcut, null);
promise.resolve(true);
} catch (Exception e) {
promise.reject("ERROR", e.getMessage());
}
}
@ReactMethod
public void removeShortcut(final String id, Promise promise) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
promise.reject("UNSUPPORTED", "Pinned launcher shortcuts require Android 8.0 or higher");
return;
}
try {
ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
if (shortcutManager == null) {
promise.reject("ERROR", "ShortcutManager not available");
return;
}
shortcutManager.disableShortcuts(java.util.Collections.singletonList(id));
promise.resolve(true);
} catch (Exception e) {
promise.reject("ERROR", e.getMessage());
}
}
@ReactMethod
public void updateShortcut(final String id, final String type, final String title, final String description,final String color, Promise promise) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
promise.reject("UNSUPPORTED", "Pinned launcher shortcuts require Android 8.0 or higher");
return;
}
try {
ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
if (shortcutManager == null) {
promise.reject("ERROR", "ShortcutManager not available");
return;
}
// Get existing shortcut to preserve icon and intent
List<ShortcutInfo> shortcuts = shortcutManager.getPinnedShortcuts();
ShortcutInfo existingShortcut = null;
for (ShortcutInfo s : shortcuts) {
if (s.getId().equals(id)) {
existingShortcut = s;
break;
}
}
if (existingShortcut == null) {
return;
}
Icon icon = createLetterIcon(type, title, color);
ShortcutInfo updatedShortcut = new ShortcutInfo.Builder(mContext, id)
.setShortLabel(title)
.setIcon(icon)
.setLongLabel(description != null && !description.isEmpty() ? description : title)
.setIntent(existingShortcut.getIntent())
.build();
shortcutManager.updateShortcuts(java.util.Collections.singletonList(updatedShortcut));
promise.resolve(true);
} catch (Exception e) {
promise.reject("ERROR", e.getMessage());
}
}
@ReactMethod
public void removeAllShortcuts(Promise promise) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
promise.reject("UNSUPPORTED", "Pinned launcher shortcuts require Android 8.0 or higher");
return;
}
try {
ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
if (shortcutManager == null) {
promise.reject("ERROR", "ShortcutManager not available");
return;
}
List<ShortcutInfo> pinnedShortcuts = shortcutManager.getPinnedShortcuts();
if (!pinnedShortcuts.isEmpty()) {
List<String> ids = new ArrayList<>();
for (ShortcutInfo shortcut : pinnedShortcuts) {
ids.add(shortcut.getId());
}
shortcutManager.disableShortcuts(ids);
}
promise.resolve(true);
} catch (Exception e) {
promise.reject("ERROR", e.getMessage());
}
}
@ReactMethod
public void getAllShortcuts(Promise promise) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}
ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
WritableArray shortcuts = Arguments.createArray();
List<ShortcutInfo> infos = shortcutManager.getPinnedShortcuts();
for (ShortcutInfo info: infos) {
WritableMap data = Arguments.createMap();
data.putString("id", info.getId());
if (info.getShortLabel() != null) {
data.putString("title", info.getShortLabel().toString());
}
if (info.getLongLabel() != null) {
data.putString("description", info.getLongLabel().toString());
}
if (!Objects.requireNonNull(info.getCategories()).isEmpty()) {
if (info.getCategories().contains("note")) {
data.putString("type", "note");
} else if (info.getCategories().contains("notebook")) {
data.putString("type", "notebook");
} else if (info.getCategories().contains("tag")) {
data.putString("type", "tag");
} else if (info.getCategories().contains("color")) {
data.putString("type", "color");
}
}
shortcuts.pushMap(data);
}
promise.resolve(shortcuts);
}
private Icon createLetterIcon(String type, String title, String colorCode) {
String letter = type.contains("tag") ? "#" : title != null && !title.isEmpty()
? title.substring(0, 1).toUpperCase()
: "?";
int color = type.equals("color") ? Color.parseColor(colorCode) : getColorForLetter(letter);
if (type.equals("notebook")) return Icon.createWithResource(mContext, R.drawable.ic_notebook);
// Use a larger canvas and fill it completely to avoid white borders from launcher masking.
int iconSize = 256;
Bitmap bitmap = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
if (type.equals("color")) {
Paint backgroundPaint = new Paint();
backgroundPaint.setColor(color);
backgroundPaint.setAntiAlias(true);
canvas.drawCircle(iconSize / 2, iconSize / 2, iconSize/2, backgroundPaint);
} else {
Paint textPaint = new Paint();
textPaint.setColor(color);
textPaint.setTextSize(130);
textPaint.setAntiAlias(true);
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setTypeface(android.graphics.Typeface.create(android.graphics.Typeface.DEFAULT, android.graphics.Typeface.BOLD));
float x = iconSize / 2f;
float y = (iconSize / 2f) - ((textPaint.descent() + textPaint.ascent()) / 2f);
canvas.drawText(letter, x, y, textPaint);
}
return Icon.createWithBitmap(bitmap);
}
private int getColorForLetter(String letter) {
int[] colors = {
0xFF1976D2, // Blue
0xFFD32F2F, // Red
0xFF388E3C, // Green
0xFFF57C00, // Orange
0xFF7B1FA2, // Purple
0xFF0097A7, // Cyan
0xFFC2185B, // Pink
0xFF455A64, // Blue Grey
0xFF6A1B9A, // Deep Purple
0xFF00796B, // Teal
0xFF512DA8, // Indigo
0xFF1565C0 // Dark Blue
};
int hash = Math.abs(letter.hashCode());
return colors[hash % colors.length];
}
}

View File

@@ -1,106 +0,0 @@
package com.streetwriters.notesnook;
import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.widget.RemoteViewsService;
import android.content.Context;
import android.widget.RemoteViews;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.streetwriters.notesnook.datatypes.Reminder;
import java.util.ArrayList;
import java.util.List;
public class ReminderViewsService extends RemoteViewsService {
static String OpenReminderId = "com.streetwriters.notesnook.OpenReminderId";
@Override
public RemoteViewsFactory onGetViewFactory(Intent intent) {
return new ReminderRemoteViewsFactory(this.getApplicationContext(), intent);
}
}
class ReminderRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
private Context context;
private List<Reminder> reminders;
public ReminderRemoteViewsFactory(Context context, Intent intent) {
this.context = context;
}
@Override
public void onCreate() {
// Initialize reminders list
reminders = new ArrayList<Reminder>();
}
@Override
public void onDataSetChanged() {
reminders.clear();
SharedPreferences preferences = context.getSharedPreferences("appPreview", Context.MODE_PRIVATE);
Gson gson = new Gson();
reminders = gson.fromJson(preferences.getString("remindersList","[]"), new TypeToken<List<Reminder>>(){}.getType());
}
@Override
public void onDestroy() {
reminders.clear();
}
@Override
public int getCount() {
return reminders.size();
}
@Override
public RemoteViews getViewAt(int position) {
Reminder reminder = reminders.get(position);
boolean useMiniLayout = reminder.getDescription() == null || reminder.getDescription().isEmpty();
RemoteViews views = new RemoteViews(context.getPackageName(), useMiniLayout ? R.layout.widget_reminder_layout_small : R.layout.widget_reminder_layout);
views.setTextViewText(R.id.reminder_title, reminder.getTitle());
if (!useMiniLayout) {
views.setTextViewText(R.id.reminder_description, reminder.getDescription());
}
views.setTextViewText(R.id.reminder_time, reminder.getFormattedTime());
final Intent fillInIntent = new Intent();
final Bundle extras = new Bundle();
extras.putString(ReminderViewsService.OpenReminderId, reminder.getId());
fillInIntent.setData(Uri.parse("https://app.notesnook.com/open_reminder?id=" + reminder.getId()));
fillInIntent.putExtra(RCTNNativeModule.IntentType, "OpenReminder");
fillInIntent.putExtras(extras);
views.setOnClickFillInIntent(R.id.reminder_item_btn, fillInIntent);
return views;
}
@Override
public RemoteViews getLoadingView() {
return null;
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public boolean hasStableIds() {
return true;
}
}

View File

@@ -1,18 +1,22 @@
package com.streetwriters.notesnook;
import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.widget.RemoteViews;
import androidx.core.widget.RemoteViewsCompat;
import com.streetwriters.notesnook.datatypes.Reminder;
import java.util.List;
public class ReminderWidgetProvider extends AppWidgetProvider {
static String NewReminder = "com.streetwriters.notesnook.NewReminder";
static String OpenReminderId = "com.streetwriters.notesnook.OpenReminderId";
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
@@ -23,20 +27,10 @@ public class ReminderWidgetProvider extends AppWidgetProvider {
}
private static Bundle getActivityOptionsBundle() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
ActivityOptions activityOptions = ActivityOptions.makeBasic();
activityOptions.setPendingIntentCreatorBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
return activityOptions.toBundle();
} else
return null;
}
static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId, RemoteViews views) {
Intent listview_intent_template = new Intent(context, MainActivity.class);
listview_intent_template.setAction(Intent.ACTION_VIEW);
PendingIntent pendingIntent = PendingIntent.getActivity(context, appWidgetId, listview_intent_template, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE, getActivityOptionsBundle());
PendingIntent pendingIntent = PendingIntent.getActivity(context, appWidgetId, listview_intent_template, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE, WidgetUtils.getActivityOptionsBundle());
views.setPendingIntentTemplate(R.id.widget_list_view, pendingIntent);
Intent new_reminder_intent = new Intent(context, MainActivity.class);
@@ -44,13 +38,31 @@ public class ReminderWidgetProvider extends AppWidgetProvider {
new_reminder_intent.setAction(Intent.ACTION_VIEW);
new_reminder_intent.putExtra(RCTNNativeModule.IntentType, "NewReminder");
new_reminder_intent.setData(Uri.parse("https://app.notesnook.com/new_reminder"));
PendingIntent pendingIntent2 = PendingIntent.getActivity(context, appWidgetId, new_reminder_intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, getActivityOptionsBundle());
PendingIntent pendingIntent2 = PendingIntent.getActivity(context, appWidgetId, new_reminder_intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, WidgetUtils.getActivityOptionsBundle());
views.setOnClickPendingIntent(R.id.add_button, pendingIntent2);
Intent list_remote_adapter_intent = new Intent(context, ReminderViewsService.class);
list_remote_adapter_intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
views.setRemoteAdapter(R.id.widget_list_view, list_remote_adapter_intent);
// The rows travel with the update itself, so there is no bound service to keep in sync and
// nothing to invalidate separately: every update redraws from the current data.
List<Reminder> reminders = WidgetUtils.getWidgetReminders(context);
RemoteViewsCompat.RemoteCollectionItems.Builder items =
new RemoteViewsCompat.RemoteCollectionItems.Builder();
for (Reminder reminder : reminders) {
items.addItem(getItemId(reminder), WidgetUtils.createReminderItem(context, reminder));
}
// Two, because a reminder without a description uses the compact row layout.
items.setViewTypeCount(2);
items.setHasStableIds(true);
RemoteViewsCompat.setRemoteAdapter(context, views, appWidgetId, R.id.widget_list_view, items.build());
views.setEmptyView(R.id.widget_list_view, R.id.empty_view);
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
/**
* Ties a row to its reminder rather than to its position, so rows keep their identity when the
* list shifts around them.
*/
private static long getItemId(Reminder reminder) {
return reminder.getId() == null ? 0 : reminder.getId().hashCode();
}
}

View File

@@ -0,0 +1,28 @@
package com.streetwriters.notesnook;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
/**
* Redraws the widgets when the clock or timezone changes.
*
* Reminder rows are described relative to the current time ("Upcoming"/"Last", "Today"/"Tomorrow"),
* and a reminder that has passed drops off the list entirely. All of that is decided when the
* widget is drawn, so moving the clock leaves the previous drawing in place: a reminder can still
* read "Upcoming" long after its time has gone by.
*
* The app normally redraws the list when a reminder notification is delivered, but that does not
* happen if the reminder never fires, which is exactly the case when the clock jumps past it.
*
* Only TIME_SET and TIMEZONE_CHANGED are handled here: DATE_CHANGED is not exempt from the
* Android 8 limits on manifest-registered implicit broadcasts, so a receiver for it would never
* run. Crossing midnight is instead picked up by the widget's own periodic update.
*/
public class WidgetTimeChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
WidgetUtils.refreshAll(context);
}
}

View File

@@ -0,0 +1,299 @@
package com.streetwriters.notesnook;
import android.app.ActivityOptions;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.format.DateUtils;
import android.util.Log;
import android.widget.RemoteViews;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.streetwriters.notesnook.datatypes.Note;
import com.streetwriters.notesnook.datatypes.Reminder;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Shared helpers for the home screen widgets.
*/
public class WidgetUtils {
static final String PREFERENCES = "appPreview";
static final String REMINDERS_KEY = "remindersList";
/**
* Every row is serialized into the widget update itself, which has to fit inside a binder
* transaction, so the list cannot grow without bound. Far more than fits on screen anyway.
*/
private static final int MAX_REMINDERS = 50;
/**
* Redraws every widget that currently exists, and drops stored notes for widgets that no
* longer do.
*
* Everything else keys off what we have stored, which is fine while the app is running but
* leaves widgets showing content that no longer exists once the store is emptied underneath
* them (clearing app data) or a widget is removed while the app is stopped (onDeleted never
* arrives). Starting from the widgets the system knows about, rather than from our own data,
* is what makes this self-correcting.
*
* NoteWidget is left alone deliberately: it is a static button with no stored state, and its
* layout depends on the size it was last given.
*/
static void refreshAll(Context context) {
AppWidgetManager manager = AppWidgetManager.getInstance(context);
int[] noteWidgetIds = manager.getAppWidgetIds(
new ComponentName(context, NotePreviewWidget.class));
removeOrphanedNotes(context, noteWidgetIds);
for (int appWidgetId : noteWidgetIds) {
NotePreviewWidget.updateAppWidget(context, manager, appWidgetId);
}
for (int appWidgetId : manager.getAppWidgetIds(
new ComponentName(context, ReminderWidgetProvider.class))) {
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_reminders);
ReminderWidgetProvider.updateAppWidget(context, manager, appWidgetId, views);
}
}
/**
* Drops stored notes whose widget is gone, so the preferences file cannot grow forever.
*/
private static void removeOrphanedNotes(Context context, int[] liveWidgetIds) {
Set<String> live = new HashSet<>();
for (int appWidgetId : liveWidgetIds) live.add(String.valueOf(appWidgetId));
SharedPreferences preferences = context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE);
SharedPreferences.Editor edit = preferences.edit();
boolean changed = false;
for (String key : preferences.getAll().keySet()) {
// Leave anything that is not a widget id alone, the reminders list included.
if (parseWidgetId(key) == null || live.contains(key)) continue;
edit.remove(key);
changed = true;
}
if (changed) edit.apply();
}
/**
* The note each note widget is showing, keyed by widget id.
*
* The preferences file mixes two things: one note per widget id, and the reminders list under
* its own key. Only numeric keys are widget notes, so anything else is skipped rather than
* being treated as a note.
*/
static Map<Integer, Note> getWidgetNotes(Context context) {
SharedPreferences preferences = context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE);
Map<Integer, Note> notes = new LinkedHashMap<>();
for (Map.Entry<String, ?> entry : preferences.getAll().entrySet()) {
Integer widgetId = parseWidgetId(entry.getKey());
if (widgetId == null) continue;
if (!(entry.getValue() instanceof String)) continue;
Note note = parseNote((String) entry.getValue());
if (note == null || note.getId() == null) continue;
notes.put(widgetId, note);
}
return notes;
}
/**
* The widget id a preferences key refers to, or null if the key is not a widget id at all.
*/
private static Integer parseWidgetId(String key) {
try {
return Integer.valueOf(key);
} catch (NumberFormatException e) {
return null;
}
}
static Note parseNote(String data) {
if (data == null || data.isEmpty()) return null;
try {
return new Gson().fromJson(data, Note.class);
} catch (Exception e) {
Log.e("NotePreviewWidget", "Could not read a stored note", e);
return null;
}
}
/**
* The reminders the app last wrote out, minus any that have now dropped out of view. Reading
* and filtering happens here so the provider can push the rows straight into the widget.
*/
static List<Reminder> getWidgetReminders(Context context) {
SharedPreferences preferences = context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE);
List<Reminder> stored = null;
try {
stored = new Gson().fromJson(preferences.getString(REMINDERS_KEY, "[]"),
new TypeToken<List<Reminder>>() {}.getType());
} catch (Exception e) {
Log.e("Reminders", "Could not read the stored reminders list", e);
}
List<Reminder> active = new ArrayList<>();
if (stored == null) return active;
for (Reminder reminder : stored) {
if (!isVisibleInWidget(reminder)) continue;
if (active.size() >= MAX_REMINDERS) {
Log.w("Reminders", "Widget list truncated to " + MAX_REMINDERS + " reminders");
break;
}
active.add(reminder);
}
return active;
}
/**
* Builds a single row of the reminders list.
*/
static RemoteViews createReminderItem(Context context, Reminder reminder) {
boolean useMiniLayout = reminder.getDescription() == null || reminder.getDescription().isEmpty();
RemoteViews views = new RemoteViews(context.getPackageName(),
useMiniLayout ? R.layout.widget_reminder_layout_small : R.layout.widget_reminder_layout);
views.setTextViewText(R.id.reminder_title, reminder.getTitle());
if (!useMiniLayout) {
views.setTextViewText(R.id.reminder_description, reminder.getDescription());
}
views.setTextViewText(R.id.reminder_time, formatReminderTime(context, reminder));
Intent fillInIntent = new Intent();
fillInIntent.setData(Uri.parse("https://app.notesnook.com/open_reminder?id=" + reminder.getId()));
fillInIntent.putExtra(RCTNNativeModule.IntentType, "OpenReminder");
fillInIntent.putExtra(ReminderWidgetProvider.OpenReminderId, reminder.getId());
views.setOnClickFillInIntent(R.id.reminder_item_btn, fillInIntent);
return views;
}
/**
* Options attached to the PendingIntents our widgets hand to the launcher, opting the creator
* (us) in to background activity starts so a tap on the widget can bring up an activity.
*
* MODE_BACKGROUND_ACTIVITY_START_ALLOWED is deprecated since API 36 and Android 17 extends the
* background activity launch restrictions to IntentSender, so on API 36+ we use the narrower
* MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE instead. That is enough for widgets: the
* sender is the launcher, which is visible whenever the user taps the widget.
*/
static Bundle getActivityOptionsBundle() {
ActivityOptions activityOptions;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) {
activityOptions = ActivityOptions.makeBasic();
activityOptions.setPendingIntentCreatorBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
activityOptions = ActivityOptions.makeBasic();
activityOptions.setPendingIntentCreatorBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
} else {
return null;
}
return activityOptions.toBundle();
}
/**
* How long a reminder keeps its place in the list after going off, so the user can see that it
* happened rather than watching it vanish. Must match RECENTLY_PASSED_WINDOW in
* services/notifications.ts, which decides what gets written out in the first place.
*/
private static final long RECENTLY_PASSED_WINDOW_MS = TimeUnit.HOURS.toMillis(3);
/**
* Whether a reminder should still be drawn.
*
* We re-check here rather than trusting the stored list because that list is only rewritten
* while the app runs. This is what actually retires a reminder once its grace period is up:
* every redraw re-evaluates it against the current time.
*/
static boolean isVisibleInWidget(Reminder reminder) {
if (reminder == null) return false;
if (reminder.isDisabled()) return false;
long now = System.currentTimeMillis();
if (reminder.getSnoozeUntil() > now) return true;
if (!"once".equals(reminder.getMode())) return true;
long triggerDate = reminder.getTriggerDate() > 0 ? reminder.getTriggerDate() : reminder.getDate();
return triggerDate > now - RECENTLY_PASSED_WINDOW_MS;
}
/**
* Builds the label shown under a reminder. The app sends us the absolute trigger time plus the
* parts that never change ("5:00 PM", "12-05-2026, 5:00 PM"); everything that depends on the
* current time is decided here so it stays right as the widget redraws.
*
* Falls back to the pre-formatted string for lists written by an older version of the app.
*/
static String formatReminderTime(Context context, Reminder reminder) {
long triggerDate = reminder.getTriggerDate();
String timeOfDay = reminder.getFormattedTimeOfDay();
if (triggerDate <= 0 || timeOfDay == null || timeOfDay.isEmpty()) {
return reminder.getFormattedTime();
}
if ("permanent".equals(reminder.getMode())) {
return context.getString(R.string.reminder_ongoing);
}
long now = System.currentTimeMillis();
if (reminder.getSnoozeUntil() > now) {
return context.getString(R.string.reminder_snoozed_until, timeOfDay);
}
String text;
long dayOffset = daysFromToday(triggerDate, now);
if (dayOffset == 0) {
text = context.getString(R.string.reminder_today, timeOfDay);
} else if (dayOffset == 1) {
text = context.getString(R.string.reminder_tomorrow, timeOfDay);
} else if (dayOffset == -1) {
text = context.getString(R.string.reminder_yesterday, timeOfDay);
} else {
text = reminder.getFormattedDateTime();
if (text == null || text.isEmpty()) return reminder.getFormattedTime();
}
return context.getString(
triggerDate <= now ? R.string.reminder_last : R.string.reminder_upcoming, text);
}
/**
* Calendar days between two instants. Compares midnights rather than subtracting the raw
* difference so that "tomorrow" is still tomorrow across a DST change or just before midnight.
*/
private static long daysFromToday(long time, long now) {
long target = startOfDay(time);
long today = startOfDay(now);
return Math.round((target - today) / (double) DateUtils.DAY_IN_MILLIS);
}
private static long startOfDay(long time) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(time);
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
return calendar.getTimeInMillis();
}
}

View File

@@ -2,13 +2,14 @@ package com.streetwriters.notesnook.datatypes;
import androidx.annotation.Keep;
import java.util.concurrent.TimeUnit;
@Keep
public class Reminder extends BaseItem {
private String title;
private String description;
private String formattedTime;
private String formattedTimeOfDay; // e.g. "5:00 PM"
private String formattedDateTime; // e.g. "12-05-2026, 5:00 PM"
private long triggerDate; // absolute time this reminder next fires
private String priority; // "silent", "vibrate", "urgent"
private long date;
private String mode; // "repeat", "once", "permanent"
@@ -107,32 +108,27 @@ public class Reminder extends BaseItem {
this.formattedTime = formattedTime;
}
public String formatTime(long timeInMillis) {
long currentTime = System.currentTimeMillis();
long diff = timeInMillis - currentTime;
public String getFormattedTimeOfDay() {
return formattedTimeOfDay;
}
if (diff < TimeUnit.MINUTES.toMillis(1)) {
return "in " + (diff / 1000) + " seconds";
} else if (diff < TimeUnit.HOURS.toMillis(1)) {
long minutes = TimeUnit.MILLISECONDS.toMinutes(diff);
return "in " + minutes + " minute" + (minutes > 1 ? "s" : "");
} else if (diff < TimeUnit.DAYS.toMillis(1)) {
long hours = TimeUnit.MILLISECONDS.toHours(diff);
return "in " + hours + " hour" + (hours > 1 ? "s" : "");
} else if (diff < TimeUnit.DAYS.toMillis(2)) {
return "tomorrow";
} else if (diff < TimeUnit.DAYS.toMillis(7)) {
long days = TimeUnit.MILLISECONDS.toDays(diff);
return "in " + days + " day" + (days > 1 ? "s" : "");
} else if (diff < TimeUnit.DAYS.toMillis(30)) {
long weeks = TimeUnit.MILLISECONDS.toDays(diff) / 7;
return "in " + weeks + " week" + (weeks > 1 ? "s" : "");
} else if (diff < TimeUnit.DAYS.toMillis(365)) {
long months = TimeUnit.MILLISECONDS.toDays(diff) / 30;
return "in " + months + " month" + (months > 1 ? "s" : "");
} else {
long years = TimeUnit.MILLISECONDS.toDays(diff) / 365;
return "in " + years + " year" + (years > 1 ? "s" : "");
}
public void setFormattedTimeOfDay(String formattedTimeOfDay) {
this.formattedTimeOfDay = formattedTimeOfDay;
}
public String getFormattedDateTime() {
return formattedDateTime;
}
public void setFormattedDateTime(String formattedDateTime) {
this.formattedDateTime = formattedDateTime;
}
public long getTriggerDate() {
return triggerDate;
}
public void setTriggerDate(long triggerDate) {
this.triggerDate = triggerDate;
}
}

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 B

View File

@@ -2,6 +2,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/background"/>
<stroke android:width="0dp" android:color="#B1BCBE" />
<corners android:radius="10dp"/>
<corners android:radius="@dimen/widget_background_radius"/>
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
</shape>

View File

@@ -2,8 +2,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:theme="@style/ThemeOverlay.Notesnook.AppWidgetContainer">
android:background="@android:color/transparent">
<LinearLayout
android:layout_width="match_parent"

View File

@@ -2,8 +2,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:theme="@style/ThemeOverlay.Notesnook.AppWidgetContainer">
android:background="@android:color/transparent">
<LinearLayout
android:layout_width="match_parent"
@@ -25,7 +24,7 @@
android:textColor="@color/text"
android:textSize="16sp"
android:textStyle="bold"
android:text="Widget unconfigured" />
android:text="@string/widget_note_unconfigured_title" />
<TextView
android:id="@+id/widget_body"
android:layout_width="wrap_content"
@@ -34,7 +33,7 @@
android:layout_marginLeft="8dp"
android:textColor="@color/text"
android:textSize="14sp"
android:text="Configure this widget to show a note here." />
android:text="@string/widget_note_unconfigured_body" />
</LinearLayout>
</RelativeLayout>

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?><!--
Shown in the widget picker only. Mirrors note_widget.xml, but with sample content, since the
real layout has nothing to show until the widget has been configured.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:background="@drawable/layout_bg"
android:elevation="5dp"
android:orientation="vertical"
android:paddingHorizontal="10dp"
android:paddingVertical="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="8dp"
android:text="@string/widget_preview_note_title"
android:textColor="@color/text"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="8dp"
android:text="@string/widget_preview_note_body"
android:textColor="@color/text"
android:textSize="14sp" />
</LinearLayout>
</RelativeLayout>

View File

@@ -1,8 +0,0 @@
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="No upcoming reminders"
android:textSize="16sp"
android:textColor="@android:color/darker_gray"
android:gravity="center" />

View File

@@ -12,22 +12,23 @@
android:orientation="horizontal"
android:paddingHorizontal="12dp"
android:layout_gravity="center"
android:paddingTop="12dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_marginTop="2dp"
android:textSize="16sp"
android:textSize="15sp"
android:textStyle="bold"
android:textColor="@color/text"
android:text="Upcoming Reminders"/>
android:text="Reminders"/>
<ImageButton
android:layout_width="35dp"
android:layout_width="25dp"
android:id="@+id/add_button"
android:layout_height="35dp"
android:layout_height="25dp"
android:layout_alignParentRight="true"
android:background="@drawable/ic_newnote" />
</RelativeLayout>
@@ -57,7 +58,8 @@
android:layout_height="match_parent"
android:textAlignment="center"
android:gravity="center"
android:text="Tap on + to add reminder"/>
android:textColor="@color/text"
android:text="@string/widget_reminders_empty"/>
</LinearLayout>

View File

@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?><!--
Shown in the widget picker only. Mirrors widget_reminders.xml, but with the list replaced by
sample rows, since an adapter-backed list renders empty in the picker.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/layout_bg"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingHorizontal="12dp"
android:paddingTop="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_marginTop="2dp"
android:text="Reminders"
android:textColor="@color/text"
android:textSize="16sp"
android:textStyle="bold" />
<ImageButton
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_alignParentRight="true"
android:background="@drawable/ic_newnote"
android:contentDescription="@string/add_widget" />
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="2dp"
android:layout_marginBottom="8dp"
android:background="@color/border" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="12dp"
android:paddingBottom="12dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/widget_preview_reminder_title"
android:textColor="@color/text"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:text="@string/widget_preview_reminder_time"
android:textColor="@color/text"
android:textSize="12sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/widget_preview_reminder_title_alt"
android:textColor="@color/text"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/widget_preview_reminder_time_alt"
android:textColor="@color/text"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>

View File

@@ -1,9 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="light_blue_50">#FFE1F5FE</color>
<color name="light_blue_200">#FF81D4FA</color>
<color name="light_blue_600">#FF039BE5</color>
<color name="light_blue_900">#FF01579B</color>
<color name="bootsplash_background">#1f1f1f</color>
<color name="background">#1D1D1D</color>
<color name="border">#2E2E2E</color>

View File

@@ -17,7 +17,7 @@
</style>
<!-- BootTheme should inherit from Theme.SplashScreen -->
<style name="BootTheme" parent="Theme.SplashScreen">
<style name="BootTheme" parent="Theme.BootSplash.EdgeToEdge">
<item name="bootSplashBackground">@color/bootsplash_background</item>
<item name="bootSplashLogo">@mipmap/bootsplash_logo_dark</item>
<item name="android:navigationBarColor">@color/bootsplash_background</item>

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