Add Markdown formatting hotkeys: CTRL+K, CTRL+I, CTRL+B (#305)
* Fix usage of deprecated event.keyCode * Add CTRL+K to insert markdown link formatting * Also add CTRL+B and CTRL+I * Fix undo not working after using setValue Undo doesn't work if inputs are changed using javascript code like helpers.setValue(). The solution is to also use `document.execCommand()`. See https://stackoverflow.com/questions/27027833/is-it-possible-to-edit-a-text-input-with-javascript-and-add-to-the-undo-stack However, `document.execCommand()` is deprecated but there seems to be no alternative, see: - https://stackoverflow.com/questions/60581285/execcommand-is-now-obsolete-whats-the-alternative - https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand#browser_compatibility - https://github.com/codex-team/editor.js/discussions/2214 And so far, every browser still seems to support it: https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand#browser_compatibility --------- Co-authored-by: ekzyis <ek@stacker.news>
This commit is contained in:
		
							parent
							
								
									3ee16422f7
								
							
						
					
					
						commit
						393d4c7603
					
				@ -121,6 +121,32 @@ export function MarkdownInput ({ label, topLevel, groupClassName, onChange, setH
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function insertMarkdownFormatting (replaceFn, selectFn) {
 | 
			
		||||
  return function (input, setValue, setSelectionRange) {
 | 
			
		||||
    const start = input.selectionStart
 | 
			
		||||
    const end = input.selectionEnd
 | 
			
		||||
    const highlight = start !== end
 | 
			
		||||
    const val = input.value
 | 
			
		||||
    if (!highlight) return
 | 
			
		||||
    const selectedText = val.substring(start, end)
 | 
			
		||||
    const mdFormatted = replaceFn(selectedText)
 | 
			
		||||
    const newVal = val.substring(0, start) + mdFormatted + val.substring(end)
 | 
			
		||||
    setValue(newVal)
 | 
			
		||||
    // required for undo, see https://stackoverflow.com/a/27028258
 | 
			
		||||
    document.execCommand('insertText', false, mdFormatted)
 | 
			
		||||
    // see https://github.com/facebook/react/issues/6483
 | 
			
		||||
    // for why we don't use `input.setSelectionRange` directly (hint: event order)
 | 
			
		||||
    setSelectionRange(selectFn ? selectFn(start, mdFormatted) : { start: start + mdFormatted.length, end: start + mdFormatted.length })
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const insertMarkdownLinkFormatting = insertMarkdownFormatting(
 | 
			
		||||
  val => `[${val}](url)`,
 | 
			
		||||
  (start, mdFormatted) => ({ start: start + mdFormatted.length - 4, end: start + mdFormatted.length - 1 })
 | 
			
		||||
)
 | 
			
		||||
const insertMarkdownBoldFormatting = insertMarkdownFormatting(val => `**${val}**`)
 | 
			
		||||
const insertMarkdownItalicFormatting = insertMarkdownFormatting(val => `_${val}_`)
 | 
			
		||||
 | 
			
		||||
function FormGroup ({ className, label, children }) {
 | 
			
		||||
  return (
 | 
			
		||||
    <BootstrapForm.Group className={className}>
 | 
			
		||||
@ -137,6 +163,7 @@ function InputInner ({
 | 
			
		||||
  const [field, meta, helpers] = noForm ? [{}, {}, {}] : useField(props)
 | 
			
		||||
  const formik = noForm ? null : useFormikContext()
 | 
			
		||||
  const storageKeyPrefix = useContext(StorageKeyPrefixContext)
 | 
			
		||||
  const [selectionRange, setSelectionRange] = useState({ start: 0, end: 0 })
 | 
			
		||||
 | 
			
		||||
  const storageKey = storageKeyPrefix ? storageKeyPrefix + '-' + props.name : undefined
 | 
			
		||||
 | 
			
		||||
@ -156,6 +183,14 @@ function InputInner ({
 | 
			
		||||
    }
 | 
			
		||||
  }, [overrideValue])
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (selectionRange.start <= selectionRange.end && innerRef?.current) {
 | 
			
		||||
      const { start, end } = selectionRange
 | 
			
		||||
      const input = innerRef.current
 | 
			
		||||
      input.setSelectionRange(start, end)
 | 
			
		||||
    }
 | 
			
		||||
  }, [selectionRange.start, selectionRange.end])
 | 
			
		||||
 | 
			
		||||
  const invalid = (!formik || formik.submitCount > 0) && meta.touched && meta.error
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
@ -168,9 +203,21 @@ function InputInner ({
 | 
			
		||||
        )}
 | 
			
		||||
        <BootstrapForm.Control
 | 
			
		||||
          onKeyDown={(e) => {
 | 
			
		||||
            if (e.keyCode === 13 && (e.metaKey || e.ctrlKey)) {
 | 
			
		||||
            const metaOrCtrl = e.metaKey || e.ctrlKey
 | 
			
		||||
            if (e.key === 'Enter' && metaOrCtrl) {
 | 
			
		||||
              formik?.submitForm()
 | 
			
		||||
            }
 | 
			
		||||
            if (e.key === 'k' && metaOrCtrl) {
 | 
			
		||||
              // some browsers use CTRL+K to focus search bar so we have to prevent that behavior
 | 
			
		||||
              e.preventDefault()
 | 
			
		||||
              insertMarkdownLinkFormatting(innerRef.current, helpers.setValue, setSelectionRange)
 | 
			
		||||
            }
 | 
			
		||||
            if (e.key === 'b' && metaOrCtrl) {
 | 
			
		||||
              insertMarkdownBoldFormatting(innerRef.current, helpers.setValue, setSelectionRange)
 | 
			
		||||
            }
 | 
			
		||||
            if (e.key === 'i' && metaOrCtrl) {
 | 
			
		||||
              insertMarkdownItalicFormatting(innerRef.current, helpers.setValue, setSelectionRange)
 | 
			
		||||
            }
 | 
			
		||||
            if (onKeyDown) onKeyDown(e)
 | 
			
		||||
          }}
 | 
			
		||||
          ref={innerRef}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user