mirror of
https://github.com/standardnotes/app
synced 2026-09-20 12:13:48 -04:00
Compare commits
22
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 |
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -3,6 +3,32 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api",
|
||||
"version": "1.13.0",
|
||||
"version": "1.16.1",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -36,7 +36,7 @@
|
||||
"typescript": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/common": "^1.36.1",
|
||||
"@standardnotes/common": "^1.39.0",
|
||||
"@standardnotes/encryption": "workspace:*",
|
||||
"@standardnotes/models": "workspace:*",
|
||||
"@standardnotes/responses": "workspace:*",
|
||||
|
||||
@@ -3,4 +3,6 @@ export enum WorkspaceApiOperations {
|
||||
Inviting,
|
||||
Accepting,
|
||||
ListingWorkspaces,
|
||||
ListingWorkspaceUsers,
|
||||
InitiatingKeyshare,
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { WorkspaceType } from '@standardnotes/common'
|
||||
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'
|
||||
@@ -29,6 +32,14 @@ describe('WorkspaceApiService', () => {
|
||||
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 () => {
|
||||
@@ -97,6 +108,7 @@ describe('WorkspaceApiService', () => {
|
||||
const response = await createService().inviteToWorkspace({
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
inviteeEmail: '[email protected]',
|
||||
accessLevel: WorkspaceAccessLevel.WriteAndRead,
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
@@ -107,6 +119,7 @@ describe('WorkspaceApiService', () => {
|
||||
expect(workspaceServer.inviteToWorkspace).toHaveBeenCalledWith({
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
inviteeEmail: '[email protected]',
|
||||
accessLevel: 'write-and-read',
|
||||
})
|
||||
})
|
||||
|
||||
@@ -121,6 +134,7 @@ describe('WorkspaceApiService', () => {
|
||||
await service.inviteToWorkspace({
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
inviteeEmail: '[email protected]',
|
||||
accessLevel: WorkspaceAccessLevel.WriteAndRead,
|
||||
})
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
@@ -139,6 +153,7 @@ describe('WorkspaceApiService', () => {
|
||||
await createService().inviteToWorkspace({
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
inviteeEmail: '[email protected]',
|
||||
accessLevel: WorkspaceAccessLevel.WriteAndRead,
|
||||
})
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
@@ -252,4 +267,102 @@ describe('WorkspaceApiService', () => {
|
||||
|
||||
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,4 +1,4 @@
|
||||
import { Uuid, WorkspaceType } from '@standardnotes/common'
|
||||
import { Uuid, WorkspaceAccessLevel, WorkspaceType } from '@standardnotes/common'
|
||||
|
||||
import { ErrorMessage } from '../../Error/ErrorMessage'
|
||||
import { ApiCallError } from '../../Error/ApiCallError'
|
||||
@@ -7,6 +7,8 @@ import { WorkspaceInvitationAcceptingResponse } from '../../Response/Workspace/W
|
||||
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'
|
||||
@@ -18,6 +20,42 @@ export class WorkspaceApiService implements WorkspaceApiServiceInterface {
|
||||
this.operationsInProgress = new Map()
|
||||
}
|
||||
|
||||
async initiateKeyshare(dto: {
|
||||
workspaceUuid: string
|
||||
userUuid: string
|
||||
encryptedWorkspaceKey: string
|
||||
}): Promise<WorkspaceKeyshareInitiatingResponse> {
|
||||
this.lockOperation(WorkspaceApiOperations.InitiatingKeyshare)
|
||||
|
||||
try {
|
||||
const response = await this.workspaceServer.initiateKeyshare({
|
||||
workspaceUuid: dto.workspaceUuid,
|
||||
userUuid: dto.userUuid,
|
||||
encryptedWorkspaceKey: dto.encryptedWorkspaceKey,
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
@@ -56,13 +94,18 @@ export class WorkspaceApiService implements WorkspaceApiServiceInterface {
|
||||
}
|
||||
}
|
||||
|
||||
async inviteToWorkspace(dto: { inviteeEmail: string; workspaceUuid: Uuid }): Promise<WorkspaceInvitationResponse> {
|
||||
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)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Uuid, WorkspaceType } from '@standardnotes/common'
|
||||
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: {
|
||||
@@ -13,7 +15,11 @@ export interface WorkspaceApiServiceInterface {
|
||||
publicKey?: string
|
||||
workspaceName?: string
|
||||
}): Promise<WorkspaceCreationResponse>
|
||||
inviteToWorkspace(dto: { inviteeEmail: string; workspaceUuid: Uuid }): Promise<WorkspaceInvitationResponse>
|
||||
inviteToWorkspace(dto: {
|
||||
inviteeEmail: string
|
||||
workspaceUuid: Uuid
|
||||
accessLevel: WorkspaceAccessLevel
|
||||
}): Promise<WorkspaceInvitationResponse>
|
||||
acceptInvite(dto: {
|
||||
inviteUuid: Uuid
|
||||
userUuid: Uuid
|
||||
@@ -21,4 +27,10 @@ export interface WorkspaceApiServiceInterface {
|
||||
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,8 @@
|
||||
import { Uuid } from '@standardnotes/common'
|
||||
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,6 @@
|
||||
import { Uuid } from '@standardnotes/common'
|
||||
|
||||
export type WorkspaceUserListRequestParams = {
|
||||
workspaceUuid: Uuid
|
||||
[additionalParam: string]: unknown
|
||||
}
|
||||
@@ -9,4 +9,6 @@ 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 { 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 { 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>
|
||||
}
|
||||
@@ -16,7 +16,11 @@ 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'
|
||||
|
||||
@@ -3,6 +3,9 @@ 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`,
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { WorkspaceType } from '@standardnotes/common'
|
||||
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'
|
||||
|
||||
@@ -42,6 +45,7 @@ describe('WorkspaceServer', () => {
|
||||
const response = await createServer().inviteToWorkspace({
|
||||
inviteeEmail: '[email protected]',
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
accessLevel: WorkspaceAccessLevel.WriteAndRead,
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
@@ -83,4 +87,38 @@ describe('WorkspaceServer', () => {
|
||||
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,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -7,6 +7,10 @@ import { WorkspaceInvitationAcceptingRequestParams } from '../../Request/Workspa
|
||||
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'
|
||||
@@ -14,6 +18,23 @@ 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)
|
||||
|
||||
|
||||
@@ -6,10 +6,16 @@ import { WorkspaceInvitationAcceptingRequestParams } from '../../Request/Workspa
|
||||
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,10 @@
|
||||
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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/components-meta",
|
||||
"version": "2.7.18",
|
||||
"version": "2.7.19",
|
||||
"private": true,
|
||||
"author": "Standard Notes.",
|
||||
"main": "dist",
|
||||
|
||||
@@ -3,6 +3,42 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/desktop",
|
||||
"main": "./app/dist/index.js",
|
||||
"version": "3.23.203",
|
||||
"version": "3.23.212",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/encryption",
|
||||
"version": "1.16.3",
|
||||
"version": "1.18.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -35,7 +35,7 @@
|
||||
"typescript": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/common": "^1.36.1",
|
||||
"@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,12 @@
|
||||
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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/features",
|
||||
"version": "1.52.4",
|
||||
"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.36.1",
|
||||
"@standardnotes/common": "^1.39.0",
|
||||
"@standardnotes/security": "^1.2.0",
|
||||
"reflect-metadata": "^0.1.13"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/filepicker",
|
||||
"version": "1.23.15",
|
||||
"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.36.1",
|
||||
"@standardnotes/common": "^1.39.0",
|
||||
"@standardnotes/files": "workspace:*",
|
||||
"@standardnotes/utils": "workspace:*",
|
||||
"@types/wicg-file-system-access": "^2020.9.5",
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files",
|
||||
"version": "1.10.15",
|
||||
"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.36.1",
|
||||
"@standardnotes/common": "^1.39.0",
|
||||
"@standardnotes/encryption": "workspace:*",
|
||||
"@standardnotes/models": "workspace:*",
|
||||
"@standardnotes/responses": "workspace:*",
|
||||
|
||||
@@ -3,6 +3,50 @@
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/mobile",
|
||||
"version": "3.41.6",
|
||||
"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}
|
||||
|
||||
@@ -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,18 @@
|
||||
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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/models",
|
||||
"version": "1.25.0",
|
||||
"version": "1.27.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -31,7 +31,7 @@
|
||||
"typescript": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/common": "^1.36.1",
|
||||
"@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,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,
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ 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'
|
||||
@@ -37,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,46 @@
|
||||
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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/releases",
|
||||
"version": "1.3.130",
|
||||
"version": "1.3.140",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "dist/releases.json",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/responses",
|
||||
"version": "1.10.6",
|
||||
"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.36.1",
|
||||
"@standardnotes/common": "^1.39.0",
|
||||
"@standardnotes/features": "workspace:*",
|
||||
"@standardnotes/security": "^1.1.0",
|
||||
"reflect-metadata": "^0.1.13"
|
||||
|
||||
@@ -3,6 +3,38 @@
|
||||
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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/services",
|
||||
"version": "1.28.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.36.1",
|
||||
"@standardnotes/common": "^1.39.0",
|
||||
"@standardnotes/encryption": "workspace:^",
|
||||
"@standardnotes/files": "workspace:^",
|
||||
"@standardnotes/models": "workspace:^",
|
||||
|
||||
@@ -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,5 +1,5 @@
|
||||
import { Uuid, WorkspaceType } from '@standardnotes/common'
|
||||
import { Workspace } from '@standardnotes/models'
|
||||
import { Uuid, WorkspaceAccessLevel, WorkspaceType } from '@standardnotes/common'
|
||||
import { Workspace, WorkspaceUser } from '@standardnotes/models'
|
||||
|
||||
export interface WorkspaceClientInterface {
|
||||
createWorkspace(dto: {
|
||||
@@ -9,7 +9,11 @@ export interface WorkspaceClientInterface {
|
||||
publicKey?: string
|
||||
workspaceName?: string
|
||||
}): Promise<{ uuid: string } | null>
|
||||
inviteToWorkspace(dto: { inviteeEmail: string; workspaceUuid: Uuid }): Promise<{ uuid: string } | null>
|
||||
inviteToWorkspace(dto: {
|
||||
inviteeEmail: string
|
||||
workspaceUuid: Uuid
|
||||
accessLevel: WorkspaceAccessLevel
|
||||
}): Promise<{ uuid: string } | null>
|
||||
acceptInvite(dto: {
|
||||
inviteUuid: Uuid
|
||||
userUuid: Uuid
|
||||
@@ -17,4 +21,10 @@ export interface WorkspaceClientInterface {
|
||||
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,6 +1,6 @@
|
||||
import { WorkspaceApiServiceInterface } from '@standardnotes/api'
|
||||
import { Uuid, WorkspaceType } from '@standardnotes/common'
|
||||
import { Workspace } from '@standardnotes/models'
|
||||
import { Uuid, WorkspaceAccessLevel, WorkspaceType } from '@standardnotes/common'
|
||||
import { Workspace, WorkspaceUser } from '@standardnotes/models'
|
||||
|
||||
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
|
||||
import { AbstractService } from '../Service/AbstractService'
|
||||
@@ -14,6 +14,38 @@ export class WorkspaceManager extends AbstractService implements WorkspaceClient
|
||||
super(internalEventBus)
|
||||
}
|
||||
|
||||
async initiateKeyshare(dto: {
|
||||
workspaceUuid: string
|
||||
userUuid: string
|
||||
encryptedWorkspaceKey: 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()
|
||||
@@ -47,7 +79,11 @@ export class WorkspaceManager extends AbstractService implements WorkspaceClient
|
||||
}
|
||||
}
|
||||
|
||||
async inviteToWorkspace(dto: { inviteeEmail: string; workspaceUuid: Uuid }): Promise<{ uuid: string } | null> {
|
||||
async inviteToWorkspace(dto: {
|
||||
inviteeEmail: string
|
||||
workspaceUuid: Uuid
|
||||
accessLevel: WorkspaceAccessLevel
|
||||
}): Promise<{ uuid: string } | null> {
|
||||
try {
|
||||
const result = await this.workspaceApiService.inviteToWorkspace(dto)
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
# [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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -570,7 +570,7 @@ describe('item manager', function () {
|
||||
})
|
||||
})
|
||||
|
||||
const results = this.itemManager.getSortedTagsForNote(note)
|
||||
const results = this.itemManager.getSortedTagsForItem(note)
|
||||
|
||||
expect(results).lengthOf(tags.length)
|
||||
expect(results[0].title).to.equal(tags[1].title)
|
||||
|
||||
@@ -77,8 +77,8 @@ describe('tags as folders', () => {
|
||||
await this.application.items.addTagToNote(note2, tags.another, true)
|
||||
|
||||
// ## The note has been added to other tags
|
||||
const note1Tags = await this.application.items.getSortedTagsForNote(note1)
|
||||
const note2Tags = await this.application.items.getSortedTagsForNote(note2)
|
||||
const note1Tags = await this.application.items.getSortedTagsForItem(note1)
|
||||
const note2Tags = await this.application.items.getSortedTagsForItem(note2)
|
||||
|
||||
expect(note1Tags.length).to.equal(3)
|
||||
expect(note2Tags.length).to.equal(1)
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<script type="module" src="002.test.js"></script>
|
||||
<script type="module" src="003.test.js"></script>
|
||||
<script type="module" src="004.test.js"></script>
|
||||
<script type="module" src="workspaces.test.js"></script>
|
||||
<script type="module" src="username.test.js"></script>
|
||||
<script type="module" src="app-group.test.js"></script>
|
||||
<script type="module" src="application.test.js"></script>
|
||||
<script type="module" src="payload.test.js"></script>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
|
||||
describe('private username', () => {
|
||||
it('generates private username', async () => {
|
||||
const username = 'myusername'
|
||||
|
||||
const result = await ComputePrivateUsername(new SNWebCrypto(), username)
|
||||
|
||||
expect(result).to.equal('9aae57db8dbb233291a49cb7b8ab902336ec785e04f3be70157b8c1669014d0d')
|
||||
})
|
||||
})
|
||||
@@ -1,25 +0,0 @@
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
import * as Factory from './lib/factory.js'
|
||||
|
||||
describe('private workspaces', () => {
|
||||
it('generates identifier', async () => {
|
||||
const userphrase = 'myworkspaceuserphrase'
|
||||
const name = 'myworkspacename'
|
||||
|
||||
const result = await ComputePrivateWorkspaceIdentifier(new SNWebCrypto(), userphrase, name)
|
||||
|
||||
expect(result).to.equal('5155c13a44f333790f6564fbcee0c35a16d26a8359dd77d67d8ecc6ad5d399bb')
|
||||
})
|
||||
|
||||
it('application result matches direct function call', async () => {
|
||||
const userphrase = 'myworkspaceuserphrase'
|
||||
const name = 'myworkspacename'
|
||||
|
||||
const application = (await Factory.createAppContextWithRealCrypto()).application
|
||||
const appResult = await application.computePrivateWorkspaceIdentifier(userphrase, name)
|
||||
const directResult = await ComputePrivateWorkspaceIdentifier(new SNWebCrypto(), userphrase, name)
|
||||
|
||||
expect(appResult).to.equal(directResult)
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/snjs",
|
||||
"version": "2.136.3",
|
||||
"version": "2.139.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -68,7 +68,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/api": "workspace:*",
|
||||
"@standardnotes/common": "^1.36.1",
|
||||
"@standardnotes/common": "^1.39.0",
|
||||
"@standardnotes/domain-events": "^2.39.0",
|
||||
"@standardnotes/encryption": "workspace:*",
|
||||
"@standardnotes/features": "workspace:*",
|
||||
|
||||
@@ -3,6 +3,38 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.7.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.7.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.7.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.7.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.7.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* package import location ([b82eac0](https://github.com/standardnotes/app/commit/b82eac09a822ce1a97167b8b63ab258e2abc9034))
|
||||
|
||||
# [1.7.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.6.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.6.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/ui-services",
|
||||
"version": "1.6.5",
|
||||
"version": "1.7.5",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -23,7 +23,8 @@
|
||||
"test": "jest spec --coverage --passWithNoTests"
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/common": "^1.36.1",
|
||||
"@standardnotes/common": "^1.39.0",
|
||||
"@standardnotes/features": "workspace:^",
|
||||
"@standardnotes/filepicker": "workspace:^",
|
||||
"@standardnotes/services": "workspace:^",
|
||||
"@standardnotes/styles": "workspace:^",
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
StorageValueModes,
|
||||
FeatureStatus,
|
||||
} from '@standardnotes/services'
|
||||
import { FeatureIdentifier } from '@standardnotes/snjs'
|
||||
import { FeatureIdentifier } from '@standardnotes/features'
|
||||
|
||||
const CachedThemesKey = 'cachedThemes'
|
||||
const TimeBeforeApplyingColorScheme = 5
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.10.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.9.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/utils",
|
||||
"version": "1.9.1",
|
||||
"version": "1.10.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -25,7 +25,7 @@
|
||||
"test": "jest spec"
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/common": "^1.36.1",
|
||||
"@standardnotes/common": "^1.39.0",
|
||||
"dompurify": "^2.3.8",
|
||||
"lodash": "^4.17.21",
|
||||
"reflect-metadata": "^0.1.13"
|
||||
|
||||
@@ -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.
|
||||
|
||||
# [3.73.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))
|
||||
|
||||
## [3.72.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* bidirectional linking indicator for files ([37abac2](https://github.com/standardnotes/app/commit/37abac2ec2c839b3af9324a83e36cbbc409d86a9))
|
||||
|
||||
## [3.72.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* css variable fallback ([075d7f4](https://github.com/standardnotes/app/commit/075d7f444da13b380ed675ecfd89823f840e2d56))
|
||||
|
||||
## [3.72.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.72.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* viewport height-related issue on mobile ([9909393](https://github.com/standardnotes/app/commit/990939318f572fe2226784f5ecbd83d5f1709397))
|
||||
|
||||
# [3.72.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.71.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.71.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.71.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.71.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/web",
|
||||
"version": "3.71.5",
|
||||
"version": "3.73.0",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "dist/app.js",
|
||||
"author": "Standard Notes.",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
import { disableIosTextFieldZoom } from '@/Utils'
|
||||
import { disableIosTextFieldZoom, isDev } from '@/Utils'
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -42,22 +42,27 @@ const getKey = () => {
|
||||
return keyCount++
|
||||
}
|
||||
|
||||
let initialCorrectViewportHeight: number | null = null
|
||||
const ViewportHeightKey = '--viewport-height'
|
||||
|
||||
export const setViewportHeightWithFallback = (isOrientationChange = false) => {
|
||||
export const setViewportHeightWithFallback = () => {
|
||||
const currentHeight = parseInt(document.documentElement.style.getPropertyValue(ViewportHeightKey))
|
||||
const newValue = visualViewport && visualViewport.height > 0 ? visualViewport.height : window.innerHeight
|
||||
|
||||
if (initialCorrectViewportHeight && newValue < initialCorrectViewportHeight && !isOrientationChange) {
|
||||
if (isDev) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`currentHeight: ${currentHeight}, newValue: ${newValue}`)
|
||||
}
|
||||
|
||||
if (currentHeight && newValue < currentHeight) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!newValue) {
|
||||
document.documentElement.style.setProperty('--viewport-height', '100vh')
|
||||
document.documentElement.style.setProperty(ViewportHeightKey, '100vh')
|
||||
return
|
||||
}
|
||||
|
||||
initialCorrectViewportHeight = newValue
|
||||
document.documentElement.style.setProperty('--viewport-height', `${newValue}px`)
|
||||
document.documentElement.style.setProperty(ViewportHeightKey, `${newValue}px`)
|
||||
}
|
||||
|
||||
const setDefaultMonospaceFont = (platform?: Platform) => {
|
||||
@@ -84,26 +89,18 @@ const startApplication: StartApplication = async function startApplication(
|
||||
device.environment === Environment.Desktop ||
|
||||
(matchMedia(MediaQueryBreakpoints.md).matches && matchMedia(MediaQueryBreakpoints.pointerFine))
|
||||
|
||||
const orientationChangeHandler = () => {
|
||||
setViewportHeightWithFallback(true)
|
||||
}
|
||||
|
||||
const resizeHandler = () => {
|
||||
setViewportHeightWithFallback(false)
|
||||
}
|
||||
|
||||
const setupViewportHeightListeners = () => {
|
||||
if (!isDesktop) {
|
||||
setViewportHeightWithFallback()
|
||||
window.addEventListener('orientationchange', orientationChangeHandler)
|
||||
window.addEventListener('resize', resizeHandler)
|
||||
window.addEventListener('orientationchange', setViewportHeightWithFallback)
|
||||
window.addEventListener('resize', setViewportHeightWithFallback)
|
||||
}
|
||||
}
|
||||
|
||||
const removeViewportHeightListeners = () => {
|
||||
if (!isDesktop) {
|
||||
window.removeEventListener('orientationchange', orientationChangeHandler)
|
||||
window.removeEventListener('resize', resizeHandler)
|
||||
window.removeEventListener('orientationchange', setViewportHeightWithFallback)
|
||||
window.removeEventListener('resize', setViewportHeightWithFallback)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ type Props = {
|
||||
application: WebApplication
|
||||
viewControllerManager: ViewControllerManager
|
||||
disabled?: boolean
|
||||
onPrivateWorkspaceChange?: (isPrivate: boolean, identifier?: string) => void
|
||||
onPrivateUsernameModeChange?: (isPrivate: boolean, identifier?: string) => void
|
||||
onStrictSignInChange?: (isStrictSignIn: boolean) => void
|
||||
children?: ReactNode
|
||||
}
|
||||
@@ -19,54 +19,46 @@ const AdvancedOptions: FunctionComponent<Props> = ({
|
||||
viewControllerManager,
|
||||
application,
|
||||
disabled = false,
|
||||
onPrivateWorkspaceChange,
|
||||
onPrivateUsernameModeChange,
|
||||
onStrictSignInChange,
|
||||
children,
|
||||
}) => {
|
||||
const { server, setServer, enableServerOption, setEnableServerOption } = viewControllerManager.accountMenuController
|
||||
const [showAdvanced, setShowAdvanced] = useState(false)
|
||||
|
||||
const [isPrivateWorkspace, setIsPrivateWorkspace] = useState(false)
|
||||
const [privateWorkspaceName, setPrivateWorkspaceName] = useState('')
|
||||
const [privateWorkspaceUserphrase, setPrivateWorkspaceUserphrase] = useState('')
|
||||
const [isPrivateUsername, setIsPrivateUsername] = useState(false)
|
||||
const [privateUsername, setPrivateUsername] = useState('')
|
||||
|
||||
const [isStrictSignin, setIsStrictSignin] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const recomputePrivateWorkspaceIdentifier = async () => {
|
||||
const identifier = await application.computePrivateWorkspaceIdentifier(
|
||||
privateWorkspaceName,
|
||||
privateWorkspaceUserphrase,
|
||||
)
|
||||
const recomputePrivateUsername = async () => {
|
||||
const identifier = await application.computePrivateUsername(privateUsername)
|
||||
|
||||
if (!identifier) {
|
||||
if (privateWorkspaceName?.length > 0 && privateWorkspaceUserphrase?.length > 0) {
|
||||
application.alertService.alert('Unable to compute private workspace name.').catch(console.error)
|
||||
if (privateUsername?.length > 0) {
|
||||
application.alertService.alert('Unable to compute private username.').catch(console.error)
|
||||
}
|
||||
return
|
||||
}
|
||||
onPrivateWorkspaceChange?.(true, identifier)
|
||||
onPrivateUsernameModeChange?.(true, identifier)
|
||||
}
|
||||
|
||||
if (privateWorkspaceName && privateWorkspaceUserphrase) {
|
||||
recomputePrivateWorkspaceIdentifier().catch(console.error)
|
||||
if (privateUsername) {
|
||||
recomputePrivateUsername().catch(console.error)
|
||||
}
|
||||
}, [privateWorkspaceName, privateWorkspaceUserphrase, application, onPrivateWorkspaceChange])
|
||||
}, [privateUsername, application, onPrivateUsernameModeChange])
|
||||
|
||||
useEffect(() => {
|
||||
onPrivateWorkspaceChange?.(isPrivateWorkspace)
|
||||
}, [isPrivateWorkspace, onPrivateWorkspaceChange])
|
||||
onPrivateUsernameModeChange?.(isPrivateUsername)
|
||||
}, [isPrivateUsername, onPrivateUsernameModeChange])
|
||||
|
||||
const handleIsPrivateWorkspaceChange = useCallback(() => {
|
||||
setIsPrivateWorkspace(!isPrivateWorkspace)
|
||||
}, [isPrivateWorkspace])
|
||||
const handleIsPrivateUsernameChange = useCallback(() => {
|
||||
setIsPrivateUsername(!isPrivateUsername)
|
||||
}, [isPrivateUsername])
|
||||
|
||||
const handlePrivateWorkspaceNameChange = useCallback((name: string) => {
|
||||
setPrivateWorkspaceName(name)
|
||||
}, [])
|
||||
|
||||
const handlePrivateWorkspaceUserphraseChange = useCallback((userphrase: string) => {
|
||||
setPrivateWorkspaceUserphrase(userphrase)
|
||||
const handlePrivateUsernameNameChange = useCallback((name: string) => {
|
||||
setPrivateUsername(name)
|
||||
}, [])
|
||||
|
||||
const handleServerOptionChange: ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||
@@ -114,35 +106,28 @@ const AdvancedOptions: FunctionComponent<Props> = ({
|
||||
<div className="mb-1 flex items-center justify-between">
|
||||
<Checkbox
|
||||
name="private-workspace"
|
||||
label="Private workspace"
|
||||
checked={isPrivateWorkspace}
|
||||
label="Private username mode"
|
||||
checked={isPrivateUsername}
|
||||
disabled={disabled}
|
||||
onChange={handleIsPrivateWorkspaceChange}
|
||||
onChange={handleIsPrivateUsernameChange}
|
||||
/>
|
||||
<a href="https://standardnotes.com/help/80" target="_blank" rel="noopener noreferrer" title="Learn more">
|
||||
<Icon type="info" className="text-neutral" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{isPrivateWorkspace && (
|
||||
{isPrivateUsername && (
|
||||
<>
|
||||
<DecoratedInput
|
||||
className={{ container: 'mb-2' }}
|
||||
left={[<Icon type="server" className="text-neutral" />]}
|
||||
left={[<Icon type="account-circle" className="text-neutral" />]}
|
||||
type="text"
|
||||
placeholder="Userphrase"
|
||||
value={privateWorkspaceUserphrase}
|
||||
onChange={handlePrivateWorkspaceUserphraseChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<DecoratedInput
|
||||
className={{ container: 'mb-2' }}
|
||||
left={[<Icon type="folder" className="text-neutral" />]}
|
||||
type="text"
|
||||
placeholder="Name"
|
||||
value={privateWorkspaceName}
|
||||
onChange={handlePrivateWorkspaceNameChange}
|
||||
placeholder="Username"
|
||||
value={privateUsername}
|
||||
onChange={handlePrivateUsernameNameChange}
|
||||
disabled={disabled}
|
||||
spellcheck={false}
|
||||
autocomplete={false}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -40,7 +40,7 @@ const CreateAccount: FunctionComponent<Props> = ({
|
||||
}) => {
|
||||
const emailInputRef = useRef<HTMLInputElement>(null)
|
||||
const passwordInputRef = useRef<HTMLInputElement>(null)
|
||||
const [isPrivateWorkspace, setIsPrivateWorkspace] = useState(false)
|
||||
const [isPrivateUsername, setIsPrivateUsername] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (emailInputRef.current) {
|
||||
@@ -98,11 +98,11 @@ const CreateAccount: FunctionComponent<Props> = ({
|
||||
setPassword('')
|
||||
}, [setEmail, setMenuPane, setPassword])
|
||||
|
||||
const onPrivateWorkspaceChange = useCallback(
|
||||
(isPrivateWorkspace: boolean, privateWorkspaceIdentifier?: string) => {
|
||||
setIsPrivateWorkspace(isPrivateWorkspace)
|
||||
if (isPrivateWorkspace && privateWorkspaceIdentifier) {
|
||||
setEmail(privateWorkspaceIdentifier)
|
||||
const onPrivateUsernameChange = useCallback(
|
||||
(isPrivateUsername: boolean, privateUsernameIdentifier?: string) => {
|
||||
setIsPrivateUsername(isPrivateUsername)
|
||||
if (isPrivateUsername && privateUsernameIdentifier) {
|
||||
setEmail(privateUsernameIdentifier)
|
||||
}
|
||||
},
|
||||
[setEmail],
|
||||
@@ -123,7 +123,7 @@ const CreateAccount: FunctionComponent<Props> = ({
|
||||
<form onSubmit={handleRegisterFormSubmit} className="mb-1 px-3">
|
||||
<DecoratedInput
|
||||
className={{ container: 'mb-2' }}
|
||||
disabled={isPrivateWorkspace}
|
||||
disabled={isPrivateUsername}
|
||||
left={[<Icon type="email" className="text-neutral" />]}
|
||||
onChange={handleEmailChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
@@ -147,7 +147,7 @@ const CreateAccount: FunctionComponent<Props> = ({
|
||||
<AdvancedOptions
|
||||
application={application}
|
||||
viewControllerManager={viewControllerManager}
|
||||
onPrivateWorkspaceChange={onPrivateWorkspaceChange}
|
||||
onPrivateUsernameModeChange={onPrivateUsernameChange}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -29,7 +29,7 @@ const SignInPane: FunctionComponent<Props> = ({ application, viewControllerManag
|
||||
const [isStrictSignin, setIsStrictSignin] = useState(false)
|
||||
const [isSigningIn, setIsSigningIn] = useState(false)
|
||||
const [shouldMergeLocal, setShouldMergeLocal] = useState(true)
|
||||
const [isPrivateWorkspace, setIsPrivateWorkspace] = useState(false)
|
||||
const [isPrivateUsername, setIsPrivateUsername] = useState(false)
|
||||
|
||||
const emailInputRef = useRef<HTMLInputElement>(null)
|
||||
const passwordInputRef = useRef<HTMLInputElement>(null)
|
||||
@@ -100,11 +100,11 @@ const SignInPane: FunctionComponent<Props> = ({ application, viewControllerManag
|
||||
})
|
||||
}, [viewControllerManager, application, email, isEphemeral, isStrictSignin, password, shouldMergeLocal])
|
||||
|
||||
const onPrivateWorkspaceChange = useCallback(
|
||||
(newIsPrivateWorkspace: boolean, privateWorkspaceIdentifier?: string) => {
|
||||
setIsPrivateWorkspace(newIsPrivateWorkspace)
|
||||
if (newIsPrivateWorkspace && privateWorkspaceIdentifier) {
|
||||
setEmail(privateWorkspaceIdentifier)
|
||||
const onPrivateUsernameChange = useCallback(
|
||||
(newisPrivateUsername: boolean, privateUsernameIdentifier?: string) => {
|
||||
setIsPrivateUsername(newisPrivateUsername)
|
||||
if (newisPrivateUsername && privateUsernameIdentifier) {
|
||||
setEmail(privateUsernameIdentifier)
|
||||
}
|
||||
},
|
||||
[setEmail],
|
||||
@@ -161,7 +161,7 @@ const SignInPane: FunctionComponent<Props> = ({ application, viewControllerManag
|
||||
onChange={handleEmailChange}
|
||||
onFocus={resetInvalid}
|
||||
onKeyDown={handleKeyDown}
|
||||
disabled={isSigningIn || isPrivateWorkspace}
|
||||
disabled={isSigningIn || isPrivateUsername}
|
||||
ref={emailInputRef}
|
||||
/>
|
||||
<DecoratedPasswordInput
|
||||
@@ -206,7 +206,7 @@ const SignInPane: FunctionComponent<Props> = ({ application, viewControllerManag
|
||||
viewControllerManager={viewControllerManager}
|
||||
application={application}
|
||||
disabled={isSigningIn}
|
||||
onPrivateWorkspaceChange={onPrivateWorkspaceChange}
|
||||
onPrivateUsernameModeChange={onPrivateUsernameChange}
|
||||
onStrictSignInChange={handleStrictSigninChange}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -209,7 +209,6 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
|
||||
itemListController={viewControllerManager.itemListController}
|
||||
navigationController={viewControllerManager.navigationController}
|
||||
noAccountWarningController={viewControllerManager.noAccountWarningController}
|
||||
noteTagsController={viewControllerManager.noteTagsController}
|
||||
notesController={viewControllerManager.notesController}
|
||||
selectionController={viewControllerManager.selectionController}
|
||||
searchOptionsController={viewControllerManager.searchOptionsController}
|
||||
@@ -238,7 +237,7 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
|
||||
application={application}
|
||||
navigationController={viewControllerManager.navigationController}
|
||||
notesController={viewControllerManager.notesController}
|
||||
noteTagsController={viewControllerManager.noteTagsController}
|
||||
linkingController={viewControllerManager.linkingController}
|
||||
historyModalController={viewControllerManager.historyModalController}
|
||||
/>
|
||||
<TagContextMenuWrapper
|
||||
|
||||
+1
-1
@@ -144,7 +144,7 @@ const AttachedFilesButton: FunctionComponent<Props> = ({
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Icon type="attachment-file" />
|
||||
<Icon type="folder" />
|
||||
{attachedFilesCount > 0 && <span className="ml-2 text-sm">{attachedFilesCount}</span>}
|
||||
</button>
|
||||
<Popover
|
||||
|
||||
+20
-23
@@ -11,6 +11,8 @@ import { PopoverFileItemActionType } from './PopoverFileItemAction'
|
||||
import { PopoverTabs } from './PopoverTabs'
|
||||
import { FilesController } from '@/Controllers/FilesController'
|
||||
import { StreamingFileReader } from '@standardnotes/filepicker'
|
||||
import ClearInputButton from '../ClearInputButton/ClearInputButton'
|
||||
import DecoratedInput from '../Input/DecoratedInput'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
@@ -116,29 +118,24 @@ const AttachedFilesPopover: FunctionComponent<Props> = ({
|
||||
<div className="max-h-110 min-h-0 overflow-y-auto">
|
||||
{filteredList.length > 0 || searchQuery.length > 0 ? (
|
||||
<div className="sticky top-0 left-0 border-b border-solid border-border bg-default p-3">
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
className="w-full rounded border border-solid border-border bg-default py-1.5 px-3 text-sm text-text"
|
||||
placeholder="Search files..."
|
||||
value={searchQuery}
|
||||
onInput={(e) => {
|
||||
setSearchQuery((e.target as HTMLInputElement).value)
|
||||
}}
|
||||
ref={searchInputRef}
|
||||
/>
|
||||
{searchQuery.length > 0 && (
|
||||
<button
|
||||
className="absolute right-2 top-1/2 flex -translate-y-1/2 cursor-pointer border-0 bg-transparent p-0"
|
||||
onClick={() => {
|
||||
setSearchQuery('')
|
||||
searchInputRef.current?.focus()
|
||||
}}
|
||||
>
|
||||
<Icon type="clear-circle-filled" className="text-neutral" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<DecoratedInput
|
||||
type="text"
|
||||
className={{ container: searchQuery.length < 1 ? 'py-1.5 px-0.5' : 'py-0' }}
|
||||
placeholder="Search items..."
|
||||
value={searchQuery}
|
||||
onChange={setSearchQuery}
|
||||
ref={searchInputRef}
|
||||
right={[
|
||||
searchQuery.length > 0 && (
|
||||
<ClearInputButton
|
||||
onClick={() => {
|
||||
setSearchQuery('')
|
||||
searchInputRef.current?.focus()
|
||||
}}
|
||||
/>
|
||||
),
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{filteredList.length > 0 ? (
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||
import { ComponentPropsWithoutRef } from 'react'
|
||||
import Icon from '../Icon/Icon'
|
||||
|
||||
type Props = ComponentPropsWithoutRef<'button'>
|
||||
|
||||
const ClearInputButton = ({ className, ...props }: Props) => {
|
||||
return (
|
||||
<button className={classNames('flex cursor-pointer border-0 bg-transparent p-0', className)} {...props}>
|
||||
<Icon type="clear-circle-filled" className="text-neutral" />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default ClearInputButton
|
||||
@@ -11,7 +11,6 @@ import { ItemListController } from '@/Controllers/ItemList/ItemListController'
|
||||
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
|
||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||
import { FilesController } from '@/Controllers/FilesController'
|
||||
import { NoteTagsController } from '@/Controllers/NoteTagsController'
|
||||
import { NoAccountWarningController } from '@/Controllers/NoAccountWarningController'
|
||||
import { NotesController } from '@/Controllers/NotesController'
|
||||
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
|
||||
@@ -33,7 +32,6 @@ type Props = {
|
||||
itemListController: ItemListController
|
||||
navigationController: NavigationController
|
||||
noAccountWarningController: NoAccountWarningController
|
||||
noteTagsController: NoteTagsController
|
||||
notesController: NotesController
|
||||
selectionController: SelectedItemsController
|
||||
searchOptionsController: SearchOptionsController
|
||||
@@ -46,7 +44,6 @@ const ContentListView: FunctionComponent<Props> = ({
|
||||
itemListController,
|
||||
navigationController,
|
||||
noAccountWarningController,
|
||||
noteTagsController,
|
||||
notesController,
|
||||
selectionController,
|
||||
searchOptionsController,
|
||||
@@ -167,16 +164,11 @@ const ContentListView: FunctionComponent<Props> = ({
|
||||
const panelResizeFinishCallback: ResizeFinishCallback = useCallback(
|
||||
(width, _lastLeft, _isMaxWidth, isCollapsed) => {
|
||||
application.setPreference(PrefKey.NotesPanelWidth, width).catch(console.error)
|
||||
noteTagsController.reloadTagsContainerMaxWidth()
|
||||
application.publishPanelDidResizeEvent(PANEL_NAME_NOTES, isCollapsed)
|
||||
},
|
||||
[application, noteTagsController],
|
||||
[application],
|
||||
)
|
||||
|
||||
const panelWidthEventCallback = useCallback(() => {
|
||||
noteTagsController.reloadTagsContainerMaxWidth()
|
||||
}, [noteTagsController])
|
||||
|
||||
const addButtonLabel = useMemo(
|
||||
() => (isFilesSmartView ? 'Upload file' : 'Create a new note in the selected tag'),
|
||||
[isFilesSmartView],
|
||||
@@ -259,7 +251,6 @@ const ContentListView: FunctionComponent<Props> = ({
|
||||
side={PanelSide.Right}
|
||||
type={PanelResizeType.WidthOnly}
|
||||
resizeFinishCallback={panelResizeFinishCallback}
|
||||
widthEventCallback={panelWidthEventCallback}
|
||||
width={panelWidth}
|
||||
left={0}
|
||||
/>
|
||||
|
||||
@@ -32,7 +32,7 @@ const NoteListItem: FunctionComponent<DisplayableListItemProps> = ({
|
||||
const editorForNote = application.componentManager.editorForNote(item as SNNote)
|
||||
const editorName = editorForNote?.name ?? PLAIN_EDITOR_NAME
|
||||
const [icon, tint] = application.iconsController.getIconAndTintForNoteType(editorForNote?.package_info.note_type)
|
||||
const hasFiles = application.items.getFilesForNote(item as SNNote).length > 0
|
||||
const hasFiles = application.items.getSortedFilesLinkingToItem(item).length > 0
|
||||
|
||||
const openNoteContextMenu = (posX: number, posY: number) => {
|
||||
notesController.setContextMenuOpen(false)
|
||||
|
||||
@@ -5,6 +5,8 @@ import FileOptionsPanel from '@/Components/FileContextMenu/FileOptionsPanel'
|
||||
import FilePreview from '@/Components/FilePreview/FilePreview'
|
||||
import { FileViewProps } from './FileViewProps'
|
||||
import MobileItemsListButton from '../NoteGroupView/MobileItemsListButton'
|
||||
import LinkedItemsButton from '../LinkedItems/LinkedItemsButton'
|
||||
import LinkedItemBubblesContainer from '../LinkedItems/LinkedItemBubblesContainer'
|
||||
import Icon from '../Icon/Icon'
|
||||
import Popover from '../Popover/Popover'
|
||||
import FilePreviewInfoPanel from '../FilePreview/FilePreviewInfoPanel'
|
||||
@@ -63,6 +65,10 @@ const FileViewWithoutProtection = ({ application, viewControllerManager, file }:
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<LinkedItemsButton
|
||||
filesController={viewControllerManager.filesController}
|
||||
linkingController={viewControllerManager.linkingController}
|
||||
/>
|
||||
<button
|
||||
className="bg-text-padding flex h-8 min-w-8 cursor-pointer items-center justify-center rounded-full border border-solid border-border text-neutral hover:bg-contrast focus:bg-contrast"
|
||||
title="File information panel"
|
||||
@@ -87,6 +93,7 @@ const FileViewWithoutProtection = ({ application, viewControllerManager, file }:
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<LinkedItemBubblesContainer linkingController={viewControllerManager.linkingController} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex min-h-0 flex-grow flex-col">
|
||||
|
||||
@@ -5,6 +5,7 @@ import * as icons from '@standardnotes/icons'
|
||||
export const ICONS = {
|
||||
'account-circle': icons.AccountCircleIcon,
|
||||
'arrow-left': icons.ArrowLeftIcon,
|
||||
'arrow-right': icons.ArrowRightIcon,
|
||||
'arrows-sort-down': icons.ArrowsSortDownIcon,
|
||||
'arrows-sort-up': icons.ArrowsSortUpIcon,
|
||||
'attachment-file': icons.AttachmentFileIcon,
|
||||
|
||||
@@ -20,6 +20,7 @@ const DecoratedInput = forwardRef(
|
||||
(
|
||||
{
|
||||
autocomplete = false,
|
||||
spellcheck = true,
|
||||
className,
|
||||
disabled = false,
|
||||
id,
|
||||
@@ -68,6 +69,7 @@ const DecoratedInput = forwardRef(
|
||||
title={title}
|
||||
type={type}
|
||||
value={value}
|
||||
spellCheck={spellcheck}
|
||||
/>
|
||||
|
||||
{right && (
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user