diff --git a/packages/happy-app/sources/components/markdown/MermaidRenderer.tsx b/packages/happy-app/sources/components/markdown/MermaidRenderer.tsx index 290d48854..1252df2ae 100644 --- a/packages/happy-app/sources/components/markdown/MermaidRenderer.tsx +++ b/packages/happy-app/sources/components/markdown/MermaidRenderer.tsx @@ -1,9 +1,11 @@ import * as React from 'react'; -import { View, Platform, Text } from 'react-native'; +import { View, Platform, Text, Pressable } from 'react-native'; import { WebView } from 'react-native-webview'; import { StyleSheet, useUnistyles } from 'react-native-unistyles'; import { Typography } from '@/constants/Typography'; import { t } from '@/text'; +import * as Clipboard from 'expo-clipboard'; +import { Modal } from '@/modal'; // Style for Web platform const webStyle: any = { @@ -20,6 +22,26 @@ export const MermaidRenderer = React.memo((props: { const { theme } = useUnistyles(); const [dimensions, setDimensions] = React.useState({ width: 0, height: 200 }); const [svgContent, setSvgContent] = React.useState(null); + const [isHovered, setIsHovered] = React.useState(false); + + const copyMermaidSource = React.useCallback(async () => { + try { + await Clipboard.setStringAsync(props.content); + Modal.alert(t('common.success'), t('markdown.mermaidCopied'), [{ text: t('common.ok'), style: 'cancel' }]); + } catch (error) { + console.error('Failed to copy mermaid source:', error); + Modal.alert(t('common.error'), t('markdown.copyFailed'), [{ text: t('common.ok'), style: 'cancel' }]); + } + }, [props.content]); + + // Reusable copy button component + const renderCopyButton = (visible: boolean) => ( + + + {t('common.copy')} + + + ); const onLayout = React.useCallback((event: any) => { const { width } = event.nativeEvent.layout; @@ -93,12 +115,19 @@ export const MermaidRenderer = React.memo((props: { } return ( - + setIsHovered(true)} + // @ts-ignore - Web only events + onMouseLeave={() => setIsHovered(false)} + > {/* @ts-ignore - Web only */}
+ {renderCopyButton(isHovered)} ); } @@ -164,6 +193,7 @@ export const MermaidRenderer = React.memo((props: { } }} /> + {renderCopyButton(true)} ); @@ -173,11 +203,13 @@ const style = StyleSheet.create((theme) => ({ container: { marginVertical: 8, width: '100%', + position: 'relative', }, innerContainer: { width: '100%', backgroundColor: theme.colors.surfaceHighest, borderRadius: 8, + position: 'relative', }, loadingContainer: { justifyContent: 'center', @@ -215,4 +247,32 @@ const style = StyleSheet.create((theme) => ({ fontSize: 14, lineHeight: 20, }, + copyButtonWrapper: { + position: 'absolute', + top: 8, + right: 8, + opacity: 0, + zIndex: 10, + elevation: 10, + pointerEvents: 'none', + }, + copyButtonWrapperVisible: { + opacity: 1, + pointerEvents: 'auto', + }, + copyButton: { + backgroundColor: theme.colors.surfaceHighest, + paddingHorizontal: 8, + paddingVertical: 4, + borderRadius: 4, + borderWidth: 1, + borderColor: theme.colors.divider, + cursor: 'pointer', + }, + copyButtonText: { + ...Typography.default(), + color: theme.colors.text, + fontSize: 12, + lineHeight: 16, + }, })); diff --git a/packages/happy-app/sources/text/translations/ca.ts b/packages/happy-app/sources/text/translations/ca.ts index 76d928500..903617a4b 100644 --- a/packages/happy-app/sources/text/translations/ca.ts +++ b/packages/happy-app/sources/text/translations/ca.ts @@ -783,6 +783,7 @@ export const ca: TranslationStructure = { codeCopied: 'Codi copiat', copyFailed: 'Error al copiar', mermaidRenderFailed: 'Error al renderitzar el diagrama mermaid', + mermaidCopied: 'Codi font de Mermaid copiat', }, artifacts: { diff --git a/packages/happy-app/sources/text/translations/en.ts b/packages/happy-app/sources/text/translations/en.ts index 6329b24eb..8b53a58fa 100644 --- a/packages/happy-app/sources/text/translations/en.ts +++ b/packages/happy-app/sources/text/translations/en.ts @@ -798,6 +798,7 @@ export const en: TranslationStructure = { codeCopied: 'Code copied', copyFailed: 'Failed to copy', mermaidRenderFailed: 'Failed to render mermaid diagram', + mermaidCopied: 'Mermaid source copied', }, artifacts: { diff --git a/packages/happy-app/sources/text/translations/es.ts b/packages/happy-app/sources/text/translations/es.ts index 41c17664e..a2b4e8b25 100644 --- a/packages/happy-app/sources/text/translations/es.ts +++ b/packages/happy-app/sources/text/translations/es.ts @@ -783,6 +783,7 @@ export const es: TranslationStructure = { codeCopied: 'Código copiado', copyFailed: 'Error al copiar', mermaidRenderFailed: 'Error al renderizar el diagrama mermaid', + mermaidCopied: 'Código fuente de Mermaid copiado', }, artifacts: { diff --git a/packages/happy-app/sources/text/translations/it.ts b/packages/happy-app/sources/text/translations/it.ts index 6dbfb52ac..25432d8ea 100644 --- a/packages/happy-app/sources/text/translations/it.ts +++ b/packages/happy-app/sources/text/translations/it.ts @@ -812,6 +812,7 @@ export const it: TranslationStructure = { codeCopied: 'Codice copiato', copyFailed: 'Copia non riuscita', mermaidRenderFailed: 'Impossibile renderizzare il diagramma mermaid', + mermaidCopied: 'Codice sorgente Mermaid copiato', }, artifacts: { diff --git a/packages/happy-app/sources/text/translations/ja.ts b/packages/happy-app/sources/text/translations/ja.ts index 090480d7a..98c222932 100644 --- a/packages/happy-app/sources/text/translations/ja.ts +++ b/packages/happy-app/sources/text/translations/ja.ts @@ -815,6 +815,7 @@ export const ja: TranslationStructure = { codeCopied: 'コードをコピーしました', copyFailed: 'コピーに失敗しました', mermaidRenderFailed: 'Mermaidダイアグラムのレンダリングに失敗しました', + mermaidCopied: 'Mermaidソースコードをコピーしました', }, artifacts: { diff --git a/packages/happy-app/sources/text/translations/pl.ts b/packages/happy-app/sources/text/translations/pl.ts index 55401af6a..152e50ef5 100644 --- a/packages/happy-app/sources/text/translations/pl.ts +++ b/packages/happy-app/sources/text/translations/pl.ts @@ -793,6 +793,7 @@ export const pl: TranslationStructure = { codeCopied: 'Kod skopiowany', copyFailed: 'Błąd kopiowania', mermaidRenderFailed: 'Nie udało się wyświetlić diagramu mermaid', + mermaidCopied: 'Kod źródłowy Mermaid skopiowany', }, artifacts: { diff --git a/packages/happy-app/sources/text/translations/pt.ts b/packages/happy-app/sources/text/translations/pt.ts index 75d9afed2..e233d99c1 100644 --- a/packages/happy-app/sources/text/translations/pt.ts +++ b/packages/happy-app/sources/text/translations/pt.ts @@ -783,6 +783,7 @@ export const pt: TranslationStructure = { codeCopied: 'Código copiado', copyFailed: 'Falha ao copiar', mermaidRenderFailed: 'Falha ao renderizar diagrama mermaid', + mermaidCopied: 'Código fonte Mermaid copiado', }, artifacts: { diff --git a/packages/happy-app/sources/text/translations/ru.ts b/packages/happy-app/sources/text/translations/ru.ts index 930474bb7..59366feef 100644 --- a/packages/happy-app/sources/text/translations/ru.ts +++ b/packages/happy-app/sources/text/translations/ru.ts @@ -793,6 +793,7 @@ export const ru: TranslationStructure = { codeCopied: 'Код скопирован', copyFailed: 'Ошибка копирования', mermaidRenderFailed: 'Не удалось отобразить диаграмму mermaid', + mermaidCopied: 'Исходный код Mermaid скопирован', }, artifacts: { diff --git a/packages/happy-app/sources/text/translations/zh-Hans.ts b/packages/happy-app/sources/text/translations/zh-Hans.ts index a9db3ead3..e13e55d65 100644 --- a/packages/happy-app/sources/text/translations/zh-Hans.ts +++ b/packages/happy-app/sources/text/translations/zh-Hans.ts @@ -785,6 +785,7 @@ export const zhHans: TranslationStructure = { codeCopied: '代码已复制', copyFailed: '复制失败', mermaidRenderFailed: '渲染 mermaid 图表失败', + mermaidCopied: 'Mermaid 源码已复制', }, artifacts: { diff --git a/packages/happy-app/sources/text/translations/zh-Hant.ts b/packages/happy-app/sources/text/translations/zh-Hant.ts index e09ca6f3c..4a3e23a4d 100644 --- a/packages/happy-app/sources/text/translations/zh-Hant.ts +++ b/packages/happy-app/sources/text/translations/zh-Hant.ts @@ -784,6 +784,7 @@ export const zhHant: TranslationStructure = { codeCopied: '程式碼已複製', copyFailed: '複製失敗', mermaidRenderFailed: '渲染 mermaid 圖表失敗', + mermaidCopied: 'Mermaid 原始碼已複製', }, artifacts: {