From caa8eff216b2d7a47f849f85f1eb2248eea10c62 Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Tue, 24 Jun 2025 12:32:27 +0530 Subject: [PATCH] feat: Tag search will now only show the direct tag results instead of also showing the parent and siblings (#2906) --- .../Runtime/Display/Search/SearchUtilities.ts | 5 ++- .../Domain/Runtime/Display/Search/Types.ts | 1 + .../javascripts/Components/Tags/TagsList.tsx | 14 +++++- .../Components/Tags/TagsListItem.tsx | 43 +++++++++++++++---- .../Navigation/NavigationController.ts | 14 +++++- 5 files changed, 63 insertions(+), 14 deletions(-) diff --git a/packages/models/src/Domain/Runtime/Display/Search/SearchUtilities.ts b/packages/models/src/Domain/Runtime/Display/Search/SearchUtilities.ts index 4fc0e2563..0f265ec87 100644 --- a/packages/models/src/Domain/Runtime/Display/Search/SearchUtilities.ts +++ b/packages/models/src/Domain/Runtime/Display/Search/SearchUtilities.ts @@ -38,8 +38,11 @@ export function itemMatchesQuery( searchQuery: SearchQuery, collection: ReferenceLookupCollection, ): boolean { + const shouldCheckForSomeTagMatches = searchQuery.shouldCheckForSomeTagMatches ?? true const itemTags = collection.elementsReferencingElement(itemToMatch, ContentType.TYPES.Tag) as SNTag[] - const someTagsMatches = itemTags.some((tag) => matchResultForStringQuery(tag, searchQuery.query) !== MatchResult.None) + const someTagsMatches = + shouldCheckForSomeTagMatches && + itemTags.some((tag) => matchResultForStringQuery(tag, searchQuery.query) !== MatchResult.None) if (itemToMatch.protected && !searchQuery.includeProtectedNoteText) { const match = matchResultForStringQuery(itemToMatch, searchQuery.query) diff --git a/packages/models/src/Domain/Runtime/Display/Search/Types.ts b/packages/models/src/Domain/Runtime/Display/Search/Types.ts index 5e47f0111..e574abcaa 100644 --- a/packages/models/src/Domain/Runtime/Display/Search/Types.ts +++ b/packages/models/src/Domain/Runtime/Display/Search/Types.ts @@ -5,6 +5,7 @@ import { SearchableItem } from './SearchableItem' export type SearchQuery = { query: string includeProtectedNoteText: boolean + shouldCheckForSomeTagMatches?: boolean } export interface ReferenceLookupCollection { diff --git a/packages/web/src/javascripts/Components/Tags/TagsList.tsx b/packages/web/src/javascripts/Components/Tags/TagsList.tsx index da69f7eb7..cd14f58ca 100644 --- a/packages/web/src/javascripts/Components/Tags/TagsList.tsx +++ b/packages/web/src/javascripts/Components/Tags/TagsList.tsx @@ -6,16 +6,26 @@ import { TagListSectionType } from './TagListSection' import { TagsListItem } from './TagsListItem' import { useApplication } from '../ApplicationProvider' import { useListKeyboardNavigation } from '@/Hooks/useListKeyboardNavigation' +import { NavigationController } from '@/Controllers/Navigation/NavigationController' type Props = { type: TagListSectionType } +function getAllTagsForType(controller: NavigationController, type: TagListSectionType) { + if (type === 'all') { + if (controller.isSearching) { + return controller.tags + } + return controller.allLocalRootTags + } + return controller.starredTags +} + const TagsList: FunctionComponent = ({ type }: Props) => { const application = useApplication() - const allTags = - type === 'all' ? application.navigationController.allLocalRootTags : application.navigationController.starredTags + const allTags = getAllTagsForType(application.navigationController, type) const openTagContextMenu = useCallback( (x: number, y: number) => { diff --git a/packages/web/src/javascripts/Components/Tags/TagsListItem.tsx b/packages/web/src/javascripts/Components/Tags/TagsListItem.tsx index 918e5572b..69a3cd98f 100644 --- a/packages/web/src/javascripts/Components/Tags/TagsListItem.tsx +++ b/packages/web/src/javascripts/Components/Tags/TagsListItem.tsx @@ -26,6 +26,8 @@ import { log, LoggingDomain } from '@/Logging' import { NoteDragDataFormat, TagDragDataFormat } from './DragNDrop' import { usePremiumModal } from '@/Hooks/usePremiumModal' import { useApplication } from '../ApplicationProvider' +import { mergeRefs } from '../../Hooks/mergeRefs' +import { getTitleForLinkedTag } from '../../Utils/Items/Display/getTitleForLinkedTag' type Props = { tag: SNTag @@ -46,6 +48,8 @@ export const TagsListItem: FunctionComponent = observer( const [title, setTitle] = useState(tag.title || '') const [subtagTitle, setSubtagTitle] = useState('') + + const tagRef = useRef(null) const inputRef = useRef(null) const subtagInputRef = useRef(null) const menuButtonRef = useRef(null) @@ -75,6 +79,8 @@ export const TagsListItem: FunctionComponent = observer( const [isBeingDraggedOver, setIsBeingDraggedOver] = useState(false) + const tagHierarchyPrefix = navigationController.isSearching && getTitleForLinkedTag(tag, application)?.titlePrefix + useEffect(() => { if (!hadChildren && hasChildren) { setShowChildren(true) @@ -117,6 +123,18 @@ export const TagsListItem: FunctionComponent = observer( const selectCurrentTag = useCallback(async () => { await navigationController.setSelectedTag(tag, type, { userTriggered: true, + scrollIntoView: false, + }) + }, [navigationController, tag, type]) + + const clearSearchAndSelectCurrentTag = useCallback(async () => { + if (!navigationController.isSearching) { + return + } + navigationController.setSearchQuery('') + await navigationController.setSelectedTag(tag, type, { + userTriggered: true, + scrollIntoView: true, }) }, [navigationController, tag, type]) @@ -196,8 +214,6 @@ export const TagsListItem: FunctionComponent = observer( [navigationController, onContextMenu, tag, type], ) - const tagRef = useRef(null) - const { addDragTarget, removeDragTarget } = useFileDragNDrop() useEffect(() => { @@ -292,6 +308,7 @@ export const TagsListItem: FunctionComponent = observer( isBeingDraggedOver && 'is-drag-over', )} onClick={selectCurrentTag} + onDoubleClick={clearSearchAndSelectCurrentTag} onKeyDown={(event) => { if (event.key === KeyboardKey.Enter || event.key === KeyboardKey.Space) { selectCurrentTag().catch(console.error) @@ -301,7 +318,18 @@ export const TagsListItem: FunctionComponent = observer( setTagExpanded(true) } }} - ref={tagRef} + ref={mergeRefs([ + tagRef, + function scrollIntoViewIfNeeded(el) { + if (!el || navigationController.tagToScrollIntoView !== tag) { + return + } + el.scrollIntoView({ + block: 'center', + }) + navigationController.tagToScrollIntoView = undefined + }, + ])} style={{ paddingLeft: `${level * PADDING_PER_LEVEL_PX + PADDING_BASE_PX}px`, }} @@ -330,9 +358,7 @@ export const TagsListItem: FunctionComponent = observer( {isEditing && ( = observer( {!isEditing && ( <>
+ {tagHierarchyPrefix && {tagHierarchyPrefix}} {title}
diff --git a/packages/web/src/javascripts/Controllers/Navigation/NavigationController.ts b/packages/web/src/javascripts/Controllers/Navigation/NavigationController.ts index 8fc42f72c..00e99e98a 100644 --- a/packages/web/src/javascripts/Controllers/Navigation/NavigationController.ts +++ b/packages/web/src/javascripts/Controllers/Navigation/NavigationController.ts @@ -59,6 +59,7 @@ export class NavigationController previouslySelected_: AnyTag | undefined = undefined editing_: SNTag | SmartView | undefined = undefined addingSubtagTo: SNTag | undefined = undefined + tagToScrollIntoView: AnyTag | undefined = undefined contextMenuOpen = false contextMenuClickLocation: { x: number; y: number } = { x: 0, y: 0 } @@ -390,10 +391,14 @@ export class NavigationController return [] } + if (this.isSearching) { + return [] + } + const children = this.items.getTagChildren(tag) const childrenUuids = children.map((childTag) => childTag.uuid) - const childrenTags = this.isSearching ? children : this.tags.filter((tag) => childrenUuids.includes(tag.uuid)) + const childrenTags = this.tags.filter((tag) => childrenUuids.includes(tag.uuid)) return childrenTags } @@ -479,8 +484,9 @@ export class NavigationController public async setSelectedTag( tag: AnyTag | undefined, location: TagListSectionType, - { userTriggered } = { userTriggered: false }, + options?: { userTriggered: boolean; scrollIntoView?: boolean }, ) { + const { userTriggered = false, scrollIntoView = false } = options || {} if (tag && tag.conflictOf) { this._changeAndSaveItem .execute(tag, (mutator) => { @@ -512,6 +518,9 @@ export class NavigationController }, InternalEventPublishStrategy.SEQUENCE, ) + if (userTriggered && scrollIntoView) { + this.tagToScrollIntoView = tag + } }) } @@ -678,6 +687,7 @@ export class NavigationController searchQuery: { query: this.searchQuery, includeProtectedNoteText: false, + shouldCheckForSomeTagMatches: false, }, }) this.reloadTags()