Compare commits

...
76 changed files with 752 additions and 417 deletions
@@ -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
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.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
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api",
"version": "1.17.1",
"version": "1.17.2",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@standardnotes/desktop",
"main": "./app/dist/index.js",
"version": "3.23.234",
"version": "3.23.236",
"license": "AGPL-3.0-or-later",
"author": "Standard Notes.",
"private": true,
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.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 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/encryption",
"version": "1.18.4",
"version": "1.18.5",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.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 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/filepicker",
"version": "1.24.6",
"version": "1.24.7",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.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 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files",
"version": "1.11.6",
"version": "1.11.7",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/mobile",
"version": "3.43.21",
"version": "3.43.23",
"author": "Standard Notes.",
"private": true,
"license": "AGPL-3.0-or-later",
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.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
+2 -1
View File
@@ -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())
+6 -10
View File
@@ -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'
@@ -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'
+4 -3
View File
@@ -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'
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.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 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/releases",
"version": "1.3.163",
"version": "1.3.165",
"license": "AGPL-3.0-or-later",
"main": "dist/releases.json",
"types": "dist/index.d.ts",
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.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 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/services",
"version": "1.34.3",
"version": "1.34.4",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [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
+1 -1
View File
@@ -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 {
-1
View File
@@ -1,3 +1,2 @@
export * from './ApplicationEventPayload'
export * from './IconType'
export * from './UuidString'
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/snjs",
"version": "2.142.3",
"version": "2.143.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.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
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/ui-services",
"version": "1.9.1",
"version": "1.9.2",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [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
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/web",
"version": "3.82.6",
"version": "3.84.0",
"license": "AGPL-3.0-or-later",
"main": "dist/app.js",
"author": "Standard Notes.",
@@ -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}
@@ -111,6 +111,10 @@ const ContentListView: FunctionComponent<Props> = ({
const { selectedUuids, selectNextItem, selectPreviousItem } = selectionController
const { selected: selectedTag } = navigationController
const icon = selectedTag?.iconString
const isFilesSmartView = useMemo(
() => navigationController.selected?.uuid === SystemViewId.Files,
[navigationController.selected?.uuid],
@@ -259,6 +263,7 @@ const ContentListView: FunctionComponent<Props> = ({
<ContentListHeader
application={application}
panelTitle={panelTitle}
icon={icon}
addButtonLabel={addButtonLabel}
addNewItem={addNewItem}
isFilesSmartView={isFilesSmartView}
@@ -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>
@@ -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>
))}
@@ -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
@@ -6,6 +6,7 @@ import { IconType } from '@standardnotes/snjs'
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants'
import { MenuItemType } from './MenuItemType'
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,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}
@@ -71,8 +71,8 @@ const PreferencesView: FunctionComponent<PreferencesProps> = ({
onClick={() => {
closePreferences()
}}
type="normal"
icon="close"
label="Close preferences"
/>
</div>
<PreferencesCanvas
@@ -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',
}
@@ -2,7 +2,7 @@ import Icon from '@/Components/Icon/Icon'
import { FeaturesController } from '@/Controllers/FeaturesController'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import '@reach/tooltip/styles.css'
import { SmartView, SystemViewId, IconType, isSystemView } from '@standardnotes/snjs'
import { SmartView, SystemViewId, isSystemView } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import {
FormEventHandler,
@@ -27,19 +27,6 @@ type Props = {
const PADDING_BASE_PX = 14
const PADDING_PER_LEVEL_PX = 21
const smartViewIconType = (view: SmartView, isSelected: boolean): IconType => {
const mapping: Record<SystemViewId, IconType> = {
[SystemViewId.AllNotes]: isSelected ? 'notes-filled' : 'notes',
[SystemViewId.Files]: 'folder',
[SystemViewId.ArchivedNotes]: 'archive',
[SystemViewId.TrashedNotes]: 'trash',
[SystemViewId.UntaggedNotes]: 'hashtag-off',
[SystemViewId.StarredNotes]: 'star-filled',
}
return mapping[view.uuid as SystemViewId] || 'hashtag'
}
const getIconClass = (view: SmartView, isSelected: boolean): string => {
const mapping: Partial<Record<SystemViewId, string>> = {
[SystemViewId.StarredNotes]: 'text-warning',
@@ -111,7 +98,6 @@ const SmartViewsListItem: FunctionComponent<Props> = ({ view, tagsState }) => {
}, [tagsState, view])
const isFaded = false
const iconType = smartViewIconType(view, isSelected)
const iconClass = getIconClass(view, isSelected)
return (
@@ -127,7 +113,7 @@ const SmartViewsListItem: FunctionComponent<Props> = ({ view, tagsState }) => {
>
<div className="tag-info">
<div className={'tag-icon mr-2'}>
<Icon type={iconType} className={iconClass} />
<Icon type={view.iconString} className={iconClass} />
</div>
{isEditing ? (
<input
@@ -5,13 +5,14 @@ import Menu from '@/Components/Menu/Menu'
import MenuItem from '@/Components/Menu/MenuItem'
import { MenuItemType } from '@/Components/Menu/MenuItemType'
import { usePremiumModal } from '@/Hooks/usePremiumModal'
import { SNTag } from '@standardnotes/snjs'
import { SNTag, VectorIconNameOrEmoji, DefaultTagIconName } from '@standardnotes/snjs'
import { useCloseOnClickOutside } from '@/Hooks/useCloseOnClickOutside'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import HorizontalSeparator from '../Shared/HorizontalSeparator'
import { formatDateForContextMenu } from '@/Utils/DateUtils'
import { PremiumFeatureIconClass, PremiumFeatureIconName } from '../Icon/PremiumFeatureIcon'
import Popover from '../Popover/Popover'
import IconPicker from '../Icon/IconPicker'
type ContextMenuProps = {
navigationController: NavigationController
@@ -22,7 +23,7 @@ type ContextMenuProps = {
const TagContextMenu = ({ navigationController, isEntitledToFolders, selectedTag }: ContextMenuProps) => {
const premiumModal = usePremiumModal()
const { contextMenuOpen, contextMenuClickLocation } = navigationController
const { contextMenuOpen, contextMenuClickLocation, application } = navigationController
const contextMenuRef = useRef<HTMLDivElement>(null)
useCloseOnClickOutside(contextMenuRef, () => navigationController.setContextMenuOpen(false))
@@ -39,6 +40,7 @@ const TagContextMenu = ({ navigationController, isEntitledToFolders, selectedTag
const onClickRename = useCallback(() => {
navigationController.setContextMenuOpen(false)
navigationController.editingFrom = navigationController.contextMenuOpenFrom
navigationController.editingTag = selectedTag
}, [navigationController, selectedTag])
@@ -51,6 +53,15 @@ const TagContextMenu = ({ navigationController, isEntitledToFolders, selectedTag
[selectedTag.userModifiedDate],
)
const handleIconChange = (value?: VectorIconNameOrEmoji) => {
navigationController.setIcon(selectedTag, value || DefaultTagIconName)
}
const onClickStar = useCallback(() => {
navigationController.setFavorite(selectedTag, !selectedTag.starred).catch(console.error)
navigationController.setContextMenuOpen(false)
}, [navigationController, selectedTag])
const tagCreatedAt = useMemo(() => formatDateForContextMenu(selectedTag.created_at), [selectedTag.created_at])
return (
@@ -62,6 +73,20 @@ const TagContextMenu = ({ navigationController, isEntitledToFolders, selectedTag
>
<div ref={contextMenuRef}>
<Menu a11yLabel="Tag context menu" isOpen={contextMenuOpen}>
<IconPicker
key={'icon-picker'}
onIconChange={handleIconChange}
selectedValue={selectedTag.iconString}
platform={application.platform}
className={'px-3 py-1.5'}
/>
<HorizontalSeparator classes="my-2" />
<MenuItem type={MenuItemType.IconButton} className={'justify-between py-1.5'} onClick={onClickStar}>
<div className="flex items-center">
<Icon type="star" className="mr-2 text-neutral" />
{selectedTag.starred ? 'Unfavorite' : 'Favorite'}
</div>
</MenuItem>
<MenuItem type={MenuItemType.IconButton} className={'justify-between py-1.5'} onClick={onClickAddSubtag}>
<div className="flex items-center">
<Icon type="add" className="mr-2 text-neutral" />
@@ -0,0 +1 @@
export type TagListSectionType = 'all' | 'favorites'
@@ -5,15 +5,17 @@ import { FunctionComponent, useCallback } from 'react'
import { DndProvider } from 'react-dnd'
import { HTML5Backend } from 'react-dnd-html5-backend'
import RootTagDropZone from './RootTagDropZone'
import { TagListSectionType } from './TagListSection'
import { TagsListItem } from './TagsListItem'
type Props = {
viewControllerManager: ViewControllerManager
type: TagListSectionType
}
const TagsList: FunctionComponent<Props> = ({ viewControllerManager }: Props) => {
const tagsState = viewControllerManager.navigationController
const allTags = tagsState.allLocalRootTags
const TagsList: FunctionComponent<Props> = ({ viewControllerManager, type }: Props) => {
const navigationController = viewControllerManager.navigationController
const allTags = type === 'all' ? navigationController.allLocalRootTags : navigationController.starredTags
const backend = HTML5Backend
@@ -23,10 +25,11 @@ const TagsList: FunctionComponent<Props> = ({ viewControllerManager }: Props) =>
x: posX,
y: posY,
})
viewControllerManager.navigationController.setContextMenuOpenFrom(type)
viewControllerManager.navigationController.reloadContextMenuLayout()
viewControllerManager.navigationController.setContextMenuOpen(true)
},
[viewControllerManager],
[viewControllerManager, type],
)
const onContextMenu = useCallback(
@@ -49,17 +52,20 @@ const TagsList: FunctionComponent<Props> = ({ viewControllerManager }: Props) =>
level={0}
key={tag.uuid}
tag={tag}
tagsState={tagsState}
type={type}
navigationController={navigationController}
features={viewControllerManager.featuresController}
linkingController={viewControllerManager.linkingController}
onContextMenu={onContextMenu}
/>
)
})}
<RootTagDropZone
tagsState={viewControllerManager.navigationController}
featuresState={viewControllerManager.featuresController}
/>
{type === 'all' && (
<RootTagDropZone
tagsState={viewControllerManager.navigationController}
featuresState={viewControllerManager.featuresController}
/>
)}
</>
)}
</DndProvider>
@@ -5,13 +5,14 @@ import { KeyboardKey } from '@standardnotes/ui-services'
import { FeaturesController } from '@/Controllers/FeaturesController'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import '@reach/tooltip/styles.css'
import { SNTag } from '@standardnotes/snjs'
import { IconType, SNTag } from '@standardnotes/snjs'
import { computed } from 'mobx'
import { observer } from 'mobx-react-lite'
import {
FormEventHandler,
FunctionComponent,
KeyboardEventHandler,
MouseEvent,
MouseEventHandler,
useCallback,
useEffect,
@@ -26,10 +27,12 @@ import { classNames } from '@/Utils/ConcatenateClassNames'
import { mergeRefs } from '@/Hooks/mergeRefs'
import { useFileDragNDrop } from '../FileDragNDropProvider/FileDragNDropProvider'
import { LinkingController } from '@/Controllers/LinkingController'
import { TagListSectionType } from './TagListSection'
type Props = {
tag: SNTag
tagsState: NavigationController
type: TagListSectionType
navigationController: NavigationController
features: FeaturesController
linkingController: LinkingController
level: number
@@ -40,25 +43,27 @@ const PADDING_BASE_PX = 14
const PADDING_PER_LEVEL_PX = 21
export const TagsListItem: FunctionComponent<Props> = observer(
({ tag, features, tagsState, level, onContextMenu, linkingController }) => {
({ tag, type, features, navigationController: navigationController, level, onContextMenu, linkingController }) => {
const { toggleAppPane } = useResponsiveAppPane()
const isFavorite = type === 'favorites'
const [title, setTitle] = useState(tag.title || '')
const [subtagTitle, setSubtagTitle] = useState('')
const inputRef = useRef<HTMLInputElement>(null)
const subtagInputRef = useRef<HTMLInputElement>(null)
const menuButtonRef = useRef<HTMLAnchorElement>(null)
const isSelected = tagsState.selected === tag
const isEditing = tagsState.editingTag === tag
const isAddingSubtag = tagsState.addingSubtagTo === tag
const noteCounts = computed(() => tagsState.getNotesCount(tag))
const isSelected = navigationController.selected === tag
const isEditing = navigationController.editingTag === tag && navigationController.editingFrom === type
const isAddingSubtag = navigationController.addingSubtagTo === tag
const noteCounts = computed(() => navigationController.getNotesCount(tag))
const childrenTags = computed(() => tagsState.getChildren(tag)).get()
const childrenTags = computed(() => navigationController.getChildren(tag)).get()
const hasChildren = childrenTags.length > 0
const hasFolders = features.hasFolders
const hasAtLeastOneFolder = tagsState.hasAtLeastOneFolder
const hasAtLeastOneFolder = navigationController.hasAtLeastOneFolder
const premiumModal = usePremiumModal()
@@ -76,27 +81,28 @@ export const TagsListItem: FunctionComponent<Props> = observer(
setTitle(tag.title || '')
}, [setTitle, tag])
const toggleChildren: MouseEventHandler = useCallback(
(e) => {
e.stopPropagation()
const toggleChildren = useCallback(
(e?: MouseEvent) => {
e?.stopPropagation()
const shouldShowChildren = !showChildren
setShowChildren(shouldShowChildren)
tagsState.setExpanded(tag, shouldShowChildren)
navigationController.setExpanded(tag, shouldShowChildren)
},
[showChildren, tag, tagsState],
[showChildren, tag, navigationController],
)
const selectCurrentTag = useCallback(async () => {
await tagsState.setSelectedTag(tag, {
await navigationController.setSelectedTag(tag, {
userTriggered: true,
})
toggleChildren()
toggleAppPane(AppPaneId.Items)
}, [tagsState, tag, toggleAppPane])
}, [navigationController, tag, toggleAppPane, toggleChildren])
const onBlur = useCallback(() => {
tagsState.save(tag, title).catch(console.error)
navigationController.save(tag, title).catch(console.error)
setTitle(tag.title)
}, [tagsState, tag, title, setTitle])
}, [navigationController, tag, title, setTitle])
const onInput: FormEventHandler = useCallback(
(e) => {
@@ -128,9 +134,9 @@ export const TagsListItem: FunctionComponent<Props> = observer(
}, [])
const onSubtagInputBlur = useCallback(() => {
tagsState.createSubtagAndAssignParent(tag, subtagTitle).catch(console.error)
navigationController.createSubtagAndAssignParent(tag, subtagTitle).catch(console.error)
setSubtagTitle('')
}, [subtagTitle, tag, tagsState])
}, [subtagTitle, tag, navigationController])
const onSubtagKeyDown: KeyboardEventHandler = useCallback(
(e) => {
@@ -166,21 +172,21 @@ export const TagsListItem: FunctionComponent<Props> = observer(
() => ({
accept: ItemTypes.TAG,
canDrop: (item) => {
return tagsState.isValidTagParent(tag, item as SNTag)
return navigationController.isValidTagParent(tag, item as SNTag)
},
drop: (item) => {
if (!hasFolders) {
premiumModal.activate(TAG_FOLDERS_FEATURE_NAME)
return
}
tagsState.assignParent(item.uuid, tag.uuid).catch(console.error)
navigationController.assignParent(item.uuid, tag.uuid).catch(console.error)
},
collect: (monitor) => ({
isOver: !!monitor.isOver(),
canDrop: !!monitor.canDrop(),
}),
}),
[tag, tagsState, hasFolders, premiumModal],
[tag, navigationController, hasFolders, premiumModal],
)
const readyToDrop = isOver && canDrop
@@ -194,16 +200,16 @@ export const TagsListItem: FunctionComponent<Props> = observer(
return
}
const contextMenuOpen = tagsState.contextMenuOpen
const contextMenuOpen = navigationController.contextMenuOpen
const menuButtonRect = menuButtonRef.current?.getBoundingClientRect()
if (contextMenuOpen) {
tagsState.setContextMenuOpen(false)
navigationController.setContextMenuOpen(false)
} else {
onContextMenu(tag, menuButtonRect.right, menuButtonRect.top)
}
},
[onContextMenu, tagsState, tag],
[onContextMenu, navigationController, tag],
)
const tagRef = useRef<HTMLDivElement>(null)
@@ -248,7 +254,7 @@ export const TagsListItem: FunctionComponent<Props> = observer(
}}
>
<div className="tag-info" title={title} ref={dropRef}>
{hasAtLeastOneFolder && (
{hasAtLeastOneFolder && !isFavorite && (
<div className="tag-fold-container">
<a
role="button"
@@ -262,12 +268,12 @@ export const TagsListItem: FunctionComponent<Props> = observer(
</div>
)}
<div className={'tag-icon draggable mr-2'} ref={dragRef}>
<Icon type="hashtag" className={`${isSelected ? 'text-info' : 'text-neutral'}`} />
<Icon type={tag.iconString as IconType} className={`${isSelected ? 'text-info' : 'text-neutral'}`} />
</div>
{isEditing ? (
<input
className={'title editing focus:shadow-none focus:outline-none'}
id={`react-tag-${tag.uuid}`}
id={`react-tag-${tag.uuid}-${type}`}
onBlur={onBlur}
onInput={onInput}
value={title}
@@ -278,7 +284,7 @@ export const TagsListItem: FunctionComponent<Props> = observer(
) : (
<div
className={'title overflow-hidden text-left focus:shadow-none focus:outline-none'}
id={`react-tag-${tag.uuid}`}
id={`react-tag-${tag.uuid}-${type}`}
>
{title}
</div>
@@ -334,7 +340,8 @@ export const TagsListItem: FunctionComponent<Props> = observer(
level={level + 1}
key={tag.uuid}
tag={tag}
tagsState={tagsState}
type={type}
navigationController={navigationController}
features={features}
linkingController={linkingController}
onContextMenu={onContextMenu}
@@ -52,22 +52,37 @@ const TagsSection: FunctionComponent<Props> = ({ viewControllerManager }) => {
}, [viewControllerManager, checkIfMigrationNeeded])
return (
<section>
<div className={'section-title-bar'}>
<div className="section-title-bar-header">
<TagsSectionTitle
features={viewControllerManager.featuresController}
hasMigration={hasMigration}
onClickMigration={runMigration}
/>
<TagsSectionAddButton
tags={viewControllerManager.navigationController}
features={viewControllerManager.featuresController}
/>
<>
{viewControllerManager.navigationController.starredTags.length > 0 && (
<section>
<div className={'section-title-bar'}>
<div className="section-title-bar-header">
<div className="title text-sm">
<span className="font-bold">Favorites</span>
</div>
</div>
</div>
<TagsList type="favorites" viewControllerManager={viewControllerManager} />
</section>
)}
<section>
<div className={'section-title-bar'}>
<div className="section-title-bar-header">
<TagsSectionTitle
features={viewControllerManager.featuresController}
hasMigration={hasMigration}
onClickMigration={runMigration}
/>
<TagsSectionAddButton
tags={viewControllerManager.navigationController}
features={viewControllerManager.featuresController}
/>
</div>
</div>
</div>
<TagsList viewControllerManager={viewControllerManager} />
</section>
<TagsList type="all" viewControllerManager={viewControllerManager} />
</section>
</>
)
}
@@ -64,7 +64,7 @@ export class ItemListController
items: ListableContentItem[] = []
notesToDisplay = 0
pageSize = 0
panelTitle = 'All Notes'
panelTitle = 'Notes'
panelWidth = 0
renderedItems: ListableContentItem[] = []
searchSubmitted = false
@@ -236,9 +236,11 @@ export class LinkingController extends AbstractViewController {
} else if (item instanceof FileItem) {
const icon = this.application.iconsController.getIconForFileType(item.mimeType)
return [icon, 'text-info']
} else if (item instanceof SNTag) {
return [item.iconString as IconType, 'text-info']
}
return ['hashtag', 'text-info']
throw new Error('Unhandled case in getLinkedItemIcon')
}
activateItem = async (item: LinkableItem): Promise<AppPaneId | undefined> => {
@@ -14,6 +14,7 @@ import {
SystemViewId,
InternalEventBus,
InternalEventPublishStrategy,
VectorIconNameOrEmoji,
} from '@standardnotes/snjs'
import { action, computed, makeAutoObservable, makeObservable, observable, reaction, runInAction } from 'mobx'
import { WebApplication } from '../../Application/Application'
@@ -24,6 +25,7 @@ import { AnyTag } from './AnyTagType'
import { CrossControllerEvent } from '../CrossControllerEvent'
import { AbstractViewController } from '../Abstract/AbstractViewController'
import { Persistable } from '../Abstract/Persistable'
import { TagListSectionType } from '@/Components/Tags/TagListSection'
export type NavigationControllerPersistableValue = {
selectedTagUuid: AnyTag['uuid']
@@ -35,14 +37,17 @@ export class NavigationController
{
tags: SNTag[] = []
smartViews: SmartView[] = []
starredTags: SNTag[] = []
allNotesCount_ = 0
selectedUuid: AnyTag['uuid'] | undefined = undefined
selected_: AnyTag | undefined
previouslySelected_: AnyTag | undefined
editing_: SNTag | SmartView | undefined
editingFrom?: TagListSectionType
addingSubtagTo: SNTag | undefined
contextMenuOpen = false
contextMenuOpenFrom?: TagListSectionType
contextMenuPosition: { top?: number; left: number; bottom?: number } = {
top: 0,
left: 0,
@@ -66,6 +71,7 @@ export class NavigationController
makeObservable(this, {
tags: observable,
starredTags: observable,
smartViews: observable.ref,
hasAtLeastOneFolder: computed,
allNotesCount_: observable,
@@ -111,7 +117,7 @@ export class NavigationController
this.application.streamItems([ContentType.Tag, ContentType.SmartView], ({ changed, removed }) => {
runInAction(() => {
this.tags = this.application.items.getDisplayableTags()
this.starredTags = this.tags.filter((tag) => tag.starred)
this.smartViews = this.application.items.getSmartViews()
const currentSelectedTag = this.selected_
@@ -266,6 +272,10 @@ export class NavigationController
this.addingSubtagTo = tag
}
setContextMenuOpenFrom(section: TagListSectionType): void {
this.contextMenuOpenFrom = section
}
setContextMenuOpen(open: boolean): void {
this.contextMenuOpen = open
}
@@ -476,6 +486,22 @@ export class NavigationController
.catch(console.error)
}
public async setFavorite(tag: SNTag, favorite: boolean) {
return this.application.mutator
.changeAndSaveItem<TagMutator>(tag, (mutator) => {
mutator.starred = favorite
})
.catch(console.error)
}
public setIcon(tag: SNTag, icon: VectorIconNameOrEmoji) {
this.application.mutator
.changeAndSaveItem<TagMutator>(tag, (mutator) => {
mutator.iconString = icon
})
.catch(console.error)
}
public get editingTag(): SNTag | SmartView | undefined {
return this.editing_
}
@@ -500,6 +526,7 @@ export class NavigationController
}
public undoCreateNewTag() {
this.editingFrom = undefined
this.editing_ = undefined
const previousTag = this.previouslySelected_ || this.smartViews[0]
void this.setSelectedTag(previousTag)
@@ -528,6 +555,7 @@ export class NavigationController
const hasDuplicatedTitle = siblings.some((other) => other.title.toLowerCase() === newTitle.toLowerCase())
runInAction(() => {
this.editingFrom = undefined
this.editing_ = undefined
})
+4
View File
@@ -8,6 +8,8 @@ module.exports = {
extend: {
spacing: {
4.5: '1.125rem',
8.5: '2.125rem',
13: '3.25rem',
18: '4.5rem',
26: '6.5rem',
30: '7.5rem',
@@ -27,6 +29,8 @@ module.exports = {
4: '1rem',
5: '1.25rem',
8: '2rem',
8.5: '2.125rem',
9: '2.25rem',
15: '3.75rem',
20: '5rem',
24: '6rem',
+1
View File
@@ -6666,6 +6666,7 @@ __metadata:
"@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
lodash: ^4.17.21