Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added search bar to search tags and smart views #2815

Merged
merged 11 commits into from Feb 2, 2024
9 changes: 6 additions & 3 deletions packages/models/src/Domain/Runtime/Display/DisplayOptions.ts
Expand Up @@ -19,13 +19,16 @@ export interface NotesAndFilesDisplayOptions extends GenericDisplayOptions {
customFilter?: DisplayControllerCustomFilter
}

export type TagsDisplayOptions = GenericDisplayOptions
export interface TagsAndViewsDisplayOptions extends GenericDisplayOptions {
searchQuery?: SearchQuery
customFilter?: DisplayControllerCustomFilter
}

export interface DisplayControllerDisplayOptions extends GenericDisplayOptions {
sortBy: CollectionSortProperty
sortDirection: CollectionSortDirection
}

export type NotesAndFilesDisplayControllerOptions = NotesAndFilesDisplayOptions & DisplayControllerDisplayOptions
export type TagsDisplayControllerOptions = TagsDisplayOptions & DisplayControllerDisplayOptions
export type AnyDisplayOptions = NotesAndFilesDisplayOptions | TagsDisplayOptions | GenericDisplayOptions
export type TagsDisplayControllerOptions = TagsAndViewsDisplayOptions & DisplayControllerDisplayOptions
export type AnyDisplayOptions = NotesAndFilesDisplayOptions | TagsAndViewsDisplayOptions | GenericDisplayOptions
2 changes: 2 additions & 0 deletions packages/services/src/Domain/Item/ItemManagerInterface.ts
Expand Up @@ -21,6 +21,7 @@ import {
NotesAndFilesDisplayControllerOptions,
ComponentInterface,
ItemStream,
TagsAndViewsDisplayOptions,
} from '@standardnotes/models'
import { AbstractService } from '../Service/AbstractService'

