From e7e4deffc687e741445c51cfdd3ae5d8f6d80859 Mon Sep 17 00:00:00 2001 From: Abhi-Bohora Date: Fri, 6 Dec 2024 18:20:22 +0545 Subject: [PATCH 1/2] feat: added keyboard shortcut for md --- .../components/fields/MarkdownInput/index.tsx | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/packages/shared/src/components/fields/MarkdownInput/index.tsx b/packages/shared/src/components/fields/MarkdownInput/index.tsx index 2da6b58901..dea292f849 100644 --- a/packages/shared/src/components/fields/MarkdownInput/index.tsx +++ b/packages/shared/src/components/fields/MarkdownInput/index.tsx @@ -170,6 +170,119 @@ function MarkdownInput( focusInput(textareaRef.current, [content.length, content.length]); }, []); + const handleMarkdownShortcut = ( + e: React.KeyboardEvent, + ) => { + const textarea = textareaRef.current; + const { selectionStart, selectionEnd, value } = textarea; + + const updateTextarea = ( + newValue: string, + newStart: number, + newEnd: number, + ) => { + onValueUpdate?.(newValue); + callbacks.onInput?.({ + currentTarget: { value: newValue }, + } as React.FormEvent); + + requestAnimationFrame(() => { + textarea.value = newValue; + textarea.setSelectionRange(newStart, newEnd); + }); + }; + + const handleTextFormatting = (symbol: string) => { + const selectedValue = value.substring(selectionStart, selectionEnd); + const trimmedValue = selectedValue.trim(); + + const leadingWhitespace = + selectedValue.length - selectedValue.trimStart().length; + const newStart = selectionStart + leadingWhitespace; + const newEnd = newStart + trimmedValue.length; + const symbolLength = symbol.length; + + const isFormatted = + value.substring(newStart - symbolLength, newStart) === symbol && + value.substring(newEnd, newEnd + symbolLength) === symbol; + + let updatedValue; + let updatedStart; + let updatedEnd; + + if (isFormatted) { + updatedValue = + value.substring(0, newStart - symbolLength) + + trimmedValue + + value.substring(newEnd + symbolLength); + updatedStart = newStart - symbolLength; + updatedEnd = newEnd - symbolLength; + } else { + updatedValue = + value.substring(0, newStart) + + symbol + + trimmedValue + + symbol + + value.substring(newEnd); + updatedStart = newStart + symbolLength; + updatedEnd = newEnd + symbolLength; + } + + updateTextarea(updatedValue, updatedStart, updatedEnd); + }; + + const handleLinkPaste = () => { + navigator.clipboard.readText().then((clipboardText) => { + const selectedText = value.substring(selectionStart, selectionEnd); + const trimmedText = selectedText.trim(); + + const leadingWhitespace = + selectedText.length - selectedText.trimStart().length; + const trailingWhitespace = + selectedText.length - selectedText.trimEnd().length; + + const newStart = selectionStart + leadingWhitespace; + const newEnd = selectionEnd - trailingWhitespace; + + const newText = + /^https?:\/\//.test(clipboardText) && trimmedText + ? `${value.substring( + 0, + newStart, + )}[${trimmedText}](${clipboardText})${value.substring(newEnd)}` + : value.substring(0, selectionStart) + + clipboardText + + value.substring(selectionEnd); + + const linkEnd = newStart + `[${trimmedText}](${clipboardText})`.length; + updateTextarea(newText, linkEnd, linkEnd); + }); + }; + + if (e.metaKey || e.ctrlKey) { + switch (e.key) { + case 'b': + e.preventDefault(); + handleTextFormatting('**'); + break; + case 'i': + e.preventDefault(); + handleTextFormatting('_'); + break; + case 'v': + e.preventDefault(); + handleLinkPaste(); + break; + case 'l': + e.preventDefault(); + onLinkCommand?.(); + break; + default: + break; + } + } + }; + return (
e.preventDefault()} // for better experience and stop opening the file with browser maxLength={maxInputLength} /> From 61a9f1eb07094aac3d118f5738f0d4817fe4a7a0 Mon Sep 17 00:00:00 2001 From: Abhi-Bohora Date: Fri, 3 Jan 2025 20:26:12 +0545 Subject: [PATCH 2/2] generic fn and using isValidHttpUrl --- .../components/fields/MarkdownInput/index.tsx | 53 ++++++++++++------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/packages/shared/src/components/fields/MarkdownInput/index.tsx b/packages/shared/src/components/fields/MarkdownInput/index.tsx index dea292f849..f7cec2b9f9 100644 --- a/packages/shared/src/components/fields/MarkdownInput/index.tsx +++ b/packages/shared/src/components/fields/MarkdownInput/index.tsx @@ -41,6 +41,7 @@ import { Divider } from '../../utilities'; import { usePopupSelector } from '../../../hooks/usePopupSelector'; import { focusInput } from '../../../lib/textarea'; import CloseButton from '../../CloseButton'; +import { isValidHttpUrl } from '../../../lib'; interface ClassName { container?: string; @@ -170,6 +171,25 @@ function MarkdownInput( focusInput(textareaRef.current, [content.length, content.length]); }, []); + const getTextBoundaries = ( + text: string, + selectionStart: number, + selectionEnd: number, + ) => { + const selectedText = text.substring(selectionStart, selectionEnd); + const trimmedText = selectedText.trim(); + const leadingWhitespace = + selectedText.length - selectedText.trimStart().length; + const trailingWhitespace = + selectedText.length - selectedText.trimEnd().length; + + return { + trimmedText, + newStart: selectionStart + leadingWhitespace, + newEnd: selectionEnd - trailingWhitespace, + }; + }; + const handleMarkdownShortcut = ( e: React.KeyboardEvent, ) => { @@ -193,13 +213,11 @@ function MarkdownInput( }; const handleTextFormatting = (symbol: string) => { - const selectedValue = value.substring(selectionStart, selectionEnd); - const trimmedValue = selectedValue.trim(); - - const leadingWhitespace = - selectedValue.length - selectedValue.trimStart().length; - const newStart = selectionStart + leadingWhitespace; - const newEnd = newStart + trimmedValue.length; + const { trimmedText, newStart, newEnd } = getTextBoundaries( + value, + selectionStart, + selectionEnd, + ); const symbolLength = symbol.length; const isFormatted = @@ -213,7 +231,7 @@ function MarkdownInput( if (isFormatted) { updatedValue = value.substring(0, newStart - symbolLength) + - trimmedValue + + trimmedText + value.substring(newEnd + symbolLength); updatedStart = newStart - symbolLength; updatedEnd = newEnd - symbolLength; @@ -221,7 +239,7 @@ function MarkdownInput( updatedValue = value.substring(0, newStart) + symbol + - trimmedValue + + trimmedText + symbol + value.substring(newEnd); updatedStart = newStart + symbolLength; @@ -233,19 +251,14 @@ function MarkdownInput( const handleLinkPaste = () => { navigator.clipboard.readText().then((clipboardText) => { - const selectedText = value.substring(selectionStart, selectionEnd); - const trimmedText = selectedText.trim(); - - const leadingWhitespace = - selectedText.length - selectedText.trimStart().length; - const trailingWhitespace = - selectedText.length - selectedText.trimEnd().length; - - const newStart = selectionStart + leadingWhitespace; - const newEnd = selectionEnd - trailingWhitespace; + const { trimmedText, newStart, newEnd } = getTextBoundaries( + value, + selectionStart, + selectionEnd, + ); const newText = - /^https?:\/\//.test(clipboardText) && trimmedText + isValidHttpUrl(clipboardText) && trimmedText ? `${value.substring( 0, newStart,