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
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 @@ -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
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);
}
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 +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
Loading