fix: Fixed issue where entering a space or punctuation in Super autocomplete would dismiss menu
This commit is contained in:
@@ -10,6 +10,7 @@ import { LinkableItem } from '@/Utils/Items/Search/LinkableItem'
|
|||||||
import { getIconForItem } from '@/Utils/Items/Icons/getIconForItem'
|
import { getIconForItem } from '@/Utils/Items/Icons/getIconForItem'
|
||||||
import { useApplication } from '../ApplicationProvider'
|
import { useApplication } from '../ApplicationProvider'
|
||||||
import { getTitleForLinkedTag } from '@/Utils/Items/Display/getTitleForLinkedTag'
|
import { getTitleForLinkedTag } from '@/Utils/Items/Display/getTitleForLinkedTag'
|
||||||
|
import { getItemTitleInContextOfLinkBubble } from '@/Utils/Items/Search/doesItemMatchSearchQuery'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
link: ItemLink
|
link: ItemLink
|
||||||
@@ -117,7 +118,7 @@ const LinkedItemBubble = ({
|
|||||||
{link.type === 'linked-by' && link.item.content_type !== ContentType.Tag && (
|
{link.type === 'linked-by' && link.item.content_type !== ContentType.Tag && (
|
||||||
<span className={!isBidirectional ? 'hidden group-focus:block' : ''}>Linked By:</span>
|
<span className={!isBidirectional ? 'hidden group-focus:block' : ''}>Linked By:</span>
|
||||||
)}
|
)}
|
||||||
{link.item.title}
|
{getItemTitleInContextOfLinkBubble(link.item)}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
{showUnlinkButton && (
|
{showUnlinkButton && (
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { LinkableItem } from '@/Utils/Items/Search/LinkableItem'
|
|||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { useApplication } from '../ApplicationProvider'
|
import { useApplication } from '../ApplicationProvider'
|
||||||
import Icon from '../Icon/Icon'
|
import Icon from '../Icon/Icon'
|
||||||
|
import { getItemTitleInContextOfLinkBubble } from '@/Utils/Items/Search/doesItemMatchSearchQuery'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
item: LinkableItem
|
item: LinkableItem
|
||||||
@@ -16,7 +17,7 @@ const LinkedItemMeta = ({ item, searchQuery }: Props) => {
|
|||||||
const application = useApplication()
|
const application = useApplication()
|
||||||
const [icon, className] = getIconForItem(item, application)
|
const [icon, className] = getIconForItem(item, application)
|
||||||
const tagTitle = getTitleForLinkedTag(item, application)
|
const tagTitle = getTitleForLinkedTag(item, application)
|
||||||
const title = item.title ?? ''
|
const title = getItemTitleInContextOfLinkBubble(item)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -905,9 +905,7 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{editorMode !== 'super' && (
|
|
||||||
<LinkedItemBubblesContainer linkingController={this.viewControllerManager.linkingController} />
|
<LinkedItemBubblesContainer linkingController={this.viewControllerManager.linkingController} />
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||||
import { LexicalTypeaheadMenuPlugin, useBasicTypeaheadTriggerMatch } from '@lexical/react/LexicalTypeaheadMenuPlugin'
|
import { LexicalTypeaheadMenuPlugin } from '@lexical/react/LexicalTypeaheadMenuPlugin'
|
||||||
import { TextNode } from 'lexical'
|
import { TextNode } from 'lexical'
|
||||||
import { FunctionComponent, useCallback, useMemo, useState } from 'react'
|
import { FunctionComponent, useCallback, useMemo, useState } from 'react'
|
||||||
import { ItemSelectionItemComponent } from './ItemSelectionItemComponent'
|
import { ItemSelectionItemComponent } from './ItemSelectionItemComponent'
|
||||||
@@ -12,6 +12,7 @@ import { INSERT_BUBBLE_COMMAND, INSERT_FILE_COMMAND } from '../Commands'
|
|||||||
import { useLinkingController } from '../../../../../Controllers/LinkingControllerProvider'
|
import { useLinkingController } from '../../../../../Controllers/LinkingControllerProvider'
|
||||||
import { PopoverClassNames } from '../ClassNames'
|
import { PopoverClassNames } from '../ClassNames'
|
||||||
import { isMobileScreen } from '@/Utils'
|
import { isMobileScreen } from '@/Utils'
|
||||||
|
import { useTypeaheadAllowingSpacesAndPunctuation } from './useTypeaheadAllowingSpacesAndPunctuation'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
currentNote: SNNote
|
currentNote: SNNote
|
||||||
@@ -26,7 +27,7 @@ export const ItemSelectionPlugin: FunctionComponent<Props> = ({ currentNote }) =
|
|||||||
|
|
||||||
const [queryString, setQueryString] = useState<string | null>('')
|
const [queryString, setQueryString] = useState<string | null>('')
|
||||||
|
|
||||||
const checkForTriggerMatch = useBasicTypeaheadTriggerMatch('@', {
|
const checkForTriggerMatch = useTypeaheadAllowingSpacesAndPunctuation('@', {
|
||||||
minLength: 0,
|
minLength: 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { LexicalEditor } from 'lexical'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
export type QueryMatch = {
|
||||||
|
leadOffset: number
|
||||||
|
matchingString: string
|
||||||
|
replaceableString: string
|
||||||
|
}
|
||||||
|
type TriggerFn = (text: string, editor: LexicalEditor) => QueryMatch | null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derived from
|
||||||
|
* https://github.com/facebook/lexical/blob/main/packages/lexical-react/src/LexicalTypeaheadMenuPlugin.tsx#L545
|
||||||
|
*/
|
||||||
|
export function useTypeaheadAllowingSpacesAndPunctuation(
|
||||||
|
trigger: string,
|
||||||
|
{ minLength = 1, maxLength = 75 }: { minLength?: number; maxLength?: number },
|
||||||
|
): TriggerFn {
|
||||||
|
return useCallback(
|
||||||
|
(text: string) => {
|
||||||
|
const validChars = '[^' + trigger + ']'
|
||||||
|
const TypeaheadTriggerRegex = new RegExp(
|
||||||
|
'(^|\\s|\\()(' + '[' + trigger + ']' + '((?:' + validChars + '){0,' + maxLength + '})' + ')$',
|
||||||
|
)
|
||||||
|
const match = TypeaheadTriggerRegex.exec(text)
|
||||||
|
if (match !== null) {
|
||||||
|
const maybeLeadingWhitespace = match[1]
|
||||||
|
const matchingString = match[3]
|
||||||
|
if (matchingString.length >= minLength) {
|
||||||
|
return {
|
||||||
|
leadOffset: match.index + maybeLeadingWhitespace.length,
|
||||||
|
matchingString,
|
||||||
|
replaceableString: match[2],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
[maxLength, minLength, trigger],
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,12 +1,26 @@
|
|||||||
import { SNTag, WebApplicationInterface, DecryptedItemInterface, ItemContent } from '@standardnotes/snjs'
|
import { WebApplicationInterface, DecryptedItemInterface, ItemContent, isNote, isTag } from '@standardnotes/snjs'
|
||||||
|
|
||||||
|
export function getItemTitleInContextOfLinkBubble(item: DecryptedItemInterface<ItemContent>) {
|
||||||
|
return item.title && item.title.length > 0 ? item.title : isNote(item) ? item.preview_plain : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function getItemSearchableString(item: DecryptedItemInterface<ItemContent>, application: WebApplicationInterface) {
|
||||||
|
if (isNote(item)) {
|
||||||
|
return item.title.length > 0 ? item.title : item.preview_plain
|
||||||
|
} else if (isTag(item)) {
|
||||||
|
return application.items.getTagLongTitle(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return item.title ?? ''
|
||||||
|
}
|
||||||
|
|
||||||
export function doesItemMatchSearchQuery(
|
export function doesItemMatchSearchQuery(
|
||||||
item: DecryptedItemInterface<ItemContent>,
|
item: DecryptedItemInterface<ItemContent>,
|
||||||
searchQuery: string,
|
searchQuery: string,
|
||||||
application: WebApplicationInterface,
|
application: WebApplicationInterface,
|
||||||
) {
|
) {
|
||||||
const title = item instanceof SNTag ? application.items.getTagLongTitle(item) : item.title ?? ''
|
const title = getItemSearchableString(item, application).toLowerCase()
|
||||||
const matchesQuery = title.toLowerCase().includes(searchQuery.toLowerCase())
|
const matchesQuery = title.includes(searchQuery.toLowerCase())
|
||||||
const isArchivedOrTrashed = item.archived || item.trashed
|
const isArchivedOrTrashed = item.archived || item.trashed
|
||||||
const isValidSearchResult = matchesQuery && !isArchivedOrTrashed
|
const isValidSearchResult = matchesQuery && !isArchivedOrTrashed
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user