Compare commits

...
Author SHA1 Message Date
StandardNotes CI 5a8a37413e chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].9
 - @standardnotes/[email protected].8
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected].3
2022-12-29 07:50:08 +00:00
Aman Harwara 113d3bebb4 fix: Fixed issue where Super note font size would shift on first load 2022-12-29 12:51:32 +05:30
Aman Harwara 874f2e90bb Merge branch 'main' of github.com:standardnotes/app 2022-12-29 12:31:59 +05:30
Aman Harwara df4acbb1bc fix: Fixed issue where converting a note to Super from the note options menu would not work 2022-12-29 12:31:50 +05:30
StandardNotes CI b75be1d0f8 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
2022-12-29 05:02:30 +00:00
Mo 015dd1e806 chore: bump version (skip e2e) 2022-12-28 22:47:56 -06:00
Mo 8e3ae80aa0 fix: Fixed issue where Moments could incorrectly get engaged during biometrics unlocking 2022-12-28 22:20:44 -06:00
StandardNotes CI 5a0d87845c chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].8
 - @standardnotes/[email protected].7
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected].0
2022-12-28 21:47:36 +00:00
Aman Harwara b764296c8f feat: Added "Files Table View" to Labs 2022-12-29 02:44:04 +05:30
StandardNotes CI b0c11b1b4d chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
2022-12-28 17:21:41 +00:00
Aman Harwara e81ecfc024 Update CHANGELOG.md 2022-12-28 22:20:28 +05:30
Aman Harwara b408d0a4c5 Update CHANGELOG.md.json 2022-12-28 22:20:08 +05:30
StandardNotes CI 9ec68c9bf6 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
2022-12-28 16:44:26 +00:00
Aman Harwara 2611c690b0 fix: Fixed issue where Moments would capture even after disabling (#2122) 2022-12-28 21:44:01 +05:30
StandardNotes CI ef6e971592 chore(release): publish
- @standardnotes/[email protected]
2022-12-28 07:47:42 +00:00
Karol Sójko 1c48dd135c fix(docs): update self-hosted instructions 2022-12-28 08:20:16 +01:00
StandardNotes CI 495786514e chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].7
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected].1
2022-12-27 20:17:11 +00:00
Aman Harwara cfd6cf5932 refactor(dev-only): disable note list keyboard nav when showing files table view 2022-12-28 01:20:08 +05:30
Aman Harwara 8c5944f233 refactor(dev-only): allow sorting files table view by file size 2022-12-28 01:14:57 +05:30
Aman Harwara 2b63484758 refactor(dev-only): use file view display prefs for files table 2022-12-28 00:49:09 +05:30
StandardNotes CI ccb15faff5 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].0
2022-12-27 18:29:52 +00:00
Aman Harwara dc33aef660 feat: Allow changing per-view display preferences for system views (#2120) 2022-12-27 23:33:08 +05:30
StandardNotes CI 771758fcd4 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected]
2022-12-27 15:59:31 +00:00
Aman Harwara 3fbe28e068 refactor(dev-only): import tools (#2121) 2022-12-27 21:03:01 +05:30
Karol Sójko e67240821c chore: fix triggered workflow for e2e test suites 2022-12-27 15:52:10 +01:00
StandardNotes CI d7c6ef6134 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2022-12-27 13:20:25 +00:00
Karol Sójko 8311271a49 fix(snjs): skip workspaces tests as they are a pending backlog feature 2022-12-27 13:49:02 +01:00
73 changed files with 1972 additions and 115 deletions
+3 -3
View File
@@ -67,11 +67,11 @@ jobs:
uses: convictional/trigger-workflow-and-wait@master
with:
owner: standardnotes
repo: e2e
repo: self-hosted
github_token: ${{ secrets.CI_PAT_TOKEN }}
workflow_file_name: testing-with-stable-server.yml
workflow_file_name: testing-with-updating-client-and-server.yml
wait_interval: 30
client_payload: '{"image_tag": "${{ github.sha }}"}'
client_payload: '{"snjs_image_tag": "${{ github.sha }}"}'
propagate_failure: true
trigger_workflow: true
wait_workflow: true
+3 -3
View File
@@ -43,11 +43,11 @@ jobs:
uses: convictional/trigger-workflow-and-wait@master
with:
owner: standardnotes
repo: e2e
repo: self-hosted
github_token: ${{ secrets.CI_PAT_TOKEN }}
workflow_file_name: testing-with-stable-server.yml
workflow_file_name: testing-with-updating-client-and-server.yml
wait_interval: 30
client_payload: '{"image_tag": "${{ github.sha }}"}'
client_payload: '{"snjs_image_tag": "${{ github.sha }}"}'
propagate_failure: true
trigger_workflow: true
wait_workflow: true
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.20.26](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)
**Note:** Version bump only for package @standardnotes/api
## [1.20.25](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-28)
**Note:** Version bump only for package @standardnotes/api
## [1.20.24](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/api
## [1.20.23](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/api
## [1.20.22](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-21)
### Bug Fixes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api",
"version": "1.20.22",
"version": "1.20.26",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+36
View File
@@ -3,6 +3,42 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.104.59](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)
**Note:** Version bump only for package @standardnotes/desktop
## [3.104.58](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)
**Note:** Version bump only for package @standardnotes/desktop
## [3.104.57](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-28)
**Note:** Version bump only for package @standardnotes/desktop
## [3.104.56](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-28)
**Note:** Version bump only for package @standardnotes/desktop
## [3.104.55](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-28)
**Note:** Version bump only for package @standardnotes/desktop
## [3.104.54](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/desktop
## [3.104.53](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/desktop
## [3.104.52](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/desktop
## [3.104.51](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/desktop
## [3.104.50](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/desktop
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@standardnotes/desktop",
"main": "./app/dist/index.js",
"version": "3.104.50",
"version": "3.104.59",
"license": "AGPL-3.0-or-later",
"author": "Standard Notes.",
"private": true,
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.2.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-28)
### Bug Fixes
* **docs:** update self-hosted instructions ([1c48dd1](https://github.com/standardnotes/app/commit/1c48dd135ced93832495fdd89b2940635f04205a))
## [0.2.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-19)
**Note:** Version bump only for package @standardnotes/docs
+5 -11
View File
@@ -141,12 +141,12 @@ Due to mounted volumes, we recommend running the setup as a root user. If you wi
## Install Standard Notes
1. Clone the `standalone` repo:
1. Clone the `self-hosted` repo:
```shell
cd ~
git clone --single-branch --branch main https://github.com/standardnotes/standalone.git
cd standalone
git clone --single-branch --branch main https://github.com/standardnotes/self-hosted.git
cd self-hosted
```
1. Initialize default configuration files:
@@ -165,15 +165,9 @@ Due to mounted volumes, we recommend running the setup as a root user. If you wi
1. Generate random values for the necessary environment variables:
```shell
sed -i "s/auth_jwt_secret/$(openssl rand -hex 32)/g" .env
sed -i "s/legacy_jwt_secret/$(openssl rand -hex 32)/g" docker/auth.env
sed -i "s/secret_key/$(openssl rand -hex 32)/g" docker/auth.env
sed -i "s/server_key/$(openssl rand -hex 32)/g" docker/auth.env
sed -i "s/secret/$(openssl rand -hex 32)/g" docker/auth.env
./server.sh generate-keys
```
**Note:** If you are running `sed` on macOS or BSD, change instances of `sed -i` to `sed -i ''`.
1. (Optional) Restart the server:
```shell
@@ -217,7 +211,7 @@ Due to mounted volumes, we recommend running the setup as a root user. If you wi
syncing-server-js-worker-standalone "./wait-for.sh db 33…" syncing-server-js-worker running
```
Your Standard Notes server is ready once all the services have a `STATUS` of `Up`. This process took about 11 minutes on a Ubuntu 20.04 server with 2GB RAM and 1 CPU.
Your Standard Notes server is ready once all the services have a `STATUS` of `Up`. This process took about 11 minutes on a Ubuntu 20.04 server with 2GB RAM and 1 CPU. If you want to control when this process finishes you can run `./server.sh wait-for-startup`.
1. You should be able now to check that the syncing server is running by checking `http://localhost:3000/healthcheck`. You must do this on the server:
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/docs",
"version": "0.2.4",
"version": "0.2.5",
"license": "AGPL-3.0-or-later",
"author": "Standard Notes.",
"private": true,
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.19.31](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)
**Note:** Version bump only for package @standardnotes/encryption
## [1.19.30](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-28)
**Note:** Version bump only for package @standardnotes/encryption
## [1.19.29](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/encryption
## [1.19.28](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/encryption
## [1.19.27](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-08)
**Note:** Version bump only for package @standardnotes/encryption
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/encryption",
"version": "1.19.27",
"version": "1.19.31",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+10
View File
@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.57.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)
**Note:** Version bump only for package @standardnotes/features
# [1.57.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-28)
### Features
* Added "Files Table View" to Labs ([b764296](https://github.com/standardnotes/app/commit/b764296c8f831d06f854dcf2982e644c27e14e7c))
# [1.56.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
### Features
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/features",
"version": "1.56.0",
"version": "1.57.1",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -38,6 +38,8 @@ export enum FeatureIdentifier {
TaskEditor = 'org.standardnotes.simple-task-editor',
TokenVaultEditor = 'org.standardnotes.token-vault',
FilesTableView = 'org.standardnotes.files-table-view',
DeprecatedBoldEditor = 'org.standardnotes.bold-editor',
DeprecatedMarkdownBasicEditor = 'org.standardnotes.simple-markdown-editor',
DeprecatedMarkdownMathEditor = 'org.standardnotes.fancy-markdown-editor',
@@ -51,4 +53,4 @@ export enum FeatureIdentifier {
*/
export const LegacyFileSafeIdentifier = 'org.standardnotes.legacy.file-safe'
export const ExperimentalFeatures = []
export const ExperimentalFeatures = [FeatureIdentifier.FilesTableView]
@@ -1,5 +1,18 @@
import { RoleName, SubscriptionName } from '@standardnotes/common'
import { FeatureDescription } from '../Feature/FeatureDescription'
import { FeatureIdentifier } from '../Feature/FeatureIdentifier'
import { PermissionName } from '../Permission/PermissionName'
export function experimentalFeatures(): FeatureDescription[] {
return []
const filesTableView: FeatureDescription = {
identifier: FeatureIdentifier.FilesTableView,
name: 'Files Table View',
description:
'Replaces the current Files view with a table view, with name, size, and date sort options. Requires reload to take effect.',
availableInSubscriptions: [SubscriptionName.PlusPlan, SubscriptionName.ProPlan],
availableInRoles: [RoleName.PlusUser, RoleName.ProUser],
permission_name: PermissionName.FilesTableView,
}
return [filesTableView]
}
@@ -40,4 +40,5 @@ export enum PermissionName {
TwoFactorAuth = 'server:two-factor-auth',
SubscriptionSharing = 'server:subscription-sharing',
SuperEditor = 'editor:super-editor',
FilesTableView = 'app:files-table-view',
}
+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.27.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)
**Note:** Version bump only for package @standardnotes/filepicker
## [1.27.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-28)
**Note:** Version bump only for package @standardnotes/filepicker
## [1.27.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/filepicker
## [1.27.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/filepicker
## [1.27.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/filepicker
## [1.27.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-08)
**Note:** Version bump only for package @standardnotes/filepicker
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/filepicker",
"version": "1.27.4",
"version": "1.27.9",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -46,6 +46,8 @@ function selectFiles(): Promise<File[]> {
files.push(file)
}
resolve(files)
// Reset input value so that onchange is triggered again if the same file is selected
input.value = ''
}
input.click()
})
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.14.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)
**Note:** Version bump only for package @standardnotes/files
## [1.14.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-28)
**Note:** Version bump only for package @standardnotes/files
## [1.14.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/files
## [1.14.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/files
## [1.14.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-08)
**Note:** Version bump only for package @standardnotes/files
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files",
"version": "1.14.4",
"version": "1.14.8",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+36
View File
@@ -3,6 +3,42 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.49.56](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)
**Note:** Version bump only for package @standardnotes/mobile
## [3.49.55](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)
**Note:** Version bump only for package @standardnotes/mobile
## [3.49.54](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-28)
**Note:** Version bump only for package @standardnotes/mobile
## [3.49.53](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-28)
**Note:** Version bump only for package @standardnotes/mobile
## [3.49.52](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-28)
**Note:** Version bump only for package @standardnotes/mobile
## [3.49.51](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/mobile
## [3.49.50](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/mobile
## [3.49.49](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/mobile
## [3.49.48](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/mobile
## [3.49.47](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/mobile
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/mobile",
"version": "3.49.47",
"version": "3.49.56",
"author": "Standard Notes.",
"private": true,
"license": "AGPL-3.0-or-later",
+18
View File
@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.40.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)
**Note:** Version bump only for package @standardnotes/models
## [1.40.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-28)
**Note:** Version bump only for package @standardnotes/models
## [1.40.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/models
# [1.40.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
### Features
* Allow changing per-view display preferences for system views ([#2120](https://github.com/standardnotes/app/issues/2120)) ([dc33aef](https://github.com/standardnotes/app/commit/dc33aef6608a364107a884ad2f26c14bceea23b7))
## [1.39.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-08)
**Note:** Version bump only for package @standardnotes/models
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/models",
"version": "1.39.3",
"version": "1.40.3",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -7,6 +7,7 @@ export interface SortableItem {
userModifiedDate: Date
title?: string
pinned: boolean
decryptedSize?: number
}
export const CollectionSort: Record<string, keyof SortableItem> = {
@@ -1,5 +1,7 @@
import { CollectionSortProperty } from '../../Runtime/Collection/CollectionSort'
import { EditorIdentifier, FeatureIdentifier } from '@standardnotes/features'
import { SystemViewId } from '../SmartView'
import { TagPreferences } from '../Tag'
export enum PrefKey {
TagsPanelWidth = 'tagsPanelWidth',
@@ -40,6 +42,7 @@ export enum PrefKey {
DarkMode = 'darkMode',
DefaultEditorIdentifier = 'defaultEditorIdentifier',
MomentsDefaultTagUuid = 'momentsDefaultTagUuid',
SystemViewPreferences = 'systemViewPreferences',
}
export enum NewNoteTitleFormat {
@@ -105,4 +108,5 @@ export type PrefValue = {
[PrefKey.DarkMode]: boolean
[PrefKey.DefaultEditorIdentifier]: EditorIdentifier
[PrefKey.MomentsDefaultTagUuid]: string | undefined
[PrefKey.SystemViewPreferences]: Partial<Record<SystemViewId, TagPreferences>>
}
+36
View File
@@ -3,6 +3,42 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.4.82](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.81](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.80](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-28)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.79](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-28)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.78](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-28)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.77](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.76](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.75](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.74](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.73](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/releases
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/releases",
"version": "1.4.73",
"version": "1.4.82",
"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.12.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)
**Note:** Version bump only for package @standardnotes/responses
## [1.12.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-28)
**Note:** Version bump only for package @standardnotes/responses
## [1.12.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
**Note:** Version bump only for package @standardnotes/responses
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/responses",
"version": "1.12.10",
"version": "1.12.12",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+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.50.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)
**Note:** Version bump only for package @standardnotes/services
## [1.50.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-28)
**Note:** Version bump only for package @standardnotes/services
## [1.50.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/services
## [1.50.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/services
## [1.50.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/services
## [1.50.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-21)
**Note:** Version bump only for package @standardnotes/services
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/services",
"version": "1.50.9",
"version": "1.50.14",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -18,4 +18,5 @@ export interface WebApplicationInterface extends ApplicationInterface {
handleAndroidBackButtonPressed(): void
addAndroidBackHandlerEventListener(listener: () => boolean): (() => void) | undefined
setAndroidBackHandlerFallbackListener(listener: () => boolean): void
generateUUID(): string
}
@@ -55,6 +55,8 @@ export interface ItemsClientInterface {
getItems<T extends DecryptedItemInterface>(contentType: ContentType | ContentType[]): T[]
insertItem(item: DecryptedItemInterface): Promise<DecryptedItemInterface>
notesMatchingSmartView(view: SmartView): SNNote[]
addNoteCountChangeObserver(observer: TagItemCountChangeObserver): () => void
+26
View File
@@ -3,6 +3,32 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.156.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)
**Note:** Version bump only for package @standardnotes/snjs
## [2.156.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-28)
**Note:** Version bump only for package @standardnotes/snjs
## [2.156.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/snjs
## [2.156.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/snjs
## [2.156.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/snjs
## [2.156.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
### Bug Fixes
* **snjs:** skip workspaces tests as they are a pending backlog feature ([8311271](https://github.com/standardnotes/app/commit/8311271a49fe707adc4e26c7b465861e65ee9006))
## [2.156.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
### Bug Fixes
+1 -1
View File
@@ -2,7 +2,7 @@ import * as Factory from './lib/factory.js'
chai.use(chaiAsPromised)
const expect = chai.expect
describe('workspaces', function () {
describe.skip('workspaces', function () {
this.timeout(Factory.TwentySecondTimeout)
let ownerContext
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/snjs",
"version": "2.156.10",
"version": "2.156.16",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+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.21.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)
**Note:** Version bump only for package @standardnotes/ui-services
## [1.21.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-28)
**Note:** Version bump only for package @standardnotes/ui-services
## [1.21.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/ui-services
## [1.21.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/ui-services
## [1.21.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/ui-services
# [1.21.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-24)
### Features
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/ui-services",
"version": "1.21.0",
"version": "1.21.5",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -0,0 +1,89 @@
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
import { WebApplicationInterface } from '@standardnotes/services'
import { AegisToAuthenticatorConverter } from './AegisToAuthenticatorConverter'
import data from './testData'
describe('AegisConverter', () => {
let application: WebApplicationInterface
beforeEach(() => {
application = {
generateUUID: jest.fn().mockReturnValue('test'),
} as unknown as WebApplicationInterface
})
it('should parse entries', () => {
const converter = new AegisToAuthenticatorConverter(application)
const result = converter.parseEntries(data)
expect(result).not.toBeNull()
expect(result?.length).toBe(2)
expect(result?.[0]).toStrictEqual({
service: 'TestMail',
account: '[email protected]',
secret: 'TESTMAILTESTMAILTESTMAILTESTMAIL',
notes: 'Some note',
})
expect(result?.[1]).toStrictEqual({
service: 'Some Service',
account: '[email protected]',
secret: 'SOMESERVICESOMESERVICESOMESERVIC',
notes: 'Some other service',
})
})
it('should create note from entries with editor info', () => {
const converter = new AegisToAuthenticatorConverter(application)
const parsedEntries = converter.parseEntries(data)
const result = converter.createNoteFromEntries(
parsedEntries!,
{
lastModified: 123456789,
name: 'test.json',
},
true,
)
expect(result).not.toBeNull()
expect(result.content_type).toBe('Note')
expect(result.created_at).toBeInstanceOf(Date)
expect(result.updated_at).toBeInstanceOf(Date)
expect(result.uuid).not.toBeNull()
expect(result.content.title).toBe('test')
expect(result.content.text).toBe(
'[{"service":"TestMail","account":"[email protected]","secret":"TESTMAILTESTMAILTESTMAILTESTMAIL","notes":"Some note"},{"service":"Some Service","account":"[email protected]","secret":"SOMESERVICESOMESERVICESOMESERVIC","notes":"Some other service"}]',
)
expect(result.content.noteType).toBe(NoteType.Authentication)
expect(result.content.editorIdentifier).toBe(FeatureIdentifier.TokenVaultEditor)
})
it('should create note from entries without editor info', () => {
const converter = new AegisToAuthenticatorConverter(application)
const parsedEntries = converter.parseEntries(data)
const result = converter.createNoteFromEntries(
parsedEntries!,
{
lastModified: 123456789,
name: 'test.json',
},
false,
)
expect(result).not.toBeNull()
expect(result.content_type).toBe('Note')
expect(result.created_at).toBeInstanceOf(Date)
expect(result.updated_at).toBeInstanceOf(Date)
expect(result.uuid).not.toBeNull()
expect(result.content.title).toBe('test')
expect(result.content.text).toBe(
'[{"service":"TestMail","account":"[email protected]","secret":"TESTMAILTESTMAILTESTMAILTESTMAIL","notes":"Some note"},{"service":"Some Service","account":"[email protected]","secret":"SOMESERVICESOMESERVICESOMESERVIC","notes":"Some other service"}]',
)
expect(result.content.noteType).toBeFalsy()
expect(result.content.editorIdentifier).toBeFalsy()
})
})
@@ -0,0 +1,92 @@
import { DecryptedTransferPayload, NoteContent } from '@standardnotes/models'
import { ContentType } from '@standardnotes/common'
import { readFileAsText } from '../Utils'
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
import { WebApplicationInterface } from '@standardnotes/services'
import { Importer } from '../Importer'
type AegisData = {
db: {
entries: {
issuer: string
name: string
info: {
secret: string
}
note: string
}[]
}
}
type AuthenticatorEntry = {
service: string
account: string
secret: string
notes: string
}
export class AegisToAuthenticatorConverter extends Importer {
constructor(protected override application: WebApplicationInterface) {
super(application)
}
createNoteFromEntries(
entries: AuthenticatorEntry[],
file: {
lastModified: number
name: string
},
addEditorInfo: boolean,
): DecryptedTransferPayload<NoteContent> {
return {
created_at: new Date(file.lastModified),
created_at_timestamp: file.lastModified,
updated_at: new Date(file.lastModified),
updated_at_timestamp: file.lastModified,
uuid: this.application.generateUUID(),
content_type: ContentType.Note,
content: {
title: file.name.split('.')[0],
text: JSON.stringify(entries),
references: [],
...(addEditorInfo && {
noteType: NoteType.Authentication,
editorIdentifier: FeatureIdentifier.TokenVaultEditor,
}),
},
}
}
async convertAegisBackupFileToNote(
file: File,
addEditorInfo: boolean,
): Promise<DecryptedTransferPayload<NoteContent>> {
const content = await readFileAsText(file)
const entries = this.parseEntries(content)
if (!entries) {
throw new Error('Could not parse entries')
}
return this.createNoteFromEntries(entries, file, addEditorInfo)
}
parseEntries(data: string): AuthenticatorEntry[] | null {
try {
const json = JSON.parse(data) as AegisData
const entries = json.db.entries.map((entry) => {
return {
service: entry.issuer,
account: entry.name,
secret: entry.info.secret,
notes: entry.note,
} as AuthenticatorEntry
})
return entries
} catch (error) {
console.error(error)
return null
}
}
}
@@ -0,0 +1,42 @@
const data = {
version: 1,
header: {
slots: null,
params: null,
},
db: {
version: 2,
entries: [
{
type: 'totp',
uuid: 'c74a11c4-4f23-417b-818a-e11f6a4d51d7',
name: '[email protected]',
issuer: 'TestMail',
note: 'Some note',
icon: null,
info: {
secret: 'TESTMAILTESTMAILTESTMAILTESTMAIL',
algo: 'SHA1',
digits: 6,
period: 30,
},
},
{
type: 'totp',
uuid: '803ed58f-b2c4-386c-9aad-645a47309124',
name: '[email protected]',
issuer: 'Some Service',
note: 'Some other service',
icon: null,
info: {
secret: 'SOMESERVICESOMESERVICESOMESERVIC',
algo: 'SHA1',
digits: 6,
period: 30,
},
},
],
},
}
export default JSON.stringify(data, null, 2)
@@ -0,0 +1,72 @@
/**
* @jest-environment jsdom
*/
import { ContentType } from '@standardnotes/common'
import { WebApplicationInterface } from '@standardnotes/services'
import { DecryptedTransferPayload, NoteContent, TagContent } from '@standardnotes/models'
import { EvernoteConverter } from './EvernoteConverter'
import data from './testData'
// Mock dayjs so dayjs.extend() doesn't throw an error in EvernoteConverter.ts
jest.mock('dayjs', () => {
return {
__esModule: true,
default: {
extend: jest.fn(),
utc: jest.fn().mockReturnValue({
toDate: jest.fn().mockReturnValue(new Date()),
}),
},
}
})
describe('EvernoteConverter', () => {
let application: WebApplicationInterface
beforeEach(() => {
application = {
generateUUID: jest.fn().mockReturnValue(Math.random()),
} as any as WebApplicationInterface
})
it('should parse and strip html', () => {
const converter = new EvernoteConverter(application)
const result = converter.parseENEXData(data, true)
expect(result).not.toBeNull()
expect(result?.length).toBe(3)
expect(result?.[0].content_type).toBe(ContentType.Note)
expect((result?.[0] as DecryptedTransferPayload<NoteContent>).content.text).toBe('This is a test.')
expect(result?.[1].content_type).toBe(ContentType.Note)
expect((result?.[1] as DecryptedTransferPayload<NoteContent>).content.text).toBe(
'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
)
expect(result?.[2].content_type).toBe(ContentType.Tag)
expect((result?.[2] as DecryptedTransferPayload<TagContent>).content.title).toBe('evernote')
expect((result?.[2] as DecryptedTransferPayload<TagContent>).content.references.length).toBe(2)
expect((result?.[2] as DecryptedTransferPayload<TagContent>).content.references[0].uuid).toBe(result?.[0].uuid)
expect((result?.[2] as DecryptedTransferPayload<TagContent>).content.references[1].uuid).toBe(result?.[1].uuid)
})
it('should parse and not strip html', () => {
const converter = new EvernoteConverter(application)
const result = converter.parseENEXData(data, false)
expect(result).not.toBeNull()
expect(result?.length).toBe(3)
expect(result?.[0].content_type).toBe(ContentType.Note)
expect((result?.[0] as DecryptedTransferPayload<NoteContent>).content.text).toBe('<div>This is a test.</div>')
expect(result?.[1].content_type).toBe(ContentType.Note)
expect((result?.[1] as DecryptedTransferPayload<NoteContent>).content.text).toBe(
'<div>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</div>',
)
expect(result?.[2].content_type).toBe(ContentType.Tag)
expect((result?.[2] as DecryptedTransferPayload<TagContent>).content.title).toBe('evernote')
expect((result?.[2] as DecryptedTransferPayload<TagContent>).content.references.length).toBe(2)
expect((result?.[2] as DecryptedTransferPayload<TagContent>).content.references[0].uuid).toBe(result?.[0].uuid)
expect((result?.[2] as DecryptedTransferPayload<TagContent>).content.references[1].uuid).toBe(result?.[1].uuid)
})
})
@@ -0,0 +1,164 @@
import { ContentType } from '@standardnotes/common'
import { WebApplicationInterface } from '@standardnotes/services'
import { DecryptedTransferPayload, NoteContent, TagContent } from '@standardnotes/models'
import { readFileAsText } from '../Utils'
import { Importer } from '../Importer'
import dayjs from 'dayjs'
import customParseFormat from 'dayjs/plugin/customParseFormat'
import utc from 'dayjs/plugin/utc'
dayjs.extend(customParseFormat)
dayjs.extend(utc)
const dateFormat = 'YYYYMMDDTHHmmss'
export class EvernoteConverter extends Importer {
constructor(protected override application: WebApplicationInterface) {
super(application)
}
async convertENEXFileToNotesAndTags(file: File, stripHTML: boolean): Promise<DecryptedTransferPayload[]> {
const content = await readFileAsText(file)
const notesAndTags = this.parseENEXData(content, stripHTML)
return notesAndTags
}
parseENEXData(data: string, stripHTML = false, defaultTagName = 'evernote') {
const xmlDoc = this.loadXMLString(data, 'xml')
const xmlNotes = xmlDoc.getElementsByTagName('note')
const notes: DecryptedTransferPayload<NoteContent>[] = []
const tags: DecryptedTransferPayload<TagContent>[] = []
let defaultTag: DecryptedTransferPayload<TagContent> | undefined
if (defaultTagName) {
const now = new Date()
defaultTag = {
created_at: now,
created_at_timestamp: now.getTime(),
updated_at: now,
updated_at_timestamp: now.getTime(),
uuid: this.application.generateUUID(),
content_type: ContentType.Tag,
content: {
title: defaultTagName,
expanded: false,
iconString: '',
references: [],
},
}
}
function findTag(title: string | null) {
return tags.filter(function (tag) {
return tag.content.title == title
})[0]
}
function addTag(tag: DecryptedTransferPayload<TagContent>) {
tags.push(tag)
}
for (const [index, xmlNote] of Array.from(xmlNotes).entries()) {
const title = xmlNote.getElementsByTagName('title')[0].textContent
const created = xmlNote.getElementsByTagName('created')[0].textContent
const updatedNodes = xmlNote.getElementsByTagName('updated')
const updated = updatedNodes.length ? updatedNodes[0].textContent : null
const contentNode = xmlNote.getElementsByTagName('content')[0]
let contentXmlString
/** Find the node with the content */
for (const node of Array.from(contentNode.childNodes)) {
if (node instanceof CDATASection) {
contentXmlString = node.nodeValue
break
}
}
if (!contentXmlString) {
continue
}
const contentXml = this.loadXMLString(contentXmlString, 'html')
let contentHTML = contentXml.getElementsByTagName('en-note')[0].innerHTML
if (stripHTML) {
contentHTML = contentHTML.replace(/<\/div>/g, '</div>\n')
contentHTML = contentHTML.replace(/<li[^>]*>/g, '\n')
contentHTML = contentHTML.trim()
}
const text = stripHTML ? this.stripHTML(contentHTML) : contentHTML
const createdAtDate = created ? dayjs.utc(created, dateFormat).toDate() : new Date()
const updatedAtDate = updated ? dayjs.utc(updated, dateFormat).toDate() : createdAtDate
const note: DecryptedTransferPayload<NoteContent> = {
created_at: createdAtDate,
created_at_timestamp: createdAtDate.getTime(),
updated_at: updatedAtDate,
updated_at_timestamp: updatedAtDate.getTime(),
uuid: this.application.generateUUID(),
content_type: ContentType.Note,
content: {
title: !title ? `Imported note ${index + 1} from Evernote` : title,
text,
references: [],
},
}
if (defaultTag) {
defaultTag.content.references.push({
content_type: ContentType.Note,
uuid: note.uuid,
})
}
const xmlTags = xmlNote.getElementsByTagName('tag')
for (const tagXml of Array.from(xmlTags)) {
const tagName = tagXml.childNodes[0].nodeValue
let tag = findTag(tagName)
if (!tag) {
const now = new Date()
tag = {
uuid: this.application.generateUUID(),
content_type: ContentType.Tag,
created_at: now,
created_at_timestamp: now.getTime(),
updated_at: now,
updated_at_timestamp: now.getTime(),
content: {
title: tagName || `Imported tag ${index + 1} from Evernote`,
expanded: false,
iconString: '',
references: [],
},
}
addTag(tag)
}
note.content.references.push({ content_type: tag.content_type, uuid: tag.uuid })
tag.content.references.push({ content_type: note.content_type, uuid: note.uuid })
}
notes.push(note)
}
const allItems: DecryptedTransferPayload[] = [...notes, ...tags]
if (defaultTag) {
allItems.push(defaultTag)
}
return allItems
}
loadXMLString(string: string, type: 'html' | 'xml') {
let xmlDoc
if (window.DOMParser) {
const parser = new DOMParser()
xmlDoc = parser.parseFromString(string, `text/${type}`)
} else {
throw new Error('Could not parse XML string')
}
return xmlDoc
}
stripHTML(html: string) {
const tmp = document.createElement('html')
tmp.innerHTML = html
return tmp.textContent || tmp.innerText || ''
}
}
@@ -0,0 +1,26 @@
export default `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE en-export SYSTEM "http://xml.evernote.com/pub/evernote-export3.dtd">
<en-export export-date="20210408T052957Z" application="Evernote" version="10.8.5">
<note>
<title>Testing 1</title>
<created>20210308T051614Z</created>
<updated>20210308T051855Z</updated>
<note-attributes>
</note-attributes>
<content>
<![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note><div>This is a test.</div></en-note> ]]>
</content>
</note>
<note>
<title></title>
<created>20200508T234829Z</created>
<updated>20200508T235233Z</updated>
<note-attributes>
</note-attributes>
<content>
<![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note><div>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</div></en-note> ]]>
</content>
</note>
</en-export>`
@@ -0,0 +1,54 @@
/**
* @jest-environment jsdom
*/
import { WebApplicationInterface } from '@standardnotes/snjs/dist/@types'
import { jsonTestData, htmlTestData } from './testData'
import { GoogleKeepConverter } from './GoogleKeepConverter'
describe('GoogleKeepConverter', () => {
let application: WebApplicationInterface
beforeEach(() => {
application = {
generateUUID: jest.fn().mockReturnValue('uuid'),
} as unknown as WebApplicationInterface
})
it('should parse json data', () => {
const converter = new GoogleKeepConverter(application)
const result = converter.tryParseAsJson(jsonTestData)
expect(result).not.toBeNull()
expect(result?.created_at).toBeInstanceOf(Date)
expect(result?.updated_at).toBeInstanceOf(Date)
expect(result?.uuid).not.toBeNull()
expect(result?.content_type).toBe('Note')
expect(result?.content.title).toBe('Testing 1')
expect(result?.content.text).toBe('This is a test.')
expect(result?.content.trashed).toBe(false)
expect(result?.content.archived).toBe(false)
expect(result?.content.pinned).toBe(false)
})
it('should parse html data', () => {
const converter = new GoogleKeepConverter(application)
const result = converter.tryParseAsHtml(
htmlTestData,
{
name: 'note-2.html',
},
false,
)
expect(result).not.toBeNull()
expect(result?.created_at).toBeInstanceOf(Date)
expect(result?.updated_at).toBeInstanceOf(Date)
expect(result?.uuid).not.toBeNull()
expect(result?.content_type).toBe('Note')
expect(result?.content.title).toBe('Testing 2')
expect(result?.content.text).toBe('Lorem ipsum dolor sit amet, consectetur adipiscing elit.')
})
})
@@ -0,0 +1,126 @@
import { WebApplicationInterface } from '@standardnotes/services'
import { ContentType } from '@standardnotes/common'
import { DecryptedTransferPayload, NoteContent } from '@standardnotes/models'
import { readFileAsText } from '../Utils'
import { Importer } from '../Importer'
type GoogleKeepJsonNote = {
color: string
isTrashed: boolean
isPinned: boolean
isArchived: boolean
textContent: string
title: string
userEditedTimestampUsec: number
}
export class GoogleKeepConverter extends Importer {
constructor(protected override application: WebApplicationInterface) {
super(application)
}
async convertGoogleKeepBackupFileToNote(
file: File,
stripHtml: boolean,
): Promise<DecryptedTransferPayload<NoteContent>> {
const content = await readFileAsText(file)
const possiblePayloadFromJson = this.tryParseAsJson(content)
if (possiblePayloadFromJson) {
return possiblePayloadFromJson
}
const possiblePayloadFromHtml = this.tryParseAsHtml(content, file, stripHtml)
if (possiblePayloadFromHtml) {
return possiblePayloadFromHtml
}
throw new Error('Could not parse Google Keep backup file')
}
tryParseAsHtml(data: string, file: { name: string }, stripHtml: boolean): DecryptedTransferPayload<NoteContent> {
const rootElement = document.createElement('html')
rootElement.innerHTML = data
const contentElement = rootElement.getElementsByClassName('content')[0]
let content: string | null
// Replace <br> with \n so line breaks get recognised
contentElement.innerHTML = contentElement.innerHTML.replace(/<br>/g, '\n')
if (stripHtml) {
content = contentElement.textContent
} else {
content = contentElement.innerHTML
}
if (!content) {
throw new Error('Could not parse content')
}
const title = rootElement.getElementsByClassName('title')[0]?.textContent || file.name
const date = this.getDateFromGKeepNote(data) || new Date()
return {
created_at: date,
created_at_timestamp: date.getTime(),
updated_at: date,
updated_at_timestamp: date.getTime(),
uuid: this.application.generateUUID(),
content_type: ContentType.Note,
content: {
title: title,
text: content,
references: [],
},
}
}
getDateFromGKeepNote(note: string) {
const regexWithTitle = /.*(?=<\/div>\n<div class="title">)/
const regexWithoutTitle = /.*(?=<\/div>\n\n<div class="content">)/
const possibleDateStringWithTitle = regexWithTitle.exec(note)?.[0]
const possibleDateStringWithoutTitle = regexWithoutTitle.exec(note)?.[0]
if (possibleDateStringWithTitle) {
const date = new Date(possibleDateStringWithTitle)
if (date.toString() !== 'Invalid Date' && date.toString() !== 'NaN') {
return date
}
}
if (possibleDateStringWithoutTitle) {
const date = new Date(possibleDateStringWithoutTitle)
if (date.toString() !== 'Invalid Date' && date.toString() !== 'NaN') {
return date
}
}
return
}
tryParseAsJson(data: string): DecryptedTransferPayload<NoteContent> | null {
try {
const parsed = JSON.parse(data) as GoogleKeepJsonNote
const date = new Date(parsed.userEditedTimestampUsec / 1000)
return {
created_at: date,
created_at_timestamp: date.getTime(),
updated_at: date,
updated_at_timestamp: date.getTime(),
uuid: this.application.generateUUID(),
content_type: ContentType.Note,
content: {
title: parsed.title,
text: parsed.textContent,
references: [],
archived: Boolean(parsed.isArchived),
trashed: Boolean(parsed.isTrashed),
pinned: Boolean(parsed.isPinned),
},
}
} catch (e) {
return null
}
}
}
@@ -0,0 +1,290 @@
const json = {
color: 'DEFAULT',
isTrashed: false,
isPinned: false,
isArchived: false,
textContent: 'This is a test.',
title: 'Testing 1',
userEditedTimestampUsec: 1618528050144000,
}
export const jsonTestData = JSON.stringify(json)
export const htmlTestData = `<?xml version="1.0" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Testing 2</title>
<style type="text/css">
body {
font-family: 'Roboto Condensed','Droid Sans',arial,sans-serif;
font-size: 15px;
color: rgba(0,0,0,0.8);
word-wrap: break-word;
background-color: #e8e8e8;
}
hr {
display: block;
margin-top: 10px;
margin-bottom: 10px;
margin-left: 5px;
margin-right: 5px;
border-style: inset dashed;
border-width: 1px;
}
.note {
outline: none;
box-shadow: 0 2px 1px rgba(0,0,0,0.08);
box-sizing: border-box;
max-width: 600px;
min-width: 240px;
margin: 20px;
background-color: rgb(255, 255, 255);
}
.note .heading {
font-size: 12px;
padding: 15px 15px 0 15px;
color: rgba(100,100,100,0.8);
}
.note .title {
font-size: 17px;
font-weight: bold;
padding: 15px 15px 0 15px;
min-height: 28px;
}
.note .content {
padding: 12px 15px 15px 15px;
font-family: 'Roboto Slab','Times New Roman',serif;
font-size: 14px;
}
.note .attachments {
padding: 0 15px 15px 15px;
}
.attachments ul {
padding: 0;
margin: 0;
}
.attachments li {
list-style-type: none;
margin-top: 12px;
}
.attachments li img {
max-width: 100%;
}
.attachments .audio {
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxOC4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4KPCFET0NUWVBFIHN2ZyAgUFVCTElDICctLy9XM0MvL0RURCBTVkcgMS4xLy9FTicgICdodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQnPgo8c3ZnIGlkPSJMYXllcl8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbDpzcGFjZT0icHJlc2VydmUiIHZpZXdCb3g9IjAgMCAyMCAyMCIgdmVyc2lvbj0iMS4xIiB5PSIwcHgiIHg9IjBweCIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDIwIDIwIj4KPHBhdGggZD0ibTEgN3Y2aDRsNSA1di0xNmwtNSA1aC00em0xMy41IDNjMC0xLjgtMS0zLjMtMi41LTR2OGMxLjUtMC43IDIuNS0yLjIgMi41LTR6bS0yLjUtOC44djIuMWMyLjkgMC45IDUgMy41IDUgNi43cy0yLjEgNS44LTUgNi43djIuMWM0LTAuOSA3LTQuNSA3LTguOHMtMy03LjktNy04Ljh6Ii8+Cjwvc3ZnPgo=);
background-size: 18px 18px;
background-repeat: no-repeat;
background-position: center;
width: 22px;
height: 22px;
display: block;
}
.note .list {
list-style: none;
padding: 0;
margin: 0;
}
.note .listitem {
}
.note .listitem .bullet {
position: absolute;
}
.note .listitem .text {
margin-left: 20px;
}
.note .identifier {
color: rgba(0, 0, 0, 0.5);
}
.note .identifier:before {
content: "(";
}
.note .identifier:after {
content: ")";
}
/* Only show identifiers when the element is hovered. */
.note .listitem .identifier,
.note .chip .identifier {
display: none;
}
.note .listitem:hover .identifier,
.note .chip:hover .identifier {
display: inline;
}
.note .chips {
padding: 12px 15px 15px 15px;
}
.note .chip {
display: inline-block;
max-width: 198px;
margin: 2px 4px 2px 0;
padding: 2px 5px;
background: rgba(0, 0, 0, 0.1);
border-radius: 2px;
color: rgba(0, 0, 0, 0.7);
font-size: 11px;
font-family: 'Roboto','Droid Sans',arial,sans-serif;
white-space: nowrap;
text-overflow: ellipsis;
}
.note .chip a {
text-decoration: none;
color: inherit;
}
.chip-icon {
display: inline-block;
width: 14px;
height: 14px;
background-size: 100%;
margin-right: 5px;
vertical-align: middle;
}
.annotation.CALENDAR .chip-icon {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iIzAwMDAwMCI+CiAgICA8cGF0aCBkPSJNMTcgMTJoLTV2NWg1di01ek0xNiAxdjJIOFYxSDZ2Mkg1Yy0xLjExIDAtMS45OS45LTEuOTkgMkwzIDE5YzAgMS4xLjg5IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJoLTFWMWgtMnptMyAxOEg1VjhoMTR2MTF6Ii8+CiAgICA8cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+Cjwvc3ZnPgo=);
}
.annotation.DOCS .chip-icon {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iIzAwMDAwMCI+CiAgICA8cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+CiAgICA8cGF0aCBkPSJNMTkgM0g1Yy0xLjEgMC0yIC45LTIgMnYxNGMwIDEuMS45IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6bS0xLjk5IDZIN1Y3aDEwLjAxdjJ6bTAgNEg3di0yaDEwLjAxdjJ6bS0zIDRIN3YtMmg3LjAxdjJ6Ii8+Cjwvc3ZnPgo=);
}
.annotation.GMAIL .chip-icon {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iIzAwMDAwMCI+CiAgICA8cGF0aCBmaWxsPSJub25lIiBkPSJNLTYxOC0yMTA0SDc4MnYzNjAwSC02MTh6TTAgMGgyNHYyNEgweiIvPgogICAgPHBhdGggZD0iTTIwIDRINGMtMS4xIDAtMiAuOS0yIDJ2MTJjMCAxLjEuOSAyIDIgMmgxNmMxLjEgMCAyLS45IDItMlY2YzAtMS4xLS45LTItMi0yem0wIDE0aC0yVjkuMkwxMiAxMyA2IDkuMlYxOEg0VjZoMS4ybDYuOCA0LjJMMTguOCA2SDIwdjEyeiIvPgo8L3N2Zz4K);
}
.annotation.SHEETS .chip-icon {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iIzAwMDAwMCI+CiAgICA8cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+CiAgICA8cGF0aCBkPSJNMTkgM0g1Yy0xLjEgMC0xLjk5LjktMS45OSAyTDMgOHYxMWMwIDEuMS45IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6bTAgOGgtOHY4SDl2LThINVY5aDRWNWgydjRoOHYyeiIvPgo8L3N2Zz4K);
}
.annotation.SLIDES .chip-icon {
background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjRweCIgaGVpZ2h0PSIyNHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgZmlsbD0iIzAwMDAwMCI+CiAgICA8cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+CiAgICA8cGF0aCBkPSJNMTkgM0g1Yy0xLjEgMC0xLjk5LjktMS45OSAydjE0YzAgMS4xLjg5IDIgMS45OSAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6bTAgMTNINVY4aDE0djh6Ii8+Cjwvc3ZnPgo=);
}
.annotation.WEBLINK .chip-icon {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iIzAwMDAwMCI+CiAgICA8cGF0aCBkPSJNMTkgNEg1Yy0xLjExIDAtMiAuOS0yIDJ2MTJjMCAxLjEuODkgMiAyIDJoMTRjMS4xIDAgMi0uOSAyLTJWNmMwLTEuMS0uODktMi0yLTJ6bTAgMTRINVY4aDE0djEweiIvPgogICAgPHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgyNHYyNEgweiIvPgo8L3N2Zz4K);
}
.sharees h2 {
display: none;
}
.sharees ul {
list-style: none;
margin: 0;
padding: 0 15px 15px 15px;
}
.sharees li {
display: inline-block;
width: 22px;
height: 22px;
text-indent: 100%;
white-space: nowrap;
overflow: hidden;
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iIzAwMDAwMCI+CiAgICA8cGF0aCBkPSJNMTIgMkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnptMCAzYzEuNjYgMCAzIDEuMzQgMyAzcy0xLjM0IDMtMyAzLTMtMS4zNC0zLTMgMS4zNC0zIDMtM3ptMCAxNC4yYy0yLjUgMC00LjcxLTEuMjgtNi0zLjIyLjAzLTEuOTkgNC0zLjA4IDYtMy4wOCAxLjk5IDAgNS45NyAxLjA5IDYgMy4wOC0xLjI5IDEuOTQtMy41IDMuMjItNiAzLjIyeiIvPgogICAgPHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPgo8L3N2Zz4K);
background-size: 18px 18px;
background-repeat: no-repeat;
background-position: center;
}
.sharees li.group {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iIzAwMDAwMCI+CiAgICA8cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+CiAgICA8cGF0aCBkPSJNMTYgMTFjMS42NiAwIDIuOTktMS4zNCAyLjk5LTNTMTcuNjYgNSAxNiA1Yy0xLjY2IDAtMyAxLjM0LTMgM3MxLjM0IDMgMyAzem0tOCAwYzEuNjYgMCAyLjk5LTEuMzQgMi45OS0zUzkuNjYgNSA4IDVDNi4zNCA1IDUgNi4zNCA1IDhzMS4zNCAzIDMgM3ptMCAyYy0yLjMzIDAtNyAxLjE3LTcgMy41VjE5aDE0di0yLjVjMC0yLjMzLTQuNjctMy41LTctMy41em04IDBjLS4yOSAwLS42Mi4wMi0uOTcuMDUgMS4xNi44NCAxLjk3IDEuOTcgMS45NyAzLjQ1VjE5aDZ2LTIuNWMwLTIuMzMtNC42Ny0zLjUtNy0zLjV6Ii8+Cjwvc3ZnPgo=);
}
.note .meta-icons {
float: right;
}
.note .meta-icons span {
display: inline-block;
background-size: 18px 18px;
background-repeat: no-repeat;
background-position: center;
width: 22px;
height: 22px;
padding-left: 4px;
}
.meta-icons .pinned {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMHB4IiBoZWlnaHQ9IjIwcHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iIzQyODVmNCI+DQogICAgPHBhdGggZD0iTTE2IDVoLjk5TDE3IDNIN3YyaDF2N2wtMiAydjJoNXY2bDEgMSAxLTF2LTZoNXYtMmwtMi0yVjV6Ii8+DQogICAgPHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgyNHYyNEgweiIvPg0KPC9zdmc+);
}
.meta-icons .archived {
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxOC4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4KPCFET0NUWVBFIHN2ZyAgUFVCTElDICctLy9XM0MvL0RURCBTVkcgMS4xLy9FTicgICdodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQnPgo8c3ZnIGlkPSJMYXllcl8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbDpzcGFjZT0icHJlc2VydmUiIHZpZXdCb3g9IjAgMCAxOCAxOCIgdmVyc2lvbj0iMS4xIiB5PSIwcHgiIHg9IjBweCIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDE4IDE4Ij4KPHBhdGggZD0ibTE2LjYgM2wtMS4yLTEuNWMtMC4yLTAuMy0wLjYtMC41LTEtMC41aC0xMC43Yy0wLjQgMC0wLjggMC4yLTEgMC41bC0xLjMgMS41Yy0wLjIgMC4zLTAuNCAwLjctMC40IDEuMXYxMS4xYzAgMSAwLjggMS44IDEuOCAxLjhoMTIuNGMxIDAgMS44LTAuOCAxLjgtMS44di0xMS4xYzAtMC40LTAuMi0wLjgtMC40LTEuMXptLTcuNiAxMC45bC00LjktNC45aDMuMXYtMS44aDMuNnYxLjhoMy4xbC00LjkgNC45em0tNi4xLTExLjFsMC43LTAuOWgxMC43bDAuOCAwLjloLTEyLjJ6Ii8+Cjwvc3ZnPgo=);
}
.meta-icons .trashed {
background-image: url(data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE4cHgiIHdpZHRoPSIxOHB4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0OCA0OCIgZmlsbD0iIzAwMDAwMCI+DQogPHBhdGggZD0ibTEyIDM4YzAgMi4yMSAxLjc5IDQgNCA0aDE2YzIuMjEgMCA0LTEuNzkgNC00di0yNGgtMjR2MjR6bTI2LTMwaC03bC0yLTJoLTEwbC0yIDJoLTd2NGgyOHYtNHoiLz4NCiA8cGF0aCBkPSJtMCAwaDQ4djQ4aC00OHoiIGZpbGw9Im5vbmUiLz4NCjwvc3ZnPg==);
}
.checked {
text-decoration: line-through;
}
.RED {
background-color: rgb(255, 109, 63) !important;
}
.ORANGE {
background-color: rgb(255, 155, 0) !important;
}
.YELLOW {
background-color: rgb(255, 218, 0) !important;
}
.GREEN {
background-color: rgb(149, 214, 65) !important;
}
.TEAL {
background-color: rgb(28, 232, 181) !important;
}
.BLUE {
background-color: rgb(63, 195, 255) !important;
}
.GRAY {
background-color: rgb(184, 196, 201) !important;
}
/* go/keep-more-colors-eng */
.CERULEAN {
background-color: rgb(130, 177, 255) !important;
}
.PURPLE {
background-color: rgb(179, 136, 255) !important;
}
.PINK {
background-color: rgb(248, 187, 208) !important;
}
.BROWN {
background-color: rgb(215, 204, 200) !important;
}
</style></head>
<body><div class="note"><div class="heading"><div class="meta-icons">
</div>
Apr 15, 2021, 7:07:43 PM</div>
<div class="title">Testing 2</div>
<div class="content">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</div>
</div></body></html>`
@@ -0,0 +1,14 @@
import { WebApplicationInterface } from '@standardnotes/services'
import { DecryptedTransferPayload } from '@standardnotes/snjs'
export class Importer {
constructor(protected application: WebApplicationInterface) {}
async importFromTransferPayloads(payloads: DecryptedTransferPayload[]): Promise<void> {
for (const payload of payloads) {
const itemPayload = this.application.items.createPayloadFromObject(payload)
const item = this.application.items.createItemFromPayload(itemPayload)
await this.application.mutator.insertItem(item)
}
}
}
@@ -0,0 +1,46 @@
import { WebApplicationInterface } from '@standardnotes/services'
import { SimplenoteConverter } from './SimplenoteConverter'
import data from './testData'
describe('SimplenoteConverter', () => {
let application: WebApplicationInterface
beforeEach(() => {
application = {
generateUUID: jest.fn().mockReturnValue('uuid'),
} as any
})
it('should parse', () => {
const converter = new SimplenoteConverter(application)
const result = converter.parse(data)
expect(result).not.toBeNull()
expect(result?.length).toBe(3)
expect(result?.[0].created_at).toBeInstanceOf(Date)
expect(result?.[0].updated_at).toBeInstanceOf(Date)
expect(result?.[0].uuid).not.toBeNull()
expect(result?.[0].content_type).toBe('Note')
expect(result?.[0].content.title).toBe('Testing 1')
expect(result?.[0].content.text).toBe("This is the 1st note's content.")
expect(result?.[0].content.trashed).toBe(false)
expect(result?.[1].created_at).toBeInstanceOf(Date)
expect(result?.[1].updated_at).toBeInstanceOf(Date)
expect(result?.[1].uuid).not.toBeNull()
expect(result?.[1].content_type).toBe('Note')
expect(result?.[1].content.title).toBe('Testing 2')
expect(result?.[1].content.text).toBe("This is the 2nd note's content.")
expect(result?.[1].content.trashed).toBe(false)
expect(result?.[2].created_at).toBeInstanceOf(Date)
expect(result?.[2].updated_at).toBeInstanceOf(Date)
expect(result?.[2].uuid).not.toBeNull()
expect(result?.[2].content_type).toBe('Note')
expect(result?.[2].content.title).not.toBeFalsy()
expect(result?.[2].content.text).toBe('Welcome to Simplenote!')
expect(result?.[2].content.trashed).toBe(true)
})
})
@@ -0,0 +1,78 @@
import { DecryptedTransferPayload, NoteContent } from '@standardnotes/models'
import { ContentType } from '@standardnotes/common'
import { readFileAsText } from '../Utils'
import { WebApplicationInterface } from '@standardnotes/services'
import { Importer } from '../Importer'
type SimplenoteItem = {
creationDate: string
lastModified: string
content: string
}
type SimplenoteData = {
activeNotes: SimplenoteItem[]
trashedNotes: SimplenoteItem[]
}
export class SimplenoteConverter extends Importer {
constructor(protected override application: WebApplicationInterface) {
super(application)
}
createNoteFromItem(item: SimplenoteItem, trashed: boolean): DecryptedTransferPayload<NoteContent> {
const createdAtDate = new Date(item.creationDate)
const updatedAtDate = new Date(item.lastModified)
const splitItemContent = item.content.split('\r\n')
const hasTitleAndContent = splitItemContent.length === 2
const title =
hasTitleAndContent && splitItemContent[0].length ? splitItemContent[0] : createdAtDate.toLocaleString()
const content = hasTitleAndContent && splitItemContent[1].length ? splitItemContent[1] : item.content
return {
created_at: createdAtDate,
created_at_timestamp: createdAtDate.getTime(),
updated_at: updatedAtDate,
updated_at_timestamp: updatedAtDate.getTime(),
uuid: this.application.generateUUID(),
content_type: ContentType.Note,
content: {
title,
text: content,
references: [],
trashed,
appData: {
'org.standardnotes.sn': {
client_updated_at: updatedAtDate,
},
},
},
}
}
async convertSimplenoteBackupFileToNotes(file: File): Promise<DecryptedTransferPayload<NoteContent>[]> {
const content = await readFileAsText(file)
const notes = this.parse(content)
if (!notes) {
throw new Error('Could not parse notes')
}
return notes
}
parse(data: string) {
try {
const parsed = JSON.parse(data) as SimplenoteData
const activeNotes = parsed.activeNotes.reverse().map((item) => this.createNoteFromItem(item, false))
const trashedNotes = parsed.trashedNotes.reverse().map((item) => this.createNoteFromItem(item, true))
return [...activeNotes, ...trashedNotes]
} catch (error) {
console.error(error)
return null
}
}
}
@@ -0,0 +1,26 @@
const data = {
activeNotes: [
{
id: '43349052-4efa-48c2-bdd6-8323124451b1',
content: "Testing 2\r\nThis is the 2nd note's content.",
creationDate: '2020-06-08T21:28:43.856Z',
lastModified: '2021-04-16T06:21:53.124Z',
},
{
id: '2a338440-4a24-4180-9805-1110d325642c',
content: "Testing 1\r\nThis is the 1st note's content.",
creationDate: '2020-06-08T21:28:38.241Z',
lastModified: '2021-04-16T06:21:58.294Z',
},
],
trashedNotes: [
{
id: 'agtzaW1wbGUtbm90ZXIRCxIETm90ZRiAgLCvy-3gCAw',
content: 'Welcome to Simplenote!',
creationDate: '2020-06-08T21:28:28.434Z',
lastModified: '2021-04-16T06:20:14.143Z',
},
],
}
export default JSON.stringify(data, null, 2)
+13
View File
@@ -0,0 +1,13 @@
export const readFileAsText = (file: File): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = (e) => {
if (e.target?.result) {
resolve(e.target.result as string)
} else {
reject()
}
}
reader.readAsText(file)
})
}
+4
View File
@@ -32,3 +32,7 @@ export * from './Theme/ThemeManager'
export * from './Toast/ToastService'
export * from './Toast/ToastServiceInterface'
export * from './StatePersistence/StatePersistence'
export * from './Import/AegisConverter/AegisToAuthenticatorConverter'
export * from './Import/SimplenoteConverter/SimplenoteConverter'
export * from './Import/GoogleKeepConverter/GoogleKeepConverter'
export * from './Import/EvernoteConverter/EvernoteConverter'
+1
View File
@@ -4,6 +4,7 @@
"skipLibCheck": true,
"rootDir": "./src",
"outDir": "./dist",
"allowSyntheticDefaultImports": true,
"jsx": "react-jsx"
},
"include": ["src/**/*"],
+47
View File
@@ -3,6 +3,53 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.130.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)
### Bug Fixes
* Fixed issue where converting a note to Super from the note options menu would not work ([df4acbb](https://github.com/standardnotes/app/commit/df4acbb1bcd9f3bb11717dac47c31c97a1de4ff5))
* Fixed issue where Super note font size would shift on first load ([113d3be](https://github.com/standardnotes/app/commit/113d3bebb43451ec2c815eabc255dfb790a91431))
## [3.130.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)
### Bug Fixes
* Fixed issue where Moments could incorrectly get engaged during biometrics unlocking ([8e3ae80](https://github.com/standardnotes/app/commit/8e3ae80aa09c0c60e4f3ad08497fd656a1231465))
# [3.130.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-28)
### Features
* Added "Files Table View" to Labs ([b764296](https://github.com/standardnotes/app/commit/b764296c8f831d06f854dcf2982e644c27e14e7c))
## [3.129.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-28)
**Note:** Version bump only for package @standardnotes/web
## [3.129.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-28)
### Bug Fixes
* Fixed issue where Moments on mobile would not be properly disabled until app restart ([#2122](https://github.com/standardnotes/app/issues/2122)) ([2611c69](https://github.com/standardnotes/app/commit/2611c690b08c2c9654a5030eef74d6418d5e6b4e))
## [3.129.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/web
# [3.129.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
### Features
* Allow changing per-view display preferences for system views ([#2120](https://github.com/standardnotes/app/issues/2120)) ([dc33aef](https://github.com/standardnotes/app/commit/dc33aef6608a364107a884ad2f26c14bceea23b7))
## [3.128.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/web
## [3.128.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/web
## [3.128.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
**Note:** Version bump only for package @standardnotes/web
+116
View File
@@ -1,5 +1,121 @@
{
"versions": [
{
"version": "3.130.3",
"title": "[3.130.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)",
"date": null,
"body": "### Bug Fixes\n\n* Fixed issue where converting a note to Super from the note options menu would not work ([df4acbb](https://github.com/standardnotes/app/commit/df4acbb1bcd9f3bb11717dac47c31c97a1de4ff5))\n* Fixed issue where Super note font size would shift on first load ([113d3be](https://github.com/standardnotes/app/commit/113d3bebb43451ec2c815eabc255dfb790a91431))",
"parsed": {
"_": [
"Fixed issue where converting a note to Super from the note options menu would not work (df4acbb)",
"Fixed issue where Super note font size would shift on first load (113d3be)"
],
"Bug Fixes": [
"Fixed issue where converting a note to Super from the note options menu would not work (df4acbb)",
"Fixed issue where Super note font size would shift on first load (113d3be)"
]
}
},
{
"version": "3.130.2",
"title": "[3.130.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)",
"date": null,
"body": "### Bug Fixes\n\n* Fixed issue where Moments could incorrectly get engaged during biometrics unlocking ([8e3ae80](https://github.com/standardnotes/app/commit/8e3ae80aa09c0c60e4f3ad08497fd656a1231465))",
"parsed": {
"_": [
"Fixed issue where Moments could incorrectly get engaged during biometrics unlocking (8e3ae80)"
],
"Bug Fixes": [
"Fixed issue where Moments could incorrectly get engaged during biometrics unlocking (8e3ae80)"
]
}
},
{
"version": "3.130.0",
"title": "[3.130.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-28)",
"date": null,
"body": "### Features\n\n* Added \"Files Table View\" to Labs ([b764296](https://github.com/standardnotes/app/commit/b764296c8f831d06f854dcf2982e644c27e14e7c))",
"parsed": {
"_": [
"Added \"Files Table View\" to Labs (b764296)"
],
"Features": [
"Added \"Files Table View\" to Labs (b764296)"
]
}
},
{
"version": "3.129.3",
"title": "[3.129.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-28)",
"date": null,
"body": "**Note:** Version bump only for package @standardnotes/web",
"parsed": {
"_": [
"Note: Version bump only for package @standardnotes/web"
]
}
},
{
"version": "3.129.2",
"title": "[3.129.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-28)",
"date": null,
"body": "### Bug Fixes\n\n* Fixed issue where Moments on mobile would not be properly disabled until app restart ([#2122](https://github.com/standardnotes/app/issues/2122)) ([2611c69](https://github.com/standardnotes/app/commit/2611c690b08c2c9654a5030eef74d6418d5e6b4e))",
"parsed": {
"_": [
"Fixed issue where Moments on mobile would not be properly disabled until app restart (#2122) (2611c69)"
],
"Bug Fixes": [
"Fixed issue where Moments on mobile would not be properly disabled until app restart (#2122) (2611c69)"
]
}
},
{
"version": "3.129.1",
"title": "[3.129.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)",
"date": null,
"body": "**Note:** Version bump only for package @standardnotes/web",
"parsed": {
"_": [
"Note: Version bump only for package @standardnotes/web"
]
}
},
{
"version": "3.129.0",
"title": "[3.129.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)",
"date": null,
"body": "### Features\n\n* Allow changing per-view display preferences for system views ([#2120](https://github.com/standardnotes/app/issues/2120)) ([dc33aef](https://github.com/standardnotes/app/commit/dc33aef6608a364107a884ad2f26c14bceea23b7))",
"parsed": {
"_": [
"Allow changing per-view display preferences for system views (#2120) (dc33aef)"
],
"Features": [
"Allow changing per-view display preferences for system views (#2120) (dc33aef)"
]
}
},
{
"version": "3.128.11",
"title": "[3.128.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)",
"date": null,
"body": "**Note:** Version bump only for package @standardnotes/web",
"parsed": {
"_": [
"Note: Version bump only for package @standardnotes/web"
]
}
},
{
"version": "3.128.10",
"title": "[3.128.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)",
"date": null,
"body": "**Note:** Version bump only for package @standardnotes/web",
"parsed": {
"_": [
"Note: Version bump only for package @standardnotes/web"
]
}
},
{
"version": "3.128.9",
"title": "[3.128.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/web",
"version": "3.128.9",
"version": "3.130.3",
"license": "AGPL-3.0-or-later",
"main": "dist/app.js",
"author": "Standard Notes.",
@@ -430,4 +430,8 @@ export class WebApplication extends SNApplication implements WebApplicationInter
this.getViewControllerManager().preferencesController.setCurrentPane(pane)
}
}
generateUUID(): string {
return this.options.crypto.generateUUID()
}
}
@@ -14,6 +14,8 @@ import { ApplicationGroup } from '@/Application/ApplicationGroup'
import { formatLastSyncDate } from '@/Utils/DateUtils'
import Spinner from '@/Components/Spinner/Spinner'
import { MenuItemIconSize } from '@/Constants/TailwindClassNames'
import ImportMenuOption from './ImportMenuOption'
import { featureTrunkEnabled, FeatureTrunkName } from '@/FeatureTrunk'
type Props = {
viewControllerManager: ViewControllerManager
@@ -187,6 +189,7 @@ const GeneralAccountMenu: FunctionComponent<Props> = ({
<Icon type="open-in" className={iconClassName} />
Open FileSend
</MenuItem>
{featureTrunkEnabled(FeatureTrunkName.ImportTools) && <ImportMenuOption />}
{user ? (
<>
<MenuItemSeparator />
@@ -0,0 +1,115 @@
import { classNames, FeatureIdentifier, FeatureStatus } from '@standardnotes/snjs'
import Icon from '../Icon/Icon'
import MenuItem from '../Menu/MenuItem'
import { MenuItemIconSize } from '@/Constants/TailwindClassNames'
import { useRef, useState } from 'react'
import Popover from '../Popover/Popover'
import Menu from '../Menu/Menu'
import { ClassicFileReader } from '@standardnotes/filepicker'
import {
AegisToAuthenticatorConverter,
EvernoteConverter,
GoogleKeepConverter,
SimplenoteConverter,
} from '@standardnotes/ui-services'
import { useApplication } from '../ApplicationProvider'
const iconClassName = classNames('mr-2 text-neutral', MenuItemIconSize)
const ImportMenuOption = () => {
const application = useApplication()
const anchorRef = useRef<HTMLButtonElement>(null)
const [isMenuOpen, setIsMenuOpen] = useState(false)
const togglePopover = () => {
setIsMenuOpen((isOpen) => !isOpen)
}
return (
<>
<MenuItem ref={anchorRef} onClick={togglePopover}>
<Icon type="upload" className={iconClassName} />
Import
<Icon type="chevron-right" className={`ml-auto text-neutral ${MenuItemIconSize}`} />
</MenuItem>
<Popover
anchorElement={anchorRef.current}
className="py-2"
open={isMenuOpen}
side="right"
align="end"
togglePopover={togglePopover}
>
<Menu a11yLabel="Import options menu" isOpen={isMenuOpen}>
<MenuItem
onClick={() => {
setIsMenuOpen((isOpen) => !isOpen)
}}
>
<Icon type="plain-text" className={iconClassName} />
Plaintext
</MenuItem>
<MenuItem
onClick={async () => {
const files = await ClassicFileReader.selectFiles()
files.forEach(async (file) => {
const converter = new GoogleKeepConverter(application)
const noteTransferPayload = await converter.convertGoogleKeepBackupFileToNote(file, false)
void converter.importFromTransferPayloads([noteTransferPayload])
})
}}
>
<Icon type="plain-text" className={iconClassName} />
Google Keep
</MenuItem>
<MenuItem
onClick={async () => {
const files = await ClassicFileReader.selectFiles()
files.forEach(async (file) => {
const converter = new EvernoteConverter(application)
const noteAndTagPayloads = await converter.convertENEXFileToNotesAndTags(file, true)
void converter.importFromTransferPayloads(noteAndTagPayloads)
})
}}
>
<Icon type="rich-text" className={iconClassName} />
Evernote
</MenuItem>
<MenuItem
onClick={async () => {
const files = await ClassicFileReader.selectFiles()
files.forEach(async (file) => {
const converter = new SimplenoteConverter(application)
const noteTransferPayloads = await converter.convertSimplenoteBackupFileToNotes(file)
void converter.importFromTransferPayloads(noteTransferPayloads)
})
}}
>
<Icon type="rich-text" className={iconClassName} />
Simplenote
</MenuItem>
<MenuItem
onClick={async () => {
const files = await ClassicFileReader.selectFiles()
const isEntitledToAuthenticator =
application.features.getFeatureStatus(FeatureIdentifier.TokenVaultEditor) === FeatureStatus.Entitled
files.forEach(async (file) => {
const converter = new AegisToAuthenticatorConverter(application)
const noteTransferPayload = await converter.convertAegisBackupFileToNote(
file,
isEntitledToAuthenticator,
)
void converter.importFromTransferPayloads([noteTransferPayload])
})
}}
>
<Icon type="lock-filled" className={iconClassName} />
Aegis
</MenuItem>
</Menu>
</Popover>
</>
)
}
export default ImportMenuOption
@@ -12,8 +12,7 @@ import { NavigationController } from '@/Controllers/Navigation/NavigationControl
import { NotesController } from '@/Controllers/NotesController/NotesController'
import { ElementIds } from '@/Constants/ElementIDs'
import { classNames } from '@standardnotes/utils'
import { ContentType, SNTag } from '@standardnotes/snjs'
import { featureTrunkEnabled, FeatureTrunkName } from '@/FeatureTrunk'
import { ContentType, FeatureIdentifier, SNTag } from '@standardnotes/snjs'
type Props = {
application: WebApplication
@@ -98,7 +97,7 @@ const ContentList: FunctionComponent<Props> = ({
const hasNotes = items.some((item) => item.content_type === ContentType.Note)
const isFilesTableViewEnabled = featureTrunkEnabled(FeatureTrunkName.FilesTableView)
const isFilesTableViewEnabled = application.features.isExperimentalFeatureEnabled(FeatureIdentifier.FilesTableView)
return (
<div
@@ -1,5 +1,4 @@
import { featureTrunkEnabled, FeatureTrunkName } from '@/FeatureTrunk'
import { ContentType, FileItem, SNNote } from '@standardnotes/snjs'
import { ContentType, FeatureIdentifier, FileItem, SNNote } from '@standardnotes/snjs'
import React, { FunctionComponent } from 'react'
import FileListItem from './FileListItem'
import FileListItemCard from './FileListItemCard'
@@ -8,7 +7,9 @@ import { AbstractListItemProps, doListItemPropsMeritRerender } from './Types/Abs
import { ListableContentItem } from './Types/ListableContentItem'
const ContentListItem: FunctionComponent<AbstractListItemProps<ListableContentItem>> = (props) => {
const isFilesTableViewEnabled = featureTrunkEnabled(FeatureTrunkName.FilesTableView)
const isFilesTableViewEnabled = props.application.features.isExperimentalFeatureEnabled(
FeatureIdentifier.FilesTableView,
)
switch (props.item.content_type) {
case ContentType.Note:
@@ -9,7 +9,7 @@ import {
} from '@standardnotes/ui-services'
import { WebApplication } from '@/Application/Application'
import { PANEL_NAME_NOTES } from '@/Constants/Constants'
import { FileItem, PrefKey, SystemViewId, WebAppEvent } from '@standardnotes/snjs'
import { FeatureIdentifier, FileItem, PrefKey, SystemViewId, WebAppEvent } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { forwardRef, useCallback, useEffect, useMemo } from 'react'
import ContentList from '@/Components/ContentListView/ContentList'
@@ -39,7 +39,6 @@ import { isMobileScreen } from '@/Utils'
import FloatingAddButton from './FloatingAddButton'
import FilesTableView from '../FilesTableView/FilesTableView'
import { FeaturesController } from '@/Controllers/FeaturesController'
import { featureTrunkEnabled, FeatureTrunkName } from '@/FeatureTrunk'
type Props = {
accountMenuController: AccountMenuController
@@ -182,6 +181,9 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
}
}, [isFilesSmartView, filesController, createNewNote, toggleAppPane, application])
const isFilesTableViewEnabled = application.features.isExperimentalFeatureEnabled(FeatureIdentifier.FilesTableView)
const shouldShowFilesTableView = isFilesTableViewEnabled && selectedTag?.uuid === SystemViewId.Files
useEffect(() => {
const searchBarElement = document.getElementById(ElementIds.SearchBar)
/**
@@ -204,6 +206,9 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
if (searchBarElement === document.activeElement) {
searchBarElement?.blur()
}
if (shouldShowFilesTableView) {
return
}
selectNextItem()
},
},
@@ -211,6 +216,9 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
command: PREVIOUS_LIST_ITEM_KEYBOARD_COMMAND,
element: document.body,
onKeyDown: () => {
if (shouldShowFilesTableView) {
return
}
selectPreviousItem()
},
},
@@ -252,6 +260,7 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
selectNextItem,
selectPreviousItem,
selectionController,
shouldShowFilesTableView,
])
const shortcutForCreate = useMemo(
@@ -285,9 +294,6 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
}
}, [selectedUuids, innerRef, isCurrentNoteTemplate, renderedItems, panes])
const isFilesTableViewEnabled = featureTrunkEnabled(FeatureTrunkName.FilesTableView)
const shouldShowFilesTableView = isFilesTableViewEnabled && selectedTag?.uuid === SystemViewId.Files
return (
<div
id={id}
@@ -315,7 +321,7 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
itemListController={itemListController}
/>
)}
{(!isFilesTableViewEnabled || isMobileScreen()) && (
{(!shouldShowFilesTableView || isMobileScreen()) && (
<SearchBar
itemListController={itemListController}
searchOptionsController={searchOptionsController}
@@ -1,6 +1,7 @@
import {
CollectionSort,
CollectionSortProperty,
FeatureIdentifier,
IconType,
isSmartView,
isSystemView,
@@ -24,7 +25,6 @@ import NoSubscriptionBanner from '@/Components/NoSubscriptionBanner/NoSubscripti
import MenuRadioButtonItem from '@/Components/Menu/MenuRadioButtonItem'
import MenuSwitchButtonItem from '@/Components/Menu/MenuSwitchButtonItem'
import { Pill } from '@/Components/Preferences/PreferencesComponents/Content'
import { featureTrunkEnabled, FeatureTrunkName } from '@/FeatureTrunk'
const DailyEntryModeEnabled = true
@@ -35,11 +35,14 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
selectedTag,
}) => {
const isSystemTag = isSmartView(selectedTag) && isSystemView(selectedTag)
const [currentMode, setCurrentMode] = useState<PreferenceMode>(selectedTag.preferences ? 'tag' : 'global')
const selectedTagPreferences = isSystemTag
? application.getPreference(PrefKey.SystemViewPreferences)?.[selectedTag.uuid as SystemViewId]
: selectedTag.preferences
const [currentMode, setCurrentMode] = useState<PreferenceMode>(selectedTagPreferences ? 'tag' : 'global')
const [preferences, setPreferences] = useState<TagPreferences>({})
const hasSubscription = application.hasValidSubscription()
const controlsDisabled = currentMode === 'tag' && !hasSubscription
const isDailyEntry = selectedTag.preferences?.entryMode === 'daily'
const isDailyEntry = selectedTagPreferences?.entryMode === 'daily'
const reloadPreferences = useCallback(() => {
const globalValues: TagPreferences = {
@@ -71,24 +74,59 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
} else {
setPreferences({
...globalValues,
...selectedTag.preferences,
...selectedTagPreferences,
})
}
}, [currentMode, setPreferences, selectedTag, application])
}, [application, currentMode, selectedTagPreferences])
useEffect(() => {
reloadPreferences()
}, [reloadPreferences])
const changeGlobalPreferences = useCallback(
async (properties: Partial<TagPreferences>) => {
for (const key of Object.keys(properties)) {
const value = properties[key as keyof TagPreferences]
await application.setPreference(key as PrefKey, value).catch(console.error)
reloadPreferences()
}
},
[application, reloadPreferences],
)
const changeSystemViewPreferences = useCallback(
async (properties: Partial<TagPreferences>) => {
if (!selectedTag) {
return
}
if (!isSystemTag) {
return
}
const systemViewPreferences = application.getPreference(PrefKey.SystemViewPreferences) || {}
const systemViewPreferencesForTag = systemViewPreferences[selectedTag.uuid as SystemViewId] || {}
await application.setPreference(PrefKey.SystemViewPreferences, {
...systemViewPreferences,
[selectedTag.uuid as SystemViewId]: {
...systemViewPreferencesForTag,
...properties,
},
})
reloadPreferences()
},
[application, isSystemTag, reloadPreferences, selectedTag],
)
const changePreferences = useCallback(
async (properties: Partial<TagPreferences>) => {
if (currentMode === 'global') {
for (const key of Object.keys(properties)) {
const value = properties[key as keyof TagPreferences]
await application.setPreference(key as PrefKey, value).catch(console.error)
reloadPreferences()
}
await changeGlobalPreferences(properties)
} else if (isSystemTag) {
await changeSystemViewPreferences(properties)
} else {
await application.mutator.changeAndSaveItem<TagMutator>(selectedTag, (mutator) => {
mutator.preferences = {
@@ -98,14 +136,23 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
})
}
},
[reloadPreferences, application, currentMode, selectedTag],
[currentMode, isSystemTag, changeGlobalPreferences, changeSystemViewPreferences, application.mutator, selectedTag],
)
const resetTagPreferences = useCallback(() => {
const resetTagPreferences = useCallback(async () => {
if (isSystemTag) {
await application.setPreference(PrefKey.SystemViewPreferences, {
...application.getPreference(PrefKey.SystemViewPreferences),
[selectedTag.uuid as SystemViewId]: undefined,
})
reloadPreferences()
return
}
void application.mutator.changeAndSaveItem<TagMutator>(selectedTag, (mutator) => {
mutator.preferences = undefined
})
}, [application, selectedTag])
}, [application, isSystemTag, reloadPreferences, selectedTag])
const toggleSortReverse = useCallback(() => {
void changePreferences({ sortReverse: !preferences.sortReverse })
@@ -203,7 +250,7 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
)
}
const isFilesTableViewEnabled = featureTrunkEnabled(FeatureTrunkName.FilesTableView)
const isFilesTableViewEnabled = application.features.isExperimentalFeatureEnabled(FeatureIdentifier.FilesTableView)
const shouldHideNonApplicableOptions = isFilesTableViewEnabled && selectedTag?.uuid === SystemViewId.Files
return (
@@ -212,7 +259,7 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
<div className={classNames('mt-1.5 flex w-full justify-between px-3', !controlsDisabled && 'mb-3')}>
<div className="flex items-center gap-1.5">
<TabButton label="Global" mode="global" />
{!isSystemTag && <TabButton label={selectedTag.title} icon={selectedTag.iconString} mode="tag" />}
<TabButton label={selectedTag.title} icon={selectedTag.iconString} mode="tag" />
</div>
{currentMode === 'tag' && (
<button className="text-base lg:text-sm" onClick={resetTagPreferences}>
@@ -363,7 +410,7 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
</>
)}
{currentMode === 'tag' && DailyEntryModeEnabled && (
{currentMode === 'tag' && !isSystemTag && DailyEntryModeEnabled && (
<>
<MenuItemSeparator />
<MenuSwitchButtonItem
@@ -385,7 +432,7 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
</>
)}
{!shouldHideNonApplicableOptions && (
{!shouldHideNonApplicableOptions && (!isSystemTag || currentMode === 'global') && (
<>
<MenuItemSeparator />
<NewNotePreferences
@@ -1,4 +1,13 @@
import { FeatureIdentifier, NewNoteTitleFormat, PrefKey, EditorIdentifier, TagPreferences } from '@standardnotes/snjs'
import {
FeatureIdentifier,
NewNoteTitleFormat,
PrefKey,
EditorIdentifier,
TagPreferences,
isSmartView,
isSystemView,
SystemViewId,
} from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { ChangeEventHandler, FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
import { PrefDefaults } from '@/Constants/PrefDefaults'
@@ -31,6 +40,11 @@ const NewNotePreferences: FunctionComponent<Props> = ({
changePreferencesCallback,
disabled,
}: Props) => {
const isSystemTag = isSmartView(selectedTag) && isSystemView(selectedTag)
const selectedTagPreferences = isSystemTag
? application.getPreference(PrefKey.SystemViewPreferences)?.[selectedTag.uuid as SystemViewId]
: selectedTag.preferences
const [editorItems, setEditorItems] = useState<DropdownItem[]>([])
const [defaultEditorIdentifier, setDefaultEditorIdentifier] = useState<EditorIdentifier>(
FeatureIdentifier.PlainEditor,
@@ -45,15 +59,15 @@ const NewNotePreferences: FunctionComponent<Props> = ({
}, [application])
const reloadPreferences = useCallback(() => {
if (mode === 'tag' && selectedTag.preferences?.editorIdentifier) {
setDefaultEditorIdentifier(selectedTag.preferences?.editorIdentifier)
if (mode === 'tag' && selectedTagPreferences?.editorIdentifier) {
setDefaultEditorIdentifier(selectedTagPreferences?.editorIdentifier)
} else {
const globalDefault = getGlobalEditorDefaultIdentifier()
setDefaultEditorIdentifier(globalDefault)
}
if (mode === 'tag' && selectedTag.preferences?.newNoteTitleFormat) {
setNewNoteTitleFormat(selectedTag.preferences?.newNoteTitleFormat)
if (mode === 'tag' && selectedTagPreferences?.newNoteTitleFormat) {
setNewNoteTitleFormat(selectedTagPreferences?.newNoteTitleFormat)
} else {
setNewNoteTitleFormat(
application.getPreference(PrefKey.NewNoteTitleFormat, PrefDefaults[PrefKey.NewNoteTitleFormat]),
@@ -61,22 +75,21 @@ const NewNotePreferences: FunctionComponent<Props> = ({
}
}, [
mode,
selectedTag,
application,
selectedTagPreferences?.editorIdentifier,
selectedTagPreferences?.newNoteTitleFormat,
getGlobalEditorDefaultIdentifier,
setDefaultEditorIdentifier,
setNewNoteTitleFormat,
application,
])
useEffect(() => {
if (mode === 'tag' && selectedTag.preferences?.customNoteTitleFormat) {
setCustomNoteTitleFormat(selectedTag.preferences?.customNoteTitleFormat)
if (mode === 'tag' && selectedTagPreferences?.customNoteTitleFormat) {
setCustomNoteTitleFormat(selectedTagPreferences?.customNoteTitleFormat)
} else {
setCustomNoteTitleFormat(
application.getPreference(PrefKey.CustomNoteTitleFormat, PrefDefaults[PrefKey.CustomNoteTitleFormat]),
)
}
}, [application, mode, selectedTag])
}, [application, mode, selectedTag, selectedTagPreferences?.customNoteTitleFormat])
useEffect(() => {
void reloadPreferences()
@@ -12,6 +12,7 @@ import {
ApplicationEvent,
naturalSort,
FileBackupRecord,
SystemViewId,
} from '@standardnotes/snjs'
import { useState, useEffect, useCallback, useMemo, useRef } from 'react'
import { FileItemActionType } from '../AttachedFilesPopover/PopoverFileItemAction'
@@ -166,26 +167,46 @@ const FilesTableView = ({ application, filesController, featuresController, link
.getDisplayableNotesAndFiles()
.filter((item) => item.content_type === ContentType.File) as FileItem[]
const [sortBy, setSortBy] = useState<keyof SortableItem>(
application.getPreference(PrefKey.SortNotesBy, PrefDefaults[PrefKey.SortNotesBy]),
)
const [sortReversed, setSortReversed] = useState(
application.getPreference(PrefKey.SortNotesReverse, PrefDefaults[PrefKey.SortNotesReverse]),
)
const getSortByPreference = useCallback(() => {
const globalPrefValue = application.getPreference(PrefKey.SortNotesBy, PrefDefaults[PrefKey.SortNotesBy])
const filesViewPrefValue = application.getPreference(PrefKey.SystemViewPreferences)?.[SystemViewId.Files]?.sortBy
return filesViewPrefValue ?? globalPrefValue
}, [application])
const getSortReversedPreference = useCallback(() => {
const globalPrefValue = application.getPreference(PrefKey.SortNotesReverse, PrefDefaults[PrefKey.SortNotesReverse])
const filesViewPrefValue = application.getPreference(PrefKey.SystemViewPreferences)?.[SystemViewId.Files]
?.sortReverse
return filesViewPrefValue ?? globalPrefValue
}, [application])
const [sortBy, setSortBy] = useState<keyof SortableItem>(() => getSortByPreference())
const [sortReversed, setSortReversed] = useState(() => getSortReversedPreference())
useEffect(() => {
return application.addEventObserver(async (event) => {
if (event === ApplicationEvent.PreferencesChanged) {
setSortBy(application.getPreference(PrefKey.SortNotesBy, PrefDefaults[PrefKey.SortNotesBy]))
setSortReversed(application.getPreference(PrefKey.SortNotesReverse, PrefDefaults[PrefKey.SortNotesReverse]))
setSortBy(getSortByPreference())
setSortReversed(getSortReversedPreference())
}
})
}, [application])
}, [application, getSortByPreference, getSortReversedPreference])
const onSortChange = useCallback(
async (sortBy: keyof SortableItem, sortReversed: boolean) => {
await application.setPreference(PrefKey.SortNotesBy, sortBy)
await application.setPreference(PrefKey.SortNotesReverse, sortReversed)
const systemViewPreferences = application.getPreference(PrefKey.SystemViewPreferences) || {}
const filesViewPreferences = systemViewPreferences[SystemViewId.Files] || {}
await application.setPreference(PrefKey.SystemViewPreferences, {
...systemViewPreferences,
[SystemViewId.Files]: {
...filesViewPreferences,
sortBy,
sortReverse: sortReversed,
},
})
},
[application],
)
@@ -214,6 +235,7 @@ const FilesTableView = ({ application, filesController, featuresController, link
},
{
name: 'Size',
sortBy: 'decryptedSize',
cell: (file) => {
return formatSizeToReadableString(file.decryptedSize)
},
@@ -136,8 +136,12 @@ export const SuperEditor: FunctionComponent<Props> = ({
return disposer
}, [controller, controller.item.uuid])
const [lineHeight, setLineHeight] = useState<EditorLineHeight>(PrefDefaults[PrefKey.EditorLineHeight])
const [fontSize, setFontSize] = useState<EditorFontSize | undefined>()
const [lineHeight, setLineHeight] = useState<EditorLineHeight>(() =>
application.getPreference(PrefKey.EditorLineHeight, PrefDefaults[PrefKey.EditorLineHeight]),
)
const [fontSize, setFontSize] = useState<EditorFontSize | undefined>(() =>
application.getPreference(PrefKey.EditorFontSize, PrefDefaults[PrefKey.EditorFontSize]),
)
const reloadPreferences = useCallback(() => {
const lineHeight = application.getPreference(PrefKey.EditorLineHeight, PrefDefaults[PrefKey.EditorLineHeight])
@@ -30,9 +30,9 @@ export const usePopoverCloseOnClickOutside = ({
const isAnchorElement = anchorElement ? anchorElement === event.target || anchorElement.contains(target) : false
const closestPopoverId = target.closest('[data-popover]')?.getAttribute('data-popover')
const isDescendantOfChildPopover = closestPopoverId && childPopovers.has(closestPopoverId)
const isDescendantOfChallengeModal = !!target.closest('.challenge-modal')
const isDescendantOfModal = !!target.closest('[aria-modal="true"]')
if (!isDescendantOfMenu && !isAnchorElement && !isDescendantOfChildPopover && !isDescendantOfChallengeModal) {
if (!isDescendantOfMenu && !isAnchorElement && !isDescendantOfChildPopover && !isDescendantOfModal) {
if (!disabled) {
togglePopover?.()
}
@@ -20,6 +20,8 @@ import {
useBoolean,
isTag,
isFile,
isSmartView,
isSystemView,
} from '@standardnotes/snjs'
import { action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'
import { WebApplication } from '../../Application/Application'
@@ -502,10 +504,14 @@ export class ItemListController extends AbstractViewController implements Intern
const newDisplayOptions = {} as DisplayOptions
const newWebDisplayOptions = {} as WebDisplayOptions
const selectedTag = this.navigationController.selected
const isSystemTag = selectedTag && isSmartView(selectedTag) && isSystemView(selectedTag)
const selectedTagPreferences = isSystemTag
? this.application.getPreference(PrefKey.SystemViewPreferences)?.[selectedTag.uuid as SystemViewId]
: selectedTag?.preferences
const currentSortBy = this.displayOptions.sortBy
let sortBy =
selectedTag?.preferences?.sortBy ||
selectedTagPreferences?.sortBy ||
this.application.getPreference(PrefKey.SortNotesBy, PrefDefaults[PrefKey.SortNotesBy])
if (sortBy === CollectionSort.UpdatedAt || (sortBy as string) === 'client_updated_at') {
sortBy = CollectionSort.UpdatedAt
@@ -515,49 +521,49 @@ export class ItemListController extends AbstractViewController implements Intern
const currentSortDirection = this.displayOptions.sortDirection
newDisplayOptions.sortDirection =
useBoolean(
selectedTag?.preferences?.sortReverse,
selectedTagPreferences?.sortReverse,
this.application.getPreference(PrefKey.SortNotesReverse, PrefDefaults[PrefKey.SortNotesReverse]),
) === false
? 'dsc'
: 'asc'
newDisplayOptions.includeArchived = useBoolean(
selectedTag?.preferences?.showArchived,
selectedTagPreferences?.showArchived,
this.application.getPreference(PrefKey.NotesShowArchived, PrefDefaults[PrefKey.NotesShowArchived]),
)
newDisplayOptions.includeTrashed = useBoolean(
selectedTag?.preferences?.showTrashed,
selectedTagPreferences?.showTrashed,
this.application.getPreference(PrefKey.NotesShowTrashed, PrefDefaults[PrefKey.NotesShowTrashed]),
)
newDisplayOptions.includePinned = !useBoolean(
selectedTag?.preferences?.hidePinned,
selectedTagPreferences?.hidePinned,
this.application.getPreference(PrefKey.NotesHidePinned, PrefDefaults[PrefKey.NotesHidePinned]),
)
newDisplayOptions.includeProtected = !useBoolean(
selectedTag?.preferences?.hideProtected,
selectedTagPreferences?.hideProtected,
this.application.getPreference(PrefKey.NotesHideProtected, PrefDefaults[PrefKey.NotesHideProtected]),
)
newWebDisplayOptions.hideNotePreview = useBoolean(
selectedTag?.preferences?.hideNotePreview,
selectedTagPreferences?.hideNotePreview,
this.application.getPreference(PrefKey.NotesHideNotePreview, PrefDefaults[PrefKey.NotesHideNotePreview]),
)
newWebDisplayOptions.hideDate = useBoolean(
selectedTag?.preferences?.hideDate,
selectedTagPreferences?.hideDate,
this.application.getPreference(PrefKey.NotesHideDate, PrefDefaults[PrefKey.NotesHideDate]),
)
newWebDisplayOptions.hideTags = useBoolean(
selectedTag?.preferences?.hideTags,
selectedTagPreferences?.hideTags,
this.application.getPreference(PrefKey.NotesHideTags, PrefDefaults[PrefKey.NotesHideTags]),
)
newWebDisplayOptions.hideEditorIcon = useBoolean(
selectedTag?.preferences?.hideEditorIcon,
selectedTagPreferences?.hideEditorIcon,
this.application.getPreference(PrefKey.NotesHideEditorIcon, PrefDefaults[PrefKey.NotesHideEditorIcon]),
)
@@ -621,8 +627,14 @@ export class ItemListController extends AbstractViewController implements Intern
return this.noteFilterText
}
const selectedTag = this.navigationController.selected
const isSystemTag = selectedTag && isSmartView(selectedTag) && isSystemView(selectedTag)
const selectedTagPreferences = isSystemTag
? this.application.getPreference(PrefKey.SystemViewPreferences)?.[selectedTag.uuid as SystemViewId]
: selectedTag?.preferences
const titleFormat =
this.navigationController.selected?.preferences?.newNoteTitleFormat ||
selectedTagPreferences?.newNoteTitleFormat ||
this.application.getPreference(PrefKey.NewNoteTitleFormat, PrefDefaults[PrefKey.NewNoteTitleFormat])
if (titleFormat === NewNoteTitleFormat.CurrentNoteCount) {
@@ -29,11 +29,13 @@ export class MomentsService extends AbstractViewController {
void this.beginTakingPhotos()
}
}, ApplicationEvent.LocalDataLoaded),
application.addEventObserver(async () => {
this.disableMoments()
this.pauseMoments()
}, ApplicationEvent.BiometricsSoftLockEngaged),
application.addEventObserver(async () => {
this.enableMoments()
this.resumeMoments()
}, ApplicationEvent.BiometricsSoftLockDisengaged),
)
@@ -67,7 +69,23 @@ export class MomentsService extends AbstractViewController {
clearInterval(this.intervalReference)
}
private pauseMoments(): void {
clearInterval(this.intervalReference)
}
private resumeMoments(): void {
if (!this.isEnabled) {
return
}
void this.beginTakingPhotos()
}
private beginTakingPhotos() {
if (this.intervalReference) {
clearInterval(this.intervalReference)
}
void this.takePhoto()
this.intervalReference = setInterval(
@@ -93,16 +111,24 @@ export class MomentsService extends AbstractViewController {
return
}
const toastId = addToast({
type: ToastType.Regular,
message: 'Capturing Moment...',
pauseOnWindowBlur: false,
})
const isAppInForeground = document.visibilityState === 'visible'
let toastId: string | undefined
if (isAppInForeground) {
toastId = addToast({
type: ToastType.Regular,
message: 'Capturing Moment...',
pauseOnWindowBlur: false,
})
}
if (this.application.desktopDevice) {
const granted = await this.application.desktopDevice.askForMediaAccess('camera')
if (!granted) {
dismissToast(toastId)
if (toastId) {
dismissToast(toastId)
}
addToast({
type: ToastType.Error,
message: 'Please enable Camera permissions for Standard Notes to enable Moments.',
@@ -130,12 +156,13 @@ export class MomentsService extends AbstractViewController {
}
}
dismissToast(toastId)
if (toastId) {
dismissToast(toastId)
}
const uploadedFile = await this.filesController.uploadNewFile(file)
if (uploadedFile) {
const isAppInForeground = document.visibilityState === 'visible'
if (isAppInForeground) {
void this.application.linkingController.linkItemToSelectedItem(uploadedFile)
}
+2 -2
View File
@@ -2,12 +2,12 @@ import { isDev } from '@/Utils'
export enum FeatureTrunkName {
Super,
FilesTableView,
ImportTools,
}
const FeatureTrunkStatus: Record<FeatureTrunkName, boolean> = {
[FeatureTrunkName.Super]: isDev && true,
[FeatureTrunkName.FilesTableView]: isDev && true,
[FeatureTrunkName.ImportTools]: isDev && true,
}
export function featureTrunkEnabled(trunk: FeatureTrunkName): boolean {