Skip to content

Commit

Permalink
Handle context menu format bold and italic (#556)
Browse files Browse the repository at this point in the history
  • Loading branch information
bernhardoj authored Jan 8, 2025
1 parent 100fa3b commit f619621
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 1 deletion.
12 changes: 12 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ import {PlatformInfo} from './PlatformInfo';
// We don't need this workaround in New Expensify App since Reanimated is imported before Live Markdown.
console.log(Animated);

function handleFormatSelection(selectedText: string, formatCommand: string) {
switch (formatCommand) {
case 'formatBold':
return `*${selectedText}*`;
case 'formatItalic':
return `_${selectedText}_`;
default:
return selectedText;
}
}

export default function App() {
const [value, setValue] = React.useState(TEST_CONST.EXAMPLE_CONTENT);
const [textColorState, setTextColorState] = React.useState(false);
Expand Down Expand Up @@ -48,6 +59,7 @@ export default function App() {
<PlatformInfo />
<MarkdownTextInput
multiline
formatSelection={handleFormatSelection}
autoCapitalize="none"
value={value}
onChangeText={setValue}
Expand Down
1 change: 1 addition & 0 deletions src/MarkdownTextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ function unregisterParser(parserId: number) {

interface MarkdownTextInputProps extends TextInputProps, InlineImagesInputProps {
markdownStyle?: PartialMarkdownStyle;
formatSelection?: (selectedText: string, formatCommand: string) => string;
parser: (value: string) => MarkdownRange[];
}

Expand Down
50 changes: 49 additions & 1 deletion src/MarkdownTextInput.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const useClientEffect = typeof window === 'undefined' ? useEffect : useLayoutEff
interface MarkdownTextInputProps extends TextInputProps, InlineImagesInputProps {
markdownStyle?: MarkdownStyle;
parser: (text: string) => MarkdownRange[];
formatSelection?: (selectedText: string, formatCommand: string) => string;
onClick?: (e: MouseEvent<HTMLDivElement>) => void;
dir?: string;
disabled?: boolean;
Expand Down Expand Up @@ -85,6 +86,7 @@ const MarkdownTextInput = React.forwardRef<MarkdownTextInput, MarkdownTextInputP
multiline = false,
markdownStyle,
parser,
formatSelection,
onBlur,
onChange,
onChangeText,
Expand Down Expand Up @@ -236,6 +238,33 @@ const MarkdownTextInput = React.forwardRef<MarkdownTextInput, MarkdownTextInputP
[parser, parseText, processedMarkdownStyle],
);

const handleFormatSelection = useCallback(
(target: MarkdownTextInputElement, parsedText: string, cursorPosition: number, formatCommand: string): ParseTextResult => {
if (!contentSelection.current || contentSelection.current.end - contentSelection.current.start < 1) {
throw new Error('[react-native-live-markdown] Trying to apply format command on empty selection');
}

if (!formatSelection) {
return parseText(parser, target, parsedText, processedMarkdownStyle, cursorPosition);
}

const selectedText = parsedText.slice(contentSelection.current.start, contentSelection.current.end);
const formattedText = formatSelection(selectedText, formatCommand);

if (selectedText === formattedText) {
return parseText(parser, target, parsedText, processedMarkdownStyle, cursorPosition);
}

const prefix = parsedText.slice(0, contentSelection.current.start);
const suffix = parsedText.slice(contentSelection.current.end);
const diffLength = formattedText.length - selectedText.length;
const text = `${prefix}${formattedText}${suffix}`;

return parseText(parser, target, text, processedMarkdownStyle, cursorPosition + diffLength, true);
},
[parser, parseText, formatSelection, processedMarkdownStyle],
);

// Placeholder text color logic
const updateTextColor = useCallback(
(node: HTMLDivElement, text: string) => {
Expand Down Expand Up @@ -361,6 +390,11 @@ const MarkdownTextInput = React.forwardRef<MarkdownTextInput, MarkdownTextInputP
case 'historyRedo':
newInputUpdate = redo(divRef.current);
break;
case 'formatBold':
case 'formatItalic':
case 'formatUnderline':
newInputUpdate = handleFormatSelection(divRef.current, parsedText, newCursorPosition, inputType);
break;
default:
newInputUpdate = parseText(parser, divRef.current, parsedText, processedMarkdownStyle, newCursorPosition, true, !inputType, inputType === 'pasteText');
}
Expand Down Expand Up @@ -414,7 +448,21 @@ const MarkdownTextInput = React.forwardRef<MarkdownTextInput, MarkdownTextInputP

handleContentSizeChange();
},
[parser, updateTextColor, updateSelection, onChange, onChangeText, handleContentSizeChange, undo, redo, parseText, processedMarkdownStyle, setEventProps, maxLength],
[
parser,
updateTextColor,
updateSelection,
onChange,
onChangeText,
handleContentSizeChange,
undo,
redo,
handleFormatSelection,
parseText,
processedMarkdownStyle,
setEventProps,
maxLength,
],
);

const insertText = useCallback(
Expand Down

0 comments on commit f619621

Please sign in to comment.