Compare commits

...
Author SHA1 Message Date
StandardNotes CI ae03f84651 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
2023-05-05 17:12:47 +00:00
Aman Harwara 60e51d334c chore: only show links container toggle if container can be truncated (#2326) 2023-05-05 22:09:33 +05:30
StandardNotes CI 8c1f4bd064 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected].1
2023-05-05 15:16:09 +00:00
Aman Harwara 2b845e814e refactor: clipper screenshot (#2325) 2023-05-05 20:12:44 +05:30
Mo 313ce08c15 chore: fix test 2023-05-05 09:24:33 -05:00
Aman Harwara a811582a7f Merge branch 'main' of github.com:standardnotes/app 2023-05-05 19:47:06 +05:30
Aman Harwara b40c65871a chore: fix modal on mobile 2023-05-05 19:46:40 +05:30
Mo a7128fb6d6 chore: fix timing of successful purchase alert 2023-05-05 08:52:10 -05:00
StandardNotes CI a2dedd2675 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].9
 - @standardnotes/[email protected].9
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].0
2023-05-05 12:04:09 +00:00
Aman Harwara 071d5b1b56 Merge branch 'main' of github.com:standardnotes/app 2023-05-05 17:03:22 +05:30
Aman Harwara 0d3b7f7d94 feat(clipper): Added "Clip as screenshot" switch to allow clipping content as a screenshot and save it to Files 2023-05-05 17:03:13 +05:30
StandardNotes CI 1017683b44 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].8
 - @standardnotes/[email protected].8
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].1
2023-05-04 18:06:15 +00:00
Aman Harwara 74f8f9fafe chore: use generateNodesFromDOM from core 2023-05-04 23:01:56 +05:30
Aman Harwara c7d28d2b2f chore: fix keyboard shortcut priority 2023-05-04 22:25:58 +05:30
StandardNotes CI 9fa6919618 chore(release): publish
- @standardnotes/[email protected].5
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].7
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].7
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
2023-05-04 12:43:02 +00:00
Aman Harwara 9fbb845b1d feat: Replaced margin resizers with "Editor width" options. You can set it globally from Preferences > Appearance or per-note from the note options menu (#2324) 2023-05-04 17:42:16 +05:30
StandardNotes CI 488142683c chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].4
2023-05-04 11:30:04 +00:00
Mo 8385b4e89f fix: workspace names now default to account email after signing in. It can later be changed from the workspace switcher menu. 2023-05-04 05:54:11 -05:00
StandardNotes CI d9c480b67b chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
2023-05-04 10:40:29 +00:00
Aman Harwara c9d8a686ff chore: fix preferences menu placement on mobile 2023-05-04 15:41:57 +05:30
Karol Sójko 85ad62389b chore(web): fix copy of successfully joining shared subscription notification 2023-05-04 12:03:32 +02:00
StandardNotes CI 6e6a511771 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
2023-05-03 14:15:57 +00:00
Mo 44e91b5ede chore: copy and style improvements 2023-05-03 08:41:25 -05:00
StandardNotes CI 4aad554c1a chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].9
 - @standardnotes/[email protected].1
2023-05-03 13:28:45 +00:00
Mo 9f937f217b chore: remove phased out storage encryption policy (#2323) 2023-05-03 07:56:01 -05:00
StandardNotes CI e3f8b81139 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
2023-05-03 12:44:33 +00:00
Mo b117271d61 feat: Added Export option for Authenticator entries 2023-05-03 07:11:17 -05:00
StandardNotes CI e635e06ca0 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].8
 - @standardnotes/[email protected].1
2023-05-02 21:21:11 +00:00
Mo 75ecf4c393 fix: automatically convert Super notes to Markdown for the Plaintext Backups feature 2023-05-02 15:46:24 -05:00
Aman Harwara 2eddc3a1c6 chore: fix prefs open by default 2023-05-03 01:43:42 +05:30
StandardNotes CI 90d1ead9e3 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].7
 - @standardnotes/[email protected].0
