Compare commits

...
Author SHA1 Message Date
StandardNotes CI ef6e971592 chore(release): publish
- @standardnotes/[email protected]
2022-12-28 07:47:42 +00:00
Karol Sójko 1c48dd135c fix(docs): update self-hosted instructions 2022-12-28 08:20:16 +01:00
StandardNotes CI 495786514e chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].7
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected].1
2022-12-27 20:17:11 +00:00
Aman Harwara cfd6cf5932 refactor(dev-only): disable note list keyboard nav when showing files table view 2022-12-28 01:20:08 +05:30
Aman Harwara 8c5944f233 refactor(dev-only): allow sorting files table view by file size 2022-12-28 01:14:57 +05:30
Aman Harwara 2b63484758 refactor(dev-only): use file view display prefs for files table 2022-12-28 00:49:09 +05:30
StandardNotes CI ccb15faff5 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].0
2022-12-27 18:29:52 +00:00
Aman Harwara dc33aef660 feat: Allow changing per-view display preferences for system views (#2120) 2022-12-27 23:33:08 +05:30
StandardNotes CI 771758fcd4 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected]
2022-12-27 15:59:31 +00:00
Aman Harwara 3fbe28e068 refactor(dev-only): import tools (#2121) 2022-12-27 21:03:01 +05:30
Karol Sójko e67240821c chore: fix triggered workflow for e2e test suites 2022-12-27 15:52:10 +01:00
StandardNotes CI d7c6ef6134 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2022-12-27 13:20:25 +00:00
Karol Sójko 8311271a49 fix(snjs): skip workspaces tests as they are a pending backlog feature 2022-12-27 13:49:02 +01:00
StandardNotes CI 4e61d9b3e3 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].9
2022-12-27 10:16:51 +00:00
Karol Sójko dd41082e71 fix(snjs): add error log upon not being able to reach the mock event publisher 2022-12-27 10:48:55 +01:00
StandardNotes CI adc6e9a353 chore(release): publish
- @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].8
2022-12-26 13:04:05 +00:00
Aman Harwara bfd9f37331 Merge branch 'main' of github.com:standardnotes/app 2022-12-26 18:08:00 +05:30
Aman Harwara 044a4fc36b fix: Fixed issue where right-clicking system view would open "Edit smart view" modal 2022-12-26 18:07:53 +05:30
StandardNotes CI fe4841c631 chore(release): publish
- @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].7
2022-12-26 11:18:12 +00:00
Aman Harwara 14a98b5736 Merge branch 'main' of github.com:standardnotes/app 2022-12-26 16:22:36 +05:30
Aman Harwara 326e1a455b feat: Clicking the link option in Super notes will automatically add the selected text as the link 2022-12-26 16:22:26 +05:30
StandardNotes CI c3c8c2b9ce chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].6
2022-12-26 10:25:01 +00:00
Aman Harwara 5e79cf90f4 Merge branch 'main' of github.com:standardnotes/app 2022-12-26 15:28:13 +05:30
Aman Harwara e798b71b9f fix: Fixed issue where adding child tag to note would also add all parent tags even though the related setting was turned off 2022-12-26 15:28:02 +05:30
StandardNotes CI afd89c387f chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].5
2022-12-26 07:31:06 +00:00
Aman Harwara 6bf6924a8b refactor(dev-only): table deferred loading and aria improvements 2022-12-26 12:34:06 +05:30
StandardNotes CI 0f7b1539e0 chore(release): publish
- @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].4
2022-12-24 18:59:31 +00:00
Aman Harwara e1c21d5fbd Merge branch 'main' of github.com:standardnotes/app 2022-12-25 00:02:45 +05:30
Aman Harwara 52d0fed437 refactor(dev-only): add animation to file view search box and fix search bar on mobile 2022-12-25 00:02:36 +05:30
StandardNotes CI caee8f1c6f chore(release): publish
- @standardnotes/[email protected].2
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
2022-12-24 18:23:07 +00:00
Aman Harwara 40194d010f refactor(dev-only): add blurred bg to file table row actions 2022-12-24 23:27:39 +05:30
Aman Harwara 0373bf9a2d Merge branch 'main' of github.com:standardnotes/app 2022-12-24 23:01:22 +05:30
Aman Harwara d7eb43401a refactor(dev-only): fixed link item bubble bg color and text 2022-12-24 23:01:13 +05:30
StandardNotes CI a5a866a62a chore(release): publish
- @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
2022-12-24 16:55:41 +00:00
Aman Harwara 6090962b2c Merge branch 'main' of github.com:standardnotes/app 2022-12-24 21:56:48 +05:30
Aman Harwara a9445dfcca refactor(dev-only): file list item style 2022-12-24 21:56:38 +05:30
Aman Harwara b3fe769a60 refactor(dev-only): hide non-applicable options in display options menu 2022-12-24 21:55:24 +05:30
Aman Harwara fc6d28d166 refactor(dev-only): show file local backup status in table 2022-12-24 21:54:53 +05:30
Aman Harwara 03eac4353d refactor(dev-only): align text-only cells correctly in table 2022-12-24 21:54:18 +05:30
StandardNotes CI 58d84c47c1 chore(release): publish
- @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
2022-12-24 15:38:00 +00:00
Aman Harwara 577318e208 feat: Persist collapsible block state in Super notes (#2119) 2022-12-24 20:35:41 +05:30
StandardNotes CI b8da877804 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
2022-12-24 12:40:15 +00:00
Aman Harwara 17f4d20fb5 Merge branch 'main' of github.com:standardnotes/app 2022-12-24 17:43:35 +05:30
Aman Harwara 4f07b915f0 feat: Added FileSend links to general account menu and preferences menu 2022-12-24 17:43:26 +05:30
StandardNotes CI f2f61b18e2 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2022-12-24 12:11:23 +00:00
Aman Harwara 9fb506e532 refactor(dev-only): show file link row action in table view 2022-12-24 17:09:26 +05:30
Aman Harwara 3b64acc290 refactor(dev-only): files table view attached column wrapping 2022-12-24 17:05:13 +05:30
Aman Harwara e6824a3414 chore: remove comment 2022-12-24 17:00:17 +05:30
Aman Harwara 08985bb5c8 refactor(dev-only): file preview navigation from table view 2022-12-24 16:58:46 +05:30
Aman Harwara 77cb6e7891 refactor(dev-only): files table view 2022-12-24 16:48:55 +05:30
Aman Harwara 7e7c5e14ab refactor(dev-only): files table view (#2118) 2022-12-24 16:46:18 +05:30
StandardNotes CI 631be2e494 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2022-12-23 18:11:05 +00:00
Mo fe36009482 fix: Fixed issue where navigating to a Daily Notebook on iOS would cause editor panel to be offscreen 2022-12-23 11:46:22 -06:00
StandardNotes CI e77e852801 chore(release): publish
- @standardnotes/[email protected].2
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2022-12-23 17:41:00 +00:00
Aman Harwara c59d824240 fix: Made Moments-related toast messages dismissable 2022-12-23 22:44:06 +05:30
Aman Harwara 2f3bc9b85b chore: Update Lexical to 0.7.5 (#2117) 2022-12-23 22:22:19 +05:30
StandardNotes CI 1ec173dcd6 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2022-12-23 09:59:41 +00:00
Aman Harwara 35b00737bf fix: Fixed issue where Super note would convert whole line into tag even after closing menu 2022-12-23 15:03:18 +05:30
StandardNotes CI f3e147f4c9 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].9
 - @standardnotes/[email protected]
2022-12-22 15:23:29 +00:00
Mo 63fdb328cb chore: adjust mobile price copy 2022-12-22 08:54:02 -06:00
Aman Harwara 94c280256e fix: Disabled Moments photo capture when app is passcode or biometrics locked (#2114) 2022-12-22 20:07:48 +05:30
StandardNotes CI cf0dc19f63 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].9
 - @standardnotes/[email protected].8
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected].9
2022-12-21 13:48:03 +00:00
Karol Sójko 2696b07759 fix(api): handling expired token responses (#2113) 2022-12-21 14:22:20 +01:00
StandardNotes CI 2cc5afb00a chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].8
 - @standardnotes/[email protected].7
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected].8
2022-12-20 19:24:12 +00:00
Aman Harwara 1828f80ba5 chore: removed unnecessary warning 2022-12-21 00:27:16 +05:30
Aman Harwara 9c260c02f1 Merge branch 'main' of github.com:standardnotes/app 2022-12-20 23:58:31 +05:30
Aman Harwara e4b3028d67 fix: Increased interval between Moments capturing photos 2022-12-20 23:54:05 +05:30
Aman Harwara eceb46bed7 fix: Fixed issue where Moments-related toasts would not get correctly dismissed 2022-12-20 23:52:43 +05:30
StandardNotes CI b9e0d0d154 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].7
2022-12-20 17:03:06 +00:00
Aman Harwara a7add80633 fix: Added potential fix for issue where some video would show as not supported 2022-12-20 22:02:30 +05:30
StandardNotes CI 0941183c8b chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].6
2022-12-20 13:58:49 +00:00
Aman Harwara c94035c1d6 feat-dev(wip): files table view (#2100) 2022-12-20 19:01:24 +05:30
StandardNotes CI 343c39e873 chore(release): publish
- @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].5
2022-12-19 17:43:16 +00:00
Mo 889f245ab9 chore: upgrade web deps and lexical (#2094) 2022-12-19 11:11:32 -06:00
Tony e8ada146e1 docs: update edit link (#2104) 2022-12-19 11:09:16 -06:00
StandardNotes CI c9c5e915ce chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected].4
2022-12-19 15:22:56 +00:00
Karol Sójko a1ec13d0b0 fix(snjs): skip subscriptions test suite until fixed 2022-12-19 15:52:32 +01:00
Aman Harwara 31bb03943d refactor: de-couple linking controller from active item (#2108) 2022-12-19 20:08:57 +05:30
StandardNotes CI 2b84c242f7 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected].3
2022-12-19 13:49:02 +00:00
Karol Sójko b8bf8ca112 fix(snjs): mark flaky subscription test as skipped until fixed 2022-12-19 14:23:34 +01:00
StandardNotes CI c914b88e2d chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].7
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].2
2022-12-19 10:06:43 +00:00
Aman Harwara 0169224bf2 chore(ci): trigger publish (skip e2e) 2022-12-19 15:22:08 +05:30
Aman Harwara 4049b19170 Merge branch 'main' of github.com:standardnotes/app 2022-12-19 14:44:46 +05:30
Aman Harwara df4148f824 fix: Fixed issue where the first item in item link autcomplete would not be correctly focused (skip e2e) 2022-12-19 14:44:34 +05:30
Aman Harwara 91f3295cff fix: Fixed white screen issue on mobile 2022-12-19 13:36:16 +05:30
StandardNotes CI c9078f374b chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].1
2022-12-19 07:53:04 +00:00
Karol Sójko bb4f1ff099 fix(snjs): refreshing sessions (#2106)
* fix(snjs): refreshing sessions

* fix(snjs): bring back all tests

* fix(snjs): passing session tokens values

* fix(api): remove redundant specs

* fix(snjs): add projecting sessions to storage values

* fix(snjs): deps tree

* fix(snjs): bring back subscription tests

* fix(snjs): remove only tag for migration tests

* fix(snjs): session specs
2022-12-19 08:28:10 +01:00
StandardNotes CI abdaec89b7 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
2022-12-16 20:09:12 +00:00
Aman Harwara f559442a67 feat: Added "Whats New" indicator to Preferences button (#2107) 2022-12-17 00:35:25 +05:30
StandardNotes CI 51aed0a518 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].4
2022-12-15 16:01:35 +00:00
Mo 35bb671e31 build: only unpack keytar for macOS to avoid Windows runtime issue (skip e2e) 2022-12-15 09:47:17 -06:00
StandardNotes CI d76b190aba chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
2022-12-15 09:54:12 +00:00
Aman Harwara d6fd85e451 fix(desktop): Fixed issue where the "Record video" modal would error after stopping recording 2022-12-15 14:55:31 +05:30
StandardNotes CI c90087462f chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
2022-12-14 16:57:03 +00:00
Mo 58838a44cc ci: trigger desktop build 2022-12-14 10:24:20 -06:00
StandardNotes CI 0223800fdf chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
2022-12-14 15:55:51 +00:00
Mo 318b1fdb0f build: remove entitlements no longer required by newer versions of Electron 2022-12-14 08:54:56 -06:00
StandardNotes CI fb517a91b0 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].0
2022-12-13 17:15:10 +00:00
Mo c7b158eb64 ci(temp): skip subscription tests 2022-12-13 10:50:44 -06:00
Aman Harwara d4b63e4ea6 feat: Ability to record videos and capture photos directly in app by selecting + in Files view (#2095) 2022-12-13 21:41:41 +05:30
Karol Sójko cd8596b14e Revert "fix(snjs): add sleep intervals for subscription sharing tests"
This reverts commit 4e576d7526.
2022-12-13 06:39:23 +01:00
Karol Sójko 6c11766051 chore: upgrade trigger-workflow-and-wait procedure 2022-12-12 13:25:14 +01:00
Karol Sójko 7f41564328 fix(snjs): remove @newrelic/native-metrics by upgrading @standardnotes/settings 2022-12-12 13:23:20 +01:00
Karol Sójko 4e576d7526 fix(snjs): add sleep intervals for subscription sharing tests 2022-12-12 13:22:53 +01:00
StandardNotes CI 317c6555d2 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
2022-12-08 19:53:54 +00:00
Aman Harwara 220f62665e feat: Added "Open" button to file upload success notification (#2091) 2022-12-09 00:58:32 +05:30
StandardNotes CI 1dc9086257 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
2022-12-08 17:37:43 +00:00
Mo d149b500ee feat: Add current Writing Streak to Daily Notebooks (#2093) 2022-12-08 11:11:13 -06:00
StandardNotes CI ad70bea368 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected].1
2022-12-08 16:09:41 +00:00
Aman Harwara 3bcd969f83 chore: remove unnecessary conditions 2022-12-08 21:12:45 +05:30
Aman Harwara d4af612fea fix: Fixed issue where passcode lock setting was not visible on mobile 2022-12-08 21:00:54 +05:30
Aman Harwara b068b814f2 refactor: allow opting out of toast pause on window blur behavior 2022-12-08 20:49:13 +05:30
StandardNotes CI 51de012236 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
2022-12-08 03:35:46 +00:00
Mo 2694c2f1a9 refactor: class-based photo recorder for Moments 2022-12-07 21:09:23 -06:00
Mo d3609853c6 fix: alignment of Loading text in What's New screen 2022-12-07 20:37:53 -06:00
Mo a7ac715499 feat: Moments will now create a link with the current open note, so you can look back on what you were working on during a moment 2022-12-07 20:34:17 -06:00
StandardNotes CI 5ac93a6bd8 chore(release): publish
- @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected].0
2022-12-07 17:40:15 +00:00
Mo be4cc4e605 feat: add ability to convert selection in Super to bulleted or numbered list 2022-12-07 11:14:03 -06:00
StandardNotes CI 2fa3fec9ab chore(release): publish
- @standardnotes/[email protected]
2022-12-07 15:08:32 +00:00
Mo 059ee9ea36 chore: update documentation 2022-12-07 08:39:07 -06:00
Mo 66d08895fe chore: update documentation 2022-12-07 08:35:47 -06:00
StandardNotes CI 518643dc71 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected].1
2022-12-07 13:03:02 +00:00
Aman Harwara 5a0c005e8c fix: Fixed issue about note UUID not being selectable in menu 2022-12-07 18:01:07 +05:30
Aman Harwara 45cdab6f6c fix: Fixed workspace menu item rename issue 2022-12-07 17:46:49 +05:30
StandardNotes CI 46245b5569 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
2022-12-07 04:27:17 +00:00
Mo 8b103307cb fix: improve variance between Super header font sizes and weights 2022-12-06 22:01:44 -06:00
Mo a70d1009a3 feat: add Indent/Outdent options in Super when on mobile 2022-12-06 21:56:05 -06:00
Mo a5b1ff9595 fix: Fixed issue where entering a space or punctuation in Super autocomplete would dismiss menu 2022-12-06 21:43:45 -06:00
Mo d691d7a3b3 fix: Fixed issue where Dark theme could be called 'Focus' in Appearance preferences 2022-12-06 21:13:04 -06:00
StandardNotes CI 71675dc6a2 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
2022-12-06 19:39:07 +00:00
Aman Harwara 6a1b6cef1b fix: Fixed error when trying to upload file 2022-12-07 00:42:03 +05:30
Karol Sójko 3d8c12a789 fix: invalidate cache on web deploy for both .com and .org domains 2022-12-06 09:34:45 +01:00
StandardNotes CI 370aaecfb8 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].2
2022-12-06 01:52:37 +00:00
Mo 68bfb2ff68 chore: fix test 2022-12-05 19:25:46 -06:00
Mo a66bb08c3b fix: fixed issue where popover menus would open with incorrect positioning on desktop 2022-12-05 17:34:41 -06:00
Mo ccde207de8 chore: add deprecation notice on dot org site 2022-12-05 17:20:47 -06:00
Mo d3262b0f01 chore: fix permission 2022-12-05 17:05:34 -06:00
Mo ffbc984c58 fix: fixed issue where camera was not accessible for Moments on Android 2022-12-05 17:01:04 -06:00
StandardNotes CI 979163bae7 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
2022-12-05 22:15:54 +00:00
Mo c928f8a281 fix: fixes issue where add button would not be visible at times on iOS (#2087)
* fix: fixes issue where add button would not be visible at times on iOS

* fix: fixes issue where screens could not be navigated with certain window sizes
2022-12-05 15:50:19 -06:00
StandardNotes CI 0e10b347b6 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected].9
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].0
2022-12-05 16:06:51 +00:00
Mo caf2c4a876 feat: add Super note type to list of note types (#2086) 2022-12-05 09:38:42 -06:00
Mo 1d22365086 fix: fixes issue where first change was ignored with Super notes 2022-12-05 07:56:10 -06:00
Mo bd5ccbfab6 refactor: avoid deallocing note controller until local propagation complete 2022-12-05 07:53:21 -06:00
StandardNotes CI 46b19d8caf chore(release): publish
- @standardnotes/[email protected].9
 - @standardnotes/[email protected].8
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
2022-12-05 13:22:42 +00:00
Mo a6a0d615c5 fix: fixes issue where toggled-off panes would reappear after making selection 2022-12-05 06:55:39 -06:00
StandardNotes CI f3203e2e80 chore(release): publish
- @standardnotes/[email protected].8
 - @standardnotes/[email protected].7
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
2022-12-05 09:41:22 +00:00
Aman Harwara e0edabb4b9 Merge branch 'main' of github.com:standardnotes/app 2022-12-05 14:17:36 +05:30
Aman Harwara e68c708628 feat: Added keyboard shortcut to toggle dark theme (Ctrl/Cmd+Shift+D) 2022-12-05 14:17:29 +05:30
StandardNotes CI e4f3f26937 chore(release): publish
- @standardnotes/[email protected].7
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
2022-12-05 08:34:49 +00:00
Aman Harwara 8575b3e714 feat: Show preferences keyboard shortcut in tooltip 2022-12-05 13:37:21 +05:30
Aman Harwara 7c261af230 feat: Added keyboard shortcut (Ctrl/Cmd+,) for opening preferences 2022-12-05 13:34:34 +05:30
Aman Harwara 71e20f2a2f fix: Fixed purchase flow padding 2022-12-05 13:18:22 +05:30
StandardNotes CI 3855b9f767 chore(release): publish
- @standardnotes/[email protected].6
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].5
2022-12-04 11:47:50 +00:00
Aman Harwara 5c29e22ea4 Merge branch 'main' of github.com:standardnotes/app 2022-12-04 16:49:55 +05:30
Aman Harwara 07cb00a815 fix(mobile): Fixed issue where pressing the back button on editor screen wouldn't switch back to the items list on Android 2022-12-04 16:49:46 +05:30
StandardNotes CI 4f2ee4b56f chore(release): publish
- @standardnotes/[email protected].5
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].4
2022-12-04 06:06:27 +00:00
Aman Harwara 90e10c76f7 refactor: separate selectAndUploadFile and uploadFile functions (#2085) 2022-12-04 11:11:37 +05:30
StandardNotes CI 2175c9cdcb chore(release): publish
- @standardnotes/[email protected].4
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
2022-12-02 17:31:37 +00:00
Mo 8c41f0ecc3 fix: fixes issue where Moments camera wouldn't properly start on desktop app (#2084) 2022-12-02 11:05:32 -06:00
StandardNotes CI c8c166d2a0 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].9
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].2
2022-12-02 16:44:12 +00:00
Aman Harwara 6cacdd8c58 fix(mobile): Fixes issue where file upload doesn't work with immediate biometrics (#2083) 2022-12-02 21:48:56 +05:30
Mo b5b6192b60 fix: fixes issue where files imported from another account could not be deleted (#2082) 2022-12-02 10:13:25 -06:00
StandardNotes CI 635e2d9150 chore(release): publish
- @standardnotes/[email protected].2
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
2022-12-02 15:59:51 +00:00
Aman Harwara 0c23e4d36c fix(mobile): PDF preview causing loop when biometrics are set to Immediate (#2080) 2022-12-02 21:02:52 +05:30
337 changed files with 8099 additions and 3248 deletions
+4 -4
View File
@@ -64,14 +64,14 @@ jobs:
- name: Run E2E test suite
if: "${{ contains(github.event.head_commit.message, 'skip e2e') == false }}"
uses: convictional/trigger-workflow-and-wait@v1.6.1
uses: convictional/trigger-workflow-and-wait@master
with:
owner: standardnotes
repo: e2e
repo: self-hosted
github_token: ${{ secrets.CI_PAT_TOKEN }}
workflow_file_name: testing-with-stable-server.yml
workflow_file_name: testing-with-updating-client-and-server.yml
wait_interval: 30
client_payload: '{"image_tag": "${{ github.sha }}"}'
client_payload: '{"snjs_image_tag": "${{ github.sha }}"}'
propagate_failure: true
trigger_workflow: true
wait_workflow: true
+4 -4
View File
@@ -40,14 +40,14 @@ jobs:
docker tag standardnotes/snjs:${{ github.sha }} standardnotes/snjs:test
docker push standardnotes/snjs:test
- name: Run E2E test suite
uses: convictional/trigger-workflow-and-wait@v1.6.1
uses: convictional/trigger-workflow-and-wait@master
with:
owner: standardnotes
repo: e2e
repo: self-hosted
github_token: ${{ secrets.CI_PAT_TOKEN }}
workflow_file_name: testing-with-stable-server.yml
workflow_file_name: testing-with-updating-client-and-server.yml
wait_interval: 30
client_payload: '{"image_tag": "${{ github.sha }}"}'
client_payload: '{"snjs_image_tag": "${{ github.sha }}"}'
propagate_failure: true
trigger_workflow: true
wait_workflow: true
+10 -2
View File
@@ -45,10 +45,18 @@ jobs:
aws-region: us-east-1
- name: Deploy static site to S3 bucket
run: aws s3 sync packages/web/dist/ s3://app.standardnotes.com --delete
- name: Invalidate CloudFront Cache
- name: Invalidate CloudFront Cache on .COM
uses: chetan/invalidate-cloudfront-action@master
env:
DISTRIBUTION: ${{ secrets.WEBAPP_CLOUDFRONT_DISTRIBUTION_ID }}
DISTRIBUTION: ${{ secrets.WEBAPP_CLOUDFRONT_COM_DISTRIBUTION_ID }}
PATHS: '/*'
AWS_REGION: 'us-east-1'
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Invalidate CloudFront Cache on .ORG
uses: chetan/invalidate-cloudfront-action@master
env:
DISTRIBUTION: ${{ secrets.WEBAPP_CLOUDFRONT_ORG_DISTRIBUTION_ID }}
PATHS: '/*'
AWS_REGION: 'us-east-1'
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+36
View File
@@ -3,6 +3,42 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.20.24](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/api
## [1.20.23](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/api
## [1.20.22](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-21)
### Bug Fixes
* **api:** handling expired token responses ([#2113](https://github.com/standardnotes/app/issues/2113)) ([2696b07](https://github.com/standardnotes/app/commit/2696b07759104fb1d40781897f69e63133ad824a))
## [1.20.21](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-19)
**Note:** Version bump only for package @standardnotes/api
## [1.20.20](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-19)
### Bug Fixes
* **snjs:** refreshing sessions ([#2106](https://github.com/standardnotes/app/issues/2106)) ([bb4f1ff](https://github.com/standardnotes/app/commit/bb4f1ff0990eb5a6f64e76a699fb95540cffdbb4))
## [1.20.19](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-08)
**Note:** Version bump only for package @standardnotes/api
## [1.20.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
**Note:** Version bump only for package @standardnotes/api
## [1.20.17](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
**Note:** Version bump only for package @standardnotes/api
## [1.20.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
**Note:** Version bump only for package @standardnotes/api
-8
View File
@@ -6,12 +6,4 @@ module.exports = {
transform: {
'^.+\\.tsx?$': ['ts-jest', { tsconfig: 'tsconfig.json' }],
},
coverageThreshold: {
global: {
branches: 22,
functions: 69,
lines: 67,
statements: 67
}
}
};
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api",
"version": "1.20.16",
"version": "1.20.24",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -23,7 +23,7 @@
"build": "tsc -p tsconfig.json",
"lint": "eslint src --ext .ts",
"lint:fix": "eslint src --ext .ts --fix",
"test": "jest spec --coverage"
"test": "jest spec --coverage --passWithNoTests"
},
"devDependencies": {
"@types/jest": "^29.2.3",
@@ -37,6 +37,7 @@
},
"dependencies": {
"@standardnotes/common": "^1.45.0",
"@standardnotes/domain-core": "^1.11.0",
"@standardnotes/encryption": "workspace:*",
"@standardnotes/models": "workspace:*",
"@standardnotes/responses": "workspace:*",
@@ -1,211 +0,0 @@
import { type Invitation } from '@standardnotes/models'
import { SubscriptionInviteAcceptResponse } from '../../Response/Subscription/SubscriptionInviteAcceptResponse'
import { SubscriptionInviteCancelResponse } from '../../Response/Subscription/SubscriptionInviteCancelResponse'
import { SubscriptionInviteListResponse } from '../../Response/Subscription/SubscriptionInviteListResponse'
import { SubscriptionInviteResponse } from '../../Response/Subscription/SubscriptionInviteResponse'
import { SubscriptionServerInterface } from '../../Server/Subscription/SubscriptionServerInterface'
import { SubscriptionApiOperations } from './SubscriptionApiOperations'
import { SubscriptionApiService } from './SubscriptionApiService'
describe('SubscriptionApiService', () => {
let subscriptionServer: SubscriptionServerInterface
const createService = () => new SubscriptionApiService(subscriptionServer)
beforeEach(() => {
subscriptionServer = {} as jest.Mocked<SubscriptionServerInterface>
subscriptionServer.invite = jest.fn().mockReturnValue({
data: { success: true, sharedSubscriptionInvitationUuid: '1-2-3' },
} as jest.Mocked<SubscriptionInviteResponse>)
subscriptionServer.cancelInvite = jest.fn().mockReturnValue({
data: { success: true },
} as jest.Mocked<SubscriptionInviteCancelResponse>)
subscriptionServer.listInvites = jest.fn().mockReturnValue({
data: { invitations: [{} as jest.Mocked<Invitation>] },
} as jest.Mocked<SubscriptionInviteListResponse>)
subscriptionServer.acceptInvite = jest.fn().mockReturnValue({
data: { success: true },
} as jest.Mocked<SubscriptionInviteAcceptResponse>)
})
it('should invite a user', async () => {
const response = await createService().invite('[email protected]')
expect(response).toEqual({
data: {
success: true,
sharedSubscriptionInvitationUuid: '1-2-3',
},
})
expect(subscriptionServer.invite).toHaveBeenCalledWith({
api: '20200115',
identifier: '[email protected]',
})
})
it('should not invite a user if it is already inviting', async () => {
const service = createService()
Object.defineProperty(service, 'operationsInProgress', {
get: () => new Map([[SubscriptionApiOperations.Inviting, true]]),
})
let error = null
try {
await service.invite('[email protected]')
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should not invite a user if the server fails', async () => {
subscriptionServer.invite = jest.fn().mockImplementation(() => {
throw new Error('Oops')
})
let error = null
try {
await createService().invite('[email protected]')
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should cancel an invite', async () => {
const response = await createService().cancelInvite('1-2-3')
expect(response).toEqual({
data: {
success: true,
},
})
expect(subscriptionServer.cancelInvite).toHaveBeenCalledWith({
api: '20200115',
inviteUuid: '1-2-3',
})
})
it('should not cancel an invite if it is already canceling', async () => {
const service = createService()
Object.defineProperty(service, 'operationsInProgress', {
get: () => new Map([[SubscriptionApiOperations.CancelingInvite, true]]),
})
let error = null
try {
await service.cancelInvite('1-2-3')
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should not cancel an invite if the server fails', async () => {
subscriptionServer.cancelInvite = jest.fn().mockImplementation(() => {
throw new Error('Oops')
})
let error = null
try {
await createService().cancelInvite('1-2-3')
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should list invites', async () => {
const response = await createService().listInvites()
expect(response).toEqual({
data: {
invitations: [{} as jest.Mocked<Invitation>],
},
})
expect(subscriptionServer.listInvites).toHaveBeenCalledWith({
api: '20200115',
})
})
it('should not list invitations if it is already listing', async () => {
const service = createService()
Object.defineProperty(service, 'operationsInProgress', {
get: () => new Map([[SubscriptionApiOperations.ListingInvites, true]]),
})
let error = null
try {
await service.listInvites()
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should not list invites if the server fails', async () => {
subscriptionServer.listInvites = jest.fn().mockImplementation(() => {
throw new Error('Oops')
})
let error = null
try {
await createService().listInvites()
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should accept an invite', async () => {
const response = await createService().acceptInvite('1-2-3')
expect(response).toEqual({
data: {
success: true,
},
})
expect(subscriptionServer.acceptInvite).toHaveBeenCalledWith({
inviteUuid: '1-2-3',
})
})
it('should not accept an invite if it is already accepting', async () => {
const service = createService()
Object.defineProperty(service, 'operationsInProgress', {
get: () => new Map([[SubscriptionApiOperations.AcceptingInvite, true]]),
})
let error = null
try {
await service.acceptInvite('1-2-3')
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should not accept an invite if the server fails', async () => {
subscriptionServer.acceptInvite = jest.fn().mockImplementation(() => {
throw new Error('Oops')
})
let error = null
try {
await createService().acceptInvite('1-2-3')
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
})
@@ -1,197 +0,0 @@
import { ProtocolVersion, UserRequestType } from '@standardnotes/common'
import { type RootKeyParamsInterface } from '@standardnotes/models'
import { UserDeletionResponse } from '../../Response/User/UserDeletionResponse'
import { UserRegistrationResponse } from '../../Response/User/UserRegistrationResponse'
import { UserRequestResponse } from '../../Response/UserRequest/UserRequestResponse'
import { UserServerInterface } from '../../Server'
import { UserRequestServerInterface } from '../../Server/UserRequest/UserRequestServerInterface'
import { UserApiOperations } from './UserApiOperations'
import { UserApiService } from './UserApiService'
describe('UserApiService', () => {
let userServer: UserServerInterface
let userRequestServer: UserRequestServerInterface
let keyParams: RootKeyParamsInterface
const createService = () => new UserApiService(userServer, userRequestServer)
beforeEach(() => {
userServer = {} as jest.Mocked<UserServerInterface>
userServer.register = jest.fn().mockReturnValue({
data: { user: { email: '[email protected]', uuid: '1-2-3' } },
} as jest.Mocked<UserRegistrationResponse>)
userServer.deleteAccount = jest.fn().mockReturnValue({
data: { message: 'Success' },
} as jest.Mocked<UserDeletionResponse>)
userRequestServer = {} as jest.Mocked<UserRequestServerInterface>
userRequestServer.submitUserRequest = jest.fn().mockReturnValue({
data: { success: true },
} as jest.Mocked<UserRequestResponse>)
keyParams = {} as jest.Mocked<RootKeyParamsInterface>
keyParams.getPortableValue = jest.fn().mockReturnValue({
identifier: '[email protected]',
version: ProtocolVersion.V004,
})
})
it('should register a user', async () => {
const response = await createService().register({
email: '[email protected]',
serverPassword: 'testpasswd',
keyParams,
ephemeral: false,
})
expect(response).toEqual({
data: {
user: {
email: '[email protected]',
uuid: '1-2-3',
},
},
})
expect(userServer.register).toHaveBeenCalledWith({
api: '20200115',
email: '[email protected]',
ephemeral: false,
identifier: '[email protected]',
password: 'testpasswd',
version: '004',
})
})
it('should not register a user if it is already registering', async () => {
const service = createService()
Object.defineProperty(service, 'operationsInProgress', {
get: () => new Map([[UserApiOperations.Registering, true]]),
})
let error = null
try {
await service.register({ email: '[email protected]', serverPassword: 'testpasswd', keyParams, ephemeral: false })
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should not register a user if the server fails', async () => {
userServer.register = jest.fn().mockImplementation(() => {
throw new Error('Oops')
})
let error = null
try {
await createService().register({
email: '[email protected]',
serverPassword: 'testpasswd',
keyParams,
ephemeral: false,
})
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should submit a user request', async () => {
const response = await createService().submitUserRequest({
userUuid: '1-2-3',
requestType: UserRequestType.ExitDiscount,
})
expect(response).toEqual({
data: {
success: true,
},
})
expect(userRequestServer.submitUserRequest).toHaveBeenCalledWith({
userUuid: '1-2-3',
requestType: UserRequestType.ExitDiscount,
})
})
it('should not submit a user request if it is already submitting', async () => {
const service = createService()
Object.defineProperty(service, 'operationsInProgress', {
get: () => new Map([[UserApiOperations.SubmittingRequest, true]]),
})
let error = null
try {
await service.submitUserRequest({ userUuid: '1-2-3', requestType: UserRequestType.ExitDiscount })
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should not submit a user request if the server fails', async () => {
userRequestServer.submitUserRequest = jest.fn().mockImplementation(() => {
throw new Error('Oops')
})
let error = null
try {
await createService().submitUserRequest({
userUuid: '1-2-3',
requestType: UserRequestType.ExitDiscount,
})
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should delete a user', async () => {
const response = await createService().deleteAccount('1-2-3')
expect(response).toEqual({
data: {
message: 'Success',
},
})
expect(userServer.deleteAccount).toHaveBeenCalledWith({
userUuid: '1-2-3',
})
})
it('should not delete a user if it is already deleting', async () => {
const service = createService()
Object.defineProperty(service, 'operationsInProgress', {
get: () => new Map([[UserApiOperations.DeletingAccount, true]]),
})
let error = null
try {
await service.deleteAccount('1-2-3')
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should not delete a user if the server fails', async () => {
userServer.deleteAccount = jest.fn().mockImplementation(() => {
throw new Error('Oops')
})
let error = null
try {
await createService().deleteAccount('1-2-3')
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
})
@@ -1,61 +0,0 @@
import { WebSocketConnectionTokenResponse } from '../../Response'
import { WebSocketServerInterface } from '../../Server/WebSocket/WebSocketServerInterface'
import { WebSocketApiOperations } from './WebSocketApiOperations'
import { WebSocketApiService } from './WebSocketApiService'
describe('WebSocketApiService', () => {
let webSocketServer: WebSocketServerInterface
const createService = () => new WebSocketApiService(webSocketServer)
beforeEach(() => {
webSocketServer = {} as jest.Mocked<WebSocketServerInterface>
webSocketServer.createConnectionToken = jest.fn().mockReturnValue({
data: { token: 'foobar' },
} as jest.Mocked<WebSocketConnectionTokenResponse>)
})
it('should create a websocket connection token', async () => {
const response = await createService().createConnectionToken()
expect(response).toEqual({
data: {
token: 'foobar',
},
})
expect(webSocketServer.createConnectionToken).toHaveBeenCalledWith({})
})
it('should not create a token if it is already creating', async () => {
const service = createService()
Object.defineProperty(service, 'operationsInProgress', {
get: () => new Map([[WebSocketApiOperations.CreatingConnectionToken, true]]),
})
let error = null
try {
await service.createConnectionToken()
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should not create a token if the server fails', async () => {
webSocketServer.createConnectionToken = jest.fn().mockImplementation(() => {
throw new Error('Oops')
})
let error = null
try {
await createService().createConnectionToken()
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
})
@@ -1,368 +0,0 @@
import { WorkspaceAccessLevel, WorkspaceType } from '@standardnotes/common'
import { HttpStatusCode } from '../../Http'
import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse'
import { WorkspaceInvitationAcceptingResponse } from '../../Response/Workspace/WorkspaceInvitationAcceptingResponse'
import { WorkspaceInvitationResponse } from '../../Response/Workspace/WorkspaceInvitationResponse'
import { WorkspaceListResponse } from '../../Response/Workspace/WorkspaceListResponse'
import { WorkspaceUserListResponse } from '../../Response/Workspace/WorkspaceUserListResponse'
import { WorkspaceServerInterface } from '../../Server/Workspace/WorkspaceServerInterface'
import { WorkspaceKeyshareInitiatingResponse } from '../../Response/Workspace/WorkspaceKeyshareInitiatingResponse'
import { WorkspaceApiOperations } from './WorkspaceApiOperations'
import { WorkspaceApiService } from './WorkspaceApiService'
describe('WorkspaceApiService', () => {
let workspaceServer: WorkspaceServerInterface
const createService = () => new WorkspaceApiService(workspaceServer)
beforeEach(() => {
workspaceServer = {} as jest.Mocked<WorkspaceServerInterface>
workspaceServer.createWorkspace = jest.fn().mockReturnValue({
data: { uuid: '1-2-3' },
} as jest.Mocked<WorkspaceCreationResponse>)
workspaceServer.inviteToWorkspace = jest.fn().mockReturnValue({
data: { uuid: 'i-1-2-3' },
} as jest.Mocked<WorkspaceInvitationResponse>)
workspaceServer.acceptInvite = jest.fn().mockReturnValue({
data: { success: true },
} as jest.Mocked<WorkspaceInvitationAcceptingResponse>)
workspaceServer.listWorkspaces = jest.fn().mockReturnValue({
status: HttpStatusCode.Success,
data: { ownedWorkspaces: [], joinedWorkspaces: [] },
} as jest.Mocked<WorkspaceListResponse>)
workspaceServer.listWorkspaceUsers = jest.fn().mockReturnValue({
status: HttpStatusCode.Success,
data: { users: [] },
} as jest.Mocked<WorkspaceUserListResponse>)
workspaceServer.initiateKeyshare = jest.fn().mockReturnValue({
status: HttpStatusCode.Success,
data: { success: true },
} as jest.Mocked<WorkspaceKeyshareInitiatingResponse>)
})
it('should create a workspace', async () => {
const response = await createService().createWorkspace({
workspaceType: WorkspaceType.Private,
encryptedPrivateKey: 'foo',
encryptedWorkspaceKey: 'bar',
publicKey: 'buzz',
})
expect(response).toEqual({
data: {
uuid: '1-2-3',
},
})
expect(workspaceServer.createWorkspace).toHaveBeenCalledWith({
encryptedPrivateKey: 'foo',
encryptedWorkspaceKey: 'bar',
publicKey: 'buzz',
workspaceType: 'private',
})
})
it('should not create a workspace if it is already creating', async () => {
const service = createService()
Object.defineProperty(service, 'operationsInProgress', {
get: () => new Map([[WorkspaceApiOperations.Creating, true]]),
})
let error = null
try {
await service.createWorkspace({
workspaceType: WorkspaceType.Private,
encryptedPrivateKey: 'foo',
encryptedWorkspaceKey: 'bar',
publicKey: 'buzz',
})
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should not create a workspace if the server fails', async () => {
workspaceServer.createWorkspace = jest.fn().mockImplementation(() => {
throw new Error('Oops')
})
let error = null
try {
await createService().createWorkspace({
workspaceType: WorkspaceType.Private,
encryptedPrivateKey: 'foo',
encryptedWorkspaceKey: 'bar',
publicKey: 'buzz',
})
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should invite to a workspace', async () => {
const response = await createService().inviteToWorkspace({
workspaceUuid: 'w-1-2-3',
inviteeEmail: '[email protected]',
accessLevel: WorkspaceAccessLevel.WriteAndRead,
})
expect(response).toEqual({
data: {
uuid: 'i-1-2-3',
},
})
expect(workspaceServer.inviteToWorkspace).toHaveBeenCalledWith({
workspaceUuid: 'w-1-2-3',
inviteeEmail: '[email protected]',
accessLevel: 'write-and-read',
})
})
it('should not invite to a workspace if it is already inviting', async () => {
const service = createService()
Object.defineProperty(service, 'operationsInProgress', {
get: () => new Map([[WorkspaceApiOperations.Inviting, true]]),
})
let error = null
try {
await service.inviteToWorkspace({
workspaceUuid: 'w-1-2-3',
inviteeEmail: '[email protected]',
accessLevel: WorkspaceAccessLevel.WriteAndRead,
})
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should not invite to a workspace if the server fails', async () => {
workspaceServer.inviteToWorkspace = jest.fn().mockImplementation(() => {
throw new Error('Oops')
})
let error = null
try {
await createService().inviteToWorkspace({
workspaceUuid: 'w-1-2-3',
inviteeEmail: '[email protected]',
accessLevel: WorkspaceAccessLevel.WriteAndRead,
})
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should accept invite to a workspace', async () => {
const response = await createService().acceptInvite({
userUuid: 'u-1-2-3',
inviteUuid: 'i-1-2-3',
publicKey: 'foo',
encryptedPrivateKey: 'bar',
})
expect(response).toEqual({
data: {
success: true,
},
})
expect(workspaceServer.acceptInvite).toHaveBeenCalledWith({
userUuid: 'u-1-2-3',
inviteUuid: 'i-1-2-3',
publicKey: 'foo',
encryptedPrivateKey: 'bar',
})
})
it('should not accept invite to a workspace if it is already accepting', async () => {
const service = createService()
Object.defineProperty(service, 'operationsInProgress', {
get: () => new Map([[WorkspaceApiOperations.Accepting, true]]),
})
let error = null
try {
await service.acceptInvite({
userUuid: 'u-1-2-3',
inviteUuid: 'i-1-2-3',
publicKey: 'foo',
encryptedPrivateKey: 'bar',
})
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should not accept invite to a workspace if the server fails', async () => {
workspaceServer.acceptInvite = jest.fn().mockImplementation(() => {
throw new Error('Oops')
})
let error = null
try {
await createService().acceptInvite({
userUuid: 'u-1-2-3',
inviteUuid: 'i-1-2-3',
publicKey: 'foo',
encryptedPrivateKey: 'bar',
})
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should list workspaces', async () => {
const response = await createService().listWorkspaces()
expect(response).toEqual({
status: 200,
data: {
ownedWorkspaces: [],
joinedWorkspaces: [],
},
})
expect(workspaceServer.listWorkspaces).toHaveBeenCalled()
})
it('should not list workspaces if it is already listing them', async () => {
const service = createService()
Object.defineProperty(service, 'operationsInProgress', {
get: () => new Map([[WorkspaceApiOperations.ListingWorkspaces, true]]),
})
let error = null
try {
await service.listWorkspaces()
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should not list workspaces if the server fails', async () => {
workspaceServer.listWorkspaces = jest.fn().mockImplementation(() => {
throw new Error('Oops')
})
let error = null
try {
await createService().listWorkspaces()
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should list workspace users', async () => {
const response = await createService().listWorkspaceUsers({ workspaceUuid: 'w-1-2-3' })
expect(response).toEqual({
status: 200,
data: {
users: [],
},
})
expect(workspaceServer.listWorkspaceUsers).toHaveBeenCalledWith({ workspaceUuid: 'w-1-2-3' })
})
it('should not list workspace users if it is already listing them', async () => {
const service = createService()
Object.defineProperty(service, 'operationsInProgress', {
get: () => new Map([[WorkspaceApiOperations.ListingWorkspaceUsers, true]]),
})
let error = null
try {
await service.listWorkspaceUsers({ workspaceUuid: 'w-1-2-3' })
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should not list workspace users if the server fails', async () => {
workspaceServer.listWorkspaceUsers = jest.fn().mockImplementation(() => {
throw new Error('Oops')
})
let error = null
try {
await createService().listWorkspaceUsers({ workspaceUuid: 'w-1-2-3' })
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should initiate keyshare in workspace for user', async () => {
const response = await createService().initiateKeyshare({
workspaceUuid: 'w-1-2-3',
userUuid: 'u-1-2-3',
encryptedWorkspaceKey: 'foobar',
})
expect(response).toEqual({
status: 200,
data: {
success: true,
},
})
expect(workspaceServer.initiateKeyshare).toHaveBeenCalledWith({
workspaceUuid: 'w-1-2-3',
userUuid: 'u-1-2-3',
encryptedWorkspaceKey: 'foobar',
})
})
it('should not initiate keyshare in workspace if it is already initiating', async () => {
const service = createService()
Object.defineProperty(service, 'operationsInProgress', {
get: () => new Map([[WorkspaceApiOperations.InitiatingKeyshare, true]]),
})
let error = null
try {
await service.initiateKeyshare({ workspaceUuid: 'w-1-2-3', userUuid: 'u-1-2-3', encryptedWorkspaceKey: 'foobar' })
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should not initiate keyshare in workspace if the server fails', async () => {
workspaceServer.initiateKeyshare = jest.fn().mockImplementation(() => {
throw new Error('Oops')
})
let error = null
try {
await createService().initiateKeyshare({
workspaceUuid: 'w-1-2-3',
userUuid: 'u-1-2-3',
encryptedWorkspaceKey: 'foobar',
})
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
})
@@ -1,42 +0,0 @@
import { Environment } from '@standardnotes/models'
import { HttpResponseMeta } from './HttpResponseMeta'
import { HttpService } from './HttpService'
describe('HttpService', () => {
const environment = Environment.Web
const appVersion = '1.2.3'
const snjsVersion = '2.3.4'
const host = 'http://bar'
let updateMetaCallback: (meta: HttpResponseMeta) => void
const createService = () => {
const service = new HttpService(environment, appVersion, snjsVersion, updateMetaCallback)
service.setHost(host)
return service
}
beforeEach(() => {
updateMetaCallback = jest.fn()
})
it('should set host', () => {
const service = createService()
expect(service['host']).toEqual('http://bar')
service.setHost('http://foo')
expect(service['host']).toEqual('http://foo')
})
it('should set and use the authorization token', () => {
const service = createService()
expect(service['authorizationToken']).toBeUndefined()
service.setAuthorizationToken('foo-bar')
expect(service['authorizationToken']).toEqual('foo-bar')
})
})
+79 -14
View File
@@ -1,5 +1,7 @@
import { isString, joinPaths, sleep } from '@standardnotes/utils'
import { Environment } from '@standardnotes/models'
import { Session, SessionToken } from '@standardnotes/domain-core'
import { HttpRequestParams } from './HttpRequestParams'
import { HttpVerb } from './HttpVerb'
import { HttpRequest } from './HttpRequest'
@@ -10,21 +12,26 @@ import { XMLHttpRequestState } from './XMLHttpRequestState'
import { ErrorMessage } from '../Error/ErrorMessage'
import { HttpResponseMeta } from './HttpResponseMeta'
import { HttpErrorResponseBody } from './HttpErrorResponseBody'
import { Paths } from '../Server/Auth/Paths'
import { SessionRefreshResponse } from '../Response/Auth/SessionRefreshResponse'
export class HttpService implements HttpServiceInterface {
private authorizationToken?: string
private session: Session | null
private __latencySimulatorMs?: number
private host!: string
private declare host: string
constructor(
private environment: Environment,
private appVersion: string,
private snjsVersion: string,
private updateMetaCallback: (meta: HttpResponseMeta) => void,
) {}
private refreshSessionCallback: (session: Session) => void,
) {
this.session = null
}
setAuthorizationToken(authorizationToken: string): void {
this.authorizationToken = authorizationToken
setSession(session: Session): void {
this.session = session
}
setHost(host: string): void {
@@ -36,7 +43,7 @@ export class HttpService implements HttpServiceInterface {
url: joinPaths(this.host, path),
params,
verb: HttpVerb.Get,
authentication: authentication ?? this.authorizationToken,
authentication: authentication ?? this.session?.accessToken.value,
})
}
@@ -45,7 +52,7 @@ export class HttpService implements HttpServiceInterface {
url: joinPaths(this.host, path),
params,
verb: HttpVerb.Post,
authentication: authentication ?? this.authorizationToken,
authentication: authentication ?? this.session?.accessToken.value,
})
}
@@ -54,7 +61,7 @@ export class HttpService implements HttpServiceInterface {
url: joinPaths(this.host, path),
params,
verb: HttpVerb.Put,
authentication: authentication ?? this.authorizationToken,
authentication: authentication ?? this.session?.accessToken.value,
})
}
@@ -63,7 +70,7 @@ export class HttpService implements HttpServiceInterface {
url: joinPaths(this.host, path),
params,
verb: HttpVerb.Patch,
authentication: authentication ?? this.authorizationToken,
authentication: authentication ?? this.session?.accessToken.value,
})
}
@@ -72,7 +79,7 @@ export class HttpService implements HttpServiceInterface {
url: joinPaths(this.host, path),
params,
verb: HttpVerb.Delete,
authentication: authentication ?? this.authorizationToken,
authentication: authentication ?? this.session?.accessToken.value,
})
}
@@ -89,9 +96,68 @@ export class HttpService implements HttpServiceInterface {
this.updateMetaCallback(response.meta)
}
if (response.status === HttpStatusCode.ExpiredAccessToken) {
const isSessionRefreshed = await this.refreshSession()
if (!isSessionRefreshed) {
return response
}
httpRequest.authentication = this.session?.accessToken.value
return this.runHttp(httpRequest)
}
return response
}
private async refreshSession(): Promise<boolean> {
if (this.session === null) {
return false
}
const response = (await this.post(Paths.v1.refreshSession, {
access_token: this.session.accessToken.value,
refresh_token: this.session.refreshToken.value,
})) as SessionRefreshResponse
if (response.data.error) {
return false
}
if (response.meta) {
this.updateMetaCallback(response.meta)
}
const accessTokenOrError = SessionToken.create(
response.data.session.access_token,
response.data.session.access_expiration,
)
if (accessTokenOrError.isFailed()) {
return false
}
const accessToken = accessTokenOrError.getValue()
const refreshTokenOrError = SessionToken.create(
response.data.session.refresh_token,
response.data.session.refresh_expiration,
)
if (refreshTokenOrError.isFailed()) {
return false
}
const refreshToken = refreshTokenOrError.getValue()
const sessionOrError = Session.create(accessToken, refreshToken, response.data.session.readonly_access)
if (sessionOrError.isFailed()) {
return false
}
this.setSession(sessionOrError.getValue())
this.refreshSessionCallback(this.session)
return true
}
private createRequestBody(httpRequest: HttpRequest): string | Uint8Array | undefined {
if (
httpRequest.params !== undefined &&
@@ -199,13 +265,12 @@ export class HttpService implements HttpServiceInterface {
} catch (error) {
console.error(error)
}
if (httpStatus >= HttpStatusCode.Success && httpStatus < HttpStatusCode.MultipleChoices) {
resolve(response)
} else {
if (httpStatus >= HttpStatusCode.Success && httpStatus < HttpStatusCode.InternalServerError) {
if (httpStatus === HttpStatusCode.Forbidden && response.data && response.data.error !== undefined) {
;(response.data as HttpErrorResponseBody).error.message = ErrorMessage.RateLimited
}
resolve(response)
} else {
reject(response)
}
}
@@ -1,9 +1,11 @@
import { Session } from '@standardnotes/domain-core'
import { HttpRequestParams } from './HttpRequestParams'
import { HttpResponse } from './HttpResponse'
export interface HttpServiceInterface {
setHost(host: string): void
setAuthorizationToken(authorizationToken: string): void
setSession(session: Session): void
get(path: string, params?: HttpRequestParams, authentication?: string): Promise<HttpResponse>
post(path: string, params?: HttpRequestParams, authentication?: string): Promise<HttpResponse>
put(path: string, params?: HttpRequestParams, authentication?: string): Promise<HttpResponse>
@@ -7,4 +7,5 @@ export enum HttpStatusCode {
Forbidden = 403,
Gone = 410,
ExpiredAccessToken = 498,
InternalServerError = 500,
}
@@ -0,0 +1,9 @@
import { Either } from '@standardnotes/common'
import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody'
import { HttpResponse } from '../../Http/HttpResponse'
import { SessionRefreshResponseBody } from './SessionRefreshResponseBody'
export interface SessionRefreshResponse extends HttpResponse {
data: Either<SessionRefreshResponseBody, HttpErrorResponseBody>
}
@@ -0,0 +1,9 @@
export type SessionRefreshResponseBody = {
session: {
access_token: string
refresh_token: string
access_expiration: number
refresh_expiration: number
readonly_access: boolean
}
}
@@ -0,0 +1,9 @@
const SessionPaths = {
refreshSession: '/v1/sessions/refresh',
}
export const Paths = {
v1: {
...SessionPaths,
},
}
@@ -1,106 +0,0 @@
import { type Invitation } from '@standardnotes/models'
import { ApiVersion } from '../../Api'
import { HttpServiceInterface } from '../../Http'
import { SubscriptionInviteResponse } from '../../Response'
import { SubscriptionInviteAcceptResponse } from '../../Response/Subscription/SubscriptionInviteAcceptResponse'
import { SubscriptionInviteCancelResponse } from '../../Response/Subscription/SubscriptionInviteCancelResponse'
import { SubscriptionInviteDeclineResponse } from '../../Response/Subscription/SubscriptionInviteDeclineResponse'
import { SubscriptionInviteListResponse } from '../../Response/Subscription/SubscriptionInviteListResponse'
import { SubscriptionServer } from './SubscriptionServer'
describe('SubscriptionServer', () => {
let httpService: HttpServiceInterface
const createServer = () => new SubscriptionServer(httpService)
beforeEach(() => {
httpService = {} as jest.Mocked<HttpServiceInterface>
})
it('should invite a user to a shared subscription', async () => {
httpService.post = jest.fn().mockReturnValue({
data: { success: true, sharedSubscriptionInvitationUuid: '1-2-3' },
} as jest.Mocked<SubscriptionInviteResponse>)
const response = await createServer().invite({
api: ApiVersion.v0,
identifier: '[email protected]',
})
expect(response).toEqual({
data: {
success: true,
sharedSubscriptionInvitationUuid: '1-2-3',
},
})
})
it('should accept an invite to a shared subscription', async () => {
httpService.post = jest.fn().mockReturnValue({
data: { success: true },
} as jest.Mocked<SubscriptionInviteAcceptResponse>)
const response = await createServer().acceptInvite({
api: ApiVersion.v0,
inviteUuid: '1-2-3',
})
expect(response).toEqual({
data: {
success: true,
},
})
})
it('should decline an invite to a shared subscription', async () => {
httpService.get = jest.fn().mockReturnValue({
data: { success: true },
} as jest.Mocked<SubscriptionInviteDeclineResponse>)
const response = await createServer().declineInvite({
api: ApiVersion.v0,
inviteUuid: '1-2-3',
})
expect(response).toEqual({
data: {
success: true,
},
})
})
it('should cancel an invite to a shared subscription', async () => {
httpService.delete = jest.fn().mockReturnValue({
data: { success: true },
} as jest.Mocked<SubscriptionInviteCancelResponse>)
const response = await createServer().cancelInvite({
api: ApiVersion.v0,
inviteUuid: '1-2-3',
})
expect(response).toEqual({
data: {
success: true,
},
})
})
it('should list invitations to a shared subscription', async () => {
httpService.get = jest.fn().mockReturnValue({
data: { invitations: [{} as jest.Mocked<Invitation>] },
} as jest.Mocked<SubscriptionInviteListResponse>)
const response = await createServer().listInvites({
api: ApiVersion.v0,
})
expect(response).toEqual({
data: {
invitations: [{} as jest.Mocked<Invitation>],
},
})
})
})
@@ -1,54 +0,0 @@
import { ProtocolVersion } from '@standardnotes/common'
import { ApiVersion } from '../../Api'
import { HttpServiceInterface } from '../../Http'
import { UserDeletionResponse, UserRegistrationResponse } from '../../Response'
import { UserServer } from './UserServer'
describe('UserServer', () => {
let httpService: HttpServiceInterface
const createServer = () => new UserServer(httpService)
beforeEach(() => {
httpService = {} as jest.Mocked<HttpServiceInterface>
httpService.post = jest.fn().mockReturnValue({
data: { user: { email: '[email protected]', uuid: '1-2-3' } },
} as jest.Mocked<UserRegistrationResponse>)
httpService.delete = jest.fn().mockReturnValue({
data: { message: 'Success' },
} as jest.Mocked<UserDeletionResponse>)
})
it('should register a user', async () => {
const response = await createServer().register({
password: 'test',
api: ApiVersion.v0,
email: '[email protected]',
ephemeral: false,
version: ProtocolVersion.V004,
pw_nonce: 'test',
identifier: '[email protected]',
})
expect(response).toEqual({
data: {
user: {
email: '[email protected]',
uuid: '1-2-3',
},
},
})
})
it('should delete a user', async () => {
const response = await createServer().deleteAccount({
userUuid: '1-2-3',
})
expect(response).toEqual({
data: {
message: 'Success',
},
})
})
})
@@ -1,32 +0,0 @@
import { UserRequestType } from '@standardnotes/common'
import { HttpServiceInterface } from '../../Http'
import { UserRequestResponse } from '../../Response/UserRequest/UserRequestResponse'
import { UserRequestServer } from './UserRequestServer'
describe('UserRequestServer', () => {
let httpService: HttpServiceInterface
const createServer = () => new UserRequestServer(httpService)
beforeEach(() => {
httpService = {} as jest.Mocked<HttpServiceInterface>
httpService.post = jest.fn().mockReturnValue({
data: { success: true },
} as jest.Mocked<UserRequestResponse>)
})
it('should submit a user request', async () => {
const response = await createServer().submitUserRequest({
userUuid: '1-2-3',
requestType: UserRequestType.ExitDiscount,
})
expect(response).toEqual({
data: {
success: true,
},
})
})
})
@@ -1,27 +0,0 @@
import { HttpServiceInterface } from '../../Http'
import { WebSocketConnectionTokenResponse } from '../../Response'
import { WebSocketServer } from './WebSocketServer'
describe('WebSocketServer', () => {
let httpService: HttpServiceInterface
const createServer = () => new WebSocketServer(httpService)
beforeEach(() => {
httpService = {} as jest.Mocked<HttpServiceInterface>
httpService.post = jest.fn().mockReturnValue({
data: { token: 'foobar' },
} as jest.Mocked<WebSocketConnectionTokenResponse>)
})
it('should create a websocket connection token', async () => {
const response = await createServer().createConnectionToken({})
expect(response).toEqual({
data: {
token: 'foobar',
},
})
})
})
@@ -1,124 +0,0 @@
import { WorkspaceAccessLevel, WorkspaceType } from '@standardnotes/common'
import { HttpServiceInterface, HttpStatusCode } from '../../Http'
import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse'
import { WorkspaceInvitationAcceptingResponse } from '../../Response/Workspace/WorkspaceInvitationAcceptingResponse'
import { WorkspaceInvitationResponse } from '../../Response/Workspace/WorkspaceInvitationResponse'
import { WorkspaceKeyshareInitiatingResponse } from '../../Response/Workspace/WorkspaceKeyshareInitiatingResponse'
import { WorkspaceListResponse } from '../../Response/Workspace/WorkspaceListResponse'
import { WorkspaceUserListResponse } from '../../Response/Workspace/WorkspaceUserListResponse'
import { WorkspaceServer } from './WorkspaceServer'
describe('WorkspaceServer', () => {
let httpService: HttpServiceInterface
const createServer = () => new WorkspaceServer(httpService)
beforeEach(() => {
httpService = {} as jest.Mocked<HttpServiceInterface>
httpService.post = jest.fn().mockReturnValue({
data: { uuid: '1-2-3' },
} as jest.Mocked<WorkspaceCreationResponse>)
})
it('should create a workspace', async () => {
const response = await createServer().createWorkspace({
workspaceType: WorkspaceType.Private,
encryptedPrivateKey: 'foo',
encryptedWorkspaceKey: 'bar',
publicKey: 'buzz',
})
expect(response).toEqual({
data: {
uuid: '1-2-3',
},
})
})
it('should inivte to a workspace', async () => {
httpService.post = jest.fn().mockReturnValue({
data: { uuid: 'i-1-2-3' },
} as jest.Mocked<WorkspaceInvitationResponse>)
const response = await createServer().inviteToWorkspace({
inviteeEmail: '[email protected]',
workspaceUuid: 'w-1-2-3',
accessLevel: WorkspaceAccessLevel.WriteAndRead,
})
expect(response).toEqual({
data: {
uuid: 'i-1-2-3',
},
})
})
it('should accept invitation to a workspace', async () => {
httpService.post = jest.fn().mockReturnValue({
data: { success: true },
} as jest.Mocked<WorkspaceInvitationAcceptingResponse>)
const response = await createServer().acceptInvite({
encryptedPrivateKey: 'foo',
inviteUuid: 'i-1-2-3',
publicKey: 'bar',
userUuid: 'u-1-2-3',
})
expect(response).toEqual({
data: {
success: true,
},
})
})
it('should list workspaces', async () => {
httpService.get = jest.fn().mockReturnValue({
status: HttpStatusCode.Success,
data: { ownedWorkspaces: [], joinedWorkspaces: [] },
} as jest.Mocked<WorkspaceListResponse>)
const response = await createServer().listWorkspaces({})
expect(response).toEqual({
status: 200,
data: { ownedWorkspaces: [], joinedWorkspaces: [] },
})
})
it('should list workspace users', async () => {
httpService.get = jest.fn().mockReturnValue({
status: HttpStatusCode.Success,
data: { users: [] },
} as jest.Mocked<WorkspaceUserListResponse>)
const response = await createServer().listWorkspaceUsers({ workspaceUuid: 'w-1-2-3' })
expect(response).toEqual({
status: 200,
data: { users: [] },
})
})
it('should initiate keyshare for user in a workspace', async () => {
httpService.post = jest.fn().mockReturnValue({
status: HttpStatusCode.Success,
data: { success: true },
} as jest.Mocked<WorkspaceKeyshareInitiatingResponse>)
const response = await createServer().initiateKeyshare({
workspaceUuid: 'w-1-2-3',
userUuid: 'u-1-2-3',
encryptedWorkspaceKey: 'foobar',
})
expect(response).toEqual({
status: 200,
data: {
success: true,
},
})
})
})
+48
View File
@@ -3,6 +3,54 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.12.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-26)
**Note:** Version bump only for package @standardnotes/blocks-editor
# [1.12.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-26)
### Features
* Clicking the link option in Super notes will automatically add the selected text as the link ([326e1a4](https://github.com/standardnotes/app/commit/326e1a455bb510d795f357123f617618267848cc))
## [1.11.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-24)
**Note:** Version bump only for package @standardnotes/blocks-editor
## [1.11.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-24)
**Note:** Version bump only for package @standardnotes/blocks-editor
## [1.11.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-24)
**Note:** Version bump only for package @standardnotes/blocks-editor
# [1.11.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-24)
### Features
* Persist collapsible block state in Super notes ([#2119](https://github.com/standardnotes/app/issues/2119)) ([577318e](https://github.com/standardnotes/app/commit/577318e208367dc52caf2d76fd87419eaead7ed4))
## [1.10.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-23)
**Note:** Version bump only for package @standardnotes/blocks-editor
## [1.10.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-19)
**Note:** Version bump only for package @standardnotes/blocks-editor
# [1.10.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-07)
### Features
* add ability to convert selection in Super to bulleted or numbered list ([be4cc4e](https://github.com/standardnotes/app/commit/be4cc4e605100e0a86de214e548e9ed10e2568aa))
## [1.9.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-07)
### Bug Fixes
* improve variance between Super header font sizes and weights ([8b10330](https://github.com/standardnotes/app/commit/8b103307cba41855713b8627cf5d66a6fef6ff52))
## [1.9.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
### Bug Fixes
+4 -4
View File
@@ -1,17 +1,17 @@
{
"name": "@standardnotes/blocks-editor",
"version": "1.9.6",
"version": "1.12.1",
"private": true,
"main": "./src/index.ts",
"scripts": {
"tsc": "tsc -p tsconfig.json"
},
"dependencies": {
"@lexical/react": "^0.6.0",
"@lexical/react": "0.7.5",
"@standardnotes/icons": "workspace:*",
"@types/react": "^18.0.25",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"lexical": "^0.6.0",
"lexical": "0.7.5",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
@@ -21,6 +21,7 @@ import DraggableBlockPlugin from '../Lexical/Plugins/DraggableBlockPlugin';
import CodeHighlightPlugin from '../Lexical/Plugins/CodeHighlightPlugin';
import FloatingTextFormatToolbarPlugin from '../Lexical/Plugins/FloatingTextFormatToolbarPlugin';
import FloatingLinkEditorPlugin from '../Lexical/Plugins/FloatingLinkEditorPlugin';
import {TabIndentationPlugin} from '../Lexical/Plugins/TabIndentationPlugin';
import {truncateString} from './Utils';
import {SuperEditorContentId} from './Constants';
import {classNames} from '@standardnotes/utils';
@@ -107,7 +108,7 @@ export const BlocksEditor: FunctionComponent<BlocksEditorProps> = ({
</div>
</div>
}
placeholder=""
placeholder={null}
ErrorBoundary={LexicalErrorBoundary}
/>
<ListPlugin />
@@ -125,6 +126,7 @@ export const BlocksEditor: FunctionComponent<BlocksEditorProps> = ({
<TwitterPlugin />
<YouTubePlugin />
<CollapsiblePlugin />
<TabIndentationPlugin />
{!readonly && floatingAnchorElem && (
<>
<FloatingTextFormatToolbarPlugin anchorElem={floatingAnchorElem} />
@@ -192,9 +192,10 @@ export function AutoEmbedDialog({
const embedResult =
text != null && urlMatch != null ? embedConfig.parseUrl(text) : null;
const onClick = () => {
if (embedResult != null) {
embedConfig.insertNode(editor, embedResult);
const onClick = async () => {
const result = await embedResult;
if (result != null) {
embedConfig.insertNode(editor, result);
onClose();
}
};
@@ -20,6 +20,7 @@ type SerializedCollapsibleContainerNode = Spread<
{
type: 'collapsible-container';
version: 1;
open: boolean;
},
SerializedElementNode
>;
@@ -29,25 +30,27 @@ export class CollapsibleContainerNode extends ElementNode {
constructor(open: boolean, key?: NodeKey) {
super(key);
this.__open = open;
this.__open = open ?? false;
}
static getType(): string {
static override getType(): string {
return 'collapsible-container';
}
static clone(node: CollapsibleContainerNode): CollapsibleContainerNode {
static override clone(
node: CollapsibleContainerNode,
): CollapsibleContainerNode {
return new CollapsibleContainerNode(node.__open, node.__key);
}
createDOM(config: EditorConfig): HTMLElement {
override createDOM(config: EditorConfig): HTMLElement {
const dom = document.createElement('details');
dom.classList.add('Collapsible__container');
dom.open = this.__open;
return dom;
}
updateDOM(
override updateDOM(
prevNode: CollapsibleContainerNode,
dom: HTMLDetailsElement,
): boolean {
@@ -62,18 +65,19 @@ export class CollapsibleContainerNode extends ElementNode {
return {};
}
static importJSON(
static override importJSON(
serializedNode: SerializedCollapsibleContainerNode,
): CollapsibleContainerNode {
const node = $createCollapsibleContainerNode();
const node = $createCollapsibleContainerNode(serializedNode.open);
return node;
}
exportJSON(): SerializedCollapsibleContainerNode {
override exportJSON(): SerializedCollapsibleContainerNode {
return {
...super.exportJSON(),
type: 'collapsible-container',
version: 1,
open: this.__open,
};
}
@@ -91,8 +95,10 @@ export class CollapsibleContainerNode extends ElementNode {
}
}
export function $createCollapsibleContainerNode(): CollapsibleContainerNode {
return new CollapsibleContainerNode(true);
export function $createCollapsibleContainerNode(
open: boolean,
): CollapsibleContainerNode {
return new CollapsibleContainerNode(open);
}
export function $isCollapsibleContainerNode(
@@ -24,21 +24,24 @@ type SerializedCollapsibleContentNode = Spread<
>;
export class CollapsibleContentNode extends ElementNode {
static getType(): string {
static override getType(): string {
return 'collapsible-content';
}
static clone(node: CollapsibleContentNode): CollapsibleContentNode {
static override clone(node: CollapsibleContentNode): CollapsibleContentNode {
return new CollapsibleContentNode(node.__key);
}
createDOM(config: EditorConfig): HTMLElement {
override createDOM(config: EditorConfig): HTMLElement {
const dom = document.createElement('div');
dom.classList.add('Collapsible__content');
return dom;
}
updateDOM(prevNode: CollapsibleContentNode, dom: HTMLElement): boolean {
override updateDOM(
prevNode: CollapsibleContentNode,
dom: HTMLElement,
): boolean {
return false;
}
@@ -46,17 +49,17 @@ export class CollapsibleContentNode extends ElementNode {
return {};
}
static importJSON(
static override importJSON(
serializedNode: SerializedCollapsibleContentNode,
): CollapsibleContentNode {
return $createCollapsibleContentNode();
}
isShadowRoot(): boolean {
override isShadowRoot(): boolean {
return true;
}
exportJSON(): SerializedCollapsibleContentNode {
override exportJSON(): SerializedCollapsibleContentNode {
return {
...super.exportJSON(),
type: 'collapsible-content',
@@ -31,21 +31,34 @@ type SerializedCollapsibleTitleNode = Spread<
>;
export class CollapsibleTitleNode extends ElementNode {
static getType(): string {
static override getType(): string {
return 'collapsible-title';
}
static clone(node: CollapsibleTitleNode): CollapsibleTitleNode {
static override clone(node: CollapsibleTitleNode): CollapsibleTitleNode {
return new CollapsibleTitleNode(node.__key);
}
createDOM(config: EditorConfig, editor: LexicalEditor): HTMLElement {
override createDOM(config: EditorConfig, editor: LexicalEditor): HTMLElement {
const dom = document.createElement('summary');
dom.classList.add('Collapsible__title');
dom.onclick = (event) => {
event.preventDefault();
event.stopPropagation();
editor.update(() => {
const containerNode = this.getParentOrThrow();
if ($isCollapsibleContainerNode(containerNode)) {
containerNode.toggleOpen();
}
});
};
return dom;
}
updateDOM(prevNode: CollapsibleTitleNode, dom: HTMLElement): boolean {
override updateDOM(
prevNode: CollapsibleTitleNode,
dom: HTMLElement,
): boolean {
return false;
}
@@ -53,13 +66,13 @@ export class CollapsibleTitleNode extends ElementNode {
return {};
}
static importJSON(
static override importJSON(
serializedNode: SerializedCollapsibleTitleNode,
): CollapsibleTitleNode {
return $createCollapsibleTitleNode();
}
exportJSON(): SerializedCollapsibleTitleNode {
override exportJSON(): SerializedCollapsibleTitleNode {
return {
...super.exportJSON(),
type: 'collapsible-title',
@@ -67,12 +80,12 @@ export class CollapsibleTitleNode extends ElementNode {
};
}
collapseAtStart(_selection: RangeSelection): boolean {
override collapseAtStart(_selection: RangeSelection): boolean {
this.getParentOrThrow().insertBefore(this);
return true;
}
insertNewAfter(): ElementNode {
override insertNewAfter(): ElementNode {
const containerNode = this.getParentOrThrow();
if (!$isCollapsibleContainerNode(containerNode)) {
@@ -204,7 +204,7 @@ export default function CollapsiblePlugin(): JSX.Element | null {
const content = $createCollapsibleContentNode().append(
$createParagraphNode(),
);
const container = $createCollapsibleContainerNode().append(
const container = $createCollapsibleContainerNode(true).append(
title,
content,
);
@@ -12,6 +12,7 @@ import {mergeRegister} from '@lexical/utils';
import {
$getNearestNodeFromDOMNode,
$getNodeByKey,
$getRoot,
COMMAND_PRIORITY_HIGH,
COMMAND_PRIORITY_LOW,
DRAGOVER_COMMAND,
@@ -58,8 +59,7 @@ function getCurrentIndex(keysLength: number): number {
}
function getTopLevelNodeKeys(editor: LexicalEditor): string[] {
const root = editor.getEditorState()._nodeMap.get('root');
return root ? root.__children : [];
return editor.getEditorState().read(() => $getRoot().getChildrenKeys());
}
function elementContainingEventLocation(
@@ -11,7 +11,11 @@ import './index.css';
import {$isCodeHighlightNode} from '@lexical/code';
import {$isLinkNode, TOGGLE_LINK_COMMAND} from '@lexical/link';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import {mergeRegister} from '@lexical/utils';
import {
mergeRegister,
$findMatchingParent,
$getNearestNodeOfType,
} from '@lexical/utils';
import {
$getSelection,
$isRangeSelection,
@@ -20,7 +24,17 @@ import {
FORMAT_TEXT_COMMAND,
LexicalEditor,
SELECTION_CHANGE_COMMAND,
$isRootOrShadowRoot,
COMMAND_PRIORITY_CRITICAL,
} from 'lexical';
import {$isHeadingNode} from '@lexical/rich-text';
import {
INSERT_UNORDERED_LIST_COMMAND,
REMOVE_LIST_COMMAND,
$isListNode,
ListNode,
INSERT_ORDERED_LIST_COMMAND,
} from '@lexical/list';
import {useCallback, useEffect, useRef, useState} from 'react';
import {createPortal} from 'react-dom';
@@ -36,8 +50,26 @@ import {
LinkIcon,
SuperscriptIcon,
SubscriptIcon,
ListBulleted,
ListNumbered,
} from '@standardnotes/icons';
import {IconComponent} from '../../Theme/IconComponent';
import {sanitizeUrl} from '../../Utils/sanitizeUrl';
const blockTypeToBlockName = {
bullet: 'Bulleted List',
check: 'Check List',
code: 'Code Block',
h1: 'Heading 1',
h2: 'Heading 2',
h3: 'Heading 3',
h4: 'Heading 4',
h5: 'Heading 5',
h6: 'Heading 6',
number: 'Numbered List',
paragraph: 'Normal',
quote: 'Quote',
};
const IconSize = 15;
@@ -52,6 +84,8 @@ function TextFormatFloatingToolbar({
isStrikethrough,
isSubscript,
isSuperscript,
isBulletedList,
isNumberedList,
}: {
editor: LexicalEditor;
anchorElem: HTMLElement;
@@ -63,17 +97,43 @@ function TextFormatFloatingToolbar({
isSubscript: boolean;
isSuperscript: boolean;
isUnderline: boolean;
isBulletedList: boolean;
isNumberedList: boolean;
}): JSX.Element {
const popupCharStylesEditorRef = useRef<HTMLDivElement | null>(null);
const insertLink = useCallback(() => {
if (!isLink) {
editor.dispatchCommand(TOGGLE_LINK_COMMAND, 'https://');
editor.update(() => {
const selection = $getSelection();
const textContent = selection?.getTextContent();
if (!textContent) {
editor.dispatchCommand(TOGGLE_LINK_COMMAND, 'https://');
return;
}
editor.dispatchCommand(TOGGLE_LINK_COMMAND, sanitizeUrl(textContent));
});
} else {
editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
}
}, [editor, isLink]);
const formatBulletList = useCallback(() => {
if (!isBulletedList) {
editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined);
} else {
editor.dispatchCommand(REMOVE_LIST_COMMAND, undefined);
}
}, [isBulletedList]);
const formatNumberedList = useCallback(() => {
if (!isNumberedList) {
editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined);
} else {
editor.dispatchCommand(REMOVE_LIST_COMMAND, undefined);
}
}, [isNumberedList]);
const updateTextFormatFloatingToolbar = useCallback(() => {
const selection = $getSelection();
@@ -226,6 +286,22 @@ function TextFormatFloatingToolbar({
<LinkIcon />
</IconComponent>
</button>
<button
onClick={formatBulletList}
className={'popup-item spaced ' + (isBulletedList ? 'active' : '')}
aria-label="Insert bulleted list">
<IconComponent size={IconSize}>
<ListBulleted />
</IconComponent>
</button>
<button
onClick={formatNumberedList}
className={'popup-item spaced ' + (isNumberedList ? 'active' : '')}
aria-label="Insert numbered list">
<IconComponent size={IconSize}>
<ListNumbered />
</IconComponent>
</button>
</>
)}
</div>
@@ -236,6 +312,7 @@ function useFloatingTextFormatToolbar(
editor: LexicalEditor,
anchorElem: HTMLElement,
): JSX.Element | null {
const [activeEditor, setActiveEditor] = useState(editor);
const [isText, setIsText] = useState(false);
const [isLink, setIsLink] = useState(false);
const [isBold, setIsBold] = useState(false);
@@ -245,6 +322,8 @@ function useFloatingTextFormatToolbar(
const [isSubscript, setIsSubscript] = useState(false);
const [isSuperscript, setIsSuperscript] = useState(false);
const [isCode, setIsCode] = useState(false);
const [blockType, setBlockType] =
useState<keyof typeof blockTypeToBlockName>('paragraph');
const updatePopup = useCallback(() => {
editor.getEditorState().read(() => {
@@ -270,6 +349,42 @@ function useFloatingTextFormatToolbar(
return;
}
const anchorNode = selection.anchor.getNode();
let element =
anchorNode.getKey() === 'root'
? anchorNode
: $findMatchingParent(anchorNode, (e) => {
const parent = e.getParent();
return parent !== null && $isRootOrShadowRoot(parent);
});
if (element === null) {
element = anchorNode.getTopLevelElementOrThrow();
}
const elementKey = element.getKey();
const elementDOM = activeEditor.getElementByKey(elementKey);
if (elementDOM !== null) {
if ($isListNode(element)) {
const parentList = $getNearestNodeOfType<ListNode>(
anchorNode,
ListNode,
);
const type = parentList
? parentList.getListType()
: element.getListType();
setBlockType(type);
} else {
const type = $isHeadingNode(element)
? element.getTag()
: element.getType();
if (type in blockTypeToBlockName) {
setBlockType(type as keyof typeof blockTypeToBlockName);
}
}
}
const node = getSelectedNode(selection);
// Update text format
@@ -298,14 +413,19 @@ function useFloatingTextFormatToolbar(
setIsText(false);
}
});
}, [editor]);
}, [editor, activeEditor]);
useEffect(() => {
document.addEventListener('selectionchange', updatePopup);
return () => {
document.removeEventListener('selectionchange', updatePopup);
};
}, [updatePopup]);
return editor.registerCommand(
SELECTION_CHANGE_COMMAND,
(_payload, newEditor) => {
setActiveEditor(newEditor);
updatePopup();
return false;
},
COMMAND_PRIORITY_CRITICAL,
);
}, [editor, updatePopup]);
useEffect(() => {
return mergeRegister(
@@ -336,6 +456,8 @@ function useFloatingTextFormatToolbar(
isSuperscript={isSuperscript}
isUnderline={isUnderline}
isCode={isCode}
isBulletedList={blockType === 'bullet'}
isNumberedList={blockType === 'number'}
/>,
anchorElem,
);

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