mirror of
https://github.com/standardnotes/app
synced 2026-09-20 12:13:48 -04:00
Compare commits
46
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8553914d08 | ||
|
|
18c821d8eb | ||
|
|
038e456c6a | ||
|
|
37abac2ec2 | ||
|
|
81532f2f20 | ||
|
|
2b89ad488f | ||
|
|
075d7f444d | ||
|
|
a2dc739686 | ||
|
|
fabce1f7ac | ||
|
|
3fbbacfc25 | ||
|
|
990939318f | ||
|
|
46e3cae804 | ||
|
|
e3f28421ff | ||
|
|
d22c164e5d | ||
|
|
5cf37037b7 | ||
|
|
9fc77d861e | ||
|
|
b80b04d374 | ||
|
|
b82eac09a8 | ||
|
|
7c9f0d3143 | ||
|
|
fb41f651ec | ||
|
|
7a43e2d368 | ||
|
|
49b3d2714f | ||
|
|
1540143dad | ||
|
|
8376381981 | ||
|
|
15e2c82e65 | ||
|
|
1c22c1ab0c | ||
|
|
27d2c95b5b | ||
|
|
dcb8024deb | ||
|
|
85d2415625 | ||
|
|
e2e7605414 | ||
|
|
158ca6ac6a | ||
|
|
a275a45753 | ||
|
|
8863ee82bb | ||
|
|
6a4b5c5cdc | ||
|
|
cd1669f56f | ||
|
|
037007f0ec | ||
|
|
e7ca12babe | ||
|
|
c4c33db1a4 | ||
|
|
734b986294 | ||
|
|
59db63f052 | ||
|
|
20226c3269 | ||
|
|
a1352d9f65 | ||
|
|
7d21046b51 | ||
|
|
a365c17c46 | ||
|
|
c1981334e1 | ||
|
|
1e3acd50e9 |
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -3,6 +3,55 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.16.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
# [1.16.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** add keyshare initiation for workspaces ([fabce1f](https://github.com/standardnotes/app/commit/fabce1f7aca5c9caaa5bb3908b2dc68063c690d0))
|
||||
|
||||
## [1.15.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
# [1.15.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** add listing workspace users ([fb41f65](https://github.com/standardnotes/app/commit/fb41f651eca45dd62314609c2f389d5dc683c36e))
|
||||
|
||||
# [1.14.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** add access level to invitation requests ([49b3d27](https://github.com/standardnotes/app/commit/49b3d2714fbe4ac9f9242097b8df6b4e3d604513))
|
||||
|
||||
# [1.13.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** add accepting invites to workspace ([15e2c82](https://github.com/standardnotes/app/commit/15e2c82e654ce3f983cd42b01f14d18435b3e939))
|
||||
* **api:** add listing workspaces ([8376381](https://github.com/standardnotes/app/commit/837638198112df24c30e53803a7ddf5f841975b0))
|
||||
|
||||
## [1.12.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
# [1.12.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api:** specs ([85d2415](https://github.com/standardnotes/app/commit/85d24156254015914c684f900c77c080caa52eb7))
|
||||
* **api:** workspace creation arguments ([a275a45](https://github.com/standardnotes/app/commit/a275a45753abc4a29a89de5fe784260165297bbe))
|
||||
* linter issues ([e2e7605](https://github.com/standardnotes/app/commit/e2e76054142e8d67cac69280e2a3efcb15cf67f1))
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** add inviting to workspace ([158ca6a](https://github.com/standardnotes/app/commit/158ca6ac6a428e082c8f984791f80fcaadcb3e34))
|
||||
|
||||
# [1.11.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api",
|
||||
"version": "1.11.0",
|
||||
"version": "1.16.1",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -36,7 +36,7 @@
|
||||
"typescript": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/common": "^1.32.0",
|
||||
"@standardnotes/common": "^1.39.0",
|
||||
"@standardnotes/encryption": "workspace:*",
|
||||
"@standardnotes/models": "workspace:*",
|
||||
"@standardnotes/responses": "workspace:*",
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
export enum WorkspaceApiOperations {
|
||||
Creating,
|
||||
Inviting,
|
||||
Accepting,
|
||||
ListingWorkspaces,
|
||||
ListingWorkspaceUsers,
|
||||
InitiatingKeyshare,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import { WorkspaceAccessLevel, WorkspaceType } from '@standardnotes/common'
|
||||
|
||||
import { HttpStatusCode } from '../../Http'
|
||||
import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse'
|
||||
import { WorkspaceInvitationAcceptingResponse } from '../../Response/Workspace/WorkspaceInvitationAcceptingResponse'
|
||||
import { WorkspaceInvitationResponse } from '../../Response/Workspace/WorkspaceInvitationResponse'
|
||||
import { WorkspaceListResponse } from '../../Response/Workspace/WorkspaceListResponse'
|
||||
import { WorkspaceUserListResponse } from '../../Response/Workspace/WorkspaceUserListResponse'
|
||||
import { WorkspaceServerInterface } from '../../Server/Workspace/WorkspaceServerInterface'
|
||||
import { WorkspaceKeyshareInitiatingResponse } from '../../Response/Workspace/WorkspaceKeyshareInitiatingResponse'
|
||||
|
||||
import { WorkspaceApiOperations } from './WorkspaceApiOperations'
|
||||
import { WorkspaceApiService } from './WorkspaceApiService'
|
||||
@@ -14,10 +22,29 @@ describe('WorkspaceApiService', () => {
|
||||
workspaceServer.createWorkspace = jest.fn().mockReturnValue({
|
||||
data: { uuid: '1-2-3' },
|
||||
} as jest.Mocked<WorkspaceCreationResponse>)
|
||||
workspaceServer.inviteToWorkspace = jest.fn().mockReturnValue({
|
||||
data: { uuid: 'i-1-2-3' },
|
||||
} as jest.Mocked<WorkspaceInvitationResponse>)
|
||||
workspaceServer.acceptInvite = jest.fn().mockReturnValue({
|
||||
data: { success: true },
|
||||
} as jest.Mocked<WorkspaceInvitationAcceptingResponse>)
|
||||
workspaceServer.listWorkspaces = jest.fn().mockReturnValue({
|
||||
status: HttpStatusCode.Success,
|
||||
data: { ownedWorkspaces: [], joinedWorkspaces: [] },
|
||||
} as jest.Mocked<WorkspaceListResponse>)
|
||||
workspaceServer.listWorkspaceUsers = jest.fn().mockReturnValue({
|
||||
status: HttpStatusCode.Success,
|
||||
data: { users: [] },
|
||||
} as jest.Mocked<WorkspaceUserListResponse>)
|
||||
workspaceServer.initiateKeyshare = jest.fn().mockReturnValue({
|
||||
status: HttpStatusCode.Success,
|
||||
data: { success: true },
|
||||
} as jest.Mocked<WorkspaceKeyshareInitiatingResponse>)
|
||||
})
|
||||
|
||||
it('should create a workspace', async () => {
|
||||
const response = await createService().createWorkspace({
|
||||
workspaceType: WorkspaceType.Private,
|
||||
encryptedPrivateKey: 'foo',
|
||||
encryptedWorkspaceKey: 'bar',
|
||||
publicKey: 'buzz',
|
||||
@@ -32,6 +59,7 @@ describe('WorkspaceApiService', () => {
|
||||
encryptedPrivateKey: 'foo',
|
||||
encryptedWorkspaceKey: 'bar',
|
||||
publicKey: 'buzz',
|
||||
workspaceType: 'private',
|
||||
})
|
||||
})
|
||||
|
||||
@@ -44,6 +72,7 @@ describe('WorkspaceApiService', () => {
|
||||
let error = null
|
||||
try {
|
||||
await service.createWorkspace({
|
||||
workspaceType: WorkspaceType.Private,
|
||||
encryptedPrivateKey: 'foo',
|
||||
encryptedWorkspaceKey: 'bar',
|
||||
publicKey: 'buzz',
|
||||
@@ -63,6 +92,7 @@ describe('WorkspaceApiService', () => {
|
||||
let error = null
|
||||
try {
|
||||
await createService().createWorkspace({
|
||||
workspaceType: WorkspaceType.Private,
|
||||
encryptedPrivateKey: 'foo',
|
||||
encryptedWorkspaceKey: 'bar',
|
||||
publicKey: 'buzz',
|
||||
@@ -73,4 +103,266 @@ describe('WorkspaceApiService', () => {
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should invite to a workspace', async () => {
|
||||
const response = await createService().inviteToWorkspace({
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
inviteeEmail: '[email protected]',
|
||||
accessLevel: WorkspaceAccessLevel.WriteAndRead,
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
uuid: 'i-1-2-3',
|
||||
},
|
||||
})
|
||||
expect(workspaceServer.inviteToWorkspace).toHaveBeenCalledWith({
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
inviteeEmail: '[email protected]',
|
||||
accessLevel: 'write-and-read',
|
||||
})
|
||||
})
|
||||
|
||||
it('should not invite to a workspace if it is already inviting', async () => {
|
||||
const service = createService()
|
||||
Object.defineProperty(service, 'operationsInProgress', {
|
||||
get: () => new Map([[WorkspaceApiOperations.Inviting, true]]),
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await service.inviteToWorkspace({
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
inviteeEmail: '[email protected]',
|
||||
accessLevel: WorkspaceAccessLevel.WriteAndRead,
|
||||
})
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should not invite to a workspace if the server fails', async () => {
|
||||
workspaceServer.inviteToWorkspace = jest.fn().mockImplementation(() => {
|
||||
throw new Error('Oops')
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await createService().inviteToWorkspace({
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
inviteeEmail: '[email protected]',
|
||||
accessLevel: WorkspaceAccessLevel.WriteAndRead,
|
||||
})
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should accept invite to a workspace', async () => {
|
||||
const response = await createService().acceptInvite({
|
||||
userUuid: 'u-1-2-3',
|
||||
inviteUuid: 'i-1-2-3',
|
||||
publicKey: 'foo',
|
||||
encryptedPrivateKey: 'bar',
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
success: true,
|
||||
},
|
||||
})
|
||||
expect(workspaceServer.acceptInvite).toHaveBeenCalledWith({
|
||||
userUuid: 'u-1-2-3',
|
||||
inviteUuid: 'i-1-2-3',
|
||||
publicKey: 'foo',
|
||||
encryptedPrivateKey: 'bar',
|
||||
})
|
||||
})
|
||||
|
||||
it('should not accept invite to a workspace if it is already accepting', async () => {
|
||||
const service = createService()
|
||||
Object.defineProperty(service, 'operationsInProgress', {
|
||||
get: () => new Map([[WorkspaceApiOperations.Accepting, true]]),
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await service.acceptInvite({
|
||||
userUuid: 'u-1-2-3',
|
||||
inviteUuid: 'i-1-2-3',
|
||||
publicKey: 'foo',
|
||||
encryptedPrivateKey: 'bar',
|
||||
})
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should not accept invite to a workspace if the server fails', async () => {
|
||||
workspaceServer.acceptInvite = jest.fn().mockImplementation(() => {
|
||||
throw new Error('Oops')
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await createService().acceptInvite({
|
||||
userUuid: 'u-1-2-3',
|
||||
inviteUuid: 'i-1-2-3',
|
||||
publicKey: 'foo',
|
||||
encryptedPrivateKey: 'bar',
|
||||
})
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should list workspaces', async () => {
|
||||
const response = await createService().listWorkspaces()
|
||||
|
||||
expect(response).toEqual({
|
||||
status: 200,
|
||||
data: {
|
||||
ownedWorkspaces: [],
|
||||
joinedWorkspaces: [],
|
||||
},
|
||||
})
|
||||
expect(workspaceServer.listWorkspaces).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not list workspaces if it is already listing them', async () => {
|
||||
const service = createService()
|
||||
Object.defineProperty(service, 'operationsInProgress', {
|
||||
get: () => new Map([[WorkspaceApiOperations.ListingWorkspaces, true]]),
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await service.listWorkspaces()
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should not list workspaces if the server fails', async () => {
|
||||
workspaceServer.listWorkspaces = jest.fn().mockImplementation(() => {
|
||||
throw new Error('Oops')
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await createService().listWorkspaces()
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should list workspace users', async () => {
|
||||
const response = await createService().listWorkspaceUsers({ workspaceUuid: 'w-1-2-3' })
|
||||
|
||||
expect(response).toEqual({
|
||||
status: 200,
|
||||
data: {
|
||||
users: [],
|
||||
},
|
||||
})
|
||||
expect(workspaceServer.listWorkspaceUsers).toHaveBeenCalledWith({ workspaceUuid: 'w-1-2-3' })
|
||||
})
|
||||
|
||||
it('should not list workspace users if it is already listing them', async () => {
|
||||
const service = createService()
|
||||
Object.defineProperty(service, 'operationsInProgress', {
|
||||
get: () => new Map([[WorkspaceApiOperations.ListingWorkspaceUsers, true]]),
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await service.listWorkspaceUsers({ workspaceUuid: 'w-1-2-3' })
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should not list workspace users if the server fails', async () => {
|
||||
workspaceServer.listWorkspaceUsers = jest.fn().mockImplementation(() => {
|
||||
throw new Error('Oops')
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await createService().listWorkspaceUsers({ workspaceUuid: 'w-1-2-3' })
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should initiate keyshare in workspace for user', async () => {
|
||||
const response = await createService().initiateKeyshare({
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
userUuid: 'u-1-2-3',
|
||||
encryptedWorkspaceKey: 'foobar',
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
status: 200,
|
||||
data: {
|
||||
success: true,
|
||||
},
|
||||
})
|
||||
expect(workspaceServer.initiateKeyshare).toHaveBeenCalledWith({
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
userUuid: 'u-1-2-3',
|
||||
encryptedWorkspaceKey: 'foobar',
|
||||
})
|
||||
})
|
||||
|
||||
it('should not initiate keyshare in workspace if it is already initiating', async () => {
|
||||
const service = createService()
|
||||
Object.defineProperty(service, 'operationsInProgress', {
|
||||
get: () => new Map([[WorkspaceApiOperations.InitiatingKeyshare, true]]),
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await service.initiateKeyshare({ workspaceUuid: 'w-1-2-3', userUuid: 'u-1-2-3', encryptedWorkspaceKey: 'foobar' })
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should not initiate keyshare in workspace if the server fails', async () => {
|
||||
workspaceServer.initiateKeyshare = jest.fn().mockImplementation(() => {
|
||||
throw new Error('Oops')
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await createService().initiateKeyshare({
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
userUuid: 'u-1-2-3',
|
||||
encryptedWorkspaceKey: 'foobar',
|
||||
})
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import { Uuid, WorkspaceAccessLevel, WorkspaceType } from '@standardnotes/common'
|
||||
|
||||
import { ErrorMessage } from '../../Error/ErrorMessage'
|
||||
import { ApiCallError } from '../../Error/ApiCallError'
|
||||
import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse'
|
||||
import { WorkspaceInvitationAcceptingResponse } from '../../Response/Workspace/WorkspaceInvitationAcceptingResponse'
|
||||
import { WorkspaceInvitationResponse } from '../../Response/Workspace/WorkspaceInvitationResponse'
|
||||
import { WorkspaceServerInterface } from '../../Server/Workspace/WorkspaceServerInterface'
|
||||
import { WorkspaceListResponse } from '../../Response/Workspace/WorkspaceListResponse'
|
||||
import { WorkspaceUserListResponse } from '../../Response/Workspace/WorkspaceUserListResponse'
|
||||
import { WorkspaceKeyshareInitiatingResponse } from '../../Response/Workspace/WorkspaceKeyshareInitiatingResponse'
|
||||
|
||||
import { WorkspaceApiServiceInterface } from './WorkspaceApiServiceInterface'
|
||||
import { WorkspaceApiOperations } from './WorkspaceApiOperations'
|
||||
@@ -13,31 +20,137 @@ export class WorkspaceApiService implements WorkspaceApiServiceInterface {
|
||||
this.operationsInProgress = new Map()
|
||||
}
|
||||
|
||||
async createWorkspace(dto: {
|
||||
async initiateKeyshare(dto: {
|
||||
workspaceUuid: string
|
||||
userUuid: string
|
||||
encryptedWorkspaceKey: string
|
||||
encryptedPrivateKey: string
|
||||
publicKey: string
|
||||
workspaceName?: string
|
||||
}): Promise<WorkspaceCreationResponse> {
|
||||
if (this.operationsInProgress.get(WorkspaceApiOperations.Creating)) {
|
||||
throw new ApiCallError(ErrorMessage.GenericInProgress)
|
||||
}
|
||||
|
||||
this.operationsInProgress.set(WorkspaceApiOperations.Creating, true)
|
||||
}): Promise<WorkspaceKeyshareInitiatingResponse> {
|
||||
this.lockOperation(WorkspaceApiOperations.InitiatingKeyshare)
|
||||
|
||||
try {
|
||||
const response = await this.workspaceServer.createWorkspace({
|
||||
encryptedPrivateKey: dto.encryptedPrivateKey,
|
||||
const response = await this.workspaceServer.initiateKeyshare({
|
||||
workspaceUuid: dto.workspaceUuid,
|
||||
userUuid: dto.userUuid,
|
||||
encryptedWorkspaceKey: dto.encryptedWorkspaceKey,
|
||||
publicKey: dto.publicKey,
|
||||
workspaceName: dto.workspaceName,
|
||||
})
|
||||
|
||||
this.operationsInProgress.set(WorkspaceApiOperations.Creating, false)
|
||||
this.unlockOperation(WorkspaceApiOperations.InitiatingKeyshare)
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
throw new ApiCallError(ErrorMessage.GenericFail)
|
||||
}
|
||||
}
|
||||
|
||||
async listWorkspaceUsers(dto: { workspaceUuid: string }): Promise<WorkspaceUserListResponse> {
|
||||
this.lockOperation(WorkspaceApiOperations.ListingWorkspaceUsers)
|
||||
|
||||
try {
|
||||
const response = await this.workspaceServer.listWorkspaceUsers({ workspaceUuid: dto.workspaceUuid })
|
||||
|
||||
this.unlockOperation(WorkspaceApiOperations.ListingWorkspaceUsers)
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
throw new ApiCallError(ErrorMessage.GenericFail)
|
||||
}
|
||||
}
|
||||
|
||||
async listWorkspaces(): Promise<WorkspaceListResponse> {
|
||||
this.lockOperation(WorkspaceApiOperations.ListingWorkspaces)
|
||||
|
||||
try {
|
||||
const response = await this.workspaceServer.listWorkspaces({})
|
||||
|
||||
this.unlockOperation(WorkspaceApiOperations.ListingWorkspaces)
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
throw new ApiCallError(ErrorMessage.GenericFail)
|
||||
}
|
||||
}
|
||||
|
||||
async acceptInvite(dto: {
|
||||
inviteUuid: string
|
||||
userUuid: string
|
||||
publicKey: string
|
||||
encryptedPrivateKey: string
|
||||
}): Promise<WorkspaceInvitationAcceptingResponse> {
|
||||
this.lockOperation(WorkspaceApiOperations.Accepting)
|
||||
|
||||
try {
|
||||
const response = await this.workspaceServer.acceptInvite({
|
||||
encryptedPrivateKey: dto.encryptedPrivateKey,
|
||||
publicKey: dto.publicKey,
|
||||
inviteUuid: dto.inviteUuid,
|
||||
userUuid: dto.userUuid,
|
||||
})
|
||||
|
||||
this.unlockOperation(WorkspaceApiOperations.Accepting)
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
throw new ApiCallError(ErrorMessage.GenericFail)
|
||||
}
|
||||
}
|
||||
|
||||
async inviteToWorkspace(dto: {
|
||||
inviteeEmail: string
|
||||
workspaceUuid: Uuid
|
||||
accessLevel: WorkspaceAccessLevel
|
||||
}): Promise<WorkspaceInvitationResponse> {
|
||||
this.lockOperation(WorkspaceApiOperations.Inviting)
|
||||
|
||||
try {
|
||||
const response = await this.workspaceServer.inviteToWorkspace({
|
||||
inviteeEmail: dto.inviteeEmail,
|
||||
workspaceUuid: dto.workspaceUuid,
|
||||
accessLevel: dto.accessLevel,
|
||||
})
|
||||
|
||||
this.unlockOperation(WorkspaceApiOperations.Inviting)
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
throw new ApiCallError(ErrorMessage.GenericFail)
|
||||
}
|
||||
}
|
||||
|
||||
async createWorkspace(dto: {
|
||||
workspaceType: WorkspaceType
|
||||
encryptedWorkspaceKey?: string
|
||||
encryptedPrivateKey?: string
|
||||
publicKey?: string
|
||||
workspaceName?: string
|
||||
}): Promise<WorkspaceCreationResponse> {
|
||||
this.lockOperation(WorkspaceApiOperations.Creating)
|
||||
|
||||
try {
|
||||
const response = await this.workspaceServer.createWorkspace({
|
||||
workspaceType: dto.workspaceType,
|
||||
encryptedPrivateKey: dto.encryptedPrivateKey,
|
||||
encryptedWorkspaceKey: dto.encryptedWorkspaceKey,
|
||||
publicKey: dto.publicKey,
|
||||
workspaceName: dto.workspaceName,
|
||||
})
|
||||
|
||||
this.unlockOperation(WorkspaceApiOperations.Creating)
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
throw new ApiCallError(ErrorMessage.GenericFail)
|
||||
}
|
||||
}
|
||||
|
||||
private lockOperation(operation: WorkspaceApiOperations): void {
|
||||
if (this.operationsInProgress.get(operation)) {
|
||||
throw new ApiCallError(ErrorMessage.GenericInProgress)
|
||||
}
|
||||
|
||||
this.operationsInProgress.set(operation, true)
|
||||
}
|
||||
|
||||
private unlockOperation(operation: WorkspaceApiOperations): void {
|
||||
this.operationsInProgress.set(operation, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,36 @@
|
||||
import { WorkspaceCreationResponse } from '../../Response'
|
||||
import { Uuid, WorkspaceAccessLevel, WorkspaceType } from '@standardnotes/common'
|
||||
|
||||
import { WorkspaceKeyshareInitiatingResponse } from '../../Response/Workspace/WorkspaceKeyshareInitiatingResponse'
|
||||
import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse'
|
||||
import { WorkspaceInvitationAcceptingResponse } from '../../Response/Workspace/WorkspaceInvitationAcceptingResponse'
|
||||
import { WorkspaceInvitationResponse } from '../../Response/Workspace/WorkspaceInvitationResponse'
|
||||
import { WorkspaceListResponse } from '../../Response/Workspace/WorkspaceListResponse'
|
||||
import { WorkspaceUserListResponse } from '../../Response/Workspace/WorkspaceUserListResponse'
|
||||
|
||||
export interface WorkspaceApiServiceInterface {
|
||||
createWorkspace(dto: {
|
||||
encryptedWorkspaceKey: string
|
||||
encryptedPrivateKey: string
|
||||
publicKey: string
|
||||
workspaceType: WorkspaceType
|
||||
encryptedWorkspaceKey?: string
|
||||
encryptedPrivateKey?: string
|
||||
publicKey?: string
|
||||
workspaceName?: string
|
||||
}): Promise<WorkspaceCreationResponse>
|
||||
inviteToWorkspace(dto: {
|
||||
inviteeEmail: string
|
||||
workspaceUuid: Uuid
|
||||
accessLevel: WorkspaceAccessLevel
|
||||
}): Promise<WorkspaceInvitationResponse>
|
||||
acceptInvite(dto: {
|
||||
inviteUuid: Uuid
|
||||
userUuid: Uuid
|
||||
publicKey: string
|
||||
encryptedPrivateKey: string
|
||||
}): Promise<WorkspaceInvitationAcceptingResponse>
|
||||
listWorkspaces(): Promise<WorkspaceListResponse>
|
||||
listWorkspaceUsers(dto: { workspaceUuid: Uuid }): Promise<WorkspaceUserListResponse>
|
||||
initiateKeyshare(dto: {
|
||||
workspaceUuid: Uuid
|
||||
userUuid: Uuid
|
||||
encryptedWorkspaceKey: string
|
||||
}): Promise<WorkspaceKeyshareInitiatingResponse>
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { WorkspaceType } from '@standardnotes/common'
|
||||
|
||||
export type WorkspaceCreationRequestParams = {
|
||||
encryptedWorkspaceKey: string
|
||||
encryptedPrivateKey: string
|
||||
publicKey: string
|
||||
workspaceType: WorkspaceType
|
||||
encryptedWorkspaceKey?: string
|
||||
encryptedPrivateKey?: string
|
||||
publicKey?: string
|
||||
workspaceName?: string
|
||||
[additionalParam: string]: unknown
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Uuid } from '@standardnotes/common'
|
||||
|
||||
export type WorkspaceInvitationAcceptingRequestParams = {
|
||||
inviteUuid: Uuid
|
||||
userUuid: Uuid
|
||||
publicKey: string
|
||||
encryptedPrivateKey: string
|
||||
[additionalParam: string]: unknown
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Uuid, WorkspaceAccessLevel } from '@standardnotes/common'
|
||||
|
||||
export type WorkspaceInvitationRequestParams = {
|
||||
workspaceUuid: Uuid
|
||||
inviteeEmail: string
|
||||
accessLevel: WorkspaceAccessLevel
|
||||
[additionalParam: string]: unknown
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Uuid } from '@standardnotes/common'
|
||||
|
||||
export type WorkspaceKeyshareInitiatingRequestParams = {
|
||||
userUuid: Uuid
|
||||
workspaceUuid: Uuid
|
||||
encryptedWorkspaceKey: string
|
||||
[additionalParam: string]: unknown
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export type WorkspaceListRequestParams = {
|
||||
[additionalParam: string]: unknown
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { Uuid } from '@standardnotes/common'
|
||||
|
||||
export type WorkspaceUserListRequestParams = {
|
||||
workspaceUuid: Uuid
|
||||
[additionalParam: string]: unknown
|
||||
}
|
||||
@@ -7,3 +7,8 @@ export * from './Subscription/SubscriptionInviteRequestParams'
|
||||
export * from './User/UserRegistrationRequestParams'
|
||||
export * from './WebSocket/WebSocketConnectionTokenRequestParams'
|
||||
export * from './Workspace/WorkspaceCreationRequestParams'
|
||||
export * from './Workspace/WorkspaceInvitationAcceptingRequestParams'
|
||||
export * from './Workspace/WorkspaceInvitationRequestParams'
|
||||
export * from './Workspace/WorkspaceKeyshareInitiatingRequestParams'
|
||||
export * from './Workspace/WorkspaceListRequestParams'
|
||||
export * from './Workspace/WorkspaceUserListRequestParams'
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Either } from '@standardnotes/common'
|
||||
|
||||
import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody'
|
||||
import { HttpResponse } from '../../Http/HttpResponse'
|
||||
import { WorkspaceInvitationAcceptingResponseBody } from './WorkspaceInvitationAcceptingResponseBody'
|
||||
|
||||
export interface WorkspaceInvitationAcceptingResponse extends HttpResponse {
|
||||
data: Either<WorkspaceInvitationAcceptingResponseBody, HttpErrorResponseBody>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export type WorkspaceInvitationAcceptingResponseBody = {
|
||||
success: boolean
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Either } from '@standardnotes/common'
|
||||
|
||||
import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody'
|
||||
import { HttpResponse } from '../../Http/HttpResponse'
|
||||
import { WorkspaceInvitationResponseBody } from './WorkspaceInvitationResponseBody'
|
||||
|
||||
export interface WorkspaceInvitationResponse extends HttpResponse {
|
||||
data: Either<WorkspaceInvitationResponseBody, HttpErrorResponseBody>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export type WorkspaceInvitationResponseBody = {
|
||||
uuid: string
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Either } from '@standardnotes/common'
|
||||
|
||||
import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody'
|
||||
import { HttpResponse } from '../../Http/HttpResponse'
|
||||
import { WorkspaceKeyshareInitiatingResponseBody } from './WorkspaceKeyshareInitiatingResponseBody'
|
||||
|
||||
export interface WorkspaceKeyshareInitiatingResponse extends HttpResponse {
|
||||
data: Either<WorkspaceKeyshareInitiatingResponseBody, HttpErrorResponseBody>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export type WorkspaceKeyshareInitiatingResponseBody = {
|
||||
success: boolean
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Either } from '@standardnotes/common'
|
||||
|
||||
import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody'
|
||||
import { HttpResponse } from '../../Http/HttpResponse'
|
||||
import { WorkspaceListResponseBody } from './WorkspaceListResponseBody'
|
||||
|
||||
export interface WorkspaceListResponse extends HttpResponse {
|
||||
data: Either<WorkspaceListResponseBody, HttpErrorResponseBody>
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { Workspace } from '@standardnotes/models'
|
||||
|
||||
export type WorkspaceListResponseBody = {
|
||||
ownedWorkspaces: Array<Workspace>
|
||||
joinedWorkspaces: Array<Workspace>
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Either } from '@standardnotes/common'
|
||||
|
||||
import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody'
|
||||
import { HttpResponse } from '../../Http/HttpResponse'
|
||||
import { WorkspaceUserListResponseBody } from './WorkspaceUserListResponseBody'
|
||||
|
||||
export interface WorkspaceUserListResponse extends HttpResponse {
|
||||
data: Either<WorkspaceUserListResponseBody, HttpErrorResponseBody>
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { WorkspaceUser } from '@standardnotes/models'
|
||||
|
||||
export type WorkspaceUserListResponseBody = {
|
||||
users: Array<WorkspaceUser>
|
||||
}
|
||||
@@ -14,3 +14,13 @@ export * from './WebSocket/WebSocketConnectionTokenResponse'
|
||||
export * from './WebSocket/WebSocketConnectionTokenResponseBody'
|
||||
export * from './Workspace/WorkspaceCreationResponse'
|
||||
export * from './Workspace/WorkspaceCreationResponseBody'
|
||||
export * from './Workspace/WorkspaceInvitationAcceptingResponse'
|
||||
export * from './Workspace/WorkspaceInvitationAcceptingResponseBody'
|
||||
export * from './Workspace/WorkspaceKeyshareInitiatingResponse'
|
||||
export * from './Workspace/WorkspaceKeyshareInitiatingResponseBody'
|
||||
export * from './Workspace/WorkspaceInvitationResponse'
|
||||
export * from './Workspace/WorkspaceInvitationResponseBody'
|
||||
export * from './Workspace/WorkspaceListResponse'
|
||||
export * from './Workspace/WorkspaceListResponseBody'
|
||||
export * from './Workspace/WorkspaceUserListResponse'
|
||||
export * from './Workspace/WorkspaceUserListResponseBody'
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import { Uuid } from '@standardnotes/common'
|
||||
|
||||
const WorkspacePaths = {
|
||||
createWorkspace: '/v1/workspaces',
|
||||
listWorkspaces: '/v1/workspaces',
|
||||
listWorkspaceUsers: (uuid: Uuid) => `/v1/workspaces/${uuid}/users`,
|
||||
initiateKeyshare: (worksapceUuid: Uuid, userUuid: Uuid) =>
|
||||
`/v1/workspaces/${worksapceUuid}/users/${userUuid}/keyshare`,
|
||||
inviteToWorkspace: (uuid: Uuid) => `/v1/workspaces/${uuid}/invites`,
|
||||
acceptInvite: (uuid: Uuid) => `/v1/invites/${uuid}/accept`,
|
||||
}
|
||||
|
||||
export const Paths = {
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import { HttpServiceInterface } from '../../Http'
|
||||
import { WorkspaceAccessLevel, WorkspaceType } from '@standardnotes/common'
|
||||
|
||||
import { HttpServiceInterface, HttpStatusCode } from '../../Http'
|
||||
import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse'
|
||||
import { WorkspaceInvitationAcceptingResponse } from '../../Response/Workspace/WorkspaceInvitationAcceptingResponse'
|
||||
import { WorkspaceInvitationResponse } from '../../Response/Workspace/WorkspaceInvitationResponse'
|
||||
import { WorkspaceKeyshareInitiatingResponse } from '../../Response/Workspace/WorkspaceKeyshareInitiatingResponse'
|
||||
import { WorkspaceListResponse } from '../../Response/Workspace/WorkspaceListResponse'
|
||||
import { WorkspaceUserListResponse } from '../../Response/Workspace/WorkspaceUserListResponse'
|
||||
|
||||
import { WorkspaceServer } from './WorkspaceServer'
|
||||
|
||||
@@ -17,6 +24,7 @@ describe('WorkspaceServer', () => {
|
||||
|
||||
it('should create a workspace', async () => {
|
||||
const response = await createServer().createWorkspace({
|
||||
workspaceType: WorkspaceType.Private,
|
||||
encryptedPrivateKey: 'foo',
|
||||
encryptedWorkspaceKey: 'bar',
|
||||
publicKey: 'buzz',
|
||||
@@ -28,4 +36,89 @@ describe('WorkspaceServer', () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should inivte to a workspace', async () => {
|
||||
httpService.post = jest.fn().mockReturnValue({
|
||||
data: { uuid: 'i-1-2-3' },
|
||||
} as jest.Mocked<WorkspaceInvitationResponse>)
|
||||
|
||||
const response = await createServer().inviteToWorkspace({
|
||||
inviteeEmail: '[email protected]',
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
accessLevel: WorkspaceAccessLevel.WriteAndRead,
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
uuid: 'i-1-2-3',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should accept invitation to a workspace', async () => {
|
||||
httpService.post = jest.fn().mockReturnValue({
|
||||
data: { success: true },
|
||||
} as jest.Mocked<WorkspaceInvitationAcceptingResponse>)
|
||||
|
||||
const response = await createServer().acceptInvite({
|
||||
encryptedPrivateKey: 'foo',
|
||||
inviteUuid: 'i-1-2-3',
|
||||
publicKey: 'bar',
|
||||
userUuid: 'u-1-2-3',
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
success: true,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should list workspaces', async () => {
|
||||
httpService.get = jest.fn().mockReturnValue({
|
||||
status: HttpStatusCode.Success,
|
||||
data: { ownedWorkspaces: [], joinedWorkspaces: [] },
|
||||
} as jest.Mocked<WorkspaceListResponse>)
|
||||
|
||||
const response = await createServer().listWorkspaces({})
|
||||
|
||||
expect(response).toEqual({
|
||||
status: 200,
|
||||
data: { ownedWorkspaces: [], joinedWorkspaces: [] },
|
||||
})
|
||||
})
|
||||
|
||||
it('should list workspace users', async () => {
|
||||
httpService.get = jest.fn().mockReturnValue({
|
||||
status: HttpStatusCode.Success,
|
||||
data: { users: [] },
|
||||
} as jest.Mocked<WorkspaceUserListResponse>)
|
||||
|
||||
const response = await createServer().listWorkspaceUsers({ workspaceUuid: 'w-1-2-3' })
|
||||
|
||||
expect(response).toEqual({
|
||||
status: 200,
|
||||
data: { users: [] },
|
||||
})
|
||||
})
|
||||
|
||||
it('should initiate keyshare for user in a workspace', async () => {
|
||||
httpService.post = jest.fn().mockReturnValue({
|
||||
status: HttpStatusCode.Success,
|
||||
data: { success: true },
|
||||
} as jest.Mocked<WorkspaceKeyshareInitiatingResponse>)
|
||||
|
||||
const response = await createServer().initiateKeyshare({
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
userUuid: 'u-1-2-3',
|
||||
encryptedWorkspaceKey: 'foobar',
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
status: 200,
|
||||
data: {
|
||||
success: true,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
import { HttpServiceInterface } from '../../Http/HttpServiceInterface'
|
||||
import { WorkspaceInvitationRequestParams } from '../../Request/Workspace/WorkspaceInvitationRequestParams'
|
||||
import { WorkspaceCreationRequestParams } from '../../Request/Workspace/WorkspaceCreationRequestParams'
|
||||
import { WorkspaceInvitationResponse } from '../../Response/Workspace/WorkspaceInvitationResponse'
|
||||
import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse'
|
||||
import { WorkspaceInvitationAcceptingRequestParams } from '../../Request/Workspace/WorkspaceInvitationAcceptingRequestParams'
|
||||
import { WorkspaceInvitationAcceptingResponse } from '../../Response/Workspace/WorkspaceInvitationAcceptingResponse'
|
||||
import { WorkspaceListRequestParams } from '../../Request/Workspace/WorkspaceListRequestParams'
|
||||
import { WorkspaceListResponse } from '../../Response/Workspace/WorkspaceListResponse'
|
||||
import { WorkspaceUserListRequestParams } from '../../Request/Workspace/WorkspaceUserListRequestParams'
|
||||
import { WorkspaceUserListResponse } from '../../Response/Workspace/WorkspaceUserListResponse'
|
||||
import { WorkspaceKeyshareInitiatingRequestParams } from '../../Request/Workspace/WorkspaceKeyshareInitiatingRequestParams'
|
||||
import { WorkspaceKeyshareInitiatingResponse } from '../../Response/Workspace/WorkspaceKeyshareInitiatingResponse'
|
||||
|
||||
import { Paths } from './Paths'
|
||||
import { WorkspaceServerInterface } from './WorkspaceServerInterface'
|
||||
@@ -8,6 +18,41 @@ import { WorkspaceServerInterface } from './WorkspaceServerInterface'
|
||||
export class WorkspaceServer implements WorkspaceServerInterface {
|
||||
constructor(private httpService: HttpServiceInterface) {}
|
||||
|
||||
async initiateKeyshare(
|
||||
params: WorkspaceKeyshareInitiatingRequestParams,
|
||||
): Promise<WorkspaceKeyshareInitiatingResponse> {
|
||||
const response = await this.httpService.post(
|
||||
Paths.v1.initiateKeyshare(params.workspaceUuid, params.userUuid),
|
||||
params,
|
||||
)
|
||||
|
||||
return response as WorkspaceKeyshareInitiatingResponse
|
||||
}
|
||||
|
||||
async listWorkspaceUsers(params: WorkspaceUserListRequestParams): Promise<WorkspaceUserListResponse> {
|
||||
const response = await this.httpService.get(Paths.v1.listWorkspaceUsers(params.workspaceUuid), params)
|
||||
|
||||
return response as WorkspaceUserListResponse
|
||||
}
|
||||
|
||||
async listWorkspaces(params: WorkspaceListRequestParams): Promise<WorkspaceListResponse> {
|
||||
const response = await this.httpService.get(Paths.v1.listWorkspaces, params)
|
||||
|
||||
return response as WorkspaceListResponse
|
||||
}
|
||||
|
||||
async acceptInvite(params: WorkspaceInvitationAcceptingRequestParams): Promise<WorkspaceInvitationAcceptingResponse> {
|
||||
const response = await this.httpService.post(Paths.v1.acceptInvite(params.inviteUuid), params)
|
||||
|
||||
return response as WorkspaceInvitationAcceptingResponse
|
||||
}
|
||||
|
||||
async inviteToWorkspace(params: WorkspaceInvitationRequestParams): Promise<WorkspaceInvitationResponse> {
|
||||
const response = await this.httpService.post(Paths.v1.inviteToWorkspace(params.workspaceUuid), params)
|
||||
|
||||
return response as WorkspaceInvitationResponse
|
||||
}
|
||||
|
||||
async createWorkspace(params: WorkspaceCreationRequestParams): Promise<WorkspaceCreationResponse> {
|
||||
const response = await this.httpService.post(Paths.v1.createWorkspace, params)
|
||||
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
import { WorkspaceInvitationRequestParams } from '../../Request/Workspace/WorkspaceInvitationRequestParams'
|
||||
import { WorkspaceCreationRequestParams } from '../../Request/Workspace/WorkspaceCreationRequestParams'
|
||||
import { WorkspaceInvitationResponse } from '../../Response/Workspace/WorkspaceInvitationResponse'
|
||||
import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse'
|
||||
import { WorkspaceInvitationAcceptingRequestParams } from '../../Request/Workspace/WorkspaceInvitationAcceptingRequestParams'
|
||||
import { WorkspaceInvitationAcceptingResponse } from '../../Response/Workspace/WorkspaceInvitationAcceptingResponse'
|
||||
import { WorkspaceListRequestParams } from '../../Request/Workspace/WorkspaceListRequestParams'
|
||||
import { WorkspaceListResponse } from '../../Response/Workspace/WorkspaceListResponse'
|
||||
import { WorkspaceUserListRequestParams } from '../../Request/Workspace/WorkspaceUserListRequestParams'
|
||||
import { WorkspaceUserListResponse } from '../../Response/Workspace/WorkspaceUserListResponse'
|
||||
import { WorkspaceKeyshareInitiatingRequestParams } from '../../Request/Workspace/WorkspaceKeyshareInitiatingRequestParams'
|
||||
import { WorkspaceKeyshareInitiatingResponse } from '../../Response/Workspace/WorkspaceKeyshareInitiatingResponse'
|
||||
|
||||
export interface WorkspaceServerInterface {
|
||||
createWorkspace(params: WorkspaceCreationRequestParams): Promise<WorkspaceCreationResponse>
|
||||
listWorkspaces(params: WorkspaceListRequestParams): Promise<WorkspaceListResponse>
|
||||
listWorkspaceUsers(params: WorkspaceUserListRequestParams): Promise<WorkspaceUserListResponse>
|
||||
inviteToWorkspace(params: WorkspaceInvitationRequestParams): Promise<WorkspaceInvitationResponse>
|
||||
acceptInvite(params: WorkspaceInvitationAcceptingRequestParams): Promise<WorkspaceInvitationAcceptingResponse>
|
||||
initiateKeyshare(params: WorkspaceKeyshareInitiatingRequestParams): Promise<WorkspaceKeyshareInitiatingResponse>
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
## [2.7.19](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/components-meta
|
||||
|
||||
## [2.7.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/components-meta
|
||||
|
||||
## [2.7.17](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/components-meta
|
||||
|
||||
## [2.7.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/components-meta
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/components-meta",
|
||||
"version": "2.7.16",
|
||||
"version": "2.7.19",
|
||||
"private": true,
|
||||
"author": "Standard Notes.",
|
||||
"main": "dist",
|
||||
|
||||
@@ -3,6 +3,72 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.23.212](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.211](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.210](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.209](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.208](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.207](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.206](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.205](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.204](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.203](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.202](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.201](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.200](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.199](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.198](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **desktop:** use different method to send messages across main and render without node integration in preload ([#1769](https://github.com/standardnotes/app/issues/1769)) ([20226c3](https://github.com/standardnotes/app/commit/20226c326945ad9dc524f6dcbfc1c1274befb652))
|
||||
|
||||
## [3.23.197](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.196](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
@@ -5,6 +5,7 @@ import { autoUpdater } from 'electron-updater'
|
||||
import { action, autorun, computed, makeObservable, observable } from 'mobx'
|
||||
import { MessageType } from '../../../test/TestIpcMessage'
|
||||
import { AppState } from '../../AppState'
|
||||
import { MessageToWebApp } from '../Shared/IpcMessages'
|
||||
import { BackupsManagerInterface } from './Backups/BackupsManagerInterface'
|
||||
import { StoreKeys } from './Store/StoreKeys'
|
||||
import { updates as str } from './Strings'
|
||||
@@ -113,12 +114,12 @@ export function setupUpdates(window: BrowserWindow, appState: AppState, backupsM
|
||||
setInterval(checkUpdateSafety, oneHour)
|
||||
|
||||
autoUpdater.on('update-downloaded', (info: { version?: string }) => {
|
||||
window.webContents.send('update-available', null)
|
||||
window.webContents.send(MessageToWebApp.UpdateAvailable, null)
|
||||
updateState.autoUpdateHasBeenDownloaded(info.version || null)
|
||||
})
|
||||
|
||||
autoUpdater.on('error', logError)
|
||||
autoUpdater.on('update-available', (info: { version?: string }) => {
|
||||
autoUpdater.on(MessageToWebApp.UpdateAvailable, (info: { version?: string }) => {
|
||||
updateState.checkedForUpdate(info.version || null)
|
||||
if (updateState.enableAutoUpdate) {
|
||||
const canUpdate = checkUpdateSafety()
|
||||
|
||||
@@ -166,6 +166,7 @@ async function createWindow(store: Store): Promise<Electron.BrowserWindow> {
|
||||
spellcheck: true,
|
||||
nodeIntegration: isTesting(),
|
||||
contextIsolation: true,
|
||||
sandbox: true,
|
||||
preload: Paths.preloadJs,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,42 +1,28 @@
|
||||
import { IpcRendererEvent } from 'electron/renderer'
|
||||
import { MessageToWebApp } from '../Shared/IpcMessages'
|
||||
const { ipcRenderer } = require('electron')
|
||||
const path = require('path')
|
||||
const rendererPath = path.join('file://', __dirname, '/renderer.js')
|
||||
const RemoteBridge = require('@electron/remote').getGlobal('RemoteBridge')
|
||||
const { contextBridge } = require('electron')
|
||||
|
||||
type MainEventCallback = (event: IpcRendererEvent, value: any) => void
|
||||
|
||||
process.once('loaded', function () {
|
||||
contextBridge.exposeInMainWorld('electronRemoteBridge', RemoteBridge.exposableValue)
|
||||
|
||||
listenForIpcEventsFromMainProcess()
|
||||
contextBridge.exposeInMainWorld('electronMainEvents', {
|
||||
handleUpdateAvailable: (callback: MainEventCallback) => ipcRenderer.on(MessageToWebApp.UpdateAvailable, callback),
|
||||
|
||||
handlePerformAutomatedBackup: (callback: MainEventCallback) =>
|
||||
ipcRenderer.on(MessageToWebApp.PerformAutomatedBackup, callback),
|
||||
|
||||
handleFinishedSavingBackup: (callback: MainEventCallback) =>
|
||||
ipcRenderer.on(MessageToWebApp.FinishedSavingBackup, callback),
|
||||
|
||||
handleWindowBlurred: (callback: MainEventCallback) => ipcRenderer.on(MessageToWebApp.WindowBlurred, callback),
|
||||
|
||||
handleWindowFocused: (callback: MainEventCallback) => ipcRenderer.on(MessageToWebApp.WindowFocused, callback),
|
||||
|
||||
handleInstallComponentComplete: (callback: MainEventCallback) =>
|
||||
ipcRenderer.on(MessageToWebApp.InstallComponentComplete, callback),
|
||||
})
|
||||
})
|
||||
|
||||
function listenForIpcEventsFromMainProcess() {
|
||||
const sendMessageToRenderProcess = (message: string, payload = {}) => {
|
||||
window.postMessage(JSON.stringify({ message, data: payload }), rendererPath)
|
||||
}
|
||||
|
||||
ipcRenderer.on(MessageToWebApp.UpdateAvailable, function (_event, data) {
|
||||
sendMessageToRenderProcess(MessageToWebApp.UpdateAvailable, data)
|
||||
})
|
||||
|
||||
ipcRenderer.on(MessageToWebApp.PerformAutomatedBackup, function (_event, data) {
|
||||
sendMessageToRenderProcess(MessageToWebApp.PerformAutomatedBackup, data)
|
||||
})
|
||||
|
||||
ipcRenderer.on(MessageToWebApp.FinishedSavingBackup, function (_event, data) {
|
||||
sendMessageToRenderProcess(MessageToWebApp.FinishedSavingBackup, data)
|
||||
})
|
||||
|
||||
ipcRenderer.on(MessageToWebApp.WindowBlurred, function (_event, data) {
|
||||
sendMessageToRenderProcess(MessageToWebApp.WindowBlurred, data)
|
||||
})
|
||||
|
||||
ipcRenderer.on(MessageToWebApp.WindowFocused, function (_event, data) {
|
||||
sendMessageToRenderProcess(MessageToWebApp.WindowFocused, data)
|
||||
})
|
||||
|
||||
ipcRenderer.on(MessageToWebApp.InstallComponentComplete, function (_event, data) {
|
||||
sendMessageToRenderProcess(MessageToWebApp.InstallComponentComplete, data)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DesktopClientRequiresWebMethods } from '@web/Application/Device/DesktopSnjsExports'
|
||||
import { StartApplication } from '@web/Application/Device/StartApplication'
|
||||
import { MessageToWebApp } from '../Shared/IpcMessages'
|
||||
import { IpcRendererEvent } from 'electron/renderer'
|
||||
import { CrossProcessBridge } from './CrossProcessBridge'
|
||||
import { DesktopDevice } from './DesktopDevice'
|
||||
|
||||
@@ -23,6 +23,7 @@ declare global {
|
||||
purchaseUrl: string
|
||||
startApplication: StartApplication
|
||||
zip: any
|
||||
electronMainEvents: any
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,8 +42,6 @@ const loadAndStartApplication = async () => {
|
||||
window.device = await createDesktopDevice(remoteBridge)
|
||||
|
||||
window.startApplication(DEFAULT_SYNC_SERVER, window.device, window.enableUnfinishedFeatures, WEBSOCKET_URL)
|
||||
|
||||
listenForMessagesSentFromMainToPreloadToUs(window.device)
|
||||
}
|
||||
|
||||
window.onload = () => {
|
||||
@@ -134,35 +133,26 @@ async function configureWindow(remoteBridge: CrossProcessBridge) {
|
||||
}
|
||||
}
|
||||
|
||||
function listenForMessagesSentFromMainToPreloadToUs(device: DesktopDevice) {
|
||||
window.addEventListener('message', async (event) => {
|
||||
// We don't have access to the full file path.
|
||||
if (event.origin !== 'file://') {
|
||||
return
|
||||
}
|
||||
let payload
|
||||
try {
|
||||
payload = JSON.parse(event.data)
|
||||
} catch (e) {
|
||||
// message doesn't belong to us
|
||||
return
|
||||
}
|
||||
const receiver = window.webClient
|
||||
const message = payload.message
|
||||
const data = payload.data
|
||||
window.electronMainEvents.handleUpdateAvailable(() => {
|
||||
window.webClient.updateAvailable()
|
||||
})
|
||||
|
||||
if (message === MessageToWebApp.WindowBlurred) {
|
||||
receiver.windowLostFocus()
|
||||
} else if (message === MessageToWebApp.WindowFocused) {
|
||||
receiver.windowGainedFocus()
|
||||
} else if (message === MessageToWebApp.InstallComponentComplete) {
|
||||
receiver.onComponentInstallationComplete(data.component, undefined)
|
||||
} else if (message === MessageToWebApp.UpdateAvailable) {
|
||||
receiver.updateAvailable()
|
||||
} else if (message === MessageToWebApp.PerformAutomatedBackup) {
|
||||
void device.downloadBackup()
|
||||
} else if (message === MessageToWebApp.FinishedSavingBackup) {
|
||||
receiver.didFinishBackup(data.success)
|
||||
}
|
||||
})
|
||||
}
|
||||
window.electronMainEvents.handlePerformAutomatedBackup(() => {
|
||||
void window.device.downloadBackup()
|
||||
})
|
||||
|
||||
window.electronMainEvents.handleFinishedSavingBackup((_: IpcRendererEvent, data: any) => {
|
||||
window.webClient.didFinishBackup(data.success)
|
||||
})
|
||||
|
||||
window.electronMainEvents.handleWindowBlurred(() => {
|
||||
window.webClient.windowLostFocus()
|
||||
})
|
||||
|
||||
window.electronMainEvents.handleWindowFocused(() => {
|
||||
window.webClient.windowGainedFocus()
|
||||
})
|
||||
|
||||
window.electronMainEvents.handleInstallComponentComplete((_: IpcRendererEvent, data: any) => {
|
||||
window.webClient.onComponentInstallationComplete(data.component, undefined)
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/desktop",
|
||||
"main": "./app/dist/index.js",
|
||||
"version": "3.23.196",
|
||||
"version": "3.23.212",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
|
||||
@@ -3,6 +3,36 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.18.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
### Features
|
||||
|
||||
* replace private workspaces with private usernames ([#1783](https://github.com/standardnotes/app/issues/1783)) ([18c821d](https://github.com/standardnotes/app/commit/18c821d8eb51beb6f54211eb1d6eb454303044f5))
|
||||
|
||||
## [1.17.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
# [1.17.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** add listing workspace users ([fb41f65](https://github.com/standardnotes/app/commit/fb41f651eca45dd62314609c2f389d5dc683c36e))
|
||||
|
||||
## [1.16.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.16.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.16.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api:** workspace creation arguments ([a275a45](https://github.com/standardnotes/app/commit/a275a45753abc4a29a89de5fe784260165297bbe))
|
||||
|
||||
# [1.16.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/encryption",
|
||||
"version": "1.16.0",
|
||||
"version": "1.18.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -35,7 +35,7 @@
|
||||
"typescript": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/common": "^1.32.0",
|
||||
"@standardnotes/common": "^1.39.0",
|
||||
"@standardnotes/models": "workspace:*",
|
||||
"@standardnotes/responses": "workspace:*",
|
||||
"@standardnotes/sncrypto-common": "workspace:*",
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
||||
|
||||
const PrivateUserNameV1 = 'StandardNotes-PrivateUsername-V1'
|
||||
|
||||
export async function ComputePrivateUsername(
|
||||
crypto: PureCryptoInterface,
|
||||
usernameInput: string,
|
||||
): Promise<string | undefined> {
|
||||
const result = await crypto.hmac256(
|
||||
await crypto.sha256(PrivateUserNameV1),
|
||||
await crypto.sha256(usernameInput.trim().toLowerCase()),
|
||||
)
|
||||
|
||||
if (result == undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
||||
|
||||
export async function ComputePrivateWorkspaceIdentifier(
|
||||
crypto: PureCryptoInterface,
|
||||
userphrase: string,
|
||||
name: string,
|
||||
): Promise<string | undefined> {
|
||||
const identifier = await crypto.hmac256(
|
||||
await crypto.sha256(name.trim().toLowerCase()),
|
||||
await crypto.sha256(userphrase.trim().toLowerCase()),
|
||||
)
|
||||
|
||||
if (identifier == undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return identifier
|
||||
}
|
||||
@@ -34,4 +34,4 @@ export * from './Types/EncryptedParameters'
|
||||
export * from './Types/ItemAuthenticatedData'
|
||||
export * from './Types/LegacyAttachedData'
|
||||
export * from './Types/RootKeyEncryptedAuthenticatedData'
|
||||
export * from './Workspace/PrivateWorkspace'
|
||||
export * from './Username/PrivateUsername'
|
||||
|
||||
@@ -3,6 +3,24 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.53.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** add listing workspace users ([fb41f65](https://github.com/standardnotes/app/commit/fb41f651eca45dd62314609c2f389d5dc683c36e))
|
||||
|
||||
## [1.52.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* dark mode not working in editors ([#1773](https://github.com/standardnotes/app/issues/1773)) ([27d2c95](https://github.com/standardnotes/app/commit/27d2c95b5b82645bf6c0372979f05236c83a10f6))
|
||||
|
||||
## [1.52.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api:** workspace creation arguments ([a275a45](https://github.com/standardnotes/app/commit/a275a45753abc4a29a89de5fe784260165297bbe))
|
||||
|
||||
## [1.52.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/features",
|
||||
"version": "1.52.2",
|
||||
"version": "1.53.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -26,7 +26,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/auth": "^3.19.4",
|
||||
"@standardnotes/common": "^1.32.0",
|
||||
"@standardnotes/common": "^1.39.0",
|
||||
"@standardnotes/security": "^1.2.0",
|
||||
"reflect-metadata": "^0.1.13"
|
||||
},
|
||||
|
||||
@@ -21,7 +21,7 @@ export enum FeatureIdentifier {
|
||||
|
||||
AutobiographyTheme = 'org.standardnotes.theme-autobiography',
|
||||
DynamicTheme = 'org.standardnotes.theme-dynamic',
|
||||
FocusedTheme = 'org.standardnotes.theme-focus',
|
||||
DarkTheme = 'org.standardnotes.theme-focus',
|
||||
FocusMode = 'org.standardnotes.focus-mode',
|
||||
FuturaTheme = 'org.standardnotes.theme-futura',
|
||||
MidnightTheme = 'org.standardnotes.theme-midnight',
|
||||
|
||||
@@ -68,10 +68,10 @@ export function themes(): ThemeFeatureDescription[] {
|
||||
},
|
||||
})
|
||||
|
||||
const focus: ThemeFeatureDescription = FillThemeComponentDefaults({
|
||||
const dark: ThemeFeatureDescription = FillThemeComponentDefaults({
|
||||
availableInSubscriptions: [SubscriptionName.PlusPlan, SubscriptionName.ProPlan],
|
||||
name: 'Focus',
|
||||
identifier: FeatureIdentifier.FocusedTheme,
|
||||
name: 'Dark',
|
||||
identifier: FeatureIdentifier.DarkTheme,
|
||||
permission_name: PermissionName.FocusedTheme,
|
||||
description: 'For when you need to go in.',
|
||||
thumbnail_url: 'https://s3.amazonaws.com/standard-notes/screenshots/models/themes/focus-with-mobile.jpg',
|
||||
@@ -109,5 +109,5 @@ export function themes(): ThemeFeatureDescription[] {
|
||||
description: 'A smart theme that minimizes the tags and notes panels when they are not in use.',
|
||||
})
|
||||
|
||||
return [midnight, futura, solarizedDark, autobiography, focus, titanium, dynamic]
|
||||
return [midnight, futura, solarizedDark, autobiography, dark, titanium, dynamic]
|
||||
}
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.24.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.24.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
# [1.24.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** add listing workspace users ([fb41f65](https://github.com/standardnotes/app/commit/fb41f651eca45dd62314609c2f389d5dc683c36e))
|
||||
|
||||
## [1.23.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.23.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.23.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api:** workspace creation arguments ([a275a45](https://github.com/standardnotes/app/commit/a275a45753abc4a29a89de5fe784260165297bbe))
|
||||
|
||||
## [1.23.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/filepicker",
|
||||
"version": "1.23.12",
|
||||
"version": "1.24.2",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -31,7 +31,7 @@
|
||||
"ts-node": "^10.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/common": "^1.32.0",
|
||||
"@standardnotes/common": "^1.39.0",
|
||||
"@standardnotes/files": "workspace:*",
|
||||
"@standardnotes/utils": "workspace:*",
|
||||
"@types/wicg-file-system-access": "^2020.9.5",
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.11.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.11.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
# [1.11.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** add listing workspace users ([fb41f65](https://github.com/standardnotes/app/commit/fb41f651eca45dd62314609c2f389d5dc683c36e))
|
||||
|
||||
## [1.10.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.10.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.10.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api:** workspace creation arguments ([a275a45](https://github.com/standardnotes/app/commit/a275a45753abc4a29a89de5fe784260165297bbe))
|
||||
|
||||
## [1.10.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files",
|
||||
"version": "1.10.12",
|
||||
"version": "1.11.2",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -30,7 +30,7 @@
|
||||
"ts-jest": "^28.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/common": "^1.32.0",
|
||||
"@standardnotes/common": "^1.39.0",
|
||||
"@standardnotes/encryption": "workspace:*",
|
||||
"@standardnotes/models": "workspace:*",
|
||||
"@standardnotes/responses": "workspace:*",
|
||||
|
||||
@@ -3,6 +3,88 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.42.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.42.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.42.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.42.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.42.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
# [3.42.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
### Features
|
||||
|
||||
* item linking ([#1779](https://github.com/standardnotes/app/issues/1779)) ([e3f2842](https://github.com/standardnotes/app/commit/e3f28421ff042c635ad2ae645c102c27e3e3f9c7))
|
||||
|
||||
## [3.41.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **mobile:** issue with black screen on iOS ([#1777](https://github.com/standardnotes/app/issues/1777)) ([9fc77d8](https://github.com/standardnotes/app/commit/9fc77d861e501d63ee97875bba2ddda51d14fd0b))
|
||||
|
||||
## [3.41.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.41.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.41.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.41.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.41.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.41.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.41.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.41.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.41.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
# [3.41.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **mobile:** disable landscape for iPhone ([a365c17](https://github.com/standardnotes/app/commit/a365c17c46f38e6d8cacade47542fd4ec625bc0d))
|
||||
|
||||
### Features
|
||||
|
||||
* **mobile:** delete account option in settings ([#1768](https://github.com/standardnotes/app/issues/1768)) ([7d21046](https://github.com/standardnotes/app/commit/7d21046b5189eb14a5957bda6fa788d743d8ebb6))
|
||||
|
||||
## [3.40.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.40.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
@@ -302,7 +302,7 @@ PODS:
|
||||
- React
|
||||
- react-native-version-info (1.1.1):
|
||||
- React-Core
|
||||
- react-native-webview (11.17.2):
|
||||
- react-native-webview (11.23.1):
|
||||
- React-Core
|
||||
- React-perflogger (0.67.4)
|
||||
- React-RCTActionSheet (0.67.4):
|
||||
@@ -719,7 +719,7 @@ SPEC CHECKSUMS:
|
||||
react-native-sodium-jsi: c8901320767d00385f9111bc95ba25aaa9a29890
|
||||
react-native-static-server: 880d9b697ef68722e6fa06ae06e6b843e906ed0b
|
||||
react-native-version-info: a106f23009ac0db4ee00de39574eb546682579b9
|
||||
react-native-webview: 380c1a03ec94b7ed764dac8db1e7c9952d08c93a
|
||||
react-native-webview: d33e2db8925d090871ffeb232dfa50cb3a727581
|
||||
React-perflogger: 0afaf2f01a47fd0fc368a93bfbb5bd3b26db6e7f
|
||||
React-RCTActionSheet: 59f35c4029e0b532fc42114241a06e170b7431a2
|
||||
React-RCTAnimation: aae4f4bed122e78bdab72f7118d291d70a932ce2
|
||||
|
||||
@@ -138,6 +138,10 @@
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~iphone</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>supportsAlternateIcons</key>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/mobile",
|
||||
"version": "3.40.1",
|
||||
"version": "3.42.5",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-or-later",
|
||||
@@ -80,7 +80,7 @@
|
||||
"react-native-url-polyfill": "^1.3.0",
|
||||
"react-native-vector-icons": "^9.1.0",
|
||||
"react-native-version-info": "^1.1.1",
|
||||
"react-native-webview": "11.17.2",
|
||||
"react-native-webview": "11.23.1",
|
||||
"react-native-zip-archive": "^6.0.6",
|
||||
"react-navigation-header-buttons": "^9.0.1",
|
||||
"styled-components": "5.3.5"
|
||||
|
||||
@@ -68,7 +68,7 @@ export const useFiles = ({ note }: Props) => {
|
||||
const filesService = application.getFilesService()
|
||||
|
||||
const reloadAttachedFiles = useCallback(() => {
|
||||
setAttachedFiles(application.items.getFilesForNote(note).sort(filesService.sortByName))
|
||||
setAttachedFiles(application.items.getSortedFilesLinkingToItem(note).sort(filesService.sortByName))
|
||||
}, [application.items, filesService.sortByName, note])
|
||||
|
||||
const reloadAllFiles = useCallback(() => {
|
||||
|
||||
@@ -204,6 +204,12 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo
|
||||
onError={(err) => console.error('An error has occurred', err)}
|
||||
onHttpError={() => console.error('An HTTP error occurred')}
|
||||
onMessage={onMessage}
|
||||
onContentProcessDidTerminate={() => {
|
||||
webViewRef.current?.reload()
|
||||
}}
|
||||
onRenderProcessGone={() => {
|
||||
webViewRef.current?.reload()
|
||||
}}
|
||||
allowFileAccess={true}
|
||||
allowUniversalAccessFromFileURLs={true}
|
||||
injectedJavaScriptBeforeContentLoaded={injectedJS}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { ApplicationContext } from '@Root/ApplicationContext'
|
||||
import { ButtonCell } from '@Root/Components/ButtonCell'
|
||||
import { SectionHeader } from '@Root/Components/SectionHeader'
|
||||
import { TableSection } from '@Root/Components/TableSection'
|
||||
import { ButtonType } from '@standardnotes/snjs'
|
||||
import React, { useContext, useState } from 'react'
|
||||
import { RegularView } from './AuthSection.styled'
|
||||
|
||||
export const DeleteSection = () => {
|
||||
const application = useContext(ApplicationContext)
|
||||
const [deleting, setDeleting] = useState(false)
|
||||
|
||||
const deleteAccount = async () => {
|
||||
const message =
|
||||
"This action is irreversible. After deletion completes, you will be signed out on all devices, and this application will exit. If you have an active paid subscription, cancel the subscription first. Otherwise, if you'd like to keep the subscription, you can re-register with the same email after deletion, and your subscription will be linked back up with your account."
|
||||
const confirmed = await application!.alertService.confirm(
|
||||
message,
|
||||
'Are you sure?',
|
||||
'Delete Account',
|
||||
ButtonType.Danger,
|
||||
)
|
||||
|
||||
if (!confirmed) {
|
||||
return
|
||||
}
|
||||
|
||||
setDeleting(true)
|
||||
|
||||
const result = await application!.user.deleteAccount()
|
||||
if (result.error) {
|
||||
void application!.alertService.alert('An error occurred while deleting your account. Please try again.')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<RegularView>
|
||||
<SectionHeader />
|
||||
<TableSection>
|
||||
<ButtonCell
|
||||
first
|
||||
last
|
||||
important
|
||||
leftAligned={true}
|
||||
title={deleting ? 'Deleting...' : 'Delete Account'}
|
||||
onPress={deleteAccount}
|
||||
></ButtonCell>
|
||||
</TableSection>
|
||||
</RegularView>
|
||||
)
|
||||
}
|
||||
@@ -153,7 +153,7 @@ export const WorkspacesSection = () => {
|
||||
}
|
||||
|
||||
await appGroup.unloadCurrentAndCreateNewDescriptor()
|
||||
}, [WorkspaceAction.AddAnother, appGroup, applicationDescriptors, getWorkspaceActionConfirmation])
|
||||
}, [WorkspaceAction.AddAnother, appGroup, getWorkspaceActionConfirmation])
|
||||
|
||||
const signOutAllWorkspaces = useCallback(async () => {
|
||||
try {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ApplicationEvent, FeatureIdentifier, FeatureStatus } from '@standardnot
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { AuthSection } from './Sections/AuthSection'
|
||||
import { CompanySection } from './Sections/CompanySection'
|
||||
import { DeleteSection } from './Sections/DeleteSection'
|
||||
import { EncryptionSection } from './Sections/EncryptionSection'
|
||||
import { NewMobileSection } from './Sections/NewMobilePreview'
|
||||
import { OptionsSection } from './Sections/OptionsSection'
|
||||
@@ -68,6 +69,7 @@ export const Settings = (props: Props) => {
|
||||
<ProtectionsSection title="Protections" protectionsAvailable={protectionsAvailable} />
|
||||
<EncryptionSection encryptionAvailable={!!encryptionAvailable} title={'Encryption Status'} />
|
||||
<CompanySection title="Standard Notes" />
|
||||
{application.hasAccount() && <DeleteSection />}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ export const NoteSideMenu = React.memo((props: Props) => {
|
||||
setAttachedFilesLength(0)
|
||||
return
|
||||
}
|
||||
setAttachedFilesLength(application.items.getFilesForNote(note).length)
|
||||
setAttachedFilesLength(application.items.getSortedFilesLinkingToItem(note).length)
|
||||
}, [application, note])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -147,7 +147,7 @@ export const NoteSideMenu = React.memo((props: Props) => {
|
||||
return
|
||||
}
|
||||
const removeFilesObserver = application.streamItems(ContentType.File, () => {
|
||||
setAttachedFilesLength(application.items.getFilesForNote(note).length)
|
||||
setAttachedFilesLength(application.items.getSortedFilesLinkingToItem(note).length)
|
||||
})
|
||||
return () => {
|
||||
removeFilesObserver()
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.27.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
### Features
|
||||
|
||||
* item linking ([#1779](https://github.com/standardnotes/app/issues/1779)) ([e3f2842](https://github.com/standardnotes/app/commit/e3f28421ff042c635ad2ae645c102c27e3e3f9c7))
|
||||
|
||||
# [1.26.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** add listing workspace users ([fb41f65](https://github.com/standardnotes/app/commit/fb41f651eca45dd62314609c2f389d5dc683c36e))
|
||||
|
||||
# [1.25.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** add listing workspaces ([8376381](https://github.com/standardnotes/app/commit/837638198112df24c30e53803a7ddf5f841975b0))
|
||||
|
||||
## [1.24.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
## [1.24.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api:** workspace creation arguments ([a275a45](https://github.com/standardnotes/app/commit/a275a45753abc4a29a89de5fe784260165297bbe))
|
||||
|
||||
# [1.24.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/models",
|
||||
"version": "1.24.0",
|
||||
"version": "1.27.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -31,7 +31,7 @@
|
||||
"typescript": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/common": "^1.32.0",
|
||||
"@standardnotes/common": "^1.39.0",
|
||||
"@standardnotes/features": "workspace:*",
|
||||
"@standardnotes/responses": "workspace:*",
|
||||
"@standardnotes/utils": "workspace:*",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { ContenteReferenceType } from './ContenteReferenceType'
|
||||
import { ContentReferenceType } from './ContenteReferenceType'
|
||||
|
||||
export interface AnonymousReference {
|
||||
uuid: string
|
||||
content_type: ContentType
|
||||
reference_type: ContenteReferenceType
|
||||
reference_type: ContentReferenceType
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export enum ContenteReferenceType {
|
||||
export enum ContentReferenceType {
|
||||
TagToParentTag = 'TagToParentTag',
|
||||
FileToNote = 'FileToNote',
|
||||
TagToFile = 'TagToFile',
|
||||
FileToNote = 'FileToNote',
|
||||
FileToFile = 'FileToFile',
|
||||
NoteToNote = 'NoteToNote',
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { AnonymousReference } from './AnonymousReference'
|
||||
import { ContentReferenceType } from './ContenteReferenceType'
|
||||
|
||||
export interface FileToFileReference extends AnonymousReference {
|
||||
content_type: ContentType.File
|
||||
reference_type: ContentReferenceType.FileToFile
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { AnonymousReference } from './AnonymousReference'
|
||||
import { ContenteReferenceType } from './ContenteReferenceType'
|
||||
import { ContentReferenceType } from './ContenteReferenceType'
|
||||
|
||||
export interface FileToNoteReference extends AnonymousReference {
|
||||
content_type: ContentType.Note
|
||||
reference_type: ContenteReferenceType.FileToNote
|
||||
reference_type: ContentReferenceType.FileToNote
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { ItemInterface } from '../Item/Interfaces/ItemInterface'
|
||||
import { ContenteReferenceType } from './ContenteReferenceType'
|
||||
import { ContentReferenceType } from './ContenteReferenceType'
|
||||
import { ContentReference } from './ContentReference'
|
||||
import { LegacyAnonymousReference } from './LegacyAnonymousReference'
|
||||
import { LegacyTagToNoteReference } from './LegacyTagToNoteReference'
|
||||
@@ -26,5 +26,5 @@ export const isLegacyTagToNoteReference = (
|
||||
}
|
||||
|
||||
export const isTagToParentTagReference = (x: ContentReference): x is TagToParentTagReference => {
|
||||
return isReference(x) && x.reference_type === ContenteReferenceType.TagToParentTag
|
||||
return isReference(x) && x.reference_type === ContentReferenceType.TagToParentTag
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { AnonymousReference } from './AnonymousReference'
|
||||
import { ContentReferenceType } from './ContenteReferenceType'
|
||||
|
||||
export interface NoteToNoteReference extends AnonymousReference {
|
||||
content_type: ContentType.Note
|
||||
reference_type: ContentReferenceType.NoteToNote
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { AnonymousReference } from './AnonymousReference'
|
||||
import { ContenteReferenceType } from './ContenteReferenceType'
|
||||
import { ContentReferenceType } from './ContenteReferenceType'
|
||||
|
||||
export interface TagToFileReference extends AnonymousReference {
|
||||
content_type: ContentType.File
|
||||
reference_type: ContenteReferenceType.TagToFile
|
||||
reference_type: ContentReferenceType.TagToFile
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { AnonymousReference } from './AnonymousReference'
|
||||
import { ContenteReferenceType } from './ContenteReferenceType'
|
||||
import { ContentReferenceType } from './ContenteReferenceType'
|
||||
|
||||
export interface TagToParentTagReference extends AnonymousReference {
|
||||
content_type: ContentType.Tag
|
||||
reference_type: ContenteReferenceType.TagToParentTag
|
||||
reference_type: ContentReferenceType.TagToParentTag
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { WorkspaceType } from '@standardnotes/common'
|
||||
|
||||
export type Workspace = {
|
||||
uuid: string
|
||||
type: WorkspaceType
|
||||
name: string | null
|
||||
keyRotationIndex: number
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Uuid, WorkspaceAccessLevel, WorkspaceUserStatus } from '@standardnotes/common'
|
||||
|
||||
export type WorkspaceUser = {
|
||||
uuid: Uuid
|
||||
accessLevel: WorkspaceAccessLevel
|
||||
userUuid: Uuid
|
||||
userDisplayName: string | null
|
||||
workspaceUuid: Uuid
|
||||
encryptedWorkspaceKey: string | null
|
||||
publicKey: string | null
|
||||
encryptedPrivateKey: string | null
|
||||
status: WorkspaceUserStatus
|
||||
keyRotationIndex: number
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
}
|
||||
+20
-7
@@ -1,10 +1,10 @@
|
||||
import { NoteContent } from './../../../Syncable/Note/NoteContent'
|
||||
import { NoteContent } from '../../../Syncable/Note/NoteContent'
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { DecryptedItem, EncryptedItem } from '../../../Abstract/Item'
|
||||
import { DecryptedPayload, EncryptedPayload, PayloadTimestampDefaults } from '../../../Abstract/Payload'
|
||||
import { ItemCollection } from './ItemCollection'
|
||||
import { FillItemContent } from '../../../Abstract/Content/ItemContent'
|
||||
import { TagNotesIndex } from './TagNotesIndex'
|
||||
import { TagItemsIndex } from './TagItemsIndex'
|
||||
import { ItemDelta } from '../../Index/ItemDelta'
|
||||
import { AnyItemInterface } from '../../../Abstract/Item/Interfaces/UnionTypes'
|
||||
|
||||
@@ -24,10 +24,10 @@ describe('tag notes index', () => {
|
||||
return new EncryptedItem(payload)
|
||||
}
|
||||
|
||||
const createDecryptedItem = (uuid?: string) => {
|
||||
const createDecryptedItem = (uuid?: string, content_type = ContentType.Note) => {
|
||||
const payload = new DecryptedPayload({
|
||||
uuid: uuid || String(Math.random()),
|
||||
content_type: ContentType.Note,
|
||||
content_type,
|
||||
content: FillItemContent<NoteContent>({
|
||||
title: 'foo',
|
||||
}),
|
||||
@@ -46,20 +46,33 @@ describe('tag notes index', () => {
|
||||
}
|
||||
}
|
||||
|
||||
it('should count both notes and files', () => {
|
||||
const collection = new ItemCollection()
|
||||
const index = new TagItemsIndex(collection)
|
||||
|
||||
const decryptedNote = createDecryptedItem('note')
|
||||
const decryptedFile = createDecryptedItem('file')
|
||||
collection.set([decryptedNote, decryptedFile])
|
||||
index.onChange(createChangeDelta(decryptedNote))
|
||||
index.onChange(createChangeDelta(decryptedFile))
|
||||
|
||||
expect(index.allCountableItemsCount()).toEqual(2)
|
||||
})
|
||||
|
||||
it('should decrement count after decrypted note becomes errored', () => {
|
||||
const collection = new ItemCollection()
|
||||
const index = new TagNotesIndex(collection)
|
||||
const index = new TagItemsIndex(collection)
|
||||
|
||||
const decryptedItem = createDecryptedItem()
|
||||
collection.set(decryptedItem)
|
||||
index.onChange(createChangeDelta(decryptedItem))
|
||||
|
||||
expect(index.allCountableNotesCount()).toEqual(1)
|
||||
expect(index.allCountableItemsCount()).toEqual(1)
|
||||
|
||||
const encryptedItem = createEncryptedItem(decryptedItem.uuid)
|
||||
collection.set(encryptedItem)
|
||||
index.onChange(createChangeDelta(encryptedItem))
|
||||
|
||||
expect(index.allCountableNotesCount()).toEqual(0)
|
||||
expect(index.allCountableItemsCount()).toEqual(0)
|
||||
})
|
||||
})
|
||||
+34
-32
@@ -7,22 +7,22 @@ import { ItemDelta } from '../../Index/ItemDelta'
|
||||
import { isDecryptedItem, ItemInterface } from '../../../Abstract/Item'
|
||||
|
||||
type AllNotesUuidSignifier = undefined
|
||||
export type TagNoteCountChangeObserver = (tagUuid: Uuid | AllNotesUuidSignifier) => void
|
||||
export type TagItemCountChangeObserver = (tagUuid: Uuid | AllNotesUuidSignifier) => void
|
||||
|
||||
export class TagNotesIndex implements SNIndex {
|
||||
private tagToNotesMap: Partial<Record<Uuid, Set<Uuid>>> = {}
|
||||
private allCountableNotes = new Set<Uuid>()
|
||||
export class TagItemsIndex implements SNIndex {
|
||||
private tagToItemsMap: Partial<Record<Uuid, Set<Uuid>>> = {}
|
||||
private allCountableItems = new Set<Uuid>()
|
||||
|
||||
constructor(private collection: ItemCollection, public observers: TagNoteCountChangeObserver[] = []) {}
|
||||
constructor(private collection: ItemCollection, public observers: TagItemCountChangeObserver[] = []) {}
|
||||
|
||||
private isNoteCountable = (note: ItemInterface) => {
|
||||
if (isDecryptedItem(note)) {
|
||||
return !note.archived && !note.trashed
|
||||
private isItemCountable = (item: ItemInterface) => {
|
||||
if (isDecryptedItem(item)) {
|
||||
return !item.archived && !item.trashed
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public addCountChangeObserver(observer: TagNoteCountChangeObserver): () => void {
|
||||
public addCountChangeObserver(observer: TagItemCountChangeObserver): () => void {
|
||||
this.observers.push(observer)
|
||||
|
||||
const thislessEventObservers = this.observers
|
||||
@@ -37,30 +37,32 @@ export class TagNotesIndex implements SNIndex {
|
||||
}
|
||||
}
|
||||
|
||||
public allCountableNotesCount(): number {
|
||||
return this.allCountableNotes.size
|
||||
public allCountableItemsCount(): number {
|
||||
return this.allCountableItems.size
|
||||
}
|
||||
|
||||
public countableNotesForTag(tag: SNTag): number {
|
||||
return this.tagToNotesMap[tag.uuid]?.size || 0
|
||||
public countableItemsForTag(tag: SNTag): number {
|
||||
return this.tagToItemsMap[tag.uuid]?.size || 0
|
||||
}
|
||||
|
||||
public onChange(delta: ItemDelta): void {
|
||||
const notes = [...delta.changed, ...delta.inserted, ...delta.discarded].filter(
|
||||
(i) => i.content_type === ContentType.Note,
|
||||
const items = [...delta.changed, ...delta.inserted, ...delta.discarded].filter(
|
||||
(i) => i.content_type === ContentType.Note || i.content_type === ContentType.File,
|
||||
)
|
||||
const tags = [...delta.changed, ...delta.inserted].filter(isDecryptedItem).filter(isTag)
|
||||
|
||||
this.receiveNoteChanges(notes)
|
||||
this.receiveItemChanges(items)
|
||||
this.receiveTagChanges(tags)
|
||||
}
|
||||
|
||||
private receiveTagChanges(tags: SNTag[]): void {
|
||||
for (const tag of tags) {
|
||||
const uuids = tag.noteReferences.map((ref) => ref.uuid)
|
||||
const countableUuids = uuids.filter((uuid) => this.allCountableNotes.has(uuid))
|
||||
const previousSet = this.tagToNotesMap[tag.uuid]
|
||||
this.tagToNotesMap[tag.uuid] = new Set(countableUuids)
|
||||
const uuids = tag.references
|
||||
.filter((ref) => ref.content_type === ContentType.Note || ref.content_type === ContentType.File)
|
||||
.map((ref) => ref.uuid)
|
||||
const countableUuids = uuids.filter((uuid) => this.allCountableItems.has(uuid))
|
||||
const previousSet = this.tagToItemsMap[tag.uuid]
|
||||
this.tagToItemsMap[tag.uuid] = new Set(countableUuids)
|
||||
|
||||
if (previousSet?.size !== countableUuids.length) {
|
||||
this.notifyObservers(tag.uuid)
|
||||
@@ -68,26 +70,26 @@ export class TagNotesIndex implements SNIndex {
|
||||
}
|
||||
}
|
||||
|
||||
private receiveNoteChanges(notes: ItemInterface[]): void {
|
||||
const previousAllCount = this.allCountableNotes.size
|
||||
private receiveItemChanges(items: ItemInterface[]): void {
|
||||
const previousAllCount = this.allCountableItems.size
|
||||
|
||||
for (const note of notes) {
|
||||
const isCountable = this.isNoteCountable(note)
|
||||
for (const item of items) {
|
||||
const isCountable = this.isItemCountable(item)
|
||||
if (isCountable) {
|
||||
this.allCountableNotes.add(note.uuid)
|
||||
this.allCountableItems.add(item.uuid)
|
||||
} else {
|
||||
this.allCountableNotes.delete(note.uuid)
|
||||
this.allCountableItems.delete(item.uuid)
|
||||
}
|
||||
|
||||
const associatedTagUuids = this.collection.uuidsThatReferenceUuid(note.uuid)
|
||||
const associatedTagUuids = this.collection.uuidsThatReferenceUuid(item.uuid)
|
||||
|
||||
for (const tagUuid of associatedTagUuids) {
|
||||
const set = this.setForTag(tagUuid)
|
||||
const previousCount = set.size
|
||||
if (isCountable) {
|
||||
set.add(note.uuid)
|
||||
set.add(item.uuid)
|
||||
} else {
|
||||
set.delete(note.uuid)
|
||||
set.delete(item.uuid)
|
||||
}
|
||||
if (previousCount !== set.size) {
|
||||
this.notifyObservers(tagUuid)
|
||||
@@ -95,16 +97,16 @@ export class TagNotesIndex implements SNIndex {
|
||||
}
|
||||
}
|
||||
|
||||
if (previousAllCount !== this.allCountableNotes.size) {
|
||||
if (previousAllCount !== this.allCountableItems.size) {
|
||||
this.notifyObservers(undefined)
|
||||
}
|
||||
}
|
||||
|
||||
private setForTag(uuid: Uuid): Set<Uuid> {
|
||||
let set = this.tagToNotesMap[uuid]
|
||||
let set = this.tagToItemsMap[uuid]
|
||||
if (!set) {
|
||||
set = new Set()
|
||||
this.tagToNotesMap[uuid] = set
|
||||
this.tagToItemsMap[uuid] = set
|
||||
}
|
||||
return set
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { SNNote } from '../Note/Note'
|
||||
import { FileContent } from './File'
|
||||
import { FileContent, FileItem } from './File'
|
||||
import { FileToNoteReference } from '../../Abstract/Reference/FileToNoteReference'
|
||||
import { ContenteReferenceType } from '../../Abstract/Reference/ContenteReferenceType'
|
||||
import { ContentReferenceType } from '../../Abstract/Reference/ContenteReferenceType'
|
||||
import { DecryptedItemMutator } from '../../Abstract/Item/Mutator/DecryptedItemMutator'
|
||||
import { FileToFileReference } from '../../Abstract/Reference/FileToFileReference'
|
||||
|
||||
export class FileMutator extends DecryptedItemMutator<FileContent> {
|
||||
set name(newName: string) {
|
||||
@@ -16,7 +17,7 @@ export class FileMutator extends DecryptedItemMutator<FileContent> {
|
||||
|
||||
public addNote(note: SNNote): void {
|
||||
const reference: FileToNoteReference = {
|
||||
reference_type: ContenteReferenceType.FileToNote,
|
||||
reference_type: ContentReferenceType.FileToNote,
|
||||
content_type: ContentType.Note,
|
||||
uuid: note.uuid,
|
||||
}
|
||||
@@ -30,4 +31,22 @@ export class FileMutator extends DecryptedItemMutator<FileContent> {
|
||||
const references = this.immutableItem.references.filter((ref) => ref.uuid !== note.uuid)
|
||||
this.mutableContent.references = references
|
||||
}
|
||||
|
||||
public addFile(file: FileItem): void {
|
||||
if (this.immutableItem.isReferencingItem(file)) {
|
||||
return
|
||||
}
|
||||
|
||||
const reference: FileToFileReference = {
|
||||
uuid: file.uuid,
|
||||
content_type: ContentType.File,
|
||||
reference_type: ContentReferenceType.FileToFile,
|
||||
}
|
||||
|
||||
this.mutableContent.references.push(reference)
|
||||
}
|
||||
|
||||
public removeFile(file: FileItem): void {
|
||||
this.mutableContent.references = this.mutableContent.references.filter((r) => r.uuid !== file.uuid)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { AppDataField } from '../../Abstract/Item/Types/AppDataField'
|
||||
import { NoteContent } from './NoteContent'
|
||||
import { DecryptedItemMutator } from '../../Abstract/Item/Mutator/DecryptedItemMutator'
|
||||
import { SNNote } from './Note'
|
||||
import { NoteToNoteReference } from '../../Abstract/Reference/NoteToNoteReference'
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { ContentReferenceType } from '../../Abstract/Item'
|
||||
|
||||
export class NoteMutator extends DecryptedItemMutator<NoteContent> {
|
||||
set title(title: string) {
|
||||
@@ -38,4 +42,22 @@ export class NoteMutator extends DecryptedItemMutator<NoteContent> {
|
||||
this.mutableContent.spellcheck = !this.mutableContent.spellcheck
|
||||
}
|
||||
}
|
||||
|
||||
public addNote(note: SNNote): void {
|
||||
if (this.immutableItem.isReferencingItem(note)) {
|
||||
return
|
||||
}
|
||||
|
||||
const reference: NoteToNoteReference = {
|
||||
uuid: note.uuid,
|
||||
content_type: ContentType.Note,
|
||||
reference_type: ContentReferenceType.NoteToNote,
|
||||
}
|
||||
|
||||
this.mutableContent.references.push(reference)
|
||||
}
|
||||
|
||||
public removeNote(note: SNNote): void {
|
||||
this.mutableContent.references = this.mutableContent.references.filter((r) => r.uuid !== note.uuid)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { ContenteReferenceType, MutationType } from '../../Abstract/Item'
|
||||
import { ContentReferenceType, MutationType } from '../../Abstract/Item'
|
||||
import { createFile, createTag } from '../../Utilities/Test/SpecUtils'
|
||||
import { SNTag } from './Tag'
|
||||
import { TagMutator } from './TagMutator'
|
||||
@@ -16,7 +16,7 @@ describe('tag mutator', () => {
|
||||
expect(result.content.references[0]).toEqual({
|
||||
uuid: file.uuid,
|
||||
content_type: ContentType.File,
|
||||
reference_type: ContenteReferenceType.TagToFile,
|
||||
reference_type: ContentReferenceType.TagToFile,
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { FileItem } from '../File'
|
||||
import { SNNote } from '../Note'
|
||||
import { isTagToParentTagReference } from '../../Abstract/Reference/Functions'
|
||||
import { TagToParentTagReference } from '../../Abstract/Reference/TagToParentTagReference'
|
||||
import { ContenteReferenceType } from '../../Abstract/Reference/ContenteReferenceType'
|
||||
import { ContentReferenceType } from '../../Abstract/Reference/ContenteReferenceType'
|
||||
import { DecryptedItemMutator } from '../../Abstract/Item/Mutator/DecryptedItemMutator'
|
||||
import { TagToFileReference } from '../../Abstract/Reference/TagToFileReference'
|
||||
|
||||
@@ -21,7 +21,7 @@ export class TagMutator extends DecryptedItemMutator<TagContent> {
|
||||
const references = this.immutableItem.references.filter((ref) => !isTagToParentTagReference(ref))
|
||||
|
||||
const reference: TagToParentTagReference = {
|
||||
reference_type: ContenteReferenceType.TagToParentTag,
|
||||
reference_type: ContentReferenceType.TagToParentTag,
|
||||
content_type: ContentType.Tag,
|
||||
uuid: tag.uuid,
|
||||
}
|
||||
@@ -41,7 +41,7 @@ export class TagMutator extends DecryptedItemMutator<TagContent> {
|
||||
}
|
||||
|
||||
const reference: TagToFileReference = {
|
||||
reference_type: ContenteReferenceType.TagToFile,
|
||||
reference_type: ContentReferenceType.TagToFile,
|
||||
content_type: ContentType.File,
|
||||
uuid: file.uuid,
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ export * from './Api/Subscription/Invitation'
|
||||
export * from './Api/Subscription/InvitationStatus'
|
||||
export * from './Api/Subscription/InviteeIdentifierType'
|
||||
export * from './Api/Subscription/InviterIdentifierType'
|
||||
export * from './Api/Workspace/Workspace'
|
||||
export * from './Api/Workspace/WorkspaceUser'
|
||||
export * from './Device/Environment'
|
||||
export * from './Device/Platform'
|
||||
export * from './Local/KeyParams/RootKeyParamsInterface'
|
||||
@@ -36,7 +38,7 @@ export * from './Local/RootKey/RootKeyContent'
|
||||
export * from './Local/RootKey/RootKeyInterface'
|
||||
export * from './Runtime/Collection/CollectionSort'
|
||||
export * from './Runtime/Collection/Item/ItemCollection'
|
||||
export * from './Runtime/Collection/Item/TagNotesIndex'
|
||||
export * from './Runtime/Collection/Item/TagItemsIndex'
|
||||
export * from './Runtime/Collection/Payload/ImmutablePayloadCollection'
|
||||
export * from './Runtime/Collection/Payload/PayloadCollection'
|
||||
export * from './Runtime/Deltas'
|
||||
|
||||
@@ -3,6 +3,78 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.3.140](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.139](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.138](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.137](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.136](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.135](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.134](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.133](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.132](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.131](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.130](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.129](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.128](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.127](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.126](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.125](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.124](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.123](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.122](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/releases",
|
||||
"version": "1.3.122",
|
||||
"version": "1.3.140",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "dist/releases.json",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.11.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** add listing workspace users ([fb41f65](https://github.com/standardnotes/app/commit/fb41f651eca45dd62314609c2f389d5dc683c36e))
|
||||
|
||||
## [1.10.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/responses
|
||||
|
||||
## [1.10.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api:** workspace creation arguments ([a275a45](https://github.com/standardnotes/app/commit/a275a45753abc4a29a89de5fe784260165297bbe))
|
||||
|
||||
## [1.10.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/responses
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/responses",
|
||||
"version": "1.10.4",
|
||||
"version": "1.11.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -31,7 +31,7 @@
|
||||
"ts-jest": "^28.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/common": "^1.32.0",
|
||||
"@standardnotes/common": "^1.39.0",
|
||||
"@standardnotes/features": "workspace:*",
|
||||
"@standardnotes/security": "^1.1.0",
|
||||
"reflect-metadata": "^0.1.13"
|
||||
|
||||
@@ -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.32.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
## [1.32.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
# [1.32.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** add keyshare initiation for workspaces ([fabce1f](https://github.com/standardnotes/app/commit/fabce1f7aca5c9caaa5bb3908b2dc68063c690d0))
|
||||
|
||||
# [1.31.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
### Features
|
||||
|
||||
* item linking ([#1779](https://github.com/standardnotes/app/issues/1779)) ([e3f2842](https://github.com/standardnotes/app/commit/e3f28421ff042c635ad2ae645c102c27e3e3f9c7))
|
||||
|
||||
# [1.30.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** add listing workspace users ([fb41f65](https://github.com/standardnotes/app/commit/fb41f651eca45dd62314609c2f389d5dc683c36e))
|
||||
|
||||
# [1.29.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** add access level to invitation requests ([49b3d27](https://github.com/standardnotes/app/commit/49b3d2714fbe4ac9f9242097b8df6b4e3d604513))
|
||||
|
||||
# [1.28.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** add accepting invites to workspace ([15e2c82](https://github.com/standardnotes/app/commit/15e2c82e654ce3f983cd42b01f14d18435b3e939))
|
||||
* **api:** add listing workspaces ([8376381](https://github.com/standardnotes/app/commit/837638198112df24c30e53803a7ddf5f841975b0))
|
||||
|
||||
## [1.27.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* dark mode not working in editors ([#1773](https://github.com/standardnotes/app/issues/1773)) ([27d2c95](https://github.com/standardnotes/app/commit/27d2c95b5b82645bf6c0372979f05236c83a10f6))
|
||||
|
||||
# [1.27.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api:** workspace creation arguments ([a275a45](https://github.com/standardnotes/app/commit/a275a45753abc4a29a89de5fe784260165297bbe))
|
||||
* linter issues ([e2e7605](https://github.com/standardnotes/app/commit/e2e76054142e8d67cac69280e2a3efcb15cf67f1))
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** add inviting to workspace ([158ca6a](https://github.com/standardnotes/app/commit/158ca6ac6a428e082c8f984791f80fcaadcb3e34))
|
||||
|
||||
# [1.26.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/services",
|
||||
"version": "1.26.0",
|
||||
"version": "1.32.2",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -26,7 +26,7 @@
|
||||
"dependencies": {
|
||||
"@standardnotes/api": "workspace:^",
|
||||
"@standardnotes/auth": "^3.19.4",
|
||||
"@standardnotes/common": "^1.32.0",
|
||||
"@standardnotes/common": "^1.39.0",
|
||||
"@standardnotes/encryption": "workspace:^",
|
||||
"@standardnotes/files": "workspace:^",
|
||||
"@standardnotes/models": "workspace:^",
|
||||
|
||||
@@ -77,7 +77,7 @@ describe('fileService', () => {
|
||||
crypto.xchacha20StreamEncryptorPush = jest.fn().mockReturnValue(new Uint8Array())
|
||||
})
|
||||
|
||||
it.only('should cache file after download', async () => {
|
||||
it('should cache file after download', async () => {
|
||||
const file = {
|
||||
uuid: '1',
|
||||
decryptedSize: 100_000,
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
FileItem,
|
||||
SNTag,
|
||||
SmartView,
|
||||
TagNoteCountChangeObserver,
|
||||
TagItemCountChangeObserver,
|
||||
DecryptedPayloadInterface,
|
||||
EncryptedItemInterface,
|
||||
DecryptedTransferPayload,
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
SNTheme,
|
||||
DisplayOptions,
|
||||
ItemsKeyInterface,
|
||||
ItemContent,
|
||||
} from '@standardnotes/models'
|
||||
|
||||
export interface ItemsClientInterface {
|
||||
@@ -23,12 +24,12 @@ export interface ItemsClientInterface {
|
||||
|
||||
disassociateFileWithNote(file: FileItem, note: SNNote): Promise<FileItem>
|
||||
|
||||
getFilesForNote(note: SNNote): FileItem[]
|
||||
|
||||
renameFile(file: FileItem, name: string): Promise<FileItem>
|
||||
|
||||
addTagToNote(note: SNNote, tag: SNTag, addHierarchy: boolean): Promise<SNTag[]>
|
||||
|
||||
addTagToFile(file: FileItem, tag: SNTag, addHierarchy: boolean): Promise<SNTag[]>
|
||||
|
||||
/** Creates an unmanaged, un-inserted item from a payload. */
|
||||
createItemFromPayload(payload: DecryptedPayloadInterface): DecryptedItemInterface
|
||||
|
||||
@@ -54,7 +55,7 @@ export interface ItemsClientInterface {
|
||||
|
||||
notesMatchingSmartView(view: SmartView): SNNote[]
|
||||
|
||||
addNoteCountChangeObserver(observer: TagNoteCountChangeObserver): () => void
|
||||
addNoteCountChangeObserver(observer: TagItemCountChangeObserver): () => void
|
||||
|
||||
allCountableNotesCount(): number
|
||||
|
||||
@@ -72,6 +73,14 @@ export interface ItemsClientInterface {
|
||||
|
||||
itemsReferencingItem(itemToLookupUuidFor: DecryptedItemInterface, contentType?: ContentType): DecryptedItemInterface[]
|
||||
|
||||
linkNoteToNote(note: SNNote, otherNote: SNNote): Promise<SNNote>
|
||||
linkFileToFile(file: FileItem, otherFile: FileItem): Promise<FileItem>
|
||||
|
||||
unlinkItem(
|
||||
item: DecryptedItemInterface<ItemContent>,
|
||||
itemToUnlink: DecryptedItemInterface<ItemContent>,
|
||||
): Promise<DecryptedItemInterface<ItemContent>>
|
||||
|
||||
/**
|
||||
* Finds tags with title or component starting with a search query and (optionally) not associated with a note
|
||||
* @param searchQuery - The query string to match
|
||||
@@ -101,10 +110,16 @@ export interface ItemsClientInterface {
|
||||
|
||||
/**
|
||||
* Get tags for a note sorted in natural order
|
||||
* @param note - The note whose tags will be returned
|
||||
* @returns Array containing tags associated with a note
|
||||
* @param item - The item whose tags will be returned
|
||||
* @returns Array containing tags associated with an item
|
||||
*/
|
||||
getSortedTagsForNote(note: SNNote): SNTag[]
|
||||
getSortedTagsForItem(item: DecryptedItemInterface<ItemContent>): SNTag[]
|
||||
|
||||
getSortedLinkedFilesForItem(item: DecryptedItemInterface<ItemContent>): FileItem[]
|
||||
getSortedFilesLinkingToItem(item: DecryptedItemInterface<ItemContent>): FileItem[]
|
||||
|
||||
getSortedLinkedNotesForItem(item: DecryptedItemInterface<ItemContent>): SNNote[]
|
||||
getSortedNotesLinkingToItem(item: DecryptedItemInterface<ItemContent>): SNNote[]
|
||||
|
||||
isSmartViewTitle(title: string): boolean
|
||||
|
||||
@@ -137,4 +152,12 @@ export interface ItemsClientInterface {
|
||||
* @returns Whether the item is a template (unmanaged)
|
||||
*/
|
||||
isTemplateItem(item: DecryptedItemInterface): boolean
|
||||
|
||||
/**
|
||||
* @returns `'direct'` if `itemOne` has the reference to `itemTwo`, `'indirect'` if `itemTwo` has the reference to `itemOne`, `'unlinked'` if neither reference each other
|
||||
*/
|
||||
relationshipTypeForItems(
|
||||
itemOne: DecryptedItemInterface,
|
||||
itemTwo: DecryptedItemInterface,
|
||||
): 'direct' | 'indirect' | 'unlinked'
|
||||
}
|
||||
|
||||
@@ -1,8 +1,30 @@
|
||||
import { Uuid, WorkspaceAccessLevel, WorkspaceType } from '@standardnotes/common'
|
||||
import { Workspace, WorkspaceUser } from '@standardnotes/models'
|
||||
|
||||
export interface WorkspaceClientInterface {
|
||||
createWorkspace(dto: {
|
||||
encryptedWorkspaceKey: string
|
||||
encryptedPrivateKey: string
|
||||
publicKey: string
|
||||
workspaceType: WorkspaceType
|
||||
encryptedWorkspaceKey?: string
|
||||
encryptedPrivateKey?: string
|
||||
publicKey?: string
|
||||
workspaceName?: string
|
||||
}): Promise<{ uuid: string } | null>
|
||||
inviteToWorkspace(dto: {
|
||||
inviteeEmail: string
|
||||
workspaceUuid: Uuid
|
||||
accessLevel: WorkspaceAccessLevel
|
||||
}): Promise<{ uuid: string } | null>
|
||||
acceptInvite(dto: {
|
||||
inviteUuid: Uuid
|
||||
userUuid: Uuid
|
||||
publicKey: string
|
||||
encryptedPrivateKey: string
|
||||
}): Promise<{ success: boolean }>
|
||||
listWorkspaces(): Promise<{ ownedWorkspaces: Array<Workspace>; joinedWorkspaces: Array<Workspace> }>
|
||||
listWorkspaceUsers(dto: { workspaceUuid: Uuid }): Promise<{ users: Array<WorkspaceUser> }>
|
||||
initiateKeyshare(dto: {
|
||||
workspaceUuid: Uuid
|
||||
userUuid: Uuid
|
||||
encryptedWorkspaceKey: string
|
||||
}): Promise<{ success: boolean }>
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { WorkspaceApiServiceInterface } from '@standardnotes/api'
|
||||
import { Uuid, WorkspaceAccessLevel, WorkspaceType } from '@standardnotes/common'
|
||||
import { Workspace, WorkspaceUser } from '@standardnotes/models'
|
||||
|
||||
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
|
||||
import { AbstractService } from '../Service/AbstractService'
|
||||
import { WorkspaceClientInterface } from './WorkspaceClientInterface'
|
||||
@@ -11,10 +14,94 @@ export class WorkspaceManager extends AbstractService implements WorkspaceClient
|
||||
super(internalEventBus)
|
||||
}
|
||||
|
||||
async createWorkspace(dto: {
|
||||
async initiateKeyshare(dto: {
|
||||
workspaceUuid: string
|
||||
userUuid: string
|
||||
encryptedWorkspaceKey: string
|
||||
encryptedPrivateKey: string
|
||||
}): Promise<{ success: boolean }> {
|
||||
try {
|
||||
const result = await this.workspaceApiService.initiateKeyshare(dto)
|
||||
|
||||
if (result.data.error !== undefined) {
|
||||
return { success: false }
|
||||
}
|
||||
|
||||
return result.data
|
||||
} catch (error) {
|
||||
return { success: false }
|
||||
}
|
||||
}
|
||||
|
||||
async listWorkspaceUsers(dto: { workspaceUuid: string }): Promise<{ users: WorkspaceUser[] }> {
|
||||
try {
|
||||
const result = await this.workspaceApiService.listWorkspaceUsers(dto)
|
||||
|
||||
if (result.data.error !== undefined) {
|
||||
return { users: [] }
|
||||
}
|
||||
|
||||
return result.data
|
||||
} catch (error) {
|
||||
return { users: [] }
|
||||
}
|
||||
}
|
||||
|
||||
async listWorkspaces(): Promise<{ ownedWorkspaces: Workspace[]; joinedWorkspaces: Workspace[] }> {
|
||||
try {
|
||||
const result = await this.workspaceApiService.listWorkspaces()
|
||||
|
||||
if (result.data.error !== undefined) {
|
||||
return { ownedWorkspaces: [], joinedWorkspaces: [] }
|
||||
}
|
||||
|
||||
return result.data
|
||||
} catch (error) {
|
||||
return { ownedWorkspaces: [], joinedWorkspaces: [] }
|
||||
}
|
||||
}
|
||||
|
||||
async acceptInvite(dto: {
|
||||
inviteUuid: string
|
||||
userUuid: string
|
||||
publicKey: string
|
||||
encryptedPrivateKey: string
|
||||
}): Promise<{ success: boolean }> {
|
||||
try {
|
||||
const result = await this.workspaceApiService.acceptInvite(dto)
|
||||
|
||||
if (result.data.error !== undefined) {
|
||||
return { success: false }
|
||||
}
|
||||
|
||||
return result.data
|
||||
} catch (error) {
|
||||
return { success: false }
|
||||
}
|
||||
}
|
||||
|
||||
async inviteToWorkspace(dto: {
|
||||
inviteeEmail: string
|
||||
workspaceUuid: Uuid
|
||||
accessLevel: WorkspaceAccessLevel
|
||||
}): Promise<{ uuid: string } | null> {
|
||||
try {
|
||||
const result = await this.workspaceApiService.inviteToWorkspace(dto)
|
||||
|
||||
if (result.data.error !== undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
return result.data
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async createWorkspace(dto: {
|
||||
workspaceType: WorkspaceType
|
||||
encryptedWorkspaceKey?: string
|
||||
encryptedPrivateKey?: string
|
||||
publicKey?: string
|
||||
workspaceName?: string
|
||||
}): Promise<{ uuid: string } | null> {
|
||||
try {
|
||||
|
||||
@@ -3,6 +3,52 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.139.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
### Features
|
||||
|
||||
* replace private workspaces with private usernames ([#1783](https://github.com/standardnotes/app/issues/1783)) ([18c821d](https://github.com/standardnotes/app/commit/18c821d8eb51beb6f54211eb1d6eb454303044f5))
|
||||
|
||||
## [2.138.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.138.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
# [2.138.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
### Features
|
||||
|
||||
* item linking ([#1779](https://github.com/standardnotes/app/issues/1779)) ([e3f2842](https://github.com/standardnotes/app/commit/e3f28421ff042c635ad2ae645c102c27e3e3f9c7))
|
||||
|
||||
# [2.137.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** add listing workspace users ([fb41f65](https://github.com/standardnotes/app/commit/fb41f651eca45dd62314609c2f389d5dc683c36e))
|
||||
|
||||
## [2.136.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.136.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.136.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* dark mode not working in editors ([#1773](https://github.com/standardnotes/app/issues/1773)) ([27d2c95](https://github.com/standardnotes/app/commit/27d2c95b5b82645bf6c0372979f05236c83a10f6))
|
||||
|
||||
## [2.136.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api:** workspace creation arguments ([a275a45](https://github.com/standardnotes/app/commit/a275a45753abc4a29a89de5fe784260165297bbe))
|
||||
|
||||
# [2.136.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -53,7 +53,7 @@ import {
|
||||
WorkspaceManager,
|
||||
} from '@standardnotes/services'
|
||||
import { FilesClientInterface } from '@standardnotes/files'
|
||||
import { ComputePrivateWorkspaceIdentifier } from '@standardnotes/encryption'
|
||||
import { ComputePrivateUsername } from '@standardnotes/encryption'
|
||||
import { useBoolean } from '@standardnotes/utils'
|
||||
import {
|
||||
BackupFile,
|
||||
@@ -272,8 +272,8 @@ export class SNApplication
|
||||
return this.componentManagerService
|
||||
}
|
||||
|
||||
public computePrivateWorkspaceIdentifier(userphrase: string, name: string): Promise<string | undefined> {
|
||||
return ComputePrivateWorkspaceIdentifier(this.options.crypto, userphrase, name)
|
||||
public computePrivateUsername(username: string): Promise<string | undefined> {
|
||||
return ComputePrivateUsername(this.options.crypto, username)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1497,7 +1497,10 @@ export class SNApplication
|
||||
const syncEventCallback = async (eventName: ExternalServices.SyncEvent) => {
|
||||
const appEvent = applicationEventForSyncEvent(eventName)
|
||||
if (appEvent) {
|
||||
await this.protocolService.onSyncEvent(eventName)
|
||||
|
||||
await this.notifyEvent(appEvent)
|
||||
|
||||
if (appEvent === ApplicationEvent.CompletedFullSync) {
|
||||
if (!this.handledFullSyncStage) {
|
||||
this.handledFullSyncStage = true
|
||||
@@ -1505,7 +1508,6 @@ export class SNApplication
|
||||
}
|
||||
}
|
||||
}
|
||||
await this.protocolService.onSyncEvent(eventName)
|
||||
}
|
||||
const uninstall = this.syncService.addEventObserver(syncEventCallback)
|
||||
this.serviceObservers.push(uninstall)
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
SetOfflineFeaturesFunctionResponse,
|
||||
StorageKey,
|
||||
} from '@standardnotes/services'
|
||||
import { FeatureIdentifier } from '@standardnotes/features'
|
||||
|
||||
type GetOfflineSubscriptionDetailsResponse = OfflineSubscriptionEntitlements | ClientDisplayableError
|
||||
|
||||
@@ -145,7 +146,10 @@ export class SNFeaturesService
|
||||
|
||||
override async handleApplicationStage(stage: ApplicationStage): Promise<void> {
|
||||
await super.handleApplicationStage(stage)
|
||||
|
||||
if (stage === ApplicationStage.FullSyncCompleted_13) {
|
||||
void this.addDarkTheme()
|
||||
|
||||
if (!this.hasOnlineSubscription()) {
|
||||
const offlineRepo = this.getOfflineRepo()
|
||||
if (offlineRepo) {
|
||||
@@ -155,6 +159,14 @@ export class SNFeaturesService
|
||||
}
|
||||
}
|
||||
|
||||
private async addDarkTheme() {
|
||||
const darkThemeFeature = FeaturesImports.FindNativeFeature(FeatureIdentifier.DarkTheme)
|
||||
|
||||
if (darkThemeFeature) {
|
||||
await this.mapRemoteNativeFeaturesToItems([darkThemeFeature])
|
||||
}
|
||||
}
|
||||
|
||||
public enableExperimentalFeature(identifier: FeaturesImports.FeatureIdentifier): void {
|
||||
const feature = this.getUserFeature(identifier)
|
||||
if (!feature) {
|
||||
@@ -366,7 +378,7 @@ export class SNFeaturesService
|
||||
if (!arraysEqual(this.roles, roles)) {
|
||||
void this.notifyEvent(FeaturesEvent.UserRolesChanged)
|
||||
}
|
||||
await this.storageService.setValue(StorageKey.UserRoles, this.roles)
|
||||
this.storageService.setValue(StorageKey.UserRoles, this.roles)
|
||||
}
|
||||
|
||||
public async didDownloadFeatures(features: FeaturesImports.FeatureDescription[]): Promise<void> {
|
||||
@@ -448,7 +460,15 @@ export class SNFeaturesService
|
||||
return FeaturesImports.FindNativeFeature(featureId)?.deprecated === true
|
||||
}
|
||||
|
||||
public isFreeFeature(featureId: FeaturesImports.FeatureIdentifier) {
|
||||
return [FeatureIdentifier.DarkTheme].includes(featureId)
|
||||
}
|
||||
|
||||
public getFeatureStatus(featureId: FeaturesImports.FeatureIdentifier): FeatureStatus {
|
||||
if (this.isFreeFeature(featureId)) {
|
||||
return FeatureStatus.Entitled
|
||||
}
|
||||
|
||||
const isDeprecated = this.isFeatureDeprecated(featureId)
|
||||
if (isDeprecated) {
|
||||
if (this.hasPaidOnlineOrOfflineSubscription()) {
|
||||
@@ -548,7 +568,9 @@ export class SNFeaturesService
|
||||
|
||||
let hasChanges = false
|
||||
const now = new Date()
|
||||
const expired = new Date(feature.expires_at || 0).getTime() < now.getTime()
|
||||
const expired = this.isFreeFeature(feature.identifier)
|
||||
? false
|
||||
: new Date(feature.expires_at || 0).getTime() < now.getTime()
|
||||
|
||||
const existingItem = currentItems.find((item) => {
|
||||
if (item.content.package_info) {
|
||||
|
||||
@@ -405,41 +405,6 @@ describe('itemManager', () => {
|
||||
const notes = itemManager.getDisplayableNotes()
|
||||
expect(notes).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('adding a note to a tag hierarchy should add the note to its parent too', async () => {
|
||||
itemManager = createService()
|
||||
const parentTag = createTag('parent')
|
||||
const childTag = createTag('child')
|
||||
const note = createNote('note')
|
||||
|
||||
await itemManager.insertItems([parentTag, childTag, note])
|
||||
await itemManager.setTagParent(parentTag, childTag)
|
||||
|
||||
await itemManager.addTagToNote(note, childTag, true)
|
||||
|
||||
const tags = itemManager.getSortedTagsForNote(note)
|
||||
|
||||
expect(tags).toHaveLength(2)
|
||||
expect(tags[0].uuid).toEqual(childTag.uuid)
|
||||
expect(tags[1].uuid).toEqual(parentTag.uuid)
|
||||
})
|
||||
|
||||
it('adding a note to a tag hierarchy should not add the note to its parent if hierarchy option is disabled', async () => {
|
||||
itemManager = createService()
|
||||
const parentTag = createTag('parent')
|
||||
const childTag = createTag('child')
|
||||
const note = createNote('note')
|
||||
|
||||
await itemManager.insertItems([parentTag, childTag, note])
|
||||
await itemManager.setTagParent(parentTag, childTag)
|
||||
|
||||
await itemManager.addTagToNote(note, childTag, false)
|
||||
|
||||
const tags = itemManager.getSortedTagsForNote(note)
|
||||
|
||||
expect(tags).toHaveLength(1)
|
||||
expect(tags[0].uuid).toEqual(childTag.uuid)
|
||||
})
|
||||
})
|
||||
|
||||
describe('template items', () => {
|
||||
@@ -703,47 +668,6 @@ describe('itemManager', () => {
|
||||
})
|
||||
|
||||
describe('files', () => {
|
||||
it('associates with note', async () => {
|
||||
itemManager = createService()
|
||||
const note = createNote('invoices')
|
||||
const file = createFile('invoice_1.pdf')
|
||||
await itemManager.insertItems([note, file])
|
||||
|
||||
const resultingFile = await itemManager.associateFileWithNote(file, note)
|
||||
const references = resultingFile.references
|
||||
|
||||
expect(references).toHaveLength(1)
|
||||
expect(references[0].uuid).toEqual(note.uuid)
|
||||
})
|
||||
|
||||
it('disassociates with note', async () => {
|
||||
itemManager = createService()
|
||||
const note = createNote('invoices')
|
||||
const file = createFile('invoice_1.pdf')
|
||||
await itemManager.insertItems([note, file])
|
||||
|
||||
const associatedFile = await itemManager.associateFileWithNote(file, note)
|
||||
const disassociatedFile = await itemManager.disassociateFileWithNote(associatedFile, note)
|
||||
const references = disassociatedFile.references
|
||||
|
||||
expect(references).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('should get files associated with note', async () => {
|
||||
itemManager = createService()
|
||||
const note = createNote('invoices')
|
||||
const file = createFile('invoice_1.pdf')
|
||||
const secondFile = createFile('unrelated-file.xlsx')
|
||||
await itemManager.insertItems([note, file, secondFile])
|
||||
|
||||
await itemManager.associateFileWithNote(file, note)
|
||||
|
||||
const filesAssociatedWithNote = itemManager.getFilesForNote(note)
|
||||
|
||||
expect(filesAssociatedWithNote).toHaveLength(1)
|
||||
expect(filesAssociatedWithNote[0].uuid).toBe(file.uuid)
|
||||
})
|
||||
|
||||
it('should correctly rename file to filename that has extension', async () => {
|
||||
itemManager = createService()
|
||||
const file = createFile('initialName.ext')
|
||||
@@ -774,4 +698,249 @@ describe('itemManager', () => {
|
||||
expect(renamedFile.name).toBe('anotherName')
|
||||
})
|
||||
})
|
||||
|
||||
describe('linking', () => {
|
||||
it('adding a note to a tag hierarchy should add the note to its parent too', async () => {
|
||||
itemManager = createService()
|
||||
const parentTag = createTag('parent')
|
||||
const childTag = createTag('child')
|
||||
const note = createNote('note')
|
||||
|
||||
await itemManager.insertItems([parentTag, childTag, note])
|
||||
await itemManager.setTagParent(parentTag, childTag)
|
||||
|
||||
await itemManager.addTagToNote(note, childTag, true)
|
||||
|
||||
const tags = itemManager.getSortedTagsForItem(note)
|
||||
|
||||
expect(tags).toHaveLength(2)
|
||||
expect(tags[0].uuid).toEqual(childTag.uuid)
|
||||
expect(tags[1].uuid).toEqual(parentTag.uuid)
|
||||
})
|
||||
|
||||
it('adding a note to a tag hierarchy should not add the note to its parent if hierarchy option is disabled', async () => {
|
||||
itemManager = createService()
|
||||
const parentTag = createTag('parent')
|
||||
const childTag = createTag('child')
|
||||
const note = createNote('note')
|
||||
|
||||
await itemManager.insertItems([parentTag, childTag, note])
|
||||
await itemManager.setTagParent(parentTag, childTag)
|
||||
|
||||
await itemManager.addTagToNote(note, childTag, false)
|
||||
|
||||
const tags = itemManager.getSortedTagsForItem(note)
|
||||
|
||||
expect(tags).toHaveLength(1)
|
||||
expect(tags[0].uuid).toEqual(childTag.uuid)
|
||||
})
|
||||
|
||||
it('adding a file to a tag hierarchy should add the file to its parent too', async () => {
|
||||
itemManager = createService()
|
||||
const parentTag = createTag('parent')
|
||||
const childTag = createTag('child')
|
||||
const file = createFile('file')
|
||||
|
||||
await itemManager.insertItems([parentTag, childTag, file])
|
||||
await itemManager.setTagParent(parentTag, childTag)
|
||||
|
||||
await itemManager.addTagToFile(file, childTag, true)
|
||||
|
||||
const tags = itemManager.getSortedTagsForItem(file)
|
||||
|
||||
expect(tags).toHaveLength(2)
|
||||
expect(tags[0].uuid).toEqual(childTag.uuid)
|
||||
expect(tags[1].uuid).toEqual(parentTag.uuid)
|
||||
})
|
||||
|
||||
it('adding a file to a tag hierarchy should not add the file to its parent if hierarchy option is disabled', async () => {
|
||||
itemManager = createService()
|
||||
const parentTag = createTag('parent')
|
||||
const childTag = createTag('child')
|
||||
const file = createFile('file')
|
||||
|
||||
await itemManager.insertItems([parentTag, childTag, file])
|
||||
await itemManager.setTagParent(parentTag, childTag)
|
||||
|
||||
await itemManager.addTagToFile(file, childTag, false)
|
||||
|
||||
const tags = itemManager.getSortedTagsForItem(file)
|
||||
|
||||
expect(tags).toHaveLength(1)
|
||||
expect(tags[0].uuid).toEqual(childTag.uuid)
|
||||
})
|
||||
|
||||
it('should link file with note', async () => {
|
||||
itemManager = createService()
|
||||
const note = createNote('invoices')
|
||||
const file = createFile('invoice_1.pdf')
|
||||
await itemManager.insertItems([note, file])
|
||||
|
||||
const resultingFile = await itemManager.associateFileWithNote(file, note)
|
||||
const references = resultingFile.references
|
||||
|
||||
expect(references).toHaveLength(1)
|
||||
expect(references[0].uuid).toEqual(note.uuid)
|
||||
})
|
||||
|
||||
it('should unlink file from note', async () => {
|
||||
itemManager = createService()
|
||||
const note = createNote('invoices')
|
||||
const file = createFile('invoice_1.pdf')
|
||||
await itemManager.insertItems([note, file])
|
||||
|
||||
const associatedFile = await itemManager.associateFileWithNote(file, note)
|
||||
const disassociatedFile = await itemManager.disassociateFileWithNote(associatedFile, note)
|
||||
const references = disassociatedFile.references
|
||||
|
||||
expect(references).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('should get files linked with note', async () => {
|
||||
itemManager = createService()
|
||||
const note = createNote('invoices')
|
||||
const file = createFile('invoice_1.pdf')
|
||||
const secondFile = createFile('unrelated-file.xlsx')
|
||||
await itemManager.insertItems([note, file, secondFile])
|
||||
|
||||
await itemManager.associateFileWithNote(file, note)
|
||||
|
||||
const filesAssociatedWithNote = itemManager.getSortedFilesLinkingToItem(note)
|
||||
|
||||
expect(filesAssociatedWithNote).toHaveLength(1)
|
||||
expect(filesAssociatedWithNote[0].uuid).toBe(file.uuid)
|
||||
})
|
||||
|
||||
it('should link note to note', async () => {
|
||||
itemManager = createService()
|
||||
const note = createNote('research')
|
||||
const note2 = createNote('citation')
|
||||
await itemManager.insertItems([note, note2])
|
||||
|
||||
const resultingNote = await itemManager.linkNoteToNote(note, note2)
|
||||
const references = resultingNote.references
|
||||
|
||||
expect(references).toHaveLength(1)
|
||||
expect(references[0].uuid).toEqual(note2.uuid)
|
||||
})
|
||||
|
||||
it('should link file to file', async () => {
|
||||
itemManager = createService()
|
||||
const file = createFile('research')
|
||||
const file2 = createFile('citation')
|
||||
await itemManager.insertItems([file, file2])
|
||||
|
||||
const resultingfile = await itemManager.linkFileToFile(file, file2)
|
||||
const references = resultingfile.references
|
||||
|
||||
expect(references).toHaveLength(1)
|
||||
expect(references[0].uuid).toEqual(file2.uuid)
|
||||
})
|
||||
|
||||
it('should get the relationship type for two items', async () => {
|
||||
itemManager = createService()
|
||||
const firstNote = createNote('First note')
|
||||
const secondNote = createNote('Second note')
|
||||
const unlinkedNote = createNote('Unlinked note')
|
||||
await itemManager.insertItems([firstNote, secondNote, unlinkedNote])
|
||||
|
||||
const firstNoteLinkedToSecond = await itemManager.linkNoteToNote(firstNote, secondNote)
|
||||
|
||||
const relationshipOfFirstNoteToSecond = itemManager.relationshipTypeForItems(firstNoteLinkedToSecond, secondNote)
|
||||
const relationshipOfSecondNoteToFirst = itemManager.relationshipTypeForItems(secondNote, firstNoteLinkedToSecond)
|
||||
const relationshipOfFirstNoteToUnlinked = itemManager.relationshipTypeForItems(
|
||||
firstNoteLinkedToSecond,
|
||||
unlinkedNote,
|
||||
)
|
||||
|
||||
expect(relationshipOfFirstNoteToSecond).toBe('direct')
|
||||
expect(relationshipOfSecondNoteToFirst).toBe('indirect')
|
||||
expect(relationshipOfFirstNoteToUnlinked).toBe('unlinked')
|
||||
})
|
||||
|
||||
it('should unlink itemToUnlink from item', async () => {
|
||||
itemManager = createService()
|
||||
const note = createNote('Note 1')
|
||||
const note2 = createNote('Note 2')
|
||||
await itemManager.insertItems([note, note2])
|
||||
|
||||
const linkedItem = await itemManager.linkNoteToNote(note, note2)
|
||||
const unlinkedItem = await itemManager.unlinkItem(linkedItem, note2)
|
||||
const references = unlinkedItem.references
|
||||
|
||||
expect(references).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('should get all linked files for item', async () => {
|
||||
itemManager = createService()
|
||||
const file = createFile('A1')
|
||||
const file2 = createFile('B2')
|
||||
const file3 = createFile('C3')
|
||||
|
||||
await itemManager.insertItems([file, file2, file3])
|
||||
|
||||
await itemManager.linkFileToFile(file, file3)
|
||||
await itemManager.linkFileToFile(file, file2)
|
||||
|
||||
const sortedFilesForItem = itemManager.getSortedLinkedFilesForItem(file)
|
||||
|
||||
expect(sortedFilesForItem).toHaveLength(2)
|
||||
expect(sortedFilesForItem[0].uuid).toEqual(file2.uuid)
|
||||
expect(sortedFilesForItem[1].uuid).toEqual(file3.uuid)
|
||||
})
|
||||
|
||||
it('should get all files linking to item', async () => {
|
||||
itemManager = createService()
|
||||
const baseFile = createFile('file')
|
||||
const fileToLink1 = createFile('A1')
|
||||
const fileToLink2 = createFile('B2')
|
||||
|
||||
await itemManager.insertItems([baseFile, fileToLink1, fileToLink2])
|
||||
|
||||
await itemManager.linkFileToFile(fileToLink2, baseFile)
|
||||
await itemManager.linkFileToFile(fileToLink1, baseFile)
|
||||
|
||||
const sortedFilesForItem = itemManager.getSortedFilesLinkingToItem(baseFile)
|
||||
|
||||
expect(sortedFilesForItem).toHaveLength(2)
|
||||
expect(sortedFilesForItem[0].uuid).toEqual(fileToLink1.uuid)
|
||||
expect(sortedFilesForItem[1].uuid).toEqual(fileToLink2.uuid)
|
||||
})
|
||||
|
||||
it('should get all linked notes for item', async () => {
|
||||
itemManager = createService()
|
||||
const baseNote = createNote('note')
|
||||
const noteToLink1 = createNote('A1')
|
||||
const noteToLink2 = createNote('B2')
|
||||
|
||||
await itemManager.insertItems([baseNote, noteToLink1, noteToLink2])
|
||||
|
||||
await itemManager.linkNoteToNote(baseNote, noteToLink2)
|
||||
await itemManager.linkNoteToNote(baseNote, noteToLink1)
|
||||
|
||||
const sortedFilesForItem = itemManager.getSortedLinkedNotesForItem(baseNote)
|
||||
|
||||
expect(sortedFilesForItem).toHaveLength(2)
|
||||
expect(sortedFilesForItem[0].uuid).toEqual(noteToLink1.uuid)
|
||||
expect(sortedFilesForItem[1].uuid).toEqual(noteToLink2.uuid)
|
||||
})
|
||||
|
||||
it('should get all notes linking to item', async () => {
|
||||
itemManager = createService()
|
||||
const baseNote = createNote('note')
|
||||
const noteToLink1 = createNote('A1')
|
||||
const noteToLink2 = createNote('B2')
|
||||
|
||||
await itemManager.insertItems([baseNote, noteToLink1, noteToLink2])
|
||||
|
||||
await itemManager.linkNoteToNote(noteToLink2, baseNote)
|
||||
await itemManager.linkNoteToNote(noteToLink1, baseNote)
|
||||
|
||||
const sortedFilesForItem = itemManager.getSortedNotesLinkingToItem(baseNote)
|
||||
|
||||
expect(sortedFilesForItem).toHaveLength(2)
|
||||
expect(sortedFilesForItem[0].uuid).toEqual(noteToLink1.uuid)
|
||||
expect(sortedFilesForItem[1].uuid).toEqual(noteToLink2.uuid)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -9,7 +9,7 @@ import * as Services from '@standardnotes/services'
|
||||
import { PayloadManagerChangeData } from '../Payloads'
|
||||
import { DiagnosticInfo, ItemsClientInterface } from '@standardnotes/services'
|
||||
import { ApplicationDisplayOptions } from '@Lib/Application/Options/OptionalOptions'
|
||||
import { CollectionSort } from '@standardnotes/models'
|
||||
import { CollectionSort, DecryptedItemInterface, ItemContent } from '@standardnotes/models'
|
||||
|
||||
type ItemsChangeObserver<I extends Models.DecryptedItemInterface = Models.DecryptedItemInterface> = {
|
||||
contentType: ContentType[]
|
||||
@@ -32,7 +32,7 @@ export class ItemManager
|
||||
private observers: ItemsChangeObserver[] = []
|
||||
private collection!: Models.ItemCollection
|
||||
private systemSmartViews: Models.SmartView[]
|
||||
private tagNotesIndex!: Models.TagNotesIndex
|
||||
private tagItemsIndex!: Models.TagItemsIndex
|
||||
|
||||
private navigationDisplayController!: Models.ItemDisplayController<Models.SNNote | Models.FileItem>
|
||||
private tagDisplayController!: Models.ItemDisplayController<Models.SNTag>
|
||||
@@ -96,7 +96,7 @@ export class ItemManager
|
||||
sortDirection: 'asc',
|
||||
})
|
||||
|
||||
this.tagNotesIndex = new Models.TagNotesIndex(this.collection, this.tagNotesIndex?.observers)
|
||||
this.tagItemsIndex = new Models.TagItemsIndex(this.collection, this.tagItemsIndex?.observers)
|
||||
}
|
||||
|
||||
private get allDisplayControllers(): Models.ItemDisplayController<Models.DisplayItem>[] {
|
||||
@@ -219,7 +219,7 @@ export class ItemManager
|
||||
;(this.unsubChangeObserver as unknown) = undefined
|
||||
;(this.payloadManager as unknown) = undefined
|
||||
;(this.collection as unknown) = undefined
|
||||
;(this.tagNotesIndex as unknown) = undefined
|
||||
;(this.tagItemsIndex as unknown) = undefined
|
||||
;(this.tagDisplayController as unknown) = undefined
|
||||
;(this.navigationDisplayController as unknown) = undefined
|
||||
;(this.itemsKeyDisplayController as unknown) = undefined
|
||||
@@ -284,23 +284,23 @@ export class ItemManager
|
||||
return TagsToFoldersMigrationApplicator.isApplicableToCurrentData(this)
|
||||
}
|
||||
|
||||
public addNoteCountChangeObserver(observer: Models.TagNoteCountChangeObserver): () => void {
|
||||
return this.tagNotesIndex.addCountChangeObserver(observer)
|
||||
public addNoteCountChangeObserver(observer: Models.TagItemCountChangeObserver): () => void {
|
||||
return this.tagItemsIndex.addCountChangeObserver(observer)
|
||||
}
|
||||
|
||||
public allCountableNotesCount(): number {
|
||||
return this.tagNotesIndex.allCountableNotesCount()
|
||||
return this.tagItemsIndex.allCountableItemsCount()
|
||||
}
|
||||
|
||||
public countableNotesForTag(tag: Models.SNTag | Models.SmartView): number {
|
||||
if (tag instanceof Models.SmartView) {
|
||||
if (tag.uuid === Models.SystemViewId.AllNotes) {
|
||||
return this.tagNotesIndex.allCountableNotesCount()
|
||||
return this.tagItemsIndex.allCountableItemsCount()
|
||||
}
|
||||
|
||||
throw Error('countableNotesForTag is not meant to be used for smart views.')
|
||||
throw Error('countableItemsForTag is not meant to be used for smart views.')
|
||||
}
|
||||
return this.tagNotesIndex.countableNotesForTag(tag)
|
||||
return this.tagItemsIndex.countableItemsForTag(tag)
|
||||
}
|
||||
|
||||
public getNoteCount(): number {
|
||||
@@ -406,7 +406,7 @@ export class ItemManager
|
||||
}
|
||||
|
||||
this.collection.onChange(delta)
|
||||
this.tagNotesIndex.onChange(delta)
|
||||
this.tagItemsIndex.onChange(delta)
|
||||
|
||||
const affectedContentTypesArray = Array.from(affectedContentTypes.values())
|
||||
for (const controller of this.allDisplayControllers) {
|
||||
@@ -1140,20 +1140,106 @@ export class ItemManager
|
||||
)
|
||||
}
|
||||
|
||||
public async addTagToFile(file: Models.FileItem, tag: Models.SNTag, addHierarchy: boolean): Promise<Models.SNTag[]> {
|
||||
let tagsToAdd = [tag]
|
||||
|
||||
if (addHierarchy) {
|
||||
const parentChainTags = this.getTagParentChain(tag)
|
||||
tagsToAdd = [...parentChainTags, tag]
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
tagsToAdd.map((tagToAdd) => {
|
||||
return this.changeTag(tagToAdd, (mutator) => {
|
||||
mutator.addFile(file)
|
||||
}) as Promise<Models.SNTag>
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
public async linkNoteToNote(note: Models.SNNote, otherNote: Models.SNNote): Promise<Models.SNNote> {
|
||||
return this.changeItem<Models.NoteMutator, Models.SNNote>(note, (mutator) => {
|
||||
mutator.addNote(otherNote)
|
||||
})
|
||||
}
|
||||
|
||||
public async linkFileToFile(file: Models.FileItem, otherFile: Models.FileItem): Promise<Models.FileItem> {
|
||||
return this.changeItem<Models.FileMutator, Models.FileItem>(file, (mutator) => {
|
||||
mutator.addFile(otherFile)
|
||||
})
|
||||
}
|
||||
|
||||
public async unlinkItem(
|
||||
item: DecryptedItemInterface<ItemContent>,
|
||||
itemToUnlink: DecryptedItemInterface<ItemContent>,
|
||||
) {
|
||||
return this.changeItem(item, (mutator) => {
|
||||
mutator.removeItemAsRelationship(itemToUnlink)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tags for a note sorted in natural order
|
||||
* @param note - The note whose tags will be returned
|
||||
* @returns Array containing tags associated with a note
|
||||
* @param item - The item whose tags will be returned
|
||||
* @returns Array containing tags associated with an item
|
||||
*/
|
||||
public getSortedTagsForNote(note: Models.SNNote): Models.SNTag[] {
|
||||
public getSortedTagsForItem(item: DecryptedItemInterface<ItemContent>): Models.SNTag[] {
|
||||
return naturalSort(
|
||||
this.itemsReferencingItem(note).filter((ref) => {
|
||||
this.itemsReferencingItem(item).filter((ref) => {
|
||||
return ref?.content_type === ContentType.Tag
|
||||
}) as Models.SNTag[],
|
||||
'title',
|
||||
)
|
||||
}
|
||||
|
||||
public getSortedLinkedFilesForItem(item: DecryptedItemInterface<ItemContent>): Models.FileItem[] {
|
||||
if (this.isTemplateItem(item)) {
|
||||
return []
|
||||
}
|
||||
|
||||
const filesReferencedByItem = this.referencesForItem(item).filter(
|
||||
(ref) => ref.content_type === ContentType.File,
|
||||
) as Models.FileItem[]
|
||||
|
||||
return naturalSort(filesReferencedByItem, 'title')
|
||||
}
|
||||
|
||||
public getSortedFilesLinkingToItem(item: DecryptedItemInterface<ItemContent>): Models.FileItem[] {
|
||||
if (this.isTemplateItem(item)) {
|
||||
return []
|
||||
}
|
||||
|
||||
const filesReferencingItem = this.itemsReferencingItem(item).filter(
|
||||
(ref) => ref.content_type === ContentType.File,
|
||||
) as Models.FileItem[]
|
||||
|
||||
return naturalSort(filesReferencingItem, 'title')
|
||||
}
|
||||
|
||||
public getSortedLinkedNotesForItem(item: DecryptedItemInterface<ItemContent>): Models.SNNote[] {
|
||||
if (this.isTemplateItem(item)) {
|
||||
return []
|
||||
}
|
||||
|
||||
const notesReferencedByItem = this.referencesForItem(item).filter(
|
||||
(ref) => ref.content_type === ContentType.Note,
|
||||
) as Models.SNNote[]
|
||||
|
||||
return naturalSort(notesReferencedByItem, 'title')
|
||||
}
|
||||
|
||||
public getSortedNotesLinkingToItem(item: Models.DecryptedItemInterface<Models.ItemContent>): Models.SNNote[] {
|
||||
if (this.isTemplateItem(item)) {
|
||||
return []
|
||||
}
|
||||
|
||||
const notesReferencingItem = this.itemsReferencingItem(item).filter(
|
||||
(ref) => ref.content_type === ContentType.Note,
|
||||
) as Models.SNNote[]
|
||||
|
||||
return naturalSort(notesReferencingItem, 'title')
|
||||
}
|
||||
|
||||
public async createTag(title: string, parentItemToLookupUuidFor?: Models.SNTag): Promise<Models.SNTag> {
|
||||
const newTag = await this.createItem<Models.SNTag>(
|
||||
ContentType.Tag,
|
||||
@@ -1312,12 +1398,6 @@ export class ItemManager
|
||||
}
|
||||
}
|
||||
|
||||
public getFilesForNote(note: Models.SNNote): Models.FileItem[] {
|
||||
return (
|
||||
this.itemsReferencingItem(note).filter((ref) => ref.content_type === ContentType.File) as Models.FileItem[]
|
||||
).sort((a, b) => (a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1))
|
||||
}
|
||||
|
||||
public renameFile(file: Models.FileItem, name: string): Promise<Models.FileItem> {
|
||||
return this.changeItem<Models.FileMutator, Models.FileItem>(file, (mutator) => {
|
||||
mutator.name = name
|
||||
@@ -1353,6 +1433,23 @@ export class ItemManager
|
||||
return this.findAnyItems(uuids) as (Models.DecryptedItemInterface | Models.DeletedItemInterface)[]
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns `'direct'` if `itemOne` has the reference to `itemTwo`, `'indirect'` if `itemTwo` has the reference to `itemOne`, `'unlinked'` if neither reference each other
|
||||
*/
|
||||
public relationshipTypeForItems(
|
||||
itemOne: Models.DecryptedItemInterface<Models.ItemContent>,
|
||||
itemTwo: Models.DecryptedItemInterface<Models.ItemContent>,
|
||||
): 'direct' | 'indirect' | 'unlinked' {
|
||||
const itemOneReferencesItemTwo = !!this.referencesForItem(itemOne).find(
|
||||
(reference) => reference.uuid === itemTwo.uuid,
|
||||
)
|
||||
const itemTwoReferencesItemOne = !!this.referencesForItem(itemTwo).find(
|
||||
(reference) => reference.uuid === itemOne.uuid,
|
||||
)
|
||||
|
||||
return itemOneReferencesItemTwo ? 'direct' : itemTwoReferencesItemOne ? 'indirect' : 'unlinked'
|
||||
}
|
||||
|
||||
override getDiagnostics(): Promise<DiagnosticInfo | undefined> {
|
||||
return Promise.resolve({
|
||||
items: {
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable no-undef */
|
||||
import { BaseItemCounts } from './lib/Applications.js'
|
||||
import * as Factory from './lib/factory.js'
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
|
||||
describe('application instances', () => {
|
||||
const BASE_ITEM_COUNT = 2 /** Default items key, user preferences */
|
||||
|
||||
const syncOptions = {
|
||||
checkIntegrity: true,
|
||||
awaitAll: true,
|
||||
@@ -27,8 +26,8 @@ describe('application instances', () => {
|
||||
expect(app1.payloadManager).to.not.equal(app2.payloadManager)
|
||||
|
||||
await Factory.createMappedNote(app1)
|
||||
expect(app1.itemManager.items.length).length.to.equal(BASE_ITEM_COUNT + 1)
|
||||
expect(app2.itemManager.items.length).to.equal(BASE_ITEM_COUNT)
|
||||
expect(app1.itemManager.items.length).length.to.equal(BaseItemCounts.DefaultItems + 1)
|
||||
expect(app2.itemManager.items.length).to.equal(BaseItemCounts.DefaultItems)
|
||||
await Factory.safeDeinit(app1)
|
||||
await Factory.safeDeinit(app2)
|
||||
})
|
||||
@@ -40,14 +39,14 @@ describe('application instances', () => {
|
||||
await Factory.createMappedNote(app1)
|
||||
await app1.syncService.sync(syncOptions)
|
||||
|
||||
expect((await app1.diskStorageService.getAllRawPayloads()).length).length.to.equal(BASE_ITEM_COUNT + 1)
|
||||
expect((await app2.diskStorageService.getAllRawPayloads()).length).length.to.equal(BASE_ITEM_COUNT)
|
||||
expect((await app1.diskStorageService.getAllRawPayloads()).length).length.to.equal(BaseItemCounts.DefaultItems + 1)
|
||||
expect((await app2.diskStorageService.getAllRawPayloads()).length).length.to.equal(BaseItemCounts.DefaultItems)
|
||||
|
||||
await Factory.createMappedNote(app2)
|
||||
await app2.syncService.sync(syncOptions)
|
||||
|
||||
expect((await app1.diskStorageService.getAllRawPayloads()).length).length.to.equal(BASE_ITEM_COUNT + 1)
|
||||
expect((await app2.diskStorageService.getAllRawPayloads()).length).length.to.equal(BASE_ITEM_COUNT + 1)
|
||||
expect((await app1.diskStorageService.getAllRawPayloads()).length).length.to.equal(BaseItemCounts.DefaultItems + 1)
|
||||
expect((await app2.diskStorageService.getAllRawPayloads()).length).length.to.equal(BaseItemCounts.DefaultItems + 1)
|
||||
await Factory.safeDeinit(app1)
|
||||
await Factory.safeDeinit(app2)
|
||||
})
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable no-undef */
|
||||
import { BaseItemCounts } from './lib/Applications.js'
|
||||
import * as Factory from './lib/factory.js'
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
|
||||
describe('auth fringe cases', () => {
|
||||
const BASE_ITEM_COUNT = ['default items key', 'user prefs'].length
|
||||
|
||||
const createContext = async () => {
|
||||
const application = await Factory.createInitAppWithFakeCrypto()
|
||||
return {
|
||||
expectedItemCount: BASE_ITEM_COUNT,
|
||||
expectedItemCount: BaseItemCounts.DefaultItems,
|
||||
application: application,
|
||||
email: UuidGenerator.GenerateUuid(),
|
||||
password: UuidGenerator.GenerateUuid(),
|
||||
@@ -100,9 +99,13 @@ describe('auth fringe cases', () => {
|
||||
|
||||
expect(newApplication.itemManager.getDisplayableNotes().length).to.equal(2)
|
||||
|
||||
expect(newApplication.itemManager.getDisplayableNotes().find((n) => n.uuid === firstVersionOfNote.uuid).text).to.equal(staleText)
|
||||
expect(
|
||||
newApplication.itemManager.getDisplayableNotes().find((n) => n.uuid === firstVersionOfNote.uuid).text,
|
||||
).to.equal(staleText)
|
||||
|
||||
const conflictedCopy = newApplication.itemManager.getDisplayableNotes().find((n) => n.uuid !== firstVersionOfNote.uuid)
|
||||
const conflictedCopy = newApplication.itemManager
|
||||
.getDisplayableNotes()
|
||||
.find((n) => n.uuid !== firstVersionOfNote.uuid)
|
||||
expect(conflictedCopy.text).to.equal(serverText)
|
||||
expect(conflictedCopy.duplicate_of).to.equal(firstVersionOfNote.uuid)
|
||||
await Factory.safeDeinit(newApplication)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable no-undef */
|
||||
import { BaseItemCounts } from './lib/Applications.js'
|
||||
import * as Factory from './lib/factory.js'
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
@@ -7,8 +8,6 @@ const expect = chai.expect
|
||||
describe('basic auth', function () {
|
||||
this.timeout(Factory.TenSecondTimeout)
|
||||
|
||||
const BASE_ITEM_COUNT = 2 /** Default items key, user preferences */
|
||||
|
||||
const syncOptions = {
|
||||
checkIntegrity: true,
|
||||
awaitAll: true,
|
||||
@@ -16,7 +15,7 @@ describe('basic auth', function () {
|
||||
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
this.expectedItemCount = BASE_ITEM_COUNT
|
||||
this.expectedItemCount = BaseItemCounts.DefaultItems
|
||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
||||
this.email = UuidGenerator.GenerateUuid()
|
||||
this.password = UuidGenerator.GenerateUuid()
|
||||
@@ -62,7 +61,7 @@ describe('basic auth', function () {
|
||||
expect(this.application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.RootKeyNone)
|
||||
|
||||
const rawPayloads = await this.application.diskStorageService.getAllRawPayloads()
|
||||
expect(rawPayloads.length).to.equal(BASE_ITEM_COUNT)
|
||||
expect(rawPayloads.length).to.equal(BaseItemCounts.DefaultItems)
|
||||
})
|
||||
|
||||
it('successfully signs in to registered account', async function () {
|
||||
@@ -408,7 +407,7 @@ describe('basic auth', function () {
|
||||
|
||||
this.application = await Factory.signOutApplicationAndReturnNew(this.application)
|
||||
|
||||
expect(this.application.itemManager.items.length).to.equal(BASE_ITEM_COUNT)
|
||||
expect(this.application.itemManager.items.length).to.equal(BaseItemCounts.DefaultItems)
|
||||
expect(this.application.payloadManager.invalidPayloads.length).to.equal(0)
|
||||
|
||||
/** Should login with new password */
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable no-undef */
|
||||
import { BaseItemCounts } from './lib/Applications.js'
|
||||
import * as Factory from './lib/factory.js'
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
@@ -24,8 +25,8 @@ describe('backups', function () {
|
||||
this.application = null
|
||||
})
|
||||
|
||||
const BASE_ITEM_COUNT_ENCRYPTED = ['ItemsKey', 'UserPreferences'].length
|
||||
const BASE_ITEM_COUNT_DECRYPTED = ['UserPreferences'].length
|
||||
const BASE_ITEM_COUNT_ENCRYPTED = BaseItemCounts.DefaultItems
|
||||
const BASE_ITEM_COUNT_DECRYPTED = ['UserPreferences', 'DarkTheme'].length
|
||||
|
||||
it('backup file should have a version number', async function () {
|
||||
let data = await this.application.createDecryptedBackupFile()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user