mirror of
https://github.com/standardnotes/app
synced 2026-09-19 18:13:48 -04:00
Compare commits
27
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a8a37413e | ||
|
|
113d3bebb4 | ||
|
|
874f2e90bb | ||
|
|
df4acbb1bc | ||
|
|
b75be1d0f8 | ||
|
|
015dd1e806 | ||
|
|
8e3ae80aa0 | ||
|
|
5a0d87845c | ||
|
|
b764296c8f | ||
|
|
b0c11b1b4d | ||
|
|
e81ecfc024 | ||
|
|
b408d0a4c5 | ||
|
|
9ec68c9bf6 | ||
|
|
2611c690b0 | ||
|
|
ef6e971592 | ||
|
|
1c48dd135c | ||
|
|
495786514e | ||
|
|
cfd6cf5932 | ||
|
|
8c5944f233 | ||
|
|
2b63484758 | ||
|
|
ccb15faff5 | ||
|
|
dc33aef660 | ||
|
|
771758fcd4 | ||
|
|
3fbe28e068 | ||
|
|
e67240821c | ||
|
|
d7c6ef6134 | ||
|
|
8311271a49 |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api",
|
||||
"version": "1.20.22",
|
||||
"version": "1.20.26",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -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,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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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,
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/encryption",
|
||||
"version": "1.19.27",
|
||||
"version": "1.19.31",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -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,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',
|
||||
}
|
||||
|
||||
@@ -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,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()
|
||||
})
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files",
|
||||
"version": "1.14.4",
|
||||
"version": "1.14.8",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -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,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",
|
||||
|
||||
@@ -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,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>>
|
||||
}
|
||||
|
||||
@@ -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,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",
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/responses",
|
||||
"version": "1.12.10",
|
||||
"version": "1.12.12",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/snjs",
|
||||
"version": "2.156.10",
|
||||
"version": "2.156.16",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -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,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)
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"skipLibCheck": true,
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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}
|
||||
|
||||
+65
-18
@@ -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
|
||||
|
||||
+25
-12
@@ -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])
|
||||
|
||||
+2
-2
@@ -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,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 {
|
||||
|
||||
Reference in New Issue
Block a user