Compare commits

...
Author SHA1 Message Date
StandardNotes CI 173bacdfa3 chore(release): publish
- @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].6
2022-10-21 13:01:13 +00:00
Karol Sójko c503d781ae fix(web): event observer interface import 2022-10-21 14:31:07 +02:00
Karol Sójko 9fb85cd77a fix: refactor application event observing on web 2022-10-21 14:28:11 +02:00
StandardNotes CI bd40a49484 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].5
2022-10-21 12:07:49 +00:00
Aman Harwara 4a1612c7dc fix: item selection/unselection on deleting 2022-10-21 17:09:24 +05:30
Aman Harwara d3ed4391b7 fix: template note getting unselected after typing 2022-10-21 16:58:28 +05:30
StandardNotes CI 856e499d89 chore(release): publish
- @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].4
2022-10-21 08:55:09 +00:00
Karol Sójko 4f0805d5b0 feat(api): add accepting invites by the client 2022-10-21 10:24:49 +02:00
Karol Sójko 401a35f4db fix(ui-services): add route type getter 2022-10-21 10:15:13 +02:00
Karol Sójko 83b484002c feat: add accepting subscription invite route type 2022-10-21 10:05:52 +02:00
StandardNotes CI 30310dbb45 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
2022-10-21 07:23:34 +00:00
Aman Harwara c6e3580678 fix: tag collapse/expand not persisting 2022-10-21 12:24:46 +05:30
Aman Harwara ea285a36d8 Merge branch 'main' of github.com:standardnotes/app 2022-10-21 12:18:22 +05:30
Aman Harwara 1addaf4c39 fix: default auto-dark theme 2022-10-21 12:17:58 +05:30
StandardNotes CI 92bba942f1 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
2022-10-21 06:45:01 +00:00
Aman Harwara f71e6fe703 fix: improve plaintext editor performance on mobile (#1855) 2022-10-21 11:49:23 +05:30
StandardNotes CI 143b528b73 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
2022-10-20 18:35:14 +00:00
Mo 5d202a823e Revert "feat: ability to favorite tags (#1852)"
This reverts commit 4f3b258363.
2022-10-20 13:00:22 -05:00
StandardNotes CI 8d7d577b1c chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
2022-10-20 15:23:16 +00:00
Aman Harwara 76f23e13ec fix: mobx warnings and react render phase set state error 2022-10-20 20:24:56 +05:30
Mo 4f3b258363 feat: ability to favorite tags (#1852) 2022-10-20 09:28:35 -05:00
StandardNotes CI 9cc3304741 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
2022-10-20 13:54:34 +00:00
Aman Harwara 4432f1cb4c feat: persist selected tag & note locally (#1851) 2022-10-20 18:56:59 +05:30
StandardNotes CI 6c47f95748 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
2022-10-20 11:34:22 +00:00
Aman Harwara 57fab4f670 fix: "Forgot passcode" and "Switch workspace" lock screen options not working on mobile (#1850) 2022-10-20 16:26:21 +05:30
StandardNotes CI fb7b9350b2 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
2022-10-20 09:09:38 +00:00
Aman Harwara fb72c2fdae fix: help & feedback links on mobile (#1849) 2022-10-20 14:10:23 +05:30
Aman Harwara 050fa9cabc feat: make sans-serif font default 2022-10-20 13:36:00 +05:30
Aman Harwara a8535cfa23 fix: make tags non-tabbable 2022-10-20 13:34:28 +05:30
Aman Harwara 27bb4ebdae fix: remove tinted border from change note type button 2022-10-20 13:29:59 +05:30
Aman Harwara 60a8e8b92b fix: links for template notes 2022-10-20 13:06:38 +05:30
Aman Harwara 1f07d9fa17 Merge branch 'main' of github.com:standardnotes/app 2022-10-20 12:45:03 +05:30
Aman Harwara d333d4f188 fix: file not linking to note when uploaded by dragging on note 2022-10-20 12:44:50 +05:30
StandardNotes CI 7e3d7a8825 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
2022-10-19 21:18:20 +00:00
Aman Harwara e99c7b7c51 feat: improved file drag-n-drop experience (#1848) 2022-10-20 02:18:52 +05:30
StandardNotes CI 0282a7958a chore(release): publish
- @standardnotes/[email protected].5
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
2022-10-19 20:15:08 +00:00
Mo 4030953b00 fix: issue with not being able to unlink a file from a note (#1836) 2022-10-19 14:36:30 -05:00
Mo ba9a9f8d01 ci: conditional (skip e2e) 2022-10-19 06:56:52 -05:00
Mo 80e76ee52c ci: conditional (skip e2e) 2022-10-19 06:56:00 -05:00
StandardNotes CI bbebfb42fd chore(release): publish
- @standardnotes/[email protected].4
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected].9
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
2022-10-18 16:21:13 +00:00
Mo 9954bdc29f feat: authorize notes for listed (#1823) 2022-10-18 10:49:30 -05:00
Aman Harwara e4de9c1be7 fix: hide dynamic panels & focused mode options on mobile 2022-10-18 21:02:41 +05:30
Mo 07ffc01ede refactor: add redundant protection checks (#1822) 2022-10-18 10:15:14 -05:00
Aman Harwara eec7ae1589 fix: use mobx action for setting state 2022-10-18 20:30:39 +05:30
Aman Harwara 361f773f2c fix: editor width on mobile 2022-10-18 20:20:01 +05:30
Aman Harwara 5ffd37d447 fix: context menu opening when swiping up from bottom on iOS 2022-10-18 20:04:36 +05:30
Mo 2b66ff82ee refactor: note editor relationships (#1821) 2022-10-18 08:59:24 -05:00
Mo c83dc48d3f ci: pr linking (skip e2e) 2022-10-18 06:39:33 -05:00
StandardNotes CI 8dc7eaf3a4 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].8
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
2022-10-18 09:00:14 +00:00
Aman Harwara e40cd157b6 fix: remove switch to classic mx option 2022-10-18 13:54:45 +05:30
Mo 66805fa552 ci: no PRs if depandabot (skip e2e) 2022-10-17 17:00:47 -05:00
163 changed files with 2649 additions and 2079 deletions
+3
View File
@@ -1,4 +1,7 @@
{
"extends": ["./node_modules/@standardnotes/config/src/.eslintrc"],
"rules": {
"max-classes-per-file": ["error", 1]
},
"ignorePatterns": [".eslintrc.js", "*.webpack.*.js", "webpack-defaults.js", "jest.config.js", "__mocks__", "**/**/coverage"]
}
+12
View File
@@ -36,11 +36,23 @@ jobs:
run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
id: extract_branch
- name: Find open PR
uses: juliangruber/find-pull-request-action@v1
id: find-pull-request
with:
branch: ${{ steps.extract_branch.outputs.branch }}
- run: echo "Pull Request ${number} (${sha})"
env:
number: ${{ steps.find-pull-request.outputs.number }}
sha: ${{ steps.find-pull-request.outputs.head-sha }}
- uses: peter-evans/create-pull-request@v4
if: startsWith(github.event.head_commit.message, 'chore(deps)') == false && startsWith(github.event.head_commit.message, 'chore(deps-dev)') == false
with:
token: ${{ secrets.CI_PAT_TOKEN }}
branch: ${{ github.ref }}
base: main
title: ${{ steps.extract_branch.outputs.branch }}
body: "Public PR: https://github.com/standardnotes/app/pull/${{ steps.find-pull-request.outputs.number }}"
assignees: ${{ github.actor }}
reviewers: ${{ github.actor }}
+1 -1
View File
@@ -66,7 +66,7 @@
"css-loader": "^6.7.1",
"eslint": "^8.17.0",
"eslint-plugin-prettier": "^4.2.1",
"husky": "^8.0.0",
"husky": "^8.0.1",
"lint-staged": "^13.0.1",
"npm-check-updates": "^14.1.1",
"prettier": "^2.6.2",
+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.17.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
### Bug Fixes
* refactor application event observing on web ([9fb85cd](https://github.com/standardnotes/app/commit/9fb85cd77a627364738a9c21706431d2a01e9c44))
# [1.17.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
### Features
* **api:** add accepting invites by the client ([4f0805d](https://github.com/standardnotes/app/commit/4f0805d5b078e5a3524f5d6699b45dfd7e3456f3))
## [1.16.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-19)
**Note:** Version bump only for package @standardnotes/api
## [1.16.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-18)
**Note:** Version bump only for package @standardnotes/api
## [1.16.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-17)
**Note:** Version bump only for package @standardnotes/api
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api",
"version": "1.16.3",
"version": "1.17.1",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -2,4 +2,5 @@ export enum SubscriptionApiOperations {
Inviting,
CancelingInvite,
ListingInvites,
AcceptingInvite,
}
@@ -1,5 +1,6 @@
import { Invitation } from '@standardnotes/models'
import { SubscriptionInviteAcceptResponse } from '../../Response/Subscription/SubscriptionInviteAcceptResponse'
import { SubscriptionInviteCancelResponse } from '../../Response/Subscription/SubscriptionInviteCancelResponse'
import { SubscriptionInviteListResponse } from '../../Response/Subscription/SubscriptionInviteListResponse'
import { SubscriptionInviteResponse } from '../../Response/Subscription/SubscriptionInviteResponse'
@@ -24,6 +25,9 @@ describe('SubscriptionApiService', () => {
subscriptionServer.listInvites = jest.fn().mockReturnValue({
data: { invitations: [{} as jest.Mocked<Invitation>] },
} as jest.Mocked<SubscriptionInviteListResponse>)
subscriptionServer.acceptInvite = jest.fn().mockReturnValue({
data: { success: true },
} as jest.Mocked<SubscriptionInviteAcceptResponse>)
})
it('should invite a user', async () => {
@@ -160,4 +164,48 @@ describe('SubscriptionApiService', () => {
expect(error).not.toBeNull()
})
it('should accept an invite', async () => {
const response = await createService().acceptInvite('1-2-3')
expect(response).toEqual({
data: {
success: true,
},
})
expect(subscriptionServer.acceptInvite).toHaveBeenCalledWith({
inviteUuid: '1-2-3',
})
})
it('should not accept an invite if it is already accepting', async () => {
const service = createService()
Object.defineProperty(service, 'operationsInProgress', {
get: () => new Map([[SubscriptionApiOperations.AcceptingInvite, true]]),
})
let error = null
try {
await service.acceptInvite('1-2-3')
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should not accept an invite if the server fails', async () => {
subscriptionServer.acceptInvite = jest.fn().mockImplementation(() => {
throw new Error('Oops')
})
let error = null
try {
await createService().acceptInvite('1-2-3')
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
})
@@ -6,9 +6,11 @@ import { SubscriptionServerInterface } from '../../Server/Subscription/Subscript
import { SubscriptionInviteResponse } from '../../Response/Subscription/SubscriptionInviteResponse'
import { SubscriptionInviteListResponse } from '../../Response/Subscription/SubscriptionInviteListResponse'
import { SubscriptionInviteCancelResponse } from '../../Response/Subscription/SubscriptionInviteCancelResponse'
import { SubscriptionInviteAcceptResponse } from '../../Response/Subscription/SubscriptionInviteAcceptResponse'
import { SubscriptionApiServiceInterface } from './SubscriptionApiServiceInterface'
import { SubscriptionApiOperations } from './SubscriptionApiOperations'
import { Uuid } from '@standardnotes/common'
export class SubscriptionApiService implements SubscriptionApiServiceInterface {
private operationsInProgress: Map<SubscriptionApiOperations, boolean>
@@ -78,4 +80,24 @@ export class SubscriptionApiService implements SubscriptionApiServiceInterface {
throw new ApiCallError(ErrorMessage.GenericFail)
}
}
async acceptInvite(inviteUuid: Uuid): Promise<SubscriptionInviteAcceptResponse> {
if (this.operationsInProgress.get(SubscriptionApiOperations.AcceptingInvite)) {
throw new ApiCallError(ErrorMessage.GenericInProgress)
}
this.operationsInProgress.set(SubscriptionApiOperations.AcceptingInvite, true)
try {
const response = await this.subscriptionServer.acceptInvite({
inviteUuid,
})
this.operationsInProgress.set(SubscriptionApiOperations.AcceptingInvite, false)
return response
} catch (error) {
throw new ApiCallError(ErrorMessage.GenericFail)
}
}
}
@@ -1,5 +1,6 @@
import { Uuid } from '@standardnotes/common'
import { SubscriptionInviteAcceptResponse } from '../../Response/Subscription/SubscriptionInviteAcceptResponse'
import { SubscriptionInviteCancelResponse } from '../../Response/Subscription/SubscriptionInviteCancelResponse'
import { SubscriptionInviteListResponse } from '../../Response/Subscription/SubscriptionInviteListResponse'
import { SubscriptionInviteResponse } from '../../Response/Subscription/SubscriptionInviteResponse'
@@ -8,4 +9,5 @@ export interface SubscriptionApiServiceInterface {
invite(inviteeEmail: string): Promise<SubscriptionInviteResponse>
listInvites(): Promise<SubscriptionInviteListResponse>
cancelInvite(inviteUuid: Uuid): Promise<SubscriptionInviteCancelResponse>
acceptInvite(inviteUuid: Uuid): Promise<SubscriptionInviteAcceptResponse>
}
@@ -1,10 +1,6 @@
import { Uuid } from '@standardnotes/common'
import { ApiEndpointParam } from '../ApiEndpointParam'
import { ApiVersion } from '../../Api/ApiVersion'
export type SubscriptionInviteAcceptRequestParams = {
[ApiEndpointParam.ApiVersion]: ApiVersion.v0
inviteUuid: Uuid
[additionalParam: string]: unknown
}
@@ -1,3 +1 @@
export type SubscriptionInviteAcceptResponseBody = {
success: boolean
}
export type SubscriptionInviteAcceptResponseBody = { success: true } | { success: false; message: string }
+7
View File
@@ -3,6 +3,13 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.7.21](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-18)
### Reverts
* Revert "chore: remove deprecated editors folder" ([fc00b5c](https://github.com/standardnotes/app/commit/fc00b5c4996045750589c3e08c139d5d39bf4140))
* Revert "chore: fix code editor build" ([bb8ac49](https://github.com/standardnotes/app/commit/bb8ac49052136b3f688f19df5c71c2c32a85ed51))
## [2.7.20](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-13)
**Note:** Version bump only for package @standardnotes/components-meta
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/components-meta",
"version": "2.7.20",
"version": "2.7.21",
"private": true,
"author": "Standard Notes.",
"main": "dist",
+56
View File
@@ -3,6 +3,62 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.23.234](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.233](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.232](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.231](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.230](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.229](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-20)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.228](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-20)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.227](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-20)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.226](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-20)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.225](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-20)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.224](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-19)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.223](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-19)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.222](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-18)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.221](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-18)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.220](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-17)
**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.220",
"version": "3.23.234",
"license": "AGPL-3.0-or-later",
"author": "Standard Notes.",
"private": true,
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.18.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-19)
**Note:** Version bump only for package @standardnotes/encryption
## [1.18.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-18)
**Note:** Version bump only for package @standardnotes/encryption
## [1.18.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-17)
**Note:** Version bump only for package @standardnotes/encryption
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/encryption",
"version": "1.18.2",
"version": "1.18.4",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+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.
## [1.53.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-18)
**Note:** Version bump only for package @standardnotes/features
## [1.53.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-13)
**Note:** Version bump only for package @standardnotes/features
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/features",
"version": "1.53.1",
"version": "1.53.2",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -5,4 +5,5 @@ export enum NoteType {
RichText = 'rich-text',
Spreadsheet = 'spreadsheet',
Task = 'task',
Plain = 'plain-text',
}
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.24.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-19)
**Note:** Version bump only for package @standardnotes/filepicker
## [1.24.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-18)
**Note:** Version bump only for package @standardnotes/filepicker
## [1.24.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-17)
**Note:** Version bump only for package @standardnotes/filepicker
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/filepicker",
"version": "1.24.4",
"version": "1.24.6",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.11.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-19)
**Note:** Version bump only for package @standardnotes/files
## [1.11.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-18)
**Note:** Version bump only for package @standardnotes/files
## [1.11.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-17)
**Note:** Version bump only for package @standardnotes/files
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files",
"version": "1.11.4",
"version": "1.11.6",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+58
View File
@@ -3,6 +3,64 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.43.21](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
**Note:** Version bump only for package @standardnotes/mobile
## [3.43.20](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
**Note:** Version bump only for package @standardnotes/mobile
## [3.43.19](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
**Note:** Version bump only for package @standardnotes/mobile
## [3.43.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
**Note:** Version bump only for package @standardnotes/mobile
## [3.43.17](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
**Note:** Version bump only for package @standardnotes/mobile
## [3.43.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-20)
**Note:** Version bump only for package @standardnotes/mobile
## [3.43.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-20)
**Note:** Version bump only for package @standardnotes/mobile
## [3.43.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-20)
**Note:** Version bump only for package @standardnotes/mobile
## [3.43.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-20)
**Note:** Version bump only for package @standardnotes/mobile
## [3.43.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-20)
### Bug Fixes
* help & feedback links on mobile ([#1849](https://github.com/standardnotes/app/issues/1849)) ([fb72c2f](https://github.com/standardnotes/app/commit/fb72c2fdae277cb4e348ce5ee550f0545f281dcd))
## [3.43.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-19)
**Note:** Version bump only for package @standardnotes/mobile
## [3.43.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-19)
**Note:** Version bump only for package @standardnotes/mobile
## [3.43.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-18)
**Note:** Version bump only for package @standardnotes/mobile
## [3.43.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-18)
**Note:** Version bump only for package @standardnotes/mobile
## [3.43.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-17)
**Note:** Version bump only for package @standardnotes/mobile
@@ -49,6 +49,10 @@
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https"/>
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="mailto"/>
</intent>
</queries>
</manifest>
@@ -74,6 +74,7 @@
<array>
<string>http</string>
<string>https</string>
<string>mailto</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
@@ -52,6 +52,7 @@
<array>
<string>http</string>
<string>https</string>
<string>mailto</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/mobile",
"version": "3.43.7",
"version": "3.43.21",
"author": "Standard Notes.",
"private": true,
"license": "AGPL-3.0-or-later",
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.29.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-19)
### Bug Fixes
* issue with not being able to unlink a file from a note ([#1836](https://github.com/standardnotes/app/issues/1836)) ([4030953](https://github.com/standardnotes/app/commit/4030953b00ad609dc2b0f5b66cfd971d449f6121))
# [1.29.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-18)
### Features
* authorize notes for listed ([#1823](https://github.com/standardnotes/app/issues/1823)) ([9954bdc](https://github.com/standardnotes/app/commit/9954bdc29f9de2a42c4539d55749991d86a72943))
# [1.28.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-17)
### Features
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/models",
"version": "1.28.0",
"version": "1.29.1",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -8,6 +8,6 @@ export enum AppDataField {
NotAvailableOnMobile = 'notAvailableOnMobile',
MobileActive = 'mobileActive',
LastSize = 'lastSize',
PrefersPlainEditor = 'prefersPlainEditor',
LegacyPrefersPlainEditor = 'prefersPlainEditor',
ComponentInstallError = 'installError',
}
@@ -5,6 +5,7 @@ import { FillItemContent } from '../../Abstract/Content/ItemContent'
import { SNComponent } from './Component'
import { ComponentContent } from './ComponentContent'
import { PayloadTimestampDefaults } from '../../Abstract/Payload'
import { NoteType } from '@standardnotes/features'
describe('component model', () => {
it('valid hosted url should ignore url', () => {
@@ -46,4 +47,42 @@ describe('component model', () => {
expect(component.hasValidHostedUrl()).toBe(true)
expect(component.hosted_url).toBe('http://foo.com')
})
it('should return noteType as specified in package_info', () => {
const component = new SNComponent(
new DecryptedPayload(
{
uuid: String(Math.random()),
content_type: ContentType.Component,
content: FillItemContent({
package_info: {
note_type: NoteType.Authentication,
},
} as ComponentContent),
...PayloadTimestampDefaults(),
},
PayloadSource.Constructor,
),
)
expect(component.noteType).toEqual(NoteType.Authentication)
})
it('should return plain as noteType if no note type defined in package_info', () => {
const component = new SNComponent(
new DecryptedPayload(
{
uuid: String(Math.random()),
content_type: ContentType.Component,
content: FillItemContent({
package_info: {},
} as ComponentContent),
...PayloadTimestampDefaults(),
},
PayloadSource.Constructor,
),
)
expect(component.noteType).toEqual(NoteType.Plain)
})
})
@@ -7,6 +7,7 @@ import {
ComponentFlag,
ComponentPermission,
FindNativeFeature,
NoteType,
} from '@standardnotes/features'
import { AppDataField } from '../../Abstract/Item/Types/AppDataField'
import { ComponentContent, ComponentInterface } from './ComponentContent'
@@ -177,6 +178,10 @@ export class SNComponent extends DecryptedItem<ComponentContent> implements Comp
return this.package_info as ThirdPartyFeatureDescription
}
public get noteType(): NoteType {
return this.package_info.note_type || NoteType.Plain
}
public get isDeprecated(): boolean {
let flags: string[] = this.package_info.flags ?? []
flags = flags.map((flag: string) => flag.toLowerCase())
@@ -4,7 +4,8 @@ import { DecryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/Dec
import { FileMetadata } from './FileMetadata'
import { FileProtocolV1 } from './FileProtocolV1'
import { SortableItem } from '../../Runtime/Collection/CollectionSort'
import { ConflictStrategy } from '../../Abstract/Item'
import { ConflictStrategy, ItemInterface } from '../../Abstract/Item'
import { ContentType } from '@standardnotes/common'
type EncryptedBytesLength = number
type DecryptedBytesLength = number
@@ -31,6 +32,8 @@ export type FileContentSpecialized = FileContentWithoutSize & FileMetadata & Siz
export type FileContent = FileContentSpecialized & ItemContent
export const isFile = (x: ItemInterface): x is FileItem => x.content_type === ContentType.File
export class FileItem
extends DecryptedItem<FileContent>
implements FileContentWithoutSize, Sizes, FileProtocolV1, FileMetadata, SortableItem
@@ -29,14 +29,9 @@ describe('SNNote Tests', () => {
expect(note.preview_html).toBeFalsy()
})
it('should set mobilePrefersPlainEditor when given a valid choice', () => {
const selected = createNote({
mobilePrefersPlainEditor: true,
})
it('should not set default value for note type if none is provided', () => {
const note = createNote({})
const unselected = createNote()
expect(selected.mobilePrefersPlainEditor).toBeTruthy()
expect(unselected.mobilePrefersPlainEditor).toBe(undefined)
expect(note.noteType).toBe(undefined)
})
})
@@ -1,7 +1,8 @@
import { AppDataField } from './../../Abstract/Item/Types/AppDataField'
import { ContentType } from '@standardnotes/common'
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
import { DecryptedItem } from '../../Abstract/Item/Implementations/DecryptedItem'
import { ItemInterface } from '../../Abstract/Item/Interfaces/ItemInterface'
import { AppDataField } from '../../Abstract/Item/Types/AppDataField'
import { DecryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/DecryptedPayload'
import { NoteContent, NoteContentSpecialized } from './NoteContent'
@@ -10,12 +11,15 @@ export const isNote = (x: ItemInterface): x is SNNote => x.content_type === Cont
export class SNNote extends DecryptedItem<NoteContent> implements NoteContentSpecialized {
public readonly title: string
public readonly text: string
public readonly mobilePrefersPlainEditor?: boolean
public readonly hidePreview: boolean = false
public readonly preview_plain: string
public readonly preview_html: string
public readonly prefersPlainEditor: boolean
public readonly spellcheck?: boolean
public readonly noteType?: NoteType
public readonly authorizedForListed: boolean
/** The package_info.identifier of the editor (not its uuid), such as org.standardnotes.advanced-markdown */
public readonly editorIdentifier?: FeatureIdentifier | string
constructor(payload: DecryptedPayloadInterface<NoteContent>) {
super(payload)
@@ -26,9 +30,15 @@ export class SNNote extends DecryptedItem<NoteContent> implements NoteContentSpe
this.preview_html = String(this.payload.content.preview_html || '')
this.hidePreview = Boolean(this.payload.content.hidePreview)
this.spellcheck = this.payload.content.spellcheck
this.noteType = this.payload.content.noteType
this.editorIdentifier = this.payload.content.editorIdentifier
this.authorizedForListed = this.payload.content.authorizedForListed || false
this.prefersPlainEditor = this.getAppDomainValueWithDefault(AppDataField.PrefersPlainEditor, false)
this.mobilePrefersPlainEditor = this.payload.content.mobilePrefersPlainEditor
if (!this.noteType) {
const prefersPlain = this.getAppDomainValueWithDefault(AppDataField.LegacyPrefersPlainEditor, false)
if (prefersPlain) {
this.noteType = NoteType.Plain
}
}
}
}
@@ -1,13 +1,16 @@
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
import { ItemContent } from '../../Abstract/Content/ItemContent'
export interface NoteContentSpecialized {
title: string
text: string
mobilePrefersPlainEditor?: boolean
hidePreview?: boolean
preview_plain?: string
preview_html?: string
spellcheck?: boolean
noteType?: NoteType
editorIdentifier?: FeatureIdentifier | string
authorizedForListed?: boolean
}
export type NoteContent = NoteContentSpecialized & ItemContent
@@ -0,0 +1,24 @@
import { NoteMutator } from './NoteMutator'
import { createNote } from './../../Utilities/Test/SpecUtils'
import { MutationType } from '../../Abstract/Item'
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
describe('note mutator', () => {
it('sets noteType', () => {
const note = createNote({})
const mutator = new NoteMutator(note, MutationType.NoUpdateUserTimestamps)
mutator.noteType = NoteType.Authentication
const result = mutator.getResult()
expect(result.content.noteType).toEqual(NoteType.Authentication)
})
it('sets editorIdentifier', () => {
const note = createNote({})
const mutator = new NoteMutator(note, MutationType.NoUpdateUserTimestamps)
mutator.editorIdentifier = FeatureIdentifier.MarkdownProEditor
const result = mutator.getResult()
expect(result.content.editorIdentifier).toEqual(FeatureIdentifier.MarkdownProEditor)
})
})
@@ -1,10 +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'
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
export class NoteMutator extends DecryptedItemMutator<NoteContent> {
set title(title: string) {
@@ -27,14 +27,22 @@ export class NoteMutator extends DecryptedItemMutator<NoteContent> {
this.mutableContent.preview_html = preview_html
}
set prefersPlainEditor(prefersPlainEditor: boolean) {
this.setAppDataItem(AppDataField.PrefersPlainEditor, prefersPlainEditor)
}
set spellcheck(spellcheck: boolean) {
this.mutableContent.spellcheck = spellcheck
}
set noteType(noteType: NoteType) {
this.mutableContent.noteType = noteType
}
set editorIdentifier(identifier: FeatureIdentifier | string | undefined) {
this.mutableContent.editorIdentifier = identifier
}
set authorizedForListed(authorizedForListed: boolean) {
this.mutableContent.authorizedForListed = authorizedForListed
}
toggleSpellcheck(): void {
if (this.mutableContent.spellcheck == undefined) {
this.mutableContent.spellcheck = false
@@ -1,55 +0,0 @@
import { DecryptedPayloadInterface } from './../../Abstract/Payload/Interfaces/DecryptedPayload'
import { ComponentContent } from '../../Syncable/Component/ComponentContent'
import { ComponentArea } from '@standardnotes/features'
import { ContentType } from '@standardnotes/common'
import { ComponentMutator, SNComponent } from '../../Syncable/Component'
import { CreateDecryptedItemFromPayload } from '../Item/ItemGenerator'
import { ImmutablePayloadCollection } from '../../Runtime/Collection/Payload/ImmutablePayloadCollection'
import { MutationType } from '../../Abstract/Item/Types/MutationType'
import { FullyFormedPayloadInterface } from '../../Abstract/Payload/Interfaces/UnionTypes'
import { isDecryptedPayload } from '../../Abstract/Payload'
import { SyncResolvedPayload } from '../../Runtime/Deltas/Utilities/SyncResolvedPayload'
export type AffectorFunction = (
basePayload: FullyFormedPayloadInterface,
duplicatePayload: FullyFormedPayloadInterface,
baseCollection: ImmutablePayloadCollection<FullyFormedPayloadInterface>,
) => SyncResolvedPayload[]
const NoteDuplicationAffectedPayloads: AffectorFunction = (
basePayload: FullyFormedPayloadInterface,
duplicatePayload: FullyFormedPayloadInterface,
baseCollection: ImmutablePayloadCollection<FullyFormedPayloadInterface>,
) => {
/** If note has editor, maintain editor relationship in duplicate note */
const components = baseCollection
.all(ContentType.Component)
.filter(isDecryptedPayload)
.map((payload) => {
return CreateDecryptedItemFromPayload<ComponentContent, SNComponent>(
payload as DecryptedPayloadInterface<ComponentContent>,
)
})
const editor = components
.filter((c) => c.area === ComponentArea.Editor)
.find((e) => {
return e.isExplicitlyEnabledForItem(basePayload.uuid)
})
if (!editor) {
return []
}
/** Modify the editor to include new note */
const mutator = new ComponentMutator(editor, MutationType.NoUpdateUserTimestamps)
mutator.associateWithItem(duplicatePayload.uuid)
const result = mutator.getResult() as SyncResolvedPayload
return [result]
}
export const AffectorMapping = {
[ContentType.Note]: NoteDuplicationAffectedPayloads,
} as Partial<Record<ContentType, AffectorFunction>>
@@ -2,7 +2,6 @@ import { PayloadSource } from './../../Abstract/Payload/Types/PayloadSource'
import { extendArray, UuidGenerator } from '@standardnotes/utils'
import { ImmutablePayloadCollection } from '../../Runtime/Collection/Payload/ImmutablePayloadCollection'
import { ItemContent } from '../../Abstract/Content/ItemContent'
import { AffectorMapping } from './AffectorFunction'
import { PayloadsByUpdatingReferencingPayloadReferences } from './PayloadsByUpdatingReferencingPayloadReferences'
import { isDecryptedPayload } from '../../Abstract/Payload/Interfaces/TypeCheck'
import { FullyFormedPayloadInterface } from '../../Abstract/Payload/Interfaces/UnionTypes'
@@ -69,13 +68,5 @@ export function PayloadsByDuplicating<C extends ItemContent = ItemContent>(dto:
extendArray(results, updatedReferencing)
}
const affector = AffectorMapping[payload.content_type]
if (affector) {
const affected = affector(payload, copy, baseCollection)
if (affected) {
extendArray(results, affected)
}
}
return results
}
-1
View File
@@ -74,7 +74,6 @@ export * from './Utilities/Item/FindItem'
export * from './Utilities/Item/ItemContentsDiffer'
export * from './Utilities/Item/ItemContentsEqual'
export * from './Utilities/Item/ItemGenerator'
export * from './Utilities/Payload/AffectorFunction'
export * from './Utilities/Payload/CopyPayloadWithContentOverride'
export * from './Utilities/Payload/CreatePayload'
export * from './Utilities/Payload/FindPayload'
+56
View File
@@ -3,6 +3,62 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.3.163](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.162](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.161](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.160](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.159](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.158](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-20)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.157](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-20)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.156](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-20)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.155](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-20)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.154](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-20)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.153](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-19)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.152](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-19)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.151](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-18)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.150](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-18)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.149](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-17)
**Note:** Version bump only for package @standardnotes/releases
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/releases",
"version": "1.3.149",
"version": "1.3.163",
"license": "AGPL-3.0-or-later",
"main": "dist/releases.json",
"types": "dist/index.d.ts",
+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.
## [1.11.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-18)
**Note:** Version bump only for package @standardnotes/responses
## [1.11.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-13)
**Note:** Version bump only for package @standardnotes/responses
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/responses",
"version": "1.11.1",
"version": "1.11.2",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+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.34.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
**Note:** Version bump only for package @standardnotes/services
## [1.34.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
**Note:** Version bump only for package @standardnotes/services
## [1.34.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-19)
### Bug Fixes
* issue with not being able to unlink a file from a note ([#1836](https://github.com/standardnotes/app/issues/1836)) ([4030953](https://github.com/standardnotes/app/commit/4030953b00ad609dc2b0f5b66cfd971d449f6121))
# [1.34.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-18)
### Features
* authorize notes for listed ([#1823](https://github.com/standardnotes/app/issues/1823)) ([9954bdc](https://github.com/standardnotes/app/commit/9954bdc29f9de2a42c4539d55749991d86a72943))
## [1.33.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-17)
**Note:** Version bump only for package @standardnotes/services
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/services",
"version": "1.33.1",
"version": "1.34.3",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -24,4 +24,5 @@ export enum ChallengeReason {
UnprotectFile,
UnprotectNote,
DeleteAccount,
AuthorizeNoteForListed,
}
@@ -20,5 +20,5 @@ export interface ComponentManagerInterface {
urlOverride?: string,
): ComponentViewerInterface
presentPermissionsDialog(_dialog: PermissionDialog): void
getDefaultEditor(): SNComponent
getDefaultEditor(): SNComponent | undefined
}
@@ -0,0 +1,7 @@
/* istanbul ignore file */
export enum ItemRelationshipDirection {
AReferencesB,
BReferencesA,
NoRelationship,
}
@@ -1,3 +1,5 @@
/* istanbul ignore file */
import { ContentType, Uuid } from '@standardnotes/common'
import {
SNNote,
@@ -76,9 +78,9 @@ export interface ItemsClientInterface {
linkNoteToNote(note: SNNote, otherNote: SNNote): Promise<SNNote>
linkFileToFile(file: FileItem, otherFile: FileItem): Promise<FileItem>
unlinkItem(
item: DecryptedItemInterface<ItemContent>,
itemToUnlink: DecryptedItemInterface<ItemContent>,
unlinkItems(
itemOne: DecryptedItemInterface<ItemContent>,
itemTwo: DecryptedItemInterface<ItemContent>,
): Promise<DecryptedItemInterface<ItemContent>>
/**
@@ -115,12 +117,6 @@ export interface ItemsClientInterface {
*/
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
getSmartViews(): SmartView[]
@@ -152,12 +148,4 @@ 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
View File
@@ -54,6 +54,7 @@ export * from './Item/ItemCounterInterface'
export * from './Item/ItemManagerInterface'
export * from './Item/ItemsClientInterface'
export * from './Item/ItemsServerInterface'
export * from './Item/ItemRelationshipDirection'
export * from './Mutator/MutatorClientInterface'
export * from './Payloads/PayloadManagerInterface'
export * from './Preferences/PreferenceServiceInterface'
+22
View File
@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.142.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
### Bug Fixes
* refactor application event observing on web ([9fb85cd](https://github.com/standardnotes/app/commit/9fb85cd77a627364738a9c21706431d2a01e9c44))
## [2.142.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
**Note:** Version bump only for package @standardnotes/snjs
## [2.142.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-19)
### Bug Fixes
* issue with not being able to unlink a file from a note ([#1836](https://github.com/standardnotes/app/issues/1836)) ([4030953](https://github.com/standardnotes/app/commit/4030953b00ad609dc2b0f5b66cfd971d449f6121))
# [2.142.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-18)
### Features
* authorize notes for listed ([#1823](https://github.com/standardnotes/app/issues/1823)) ([9954bdc](https://github.com/standardnotes/app/commit/9954bdc29f9de2a42c4539d55749991d86a72943))
## [2.141.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-17)
**Note:** Version bump only for package @standardnotes/snjs
+10 -25
View File
@@ -68,7 +68,7 @@ import { ClientDisplayableError } from '@standardnotes/responses'
import { SnjsVersion } from './../Version'
import { SNLog } from '../Log'
import { Challenge, ChallengeResponse } from '../Services'
import { Challenge, ChallengeResponse, ListedClientInterface } from '../Services'
import { ApplicationConstructorOptions, FullyResolvedApplicationOptions } from './Options/ApplicationOptions'
import { ApplicationOptionsDefaults } from './Options/Defaults'
@@ -86,9 +86,7 @@ type ApplicationObserver = {
type ObserverRemover = () => void
export class SNApplication
implements ApplicationInterface, AppGroupManagedApplication, InternalServices.ListedClientInterface
{
export class SNApplication implements ApplicationInterface, AppGroupManagedApplication {
onDeinit!: ExternalServices.DeinitCallback
/**
@@ -273,6 +271,10 @@ export class SNApplication
return this.componentManagerService
}
public get listed(): ListedClientInterface {
return this.listedService
}
public computePrivateUsername(username: string): Promise<string | undefined> {
return ComputePrivateUsername(this.options.crypto, username)
}
@@ -682,25 +684,6 @@ export class SNApplication
return this.protectionService.authorizeSearchingProtectedNotesText()
}
public canRegisterNewListedAccount(): boolean {
return this.listedService.canRegisterNewListedAccount()
}
public async requestNewListedAccount(): Promise<Responses.ListedAccount | undefined> {
return this.listedService.requestNewListedAccount()
}
public async getListedAccounts(): Promise<Responses.ListedAccount[]> {
return this.listedService.getListedAccounts()
}
public getListedAccountInfo(
account: Responses.ListedAccount,
inContextOfItem?: UuidString,
): Promise<Responses.ListedAccountInfo | undefined> {
return this.listedService.getListedAccountInfo(account, inContextOfItem)
}
public async createEncryptedBackupFileForAutomatedDesktopBackups(): Promise<BackupFile | undefined> {
return this.protocolService.createEncryptedBackupFile()
}
@@ -1096,11 +1079,11 @@ export class SNApplication
this.createComponentManager()
this.createMigrationService()
this.createMfaService()
this.createListedService()
this.createActionsManager()
this.createFileService()
this.createIntegrityService()
this.createMutatorService()
this.createListedService()
this.createActionsManager()
this.createStatusService()
if (isDesktopDevice(this.deviceInterface)) {
@@ -1175,6 +1158,8 @@ export class SNApplication
this.itemManager,
this.settingsService,
this.deprecatedHttpService,
this.protectionService,
this.mutator,
this.internalEventBus,
)
this.services.push(this.listedService)
@@ -0,0 +1,33 @@
import { SNApplication } from './../Application/Application'
import { ContentType } from '@standardnotes/common'
import { MutatorService } from './../Services/Mutator/MutatorService'
import { SNComponentManager } from './../Services/ComponentManager/ComponentManager'
import { NoteType } from '@standardnotes/features'
import { NoteViewController } from './NoteViewController'
describe('note view controller', () => {
let application: SNApplication
beforeEach(() => {
application = {} as jest.Mocked<SNApplication>
application.streamItems = jest.fn()
const componentManager = {} as jest.Mocked<SNComponentManager>
componentManager.getDefaultEditor = jest.fn()
Object.defineProperty(application, 'componentManager', { value: componentManager })
const mutator = {} as jest.Mocked<MutatorService>
mutator.createTemplateItem = jest.fn()
Object.defineProperty(application, 'mutator', { value: mutator })
})
it('should create notes with plaintext note type', async () => {
const controller = new NoteViewController(application)
await controller.initialize(false)
expect(application.mutator.createTemplateItem).toHaveBeenCalledWith(
ContentType.Note,
expect.objectContaining({ noteType: NoteType.Plain }),
)
})
})
@@ -1,3 +1,4 @@
import { NoteType } from '@standardnotes/features'
import {
NoteMutator,
SNNote,
@@ -66,9 +67,12 @@ export class NoteViewController implements ItemViewControllerInterface {
async initialize(addTagHierarchy: boolean): Promise<void> {
if (!this.item) {
const editor = this.application.componentManager.getDefaultEditor()
const note = this.application.mutator.createTemplateItem<NoteContent, SNNote>(ContentType.Note, {
text: '',
title: this.defaultTitle || '',
noteType: editor?.noteType || NoteType.Plain,
editorIdentifier: editor?.identifier,
references: [],
})
@@ -167,6 +167,7 @@ export const ChallengeStrings = {
SelectProtectedNote: 'Authentication is required to select a protected note',
DisableMfa: 'Authentication is required to disable two-factor authentication',
DeleteAccount: 'Authentication is required to delete your account',
ListedAuthorization: 'Authentication is required to approve this note for Listed',
}
export const ErrorAlertStrings = {
@@ -79,6 +79,8 @@ export class Challenge implements ChallengeInterface {
return ChallengeStrings.DisableMfa
case ChallengeReason.DeleteAccount:
return ChallengeStrings.DeleteAccount
case ChallengeReason.AuthorizeNoteForListed:
return ChallengeStrings.ListedAuthorization
case ChallengeReason.Custom:
return ''
default:
@@ -3,12 +3,14 @@
*/
import { SNPreferencesService } from '../Preferences/PreferencesService'
import { createNote } from './../../Spec/SpecUtils'
import {
ComponentAction,
ComponentPermission,
FeatureDescription,
FindNativeFeature,
FeatureIdentifier,
NoteType,
} from '@standardnotes/features'
import { ContentType } from '@standardnotes/common'
import { GenericItem, SNComponent, Environment, Platform } from '@standardnotes/models'
@@ -306,6 +308,26 @@ describe('featuresService', () => {
})
})
describe('editors', () => {
it('getEditorForNote should return undefined is note type is plain', () => {
const note = createNote({
noteType: NoteType.Plain,
})
const manager = createManager(Environment.Web, Platform.MacWeb)
expect(manager.editorForNote(note)).toBe(undefined)
})
it('getEditorForNote should call legacy function if no note editorIdentifier or noteType', () => {
const note = createNote({})
const manager = createManager(Environment.Web, Platform.MacWeb)
manager['legacyGetEditorForNote'] = jest.fn()
manager.editorForNote(note)
expect(manager['legacyGetEditorForNote']).toHaveBeenCalled()
})
})
describe('editor change alert', () => {
it('should not require alert switching from plain editor', () => {
const manager = createManager(Environment.Web, Platform.MacWeb)
@@ -17,7 +17,14 @@ import {
import { SNSyncService } from '@Lib/Services/Sync/SyncService'
import find from 'lodash/find'
import uniq from 'lodash/uniq'
import { ComponentArea, ComponentAction, ComponentPermission, FindNativeFeature } from '@standardnotes/features'
import {
ComponentArea,
ComponentAction,
ComponentPermission,
FindNativeFeature,
NoteType,
FeatureIdentifier,
} from '@standardnotes/features'
import { Copy, filterFromArray, removeFromArray, sleep, assert } from '@standardnotes/utils'
import { UuidString } from '@Lib/Types/UuidString'
import { AllowedBatchContentTypes } from '@Lib/Services/ComponentManager/Types'
@@ -112,6 +119,10 @@ export class SNComponentManager
})
}
componentWithIdentifier(identifier: FeatureIdentifier | string): SNComponent | undefined {
return this.components.find((component) => component.identifier === identifier)
}
override deinit(): void {
super.deinit()
@@ -569,13 +580,6 @@ export class SNComponentManager
}
allComponentIframes(): HTMLIFrameElement[] {
if (this.isMobile) {
/**
* Retrieving all iframes is typically related to lifecycle management of
* non-editor components. So this function is not useful to mobile.
*/
return []
}
return Array.from(document.getElementsByTagName('iframe'))
}
@@ -584,23 +588,29 @@ export class SNComponentManager
}
editorForNote(note: SNNote): SNComponent | undefined {
if (note.editorIdentifier) {
return this.componentWithIdentifier(note.editorIdentifier)
}
if (note.noteType === NoteType.Plain) {
return undefined
}
return this.legacyGetEditorForNote(note)
}
/**
* Uses legacy approach of note/editor association. New method uses note.editorIdentifier and note.noteType directly.
*/
private legacyGetEditorForNote(note: SNNote): SNComponent | undefined {
const editors = this.componentsForArea(ComponentArea.Editor)
for (const editor of editors) {
if (editor.isExplicitlyEnabledForItem(note.uuid)) {
return editor
}
}
let defaultEditor
/* No editor found for note. Use default editor, if note does not prefer system editor */
if (this.isMobile) {
if (!note.mobilePrefersPlainEditor) {
defaultEditor = this.getDefaultEditor()
}
} else {
if (!note.prefersPlainEditor) {
defaultEditor = this.getDefaultEditor()
}
}
const defaultEditor = this.getDefaultEditor()
if (defaultEditor && !defaultEditor.isExplicitlyDisabledForItem(note.uuid)) {
return defaultEditor
} else {
@@ -608,15 +618,9 @@ export class SNComponentManager
}
}
getDefaultEditor(): SNComponent {
getDefaultEditor(): SNComponent | undefined {
const editors = this.componentsForArea(ComponentArea.Editor)
if (this.isMobile) {
return editors.filter((e) => {
return e.isMobileDefault
})[0]
} else {
return editors.filter((e) => e.isDefaultEditor())[0]
}
return editors.filter((e) => e.isDefaultEditor())[0]
}
permissionsStringForPermissions(permissions: ComponentPermission[], component: SNComponent): string {
@@ -1,5 +1,5 @@
import { ContentType } from '@standardnotes/common'
import { InternalEventBusInterface } from '@standardnotes/services'
import { InternalEventBusInterface, ItemRelationshipDirection } from '@standardnotes/services'
import { ItemManager } from './ItemManager'
import { PayloadManager } from '../Payloads/PayloadManager'
import { UuidGenerator } from '@standardnotes/utils'
@@ -14,6 +14,7 @@ import {
SmartView,
SystemViewId,
} from '@standardnotes/models'
import { createNoteWithTitle } from '../../Spec/SpecUtils'
const setupRandomUuid = () => {
UuidGenerator.SetGenerator(() => String(Math.random()))
@@ -83,19 +84,6 @@ describe('itemManager', () => {
)
}
const createNote = (title: string) => {
return new Models.SNNote(
new Models.DecryptedPayload({
uuid: String(Math.random()),
content_type: ContentType.Note,
content: Models.FillItemContent<Models.NoteContent>({
title: title,
}),
...PayloadTimestampDefaults(),
}),
)
}
const createFile = (name: string) => {
return new Models.FileItem(
new Models.DecryptedPayload({
@@ -168,7 +156,7 @@ describe('itemManager', () => {
it('viewing notes with tag', async () => {
itemManager = createService()
const tag = createTag('parent')
const note = createNote('note')
const note = createNoteWithTitle('note')
await itemManager.insertItems([tag, note])
await itemManager.addTagToNote(note, tag, false)
@@ -185,9 +173,9 @@ describe('itemManager', () => {
it('viewing trashed notes smart view should include archived notes', async () => {
itemManager = createService()
const archivedNote = createNote('archived')
const trashedNote = createNote('trashed')
const archivedAndTrashedNote = createNote('archived&trashed')
const archivedNote = createNoteWithTitle('archived')
const trashedNote = createNoteWithTitle('trashed')
const archivedAndTrashedNote = createNoteWithTitle('archived&trashed')
await itemManager.insertItems([archivedNote, trashedNote, archivedAndTrashedNote])
@@ -389,8 +377,8 @@ describe('itemManager', () => {
await itemManager.insertItems([parentTag, childTag])
await itemManager.setTagParent(parentTag, childTag)
const parentNote = createNote('parentNote')
const childNote = createNote('childNote')
const parentNote = createNoteWithTitle('parentNote')
const childNote = createNoteWithTitle('childNote')
await itemManager.insertItems([parentNote, childNote])
await itemManager.addTagToNote(parentNote, parentTag, false)
@@ -477,7 +465,7 @@ describe('itemManager', () => {
const fooDelimiter = await itemManager.createTag('bar.foo')
const barFooDelimiter = await itemManager.createTag('baz.bar.foo')
const fooAttached = await itemManager.createTag('Foo')
const note = createNote('note')
const note = createNoteWithTitle('note')
await itemManager.insertItems([foo, foobar, bar, barfoo, fooDelimiter, barFooDelimiter, fooAttached, note])
await itemManager.addTagToNote(note, fooAttached, false)
@@ -501,8 +489,8 @@ describe('itemManager', () => {
await itemManager.insertItems([parentTag, childTag])
await itemManager.setTagParent(parentTag, childTag)
const parentNote = createNote('parentNote')
const childNote = createNote('childNote')
const parentNote = createNoteWithTitle('parentNote')
const childNote = createNoteWithTitle('childNote')
await itemManager.insertItems([parentNote, childNote])
await itemManager.addTagToNote(parentNote, parentTag, false)
@@ -519,8 +507,8 @@ describe('itemManager', () => {
const tag1 = createTag('tag 1')
await itemManager.insertItems([tag1])
const note1 = createNote('note 1')
const note2 = createNote('note 2')
const note1 = createNoteWithTitle('note 1')
const note2 = createNoteWithTitle('note 2')
await itemManager.insertItems([note1, note2])
await itemManager.addTagToNote(note1, tag1, false)
@@ -654,8 +642,8 @@ describe('itemManager', () => {
const view = itemManager.untaggedNotesSmartView
const tag = createTag('tag')
const untaggedNote = createNote('note')
const taggedNote = createNote('taggedNote')
const untaggedNote = createNoteWithTitle('note')
const taggedNote = createNoteWithTitle('taggedNote')
await itemManager.insertItems([tag, untaggedNote, taggedNote])
expect(itemManager.notesMatchingSmartView(view)).toHaveLength(2)
@@ -704,7 +692,7 @@ describe('itemManager', () => {
itemManager = createService()
const parentTag = createTag('parent')
const childTag = createTag('child')
const note = createNote('note')
const note = createNoteWithTitle('note')
await itemManager.insertItems([parentTag, childTag, note])
await itemManager.setTagParent(parentTag, childTag)
@@ -722,7 +710,7 @@ describe('itemManager', () => {
itemManager = createService()
const parentTag = createTag('parent')
const childTag = createTag('child')
const note = createNote('note')
const note = createNoteWithTitle('note')
await itemManager.insertItems([parentTag, childTag, note])
await itemManager.setTagParent(parentTag, childTag)
@@ -772,7 +760,7 @@ describe('itemManager', () => {
it('should link file with note', async () => {
itemManager = createService()
const note = createNote('invoices')
const note = createNoteWithTitle('invoices')
const file = createFile('invoice_1.pdf')
await itemManager.insertItems([note, file])
@@ -785,7 +773,7 @@ describe('itemManager', () => {
it('should unlink file from note', async () => {
itemManager = createService()
const note = createNote('invoices')
const note = createNoteWithTitle('invoices')
const file = createFile('invoice_1.pdf')
await itemManager.insertItems([note, file])
@@ -796,25 +784,10 @@ describe('itemManager', () => {
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')
const note = createNoteWithTitle('research')
const note2 = createNoteWithTitle('citation')
await itemManager.insertItems([note, note2])
const resultingNote = await itemManager.linkNoteToNote(note, note2)
@@ -839,108 +812,56 @@ describe('itemManager', () => {
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')
const firstNote = createNoteWithTitle('First note')
const secondNote = createNoteWithTitle('Second note')
const unlinkedNote = createNoteWithTitle('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(
const relationshipOfFirstNoteToSecond = itemManager.relationshipDirectionBetweenItems(
firstNoteLinkedToSecond,
secondNote,
)
const relationshipOfSecondNoteToFirst = itemManager.relationshipDirectionBetweenItems(
secondNote,
firstNoteLinkedToSecond,
)
const relationshipOfFirstNoteToUnlinked = itemManager.relationshipDirectionBetweenItems(
firstNoteLinkedToSecond,
unlinkedNote,
)
expect(relationshipOfFirstNoteToSecond).toBe('direct')
expect(relationshipOfSecondNoteToFirst).toBe('indirect')
expect(relationshipOfFirstNoteToUnlinked).toBe('unlinked')
expect(relationshipOfFirstNoteToSecond).toBe(ItemRelationshipDirection.AReferencesB)
expect(relationshipOfSecondNoteToFirst).toBe(ItemRelationshipDirection.BReferencesA)
expect(relationshipOfFirstNoteToUnlinked).toBe(ItemRelationshipDirection.NoRelationship)
})
it('should unlink itemToUnlink from item', async () => {
it('should unlink itemOne from itemTwo if relation is direct', async () => {
itemManager = createService()
const note = createNote('Note 1')
const note2 = createNote('Note 2')
const note = createNoteWithTitle('Note 1')
const note2 = createNoteWithTitle('Note 2')
await itemManager.insertItems([note, note2])
const linkedItem = await itemManager.linkNoteToNote(note, note2)
const unlinkedItem = await itemManager.unlinkItem(linkedItem, note2)
const unlinkedItem = await itemManager.unlinkItems(linkedItem, note2)
const references = unlinkedItem.references
expect(unlinkedItem.uuid).toBe(note.uuid)
expect(references).toHaveLength(0)
})
it('should get all linked files for item', async () => {
it('should unlink itemTwo from itemOne if relation is indirect', async () => {
itemManager = createService()
const file = createFile('A1')
const file2 = createFile('B2')
const file3 = createFile('C3')
const note = createNoteWithTitle('Note 1')
const note2 = createNoteWithTitle('Note 2')
await itemManager.insertItems([note, note2])
await itemManager.insertItems([file, file2, file3])
const linkedItem = await itemManager.linkNoteToNote(note, note2)
const changedItem = await itemManager.unlinkItems(linkedItem, note2)
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)
expect(changedItem.uuid).toBe(note.uuid)
expect(changedItem.references).toHaveLength(0)
})
})
})
+24 -69
View File
@@ -7,7 +7,7 @@ import { UuidString } from '../../Types/UuidString'
import * as Models from '@standardnotes/models'
import * as Services from '@standardnotes/services'
import { PayloadManagerChangeData } from '../Payloads'
import { DiagnosticInfo, ItemsClientInterface } from '@standardnotes/services'
import { DiagnosticInfo, ItemsClientInterface, ItemRelationshipDirection } from '@standardnotes/services'
import { ApplicationDisplayOptions } from '@Lib/Application/Options/OptionalOptions'
import { CollectionSort, DecryptedItemInterface, ItemContent } from '@standardnotes/models'
@@ -1169,12 +1169,18 @@ export class ItemManager
})
}
public async unlinkItem(
item: DecryptedItemInterface<ItemContent>,
itemToUnlink: DecryptedItemInterface<ItemContent>,
) {
return this.changeItem(item, (mutator) => {
mutator.removeItemAsRelationship(itemToUnlink)
public async unlinkItems(itemA: DecryptedItemInterface<ItemContent>, itemB: DecryptedItemInterface<ItemContent>) {
const relationshipDirection = this.relationshipDirectionBetweenItems(itemA, itemB)
if (relationshipDirection === ItemRelationshipDirection.NoRelationship) {
throw new Error('Trying to unlink already unlinked items')
}
const itemToChange = relationshipDirection === ItemRelationshipDirection.AReferencesB ? itemA : itemB
const itemToRemove = itemToChange === itemA ? itemB : itemA
return this.changeItem(itemToChange, (mutator) => {
mutator.removeItemAsRelationship(itemToRemove)
})
}
@@ -1192,54 +1198,6 @@ export class ItemManager
)
}
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,
@@ -1433,21 +1391,18 @@ 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.isTemplateItem(itemOne)
? false
: !!this.referencesForItem(itemOne).find((reference) => reference.uuid === itemTwo.uuid)
const itemTwoReferencesItemOne = this.isTemplateItem(itemTwo)
? false
: !!this.referencesForItem(itemTwo).find((reference) => reference.uuid === itemOne.uuid)
public relationshipDirectionBetweenItems(
itemA: Models.DecryptedItemInterface<Models.ItemContent>,
itemB: Models.DecryptedItemInterface<Models.ItemContent>,
): ItemRelationshipDirection {
const itemAReferencesItemB = !!itemA.references.find((reference) => reference.uuid === itemB.uuid)
const itemBReferencesItemA = !!itemB.references.find((reference) => reference.uuid === itemA.uuid)
return itemOneReferencesItemTwo ? 'direct' : itemTwoReferencesItemOne ? 'indirect' : 'unlinked'
return itemAReferencesItemB
? ItemRelationshipDirection.AReferencesB
: itemBReferencesItemA
? ItemRelationshipDirection.BReferencesA
: ItemRelationshipDirection.NoRelationship
}
override getDiagnostics(): Promise<DiagnosticInfo | undefined> {
@@ -1,3 +1,4 @@
import { SNNote } from '@standardnotes/models'
import { Uuid } from '@standardnotes/common'
import { ListedAccount, ListedAccountInfo } from '@standardnotes/responses'
@@ -6,4 +7,6 @@ export interface ListedClientInterface {
requestNewListedAccount: () => Promise<ListedAccount | undefined>
getListedAccounts(): Promise<ListedAccount[]>
getListedAccountInfo(account: ListedAccount, inContextOfItem?: Uuid): Promise<ListedAccountInfo | undefined>
isNoteAuthorizedForListed(note: SNNote): boolean
authorizeNoteForListed(note: SNNote): Promise<boolean>
}
@@ -8,8 +8,9 @@ import { SNSettingsService } from '../Settings/SNSettingsService'
import { ListedClientInterface } from './ListedClientInterface'
import { SNApiService } from '../Api/ApiService'
import { ListedAccount, ListedAccountInfo, ListedAccountInfoResponse } from '@standardnotes/responses'
import { SNActionsExtension } from '@standardnotes/models'
import { AbstractService, InternalEventBusInterface } from '@standardnotes/services'
import { NoteMutator, SNActionsExtension, SNNote } from '@standardnotes/models'
import { AbstractService, InternalEventBusInterface, MutatorClientInterface } from '@standardnotes/services'
import { SNProtectionService } from '../Protection'
export class ListedService extends AbstractService implements ListedClientInterface {
constructor(
@@ -17,6 +18,8 @@ export class ListedService extends AbstractService implements ListedClientInterf
private itemManager: ItemManager,
private settingsService: SNSettingsService,
private httpSerivce: SNHttpService,
private protectionService: SNProtectionService,
private mutatorService: MutatorClientInterface,
protected override internalEventBus: InternalEventBusInterface,
) {
super(internalEventBus)
@@ -27,6 +30,8 @@ export class ListedService extends AbstractService implements ListedClientInterf
;(this.settingsService as unknown) = undefined
;(this.apiService as unknown) = undefined
;(this.httpSerivce as unknown) = undefined
;(this.protectionService as unknown) = undefined
;(this.mutatorService as unknown) = undefined
super.deinit()
}
@@ -34,6 +39,23 @@ export class ListedService extends AbstractService implements ListedClientInterf
return this.apiService.user != undefined
}
public isNoteAuthorizedForListed(note: SNNote): boolean {
return note.authorizedForListed
}
public async authorizeNoteForListed(note: SNNote): Promise<boolean> {
const result = await this.protectionService.authorizeListedPublishing()
if (result === false) {
return false
}
await this.mutatorService.changeAndSaveItem<NoteMutator>(note, (mutator) => {
mutator.authorizedForListed = true
})
return true
}
/**
* Account creation is asyncronous on the backend due to message-based nature of architecture.
* In order to get the newly created account, we poll the server to check for new accounts.
@@ -73,8 +95,11 @@ export class ListedService extends AbstractService implements ListedClientInterf
if (inContextOfItem) {
url += `&item_uuid=${inContextOfItem}`
}
const response = (await this.httpSerivce.getAbsolute(url)) as ListedAccountInfoResponse
if (response.error || !response.data || isString(response.data)) {
const response = (await this.httpSerivce.getAbsolute(url).catch((error) => {
console.error(error)
})) as ListedAccountInfoResponse
if (!response || response.error || !response.data || isString(response.data)) {
return undefined
}
@@ -219,13 +219,18 @@ export class SNProtectionService extends AbstractService<ProtectionEvent> implem
return this.authorizeAction(ChallengeReason.RevokeSession)
}
async authorizeListedPublishing(): Promise<boolean> {
return this.authorizeAction(ChallengeReason.AuthorizeNoteForListed, { forcePrompt: true })
}
async authorizeAction(
reason: ChallengeReason,
{ fallBackToAccountPassword = true, requireAccountPassword = false } = {},
{ fallBackToAccountPassword = true, requireAccountPassword = false, forcePrompt = false } = {},
): Promise<boolean> {
return this.validateOrRenewSession(reason, {
requireAccountPassword,
fallBackToAccountPassword,
forcePrompt,
})
}
@@ -295,9 +300,9 @@ export class SNProtectionService extends AbstractService<ProtectionEvent> implem
private async validateOrRenewSession(
reason: ChallengeReason,
{ fallBackToAccountPassword = true, requireAccountPassword = false } = {},
{ fallBackToAccountPassword = true, requireAccountPassword = false, forcePrompt = false } = {},
): Promise<boolean> {
if (this.getSessionExpiryDate() > new Date()) {
if (this.getSessionExpiryDate() > new Date() && !forcePrompt) {
return true
}
@@ -139,7 +139,7 @@ export class SNSessionManager extends AbstractService<SessionEvent> implements S
return isNullOrUndefined(this.apiService.getSession())
}
public getUser() {
public getUser(): Responses.User | undefined {
return this.user
}
@@ -1,8 +1,8 @@
import { ClientDisplayableError } from '@standardnotes/responses'
import { ClientDisplayableError, User } from '@standardnotes/responses'
import { Base64String } from '@standardnotes/sncrypto-common'
export interface SessionsClientInterface {
createDemoShareToken(): Promise<Base64String | ClientDisplayableError>
populateSessionFromDemoShareToken(token: Base64String): Promise<void>
getUser(): User | undefined
}
+29
View File
@@ -0,0 +1,29 @@
import { ContentType } from '@standardnotes/common'
import * as Models from '@standardnotes/models'
export const createNote = (payload?: Partial<Models.NoteContent>): Models.SNNote => {
return new Models.SNNote(
new Models.DecryptedPayload(
{
uuid: String(Math.random()),
content_type: ContentType.Note,
content: Models.FillItemContent({ ...payload }),
...Models.PayloadTimestampDefaults(),
},
Models.PayloadSource.Constructor,
),
)
}
export const createNoteWithTitle = (title: string) => {
return new Models.SNNote(
new Models.DecryptedPayload({
uuid: String(Math.random()),
content_type: ContentType.Note,
content: Models.FillItemContent<Models.NoteContent>({
title: title,
}),
...Models.PayloadTimestampDefaults(),
}),
)
}
@@ -356,14 +356,13 @@ describe('app models', () => {
})
it('maintains editor reference when duplicating note', async function () {
const note = await Factory.createMappedNote(this.application)
const editor = await this.application.itemManager.createItem(
ContentType.Component,
{ area: ComponentArea.Editor },
{ area: ComponentArea.Editor, package_info: { identifier: 'foo-editor' } },
true,
)
await this.application.itemManager.changeComponent(editor, (mutator) => {
mutator.associateWithItem(note.uuid)
const note = await Factory.insertItemWithOverride(this.application, ContentType.Note, {
editorIdentifier: 'foo-editor',
})
expect(this.application.componentManager.editorForNote(note).uuid).to.equal(editor.uuid)
@@ -114,59 +114,6 @@ describe('online conflict handling', function () {
await this.sharedFinalAssertions()
})
it('duplicating note should maintain editor ref', async function () {
const note = await Factory.createSyncedNote(this.application)
this.expectedItemCount++
const basePayload = createDirtyPayload(ContentType.Component)
const payload = basePayload.copy({
content: {
...basePayload.content,
area: ComponentArea.Editor,
},
})
const editor = await this.application.itemManager.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
this.expectedItemCount++
await this.application.syncService.sync(syncOptions)
await this.application.mutator.changeAndSaveItem(
editor,
(mutator) => {
mutator.associateWithItem(note.uuid)
},
undefined,
undefined,
syncOptions,
)
expect(this.application.componentManager.editorForNote(note)).to.be.ok
/** Conflict the note */
/** First modify the item without saving so that
* our local contents digress from the server's */
await this.application.mutator.changeItem(note, (mutator) => {
mutator.title = `${Math.random()}`
})
await Factory.changePayloadTimeStampAndSync(
this.application,
note.payload,
Factory.dateToMicroseconds(Factory.yesterday()),
{
title: 'zar',
},
syncOptions,
)
this.expectedItemCount++
const duplicate = this.application.itemManager.getDisplayableNotes().find((n) => {
return n.uuid !== note.uuid
})
expect(duplicate).to.be.ok
expect(this.application.componentManager.editorForNote(duplicate)).to.be.ok
await this.sharedFinalAssertions()
})
it('should create conflicted copy if incoming server item attempts to overwrite local dirty item', async function () {
// create an item and sync it
const note = await Factory.createMappedNote(this.application)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/snjs",
"version": "2.141.1",
"version": "2.142.3",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+24
View File
@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.9.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
### Bug Fixes
* refactor application event observing on web ([9fb85cd](https://github.com/standardnotes/app/commit/9fb85cd77a627364738a9c21706431d2a01e9c44))
# [1.9.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
### Bug Fixes
* **ui-services:** add route type getter ([401a35f](https://github.com/standardnotes/app/commit/401a35f4db9e7f090ad84c2d9e0ce5b465c7e790))
### Features
* add accepting subscription invite route type ([83b4840](https://github.com/standardnotes/app/commit/83b484002c066675e58625d7c0a1f7a7566df787))
## [1.8.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-19)
**Note:** Version bump only for package @standardnotes/ui-services
## [1.8.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-18)
**Note:** Version bump only for package @standardnotes/ui-services
## [1.8.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-17)
**Note:** Version bump only for package @standardnotes/ui-services
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/ui-services",
"version": "1.8.3",
"version": "1.9.1",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -0,0 +1,3 @@
export type DemoParams = {
token: string
}
@@ -0,0 +1,3 @@
export type OnboardingParams = {
fromHomepage: boolean
}
@@ -0,0 +1,4 @@
export type PurchaseParams = {
plan: string
period: string
}
@@ -0,0 +1,5 @@
import { PreferenceId } from '../../Preferences/PreferenceId'
export type SettingsParams = {
panel: PreferenceId
}
@@ -0,0 +1,5 @@
import { Uuid } from '@standardnotes/common'
export type SubscriptionInviteParams = {
inviteUuid: Uuid
}
@@ -0,0 +1,6 @@
export enum RootQueryParam {
Purchase = 'purchase',
Settings = 'settings',
DemoToken = 'demo-token',
AcceptSubscriptionInvite = 'accept-subscription-invite',
}
@@ -0,0 +1,4 @@
export enum RootRoutes {
Onboarding = '/onboard',
None = '/',
}
@@ -1,18 +0,0 @@
import { PreferenceId } from '../Preferences/PreferenceId'
export type OnboardingParams = {
fromHomepage: boolean
}
export type SettingsParams = {
panel: PreferenceId
}
export type DemoParams = {
token: string
}
export type PurchaseParams = {
plan: string
period: string
}
@@ -48,4 +48,12 @@ describe('route parser', () => {
expect(() => parser.onboardingParams).toThrowError('Accessing invalid params')
})
it('routes to subscription sharing', () => {
const url = 'https://app.standardnotes.com/?accept-subscription-invite=1-2-3'
const parser = new RouteParser(url)
expect(parser.type).toEqual(RouteType.AcceptSubscriptionInvite)
expect(parser.subscriptionInviteParams.inviteUuid).toEqual('1-2-3')
})
})
+57 -43
View File
@@ -1,54 +1,43 @@
import { Uuid } from '@standardnotes/common'
import { PreferenceId } from './../Preferences/PreferenceId'
import { DemoParams, OnboardingParams, PurchaseParams, SettingsParams } from './RouteParams'
import { DemoParams } from './Params/DemoParams'
import { OnboardingParams } from './Params/OnboardingParams'
import { PurchaseParams } from './Params/PurchaseParams'
import { SettingsParams } from './Params/SettingsParams'
import { SubscriptionInviteParams } from './Params/SubscriptionInviteParams'
import { RootQueryParam } from './RootQueryParam'
import { RootRoutes } from './RootRoutes'
import { RouteParserInterface } from './RouteParserInterface'
import { RouteType } from './RouteType'
enum RootRoutes {
Onboarding = '/onboard',
None = '/',
}
enum RootQueryParam {
Purchase = 'purchase',
Settings = 'settings',
DemoToken = 'demo-token',
}
export class RouteParser {
export class RouteParser implements RouteParserInterface {
private url: URL
private readonly path: string
public readonly type: RouteType
private readonly parsedType: RouteType
private readonly searchParams: URLSearchParams
constructor(url: string) {
this.url = new URL(url)
this.path = this.url.pathname
this.searchParams = this.url.searchParams
this.parsedType = this.parseTypeFromQueryParameters()
}
const pathUsesRootQueryParams = this.path === RootRoutes.None
get type(): RouteType {
return this.parsedType
}
if (pathUsesRootQueryParams) {
if (this.searchParams.has(RootQueryParam.Purchase)) {
this.type = RouteType.Purchase
} else if (this.searchParams.has(RootQueryParam.Settings)) {
this.type = RouteType.Settings
} else if (this.searchParams.has(RootQueryParam.DemoToken)) {
this.type = RouteType.Demo
} else {
this.type = RouteType.None
}
} else {
if (this.path === RootRoutes.Onboarding) {
this.type = RouteType.Onboarding
} else {
this.type = RouteType.None
}
get subscriptionInviteParams(): SubscriptionInviteParams {
this.checkForProperRouteType(RouteType.AcceptSubscriptionInvite)
return {
inviteUuid: this.searchParams.get(RootQueryParam.AcceptSubscriptionInvite) as Uuid,
}
}
get demoParams(): DemoParams {
if (this.type !== RouteType.Demo) {
throw new Error('Accessing invalid params')
}
this.checkForProperRouteType(RouteType.Demo)
return {
token: this.searchParams.get(RootQueryParam.DemoToken) as string,
@@ -56,9 +45,7 @@ export class RouteParser {
}
get settingsParams(): SettingsParams {
if (this.type !== RouteType.Settings) {
throw new Error('Accessing invalid params')
}
this.checkForProperRouteType(RouteType.Settings)
return {
panel: this.searchParams.get(RootQueryParam.Settings) as PreferenceId,
@@ -66,9 +53,7 @@ export class RouteParser {
}
get purchaseParams(): PurchaseParams {
if (this.type !== RouteType.Purchase) {
throw new Error('Accessing invalid params')
}
this.checkForProperRouteType(RouteType.Purchase)
return {
plan: this.searchParams.get('plan') as string,
@@ -77,12 +62,41 @@ export class RouteParser {
}
get onboardingParams(): OnboardingParams {
if (this.type !== RouteType.Onboarding) {
throw new Error('Accessing invalid params')
}
this.checkForProperRouteType(RouteType.Onboarding)
return {
fromHomepage: !!this.searchParams.get('from_homepage'),
}
}
private checkForProperRouteType(type: RouteType): void {
if (this.parsedType !== type) {
throw new Error('Accessing invalid params')
}
}
private parseTypeFromQueryParameters(): RouteType {
if (this.path === RootRoutes.Onboarding) {
return RouteType.Onboarding
}
if (this.path !== RootRoutes.None) {
return RouteType.None
}
const rootQueryParametersMap: Map<RootQueryParam, RouteType> = new Map([
[RootQueryParam.Purchase, RouteType.Purchase],
[RootQueryParam.Settings, RouteType.Settings],
[RootQueryParam.DemoToken, RouteType.Demo],
[RootQueryParam.AcceptSubscriptionInvite, RouteType.AcceptSubscriptionInvite],
])
for (const rootQueryParam of rootQueryParametersMap.keys()) {
if (this.searchParams.has(rootQueryParam)) {
return rootQueryParametersMap.get(rootQueryParam) as RouteType
}
}
return RouteType.None
}
}
@@ -0,0 +1,15 @@
import { DemoParams } from './Params/DemoParams'
import { OnboardingParams } from './Params/OnboardingParams'
import { PurchaseParams } from './Params/PurchaseParams'
import { SettingsParams } from './Params/SettingsParams'
import { SubscriptionInviteParams } from './Params/SubscriptionInviteParams'
import { RouteType } from './RouteType'
export interface RouteParserInterface {
get demoParams(): DemoParams
get settingsParams(): SettingsParams
get purchaseParams(): PurchaseParams
get onboardingParams(): OnboardingParams
get subscriptionInviteParams(): SubscriptionInviteParams
get type(): RouteType
}
+10 -4
View File
@@ -4,10 +4,16 @@ import {
ApplicationInterface,
InternalEventBusInterface,
} from '@standardnotes/services'
import { RouteParser } from './RouteParser'
import { RouteServiceEvent } from './RouteServiceEvent'
export class RouteService extends AbstractService<RouteServiceEvent, RouteParser> {
import { RouteParser } from './RouteParser'
import { RouteParserInterface } from './RouteParserInterface'
import { RouteServiceEvent } from './RouteServiceEvent'
import { RouteServiceInterface } from './RouteServiceInterface'
export class RouteService
extends AbstractService<RouteServiceEvent, RouteParserInterface>
implements RouteServiceInterface
{
private unsubApp!: () => void
constructor(
@@ -24,7 +30,7 @@ export class RouteService extends AbstractService<RouteServiceEvent, RouteParser
this.unsubApp()
}
public getRoute(): RouteParser {
public getRoute(): RouteParserInterface {
return new RouteParser(window.location.href)
}
@@ -0,0 +1,7 @@
import { RouteParserInterface } from './RouteParserInterface'
export interface RouteServiceInterface {
deinit(): void
getRoute(): RouteParserInterface
removeSettingsFromURLQueryParameters(): void
}
@@ -2,6 +2,7 @@ export enum RouteType {
Onboarding = 'onboarding',
Settings = 'settings',
Purchase = 'purchase',
AcceptSubscriptionInvite = 'accept-subscription-invite',
Demo = 'demo',
None = 'none',
}
+6 -1
View File
@@ -3,10 +3,15 @@ export * from './Alert/WebAlertService'
export * from './Archive/ArchiveManager'
export * from './IO/IOService'
export * from './Preferences/PreferenceId'
export * from './Route/RouteParams'
export * from './Route/Params/DemoParams'
export * from './Route/Params/OnboardingParams'
export * from './Route/Params/PurchaseParams'
export * from './Route/Params/SettingsParams'
export * from './Route/Params/SubscriptionInviteParams'
export * from './Route/RouteParser'
export * from './Route/RouteType'
export * from './Route/RouteService'
export * from './Route/RouteServiceInterface'
export * from './Route/RouteServiceEvent'
export * from './Security/AutolockService'
export * from './Storage/LocalStorage'
+104
View File
@@ -3,6 +3,110 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.82.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
### Bug Fixes
* refactor application event observing on web ([9fb85cd](https://github.com/standardnotes/app/commit/9fb85cd77a627364738a9c21706431d2a01e9c44))
* **web:** event observer interface import ([c503d78](https://github.com/standardnotes/app/commit/c503d781aefcc1a15d3a8da1ee09294d176e4b89))
## [3.82.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
### Bug Fixes
* item selection/unselection on deleting ([4a1612c](https://github.com/standardnotes/app/commit/4a1612c7dc8ba2af0d28ac71d5dfcdf059cd5ea0))
* template note getting unselected after typing ([d3ed439](https://github.com/standardnotes/app/commit/d3ed4391b7f6b77ced485884a97190449a1b5507))
## [3.82.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
**Note:** Version bump only for package @standardnotes/web
## [3.82.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
### Bug Fixes
* default auto-dark theme ([1addaf4](https://github.com/standardnotes/app/commit/1addaf4c3941364f695545669158f09f4c012dea))
* tag collapse/expand not persisting ([c6e3580](https://github.com/standardnotes/app/commit/c6e3580678dbed6562041ce5afed47563276843a))
## [3.82.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
### Bug Fixes
* improve plaintext editor performance on mobile ([#1855](https://github.com/standardnotes/app/issues/1855)) ([f71e6fe](https://github.com/standardnotes/app/commit/f71e6fe70397cf9ab2c109cad52780088337fc47))
## [3.82.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-20)
### Reverts
* Revert "feat: ability to favorite tags (#1852)" ([5d202a8](https://github.com/standardnotes/app/commit/5d202a823e2a89c5e6fe7e07306957f2b2ea68b1)), closes [#1852](https://github.com/standardnotes/app/issues/1852)
# [3.82.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-20)
### Bug Fixes
* mobx warnings and react render phase set state error ([76f23e1](https://github.com/standardnotes/app/commit/76f23e13ecc8795b4e6cbbb37c32db262c0084c2))
### Features
* ability to favorite tags ([#1852](https://github.com/standardnotes/app/issues/1852)) ([4f3b258](https://github.com/standardnotes/app/commit/4f3b258363ded1a340bd1a7702da7cfd6415b954))
# [3.81.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-20)
### Features
* persist selected tag & note locally ([#1851](https://github.com/standardnotes/app/issues/1851)) ([4432f1c](https://github.com/standardnotes/app/commit/4432f1cb4cfc2042ddfb7bd574d53270626ebe0d))
## [3.80.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-20)
### Bug Fixes
* "Forgot passcode" and "Switch workspace" lock screen options not working on mobile ([#1850](https://github.com/standardnotes/app/issues/1850)) ([57fab4f](https://github.com/standardnotes/app/commit/57fab4f670faeaa0018d9d1a678e50d1e90052aa))
# [3.80.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-20)
### Bug Fixes
* file not linking to note when uploaded by dragging on note ([d333d4f](https://github.com/standardnotes/app/commit/d333d4f1887672b526a5d8fc659e6dd2b1b12e21))
* help & feedback links on mobile ([#1849](https://github.com/standardnotes/app/issues/1849)) ([fb72c2f](https://github.com/standardnotes/app/commit/fb72c2fdae277cb4e348ce5ee550f0545f281dcd))
* links for template notes ([60a8e8b](https://github.com/standardnotes/app/commit/60a8e8b92b1e5c0f4c4c007591aa115fce51930b))
* make tags non-tabbable ([a8535cf](https://github.com/standardnotes/app/commit/a8535cfa2380a4ddd648d5d099701c0b5e8f3a22))
* remove tinted border from change note type button ([27bb4eb](https://github.com/standardnotes/app/commit/27bb4ebdae259718c0e13134c2ffdce7c681f0da))
### Features
* make sans-serif font default ([050fa9c](https://github.com/standardnotes/app/commit/050fa9cabca65bba4845fc9b315c81ce524e8b0d))
# [3.79.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-19)
### Features
* improved file drag-n-drop experience ([#1848](https://github.com/standardnotes/app/issues/1848)) ([e99c7b7](https://github.com/standardnotes/app/commit/e99c7b7c516b0c822f0e663843146c7f5cf1fb86))
## [3.78.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-19)
### Bug Fixes
* issue with not being able to unlink a file from a note ([#1836](https://github.com/standardnotes/app/issues/1836)) ([4030953](https://github.com/standardnotes/app/commit/4030953b00ad609dc2b0f5b66cfd971d449f6121))
# [3.78.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-18)
### Bug Fixes
* context menu opening when swiping up from bottom on iOS ([5ffd37d](https://github.com/standardnotes/app/commit/5ffd37d4478aec638392962f45d3a4428a8da86a))
* editor width on mobile ([361f773](https://github.com/standardnotes/app/commit/361f773f2c3c65255e0d5cc8d991fac0f4521fff))
* hide dynamic panels & focused mode options on mobile ([e4de9c1](https://github.com/standardnotes/app/commit/e4de9c1be76c758f36e5068a8395bb3abd19069a))
* use mobx action for setting state ([eec7ae1](https://github.com/standardnotes/app/commit/eec7ae1589f7eae36ba810942911ccb3b8d863ee))
### Features
* authorize notes for listed ([#1823](https://github.com/standardnotes/app/issues/1823)) ([9954bdc](https://github.com/standardnotes/app/commit/9954bdc29f9de2a42c4539d55749991d86a72943))
## [3.77.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-18)
### Bug Fixes
* remove switch to classic mx option ([e40cd15](https://github.com/standardnotes/app/commit/e40cd157b6d1683cf9eb9548714c604649134971))
# [3.77.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-17)
### Features
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/web",
"version": "3.77.0",
"version": "3.82.6",
"license": "AGPL-3.0-or-later",
"main": "dist/app.js",
"author": "Standard Notes.",
@@ -20,6 +20,7 @@ import {
MobileDeviceInterface,
MobileUnlockTiming,
InternalEventBus,
DecryptedItem,
} from '@standardnotes/snjs'
import { makeObservable, observable } from 'mobx'
import { PanelResizedData } from '@/Types/PanelResizedData'
@@ -30,6 +31,7 @@ import {
AutolockService,
IOService,
RouteService,
RouteServiceInterface,
ThemeManager,
WebAlertService,
} from '@standardnotes/ui-services'
@@ -49,7 +51,7 @@ export class WebApplication extends SNApplication implements WebApplicationInter
private onVisibilityChange: () => void
private mobileWebReceiver?: MobileWebReceiver
private androidBackHandler?: AndroidBackHandler
public readonly routeService: RouteService
public readonly routeService: RouteServiceInterface
constructor(
deviceInterface: WebOrDesktopDevice,
@@ -308,4 +310,12 @@ export class WebApplication extends SNApplication implements WebApplicationInter
}
return
}
isAuthorizedToRenderItem(item: DecryptedItem): boolean {
if (item.protected && this.hasProtectionSources()) {
return this.hasUnprotectedAccessSession()
}
return true
}
}
@@ -221,6 +221,7 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
notesController={viewControllerManager.notesController}
selectionController={viewControllerManager.selectionController}
searchOptionsController={viewControllerManager.searchOptionsController}
linkingController={viewControllerManager.linkingController}
/>
<NoteGroupView application={application} />
</FileDragNDropProvider>
@@ -1,171 +0,0 @@
import { WebApplication } from '@/Application/Application'
import { observer } from 'mobx-react-lite'
import { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
import Icon from '@/Components/Icon/Icon'
import AttachedFilesPopover from './AttachedFilesPopover'
import { usePremiumModal } from '@/Hooks/usePremiumModal'
import { PopoverTabs } from './PopoverTabs'
import { NotesController } from '@/Controllers/NotesController'
import { FilePreviewModalController } from '@/Controllers/FilePreviewModalController'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import { FeaturesController } from '@/Controllers/FeaturesController'
import { FilesController } from '@/Controllers/FilesController'
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
import { useFileDragNDrop } from '@/Components/FileDragNDropProvider/FileDragNDropProvider'
import { FileItem, SNNote } from '@standardnotes/snjs'
import { addToast, ToastType } from '@standardnotes/toast'
import { classNames } from '@/Utils/ConcatenateClassNames'
import Popover from '../Popover/Popover'
type Props = {
application: WebApplication
featuresController: FeaturesController
filePreviewModalController: FilePreviewModalController
filesController: FilesController
navigationController: NavigationController
notesController: NotesController
selectionController: SelectedItemsController
onClickPreprocessing?: () => Promise<void>
}
const AttachedFilesButton: FunctionComponent<Props> = ({
application,
featuresController,
filesController,
navigationController,
notesController,
selectionController,
onClickPreprocessing,
}: Props) => {
const { allFiles, attachedFiles } = filesController
const attachedFilesCount = attachedFiles.length
const premiumModal = usePremiumModal()
const note: SNNote | undefined = notesController.firstSelectedNote
const [isOpen, setIsOpen] = useState(false)
const buttonRef = useRef<HTMLButtonElement>(null)
const containerRef = useRef<HTMLDivElement>(null)
const [currentTab, setCurrentTab] = useState(
navigationController.isInFilesView ? PopoverTabs.AllFiles : PopoverTabs.AttachedFiles,
)
const isAttachedTabDisabled = navigationController.isInFilesView || selectionController.selectedItemsCount > 1
useEffect(() => {
if (isAttachedTabDisabled && currentTab === PopoverTabs.AttachedFiles) {
setCurrentTab(PopoverTabs.AllFiles)
}
}, [currentTab, isAttachedTabDisabled])
const toggleAttachedFilesMenu = useCallback(async () => {
const newOpenState = !isOpen
if (newOpenState && onClickPreprocessing) {
await onClickPreprocessing()
}
setIsOpen(newOpenState)
}, [onClickPreprocessing, isOpen])
const prospectivelyShowFilesPremiumModal = useCallback(() => {
if (!featuresController.hasFiles) {
premiumModal.activate('Files')
}
}, [featuresController.hasFiles, premiumModal])
const toggleAttachedFilesMenuWithEntitlementCheck = useCallback(async () => {
prospectivelyShowFilesPremiumModal()
await toggleAttachedFilesMenu()
}, [toggleAttachedFilesMenu, prospectivelyShowFilesPremiumModal])
const attachFileToNote = useCallback(
async (file: FileItem) => {
if (!note) {
addToast({
type: ToastType.Error,
message: 'Could not attach file because selected note was unselected or deleted',
})
return
}
await application.items.associateFileWithNote(file, note)
},
[application.items, note],
)
const { isDraggingFiles, addFilesDragInCallback, addFilesDropCallback } = useFileDragNDrop()
useEffect(() => {
if (isDraggingFiles && !isOpen) {
void toggleAttachedFilesMenu()
}
}, [isDraggingFiles, isOpen, toggleAttachedFilesMenu])
const filesDragInCallback = useCallback((tab: PopoverTabs) => {
setCurrentTab(tab)
}, [])
useEffect(() => {
addFilesDragInCallback(filesDragInCallback)
}, [addFilesDragInCallback, filesDragInCallback])
const filesDropCallback = useCallback(
(uploadedFiles: FileItem[]) => {
if (currentTab === PopoverTabs.AttachedFiles) {
uploadedFiles.forEach((file) => {
attachFileToNote(file).catch(console.error)
})
}
},
[attachFileToNote, currentTab],
)
useEffect(() => {
addFilesDropCallback(filesDropCallback)
}, [addFilesDropCallback, filesDropCallback])
return (
<div ref={containerRef}>
<button
className={classNames(
'bg-text-padding flex h-8 min-w-8 cursor-pointer items-center justify-center rounded-full border border-solid border-border text-neutral hover:bg-contrast focus:bg-contrast',
attachedFilesCount > 0 ? 'py-1 px-3' : '',
)}
title="Attached files"
aria-label="Attached files"
onClick={toggleAttachedFilesMenuWithEntitlementCheck}
ref={buttonRef}
onKeyDown={(event) => {
if (event.key === 'Escape') {
setIsOpen(false)
}
}}
>
<Icon type="folder" />
{attachedFilesCount > 0 && <span className="ml-2 text-sm">{attachedFilesCount}</span>}
</button>
<Popover
togglePopover={toggleAttachedFilesMenuWithEntitlementCheck}
anchorElement={buttonRef.current}
open={isOpen}
className="pt-2 md:pt-0"
>
<AttachedFilesPopover
application={application}
filesController={filesController}
attachedFiles={attachedFiles}
allFiles={allFiles}
currentTab={currentTab}
isDraggingFiles={isDraggingFiles}
setCurrentTab={setCurrentTab}
attachedTabDisabled={isAttachedTabDisabled}
/>
</Popover>
</div>
)
}
export default observer(AttachedFilesButton)
@@ -1,206 +0,0 @@
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants'
import { WebApplication } from '@/Application/Application'
import { FileItem } from '@standardnotes/snjs'
import { FilesIllustration } from '@standardnotes/icons'
import { observer } from 'mobx-react-lite'
import { Dispatch, FunctionComponent, SetStateAction, useRef, useState } from 'react'
import Button from '@/Components/Button/Button'
import Icon from '@/Components/Icon/Icon'
import PopoverFileItem from './PopoverFileItem'
import { PopoverFileItemActionType } from './PopoverFileItemAction'
import { PopoverTabs } from './PopoverTabs'
import { FilesController } from '@/Controllers/FilesController'
import { StreamingFileReader } from '@standardnotes/filepicker'
import ClearInputButton from '../ClearInputButton/ClearInputButton'
import DecoratedInput from '../Input/DecoratedInput'
type Props = {
application: WebApplication
filesController: FilesController
allFiles: FileItem[]
attachedFiles: FileItem[]
currentTab: PopoverTabs
isDraggingFiles: boolean
setCurrentTab: Dispatch<SetStateAction<PopoverTabs>>
attachedTabDisabled: boolean
}
const AttachedFilesPopover: FunctionComponent<Props> = ({
application,
filesController,
allFiles,
attachedFiles,
currentTab,
isDraggingFiles,
setCurrentTab,
attachedTabDisabled,
}) => {
const fileInputRef = useRef<HTMLInputElement>(null)
const [searchQuery, setSearchQuery] = useState('')
const searchInputRef = useRef<HTMLInputElement>(null)
const filesList = currentTab === PopoverTabs.AttachedFiles ? attachedFiles : allFiles
const filteredList =
searchQuery.length > 0
? filesList.filter((file) => file.name.toLowerCase().indexOf(searchQuery.toLowerCase()) !== -1)
: filesList
const attachFilesIfRequired = (files: FileItem[]) => {
if (currentTab === PopoverTabs.AttachedFiles) {
files.forEach((file) => {
filesController
.handleFileAction({
type: PopoverFileItemActionType.AttachFileToNote,
payload: { file },
})
.catch(console.error)
})
}
}
const handleAttachFilesClick = async () => {
if (!StreamingFileReader.available()) {
fileInputRef.current?.click()
return
}
const uploadedFiles = await filesController.uploadNewFile()
if (!uploadedFiles) {
return
}
attachFilesIfRequired(uploadedFiles)
}
const previewHandler = (file: FileItem) => {
filesController
.handleFileAction({
type: PopoverFileItemActionType.PreviewFile,
payload: { file, otherFiles: currentTab === PopoverTabs.AllFiles ? allFiles : attachedFiles },
})
.catch(console.error)
}
return (
<div
className="flex flex-col"
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
style={{
border: isDraggingFiles ? '2px dashed var(--sn-stylekit-info-color)' : '',
}}
>
<div className="flex border-b border-solid border-border">
<button
id={PopoverTabs.AttachedFiles}
className={`relative cursor-pointer border-0 bg-default px-3 py-2.5 text-sm focus:bg-info-backdrop focus:shadow-bottom ${
currentTab === PopoverTabs.AttachedFiles ? 'font-medium text-info shadow-bottom' : 'text-text'
} ${attachedTabDisabled ? 'cursor-not-allowed text-neutral' : ''}`}
onClick={() => {
setCurrentTab(PopoverTabs.AttachedFiles)
}}
disabled={attachedTabDisabled}
>
Attached
</button>
<button
id={PopoverTabs.AllFiles}
className={`relative cursor-pointer border-0 bg-default px-3 py-2.5 text-sm focus:bg-info-backdrop focus:shadow-bottom ${
currentTab === PopoverTabs.AllFiles ? 'font-medium text-info shadow-bottom' : 'text-text'
}`}
onClick={() => {
setCurrentTab(PopoverTabs.AllFiles)
}}
>
All files
</button>
</div>
<div className="max-h-110 min-h-0 overflow-y-auto">
{filteredList.length > 0 || searchQuery.length > 0 ? (
<div className="sticky top-0 left-0 border-b border-solid border-border bg-default p-3">
<DecoratedInput
type="text"
className={{ container: searchQuery.length < 1 ? 'py-1.5 px-0.5' : 'py-0' }}
placeholder="Search items..."
value={searchQuery}
onChange={setSearchQuery}
ref={searchInputRef}
right={[
searchQuery.length > 0 && (
<ClearInputButton
onClick={() => {
setSearchQuery('')
searchInputRef.current?.focus()
}}
/>
),
]}
/>
</div>
) : null}
{filteredList.length > 0 ? (
filteredList.map((file: FileItem) => {
return (
<PopoverFileItem
key={file.uuid}
file={file}
isAttachedToNote={attachedFiles.includes(file)}
handleFileAction={filesController.handleFileAction}
getIconType={application.iconsController.getIconForFileType}
previewHandler={previewHandler}
/>
)
})
) : (
<div className="flex w-full flex-col items-center justify-center py-8">
<div className="mb-2 h-18 w-18">
<FilesIllustration />
</div>
<div className="mb-3 text-sm font-medium">
{searchQuery.length > 0
? 'No result found'
: currentTab === PopoverTabs.AttachedFiles
? 'No files attached to this note'
: 'No files found in this account'}
</div>
<Button onClick={handleAttachFilesClick}>
{currentTab === PopoverTabs.AttachedFiles ? 'Attach' : 'Upload'} files
</Button>
<div className="mt-3 text-xs text-passive-0">Or drop your files here</div>
</div>
)}
</div>
<input
type="file"
className="absolute top-0 left-0 -z-50 h-px w-px opacity-0"
multiple
ref={fileInputRef}
onChange={async (event) => {
const files = event.currentTarget.files
if (!files) {
return
}
for (const file of files) {
const uploadedFiles = await filesController.uploadNewFile(file)
if (uploadedFiles) {
attachFilesIfRequired(uploadedFiles)
}
}
}}
/>
{filteredList.length > 0 && (
<button
className="flex w-full cursor-pointer items-center border-0 border-t border-solid border-border bg-transparent px-3 py-3 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
onClick={handleAttachFilesClick}
>
<Icon type="add" className="mr-2 text-neutral" />
{currentTab === PopoverTabs.AttachedFiles ? 'Attach' : 'Upload'} files
</button>
)}
</div>
)
}
export default observer(AttachedFilesPopover)
@@ -1,124 +0,0 @@
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants'
import { KeyboardKey } from '@standardnotes/ui-services'
import { formatSizeToReadableString } from '@standardnotes/filepicker'
import { FileItem } from '@standardnotes/snjs'
import {
FormEventHandler,
FunctionComponent,
KeyboardEventHandler,
useCallback,
useEffect,
useRef,
useState,
} from 'react'
import Icon from '@/Components/Icon/Icon'
import { PopoverFileItemActionType } from './PopoverFileItemAction'
import PopoverFileSubmenu from './PopoverFileSubmenu'
import { getFileIconComponent } from './getFileIconComponent'
import { PopoverFileItemProps } from './PopoverFileItemProps'
const PopoverFileItem: FunctionComponent<PopoverFileItemProps> = ({
file,
isAttachedToNote,
handleFileAction,
getIconType,
previewHandler,
}) => {
const [fileName, setFileName] = useState(file.name)
const [isRenamingFile, setIsRenamingFile] = useState(false)
const itemRef = useRef<HTMLDivElement>(null)
const fileNameInputRef = useRef<HTMLInputElement>(null)
useEffect(() => {
if (isRenamingFile) {
fileNameInputRef.current?.focus()
}
}, [isRenamingFile])
const renameFile = useCallback(
async (file: FileItem, name: string) => {
if (name.length < 1) {
return
}
await handleFileAction({
type: PopoverFileItemActionType.RenameFile,
payload: {
file,
name,
},
})
setIsRenamingFile(false)
},
[handleFileAction],
)
const handleFileNameInput: FormEventHandler<HTMLInputElement> = useCallback((event) => {
setFileName((event.target as HTMLInputElement).value)
}, [])
const handleFileNameInputKeyDown: KeyboardEventHandler = useCallback(
(event) => {
if (fileName.length > 0 && event.key === KeyboardKey.Enter) {
itemRef.current?.focus()
}
},
[fileName.length],
)
const handleFileNameInputBlur = useCallback(() => {
renameFile(file, fileName).catch(console.error)
}, [file, fileName, renameFile])
const handleClick = useCallback(() => {
if (isRenamingFile) {
return
}
previewHandler(file)
}, [file, isRenamingFile, previewHandler])
return (
<div
ref={itemRef}
className="flex items-center justify-between p-3 focus:shadow-none"
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
>
<div onClick={handleClick} className="flex cursor-pointer items-center">
{getFileIconComponent(getIconType(file.mimeType), 'w-8 h-8 flex-shrink-0')}
<div className="mx-4 flex flex-col">
{isRenamingFile ? (
<input
type="text"
className="text-input mb-1 border border-solid border-border bg-transparent px-1.5 py-1 text-foreground"
value={fileName}
ref={fileNameInputRef}
onInput={handleFileNameInput}
onKeyDown={handleFileNameInputKeyDown}
onBlur={handleFileNameInputBlur}
/>
) : (
<div className="break-word mb-1 text-mobile-menu-item md:text-sm">
<span className="align-middle">{file.name}</span>
{file.protected && (
<Icon type="lock-filled" className="ml-2 inline align-middle text-neutral" size="small" />
)}
</div>
)}
<div className="text-xs text-passive-0">
{file.created_at.toLocaleString()} · {formatSizeToReadableString(file.decryptedSize)}
</div>
</div>
</div>
<PopoverFileSubmenu
file={file}
isAttachedToNote={isAttachedToNote}
handleFileAction={handleFileAction}
setIsRenamingFile={setIsRenamingFile}
previewHandler={previewHandler}
/>
</div>
)
}
export default PopoverFileItem
@@ -1,20 +0,0 @@
import { IconType, FileItem } from '@standardnotes/snjs'
import { Dispatch, SetStateAction } from 'react'
import { PopoverFileItemAction } from './PopoverFileItemAction'
type CommonProps = {
file: FileItem
isAttachedToNote: boolean
handleFileAction: (action: PopoverFileItemAction) => Promise<{
didHandleAction: boolean
}>
previewHandler: (file: FileItem) => void
}
export type PopoverFileItemProps = CommonProps & {
getIconType(type: string): IconType
}
export type PopoverFileSubmenuProps = CommonProps & {
setIsRenamingFile: Dispatch<SetStateAction<boolean>>
}
@@ -1,154 +0,0 @@
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants'
import { FunctionComponent, useCallback, useRef, useState } from 'react'
import Icon from '@/Components/Icon/Icon'
import Switch from '@/Components/Switch/Switch'
import { PopoverFileSubmenuProps } from './PopoverFileItemProps'
import { PopoverFileItemActionType } from './PopoverFileItemAction'
import HorizontalSeparator from '../Shared/HorizontalSeparator'
import { formatSizeToReadableString } from '@standardnotes/filepicker'
import Popover from '../Popover/Popover'
const PopoverFileSubmenu: FunctionComponent<PopoverFileSubmenuProps> = ({
file,
isAttachedToNote,
handleFileAction,
setIsRenamingFile,
previewHandler,
}) => {
const menuContainerRef = useRef<HTMLDivElement>(null)
const menuButtonRef = useRef<HTMLButtonElement>(null)
const [isOpen, setIsOpen] = useState(false)
const [isFileProtected, setIsFileProtected] = useState(file.protected)
const closeMenu = useCallback(() => {
setIsOpen(false)
}, [])
const toggleMenu = useCallback(() => {
setIsOpen((isOpen) => !isOpen)
}, [])
return (
<div ref={menuContainerRef}>
<button
ref={menuButtonRef}
onClick={toggleMenu}
className="h-7 w-7 cursor-pointer rounded-full border-0 bg-transparent p-1 hover:bg-contrast"
>
<Icon type="more" className="text-neutral" />
</button>
<Popover anchorElement={menuButtonRef.current} open={isOpen} togglePopover={toggleMenu} className="py-2">
<button
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-sm"
onClick={() => {
previewHandler(file)
closeMenu()
}}
>
<Icon type="file" className="mr-2 text-neutral" />
Preview file
</button>
{isAttachedToNote ? (
<button
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-sm"
onClick={() => {
handleFileAction({
type: PopoverFileItemActionType.DetachFileToNote,
payload: { file },
}).catch(console.error)
closeMenu()
}}
>
<Icon type="link-off" className="mr-2 text-neutral" />
Detach from note
</button>
) : (
<button
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-sm"
onClick={() => {
handleFileAction({
type: PopoverFileItemActionType.AttachFileToNote,
payload: { file },
}).catch(console.error)
closeMenu()
}}
>
<Icon type="link" className="mr-2 text-neutral" />
Attach to note
</button>
)}
<HorizontalSeparator classes="my-1" />
<button
className="flex w-full cursor-pointer items-center justify-between border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-sm"
onClick={() => {
handleFileAction({
type: PopoverFileItemActionType.ToggleFileProtection,
payload: { file },
callback: (isProtected: boolean) => {
setIsFileProtected(isProtected)
},
}).catch(console.error)
}}
>
<span className="flex items-center">
<Icon type="password" className="mr-2 text-neutral" />
Password protection
</span>
<Switch
className="pointer-events-none px-0"
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
checked={isFileProtected}
/>
</button>
<HorizontalSeparator classes="my-1" />
<button
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-sm"
onClick={() => {
handleFileAction({
type: PopoverFileItemActionType.DownloadFile,
payload: { file },
}).catch(console.error)
closeMenu()
}}
>
<Icon type="download" className="mr-2 text-neutral" />
Download
</button>
<button
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-sm"
onClick={() => {
setIsRenamingFile(true)
closeMenu()
}}
>
<Icon type="pencil" className="mr-2 text-neutral" />
Rename
</button>
<button
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-sm"
onClick={() => {
handleFileAction({
type: PopoverFileItemActionType.DeleteFile,
payload: { file },
}).catch(console.error)
closeMenu()
}}
>
<Icon type="trash" className="mr-2 text-danger" />
<span className="text-danger">Delete permanently</span>
</button>
<div className="px-3 py-1 text-xs font-medium text-neutral">
<div className="mb-1">
<span className="font-semibold">File ID:</span> {file.uuid}
</div>
<div>
<span className="font-semibold">Size:</span> {formatSizeToReadableString(file.decryptedSize)}
</div>
</div>
</Popover>
</div>
)
}
export default PopoverFileSubmenu
@@ -1,4 +0,0 @@
export enum PopoverTabs {
AttachedFiles = 'attached-files-tab',
AllFiles = 'all-files-tab',
}

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