Compare commits

...
Author SHA1 Message Date
StandardNotes CI 8553914d08 chore(release): publish
- @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
2022-10-12 19:22:17 +00:00
Mo 18c821d8eb feat: replace private workspaces with private usernames (#1783) 2022-10-12 13:52:34 -05:00
StandardNotes CI 038e456c6a chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].4
2022-10-12 17:11:49 +00:00
Aman Harwara 37abac2ec2 fix: bidirectional linking indicator for files 2022-10-12 22:01:42 +05:30
Aman Harwara 81532f2f20 refactor: item linking (#1781) 2022-10-12 21:57:51 +05:30
StandardNotes CI 2b89ad488f chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
2022-10-12 15:57:21 +00:00
Aman Harwara 075d7f444d fix: css variable fallback 2022-10-12 20:58:02 +05:30
StandardNotes CI a2dc739686 chore(release): publish
- @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
2022-10-12 12:41:26 +00:00
Karol Sójko fabce1f7ac feat(api): add keyshare initiation for workspaces 2022-10-12 14:05:11 +02:00
StandardNotes CI 3fbbacfc25 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
2022-10-11 19:52:04 +00:00
Aman Harwara 990939318f fix: viewport height-related issue on mobile 2022-10-12 00:54:15 +05:30
StandardNotes CI 46e3cae804 chore(release): publish
- @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
2022-10-11 18:53:18 +00:00
Aman Harwara e3f28421ff feat: item linking (#1779) 2022-10-11 23:54:00 +05:30
StandardNotes CI d22c164e5d chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
2022-10-11 14:53:46 +00:00
Mo 5cf37037b7 chore(mobile): podfile.lock 2022-10-11 09:23:31 -05:00
Mo 9fc77d861e fix(mobile): issue with black screen on iOS (#1777) 2022-10-11 09:08:34 -05:00
StandardNotes CI b80b04d374 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].9
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].8
2022-10-11 13:04:23 +00:00
Mo b82eac09a8 fix: package import location 2022-10-11 07:30:49 -05:00
StandardNotes CI 7c9f0d3143 chore(release): publish
- @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].8
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].7
2022-10-11 10:54:24 +00:00
Karol Sójko fb41f651ec feat(api): add listing workspace users 2022-10-11 12:21:06 +02:00
StandardNotes CI 7a43e2d368 chore(release): publish
- @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].7
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].6
2022-10-11 10:20:35 +00:00
Karol Sójko 49b3d2714f feat(api): add access level to invitation requests 2022-10-11 11:46:27 +02:00
StandardNotes CI 1540143dad chore(release): publish
- @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].5
2022-10-11 09:26:07 +00:00
Karol Sójko 8376381981 feat(api): add listing workspaces 2022-10-11 10:57:24 +02:00
Karol Sójko 15e2c82e65 feat(api): add accepting invites to workspace 2022-10-11 10:33:23 +02:00
149 changed files with 3514 additions and 1489 deletions
+33
View File
@@ -3,6 +3,39 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.16.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
**Note:** Version bump only for package @standardnotes/api
# [1.16.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
### Features
* **api:** add keyshare initiation for workspaces ([fabce1f](https://github.com/standardnotes/app/commit/fabce1f7aca5c9caaa5bb3908b2dc68063c690d0))
## [1.15.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
**Note:** Version bump only for package @standardnotes/api
# [1.15.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
### Features
* **api:** add listing workspace users ([fb41f65](https://github.com/standardnotes/app/commit/fb41f651eca45dd62314609c2f389d5dc683c36e))
# [1.14.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
### Features
* **api:** add access level to invitation requests ([49b3d27](https://github.com/standardnotes/app/commit/49b3d2714fbe4ac9f9242097b8df6b4e3d604513))
# [1.13.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
### Features
* **api:** add accepting invites to workspace ([15e2c82](https://github.com/standardnotes/app/commit/15e2c82e654ce3f983cd42b01f14d18435b3e939))
* **api:** add listing workspaces ([8376381](https://github.com/standardnotes/app/commit/837638198112df24c30e53803a7ddf5f841975b0))
## [1.12.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
**Note:** Version bump only for package @standardnotes/api
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api",
"version": "1.12.1",
"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:*",
@@ -1,4 +1,8 @@
export enum WorkspaceApiOperations {
Creating,
Inviting,
Accepting,
ListingWorkspaces,
ListingWorkspaceUsers,
InitiatingKeyshare,
}
@@ -1,7 +1,13 @@
import { WorkspaceType } from '@standardnotes/common'
import { WorkspaceInvitationResponse } from '../../Response'
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'
@@ -19,6 +25,21 @@ describe('WorkspaceApiService', () => {
workspaceServer.inviteToWorkspace = jest.fn().mockReturnValue({
data: { uuid: 'i-1-2-3' },
} as jest.Mocked<WorkspaceInvitationResponse>)
workspaceServer.acceptInvite = jest.fn().mockReturnValue({
data: { success: true },
} as jest.Mocked<WorkspaceInvitationAcceptingResponse>)
workspaceServer.listWorkspaces = jest.fn().mockReturnValue({
status: HttpStatusCode.Success,
data: { ownedWorkspaces: [], joinedWorkspaces: [] },
} as jest.Mocked<WorkspaceListResponse>)
workspaceServer.listWorkspaceUsers = jest.fn().mockReturnValue({
status: HttpStatusCode.Success,
data: { users: [] },
} as jest.Mocked<WorkspaceUserListResponse>)
workspaceServer.initiateKeyshare = jest.fn().mockReturnValue({
status: HttpStatusCode.Success,
data: { success: true },
} as jest.Mocked<WorkspaceKeyshareInitiatingResponse>)
})
it('should create a workspace', async () => {
@@ -87,6 +108,7 @@ describe('WorkspaceApiService', () => {
const response = await createService().inviteToWorkspace({
workspaceUuid: 'w-1-2-3',
inviteeEmail: '[email protected]',
accessLevel: WorkspaceAccessLevel.WriteAndRead,
})
expect(response).toEqual({
@@ -97,6 +119,7 @@ describe('WorkspaceApiService', () => {
expect(workspaceServer.inviteToWorkspace).toHaveBeenCalledWith({
workspaceUuid: 'w-1-2-3',
inviteeEmail: '[email protected]',
accessLevel: 'write-and-read',
})
})
@@ -111,6 +134,7 @@ describe('WorkspaceApiService', () => {
await service.inviteToWorkspace({
workspaceUuid: 'w-1-2-3',
inviteeEmail: '[email protected]',
accessLevel: WorkspaceAccessLevel.WriteAndRead,
})
} catch (caughtError) {
error = caughtError
@@ -129,6 +153,211 @@ describe('WorkspaceApiService', () => {
await createService().inviteToWorkspace({
workspaceUuid: 'w-1-2-3',
inviteeEmail: '[email protected]',
accessLevel: WorkspaceAccessLevel.WriteAndRead,
})
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should accept invite to a workspace', async () => {
const response = await createService().acceptInvite({
userUuid: 'u-1-2-3',
inviteUuid: 'i-1-2-3',
publicKey: 'foo',
encryptedPrivateKey: 'bar',
})
expect(response).toEqual({
data: {
success: true,
},
})
expect(workspaceServer.acceptInvite).toHaveBeenCalledWith({
userUuid: 'u-1-2-3',
inviteUuid: 'i-1-2-3',
publicKey: 'foo',
encryptedPrivateKey: 'bar',
})
})
it('should not accept invite to a workspace if it is already accepting', async () => {
const service = createService()
Object.defineProperty(service, 'operationsInProgress', {
get: () => new Map([[WorkspaceApiOperations.Accepting, true]]),
})
let error = null
try {
await service.acceptInvite({
userUuid: 'u-1-2-3',
inviteUuid: 'i-1-2-3',
publicKey: 'foo',
encryptedPrivateKey: 'bar',
})
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should not accept invite to a workspace if the server fails', async () => {
workspaceServer.acceptInvite = jest.fn().mockImplementation(() => {
throw new Error('Oops')
})
let error = null
try {
await createService().acceptInvite({
userUuid: 'u-1-2-3',
inviteUuid: 'i-1-2-3',
publicKey: 'foo',
encryptedPrivateKey: 'bar',
})
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should list workspaces', async () => {
const response = await createService().listWorkspaces()
expect(response).toEqual({
status: 200,
data: {
ownedWorkspaces: [],
joinedWorkspaces: [],
},
})
expect(workspaceServer.listWorkspaces).toHaveBeenCalled()
})
it('should not list workspaces if it is already listing them', async () => {
const service = createService()
Object.defineProperty(service, 'operationsInProgress', {
get: () => new Map([[WorkspaceApiOperations.ListingWorkspaces, true]]),
})
let error = null
try {
await service.listWorkspaces()
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should not list workspaces if the server fails', async () => {
workspaceServer.listWorkspaces = jest.fn().mockImplementation(() => {
throw new Error('Oops')
})
let error = null
try {
await createService().listWorkspaces()
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should list workspace users', async () => {
const response = await createService().listWorkspaceUsers({ workspaceUuid: 'w-1-2-3' })
expect(response).toEqual({
status: 200,
data: {
users: [],
},
})
expect(workspaceServer.listWorkspaceUsers).toHaveBeenCalledWith({ workspaceUuid: 'w-1-2-3' })
})
it('should not list workspace users if it is already listing them', async () => {
const service = createService()
Object.defineProperty(service, 'operationsInProgress', {
get: () => new Map([[WorkspaceApiOperations.ListingWorkspaceUsers, true]]),
})
let error = null
try {
await service.listWorkspaceUsers({ workspaceUuid: 'w-1-2-3' })
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should not list workspace users if the server fails', async () => {
workspaceServer.listWorkspaceUsers = jest.fn().mockImplementation(() => {
throw new Error('Oops')
})
let error = null
try {
await createService().listWorkspaceUsers({ workspaceUuid: 'w-1-2-3' })
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should initiate keyshare in workspace for user', async () => {
const response = await createService().initiateKeyshare({
workspaceUuid: 'w-1-2-3',
userUuid: 'u-1-2-3',
encryptedWorkspaceKey: 'foobar',
})
expect(response).toEqual({
status: 200,
data: {
success: true,
},
})
expect(workspaceServer.initiateKeyshare).toHaveBeenCalledWith({
workspaceUuid: 'w-1-2-3',
userUuid: 'u-1-2-3',
encryptedWorkspaceKey: 'foobar',
})
})
it('should not initiate keyshare in workspace if it is already initiating', async () => {
const service = createService()
Object.defineProperty(service, 'operationsInProgress', {
get: () => new Map([[WorkspaceApiOperations.InitiatingKeyshare, true]]),
})
let error = null
try {
await service.initiateKeyshare({ workspaceUuid: 'w-1-2-3', userUuid: 'u-1-2-3', encryptedWorkspaceKey: 'foobar' })
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should not initiate keyshare in workspace if the server fails', async () => {
workspaceServer.initiateKeyshare = jest.fn().mockImplementation(() => {
throw new Error('Oops')
})
let error = null
try {
await createService().initiateKeyshare({
workspaceUuid: 'w-1-2-3',
userUuid: 'u-1-2-3',
encryptedWorkspaceKey: 'foobar',
})
} catch (caughtError) {
error = caughtError
@@ -1,15 +1,18 @@
import { Uuid, WorkspaceType } from '@standardnotes/common'
import { Uuid, WorkspaceAccessLevel, WorkspaceType } from '@standardnotes/common'
import { ErrorMessage } from '../../Error/ErrorMessage'
import { ApiCallError } from '../../Error/ApiCallError'
import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse'
import { WorkspaceInvitationAcceptingResponse } from '../../Response/Workspace/WorkspaceInvitationAcceptingResponse'
import { WorkspaceInvitationResponse } from '../../Response/Workspace/WorkspaceInvitationResponse'
import { WorkspaceServerInterface } from '../../Server/Workspace/WorkspaceServerInterface'
import { WorkspaceListResponse } from '../../Response/Workspace/WorkspaceListResponse'
import { WorkspaceUserListResponse } from '../../Response/Workspace/WorkspaceUserListResponse'
import { WorkspaceKeyshareInitiatingResponse } from '../../Response/Workspace/WorkspaceKeyshareInitiatingResponse'
import { WorkspaceApiServiceInterface } from './WorkspaceApiServiceInterface'
import { WorkspaceApiOperations } from './WorkspaceApiOperations'
import { WorkspaceInvitationResponse } from '../../Response'
export class WorkspaceApiService implements WorkspaceApiServiceInterface {
private operationsInProgress: Map<WorkspaceApiOperations, boolean>
@@ -17,20 +20,95 @@ export class WorkspaceApiService implements WorkspaceApiServiceInterface {
this.operationsInProgress = new Map()
}
async inviteToWorkspace(dto: { inviteeEmail: string; workspaceUuid: Uuid }): Promise<WorkspaceInvitationResponse> {
if (this.operationsInProgress.get(WorkspaceApiOperations.Inviting)) {
throw new ApiCallError(ErrorMessage.GenericInProgress)
}
async initiateKeyshare(dto: {
workspaceUuid: string
userUuid: string
encryptedWorkspaceKey: string
}): Promise<WorkspaceKeyshareInitiatingResponse> {
this.lockOperation(WorkspaceApiOperations.InitiatingKeyshare)
this.operationsInProgress.set(WorkspaceApiOperations.Inviting, true)
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)
try {
const response = await this.workspaceServer.listWorkspaces({})
this.unlockOperation(WorkspaceApiOperations.ListingWorkspaces)
return response
} catch (error) {
throw new ApiCallError(ErrorMessage.GenericFail)
}
}
async acceptInvite(dto: {
inviteUuid: string
userUuid: string
publicKey: string
encryptedPrivateKey: string
}): Promise<WorkspaceInvitationAcceptingResponse> {
this.lockOperation(WorkspaceApiOperations.Accepting)
try {
const response = await this.workspaceServer.acceptInvite({
encryptedPrivateKey: dto.encryptedPrivateKey,
publicKey: dto.publicKey,
inviteUuid: dto.inviteUuid,
userUuid: dto.userUuid,
})
this.unlockOperation(WorkspaceApiOperations.Accepting)
return response
} catch (error) {
throw new ApiCallError(ErrorMessage.GenericFail)
}
}
async inviteToWorkspace(dto: {
inviteeEmail: string
workspaceUuid: Uuid
accessLevel: WorkspaceAccessLevel
}): Promise<WorkspaceInvitationResponse> {
this.lockOperation(WorkspaceApiOperations.Inviting)
try {
const response = await this.workspaceServer.inviteToWorkspace({
inviteeEmail: dto.inviteeEmail,
workspaceUuid: dto.workspaceUuid,
accessLevel: dto.accessLevel,
})
this.operationsInProgress.set(WorkspaceApiOperations.Inviting, false)
this.unlockOperation(WorkspaceApiOperations.Inviting)
return response
} catch (error) {
@@ -45,11 +123,7 @@ export class WorkspaceApiService implements WorkspaceApiServiceInterface {
publicKey?: string
workspaceName?: string
}): Promise<WorkspaceCreationResponse> {
if (this.operationsInProgress.get(WorkspaceApiOperations.Creating)) {
throw new ApiCallError(ErrorMessage.GenericInProgress)
}
this.operationsInProgress.set(WorkspaceApiOperations.Creating, true)
this.lockOperation(WorkspaceApiOperations.Creating)
try {
const response = await this.workspaceServer.createWorkspace({
@@ -60,11 +134,23 @@ export class WorkspaceApiService implements WorkspaceApiServiceInterface {
workspaceName: dto.workspaceName,
})
this.operationsInProgress.set(WorkspaceApiOperations.Creating, false)
this.unlockOperation(WorkspaceApiOperations.Creating)
return response
} catch (error) {
throw new ApiCallError(ErrorMessage.GenericFail)
}
}
private lockOperation(operation: WorkspaceApiOperations): void {
if (this.operationsInProgress.get(operation)) {
throw new ApiCallError(ErrorMessage.GenericInProgress)
}
this.operationsInProgress.set(operation, true)
}
private unlockOperation(operation: WorkspaceApiOperations): void {
this.operationsInProgress.set(operation, false)
}
}
@@ -1,6 +1,11 @@
import { Uuid, WorkspaceType } from '@standardnotes/common'
import { Uuid, WorkspaceAccessLevel, WorkspaceType } from '@standardnotes/common'
import { WorkspaceCreationResponse, WorkspaceInvitationResponse } from '../../Response'
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: {
@@ -10,5 +15,22 @@ 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
publicKey: string
encryptedPrivateKey: string
}): Promise<WorkspaceInvitationAcceptingResponse>
listWorkspaces(): Promise<WorkspaceListResponse>
listWorkspaceUsers(dto: { workspaceUuid: Uuid }): Promise<WorkspaceUserListResponse>
initiateKeyshare(dto: {
workspaceUuid: Uuid
userUuid: Uuid
encryptedWorkspaceKey: string
}): Promise<WorkspaceKeyshareInitiatingResponse>
}
@@ -0,0 +1,9 @@
import { Uuid } from '@standardnotes/common'
export type WorkspaceInvitationAcceptingRequestParams = {
inviteUuid: Uuid
userUuid: Uuid
publicKey: string
encryptedPrivateKey: string
[additionalParam: string]: unknown
}
@@ -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,3 @@
export type WorkspaceListRequestParams = {
[additionalParam: string]: unknown
}
@@ -0,0 +1,6 @@
import { Uuid } from '@standardnotes/common'
export type WorkspaceUserListRequestParams = {
workspaceUuid: Uuid
[additionalParam: string]: unknown
}
+4
View File
@@ -7,4 +7,8 @@ export * from './Subscription/SubscriptionInviteRequestParams'
export * from './User/UserRegistrationRequestParams'
export * from './WebSocket/WebSocketConnectionTokenRequestParams'
export * from './Workspace/WorkspaceCreationRequestParams'
export * from './Workspace/WorkspaceInvitationAcceptingRequestParams'
export * from './Workspace/WorkspaceInvitationRequestParams'
export * from './Workspace/WorkspaceKeyshareInitiatingRequestParams'
export * from './Workspace/WorkspaceListRequestParams'
export * from './Workspace/WorkspaceUserListRequestParams'
@@ -0,0 +1,9 @@
import { Either } from '@standardnotes/common'
import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody'
import { HttpResponse } from '../../Http/HttpResponse'
import { WorkspaceInvitationAcceptingResponseBody } from './WorkspaceInvitationAcceptingResponseBody'
export interface WorkspaceInvitationAcceptingResponse extends HttpResponse {
data: Either<WorkspaceInvitationAcceptingResponseBody, HttpErrorResponseBody>
}
@@ -0,0 +1,3 @@
export type WorkspaceInvitationAcceptingResponseBody = {
success: boolean
}
@@ -0,0 +1,9 @@
import { Either } from '@standardnotes/common'
import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody'
import { HttpResponse } from '../../Http/HttpResponse'
import { WorkspaceKeyshareInitiatingResponseBody } from './WorkspaceKeyshareInitiatingResponseBody'
export interface WorkspaceKeyshareInitiatingResponse extends HttpResponse {
data: Either<WorkspaceKeyshareInitiatingResponseBody, HttpErrorResponseBody>
}
@@ -0,0 +1,3 @@
export type WorkspaceKeyshareInitiatingResponseBody = {
success: boolean
}
@@ -0,0 +1,9 @@
import { Either } from '@standardnotes/common'
import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody'
import { HttpResponse } from '../../Http/HttpResponse'
import { WorkspaceListResponseBody } from './WorkspaceListResponseBody'
export interface WorkspaceListResponse extends HttpResponse {
data: Either<WorkspaceListResponseBody, HttpErrorResponseBody>
}
@@ -0,0 +1,6 @@
import { Workspace } from '@standardnotes/models'
export type WorkspaceListResponseBody = {
ownedWorkspaces: Array<Workspace>
joinedWorkspaces: Array<Workspace>
}
@@ -0,0 +1,9 @@
import { Either } from '@standardnotes/common'
import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody'
import { HttpResponse } from '../../Http/HttpResponse'
import { WorkspaceUserListResponseBody } from './WorkspaceUserListResponseBody'
export interface WorkspaceUserListResponse extends HttpResponse {
data: Either<WorkspaceUserListResponseBody, HttpErrorResponseBody>
}
@@ -0,0 +1,5 @@
import { WorkspaceUser } from '@standardnotes/models'
export type WorkspaceUserListResponseBody = {
users: Array<WorkspaceUser>
}
@@ -14,5 +14,13 @@ export * from './WebSocket/WebSocketConnectionTokenResponse'
export * from './WebSocket/WebSocketConnectionTokenResponseBody'
export * from './Workspace/WorkspaceCreationResponse'
export * from './Workspace/WorkspaceCreationResponseBody'
export * from './Workspace/WorkspaceInvitationAcceptingResponse'
export * from './Workspace/WorkspaceInvitationAcceptingResponseBody'
export * from './Workspace/WorkspaceKeyshareInitiatingResponse'
export * from './Workspace/WorkspaceKeyshareInitiatingResponseBody'
export * from './Workspace/WorkspaceInvitationResponse'
export * from './Workspace/WorkspaceInvitationResponseBody'
export * from './Workspace/WorkspaceListResponse'
export * from './Workspace/WorkspaceListResponseBody'
export * from './Workspace/WorkspaceUserListResponse'
export * from './Workspace/WorkspaceUserListResponseBody'
@@ -2,7 +2,12 @@ import { Uuid } from '@standardnotes/common'
const WorkspacePaths = {
createWorkspace: '/v1/workspaces',
listWorkspaces: '/v1/workspaces',
listWorkspaceUsers: (uuid: Uuid) => `/v1/workspaces/${uuid}/users`,
initiateKeyshare: (worksapceUuid: Uuid, userUuid: Uuid) =>
`/v1/workspaces/${worksapceUuid}/users/${userUuid}/keyshare`,
inviteToWorkspace: (uuid: Uuid) => `/v1/workspaces/${uuid}/invites`,
acceptInvite: (uuid: Uuid) => `/v1/invites/${uuid}/accept`,
}
export const Paths = {
@@ -1,7 +1,12 @@
import { WorkspaceType } from '@standardnotes/common'
import { HttpServiceInterface } from '../../Http'
import { WorkspaceAccessLevel, WorkspaceType } from '@standardnotes/common'
import { HttpServiceInterface, HttpStatusCode } from '../../Http'
import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse'
import { WorkspaceInvitationAcceptingResponse } from '../../Response/Workspace/WorkspaceInvitationAcceptingResponse'
import { WorkspaceInvitationResponse } from '../../Response/Workspace/WorkspaceInvitationResponse'
import { WorkspaceKeyshareInitiatingResponse } from '../../Response/Workspace/WorkspaceKeyshareInitiatingResponse'
import { WorkspaceListResponse } from '../../Response/Workspace/WorkspaceListResponse'
import { WorkspaceUserListResponse } from '../../Response/Workspace/WorkspaceUserListResponse'
import { WorkspaceServer } from './WorkspaceServer'
@@ -40,6 +45,7 @@ describe('WorkspaceServer', () => {
const response = await createServer().inviteToWorkspace({
inviteeEmail: '[email protected]',
workspaceUuid: 'w-1-2-3',
accessLevel: WorkspaceAccessLevel.WriteAndRead,
})
expect(response).toEqual({
@@ -48,4 +54,71 @@ describe('WorkspaceServer', () => {
},
})
})
it('should accept invitation to a workspace', async () => {
httpService.post = jest.fn().mockReturnValue({
data: { success: true },
} as jest.Mocked<WorkspaceInvitationAcceptingResponse>)
const response = await createServer().acceptInvite({
encryptedPrivateKey: 'foo',
inviteUuid: 'i-1-2-3',
publicKey: 'bar',
userUuid: 'u-1-2-3',
})
expect(response).toEqual({
data: {
success: true,
},
})
})
it('should list workspaces', async () => {
httpService.get = jest.fn().mockReturnValue({
status: HttpStatusCode.Success,
data: { ownedWorkspaces: [], joinedWorkspaces: [] },
} as jest.Mocked<WorkspaceListResponse>)
const response = await createServer().listWorkspaces({})
expect(response).toEqual({
status: 200,
data: { ownedWorkspaces: [], joinedWorkspaces: [] },
})
})
it('should list workspace users', async () => {
httpService.get = jest.fn().mockReturnValue({
status: HttpStatusCode.Success,
data: { users: [] },
} as jest.Mocked<WorkspaceUserListResponse>)
const response = await createServer().listWorkspaceUsers({ workspaceUuid: 'w-1-2-3' })
expect(response).toEqual({
status: 200,
data: { users: [] },
})
})
it('should initiate keyshare for user in a workspace', async () => {
httpService.post = jest.fn().mockReturnValue({
status: HttpStatusCode.Success,
data: { success: true },
} as jest.Mocked<WorkspaceKeyshareInitiatingResponse>)
const response = await createServer().initiateKeyshare({
workspaceUuid: 'w-1-2-3',
userUuid: 'u-1-2-3',
encryptedWorkspaceKey: 'foobar',
})
expect(response).toEqual({
status: 200,
data: {
success: true,
},
})
})
})
@@ -3,6 +3,14 @@ import { WorkspaceInvitationRequestParams } from '../../Request/Workspace/Worksp
import { WorkspaceCreationRequestParams } from '../../Request/Workspace/WorkspaceCreationRequestParams'
import { WorkspaceInvitationResponse } from '../../Response/Workspace/WorkspaceInvitationResponse'
import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse'
import { WorkspaceInvitationAcceptingRequestParams } from '../../Request/Workspace/WorkspaceInvitationAcceptingRequestParams'
import { WorkspaceInvitationAcceptingResponse } from '../../Response/Workspace/WorkspaceInvitationAcceptingResponse'
import { WorkspaceListRequestParams } from '../../Request/Workspace/WorkspaceListRequestParams'
import { WorkspaceListResponse } from '../../Response/Workspace/WorkspaceListResponse'
import { WorkspaceUserListRequestParams } from '../../Request/Workspace/WorkspaceUserListRequestParams'
import { WorkspaceUserListResponse } from '../../Response/Workspace/WorkspaceUserListResponse'
import { WorkspaceKeyshareInitiatingRequestParams } from '../../Request/Workspace/WorkspaceKeyshareInitiatingRequestParams'
import { WorkspaceKeyshareInitiatingResponse } from '../../Response/Workspace/WorkspaceKeyshareInitiatingResponse'
import { Paths } from './Paths'
import { WorkspaceServerInterface } from './WorkspaceServerInterface'
@@ -10,6 +18,35 @@ import { WorkspaceServerInterface } from './WorkspaceServerInterface'
export class WorkspaceServer implements WorkspaceServerInterface {
constructor(private httpService: HttpServiceInterface) {}
async initiateKeyshare(
params: WorkspaceKeyshareInitiatingRequestParams,
): Promise<WorkspaceKeyshareInitiatingResponse> {
const response = await this.httpService.post(
Paths.v1.initiateKeyshare(params.workspaceUuid, params.userUuid),
params,
)
return response as WorkspaceKeyshareInitiatingResponse
}
async listWorkspaceUsers(params: WorkspaceUserListRequestParams): Promise<WorkspaceUserListResponse> {
const response = await this.httpService.get(Paths.v1.listWorkspaceUsers(params.workspaceUuid), params)
return response as WorkspaceUserListResponse
}
async listWorkspaces(params: WorkspaceListRequestParams): Promise<WorkspaceListResponse> {
const response = await this.httpService.get(Paths.v1.listWorkspaces, params)
return response as WorkspaceListResponse
}
async acceptInvite(params: WorkspaceInvitationAcceptingRequestParams): Promise<WorkspaceInvitationAcceptingResponse> {
const response = await this.httpService.post(Paths.v1.acceptInvite(params.inviteUuid), params)
return response as WorkspaceInvitationAcceptingResponse
}
async inviteToWorkspace(params: WorkspaceInvitationRequestParams): Promise<WorkspaceInvitationResponse> {
const response = await this.httpService.post(Paths.v1.inviteToWorkspace(params.workspaceUuid), params)
@@ -2,8 +2,20 @@ import { WorkspaceInvitationRequestParams } from '../../Request/Workspace/Worksp
import { WorkspaceCreationRequestParams } from '../../Request/Workspace/WorkspaceCreationRequestParams'
import { WorkspaceInvitationResponse } from '../../Response/Workspace/WorkspaceInvitationResponse'
import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse'
import { WorkspaceInvitationAcceptingRequestParams } from '../../Request/Workspace/WorkspaceInvitationAcceptingRequestParams'
import { WorkspaceInvitationAcceptingResponse } from '../../Response/Workspace/WorkspaceInvitationAcceptingResponse'
import { WorkspaceListRequestParams } from '../../Request/Workspace/WorkspaceListRequestParams'
import { WorkspaceListResponse } from '../../Response/Workspace/WorkspaceListResponse'
import { WorkspaceUserListRequestParams } from '../../Request/Workspace/WorkspaceUserListRequestParams'
import { WorkspaceUserListResponse } from '../../Response/Workspace/WorkspaceUserListResponse'
import { WorkspaceKeyshareInitiatingRequestParams } from '../../Request/Workspace/WorkspaceKeyshareInitiatingRequestParams'
import { WorkspaceKeyshareInitiatingResponse } from '../../Response/Workspace/WorkspaceKeyshareInitiatingResponse'
export interface WorkspaceServerInterface {
createWorkspace(params: WorkspaceCreationRequestParams): Promise<WorkspaceCreationResponse>
listWorkspaces(params: WorkspaceListRequestParams): Promise<WorkspaceListResponse>
listWorkspaceUsers(params: WorkspaceUserListRequestParams): Promise<WorkspaceUserListResponse>
inviteToWorkspace(params: WorkspaceInvitationRequestParams): Promise<WorkspaceInvitationResponse>
acceptInvite(params: WorkspaceInvitationAcceptingRequestParams): Promise<WorkspaceInvitationAcceptingResponse>
initiateKeyshare(params: WorkspaceKeyshareInitiatingRequestParams): Promise<WorkspaceKeyshareInitiatingResponse>
}
+4
View File
@@ -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 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/components-meta",
"version": "2.7.18",
"version": "2.7.19",
"private": true,
"author": "Standard Notes.",
"main": "dist",
+40
View File
@@ -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.
## [3.23.212](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.211](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.210](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.209](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.208](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.207](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.206](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.205](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.204](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.203](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.202](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
**Note:** Version bump only for package @standardnotes/desktop
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@standardnotes/desktop",
"main": "./app/dist/index.js",
"version": "3.23.202",
"version": "3.23.212",
"license": "AGPL-3.0-or-later",
"author": "Standard Notes.",
"private": true,
+20
View File
@@ -3,6 +3,26 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.18.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
### Features
* replace private workspaces with private usernames ([#1783](https://github.com/standardnotes/app/issues/1783)) ([18c821d](https://github.com/standardnotes/app/commit/18c821d8eb51beb6f54211eb1d6eb454303044f5))
## [1.17.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
**Note:** Version bump only for package @standardnotes/encryption
# [1.17.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
### Features
* **api:** add listing workspace users ([fb41f65](https://github.com/standardnotes/app/commit/fb41f651eca45dd62314609c2f389d5dc683c36e))
## [1.16.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
**Note:** Version bump only for package @standardnotes/encryption
## [1.16.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
**Note:** Version bump only for package @standardnotes/encryption
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/encryption",
"version": "1.16.2",
"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
}
+1 -1
View File
@@ -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'
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [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
+2 -2
View File
@@ -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"
},
+18
View File
@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.24.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
**Note:** Version bump only for package @standardnotes/filepicker
## [1.24.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
**Note:** Version bump only for package @standardnotes/filepicker
# [1.24.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
### Features
* **api:** add listing workspace users ([fb41f65](https://github.com/standardnotes/app/commit/fb41f651eca45dd62314609c2f389d5dc683c36e))
## [1.23.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
**Note:** Version bump only for package @standardnotes/filepicker
## [1.23.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
**Note:** Version bump only for package @standardnotes/filepicker
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/filepicker",
"version": "1.23.14",
"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",
+18
View File
@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.11.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
**Note:** Version bump only for package @standardnotes/files
## [1.11.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
**Note:** Version bump only for package @standardnotes/files
# [1.11.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
### Features
* **api:** add listing workspace users ([fb41f65](https://github.com/standardnotes/app/commit/fb41f651eca45dd62314609c2f389d5dc683c36e))
## [1.10.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
**Note:** Version bump only for package @standardnotes/files
## [1.10.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
**Note:** Version bump only for package @standardnotes/files
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files",
"version": "1.10.14",
"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:*",
+48
View File
@@ -3,6 +3,54 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.42.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
**Note:** Version bump only for package @standardnotes/mobile
## [3.42.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
**Note:** Version bump only for package @standardnotes/mobile
## [3.42.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
**Note:** Version bump only for package @standardnotes/mobile
## [3.42.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
**Note:** Version bump only for package @standardnotes/mobile
## [3.42.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
**Note:** Version bump only for package @standardnotes/mobile
# [3.42.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
### Features
* item linking ([#1779](https://github.com/standardnotes/app/issues/1779)) ([e3f2842](https://github.com/standardnotes/app/commit/e3f28421ff042c635ad2ae645c102c27e3e3f9c7))
## [3.41.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
### Bug Fixes
* **mobile:** issue with black screen on iOS ([#1777](https://github.com/standardnotes/app/issues/1777)) ([9fc77d8](https://github.com/standardnotes/app/commit/9fc77d861e501d63ee97875bba2ddda51d14fd0b))
## [3.41.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
**Note:** Version bump only for package @standardnotes/mobile
## [3.41.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
**Note:** Version bump only for package @standardnotes/mobile
## [3.41.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
**Note:** Version bump only for package @standardnotes/mobile
## [3.41.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
**Note:** Version bump only for package @standardnotes/mobile
## [3.41.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
**Note:** Version bump only for package @standardnotes/mobile
+2 -2
View File
@@ -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
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/mobile",
"version": "3.41.5",
"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"
+1 -1
View File
@@ -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()
+18
View File
@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.27.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
### Features
* item linking ([#1779](https://github.com/standardnotes/app/issues/1779)) ([e3f2842](https://github.com/standardnotes/app/commit/e3f28421ff042c635ad2ae645c102c27e3e3f9c7))
# [1.26.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
### Features
* **api:** add listing workspace users ([fb41f65](https://github.com/standardnotes/app/commit/fb41f651eca45dd62314609c2f389d5dc683c36e))
# [1.25.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
### Features
* **api:** add listing workspaces ([8376381](https://github.com/standardnotes/app/commit/837638198112df24c30e53803a7ddf5f841975b0))
## [1.24.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
**Note:** Version bump only for package @standardnotes/models
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/models",
"version": "1.24.2",
"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,10 @@
import { WorkspaceType } from '@standardnotes/common'
export type Workspace = {
uuid: string
type: WorkspaceType
name: string | null
keyRotationIndex: number
createdAt: number
updatedAt: number
}
@@ -0,0 +1,16 @@
import { Uuid, WorkspaceAccessLevel, WorkspaceUserStatus } from '@standardnotes/common'
export type WorkspaceUser = {
uuid: Uuid
accessLevel: WorkspaceAccessLevel
userUuid: Uuid
userDisplayName: string | null
workspaceUuid: Uuid
encryptedWorkspaceKey: string | null
publicKey: string | null
encryptedPrivateKey: string | null
status: WorkspaceUserStatus
keyRotationIndex: number
createdAt: number
updatedAt: number
}
@@ -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)
})
})
@@ -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,
}
+3 -1
View File
@@ -28,6 +28,8 @@ export * from './Api/Subscription/Invitation'
export * from './Api/Subscription/InvitationStatus'
export * from './Api/Subscription/InviteeIdentifierType'
export * from './Api/Subscription/InviterIdentifierType'
export * from './Api/Workspace/Workspace'
export * from './Api/Workspace/WorkspaceUser'
export * from './Device/Environment'
export * from './Device/Platform'
export * from './Local/KeyParams/RootKeyParamsInterface'
@@ -36,7 +38,7 @@ export * from './Local/RootKey/RootKeyContent'
export * from './Local/RootKey/RootKeyInterface'
export * from './Runtime/Collection/CollectionSort'
export * from './Runtime/Collection/Item/ItemCollection'
export * from './Runtime/Collection/Item/TagNotesIndex'
export * from './Runtime/Collection/Item/TagItemsIndex'
export * from './Runtime/Collection/Payload/ImmutablePayloadCollection'
export * from './Runtime/Collection/Payload/PayloadCollection'
export * from './Runtime/Deltas'
+44
View File
@@ -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.
## [1.3.140](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.139](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.138](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.137](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.136](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.135](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.134](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.133](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.132](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.131](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.130](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.129](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
**Note:** Version bump only for package @standardnotes/releases
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/releases",
"version": "1.3.129",
"version": "1.3.140",
"license": "AGPL-3.0-or-later",
"main": "dist/releases.json",
"types": "dist/index.d.ts",
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [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
+2 -2
View File
@@ -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"
+39
View File
@@ -3,6 +3,45 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.32.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
**Note:** Version bump only for package @standardnotes/services
## [1.32.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
**Note:** Version bump only for package @standardnotes/services
# [1.32.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
### Features
* **api:** add keyshare initiation for workspaces ([fabce1f](https://github.com/standardnotes/app/commit/fabce1f7aca5c9caaa5bb3908b2dc68063c690d0))
# [1.31.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
### Features
* item linking ([#1779](https://github.com/standardnotes/app/issues/1779)) ([e3f2842](https://github.com/standardnotes/app/commit/e3f28421ff042c635ad2ae645c102c27e3e3f9c7))
# [1.30.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
### Features
* **api:** add listing workspace users ([fb41f65](https://github.com/standardnotes/app/commit/fb41f651eca45dd62314609c2f389d5dc683c36e))
# [1.29.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
### Features
* **api:** add access level to invitation requests ([49b3d27](https://github.com/standardnotes/app/commit/49b3d2714fbe4ac9f9242097b8df6b4e3d604513))
# [1.28.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
### Features
* **api:** add accepting invites to workspace ([15e2c82](https://github.com/standardnotes/app/commit/15e2c82e654ce3f983cd42b01f14d18435b3e939))
* **api:** add listing workspaces ([8376381](https://github.com/standardnotes/app/commit/837638198112df24c30e53803a7ddf5f841975b0))
## [1.27.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
### Bug Fixes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/services",
"version": "1.27.1",
"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,4 +1,5 @@
import { Uuid, WorkspaceType } from '@standardnotes/common'
import { Uuid, WorkspaceAccessLevel, WorkspaceType } from '@standardnotes/common'
import { Workspace, WorkspaceUser } from '@standardnotes/models'
export interface WorkspaceClientInterface {
createWorkspace(dto: {
@@ -8,5 +9,22 @@ 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
publicKey: string
encryptedPrivateKey: string
}): Promise<{ success: boolean }>
listWorkspaces(): Promise<{ ownedWorkspaces: Array<Workspace>; joinedWorkspaces: Array<Workspace> }>
listWorkspaceUsers(dto: { workspaceUuid: Uuid }): Promise<{ users: Array<WorkspaceUser> }>
initiateKeyshare(dto: {
workspaceUuid: Uuid
userUuid: Uuid
encryptedWorkspaceKey: string
}): Promise<{ success: boolean }>
}
@@ -1,5 +1,6 @@
import { WorkspaceApiServiceInterface } from '@standardnotes/api'
import { Uuid, WorkspaceType } from '@standardnotes/common'
import { Uuid, WorkspaceAccessLevel, WorkspaceType } from '@standardnotes/common'
import { Workspace, WorkspaceUser } from '@standardnotes/models'
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
import { AbstractService } from '../Service/AbstractService'
@@ -13,7 +14,76 @@ export class WorkspaceManager extends AbstractService implements WorkspaceClient
super(internalEventBus)
}
async inviteToWorkspace(dto: { inviteeEmail: string; workspaceUuid: Uuid }): Promise<{ uuid: string } | null> {
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()
if (result.data.error !== undefined) {
return { ownedWorkspaces: [], joinedWorkspaces: [] }
}
return result.data
} catch (error) {
return { ownedWorkspaces: [], joinedWorkspaces: [] }
}
}
async acceptInvite(dto: {
inviteUuid: string
userUuid: string
publicKey: string
encryptedPrivateKey: string
}): Promise<{ success: boolean }> {
try {
const result = await this.workspaceApiService.acceptInvite(dto)
if (result.data.error !== undefined) {
return { success: false }
}
return result.data
} catch (error) {
return { success: false }
}
}
async inviteToWorkspace(dto: {
inviteeEmail: string
workspaceUuid: Uuid
accessLevel: WorkspaceAccessLevel
}): Promise<{ uuid: string } | null> {
try {
const result = await this.workspaceApiService.inviteToWorkspace(dto)
+34
View File
@@ -3,6 +3,40 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.139.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
### Features
* replace private workspaces with private usernames ([#1783](https://github.com/standardnotes/app/issues/1783)) ([18c821d](https://github.com/standardnotes/app/commit/18c821d8eb51beb6f54211eb1d6eb454303044f5))
## [2.138.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
**Note:** Version bump only for package @standardnotes/snjs
## [2.138.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-12)
**Note:** Version bump only for package @standardnotes/snjs
# [2.138.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
### Features
* item linking ([#1779](https://github.com/standardnotes/app/issues/1779)) ([e3f2842](https://github.com/standardnotes/app/commit/e3f28421ff042c635ad2ae645c102c27e3e3f9c7))
# [2.137.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
### Features
* **api:** add listing workspace users ([fb41f65](https://github.com/standardnotes/app/commit/fb41f651eca45dd62314609c2f389d5dc683c36e))
## [2.136.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
**Note:** Version bump only for package @standardnotes/snjs
## [2.136.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-11)
**Note:** Version bump only for package @standardnotes/snjs
## [2.136.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
### Bug Fixes
+3 -3
View File
@@ -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)
})
})
})
+118 -21
View File
@@ -9,7 +9,7 @@ import * as Services from '@standardnotes/services'
import { PayloadManagerChangeData } from '../Payloads'
import { DiagnosticInfo, ItemsClientInterface } from '@standardnotes/services'
import { ApplicationDisplayOptions } from '@Lib/Application/Options/OptionalOptions'
import { CollectionSort } from '@standardnotes/models'
import { CollectionSort, DecryptedItemInterface, ItemContent } from '@standardnotes/models'
type ItemsChangeObserver<I extends Models.DecryptedItemInterface = Models.DecryptedItemInterface> = {
contentType: ContentType[]
@@ -32,7 +32,7 @@ export class ItemManager
private observers: ItemsChangeObserver[] = []
private collection!: Models.ItemCollection
private systemSmartViews: Models.SmartView[]
private tagNotesIndex!: Models.TagNotesIndex
private tagItemsIndex!: Models.TagItemsIndex
private navigationDisplayController!: Models.ItemDisplayController<Models.SNNote | Models.FileItem>
private tagDisplayController!: Models.ItemDisplayController<Models.SNTag>
@@ -96,7 +96,7 @@ export class ItemManager
sortDirection: 'asc',
})
this.tagNotesIndex = new Models.TagNotesIndex(this.collection, this.tagNotesIndex?.observers)
this.tagItemsIndex = new Models.TagItemsIndex(this.collection, this.tagItemsIndex?.observers)
}
private get allDisplayControllers(): Models.ItemDisplayController<Models.DisplayItem>[] {
@@ -219,7 +219,7 @@ export class ItemManager
;(this.unsubChangeObserver as unknown) = undefined
;(this.payloadManager as unknown) = undefined
;(this.collection as unknown) = undefined
;(this.tagNotesIndex as unknown) = undefined
;(this.tagItemsIndex as unknown) = undefined
;(this.tagDisplayController as unknown) = undefined
;(this.navigationDisplayController as unknown) = undefined
;(this.itemsKeyDisplayController as unknown) = undefined
@@ -284,23 +284,23 @@ export class ItemManager
return TagsToFoldersMigrationApplicator.isApplicableToCurrentData(this)
}
public addNoteCountChangeObserver(observer: Models.TagNoteCountChangeObserver): () => void {
return this.tagNotesIndex.addCountChangeObserver(observer)
public addNoteCountChangeObserver(observer: Models.TagItemCountChangeObserver): () => void {
return this.tagItemsIndex.addCountChangeObserver(observer)
}
public allCountableNotesCount(): number {
return this.tagNotesIndex.allCountableNotesCount()
return this.tagItemsIndex.allCountableItemsCount()
}
public countableNotesForTag(tag: Models.SNTag | Models.SmartView): number {
if (tag instanceof Models.SmartView) {
if (tag.uuid === Models.SystemViewId.AllNotes) {
return this.tagNotesIndex.allCountableNotesCount()
return this.tagItemsIndex.allCountableItemsCount()
}
throw Error('countableNotesForTag is not meant to be used for smart views.')
throw Error('countableItemsForTag is not meant to be used for smart views.')
}
return this.tagNotesIndex.countableNotesForTag(tag)
return this.tagItemsIndex.countableItemsForTag(tag)
}
public getNoteCount(): number {
@@ -406,7 +406,7 @@ export class ItemManager
}
this.collection.onChange(delta)
this.tagNotesIndex.onChange(delta)
this.tagItemsIndex.onChange(delta)
const affectedContentTypesArray = Array.from(affectedContentTypes.values())
for (const controller of this.allDisplayControllers) {
@@ -1140,20 +1140,106 @@ export class ItemManager
)
}
public async addTagToFile(file: Models.FileItem, tag: Models.SNTag, addHierarchy: boolean): Promise<Models.SNTag[]> {
let tagsToAdd = [tag]
if (addHierarchy) {
const parentChainTags = this.getTagParentChain(tag)
tagsToAdd = [...parentChainTags, tag]
}
return Promise.all(
tagsToAdd.map((tagToAdd) => {
return this.changeTag(tagToAdd, (mutator) => {
mutator.addFile(file)
}) as Promise<Models.SNTag>
}),
)
}
public async linkNoteToNote(note: Models.SNNote, otherNote: Models.SNNote): Promise<Models.SNNote> {
return this.changeItem<Models.NoteMutator, Models.SNNote>(note, (mutator) => {
mutator.addNote(otherNote)
})
}
public async linkFileToFile(file: Models.FileItem, otherFile: Models.FileItem): Promise<Models.FileItem> {
return this.changeItem<Models.FileMutator, Models.FileItem>(file, (mutator) => {
mutator.addFile(otherFile)
})
}
public async unlinkItem(
item: DecryptedItemInterface<ItemContent>,
itemToUnlink: DecryptedItemInterface<ItemContent>,
) {
return this.changeItem(item, (mutator) => {
mutator.removeItemAsRelationship(itemToUnlink)
})
}
/**
* Get tags for a note sorted in natural order
* @param note - The note whose tags will be returned
* @returns Array containing tags associated with a note
* @param item - The item whose tags will be returned
* @returns Array containing tags associated with an item
*/
public getSortedTagsForNote(note: Models.SNNote): Models.SNTag[] {
public getSortedTagsForItem(item: DecryptedItemInterface<ItemContent>): Models.SNTag[] {
return naturalSort(
this.itemsReferencingItem(note).filter((ref) => {
this.itemsReferencingItem(item).filter((ref) => {
return ref?.content_type === ContentType.Tag
}) as Models.SNTag[],
'title',
)
}
public getSortedLinkedFilesForItem(item: DecryptedItemInterface<ItemContent>): Models.FileItem[] {
if (this.isTemplateItem(item)) {
return []
}
const filesReferencedByItem = this.referencesForItem(item).filter(
(ref) => ref.content_type === ContentType.File,
) as Models.FileItem[]
return naturalSort(filesReferencedByItem, 'title')
}
public getSortedFilesLinkingToItem(item: DecryptedItemInterface<ItemContent>): Models.FileItem[] {
if (this.isTemplateItem(item)) {
return []
}
const filesReferencingItem = this.itemsReferencingItem(item).filter(
(ref) => ref.content_type === ContentType.File,
) as Models.FileItem[]
return naturalSort(filesReferencingItem, 'title')
}
public getSortedLinkedNotesForItem(item: DecryptedItemInterface<ItemContent>): Models.SNNote[] {
if (this.isTemplateItem(item)) {
return []
}
const notesReferencedByItem = this.referencesForItem(item).filter(
(ref) => ref.content_type === ContentType.Note,
) as Models.SNNote[]
return naturalSort(notesReferencedByItem, 'title')
}
public getSortedNotesLinkingToItem(item: Models.DecryptedItemInterface<Models.ItemContent>): Models.SNNote[] {
if (this.isTemplateItem(item)) {
return []
}
const notesReferencingItem = this.itemsReferencingItem(item).filter(
(ref) => ref.content_type === ContentType.Note,
) as Models.SNNote[]
return naturalSort(notesReferencingItem, 'title')
}
public async createTag(title: string, parentItemToLookupUuidFor?: Models.SNTag): Promise<Models.SNTag> {
const newTag = await this.createItem<Models.SNTag>(
ContentType.Tag,
@@ -1312,12 +1398,6 @@ export class ItemManager
}
}
public getFilesForNote(note: Models.SNNote): Models.FileItem[] {
return (
this.itemsReferencingItem(note).filter((ref) => ref.content_type === ContentType.File) as Models.FileItem[]
).sort((a, b) => (a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1))
}
public renameFile(file: Models.FileItem, name: string): Promise<Models.FileItem> {
return this.changeItem<Models.FileMutator, Models.FileItem>(file, (mutator) => {
mutator.name = name
@@ -1353,6 +1433,23 @@ export class ItemManager
return this.findAnyItems(uuids) as (Models.DecryptedItemInterface | Models.DeletedItemInterface)[]
}
/**
* @returns `'direct'` if `itemOne` has the reference to `itemTwo`, `'indirect'` if `itemTwo` has the reference to `itemOne`, `'unlinked'` if neither reference each other
*/
public relationshipTypeForItems(
itemOne: Models.DecryptedItemInterface<Models.ItemContent>,
itemTwo: Models.DecryptedItemInterface<Models.ItemContent>,
): 'direct' | 'indirect' | 'unlinked' {
const itemOneReferencesItemTwo = !!this.referencesForItem(itemOne).find(
(reference) => reference.uuid === itemTwo.uuid,
)
const itemTwoReferencesItemOne = !!this.referencesForItem(itemTwo).find(
(reference) => reference.uuid === itemOne.uuid,
)
return itemOneReferencesItemTwo ? 'direct' : itemTwoReferencesItemOne ? 'indirect' : 'unlinked'
}
override getDiagnostics(): Promise<DiagnosticInfo | undefined> {
return Promise.resolve({
items: {
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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>
+12
View File
@@ -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')
})
})
-25
View File
@@ -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)
})
})
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/snjs",
"version": "2.136.2",
"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:*",
+36
View File
@@ -3,6 +3,42 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.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.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
### Bug Fixes
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/ui-services",
"version": "1.6.4",
"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
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [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
+2 -2
View File
@@ -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"
+50
View File
@@ -3,6 +3,56 @@
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
## [3.71.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-10)
### Bug Fixes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/web",
"version": "3.71.4",
"version": "3.73.0",
"license": "AGPL-3.0-or-later",
"main": "dist/app.js",
"author": "Standard Notes.",
+16 -19
View File
@@ -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
@@ -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

Some files were not shown because too many files have changed in this diff Show More