refactor: replace 'preact' with 'react' (#1048)

This commit is contained in:
Aman Harwara
2022-05-30 12:42:52 +05:30
committed by GitHub
parent e74b4953ea
commit 8c368dd96b
231 changed files with 4794 additions and 4302 deletions

View File

@@ -3,11 +3,10 @@ import { KeyboardKey } from '@/Services/IOService'
import { AppState } from '@/UIModels/AppState'
import { UuidString } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'preact'
import { FunctionComponent, KeyboardEventHandler, UIEventHandler, useCallback } from 'react'
import { FOCUSABLE_BUT_NOT_TABBABLE, NOTES_LIST_SCROLL_THRESHOLD } from '@/Constants'
import { ListableContentItem } from './Types/ListableContentItem'
import { ContentListItem } from './ContentListItem'
import { useCallback } from 'preact/hooks'
import ContentListItem from './ContentListItem'
type Props = {
application: WebApplication
@@ -17,59 +16,59 @@ type Props = {
paginate: () => void
}
export const ContentList: FunctionComponent<Props> = observer(
({ application, appState, items, selectedItems, paginate }) => {
const { selectPreviousItem, selectNextItem } = appState.contentListView
const { hideTags, hideDate, hideNotePreview, hideEditorIcon } = appState.contentListView.webDisplayOptions
const { sortBy } = appState.contentListView.displayOptions
const ContentList: FunctionComponent<Props> = ({ application, appState, items, selectedItems, paginate }) => {
const { selectPreviousItem, selectNextItem } = appState.contentListView
const { hideTags, hideDate, hideNotePreview, hideEditorIcon } = appState.contentListView.webDisplayOptions
const { sortBy } = appState.contentListView.displayOptions
const onScroll = useCallback(
(e: Event) => {
const offset = NOTES_LIST_SCROLL_THRESHOLD
const element = e.target as HTMLElement
if (element.scrollTop + element.offsetHeight >= element.scrollHeight - offset) {
paginate()
}
},
[paginate],
)
const onScroll: UIEventHandler = useCallback(
(e) => {
const offset = NOTES_LIST_SCROLL_THRESHOLD
const element = e.target as HTMLElement
if (element.scrollTop + element.offsetHeight >= element.scrollHeight - offset) {
paginate()
}
},
[paginate],
)
const onKeyDown = useCallback(
(e: KeyboardEvent) => {
if (e.key === KeyboardKey.Up) {
e.preventDefault()
selectPreviousItem()
} else if (e.key === KeyboardKey.Down) {
e.preventDefault()
selectNextItem()
}
},
[selectNextItem, selectPreviousItem],
)
const onKeyDown: KeyboardEventHandler = useCallback(
(e) => {
if (e.key === KeyboardKey.Up) {
e.preventDefault()
selectPreviousItem()
} else if (e.key === KeyboardKey.Down) {
e.preventDefault()
selectNextItem()
}
},
[selectNextItem, selectPreviousItem],
)
return (
<div
className="infinite-scroll focus:shadow-none focus:outline-none"
id="notes-scrollable"
onScroll={onScroll}
onKeyDown={onKeyDown}
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
>
{items.map((item) => (
<ContentListItem
key={item.uuid}
application={application}
appState={appState}
item={item}
selected={!!selectedItems[item.uuid]}
hideDate={hideDate}
hidePreview={hideNotePreview}
hideTags={hideTags}
hideIcon={hideEditorIcon}
sortBy={sortBy}
/>
))}
</div>
)
},
)
return (
<div
className="infinite-scroll focus:shadow-none focus:outline-none"
id="notes-scrollable"
onScroll={onScroll}
onKeyDown={onKeyDown}
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
>
{items.map((item) => (
<ContentListItem
key={item.uuid}
application={application}
appState={appState}
item={item}
selected={!!selectedItems[item.uuid]}
hideDate={hideDate}
hidePreview={hideNotePreview}
hideTags={hideTags}
hideIcon={hideEditorIcon}
sortBy={sortBy}
/>
))}
</div>
)
}
export default observer(ContentList)

View File

@@ -1,10 +1,10 @@
import { ContentType, SNTag } from '@standardnotes/snjs'
import { FunctionComponent } from 'preact'
import { FileListItem } from './FileListItem'
import { NoteListItem } from './NoteListItem'
import { FunctionComponent } from 'react'
import FileListItem from './FileListItem'
import NoteListItem from './NoteListItem'
import { AbstractListItemProps } from './Types/AbstractListItemProps'
export const ContentListItem: FunctionComponent<AbstractListItemProps> = (props) => {
const ContentListItem: FunctionComponent<AbstractListItemProps> = (props) => {
const getTags = () => {
if (props.hideTags) {
return []
@@ -34,3 +34,5 @@ export const ContentListItem: FunctionComponent<AbstractListItemProps> = (props)
return null
}
}
export default ContentListItem

View File

@@ -4,27 +4,29 @@ import { AppState } from '@/UIModels/AppState'
import { PANEL_NAME_NOTES } from '@/Constants'
import { PrefKey } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'preact'
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
import { ContentList } from '@/Components/ContentListView/ContentList'
import { NotesListOptionsMenu } from '@/Components/ContentListView/NotesListOptionsMenu'
import { NoAccountWarning } from '@/Components/NoAccountWarning/NoAccountWarning'
import { SearchOptions } from '@/Components/SearchOptions/SearchOptions'
import { PanelSide, ResizeFinishCallback, PanelResizer, PanelResizeType } from '@/Components/PanelResizer/PanelResizer'
import {
ChangeEventHandler,
FunctionComponent,
KeyboardEventHandler,
useCallback,
useEffect,
useRef,
useState,
} from 'react'
import ContentList from '@/Components/ContentListView/ContentList'
import NotesListOptionsMenu from '@/Components/ContentListView/NotesListOptionsMenu'
import NoAccountWarningWrapper from '@/Components/NoAccountWarning/NoAccountWarning'
import SearchOptions from '@/Components/SearchOptions/SearchOptions'
import PanelResizer, { PanelSide, ResizeFinishCallback, PanelResizeType } from '@/Components/PanelResizer/PanelResizer'
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
import { isStateDealloced } from '@/UIModels/AppState/AbstractState'
type Props = {
application: WebApplication
appState: AppState
}
export const ContentListView: FunctionComponent<Props> = observer(({ application, appState }) => {
if (isStateDealloced(appState)) {
return null
}
const ContentListView: FunctionComponent<Props> = ({ application, appState }) => {
const itemsViewPanelRef = useRef<HTMLDivElement>(null)
const displayOptionsMenuRef = useRef<HTMLDivElement>(null)
@@ -104,9 +106,9 @@ export const ContentListView: FunctionComponent<Props> = observer(({ application
}
}, [application.io, createNewNote, searchBarElement, selectNextItem, selectPreviousItem])
const onNoteFilterTextChange = useCallback(
(e: Event) => {
setNoteFilterText((e.target as HTMLInputElement).value)
const onNoteFilterTextChange: ChangeEventHandler<HTMLInputElement> = useCallback(
(e) => {
setNoteFilterText(e.target.value)
},
[setNoteFilterText],
)
@@ -114,8 +116,8 @@ export const ContentListView: FunctionComponent<Props> = observer(({ application
const onSearchFocused = useCallback(() => setFocusedSearch(true), [])
const onSearchBlurred = useCallback(() => setFocusedSearch(false), [])
const onNoteFilterKeyUp = useCallback(
(e: KeyboardEvent) => {
const onNoteFilterKeyUp: KeyboardEventHandler = useCallback(
(e) => {
if (e.key === KeyboardKey.Enter) {
onFilterEnter()
}
@@ -176,10 +178,10 @@ export const ContentListView: FunctionComponent<Props> = observer(({ application
onKeyUp={onNoteFilterKeyUp}
onFocus={onSearchFocused}
onBlur={onSearchBlurred}
autocomplete="off"
autoComplete="off"
/>
{noteFilterText && (
<button onClick={clearFilterText} aria-role="button" id="search-clear-button">
<button onClick={clearFilterText} id="search-clear-button">
</button>
)}
@@ -191,7 +193,7 @@ export const ContentListView: FunctionComponent<Props> = observer(({ application
</div>
)}
</div>
<NoAccountWarning appState={appState} />
<NoAccountWarningWrapper appState={appState} />
</div>
<div id="items-menu-bar" className="sn-component" ref={displayOptionsMenuRef}>
<div className="sk-app-bar no-edges">
@@ -253,4 +255,6 @@ export const ContentListView: FunctionComponent<Props> = observer(({ application
)}
</div>
)
})
}
export default observer(ContentListView)

View File

@@ -1,81 +1,89 @@
import { FileItem } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'preact'
import { useCallback } from 'preact/hooks'
import { getFileIconComponent } from '../AttachedFilesPopover/PopoverFileItem'
import { ListItemConflictIndicator } from './ListItemConflictIndicator'
import { ListItemFlagIcons } from './ListItemFlagIcons'
import { ListItemTags } from './ListItemTags'
import { ListItemMetadata } from './ListItemMetadata'
import { FunctionComponent, useCallback } from 'react'
import { getFileIconComponent } from '../AttachedFilesPopover/getFileIconComponent'
import ListItemConflictIndicator from './ListItemConflictIndicator'
import ListItemFlagIcons from './ListItemFlagIcons'
import ListItemTags from './ListItemTags'
import ListItemMetadata from './ListItemMetadata'
import { DisplayableListItemProps } from './Types/DisplayableListItemProps'
export const FileListItem: FunctionComponent<DisplayableListItemProps> = observer(
({ application, appState, hideDate, hideIcon, hideTags, item, selected, sortBy, tags }) => {
const openFileContextMenu = useCallback(
(posX: number, posY: number) => {
appState.files.setFileContextMenuLocation({
x: posX,
y: posY,
})
appState.files.setShowFileContextMenu(true)
},
[appState.files],
)
const openContextMenu = useCallback(
(posX: number, posY: number) => {
void appState.contentListView.selectItemWithScrollHandling(item, {
userTriggered: true,
scrollIntoView: false,
})
openFileContextMenu(posX, posY)
},
[appState.contentListView, item, openFileContextMenu],
)
const onClick = useCallback(() => {
void appState.selectedItems.selectItem(item.uuid, true).then(({ didSelect }) => {
if (didSelect && appState.selectedItems.selectedItemsCount < 2) {
appState.filePreviewModal.activate(item as FileItem, appState.files.allFiles)
}
const FileListItem: FunctionComponent<DisplayableListItemProps> = ({
application,
appState,
hideDate,
hideIcon,
hideTags,
item,
selected,
sortBy,
tags,
}) => {
const openFileContextMenu = useCallback(
(posX: number, posY: number) => {
appState.files.setFileContextMenuLocation({
x: posX,
y: posY,
})
}, [appState.filePreviewModal, appState.files.allFiles, appState.selectedItems, item])
appState.files.setShowFileContextMenu(true)
},
[appState.files],
)
const IconComponent = () =>
getFileIconComponent(
application.iconsController.getIconForFileType((item as FileItem).mimeType),
'w-5 h-5 flex-shrink-0',
)
const openContextMenu = useCallback(
async (posX: number, posY: number) => {
const { didSelect } = await appState.selectedItems.selectItem(item.uuid)
if (didSelect) {
openFileContextMenu(posX, posY)
}
},
[appState.selectedItems, item.uuid, openFileContextMenu],
)
return (
<div
className={`content-list-item flex items-stretch w-full cursor-pointer ${
selected && 'selected border-0 border-l-2px border-solid border-info'
}`}
id={item.uuid}
onClick={onClick}
onContextMenu={(event) => {
event.preventDefault()
openContextMenu(event.clientX, event.clientY)
}}
>
{!hideIcon ? (
<div className="flex flex-col items-center justify-between p-4.5 pr-3 mr-0">
<IconComponent />
</div>
) : (
<div className="pr-4" />
)}
<div className="flex-grow min-w-0 py-4 px-0 border-0 border-b-1 border-solid border-main">
<div className="flex items-start justify-between font-semibold text-base leading-1.3 overflow-hidden">
<div className="break-word mr-2">{item.title}</div>
</div>
<ListItemMetadata item={item} hideDate={hideDate} sortBy={sortBy} />
<ListItemTags hideTags={hideTags} tags={tags} />
<ListItemConflictIndicator item={item} />
</div>
<ListItemFlagIcons item={item} />
</div>
const onClick = useCallback(() => {
void appState.selectedItems.selectItem(item.uuid, true).then(({ didSelect }) => {
if (didSelect && appState.selectedItems.selectedItemsCount < 2) {
appState.filePreviewModal.activate(item as FileItem, appState.files.allFiles)
}
})
}, [appState.filePreviewModal, appState.files.allFiles, appState.selectedItems, item])
const IconComponent = () =>
getFileIconComponent(
application.iconsController.getIconForFileType((item as FileItem).mimeType),
'w-5 h-5 flex-shrink-0',
)
},
)
return (
<div
className={`content-list-item flex items-stretch w-full cursor-pointer ${
selected && 'selected border-0 border-l-2px border-solid border-info'
}`}
id={item.uuid}
onClick={onClick}
onContextMenu={(event) => {
event.preventDefault()
void openContextMenu(event.clientX, event.clientY)
}}
>
{!hideIcon ? (
<div className="flex flex-col items-center justify-between p-4.5 pr-3 mr-0">
<IconComponent />
</div>
) : (
<div className="pr-4" />
)}
<div className="flex-grow min-w-0 py-4 px-0 border-0 border-b-1 border-solid border-main">
<div className="flex items-start justify-between font-semibold text-base leading-1.3 overflow-hidden">
<div className="break-word mr-2">{item.title}</div>
</div>
<ListItemMetadata item={item} hideDate={hideDate} sortBy={sortBy} />
<ListItemTags hideTags={hideTags} tags={tags} />
<ListItemConflictIndicator item={item} />
</div>
<ListItemFlagIcons item={item} />
</div>
)
}
export default observer(FileListItem)

View File

@@ -1,11 +1,13 @@
import { FunctionComponent } from 'preact'
import { FunctionComponent } from 'react'
import { ListableContentItem } from './Types/ListableContentItem'
export const ListItemConflictIndicator: FunctionComponent<{
type Props = {
item: {
conflictOf?: ListableContentItem['conflictOf']
}
}> = ({ item }) => {
}
const ListItemConflictIndicator: FunctionComponent<Props> = ({ item }) => {
return item.conflictOf ? (
<div className="flex flex-wrap items-center mt-0.5">
<div className={'py-1 px-1.5 rounded mr-1 mt-2 bg-danger color-danger-contrast'}>
@@ -14,3 +16,5 @@ export const ListItemConflictIndicator: FunctionComponent<{
</div>
) : null
}
export default ListItemConflictIndicator

View File

@@ -1,5 +1,5 @@
import { FunctionComponent } from 'preact'
import { Icon } from '@/Components/Icon/Icon'
import { FunctionComponent } from 'react'
import Icon from '@/Components/Icon/Icon'
import { ListableContentItem } from './Types/ListableContentItem'
type Props = {
@@ -12,7 +12,7 @@ type Props = {
hasFiles?: boolean
}
export const ListItemFlagIcons: FunctionComponent<Props> = ({ item, hasFiles = false }) => {
const ListItemFlagIcons: FunctionComponent<Props> = ({ item, hasFiles = false }) => {
return (
<div className="flex items-start p-4 pl-0 border-0 border-b-1 border-solid border-main">
{item.locked && (
@@ -43,3 +43,5 @@ export const ListItemFlagIcons: FunctionComponent<Props> = ({ item, hasFiles = f
</div>
)
}
export default ListItemFlagIcons

View File

@@ -1,5 +1,5 @@
import { CollectionSort, SortableItem } from '@standardnotes/snjs'
import { FunctionComponent } from 'preact'
import { FunctionComponent } from 'react'
import { ListableContentItem } from './Types/ListableContentItem'
type Props = {
@@ -12,7 +12,7 @@ type Props = {
sortBy: keyof SortableItem | undefined
}
export const ListItemMetadata: FunctionComponent<Props> = ({ item, hideDate, sortBy }) => {
const ListItemMetadata: FunctionComponent<Props> = ({ item, hideDate, sortBy }) => {
const showModifiedDate = sortBy === CollectionSort.UpdatedAt
if (hideDate && !item.protected) {
@@ -27,3 +27,5 @@ export const ListItemMetadata: FunctionComponent<Props> = ({ item, hideDate, sor
</div>
)
}
export default ListItemMetadata

View File

@@ -1,10 +1,12 @@
import { FunctionComponent } from 'preact'
import { Icon } from '@/Components/Icon/Icon'
import { FunctionComponent } from 'react'
import Icon from '@/Components/Icon/Icon'
export const ListItemTags: FunctionComponent<{
type Props = {
hideTags: boolean
tags: string[]
}> = ({ hideTags, tags }) => {
}
const ListItemTags: FunctionComponent<Props> = ({ hideTags, tags }) => {
if (hideTags || !tags.length) {
return null
}
@@ -12,7 +14,10 @@ export const ListItemTags: FunctionComponent<{
return (
<div className="flex flex-wrap mt-1.5 text-xs gap-2">
{tags.map((tag) => (
<span className="inline-flex items-center py-1 px-1.5 bg-passive-4-opacity-variant color-foreground rounded-0.5">
<span
className="inline-flex items-center py-1 px-1.5 bg-passive-4-opacity-variant color-foreground rounded-0.5"
key={tag}
>
<Icon type="hashtag" className="sn-icon--small color-passive-1 mr-1" />
<span>{tag}</span>
</span>
@@ -20,3 +25,5 @@ export const ListItemTags: FunctionComponent<{
</div>
)
}
export default ListItemTags

View File

@@ -1,84 +1,97 @@
import { PLAIN_EDITOR_NAME } from '@/Constants'
import { sanitizeHtmlString, SNNote } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'preact'
import { Icon } from '@/Components/Icon/Icon'
import { ListItemConflictIndicator } from './ListItemConflictIndicator'
import { ListItemFlagIcons } from './ListItemFlagIcons'
import { ListItemTags } from './ListItemTags'
import { ListItemMetadata } from './ListItemMetadata'
import { FunctionComponent } from 'react'
import Icon from '@/Components/Icon/Icon'
import ListItemConflictIndicator from './ListItemConflictIndicator'
import ListItemFlagIcons from './ListItemFlagIcons'
import ListItemTags from './ListItemTags'
import ListItemMetadata from './ListItemMetadata'
import { DisplayableListItemProps } from './Types/DisplayableListItemProps'
export const NoteListItem: FunctionComponent<DisplayableListItemProps> = observer(
({ application, appState, hideDate, hideIcon, hideTags, hidePreview, item, selected, sortBy, tags }) => {
const editorForNote = application.componentManager.editorForNote(item as SNNote)
const editorName = editorForNote?.name ?? PLAIN_EDITOR_NAME
const [icon, tint] = application.iconsController.getIconAndTintForNoteType(editorForNote?.package_info.note_type)
const hasFiles = application.items.getFilesForNote(item as SNNote).length > 0
const NoteListItem: FunctionComponent<DisplayableListItemProps> = ({
application,
appState,
hideDate,
hideIcon,
hideTags,
hidePreview,
item,
selected,
sortBy,
tags,
}) => {
const editorForNote = application.componentManager.editorForNote(item as SNNote)
const editorName = editorForNote?.name ?? PLAIN_EDITOR_NAME
const [icon, tint] = application.iconsController.getIconAndTintForNoteType(editorForNote?.package_info.note_type)
const hasFiles = application.items.getFilesForNote(item as SNNote).length > 0
const openNoteContextMenu = (posX: number, posY: number) => {
appState.notes.setContextMenuClickLocation({
x: posX,
y: posY,
})
appState.notes.reloadContextMenuLayout()
appState.notes.setContextMenuOpen(true)
}
const openNoteContextMenu = (posX: number, posY: number) => {
appState.notes.setContextMenuClickLocation({
x: posX,
y: posY,
})
appState.notes.reloadContextMenuLayout()
appState.notes.setContextMenuOpen(true)
}
const openContextMenu = (posX: number, posY: number) => {
void appState.selectedItems.selectItem(item.uuid, true)
const openContextMenu = async (posX: number, posY: number) => {
const { didSelect } = await appState.selectedItems.selectItem(item.uuid, true)
if (didSelect) {
openNoteContextMenu(posX, posY)
}
}
return (
<div
className={`content-list-item flex items-stretch w-full cursor-pointer ${
selected && 'selected border-0 border-l-2px border-solid border-info'
}`}
id={item.uuid}
onClick={() => {
void appState.selectedItems.selectItem(item.uuid, true)
}}
onContextMenu={(event) => {
event.preventDefault()
openContextMenu(event.clientX, event.clientY)
}}
>
{!hideIcon ? (
<div className="flex flex-col items-center justify-between p-4 pr-3 mr-0">
<Icon ariaLabel={`Icon for ${editorName}`} type={icon} className={`color-accessory-tint-${tint}`} />
</div>
) : (
<div className="pr-4" />
)}
<div className="flex-grow min-w-0 py-4 px-0 border-0 border-b-1 border-solid border-main">
<div className="flex items-start justify-between font-semibold text-base leading-1.3 overflow-hidden">
<div className="break-word mr-2">{item.title}</div>
</div>
{!hidePreview && !item.hidePreview && !item.protected && (
<div className="overflow-hidden overflow-ellipsis text-sm">
{item.preview_html && (
<div
className="my-1"
dangerouslySetInnerHTML={{
__html: sanitizeHtmlString(item.preview_html),
}}
></div>
)}
{!item.preview_html && item.preview_plain && (
<div className="leading-1.3 overflow-hidden line-clamp-1 mt-1">{item.preview_plain}</div>
)}
{!item.preview_html && !item.preview_plain && item.text && (
<div className="leading-1.3 overflow-hidden line-clamp-1 mt-1">{item.text}</div>
)}
</div>
)}
<ListItemMetadata item={item} hideDate={hideDate} sortBy={sortBy} />
<ListItemTags hideTags={hideTags} tags={tags} />
<ListItemConflictIndicator item={item} />
return (
<div
className={`content-list-item flex items-stretch w-full cursor-pointer ${
selected && 'selected border-0 border-l-2px border-solid border-info'
}`}
id={item.uuid}
onClick={() => {
void appState.selectedItems.selectItem(item.uuid, true)
}}
onContextMenu={(event) => {
event.preventDefault()
void openContextMenu(event.clientX, event.clientY)
}}
>
{!hideIcon ? (
<div className="flex flex-col items-center justify-between p-4 pr-3 mr-0">
<Icon ariaLabel={`Icon for ${editorName}`} type={icon} className={`color-accessory-tint-${tint}`} />
</div>
<ListItemFlagIcons item={item} hasFiles={hasFiles} />
) : (
<div className="pr-4" />
)}
<div className="flex-grow min-w-0 py-4 px-0 border-0 border-b-1 border-solid border-main">
<div className="flex items-start justify-between font-semibold text-base leading-1.3 overflow-hidden">
<div className="break-word mr-2">{item.title}</div>
</div>
{!hidePreview && !item.hidePreview && !item.protected && (
<div className="overflow-hidden overflow-ellipsis text-sm">
{item.preview_html && (
<div
className="my-1"
dangerouslySetInnerHTML={{
__html: sanitizeHtmlString(item.preview_html),
}}
></div>
)}
{!item.preview_html && item.preview_plain && (
<div className="leading-1.3 overflow-hidden line-clamp-1 mt-1">{item.preview_plain}</div>
)}
{!item.preview_html && !item.preview_plain && item.text && (
<div className="leading-1.3 overflow-hidden line-clamp-1 mt-1">{item.text}</div>
)}
</div>
)}
<ListItemMetadata item={item} hideDate={hideDate} sortBy={sortBy} />
<ListItemTags hideTags={hideTags} tags={tags} />
<ListItemConflictIndicator item={item} />
</div>
)
},
)
<ListItemFlagIcons item={item} hasFiles={hasFiles} />
</div>
)
}
export default observer(NoteListItem)

View File

@@ -1,11 +1,12 @@
import { WebApplication } from '@/UIModels/Application'
import { CollectionSort, CollectionSortProperty, PrefKey } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'preact'
import { useCallback, useState } from 'preact/hooks'
import { Icon } from '@/Components/Icon/Icon'
import { Menu } from '@/Components/Menu/Menu'
import { MenuItem, MenuItemSeparator, MenuItemType } from '@/Components/Menu/MenuItem'
import { FunctionComponent, useCallback, useState } from 'react'
import Icon from '@/Components/Icon/Icon'
import Menu from '@/Components/Menu/Menu'
import MenuItem from '@/Components/Menu/MenuItem'
import MenuItemSeparator from '@/Components/Menu/MenuItemSeparator'
import { MenuItemType } from '@/Components/Menu/MenuItemType'
type Props = {
application: WebApplication
@@ -14,235 +15,238 @@ type Props = {
isOpen: boolean
}
export const NotesListOptionsMenu: FunctionComponent<Props> = observer(
({ closeDisplayOptionsMenu, closeOnBlur, application, isOpen }) => {
const [sortBy, setSortBy] = useState(() => application.getPreference(PrefKey.SortNotesBy, CollectionSort.CreatedAt))
const [sortReverse, setSortReverse] = useState(() => application.getPreference(PrefKey.SortNotesReverse, false))
const [hidePreview, setHidePreview] = useState(() => application.getPreference(PrefKey.NotesHideNotePreview, false))
const [hideDate, setHideDate] = useState(() => application.getPreference(PrefKey.NotesHideDate, false))
const [hideTags, setHideTags] = useState(() => application.getPreference(PrefKey.NotesHideTags, true))
const [hidePinned, setHidePinned] = useState(() => application.getPreference(PrefKey.NotesHidePinned, false))
const [showArchived, setShowArchived] = useState(() => application.getPreference(PrefKey.NotesShowArchived, false))
const [showTrashed, setShowTrashed] = useState(() => application.getPreference(PrefKey.NotesShowTrashed, false))
const [hideProtected, setHideProtected] = useState(() =>
application.getPreference(PrefKey.NotesHideProtected, false),
)
const [hideEditorIcon, setHideEditorIcon] = useState(() =>
application.getPreference(PrefKey.NotesHideEditorIcon, false),
)
const NotesListOptionsMenu: FunctionComponent<Props> = ({
closeDisplayOptionsMenu,
closeOnBlur,
application,
isOpen,
}) => {
const [sortBy, setSortBy] = useState(() => application.getPreference(PrefKey.SortNotesBy, CollectionSort.CreatedAt))
const [sortReverse, setSortReverse] = useState(() => application.getPreference(PrefKey.SortNotesReverse, false))
const [hidePreview, setHidePreview] = useState(() => application.getPreference(PrefKey.NotesHideNotePreview, false))
const [hideDate, setHideDate] = useState(() => application.getPreference(PrefKey.NotesHideDate, false))
const [hideTags, setHideTags] = useState(() => application.getPreference(PrefKey.NotesHideTags, true))
const [hidePinned, setHidePinned] = useState(() => application.getPreference(PrefKey.NotesHidePinned, false))
const [showArchived, setShowArchived] = useState(() => application.getPreference(PrefKey.NotesShowArchived, false))
const [showTrashed, setShowTrashed] = useState(() => application.getPreference(PrefKey.NotesShowTrashed, false))
const [hideProtected, setHideProtected] = useState(() => application.getPreference(PrefKey.NotesHideProtected, false))
const [hideEditorIcon, setHideEditorIcon] = useState(() =>
application.getPreference(PrefKey.NotesHideEditorIcon, false),
)
const toggleSortReverse = useCallback(() => {
application.setPreference(PrefKey.SortNotesReverse, !sortReverse).catch(console.error)
setSortReverse(!sortReverse)
}, [application, sortReverse])
const toggleSortReverse = useCallback(() => {
application.setPreference(PrefKey.SortNotesReverse, !sortReverse).catch(console.error)
setSortReverse(!sortReverse)
}, [application, sortReverse])
const toggleSortBy = useCallback(
(sort: CollectionSortProperty) => {
if (sortBy === sort) {
toggleSortReverse()
} else {
setSortBy(sort)
application.setPreference(PrefKey.SortNotesBy, sort).catch(console.error)
}
},
[application, sortBy, toggleSortReverse],
)
const toggleSortBy = useCallback(
(sort: CollectionSortProperty) => {
if (sortBy === sort) {
toggleSortReverse()
} else {
setSortBy(sort)
application.setPreference(PrefKey.SortNotesBy, sort).catch(console.error)
}
},
[application, sortBy, toggleSortReverse],
)
const toggleSortByDateModified = useCallback(() => {
toggleSortBy(CollectionSort.UpdatedAt)
}, [toggleSortBy])
const toggleSortByDateModified = useCallback(() => {
toggleSortBy(CollectionSort.UpdatedAt)
}, [toggleSortBy])
const toggleSortByCreationDate = useCallback(() => {
toggleSortBy(CollectionSort.CreatedAt)
}, [toggleSortBy])
const toggleSortByCreationDate = useCallback(() => {
toggleSortBy(CollectionSort.CreatedAt)
}, [toggleSortBy])
const toggleSortByTitle = useCallback(() => {
toggleSortBy(CollectionSort.Title)
}, [toggleSortBy])
const toggleSortByTitle = useCallback(() => {
toggleSortBy(CollectionSort.Title)
}, [toggleSortBy])
const toggleHidePreview = useCallback(() => {
setHidePreview(!hidePreview)
application.setPreference(PrefKey.NotesHideNotePreview, !hidePreview).catch(console.error)
}, [application, hidePreview])
const toggleHidePreview = useCallback(() => {
setHidePreview(!hidePreview)
application.setPreference(PrefKey.NotesHideNotePreview, !hidePreview).catch(console.error)
}, [application, hidePreview])
const toggleHideDate = useCallback(() => {
setHideDate(!hideDate)
application.setPreference(PrefKey.NotesHideDate, !hideDate).catch(console.error)
}, [application, hideDate])
const toggleHideDate = useCallback(() => {
setHideDate(!hideDate)
application.setPreference(PrefKey.NotesHideDate, !hideDate).catch(console.error)
}, [application, hideDate])
const toggleHideTags = useCallback(() => {
setHideTags(!hideTags)
application.setPreference(PrefKey.NotesHideTags, !hideTags).catch(console.error)
}, [application, hideTags])
const toggleHideTags = useCallback(() => {
setHideTags(!hideTags)
application.setPreference(PrefKey.NotesHideTags, !hideTags).catch(console.error)
}, [application, hideTags])
const toggleHidePinned = useCallback(() => {
setHidePinned(!hidePinned)
application.setPreference(PrefKey.NotesHidePinned, !hidePinned).catch(console.error)
}, [application, hidePinned])
const toggleHidePinned = useCallback(() => {
setHidePinned(!hidePinned)
application.setPreference(PrefKey.NotesHidePinned, !hidePinned).catch(console.error)
}, [application, hidePinned])
const toggleShowArchived = useCallback(() => {
setShowArchived(!showArchived)
application.setPreference(PrefKey.NotesShowArchived, !showArchived).catch(console.error)
}, [application, showArchived])
const toggleShowArchived = useCallback(() => {
setShowArchived(!showArchived)
application.setPreference(PrefKey.NotesShowArchived, !showArchived).catch(console.error)
}, [application, showArchived])
const toggleShowTrashed = useCallback(() => {
setShowTrashed(!showTrashed)
application.setPreference(PrefKey.NotesShowTrashed, !showTrashed).catch(console.error)
}, [application, showTrashed])
const toggleShowTrashed = useCallback(() => {
setShowTrashed(!showTrashed)
application.setPreference(PrefKey.NotesShowTrashed, !showTrashed).catch(console.error)
}, [application, showTrashed])
const toggleHideProtected = useCallback(() => {
setHideProtected(!hideProtected)
application.setPreference(PrefKey.NotesHideProtected, !hideProtected).catch(console.error)
}, [application, hideProtected])
const toggleHideProtected = useCallback(() => {
setHideProtected(!hideProtected)
application.setPreference(PrefKey.NotesHideProtected, !hideProtected).catch(console.error)
}, [application, hideProtected])
const toggleEditorIcon = useCallback(() => {
setHideEditorIcon(!hideEditorIcon)
application.setPreference(PrefKey.NotesHideEditorIcon, !hideEditorIcon).catch(console.error)
}, [application, hideEditorIcon])
const toggleEditorIcon = useCallback(() => {
setHideEditorIcon(!hideEditorIcon)
application.setPreference(PrefKey.NotesHideEditorIcon, !hideEditorIcon).catch(console.error)
}, [application, hideEditorIcon])
return (
<Menu
className={
'sn-dropdown sn-dropdown--animated min-w-70 overflow-y-auto \
return (
<Menu
className={
'sn-dropdown sn-dropdown--animated min-w-70 overflow-y-auto \
border-1 border-solid border-main text-sm z-index-dropdown-menu \
flex flex-col py-2 top-full left-2 absolute'
}
a11yLabel="Notes list options menu"
closeMenu={closeDisplayOptionsMenu}
isOpen={isOpen}
}
a11yLabel="Notes list options menu"
closeMenu={closeDisplayOptionsMenu}
isOpen={isOpen}
>
<div className="px-3 my-1 text-xs font-semibold color-text uppercase">Sort by</div>
<MenuItem
className="py-2"
type={MenuItemType.RadioButton}
onClick={toggleSortByDateModified}
checked={sortBy === CollectionSort.UpdatedAt}
onBlur={closeOnBlur}
>
<div className="px-3 my-1 text-xs font-semibold color-text uppercase">Sort by</div>
<MenuItem
className="py-2"
type={MenuItemType.RadioButton}
onClick={toggleSortByDateModified}
checked={sortBy === CollectionSort.UpdatedAt}
onBlur={closeOnBlur}
>
<div className="flex flex-grow items-center justify-between ml-2">
<span>Date modified</span>
{sortBy === CollectionSort.UpdatedAt ? (
sortReverse ? (
<Icon type="arrows-sort-up" className="color-neutral" />
) : (
<Icon type="arrows-sort-down" className="color-neutral" />
)
) : null}
</div>
</MenuItem>
<MenuItem
className="py-2"
type={MenuItemType.RadioButton}
onClick={toggleSortByCreationDate}
checked={sortBy === CollectionSort.CreatedAt}
onBlur={closeOnBlur}
>
<div className="flex flex-grow items-center justify-between ml-2">
<span>Creation date</span>
{sortBy === CollectionSort.CreatedAt ? (
sortReverse ? (
<Icon type="arrows-sort-up" className="color-neutral" />
) : (
<Icon type="arrows-sort-down" className="color-neutral" />
)
) : null}
</div>
</MenuItem>
<MenuItem
className="py-2"
type={MenuItemType.RadioButton}
onClick={toggleSortByTitle}
checked={sortBy === CollectionSort.Title}
onBlur={closeOnBlur}
>
<div className="flex flex-grow items-center justify-between ml-2">
<span>Title</span>
{sortBy === CollectionSort.Title ? (
sortReverse ? (
<Icon type="arrows-sort-up" className="color-neutral" />
) : (
<Icon type="arrows-sort-down" className="color-neutral" />
)
) : null}
</div>
</MenuItem>
<MenuItemSeparator />
<div className="px-3 py-1 text-xs font-semibold color-text uppercase">View</div>
<MenuItem
type={MenuItemType.SwitchButton}
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={!hidePreview}
onChange={toggleHidePreview}
onBlur={closeOnBlur}
>
<div className="flex flex-col max-w-3/4">Show note preview</div>
</MenuItem>
<MenuItem
type={MenuItemType.SwitchButton}
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={!hideDate}
onChange={toggleHideDate}
onBlur={closeOnBlur}
>
Show date
</MenuItem>
<MenuItem
type={MenuItemType.SwitchButton}
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={!hideTags}
onChange={toggleHideTags}
onBlur={closeOnBlur}
>
Show tags
</MenuItem>
<MenuItem
type={MenuItemType.SwitchButton}
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={!hideEditorIcon}
onChange={toggleEditorIcon}
onBlur={closeOnBlur}
>
Show editor icon
</MenuItem>
<div className="h-1px my-2 bg-border"></div>
<div className="px-3 py-1 text-xs font-semibold color-text uppercase">Other</div>
<MenuItem
type={MenuItemType.SwitchButton}
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={!hidePinned}
onChange={toggleHidePinned}
onBlur={closeOnBlur}
>
Show pinned notes
</MenuItem>
<MenuItem
type={MenuItemType.SwitchButton}
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={!hideProtected}
onChange={toggleHideProtected}
onBlur={closeOnBlur}
>
Show protected notes
</MenuItem>
<MenuItem
type={MenuItemType.SwitchButton}
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={showArchived}
onChange={toggleShowArchived}
onBlur={closeOnBlur}
>
Show archived notes
</MenuItem>
<MenuItem
type={MenuItemType.SwitchButton}
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={showTrashed}
onChange={toggleShowTrashed}
onBlur={closeOnBlur}
>
Show trashed notes
</MenuItem>
</Menu>
)
},
)
<div className="flex flex-grow items-center justify-between ml-2">
<span>Date modified</span>
{sortBy === CollectionSort.UpdatedAt ? (
sortReverse ? (
<Icon type="arrows-sort-up" className="color-neutral" />
) : (
<Icon type="arrows-sort-down" className="color-neutral" />
)
) : null}
</div>
</MenuItem>
<MenuItem
className="py-2"
type={MenuItemType.RadioButton}
onClick={toggleSortByCreationDate}
checked={sortBy === CollectionSort.CreatedAt}
onBlur={closeOnBlur}
>
<div className="flex flex-grow items-center justify-between ml-2">
<span>Creation date</span>
{sortBy === CollectionSort.CreatedAt ? (
sortReverse ? (
<Icon type="arrows-sort-up" className="color-neutral" />
) : (
<Icon type="arrows-sort-down" className="color-neutral" />
)
) : null}
</div>
</MenuItem>
<MenuItem
className="py-2"
type={MenuItemType.RadioButton}
onClick={toggleSortByTitle}
checked={sortBy === CollectionSort.Title}
onBlur={closeOnBlur}
>
<div className="flex flex-grow items-center justify-between ml-2">
<span>Title</span>
{sortBy === CollectionSort.Title ? (
sortReverse ? (
<Icon type="arrows-sort-up" className="color-neutral" />
) : (
<Icon type="arrows-sort-down" className="color-neutral" />
)
) : null}
</div>
</MenuItem>
<MenuItemSeparator />
<div className="px-3 py-1 text-xs font-semibold color-text uppercase">View</div>
<MenuItem
type={MenuItemType.SwitchButton}
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={!hidePreview}
onChange={toggleHidePreview}
onBlur={closeOnBlur}
>
<div className="flex flex-col max-w-3/4">Show note preview</div>
</MenuItem>
<MenuItem
type={MenuItemType.SwitchButton}
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={!hideDate}
onChange={toggleHideDate}
onBlur={closeOnBlur}
>
Show date
</MenuItem>
<MenuItem
type={MenuItemType.SwitchButton}
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={!hideTags}
onChange={toggleHideTags}
onBlur={closeOnBlur}
>
Show tags
</MenuItem>
<MenuItem
type={MenuItemType.SwitchButton}
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={!hideEditorIcon}
onChange={toggleEditorIcon}
onBlur={closeOnBlur}
>
Show editor icon
</MenuItem>
<div className="h-1px my-2 bg-border"></div>
<div className="px-3 py-1 text-xs font-semibold color-text uppercase">Other</div>
<MenuItem
type={MenuItemType.SwitchButton}
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={!hidePinned}
onChange={toggleHidePinned}
onBlur={closeOnBlur}
>
Show pinned notes
</MenuItem>
<MenuItem
type={MenuItemType.SwitchButton}
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={!hideProtected}
onChange={toggleHideProtected}
onBlur={closeOnBlur}
>
Show protected notes
</MenuItem>
<MenuItem
type={MenuItemType.SwitchButton}
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={showArchived}
onChange={toggleShowArchived}
onBlur={closeOnBlur}
>
Show archived notes
</MenuItem>
<MenuItem
type={MenuItemType.SwitchButton}
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
checked={showTrashed}
onChange={toggleShowTrashed}
onBlur={closeOnBlur}
>
Show trashed notes
</MenuItem>
</Menu>
)
}
export default observer(NotesListOptionsMenu)