diff --git a/src/components/CodeBlockHeader/CodeBlockHeader.tsx b/src/components/CodeBlockHeader/CodeBlockHeader.tsx new file mode 100644 index 000000000..638f21bbd --- /dev/null +++ b/src/components/CodeBlockHeader/CodeBlockHeader.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import {View, Text, TouchableOpacity} from 'react-native'; + +import Clipboard from '@react-native-clipboard/clipboard'; +import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; + +import {CopyIcon} from '../../assets/icons'; + +import {useTheme} from '../../hooks'; + +import {createStyles} from './styles'; + +interface CodeBlockHeaderProps { + language: string; + content: string; +} + +const hapticOptions = { + enableVibrateFallback: true, + ignoreAndroidSystemSettings: false, +}; + +export const CodeBlockHeader: React.FC = ({ + language, + content, +}) => { + const theme = useTheme(); + const styles = createStyles(theme); + + const handleCopy = () => { + ReactNativeHapticFeedback.trigger('impactLight', hapticOptions); + Clipboard.setString(content.trim()); + }; + + return ( + + + {language} + + + + + + ); +}; diff --git a/src/components/CodeBlockHeader/index.ts b/src/components/CodeBlockHeader/index.ts new file mode 100644 index 000000000..297c39eb5 --- /dev/null +++ b/src/components/CodeBlockHeader/index.ts @@ -0,0 +1 @@ +export * from './CodeBlockHeader'; diff --git a/src/components/CodeBlockHeader/styles.ts b/src/components/CodeBlockHeader/styles.ts new file mode 100644 index 000000000..01c4da343 --- /dev/null +++ b/src/components/CodeBlockHeader/styles.ts @@ -0,0 +1,21 @@ +import {StyleSheet} from 'react-native'; + +import {Theme} from '../../utils/types'; + +export const createStyles = (theme: Theme) => + StyleSheet.create({ + codeHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + }, + codeLanguage: { + color: theme.colors.onSurfaceVariant, + fontSize: 12, + }, + iconTouchable: { + padding: 12, // 16 (icon) + 10*2 = 40 for accessibility + justifyContent: 'center', + alignItems: 'center', + }, + }); diff --git a/src/components/MarkdownView/MarkdownView.tsx b/src/components/MarkdownView/MarkdownView.tsx index 9ed45403b..bc3dfd367 100644 --- a/src/components/MarkdownView/MarkdownView.tsx +++ b/src/components/MarkdownView/MarkdownView.tsx @@ -10,6 +10,7 @@ import RenderHtml, { import {useTheme} from '../../hooks'; import {ThinkingBubble} from '../ThinkingBubble'; +import {CodeBlockHeader} from '../CodeBlockHeader'; import {createTagsStyles} from './styles'; @@ -46,6 +47,26 @@ const ThinkingRenderer = ({TDefaultRenderer, ...props}: any) => { ); }; +const CodeRenderer = ({TDefaultRenderer, ...props}: any) => { + const isCodeBlock = props?.tnode?.parent?.tagName === 'pre'; + + // if not code block, use the default renderer + if (!isCodeBlock) { + return ; + } + + const language = + props.tnode?.domNode?.attribs?.class?.replace('language-', '') || 'code'; + const content = props.tnode?.domNode?.children?.[0]?.data || ''; + + return ( + + + + + ); +}; + export const MarkdownView: React.FC = React.memo( ({markdownText, maxMessageWidth, selectable = false}) => { const _maxWidth = maxMessageWidth; @@ -76,6 +97,7 @@ export const MarkdownView: React.FC = React.memo( think: (props: any) => ThinkingRenderer(props), thought: (props: any) => ThinkingRenderer(props), thinking: (props: any) => ThinkingRenderer(props), + code: (props: any) => CodeRenderer(props), }), [], );