mirror of
https://github.com/standardnotes/app
synced 2026-09-21 06:13:27 -04:00
Compare commits
36
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b11abdeec | ||
|
|
b86d58bebc | ||
|
|
2d606b715e | ||
|
|
b7093e6041 | ||
|
|
dfb1566871 | ||
|
|
41ae1dae83 | ||
|
|
f7d204b205 | ||
|
|
2c599df6be | ||
|
|
80711a17fa | ||
|
|
07d18a6e9a | ||
|
|
6839ad9e5a | ||
|
|
1defde14fe | ||
|
|
febaf5185d | ||
|
|
d55d0eb806 | ||
|
|
6530aa4c79 | ||
|
|
e36c36f401 | ||
|
|
40d45d2484 | ||
|
|
bd1310b1fc | ||
|
|
062e50e901 | ||
|
|
ec61901aa5 | ||
|
|
0d15858b5f | ||
|
|
09d139846e | ||
|
|
da234f960e | ||
|
|
f7c2434141 | ||
|
|
e2901d9535 | ||
|
|
3a42925ca2 | ||
|
|
3e1557ab36 | ||
|
|
9e1583f798 | ||
|
|
35d92be63c | ||
|
|
3b61fa5911 | ||
|
|
0f046cbe83 | ||
|
|
102d9c8e2d | ||
|
|
ca01b2dc95 | ||
|
|
dbf918523c | ||
|
|
fe8521eff7 | ||
|
|
48e7820100 |
BIN
Binary file not shown.
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.26.59](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.26.58](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.26.57](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.26.56](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api",
|
||||
"version": "1.26.56",
|
||||
"version": "1.26.59",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/common": "^1.50.0",
|
||||
"@standardnotes/domain-core": "^1.28.0",
|
||||
"@standardnotes/domain-core": "^1.32.0",
|
||||
"@standardnotes/models": "workspace:*",
|
||||
"@standardnotes/responses": "workspace:*",
|
||||
"@standardnotes/utils": "workspace:*",
|
||||
|
||||
@@ -2,4 +2,8 @@ import { SharedVaultServerHash } from '@standardnotes/responses'
|
||||
|
||||
export type GetSharedVaultsResponse = {
|
||||
sharedVaults: SharedVaultServerHash[]
|
||||
designatedSurvivors: Array<{
|
||||
userUuid: string
|
||||
sharedVaultUuid: string
|
||||
}>
|
||||
}
|
||||
|
||||
@@ -3,6 +3,62 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.1.240](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.239](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.238](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.237](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.236](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.235](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.234](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.233](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.232](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.231](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.230](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.229](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.228](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.227](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.226](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-16)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/clipper",
|
||||
"description": "Web clipper browser extension for Standard Notes",
|
||||
"version": "1.1.226",
|
||||
"version": "1.1.240",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build-mv2": "yarn clean && webpack --config ./webpack.config.prod.js",
|
||||
|
||||
@@ -3,6 +3,62 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.108.172](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.171](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.170](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.169](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.168](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.167](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.166](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.165](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.164](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.163](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.162](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.161](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.160](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.159](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.158](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-16)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/desktop",
|
||||
"main": "./app/dist/index.js",
|
||||
"version": "3.108.158",
|
||||
"version": "3.108.172",
|
||||
"license": "CC BY-NC-SA 4.0",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
@@ -35,7 +35,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@electron/remote": "^2.0.9",
|
||||
"@standardnotes/domain-core": "^1.28.0",
|
||||
"@standardnotes/domain-core": "^1.32.0",
|
||||
"@standardnotes/electron-clear-data": "1.1.1",
|
||||
"@standardnotes/web": "workspace:*",
|
||||
"axios": "^1.1.3",
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.21.81](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.21.80](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.21.79](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.21.78](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/encryption",
|
||||
"version": "1.21.78",
|
||||
"version": "1.21.81",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/common": "^1.50.0",
|
||||
"@standardnotes/domain-core": "^1.28.0",
|
||||
"@standardnotes/domain-core": "^1.32.0",
|
||||
"@standardnotes/models": "workspace:*",
|
||||
"@standardnotes/responses": "workspace:*",
|
||||
"@standardnotes/sncrypto-common": "workspace:*",
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.59.19](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/features
|
||||
|
||||
## [1.59.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/features",
|
||||
"version": "1.59.18",
|
||||
"version": "1.59.19",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -26,7 +26,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/common": "^1.50.0",
|
||||
"@standardnotes/domain-core": "^1.28.0",
|
||||
"@standardnotes/domain-core": "^1.32.0",
|
||||
"reflect-metadata": "^0.1.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.28.92](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.28.91](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.28.90](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.28.89](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/filepicker",
|
||||
"version": "1.28.89",
|
||||
"version": "1.28.92",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.17.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.17.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.17.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.17.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files",
|
||||
"version": "1.17.8",
|
||||
"version": "1.17.11",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,62 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.56.151](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.150](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.149](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.148](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.147](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.146](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.145](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.144](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.143](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.142](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.141](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.140](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.139](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.138](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.137](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-16)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/mobile",
|
||||
"version": "3.56.137",
|
||||
"version": "3.56.151",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
"license": "CC BY-NC-SA 4.0",
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.47.17](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
## [1.47.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
## [1.47.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
## [1.47.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/models",
|
||||
"version": "1.47.14",
|
||||
"version": "1.47.17",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
@@ -20,7 +20,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/common": "^1.50.0",
|
||||
"@standardnotes/domain-core": "^1.28.0",
|
||||
"@standardnotes/domain-core": "^1.32.0",
|
||||
"@standardnotes/features": "workspace:*",
|
||||
"@standardnotes/responses": "workspace:*",
|
||||
"@standardnotes/sncrypto-common": "workspace:^",
|
||||
|
||||
+1
@@ -20,6 +20,7 @@ export type AsymmetricMessageSharedVaultInvite = {
|
||||
description?: string
|
||||
iconString: IconType | EmojiString
|
||||
fileBytesUsed: number
|
||||
designatedSurvivor: string | null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,10 @@ export class ItemDisplayController<I extends DisplayItem, O extends AnyDisplayOp
|
||||
return this.sortedItems
|
||||
}
|
||||
|
||||
public hasExclusiveVaultOptions(): boolean {
|
||||
return this.vaultOptions ? !isExclusionaryOptionsValue(this.vaultOptions.getOptions()) : false
|
||||
}
|
||||
|
||||
public getDisplayOptions(): DisplayControllerDisplayOptions & O {
|
||||
return this.options
|
||||
}
|
||||
|
||||
@@ -2,4 +2,5 @@ export type VaultListingSharingInfo = {
|
||||
sharedVaultUuid: string
|
||||
ownerUserUuid: string
|
||||
fileBytesUsed: number
|
||||
designatedSurvivor: string | null
|
||||
}
|
||||
|
||||
@@ -3,6 +3,62 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.4.524](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.523](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.522](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.521](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.520](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.519](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.518](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.517](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.516](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.515](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.514](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.513](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.512](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.511](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.510](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-16)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/releases",
|
||||
"version": "1.4.510",
|
||||
"version": "1.4.524",
|
||||
"license": "CC BY-NC-SA 4.0",
|
||||
"main": "dist/releases.json",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.13.43](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/responses
|
||||
|
||||
## [1.13.42](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/responses
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/responses",
|
||||
"version": "1.13.42",
|
||||
"version": "1.13.43",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.64.25](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
## [1.64.24](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
## [1.64.23](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
## [1.64.22](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
## [1.64.21](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
## [1.64.20](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
## [1.64.19](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/services",
|
||||
"version": "1.64.19",
|
||||
"version": "1.64.25",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -18,7 +18,7 @@
|
||||
"dependencies": {
|
||||
"@standardnotes/api": "workspace:^",
|
||||
"@standardnotes/common": "^1.50.0",
|
||||
"@standardnotes/domain-core": "^1.28.0",
|
||||
"@standardnotes/domain-core": "^1.32.0",
|
||||
"@standardnotes/encryption": "workspace:^",
|
||||
"@standardnotes/features": "workspace:^",
|
||||
"@standardnotes/files": "workspace:^",
|
||||
|
||||
+1
@@ -39,6 +39,7 @@ describe('ProcessAcceptedVaultInvite', () => {
|
||||
name: 'test-name',
|
||||
iconString: 'safe-square',
|
||||
fileBytesUsed: 0,
|
||||
designatedSurvivor: null,
|
||||
},
|
||||
trustedContacts: [],
|
||||
},
|
||||
|
||||
@@ -37,6 +37,7 @@ export class ProcessAcceptedVaultInvite {
|
||||
sharedVaultUuid: sharedVaultUuid,
|
||||
ownerUserUuid: ownerUuid,
|
||||
fileBytesUsed: metadata.fileBytesUsed,
|
||||
designatedSurvivor: metadata.designatedSurvivor,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import { ItemManagerInterface } from '../Item/ItemManagerInterface'
|
||||
import { SessionsClientInterface } from '../Session/SessionsClientInterface'
|
||||
import { ContactPublicKeySetInterface, TrustedContactInterface } from '@standardnotes/models'
|
||||
import { SyncLocalVaultsWithRemoteSharedVaults } from './UseCase/SyncLocalVaultsWithRemoteSharedVaults'
|
||||
import { VaultUserServiceInterface } from '../VaultUser/VaultUserServiceInterface'
|
||||
|
||||
describe('SharedVaultService', () => {
|
||||
let service: SharedVaultService
|
||||
@@ -27,6 +28,8 @@ describe('SharedVaultService', () => {
|
||||
const items = {} as jest.Mocked<ItemManagerInterface>
|
||||
items.addObserver = jest.fn()
|
||||
|
||||
const vaultUsers = {} as jest.Mocked<VaultUserServiceInterface>
|
||||
|
||||
const session = {} as jest.Mocked<SessionsClientInterface>
|
||||
const getVault = {} as jest.Mocked<GetVault>
|
||||
const getOwnedVaults = {} as jest.Mocked<GetOwnedSharedVaults>
|
||||
@@ -48,6 +51,7 @@ describe('SharedVaultService', () => {
|
||||
service = new SharedVaultService(
|
||||
items,
|
||||
session,
|
||||
vaultUsers,
|
||||
syncLocalVaultsWithRemoteSharedVaults,
|
||||
getVault,
|
||||
getOwnedVaults,
|
||||
|
||||
@@ -33,6 +33,7 @@ import { HandleKeyPairChange } from '../Contacts/UseCase/HandleKeyPairChange'
|
||||
import { FindContact } from '../Contacts/UseCase/FindContact'
|
||||
import { GetOwnedSharedVaults } from './UseCase/GetOwnedSharedVaults'
|
||||
import { SyncLocalVaultsWithRemoteSharedVaults } from './UseCase/SyncLocalVaultsWithRemoteSharedVaults'
|
||||
import { VaultUserServiceInterface } from '../VaultUser/VaultUserServiceInterface'
|
||||
|
||||
export class SharedVaultService
|
||||
extends AbstractService<SharedVaultServiceEvent, SharedVaultServiceEventPayload>
|
||||
@@ -41,6 +42,7 @@ export class SharedVaultService
|
||||
constructor(
|
||||
private items: ItemManagerInterface,
|
||||
private session: SessionsClientInterface,
|
||||
private vaultUsers: VaultUserServiceInterface,
|
||||
private _syncLocalVaultsWithRemoteSharedVaults: SyncLocalVaultsWithRemoteSharedVaults,
|
||||
private _getVault: GetVault,
|
||||
private _getOwnedSharedVaults: GetOwnedSharedVaults,
|
||||
@@ -101,7 +103,7 @@ export class SharedVaultService
|
||||
|
||||
private async handleNotification(event: NotificationServiceEventPayload): Promise<void> {
|
||||
switch (event.eventPayload.props.type.value) {
|
||||
case NotificationType.TYPES.RemovedFromSharedVault: {
|
||||
case NotificationType.TYPES.SelfRemovedFromSharedVault: {
|
||||
const vault = this._getVault.execute<SharedVaultListingInterface>({
|
||||
sharedVaultUuid: event.eventPayload.props.sharedVaultUuid.value,
|
||||
})
|
||||
@@ -110,6 +112,10 @@ export class SharedVaultService
|
||||
}
|
||||
break
|
||||
}
|
||||
case NotificationType.TYPES.UserRemovedFromSharedVault: {
|
||||
this.vaultUsers.invalidateVaultUsersCache(event.eventPayload.props.sharedVaultUuid.value).catch(console.error)
|
||||
break
|
||||
}
|
||||
case NotificationType.TYPES.SharedVaultItemRemoved: {
|
||||
const item = this.items.findItem((event.eventPayload.props.itemUuid as Uuid).value)
|
||||
if (item) {
|
||||
|
||||
@@ -32,6 +32,7 @@ export class ConvertToSharedVault {
|
||||
sharedVaultUuid: serverVaultHash.uuid,
|
||||
ownerUserUuid: serverVaultHash.user_uuid,
|
||||
fileBytesUsed: serverVaultHash.file_upload_bytes_used,
|
||||
designatedSurvivor: null,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -51,6 +51,7 @@ export class CreateSharedVault {
|
||||
sharedVaultUuid: serverVaultHash.uuid,
|
||||
ownerUserUuid: serverVaultHash.user_uuid,
|
||||
fileBytesUsed: serverVaultHash.file_upload_bytes_used,
|
||||
designatedSurvivor: null,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
+4
@@ -18,18 +18,22 @@ export class SyncLocalVaultsWithRemoteSharedVaults implements UseCaseInterface<v
|
||||
}
|
||||
const remoteVaults = remoteVaultsResponse.data.sharedVaults
|
||||
|
||||
const designatedSurvivors = remoteVaultsResponse.data.designatedSurvivors || []
|
||||
|
||||
for (const localVault of localVaults) {
|
||||
if (!localVault.isSharedVaultListing()) {
|
||||
continue
|
||||
}
|
||||
const remoteVault = remoteVaults.find((vault) => vault.uuid === localVault.sharing.sharedVaultUuid)
|
||||
if (remoteVault) {
|
||||
const designatedSurvivor = designatedSurvivors.find((survivor) => survivor.sharedVaultUuid === remoteVault.uuid)
|
||||
await this.mutator.changeItem<VaultListingMutator, SharedVaultListingInterface>(localVault, (mutator) => {
|
||||
/* istanbul ignore next */
|
||||
mutator.sharing = {
|
||||
sharedVaultUuid: remoteVault.uuid,
|
||||
ownerUserUuid: remoteVault.user_uuid,
|
||||
fileBytesUsed: remoteVault.file_upload_bytes_used,
|
||||
designatedSurvivor: designatedSurvivor ? designatedSurvivor.userUuid : null,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ export class VaultService
|
||||
async moveItemToVault(
|
||||
vault: VaultListingInterface,
|
||||
item: DecryptedItemInterface,
|
||||
): Promise<DecryptedItemInterface | undefined> {
|
||||
): Promise<Result<DecryptedItemInterface>> {
|
||||
if (this.vaultLocks.isVaultLocked(vault)) {
|
||||
throw new Error('Attempting to add item to locked vault')
|
||||
}
|
||||
@@ -167,13 +167,16 @@ export class VaultService
|
||||
text: 'This item is linked to other items that are not in the same vault. Please move those items to this vault first.',
|
||||
})
|
||||
.catch(console.error)
|
||||
return undefined
|
||||
|
||||
return Result.fail(
|
||||
'This item is linked to other items that are not in the same vault. Please move those items to this vault first.',
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
await this._moveItemsToVault.execute({ vault, items: [item] })
|
||||
|
||||
return this.items.findSureItem(item.uuid)
|
||||
return Result.ok(this.items.findSureItem(item.uuid))
|
||||
}
|
||||
|
||||
async removeItemFromVault(item: DecryptedItemInterface): Promise<DecryptedItemInterface> {
|
||||
@@ -248,16 +251,16 @@ export class VaultService
|
||||
}
|
||||
|
||||
getItemVault(item: DecryptedItemInterface): VaultListingInterface | undefined {
|
||||
if (this.items.isTemplateItem(item)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const latestItem = this.items.findItem(item.uuid)
|
||||
|
||||
if (!latestItem) {
|
||||
throw new Error('Cannot find latest version of item to get vault for')
|
||||
}
|
||||
|
||||
if (this.items.isTemplateItem(item)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (!latestItem.key_system_identifier) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -32,10 +32,7 @@ export interface VaultServiceInterface
|
||||
authorizeVaultDeletion(vault: VaultListingInterface): Promise<Result<boolean>>
|
||||
deleteVault(vault: VaultListingInterface): Promise<boolean>
|
||||
|
||||
moveItemToVault(
|
||||
vault: VaultListingInterface,
|
||||
item: DecryptedItemInterface,
|
||||
): Promise<DecryptedItemInterface | undefined>
|
||||
moveItemToVault(vault: VaultListingInterface, item: DecryptedItemInterface): Promise<Result<DecryptedItemInterface>>
|
||||
removeItemFromVault(item: DecryptedItemInterface): Promise<DecryptedItemInterface>
|
||||
isItemInVault(item: DecryptedItemInterface): boolean
|
||||
getItemVault(item: DecryptedItemInterface): VaultListingInterface | undefined
|
||||
|
||||
@@ -124,6 +124,7 @@ export class InviteToVault implements UseCaseInterface<SharedVaultInviteServerHa
|
||||
description: params.sharedVault.description,
|
||||
iconString: params.sharedVault.iconString,
|
||||
fileBytesUsed: params.sharedVault.sharing.fileBytesUsed,
|
||||
designatedSurvivor: params.sharedVault.sharing.designatedSurvivor,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -179,7 +179,8 @@ export class VaultInviteService
|
||||
|
||||
this.removePendingInvite(pendingInvite.invite.uuid)
|
||||
|
||||
void this.sync.sync()
|
||||
this.sync.sync().catch(console.error)
|
||||
this.vaultUsers.invalidateVaultUsersCache(pendingInvite.invite.shared_vault_uuid).catch(console.error)
|
||||
|
||||
await this._decryptErroredPayloads.execute()
|
||||
|
||||
@@ -191,7 +192,7 @@ export class VaultInviteService
|
||||
public async getInvitableContactsForSharedVault(
|
||||
sharedVault: SharedVaultListingInterface,
|
||||
): Promise<TrustedContactInterface[]> {
|
||||
const users = await this.vaultUsers.getSharedVaultUsers(sharedVault)
|
||||
const users = await this.vaultUsers.getSharedVaultUsersFromServer(sharedVault)
|
||||
if (!users) {
|
||||
return []
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { UserServiceInterface } from '../../User/UserServiceInterface'
|
||||
import { Result, SharedVaultUserPermission, SyncUseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { SharedVaultListingInterface } from '@standardnotes/models'
|
||||
import { VaultUserCache } from '../VaultUserCache'
|
||||
|
||||
export class IsReadonlyVaultMember implements SyncUseCaseInterface<boolean> {
|
||||
constructor(
|
||||
private users: UserServiceInterface,
|
||||
private cache: VaultUserCache,
|
||||
) {}
|
||||
|
||||
execute(vault: SharedVaultListingInterface): Result<boolean> {
|
||||
if (!vault.sharing) {
|
||||
return Result.ok(true)
|
||||
}
|
||||
|
||||
if (!vault.sharing.ownerUserUuid) {
|
||||
throw new Error(`Shared vault ${vault.sharing.sharedVaultUuid} does not have an owner user uuid`)
|
||||
}
|
||||
|
||||
const user = this.users.sureUser
|
||||
const vaultUsers = this.cache.get(vault.sharing.sharedVaultUuid)
|
||||
const userPermission = vaultUsers?.find((vaultUser) => vaultUser.user_uuid === user.uuid)?.permission
|
||||
|
||||
return Result.ok(userPermission === SharedVaultUserPermission.PERMISSIONS.Read)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { UserServiceInterface } from '../../User/UserServiceInterface'
|
||||
import { Result, SharedVaultUserPermission, SyncUseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { SharedVaultListingInterface } from '@standardnotes/models'
|
||||
import { VaultUserCache } from '../VaultUserCache'
|
||||
|
||||
export class IsVaultAdmin implements SyncUseCaseInterface<boolean> {
|
||||
constructor(
|
||||
private users: UserServiceInterface,
|
||||
private cache: VaultUserCache,
|
||||
) {}
|
||||
|
||||
execute(vault: SharedVaultListingInterface): Result<boolean> {
|
||||
if (!vault.sharing) {
|
||||
return Result.ok(true)
|
||||
}
|
||||
|
||||
if (!vault.sharing.ownerUserUuid) {
|
||||
throw new Error(`Shared vault ${vault.sharing.sharedVaultUuid} does not have an owner user uuid`)
|
||||
}
|
||||
|
||||
const user = this.users.sureUser
|
||||
const vaultUsers = this.cache.get(vault.sharing.sharedVaultUuid)
|
||||
const userPermission = vaultUsers?.find((vaultUser) => vaultUser.user_uuid === user.uuid)?.permission
|
||||
|
||||
return Result.ok(
|
||||
userPermission === SharedVaultUserPermission.PERMISSIONS.Admin || vault.sharing.ownerUserUuid === user.uuid,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,15 @@ import { InternalEventBusInterface } from './../Internal/InternalEventBusInterfa
|
||||
import { RemoveVaultMember } from './UseCase/RemoveSharedVaultMember'
|
||||
import { VaultServiceInterface } from '../Vault/VaultServiceInterface'
|
||||
import { GetVaultUsers } from './UseCase/GetVaultUsers'
|
||||
import { SharedVaultListingInterface } from '@standardnotes/models'
|
||||
import { SharedVaultListingInterface, VaultListingInterface } from '@standardnotes/models'
|
||||
import { VaultUserServiceInterface } from './VaultUserServiceInterface'
|
||||
import { ClientDisplayableError, SharedVaultUserServerHash, isClientDisplayableError } from '@standardnotes/responses'
|
||||
import { AbstractService } from './../Service/AbstractService'
|
||||
import { VaultUserServiceEvent } from './VaultUserServiceEvent'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
import { IsVaultOwner } from './UseCase/IsVaultOwner'
|
||||
import { IsReadonlyVaultMember } from './UseCase/IsReadonlyVaultMember'
|
||||
import { IsVaultAdmin } from './UseCase/IsVaultAdmin'
|
||||
|
||||
export class VaultUserService extends AbstractService<VaultUserServiceEvent> implements VaultUserServiceInterface {
|
||||
constructor(
|
||||
@@ -20,6 +22,8 @@ export class VaultUserService extends AbstractService<VaultUserServiceEvent> imp
|
||||
private _getVaultUsers: GetVaultUsers,
|
||||
private _removeVaultMember: RemoveVaultMember,
|
||||
private _isVaultOwner: IsVaultOwner,
|
||||
private _isVaultAdmin: IsVaultAdmin,
|
||||
private _isReadonlyVaultMember: IsReadonlyVaultMember,
|
||||
private _getVault: GetVault,
|
||||
private _leaveVault: LeaveVault,
|
||||
eventBus: InternalEventBusInterface,
|
||||
@@ -37,7 +41,31 @@ export class VaultUserService extends AbstractService<VaultUserServiceEvent> imp
|
||||
;(this._leaveVault as unknown) = undefined
|
||||
}
|
||||
|
||||
public async getSharedVaultUsers(
|
||||
async invalidateVaultUsersCache(sharedVaultUuid?: string) {
|
||||
if (sharedVaultUuid) {
|
||||
await this._getVaultUsers.execute({
|
||||
sharedVaultUuid: sharedVaultUuid,
|
||||
readFromCache: false,
|
||||
})
|
||||
void this.notifyEvent(VaultUserServiceEvent.InvalidatedUserCacheForVault, sharedVaultUuid)
|
||||
return
|
||||
}
|
||||
await Promise.all(
|
||||
this.vaults.getVaults().map(async (vault) => {
|
||||
if (!vault.isSharedVaultListing()) {
|
||||
return
|
||||
}
|
||||
await this._getVaultUsers.execute({
|
||||
sharedVaultUuid: vault.sharing.sharedVaultUuid,
|
||||
readFromCache: false,
|
||||
})
|
||||
void this.notifyEvent(VaultUserServiceEvent.InvalidatedUserCacheForVault, vault.sharing.sharedVaultUuid)
|
||||
}),
|
||||
)
|
||||
void this.notifyEvent(VaultUserServiceEvent.InvalidatedAllUserCache)
|
||||
}
|
||||
|
||||
public async getSharedVaultUsersFromServer(
|
||||
sharedVault: SharedVaultListingInterface,
|
||||
): Promise<SharedVaultUserServerHash[] | undefined> {
|
||||
const result = await this._getVaultUsers.execute({
|
||||
@@ -55,6 +83,17 @@ export class VaultUserService extends AbstractService<VaultUserServiceEvent> imp
|
||||
return this._isVaultOwner.execute(sharedVault).getValue()
|
||||
}
|
||||
|
||||
public isCurrentUserSharedVaultAdmin(sharedVault: SharedVaultListingInterface): boolean {
|
||||
return this._isVaultAdmin.execute(sharedVault).getValue()
|
||||
}
|
||||
|
||||
public isCurrentUserReadonlyVaultMember(vault: VaultListingInterface): boolean {
|
||||
if (!vault.isSharedVaultListing()) {
|
||||
return false
|
||||
}
|
||||
return this._isReadonlyVaultMember.execute(vault).getValue()
|
||||
}
|
||||
|
||||
async removeUserFromSharedVault(sharedVault: SharedVaultListingInterface, userUuid: string): Promise<Result<void>> {
|
||||
if (!this.isCurrentUserSharedVaultOwner(sharedVault)) {
|
||||
throw new Error('Only vault admins can remove users')
|
||||
@@ -101,4 +140,17 @@ export class VaultUserService extends AbstractService<VaultUserServiceEvent> imp
|
||||
|
||||
void this.notifyEvent(VaultUserServiceEvent.UsersChanged)
|
||||
}
|
||||
|
||||
getFormattedMemberPermission(permission: string): string {
|
||||
switch (permission) {
|
||||
case 'admin':
|
||||
return 'Admin'
|
||||
case 'write':
|
||||
return 'Read / Write'
|
||||
case 'read':
|
||||
return 'Read-only'
|
||||
default:
|
||||
return 'Unknown'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export enum VaultUserServiceEvent {
|
||||
UsersChanged = 'VaultUserServiceEvent.UsersChanged',
|
||||
InvalidatedAllUserCache = 'VaultUserServiceEvent.InvalidatedUserCache',
|
||||
InvalidatedUserCacheForVault = 'VaultUserServiceEvent.InvalidatedUserCacheForVault',
|
||||
}
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
import { ApplicationServiceInterface } from './../Service/ApplicationServiceInterface'
|
||||
import { SharedVaultListingInterface } from '@standardnotes/models'
|
||||
import { SharedVaultListingInterface, VaultListingInterface } from '@standardnotes/models'
|
||||
import { ClientDisplayableError, SharedVaultUserServerHash } from '@standardnotes/responses'
|
||||
import { VaultUserServiceEvent } from './VaultUserServiceEvent'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
|
||||
export interface VaultUserServiceInterface extends ApplicationServiceInterface<VaultUserServiceEvent, unknown> {
|
||||
getSharedVaultUsers(sharedVault: SharedVaultListingInterface): Promise<SharedVaultUserServerHash[] | undefined>
|
||||
getSharedVaultUsersFromServer(
|
||||
sharedVault: SharedVaultListingInterface,
|
||||
): Promise<SharedVaultUserServerHash[] | undefined>
|
||||
isCurrentUserSharedVaultOwner(sharedVault: SharedVaultListingInterface): boolean
|
||||
isCurrentUserSharedVaultAdmin(sharedVault: SharedVaultListingInterface): boolean
|
||||
isCurrentUserReadonlyVaultMember(vault: VaultListingInterface): boolean
|
||||
removeUserFromSharedVault(sharedVault: SharedVaultListingInterface, userUuid: string): Promise<Result<void>>
|
||||
leaveSharedVault(sharedVault: SharedVaultListingInterface): Promise<ClientDisplayableError | void>
|
||||
isVaultUserOwner(user: SharedVaultUserServerHash): boolean
|
||||
getFormattedMemberPermission(permission: string): string
|
||||
invalidateVaultUsersCache(sharedVaultUuid?: string): Promise<void>
|
||||
}
|
||||
|
||||
@@ -3,6 +3,46 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.202.85](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.202.84](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.202.83](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.202.82](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.202.81](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.202.80](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.202.79](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.202.78](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.202.77](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.202.76](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.202.75](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
@@ -469,6 +469,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
source: SyncSource.External,
|
||||
sourceDescription: 'Application Launch',
|
||||
})
|
||||
this.vaultUsers.invalidateVaultUsersCache().catch(console.error)
|
||||
})
|
||||
.catch((error) => {
|
||||
void this.notifyEvent(ApplicationEvent.LocalDatabaseReadError, error)
|
||||
|
||||
@@ -172,6 +172,8 @@ import { EncryptionOperators } from '@standardnotes/encryption'
|
||||
import { AsymmetricMessagePayload, AsymmetricMessageSharedVaultInvite } from '@standardnotes/models'
|
||||
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
||||
import { AuthorizeVaultDeletion } from '@standardnotes/services/src/Domain/Vault/UseCase/AuthorizeVaultDeletion'
|
||||
import { IsVaultAdmin } from '@standardnotes/services/src/Domain/VaultUser/UseCase/IsVaultAdmin'
|
||||
import { IsReadonlyVaultMember } from '@standardnotes/services/src/Domain/VaultUser/UseCase/IsReadonlyVaultMember'
|
||||
|
||||
export class Dependencies {
|
||||
private factory = new Map<symbol, () => unknown>()
|
||||
@@ -335,6 +337,17 @@ export class Dependencies {
|
||||
return new IsVaultOwner(this.get<UserService>(TYPES.UserService))
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.IsVaultAdmin, () => {
|
||||
return new IsVaultAdmin(this.get<UserService>(TYPES.UserService), this.get<VaultUserCache>(TYPES.VaultUserCache))
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.IsReadonlyVaultMember, () => {
|
||||
return new IsReadonlyVaultMember(
|
||||
this.get<UserService>(TYPES.UserService),
|
||||
this.get<VaultUserCache>(TYPES.VaultUserCache),
|
||||
)
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.DecryptBackupFile, () => {
|
||||
return new DecryptBackupFile(
|
||||
this.get<EncryptionService>(TYPES.EncryptionService),
|
||||
@@ -844,6 +857,8 @@ export class Dependencies {
|
||||
this.get<GetVaultUsers>(TYPES.GetVaultUsers),
|
||||
this.get<RemoveVaultMember>(TYPES.RemoveVaultMember),
|
||||
this.get<IsVaultOwner>(TYPES.IsVaultOwner),
|
||||
this.get<IsVaultAdmin>(TYPES.IsVaultAdmin),
|
||||
this.get<IsReadonlyVaultMember>(TYPES.IsReadonlyVaultMember),
|
||||
this.get<GetVault>(TYPES.GetVault),
|
||||
this.get<LeaveVault>(TYPES.LeaveVault),
|
||||
this.get<InternalEventBus>(TYPES.InternalEventBus),
|
||||
@@ -901,6 +916,7 @@ export class Dependencies {
|
||||
return new SharedVaultService(
|
||||
this.get<ItemManager>(TYPES.ItemManager),
|
||||
this.get<SessionManager>(TYPES.SessionManager),
|
||||
this.get<VaultUserService>(TYPES.VaultUserService),
|
||||
this.get<SyncLocalVaultsWithRemoteSharedVaults>(TYPES.SyncLocalVaultsWithRemoteSharedVaults),
|
||||
this.get<GetVault>(TYPES.GetVault),
|
||||
this.get<GetOwnedSharedVaults>(TYPES.GetOwnedSharedVaults),
|
||||
|
||||
@@ -152,6 +152,8 @@ export const TYPES = {
|
||||
EncryptTypeAPayloadWithKeyLookup: Symbol.for('EncryptTypeAPayloadWithKeyLookup'),
|
||||
DecryptBackupFile: Symbol.for('DecryptBackupFile'),
|
||||
IsVaultOwner: Symbol.for('IsVaultOwner'),
|
||||
IsVaultAdmin: Symbol.for('IsVaultAdmin'),
|
||||
IsReadonlyVaultMember: Symbol.for('IsReadonlyVaultMember'),
|
||||
RemoveItemsFromMemory: Symbol.for('RemoveItemsFromMemory'),
|
||||
ReencryptTypeAItems: Symbol.for('ReencryptTypeAItems'),
|
||||
DecryptErroredPayloads: Symbol.for('DecryptErroredPayloads'),
|
||||
|
||||
@@ -875,7 +875,13 @@ export class ItemManager extends Services.AbstractService implements Services.It
|
||||
}
|
||||
|
||||
public numberOfNotesWithConflicts(): number {
|
||||
return this.findItems(this.collection.uuidsOfItemsWithConflicts()).filter(Models.isNote).length
|
||||
const uuids = this.collection.uuidsOfItemsWithConflicts()
|
||||
const items = this.navigationDisplayController.hasExclusiveVaultOptions()
|
||||
? this.navigationDisplayController
|
||||
.items()
|
||||
.filter((item) => Models.isNote(item) && this.collection.uuidsOfItemsWithConflicts().includes(item.uuid))
|
||||
: this.findItems(uuids)
|
||||
return items.length
|
||||
}
|
||||
|
||||
getItemLinkedFiles(item: Models.DecryptedItemInterface): Models.FileItem[] {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const VaultTests = {
|
||||
enabled: false,
|
||||
enabled: true,
|
||||
exclusive: false,
|
||||
files: [
|
||||
'vaults/vaults.test.js',
|
||||
|
||||
@@ -7,23 +7,17 @@ const expect = chai.expect
|
||||
describe.skip('history manager', () => {
|
||||
const largeCharacterChange = 25
|
||||
|
||||
let application, history, email, password
|
||||
|
||||
const syncOptions = {
|
||||
checkIntegrity: true,
|
||||
awaitAll: true,
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
describe('session', function () {
|
||||
let application, history
|
||||
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
|
||||
application = await Factory.createInitAppWithFakeCrypto()
|
||||
history = application.dependencies.get(TYPES.HistoryManager)
|
||||
|
||||
@@ -33,6 +27,7 @@ describe.skip('history manager', () => {
|
||||
|
||||
afterEach(async function () {
|
||||
await Factory.safeDeinit(application)
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
async function setTextAndSync(application, item, text) {
|
||||
@@ -234,96 +229,102 @@ describe.skip('history manager', () => {
|
||||
describe('remote', function () {
|
||||
this.timeout(Factory.TwentySecondTimeout)
|
||||
|
||||
let context
|
||||
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
|
||||
application = await Factory.createInitAppWithFakeCrypto()
|
||||
history = application.dependencies.get(TYPES.HistoryManager)
|
||||
email = UuidGenerator.GenerateUuid()
|
||||
password = UuidGenerator.GenerateUuid()
|
||||
await Factory.registerUserToApplication({
|
||||
application: application,
|
||||
email: email,
|
||||
password: password,
|
||||
})
|
||||
context = await Factory.createVaultsContextWithRealCrypto()
|
||||
|
||||
await context.launch()
|
||||
await context.register()
|
||||
})
|
||||
|
||||
afterEach(async function () {
|
||||
await Factory.safeDeinit(application)
|
||||
await context.deinit()
|
||||
localStorage.clear()
|
||||
sinon.restore()
|
||||
context = undefined
|
||||
})
|
||||
|
||||
it('response from server should be failed if not signed in', async function () {
|
||||
await application.user.signOut()
|
||||
application = await Factory.createInitAppWithFakeCrypto()
|
||||
history = application.dependencies.get(TYPES.HistoryManager)
|
||||
const note = await context.createSyncedNote('test note')
|
||||
|
||||
const item = await Factory.createSyncedNote(application)
|
||||
await application.sync.sync(syncOptions)
|
||||
const itemHistoryOrError = await application.listRevisions.execute({ itemUuid: item.uuid })
|
||||
await context.signout()
|
||||
|
||||
const itemHistoryOrError = await context.application.listRevisions.execute({ itemUuid: note.uuid })
|
||||
expect(itemHistoryOrError.isFailed()).to.equal(true)
|
||||
})
|
||||
|
||||
it('should save initial revisions on server', async () => {
|
||||
const item = await Factory.createSyncedNote(application)
|
||||
expect(item).to.be.ok
|
||||
const note = await context.createSyncedNote('test note')
|
||||
expect(note).to.be.ok
|
||||
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay)
|
||||
|
||||
const itemHistoryOrError = await application.listRevisions.execute({ itemUuid: item.uuid })
|
||||
const itemHistoryOrError = await context.application.listRevisions.execute({ itemUuid: note.uuid })
|
||||
expect(itemHistoryOrError.isFailed()).to.equal(false)
|
||||
|
||||
const itemHistory = itemHistoryOrError.getValue()
|
||||
let itemHistory = itemHistoryOrError.getValue()
|
||||
if (itemHistory.length === 0) {
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay, 'No revisions found on the server. This is likely a delay issue. Retrying...')
|
||||
|
||||
const itemHistoryOrError = await context.application.listRevisions.execute({ itemUuid: note.uuid })
|
||||
expect(itemHistoryOrError.isFailed()).to.equal(false)
|
||||
|
||||
itemHistory = itemHistoryOrError.getValue()
|
||||
}
|
||||
|
||||
expect(itemHistory.length >= 1).to.be.true
|
||||
})
|
||||
|
||||
it('should not create new revisions within the revision frequency window', async () => {
|
||||
const item = await Factory.createSyncedNote(application)
|
||||
const note = await context.createSyncedNote('test note')
|
||||
|
||||
await application.changeAndSaveItem.execute(
|
||||
item,
|
||||
(mutator) => {
|
||||
mutator.title = Math.random()
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
syncOptions,
|
||||
)
|
||||
await context.changeNoteTitleAndSync(note, 'new title 1')
|
||||
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay)
|
||||
|
||||
const itemHistoryOrError = await application.listRevisions.execute({ itemUuid: item.uuid })
|
||||
const itemHistoryOrError = await context.application.listRevisions.execute({ itemUuid: note.uuid })
|
||||
expect(itemHistoryOrError.isFailed()).to.equal(false)
|
||||
const itemHistory = itemHistoryOrError.getValue()
|
||||
let itemHistory = itemHistoryOrError.getValue()
|
||||
if (itemHistory.length === 0) {
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay, 'No revisions found on the server. This is likely a delay issue. Retrying...')
|
||||
|
||||
const itemHistoryOrError = await context.application.listRevisions.execute({ itemUuid: note.uuid })
|
||||
expect(itemHistoryOrError.isFailed()).to.equal(false)
|
||||
|
||||
itemHistory = itemHistoryOrError.getValue()
|
||||
}
|
||||
|
||||
expect(itemHistory.length >= 1).to.be.true
|
||||
})
|
||||
|
||||
it('should create new revisions outside the revision frequency window', async function () {
|
||||
let item = await Factory.createSyncedNote(application)
|
||||
const note = await context.createSyncedNote('test note')
|
||||
|
||||
await Factory.sleep(Factory.ServerRevisionFrequency)
|
||||
/** Sync with different contents, should create new entry */
|
||||
const newTitleAfterFirstChange = `The title should be: ${Math.random()}`
|
||||
await application.changeAndSaveItem.execute(
|
||||
item,
|
||||
(mutator) => {
|
||||
mutator.title = newTitleAfterFirstChange
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
syncOptions,
|
||||
)
|
||||
|
||||
await context.changeNoteTitleAndSync(note, `The title should be: ${Math.random()}`)
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay)
|
||||
|
||||
const itemHistoryOrError = await application.listRevisions.execute({ itemUuid: item.uuid })
|
||||
const itemHistory = itemHistoryOrError.getValue()
|
||||
const itemHistoryOrError = await context.application.listRevisions.execute({ itemUuid: note.uuid })
|
||||
expect(itemHistoryOrError.isFailed()).to.equal(false)
|
||||
let itemHistory = itemHistoryOrError.getValue()
|
||||
if (itemHistory.length !== 2) {
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay, 'Not enough revisions found on the server. This is likely a delay issue. Retrying...')
|
||||
|
||||
const itemHistoryOrError = await context.application.listRevisions.execute({ itemUuid: note.uuid })
|
||||
expect(itemHistoryOrError.isFailed()).to.equal(false)
|
||||
|
||||
itemHistory = itemHistoryOrError.getValue()
|
||||
}
|
||||
|
||||
expect(itemHistory.length).to.equal(2)
|
||||
|
||||
const oldestEntry = lastElement(itemHistory)
|
||||
let revisionFromServerOrError = await application.getRevision.execute({
|
||||
itemUuid: item.uuid,
|
||||
let revisionFromServerOrError = await context.application.getRevision.execute({
|
||||
itemUuid: note.uuid,
|
||||
revisionUuid: oldestEntry.uuid,
|
||||
})
|
||||
const revisionFromServer = revisionFromServerOrError.getValue()
|
||||
@@ -331,25 +332,36 @@ describe.skip('history manager', () => {
|
||||
|
||||
let payloadFromServer = revisionFromServer.payload
|
||||
expect(payloadFromServer.errorDecrypting).to.be.undefined
|
||||
expect(payloadFromServer.uuid).to.eq(item.payload.uuid)
|
||||
expect(payloadFromServer.content).to.eql(item.payload.content)
|
||||
expect(payloadFromServer.uuid).to.eq(note.payload.uuid)
|
||||
expect(payloadFromServer.content).to.eql(note.payload.content)
|
||||
|
||||
item = application.items.findItem(item.uuid)
|
||||
const item = context.application.items.findItem(note.uuid)
|
||||
expect(payloadFromServer.content).to.not.eql(item.payload.content)
|
||||
})
|
||||
|
||||
it('duplicate revisions should not have the originals uuid', async function () {
|
||||
const note = await Factory.createSyncedNote(application)
|
||||
await Factory.markDirtyAndSyncItem(application, note)
|
||||
const dupe = await application.mutator.duplicateItem(note, true)
|
||||
await Factory.markDirtyAndSyncItem(application, dupe)
|
||||
const note = await context.createSyncedNote('test note')
|
||||
await Factory.markDirtyAndSyncItem(context.application, note)
|
||||
|
||||
const dupe = await context.application.mutator.duplicateItem(note, true)
|
||||
await Factory.markDirtyAndSyncItem(context.application, dupe)
|
||||
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay)
|
||||
|
||||
const dupeHistoryOrError = await application.listRevisions.execute({ itemUuid: dupe.uuid })
|
||||
const dupeHistory = dupeHistoryOrError.getValue()
|
||||
const dupeHistoryOrError = await context.application.listRevisions.execute({ itemUuid: dupe.uuid })
|
||||
expect(dupeHistoryOrError.isFailed()).to.equal(false)
|
||||
let dupeHistory = dupeHistoryOrError.getValue()
|
||||
if (dupeHistory.length === 0) {
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay, 'No revisions found on the server. This is likely a delay issue. Retrying...')
|
||||
|
||||
const dupeRevisionOrError = await application.getRevision.execute({
|
||||
const dupeHistoryOrError = await context.application.listRevisions.execute({ itemUuid: dupe.uuid })
|
||||
expect(dupeHistoryOrError.isFailed()).to.equal(false)
|
||||
|
||||
dupeHistory = dupeHistoryOrError.getValue()
|
||||
}
|
||||
expect(dupeHistory.length >= 1).to.be.true
|
||||
|
||||
const dupeRevisionOrError = await context.application.getRevision.execute({
|
||||
itemUuid: dupe.uuid,
|
||||
revisionUuid: dupeHistory[0].uuid,
|
||||
})
|
||||
@@ -358,54 +370,79 @@ describe.skip('history manager', () => {
|
||||
})
|
||||
|
||||
it('revisions count matches original for duplicated items', async function () {
|
||||
const note = await Factory.createSyncedNote(application)
|
||||
const note = await context.createSyncedNote('test note')
|
||||
|
||||
await Factory.sleep(Factory.ServerRevisionFrequency)
|
||||
await Factory.markDirtyAndSyncItem(application, note)
|
||||
await Factory.markDirtyAndSyncItem(context.application, note)
|
||||
|
||||
await Factory.sleep(Factory.ServerRevisionFrequency)
|
||||
await Factory.markDirtyAndSyncItem(application, note)
|
||||
await Factory.markDirtyAndSyncItem(context.application, note)
|
||||
|
||||
await Factory.sleep(Factory.ServerRevisionFrequency)
|
||||
await Factory.markDirtyAndSyncItem(application, note)
|
||||
await Factory.markDirtyAndSyncItem(context.application, note)
|
||||
|
||||
const dupe = await application.mutator.duplicateItem(note, true)
|
||||
await Factory.markDirtyAndSyncItem(application, dupe)
|
||||
const dupe = await context.application.mutator.duplicateItem(note, true)
|
||||
await Factory.markDirtyAndSyncItem(context.application, dupe)
|
||||
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay)
|
||||
|
||||
const expectedRevisions = 4
|
||||
const noteHistoryOrError = await application.listRevisions.execute({ itemUuid: note.uuid })
|
||||
const noteHistory = noteHistoryOrError.getValue()
|
||||
const noteHistoryOrError = await context.application.listRevisions.execute({ itemUuid: note.uuid })
|
||||
expect(noteHistoryOrError.isFailed()).to.equal(false)
|
||||
let noteHistory = noteHistoryOrError.getValue()
|
||||
if (noteHistory.length !== expectedRevisions) {
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay, 'No revisions found on the server. This is likely a delay issue. Retrying...')
|
||||
|
||||
const dupeHistoryOrError = await application.listRevisions.execute({ itemUuid: dupe.uuid })
|
||||
const dupeHistory = dupeHistoryOrError.getValue()
|
||||
const noteHistoryOrError = await context.application.listRevisions.execute({ itemUuid: note.uuid })
|
||||
expect(noteHistoryOrError.isFailed()).to.equal(false)
|
||||
|
||||
noteHistory = noteHistoryOrError.getValue()
|
||||
}
|
||||
expect(noteHistory.length).to.equal(expectedRevisions)
|
||||
|
||||
const dupeHistoryOrError = await context.application.listRevisions.execute({ itemUuid: dupe.uuid })
|
||||
expect(dupeHistoryOrError.isFailed()).to.equal(false)
|
||||
let dupeHistory = dupeHistoryOrError.getValue()
|
||||
if (dupeHistory.length !== expectedRevisions + 1) {
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay, 'No revisions found on the server. This is likely a delay issue. Retrying...')
|
||||
|
||||
const dupeHistoryOrError = await context.application.listRevisions.execute({ itemUuid: dupe.uuid })
|
||||
expect(dupeHistoryOrError.isFailed()).to.equal(false)
|
||||
|
||||
dupeHistory = dupeHistoryOrError.getValue()
|
||||
}
|
||||
expect(dupeHistory.length).to.equal(expectedRevisions + 1)
|
||||
}).timeout(Factory.SixtySecondTimeout)
|
||||
|
||||
it('can decrypt revisions for duplicate_of items', async function () {
|
||||
const note = await Factory.createSyncedNote(application)
|
||||
const note = await context.createSyncedNote('test note')
|
||||
await Factory.sleep(Factory.ServerRevisionFrequency)
|
||||
|
||||
const changedText = `${Math.random()}`
|
||||
await application.changeAndSaveItem.execute(note, (mutator) => {
|
||||
mutator.title = changedText
|
||||
})
|
||||
await Factory.markDirtyAndSyncItem(application, note)
|
||||
await context.changeNoteTitleAndSync(note, changedText)
|
||||
await Factory.markDirtyAndSyncItem(context.application, note)
|
||||
|
||||
const dupe = await application.mutator.duplicateItem(note, true)
|
||||
await Factory.markDirtyAndSyncItem(application, dupe)
|
||||
const dupe = await context.application.mutator.duplicateItem(note, true)
|
||||
await Factory.markDirtyAndSyncItem(context.application, dupe)
|
||||
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay)
|
||||
|
||||
const itemHistoryOrError = await application.listRevisions.execute({ itemUuid: dupe.uuid })
|
||||
const itemHistory = itemHistoryOrError.getValue()
|
||||
const itemHistoryOrError = await context.application.listRevisions.execute({ itemUuid: dupe.uuid })
|
||||
expect(itemHistoryOrError.isFailed()).to.equal(false)
|
||||
let itemHistory = itemHistoryOrError.getValue()
|
||||
if (itemHistory.length <= 1) {
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay, 'No revisions found on the server. This is likely a delay issue. Retrying...')
|
||||
|
||||
const itemHistoryOrError = await context.application.listRevisions.execute({ itemUuid: dupe.uuid })
|
||||
expect(itemHistoryOrError.isFailed()).to.equal(false)
|
||||
|
||||
itemHistory = itemHistoryOrError.getValue()
|
||||
}
|
||||
expect(itemHistory.length).to.be.above(1)
|
||||
|
||||
const newestRevision = itemHistory[0]
|
||||
|
||||
const fetchedOrError = await application.getRevision.execute({
|
||||
const fetchedOrError = await context.application.getRevision.execute({
|
||||
itemUuid: dupe.uuid,
|
||||
revisionUuid: newestRevision.uuid,
|
||||
})
|
||||
|
||||
@@ -198,7 +198,12 @@ export const createSharedVaultWithNote = async (context) => {
|
||||
|
||||
export const moveItemToVault = async (context, sharedVault, item) => {
|
||||
const promise = context.resolveWhenItemCompletesAddingToVault(item)
|
||||
const updatedItem = await context.vaults.moveItemToVault(sharedVault, item)
|
||||
const result = await context.vaults.moveItemToVault(sharedVault, item)
|
||||
await promise
|
||||
return updatedItem
|
||||
|
||||
if (result.isFailed()) {
|
||||
throw new Error(result.getError())
|
||||
}
|
||||
|
||||
return result.getValue()
|
||||
}
|
||||
|
||||
@@ -142,13 +142,13 @@ describe('shared vault deletion', function () {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
|
||||
const originalSharedVaultUsers = await context.vaultUsers.getSharedVaultUsers(sharedVault)
|
||||
const originalSharedVaultUsers = await context.vaultUsers.getSharedVaultUsersFromServer(sharedVault)
|
||||
expect(originalSharedVaultUsers.length).to.equal(2)
|
||||
|
||||
const result = await context.vaultUsers.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
|
||||
expect(result.isFailed()).to.be.false
|
||||
|
||||
const updatedSharedVaultUsers = await context.vaultUsers.getSharedVaultUsers(sharedVault)
|
||||
const updatedSharedVaultUsers = await context.vaultUsers.getSharedVaultUsersFromServer(sharedVault)
|
||||
expect(updatedSharedVaultUsers.length).to.equal(1)
|
||||
|
||||
await deinitContactContext()
|
||||
|
||||
@@ -67,7 +67,7 @@ describe('shared vault files', function () {
|
||||
const uploadedFile = await Files.uploadFile(context.files, buffer, 'my-file', 'md', 1000)
|
||||
|
||||
const sharedVault = await Collaboration.createSharedVault(context)
|
||||
const addedFile = await context.vaults.moveItemToVault(sharedVault, uploadedFile)
|
||||
const addedFile = await Collaboration.moveItemToVault(context, sharedVault, uploadedFile)
|
||||
|
||||
const downloadedBytes = await Files.downloadFile(context.files, addedFile)
|
||||
expect(downloadedBytes).to.eql(buffer)
|
||||
@@ -81,7 +81,7 @@ describe('shared vault files', function () {
|
||||
const uploadedFile = await Files.uploadFile(context.files, buffer, 'my-file', 'md', 1000, firstVault)
|
||||
|
||||
const secondVault = await Collaboration.createSharedVault(context)
|
||||
const movedFile = await context.vaults.moveItemToVault(secondVault, uploadedFile)
|
||||
const movedFile = await Collaboration.moveItemToVault(context, secondVault, uploadedFile)
|
||||
|
||||
const downloadedBytes = await Files.downloadFile(context.files, movedFile)
|
||||
expect(downloadedBytes).to.eql(buffer)
|
||||
@@ -95,13 +95,13 @@ describe('shared vault files', function () {
|
||||
const uploadedFile = await Files.uploadFile(context.files, buffer, 'my-file', 'md', 1000, firstVault)
|
||||
const privateVault = await Collaboration.createPrivateVault(context)
|
||||
|
||||
const addedFile = await context.vaults.moveItemToVault(privateVault, uploadedFile)
|
||||
const addedFile = await Collaboration.moveItemToVault(context, privateVault, uploadedFile)
|
||||
|
||||
const downloadedBytes = await Files.downloadFile(context.files, addedFile)
|
||||
expect(downloadedBytes).to.eql(buffer)
|
||||
})
|
||||
|
||||
it('moving a note to a vault should also moved linked files', async () => {
|
||||
it('should not move a note to a vault that is linked with files ', async () => {
|
||||
const note = await context.createSyncedNote()
|
||||
const response = await fetch('/mocha/assets/small_file.md')
|
||||
const buffer = new Uint8Array(await response.arrayBuffer())
|
||||
@@ -111,17 +111,12 @@ describe('shared vault files', function () {
|
||||
|
||||
const sharedVault = await Collaboration.createSharedVault(context)
|
||||
|
||||
context.vaults.alerts.confirmV2 = () => Promise.resolve(true)
|
||||
context.vaults.alerts.alertV2 = () => Promise.resolve(true)
|
||||
|
||||
await context.vaults.moveItemToVault(sharedVault, note)
|
||||
|
||||
const latestFile = context.items.findItem(updatedFile.uuid)
|
||||
|
||||
expect(context.vaults.getItemVault(latestFile).uuid).to.equal(sharedVault.uuid)
|
||||
expect(context.vaults.getItemVault(context.items.findItem(note.uuid)).uuid).to.equal(sharedVault.uuid)
|
||||
|
||||
const downloadedBytes = await Files.downloadFile(context.files, latestFile)
|
||||
expect(downloadedBytes).to.eql(buffer)
|
||||
await Factory.expectThrowsAsync(
|
||||
() => Collaboration.moveItemToVault(context, sharedVault, note),
|
||||
'This item is linked to other items that are not in the same vault. Please move those items to this vault first.',
|
||||
)
|
||||
})
|
||||
|
||||
it('should be able to move a file out of its vault', async () => {
|
||||
@@ -225,7 +220,7 @@ describe('shared vault files', function () {
|
||||
const response = await fetch('/mocha/assets/small_file.md')
|
||||
const buffer = new Uint8Array(await response.arrayBuffer())
|
||||
const uploadedFile = await Files.uploadFile(context.files, buffer, 'my-file', 'md', 1000)
|
||||
const addedFile = await context.vaults.moveItemToVault(sharedVault, uploadedFile)
|
||||
const addedFile = await Collaboration.moveItemToVault(context, sharedVault, uploadedFile)
|
||||
|
||||
await contactContext.sync()
|
||||
|
||||
|
||||
@@ -46,11 +46,29 @@ describe('shared vault revisions', function () {
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay)
|
||||
|
||||
const contactItemHistoryOrError = await contactContext.application.listRevisions.execute({ itemUuid: note.uuid })
|
||||
const contactItemHistory = contactItemHistoryOrError.getValue()
|
||||
expect(contactItemHistoryOrError.isFailed()).to.equal(false)
|
||||
let contactItemHistory = contactItemHistoryOrError.getValue()
|
||||
if (contactItemHistory.length < 2) {
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay, 'Not enough revisions found on the server. This is likely a delay issue. Retrying...')
|
||||
|
||||
const contactItemHistoryOrError = await contactContext.application.listRevisions.execute({ itemUuid: note.uuid })
|
||||
expect(contactItemHistoryOrError.isFailed()).to.equal(false)
|
||||
|
||||
contactItemHistory = contactItemHistoryOrError.getValue()
|
||||
}
|
||||
expect(contactItemHistory.length >= 2).to.be.true
|
||||
|
||||
const itemHistoryOrError = await context.application.listRevisions.execute({ itemUuid: note.uuid })
|
||||
const itemHistory = itemHistoryOrError.getValue()
|
||||
expect(itemHistoryOrError.isFailed()).to.equal(false)
|
||||
let itemHistory = itemHistoryOrError.getValue()
|
||||
if (itemHistory.length < 2) {
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay, 'Not enough revisions found on the server. This is likely a delay issue. Retrying...')
|
||||
|
||||
const itemHistoryOrError = await context.application.listRevisions.execute({ itemUuid: note.uuid })
|
||||
expect(itemHistoryOrError.isFailed()).to.equal(false)
|
||||
|
||||
itemHistory = itemHistoryOrError.getValue()
|
||||
}
|
||||
expect(itemHistory.length >= 2).to.be.true
|
||||
})
|
||||
|
||||
@@ -67,7 +85,16 @@ describe('shared vault revisions', function () {
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay)
|
||||
|
||||
const itemHistoryOrError = await context.application.listRevisions.execute({ itemUuid: note.uuid })
|
||||
const itemHistory = itemHistoryOrError.getValue()
|
||||
expect(itemHistoryOrError.isFailed()).to.equal(false)
|
||||
let itemHistory = itemHistoryOrError.getValue()
|
||||
if (itemHistory.length < 2) {
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay, 'Not enough revisions found on the server. This is likely a delay issue. Retrying...')
|
||||
|
||||
const itemHistoryOrError = await context.application.listRevisions.execute({ itemUuid: note.uuid })
|
||||
expect(itemHistoryOrError.isFailed()).to.equal(false)
|
||||
|
||||
itemHistory = itemHistoryOrError.getValue()
|
||||
}
|
||||
expect(itemHistory.length >= 2).to.be.true
|
||||
})
|
||||
|
||||
@@ -82,7 +109,16 @@ describe('shared vault revisions', function () {
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay)
|
||||
|
||||
const itemHistoryOrError = await contactContext.application.listRevisions.execute({ itemUuid: note.uuid })
|
||||
const itemHistory = itemHistoryOrError.getValue()
|
||||
expect(itemHistoryOrError.isFailed()).to.equal(false)
|
||||
let itemHistory = itemHistoryOrError.getValue()
|
||||
if (itemHistory.length < 1) {
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay, 'Not enough revisions found on the server. This is likely a delay issue. Retrying...')
|
||||
|
||||
const itemHistoryOrError = await contactContext.application.listRevisions.execute({ itemUuid: note.uuid })
|
||||
expect(itemHistoryOrError.isFailed()).to.equal(false)
|
||||
|
||||
itemHistory = itemHistoryOrError.getValue()
|
||||
}
|
||||
const newestRevision = itemHistory[0]
|
||||
|
||||
const fetchedOrError = await contactContext.application.getRevision.execute({
|
||||
@@ -110,9 +146,16 @@ describe('shared vault revisions', function () {
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay)
|
||||
|
||||
const itemHistoryOrError = await contactContext.application.listRevisions.execute({ itemUuid: note.uuid })
|
||||
expect(itemHistoryOrError.isFailed()).to.be.false
|
||||
expect(itemHistoryOrError.isFailed()).to.equal(false)
|
||||
let itemHistory = itemHistoryOrError.getValue()
|
||||
if (itemHistory.length != 0) {
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay, 'Not enough revisions found on the server. This is likely a delay issue. Retrying...')
|
||||
|
||||
const itemHistory = itemHistoryOrError.getValue()
|
||||
const itemHistoryOrError = await contactContext.application.listRevisions.execute({ itemUuid: note.uuid })
|
||||
expect(itemHistoryOrError.isFailed()).to.equal(false)
|
||||
|
||||
itemHistory = itemHistoryOrError.getValue()
|
||||
}
|
||||
expect(itemHistory.length).to.equal(0)
|
||||
})
|
||||
|
||||
@@ -127,19 +170,32 @@ describe('shared vault revisions', function () {
|
||||
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay)
|
||||
|
||||
let itemHistoryOrError = await contactContext.application.listRevisions.execute({ itemUuid: note.uuid })
|
||||
expect(itemHistoryOrError.isFailed()).to.be.false
|
||||
let itemHistory = itemHistoryOrError.getValue()
|
||||
const itemHistoryBeforeOrError = await contactContext.application.listRevisions.execute({ itemUuid: note.uuid })
|
||||
expect(itemHistoryBeforeOrError.isFailed()).to.equal(false)
|
||||
let itemHistoryBefore = itemHistoryBeforeOrError.getValue()
|
||||
if (itemHistoryBefore.length < 1) {
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay, 'Not enough revisions found on the server. This is likely a delay issue. Retrying...')
|
||||
|
||||
expect(itemHistory.length >= 1).to.be.true
|
||||
const itemHistoryOrError = await contactContext.application.listRevisions.execute({ itemUuid: note.uuid })
|
||||
expect(itemHistoryOrError.isFailed()).to.equal(false)
|
||||
|
||||
itemHistoryBefore = itemHistoryOrError.getValue()
|
||||
}
|
||||
expect(itemHistoryBefore.length >= 1).to.be.true
|
||||
|
||||
await context.vaultUsers.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay)
|
||||
|
||||
itemHistoryOrError = await contactContext.application.listRevisions.execute({ itemUuid: note.uuid })
|
||||
expect(itemHistoryOrError.isFailed()).to.be.false
|
||||
itemHistory = itemHistoryOrError.getValue()
|
||||
const itemHistoryAfterOrError = await contactContext.application.listRevisions.execute({ itemUuid: note.uuid })
|
||||
let itemHistoryAfter = itemHistoryAfterOrError.getValue()
|
||||
if (itemHistoryAfter.length != 0) {
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay, 'Not enough revisions found on the server. This is likely a delay issue. Retrying...')
|
||||
|
||||
expect(itemHistory.length).to.equal(0)
|
||||
const itemHistoryOrError = await contactContext.application.listRevisions.execute({ itemUuid: note.uuid })
|
||||
expect(itemHistoryOrError.isFailed()).to.equal(false)
|
||||
|
||||
itemHistoryAfter = itemHistoryOrError.getValue()
|
||||
}
|
||||
expect(itemHistoryAfter.length).to.equal(0)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/snjs",
|
||||
"version": "2.202.75",
|
||||
"version": "2.202.85",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -37,7 +37,7 @@
|
||||
"@babel/preset-env": "*",
|
||||
"@standardnotes/api": "workspace:*",
|
||||
"@standardnotes/common": "^1.50.0",
|
||||
"@standardnotes/domain-core": "^1.28.0",
|
||||
"@standardnotes/domain-core": "^1.32.0",
|
||||
"@standardnotes/domain-events": "^2.122.0",
|
||||
"@standardnotes/encryption": "workspace:*",
|
||||
"@standardnotes/features": "workspace:*",
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.31.20](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.31.19](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.31.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.31.17](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.31.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.31.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.31.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/ui-services",
|
||||
"version": "1.31.14",
|
||||
"version": "1.31.20",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -16,7 +16,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/common": "^1.50.0",
|
||||
"@standardnotes/domain-core": "^1.28.0",
|
||||
"@standardnotes/domain-core": "^1.32.0",
|
||||
"@standardnotes/features": "workspace:^",
|
||||
"@standardnotes/filepicker": "workspace:^",
|
||||
"@standardnotes/models": "workspace:^",
|
||||
|
||||
@@ -3,6 +3,64 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.173.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.173.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.173.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.173.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.173.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.173.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.173.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed issue where using table or image option in Super mobile toolbar would make uninteractive ([ec61901](https://github.com/standardnotes/app/commit/ec61901aa5f0c08b7b77029e225e81c19decbd60))
|
||||
|
||||
## [3.173.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.173.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.173.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.173.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.173.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.173.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.173.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
# [3.173.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-16)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,5 +1,162 @@
|
||||
{
|
||||
"versions": [
|
||||
{
|
||||
"version": "3.173.14",
|
||||
"title": "[3.173.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-22)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.173.13",
|
||||
"title": "[3.173.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-22)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.173.12",
|
||||
"title": "[3.173.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.173.11",
|
||||
"title": "[3.173.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.173.10",
|
||||
"title": "[3.173.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.173.9",
|
||||
"title": "[3.173.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-21)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.173.8",
|
||||
"title": "[3.173.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)",
|
||||
"date": null,
|
||||
"body": "### Bug Fixes\n\n* Fixed issue where using table or image option in Super mobile toolbar would make uninteractive ([ec61901](https://github.com/standardnotes/app/commit/ec61901aa5f0c08b7b77029e225e81c19decbd60))",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Fixed issue where using table or image option in Super mobile toolbar would make uninteractive (ec61901)"
|
||||
],
|
||||
"Bug Fixes": [
|
||||
"Fixed issue where using table or image option in Super mobile toolbar would make uninteractive (ec61901)"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.173.7",
|
||||
"title": "[3.173.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.173.6",
|
||||
"title": "[3.173.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.173.5",
|
||||
"title": "[3.173.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-20)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.173.4",
|
||||
"title": "[3.173.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-19)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.173.3",
|
||||
"title": "[3.173.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-19)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.173.2",
|
||||
"title": "[3.173.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-18)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.173.1",
|
||||
"title": "[3.173.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-18)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.173.0",
|
||||
"title": "[3.173.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-09-16)",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/web",
|
||||
"version": "3.173.0",
|
||||
"version": "3.173.14",
|
||||
"license": "CC BY-NC-SA 4.0",
|
||||
"main": "dist/app.js",
|
||||
"author": "Standard Notes.",
|
||||
|
||||
@@ -20,6 +20,7 @@ interface Props {
|
||||
componentViewer: ComponentViewerInterface
|
||||
requestReload?: (viewer: ComponentViewerInterface, force?: boolean) => void
|
||||
onLoad?: () => void
|
||||
readonly?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -30,7 +31,7 @@ const MaxLoadThreshold = 4000
|
||||
const VisibilityChangeKey = 'visibilitychange'
|
||||
const MSToWaitAfterIframeLoadToAvoidFlicker = 35
|
||||
|
||||
const IframeFeatureView: FunctionComponent<Props> = ({ onLoad, componentViewer, requestReload }) => {
|
||||
const IframeFeatureView: FunctionComponent<Props> = ({ onLoad, componentViewer, requestReload, readonly = false }) => {
|
||||
const application = useApplication()
|
||||
|
||||
const iframeRef = useRef<HTMLIFrameElement | null>(null)
|
||||
@@ -50,7 +51,7 @@ const IframeFeatureView: FunctionComponent<Props> = ({ onLoad, componentViewer,
|
||||
const reloadValidityStatus = useCallback(() => {
|
||||
setFeatureStatus(componentViewer.getFeatureStatus())
|
||||
if (!componentViewer.lockReadonly) {
|
||||
componentViewer.setReadonly(featureStatus !== FeatureStatus.Entitled)
|
||||
componentViewer.setReadonly(featureStatus !== FeatureStatus.Entitled || readonly)
|
||||
}
|
||||
setIsComponentValid(componentViewer.shouldRender())
|
||||
|
||||
@@ -60,7 +61,7 @@ const IframeFeatureView: FunctionComponent<Props> = ({ onLoad, componentViewer,
|
||||
|
||||
setError(componentViewer.getError())
|
||||
setDeprecationMessage(uiFeature.deprecationMessage)
|
||||
}, [componentViewer, uiFeature, featureStatus, isComponentValid, isLoading])
|
||||
}, [componentViewer, isLoading, isComponentValid, uiFeature.deprecationMessage, featureStatus, readonly])
|
||||
|
||||
useEffect(() => {
|
||||
reloadValidityStatus()
|
||||
|
||||
@@ -330,9 +330,12 @@ const ContentTableView = ({ application, items }: Props) => {
|
||||
setContextMenuItem(file)
|
||||
},
|
||||
rowActions: (item) => {
|
||||
const vault = application.vaults.getItemVault(item)
|
||||
const isReadonly = vault?.isSharedVaultListing() && application.vaultUsers.isCurrentUserReadonlyVaultMember(vault)
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<ItemLinksCell item={item} />
|
||||
{!isReadonly && <ItemLinksCell item={item} />}
|
||||
<ContextMenuCell items={[item]} />
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -69,6 +69,18 @@ const FileMenuOptions: FunctionComponent<Props> = ({
|
||||
closeMenu()
|
||||
}, [closeMenu, toggleAppPane])
|
||||
|
||||
const areSomeFilesInReadonlySharedVault = selectedFiles.some((file) => {
|
||||
const vault = application.vaults.getItemVault(file)
|
||||
return vault?.isSharedVaultListing() && application.vaultUsers.isCurrentUserReadonlyVaultMember(vault)
|
||||
})
|
||||
const hasAdminPermissionForAllSharedFiles = selectedFiles.every((file) => {
|
||||
const vault = application.vaults.getItemVault(file)
|
||||
if (!vault?.isSharedVaultListing()) {
|
||||
return true
|
||||
}
|
||||
return application.vaultUsers.isCurrentUserSharedVaultAdmin(vault)
|
||||
})
|
||||
|
||||
if (selectedFiles.length === 0) {
|
||||
return <div className="text-center">No files selected</div>
|
||||
}
|
||||
@@ -91,19 +103,25 @@ const FileMenuOptions: FunctionComponent<Props> = ({
|
||||
</>
|
||||
)}
|
||||
{application.featuresController.isEntitledToVaults() && (
|
||||
<AddToVaultMenuOption iconClassName={iconClass} items={selectedFiles} />
|
||||
<AddToVaultMenuOption
|
||||
iconClassName={iconClass}
|
||||
items={selectedFiles}
|
||||
disabled={!hasAdminPermissionForAllSharedFiles}
|
||||
/>
|
||||
)}
|
||||
<AddTagOption
|
||||
navigationController={application.navigationController}
|
||||
linkingController={application.linkingController}
|
||||
selectedItems={selectedFiles}
|
||||
iconClassName={`text-neutral mr-2 ${MenuItemIconSize}`}
|
||||
disabled={areSomeFilesInReadonlySharedVault}
|
||||
/>
|
||||
<MenuSwitchButtonItem
|
||||
checked={hasProtectedFiles}
|
||||
onChange={(hasProtectedFiles) => {
|
||||
void application.filesController.setProtectionForFiles(hasProtectedFiles, selectedFiles)
|
||||
}}
|
||||
disabled={areSomeFilesInReadonlySharedVault}
|
||||
>
|
||||
<Icon type="lock" className={`mr-2 text-neutral ${MenuItemIconSize}`} />
|
||||
Password protect
|
||||
@@ -123,6 +141,7 @@ const FileMenuOptions: FunctionComponent<Props> = ({
|
||||
onClick={() => {
|
||||
renameToggleCallback?.(true)
|
||||
}}
|
||||
disabled={areSomeFilesInReadonlySharedVault}
|
||||
>
|
||||
<Icon type="pencil" className={`mr-2 text-neutral ${MenuItemIconSize}`} />
|
||||
Rename
|
||||
@@ -133,6 +152,7 @@ const FileMenuOptions: FunctionComponent<Props> = ({
|
||||
closeMenuAndToggleFilesList()
|
||||
void application.filesController.deleteFilesPermanently(selectedFiles)
|
||||
}}
|
||||
disabled={areSomeFilesInReadonlySharedVault}
|
||||
>
|
||||
<Icon type="trash" className={`mr-2 text-danger ${MenuItemIconSize}`} />
|
||||
<span className="text-danger">Delete permanently</span>
|
||||
|
||||
@@ -113,6 +113,9 @@ const FilePreviewModal = observer(({ application }: Props) => {
|
||||
return null
|
||||
}
|
||||
|
||||
const vault = application.vaults.getItemVault(currentFile)
|
||||
const isReadonly = vault?.isSharedVaultListing() && application.vaultUsers.isCurrentUserReadonlyVaultMember(vault)
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={currentFile.name}
|
||||
@@ -181,15 +184,17 @@ const FilePreviewModal = observer(({ application }: Props) => {
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<StyledTooltip label="Rename file" className="!z-modal">
|
||||
<button
|
||||
className="mr-4 flex cursor-pointer rounded border border-solid border-border bg-transparent p-1.5 hover:bg-contrast"
|
||||
onClick={() => setIsRenaming((isRenaming) => !isRenaming)}
|
||||
aria-label="Rename file"
|
||||
>
|
||||
<Icon type="pencil-filled" className="text-neutral" />
|
||||
</button>
|
||||
</StyledTooltip>
|
||||
{!isReadonly && (
|
||||
<StyledTooltip label="Rename file" className="!z-modal">
|
||||
<button
|
||||
className="mr-4 flex cursor-pointer rounded border border-solid border-border bg-transparent p-1.5 hover:bg-contrast"
|
||||
onClick={() => setIsRenaming((isRenaming) => !isRenaming)}
|
||||
aria-label="Rename file"
|
||||
>
|
||||
<Icon type="pencil-filled" className="text-neutral" />
|
||||
</button>
|
||||
</StyledTooltip>
|
||||
)}
|
||||
<StyledTooltip label="Show linked items" className="!z-modal">
|
||||
<button
|
||||
className="mr-4 flex cursor-pointer rounded border border-solid border-border bg-transparent p-1.5 hover:bg-contrast"
|
||||
@@ -249,7 +254,11 @@ const FilePreviewModal = observer(({ application }: Props) => {
|
||||
</div>
|
||||
{showLinkedBubblesContainer && (
|
||||
<div className="-mt-1 min-h-0 flex-shrink-0 border-b border-border px-3.5 py-1.5">
|
||||
<LinkedItemBubblesContainer linkingController={application.linkingController} item={currentFile} />
|
||||
<LinkedItemBubblesContainer
|
||||
linkingController={application.linkingController}
|
||||
item={currentFile}
|
||||
readonly={isReadonly}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex min-h-0 flex-grow flex-col-reverse md:flex-row">
|
||||
|
||||
@@ -11,11 +11,35 @@ import Popover from '../Popover/Popover'
|
||||
import FilePreviewInfoPanel from '../FilePreview/FilePreviewInfoPanel'
|
||||
import { useFileDragNDrop } from '../FileDragNDropProvider'
|
||||
import RoundIconButton from '../Button/RoundIconButton'
|
||||
import { useItemVaultInfo } from '@/Hooks/useItemVaultInfo'
|
||||
import Icon from '../Icon/Icon'
|
||||
import { VaultUserServiceEvent } from '@standardnotes/snjs'
|
||||
|
||||
const SyncTimeoutNoDebounceMs = 100
|
||||
const SyncTimeoutDebounceMs = 350
|
||||
|
||||
const FileViewWithoutProtection = ({ application, file }: FileViewProps) => {
|
||||
const { vault } = useItemVaultInfo(file)
|
||||
|
||||
const [isReadonly, setIsReadonly] = useState(false)
|
||||
useEffect(() => {
|
||||
if (!vault) {
|
||||
return
|
||||
}
|
||||
|
||||
setIsReadonly(application.vaultUsers.isCurrentUserReadonlyVaultMember(vault))
|
||||
}, [application.vaultUsers, vault])
|
||||
useEffect(() => {
|
||||
return application.vaultUsers.addEventObserver((event, data) => {
|
||||
if (event === VaultUserServiceEvent.InvalidatedUserCacheForVault) {
|
||||
if ((data as string) !== vault?.sharing?.sharedVaultUuid) {
|
||||
return
|
||||
}
|
||||
setIsReadonly(vault ? application.vaultUsers.isCurrentUserReadonlyVaultMember(vault) : false)
|
||||
}
|
||||
})
|
||||
}, [application.vaultUsers, vault])
|
||||
|
||||
const syncTimeoutRef = useRef<number>()
|
||||
const fileInfoButtonRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
@@ -67,6 +91,12 @@ const FileViewWithoutProtection = ({ application, file }: FileViewProps) => {
|
||||
return (
|
||||
<div className="sn-component section editor" aria-label="File" ref={fileDragTargetRef}>
|
||||
<div className="flex flex-col">
|
||||
{isReadonly && (
|
||||
<div className="bg-warning-faded relative flex items-center px-3.5 py-2 text-sm text-accessory-tint-3">
|
||||
<Icon type="pencil-off" className="mr-3" />
|
||||
This file is readonly
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="content-title-bar section-title-bar section-title-bar z-editor-title-bar w-full"
|
||||
id="file-title-bar"
|
||||
@@ -85,11 +115,12 @@ const FileViewWithoutProtection = ({ application, file }: FileViewProps) => {
|
||||
spellCheck={false}
|
||||
defaultValue={file.name}
|
||||
autoComplete="off"
|
||||
disabled={isReadonly}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<LinkedItemsButton linkingController={application.linkingController} />
|
||||
{!isReadonly && <LinkedItemsButton linkingController={application.linkingController} />}
|
||||
<RoundIconButton
|
||||
label="File information panel"
|
||||
onClick={toggleFileInfoPanel}
|
||||
@@ -110,7 +141,11 @@ const FileViewWithoutProtection = ({ application, file }: FileViewProps) => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden md:flex">
|
||||
<LinkedItemBubblesContainer item={file} linkingController={application.linkingController} />
|
||||
<LinkedItemBubblesContainer
|
||||
item={file}
|
||||
linkingController={application.linkingController}
|
||||
readonly={isReadonly}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -166,7 +166,7 @@ const LinkedItemBubblesContainer = ({
|
||||
|
||||
const { vault, lastEditedByContact } = useItemVaultInfo(item)
|
||||
|
||||
if (readonly && itemsToDisplay.length === 0) {
|
||||
if (readonly && itemsToDisplay.length === 0 && !vault) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ const MenuItem = forwardRef(
|
||||
tabIndex={typeof tabIndex === 'number' ? tabIndex : FOCUSABLE_BUT_NOT_TABBABLE}
|
||||
className={classNames(
|
||||
'flex w-full cursor-pointer select-none border-0 bg-transparent px-3 py-2 text-left md:py-1.5',
|
||||
'text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground',
|
||||
'text-mobile-menu-item text-text enabled:hover:bg-contrast enabled:hover:text-foreground',
|
||||
'focus:bg-info-backdrop focus:shadow-none md:text-tablet-menu-item lg:text-menu-item',
|
||||
'disabled:cursor-not-allowed disabled:opacity-60',
|
||||
className,
|
||||
|
||||
@@ -37,8 +37,9 @@ const MenuSwitchButtonItem = forwardRef(
|
||||
ref={ref}
|
||||
className={classNames(
|
||||
'flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5',
|
||||
'text-left text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none',
|
||||
'text-left text-text focus:bg-info-backdrop focus:shadow-none enabled:hover:bg-contrast enabled:hover:text-foreground',
|
||||
'text-mobile-menu-item md:text-tablet-menu-item lg:text-menu-item',
|
||||
'disabled:cursor-not-allowed disabled:opacity-60',
|
||||
className,
|
||||
)}
|
||||
onClick={() => {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
SNNote,
|
||||
NoteType,
|
||||
PayloadEmitSource,
|
||||
VaultServiceInterface,
|
||||
} from '@standardnotes/snjs'
|
||||
import NoteView from './NoteView'
|
||||
import { NoteViewController } from './Controller/NoteViewController'
|
||||
@@ -19,6 +20,7 @@ describe('NoteView', () => {
|
||||
let application: WebApplication
|
||||
|
||||
let notesController: NotesController
|
||||
let vaults: VaultServiceInterface
|
||||
|
||||
const createNoteView = () =>
|
||||
new NoteView({
|
||||
@@ -36,9 +38,13 @@ describe('NoteView', () => {
|
||||
notesController.getSpellcheckStateForNote = jest.fn()
|
||||
notesController.getEditorWidthForNote = jest.fn()
|
||||
|
||||
vaults = {} as jest.Mocked<VaultServiceInterface>
|
||||
vaults.getItemVault = jest.fn().mockReturnValue(undefined)
|
||||
|
||||
application = {
|
||||
notesController,
|
||||
noteViewController,
|
||||
vaults,
|
||||
} as unknown as jest.Mocked<WebApplication>
|
||||
|
||||
application.hasProtectionSources = jest.fn().mockReturnValue(true)
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
PrefKey,
|
||||
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction,
|
||||
SNNote,
|
||||
VaultUserServiceEvent,
|
||||
} from '@standardnotes/snjs'
|
||||
import { confirmDialog, DELETE_NOTE_KEYBOARD_COMMAND, KeyboardKey } from '@standardnotes/ui-services'
|
||||
import { ChangeEventHandler, createRef, CSSProperties, KeyboardEventHandler, RefObject } from 'react'
|
||||
@@ -68,6 +69,7 @@ type State = {
|
||||
isDesktop?: boolean
|
||||
editorLineWidth: EditorLineWidth
|
||||
noteLocked: boolean
|
||||
readonly: boolean
|
||||
noteStatus?: NoteStatus
|
||||
saveError?: boolean
|
||||
showProtectedWarning: boolean
|
||||
@@ -96,6 +98,7 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
||||
private removeNoteStreamObserver?: () => void
|
||||
private removeComponentManagerObserver?: () => void
|
||||
private removeInnerNoteObserver?: () => void
|
||||
private removeVaultUsersEventHandler?: () => void
|
||||
|
||||
private protectionTimeoutId: ReturnType<typeof setTimeout> | null = null
|
||||
private noteViewElementRef: RefObject<HTMLDivElement>
|
||||
@@ -116,6 +119,8 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
||||
|
||||
this.debounceReloadEditorComponent = debounce(this.debounceReloadEditorComponent.bind(this), 25)
|
||||
|
||||
const itemVault = this.application.vaults.getItemVault(this.controller.item)
|
||||
|
||||
this.state = {
|
||||
availableStackComponents: [],
|
||||
editorStateDidLoad: false,
|
||||
@@ -124,6 +129,7 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
||||
isDesktop: isDesktopApplication(),
|
||||
noteStatus: undefined,
|
||||
noteLocked: this.controller.item.locked,
|
||||
readonly: itemVault ? this.application.vaultUsers.isCurrentUserReadonlyVaultMember(itemVault) : false,
|
||||
showProtectedWarning: false,
|
||||
spellcheck: true,
|
||||
stackComponentViewers: [],
|
||||
@@ -154,6 +160,9 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
||||
this.removeTrashKeyObserver?.()
|
||||
this.removeTrashKeyObserver = undefined
|
||||
|
||||
this.removeVaultUsersEventHandler?.()
|
||||
this.removeVaultUsersEventHandler = undefined
|
||||
|
||||
this.clearNoteProtectionInactivityTimer()
|
||||
;(this.ensureNoteIsInsertedBeforeUIAction as unknown) = undefined
|
||||
|
||||
@@ -207,6 +216,18 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
||||
override componentDidMount(): void {
|
||||
super.componentDidMount()
|
||||
|
||||
this.removeVaultUsersEventHandler = this.application.vaultUsers.addEventObserver((event, data) => {
|
||||
if (event === VaultUserServiceEvent.InvalidatedUserCacheForVault) {
|
||||
const vault = this.application.vaults.getItemVault(this.note)
|
||||
if ((data as string) !== vault?.sharing?.sharedVaultUuid) {
|
||||
return
|
||||
}
|
||||
this.setState({
|
||||
readonly: vault ? this.application.vaultUsers.isCurrentUserReadonlyVaultMember(vault) : false,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
this.registerKeyboardShortcuts()
|
||||
|
||||
this.removeInnerNoteObserver = this.controller.addNoteInnerValueChangeObserver((note, source) => {
|
||||
@@ -842,7 +863,7 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
||||
? 'component'
|
||||
: 'plain'
|
||||
|
||||
const shouldShowConflictsButton = this.state.conflictedNotes.length > 0
|
||||
const shouldShowConflictsButton = this.state.conflictedNotes.length > 0 && !this.state.readonly
|
||||
|
||||
return (
|
||||
<div aria-label="Note" className="section editor sn-component h-full md:max-h-full" ref={this.noteViewElementRef}>
|
||||
@@ -855,6 +876,13 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
||||
/>
|
||||
)}
|
||||
|
||||
{this.state.readonly && (
|
||||
<div className="bg-warning-faded flex items-center px-3.5 py-2 text-sm text-accessory-tint-3">
|
||||
<Icon type="pencil-off" className="mr-3" />
|
||||
You don't have permission to edit this note
|
||||
</div>
|
||||
)}
|
||||
|
||||
{this.state.noteLocked && (
|
||||
<EditingDisabledBanner
|
||||
onClick={() => this.application.notesController.setLockSelectedNotes(!this.state.noteLocked)}
|
||||
@@ -878,7 +906,7 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
||||
<div className="title flex-grow overflow-auto">
|
||||
<input
|
||||
className="input text-lg"
|
||||
disabled={this.state.noteLocked}
|
||||
disabled={this.state.noteLocked || this.state.readonly}
|
||||
id={ElementIds.NoteTitleEditor}
|
||||
onChange={this.onTitleChange}
|
||||
onFocus={(event) => {
|
||||
@@ -911,18 +939,22 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
||||
)}
|
||||
{renderHeaderOptions && (
|
||||
<div className="note-view-options-buttons flex items-center gap-3">
|
||||
<LinkedItemsButton
|
||||
linkingController={this.application.linkingController}
|
||||
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
||||
/>
|
||||
<ChangeEditorButton
|
||||
noteViewController={this.controller}
|
||||
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
||||
/>
|
||||
<PinNoteButton
|
||||
notesController={this.application.notesController}
|
||||
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
||||
/>
|
||||
{!this.state.readonly && (
|
||||
<>
|
||||
<LinkedItemsButton
|
||||
linkingController={this.application.linkingController}
|
||||
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
||||
/>
|
||||
<ChangeEditorButton
|
||||
noteViewController={this.controller}
|
||||
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
||||
/>
|
||||
<PinNoteButton
|
||||
notesController={this.application.notesController}
|
||||
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<NotesOptionsPanel
|
||||
notesController={this.application.notesController}
|
||||
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
||||
@@ -934,7 +966,11 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
||||
<CollaborationInfoHUD item={this.note} />
|
||||
</div>
|
||||
<div className="hidden md:block">
|
||||
<LinkedItemBubblesContainer item={this.note} linkingController={this.application.linkingController} />
|
||||
<LinkedItemBubblesContainer
|
||||
item={this.note}
|
||||
linkingController={this.application.linkingController}
|
||||
readonly={this.state.readonly}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -961,6 +997,7 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
||||
componentViewer={this.state.editorComponentViewer}
|
||||
onLoad={this.onEditorComponentLoad}
|
||||
requestReload={this.editorComponentViewerRequestsReload}
|
||||
readonly={this.state.readonly}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -971,7 +1008,7 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
||||
spellcheck={this.state.spellcheck}
|
||||
ref={this.setPlainEditorRef}
|
||||
controller={this.controller}
|
||||
locked={this.state.noteLocked}
|
||||
locked={this.state.noteLocked || this.state.readonly}
|
||||
onFocus={this.onPlainFocus}
|
||||
onBlur={this.onPlainBlur}
|
||||
/>
|
||||
@@ -986,6 +1023,7 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
||||
filesController={this.application.filesController}
|
||||
spellcheck={this.state.spellcheck}
|
||||
controller={this.controller}
|
||||
readonly={this.state.readonly}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -16,6 +16,7 @@ type Props = {
|
||||
linkingController: LinkingController
|
||||
selectedItems: DecryptedItemInterface[]
|
||||
iconClassName: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const AddTagOption: FunctionComponent<Props> = ({
|
||||
@@ -23,6 +24,7 @@ const AddTagOption: FunctionComponent<Props> = ({
|
||||
linkingController,
|
||||
selectedItems,
|
||||
iconClassName,
|
||||
disabled,
|
||||
}) => {
|
||||
const application = useApplication()
|
||||
const menuContainerRef = useRef<HTMLDivElement>(null)
|
||||
@@ -57,6 +59,7 @@ const AddTagOption: FunctionComponent<Props> = ({
|
||||
}
|
||||
}}
|
||||
ref={buttonRef}
|
||||
disabled={disabled}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<Icon type="hashtag" className={iconClassName} />
|
||||
|
||||
@@ -12,9 +12,15 @@ type ChangeEditorOptionProps = {
|
||||
application: WebApplication
|
||||
note: SNNote
|
||||
iconClassName: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const ChangeEditorOption: FunctionComponent<ChangeEditorOptionProps> = ({ application, note, iconClassName }) => {
|
||||
const ChangeEditorOption: FunctionComponent<ChangeEditorOptionProps> = ({
|
||||
application,
|
||||
note,
|
||||
iconClassName,
|
||||
disabled,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const menuContainerRef = useRef<HTMLDivElement>(null)
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
@@ -38,6 +44,7 @@ const ChangeEditorOption: FunctionComponent<ChangeEditorOptionProps> = ({ applic
|
||||
setIsOpen(false)
|
||||
}
|
||||
}}
|
||||
disabled={disabled}
|
||||
ref={buttonRef}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import MenuItem from '../Menu/MenuItem'
|
||||
|
||||
type DeletePermanentlyButtonProps = {
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
export const DeletePermanentlyButton = ({ onClick }: DeletePermanentlyButtonProps) => (
|
||||
<MenuItem onClick={onClick}>
|
||||
<Icon type="close" className="mr-2 text-danger" />
|
||||
<span className="text-danger">Delete permanently</span>
|
||||
</MenuItem>
|
||||
)
|
||||
@@ -30,7 +30,6 @@ import { KeyboardShortcutIndicator } from '../KeyboardShortcutIndicator/Keyboard
|
||||
import { NoteAttributes } from './NoteAttributes'
|
||||
import { SpellcheckOptions } from './SpellcheckOptions'
|
||||
import { NoteSizeWarning } from './NoteSizeWarning'
|
||||
import { DeletePermanentlyButton } from './DeletePermanentlyButton'
|
||||
import { useCommandService } from '../CommandProvider'
|
||||
import { iconClass } from './ClassNames'
|
||||
import SuperNoteOptions from './SuperNoteOptions'
|
||||
@@ -209,6 +208,17 @@ const NotesOptions = ({ notes, closeMenu }: NotesOptionsProps) => {
|
||||
}
|
||||
|
||||
const areSomeNotesInSharedVault = notes.some((note) => application.vaults.getItemVault(note)?.isSharedVaultListing())
|
||||
const areSomeNotesInReadonlySharedVault = notes.some((note) => {
|
||||
const vault = application.vaults.getItemVault(note)
|
||||
return vault?.isSharedVaultListing() && application.vaultUsers.isCurrentUserReadonlyVaultMember(vault)
|
||||
})
|
||||
const hasAdminPermissionForAllSharedNotes = notes.every((note) => {
|
||||
const vault = application.vaults.getItemVault(note)
|
||||
if (!vault?.isSharedVaultListing()) {
|
||||
return true
|
||||
}
|
||||
return application.vaultUsers.isCurrentUserSharedVaultAdmin(vault)
|
||||
})
|
||||
|
||||
if (notes.length === 0) {
|
||||
return null
|
||||
@@ -220,13 +230,13 @@ const NotesOptions = ({ notes, closeMenu }: NotesOptionsProps) => {
|
||||
<>
|
||||
{notes.length === 1 && (
|
||||
<>
|
||||
<MenuItem onClick={openRevisionHistoryModal}>
|
||||
<MenuItem onClick={openRevisionHistoryModal} disabled={areSomeNotesInReadonlySharedVault}>
|
||||
<Icon type="history" className={iconClass} />
|
||||
Note history
|
||||
{historyShortcut && <KeyboardShortcutIndicator className="ml-auto" shortcut={historyShortcut} />}
|
||||
</MenuItem>
|
||||
<HorizontalSeparator classes="my-2" />
|
||||
<MenuItem onClick={toggleLineWidthModal}>
|
||||
<MenuItem onClick={toggleLineWidthModal} disabled={areSomeNotesInReadonlySharedVault}>
|
||||
<Icon type="line-width" className={iconClass} />
|
||||
Editor width
|
||||
{editorWidthShortcut && <KeyboardShortcutIndicator className="ml-auto" shortcut={editorWidthShortcut} />}
|
||||
@@ -238,6 +248,7 @@ const NotesOptions = ({ notes, closeMenu }: NotesOptionsProps) => {
|
||||
onChange={(locked) => {
|
||||
application.notesController.setLockSelectedNotes(locked)
|
||||
}}
|
||||
disabled={areSomeNotesInReadonlySharedVault}
|
||||
>
|
||||
<Icon type="pencil-off" className={iconClass} />
|
||||
Prevent editing
|
||||
@@ -247,6 +258,7 @@ const NotesOptions = ({ notes, closeMenu }: NotesOptionsProps) => {
|
||||
onChange={(hidePreviews) => {
|
||||
application.notesController.setHideSelectedNotePreviews(!hidePreviews)
|
||||
}}
|
||||
disabled={areSomeNotesInReadonlySharedVault}
|
||||
>
|
||||
<Icon type="rich-text" className={iconClass} />
|
||||
Show preview
|
||||
@@ -256,6 +268,7 @@ const NotesOptions = ({ notes, closeMenu }: NotesOptionsProps) => {
|
||||
onChange={(protect) => {
|
||||
application.notesController.setProtectSelectedNotes(protect).catch(console.error)
|
||||
}}
|
||||
disabled={areSomeNotesInReadonlySharedVault}
|
||||
>
|
||||
<Icon type="lock" className={iconClass} />
|
||||
Password protect
|
||||
@@ -263,13 +276,18 @@ const NotesOptions = ({ notes, closeMenu }: NotesOptionsProps) => {
|
||||
{notes.length === 1 && (
|
||||
<>
|
||||
<HorizontalSeparator classes="my-2" />
|
||||
<ChangeEditorOption iconClassName={iconClass} application={application} note={notes[0]} />
|
||||
<ChangeEditorOption
|
||||
iconClassName={iconClass}
|
||||
application={application}
|
||||
note={notes[0]}
|
||||
disabled={areSomeNotesInReadonlySharedVault}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<HorizontalSeparator classes="my-2" />
|
||||
|
||||
{application.featuresController.isEntitledToVaults() && (
|
||||
<AddToVaultMenuOption iconClassName={iconClass} items={notes} />
|
||||
<AddToVaultMenuOption iconClassName={iconClass} items={notes} disabled={!hasAdminPermissionForAllSharedNotes} />
|
||||
)}
|
||||
|
||||
{application.navigationController.tagsCount > 0 && (
|
||||
@@ -278,12 +296,14 @@ const NotesOptions = ({ notes, closeMenu }: NotesOptionsProps) => {
|
||||
navigationController={application.navigationController}
|
||||
selectedItems={notes}
|
||||
linkingController={application.linkingController}
|
||||
disabled={areSomeNotesInReadonlySharedVault}
|
||||
/>
|
||||
)}
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
application.notesController.setStarSelectedNotes(!starred)
|
||||
}}
|
||||
disabled={areSomeNotesInReadonlySharedVault}
|
||||
>
|
||||
<Icon type="star" className={iconClass} />
|
||||
{starred ? 'Unstar' : 'Star'}
|
||||
@@ -295,6 +315,7 @@ const NotesOptions = ({ notes, closeMenu }: NotesOptionsProps) => {
|
||||
onClick={() => {
|
||||
application.notesController.setPinSelectedNotes(true)
|
||||
}}
|
||||
disabled={areSomeNotesInReadonlySharedVault}
|
||||
>
|
||||
<Icon type="pin" className={iconClass} />
|
||||
Pin to top
|
||||
@@ -306,6 +327,7 @@ const NotesOptions = ({ notes, closeMenu }: NotesOptionsProps) => {
|
||||
onClick={() => {
|
||||
application.notesController.setPinSelectedNotes(false)
|
||||
}}
|
||||
disabled={areSomeNotesInReadonlySharedVault}
|
||||
>
|
||||
<Icon type="unpin" className={iconClass} />
|
||||
Unpin
|
||||
@@ -382,7 +404,7 @@ const NotesOptions = ({ notes, closeMenu }: NotesOptionsProps) => {
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<MenuItem onClick={duplicateSelectedItems}>
|
||||
<MenuItem onClick={duplicateSelectedItems} disabled={areSomeNotesInReadonlySharedVault}>
|
||||
<Icon type="copy" className={iconClass} />
|
||||
Duplicate
|
||||
</MenuItem>
|
||||
@@ -392,6 +414,7 @@ const NotesOptions = ({ notes, closeMenu }: NotesOptionsProps) => {
|
||||
await application.notesController.setArchiveSelectedNotes(true).catch(console.error)
|
||||
closeMenuAndToggleNotesList()
|
||||
}}
|
||||
disabled={areSomeNotesInReadonlySharedVault}
|
||||
>
|
||||
<Icon type="archive" className={iconClassWarning} />
|
||||
<span className="text-warning">Archive</span>
|
||||
@@ -403,6 +426,7 @@ const NotesOptions = ({ notes, closeMenu }: NotesOptionsProps) => {
|
||||
await application.notesController.setArchiveSelectedNotes(false).catch(console.error)
|
||||
closeMenuAndToggleNotesList()
|
||||
}}
|
||||
disabled={areSomeNotesInReadonlySharedVault}
|
||||
>
|
||||
<Icon type="unarchive" className={iconClassWarning} />
|
||||
<span className="text-warning">Unarchive</span>
|
||||
@@ -410,18 +434,23 @@ const NotesOptions = ({ notes, closeMenu }: NotesOptionsProps) => {
|
||||
)}
|
||||
{notTrashed &&
|
||||
(altKeyDown ? (
|
||||
<DeletePermanentlyButton
|
||||
<MenuItem
|
||||
disabled={areSomeNotesInReadonlySharedVault}
|
||||
onClick={async () => {
|
||||
await application.notesController.deleteNotesPermanently()
|
||||
closeMenuAndToggleNotesList()
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<Icon type="close" className="mr-2 text-danger" />
|
||||
<span className="text-danger">Delete permanently</span>
|
||||
</MenuItem>
|
||||
) : (
|
||||
<MenuItem
|
||||
onClick={async () => {
|
||||
await application.notesController.setTrashSelectedNotes(true)
|
||||
closeMenuAndToggleNotesList()
|
||||
}}
|
||||
disabled={areSomeNotesInReadonlySharedVault}
|
||||
>
|
||||
<Icon type="trash" className={iconClassDanger} />
|
||||
<span className="text-danger">Move to trash</span>
|
||||
@@ -434,21 +463,27 @@ const NotesOptions = ({ notes, closeMenu }: NotesOptionsProps) => {
|
||||
await application.notesController.setTrashSelectedNotes(false)
|
||||
closeMenuAndToggleNotesList()
|
||||
}}
|
||||
disabled={areSomeNotesInReadonlySharedVault}
|
||||
>
|
||||
<Icon type="restore" className={iconClassSuccess} />
|
||||
<span className="text-success">Restore</span>
|
||||
</MenuItem>
|
||||
<DeletePermanentlyButton
|
||||
<MenuItem
|
||||
disabled={areSomeNotesInReadonlySharedVault}
|
||||
onClick={async () => {
|
||||
await application.notesController.deleteNotesPermanently()
|
||||
closeMenuAndToggleNotesList()
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<Icon type="close" className="mr-2 text-danger" />
|
||||
<span className="text-danger">Delete permanently</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={async () => {
|
||||
await application.notesController.emptyTrash()
|
||||
closeMenuAndToggleNotesList()
|
||||
}}
|
||||
disabled={areSomeNotesInReadonlySharedVault}
|
||||
>
|
||||
<div className="flex items-start">
|
||||
<Icon type="trash-sweep" className="mr-2 text-danger" />
|
||||
@@ -485,16 +520,19 @@ const NotesOptions = ({ notes, closeMenu }: NotesOptionsProps) => {
|
||||
editorForNote={editorForNote}
|
||||
notesController={application.notesController}
|
||||
note={notes[0]}
|
||||
disabled={areSomeNotesInReadonlySharedVault}
|
||||
/>
|
||||
)}
|
||||
|
||||
<HorizontalSeparator classes="my-2" />
|
||||
|
||||
<NoteAttributes application={application} note={notes[0]} />
|
||||
<NoteAttributes className="mb-2" application={application} note={notes[0]} />
|
||||
|
||||
<NoteSizeWarning note={notes[0]} />
|
||||
</>
|
||||
) : null}
|
||||
) : (
|
||||
<div className="h-2" />
|
||||
)}
|
||||
|
||||
<ModalOverlay isOpen={showExportSuperModal} close={closeSuperExportModal}>
|
||||
<SuperExportModal exportNotes={downloadSelectedItems} close={closeSuperExportModal} />
|
||||
|
||||
@@ -9,7 +9,8 @@ export const SpellcheckOptions: FunctionComponent<{
|
||||
editorForNote: UIFeature<EditorFeatureDescription | IframeComponentFeatureDescription>
|
||||
notesController: NotesController
|
||||
note: SNNote
|
||||
}> = ({ editorForNote, notesController, note }) => {
|
||||
disabled?: boolean
|
||||
}> = ({ editorForNote, notesController, note, disabled }) => {
|
||||
const spellcheckControllable = editorForNote.featureDescription.spellcheckControl
|
||||
const noteSpellcheck = !spellcheckControllable
|
||||
? true
|
||||
@@ -24,7 +25,7 @@ export const SpellcheckOptions: FunctionComponent<{
|
||||
onChange={() => {
|
||||
notesController.toggleGlobalSpellcheckForNote(note).catch(console.error)
|
||||
}}
|
||||
disabled={!spellcheckControllable}
|
||||
disabled={disabled || !spellcheckControllable}
|
||||
>
|
||||
<Icon type="notes" className={iconClass} />
|
||||
Spellcheck
|
||||
|
||||
+8
-1
@@ -22,7 +22,14 @@ const ContactItem = ({ contact }: Props) => {
|
||||
|
||||
const deleteContact = useCallback(async () => {
|
||||
if (await application.alerts.confirm('Are you sure you want to delete this contact?')) {
|
||||
void application.contacts.deleteContact(contact)
|
||||
const result = await application.contacts.deleteContact(contact)
|
||||
if (result.isFailed()) {
|
||||
application.alerts
|
||||
.alertV2({
|
||||
text: result.getError(),
|
||||
})
|
||||
.catch(console.error)
|
||||
}
|
||||
}
|
||||
}, [application.alerts, application.contacts, contact])
|
||||
|
||||
|
||||
+1
-1
@@ -130,7 +130,7 @@ const ContactInviteModal: FunctionComponent<Props> = ({ vault, onCloseDialog })
|
||||
wrapper: 'col-start-2',
|
||||
}}
|
||||
items={Object.keys(SharedVaultUserPermission.PERMISSIONS).map((key) => ({
|
||||
label: key === 'Write' ? 'Read / Write' : key,
|
||||
label: application.vaultUsers.getFormattedMemberPermission(key.toLowerCase()),
|
||||
value: key,
|
||||
}))}
|
||||
value={selectedContact.permission}
|
||||
|
||||
+2
-6
@@ -2,11 +2,10 @@ import { useApplication } from '@/Components/ApplicationProvider'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import ModalOverlay from '@/Components/Modal/ModalOverlay'
|
||||
import { InviteRecord, SharedVaultUserPermission } from '@standardnotes/snjs'
|
||||
import { InviteRecord } from '@standardnotes/snjs'
|
||||
import { useCallback, useState } from 'react'
|
||||
import EditContactModal from '../Contacts/EditContactModal'
|
||||
import { CheckmarkCircle } from '../../../../UIElements/CheckmarkCircle'
|
||||
import { capitalizeString } from '@/Utils'
|
||||
|
||||
type Props = {
|
||||
inviteRecord: InviteRecord
|
||||
@@ -36,10 +35,7 @@ const InviteItem = ({ inviteRecord }: Props) => {
|
||||
|
||||
const trustedContact = application.contacts.findSenderContactForInvite(inviteRecord.invite)
|
||||
|
||||
const permission =
|
||||
inviteRecord.invite.permission === SharedVaultUserPermission.PERMISSIONS.Write
|
||||
? 'Read / Write'
|
||||
: capitalizeString(inviteRecord.invite.permission)
|
||||
const permission = application.vaultUsers.getFormattedMemberPermission(inviteRecord.invite.permission)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -15,6 +15,8 @@ import {
|
||||
SharedVaultServiceEvent,
|
||||
VaultUserServiceEvent,
|
||||
RoleName,
|
||||
ProtocolVersion,
|
||||
compareVersions,
|
||||
} from '@standardnotes/snjs'
|
||||
import VaultItem from './Vaults/VaultItem'
|
||||
import Button from '@/Components/Button/Button'
|
||||
@@ -24,7 +26,7 @@ import PreferencesPane from '../../PreferencesComponents/PreferencesPane'
|
||||
import { ToastType, addToast } from '@standardnotes/toast'
|
||||
import NoProSubscription from '../Account/NoProSubscription'
|
||||
|
||||
const Vaults = () => {
|
||||
const Vaults = observer(() => {
|
||||
const application = useApplication()
|
||||
|
||||
const hasAccount = application.hasAccount()
|
||||
@@ -136,13 +138,11 @@ const Vaults = () => {
|
||||
<ModalOverlay isOpen={isAddContactModalOpen} close={closeAddContactModal}>
|
||||
<EditContactModal onCloseDialog={closeAddContactModal} />
|
||||
</ModalOverlay>
|
||||
|
||||
<EditVaultModal
|
||||
isVaultModalOpen={isVaultModalOpen}
|
||||
creatingSharedVault={isCreatingSharedVault}
|
||||
closeVaultModal={closeVaultModal}
|
||||
/>
|
||||
|
||||
{invites.length > 0 && (
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
@@ -155,7 +155,6 @@ const Vaults = () => {
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
)}
|
||||
|
||||
{hasAccount && (
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
@@ -173,33 +172,6 @@ const Vaults = () => {
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
)}
|
||||
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
<Title>Vaults</Title>
|
||||
{vaults.length > 0 && (
|
||||
<div className="my-2 flex flex-col gap-3.5">
|
||||
{vaults.map((vault) => {
|
||||
return <VaultItem vault={vault} key={vault.uuid} />
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{canCreateMoreVaults ? (
|
||||
<div className="mt-2.5 flex gap-3">
|
||||
<Button label="Create Vault" onClick={createNewVault} />
|
||||
<Button label="Create Shared Vault" onClick={createNewSharedVault} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-3.5">
|
||||
<NoProSubscription
|
||||
application={application}
|
||||
text={<span>Please upgrade in order to increase your shared vault limit.</span>}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
|
||||
{hasAccount && (
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
@@ -242,8 +214,59 @@ const Vaults = () => {
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
)}
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
<Title>Vaults</Title>
|
||||
{vaults.length > 0 && (
|
||||
<div className="my-2 flex flex-col gap-3.5">
|
||||
{vaults.map((vault) => {
|
||||
return <VaultItem vault={vault} key={vault.uuid} />
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{canCreateMoreVaults ? (
|
||||
<div className="mt-2.5 flex gap-3">
|
||||
<Button label="Create Vault" onClick={createNewVault} />
|
||||
{hasAccount && <Button label="Create Shared Vault" onClick={createNewSharedVault} />}
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-3.5">
|
||||
<NoProSubscription
|
||||
application={application}
|
||||
text={<span>Please upgrade in order to increase your shared vault limit.</span>}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
</PreferencesPane>
|
||||
)
|
||||
})
|
||||
|
||||
const VaultsWrapper = () => {
|
||||
const application = useApplication()
|
||||
const hasAccount = application.hasAccount()
|
||||
const accountProtocolVersion = application.getUserVersion()
|
||||
const isAccountProtocolNotSupported =
|
||||
accountProtocolVersion && compareVersions(accountProtocolVersion, ProtocolVersion.V004) < 0
|
||||
|
||||
if (hasAccount && isAccountProtocolNotSupported) {
|
||||
return (
|
||||
<PreferencesPane>
|
||||
<PreferencesGroup>
|
||||
<Title>Account update required</Title>
|
||||
<Subtitle>
|
||||
In order to use Vaults, you must update your account to use the latest data encryption version.
|
||||
</Subtitle>
|
||||
<Button primary className="mt-3" onClick={() => application.upgradeProtocolVersion().catch(console.error)}>
|
||||
Update Account
|
||||
</Button>
|
||||
</PreferencesGroup>
|
||||
</PreferencesPane>
|
||||
)
|
||||
}
|
||||
|
||||
return <Vaults />
|
||||
}
|
||||
|
||||
export default observer(Vaults)
|
||||
export default observer(VaultsWrapper)
|
||||
|
||||
+4
-3
@@ -23,7 +23,8 @@ const VaultItem = ({ vault }: Props) => {
|
||||
const [isVaultModalOpen, setIsVaultModalOpen] = useState(false)
|
||||
const closeVaultModal = () => setIsVaultModalOpen(false)
|
||||
|
||||
const { isCurrentUserAdmin, isLocked, canShowLockOption, toggleLock, ensureVaultIsUnlocked } = useVault(vault)
|
||||
const { isCurrentUserAdmin, isCurrentUserOwner, isLocked, canShowLockOption, toggleLock, ensureVaultIsUnlocked } =
|
||||
useVault(vault)
|
||||
|
||||
const deleteVault = useCallback(async () => {
|
||||
const confirm = await application.alerts.confirm(
|
||||
@@ -122,8 +123,8 @@ const VaultItem = ({ vault }: Props) => {
|
||||
<div className="mt-2 flex w-full flex-wrap gap-3">
|
||||
<Button label="Edit" onClick={openEditModal} />
|
||||
{canShowLockOption && <Button label={isLocked ? 'Unlock' : 'Lock'} onClick={toggleLock} />}
|
||||
{isCurrentUserAdmin && <Button colorStyle="danger" label="Delete" onClick={deleteVault} />}
|
||||
{!isCurrentUserAdmin && vault.isSharedVaultListing() && <Button label="Leave Vault" onClick={leaveVault} />}
|
||||
{isCurrentUserOwner && <Button colorStyle="danger" label="Delete" onClick={deleteVault} />}
|
||||
{!isCurrentUserOwner && vault.isSharedVaultListing() && <Button label="Leave Vault" onClick={leaveVault} />}
|
||||
{isCurrentUserAdmin ? (
|
||||
vault.isSharedVaultListing() ? (
|
||||
<Button colorStyle="info" label="Invite Contacts" onClick={openInviteModal} />
|
||||
|
||||
+2
-2
@@ -66,11 +66,11 @@ const EditVaultModalContent: FunctionComponent<{
|
||||
|
||||
if (existingVault.isSharedVaultListing()) {
|
||||
setIsAdmin(
|
||||
existingVault.isSharedVaultListing() && application.vaultUsers.isCurrentUserSharedVaultOwner(existingVault),
|
||||
existingVault.isSharedVaultListing() && application.vaultUsers.isCurrentUserSharedVaultAdmin(existingVault),
|
||||
)
|
||||
|
||||
setIsLoadingCollaborationInfo(true)
|
||||
const users = await application.vaultUsers.getSharedVaultUsers(existingVault)
|
||||
const users = await application.vaultUsers.getSharedVaultUsersFromServer(existingVault)
|
||||
if (users) {
|
||||
setMembers(users)
|
||||
}
|
||||
|
||||
+2
-6
@@ -1,9 +1,8 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useApplication } from '@/Components/ApplicationProvider'
|
||||
import { SharedVaultInviteServerHash, SharedVaultUserPermission } from '@standardnotes/snjs'
|
||||
import { SharedVaultInviteServerHash } from '@standardnotes/snjs'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import { capitalizeString } from '@/Utils'
|
||||
|
||||
export const VaultModalInvites = ({
|
||||
invites,
|
||||
@@ -30,10 +29,7 @@ export const VaultModalInvites = ({
|
||||
<div className="space-y-3.5">
|
||||
{invites.map((invite) => {
|
||||
const contact = application.contacts.findContactForInvite(invite)
|
||||
const permission =
|
||||
invite.permission === SharedVaultUserPermission.PERMISSIONS.Write
|
||||
? 'Read / Write'
|
||||
: capitalizeString(invite.permission)
|
||||
const permission = application.vaultUsers.getFormattedMemberPermission(invite.permission)
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
+2
-6
@@ -1,9 +1,8 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useApplication } from '@/Components/ApplicationProvider'
|
||||
import { SharedVaultUserPermission, SharedVaultUserServerHash, VaultListingInterface } from '@standardnotes/snjs'
|
||||
import { SharedVaultUserServerHash, VaultListingInterface } from '@standardnotes/snjs'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import { capitalizeString } from '@/Utils'
|
||||
|
||||
export const VaultModalMembers = ({
|
||||
members,
|
||||
@@ -35,10 +34,7 @@ export const VaultModalMembers = ({
|
||||
{members.map((member) => {
|
||||
const isMemberVaultOwner = application.vaultUsers.isVaultUserOwner(member)
|
||||
const contact = application.contacts.findContactForServerUser(member)
|
||||
const permission =
|
||||
member.permission === SharedVaultUserPermission.PERMISSIONS.Write
|
||||
? 'Read / Write'
|
||||
: capitalizeString(member.permission)
|
||||
const permission = application.vaultUsers.getFormattedMemberPermission(member.permission)
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
+5
@@ -25,6 +25,11 @@ const CodeOptionsPlugin = () => {
|
||||
const [selectedElementKey, setSelectedElementKey] = useState<NodeKey | null>(null)
|
||||
|
||||
const updateToolbar = useCallback(() => {
|
||||
if (!editor.isEditable()) {
|
||||
setIsCode(false)
|
||||
return
|
||||
}
|
||||
|
||||
const selection = $getSelection()
|
||||
if (!$isRangeSelection(selection)) {
|
||||
return
|
||||
|
||||
+7
-7
@@ -372,17 +372,17 @@ const MobileToolbarPlugin = () => {
|
||||
}, [editor, updateEditorSelection])
|
||||
|
||||
const isFocusInEditorOrToolbar = isInEditor || isInToolbar || isInLinkEditor
|
||||
if (!isMobile || !isFocusInEditorOrToolbar) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{modal}
|
||||
<div className="bg-contrast" id="super-mobile-toolbar">
|
||||
<div
|
||||
className={classNames('bg-contrast', !isMobile || !isFocusInEditorOrToolbar ? 'hidden' : '')}
|
||||
id="super-mobile-toolbar"
|
||||
>
|
||||
{isSelectionLink && (
|
||||
<div
|
||||
className="border-t border-border px-2 focus:outline-none focus:shadow-none"
|
||||
className="border-t border-border px-2 focus:shadow-none focus:outline-none"
|
||||
ref={linkEditorRef}
|
||||
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
|
||||
>
|
||||
@@ -406,7 +406,7 @@ const MobileToolbarPlugin = () => {
|
||||
return (
|
||||
<StyledTooltip showOnMobile showOnHover label={item.name} key={item.name}>
|
||||
<button
|
||||
className="flex items-center justify-center rounded p-0.5 disabled:opacity-50 select-none hover:bg-default"
|
||||
className="flex select-none items-center justify-center rounded p-0.5 hover:bg-default disabled:opacity-50"
|
||||
aria-label={item.name}
|
||||
onMouseDown={(event) => {
|
||||
event.preventDefault()
|
||||
@@ -420,7 +420,7 @@ const MobileToolbarPlugin = () => {
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
'flex items-center justify-center p-2 rounded transition-colors duration-75',
|
||||
'flex items-center justify-center rounded p-2 transition-colors duration-75',
|
||||
item.active && 'bg-info text-info-contrast',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -58,6 +58,7 @@ type Props = {
|
||||
linkingController: LinkingController
|
||||
filesController: FilesController
|
||||
spellcheck: boolean
|
||||
readonly?: boolean
|
||||
}
|
||||
|
||||
export const SuperEditor: FunctionComponent<Props> = ({
|
||||
@@ -66,6 +67,7 @@ export const SuperEditor: FunctionComponent<Props> = ({
|
||||
filesController,
|
||||
spellcheck,
|
||||
controller,
|
||||
readonly,
|
||||
}) => {
|
||||
const note = useRef(controller.item)
|
||||
const changeEditorFunction = useRef<ChangeEditorFunction>()
|
||||
@@ -217,7 +219,7 @@ export const SuperEditor: FunctionComponent<Props> = ({
|
||||
<ErrorBoundary>
|
||||
<LinkingControllerProvider controller={linkingController}>
|
||||
<FilesControllerProvider controller={filesController}>
|
||||
<BlocksEditorComposer readonly={note.current.locked} initialValue={note.current.text}>
|
||||
<BlocksEditorComposer readonly={note.current.locked || readonly} initialValue={note.current.text}>
|
||||
<BlocksEditor
|
||||
onChange={handleChange}
|
||||
className={classNames(
|
||||
@@ -227,6 +229,7 @@ export const SuperEditor: FunctionComponent<Props> = ({
|
||||
)}
|
||||
previewLength={SuperNotePreviewCharLimit}
|
||||
spellcheck={spellcheck}
|
||||
readonly={note.current.locked || readonly}
|
||||
>
|
||||
<ItemSelectionPlugin currentNote={note.current} />
|
||||
<FilePlugin currentNote={note.current} />
|
||||
@@ -242,7 +245,7 @@ export const SuperEditor: FunctionComponent<Props> = ({
|
||||
<NodeObserverPlugin nodeType={BubbleNode} onRemove={handleBubbleRemove} />
|
||||
<NodeObserverPlugin nodeType={FileNode} onRemove={handleBubbleRemove} />
|
||||
<ExportPlugin />
|
||||
<ReadonlyPlugin note={note.current} />
|
||||
{readonly === undefined && <ReadonlyPlugin note={note.current} />}
|
||||
<AutoFocusPlugin isEnabled={controller.isTemplateNote} />
|
||||
<SuperSearchContextProvider>
|
||||
<SearchPlugin />
|
||||
|
||||
@@ -22,7 +22,10 @@ const VaultMenu = observer(({ items }: { items: DecryptedItemInterface[] }) => {
|
||||
}
|
||||
|
||||
for (const item of items) {
|
||||
await application.vaults.moveItemToVault(vault, item)
|
||||
const result = await application.vaults.moveItemToVault(vault, item)
|
||||
if (result.isFailed()) {
|
||||
console.error(result.getError())
|
||||
}
|
||||
}
|
||||
},
|
||||
[application, items],
|
||||
@@ -78,6 +81,7 @@ const VaultMenu = observer(({ items }: { items: DecryptedItemInterface[] }) => {
|
||||
doesVaultContainItems(vault) ? void removeItemsFromVault() : void addItemsToVault(vault)
|
||||
}}
|
||||
className={doesVaultContainItems(vault) ? 'font-bold' : ''}
|
||||
disabled={vault.isSharedVaultListing() && application.vaultUsers.isCurrentUserReadonlyVaultMember(vault)}
|
||||
>
|
||||
<Icon
|
||||
type={vault.iconString}
|
||||
@@ -98,7 +102,15 @@ const VaultMenu = observer(({ items }: { items: DecryptedItemInterface[] }) => {
|
||||
)
|
||||
})
|
||||
|
||||
const AddToVaultMenuOption = ({ iconClassName, items }: { iconClassName: string; items: DecryptedItemInterface[] }) => {
|
||||
const AddToVaultMenuOption = ({
|
||||
iconClassName,
|
||||
items,
|
||||
disabled,
|
||||
}: {
|
||||
iconClassName: string
|
||||
items: DecryptedItemInterface[]
|
||||
disabled?: boolean
|
||||
}) => {
|
||||
const application = useApplication()
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
@@ -123,6 +135,7 @@ const AddToVaultMenuOption = ({ iconClassName, items }: { iconClassName: string;
|
||||
}
|
||||
}}
|
||||
ref={buttonRef}
|
||||
disabled={disabled}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<Icon type="safe-square" className={iconClassName} />
|
||||
|
||||
@@ -439,11 +439,7 @@ export class ItemListController
|
||||
}
|
||||
|
||||
private shouldLeaveSelectionUnchanged = (activeController: NoteViewController | FileViewController | undefined) => {
|
||||
const hasMultipleItemsSelected = this.selectedItemsCount >= 2
|
||||
|
||||
return (
|
||||
hasMultipleItemsSelected || (activeController instanceof NoteViewController && activeController.isTemplateNote)
|
||||
)
|
||||
return activeController instanceof NoteViewController && activeController.isTemplateNote
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
InternalFeatureService,
|
||||
InternalFeature,
|
||||
PreferenceServiceInterface,
|
||||
Result,
|
||||
} from '@standardnotes/snjs'
|
||||
import { FilesController } from './FilesController'
|
||||
import { ItemListController } from './ItemList/ItemListController'
|
||||
@@ -246,7 +247,7 @@ describe('LinkingController', () => {
|
||||
|
||||
application.mutator.associateFileWithNote = jest.fn().mockReturnValue({})
|
||||
|
||||
const moveToVaultSpy = (application.vaults.moveItemToVault = jest.fn())
|
||||
const moveToVaultSpy = (application.vaults.moveItemToVault = jest.fn().mockReturnValue(Result.ok()))
|
||||
|
||||
const note = createNote('test', {
|
||||
uuid: 'note',
|
||||
|
||||
@@ -213,7 +213,10 @@ export class LinkingController extends AbstractViewController implements Interna
|
||||
const noteVault = this.vaults.getItemVault(note)
|
||||
const fileVault = this.vaults.getItemVault(updatedFile)
|
||||
if (noteVault && !fileVault) {
|
||||
await this.vaults.moveItemToVault(noteVault, file)
|
||||
const result = await this.vaults.moveItemToVault(noteVault, file)
|
||||
if (result.isFailed()) {
|
||||
console.error(result.getError())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -302,7 +305,19 @@ export class LinkingController extends AbstractViewController implements Interna
|
||||
|
||||
const activeItem = this.activeItem
|
||||
if (activeItem) {
|
||||
await this.addTagToItem(newTag, activeItem)
|
||||
const itemVault = this.vaults.getItemVault(activeItem)
|
||||
|
||||
if (itemVault) {
|
||||
const movedTagOrError = await this.vaults.moveItemToVault(itemVault, newTag)
|
||||
if (movedTagOrError.isFailed()) {
|
||||
throw new Error(`Failed to move tag to item vault: ${movedTagOrError.getError()}`)
|
||||
}
|
||||
const movedTag = movedTagOrError.getValue()
|
||||
|
||||
await this.addTagToItem(movedTag as SNTag, activeItem)
|
||||
} else {
|
||||
await this.addTagToItem(newTag, activeItem)
|
||||
}
|
||||
}
|
||||
|
||||
return newTag
|
||||
|
||||
@@ -37,6 +37,9 @@ export const useVault = (vault: VaultListingInterface) => {
|
||||
}, [application.vaultDisplayService, application.vaultLocks, canShowLockOption, isLocked, vault])
|
||||
|
||||
const isCurrentUserAdmin = !vault.isSharedVaultListing()
|
||||
? true
|
||||
: application.vaultUsers.isCurrentUserSharedVaultAdmin(vault)
|
||||
const isCurrentUserOwner = !vault.isSharedVaultListing()
|
||||
? true
|
||||
: application.vaultUsers.isCurrentUserSharedVaultOwner(vault)
|
||||
|
||||
@@ -54,5 +57,6 @@ export const useVault = (vault: VaultListingInterface) => {
|
||||
toggleLock,
|
||||
ensureVaultIsUnlocked,
|
||||
isCurrentUserAdmin,
|
||||
isCurrentUserOwner,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4138,7 +4138,7 @@ __metadata:
|
||||
resolution: "@standardnotes/api@workspace:packages/api"
|
||||
dependencies:
|
||||
"@standardnotes/common": ^1.50.0
|
||||
"@standardnotes/domain-core": ^1.28.0
|
||||
"@standardnotes/domain-core": ^1.32.0
|
||||
"@standardnotes/models": "workspace:*"
|
||||
"@standardnotes/responses": "workspace:*"
|
||||
"@standardnotes/utils": "workspace:*"
|
||||
@@ -4288,7 +4288,7 @@ __metadata:
|
||||
"@babel/core": "*"
|
||||
"@babel/preset-env": "*"
|
||||
"@electron/remote": ^2.0.9
|
||||
"@standardnotes/domain-core": ^1.28.0
|
||||
"@standardnotes/domain-core": ^1.32.0
|
||||
"@standardnotes/electron-clear-data": 1.1.1
|
||||
"@standardnotes/web": "workspace:*"
|
||||
"@types/fs-extra": ^11.0.1
|
||||
@@ -4340,12 +4340,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@standardnotes/domain-core@npm:^1.28.0":
|
||||
version: 1.28.0
|
||||
resolution: "@standardnotes/domain-core@npm:1.28.0"
|
||||
"@standardnotes/domain-core@npm:^1.32.0":
|
||||
version: 1.32.0
|
||||
resolution: "@standardnotes/domain-core@npm:1.32.0"
|
||||
dependencies:
|
||||
uuid: ^9.0.0
|
||||
checksum: 9ee58eea35ed5d988513c5a24f751b9c3a2be2a324c30684db54ecbd16f0c8e19a80b8e464bde46e86905590085923c7e1abbe1b792a4ef0a19c1171e8f3b6c2
|
||||
checksum: 7d1263782caae6e7aefb245243f3ddc830efec707e2903d3dcb52bd5404d2c9464d3a24c2d4e04671fd9577b9c02bb8e9270b2571fe853b9469889c4b76c67a4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4392,7 +4392,7 @@ __metadata:
|
||||
dependencies:
|
||||
"@standardnotes/common": ^1.50.0
|
||||
"@standardnotes/config": 2.4.3
|
||||
"@standardnotes/domain-core": ^1.28.0
|
||||
"@standardnotes/domain-core": ^1.32.0
|
||||
"@standardnotes/models": "workspace:*"
|
||||
"@standardnotes/responses": "workspace:*"
|
||||
"@standardnotes/sncrypto-common": "workspace:*"
|
||||
@@ -4427,7 +4427,7 @@ __metadata:
|
||||
resolution: "@standardnotes/features@workspace:packages/features"
|
||||
dependencies:
|
||||
"@standardnotes/common": ^1.50.0
|
||||
"@standardnotes/domain-core": ^1.28.0
|
||||
"@standardnotes/domain-core": ^1.32.0
|
||||
"@types/jest": ^29.2.3
|
||||
"@typescript-eslint/eslint-plugin": "*"
|
||||
eslint: "*"
|
||||
@@ -4633,7 +4633,7 @@ __metadata:
|
||||
resolution: "@standardnotes/models@workspace:packages/models"
|
||||
dependencies:
|
||||
"@standardnotes/common": ^1.50.0
|
||||
"@standardnotes/domain-core": ^1.28.0
|
||||
"@standardnotes/domain-core": ^1.32.0
|
||||
"@standardnotes/features": "workspace:*"
|
||||
"@standardnotes/responses": "workspace:*"
|
||||
"@standardnotes/sncrypto-common": "workspace:^"
|
||||
@@ -4729,7 +4729,7 @@ __metadata:
|
||||
dependencies:
|
||||
"@standardnotes/api": "workspace:^"
|
||||
"@standardnotes/common": ^1.50.0
|
||||
"@standardnotes/domain-core": ^1.28.0
|
||||
"@standardnotes/domain-core": ^1.32.0
|
||||
"@standardnotes/encryption": "workspace:^"
|
||||
"@standardnotes/features": "workspace:^"
|
||||
"@standardnotes/files": "workspace:^"
|
||||
@@ -4830,7 +4830,7 @@ __metadata:
|
||||
"@babel/preset-env": "*"
|
||||
"@standardnotes/api": "workspace:*"
|
||||
"@standardnotes/common": ^1.50.0
|
||||
"@standardnotes/domain-core": ^1.28.0
|
||||
"@standardnotes/domain-core": ^1.32.0
|
||||
"@standardnotes/domain-events": ^2.122.0
|
||||
"@standardnotes/encryption": "workspace:*"
|
||||
"@standardnotes/features": "workspace:*"
|
||||
@@ -4955,7 +4955,7 @@ __metadata:
|
||||
resolution: "@standardnotes/ui-services@workspace:packages/ui-services"
|
||||
dependencies:
|
||||
"@standardnotes/common": ^1.50.0
|
||||
"@standardnotes/domain-core": ^1.28.0
|
||||
"@standardnotes/domain-core": ^1.32.0
|
||||
"@standardnotes/features": "workspace:^"
|
||||
"@standardnotes/filepicker": "workspace:^"
|
||||
"@standardnotes/models": "workspace:^"
|
||||
|
||||
Reference in New Issue
Block a user