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

Cannot set cursor in front of the mention tag #819

Open
higherstates opened this issue Jun 30, 2022 · 0 comments
Open

Cannot set cursor in front of the mention tag #819

higherstates opened this issue Jun 30, 2022 · 0 comments

Comments

@higherstates
Copy link

What happened?

I'm working with React v17.
I have a ContentEditable div which has the ability to mention a variable using tributejs.

  • Currently, I cannot place the cursor in front of the Tribute span tags.
  • The span is not editable. Even if it was, placing the cursor in front of it just results in editing the span tag, can't add text before the tag.

What did you expect to happen?

Being able to place the cursor in front of the mention, just like how it works in Tribute's examples.

Screenshot:

image

Reference:

1. Versions:

"react": "^17.0.2",
"react-contenteditable": "^3.3.5",
"tributejs": "^5.1.3",

2. Related code:

let savedRange: Range

const Editor = ({ placeholders }: Props) => {
  const { t } = useTranslation('template')
  const {
    field: { onChange, onBlur, value },
    fieldState: { error }
  } = useController({
    name: 'content',
    rules: {
      validate: () => {
        const isValid = editorRef.current.innerText.trim().length
        return !isValid ? (t('validationContentRequired') as string) : true
      }
    }
  })

  const editorRef = useRef(null)
  const tributeRef = useRef<Tribute<TemplatePlaceholder>>()

  const initTribute = useCallback(
    (editorElm: HTMLElement) => {
      tributeRef.current = new Tribute({
        values: placeholders,
        requireLeadingSpace: false,
        lookup: 'preview',
        containerClass: 'suggestion-container',
        menuContainer: document.getElementById('root'),
        noMatchTemplate: function () {
          return '<span style:"visibility: hidden;"></span>'
        },
        selectTemplate: function (item: TributeItem<TemplatePlaceholder>) {
          return item
            ? `<span
            placeholder="${item.original.name}"
            preview="${item.original.preview}"
            original='${item.original.original}'
            contenteditable="false"
          >${t(item.original.name)}</span>`
            : ''
        },
        menuItemTemplate: function (item: TributeItem<TemplatePlaceholder>) {
          return item ? t(item.original.name) : ''
        }
      })
      tributeRef.current.attach(editorElm)
    },
    [placeholders, t]
  )

  useEffect(() => {
    const editorElm = editorRef.current
    if (!editorElm) return undefined

    if (placeholders && !tributeRef.current) {
      initTribute(editorElm)
    }

    return () => {
      if (tributeRef.current) {
        tributeRef.current.detach(editorElm)
        tributeRef.current = null
      }
    }
  }, [placeholders, initTribute])

  const saveCaretPosition = () => {
    savedRange = RangeUtils.getCaretPosition()
  }

    return (
    <>
      <div className={classNames('editor__wrapper editor', { 'editor--error': !!error })}>


        <div className='editor__content'>
          <ContentEditable
            className='w-full h-full outline-none'
            innerRef={editorRef}
            html={value || ''}
            onKeyDown={event => {
              if (event.key === 'Enter') {
                document.execCommand('insertLineBreak')
                event.preventDefault()
              }
            }}
            onChange={(event: ContentEditableEvent) => {
              onChange(event.target.value)
            }}
            onBlur={() => {
              saveCaretPosition()
              onBlur()
            }}
            onPaste={handlePaste}
            spellCheck={false}
            contentEditable={true}
          />
        </div>
      </div>
      {!!error && <ErrorMessage>{error.message}</ErrorMessage>}
    </>
  )
}

export default React.memo(Editor)
const placeHolderRegex =
  /<span placeholder="(\w+)" preview="([\w ]*)" original=["|']([\w{} ]*|<a[^<]*<\/a>)["|']>([^>]*|<a[^<]*<\/a> ?)<\/span>/gi

const contenteditableRegex = /[ ]*contenteditable=["|']false["|']*/gi
const linePlaceholderRegex = /{{ (\w+) }}/gi

const transformMentionToPlaceholder = (content: string, type: string) => {
  content = content.replace(contenteditableRegex, '')
  content = content.replace(/&quot;/gi, '"')
  if (type === TemplateTypes.Mail) {
    return content.replace(placeHolderRegex, `<span placeholder="$1" preview="$2" original='$3'>$3</span>`)
  }

  if (type === TemplateTypes.Line) {
    return content
      .replace(placeHolderRegex, `$3`)
      .replace(/<(\/)?div>/g, '')
      .replace(/<br>/g, '\n')
  }
  return content
}


  const transformContentToMention = useCallback(
    (content: string, type = TemplateTypes.Mail) => {
      content = content?.replace(contenteditableRegex, '')
      switch (type) {
        case TemplateTypes.Mail:
          return content?.replace(placeHolderRegex, (_, p1, p2, p3) => {
            return `<span placeholder="${p1}" preview="${p2}" original='${p3}' contenteditable="false">${t(p1)}</span>`
          })
        case TemplateTypes.Line:
          return content
            ?.replace(linePlaceholderRegex, (_, p) => {
              return `<span placeholder="${p}" preview="${p}" original="{{ ${p} }}" contenteditable="false">${t(
                p
              )}</span>`
            })
            .replace(/\n/g, '<br>')
        default:
          return ''
      }
    },
    [t]
  )
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

1 participant