2023-05-02 16:42:21 +00:00
Mo 7e3db49322 feat: Automatic plaintext backup option in Preferences > Backups will backup your notes and tags into plaintext, unencrypted folders on your computer. In addition, automatic encrypted text backups preference management has moved from the top-level menu in the desktop app to Preferences > Backups. (#2322) 2023-05-02 11:05:10 -05:00
Aman Harwara 3df23cdb5c chore: ios testflight 2023-05-02 13:27:47 +05:30
StandardNotes CI e739795396 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
2023-05-01 20:36:40 +00:00
Aman Harwara b5f31c3ca3 chore: manual ios testflight deploy 2023-05-02 01:35:43 +05:30
StandardNotes CI 053aaa5c9f chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected].1
2023-05-01 16:23:02 +00:00
Aman Harwara 9e0e176bac fix: Fixed the issue where imported notes would have incorrect "Created" date and time 2023-05-01 21:23:41 +05:30
Aman Harwara b1e211f91f chore: changelog 2023-05-01 21:21:49 +05:30
Aman Harwara 880cceecd5 Revert "feat: Swipe gestures on mobile are now enabled by default and have been improved. You can disable them from Preferences > General > Labs (#2319)"
This reverts commit 6d326e2db4.
2023-05-01 21:21:00 +05:30
StandardNotes CI 30c160f4b8 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
2023-05-01 14:14:26 +00:00
Aman Harwara 2b8d7e65ff chore: fix linking input 2023-05-01 19:16:35 +05:30
Aman Harwara 6d326e2db4 feat: Swipe gestures on mobile are now enabled by default and have been improved. You can disable them from Preferences > General > Labs (#2319) 2023-05-01 18:59:05 +05:30
StandardNotes CI 9fe88c6c10 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2023-04-28 19:27:37 +00:00
Aman Harwara 17dc74aaef chore: fix table view layout on tablet-size screens 2023-04-29 00:29:33 +05:30
Aman Harwara 9c8837ecdb Merge branch 'main' of github.com:standardnotes/app 2023-04-29 00:10:26 +05:30
Aman Harwara e1a47ea884 chore: display options menu keyboard nav 2023-04-29 00:10:13 +05:30
StandardNotes CI 06d6f3b481 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2023-04-28 11:30:21 +00:00
Aman Harwara 0ab9006c52 chore: update collapsible plugin 2023-04-28 16:26:02 +05:30
Aman Harwara 4d9c9d597b chore: make collapsible arrow key behavior same as lexical 2023-04-28 16:24:38 +05:30
Aman Harwara 40179bba06 fix: Fixed issue where removing a collapsible block would unwrap the contents in reverse order 2023-04-28 16:20:46 +05:30
Aman Harwara 8c548d40c4 chore: fix rename input theming 2023-04-28 15:56:17 +05:30
StandardNotes CI a4cfd603d2 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2023-04-28 08:16:09 +00:00
Aman Harwara 27ab5449f6 chore: upgrade lexical 2023-04-28 13:15:45 +05:30
Aman Harwara 4740e43d1c chore: fix default editor dropdown 2023-04-28 13:01:50 +05:30
StandardNotes CI b8f78eb4fd chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2023-04-27 15:51:05 +00:00
Aman Harwara 7c1ded30c7 Merge branch 'main' of github.com:standardnotes/app 2023-04-27 20:46:24 +05:30
Aman Harwara 247ff62422 fix(desktop): Fixed issue on Mac desktop app where multiple files could not be downloaded 2023-04-27 20:46:18 +05:30
StandardNotes CI 420cb4d945 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2023-04-27 15:12:31 +00:00
Aman Harwara 7b6e5f95d5 chore: ios ci 2023-04-27 20:07:39 +05:30
Aman Harwara beddb3cc6c Merge branch 'main' of github.com:standardnotes/app 2023-04-27 19:53:47 +05:30
Aman Harwara 725b49568a chore: ios ci 2023-04-27 19:53:37 +05:30
StandardNotes CI 6b89a6615f chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].9
2023-04-27 13:19:37 +00:00
Aman Harwara 13d56082d8 chore: fix menu closing 2023-04-27 18:19:25 +05:30
Aman Harwara 91b3978e14 Merge branch 'main' of github.com:standardnotes/app 2023-04-27 18:12:29 +05:30
Aman Harwara b34ab6ab28 chore: fix error when converting empty super note 2023-04-27 18:12:23 +05:30
StandardNotes CI 07ec2be5ee chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].8
2023-04-27 12:37:47 +00:00
Aman Harwara d1009e368a chore: fix mobile large font size 2023-04-27 18:06:20 +05:30
Aman Harwara b0cf179a90 Merge branch 'main' of github.com:standardnotes/app 2023-04-27 17:38:57 +05:30
Aman Harwara 2c4b574bd4 chore: ios ci 2023-04-27 17:38:48 +05:30
Aman Harwara d658769531 chore: tooltips 2023-04-27 17:37:31 +05:30
StandardNotes CI a10d5a6728 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].7
2023-04-27 11:08:06 +00:00
Aman Harwara 67d3f149b2 chore: ios ci 2023-04-27 16:02:50 +05:30
Aman Harwara 0baf500616 Merge branch 'main' of github.com:standardnotes/app 2023-04-27 15:58:06 +05:30
Aman Harwara ef27a21fb1 fix(desktop): Fixed issue where preferences header & tooltips are overlapped by custom titlebar 2023-04-27 15:57:56 +05:30
Aman Harwara 76bd995c9a chore: tooltips 2023-04-27 15:13:54 +05:30
StandardNotes CI 3ada748522 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].6
2023-04-27 09:19:43 +00:00
Aman Harwara f45a639a72 chore: close file menu on download 2023-04-27 14:29:59 +05:30
Aman Harwara 8bb5bb6f9f Merge branch 'main' of github.com:standardnotes/app 2023-04-27 14:21:34 +05:30
Aman Harwara 1925d8a9c2 chore: trigger build 2023-04-27 14:21:21 +05:30
StandardNotes CI 93a1402088 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
2023-04-27 08:33:18 +00:00
Aman Harwara cb15d183ca chore: update fastlane 2023-04-27 13:29:42 +05:30
StandardNotes CI e686aa29e8 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].5
2023-04-27 07:07:21 +00:00
Aman Harwara 96905fe980 Merge branch 'main' of github.com:standardnotes/app 2023-04-27 12:09:42 +05:30
Aman Harwara bd3fabcaa7 chore: ios ci 2023-04-27 12:09:33 +05:30
StandardNotes CI c6dcbf2cc9 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].4
2023-04-26 18:40:15 +00:00
Aman Harwara a5d24fa526 Merge branch 'main' of github.com:standardnotes/app 2023-04-26 23:39:12 +05:30
Aman Harwara aa16a287fa chore: trigger build 2023-04-26 23:39:04 +05:30
StandardNotes CI c41f686fd0 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
2023-04-26 18:01:06 +00:00
Aman Harwara ec358d6b07 chore: show android system webview update prompt if app fails to load (#2318) 2023-04-26 22:58:59 +05:30
Aman Harwara af1e18b47e chore: use latest macos runner 2023-04-26 22:25:06 +05:30
StandardNotes CI 681f717072 chore(release): publish
- @standardnotes/[email protected].4
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected].3
2023-04-26 14:25:15 +00:00
Mo af84d09b7a chore(snjs): remove cloudlink 2023-04-26 08:50:46 -05:00
Aman Harwara ccac1494c2 chore: revert labs changes 2023-04-26 19:05:52 +05:30
Aman Harwara 51b2d8b115 Merge branch 'main' of github.com:standardnotes/app 2023-04-26 19:02:28 +05:30
Aman Harwara d501ae4410 chore: preferences changes 2023-04-26 19:02:19 +05:30
StandardNotes CI ac73dba1db chore(release): publish
- @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected].2
2023-04-26 13:29:50 +00:00
Aman Harwara 157ad0b052 Merge branch 'main' of github.com:standardnotes/app 2023-04-26 18:29:16 +05:30
Aman Harwara e79c4ceea4 chore: fix dropdown z-index 2023-04-26 18:29:08 +05:30
376 changed files with 4352 additions and 2797 deletions
+57
View File
@@ -0,0 +1,57 @@
name: iOS Testflight
on: workflow_dispatch
jobs:
ios:
defaults:
run:
working-directory: packages/mobile
runs-on: macos-latest
timeout-minutes: 90
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Export version from package.json
run:
echo "PACKAGE_VERSION=$(grep '"version"' ../web/package.json | cut -d '"' -f 4 | cut -d "-" -f 1)" >> $GITHUB_ENV
- name: Install dependencies
run: yarn install --immutable && yarn install:pods
- run: yarn build:mobile
- name: Set ssh connection to Github
uses: webfactory/[email protected]
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Export Apple AppStore Connect API key
run: echo "${{ secrets.APPSTORE_CONNECT_KEY }}" > ios/Authkey.p8
- name: Ruby Setup for Fastlane
uses: ruby/setup-ruby@v1
with:
working-directory: 'packages/mobile'
- name: Setup fastlane
uses: maierj/[email protected]
env:
FASTLANE_USER: ${{ secrets.FASTLANE_USER }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
with:
lane: 'ios setup'
subdirectory: 'packages/mobile'
- name: fastlane
uses: maierj/[email protected]
env:
APPLE_APP_ID: ${{ secrets.APP_APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPSTORE_CONNECT_KEY_ID: ${{ secrets.APPSTORE_CONNECT_KEY_ID }}
APPSTORE_CONNECT_KEY_ISSUER_ID: ${{ secrets.APPSTORE_CONNECT_KEY_ISSUER_ID }}
BUILD_NUMBER: ${{ github.run_number }}
FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }}
FASTLANE_SESSION: ${{ secrets.FASTLANE_SESSION }}
FASTLANE_USER: ${{ secrets.FASTLANE_USER }}
MATCH_CERTIFICATES_URL: ${{ secrets.MATCH_CERTIFICATES_URL }}
MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }}
with:
lane: 'ios testflight'
subdirectory: 'packages/mobile'
+1 -3
View File
@@ -66,12 +66,10 @@ jobs:
packages/mobile/android/app/build/outputs/apk/prod/release/app-prod-release.apk
ios:
env:
ITMSTRANSPORTER_FORCE_ITMS_PACKAGE_UPLOAD: true
defaults:
run:
working-directory: packages/mobile
runs-on: macos-11
runs-on: macos-latest
timeout-minutes: 90
steps:
- name: Checkout code
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
**Note:** Version bump only for package @standardnotes/api
## [1.26.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/api
## [1.26.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/api
## [1.26.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/api
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api",
"version": "1.26.2",
"version": "1.26.5",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+110
View File
@@ -3,6 +3,116 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-05)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-05)
**Note:** Version bump only for package @standardnotes/clipper
# [1.1.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-05)
### Features
* **clipper:** Added "Clip as screenshot" switch to allow clipping content as a screenshot and save it to Files ([0d3b7f7](https://github.com/standardnotes/app/commit/0d3b7f7d94071675c1005bbe64fb11cc91bccf58))
## [1.0.46](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
**Note:** Version bump only for package @standardnotes/clipper
## [1.0.45](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
**Note:** Version bump only for package @standardnotes/clipper
## [1.0.44](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
**Note:** Version bump only for package @standardnotes/clipper
## [1.0.43](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
**Note:** Version bump only for package @standardnotes/clipper
## [1.0.42](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-03)
**Note:** Version bump only for package @standardnotes/clipper
## [1.0.41](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-03)
**Note:** Version bump only for package @standardnotes/clipper
## [1.0.40](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-03)
**Note:** Version bump only for package @standardnotes/clipper
## [1.0.39](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-02)
**Note:** Version bump only for package @standardnotes/clipper
## [1.0.38](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-02)
**Note:** Version bump only for package @standardnotes/clipper
## [1.0.37](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-01)
**Note:** Version bump only for package @standardnotes/clipper
## [1.0.36](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-01)
**Note:** Version bump only for package @standardnotes/clipper
## [1.0.35](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-28)
**Note:** Version bump only for package @standardnotes/clipper
## [1.0.34](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-28)
**Note:** Version bump only for package @standardnotes/clipper
## [1.0.33](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-28)
**Note:** Version bump only for package @standardnotes/clipper
## [1.0.32](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/clipper
## [1.0.31](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/clipper
## [1.0.30](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/clipper
## [1.0.29](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/clipper
## [1.0.28](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/clipper
## [1.0.27](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/clipper
## [1.0.26](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/clipper
## [1.0.25](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/clipper
## [1.0.24](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/clipper
## [1.0.23](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/clipper
## [1.0.22](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/clipper
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@standardnotes/clipper",
"description": "Web clipper browser extension for Standard Notes",
"version": "1.0.22",
"version": "1.1.2",
"private": true,
"scripts": {
"build-mv2": "yarn clean && webpack --config ./webpack.config.prod.js",
@@ -1,9 +1,9 @@
import { runtime, action, browserAction, windows, storage } from 'webextension-polyfill'
import { RuntimeMessage, RuntimeMessageTypes } from '../types/message'
import { runtime, action, browserAction, windows, storage, tabs } from 'webextension-polyfill'
import { ClipPayload, RuntimeMessage, RuntimeMessageTypes } from '../types/message'
const isFirefox = navigator.userAgent.indexOf('Firefox/') !== -1
const openPopupAndClipSelection = async (payload: { title: string; content: string }) => {
const openPopupAndClipSelection = async (payload: ClipPayload) => {
await storage.local.set({ clip: payload })
if (isFirefox) {
@@ -22,11 +22,15 @@ const openPopupAndClipSelection = async (payload: { title: string; content: stri
void openPopup()
}
runtime.onMessage.addListener((message: RuntimeMessage) => {
runtime.onMessage.addListener(async (message: RuntimeMessage) => {
if (message.type === RuntimeMessageTypes.OpenPopupWithSelection) {
if (!message.payload) {
return
}
void openPopupAndClipSelection(message.payload)
} else if (message.type === RuntimeMessageTypes.CaptureVisibleTab) {
return await tabs.captureVisibleTab(undefined, {
format: 'png',
})
}
})
+64 -3
View File
@@ -3,6 +3,7 @@ import { Readability } from '@mozilla/readability'
import { RuntimeMessage, RuntimeMessageTypes } from '../types/message'
let isSelectingNodeForClipping = false
let isScreenshotMode = false
runtime.onMessage.addListener(async (message: RuntimeMessage) => {
switch (message.type) {
@@ -10,6 +11,10 @@ runtime.onMessage.addListener(async (message: RuntimeMessage) => {
isSelectingNodeForClipping = true
return
}
case RuntimeMessageTypes.ToggleScreenshotMode: {
isScreenshotMode = message.enabled
return
}
case RuntimeMessageTypes.HasSelection: {
const selection = window.getSelection()
@@ -40,6 +45,13 @@ runtime.onMessage.addListener(async (message: RuntimeMessage) => {
return { title: document.title, content: result.innerHTML, url: window.location.href }
}
case RuntimeMessageTypes.GetFullPage: {
if (isScreenshotMode) {
const content = await runtime.sendMessage({
type: RuntimeMessageTypes.CaptureVisibleTab,
})
return { title: document.title, content: content, url: window.location.href, isScreenshot: true }
}
return { title: document.title, content: document.body.innerHTML, url: window.location.href }
}
case RuntimeMessageTypes.GetArticle: {
@@ -90,7 +102,56 @@ const disableNodeSelection = () => {
nodeOverlayElement.style.visibility = 'hidden'
}
window.addEventListener('click', (event) => {
const imageFromBase64 = (base64: string) => {
return new Promise<HTMLImageElement>((resolve, reject) => {
const image = new Image()
image.onload = () => {
resolve(image)
}
image.onerror = reject
image.src = base64
})
}
const toPng = async (element: HTMLElement) => {
const visibleTab: string = await runtime.sendMessage({
type: RuntimeMessageTypes.CaptureVisibleTab,
})
const image = await imageFromBase64(visibleTab)
const canvas = document.createElement('canvas')
canvas.width = image.width
canvas.height = image.height
const context = canvas.getContext('2d')
if (!context) {
throw new Error('Could not get canvas context')
}
context.drawImage(image, 0, 0)
const elementRect = element.getBoundingClientRect()
const x = elementRect.x
const y = elementRect.y
const width = elementRect.width
const height = elementRect.height
const imageData = context.getImageData(x, y, width, height)
canvas.width = width
canvas.height = height
context.putImageData(imageData, 0, 0)
return canvas.toDataURL('image/png')
}
window.addEventListener('click', async (event) => {
if (!isSelectingNodeForClipping) {
return
}
@@ -102,10 +163,10 @@ window.addEventListener('click', (event) => {
return
}
const title = document.title
const content = target.outerHTML
const content = isScreenshotMode ? await toPng(target) : target.outerHTML
void runtime.sendMessage({
type: RuntimeMessageTypes.OpenPopupWithSelection,
payload: { title, content, url: window.location.href },
payload: { title, content, url: window.location.href, isScreenshot: isScreenshotMode },
} as RuntimeMessage)
})
+1 -1
View File
@@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "Standard Notes Clipper",
"description": "Web clipper for Standard Notes",
"permissions": ["activeTab", "storage"],
"permissions": ["activeTab", "storage", "<all_urls>"],
"browser_action": {
"default_popup": "popup/index.html?route=extension"
},
+10 -1
View File
@@ -5,6 +5,8 @@ export const RuntimeMessageTypes = {
GetFullPage: 'get-full-page',
OpenPopupWithSelection: 'open-popup-with-selection',
StartNodeSelection: 'start-node-selection',
ToggleScreenshotMode: 'toggle-screenshot-mode',
CaptureVisibleTab: 'capture-visible-tab',
} as const
export type RuntimeMessageType = typeof RuntimeMessageTypes[keyof typeof RuntimeMessageTypes]
@@ -15,6 +17,7 @@ export type ClipPayload = {
title: string
content: string
url: string
isScreenshot?: boolean
}
export type RuntimeMessageReturnTypes = {
@@ -22,8 +25,10 @@ export type RuntimeMessageReturnTypes = {
[RuntimeMessageTypes.GetSelection]: ClipPayload
[RuntimeMessageTypes.HasSelection]: boolean
[RuntimeMessageTypes.GetFullPage]: ClipPayload
[RuntimeMessageTypes.CaptureVisibleTab]: string
[RuntimeMessageTypes.OpenPopupWithSelection]: void
[RuntimeMessageTypes.StartNodeSelection]: void
[RuntimeMessageTypes.ToggleScreenshotMode]: void
}
export type RuntimeMessage =
@@ -32,5 +37,9 @@ export type RuntimeMessage =
payload: ClipPayload
}
| {
type: Exclude<RuntimeMessageType, MessagesWithClipPayload>
type: typeof RuntimeMessageTypes.ToggleScreenshotMode
enabled: boolean
}
| {
type: Exclude<RuntimeMessageType, MessagesWithClipPayload | typeof RuntimeMessageTypes.ToggleScreenshotMode>
}
@@ -1,14 +1,14 @@
import { tabs } from 'webextension-polyfill'
import { RuntimeMessageReturnTypes, RuntimeMessageType } from '../types/message'
import { RuntimeMessage, RuntimeMessageReturnTypes } from '../types/message'
export default async function sendMessageToActiveTab<T extends RuntimeMessageType>(
type: T,
): Promise<RuntimeMessageReturnTypes[T] | undefined> {
export default async function sendMessageToActiveTab<T extends RuntimeMessage>(
message: T,
): Promise<RuntimeMessageReturnTypes[T['type']] | undefined> {
const [activeTab] = await tabs.query({ active: true, currentWindow: true, windowType: 'normal' })
if (!activeTab || !activeTab.id) {
return
}
return await tabs.sendMessage(activeTab.id, { type })
return await tabs.sendMessage(activeTab.id, message)
}
+4 -4
View File
@@ -1,4 +1,4 @@
PURCHASE_URL=https://website-dev.standardnotes.com/purchase
PLANS_URL=https://website-dev.standardnotes.com/plans
DASHBOARD_URL=https://website-dev.standardnotes.com/dashboard
DEFAULT_SYNC_SERVER=https://api-dev.standardnotes.com
PURCHASE_URL=https://standardnotes.com/purchase
PLANS_URL=https://standardnotes.com/plans
DASHBOARD_URL=https://standardnotes.com/dashboard
DEFAULT_SYNC_SERVER=https://api.standardnotes.com
+112
View File
@@ -3,6 +3,118 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.106.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-05)
**Note:** Version bump only for package @standardnotes/desktop
## [3.106.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-05)
**Note:** Version bump only for package @standardnotes/desktop
## [3.106.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-05)
**Note:** Version bump only for package @standardnotes/desktop
## [3.106.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
**Note:** Version bump only for package @standardnotes/desktop
## [3.106.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
**Note:** Version bump only for package @standardnotes/desktop
## [3.106.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
**Note:** Version bump only for package @standardnotes/desktop
## [3.106.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
**Note:** Version bump only for package @standardnotes/desktop
## [3.106.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-03)
**Note:** Version bump only for package @standardnotes/desktop
## [3.106.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-03)
**Note:** Version bump only for package @standardnotes/desktop
## [3.106.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-03)
**Note:** Version bump only for package @standardnotes/desktop
## [3.106.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-02)
**Note:** Version bump only for package @standardnotes/desktop
# [3.106.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-02)
### Features
* Automatic plaintext backup option in Preferences > Backups will backup your notes and tags into plaintext, unencrypted folders on your computer. In addition, automatic encrypted text backups preference management has moved from the top-level menu in the desktop app to Preferences > Backups. ([#2322](https://github.com/standardnotes/app/issues/2322)) ([7e3db49](https://github.com/standardnotes/app/commit/7e3db49322034aa644c3c99ccf9a5f1b508583aa))
## [3.105.86](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-01)
**Note:** Version bump only for package @standardnotes/desktop
## [3.105.85](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-01)
**Note:** Version bump only for package @standardnotes/desktop
## [3.105.84](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-28)
**Note:** Version bump only for package @standardnotes/desktop
## [3.105.83](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-28)
**Note:** Version bump only for package @standardnotes/desktop
## [3.105.82](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-28)
**Note:** Version bump only for package @standardnotes/desktop
## [3.105.81](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/desktop
## [3.105.80](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/desktop
## [3.105.79](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/desktop
## [3.105.78](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/desktop
## [3.105.77](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
### Bug Fixes
* **desktop:** Fixed issue where preferences header & tooltips are overlapped by custom titlebar ([ef27a21](https://github.com/standardnotes/app/commit/ef27a21fb141c4ef1a760cf6fb550e4b713f8f98))
## [3.105.76](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/desktop
## [3.105.75](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/desktop
## [3.105.74](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/desktop
## [3.105.73](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/desktop
## [3.105.72](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/desktop
## [3.105.71](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/desktop
+6 -48
View File
@@ -1,51 +1,17 @@
# Standard Notes
<div align="center">
[![Twitter Follow](https://img.shields.io/badge/follow-%40standardnotes-blue.svg?style=flat&logo=twitter)](https://twitter.com/standardnotes)
</div>
This application makes use of the core JS/CSS/HTML code found in the [web repo](https://github.com/standardnotes/app). For issues related to the actual app experience, please post issues in the web repo.
# Standard Notes Desktop App
## Running Locally
Make sure [Yarn](https://classic.yarnpkg.com/en/) is installed on your system.
Most commands below hog up a terminal process and must be conducted in different tabs. Be sure to quit any production version of the app running on your system first.
```bash
yarn install
yarn build:web # Or `yarn dev:web`
yarn dev
# In another terminal
yarn start
cd packages/snjs && yarn start # optional to watch snjs changes
cd packages/web && yarn watch # optional to watch web changes
yarn dev # to start compilation watch process for electron-related code
yarn start # to start dev app
```
We use [commitlint](https://github.com/conventional-changelog/commitlint) to validate commit messages.
Before making a pull request, make sure to check the output of the following commands:
```bash
yarn lint
yarn test # Make sure to start `yarn dev` before running the tests, and quit any running Standard Notes applications so they don't conflict.
```
Pull requests should target the `develop` branch.
### Installing dependencies
To determine where to install a dependency:
- If it is only required for building, install it in `package.json`'s `devDependencies`
- If it is required at runtime but can be packaged by webpack, install it in `package.json`'s `dependencies`.
- If it must be distributed as a node module (not packaged by webpack), install it in `app/package.json`'s `dependencies`
- Also make sure to declare it as an external commonjs dependency in `webpack.common.js`.
## Building
Build for all platforms:
- `yarn release`
## Building natively on arm64
Building arm64 releases on amd64 systems is only possible with AppImage, Debian and universal "dir" targets.
@@ -63,14 +29,6 @@ and making sure `$GEM_HOME/bin` is added to `$PATH`.
Snap releases also require a working snapcraft / `snapd` installation.
Building can then be done by running:
- `yarn install`
Followed by
- `node scripts/build.mjs deb-arm64`
## Installation
On Linux, download the latest AppImage from the [Releases](https://github.com/standardnotes/app/releases/latest) page, and give it executable permission:
-11
View File
@@ -1,4 +1,3 @@
import { action, makeObservable, observable } from 'mobx'
import { MessageType } from '../test/TestIpcMessage'
import { Store } from './javascripts/Main/Store/Store'
import { StoreKeys } from './javascripts/Main/Store/StoreKeys'
@@ -14,7 +13,6 @@ export class AppState {
readonly startUrl = Urls.indexHtml
readonly isPrimaryInstance: boolean
public willQuitApp = false
public lastBackupDate: number | null = null
public windowState?: WindowState
public deepLinkUrl?: string
public readonly updates: UpdateState
@@ -28,11 +26,6 @@ export class AppState {
this.lastRunVersion = this.store.get(StoreKeys.LastRunVersion) || 'unknown'
this.store.set(StoreKeys.LastRunVersion, this.version)
makeObservable(this, {
lastBackupDate: observable,
setBackupCreationDate: action,
})
this.updates = new UpdateState(this)
if (isTesting()) {
@@ -45,8 +38,4 @@ export class AppState {
public isRunningVersionForFirstTime(): boolean {
return this.lastRunVersion !== this.version
}
setBackupCreationDate(date: number | null): void {
this.lastBackupDate = date
}
}
+21
View File
@@ -0,0 +1,21 @@
import { isDev } from './javascripts/Main/Utils/Utils'
import { log as utilsLog } from '@standardnotes/utils'
export const isDevMode = isDev()
export enum LoggingDomain {
Backups,
}
const LoggingStatus: Record<LoggingDomain, boolean> = {
[LoggingDomain.Backups]: true,
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function log(domain: LoggingDomain, ...args: any[]): void {
if (!isDevMode || !LoggingStatus[domain]) {
return
}
utilsLog(LoggingDomain[domain], ...args)
}
+4
View File
@@ -29,6 +29,10 @@ export function initializeApplication(args: { app: Electron.App; ipcMain: Electr
if (isDev()) {
/** Expose the app's state as a global variable. Useful for debugging */
;(global as any).appState = state
setTimeout(() => {
state.windowState?.window.webContents.openDevTools()
}, 500)
}
}
+1 -1
View File
@@ -120,7 +120,7 @@ function migrateSnapStorage() {
error?.message ?? error,
)
}
store.set(StoreKeys.BackupsLocation, newLocation)
store.set(StoreKeys.LegacyTextBackupsLocation, newLocation)
console.log('Migration: finished moving backups directory.')
}
}
@@ -1,245 +0,0 @@
import { dialog, shell, WebContents } from 'electron'
import { promises as fs } from 'fs'
import path from 'path'
import { AppMessageType, MessageType } from '../../../../test/TestIpcMessage'
import { AppState } from '../../../AppState'
import { MessageToWebApp } from '../../Shared/IpcMessages'
import { StoreKeys } from '../Store/StoreKeys'
import { backups as str } from '../Strings'
import { Paths } from '../Types/Paths'
import {
deleteDir,
deleteDirContents,
ensureDirectoryExists,
FileDoesNotExist,
moveFiles,
openDirectoryPicker,
} from '../Utils/FileUtils'
import { handleTestMessage, send } from '../Utils/Testing'
import { isTesting, last } from '../Utils/Utils'
import { BackupsManagerInterface } from './BackupsManagerInterface'
function log(...message: any) {
console.log('BackupsManager:', ...message)
}
function logError(...message: any) {
console.error('BackupsManager:', ...message)
}
export const enum EnsureRecentBackupExists {
Success = 0,
BackupsAreDisabled = 1,
FailedToCreateBackup = 2,
}
export const BackupsDirectoryName = 'Standard Notes Backups'
const BackupFileExtension = '.txt'
function backupFileNameToDate(string: string): number {
string = path.basename(string, '.txt')
const dateTimeDelimiter = string.indexOf('T')
const date = string.slice(0, dateTimeDelimiter)
const time = string.slice(dateTimeDelimiter + 1).replace(/-/g, ':')
return Date.parse(date + 'T' + time)
}
function dateToSafeFilename(date: Date) {
return date.toISOString().replace(/:/g, '-')
}
async function copyDecryptScript(location: string) {
try {
await ensureDirectoryExists(location)
await fs.copyFile(Paths.decryptScript, path.join(location, path.basename(Paths.decryptScript)))
} catch (error) {
console.error(error)
}
}
export function createBackupsManager(webContents: WebContents, appState: AppState): BackupsManagerInterface {
let backupsLocation = appState.store.get(StoreKeys.BackupsLocation)
let backupsDisabled = appState.store.get(StoreKeys.BackupsDisabled)
let needsBackup = false
if (!backupsDisabled) {
void copyDecryptScript(backupsLocation)
}
determineLastBackupDate(backupsLocation)
.then((date) => appState.setBackupCreationDate(date))
.catch(console.error)
async function setBackupsLocation(location: string) {
const previousLocation = backupsLocation
if (previousLocation === location) {
return
}
const newLocation = path.join(location, BackupsDirectoryName)
let previousLocationFiles = await fs.readdir(previousLocation)
const backupFiles = previousLocationFiles
.filter((fileName) => fileName.endsWith(BackupFileExtension))
.map((fileName) => path.join(previousLocation, fileName))
await moveFiles(backupFiles, newLocation)
await copyDecryptScript(newLocation)
previousLocationFiles = await fs.readdir(previousLocation)
if (previousLocationFiles.length === 0 || previousLocationFiles[0] === path.basename(Paths.decryptScript)) {
await deleteDir(previousLocation)
}
/** Wait for the operation to be successful before saving new location */
backupsLocation = newLocation
appState.store.set(StoreKeys.BackupsLocation, backupsLocation)
}
async function saveBackupData(data: any) {
if (backupsDisabled) {
return
}
let success: boolean
let name: string | undefined
try {
name = await writeDataToFile(data)
log(`Data backup successfully saved: ${name}`)
success = true
appState.setBackupCreationDate(Date.now())
} catch (err) {
success = false
logError('An error occurred saving backup file', err)
}
webContents.send(MessageToWebApp.FinishedSavingBackup, { success })
if (isTesting()) {
send(AppMessageType.SavedBackup)
}
return name
}
function performBackup() {
if (backupsDisabled) {
return
}
webContents.send(MessageToWebApp.PerformAutomatedBackup)
}
async function writeDataToFile(data: any): Promise<string> {
await ensureDirectoryExists(backupsLocation)
const name = dateToSafeFilename(new Date()) + BackupFileExtension
const filePath = path.join(backupsLocation, name)
await fs.writeFile(filePath, data)
return name
}
let interval: NodeJS.Timeout | undefined
function beginBackups() {
if (interval) {
clearInterval(interval)
}
needsBackup = true
const hoursInterval = 12
const seconds = hoursInterval * 60 * 60
const milliseconds = seconds * 1000
interval = setInterval(performBackup, milliseconds)
}
function toggleBackupsStatus() {
backupsDisabled = !backupsDisabled
appState.store.set(StoreKeys.BackupsDisabled, backupsDisabled)
/** Create a backup on reactivation. */
if (!backupsDisabled) {
performBackup()
}
}
if (isTesting()) {
handleTestMessage(MessageType.DataArchive, (data: any) => saveBackupData(data))
handleTestMessage(MessageType.BackupsAreEnabled, () => !backupsDisabled)
handleTestMessage(MessageType.ToggleBackupsEnabled, toggleBackupsStatus)
handleTestMessage(MessageType.BackupsLocation, () => backupsLocation)
handleTestMessage(MessageType.PerformBackup, performBackup)
handleTestMessage(MessageType.CopyDecryptScript, copyDecryptScript)
handleTestMessage(MessageType.ChangeBackupsLocation, setBackupsLocation)
}
return {
get backupsAreEnabled() {
return !backupsDisabled
},
get backupsLocation() {
return backupsLocation
},
saveBackupData,
performBackup,
beginBackups,
toggleBackupsStatus,
async backupsCount(): Promise<number> {
let files = await fs.readdir(backupsLocation)
files = files.filter((fileName) => fileName.endsWith(BackupFileExtension))
return files.length
},
applicationDidBlur() {
if (needsBackup) {
needsBackup = false
performBackup()
}
},
viewBackups() {
void shell.openPath(backupsLocation)
},
async deleteBackups() {
await deleteDirContents(backupsLocation)
return copyDecryptScript(backupsLocation)
},
async changeBackupsLocation() {
const path = await openDirectoryPicker()
if (!path) {
return
}
try {
await setBackupsLocation(path)
performBackup()
} catch (e) {
logError(e)
void dialog.showMessageBox({
message: str().errorChangingDirectory(e),
})
}
},
}
}
async function determineLastBackupDate(backupsLocation: string): Promise<number | null> {
try {
const files = (await fs.readdir(backupsLocation))
.filter((filename) => filename.endsWith(BackupFileExtension) && !Number.isNaN(backupFileNameToDate(filename)))
.sort()
const lastBackupFileName = last(files)
if (!lastBackupFileName) {
return null
}
const backupDate = backupFileNameToDate(lastBackupFileName)
if (Number.isNaN(backupDate)) {
return null
}
return backupDate
} catch (error: any) {
if (error.code !== FileDoesNotExist) {
console.error(error)
}
return null
}
}
@@ -1,13 +0,0 @@
export interface BackupsManagerInterface {
backupsAreEnabled: boolean
toggleBackupsStatus(): void
backupsLocation: string
backupsCount(): Promise<number>
applicationDidBlur(): void
changeBackupsLocation(): void
beginBackups(): void
performBackup(): void
deleteBackups(): Promise<void>
viewBackups(): void
saveBackupData(data: unknown): void
}
@@ -1,18 +1,22 @@
import { LoggingDomain, log } from './../../../Logging'
import {
FileBackupRecord,
FileBackupsDevice,
FileBackupsMapping,
FileBackupReadToken,
FileBackupReadChunkResponse,
PlaintextBackupsMapping,
DesktopWatchedDirectoriesChange,
} from '@web/Application/Device/DesktopSnjsExports'
import { AppState } from 'app/AppState'
import { shell } from 'electron'
import { promises as fs } from 'fs'
import { WebContents, shell } from 'electron'
import { StoreKeys } from '../Store/StoreKeys'
import path from 'path'
import {
deleteFile,
deleteFileIfExists,
ensureDirectoryExists,
moveDirContents,
moveFile,
openDirectoryPicker,
readJSONFile,
writeFile,
@@ -20,6 +24,10 @@ import {
} from '../Utils/FileUtils'
import { FileDownloader } from './FileDownloader'
import { FileReadOperation } from './FileReadOperation'
import { Paths } from '../Types/Paths'
import { MessageToWebApp } from '../../Shared/IpcMessages'
const TextBackupFileExtension = '.txt'
export const FileBackupsConstantsV1 = {
Version: '1.0.0',
@@ -29,107 +37,112 @@ export const FileBackupsConstantsV1 = {
export class FilesBackupManager implements FileBackupsDevice {
private readOperations: Map<string, FileReadOperation> = new Map()
private plaintextMappingCache?: PlaintextBackupsMapping
constructor(private appState: AppState) {}
constructor(private appState: AppState, private webContents: WebContents) {}
public isFilesBackupsEnabled(): Promise<boolean> {
return Promise.resolve(this.appState.store.get(StoreKeys.FileBackupsEnabled))
}
private async findUuidForPlaintextBackupFileName(
backupsDirectory: string,
targetFilename: string,
): Promise<string | undefined> {
const mapping = await this.getPlaintextBackupsMappingFile(backupsDirectory)
public async enableFilesBackups(): Promise<void> {
const currentLocation = await this.getFilesBackupsLocation()
if (!currentLocation) {
const result = await this.changeFilesBackupsLocation()
if (!result) {
return
const uuid = Object.keys(mapping.files).find((uuid) => {
const entries = mapping.files[uuid]
for (const entry of entries) {
const filePath = entry.path
const filename = path.basename(filePath)
if (filename === targetFilename) {
return true
}
}
}
return false
})
this.appState.store.set(StoreKeys.FileBackupsEnabled, true)
const mapping = this.getMappingFileFromDisk()
if (!mapping) {
await this.saveFilesBackupsMappingFile(this.defaultMappingFileValue())
}
return uuid
}
public disableFilesBackups(): Promise<void> {
this.appState.store.set(StoreKeys.FileBackupsEnabled, false)
public async migrateLegacyFileBackupsToNewStructure(newLocation: string): Promise<void> {
const legacyLocation = await this.getLegacyFilesBackupsLocation()
if (!legacyLocation) {
return
}
return Promise.resolve()
await ensureDirectoryExists(newLocation)
const legacyMappingLocation = `${legacyLocation}/info.json`
const newMappingLocation = this.getFileBackupsMappingFilePath(newLocation)
await ensureDirectoryExists(path.dirname(newMappingLocation))
await moveFile(legacyMappingLocation, newMappingLocation)
await moveDirContents(legacyLocation, newLocation)
}
public async changeFilesBackupsLocation(): Promise<string | undefined> {
const newPath = await openDirectoryPicker()
public async isLegacyFilesBackupsEnabled(): Promise<boolean> {
return this.appState.store.get(StoreKeys.LegacyFileBackupsEnabled)
}
if (!newPath) {
async wasLegacyTextBackupsExplicitlyDisabled(): Promise<boolean> {
const value = this.appState.store.get(StoreKeys.LegacyTextBackupsDisabled)
return value === true
}
async getUserDocumentsDirectory(): Promise<string> {
return Paths.documentsDir
}
public async getLegacyFilesBackupsLocation(): Promise<string | undefined> {
return this.appState.store.get(StoreKeys.LegacyFileBackupsLocation)
}
async getLegacyTextBackupsLocation(): Promise<string | undefined> {
const savedLocation = this.appState.store.get(StoreKeys.LegacyTextBackupsLocation)
if (savedLocation) {
return savedLocation
}
const LegacyTextBackupsDirectory = 'Standard Notes Backups'
return `${Paths.homeDir}/${LegacyTextBackupsDirectory}`
}
public async presentDirectoryPickerForLocationChangeAndTransferOld(
appendPath: string,
oldLocation?: string,
): Promise<string | undefined> {
const selectedDirectory = await openDirectoryPicker('Select')
if (!selectedDirectory) {
return undefined
}
const oldPath = await this.getFilesBackupsLocation()
const newPath = path.join(selectedDirectory, path.normalize(appendPath))
if (oldPath) {
await this.transferFilesBackupsToNewLocation(oldPath, newPath)
} else {
this.appState.store.set(StoreKeys.FileBackupsLocation, newPath)
await ensureDirectoryExists(newPath)
if (oldLocation) {
await moveDirContents(path.normalize(oldLocation), newPath)
}
return newPath
}
private async transferFilesBackupsToNewLocation(oldPath: string, newPath: string): Promise<void> {
const mapping = await this.getMappingFileFromDisk()
if (!mapping) {
return
}
const entries = Object.values(mapping.files)
for (const entry of entries) {
const sourcePath = path.join(oldPath, entry.relativePath)
const destinationPath = path.join(newPath, entry.relativePath)
await moveDirContents(sourcePath, destinationPath)
}
for (const entry of entries) {
entry.absolutePath = path.join(newPath, entry.relativePath)
}
const oldMappingFileLocation = this.getMappingFileLocation()
this.appState.store.set(StoreKeys.FileBackupsLocation, newPath)
const result = await this.saveFilesBackupsMappingFile(mapping)
if (result === 'success') {
await deleteFile(oldMappingFileLocation)
}
private getFileBackupsMappingFilePath(backupsLocation: string): string {
return `${backupsLocation}/.settings/info.json`
}
public getFilesBackupsLocation(): Promise<string> {
return Promise.resolve(this.appState.store.get(StoreKeys.FileBackupsLocation))
private async getFileBackupsMappingFileFromDisk(backupsLocation: string): Promise<FileBackupsMapping | undefined> {
return readJSONFile<FileBackupsMapping>(this.getFileBackupsMappingFilePath(backupsLocation))
}
private getMappingFileLocation(): string {
const base = this.appState.store.get(StoreKeys.FileBackupsLocation)
return `${base}/info.json`
}
private async getMappingFileFromDisk(): Promise<FileBackupsMapping | undefined> {
return readJSONFile<FileBackupsMapping>(this.getMappingFileLocation())
}
private defaultMappingFileValue(): FileBackupsMapping {
private defaulFileBackupstMappingFileValue(): FileBackupsMapping {
return { version: FileBackupsConstantsV1.Version, files: {} }
}
async getFilesBackupsMappingFile(): Promise<FileBackupsMapping> {
const data = await this.getMappingFileFromDisk()
async getFilesBackupsMappingFile(backupsLocation: string): Promise<FileBackupsMapping> {
const data = await this.getFileBackupsMappingFileFromDisk(backupsLocation)
if (!data) {
return this.defaultMappingFileValue()
return this.defaulFileBackupstMappingFileValue()
}
for (const entry of Object.values(data.files)) {
@@ -139,23 +152,18 @@ export class FilesBackupManager implements FileBackupsDevice {
return data
}
async openFilesBackupsLocation(): Promise<void> {
const location = await this.getFilesBackupsLocation()
async openLocation(location: string): Promise<void> {
void shell.openPath(location)
}
async openFileBackup(record: FileBackupRecord): Promise<void> {
void shell.openPath(record.absolutePath)
}
async saveFilesBackupsMappingFile(file: FileBackupsMapping): Promise<'success' | 'failed'> {
await writeJSONFile(this.getMappingFileLocation(), file)
private async saveFilesBackupsMappingFile(location: string, file: FileBackupsMapping): Promise<'success' | 'failed'> {
await writeJSONFile(this.getFileBackupsMappingFilePath(location), file)
return 'success'
}
async saveFilesBackupsFile(
location: string,
uuid: string,
metaFile: string,
downloadRequest: {
@@ -164,9 +172,7 @@ export class FilesBackupManager implements FileBackupsDevice {
url: string
},
): Promise<'success' | 'failed'> {
const backupsDir = await this.getFilesBackupsLocation()
const fileDir = `${backupsDir}/${uuid}`
const fileDir = `${location}/${uuid}`
const metaFilePath = `${fileDir}/${FileBackupsConstantsV1.MetadataFileName}`
const binaryPath = `${fileDir}/${FileBackupsConstantsV1.BinaryFileName}`
@@ -184,25 +190,24 @@ export class FilesBackupManager implements FileBackupsDevice {
const result = await downloader.run()
if (result === 'success') {
const mapping = await this.getFilesBackupsMappingFile()
const mapping = await this.getFilesBackupsMappingFile(location)
mapping.files[uuid] = {
backedUpOn: new Date(),
absolutePath: fileDir,
relativePath: uuid,
metadataFileName: FileBackupsConstantsV1.MetadataFileName,
binaryFileName: FileBackupsConstantsV1.BinaryFileName,
version: FileBackupsConstantsV1.Version,
}
await this.saveFilesBackupsMappingFile(mapping)
await this.saveFilesBackupsMappingFile(location, mapping)
}
return result
}
async getFileBackupReadToken(record: FileBackupRecord): Promise<FileBackupReadToken> {
const operation = new FileReadOperation(record)
async getFileBackupReadToken(filePath: string): Promise<FileBackupReadToken> {
const operation = new FileReadOperation(filePath)
this.readOperations.set(operation.token, operation)
@@ -224,4 +229,180 @@ export class FilesBackupManager implements FileBackupsDevice {
return result
}
async getTextBackupsCount(location: string): Promise<number> {
let files = await fs.readdir(location)
files = files.filter((fileName) => fileName.endsWith(TextBackupFileExtension))
return files.length
}
async saveTextBackupData(location: string, data: string): Promise<void> {
log(LoggingDomain.Backups, 'Saving text backup data', 'to', location)
let success: boolean
try {
await ensureDirectoryExists(location)
const name = `${new Date().toISOString().replace(/:/g, '-')}${TextBackupFileExtension}`
const filePath = path.join(location, name)
await fs.writeFile(filePath, data)
success = true
} catch (err) {
success = false
console.error('An error occurred saving backup file', err)
}
log(LoggingDomain.Backups, 'Finished saving text backup data', { success })
}
async copyDecryptScript(location: string) {
try {
await ensureDirectoryExists(location)
await fs.copyFile(Paths.decryptScript, path.join(location, path.basename(Paths.decryptScript)))
} catch (error) {
console.error(error)
}
}
private getPlaintextMappingFilePath(location: string): string {
return `${location}/.settings/info.json`
}
private async getPlaintextMappingFileFromDisk(location: string): Promise<PlaintextBackupsMapping | undefined> {
return readJSONFile<PlaintextBackupsMapping>(this.getPlaintextMappingFilePath(location))
}
private async savePlaintextBackupsMappingFile(
location: string,
file: PlaintextBackupsMapping,
): Promise<'success' | 'failed'> {
await writeJSONFile(this.getPlaintextMappingFilePath(location), file)
return 'success'
}
private defaultPlaintextMappingFileValue(): PlaintextBackupsMapping {
return { version: '1.0', files: {} }
}
async getPlaintextBackupsMappingFile(location: string): Promise<PlaintextBackupsMapping> {
if (this.plaintextMappingCache) {
return this.plaintextMappingCache
}
let data = await this.getPlaintextMappingFileFromDisk(location)
if (!data) {
data = this.defaultPlaintextMappingFileValue()
}
this.plaintextMappingCache = data
return data
}
async savePlaintextNoteBackup(
location: string,
uuid: string,
name: string,
tags: string[],
data: string,
): Promise<void> {
log(LoggingDomain.Backups, 'Saving plaintext note backup', uuid, 'to', location)
const mapping = await this.getPlaintextBackupsMappingFile(location)
if (!mapping.files[uuid]) {
mapping.files[uuid] = []
}
const removeNoteFromAllDirectories = async () => {
const records = mapping.files[uuid]
for (const record of records) {
const filePath = path.join(location, record.path)
await deleteFileIfExists(filePath)
}
mapping.files[uuid] = []
}
await removeNoteFromAllDirectories()
const writeFileToPath = async (absolutePath: string, filename: string, data: string, forTag?: string) => {
const findMappingRecord = (tag?: string) => {
const records = mapping.files[uuid]
return records.find((record) => record.tag === tag)
}
await ensureDirectoryExists(absolutePath)
const relativePath = forTag ?? ''
const filenameWithSlashesEscaped = filename.replace(/\//g, '\u2215')
const fileAbsolutePath = path.join(absolutePath, relativePath, filenameWithSlashesEscaped)
await writeFile(fileAbsolutePath, data)
const existingRecord = findMappingRecord(forTag)
if (!existingRecord) {
mapping.files[uuid].push({
tag: forTag,
path: path.join(relativePath, filename),
})
} else {
existingRecord.path = path.join(relativePath, filename)
existingRecord.tag = forTag
}
}
const uuidPart = uuid.split('-')[0]
const condensedUuidPart = uuidPart.substring(0, 4)
if (tags.length === 0) {
await writeFileToPath(location, `${name}-${condensedUuidPart}.txt`, data)
} else {
for (const tag of tags) {
await writeFileToPath(location, `${name}-${condensedUuidPart}.txt`, data, tag)
}
}
}
async persistPlaintextBackupsMappingFile(location: string): Promise<void> {
if (!this.plaintextMappingCache) {
return
}
await this.savePlaintextBackupsMappingFile(location, this.plaintextMappingCache)
}
async monitorPlaintextBackupsLocationForChanges(backupsDirectory: string): Promise<void> {
const FEATURE_ENABLED = false
if (!FEATURE_ENABLED) {
return
}
try {
const watcher = fs.watch(backupsDirectory, { recursive: true })
for await (const event of watcher) {
const { eventType, filename } = event
if (eventType !== 'change' && eventType !== 'rename') {
continue
}
const itemUuid = await this.findUuidForPlaintextBackupFileName(backupsDirectory, filename)
if (itemUuid) {
try {
const change: DesktopWatchedDirectoriesChange = {
itemUuid,
path: path.join(backupsDirectory, filename),
type: eventType,
content: await fs.readFile(path.join(backupsDirectory, filename), 'utf-8'),
}
this.webContents.send(MessageToWebApp.WatchedDirectoriesChanges, [change])
} catch (err) {
log(LoggingDomain.Backups, 'Error processing watched change', err)
continue
}
}
}
} catch (err) {
if ((err as Error).name === 'AbortError') {
return
}
throw err
}
}
}
@@ -1,6 +1,5 @@
import { FileBackupReadChunkResponse, FileBackupRecord } from '@web/Application/Device/DesktopSnjsExports'
import { FileBackupReadChunkResponse } from '@web/Application/Device/DesktopSnjsExports'
import fs from 'fs'
import path from 'path'
const ONE_MB = 1024 * 1024
const CHUNK_LIMIT = ONE_MB * 5
@@ -11,9 +10,9 @@ export class FileReadOperation {
private localFileId: number
private fileLength: number
constructor(backupRecord: FileBackupRecord) {
this.token = backupRecord.absolutePath
this.localFileId = fs.openSync(path.join(backupRecord.absolutePath, backupRecord.binaryFileName), 'r')
constructor(filePath: string) {
this.token = filePath
this.localFileId = fs.openSync(filePath, 'r')
this.fileLength = fs.fstatSync(this.localFileId).size
}
@@ -20,7 +20,6 @@ import { checkForUpdate, openChangelog, showUpdateInstallationDialog } from '../
import { handleTestMessage } from '../Utils/Testing'
import { isDev, isTesting } from '../Utils/Utils'
import { MessageType } from './../../../../test/TestIpcMessage'
import { BackupsManagerInterface } from './../Backups/BackupsManagerInterface'
import { SpellcheckerManager } from './../SpellcheckerManager'
import { MenuManagerInterface } from './MenuManagerInterface'
@@ -112,14 +111,12 @@ function suggestionsMenu(
export function createMenuManager({
window,
appState,
backupsManager,
trayManager,
store,
spellcheckerManager,
}: {
window: Electron.BrowserWindow
appState: AppState
backupsManager: BackupsManagerInterface
trayManager: TrayManager
store: Store
spellcheckerManager?: SpellcheckerManager
@@ -167,7 +164,6 @@ export function createMenuManager({
editMenu(spellcheckerManager, reload),
viewMenu(window, store, reload),
windowMenu(store, trayManager, reload),
backupsMenu(backupsManager, reload),
updateMenu(window, appState),
...(isLinux() ? [keyringMenu(window, store)] : []),
helpMenu(window, shell),
@@ -468,34 +464,6 @@ function minimizeToTrayItem(store: Store, trayManager: TrayManager, reload: () =
}
}
function backupsMenu(archiveManager: BackupsManagerInterface, reload: () => any) {
return {
label: str().backups,
submenu: [
{
label: archiveManager.backupsAreEnabled ? str().disableAutomaticBackups : str().enableAutomaticBackups,
click() {
archiveManager.toggleBackupsStatus()
reload()
},
},
Separator,
{
label: str().changeBackupsLocation,
click() {
archiveManager.changeBackupsLocation()
},
},
{
label: str().openBackupsLocation,
click() {
void shell.openPath(archiveManager.backupsLocation)
},
},
],
}
}
function updateMenu(window: BrowserWindow, appState: AppState) {
const updateState = appState.updates
let label
@@ -8,12 +8,11 @@ const rendererPath = path.join('file://', __dirname, '/renderer.js')
import {
FileBackupsDevice,
FileBackupsMapping,
FileBackupRecord,
FileBackupReadToken,
FileBackupReadChunkResponse,
PlaintextBackupsMapping,
} from '@web/Application/Device/DesktopSnjsExports'
import { app, BrowserWindow } from 'electron'
import { BackupsManagerInterface } from '../Backups/BackupsManagerInterface'
import { KeychainInterface } from '../Keychain/KeychainInterface'
import { MenuManagerInterface } from '../Menus/MenuManagerInterface'
import { Component, PackageManagerInterface } from '../Packages/PackageManagerInterface'
@@ -29,7 +28,6 @@ export class RemoteBridge implements CrossProcessBridge {
constructor(
private window: BrowserWindow,
private keychain: KeychainInterface,
private backups: BackupsManagerInterface,
private packages: PackageManagerInterface,
private search: SearchManagerInterface,
private data: RemoteDataInterface,
@@ -54,28 +52,30 @@ export class RemoteBridge implements CrossProcessBridge {
getKeychainValue: this.getKeychainValue.bind(this),
setKeychainValue: this.setKeychainValue.bind(this),
clearKeychainValue: this.clearKeychainValue.bind(this),
localBackupsCount: this.localBackupsCount.bind(this),
viewlocalBackups: this.viewlocalBackups.bind(this),
deleteLocalBackups: this.deleteLocalBackups.bind(this),
displayAppMenu: this.displayAppMenu.bind(this),
saveDataBackup: this.saveDataBackup.bind(this),
syncComponents: this.syncComponents.bind(this),
onMajorDataChange: this.onMajorDataChange.bind(this),
onSearch: this.onSearch.bind(this),
onInitialDataLoad: this.onInitialDataLoad.bind(this),
destroyAllData: this.destroyAllData.bind(this),
getFilesBackupsMappingFile: this.getFilesBackupsMappingFile.bind(this),
saveFilesBackupsFile: this.saveFilesBackupsFile.bind(this),
isFilesBackupsEnabled: this.isFilesBackupsEnabled.bind(this),
enableFilesBackups: this.enableFilesBackups.bind(this),
disableFilesBackups: this.disableFilesBackups.bind(this),
changeFilesBackupsLocation: this.changeFilesBackupsLocation.bind(this),
getFilesBackupsLocation: this.getFilesBackupsLocation.bind(this),
openFilesBackupsLocation: this.openFilesBackupsLocation.bind(this),
openFileBackup: this.openFileBackup.bind(this),
isLegacyFilesBackupsEnabled: this.isLegacyFilesBackupsEnabled.bind(this),
getLegacyFilesBackupsLocation: this.getLegacyFilesBackupsLocation.bind(this),
getFileBackupReadToken: this.getFileBackupReadToken.bind(this),
readNextChunk: this.readNextChunk.bind(this),
askForMediaAccess: this.askForMediaAccess.bind(this),
wasLegacyTextBackupsExplicitlyDisabled: this.wasLegacyTextBackupsExplicitlyDisabled.bind(this),
getLegacyTextBackupsLocation: this.getLegacyTextBackupsLocation.bind(this),
saveTextBackupData: this.saveTextBackupData.bind(this),
savePlaintextNoteBackup: this.savePlaintextNoteBackup.bind(this),
openLocation: this.openLocation.bind(this),
presentDirectoryPickerForLocationChangeAndTransferOld:
this.presentDirectoryPickerForLocationChangeAndTransferOld.bind(this),
getPlaintextBackupsMappingFile: this.getPlaintextBackupsMappingFile.bind(this),
persistPlaintextBackupsMappingFile: this.persistPlaintextBackupsMappingFile.bind(this),
getTextBackupsCount: this.getTextBackupsCount.bind(this),
migrateLegacyFileBackupsToNewStructure: this.migrateLegacyFileBackupsToNewStructure.bind(this),
getUserDocumentsDirectory: this.getUserDocumentsDirectory.bind(this),
monitorPlaintextBackupsLocationForChanges: this.monitorPlaintextBackupsLocationForChanges.bind(this),
}
}
@@ -135,51 +135,28 @@ export class RemoteBridge implements CrossProcessBridge {
return this.keychain.clearKeychainValue()
}
async localBackupsCount() {
return this.backups.backupsCount()
}
viewlocalBackups() {
this.backups.viewBackups()
}
async deleteLocalBackups() {
return this.backups.deleteBackups()
}
syncComponents(components: Component[]) {
void this.packages.syncComponents(components)
}
onMajorDataChange() {
this.backups.performBackup()
}
onSearch(text: string) {
this.search.findInPage(text)
}
onInitialDataLoad() {
this.backups.beginBackups()
}
destroyAllData() {
this.data.destroySensitiveDirectories()
}
saveDataBackup(data: unknown) {
this.backups.saveBackupData(data)
}
displayAppMenu() {
this.menus.popupMenu()
}
getFilesBackupsMappingFile(): Promise<FileBackupsMapping> {
return this.fileBackups.getFilesBackupsMappingFile()
getFilesBackupsMappingFile(location: string): Promise<FileBackupsMapping> {
return this.fileBackups.getFilesBackupsMappingFile(location)
}
saveFilesBackupsFile(
location: string,
uuid: string,
metaFile: string,
downloadRequest: {
@@ -188,43 +165,74 @@ export class RemoteBridge implements CrossProcessBridge {
url: string
},
): Promise<'success' | 'failed'> {
return this.fileBackups.saveFilesBackupsFile(uuid, metaFile, downloadRequest)
return this.fileBackups.saveFilesBackupsFile(location, uuid, metaFile, downloadRequest)
}
getFileBackupReadToken(record: FileBackupRecord): Promise<FileBackupReadToken> {
return this.fileBackups.getFileBackupReadToken(record)
getFileBackupReadToken(filePath: string): Promise<FileBackupReadToken> {
return this.fileBackups.getFileBackupReadToken(filePath)
}
readNextChunk(nextToken: string): Promise<FileBackupReadChunkResponse> {
return this.fileBackups.readNextChunk(nextToken)
}
public isFilesBackupsEnabled(): Promise<boolean> {
return this.fileBackups.isFilesBackupsEnabled()
public isLegacyFilesBackupsEnabled(): Promise<boolean> {
return this.fileBackups.isLegacyFilesBackupsEnabled()
}
public enableFilesBackups(): Promise<void> {
return this.fileBackups.enableFilesBackups()
public getLegacyFilesBackupsLocation(): Promise<string | undefined> {
return this.fileBackups.getLegacyFilesBackupsLocation()
}
public disableFilesBackups(): Promise<void> {
return this.fileBackups.disableFilesBackups()
wasLegacyTextBackupsExplicitlyDisabled(): Promise<boolean> {
return this.fileBackups.wasLegacyTextBackupsExplicitlyDisabled()
}
public changeFilesBackupsLocation(): Promise<string | undefined> {
return this.fileBackups.changeFilesBackupsLocation()
getLegacyTextBackupsLocation(): Promise<string | undefined> {
return this.fileBackups.getLegacyTextBackupsLocation()
}
public getFilesBackupsLocation(): Promise<string> {
return this.fileBackups.getFilesBackupsLocation()
saveTextBackupData(location: string, data: string): Promise<void> {
return this.fileBackups.saveTextBackupData(location, data)
}
public openFilesBackupsLocation(): Promise<void> {
return this.fileBackups.openFilesBackupsLocation()
savePlaintextNoteBackup(location: string, uuid: string, name: string, tags: string[], data: string): Promise<void> {
return this.fileBackups.savePlaintextNoteBackup(location, uuid, name, tags, data)
}
public openFileBackup(record: FileBackupRecord): Promise<void> {
return this.fileBackups.openFileBackup(record)
openLocation(path: string): Promise<void> {
return this.fileBackups.openLocation(path)
}
presentDirectoryPickerForLocationChangeAndTransferOld(
appendPath: string,
oldLocation?: string | undefined,
): Promise<string | undefined> {
return this.fileBackups.presentDirectoryPickerForLocationChangeAndTransferOld(appendPath, oldLocation)
}
getPlaintextBackupsMappingFile(location: string): Promise<PlaintextBackupsMapping> {
return this.fileBackups.getPlaintextBackupsMappingFile(location)
}
persistPlaintextBackupsMappingFile(location: string): Promise<void> {
return this.fileBackups.persistPlaintextBackupsMappingFile(location)
}
getTextBackupsCount(location: string): Promise<number> {
return this.fileBackups.getTextBackupsCount(location)
}
migrateLegacyFileBackupsToNewStructure(newPath: string): Promise<void> {
return this.fileBackups.migrateLegacyFileBackupsToNewStructure(newPath)
}
getUserDocumentsDirectory(): Promise<string> {
return this.fileBackups.getUserDocumentsDirectory()
}
monitorPlaintextBackupsLocationForChanges(backupsDirectory: string): Promise<void> {
return this.fileBackups.monitorPlaintextBackupsLocationForChanges(backupsDirectory)
}
askForMediaAccess(type: 'camera' | 'microphone'): Promise<boolean> {
@@ -156,7 +156,7 @@ export function createSpellcheckerManager(
const firstOutlier = session.availableSpellCheckerLanguages.find(
(language, index) => availableSpellCheckerLanguages[index] !== language,
)
throw new Error(`Found unsupported language code: ${firstOutlier}`)
console.error(`Found unsupported language code: ${firstOutlier}`)
}
setSpellcheckerLanguages()
@@ -4,30 +4,34 @@ export enum StoreKeys {
ExtServerHost = 'extServerHost',
UseSystemMenuBar = 'useSystemMenuBar',
MenuBarVisible = 'isMenuBarVisible',
BackupsLocation = 'backupsLocation',
BackupsDisabled = 'backupsDisabled',
MinimizeToTray = 'minimizeToTray',
EnableAutoUpdate = 'enableAutoUpdates',
ZoomFactor = 'zoomFactor',
SelectedSpellCheckerLanguageCodes = 'selectedSpellCheckerLanguageCodes',
UseNativeKeychain = 'useNativeKeychain',
FileBackupsEnabled = 'fileBackupsEnabled',
FileBackupsLocation = 'fileBackupsLocation',
LastRunVersion = 'LastRunVersion',
LegacyTextBackupsLocation = 'backupsLocation',
LegacyTextBackupsDisabled = 'backupsDisabled',
LegacyFileBackupsEnabled = 'fileBackupsEnabled',
LegacyFileBackupsLocation = 'fileBackupsLocation',
}
export interface StoreData {
[StoreKeys.ExtServerHost]: string
[StoreKeys.UseSystemMenuBar]: boolean
[StoreKeys.MenuBarVisible]: boolean
[StoreKeys.BackupsLocation]: string
[StoreKeys.BackupsDisabled]: boolean
[StoreKeys.MinimizeToTray]: boolean
[StoreKeys.EnableAutoUpdate]: boolean
[StoreKeys.UseNativeKeychain]: boolean | null
[StoreKeys.ZoomFactor]: number
[StoreKeys.SelectedSpellCheckerLanguageCodes]: Set<Language> | null
[StoreKeys.FileBackupsEnabled]: boolean
[StoreKeys.FileBackupsLocation]: string
[StoreKeys.LastRunVersion]: string
[StoreKeys.LegacyTextBackupsLocation]: string
[StoreKeys.LegacyTextBackupsDisabled]: boolean
[StoreKeys.LegacyFileBackupsEnabled]: boolean
[StoreKeys.LegacyFileBackupsLocation]: string
}
@@ -1,31 +1,30 @@
import fs from 'fs'
import path from 'path'
import { BackupsDirectoryName } from '../Backups/BackupsManager'
import { Language } from '../SpellcheckerManager'
import { FileDoesNotExist } from '../Utils/FileUtils'
import { ensureIsBoolean, isBoolean, isDev, isTesting } from '../Utils/Utils'
import { ensureIsBoolean, isBoolean } from '../Utils/Utils'
import { StoreData, StoreKeys } from './StoreKeys'
import { app, logError } from './Store'
import { logError } from './Store'
export function createSanitizedStoreData(data: any = {}): StoreData {
return {
[StoreKeys.MenuBarVisible]: ensureIsBoolean(data[StoreKeys.MenuBarVisible], true),
[StoreKeys.UseSystemMenuBar]: ensureIsBoolean(data[StoreKeys.UseSystemMenuBar], false),
[StoreKeys.BackupsDisabled]: ensureIsBoolean(data[StoreKeys.BackupsDisabled], false),
[StoreKeys.MinimizeToTray]: ensureIsBoolean(data[StoreKeys.MinimizeToTray], false),
[StoreKeys.EnableAutoUpdate]: ensureIsBoolean(data[StoreKeys.EnableAutoUpdate], true),
[StoreKeys.UseNativeKeychain]: isBoolean(data[StoreKeys.UseNativeKeychain])
? data[StoreKeys.UseNativeKeychain]
: null,
[StoreKeys.ExtServerHost]: data[StoreKeys.ExtServerHost],
[StoreKeys.BackupsLocation]: sanitizeBackupsLocation(data[StoreKeys.BackupsLocation]),
[StoreKeys.ZoomFactor]: sanitizeZoomFactor(data[StoreKeys.ZoomFactor]),
[StoreKeys.SelectedSpellCheckerLanguageCodes]: sanitizeSpellCheckerLanguageCodes(
data[StoreKeys.SelectedSpellCheckerLanguageCodes],
),
[StoreKeys.FileBackupsEnabled]: ensureIsBoolean(data[StoreKeys.FileBackupsEnabled], false),
[StoreKeys.FileBackupsLocation]: data[StoreKeys.FileBackupsLocation],
[StoreKeys.LastRunVersion]: data[StoreKeys.LastRunVersion],
[StoreKeys.LegacyTextBackupsLocation]: data[StoreKeys.LegacyTextBackupsLocation],
[StoreKeys.LegacyTextBackupsDisabled]: data[StoreKeys.LegacyTextBackupsDisabled],
[StoreKeys.LegacyFileBackupsEnabled]: data[StoreKeys.LegacyFileBackupsEnabled],
[StoreKeys.LegacyFileBackupsLocation]: data[StoreKeys.LegacyFileBackupsLocation],
}
}
function sanitizeZoomFactor(factor?: any): number {
@@ -35,29 +34,7 @@ function sanitizeZoomFactor(factor?: any): number {
return 1
}
}
function sanitizeBackupsLocation(location?: unknown): string {
const defaultPath = path.join(
isTesting() ? app.getPath('userData') : isDev() ? app.getPath('documents') : app.getPath('home'),
BackupsDirectoryName,
)
if (typeof location !== 'string') {
return defaultPath
}
try {
const stat = fs.lstatSync(location)
if (stat.isDirectory()) {
return location
}
/** Path points to something other than a directory */
return defaultPath
} catch (e) {
/** Path does not point to a valid directory */
logError(e)
return defaultPath
}
}
function sanitizeSpellCheckerLanguageCodes(languages?: unknown): Set<Language> | null {
if (!languages) {
return null
@@ -13,8 +13,6 @@ export function createEnglishStrings(): Strings {
automaticUpdatesDisabled: 'Automatic Updates Disabled',
disableAutomaticBackups: 'Disable Automatic Backups',
enableAutomaticBackups: 'Enable Automatic Backups',
changeBackupsLocation: 'Change Backups Location',
openBackupsLocation: 'Open Backups Location',
emailSupport: 'Email Support',
website: 'Website',
gitHub: 'GitHub',
@@ -146,15 +144,5 @@ export function createEnglishStrings(): Strings {
},
unknownVersionName: 'Unknown',
},
backups: {
errorChangingDirectory(error: any): string {
return (
'An error occurred while changing your backups directory. ' +
'If this issue persists, please contact support with the following ' +
'information: \n' +
JSON.stringify(error)
)
},
},
}
}
@@ -28,15 +28,5 @@ export function createFrenchStrings(): Strings {
},
extensions: fallback.extensions,
updates: fallback.updates,
backups: {
errorChangingDirectory(error: any): string {
return (
"Une erreur s'est produite lors du déplacement du dossier de " +
'sauvegardes. Si le problème est récurrent, contactez le support ' +
'technique (en anglais) avec les informations suivantes:\n' +
JSON.stringify(error)
)
},
},
}
}
@@ -53,10 +53,6 @@ export function updates() {
return str().updates
}
export function backups() {
return str().backups
}
function stringsForLocale(locale: string): Strings {
if (locale === 'en' || locale.startsWith('en-')) {
return createEnglishStrings()
@@ -4,7 +4,6 @@ export interface Strings {
tray: TrayStrings
extensions: ExtensionsStrings
updates: UpdateStrings
backups: BackupsStrings
}
interface AppMenuStrings {
@@ -18,8 +17,6 @@ interface AppMenuStrings {
automaticUpdatesDisabled: string
disableAutomaticBackups: string
enableAutomaticBackups: string
changeBackupsLocation: string
openBackupsLocation: string
emailSupport: string
website: string
gitHub: string
@@ -103,7 +100,3 @@ interface UpdateStrings {
}
unknownVersionName: string
}
interface BackupsStrings {
errorChangingDirectory(error: any): string
}
@@ -36,6 +36,9 @@ export const Paths = {
get userDataDir(): string {
return app.getPath('userData')
},
get homeDir(): string {
return app.getPath('home')
},
get documentsDir(): string {
return app.getPath('documents')
},
@@ -2,11 +2,10 @@ import { compareVersions } from 'compare-versions'
import { BrowserWindow, dialog, shell } from 'electron'
import electronLog from 'electron-log'
import { autoUpdater } from 'electron-updater'
import { action, autorun, computed, makeObservable, observable } from 'mobx'
import { action, computed, makeObservable, observable } from 'mobx'
import { MessageType } from '../../../test/TestIpcMessage'
import { AppState } from '../../AppState'
import { MessageToWebApp } from '../Shared/IpcMessages'
import { BackupsManagerInterface } from './Backups/BackupsManagerInterface'
import { StoreKeys } from './Store/StoreKeys'
import { updates as str } from './Strings'
import { autoUpdatingAvailable } from './Types/Constants'
@@ -84,7 +83,7 @@ export class UpdateState {
let updatesSetup = false
export function setupUpdates(window: BrowserWindow, appState: AppState, backupsManager: BackupsManagerInterface): void {
export function setupUpdates(window: BrowserWindow, appState: AppState): void {
if (!autoUpdatingAvailable) {
return
}
@@ -97,22 +96,6 @@ export function setupUpdates(window: BrowserWindow, appState: AppState, backupsM
const updateState = appState.updates
function checkUpdateSafety(): boolean {
let canUpdate: boolean
if (appState.store.get(StoreKeys.BackupsDisabled)) {
canUpdate = true
} else {
canUpdate = updateState.enableAutoUpdate && isLessThanOneHourFromNow(appState.lastBackupDate)
}
autoUpdater.autoInstallOnAppQuit = canUpdate
autoUpdater.autoDownload = canUpdate
return canUpdate
}
autorun(checkUpdateSafety)
const oneHour = 1 * 60 * 60 * 1000
setInterval(checkUpdateSafety, oneHour)
autoUpdater.on('update-downloaded', (info: { version?: string }) => {
window.webContents.send(MessageToWebApp.UpdateAvailable, null)
updateState.autoUpdateHasBeenDownloaded(info.version || null)
@@ -122,10 +105,9 @@ export function setupUpdates(window: BrowserWindow, appState: AppState, backupsM
autoUpdater.on(MessageToWebApp.UpdateAvailable, (info: { version?: string }) => {
updateState.checkedForUpdate(info.version || null)
if (updateState.enableAutoUpdate) {
const canUpdate = checkUpdateSafety()
if (!canUpdate) {
backupsManager.performBackup()
}
const canUpdate = updateState.enableAutoUpdate
autoUpdater.autoInstallOnAppQuit = canUpdate
autoUpdater.autoDownload = canUpdate
}
})
autoUpdater.on('update-not-available', (info: { version?: string }) => {
@@ -164,46 +146,21 @@ function quitAndInstall(window: BrowserWindow) {
}, 0)
}
function isLessThanOneHourFromNow(date: number | null) {
const now = Date.now()
const onHourMs = 1 * 60 * 60 * 1000
return now - (date ?? 0) < onHourMs
}
export async function showUpdateInstallationDialog(parentWindow: BrowserWindow, appState: AppState): Promise<void> {
if (!appState.updates.latestVersion) {
return
}
if (appState.lastBackupDate && isLessThanOneHourFromNow(appState.lastBackupDate)) {
const result = await dialog.showMessageBox(parentWindow, {
type: 'info',
title: str().updateReady.title,
message: str().updateReady.message(appState.updates.latestVersion),
buttons: [str().updateReady.installLater, str().updateReady.quitAndInstall],
cancelId: 0,
})
const result = await dialog.showMessageBox(parentWindow, {
type: 'info',
title: str().updateReady.title,
message: str().updateReady.message(appState.updates.latestVersion),
buttons: [str().updateReady.installLater, str().updateReady.quitAndInstall],
cancelId: 0,
})
const buttonIndex = result.response
if (buttonIndex === 1) {
quitAndInstall(parentWindow)
}
} else {
const cancelId = 0
const result = await dialog.showMessageBox({
type: 'warning',
title: str().updateReady.title,
message: str().updateReady.noRecentBackupMessage,
detail: str().updateReady.noRecentBackupDetail(appState.lastBackupDate),
checkboxLabel: str().updateReady.noRecentBackupChecbox,
checkboxChecked: false,
buttons: [str().updateReady.installLater, str().updateReady.quitAndInstall],
cancelId,
})
if (!result.checkboxChecked || result.response === cancelId) {
return
}
const buttonIndex = result.response
if (buttonIndex === 1) {
quitAndInstall(parentWindow)
}
}
@@ -27,9 +27,10 @@ export function debouncedJSONDiskWriter(durationMs: number, location: string, da
}, durationMs)
}
export async function openDirectoryPicker(): Promise<string | undefined> {
export async function openDirectoryPicker(buttonLabel?: string): Promise<string | undefined> {
const result = await dialog.showOpenDialog({
properties: ['openDirectory', 'showHiddenFiles', 'createDirectory'],
buttonLabel: buttonLabel,
})
return result.filePaths[0]
@@ -63,6 +64,7 @@ export function writeJSONFileSync(filepath: string, data: unknown): void {
fs.writeFileSync(filepath, JSON.stringify(data, null, 2), 'utf8')
}
/** Creates the directory if it doesn't exist. */
export async function ensureDirectoryExists(dirPath: string): Promise<void> {
try {
const stat = await fs.promises.lstat(dirPath)
@@ -251,7 +253,7 @@ export async function moveFiles(sources: string[], destDir: string): Promise<voi
return Promise.all(sources.map((fileName) => moveFile(fileName, path.join(destDir, path.basename(fileName)))))
}
async function moveFile(source: PathLike, destination: PathLike) {
export async function moveFile(source: PathLike, destination: PathLike) {
try {
await fs.promises.rename(source, destination)
} catch (_error) {
@@ -261,6 +263,14 @@ async function moveFile(source: PathLike, destination: PathLike) {
}
}
export async function deleteFileIfExists(filePath: PathLike): Promise<void> {
try {
await deleteFile(filePath)
} catch {
return
}
}
/** Deletes a file, handling EPERM and EBUSY errors on Windows. */
export async function deleteFile(filePath: PathLike): Promise<void> {
for (let i = 1, maxTries = 10; i < maxTries; i++) {
@@ -6,8 +6,6 @@ import path from 'path'
import { AppMessageType, MessageType } from '../../../test/TestIpcMessage'
import { AppState } from '../../AppState'
import { MessageToWebApp } from '../Shared/IpcMessages'
import { createBackupsManager } from './Backups/BackupsManager'
import { BackupsManagerInterface } from './Backups/BackupsManagerInterface'
import { FilesBackupManager } from './FileBackups/FileBackupsManager'
import { Keychain } from './Keychain/Keychain'
import { MediaManager } from './Media/MediaManager'
@@ -35,7 +33,6 @@ const WINDOW_MIN_HEIGHT = 400
export interface WindowState {
window: Electron.BrowserWindow
menuManager: MenuManagerInterface
backupsManager: BackupsManagerInterface
trayManager: TrayManager
}
@@ -64,7 +61,6 @@ export async function createWindowState({
;(global as any).RemoteBridge = new RemoteBridge(
window,
Keychain,
services.backupsManager,
services.packageManager,
services.searchManager,
{
@@ -93,7 +89,6 @@ export async function createWindowState({
window.on('blur', () => {
window.webContents.send(MessageToWebApp.WindowBlurred, null)
services.backupsManager.applicationDidBlur()
})
window.once('ready-to-show', () => {
@@ -201,8 +196,7 @@ async function createWindowServices(window: Electron.BrowserWindow, appState: Ap
const searchManager = initializeSearchManager(window.webContents)
initializeZoomManager(window, appState.store)
const backupsManager = createBackupsManager(window.webContents, appState)
const updateManager = setupUpdates(window, appState, backupsManager)
const updateManager = setupUpdates(window, appState)
const trayManager = createTrayManager(window, appState.store)
const spellcheckerManager = createSpellcheckerManager(appState.store, window.webContents, appLocale)
const mediaManager = new MediaManager()
@@ -214,16 +208,14 @@ async function createWindowServices(window: Electron.BrowserWindow, appState: Ap
const menuManager = createMenuManager({
appState,
window,
backupsManager,
trayManager,
store: appState.store,
spellcheckerManager,
})
const fileBackupsManager = new FilesBackupManager(appState)
const fileBackupsManager = new FilesBackupManager(appState, window.webContents)
return {
backupsManager,
updateManager,
trayManager,
spellcheckerManager,
@@ -3,52 +3,22 @@ import { Component } from '../Main/Packages/PackageManagerInterface'
export interface CrossProcessBridge extends FileBackupsDevice {
get extServerHost(): string
get useNativeKeychain(): boolean
get rendererPath(): string
get isMacOS(): boolean
get appVersion(): string
get useSystemMenuBar(): boolean
closeWindow(): void
minimizeWindow(): void
maximizeWindow(): void
unmaximizeWindow(): void
isWindowMaximized(): boolean
getKeychainValue(): Promise<unknown>
setKeychainValue: (value: unknown) => Promise<void>
clearKeychainValue(): Promise<boolean>
localBackupsCount(): Promise<number>
viewlocalBackups(): void
deleteLocalBackups(): Promise<void>
saveDataBackup(data: unknown): void
displayAppMenu(): void
syncComponents(components: Component[]): void
onMajorDataChange(): void
onSearch(text: string): void
onInitialDataLoad(): void
destroyAllData(): void
askForMediaAccess(type: 'camera' | 'microphone'): Promise<boolean>
}
@@ -1,11 +1,11 @@
import {
DesktopDeviceInterface,
Environment,
FileBackupsMapping,
RawKeychainValue,
FileBackupRecord,
FileBackupReadToken,
FileBackupReadChunkResponse,
FileBackupsMapping,
PlaintextBackupsMapping,
} from '@web/Application/Device/DesktopSnjsExports'
import { WebOrDesktopDevice } from '@web/Application/Device/WebOrDesktopDevice'
import { Component } from '../Main/Packages/PackageManagerInterface'
@@ -25,6 +25,33 @@ export class DesktopDevice extends WebOrDesktopDevice implements DesktopDeviceIn
super(appVersion)
}
openLocation(path: string): Promise<void> {
return this.remoteBridge.openLocation(path)
}
presentDirectoryPickerForLocationChangeAndTransferOld(
appendPath: string,
oldLocation?: string | undefined,
): Promise<string | undefined> {
return this.remoteBridge.presentDirectoryPickerForLocationChangeAndTransferOld(appendPath, oldLocation)
}
getFilesBackupsMappingFile(location: string): Promise<FileBackupsMapping> {
return this.remoteBridge.getFilesBackupsMappingFile(location)
}
getPlaintextBackupsMappingFile(location: string): Promise<PlaintextBackupsMapping> {
return this.remoteBridge.getPlaintextBackupsMappingFile(location)
}
persistPlaintextBackupsMappingFile(location: string): Promise<void> {
return this.remoteBridge.persistPlaintextBackupsMappingFile(location)
}
getTextBackupsCount(location: string): Promise<number> {
return this.remoteBridge.getTextBackupsCount(location)
}
async getKeychainValue() {
if (this.useNativeKeychain) {
const keychainValue = await this.remoteBridge.getKeychainValue()
@@ -57,18 +84,10 @@ export class DesktopDevice extends WebOrDesktopDevice implements DesktopDeviceIn
this.remoteBridge.syncComponents(components)
}
onMajorDataChange() {
this.remoteBridge.onMajorDataChange()
}
onSearch(text: string) {
this.remoteBridge.onSearch(text)
}
onInitialDataLoad() {
this.remoteBridge.onInitialDataLoad()
}
async clearAllDataFromDevice(workspaceIdentifiers: string[]): Promise<{ killsApplication: boolean }> {
await super.clearAllDataFromDevice(workspaceIdentifiers)
@@ -77,69 +96,36 @@ export class DesktopDevice extends WebOrDesktopDevice implements DesktopDeviceIn
return { killsApplication: true }
}
async downloadBackup() {
const receiver = window.webClient
receiver.didBeginBackup()
try {
const data = await receiver.requestBackupFile()
if (data) {
this.remoteBridge.saveDataBackup(data)
} else {
receiver.didFinishBackup(false)
}
} catch (error) {
console.error(error)
receiver.didFinishBackup(false)
}
public isLegacyFilesBackupsEnabled(): Promise<boolean> {
return this.remoteBridge.isLegacyFilesBackupsEnabled()
}
async localBackupsCount() {
return this.remoteBridge.localBackupsCount()
public getLegacyFilesBackupsLocation(): Promise<string | undefined> {
return this.remoteBridge.getLegacyFilesBackupsLocation()
}
viewlocalBackups() {
this.remoteBridge.viewlocalBackups()
wasLegacyTextBackupsExplicitlyDisabled(): Promise<boolean> {
return this.remoteBridge.wasLegacyTextBackupsExplicitlyDisabled()
}
async deleteLocalBackups() {
return this.remoteBridge.deleteLocalBackups()
getUserDocumentsDirectory(): Promise<string> {
return this.remoteBridge.getUserDocumentsDirectory()
}
public isFilesBackupsEnabled(): Promise<boolean> {
return this.remoteBridge.isFilesBackupsEnabled()
getLegacyTextBackupsLocation(): Promise<string | undefined> {
return this.remoteBridge.getLegacyTextBackupsLocation()
}
public enableFilesBackups(): Promise<void> {
return this.remoteBridge.enableFilesBackups()
saveTextBackupData(workspaceId: string, data: string): Promise<void> {
return this.remoteBridge.saveTextBackupData(workspaceId, data)
}
public disableFilesBackups(): Promise<void> {
return this.remoteBridge.disableFilesBackups()
}
public changeFilesBackupsLocation(): Promise<string | undefined> {
return this.remoteBridge.changeFilesBackupsLocation()
}
public getFilesBackupsLocation(): Promise<string> {
return this.remoteBridge.getFilesBackupsLocation()
}
async getFilesBackupsMappingFile(): Promise<FileBackupsMapping> {
return this.remoteBridge.getFilesBackupsMappingFile()
}
async openFilesBackupsLocation(): Promise<void> {
return this.remoteBridge.openFilesBackupsLocation()
}
openFileBackup(record: FileBackupRecord): Promise<void> {
return this.remoteBridge.openFileBackup(record)
savePlaintextNoteBackup(location: string, uuid: string, name: string, tags: string[], data: string): Promise<void> {
return this.remoteBridge.savePlaintextNoteBackup(location, uuid, name, tags, data)
}
async saveFilesBackupsFile(
location: string,
uuid: string,
metaFile: string,
downloadRequest: {
@@ -148,17 +134,25 @@ export class DesktopDevice extends WebOrDesktopDevice implements DesktopDeviceIn
url: string
},
): Promise<'success' | 'failed'> {
return this.remoteBridge.saveFilesBackupsFile(uuid, metaFile, downloadRequest)
return this.remoteBridge.saveFilesBackupsFile(location, uuid, metaFile, downloadRequest)
}
getFileBackupReadToken(record: FileBackupRecord): Promise<FileBackupReadToken> {
return this.remoteBridge.getFileBackupReadToken(record)
getFileBackupReadToken(filePath: string): Promise<FileBackupReadToken> {
return this.remoteBridge.getFileBackupReadToken(filePath)
}
migrateLegacyFileBackupsToNewStructure(newPath: string): Promise<void> {
return this.remoteBridge.migrateLegacyFileBackupsToNewStructure(newPath)
}
readNextChunk(token: string): Promise<FileBackupReadChunkResponse> {
return this.remoteBridge.readNextChunk(token)
}
monitorPlaintextBackupsLocationForChanges(backupsDirectory: string): Promise<void> {
return this.remoteBridge.monitorPlaintextBackupsLocationForChanges(backupsDirectory)
}
async performHardReset(): Promise<void> {
console.error('performHardReset is not yet implemented')
}
@@ -1,28 +1,25 @@
import { IpcRendererEvent } from 'electron/renderer'
import { MessageToWebApp } from '../Shared/IpcMessages'
import { ElectronMainEvents, MainEventHandler } from '../Shared/ElectronMainEvents'
const { ipcRenderer } = require('electron')
const RemoteBridge = require('@electron/remote').getGlobal('RemoteBridge')
const { contextBridge } = require('electron')
type MainEventCallback = (event: IpcRendererEvent, value: any) => void
process.once('loaded', function () {
contextBridge.exposeInMainWorld('electronRemoteBridge', RemoteBridge.exposableValue)
contextBridge.exposeInMainWorld('electronMainEvents', {
handleUpdateAvailable: (callback: MainEventCallback) => ipcRenderer.on(MessageToWebApp.UpdateAvailable, callback),
const mainEvents: ElectronMainEvents = {
setUpdateAvailableHandler: (handler: MainEventHandler) => ipcRenderer.on(MessageToWebApp.UpdateAvailable, handler),
handlePerformAutomatedBackup: (callback: MainEventCallback) =>
ipcRenderer.on(MessageToWebApp.PerformAutomatedBackup, callback),
setWindowBlurredHandler: (handler: MainEventHandler) => ipcRenderer.on(MessageToWebApp.WindowBlurred, handler),
handleFinishedSavingBackup: (callback: MainEventCallback) =>
ipcRenderer.on(MessageToWebApp.FinishedSavingBackup, callback),
setWindowFocusedHandler: (handler: MainEventHandler) => ipcRenderer.on(MessageToWebApp.WindowFocused, handler),
handleWindowBlurred: (callback: MainEventCallback) => ipcRenderer.on(MessageToWebApp.WindowBlurred, callback),
setWatchedDirectoriesChangeHandler: (handler: MainEventHandler) =>
ipcRenderer.on(MessageToWebApp.WatchedDirectoriesChanges, handler),
handleWindowFocused: (callback: MainEventCallback) => ipcRenderer.on(MessageToWebApp.WindowFocused, callback),
setInstallComponentCompleteHandler: (handler: MainEventHandler) =>
ipcRenderer.on(MessageToWebApp.InstallComponentComplete, handler),
}
handleInstallComponentComplete: (callback: MainEventCallback) =>
ipcRenderer.on(MessageToWebApp.InstallComponentComplete, callback),
})
contextBridge.exposeInMainWorld('electronMainEvents', mainEvents)
})
@@ -1,8 +1,12 @@
import { DesktopClientRequiresWebMethods } from '@web/Application/Device/DesktopSnjsExports'
import {
DesktopClientRequiresWebMethods,
DesktopWatchedDirectoriesChanges,
} from '@web/Application/Device/DesktopSnjsExports'
import { StartApplication } from '@web/Application/Device/StartApplication'
import { IpcRendererEvent } from 'electron/renderer'
import { CrossProcessBridge } from './CrossProcessBridge'
import { DesktopDevice } from './DesktopDevice'
import { ElectronMainEvents } from '../Shared/ElectronMainEvents'
declare const DEFAULT_SYNC_SERVER: string
declare const WEBSOCKET_URL: string
@@ -23,7 +27,7 @@ declare global {
purchaseUrl: string
startApplication: StartApplication
zip: unknown
electronMainEvents: any
electronMainEvents: ElectronMainEvents
}
}
@@ -114,29 +118,36 @@ async function configureWindow(remoteBridge: CrossProcessBridge) {
if (isMacOS || useSystemMenuBar) {
// !important is important here because #desktop-title-bar has display: flex.
sheet.insertRule('#desktop-title-bar { display: none !important; }', sheet.cssRules.length)
} else {
/* Use custom title bar. Take the sn-titlebar-height off of
the app content height so its not overflowing */
sheet.insertRule(
'[data-dialog] { position: relative; height: calc(100vh - var(--sn-desktop-titlebar-height)) !important; margin-top: var(--sn-desktop-titlebar-height) !important; }',
sheet.cssRules.length,
)
sheet.insertRule(
'[data-mobile-popover] { padding-top: var(--sn-desktop-titlebar-height) !important; }',
sheet.cssRules.length,
)
}
}
window.electronMainEvents.handleUpdateAvailable(() => {
window.electronMainEvents.setUpdateAvailableHandler(() => {
window.webClient.updateAvailable()
})
window.electronMainEvents.handlePerformAutomatedBackup(() => {
void window.device.downloadBackup()
})
window.electronMainEvents.handleFinishedSavingBackup((_: IpcRendererEvent, data: { success: boolean }) => {
window.webClient.didFinishBackup(data.success)
})
window.electronMainEvents.handleWindowBlurred(() => {
window.electronMainEvents.setWindowBlurredHandler(() => {
window.webClient.windowLostFocus()
})
window.electronMainEvents.handleWindowFocused(() => {
window.electronMainEvents.setWindowFocusedHandler(() => {
window.webClient.windowGainedFocus()
})
window.electronMainEvents.handleInstallComponentComplete((_: IpcRendererEvent, data: any) => {
window.electronMainEvents.setInstallComponentCompleteHandler((_: IpcRendererEvent, data: any) => {
void window.webClient.onComponentInstallationComplete(data.component, undefined)
})
window.electronMainEvents.setWatchedDirectoriesChangeHandler((_: IpcRendererEvent, changes: unknown) => {
void window.webClient.handleWatchedDirectoriesChanges(changes as DesktopWatchedDirectoriesChanges)
})
@@ -0,0 +1,11 @@
import { IpcRendererEvent } from 'electron/renderer'
export type MainEventHandler = (event: IpcRendererEvent, value: unknown) => void
export interface ElectronMainEvents {
setUpdateAvailableHandler(handler: MainEventHandler): void
setWindowBlurredHandler(handler: MainEventHandler): void
setWindowFocusedHandler(handler: MainEventHandler): void
setInstallComponentCompleteHandler(handler: MainEventHandler): void
setWatchedDirectoriesChangeHandler(handler: MainEventHandler): void
}
@@ -1,10 +1,9 @@
export enum MessageToWebApp {
UpdateAvailable = 'update-available',
PerformAutomatedBackup = 'download-backup',
FinishedSavingBackup = 'finished-saving-backup',
WindowBlurred = 'window-blurred',
WindowFocused = 'window-focused',
InstallComponentComplete = 'install-component-complete',
WatchedDirectoriesChanges = 'watched-directories-changes',
}
export enum MessageToMainProcess {
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@standardnotes/desktop",
"main": "./app/dist/index.js",
"version": "3.105.71",
"version": "3.106.11",
"license": "AGPL-3.0-or-later",
"author": "Standard Notes.",
"private": true,
@@ -23,7 +23,7 @@
"format": "prettier --write .",
"lint:eslint": "eslint app/index.ts app/application.ts app/javascripts/**/*.ts",
"lint:formatting": "prettier --check app",
"lint": "yarn lint:formatting && yarn lint:eslint app",
"lint": "yarn lint:formatting && yarn lint:eslint app && yarn tsc",
"tsc": "tsc --noEmit",
"release:mac": "node scripts/build.mjs mac",
"start": "electron ./app --enable-logging --icon _icon/icon.png",
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.21.33](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
**Note:** Version bump only for package @standardnotes/encryption
## [1.21.32](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/encryption
## [1.21.31](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/encryption
## [1.21.30](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/encryption
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/encryption",
"version": "1.21.30",
"version": "1.21.33",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.59.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/features
## [1.59.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/features
## [1.59.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/features
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/features",
"version": "1.59.1",
"version": "1.59.3",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -1,9 +1,5 @@
export enum FeatureIdentifier {
CloudLink = 'org.standardnotes.cloudlink',
DailyDropboxBackup = 'org.standardnotes.daily-dropbox-backup',
DailyEmailBackup = 'org.standardnotes.daily-email-backup',
DailyGDriveBackup = 'org.standardnotes.daily-gdrive-backup',
DailyOneDriveBackup = 'org.standardnotes.daily-onedrive-backup',
Files = 'org.standardnotes.files',
FilesLowStorageTier = 'org.standardnotes.files-low-storage-tier',
FilesMaximumStorageTier = 'org.standardnotes.files-max-storage-tier',
@@ -41,21 +41,6 @@ export function serverFeatures(): ServerFeatureDescription[] {
identifier: FeatureIdentifier.SignInAlerts,
permission_name: PermissionName.SignInAlerts,
},
{
availableInSubscriptions: [SubscriptionName.PlusPlan, SubscriptionName.ProPlan],
identifier: FeatureIdentifier.DailyDropboxBackup,
permission_name: PermissionName.DailyDropboxBackup,
},
{
availableInSubscriptions: [SubscriptionName.PlusPlan, SubscriptionName.ProPlan],
identifier: FeatureIdentifier.DailyGDriveBackup,
permission_name: PermissionName.DailyGDriveBackup,
},
{
availableInSubscriptions: [SubscriptionName.PlusPlan, SubscriptionName.ProPlan],
identifier: FeatureIdentifier.DailyOneDriveBackup,
permission_name: PermissionName.DailyOneDriveBackup,
},
{
availableInSubscriptions: [SubscriptionName.ProPlan],
identifier: FeatureIdentifier.FilesMaximumStorageTier,
@@ -2,14 +2,10 @@ export enum PermissionName {
AccountSwitcher = 'app:account-switcher',
AutobiographyTheme = 'theme:autobiography',
BoldEditor = 'editor:bold',
CloudLink = 'component:cloud-link',
CodeEditor = 'editor:code-editor',
ComponentFilesafe = 'component:filesafe',
ComponentFolders = 'component:folders',
DailyDropboxBackup = 'server:daily-dropbox-backup',
DailyEmailBackup = 'server:daily-email-backup',
DailyGDriveBackup = 'server:daily-gdrive-backup',
DailyOneDriveBackup = 'server:daily-onedrive-backup',
Files = 'app:files',
FilesMaximumStorageTier = 'server:files-max-storage-tier',
FilesLowStorageTier = 'server:files-low-storage-tier',
+20
View File
@@ -3,6 +3,26 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.28.40](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
**Note:** Version bump only for package @standardnotes/filepicker
## [1.28.39](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-02)
**Note:** Version bump only for package @standardnotes/filepicker
## [1.28.38](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-02)
**Note:** Version bump only for package @standardnotes/filepicker
## [1.28.37](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/filepicker
## [1.28.36](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/filepicker
## [1.28.35](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/filepicker
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/filepicker",
"version": "1.28.35",
"version": "1.28.40",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+24
View File
@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.15.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
**Note:** Version bump only for package @standardnotes/files
## [1.15.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-02)
### Bug Fixes
* automatically convert Super notes to Markdown for the Plaintext Backups feature ([75ecf4c](https://github.com/standardnotes/app/commit/75ecf4c393a6bc0ab2d6bf50dcbee21cef520746))
# [1.15.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-02)
### Features
* Automatic plaintext backup option in Preferences > Backups will backup your notes and tags into plaintext, unencrypted folders on your computer. In addition, automatic encrypted text backups preference management has moved from the top-level menu in the desktop app to Preferences > Backups. ([#2322](https://github.com/standardnotes/app/issues/2322)) ([7e3db49](https://github.com/standardnotes/app/commit/7e3db49322034aa644c3c99ccf9a5f1b508583aa))
## [1.14.47](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/files
## [1.14.46](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/files
## [1.14.45](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/files
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files",
"version": "1.14.45",
"version": "1.15.2",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -0,0 +1,8 @@
export type DesktopWatchedDirectoriesChange = {
itemUuid: string
path: string
type: 'rename' | 'change'
content: string
}
export type DesktopWatchedDirectoriesChanges = DesktopWatchedDirectoriesChange[]
@@ -1,12 +1,44 @@
import { FileDownloadProgress } from '../Types/FileDownloadProgress'
import { FileBackupRecord, FileBackupsMapping } from './FileBackupsMapping'
import { FileBackupsMapping } from './FileBackupsMapping'
type PlaintextNoteRecord = {
tag?: string
path: string
}
type UuidString = string
export type PlaintextBackupsMapping = {
version: string
files: Record<UuidString, PlaintextNoteRecord[]>
}
export interface FileBackupsDevice
extends FileBackupsMethods,
LegacyBackupsMethods,
PlaintextBackupsMethods,
TextBackupsMethods {
openLocation(path: string): Promise<void>
/**
* The reason we combine presenting a directory picker and transfering old files to the new location
* in one function is so we don't have to expose a general `transferDirectories` function to the web app,
* which would give it too much power.
* @param appendPath The path to append to the selected directory.
*/
presentDirectoryPickerForLocationChangeAndTransferOld(
appendPath: string,
oldLocation?: string,
): Promise<string | undefined>
monitorPlaintextBackupsLocationForChanges(backupsDirectory: string): Promise<void>
}
export type FileBackupReadToken = string
export type FileBackupReadChunkResponse = { chunk: Uint8Array; isLast: boolean; progress: FileDownloadProgress }
export interface FileBackupsDevice {
getFilesBackupsMappingFile(): Promise<FileBackupsMapping>
interface FileBackupsMethods {
getFilesBackupsMappingFile(location: string): Promise<FileBackupsMapping>
saveFilesBackupsFile(
location: string,
uuid: string,
metaFile: string,
downloadRequest: {
@@ -15,13 +47,26 @@ export interface FileBackupsDevice {
url: string
},
): Promise<'success' | 'failed'>
getFileBackupReadToken(record: FileBackupRecord): Promise<FileBackupReadToken>
getFileBackupReadToken(filePath: string): Promise<FileBackupReadToken>
readNextChunk(token: string): Promise<FileBackupReadChunkResponse>
isFilesBackupsEnabled(): Promise<boolean>
enableFilesBackups(): Promise<void>
disableFilesBackups(): Promise<void>
changeFilesBackupsLocation(): Promise<string | undefined>
getFilesBackupsLocation(): Promise<string>
openFilesBackupsLocation(): Promise<void>
openFileBackup(record: FileBackupRecord): Promise<void>
}
interface PlaintextBackupsMethods {
getPlaintextBackupsMappingFile(location: string): Promise<PlaintextBackupsMapping>
persistPlaintextBackupsMappingFile(location: string): Promise<void>
savePlaintextNoteBackup(location: string, uuid: string, name: string, tags: string[], data: string): Promise<void>
}
interface TextBackupsMethods {
getTextBackupsCount(location: string): Promise<number>
saveTextBackupData(location: string, data: string): Promise<void>
getUserDocumentsDirectory(): Promise<string>
}
interface LegacyBackupsMethods {
migrateLegacyFileBackupsToNewStructure(newPath: string): Promise<void>
isLegacyFilesBackupsEnabled(): Promise<boolean>
getLegacyFilesBackupsLocation(): Promise<string | undefined>
wasLegacyTextBackupsExplicitlyDisabled(): Promise<boolean>
getLegacyTextBackupsLocation(): Promise<string | undefined>
}
@@ -2,7 +2,6 @@ import { FileBackupsConstantsV1 } from './FileBackupsConstantsV1'
export type FileBackupRecord = {
backedUpOn: Date
absolutePath: string
relativePath: string
metadataFileName: typeof FileBackupsConstantsV1.MetadataFileName
binaryFileName: typeof FileBackupsConstantsV1.BinaryFileName
@@ -1,14 +1,37 @@
import { OnChunkCallback } from '../Chunker/OnChunkCallback'
import { DesktopWatchedDirectoriesChanges } from '../Device/DesktopWatchedChanges'
import { FileBackupRecord } from '../Device/FileBackupsMapping'
import { SuperConverterServiceInterface } from './SuperConverterServiceInterface'
export interface BackupServiceInterface {
openAllDirectoriesContainingBackupFiles(): void
prependWorkspacePathForPath(path: string): string
importWatchedDirectoryChanges(changes: DesktopWatchedDirectoriesChanges): Promise<void>
setSuperConverter(converter: SuperConverterServiceInterface): void
getFileBackupInfo(file: { uuid: string }): Promise<FileBackupRecord | undefined>
readEncryptedFileFromBackup(uuid: string, onChunk: OnChunkCallback): Promise<'success' | 'failed' | 'aborted'>
isFilesBackupsEnabled(): Promise<boolean>
isFilesBackupsEnabled(): boolean
enableFilesBackups(): Promise<void>
disableFilesBackups(): Promise<void>
disableFilesBackups(): void
changeFilesBackupsLocation(): Promise<string | undefined>
getFilesBackupsLocation(): Promise<string>
getFilesBackupsLocation(): string | undefined
openFilesBackupsLocation(): Promise<void>
openFileBackup(record: FileBackupRecord): Promise<void>
getFileBackupAbsolutePath(record: FileBackupRecord): string
isTextBackupsEnabled(): boolean
enableTextBackups(): Promise<void>
disableTextBackups(): void
getTextBackupsLocation(): string | undefined
openTextBackupsLocation(): Promise<void>
changeTextBackupsLocation(): Promise<string | undefined>
saveTextBackupData(data: string): Promise<void>
isPlaintextBackupsEnabled(): boolean
enablePlaintextBackups(): Promise<void>
disablePlaintextBackups(): void
getPlaintextBackupsLocation(): string | undefined
openPlaintextBackupsLocation(): Promise<void>
changePlaintextBackupsLocation(): Promise<string | undefined>
}
@@ -0,0 +1,3 @@
export interface SuperConverterServiceInterface {
convertString: (superString: string, toFormat: 'txt' | 'md' | 'html' | 'json') => string
}
+9 -7
View File
@@ -1,30 +1,32 @@
export * from './Api/DirectoryHandle'
export * from './Api/FileHandleRead'
export * from './Api/FileHandleReadWrite'
export * from './Api/FilesApiInterface'
export * from './Api/FileSystemApi'
export * from './Api/FileSystemNoSelection'
export * from './Api/FileSystemResult'
export * from './Api/FilesApiInterface'
export * from './Cache/FileMemoryCache'
export * from './Chunker/ByteChunker'
export * from './Chunker/OnChunkCallback'
export * from './Chunker/OrderedByteChunker'
export * from './Device/DesktopWatchedChanges'
export * from './Device/FileBackupMetadataFile'
export * from './Device/FileBackupsConstantsV1'
export * from './Device/FileBackupsDevice'
export * from './Device/FileBackupsMapping'
export * from './Operations/DownloadAndDecrypt'
export * from './Operations/EncryptAndUpload'
export * from './Service/BackupServiceInterface'
export * from './Service/SuperConverterServiceInterface'
export * from './Service/FilesClientInterface'
export * from './Service/ReadAndDecryptBackupFileFileSystemAPI'
export * from './Service/ReadAndDecryptBackupFileUsingBackupService'
export * from './Operations/DownloadAndDecrypt'
export * from './Operations/EncryptAndUpload'
export * from './UseCase/FileDecryptor'
export * from './UseCase/FileUploader'
export * from './UseCase/FileEncryptor'
export * from './UseCase/FileDownloader'
export * from './Types/DecryptedBytes'
export * from './Types/EncryptedBytes'
export * from './Types/FileDownloadProgress'
export * from './Types/FileUploadProgress'
export * from './Types/FileUploadResult'
export * from './UseCase/FileDecryptor'
export * from './UseCase/FileDownloader'
export * from './UseCase/FileEncryptor'
export * from './UseCase/FileUploader'
+122
View File
@@ -3,6 +3,128 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.53.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-05)
**Note:** Version bump only for package @standardnotes/mobile
## [3.53.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-05)
**Note:** Version bump only for package @standardnotes/mobile
## [3.53.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-05)
**Note:** Version bump only for package @standardnotes/mobile
## [3.53.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
**Note:** Version bump only for package @standardnotes/mobile
## [3.53.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
**Note:** Version bump only for package @standardnotes/mobile
## [3.53.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
**Note:** Version bump only for package @standardnotes/mobile
## [3.53.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
**Note:** Version bump only for package @standardnotes/mobile
## [3.53.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-03)
**Note:** Version bump only for package @standardnotes/mobile
## [3.53.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-03)
**Note:** Version bump only for package @standardnotes/mobile
## [3.53.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-03)
**Note:** Version bump only for package @standardnotes/mobile
## [3.53.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-02)
**Note:** Version bump only for package @standardnotes/mobile
# [3.53.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-02)
### Features
* Automatic plaintext backup option in Preferences > Backups will backup your notes and tags into plaintext, unencrypted folders on your computer. In addition, automatic encrypted text backups preference management has moved from the top-level menu in the desktop app to Preferences > Backups. ([#2322](https://github.com/standardnotes/app/issues/2322)) ([7e3db49](https://github.com/standardnotes/app/commit/7e3db49322034aa644c3c99ccf9a5f1b508583aa))
## [3.52.36](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-01)
**Note:** Version bump only for package @standardnotes/mobile
## [3.52.35](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-01)
**Note:** Version bump only for package @standardnotes/mobile
## [3.52.34](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-01)
**Note:** Version bump only for package @standardnotes/mobile
## [3.52.33](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-28)
**Note:** Version bump only for package @standardnotes/mobile
## [3.52.32](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-28)
**Note:** Version bump only for package @standardnotes/mobile
## [3.52.31](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-28)
**Note:** Version bump only for package @standardnotes/mobile
## [3.52.30](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/mobile
## [3.52.29](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/mobile
## [3.52.28](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/mobile
## [3.52.27](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/mobile
## [3.52.26](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/mobile
## [3.52.25](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/mobile
## [3.52.24](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/mobile
## [3.52.23](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/mobile
## [3.52.22](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/mobile
## [3.52.21](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/mobile
## [3.52.20](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/mobile
## [3.52.19](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/mobile
## [3.52.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/mobile
+37 -37
View File
@@ -1,7 +1,7 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.5)
CFPropertyList (3.0.6)
rexml
activesupport (6.1.5)
concurrent-ruby (~> 1.0, >= 1.0.2)
@@ -9,28 +9,28 @@ GEM
minitest (>= 5.1)
tzinfo (~> 2.0)
zeitwerk (~> 2.3)
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
addressable (2.8.4)
public_suffix (>= 2.0.2, < 6.0)
algoliasearch (1.27.5)
httpclient (~> 2.8, >= 2.8.3)
json (>= 1.5.1)
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.2.0)
aws-partitions (1.603.0)
aws-sdk-core (3.131.2)
aws-partitions (1.755.0)
aws-sdk-core (3.171.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.525.0)
aws-sigv4 (~> 1.1)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.5)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.57.0)
aws-sdk-core (~> 3, >= 3.127.0)
aws-sdk-kms (1.63.0)
aws-sdk-core (~> 3, >= 3.165.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.114.0)
aws-sdk-core (~> 3, >= 3.127.0)
aws-sdk-s3 (1.121.0)
aws-sdk-core (~> 3, >= 3.165.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
aws-sigv4 (1.5.0)
aws-sigv4 (1.5.2)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.0.3)
@@ -81,13 +81,13 @@ GEM
rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6)
dotenv (2.8.1)
emoji_regex (3.2.3)
escape (0.0.4)
ethon (0.15.0)
ffi (>= 1.15.0)
excon (0.92.3)
faraday (1.10.0)
excon (0.99.0)
faraday (1.10.3)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
@@ -116,7 +116,7 @@ GEM
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.2.6)
fastlane (2.207.0)
fastlane (2.212.2)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
@@ -160,9 +160,9 @@ GEM
fourflusher (2.3.1)
fuzzy_match (2.0.4)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.24.0)
google-apis-core (>= 0.7, < 2.a)
google-apis-core (0.7.0)
google-apis-androidpublisher_v3 (0.40.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-core (0.11.0)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
@@ -171,27 +171,27 @@ GEM
retriable (>= 2.0, < 4.a)
rexml
webrick
google-apis-iamcredentials_v1 (0.13.0)
google-apis-core (>= 0.7, < 2.a)
google-apis-playcustomapp_v1 (0.10.0)
google-apis-core (>= 0.7, < 2.a)
google-apis-storage_v1 (0.17.0)
google-apis-core (>= 0.7, < 2.a)
google-apis-iamcredentials_v1 (0.17.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-playcustomapp_v1 (0.13.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-storage_v1 (0.19.0)
google-apis-core (>= 0.9.0, < 2.a)
google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-cloud-errors (1.2.0)
google-cloud-storage (1.37.0)
google-cloud-errors (1.3.1)
google-cloud-storage (1.44.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.19.0)
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
googleauth (1.2.0)
googleauth (1.5.2)
faraday (>= 0.17.3, < 3.a)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
@@ -204,11 +204,11 @@ GEM
httpclient (2.8.3)
i18n (1.10.0)
concurrent-ruby (~> 1.0)
jmespath (1.6.1)
json (2.6.2)
jwt (2.4.1)
jmespath (1.6.2)
json (2.6.3)
jwt (2.7.0)
memoist (0.16.2)
mini_magick (4.11.0)
mini_magick (4.12.0)
mini_mime (1.1.2)
minitest (5.15.0)
molinillo (0.8.0)
@@ -220,7 +220,7 @@ GEM
netrc (0.11.0)
optparse (0.1.1)
os (1.1.4)
plist (3.6.0)
plist (3.7.0)
public_suffix (4.0.7)
rake (13.0.6)
representable (3.2.0)
@@ -239,7 +239,7 @@ GEM
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.8)
simctl (1.6.10)
CFPropertyList
naturally
terminal-notifier (2.0.0)
@@ -259,7 +259,7 @@ GEM
unf_ext
unf_ext (0.0.8.2)
unicode-display_width (1.8.0)
webrick (1.7.0)
webrick (1.8.1)
word_wrap (1.0.0)
xcode-install (2.8.0)
claide (>= 0.9.1, < 1.1.0)
@@ -291,4 +291,4 @@ RUBY VERSION
ruby 2.7.6p219
BUNDLED WITH
2.3.25
2.3.15
+7 -2
View File
@@ -34,7 +34,7 @@ platform :ios do
build_number
end
def deploy_ios(scheme)
def deploy_ios(scheme, is_testflight = false)
sign_ios
build_number = build_ios(scheme)
app_store_connect_api_key(
@@ -55,7 +55,7 @@ platform :ios do
changelog: 'Fixes and improvements.'
)
if is_prod
if is_prod && !is_testflight
upload_to_app_store(
build_number: build_number,
submit_for_review: true,
@@ -83,6 +83,11 @@ platform :ios do
deploy_ios 'StandardNotes'
end
desc 'Deploy prod app to tesflight'
lane :testflight do
deploy_ios 'StandardNotes', true
end
desc 'Setup iOS for running on CI'
lane :setup do
setup_project
@@ -16,7 +16,14 @@
window.plansUrl = "https://standardnotes.com/plans";
window.dashboardUrl = "https://standardnotes.com/dashboard";
</script>
<script>
window.onerror = function (message, source, lineno, colno, error) {
if (document.readyState === "complete") {
return
}
window.ReactNativeWebView.postMessage("appLoadError");
};
</script>
<script src="web-src/app.js"></script>
</head>
+2 -2
View File
@@ -637,7 +637,7 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/yoga"
SPEC CHECKSUMS:
boost: 57d2868c099736d80fcd648bf211b4431e51a558
boost: a7c83b31436843459a1961bfd74b96033dc77234
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
FBLazyVector: 60195509584153283780abdac5569feffb8f08cc
@@ -658,7 +658,7 @@ SPEC CHECKSUMS:
MMKV: 9c4663aa7ca255d478ff10f2f5cb7d17c1651ccd
MMKVCore: 89f5c8a66bba2dcd551779dea4d412eeec8ff5bb
OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c
RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1
RCT-Folly: 0080d0a6ebf2577475bda044aa59e2ca1f909cda
RCTRequired: bec48f07daf7bcdc2655a0cde84e07d24d2a9e2a
RCTTypeSafety: 171394eebacf71e1cfad79dbfae7ee8fc16ca80a
React: d7433ccb6a8c36e4cbed59a73c0700fc83c3e98a
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/mobile",
"version": "3.52.18",
"version": "3.53.11",
"author": "Standard Notes.",
"private": true,
"license": "AGPL-3.0-or-later",
+48 -1
View File
@@ -1,6 +1,6 @@
import { ReactNativeToWebEvent } from '@standardnotes/snjs'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Keyboard, Platform } from 'react-native'
import { Button, Keyboard, Platform, Text, View } from 'react-native'
import VersionInfo from 'react-native-version-info'
import { WebView, WebViewMessageEvent } from 'react-native-webview'
import { OnShouldStartLoadWithRequest } from 'react-native-webview/lib/WebViewTypes'
@@ -34,6 +34,8 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo
[androidBackHandlerService, colorSchemeService, stateService],
)
const [showAndroidWebviewUpdatePrompt, setShowAndroidWebviewUpdatePrompt] = useState(false)
useEffect(() => {
const removeStateServiceListener = stateService.addEventObserver((event: ReactNativeToWebEvent) => {
webViewRef.current?.postMessage(JSON.stringify({ reactNativeEvent: event, messageType: 'event' }))
@@ -228,6 +230,10 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo
const onMessage = (event: WebViewMessageEvent) => {
const message = event.nativeEvent.data
if (message === 'appLoadError' && Platform.OS === 'android') {
setShowAndroidWebviewUpdatePrompt(true)
return
}
try {
const functionData = JSON.parse(message)
void onFunctionMessage(functionData.functionName, functionData.messageId, functionData.args)
@@ -277,6 +283,47 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo
const requireInlineMediaPlaybackForMomentsFeature = true
const requireMediaUserInteractionForMomentsFeature = false
if (showAndroidWebviewUpdatePrompt) {
return (
<View
style={{
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'black',
}}
>
<Text
style={{
color: 'white',
fontSize: 20,
fontWeight: 'bold',
marginBottom: 20,
}}
>
Could not load app
</Text>
<Text
style={{
color: 'white',
fontSize: 16,
marginBottom: 20,
textAlign: 'center',
}}
>
Please make sure your Android System Webview is updated to the latest version
</Text>
<Button
title={'Update'}
onPress={() => {
setShowAndroidWebviewUpdatePrompt(false)
device.openUrl('https://play.google.com/store/apps/details?id=com.google.android.webview')
}}
/>
</View>
)
}
return (
<WebView
ref={webViewRef}
+14
View File
@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.45.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
### Features
* Replaced margin resizers with "Editor width" options. You can set it globally from Preferences > Appearance or per-note from the note options menu ([#2324](https://github.com/standardnotes/app/issues/2324)) ([9fbb845](https://github.com/standardnotes/app/commit/9fbb845b1d3b1ca6a4b064f3b63bdae1cf7777e8))
## [1.44.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/models
## [1.44.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/models
## [1.44.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/models
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/models",
"version": "1.44.1",
"version": "1.45.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -5,6 +5,7 @@ import { DecryptedItem } from '../../Abstract/Item/Implementations/DecryptedItem
import { ItemInterface } from '../../Abstract/Item/Interfaces/ItemInterface'
import { DecryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/DecryptedPayload'
import { NoteContent, NoteContentSpecialized } from './NoteContent'
import { EditorLineWidth } from '../UserPrefs'
export const isNote = (x: ItemInterface): x is SNNote => x.content_type === ContentType.Note
@@ -15,6 +16,7 @@ export class SNNote extends DecryptedItem<NoteContent> implements NoteContentSpe
public readonly preview_plain: string
public readonly preview_html: string
public readonly spellcheck?: boolean
public readonly editorWidth?: EditorLineWidth
public readonly noteType?: NoteType
public readonly authorizedForListed: boolean
@@ -30,6 +32,7 @@ export class SNNote extends DecryptedItem<NoteContent> implements NoteContentSpe
this.preview_plain = String(this.payload.content.preview_plain || '')
this.preview_html = String(this.payload.content.preview_html || '')
this.spellcheck = this.payload.content.spellcheck
this.editorWidth = this.payload.content.editorWidth
this.noteType = this.payload.content.noteType
this.editorIdentifier = this.payload.content.editorIdentifier
this.authorizedForListed = this.payload.content.authorizedForListed || false
@@ -1,5 +1,6 @@
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
import { ItemContent } from '../../Abstract/Content/ItemContent'
import { EditorLineWidth } from '../UserPrefs'
export interface NoteContentSpecialized {
title: string
@@ -8,6 +9,7 @@ export interface NoteContentSpecialized {
preview_plain?: string
preview_html?: string
spellcheck?: boolean
editorWidth?: EditorLineWidth
noteType?: NoteType
editorIdentifier?: FeatureIdentifier | string
authorizedForListed?: boolean
@@ -5,6 +5,7 @@ import { NoteToNoteReference } from '../../Abstract/Reference/NoteToNoteReferenc
import { ContentType } from '@standardnotes/common'
import { ContentReferenceType } from '../../Abstract/Item'
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
import { EditorLineWidth } from '../UserPrefs'
export class NoteMutator extends DecryptedItemMutator<NoteContent> {
set title(title: string) {
@@ -31,6 +32,10 @@ export class NoteMutator extends DecryptedItemMutator<NoteContent> {
this.mutableContent.spellcheck = spellcheck
}
set editorWidth(editorWidth: EditorLineWidth) {
this.mutableContent.editorWidth = editorWidth
}
set noteType(noteType: NoteType) {
this.mutableContent.noteType = noteType
}
@@ -12,6 +12,7 @@ export enum PrefKey {
EditorSpellcheck = 'spellcheck',
EditorResizersEnabled = 'marginResizersEnabled',
EditorLineHeight = 'editorLineHeight',
EditorLineWidth = 'editorLineWidth',
EditorFontSize = 'editorFontSize',
SortNotesBy = 'sortBy',
SortNotesReverse = 'sortReverse',
@@ -65,6 +66,13 @@ export enum EditorLineHeight {
Loose = 'Loose',
}
export enum EditorLineWidth {
Narrow = 'Narrow',
Wide = 'Wide',
Dynamic = 'Dynamic',
FullWidth = 'FullWidth',
}
export enum EditorFontSize {
ExtraSmall = 'ExtraSmall',
Small = 'Small',
@@ -107,6 +115,7 @@ export type PrefValue = {
[PrefKey.NewNoteTitleFormat]: NewNoteTitleFormat
[PrefKey.CustomNoteTitleFormat]: string
[PrefKey.EditorLineHeight]: EditorLineHeight
[PrefKey.EditorLineWidth]: EditorLineWidth
[PrefKey.EditorFontSize]: EditorFontSize
[PrefKey.UpdateSavingStatusIndicator]: boolean
[PrefKey.DarkMode]: boolean
+120
View File
@@ -3,6 +3,126 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.4.279](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-05)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.278](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-05)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.277](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-05)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.276](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.275](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.274](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.273](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.272](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-03)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.271](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-03)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.270](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-03)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.269](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-02)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.268](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-02)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.267](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-01)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.266](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-01)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.265](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-01)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.264](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-28)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.263](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-28)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.262](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-28)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.261](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.260](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.259](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.258](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.257](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.256](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.255](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.254](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.253](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.252](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.251](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.250](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.249](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/releases
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/releases",
"version": "1.4.249",
"version": "1.4.279",
"license": "AGPL-3.0-or-later",
"main": "dist/releases.json",
"types": "dist/index.d.ts",
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.13.21](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/responses
## [1.13.20](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/responses
## [1.13.19](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
**Note:** Version bump only for package @standardnotes/responses

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