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…
Reference in New Issue