Merge branch 'main' of github.com:standardnotes/app
This commit is contained in:
@@ -27,7 +27,7 @@
|
|||||||
"build:services": "yarn workspaces foreach -pt --topological-dev --verbose -R --from @standardnotes/services run build",
|
"build:services": "yarn workspaces foreach -pt --topological-dev --verbose -R --from @standardnotes/services run build",
|
||||||
"build:api": "yarn workspaces foreach -pt --topological-dev --verbose -R --from @standardnotes/api run build",
|
"build:api": "yarn workspaces foreach -pt --topological-dev --verbose -R --from @standardnotes/api run build",
|
||||||
"start:server:web": "lerna run start --scope=@standardnotes/web",
|
"start:server:web": "lerna run start --scope=@standardnotes/web",
|
||||||
"start:server:e2e": "lerna run start:test-server --scope=@standardnotes/snjs",
|
"e2e": "lerna run start:test-server --scope=@standardnotes/snjs",
|
||||||
"reset": "find . -type dir -name node_modules | xargs rm -rf && rm -rf yarn.lock && yarn install",
|
"reset": "find . -type dir -name node_modules | xargs rm -rf && rm -rf yarn.lock && yarn install",
|
||||||
"release:prod": "lerna version --conventional-commits --yes -m \"chore(release): publish\"",
|
"release:prod": "lerna version --conventional-commits --yes -m \"chore(release): publish\"",
|
||||||
"publish:prod": "lerna publish from-git --yes --no-verify-access --loglevel verbose",
|
"publish:prod": "lerna publish from-git --yes --no-verify-access --loglevel verbose",
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [3.104.67](https://github.com/standardnotes/app/compare/@standardnotes/desktop@3.132.2...@standardnotes/desktop@3.104.67) (2023-01-04)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/desktop
|
||||||
|
|
||||||
## [3.104.66](https://github.com/standardnotes/app/compare/@standardnotes/desktop@3.132.0...@standardnotes/desktop@3.104.66) (2023-01-04)
|
## [3.104.66](https://github.com/standardnotes/app/compare/@standardnotes/desktop@3.132.0...@standardnotes/desktop@3.104.66) (2023-01-04)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/desktop
|
**Note:** Version bump only for package @standardnotes/desktop
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/desktop",
|
"name": "@standardnotes/desktop",
|
||||||
"main": "./app/dist/index.js",
|
"main": "./app/dist/index.js",
|
||||||
"version": "3.104.66",
|
"version": "3.104.67",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"author": "Standard Notes.",
|
"author": "Standard Notes.",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [3.50.3](https://github.com/standardnotes/app/compare/@standardnotes/mobile@3.50.2...@standardnotes/mobile@3.50.3) (2023-01-04)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/mobile
|
||||||
|
|
||||||
## [3.50.2](https://github.com/standardnotes/app/compare/@standardnotes/mobile@3.50.1...@standardnotes/mobile@3.50.2) (2023-01-04)
|
## [3.50.2](https://github.com/standardnotes/app/compare/@standardnotes/mobile@3.50.1...@standardnotes/mobile@3.50.2) (2023-01-04)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/mobile
|
**Note:** Version bump only for package @standardnotes/mobile
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/mobile",
|
"name": "@standardnotes/mobile",
|
||||||
"version": "3.50.2",
|
"version": "3.50.3",
|
||||||
"author": "Standard Notes.",
|
"author": "Standard Notes.",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.4.90](https://github.com/standardnotes/app/compare/@standardnotes/releases@1.4.89...@standardnotes/releases@1.4.90) (2023-01-04)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/releases
|
||||||
|
|
||||||
## [1.4.89](https://github.com/standardnotes/app/compare/@standardnotes/releases@1.4.88...@standardnotes/releases@1.4.89) (2023-01-04)
|
## [1.4.89](https://github.com/standardnotes/app/compare/@standardnotes/releases@1.4.88...@standardnotes/releases@1.4.89) (2023-01-04)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/releases
|
**Note:** Version bump only for package @standardnotes/releases
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/releases",
|
"name": "@standardnotes/releases",
|
||||||
"version": "1.4.89",
|
"version": "1.4.90",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"main": "dist/releases.json",
|
"main": "dist/releases.json",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|||||||
@@ -182,5 +182,6 @@ export const ErrorAlertStrings = {
|
|||||||
|
|
||||||
export const KeychainRecoveryStrings = {
|
export const KeychainRecoveryStrings = {
|
||||||
Title: 'Restore Keychain',
|
Title: 'Restore Keychain',
|
||||||
Text: "We've detected that your keychain has been wiped. This can happen when restoring your device from a backup. Please enter your account password to restore your account keys.",
|
Text: (email: string) =>
|
||||||
|
`We've detected that your keychain has been wiped. This can happen when restoring your device from a backup. Please enter your account password for "${email}" to restore your account keys.`,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Base64String } from '@standardnotes/sncrypto-common'
|
||||||
import { UserRequestType } from '@standardnotes/common'
|
import { UserRequestType } from '@standardnotes/common'
|
||||||
import { DeinitSource } from '../Application/DeinitSource'
|
import { DeinitSource } from '../Application/DeinitSource'
|
||||||
|
|
||||||
@@ -8,4 +9,5 @@ export interface UserClientInterface {
|
|||||||
}>
|
}>
|
||||||
signOut(force?: boolean, source?: DeinitSource): Promise<void>
|
signOut(force?: boolean, source?: DeinitSource): Promise<void>
|
||||||
submitUserRequest(requestType: UserRequestType): Promise<boolean>
|
submitUserRequest(requestType: UserRequestType): Promise<boolean>
|
||||||
|
populateSessionFromDemoShareToken(token: Base64String): Promise<void>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Base64String } from '@standardnotes/sncrypto-common'
|
||||||
import { EncryptionProviderInterface, SNRootKey, SNRootKeyParams } from '@standardnotes/encryption'
|
import { EncryptionProviderInterface, SNRootKey, SNRootKeyParams } from '@standardnotes/encryption'
|
||||||
import { HttpResponse, SignInResponse, User } from '@standardnotes/responses'
|
import { HttpResponse, SignInResponse, User } from '@standardnotes/responses'
|
||||||
import { KeyParamsOrigination, UserRequestType } from '@standardnotes/common'
|
import { KeyParamsOrigination, UserRequestType } from '@standardnotes/common'
|
||||||
@@ -470,6 +471,11 @@ export class UserService extends AbstractService<AccountEvent, AccountEventData>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async populateSessionFromDemoShareToken(token: Base64String): Promise<void> {
|
||||||
|
await this.sessionManager.populateSessionFromDemoShareToken(token)
|
||||||
|
await this.notifyEvent(AccountEvent.SignedInOrRegistered)
|
||||||
|
}
|
||||||
|
|
||||||
private async setPasscodeWithoutWarning(passcode: string, origination: KeyParamsOrigination) {
|
private async setPasscodeWithoutWarning(passcode: string, origination: KeyParamsOrigination) {
|
||||||
const identifier = UuidGenerator.GenerateUuid()
|
const identifier = UuidGenerator.GenerateUuid()
|
||||||
const key = await this.protocolService.createRootKey(identifier, passcode, origination)
|
const key = await this.protocolService.createRootKey(identifier, passcode, origination)
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ Once the server infrastructure is ready, and you've built all packages, you can
|
|||||||
|
|
||||||
In app repo:
|
In app repo:
|
||||||
```
|
```
|
||||||
yarn start:server:e2e
|
yarn e2e
|
||||||
```
|
```
|
||||||
|
|
||||||
Once you are finished you can close the running local server on E2E repo by typing:
|
Once you are finished you can close the running local server on E2E repo by typing:
|
||||||
|
|||||||
@@ -1581,6 +1581,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
|||||||
this.identifier,
|
this.identifier,
|
||||||
{
|
{
|
||||||
loadBatchSize: this.options.loadBatchSize,
|
loadBatchSize: this.options.loadBatchSize,
|
||||||
|
sleepBetweenBatches: this.options.sleepBetweenBatches,
|
||||||
},
|
},
|
||||||
this.internalEventBus,
|
this.internalEventBus,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
import { ApplicationSyncOptions } from './OptionalOptions'
|
import { ApplicationDisplayOptions, ApplicationSyncOptions } from './OptionalOptions'
|
||||||
|
|
||||||
export interface ApplicationOptionsWhichHaveDefaults {
|
export interface ApplicationOptionsWhichHaveDefaults {
|
||||||
loadBatchSize: ApplicationSyncOptions['loadBatchSize']
|
loadBatchSize: ApplicationSyncOptions['loadBatchSize']
|
||||||
|
sleepBetweenBatches: ApplicationSyncOptions['sleepBetweenBatches']
|
||||||
|
allowNoteSelectionStatePersistence: ApplicationDisplayOptions['allowNoteSelectionStatePersistence']
|
||||||
|
allowMultipleSelection: ApplicationDisplayOptions['allowMultipleSelection']
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ApplicationOptionsDefaults: ApplicationOptionsWhichHaveDefaults = {
|
export const ApplicationOptionsDefaults: ApplicationOptionsWhichHaveDefaults = {
|
||||||
loadBatchSize: 700,
|
loadBatchSize: 700,
|
||||||
|
sleepBetweenBatches: 10,
|
||||||
|
allowMultipleSelection: true,
|
||||||
|
allowNoteSelectionStatePersistence: true,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,14 @@ export interface ApplicationSyncOptions {
|
|||||||
* The size of the item batch to decrypt and render upon application load.
|
* The size of the item batch to decrypt and render upon application load.
|
||||||
*/
|
*/
|
||||||
loadBatchSize: number
|
loadBatchSize: number
|
||||||
|
|
||||||
|
sleepBetweenBatches: number
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
export interface ApplicationDisplayOptions {
|
||||||
export interface ApplicationDisplayOptions {}
|
allowNoteSelectionStatePersistence: boolean
|
||||||
|
allowMultipleSelection: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export interface ApplicationOptionalConfiguratioOptions {
|
export interface ApplicationOptionalConfiguratioOptions {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AnyKeyParamsContent } from '@standardnotes/common'
|
import { AnyKeyParamsContent, KeyParamsContent004 } from '@standardnotes/common'
|
||||||
import { EncryptedPayload, EncryptedTransferPayload, isErrorDecryptingPayload } from '@standardnotes/models'
|
import { EncryptedPayload, EncryptedTransferPayload, isErrorDecryptingPayload } from '@standardnotes/models'
|
||||||
import { PreviousSnjsVersion1_0_0, PreviousSnjsVersion2_0_0, SnjsVersion } from '../Version'
|
import { PreviousSnjsVersion1_0_0, PreviousSnjsVersion2_0_0, SnjsVersion } from '../Version'
|
||||||
import { Migration } from '@Lib/Migrations/Migration'
|
import { Migration } from '@Lib/Migrations/Migration'
|
||||||
@@ -165,7 +165,7 @@ export class BaseMigration extends Migration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async repairMissingKeychain() {
|
private async repairMissingKeychain() {
|
||||||
const rawAccountParams = await this.reader.getAccountKeyParams()
|
const rawAccountParams = (await this.reader.getAccountKeyParams()) as AnyKeyParamsContent
|
||||||
|
|
||||||
/** Choose an item to decrypt against */
|
/** Choose an item to decrypt against */
|
||||||
const allItems = (
|
const allItems = (
|
||||||
@@ -196,14 +196,14 @@ export class BaseMigration extends Migration {
|
|||||||
ChallengeReason.Custom,
|
ChallengeReason.Custom,
|
||||||
false,
|
false,
|
||||||
KeychainRecoveryStrings.Title,
|
KeychainRecoveryStrings.Title,
|
||||||
KeychainRecoveryStrings.Text,
|
KeychainRecoveryStrings.Text((rawAccountParams as KeyParamsContent004).identifier),
|
||||||
)
|
)
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
this.services.challengeService.addChallengeObserver(challenge, {
|
this.services.challengeService.addChallengeObserver(challenge, {
|
||||||
onNonvalidatedSubmit: async (challengeResponse) => {
|
onNonvalidatedSubmit: async (challengeResponse) => {
|
||||||
const password = challengeResponse.values[0].value as string
|
const password = challengeResponse.values[0].value as string
|
||||||
const accountParams = this.services.protocolService.createKeyParams(rawAccountParams as AnyKeyParamsContent)
|
const accountParams = this.services.protocolService.createKeyParams(rawAccountParams)
|
||||||
const rootKey = await this.services.protocolService.computeRootKey(password, accountParams)
|
const rootKey = await this.services.protocolService.computeRootKey(password, accountParams)
|
||||||
|
|
||||||
/** TS can't detect we returned early above if itemToDecrypt is null */
|
/** TS can't detect we returned early above if itemToDecrypt is null */
|
||||||
|
|||||||
@@ -298,6 +298,9 @@ export class SNSyncService
|
|||||||
? chunks.fullEntries.remainingChunks
|
? chunks.fullEntries.remainingChunks
|
||||||
: chunks.keys.remainingChunks
|
: chunks.keys.remainingChunks
|
||||||
|
|
||||||
|
let chunkIndex = 0
|
||||||
|
const ChunkIndexOfContentTypePriorityItems = 0
|
||||||
|
|
||||||
for (const chunk of remainingChunks) {
|
for (const chunk of remainingChunks) {
|
||||||
const dbEntries = isChunkFullEntry(chunk)
|
const dbEntries = isChunkFullEntry(chunk)
|
||||||
? chunk.entries
|
? chunk.entries
|
||||||
@@ -314,7 +317,14 @@ export class SNSyncService
|
|||||||
.filter(isNotUndefined)
|
.filter(isNotUndefined)
|
||||||
|
|
||||||
await this.processPayloadBatch(payloads, totalProcessedCount, payloadCount)
|
await this.processPayloadBatch(payloads, totalProcessedCount, payloadCount)
|
||||||
|
|
||||||
|
const shouldSleepOnlyAfterFirstRegularBatch = chunkIndex > ChunkIndexOfContentTypePriorityItems
|
||||||
|
if (shouldSleepOnlyAfterFirstRegularBatch) {
|
||||||
|
await sleep(this.options.sleepBetweenBatches, false, 'Sleeping to allow interface to update')
|
||||||
|
}
|
||||||
|
|
||||||
totalProcessedCount += payloads.length
|
totalProcessedCount += payloads.length
|
||||||
|
chunkIndex++
|
||||||
}
|
}
|
||||||
|
|
||||||
this.databaseLoaded = true
|
this.databaseLoaded = true
|
||||||
@@ -353,8 +363,6 @@ export class SNSyncService
|
|||||||
if (currentPosition != undefined && payloadCount != undefined) {
|
if (currentPosition != undefined && payloadCount != undefined) {
|
||||||
this.opStatus.setDatabaseLoadStatus(currentPosition, payloadCount, false)
|
this.opStatus.setDatabaseLoadStatus(currentPosition, payloadCount, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
await sleep(1, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private setLastSyncToken(token: string) {
|
private setLastSyncToken(token: string) {
|
||||||
|
|||||||
@@ -20,16 +20,19 @@ describe('application instances', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('two distinct applications should not share model manager state', async () => {
|
it('two distinct applications should not share model manager state', async () => {
|
||||||
const app1 = await Factory.createAndInitializeApplication('app1')
|
const context1 = await Factory.createAppContext({ identifier: 'app1' })
|
||||||
const app2 = await Factory.createAndInitializeApplication('app2')
|
const context2 = await Factory.createAppContext({ identifier: 'app2' })
|
||||||
expect(app1.payloadManager).to.equal(app1.payloadManager)
|
await Promise.all([context1.launch(), context2.launch()])
|
||||||
expect(app1.payloadManager).to.not.equal(app2.payloadManager)
|
|
||||||
|
|
||||||
await Factory.createMappedNote(app1)
|
expect(context1.application.payloadManager).to.equal(context1.application.payloadManager)
|
||||||
expect(app1.itemManager.items.length).length.to.equal(BaseItemCounts.DefaultItems + 1)
|
expect(context1.application.payloadManager).to.not.equal(context2.application.payloadManager)
|
||||||
expect(app2.itemManager.items.length).to.equal(BaseItemCounts.DefaultItems)
|
|
||||||
await Factory.safeDeinit(app1)
|
await Factory.createMappedNote(context1.application)
|
||||||
await Factory.safeDeinit(app2)
|
expect(context1.application.itemManager.items.length).length.to.equal(BaseItemCounts.DefaultItems + 1)
|
||||||
|
expect(context2.application.itemManager.items.length).to.equal(BaseItemCounts.DefaultItems)
|
||||||
|
|
||||||
|
await context1.deinit()
|
||||||
|
await context2.deinit()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('two distinct applications should not share storage manager state', async () => {
|
it('two distinct applications should not share storage manager state', async () => {
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ describe('basic auth', function () {
|
|||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
localStorage.clear()
|
localStorage.clear()
|
||||||
this.expectedItemCount = BaseItemCounts.DefaultItems
|
this.expectedItemCount = BaseItemCounts.DefaultItems
|
||||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
this.context = await Factory.createAppContext()
|
||||||
|
await this.context.launch()
|
||||||
|
this.application = this.context.application
|
||||||
this.email = UuidGenerator.GenerateUuid()
|
this.email = UuidGenerator.GenerateUuid()
|
||||||
this.password = UuidGenerator.GenerateUuid()
|
this.password = UuidGenerator.GenerateUuid()
|
||||||
})
|
})
|
||||||
@@ -402,7 +404,7 @@ describe('basic auth', function () {
|
|||||||
await this.application.syncService.markAllItemsAsNeedingSyncAndPersist()
|
await this.application.syncService.markAllItemsAsNeedingSyncAndPersist()
|
||||||
await this.application.syncService.sync(syncOptions)
|
await this.application.syncService.sync(syncOptions)
|
||||||
|
|
||||||
this.application = await Factory.signOutApplicationAndReturnNew(this.application)
|
this.application = await this.context.signout()
|
||||||
|
|
||||||
expect(this.application.itemManager.items.length).to.equal(BaseItemCounts.DefaultItems)
|
expect(this.application.itemManager.items.length).to.equal(BaseItemCounts.DefaultItems)
|
||||||
expect(this.application.payloadManager.invalidPayloads.length).to.equal(0)
|
expect(this.application.payloadManager.invalidPayloads.length).to.equal(0)
|
||||||
|
|||||||
@@ -163,6 +163,13 @@ export class AppContext {
|
|||||||
return newApplication
|
return newApplication
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async signout() {
|
||||||
|
await this.application.user.signOut()
|
||||||
|
await this.initialize()
|
||||||
|
await this.launch()
|
||||||
|
return this.application
|
||||||
|
}
|
||||||
|
|
||||||
syncWithIntegrityCheck() {
|
syncWithIntegrityCheck() {
|
||||||
return this.application.sync.sync({ checkIntegrity: true, awaitAll: true })
|
return this.application.sync.sync({ checkIntegrity: true, awaitAll: true })
|
||||||
}
|
}
|
||||||
@@ -189,11 +196,36 @@ export class AppContext {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
awaitUserPrefsSingletonCreation() {
|
||||||
|
const preferences = this.application.preferencesService.preferences
|
||||||
|
if (preferences) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let didCompleteRelevantSync = false
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.application.syncService.addEventObserver((eventName, data) => {
|
||||||
|
if (!didCompleteRelevantSync) {
|
||||||
|
if (data?.savedPayloads) {
|
||||||
|
const matching = data.savedPayloads.find((p) => {
|
||||||
|
return p.content_type === ContentType.UserPrefs
|
||||||
|
})
|
||||||
|
if (matching) {
|
||||||
|
didCompleteRelevantSync = true
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async launch({ awaitDatabaseLoad = true, receiveChallenge } = { awaitDatabaseLoad: true }) {
|
async launch({ awaitDatabaseLoad = true, receiveChallenge } = { awaitDatabaseLoad: true }) {
|
||||||
await this.application.prepareForLaunch({
|
await this.application.prepareForLaunch({
|
||||||
receiveChallenge: receiveChallenge || this.handleChallenge,
|
receiveChallenge: receiveChallenge || this.handleChallenge,
|
||||||
})
|
})
|
||||||
await this.application.launch(awaitDatabaseLoad)
|
await this.application.launch(awaitDatabaseLoad)
|
||||||
|
await this.awaitUserPrefsSingletonCreation()
|
||||||
}
|
}
|
||||||
|
|
||||||
async deinit() {
|
async deinit() {
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ describe('app models', () => {
|
|||||||
|
|
||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
this.expectedItemCount = BaseItemCounts.DefaultItems
|
this.expectedItemCount = BaseItemCounts.DefaultItems
|
||||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
this.context = await Factory.createAppContext()
|
||||||
|
this.application = this.context.application
|
||||||
|
await this.context.launch()
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(async function () {
|
afterEach(async function () {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ describe('importing', function () {
|
|||||||
let application
|
let application
|
||||||
let email
|
let email
|
||||||
let password
|
let password
|
||||||
|
let context
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
localStorage.clear()
|
localStorage.clear()
|
||||||
@@ -20,11 +21,16 @@ describe('importing', function () {
|
|||||||
|
|
||||||
const setup = async ({ fakeCrypto }) => {
|
const setup = async ({ fakeCrypto }) => {
|
||||||
expectedItemCount = BaseItemCounts.DefaultItems
|
expectedItemCount = BaseItemCounts.DefaultItems
|
||||||
|
|
||||||
if (fakeCrypto) {
|
if (fakeCrypto) {
|
||||||
application = await Factory.createInitAppWithFakeCrypto()
|
context = await Factory.createAppContext()
|
||||||
} else {
|
} else {
|
||||||
application = await Factory.createInitAppWithRealCrypto()
|
context = await Factory.createAppContextWithRealCrypto()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await context.launch()
|
||||||
|
application = context.application
|
||||||
|
|
||||||
email = UuidGenerator.GenerateUuid()
|
email = UuidGenerator.GenerateUuid()
|
||||||
password = UuidGenerator.GenerateUuid()
|
password = UuidGenerator.GenerateUuid()
|
||||||
Factory.handlePasswordChallenges(application, password)
|
Factory.handlePasswordChallenges(application, password)
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ const expect = chai.expect
|
|||||||
describe('model manager mapping', () => {
|
describe('model manager mapping', () => {
|
||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
this.expectedItemCount = BaseItemCounts.DefaultItems
|
this.expectedItemCount = BaseItemCounts.DefaultItems
|
||||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
this.context = await Factory.createAppContext()
|
||||||
|
await this.context.launch()
|
||||||
|
this.application = this.context.application
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(async function () {
|
afterEach(async function () {
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ describe('notes and tags', () => {
|
|||||||
|
|
||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
this.expectedItemCount = BaseItemCounts.DefaultItems
|
this.expectedItemCount = BaseItemCounts.DefaultItems
|
||||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
this.context = await Factory.createAppContext()
|
||||||
|
await this.context.launch()
|
||||||
|
this.application = this.context.application
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(async function () {
|
afterEach(async function () {
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ const expect = chai.expect
|
|||||||
|
|
||||||
describe('tags as folders', () => {
|
describe('tags as folders', () => {
|
||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
this.context = await Factory.createAppContext()
|
||||||
|
await this.context.launch()
|
||||||
|
this.application = this.context.application
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(async function () {
|
afterEach(async function () {
|
||||||
|
|||||||
@@ -7,7 +7,11 @@ const expect = chai.expect
|
|||||||
describe('preferences', function () {
|
describe('preferences', function () {
|
||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
localStorage.clear()
|
localStorage.clear()
|
||||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
|
||||||
|
this.context = await Factory.createAppContext()
|
||||||
|
await this.context.launch()
|
||||||
|
this.application = this.context.application
|
||||||
|
|
||||||
this.email = UuidGenerator.GenerateUuid()
|
this.email = UuidGenerator.GenerateUuid()
|
||||||
this.password = UuidGenerator.GenerateUuid()
|
this.password = UuidGenerator.GenerateUuid()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -31,7 +31,11 @@ describe('singletons', function () {
|
|||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
localStorage.clear()
|
localStorage.clear()
|
||||||
this.expectedItemCount = BaseItemCounts.DefaultItems
|
this.expectedItemCount = BaseItemCounts.DefaultItems
|
||||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
|
||||||
|
this.context = await Factory.createAppContext()
|
||||||
|
await this.context.launch()
|
||||||
|
this.application = this.context.application
|
||||||
|
|
||||||
this.email = UuidGenerator.GenerateUuid()
|
this.email = UuidGenerator.GenerateUuid()
|
||||||
this.password = UuidGenerator.GenerateUuid()
|
this.password = UuidGenerator.GenerateUuid()
|
||||||
this.registerUser = async () => {
|
this.registerUser = async () => {
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ describe('storage manager', function () {
|
|||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
localStorage.clear()
|
localStorage.clear()
|
||||||
this.expectedKeyCount = BASE_KEY_COUNT
|
this.expectedKeyCount = BASE_KEY_COUNT
|
||||||
this.application = await Factory.createInitAppWithFakeCrypto(Environment.Mobile)
|
this.context = await Factory.createAppContext()
|
||||||
|
await this.context.launch()
|
||||||
|
this.application = this.context.application
|
||||||
this.email = UuidGenerator.GenerateUuid()
|
this.email = UuidGenerator.GenerateUuid()
|
||||||
this.password = UuidGenerator.GenerateUuid()
|
this.password = UuidGenerator.GenerateUuid()
|
||||||
})
|
})
|
||||||
@@ -221,7 +223,8 @@ describe('storage manager', function () {
|
|||||||
expect(decrypted.content).to.be.an.instanceof(Object)
|
expect(decrypted.content).to.be.an.instanceof(Object)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('disabling storage encryption should store items without encryption', async function () {
|
/** @TODO: Storage encryption disable is no longer available, remove tests and associated functionality */
|
||||||
|
it.skip('disabling storage encryption should store items without encryption', async function () {
|
||||||
await Factory.registerUserToApplication({
|
await Factory.registerUserToApplication({
|
||||||
application: this.application,
|
application: this.application,
|
||||||
email: this.email,
|
email: this.email,
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ describe('offline syncing', () => {
|
|||||||
|
|
||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
this.expectedItemCount = BaseItemCounts.DefaultItems
|
this.expectedItemCount = BaseItemCounts.DefaultItems
|
||||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
this.context = await Factory.createAppContext()
|
||||||
|
await this.context.launch()
|
||||||
|
this.application = this.context.application
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(async function () {
|
afterEach(async function () {
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ describe('online syncing', function () {
|
|||||||
|
|
||||||
await this.application.sync.sync(syncOptions)
|
await this.application.sync.sync(syncOptions)
|
||||||
|
|
||||||
this.application = await Factory.signOutApplicationAndReturnNew(this.application)
|
this.application = await this.context.signout()
|
||||||
|
|
||||||
expect(this.application.itemManager.items.length).to.equal(BaseItemCounts.DefaultItems)
|
expect(this.application.itemManager.items.length).to.equal(BaseItemCounts.DefaultItems)
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,11 @@ const expect = chai.expect
|
|||||||
describe('upgrading', () => {
|
describe('upgrading', () => {
|
||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
localStorage.clear()
|
localStorage.clear()
|
||||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
|
||||||
|
this.context = await Factory.createAppContext()
|
||||||
|
await this.context.launch()
|
||||||
|
|
||||||
|
this.application = this.context.application
|
||||||
this.email = UuidGenerator.GenerateUuid()
|
this.email = UuidGenerator.GenerateUuid()
|
||||||
this.password = UuidGenerator.GenerateUuid()
|
this.password = UuidGenerator.GenerateUuid()
|
||||||
this.passcode = '1234'
|
this.passcode = '1234'
|
||||||
@@ -170,7 +174,7 @@ describe('upgrading', () => {
|
|||||||
/** Delete default items key that is created on launch */
|
/** Delete default items key that is created on launch */
|
||||||
const itemsKey = await this.application.protocolService.getSureDefaultItemsKey()
|
const itemsKey = await this.application.protocolService.getSureDefaultItemsKey()
|
||||||
await this.application.itemManager.setItemToBeDeleted(itemsKey)
|
await this.application.itemManager.setItemToBeDeleted(itemsKey)
|
||||||
expect(this.application.itemManager.getDisplayableItemsKeys().length).to.equal(0)
|
expect(Uuids(this.application.itemManager.getDisplayableItemsKeys()).includes(itemsKey.uuid)).to.equal(false)
|
||||||
|
|
||||||
Factory.createMappedNote(this.application)
|
Factory.createMappedNote(this.application)
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.3.16](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.3.15...@standardnotes/toast@1.3.16) (2023-01-04)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Fixed issue where file upload toast would close before completion if you switched windows ([30dda73](https://github.com/standardnotes/app/commit/30dda73e9078bbccf91c3f43307250c84a17e8bd))
|
||||||
|
|
||||||
## [1.3.15](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.3.14...@standardnotes/toast@1.3.15) (2022-12-29)
|
## [1.3.15](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.3.14...@standardnotes/toast@1.3.15) (2022-12-29)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/toast
|
**Note:** Version bump only for package @standardnotes/toast
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/toast",
|
"name": "@standardnotes/toast",
|
||||||
"version": "1.3.15",
|
"version": "1.3.16",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "./src/index.ts",
|
"main": "./src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.22.3](https://github.com/standardnotes/app/compare/@standardnotes/ui-services@1.22.2...@standardnotes/ui-services@1.22.3) (2023-01-04)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/ui-services
|
||||||
|
|
||||||
## [1.22.2](https://github.com/standardnotes/app/compare/@standardnotes/ui-services@1.22.1...@standardnotes/ui-services@1.22.2) (2023-01-03)
|
## [1.22.2](https://github.com/standardnotes/app/compare/@standardnotes/ui-services@1.22.1...@standardnotes/ui-services@1.22.2) (2023-01-03)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/ui-services
|
**Note:** Version bump only for package @standardnotes/ui-services
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/ui-services",
|
"name": "@standardnotes/ui-services",
|
||||||
"version": "1.22.2",
|
"version": "1.22.3",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0 <17.0.0"
|
"node": ">=16.0.0 <17.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -519,9 +519,9 @@ export function truncateHexString(string: string, desiredBits: number) {
|
|||||||
* When awaited, this function allows code execution to pause for a set time.
|
* When awaited, this function allows code execution to pause for a set time.
|
||||||
* Should be used primarily for testing.
|
* Should be used primarily for testing.
|
||||||
*/
|
*/
|
||||||
export async function sleep(milliseconds: number, warn = true): Promise<void> {
|
export async function sleep(milliseconds: number, warn = true, desc = ''): Promise<void> {
|
||||||
if (warn) {
|
if (warn) {
|
||||||
console.warn(`Sleeping for ${milliseconds}ms`)
|
console.warn(`Sleeping for ${milliseconds}ms ${desc}`)
|
||||||
}
|
}
|
||||||
return new Promise<void>((resolve) => {
|
return new Promise<void>((resolve) => {
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [3.132.3](https://github.com/standardnotes/app/compare/@standardnotes/web@3.132.2...@standardnotes/web@3.132.3) (2023-01-04)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/web
|
||||||
|
|
||||||
## [3.132.2](https://github.com/standardnotes/app/compare/@standardnotes/web@3.132.0...@standardnotes/web@3.132.2) (2023-01-04)
|
## [3.132.2](https://github.com/standardnotes/app/compare/@standardnotes/web@3.132.0...@standardnotes/web@3.132.2) (2023-01-04)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/web
|
**Note:** Version bump only for package @standardnotes/web
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
{
|
{
|
||||||
"versions": [
|
"versions": [
|
||||||
|
{
|
||||||
|
"version": "3.132.3",
|
||||||
|
"title": "[3.132.3](https://github.com/standardnotes/app/compare/@standardnotes/web@3.132.2...@standardnotes/web@3.132.3) (2023-01-04)",
|
||||||
|
"date": null,
|
||||||
|
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||||
|
"parsed": {
|
||||||
|
"_": [
|
||||||
|
"Note: Version bump only for package @standardnotes/web"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "3.132.2",
|
"version": "3.132.2",
|
||||||
"title": "[3.132.2](https://github.com/standardnotes/app/compare/@standardnotes/web@3.132.0...@standardnotes/web@3.132.2) (2023-01-04)",
|
"title": "[3.132.2](https://github.com/standardnotes/app/compare/@standardnotes/web@3.132.0...@standardnotes/web@3.132.2) (2023-01-04)",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/web",
|
"name": "@standardnotes/web",
|
||||||
"version": "3.132.2",
|
"version": "3.132.3",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"main": "dist/app.js",
|
"main": "dist/app.js",
|
||||||
"author": "Standard Notes.",
|
"author": "Standard Notes.",
|
||||||
|
|||||||
@@ -78,7 +78,11 @@ export class WebApplication extends SNApplication implements WebApplicationInter
|
|||||||
appVersion: deviceInterface.appVersion,
|
appVersion: deviceInterface.appVersion,
|
||||||
webSocketUrl: webSocketUrl,
|
webSocketUrl: webSocketUrl,
|
||||||
loadBatchSize:
|
loadBatchSize:
|
||||||
deviceInterface.environment === Environment.Mobile ? 100 : ApplicationOptionsDefaults.loadBatchSize,
|
deviceInterface.environment === Environment.Mobile ? 250 : ApplicationOptionsDefaults.loadBatchSize,
|
||||||
|
sleepBetweenBatches:
|
||||||
|
deviceInterface.environment === Environment.Mobile ? 250 : ApplicationOptionsDefaults.sleepBetweenBatches,
|
||||||
|
allowMultipleSelection: deviceInterface.environment !== Environment.Mobile,
|
||||||
|
allowNoteSelectionStatePersistence: deviceInterface.environment !== Environment.Mobile,
|
||||||
})
|
})
|
||||||
|
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import { ApplicationGroup } from '@/Application/ApplicationGroup'
|
|||||||
import { formatLastSyncDate } from '@/Utils/DateUtils'
|
import { formatLastSyncDate } from '@/Utils/DateUtils'
|
||||||
import Spinner from '@/Components/Spinner/Spinner'
|
import Spinner from '@/Components/Spinner/Spinner'
|
||||||
import { MenuItemIconSize } from '@/Constants/TailwindClassNames'
|
import { MenuItemIconSize } from '@/Constants/TailwindClassNames'
|
||||||
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
viewControllerManager: ViewControllerManager
|
viewControllerManager: ViewControllerManager
|
||||||
@@ -85,22 +84,9 @@ const GeneralAccountMenu: FunctionComponent<Props> = ({
|
|||||||
setMenuPane(AccountMenuPane.SignIn)
|
setMenuPane(AccountMenuPane.SignIn)
|
||||||
}, [setMenuPane])
|
}, [setMenuPane])
|
||||||
|
|
||||||
const openFileSend = useCallback(() => {
|
|
||||||
const link = 'https://filesend.standardnotes.com/'
|
|
||||||
|
|
||||||
if (application.isNativeMobileWeb()) {
|
|
||||||
application.mobileDevice().openUrl(link)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
window.open(link, '_blank')
|
|
||||||
}, [application])
|
|
||||||
|
|
||||||
const CREATE_ACCOUNT_INDEX = 1
|
const CREATE_ACCOUNT_INDEX = 1
|
||||||
const SWITCHER_INDEX = 0
|
const SWITCHER_INDEX = 0
|
||||||
|
|
||||||
const isMobileScreen = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mt-1 mb-1 flex items-center justify-between px-3">
|
<div className="mt-1 mb-1 flex items-center justify-between px-3">
|
||||||
@@ -186,12 +172,6 @@ const GeneralAccountMenu: FunctionComponent<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
<span className="text-neutral">v{application.version}</span>
|
<span className="text-neutral">v{application.version}</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{isMobileScreen && (
|
|
||||||
<MenuItem onClick={openFileSend}>
|
|
||||||
<Icon type="open-in" className={iconClassName} />
|
|
||||||
Open FileSend
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
<MenuItem onClick={() => viewControllerManager.isImportModalVisible.set(true)}>
|
<MenuItem onClick={() => viewControllerManager.isImportModalVisible.set(true)}>
|
||||||
<Icon type="archive" className={iconClassName} />
|
<Icon type="archive" className={iconClassName} />
|
||||||
Import
|
Import
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
void application.sessions.populateSessionFromDemoShareToken(token)
|
void application.user.populateSessionFromDemoShareToken(token)
|
||||||
}, [application])
|
}, [application])
|
||||||
|
|
||||||
const onAppLaunch = useCallback(() => {
|
const onAppLaunch = useCallback(() => {
|
||||||
|
|||||||
@@ -11,15 +11,17 @@ import { ItemListController } from './ItemListController'
|
|||||||
import { ItemsReloadSource } from './ItemsReloadSource'
|
import { ItemsReloadSource } from './ItemsReloadSource'
|
||||||
|
|
||||||
describe('item list controller', () => {
|
describe('item list controller', () => {
|
||||||
|
let application: WebApplication
|
||||||
let controller: ItemListController
|
let controller: ItemListController
|
||||||
let navigationController: NavigationController
|
let navigationController: NavigationController
|
||||||
let selectionController: SelectedItemsController
|
let selectionController: SelectedItemsController
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const application = {} as jest.Mocked<WebApplication>
|
application = {} as jest.Mocked<WebApplication>
|
||||||
application.streamItems = jest.fn()
|
application.streamItems = jest.fn()
|
||||||
application.addEventObserver = jest.fn()
|
application.addEventObserver = jest.fn()
|
||||||
application.addWebEventObserver = jest.fn()
|
application.addWebEventObserver = jest.fn()
|
||||||
|
application.isNativeMobileWeb = jest.fn().mockReturnValue(false)
|
||||||
|
|
||||||
navigationController = {} as jest.Mocked<NavigationController>
|
navigationController = {} as jest.Mocked<NavigationController>
|
||||||
selectionController = {} as jest.Mocked<SelectedItemsController>
|
selectionController = {} as jest.Mocked<SelectedItemsController>
|
||||||
@@ -50,6 +52,12 @@ describe('item list controller', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should return false is platform is native mobile web', () => {
|
||||||
|
application.isNativeMobileWeb = jest.fn().mockReturnValue(true)
|
||||||
|
|
||||||
|
expect(controller.shouldSelectFirstItem(ItemsReloadSource.TagChange)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
it('should return false first item is file', () => {
|
it('should return false first item is file', () => {
|
||||||
controller.getFirstNonProtectedItem = jest.fn().mockReturnValue({
|
controller.getFirstNonProtectedItem = jest.fn().mockReturnValue({
|
||||||
content_type: ContentType.File,
|
content_type: ContentType.File,
|
||||||
|
|||||||
@@ -414,6 +414,10 @@ export class ItemListController extends AbstractViewController implements Intern
|
|||||||
}
|
}
|
||||||
|
|
||||||
shouldSelectFirstItem = (itemsReloadSource: ItemsReloadSource) => {
|
shouldSelectFirstItem = (itemsReloadSource: ItemsReloadSource) => {
|
||||||
|
if (this.application.isNativeMobileWeb()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
const item = this.getFirstNonProtectedItem()
|
const item = this.getFirstNonProtectedItem()
|
||||||
if (item && isFile(item)) {
|
if (item && isFile(item)) {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -196,10 +196,13 @@ export class NavigationController
|
|||||||
}
|
}
|
||||||
|
|
||||||
hydrateFromPersistedValue = (state: NavigationControllerPersistableValue | undefined) => {
|
hydrateFromPersistedValue = (state: NavigationControllerPersistableValue | undefined) => {
|
||||||
if (!state) {
|
const uuidsToPreventHydrationOf: string[] = [SystemViewId.Files]
|
||||||
|
|
||||||
|
if (!state || uuidsToPreventHydrationOf.includes(state.selectedTagUuid)) {
|
||||||
void this.selectHomeNavigationView()
|
void this.selectHomeNavigationView()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.selectedTagUuid) {
|
if (state.selectedTagUuid) {
|
||||||
this.selectedUuid = state.selectedTagUuid
|
this.selectedUuid = state.selectedTagUuid
|
||||||
this.selectHydratedTagOrDefault()
|
this.selectHydratedTagOrDefault()
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
InternalEventBus,
|
InternalEventBus,
|
||||||
isFile,
|
isFile,
|
||||||
Uuids,
|
Uuids,
|
||||||
|
isNote,
|
||||||
} from '@standardnotes/snjs'
|
} from '@standardnotes/snjs'
|
||||||
import { SelectionControllerPersistableValue } from '@standardnotes/ui-services'
|
import { SelectionControllerPersistableValue } from '@standardnotes/ui-services'
|
||||||
import { action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'
|
import { action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'
|
||||||
@@ -77,8 +78,14 @@ export class SelectedItemsController
|
|||||||
if (!state) {
|
if (!state) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.selectedUuids.size && state.selectedUuids.length > 0) {
|
if (!this.selectedUuids.size && state.selectedUuids.length > 0) {
|
||||||
void this.selectUuids(state.selectedUuids)
|
if (!this.application.options.allowNoteSelectionStatePersistence) {
|
||||||
|
const items = this.application.items.findItems(state.selectedUuids).filter((item) => !isNote(item))
|
||||||
|
void this.selectUuids(Uuids(items))
|
||||||
|
} else {
|
||||||
|
void this.selectUuids(state.selectedUuids)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,20 +271,22 @@ export class SelectedItemsController
|
|||||||
|
|
||||||
log(LoggingDomain.Selection, 'Select item', item.uuid)
|
log(LoggingDomain.Selection, 'Select item', item.uuid)
|
||||||
|
|
||||||
|
const supportsMultipleSelection = this.application.options.allowMultipleSelection
|
||||||
const hasMeta = this.keyboardService.activeModifiers.has(KeyboardModifier.Meta)
|
const hasMeta = this.keyboardService.activeModifiers.has(KeyboardModifier.Meta)
|
||||||
const hasCtrl = this.keyboardService.activeModifiers.has(KeyboardModifier.Ctrl)
|
const hasCtrl = this.keyboardService.activeModifiers.has(KeyboardModifier.Ctrl)
|
||||||
const hasShift = this.keyboardService.activeModifiers.has(KeyboardModifier.Shift)
|
const hasShift = this.keyboardService.activeModifiers.has(KeyboardModifier.Shift)
|
||||||
const hasMoreThanOneSelected = this.selectedItemsCount > 1
|
const hasMoreThanOneSelected = this.selectedItemsCount > 1
|
||||||
const isAuthorizedForAccess = await this.application.protections.authorizeItemAccess(item)
|
const isAuthorizedForAccess = await this.application.protections.authorizeItemAccess(item)
|
||||||
|
|
||||||
if (userTriggered && (hasMeta || hasCtrl)) {
|
if (supportsMultipleSelection && userTriggered && (hasMeta || hasCtrl)) {
|
||||||
if (this.selectedUuids.has(uuid) && hasMoreThanOneSelected) {
|
if (this.selectedUuids.has(uuid) && hasMoreThanOneSelected) {
|
||||||
this.removeSelectedItem(uuid)
|
this.removeSelectedItem(uuid)
|
||||||
} else if (isAuthorizedForAccess) {
|
} else if (isAuthorizedForAccess) {
|
||||||
this.setSelectedUuids(this.selectedUuids.add(uuid))
|
this.selectedUuids.add(uuid)
|
||||||
|
this.setSelectedUuids(this.selectedUuids)
|
||||||
this.lastSelectedItem = item
|
this.lastSelectedItem = item
|
||||||
}
|
}
|
||||||
} else if (userTriggered && hasShift) {
|
} else if (supportsMultipleSelection && userTriggered && hasShift) {
|
||||||
await this.selectItemsRange({ selectedItem: item })
|
await this.selectItemsRange({ selectedItem: item })
|
||||||
} else {
|
} else {
|
||||||
const shouldSelectNote = hasMoreThanOneSelected || !this.selectedUuids.has(uuid)
|
const shouldSelectNote = hasMoreThanOneSelected || !this.selectedUuids.has(uuid)
|
||||||
|
|||||||
Reference in New Issue
Block a user