Expand Down Expand Up @@ -130,6 +131,7 @@ export interface ItemManagerInterface extends AbstractService {
getDisplayableNotes(): SNNote[]
getDisplayableNotesAndFiles(): (SNNote | FileItem)[]
setPrimaryItemDisplayOptions(options: NotesAndFilesDisplayControllerOptions): void
setTagsAndViewsDisplayOptions(options: TagsAndViewsDisplayOptions): void
getTagPrefixTitle(tag: SNTag): string | undefined
getItemLinkedFiles(item: DecryptedItemInterface): FileItem[]
getItemLinkedNotes(item: DecryptedItemInterface): SNNote[]
Expand Down
31 changes: 24 additions & 7 deletions packages/snjs/lib/Services/Items/ItemManager.ts
Expand Up @@ -34,12 +34,12 @@ export class ItemManager extends Services.AbstractService implements Services.It
Models.SNNote | Models.FileItem,
Models.NotesAndFilesDisplayOptions
>
private tagDisplayController!: Models.ItemDisplayController<Models.SNTag, Models.TagsDisplayOptions>
private tagDisplayController!: Models.ItemDisplayController<Models.SNTag, Models.TagsAndViewsDisplayOptions>
private itemsKeyDisplayController!: Models.ItemDisplayController<SNItemsKey>
private componentDisplayController!: Models.ItemDisplayController<Models.ComponentInterface>
private themeDisplayController!: Models.ItemDisplayController<Models.ComponentInterface>
private fileDisplayController!: Models.ItemDisplayController<Models.FileItem>
private smartViewDisplayController!: Models.ItemDisplayController<Models.SmartView>
private smartViewDisplayController!: Models.ItemDisplayController<Models.SmartView, Models.TagsAndViewsDisplayOptions>

constructor(
private payloadManager: PayloadManager,
Expand Down Expand Up @@ -73,10 +73,14 @@ export class ItemManager extends Services.AbstractService implements Services.It
hiddenContentTypes: [],
},
)
this.tagDisplayController = new Models.ItemDisplayController(this.collection, [ContentType.TYPES.Tag], {
sortBy: 'title',
sortDirection: 'asc',
})
this.tagDisplayController = new Models.ItemDisplayController<Models.SNTag, Models.TagsAndViewsDisplayOptions>(
this.collection,
[ContentType.TYPES.Tag],
{
sortBy: 'title',
sortDirection: 'asc',
},
)
this.itemsKeyDisplayController = new Models.ItemDisplayController(this.collection, [ContentType.TYPES.ItemsKey], {
sortBy: 'created_at',
sortDirection: 'asc',
Expand All @@ -89,7 +93,10 @@ export class ItemManager extends Services.AbstractService implements Services.It
sortBy: 'title',
sortDirection: 'asc',
})
this.smartViewDisplayController = new Models.ItemDisplayController(this.collection, [ContentType.TYPES.SmartView], {
this.smartViewDisplayController = new Models.ItemDisplayController<
Models.SmartView,
Models.TagsAndViewsDisplayOptions
>(this.collection, [ContentType.TYPES.SmartView], {
sortBy: 'title',
sortDirection: 'asc',
})
Expand Down Expand Up @@ -194,6 +201,16 @@ export class ItemManager extends Services.AbstractService implements Services.It
this.itemCounter.setDisplayOptions(updatedOptions)
}

public setTagsAndViewsDisplayOptions(options: Models.TagsAndViewsDisplayOptions): void {
const updatedOptions: Models.TagsAndViewsDisplayOptions = {
customFilter: Models.computeUnifiedFilterForDisplayOptions(options, this.collection),
...options,
}

this.tagDisplayController.setDisplayOptions(updatedOptions)
this.smartViewDisplayController.setDisplayOptions(updatedOptions)
}

public setVaultDisplayOptions(options: Models.VaultDisplayOptions): void {
this.navigationDisplayController.setVaultDisplayOptions(options)
this.tagDisplayController.setVaultDisplayOptions(options)
Expand Down
13 changes: 6 additions & 7 deletions packages/web/src/javascripts/Components/Menu/Menu.tsx
Expand Up @@ -4,7 +4,7 @@ import {
KeyboardEventHandler,
useCallback,
useImperativeHandle,
useRef,
useState,
} from 'react'
import { KeyboardKey } from '@standardnotes/ui-services'
import { useListKeyboardNavigation } from '@/Hooks/useListKeyboardNavigation'
Expand Down Expand Up @@ -33,7 +33,7 @@ const Menu = forwardRef(
}: MenuProps,
forwardedRef,
) => {
const menuElementRef = useRef<HTMLMenuElement>(null)
const [menuElement, setMenuElement] = useState<HTMLMenuElement | null>(null)

const handleKeyDown: KeyboardEventHandler<HTMLMenuElement> = useCallback(
(event) => {
Expand All @@ -49,11 +49,10 @@ const Menu = forwardRef(

const isMobileScreen = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)

const { setInitialFocus } = useListKeyboardNavigation(
menuElementRef,
const { setInitialFocus } = useListKeyboardNavigation(menuElement, {
initialFocus,
isMobileScreen ? false : shouldAutoFocus,
)
shouldAutoFocus: isMobileScreen ? false : shouldAutoFocus,
})

useImperativeHandle(forwardedRef, () => ({
focus: () => {
Expand All @@ -65,7 +64,7 @@ const Menu = forwardRef(
<menu
className={`m-0 list-none px-4 focus:shadow-none md:px-0 ${className}`}
onKeyDown={handleKeyDown}
ref={mergeRefs([menuElementRef, forwardedRef])}
ref={mergeRefs([setMenuElement, forwardedRef])}
style={style}
aria-label={a11yLabel}
{...props}
Expand Down
@@ -1,6 +1,6 @@
import { NoteType, SNNote, classNames } from '@standardnotes/snjs'
import Modal, { ModalAction } from '../../Modal/Modal'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
import { useApplication } from '../../ApplicationProvider'
import { confirmDialog } from '@standardnotes/ui-services'
Expand Down Expand Up @@ -134,8 +134,8 @@ const NoteConflictResolutionModal = ({
[close],
)

const listRef = useRef<HTMLDivElement>(null)
useListKeyboardNavigation(listRef)
const [listElement, setListElement] = useState<HTMLDivElement | null>(null)
useListKeyboardNavigation(listElement)

const [selectedMobileTab, setSelectedMobileTab] = useState<'list' | 'preview'>('list')

Expand Down Expand Up @@ -279,7 +279,7 @@ const NoteConflictResolutionModal = ({
'w-full overflow-y-auto border-r border-border py-1.5 md:flex md:w-auto md:min-w-60 md:flex-col',
selectedMobileTab !== 'list' && 'hidden md:flex',
)}
ref={listRef}
ref={setListElement}
>
{allVersions.map((note, index) => (
<ConflictListItem
Expand Down
@@ -1,5 +1,5 @@
import { Action } from '@standardnotes/snjs'
import { FunctionComponent, useRef } from 'react'
import { FunctionComponent, useState } from 'react'
import { useListKeyboardNavigation } from '@/Hooks/useListKeyboardNavigation'
import HistoryListItem from './HistoryListItem'
import { NoteHistoryController } from '@/Controllers/NoteHistory/NoteHistoryController'
Expand All @@ -13,16 +13,16 @@ type Props = {
const LegacyHistoryList: FunctionComponent<Props> = ({ legacyHistory, noteHistoryController, onSelectRevision }) => {
const { selectLegacyRevision, selectedEntry } = noteHistoryController

const legacyHistoryListRef = useRef<HTMLDivElement>(null)
const [listElement, setListElement] = useState<HTMLDivElement | null>(null)

useListKeyboardNavigation(legacyHistoryListRef)
useListKeyboardNavigation(listElement)

return (
<div
className={`flex h-full w-full flex-col focus:shadow-none ${
!legacyHistory?.length ? 'items-center justify-center' : ''
}`}
ref={legacyHistoryListRef}
ref={setListElement}
>
{legacyHistory?.map((entry) => {
const selectedEntryUrl = (selectedEntry as Action)?.subactions?.[0].url
Expand Down
@@ -1,5 +1,5 @@
import { observer } from 'mobx-react-lite'
import { Fragment, FunctionComponent, useMemo, useRef } from 'react'
import { Fragment, FunctionComponent, useMemo, useState } from 'react'
import Icon from '@/Components/Icon/Icon'
import { useListKeyboardNavigation } from '@/Hooks/useListKeyboardNavigation'
import HistoryListItem from './HistoryListItem'
Expand All @@ -22,9 +22,9 @@ const RemoteHistoryList: FunctionComponent<RemoteHistoryListProps> = ({
}) => {
const { remoteHistory, isFetchingRemoteHistory, selectRemoteRevision, selectedEntry } = noteHistoryController

const remoteHistoryListRef = useRef<HTMLDivElement>(null)
const [listElement, setListElement] = useState<HTMLDivElement | null>(null)

useListKeyboardNavigation(remoteHistoryListRef)
useListKeyboardNavigation(listElement)

const remoteHistoryLength = useMemo(() => remoteHistory?.map((group) => group.entries).flat().length, [remoteHistory])

Expand All @@ -33,7 +33,7 @@ const RemoteHistoryList: FunctionComponent<RemoteHistoryListProps> = ({
className={`flex h-full w-full flex-col focus:shadow-none ${
isFetchingRemoteHistory || !remoteHistoryLength ? 'items-center justify-center' : ''
}`}
ref={remoteHistoryListRef}
ref={setListElement}
>
{isFetchingRemoteHistory && <Spinner className="h-5 w-5" />}
{remoteHistory?.map((group) => {
Expand Down
@@ -1,4 +1,4 @@
import { Fragment, FunctionComponent, useMemo, useRef } from 'react'
import { Fragment, FunctionComponent, useMemo, useState } from 'react'
import { useListKeyboardNavigation } from '@/Hooks/useListKeyboardNavigation'
import HistoryListItem from './HistoryListItem'
import { observer } from 'mobx-react-lite'
Expand All @@ -12,9 +12,9 @@ type Props = {
const SessionHistoryList: FunctionComponent<Props> = ({ noteHistoryController, onSelectRevision }) => {
const { sessionHistory, selectedRevision, selectSessionRevision } = noteHistoryController

const sessionHistoryListRef = useRef<HTMLDivElement>(null)
const [listElement, setListElement] = useState<HTMLDivElement | null>(null)

useListKeyboardNavigation(sessionHistoryListRef)
useListKeyboardNavigation(listElement)

const sessionHistoryLength = useMemo(
() => sessionHistory?.map((group) => group.entries).flat().length,
Expand All @@ -26,7 +26,7 @@ const SessionHistoryList: FunctionComponent<Props> = ({ noteHistoryController, o
className={`flex h-full w-full flex-col focus:shadow-none ${
!sessionHistoryLength ? 'items-center justify-center' : ''
}`}
ref={sessionHistoryListRef}
ref={setListElement}
>
{sessionHistory?.map((group) => {
if (group.entries && group.entries.length) {
Expand Down
2 changes: 2 additions & 0 deletions packages/web/src/javascripts/Components/Tags/Navigation.tsx
Expand Up @@ -17,6 +17,7 @@ import { useAvailableSafeAreaPadding } from '@/Hooks/useSafeAreaPadding'
import QuickSettingsButton from '../Footer/QuickSettingsButton'
import VaultSelectionButton from '../Footer/VaultSelectionButton'
import PreferencesButton from '../Footer/PreferencesButton'
import TagSearchBar from './TagSearchBar'

type Props = {
application: WebApplication
Expand Down Expand Up @@ -78,6 +79,7 @@ const Navigation = forwardRef<HTMLDivElement, Props>(({ application, className,
'md:hover:[overflow-y:_overlay] pointer-coarse:md:overflow-y-auto',
)}
>
<TagSearchBar navigationController={application.navigationController} />
<SmartViewsSection
application={application}
featuresController={application.featuresController}
Expand Down
22 changes: 19 additions & 3 deletions packages/web/src/javascripts/Components/Tags/SmartViewsList.tsx
Expand Up @@ -2,8 +2,9 @@ import { FeaturesController } from '@/Controllers/FeaturesController'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import { SmartView } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'react'
import { FunctionComponent, useState } from 'react'
import SmartViewsListItem from './SmartViewsListItem'
import { useListKeyboardNavigation } from '@/Hooks/useListKeyboardNavigation'

type Props = {
navigationController: NavigationController
Expand All @@ -18,8 +19,23 @@ const SmartViewsList: FunctionComponent<Props> = ({
}: Props) => {
const allViews = navigationController.smartViews

const [container, setContainer] = useState<HTMLDivElement | null>(null)

useListKeyboardNavigation(container, {
initialFocus: 0,
shouldAutoFocus: false,
shouldWrapAround: false,
resetLastFocusedOnBlur: true,
})

if (allViews.length === 0 && navigationController.isSearching) {
return (
<div className="px-4 py-1 text-base opacity-60 lg:text-sm">No smart views found. Try a different search.</div>
)
}

return (
<>
<div ref={setContainer}>
{allViews.map((view) => {
return (
<SmartViewsListItem
Expand All @@ -31,7 +47,7 @@ const SmartViewsList: FunctionComponent<Props> = ({
/>
)
})}
</>
</div>
)
}

Expand Down