mirror of
https://github.com/standardnotes/app
synced 2026-09-16 09:45:56 -04:00
Compare commits
31
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac28b29d65 | ||
|
|
18235751d4 | ||
|
|
cbd0063926 | ||
|
|
3b048a31aa | ||
|
|
4a2dc1ef7a | ||
|
|
21f725bb2a | ||
|
|
173bacdfa3 | ||
|
|
c503d781ae | ||
|
|
9fb85cd77a | ||
|
|
bd40a49484 | ||
|
|
4a1612c7dc | ||
|
|
d3ed4391b7 | ||
|
|
856e499d89 | ||
|
|
4f0805d5b0 | ||
|
|
401a35f4db | ||
|
|
83b484002c | ||
|
|
30310dbb45 | ||
|
|
c6e3580678 | ||
|
|
ea285a36d8 | ||
|
|
1addaf4c39 | ||
|
|
92bba942f1 | ||
|
|
f71e6fe703 | ||
|
|
143b528b73 | ||
|
|
5d202a823e | ||
|
|
8d7d577b1c | ||
|
|
76f23e13ec | ||
|
|
4f3b258363 | ||
|
|
9cc3304741 | ||
|
|
4432f1cb4c | ||
|
|
6c47f95748 | ||
|
|
57fab4f670 |
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -70,6 +70,8 @@ jobs:
|
||||
packages/mobile/android/app/build/outputs/apk/prod/release/app-prod-release.apk
|
||||
|
||||
ios:
|
||||
env:
|
||||
ITMSTRANSPORTER_FORCE_ITMS_PACKAGE_UPLOAD: true
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/mobile
|
||||
|
||||
@@ -49,6 +49,8 @@ jobs:
|
||||
with:
|
||||
path: packages/mobile
|
||||
ios:
|
||||
env:
|
||||
ITMSTRANSPORTER_FORCE_ITMS_PACKAGE_UPLOAD: true
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/mobile
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.17.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api",
|
||||
"version": "1.16.5",
|
||||
"version": "1.17.2",
|
||||
"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,3 +1 @@
|
||||
export type SubscriptionInviteAcceptResponseBody = {
|
||||
success: boolean
|
||||
}
|
||||
export type SubscriptionInviteAcceptResponseBody = { success: true } | { success: false; message: string }
|
||||
|
||||
@@ -3,6 +3,50 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.23.236](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.235](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.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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/desktop",
|
||||
"main": "./app/dist/index.js",
|
||||
"version": "3.23.225",
|
||||
"version": "3.23.236",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
|
||||
@@ -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.18.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/encryption",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -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.24.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/filepicker",
|
||||
"version": "1.24.6",
|
||||
"version": "1.24.7",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -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.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files",
|
||||
"version": "1.11.6",
|
||||
"version": "1.11.7",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,50 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.43.23](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.22](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.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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/mobile",
|
||||
"version": "3.43.12",
|
||||
"version": "3.43.23",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.30.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
|
||||
|
||||
### Features
|
||||
|
||||
* ability to favorite tags + customize icon ([#1858](https://github.com/standardnotes/app/issues/1858)) ([cbd0063](https://github.com/standardnotes/app/commit/cbd0063926a416c23225242a4a1796712b6b224e))
|
||||
|
||||
## [1.29.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/models",
|
||||
"version": "1.29.1",
|
||||
"version": "1.30.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -25,6 +25,7 @@
|
||||
"@types/jest": "^28.1.5",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.0",
|
||||
"eslint": "*",
|
||||
"eslint-plugin-prettier": "*",
|
||||
"jest": "^28.1.2",
|
||||
"ts-jest": "^28.0.5",
|
||||
|
||||
@@ -1,41 +1,33 @@
|
||||
import { DecryptedItem } from '../../Abstract/Item/Implementations/DecryptedItem'
|
||||
import { PredicateInterface, PredicateJsonForm } from '../../Runtime/Predicate/Interface'
|
||||
import { PredicateInterface } from '../../Runtime/Predicate/Interface'
|
||||
import { predicateFromJson } from '../../Runtime/Predicate/Generators'
|
||||
import { ItemContent } from '../../Abstract/Content/ItemContent'
|
||||
import { DecryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/DecryptedPayload'
|
||||
import { SystemViewId } from './SystemViewId'
|
||||
import { EmojiString, IconType } from '../../Utilities/Icon/IconType'
|
||||
import { SmartViewDefaultIconName, systemViewIcon } from './SmartViewIcons'
|
||||
import { SmartViewContent } from './SmartViewContent'
|
||||
|
||||
export const SMART_TAG_DSL_PREFIX = '!['
|
||||
|
||||
export enum SystemViewId {
|
||||
AllNotes = 'all-notes',
|
||||
Files = 'files',
|
||||
ArchivedNotes = 'archived-notes',
|
||||
TrashedNotes = 'trashed-notes',
|
||||
UntaggedNotes = 'untagged-notes',
|
||||
StarredNotes = 'starred-notes',
|
||||
}
|
||||
|
||||
export interface SmartViewContent extends ItemContent {
|
||||
title: string
|
||||
predicate: PredicateJsonForm
|
||||
}
|
||||
|
||||
export function isSystemView(view: SmartView): boolean {
|
||||
return Object.values(SystemViewId).includes(view.uuid as SystemViewId)
|
||||
}
|
||||
|
||||
/**
|
||||
* A tag that defines a predicate that consumers can use
|
||||
* to retrieve a dynamic list of items.
|
||||
*/
|
||||
export class SmartView extends DecryptedItem<SmartViewContent> {
|
||||
public readonly predicate!: PredicateInterface<DecryptedItem>
|
||||
public readonly title: string
|
||||
public readonly iconString: IconType | EmojiString
|
||||
|
||||
constructor(payload: DecryptedPayloadInterface<SmartViewContent>) {
|
||||
super(payload)
|
||||
this.title = String(this.content.title || '')
|
||||
|
||||
if (isSystemView(this)) {
|
||||
this.iconString = systemViewIcon(this.uuid as SystemViewId)
|
||||
} else {
|
||||
this.iconString = this.payload.content.iconString || SmartViewDefaultIconName
|
||||
}
|
||||
|
||||
try {
|
||||
this.predicate = this.content.predicate && predicateFromJson(this.content.predicate)
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { DecryptedPayload } from './../../Abstract/Payload/Implementations/DecryptedPayload'
|
||||
import { SNNote } from '../Note/Note'
|
||||
import { SmartViewContent, SmartView, SystemViewId } from './SmartView'
|
||||
import { SmartView } from './SmartView'
|
||||
import { SmartViewContent } from './SmartViewContent'
|
||||
import { SystemViewId } from './SystemViewId'
|
||||
import { ItemWithTags } from '../../Runtime/Display/Search/ItemWithTags'
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { FillItemContent } from '../../Abstract/Content/ItemContent'
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { TagContent } from './../Tag/TagContent'
|
||||
import { PredicateJsonForm } from '../../Runtime/Predicate/Interface'
|
||||
|
||||
export interface SmartViewContent extends TagContent {
|
||||
predicate: PredicateJsonForm
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { SystemViewId } from './SystemViewId'
|
||||
import { IconType } from '../../Utilities/Icon/IconType'
|
||||
|
||||
export const SmartViewIcons: Record<SystemViewId, IconType> = {
|
||||
[SystemViewId.AllNotes]: 'notes',
|
||||
[SystemViewId.Files]: 'folder',
|
||||
[SystemViewId.ArchivedNotes]: 'archive',
|
||||
[SystemViewId.TrashedNotes]: 'trash',
|
||||
[SystemViewId.UntaggedNotes]: 'hashtag-off',
|
||||
[SystemViewId.StarredNotes]: 'star-filled',
|
||||
}
|
||||
|
||||
export function systemViewIcon(id: SystemViewId): IconType {
|
||||
return SmartViewIcons[id]
|
||||
}
|
||||
|
||||
export const SmartViewDefaultIconName: IconType = 'restore'
|
||||
@@ -0,0 +1,8 @@
|
||||
export enum SystemViewId {
|
||||
AllNotes = 'all-notes',
|
||||
Files = 'files',
|
||||
ArchivedNotes = 'archived-notes',
|
||||
TrashedNotes = 'trashed-notes',
|
||||
UntaggedNotes = 'untagged-notes',
|
||||
StarredNotes = 'starred-notes',
|
||||
}
|
||||
@@ -1,2 +1,4 @@
|
||||
export * from './SmartView'
|
||||
export * from './SmartViewBuilder'
|
||||
export * from './SystemViewId'
|
||||
export * from './SmartViewContent'
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { PayloadSource } from './../../Abstract/Payload/Types/PayloadSource'
|
||||
import { DecryptedPayload } from './../../Abstract/Payload/Implementations/DecryptedPayload'
|
||||
import { SNTag, TagContent } from './Tag'
|
||||
import { SNTag } from './Tag'
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { FillItemContent } from '../../Abstract/Content/ItemContent'
|
||||
import { ContentReference } from '../../Abstract/Reference/ContentReference'
|
||||
import { PayloadTimestampDefaults } from '../../Abstract/Payload'
|
||||
import { TagContent } from './TagContent'
|
||||
|
||||
const randUuid = () => String(Math.random())
|
||||
|
||||
|
||||
@@ -1,32 +1,28 @@
|
||||
import { VectorIconNameOrEmoji, IconType } from './../../Utilities/Icon/IconType'
|
||||
import { ContentType, Uuid } from '@standardnotes/common'
|
||||
import { DecryptedItem } from '../../Abstract/Item/Implementations/DecryptedItem'
|
||||
import { ItemInterface } from '../../Abstract/Item/Interfaces/ItemInterface'
|
||||
import { ItemContent } from '../../Abstract/Content/ItemContent'
|
||||
import { ContentReference } from '../../Abstract/Reference/ContentReference'
|
||||
import { isTagToParentTagReference } from '../../Abstract/Reference/Functions'
|
||||
import { DecryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/DecryptedPayload'
|
||||
import { TagContent, TagContentSpecialized } from './TagContent'
|
||||
|
||||
export const TagFolderDelimitter = '.'
|
||||
|
||||
interface TagInterface {
|
||||
title: string
|
||||
expanded: boolean
|
||||
}
|
||||
|
||||
export type TagContent = TagInterface & ItemContent
|
||||
export const DefaultTagIconName: IconType = 'hashtag'
|
||||
|
||||
export const isTag = (x: ItemInterface): x is SNTag => x.content_type === ContentType.Tag
|
||||
|
||||
export class SNTag extends DecryptedItem<TagContent> implements TagInterface {
|
||||
export class SNTag extends DecryptedItem<TagContent> implements TagContentSpecialized {
|
||||
public readonly title: string
|
||||
|
||||
/** Whether to render child tags in view hierarchy. Opposite of collapsed. */
|
||||
public readonly iconString: VectorIconNameOrEmoji
|
||||
public readonly expanded: boolean
|
||||
|
||||
constructor(payload: DecryptedPayloadInterface<TagContent>) {
|
||||
super(payload)
|
||||
this.title = this.payload.content.title || ''
|
||||
this.expanded = this.payload.content.expanded != undefined ? this.payload.content.expanded : true
|
||||
this.iconString = this.payload.content.iconString || DefaultTagIconName
|
||||
}
|
||||
|
||||
get noteReferences(): ContentReference[] {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { IconType } from './../../Utilities/Icon/IconType'
|
||||
import { ItemContent } from '../../Abstract/Content/ItemContent'
|
||||
import { EmojiString } from '../../Utilities/Icon/IconType'
|
||||
|
||||
export interface TagContentSpecialized {
|
||||
title: string
|
||||
expanded: boolean
|
||||
iconString: IconType | EmojiString
|
||||
}
|
||||
|
||||
export type TagContent = TagContentSpecialized & ItemContent
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { TagContent, SNTag } from './Tag'
|
||||
import { SNTag } from './Tag'
|
||||
import { TagContent } from './TagContent'
|
||||
import { FileItem } from '../File'
|
||||
import { SNNote } from '../Note'
|
||||
import { isTagToParentTagReference } from '../../Abstract/Reference/Functions'
|
||||
@@ -17,6 +18,10 @@ export class TagMutator extends DecryptedItemMutator<TagContent> {
|
||||
this.mutableContent.expanded = expanded
|
||||
}
|
||||
|
||||
set iconString(iconString: string) {
|
||||
this.mutableContent.iconString = iconString
|
||||
}
|
||||
|
||||
public makeChildOf(tag: SNTag): void {
|
||||
const references = this.immutableItem.references.filter((ref) => !isTagToParentTagReference(ref))
|
||||
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './Tag'
|
||||
export * from './TagMutator'
|
||||
export * from './TagContent'
|
||||
|
||||
+4
@@ -1,3 +1,7 @@
|
||||
export type VectorIconNameOrEmoji = EmojiString | IconType
|
||||
|
||||
export type EmojiString = string
|
||||
|
||||
export type IconType =
|
||||
| 'accessibility'
|
||||
| 'account-card-details-outline'
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TagContent } from './../../Syncable/Tag/Tag'
|
||||
import { TagContent } from './../../Syncable/Tag/TagContent'
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { FillItemContent, ItemContent } from '../../Abstract/Content/ItemContent'
|
||||
import { DecryptedPayload, PayloadSource, PayloadTimestampDefaults } from '../../Abstract/Payload'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export * from './Abstract/Component/ActionObserver'
|
||||
export * from './Abstract/Component/ComponentViewerEvent'
|
||||
export * from './Abstract/Component/ComponentMessage'
|
||||
export * from './Abstract/Component/ComponentEventObserver'
|
||||
export * from './Abstract/Component/ComponentMessage'
|
||||
export * from './Abstract/Component/ComponentViewerEvent'
|
||||
export * from './Abstract/Component/IncomingComponentItemPayload'
|
||||
export * from './Abstract/Component/KeyboardModifier'
|
||||
export * from './Abstract/Component/MessageData'
|
||||
@@ -43,9 +43,9 @@ export * from './Runtime/Collection/Payload/ImmutablePayloadCollection'
|
||||
export * from './Runtime/Collection/Payload/PayloadCollection'
|
||||
export * from './Runtime/Deltas'
|
||||
export * from './Runtime/DirtyCounter/DirtyCounter'
|
||||
export * from './Runtime/Display'
|
||||
export * from './Runtime/Display/ItemDisplayController'
|
||||
export * from './Runtime/Display/Types'
|
||||
export * from './Runtime/Display'
|
||||
export * from './Runtime/History'
|
||||
export * from './Runtime/Index/ItemDelta'
|
||||
export * from './Runtime/Index/SNIndex'
|
||||
@@ -70,6 +70,7 @@ export * from './Syncable/SmartView'
|
||||
export * from './Syncable/Tag'
|
||||
export * from './Syncable/Theme'
|
||||
export * from './Syncable/UserPrefs'
|
||||
export * from './Utilities/Icon/IconType'
|
||||
export * from './Utilities/Item/FindItem'
|
||||
export * from './Utilities/Item/ItemContentsDiffer'
|
||||
export * from './Utilities/Item/ItemContentsEqual'
|
||||
|
||||
@@ -3,6 +3,50 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.3.165](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.164](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.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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/releases",
|
||||
"version": "1.3.154",
|
||||
"version": "1.3.165",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "dist/releases.json",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -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.34.4](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.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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/services",
|
||||
"version": "1.34.1",
|
||||
"version": "1.34.4",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.143.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
|
||||
|
||||
### Features
|
||||
|
||||
* ability to favorite tags + customize icon ([#1858](https://github.com/standardnotes/app/issues/1858)) ([cbd0063](https://github.com/standardnotes/app/commit/cbd0063926a416c23225242a4a1796712b6b224e))
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NoteType } from '@standardnotes/features'
|
||||
import { IconType } from '@Lib/Types/IconType'
|
||||
import { IconType } from '@standardnotes/models'
|
||||
|
||||
export class IconsController {
|
||||
getIconForFileType(type: string): IconType {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from './ApplicationEventPayload'
|
||||
export * from './IconType'
|
||||
export * from './UuidString'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/snjs",
|
||||
"version": "2.142.1",
|
||||
"version": "2.143.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -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.9.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/ui-services",
|
||||
"version": "1.8.5",
|
||||
"version": "1.9.2",
|
||||
"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')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -3,6 +3,77 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [3.84.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
|
||||
|
||||
### Features
|
||||
|
||||
* ability to favorite tags + customize icon ([#1858](https://github.com/standardnotes/app/issues/1858)) ([cbd0063](https://github.com/standardnotes/app/commit/cbd0063926a416c23225242a4a1796712b6b224e))
|
||||
|
||||
# [3.83.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-21)
|
||||
|
||||
### Features
|
||||
|
||||
* "create new note" floating button on mobile ([#1857](https://github.com/standardnotes/app/issues/1857)) ([21f725b](https://github.com/standardnotes/app/commit/21f725bb2a8d7d9be3f879cc078dd0f8134c2365))
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/web",
|
||||
"version": "3.80.0",
|
||||
"version": "3.84.0",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "dist/app.js",
|
||||
"author": "Standard Notes.",
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
AutolockService,
|
||||
IOService,
|
||||
RouteService,
|
||||
RouteServiceInterface,
|
||||
ThemeManager,
|
||||
WebAlertService,
|
||||
} from '@standardnotes/ui-services'
|
||||
@@ -50,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,
|
||||
|
||||
@@ -1,32 +1,37 @@
|
||||
import { FunctionComponent, MouseEventHandler } from 'react'
|
||||
import { ForwardedRef, forwardRef, MouseEventHandler } from 'react'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import { IconType } from '@standardnotes/snjs'
|
||||
|
||||
type ButtonType = 'normal' | 'primary'
|
||||
import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||
|
||||
type Props = {
|
||||
onClick: () => void
|
||||
type: ButtonType
|
||||
className?: string
|
||||
icon: IconType
|
||||
iconClassName?: string
|
||||
label: string
|
||||
id?: string
|
||||
}
|
||||
|
||||
const RoundIconButton: FunctionComponent<Props> = ({ onClick, type, className, icon: iconType }) => {
|
||||
const click: MouseEventHandler = (e) => {
|
||||
e.preventDefault()
|
||||
onClick()
|
||||
}
|
||||
const classes = type === 'primary' ? 'info ' : ''
|
||||
return (
|
||||
<button
|
||||
className={`m-0 flex h-8 min-w-8 cursor-pointer items-center justify-center rounded-full border border-solid border-border bg-transparent bg-clip-padding text-neutral hover:bg-contrast hover:text-text focus:bg-contrast focus:text-text focus:outline-none focus:ring-info ${classes} ${
|
||||
className ?? ''
|
||||
}`}
|
||||
onClick={click}
|
||||
>
|
||||
<Icon type={iconType} />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
const RoundIconButton = forwardRef(
|
||||
({ onClick, className, icon: iconType, iconClassName, id }: Props, ref: ForwardedRef<HTMLButtonElement>) => {
|
||||
const click: MouseEventHandler = (e) => {
|
||||
e.preventDefault()
|
||||
onClick()
|
||||
}
|
||||
return (
|
||||
<button
|
||||
className={classNames(
|
||||
'bg-text-padding m-0 flex h-8.5 min-w-8.5 cursor-pointer items-center justify-center rounded-full border border-solid border-border bg-clip-padding text-neutral hover:bg-contrast hover:text-text focus:bg-contrast focus:text-text focus:outline-none focus:ring-info md:h-8 md:min-w-8',
|
||||
className,
|
||||
)}
|
||||
onClick={click}
|
||||
ref={ref}
|
||||
id={id}
|
||||
>
|
||||
<Icon type={iconType} className={iconClassName} />
|
||||
</button>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
export default RoundIconButton
|
||||
|
||||
@@ -2,9 +2,9 @@ import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent, useCallback, useRef, useState } from 'react'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import ChangeEditorMenu from './ChangeEditorMenu'
|
||||
import Popover from '../Popover/Popover'
|
||||
import RoundIconButton from '../Button/RoundIconButton'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
@@ -38,15 +38,13 @@ const ChangeEditorButton: FunctionComponent<Props> = ({
|
||||
|
||||
return (
|
||||
<div ref={containerRef}>
|
||||
<button
|
||||
className="bg-text-padding flex h-8 min-w-8 cursor-pointer items-center justify-center rounded-full border border-border text-neutral hover:bg-contrast focus:bg-contrast"
|
||||
title="Change note type"
|
||||
aria-label="Change note type"
|
||||
<RoundIconButton
|
||||
label="Change note type"
|
||||
onClick={toggleMenu}
|
||||
ref={buttonRef}
|
||||
>
|
||||
<Icon type={selectedEditorIcon} className={`text-accessory-tint-${selectedEditorIconTint}`} />
|
||||
</button>
|
||||
icon={selectedEditorIcon}
|
||||
iconClassName={`text-accessory-tint-${selectedEditorIconTint}`}
|
||||
/>
|
||||
<Popover togglePopover={toggleMenu} anchorElement={buttonRef.current} open={isOpen} className="pt-2 md:pt-0">
|
||||
<ChangeEditorMenu
|
||||
application={application}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { KeyboardKey } from '@standardnotes/ui-services'
|
||||
import { UuidString } from '@standardnotes/snjs'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent, KeyboardEventHandler, UIEventHandler, useCallback } from 'react'
|
||||
import { FOCUSABLE_BUT_NOT_TABBABLE, NOTES_LIST_SCROLL_THRESHOLD } from '@/Constants/Constants'
|
||||
@@ -22,7 +21,7 @@ type Props = {
|
||||
navigationController: NavigationController
|
||||
notesController: NotesController
|
||||
selectionController: SelectedItemsController
|
||||
selectedItems: Record<UuidString, ListableContentItem>
|
||||
selectedUuids: SelectedItemsController['selectedUuids']
|
||||
paginate: () => void
|
||||
}
|
||||
|
||||
@@ -34,7 +33,7 @@ const ContentList: FunctionComponent<Props> = ({
|
||||
navigationController,
|
||||
notesController,
|
||||
selectionController,
|
||||
selectedItems,
|
||||
selectedUuids,
|
||||
paginate,
|
||||
}) => {
|
||||
const { selectPreviousItem, selectNextItem } = selectionController
|
||||
@@ -82,7 +81,7 @@ const ContentList: FunctionComponent<Props> = ({
|
||||
key={item.uuid}
|
||||
application={application}
|
||||
item={item}
|
||||
selected={!!selectedItems[item.uuid]}
|
||||
selected={selectedUuids.has(item.uuid)}
|
||||
hideDate={hideDate}
|
||||
hidePreview={hideNotePreview}
|
||||
hideTags={hideTags}
|
||||
|
||||
@@ -109,7 +109,11 @@ const ContentListView: FunctionComponent<Props> = ({
|
||||
searchBarElement,
|
||||
} = itemListController
|
||||
|
||||
const { selectedItems, selectNextItem, selectPreviousItem } = selectionController
|
||||
const { selectedUuids, selectNextItem, selectPreviousItem } = selectionController
|
||||
|
||||
const { selected: selectedTag } = navigationController
|
||||
|
||||
const icon = selectedTag?.iconString
|
||||
|
||||
const isFilesSmartView = useMemo(
|
||||
() => navigationController.selected?.uuid === SystemViewId.Files,
|
||||
@@ -259,6 +263,7 @@ const ContentListView: FunctionComponent<Props> = ({
|
||||
<ContentListHeader
|
||||
application={application}
|
||||
panelTitle={panelTitle}
|
||||
icon={icon}
|
||||
addButtonLabel={addButtonLabel}
|
||||
addNewItem={addNewItem}
|
||||
isFilesSmartView={isFilesSmartView}
|
||||
@@ -276,7 +281,7 @@ const ContentListView: FunctionComponent<Props> = ({
|
||||
{renderedItems.length ? (
|
||||
<ContentList
|
||||
items={renderedItems}
|
||||
selectedItems={selectedItems}
|
||||
selectedUuids={selectedUuids}
|
||||
application={application}
|
||||
paginate={paginate}
|
||||
filesController={filesController}
|
||||
|
||||
+25
-12
@@ -5,13 +5,17 @@ import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||
import Popover from '@/Components/Popover/Popover'
|
||||
import DisplayOptionsMenu from './DisplayOptionsMenu'
|
||||
import { NavigationMenuButton } from '@/Components/NavigationMenu/NavigationMenu'
|
||||
import { IconType } from '@standardnotes/snjs'
|
||||
import RoundIconButton from '@/Components/Button/RoundIconButton'
|
||||
|
||||
type Props = {
|
||||
application: {
|
||||
getPreference: WebApplication['getPreference']
|
||||
setPreference: WebApplication['setPreference']
|
||||
isNativeMobileWeb: WebApplication['isNativeMobileWeb']
|
||||
}
|
||||
panelTitle: string
|
||||
icon?: IconType | string
|
||||
addButtonLabel: string
|
||||
addNewItem: () => void
|
||||
isFilesSmartView: boolean
|
||||
@@ -21,6 +25,7 @@ type Props = {
|
||||
const ContentListHeader = ({
|
||||
application,
|
||||
panelTitle,
|
||||
icon,
|
||||
addButtonLabel,
|
||||
addNewItem,
|
||||
isFilesSmartView,
|
||||
@@ -39,21 +44,29 @@ const ContentListHeader = ({
|
||||
<div className="section-title-bar-header items-start gap-1 overflow-hidden">
|
||||
<NavigationMenuButton />
|
||||
<div className="flex min-w-0 flex-grow flex-col break-words">
|
||||
<div className="text-lg font-semibold text-text">{panelTitle}</div>
|
||||
{optionsSubtitle && <div className="text-xs text-passive-0">{optionsSubtitle}</div>}
|
||||
<div className={`flex min-w-0 flex-grow flex-row ${!optionsSubtitle ? 'items-center' : ''}`}>
|
||||
{icon && (
|
||||
<Icon
|
||||
type={icon as IconType}
|
||||
size={'large'}
|
||||
className={`ml-0.5 mr-1.5 text-neutral ${optionsSubtitle ? 'mt-1' : ''}`}
|
||||
/>
|
||||
)}
|
||||
<div className="flex min-w-0 flex-grow flex-col break-words">
|
||||
<div className="text-lg font-semibold text-text">{panelTitle}</div>
|
||||
{optionsSubtitle && <div className="text-xs text-passive-0">{optionsSubtitle}</div>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<div className="relative" ref={displayOptionsContainerRef}>
|
||||
<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',
|
||||
showDisplayOptionsMenu && 'bg-contrast',
|
||||
)}
|
||||
<RoundIconButton
|
||||
className={classNames(showDisplayOptionsMenu && 'bg-contrast')}
|
||||
onClick={toggleDisplayOptionsMenu}
|
||||
ref={displayOptionsButtonRef}
|
||||
>
|
||||
<Icon type="sort-descending" />
|
||||
</button>
|
||||
icon="sort-descending"
|
||||
label="Display options menu"
|
||||
/>
|
||||
<Popover
|
||||
open={showDisplayOptionsMenu}
|
||||
anchorElement={displayOptionsButtonRef.current}
|
||||
@@ -70,12 +83,12 @@ const ContentListHeader = ({
|
||||
</Popover>
|
||||
</div>
|
||||
<button
|
||||
className="ml-3 flex h-8 min-w-8 cursor-pointer items-center justify-center rounded-full border border-solid border-transparent bg-info text-info-contrast hover:brightness-125"
|
||||
className="absolute bottom-6 right-6 ml-3 flex h-13 w-13 cursor-pointer items-center justify-center rounded-full border border-solid border-transparent bg-info text-info-contrast hover:brightness-125 md:static md:h-8 md:w-8"
|
||||
title={addButtonLabel}
|
||||
aria-label={addButtonLabel}
|
||||
onClick={addNewItem}
|
||||
>
|
||||
<Icon type="add" />
|
||||
<Icon type="add" size="custom" className="h-6 w-6 md:h-5 md:w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
import { DisclosureButton } from '@reach/disclosure'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const StyledDisplayOptionsButton = styled(DisclosureButton).attrs(() => ({
|
||||
className:
|
||||
'flex justify-center items-center min-w-8 h-8 bg-text-padding hover:bg-contrast focus:bg-contrast text-neutral border border-solid border-border rounded-full cursor-pointer',
|
||||
}))<{
|
||||
$pressed: boolean
|
||||
}>`
|
||||
background-color: ${(props) => (props.$pressed ? 'var(--sn-stylekit-contrast-background-color)' : 'transparent')};
|
||||
`
|
||||
|
||||
export default StyledDisplayOptionsButton
|
||||
@@ -19,7 +19,7 @@ const ListItemTags: FunctionComponent<Props> = ({ hideTags, tags }) => {
|
||||
className="inline-flex items-center rounded-sm bg-passive-4-opacity-variant py-1 px-1.5 text-foreground"
|
||||
key={tag.uuid}
|
||||
>
|
||||
<Icon type="hashtag" className="mr-1 text-passive-1" size="small" />
|
||||
<Icon type={tag.iconString} className="mr-1 text-passive-1" size="small" />
|
||||
<span>{tag.title}</span>
|
||||
</span>
|
||||
))}
|
||||
|
||||
+1
@@ -5,5 +5,6 @@ export type DisplayableListItemProps = AbstractListItemProps & {
|
||||
tags: {
|
||||
uuid: SNTag['uuid']
|
||||
title: SNTag['title']
|
||||
iconString: SNTag['iconString']
|
||||
}[]
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import FileMenuOptions from './FileMenuOptions'
|
||||
import { FilesController } from '@/Controllers/FilesController'
|
||||
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
|
||||
import Popover from '../Popover/Popover'
|
||||
import RoundIconButton from '../Button/RoundIconButton'
|
||||
|
||||
type Props = {
|
||||
filesController: FilesController
|
||||
@@ -19,15 +19,7 @@ const FilesOptionsPanel = ({ filesController, selectionController }: Props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className="bg-text-padding flex h-8 min-w-8 cursor-pointer items-center justify-center rounded-full border border-solid border-border text-neutral hover:bg-contrast focus:bg-contrast"
|
||||
title="File options menu"
|
||||
aria-label="File options menu"
|
||||
onClick={toggleMenu}
|
||||
ref={buttonRef}
|
||||
>
|
||||
<Icon type="more" />
|
||||
</button>
|
||||
<RoundIconButton label="File options menu" onClick={toggleMenu} ref={buttonRef} icon="more" />
|
||||
<Popover togglePopover={toggleMenu} anchorElement={buttonRef.current} open={isOpen} className="py-2">
|
||||
<FileMenuOptions
|
||||
filesController={filesController}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ICONS } from '@/Components/Icon/Icon'
|
||||
import { IconNameToSvgMapping } from '@/Components/Icon/IconNameToSvgMapping'
|
||||
|
||||
export const getFileIconComponent = (iconType: string, className: string) => {
|
||||
const IconComponent = ICONS[iconType as keyof typeof ICONS]
|
||||
const IconComponent = IconNameToSvgMapping[iconType as keyof typeof IconNameToSvgMapping]
|
||||
|
||||
return <IconComponent className={className} />
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ import { FileViewProps } from './FileViewProps'
|
||||
import MobileItemsListButton from '../NoteGroupView/MobileItemsListButton'
|
||||
import LinkedItemsButton from '../LinkedItems/LinkedItemsButton'
|
||||
import LinkedItemBubblesContainer from '../LinkedItems/LinkedItemBubblesContainer'
|
||||
import Icon from '../Icon/Icon'
|
||||
import Popover from '../Popover/Popover'
|
||||
import FilePreviewInfoPanel from '../FilePreview/FilePreviewInfoPanel'
|
||||
import { useFileDragNDrop } from '../FileDragNDropProvider/FileDragNDropProvider'
|
||||
import RoundIconButton from '../Button/RoundIconButton'
|
||||
|
||||
const SyncTimeoutNoDebounceMs = 100
|
||||
const SyncTimeoutDebounceMs = 350
|
||||
@@ -95,15 +95,12 @@ const FileViewWithoutProtection = ({ application, viewControllerManager, file }:
|
||||
filesController={viewControllerManager.filesController}
|
||||
linkingController={viewControllerManager.linkingController}
|
||||
/>
|
||||
<button
|
||||
className="bg-text-padding flex h-8 min-w-8 cursor-pointer items-center justify-center rounded-full border border-solid border-border text-neutral hover:bg-contrast focus:bg-contrast"
|
||||
title="File information panel"
|
||||
aria-label="File information panel"
|
||||
<RoundIconButton
|
||||
label="File information panel"
|
||||
onClick={toggleFileInfoPanel}
|
||||
ref={fileInfoButtonRef}
|
||||
>
|
||||
<Icon type="info" />
|
||||
</button>
|
||||
icon="info"
|
||||
/>
|
||||
<Popover
|
||||
open={isFileInfoPanelOpen}
|
||||
togglePopover={toggleFileInfoPanel}
|
||||
|
||||
@@ -1,141 +1,77 @@
|
||||
import { FunctionComponent, useMemo } from 'react'
|
||||
import { IconType } from '@standardnotes/snjs'
|
||||
import * as icons from '@standardnotes/icons'
|
||||
|
||||
export const ICONS = {
|
||||
'account-circle': icons.AccountCircleIcon,
|
||||
'arrow-left': icons.ArrowLeftIcon,
|
||||
'arrow-right': icons.ArrowRightIcon,
|
||||
'arrows-sort-down': icons.ArrowsSortDownIcon,
|
||||
'arrows-sort-up': icons.ArrowsSortUpIcon,
|
||||
'attachment-file': icons.AttachmentFileIcon,
|
||||
'check-bold': icons.CheckBoldIcon,
|
||||
'check-circle': icons.CheckCircleIcon,
|
||||
'chevron-down': icons.ChevronDownIcon,
|
||||
'chevron-left': icons.ChevronLeftIcon,
|
||||
'chevron-right': icons.ChevronRightIcon,
|
||||
'clear-circle-filled': icons.ClearCircleFilledIcon,
|
||||
'cloud-off': icons.CloudOffIcon,
|
||||
'diamond-filled': icons.DiamondFilledIcon,
|
||||
'eye-off': icons.EyeOffIcon,
|
||||
'file-doc': icons.FileDocIcon,
|
||||
'file-image': icons.FileImageIcon,
|
||||
'file-mov': icons.FileMovIcon,
|
||||
'file-music': icons.FileMusicIcon,
|
||||
'file-other': icons.FileOtherIcon,
|
||||
'file-pdf': icons.FilePdfIcon,
|
||||
'file-ppt': icons.FilePptIcon,
|
||||
'file-xls': icons.FileXlsIcon,
|
||||
'file-zip': icons.FileZipIcon,
|
||||
'hashtag-off': icons.HashtagOffIcon,
|
||||
'link-off': icons.LinkOffIcon,
|
||||
'list-bulleted': icons.ListBulleted,
|
||||
'lock-filled': icons.LockFilledIcon,
|
||||
'menu-arrow-down-alt': icons.MenuArrowDownAlt,
|
||||
'menu-arrow-down': icons.MenuArrowDownIcon,
|
||||
'menu-arrow-right': icons.MenuArrowRightIcon,
|
||||
'menu-close': icons.MenuCloseIcon,
|
||||
'menu-variant': icons.MenuVariantIcon,
|
||||
'notes-filled': icons.NotesFilledIcon,
|
||||
'pencil-filled': icons.PencilFilledIcon,
|
||||
'pencil-off': icons.PencilOffIcon,
|
||||
'pin-filled': icons.PinFilledIcon,
|
||||
'plain-text': icons.PlainTextIcon,
|
||||
'premium-feature': icons.PremiumFeatureIcon,
|
||||
'rich-text': icons.RichTextIcon,
|
||||
'sort-descending': icons.SortDescendingIcon,
|
||||
'star-circle-filled': icons.StarCircleFilled,
|
||||
'star-filled': icons.StarFilledIcon,
|
||||
'star-variant-filled': icons.StarVariantFilledIcon,
|
||||
'trash-filled': icons.TrashFilledIcon,
|
||||
'trash-sweep': icons.TrashSweepIcon,
|
||||
'user-add': icons.UserAddIcon,
|
||||
'user-switch': icons.UserSwitch,
|
||||
accessibility: icons.AccessibilityIcon,
|
||||
add: icons.AddIcon,
|
||||
archive: icons.ArchiveIcon,
|
||||
asterisk: icons.AsteriskIcon,
|
||||
authenticator: icons.AuthenticatorIcon,
|
||||
check: icons.CheckIcon,
|
||||
close: icons.CloseIcon,
|
||||
code: icons.CodeIcon,
|
||||
copy: icons.CopyIcon,
|
||||
dashboard: icons.DashboardIcon,
|
||||
diamond: icons.DiamondIcon,
|
||||
download: icons.DownloadIcon,
|
||||
editor: icons.EditorIcon,
|
||||
email: icons.EmailIcon,
|
||||
eye: icons.EyeIcon,
|
||||
file: icons.FileIcon,
|
||||
folder: icons.FolderIcon,
|
||||
hashtag: icons.HashtagIcon,
|
||||
help: icons.HelpIcon,
|
||||
history: icons.HistoryIcon,
|
||||
info: icons.InfoIcon,
|
||||
keyboard: icons.KeyboardIcon,
|
||||
link: icons.LinkIcon,
|
||||
listed: icons.ListedIcon,
|
||||
lock: icons.LockIcon,
|
||||
markdown: icons.MarkdownIcon,
|
||||
more: icons.MoreIcon,
|
||||
notes: icons.NotesIcon,
|
||||
password: icons.PasswordIcon,
|
||||
pencil: icons.PencilIcon,
|
||||
pin: icons.PinIcon,
|
||||
restore: icons.RestoreIcon,
|
||||
search: icons.SearchIcon,
|
||||
security: icons.SecurityIcon,
|
||||
server: icons.ServerIcon,
|
||||
settings: icons.SettingsIcon,
|
||||
share: icons.ShareIcon,
|
||||
signIn: icons.SignInIcon,
|
||||
signOut: icons.SignOutIcon,
|
||||
spreadsheets: icons.SpreadsheetsIcon,
|
||||
star: icons.StarIcon,
|
||||
subtract: icons.SubtractIcon,
|
||||
sync: icons.SyncIcon,
|
||||
tasks: icons.TasksIcon,
|
||||
themes: icons.ThemesIcon,
|
||||
trash: icons.TrashIcon,
|
||||
tune: icons.TuneIcon,
|
||||
unarchive: icons.UnarchiveIcon,
|
||||
unpin: icons.UnpinIcon,
|
||||
user: icons.UserIcon,
|
||||
view: icons.ViewIcon,
|
||||
warning: icons.WarningIcon,
|
||||
window: icons.WindowIcon,
|
||||
}
|
||||
import { FunctionComponent } from 'react'
|
||||
import { VectorIconNameOrEmoji } from '@standardnotes/snjs'
|
||||
import { IconNameToSvgMapping } from './IconNameToSvgMapping'
|
||||
import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||
|
||||
type Props = {
|
||||
type: IconType
|
||||
type: VectorIconNameOrEmoji
|
||||
className?: string
|
||||
ariaLabel?: string
|
||||
size?: 'small' | 'medium' | 'normal' | 'custom'
|
||||
size?: 'small' | 'medium' | 'normal' | 'large' | 'custom'
|
||||
}
|
||||
|
||||
const ContainerDimensions = {
|
||||
small: 'w-3.5 h-3.5',
|
||||
medium: 'w-4 h-4',
|
||||
normal: 'w-5 h-5',
|
||||
large: 'w-6 h-6',
|
||||
custom: '',
|
||||
}
|
||||
|
||||
const EmojiContainerDimensions = {
|
||||
small: 'w-4 h-4',
|
||||
medium: 'w-5 h-5',
|
||||
normal: 'w-5 h-5',
|
||||
large: 'w-7 h-6',
|
||||
custom: '',
|
||||
}
|
||||
|
||||
const EmojiOffset = {
|
||||
small: '',
|
||||
medium: '',
|
||||
normal: '-mt-0.5',
|
||||
large: '',
|
||||
custom: '',
|
||||
}
|
||||
|
||||
const EmojiSize = {
|
||||
small: 'text-xs',
|
||||
medium: 'text-sm',
|
||||
normal: 'text-base',
|
||||
large: 'text-lg',
|
||||
custom: '',
|
||||
}
|
||||
|
||||
const getIconComponent = (type: VectorIconNameOrEmoji) => {
|
||||
return IconNameToSvgMapping[type as keyof typeof IconNameToSvgMapping]
|
||||
}
|
||||
|
||||
export const isIconEmoji = (type: VectorIconNameOrEmoji): boolean => {
|
||||
return getIconComponent(type) == undefined
|
||||
}
|
||||
|
||||
const Icon: FunctionComponent<Props> = ({ type, className = '', ariaLabel, size = 'normal' }) => {
|
||||
const IconComponent = ICONS[type as keyof typeof ICONS]
|
||||
|
||||
const dimensions = useMemo(() => {
|
||||
switch (size) {
|
||||
case 'small':
|
||||
return 'w-3.5 h-3.5'
|
||||
case 'medium':
|
||||
return 'w-4 h-4'
|
||||
case 'custom':
|
||||
return ''
|
||||
default:
|
||||
return 'w-5 h-5'
|
||||
}
|
||||
}, [size])
|
||||
|
||||
const IconComponent = getIconComponent(type)
|
||||
if (!IconComponent) {
|
||||
return null
|
||||
return (
|
||||
<label
|
||||
className={classNames(
|
||||
'fill-current',
|
||||
'text-center',
|
||||
EmojiSize[size],
|
||||
EmojiContainerDimensions[size],
|
||||
EmojiOffset[size],
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{type}
|
||||
</label>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<IconComponent
|
||||
className={`${dimensions} fill-current ${className}`}
|
||||
className={`${ContainerDimensions[size]} fill-current ${className}`}
|
||||
role="img"
|
||||
{...(ariaLabel ? { 'aria-label': ariaLabel } : { 'aria-hidden': true })}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import * as icons from '@standardnotes/icons'
|
||||
|
||||
export const IconNameToSvgMapping = {
|
||||
'account-circle': icons.AccountCircleIcon,
|
||||
'arrow-left': icons.ArrowLeftIcon,
|
||||
'arrow-right': icons.ArrowRightIcon,
|
||||
'arrows-sort-down': icons.ArrowsSortDownIcon,
|
||||
'arrows-sort-up': icons.ArrowsSortUpIcon,
|
||||
'attachment-file': icons.AttachmentFileIcon,
|
||||
'check-bold': icons.CheckBoldIcon,
|
||||
'check-circle': icons.CheckCircleIcon,
|
||||
'chevron-down': icons.ChevronDownIcon,
|
||||
'chevron-left': icons.ChevronLeftIcon,
|
||||
'chevron-right': icons.ChevronRightIcon,
|
||||
'clear-circle-filled': icons.ClearCircleFilledIcon,
|
||||
'cloud-off': icons.CloudOffIcon,
|
||||
'diamond-filled': icons.DiamondFilledIcon,
|
||||
'eye-off': icons.EyeOffIcon,
|
||||
'file-doc': icons.FileDocIcon,
|
||||
'file-image': icons.FileImageIcon,
|
||||
'file-mov': icons.FileMovIcon,
|
||||
'file-music': icons.FileMusicIcon,
|
||||
'file-other': icons.FileOtherIcon,
|
||||
'file-pdf': icons.FilePdfIcon,
|
||||
'file-ppt': icons.FilePptIcon,
|
||||
'file-xls': icons.FileXlsIcon,
|
||||
'file-zip': icons.FileZipIcon,
|
||||
'hashtag-off': icons.HashtagOffIcon,
|
||||
'link-off': icons.LinkOffIcon,
|
||||
'list-bulleted': icons.ListBulleted,
|
||||
'lock-filled': icons.LockFilledIcon,
|
||||
'menu-arrow-down-alt': icons.MenuArrowDownAlt,
|
||||
'menu-arrow-down': icons.MenuArrowDownIcon,
|
||||
'menu-arrow-right': icons.MenuArrowRightIcon,
|
||||
'menu-close': icons.MenuCloseIcon,
|
||||
'menu-variant': icons.MenuVariantIcon,
|
||||
'notes-filled': icons.NotesFilledIcon,
|
||||
'pencil-filled': icons.PencilFilledIcon,
|
||||
'pencil-off': icons.PencilOffIcon,
|
||||
'pin-filled': icons.PinFilledIcon,
|
||||
'plain-text': icons.PlainTextIcon,
|
||||
'premium-feature': icons.PremiumFeatureIcon,
|
||||
'rich-text': icons.RichTextIcon,
|
||||
'sort-descending': icons.SortDescendingIcon,
|
||||
'star-circle-filled': icons.StarCircleFilled,
|
||||
'star-filled': icons.StarFilledIcon,
|
||||
'star-variant-filled': icons.StarVariantFilledIcon,
|
||||
'trash-filled': icons.TrashFilledIcon,
|
||||
'trash-sweep': icons.TrashSweepIcon,
|
||||
'user-add': icons.UserAddIcon,
|
||||
'user-switch': icons.UserSwitch,
|
||||
'fullscreen-exit': icons.FullscreenExitIcon,
|
||||
accessibility: icons.AccessibilityIcon,
|
||||
add: icons.AddIcon,
|
||||
archive: icons.ArchiveIcon,
|
||||
asterisk: icons.AsteriskIcon,
|
||||
authenticator: icons.AuthenticatorIcon,
|
||||
check: icons.CheckIcon,
|
||||
close: icons.CloseIcon,
|
||||
code: icons.CodeIcon,
|
||||
copy: icons.CopyIcon,
|
||||
dashboard: icons.DashboardIcon,
|
||||
diamond: icons.DiamondIcon,
|
||||
download: icons.DownloadIcon,
|
||||
editor: icons.EditorIcon,
|
||||
email: icons.EmailIcon,
|
||||
eye: icons.EyeIcon,
|
||||
file: icons.FileIcon,
|
||||
folder: icons.FolderIcon,
|
||||
hashtag: icons.HashtagIcon,
|
||||
help: icons.HelpIcon,
|
||||
history: icons.HistoryIcon,
|
||||
info: icons.InfoIcon,
|
||||
keyboard: icons.KeyboardIcon,
|
||||
link: icons.LinkIcon,
|
||||
listed: icons.ListedIcon,
|
||||
lock: icons.LockIcon,
|
||||
markdown: icons.MarkdownIcon,
|
||||
more: icons.MoreIcon,
|
||||
notes: icons.NotesIcon,
|
||||
password: icons.PasswordIcon,
|
||||
pencil: icons.PencilIcon,
|
||||
pin: icons.PinIcon,
|
||||
restore: icons.RestoreIcon,
|
||||
search: icons.SearchIcon,
|
||||
security: icons.SecurityIcon,
|
||||
server: icons.ServerIcon,
|
||||
settings: icons.SettingsIcon,
|
||||
share: icons.ShareIcon,
|
||||
signIn: icons.SignInIcon,
|
||||
signOut: icons.SignOutIcon,
|
||||
spreadsheets: icons.SpreadsheetsIcon,
|
||||
star: icons.StarIcon,
|
||||
subtract: icons.SubtractIcon,
|
||||
sync: icons.SyncIcon,
|
||||
tasks: icons.TasksIcon,
|
||||
themes: icons.ThemesIcon,
|
||||
trash: icons.TrashIcon,
|
||||
tune: icons.TuneIcon,
|
||||
unarchive: icons.UnarchiveIcon,
|
||||
unpin: icons.UnpinIcon,
|
||||
user: icons.UserIcon,
|
||||
view: icons.ViewIcon,
|
||||
warning: icons.WarningIcon,
|
||||
window: icons.WindowIcon,
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
import { EmojiString, Platform, VectorIconNameOrEmoji } from '@standardnotes/snjs'
|
||||
import { FunctionComponent, useMemo, useRef, useState } from 'react'
|
||||
import Dropdown from '../Dropdown/Dropdown'
|
||||
import { DropdownItem } from '../Dropdown/DropdownItem'
|
||||
import { isIconEmoji } from './Icon'
|
||||
import { IconNameToSvgMapping } from './IconNameToSvgMapping'
|
||||
import { IconPickerType } from './IconPickerType'
|
||||
|
||||
type Props = {
|
||||
selectedValue: VectorIconNameOrEmoji
|
||||
onIconChange: (value?: string) => void
|
||||
platform: Platform
|
||||
className?: string
|
||||
}
|
||||
|
||||
const IconPicker = ({ selectedValue, onIconChange, platform, className }: Props) => {
|
||||
const iconOptions = useMemo(
|
||||
() =>
|
||||
[...Object.keys(IconNameToSvgMapping)].map(
|
||||
(value) =>
|
||||
({
|
||||
label: value,
|
||||
value: value,
|
||||
icon: value,
|
||||
} as DropdownItem),
|
||||
),
|
||||
[],
|
||||
)
|
||||
|
||||
const isSelectedEmoji = isIconEmoji(selectedValue)
|
||||
const isMacOS = platform === Platform.MacWeb || platform === Platform.MacDesktop
|
||||
const isWindows = platform === Platform.WindowsWeb || platform === Platform.WindowsDesktop
|
||||
|
||||
const emojiInputRef = useRef<HTMLInputElement>(null)
|
||||
const [emojiInputFocused, setEmojiInputFocused] = useState(true)
|
||||
const [currentType, setCurrentType] = useState<IconPickerType>(isSelectedEmoji ? 'emoji' : 'icon')
|
||||
const [emojiInputValue, setEmojiInputValue] = useState(isSelectedEmoji ? selectedValue : '')
|
||||
|
||||
const selectTab = (type: IconPickerType | 'reset') => {
|
||||
if (type === 'reset') {
|
||||
onIconChange(undefined)
|
||||
setEmojiInputValue('')
|
||||
} else {
|
||||
setCurrentType(type)
|
||||
}
|
||||
}
|
||||
|
||||
const TabButton: FunctionComponent<{
|
||||
label: string
|
||||
type: IconPickerType | 'reset'
|
||||
}> = ({ type, label }) => {
|
||||
const isSelected = currentType === type
|
||||
|
||||
return (
|
||||
<button
|
||||
className={`relative mr-2 cursor-pointer border-0 bg-default pb-1.5 text-sm focus:shadow-none ${
|
||||
isSelected ? 'font-medium text-info' : 'text-text'
|
||||
}`}
|
||||
onClick={() => {
|
||||
selectTab(type)
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
const handleIconChange = (value: string) => {
|
||||
onIconChange(value)
|
||||
}
|
||||
|
||||
const handleEmojiChange = (value: EmojiString) => {
|
||||
setEmojiInputValue(value)
|
||||
|
||||
const emojiLength = [...value].length
|
||||
if (emojiLength === 1) {
|
||||
onIconChange(value)
|
||||
emojiInputRef.current?.blur()
|
||||
setEmojiInputFocused(false)
|
||||
} else {
|
||||
setEmojiInputFocused(true)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`flex h-full flex-grow flex-col overflow-auto ${className}`}>
|
||||
<div className="flex">
|
||||
<TabButton label="Icon" type={'icon'} />
|
||||
<TabButton label="Emoji" type={'emoji'} />
|
||||
<TabButton label="Reset" type={'reset'} />
|
||||
</div>
|
||||
<div className={'mt-2 h-full min-h-0 overflow-auto'}>
|
||||
{currentType === 'icon' && (
|
||||
<Dropdown
|
||||
id="change-tag-icon-dropdown"
|
||||
label="Change the icon for a tag"
|
||||
items={iconOptions}
|
||||
value={selectedValue}
|
||||
onChange={handleIconChange}
|
||||
/>
|
||||
)}
|
||||
{currentType === 'emoji' && (
|
||||
<>
|
||||
<div>
|
||||
<input
|
||||
ref={emojiInputRef}
|
||||
autoComplete="off"
|
||||
autoFocus={emojiInputFocused}
|
||||
className="w-full flex-grow rounded border border-solid border-passive-3 bg-default px-2 py-1 text-base font-bold text-text focus:shadow-none focus:outline-none"
|
||||
type="text"
|
||||
value={emojiInputValue}
|
||||
onChange={({ target: input }) => handleEmojiChange((input as HTMLInputElement)?.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 text-xs text-passive-0">
|
||||
Use your keyboard to enter or paste in an emoji character.
|
||||
</div>
|
||||
{isMacOS && (
|
||||
<div className="mt-2 text-xs text-passive-0">On macOS: ⌘ + ⌃ + Space bar to bring up emoji picker.</div>
|
||||
)}
|
||||
{isWindows && (
|
||||
<div className="mt-2 text-xs text-passive-0">On Windows: Windows key + . to bring up emoji picker.</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconPicker
|
||||
@@ -0,0 +1 @@
|
||||
export type IconPickerType = 'icon' | 'emoji'
|
||||
@@ -2,7 +2,7 @@ import { FilesController } from '@/Controllers/FilesController'
|
||||
import { LinkingController } from '@/Controllers/LinkingController'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useRef, useCallback } from 'react'
|
||||
import Icon from '../Icon/Icon'
|
||||
import RoundIconButton from '../Button/RoundIconButton'
|
||||
import Popover from '../Popover/Popover'
|
||||
import StyledTooltip from '../StyledTooltip/StyledTooltip'
|
||||
import LinkedItemsPanel from './LinkedItemsPanel'
|
||||
@@ -28,14 +28,7 @@ const LinkedItemsButton = ({ linkingController, filesController, onClickPreproce
|
||||
return (
|
||||
<>
|
||||
<StyledTooltip label="Linked items panel">
|
||||
<button
|
||||
className="bg-text-padding flex h-8 min-w-8 cursor-pointer items-center justify-center rounded-full border border-solid border-border text-neutral hover:bg-contrast focus:bg-contrast"
|
||||
aria-label="Linked items panel"
|
||||
onClick={toggleMenu}
|
||||
ref={buttonRef}
|
||||
>
|
||||
<Icon type="link" />
|
||||
</button>
|
||||
<RoundIconButton label="Linked items panel" onClick={toggleMenu} ref={buttonRef} icon="link" />
|
||||
</StyledTooltip>
|
||||
<Popover togglePopover={toggleMenu} anchorElement={buttonRef.current} open={isLinkingPanelOpen} className="pb-2">
|
||||
<LinkedItemsPanel
|
||||
|
||||
@@ -5,7 +5,8 @@ import { SwitchProps } from '@/Components/Switch/SwitchProps'
|
||||
import { IconType } from '@standardnotes/snjs'
|
||||
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants'
|
||||
import { MenuItemType } from './MenuItemType'
|
||||
import RadioIndicator from '../RadioIndicator/RadioIndicator'
|
||||
import RadioIndicator from '../Radio/RadioIndicator'
|
||||
import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||
|
||||
type MenuItemProps = {
|
||||
children: ReactNode
|
||||
@@ -40,7 +41,10 @@ const MenuItem = forwardRef(
|
||||
<li className="list-none" role="none">
|
||||
<button
|
||||
ref={ref}
|
||||
className="flex w-full cursor-pointer items-center justify-between border-0 bg-transparent px-3 py-1.5 text-left text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
className={classNames(
|
||||
'flex w-full cursor-pointer items-center justify-between border-0 bg-transparent px-3 py-1.5',
|
||||
'text-left text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none',
|
||||
)}
|
||||
onClick={() => {
|
||||
onChange(!checked)
|
||||
}}
|
||||
@@ -59,7 +63,12 @@ const MenuItem = forwardRef(
|
||||
ref={ref}
|
||||
role={type === MenuItemType.RadioButton ? 'menuitemradio' : 'menuitem'}
|
||||
tabIndex={typeof tabIndex === 'number' ? tabIndex : FOCUSABLE_BUT_NOT_TABBABLE}
|
||||
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-menu-item ${className}`}
|
||||
className={classNames(
|
||||
'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-menu-item',
|
||||
className,
|
||||
)}
|
||||
onClick={onClick}
|
||||
onBlur={onBlur}
|
||||
{...(type === MenuItemType.RadioButton ? { 'aria-checked': checked } : {})}
|
||||
|
||||
@@ -9,10 +9,10 @@ import PanelResizer, { PanelSide, ResizeFinishCallback, PanelResizeType } from '
|
||||
import ResponsivePaneContent from '@/Components/ResponsivePane/ResponsivePaneContent'
|
||||
import { AppPaneId } from '@/Components/ResponsivePane/AppPaneMetadata'
|
||||
import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||
import Icon from '../Icon/Icon'
|
||||
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
|
||||
import { isIOS } from '@/Utils'
|
||||
import UpgradeNow from '../Footer/UpgradeNow'
|
||||
import RoundIconButton from '../Button/RoundIconButton'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
@@ -89,58 +89,48 @@ const Navigation: FunctionComponent<Props> = ({ application }) => {
|
||||
<TagsSection viewControllerManager={viewControllerManager} />
|
||||
</div>
|
||||
<div className="flex items-center border-t border-border px-3.5 pt-2.5 md:hidden">
|
||||
<button
|
||||
className="mr-auto flex h-8 min-w-8 cursor-pointer items-center justify-center rounded-full border border-solid border-border bg-default text-neutral hover:bg-contrast focus:bg-contrast"
|
||||
<RoundIconButton
|
||||
className="mr-auto bg-default"
|
||||
onClick={() => {
|
||||
toggleAppPane(AppPaneId.Items)
|
||||
}}
|
||||
title="Go to items list"
|
||||
aria-label="Go to items list"
|
||||
>
|
||||
<Icon type="chevron-left" />
|
||||
</button>
|
||||
label="Go to items list"
|
||||
icon="chevron-left"
|
||||
/>
|
||||
<UpgradeNow application={application} featuresController={viewControllerManager.featuresController} />
|
||||
<button
|
||||
className="ml-2.5 flex h-8 min-w-8 cursor-pointer items-center justify-center rounded-full border border-solid border-border bg-default text-neutral hover:bg-contrast focus:bg-contrast"
|
||||
<RoundIconButton
|
||||
className="ml-2.5 bg-default"
|
||||
onClick={() => {
|
||||
viewControllerManager.accountMenuController.toggleShow()
|
||||
}}
|
||||
title="Go to account menu"
|
||||
aria-label="Go to account menu"
|
||||
>
|
||||
<Icon type="account-circle" />
|
||||
</button>
|
||||
label="Go to account menu"
|
||||
icon="account-circle"
|
||||
/>
|
||||
{hasPasscode && (
|
||||
<button
|
||||
<RoundIconButton
|
||||
id="lock-item"
|
||||
onClick={() => application.lock()}
|
||||
title="Locks application and wipes unencrypted data from memory."
|
||||
aria-label="Locks application and wipes unencrypted data from memory."
|
||||
className="ml-2.5 flex h-8 min-w-8 cursor-pointer items-center justify-center rounded-full border border-solid border-border bg-default text-neutral hover:bg-contrast focus:bg-contrast"
|
||||
>
|
||||
<Icon type="lock-filled" size="custom" className="h-4.5 w-4.5" />
|
||||
</button>
|
||||
label="Locks application and wipes unencrypted data from memory."
|
||||
className="ml-2.5 bg-default"
|
||||
icon="lock-filled"
|
||||
/>
|
||||
)}
|
||||
<button
|
||||
className="ml-2.5 flex h-8 min-w-8 cursor-pointer items-center justify-center rounded-full border border-solid border-border bg-default text-neutral hover:bg-contrast focus:bg-contrast"
|
||||
<RoundIconButton
|
||||
className="ml-2.5 bg-default"
|
||||
onClick={() => {
|
||||
viewControllerManager.preferencesController.openPreferences()
|
||||
}}
|
||||
title="Go to preferences"
|
||||
aria-label="Go to preferences"
|
||||
>
|
||||
<Icon type="tune" />
|
||||
</button>
|
||||
<button
|
||||
className="ml-2.5 flex h-8 min-w-8 cursor-pointer items-center justify-center rounded-full border border-solid border-border bg-default text-neutral hover:bg-contrast focus:bg-contrast"
|
||||
label="Go to preferences"
|
||||
icon="tune"
|
||||
/>
|
||||
<RoundIconButton
|
||||
className="ml-2.5 bg-default"
|
||||
onClick={() => {
|
||||
viewControllerManager.quickSettingsMenuController.toggle()
|
||||
}}
|
||||
title="Go to quick settings menu"
|
||||
aria-label="Go to quick settings menu"
|
||||
>
|
||||
<Icon type="themes" />
|
||||
</button>
|
||||
label="Go to quick settings menu"
|
||||
icon="themes"
|
||||
/>
|
||||
</div>
|
||||
</ResponsivePaneContent>
|
||||
{panelElement && (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Icon from '../Icon/Icon'
|
||||
import RoundIconButton from '../Button/RoundIconButton'
|
||||
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
|
||||
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
|
||||
|
||||
@@ -6,8 +6,8 @@ export const NavigationMenuButton = () => {
|
||||
const { selectedPane, toggleAppPane } = useResponsiveAppPane()
|
||||
|
||||
return (
|
||||
<button
|
||||
className="bg-text-padding mr-3 inline-flex h-8 min-w-8 cursor-pointer items-center justify-center rounded-full border border-solid border-border align-middle text-neutral hover:bg-contrast focus:bg-contrast md:hidden pointer-coarse:md-only:inline-flex pointer-coarse:lg-only:inline-flex"
|
||||
<RoundIconButton
|
||||
className="mr-3 md:hidden"
|
||||
onClick={() => {
|
||||
if (selectedPane === AppPaneId.Items || selectedPane === AppPaneId.Editor) {
|
||||
toggleAppPane(AppPaneId.Navigation)
|
||||
@@ -15,10 +15,8 @@ export const NavigationMenuButton = () => {
|
||||
toggleAppPane(AppPaneId.Items)
|
||||
}
|
||||
}}
|
||||
title="Navigation menu"
|
||||
aria-label="Navigation menu"
|
||||
>
|
||||
<Icon type="menu-variant" />
|
||||
</button>
|
||||
label="Open navigation menu"
|
||||
icon="menu-variant"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
|
||||
import Icon from '../Icon/Icon'
|
||||
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
|
||||
import { useMediaQuery, MediaQueryBreakpoints } from '@/Hooks/useMediaQuery'
|
||||
import { IconType } from '@standardnotes/snjs'
|
||||
import RoundIconButton from '../Button/RoundIconButton'
|
||||
|
||||
const MobileItemsListButton = () => {
|
||||
const { toggleAppPane, isNotesListVisibleOnTablets, toggleNotesListOnTablets } = useResponsiveAppPane()
|
||||
@@ -18,8 +18,8 @@ const MobileItemsListButton = () => {
|
||||
: 'Go to items list'
|
||||
|
||||
return (
|
||||
<button
|
||||
className="bg-text-padding mr-3 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 md:hidden pointer-coarse:md-only:flex pointer-coarse:lg-only:flex"
|
||||
<RoundIconButton
|
||||
className="mr-3"
|
||||
onClick={() => {
|
||||
if (isTabletScreenSize) {
|
||||
toggleNotesListOnTablets()
|
||||
@@ -27,11 +27,9 @@ const MobileItemsListButton = () => {
|
||||
toggleAppPane(AppPaneId.Items)
|
||||
}
|
||||
}}
|
||||
title={label}
|
||||
aria-label={label}
|
||||
>
|
||||
<Icon type={iconType} />
|
||||
</button>
|
||||
label={label}
|
||||
icon={iconType}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||
import { ComponentPropsWithoutRef, ForwardedRef, forwardRef } from 'react'
|
||||
|
||||
// Based on: https://css-tricks.com/auto-growing-inputs-textareas/#aa-other-ideas
|
||||
const AutoresizingNoteViewTextarea = forwardRef(
|
||||
(
|
||||
{ value, className, ...textareaProps }: ComponentPropsWithoutRef<'textarea'>,
|
||||
ref: ForwardedRef<HTMLTextAreaElement>,
|
||||
) => {
|
||||
return (
|
||||
<div className="relative inline-grid min-h-[75vh] w-full grid-rows-1 items-stretch md:block md:flex-grow">
|
||||
<pre
|
||||
id="textarea-mobile-resizer"
|
||||
className={classNames(
|
||||
'editable font-editor break-word whitespace-pre-wrap',
|
||||
'invisible [grid-area:1_/_1] md:hidden',
|
||||
className,
|
||||
)}
|
||||
aria-hidden
|
||||
>
|
||||
{value}{' '}
|
||||
</pre>
|
||||
<textarea
|
||||
value={value}
|
||||
className={classNames('editable font-editor [grid-area:1_/_1] md:h-full md:min-h-0', className)}
|
||||
{...textareaProps}
|
||||
ref={ref}
|
||||
></textarea>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
export default AutoresizingNoteViewTextarea
|
||||
@@ -13,12 +13,11 @@ import {
|
||||
NoteViewController,
|
||||
PayloadEmitSource,
|
||||
WebAppEvent,
|
||||
Platform,
|
||||
EditorLineHeight,
|
||||
EditorFontSize,
|
||||
NoteType,
|
||||
} from '@standardnotes/snjs'
|
||||
import { debounce, isDesktopApplication, isIOS } from '@/Utils'
|
||||
import { debounce, isDesktopApplication } from '@/Utils'
|
||||
import { EditorEventSource } from '../../Types/EditorEventSource'
|
||||
import { confirmDialog, KeyboardModifier, KeyboardKey } from '@standardnotes/ui-services'
|
||||
import { STRING_DELETE_PLACEHOLDER_ATTEMPT, STRING_DELETE_LOCKED_ATTEMPT, StringDeleteNote } from '@/Constants/Strings'
|
||||
@@ -39,7 +38,6 @@ import { reloadFont } from './FontFunctions'
|
||||
import { NoteViewProps } from './NoteViewProps'
|
||||
import IndicatorCircle from '../IndicatorCircle/IndicatorCircle'
|
||||
import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||
import AutoresizingNoteViewTextarea from './AutoresizingTextarea'
|
||||
import MobileItemsListButton from '../NoteGroupView/MobileItemsListButton'
|
||||
import LinkedItemBubblesContainer from '../LinkedItems/LinkedItemBubblesContainer'
|
||||
import NoteStatusIndicator, { NoteStatus } from './NoteStatusIndicator'
|
||||
@@ -50,7 +48,6 @@ import NoteViewFileDropTarget from './NoteViewFileDropTarget'
|
||||
const MinimumStatusDuration = 400
|
||||
const TextareaDebounce = 100
|
||||
const NoteEditingDisabledText = 'Note editing disabled.'
|
||||
const StickyHeaderScrollThresholdInPx = 20
|
||||
|
||||
function sortAlphabetically(array: SNComponent[]): SNComponent[] {
|
||||
return array.sort((a, b) => (a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1))
|
||||
@@ -83,8 +80,6 @@ type State = {
|
||||
rightResizerWidth: number
|
||||
rightResizerOffset: number
|
||||
|
||||
shouldStickyHeader: boolean
|
||||
|
||||
monospaceFont?: boolean
|
||||
lineHeight?: EditorLineHeight
|
||||
fontSize?: EditorFontSize
|
||||
@@ -134,8 +129,6 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
||||
|
||||
this.textAreaChangeDebounceSave = debounce(this.textAreaChangeDebounceSave, TextareaDebounce)
|
||||
|
||||
this.handleWindowScroll = debounce(this.handleWindowScroll, 10)
|
||||
|
||||
this.state = {
|
||||
availableStackComponents: [],
|
||||
editorStateDidLoad: false,
|
||||
@@ -155,23 +148,18 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
||||
leftResizerOffset: 0,
|
||||
rightResizerWidth: 0,
|
||||
rightResizerOffset: 0,
|
||||
shouldStickyHeader: false,
|
||||
editorFeatureIdentifier: this.controller.item.editorIdentifier,
|
||||
noteType: this.controller.item.noteType,
|
||||
}
|
||||
|
||||
this.noteViewElementRef = createRef<HTMLDivElement>()
|
||||
this.editorContentRef = createRef<HTMLDivElement>()
|
||||
|
||||
window.addEventListener('scroll', this.handleWindowScroll)
|
||||
}
|
||||
|
||||
override deinit() {
|
||||
super.deinit()
|
||||
;(this.controller as unknown) = undefined
|
||||
|
||||
window.removeEventListener('scroll', this.handleWindowScroll)
|
||||
|
||||
this.removeComponentStreamObserver?.()
|
||||
;(this.removeComponentStreamObserver as unknown) = undefined
|
||||
|
||||
@@ -932,12 +920,6 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
||||
}
|
||||
}
|
||||
|
||||
handleWindowScroll = () => {
|
||||
this.setState({
|
||||
shouldStickyHeader: window.scrollY > StickyHeaderScrollThresholdInPx,
|
||||
})
|
||||
}
|
||||
|
||||
override render() {
|
||||
if (this.state.showProtectedWarning || !this.application.isAuthorizedToRenderItem(this.note)) {
|
||||
return (
|
||||
@@ -951,7 +933,11 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
||||
}
|
||||
|
||||
return (
|
||||
<div aria-label="Note" className="section editor sn-component" ref={this.noteViewElementRef}>
|
||||
<div
|
||||
aria-label="Note"
|
||||
className="section editor sn-component max-h-screen md:max-h-full"
|
||||
ref={this.noteViewElementRef}
|
||||
>
|
||||
{this.note && (
|
||||
<NoteViewFileDropTarget
|
||||
note={this.note}
|
||||
@@ -983,15 +969,7 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
||||
{this.note && (
|
||||
<div
|
||||
id="editor-title-bar"
|
||||
className={classNames(
|
||||
'content-title-bar section-title-bar z-editor-title-bar w-full bg-default',
|
||||
this.state.shouldStickyHeader && 'fixed top-0',
|
||||
this.state.shouldStickyHeader
|
||||
? isIOS() || this.application.platform === Platform.Ios
|
||||
? 'pt-safe-top'
|
||||
: 'pt-4'
|
||||
: '',
|
||||
)}
|
||||
className="content-title-bar section-title-bar z-editor-title-bar w-full bg-default pt-4"
|
||||
>
|
||||
<div className="mb-2 flex flex-wrap items-start justify-between gap-2 md:mb-0 md:flex-nowrap md:gap-4 xl:items-center">
|
||||
<div className={classNames(this.state.noteLocked && 'locked', 'flex flex-grow items-center')}>
|
||||
@@ -1017,36 +995,32 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
||||
updateSavingIndicator={this.state.updateSavingIndicator}
|
||||
/>
|
||||
</div>
|
||||
{!this.state.shouldStickyHeader && (
|
||||
<div className="flex items-center gap-3">
|
||||
<LinkedItemsButton
|
||||
filesController={this.viewControllerManager.filesController}
|
||||
linkingController={this.viewControllerManager.linkingController}
|
||||
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
||||
/>
|
||||
<ChangeEditorButton
|
||||
application={this.application}
|
||||
viewControllerManager={this.viewControllerManager}
|
||||
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
||||
/>
|
||||
<PinNoteButton
|
||||
notesController={this.viewControllerManager.notesController}
|
||||
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
||||
/>
|
||||
<NotesOptionsPanel
|
||||
application={this.application}
|
||||
navigationController={this.viewControllerManager.navigationController}
|
||||
notesController={this.viewControllerManager.notesController}
|
||||
linkingController={this.viewControllerManager.linkingController}
|
||||
historyModalController={this.viewControllerManager.historyModalController}
|
||||
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-3">
|
||||
<LinkedItemsButton
|
||||
filesController={this.viewControllerManager.filesController}
|
||||
linkingController={this.viewControllerManager.linkingController}
|
||||
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
||||
/>
|
||||
<ChangeEditorButton
|
||||
application={this.application}
|
||||
viewControllerManager={this.viewControllerManager}
|
||||
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
||||
/>
|
||||
<PinNoteButton
|
||||
notesController={this.viewControllerManager.notesController}
|
||||
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
||||
/>
|
||||
<NotesOptionsPanel
|
||||
application={this.application}
|
||||
navigationController={this.viewControllerManager.navigationController}
|
||||
notesController={this.viewControllerManager.notesController}
|
||||
linkingController={this.viewControllerManager.linkingController}
|
||||
historyModalController={this.viewControllerManager.historyModalController}
|
||||
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{!this.state.shouldStickyHeader && (
|
||||
<LinkedItemBubblesContainer linkingController={this.viewControllerManager.linkingController} />
|
||||
)}
|
||||
<LinkedItemBubblesContainer linkingController={this.viewControllerManager.linkingController} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1082,12 +1056,8 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
||||
)}
|
||||
|
||||
{this.state.editorStateDidLoad && !this.state.editorComponentViewer && !this.state.textareaUnloading && (
|
||||
<AutoresizingNoteViewTextarea
|
||||
<textarea
|
||||
autoComplete="off"
|
||||
className={classNames(
|
||||
this.state.lineHeight && `leading-${this.state.lineHeight.toLowerCase()}`,
|
||||
this.state.fontSize && PlaintextFontSizeMapping[this.state.fontSize],
|
||||
)}
|
||||
dir="auto"
|
||||
id={ElementIds.NoteTextEditor}
|
||||
onChange={this.onTextAreaChange}
|
||||
@@ -1096,7 +1066,12 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
||||
ref={(ref) => ref && this.onSystemEditorLoad(ref)}
|
||||
spellCheck={this.state.spellcheck}
|
||||
value={this.state.editorText}
|
||||
/>
|
||||
className={classNames(
|
||||
'editable font-editor flex-grow',
|
||||
this.state.lineHeight && `leading-${this.state.lineHeight.toLowerCase()}`,
|
||||
this.state.fontSize && PlaintextFontSizeMapping[this.state.fontSize],
|
||||
)}
|
||||
></textarea>
|
||||
)}
|
||||
|
||||
{this.state.marginResizersEnabled && this.editorContentRef.current ? (
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import NotesOptions from './NotesOptions'
|
||||
@@ -8,6 +7,7 @@ import { NavigationController } from '@/Controllers/Navigation/NavigationControl
|
||||
import { HistoryModalController } from '@/Controllers/NoteHistory/HistoryModalController'
|
||||
import Popover from '../Popover/Popover'
|
||||
import { LinkingController } from '@/Controllers/LinkingController'
|
||||
import RoundIconButton from '../Button/RoundIconButton'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
@@ -39,15 +39,7 @@ const NotesOptionsPanel = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className="bg-text-padding flex h-8 min-w-8 cursor-pointer items-center justify-center rounded-full border border-solid border-border text-neutral hover:bg-contrast focus:bg-contrast"
|
||||
title="Note options menu"
|
||||
aria-label="Note options menu"
|
||||
onClick={toggleMenu}
|
||||
ref={buttonRef}
|
||||
>
|
||||
<Icon type="more" />
|
||||
</button>
|
||||
<RoundIconButton label="Note options menu" onClick={toggleMenu} ref={buttonRef} icon="more" />
|
||||
<Popover togglePopover={toggleMenu} anchorElement={buttonRef.current} open={isOpen} className="select-none py-2">
|
||||
<NotesOptions
|
||||
application={application}
|
||||
|
||||
@@ -27,7 +27,7 @@ const PinNoteButton: FunctionComponent<Props> = ({ className = '', notesControll
|
||||
|
||||
return (
|
||||
<button
|
||||
className={`sn-icon-button 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 ${
|
||||
className={`sn-icon-button flex h-9 w-9 cursor-pointer items-center justify-center rounded-full border border-solid border-border text-neutral hover:bg-contrast focus:bg-contrast md:h-8 md:w-8 ${
|
||||
pinned ? 'toggled' : ''
|
||||
} ${className}`}
|
||||
onClick={togglePinned}
|
||||
|
||||
@@ -58,7 +58,7 @@ const PositionedPopoverContent = ({
|
||||
<Portal>
|
||||
<div
|
||||
className={classNames(
|
||||
'absolute top-0 left-0 flex h-full w-full min-w-80 cursor-auto flex-col overflow-y-auto rounded bg-default shadow-main md:h-auto md:max-w-xs',
|
||||
'absolute top-0 left-0 flex h-screen w-full min-w-80 cursor-auto flex-col overflow-y-auto rounded bg-default shadow-main md:h-auto md:max-w-xs',
|
||||
overrideZIndex ? overrideZIndex : 'z-dropdown-menu',
|
||||
!isDesktopScreen ? 'pt-safe-top pb-safe-bottom' : '',
|
||||
!styles && 'md:invisible',
|
||||
|
||||
@@ -9,6 +9,7 @@ import LabsPane from './Labs/Labs'
|
||||
import Advanced from '@/Components/Preferences/Panes/General/Advanced/AdvancedSection'
|
||||
import PreferencesPane from '../../PreferencesComponents/PreferencesPane'
|
||||
import PlaintextDefaults from './PlaintextDefaults'
|
||||
import Persistence from './Persistence'
|
||||
|
||||
type Props = {
|
||||
viewControllerManager: ViewControllerManager
|
||||
@@ -18,6 +19,7 @@ type Props = {
|
||||
|
||||
const General: FunctionComponent<Props> = ({ viewControllerManager, application, extensionsLatestVersions }) => (
|
||||
<PreferencesPane>
|
||||
<Persistence application={application} />
|
||||
<PlaintextDefaults application={application} />
|
||||
<Defaults application={application} />
|
||||
<Tools application={application} />
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import StyledRadioInput from '@/Components/Radio/StyledRadioInput'
|
||||
import { useState } from 'react'
|
||||
import { Title } from '../../PreferencesComponents/Content'
|
||||
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
}
|
||||
|
||||
export const ShouldPersistNoteStateKey = 'ShouldPersistNoteState'
|
||||
|
||||
const Persistence = ({ application }: Props) => {
|
||||
const [shouldPersistNoteState, setShouldPersistNoteState] = useState(application.getValue(ShouldPersistNoteStateKey))
|
||||
|
||||
const toggleStatePersistence = (shouldPersist: boolean) => {
|
||||
application.setValue(ShouldPersistNoteStateKey, shouldPersist)
|
||||
setShouldPersistNoteState(shouldPersist)
|
||||
}
|
||||
|
||||
return (
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
<Title className="mb-2">When opening the app, show...</Title>
|
||||
<label className="mb-2 flex items-center gap-2 text-sm font-medium">
|
||||
<StyledRadioInput
|
||||
name="state-persistence"
|
||||
checked={!shouldPersistNoteState}
|
||||
onChange={(event) => {
|
||||
toggleStatePersistence(!event.target.checked)
|
||||
}}
|
||||
/>
|
||||
The first note in the list
|
||||
</label>
|
||||
<label className="flex items-center gap-2 text-sm font-medium">
|
||||
<StyledRadioInput
|
||||
name="state-persistence"
|
||||
checked={!!shouldPersistNoteState}
|
||||
onChange={(event) => {
|
||||
toggleStatePersistence(event.target.checked)
|
||||
}}
|
||||
/>
|
||||
The last viewed note
|
||||
</label>
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
)
|
||||
}
|
||||
|
||||
export default Persistence
|
||||
@@ -71,8 +71,8 @@ const PreferencesView: FunctionComponent<PreferencesProps> = ({
|
||||
onClick={() => {
|
||||
closePreferences()
|
||||
}}
|
||||
type="normal"
|
||||
icon="close"
|
||||
label="Close preferences"
|
||||
/>
|
||||
</div>
|
||||
<PreferencesCanvas
|
||||
|
||||
@@ -16,7 +16,7 @@ import FocusModeSwitch from './FocusModeSwitch'
|
||||
import ThemesMenuButton from './ThemesMenuButton'
|
||||
import { ThemeItem } from './ThemeItem'
|
||||
import { sortThemes } from '@/Utils/SortThemes'
|
||||
import RadioIndicator from '../RadioIndicator/RadioIndicator'
|
||||
import RadioIndicator from '../Radio/RadioIndicator'
|
||||
import HorizontalSeparator from '../Shared/HorizontalSeparator'
|
||||
import { QuickSettingsController } from '@/Controllers/QuickSettingsController'
|
||||
import PanelSettingsSection from './PanelSettingsSection'
|
||||
|
||||
@@ -5,7 +5,7 @@ import Icon from '@/Components/Icon/Icon'
|
||||
import { usePremiumModal } from '@/Hooks/usePremiumModal'
|
||||
import Switch from '@/Components/Switch/Switch'
|
||||
import { ThemeItem } from './ThemeItem'
|
||||
import RadioIndicator from '../RadioIndicator/RadioIndicator'
|
||||
import RadioIndicator from '../Radio/RadioIndicator'
|
||||
import { PremiumFeatureIconClass, PremiumFeatureIconName } from '../Icon/PremiumFeatureIcon'
|
||||
import { isMobileScreen } from '@/Utils'
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||
import { ComponentPropsWithoutRef } from 'react'
|
||||
import RadioIndicator from './RadioIndicator'
|
||||
|
||||
type Props = ComponentPropsWithoutRef<'input'>
|
||||
|
||||
const StyledRadioInput = (props: Props) => {
|
||||
return (
|
||||
<div className="flex">
|
||||
<input type="radio" className={classNames('h-0 w-0 opacity-0', props.className)} {...props} />
|
||||
<RadioIndicator checked={!!props.checked} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default StyledRadioInput
|
||||
@@ -1,19 +1,5 @@
|
||||
import { IconType } from '@standardnotes/snjs'
|
||||
|
||||
export enum AppPaneId {
|
||||
Navigation = 'NavigationColumn',
|
||||
Items = 'ItemsColumn',
|
||||
Editor = 'EditorColumn',
|
||||
}
|
||||
|
||||
export const AppPaneTitles = {
|
||||
[AppPaneId.Navigation]: 'Navigation',
|
||||
[AppPaneId.Items]: 'Notes & Files',
|
||||
[AppPaneId.Editor]: 'Editor',
|
||||
}
|
||||
|
||||
export const AppPaneIcons: Record<AppPaneId, IconType> = {
|
||||
[AppPaneId.Navigation]: 'hashtag',
|
||||
[AppPaneId.Items]: 'notes',
|
||||
[AppPaneId.Editor]: 'plain-text',
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user