Skip to content

Commit

Permalink
Allow some special characters on TagsQuery component
Browse files Browse the repository at this point in the history
  • Loading branch information
Alejandro Hernandez committed Apr 1, 2024
1 parent dab6493 commit 2bdceb0
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ let addQuotesAndDistance = _.curry((tag, text) => {
let replaceReservedChars = _.flow(
_.toString,
// Replace characters with white space ` `
_.replace(/([+\-=&|!(){}[\]^"~*?:\\/<>;,$'])/g, ' ')
_.replace(/([&|!(){}[\]^"~*?\\<>;,$'])/g, ' '),
// Escape these characters
_.replace(/([+\-=:/])/g, '\\$1')
)

let tagToQueryString = (tag) => {
Expand Down
9 changes: 2 additions & 7 deletions packages/react/src/exampleTypes/ExpandableTagsQuery/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import ActionsMenu from '../TagsQuery/ActionsMenu.js'
import { useOutsideClick } from '@chakra-ui/react-use-outside-click'
import { sanitizeTagInputs } from 'contexture-elasticsearch/utils/keywordGenerations.js'
import KeywordGenerations from './KeywordGenerations.js'
import { wordRegex, wordRegexWithDot } from '../../greyVest/utils.js'
import { sanitizeQueryStringTag } from '../../greyVest/utils.js'

let innerHeightLimit = 40

Expand Down Expand Up @@ -133,10 +133,6 @@ let TagsWrapper = observer(
popoverOffsetY,
theme: { Icon, TagsInput, Tag, Popover },
joinOptions,
// Allow tag keywords to contain dots in them if the user is searching for exact
// words instead of their variations.
wordsMatchPattern = node.exact ? wordRegexWithDot : wordRegex,
sanitizeTags = true,
splitCommas = true,
maxTags = 1000,
hasPopover,
Expand Down Expand Up @@ -195,9 +191,8 @@ let TagsWrapper = observer(
<GridItem height={2} place="center stretch">
<TagsInput
splitCommas={splitCommas}
sanitizeTags={sanitizeTags}
sanitizeTagFn={sanitizeQueryStringTag}
maxTags={maxTags}
wordsMatchPattern={wordsMatchPattern}
tags={
node.tags?.length > 0
? _.map(tagValueField, node.tags)
Expand Down
28 changes: 8 additions & 20 deletions packages/react/src/greyVest/ExpandableTagsInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react'
import _ from 'lodash/fp.js'
import { observer } from 'mobx-react'
import { Tag as DefaultTag, Flex } from './index.js'
import { sanitizeTagWords, splitTagOnComma, wordRegex } from './utils.js'
import { createTags } from './utils.js'

export let Tags = ({
reverse = false,
Expand Down Expand Up @@ -38,7 +38,7 @@ let isValidInput = (tag, tags) => !_.isEmpty(tag) && !_.includes(tag, tags)

let ExpandableTagsInput = ({
tags,
addTags,
addTags: setTags,
removeTag,
submit = _.noop,
tagStyle,
Expand All @@ -48,30 +48,18 @@ let ExpandableTagsInput = ({
autoFocus,
onBlur = _.noop,
onInputChange = _.noop,
maxWordsPerTag = 100,
maxCharsPerTagWord = 100,
wordsMatchPattern = wordRegex,
onTagClick = _.noop,
sanitizeTags = true,
sanitizeTagFn,
Tag = DefaultTag,
...props
}) => {
let sanitizeTagFn = sanitizeTagWords(
wordsMatchPattern,
maxWordsPerTag,
maxCharsPerTagWord
)

addTags = _.flow(
_.trim,
(tags) => (splitCommas ? splitTagOnComma(tags) : _.castArray(tags)),
(tags) => (sanitizeTags ? _.map(sanitizeTagFn, tags) : tags),
_.difference(_, tags),
addTags
)

let [currentInput, setCurrentInput] = React.useState('')

let addTags = (input) => {
let newTags = createTags({ input, splitCommas, sanitizeTagFn })
setTags(_.difference(newTags, tags))
}

return (
<div style={style}>
<span className="tags-input-container">
Expand Down
44 changes: 8 additions & 36 deletions packages/react/src/greyVest/TagsInput.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import React, { forwardRef } from 'react'
import _ from 'lodash/fp.js'
import { observable } from '../utils/mobx.js'
import { observer, inject } from 'mobx-react'
import { observer } from 'mobx-react'
import Flex from './Flex.js'
import DefaultTag from './Tag.js'
import { sanitizeTagWords, splitTagOnComma, wordRegex } from './utils.js'
import { createTags } from './utils.js'

let isValidInput = (tag, tags) => !_.isEmpty(tag) && !_.includes(tag, tags)

let TagsInput = forwardRef(
(
{
tags,
addTags,
addTags: setTags,
removeTag,
submit = _.noop,
tagStyle,
Expand All @@ -22,10 +21,7 @@ let TagsInput = forwardRef(
onBlur = _.noop,
onInputChange = _.noop,
onTagClick = _.noop,
maxWordsPerTag = 100,
maxCharsPerTagWord = 100,
wordsMatchPattern = wordRegex,
sanitizeTags = true,
sanitizeTagFn,
Tag = DefaultTag,
...props
},
Expand All @@ -34,19 +30,10 @@ let TagsInput = forwardRef(
let containerRef = React.useRef()
let [currentInput, setCurrentInput] = React.useState('')

let sanitizeTagFn = sanitizeTagWords(
wordsMatchPattern,
maxWordsPerTag,
maxCharsPerTagWord
)

addTags = _.flow(
_.trim,
(tags) => (splitCommas ? splitTagOnComma(tags) : _.castArray(tags)),
(tags) => (sanitizeTags ? _.map(sanitizeTagFn, tags) : tags),
_.difference(_, tags),
addTags
)
let addTags = (input) => {
let newTags = createTags({ input, splitCommas, sanitizeTagFn })
setTags(_.difference(newTags, tags))
}

return (
<div className={'tags-input'} ref={containerRef} style={{ ...style }}>
Expand Down Expand Up @@ -118,19 +105,4 @@ let TagsInput = forwardRef(
}
)

// Just uses an internal observable array
export let MockTagsInput = inject(() => {
let tags = observable([])
return {
tags,
addTags(tag) {
tags.push(tag)
},
removeTag(tag) {
tags = _.without(tag, tags)
},
}
})(TagsInput)
MockTagsInput.displayName = 'MockTagsInput'

export default observer(TagsInput)
25 changes: 25 additions & 0 deletions packages/react/src/greyVest/TagsInput.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import _ from 'lodash/fp.js'
import React from 'react'
import TagsInput from './TagsInput.js'

export default {
component: TagsInput,
}

export const Default = () => {
let [tags, setTags] = React.useState([
'janitor',
'soap',
'cleaner',
'cleaning',
'clean',
])
return (
<TagsInput
tags={tags}
addTags={(tags) => setTags((current) => _.union(tags, current))}
removeTag={(tag) => setTags((current) => _.pull(tag, current))}
tagStyle={{ background: 'lavender' }}
/>
)
}
59 changes: 35 additions & 24 deletions packages/react/src/greyVest/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,47 @@ export let openBinding = (...lens) => ({
onClose: F.off(...lens),
})

let maxWordsPerTag = 100
let maxCharsPerTagWord = 100

// Strip these characters when splitting a tag into words. We do this to display
// tags that are closer to what we send to elastic in a `query_string` query.
//
// See https://github.com/smartprocure/contexture-elasticsearch/pull/170
//
// If in doubt, make a request to the `/{index}/analyze` elasticsearch endpoint
// to see exactly which characters get stripped out of text.
let wordRegex = /[^|><!(){}[\]^"~*?\\;,$']+/g
let words = _.words.convert({ fixed: false })

// Convert string to words, take the first maxWordsPerTag, truncate them and convert back to string
export let sanitizeTagWords = (
wordsMatchPattern,
maxWordsPerTag,
maxCharsPerTagWord
) => {
let words = _.words.convert({ fixed: false })
return _.flow(
(string) => words(string, wordsMatchPattern),
_.take(maxWordsPerTag),
_.map((word) =>
_.flow(
_.truncate({ length: maxCharsPerTagWord, omission: '' }),
// Remove beginning of line dash and space dash
_.replace(/^-| -/g, ' '),
_.trim
)(word)
),
_.join(' ')
)
}
export let sanitizeQueryStringTag = _.flow(
(string) => words(string, wordRegex),
_.take(maxWordsPerTag),
_.map((word) =>
_.flow(
_.truncate({ length: maxCharsPerTagWord, omission: '' }),
// Remove beginning of line dash and space dash
// https://github.com/smartprocure/spark/issues/10923
_.replace(/^-| -/g, ' '),
_.trim
)(word)
),
_.join(' ')
)

// Split a tag on comma into unique words
export let splitTagOnComma = _.flow(
_.trim,
let splitTagOnComma = _.flow(
_.split(','),
_.invokeMap('trim'),
_.compact,
_.uniq
)

export let wordRegex = /[-\w]+/g
export let wordRegexWithDot = /[-.\w]+/g
export let createTags = ({ input, splitCommas, sanitizeTagFn }) =>
_.flow(
_.trim,
splitCommas ? splitTagOnComma : _.identity,
_.castArray,
sanitizeTagFn ? _.map(sanitizeTagFn) : _.identity
)(input)

0 comments on commit 2bdceb0

Please sign in to comment.