add a test
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import {describe, it, expect} from '@jest/globals'
|
||||
|
||||
import {findSuggestionMatch, parsePunctuationFromTag} from '../utils'
|
||||
|
||||
describe('findSuggestionMatch', () => {
|
||||
it(`finds tag`, () => {
|
||||
const match = findSuggestionMatch({
|
||||
text: 'a #tag',
|
||||
cursorPosition: 6,
|
||||
})
|
||||
|
||||
expect(match).toEqual({
|
||||
range: {
|
||||
from: 2,
|
||||
to: 6,
|
||||
},
|
||||
query: 'tag',
|
||||
text: ' #tag',
|
||||
})
|
||||
})
|
||||
|
||||
it(`validates tag length`, () => {
|
||||
expect(
|
||||
findSuggestionMatch({
|
||||
text: '#xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
|
||||
cursorPosition: 65,
|
||||
}),
|
||||
).toEqual({
|
||||
range: {
|
||||
from: 0,
|
||||
to: 65,
|
||||
},
|
||||
query: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
|
||||
text: '#xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
|
||||
})
|
||||
|
||||
expect(
|
||||
findSuggestionMatch({
|
||||
text: '#xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxo',
|
||||
cursorPosition: 66,
|
||||
}),
|
||||
).toEqual(null)
|
||||
})
|
||||
|
||||
it(`reports tag with trailing punctuation`, () => {
|
||||
const match = findSuggestionMatch({
|
||||
text: '#tag!!!',
|
||||
cursorPosition: 7,
|
||||
})
|
||||
|
||||
expect(match).toEqual({
|
||||
range: {
|
||||
from: 0,
|
||||
to: 7,
|
||||
},
|
||||
query: 'tag!!!',
|
||||
text: '#tag!!!',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('parsePunctuationFromTag', () => {
|
||||
it(`parses with punctuation`, () => {
|
||||
expect(parsePunctuationFromTag('tag!')).toEqual({
|
||||
tag: 'tag',
|
||||
punctuation: '!',
|
||||
})
|
||||
expect(parsePunctuationFromTag('tag!!!')).toEqual({
|
||||
tag: 'tag',
|
||||
punctuation: '!!!',
|
||||
})
|
||||
})
|
||||
|
||||
it(`parses without punctuation`, () => {
|
||||
expect(parsePunctuationFromTag('tag')).toEqual({
|
||||
tag: 'tag',
|
||||
punctuation: '',
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -5,6 +5,8 @@ import {Node as ProseMirrorNode} from '@tiptap/pm/model'
|
||||
import {PluginKey} from '@tiptap/pm/state'
|
||||
import Suggestion, {SuggestionOptions} from '@estrattonbailey/tiptap-suggestion'
|
||||
|
||||
import {findSuggestionMatch} from './utils'
|
||||
|
||||
export type TagOptions = {
|
||||
HTMLAttributes: Record<string, any>
|
||||
renderLabel: (props: {options: TagOptions; node: ProseMirrorNode}) => string
|
||||
@@ -64,55 +66,13 @@ export const Tags = Node.create<TagOptions>({
|
||||
},
|
||||
findSuggestionMatch({$position}) {
|
||||
const text = $position.nodeBefore?.isText && $position.nodeBefore.text
|
||||
const cursorPosition = $position.pos
|
||||
|
||||
if (!text) {
|
||||
return null
|
||||
}
|
||||
|
||||
const regex = /(?:^|\s)(#[^\d\s]\S*)(?=\s)?/g
|
||||
const puncRegex = /\p{P}+$/gu
|
||||
const match = Array.from(text.matchAll(regex)).pop()
|
||||
|
||||
if (
|
||||
!match ||
|
||||
match.input === undefined ||
|
||||
match.index === undefined
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
const cursorPosition = $position.pos
|
||||
const startIndex = cursorPosition - text.length
|
||||
let [matchedString, tag] = match
|
||||
|
||||
const sanitized = tag.replace(puncRegex, '').replace(/^#/, '')
|
||||
|
||||
// one of our hashtag spec rules
|
||||
if (sanitized.length > 64) return null
|
||||
|
||||
const from = startIndex + match.index + matchedString.indexOf(tag)
|
||||
const to = from + tag.length
|
||||
|
||||
if (from < cursorPosition && to >= cursorPosition) {
|
||||
return {
|
||||
range: {
|
||||
from,
|
||||
to,
|
||||
},
|
||||
/**
|
||||
* This is passed to the `items({ query })` method configured in
|
||||
* `createTagsAutocomplete`.
|
||||
*
|
||||
* We parse out the punctuation later, but we don't want to pass
|
||||
* the # to the search query.
|
||||
*/
|
||||
query: tag.replace(/^#/, ''),
|
||||
// raw text string
|
||||
text: matchedString,
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
return findSuggestionMatch({text, cursorPosition})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
export function parsePunctuationFromTag(value: string) {
|
||||
const reg = /(\p{P}+)$/gu
|
||||
const tag = value.replace(reg, '')
|
||||
const punctuation = value.match(reg)?.[0] || ''
|
||||
|
||||
return {tag, punctuation}
|
||||
}
|
||||
|
||||
export function findSuggestionMatch({
|
||||
text,
|
||||
cursorPosition,
|
||||
}: {
|
||||
text: string
|
||||
cursorPosition: number
|
||||
}) {
|
||||
const regex = /(?:^|\s)(#[^\d\s]\S*)(?=\s)?/g
|
||||
const puncRegex = /\p{P}+$/gu
|
||||
const match = Array.from(text.matchAll(regex)).pop()
|
||||
|
||||
if (!match || match.input === undefined || match.index === undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
const startIndex = cursorPosition - text.length
|
||||
let [matchedString, tag] = match
|
||||
|
||||
const sanitized = tag.replace(puncRegex, '').replace(/^#/, '')
|
||||
|
||||
// one of our hashtag spec rules
|
||||
if (sanitized.length > 64) return null
|
||||
|
||||
const from = startIndex + match.index + matchedString.indexOf(tag)
|
||||
const to = from + tag.length
|
||||
|
||||
if (from < cursorPosition && to >= cursorPosition) {
|
||||
return {
|
||||
range: {
|
||||
from,
|
||||
to,
|
||||
},
|
||||
/**
|
||||
* This is passed to the `items({ query })` method configured in
|
||||
* `createTagsAutocomplete`.
|
||||
*
|
||||
* We parse out the punctuation later, but we don't want to pass
|
||||
* the # to the search query.
|
||||
*/
|
||||
query: tag.replace(/^#/, ''),
|
||||
// raw text string
|
||||
text: matchedString,
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -12,6 +12,8 @@ import {TagsAutocompleteModel} from 'state/models/ui/tags-autocomplete'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {Text} from 'view/com/util/text/Text'
|
||||
|
||||
import {parsePunctuationFromTag} from './utils'
|
||||
|
||||
type AutocompleteResult = string
|
||||
type ListProps = SuggestionProps<AutocompleteResult> & {
|
||||
autocompleteModel: TagsAutocompleteModel
|
||||
@@ -99,8 +101,7 @@ const Autocomplete = forwardRef<AutocompleteRef, ListProps>(
|
||||
|
||||
const commit = React.useCallback(
|
||||
(query: string) => {
|
||||
const tag = query.replace(/(\p{P}+)$/gu, '')
|
||||
const punctuation = query.match(/(\p{P}+)$/gu)?.[0] || ''
|
||||
const {tag, punctuation} = parsePunctuationFromTag(query)
|
||||
/*
|
||||
* This values here are passed directly to the `command` method
|
||||
* configured in the `Tags` plugin.
|
||||
|
||||
Reference in New Issue
Block a user