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

Handle context menu format bold and italic #556

Merged
merged 11 commits into from
Jan 8, 2025
13 changes: 13 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ 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) {
if (formatCommand === 'formatBold') {
return `*${selectedText}*`;
}

if (formatCommand === 'formatItalic') {
return `_${selectedText}_`;
}

return selectedText;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a nit but this is a perfect place to use switch/case block.

Suggested change
if (formatCommand === 'formatBold') {
return `*${selectedText}*`;
}
if (formatCommand === 'formatItalic') {
return `_${selectedText}_`;
}
return selectedText;
switch (formatCommand) {
case 'formatBold':
return `*${selectedText}*`;
case 'formatItalic':
return `_${selectedText}_`;
default:
return selectedText;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}

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 +60,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 @@ -53,6 +53,7 @@ function unregisterParser(parserId: number) {

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

Expand Down
46 changes: 45 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,29 @@ 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] invalid selection');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's rephrase the error message to be more specific.

Suggested change
throw new Error('[react-native-live-markdown] invalid selection');
throw new Error('[react-native-live-markdown] Trying to apply format command on empty selection');

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need the ?? selectedText part here? Maybe we should just return early if formatSelection is undefined?


if (selectedText === formattedText) {
return parseText(parser, target, parsedText, processedMarkdownStyle, cursorPosition);
}
tomekzaw marked this conversation as resolved.
Show resolved Hide resolved

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 +386,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 +444,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
Loading