ARIA-AGENT/android/src/components/MessageText.tsx

91 lines
2.9 KiB
TypeScript

/**
* MessageText — rendert Chat-Text mit Auto-Linkifizierung:
* - http(s)://... → tippbar, oeffnet im Browser
* - mailto: oder plain E-Mail → tippbar, oeffnet Mail-App
* - Telefonnummern → tippbar, oeffnet Android-Dialer
*
* Text ist durchgaengig markierbar/kopierbar (selectable).
*/
import React from 'react';
import { Text, Linking, TextStyle, StyleProp } from 'react-native';
// Regex kombiniert URL | Email | Telefonnummer.
// Gruppenreihenfolge ist wichtig fuer die Erkennung unten.
//
// URL: http://... oder https://... bis zum ersten Whitespace / Anfuehrungszeichen.
// Email: simpler Standard-Match (kein RFC-kompatibel aber gut genug).
// Telefon: internationale Form (+49..., 0049..., 0176...), darf Leerzeichen
// / Bindestriche / Schraegstriche / Klammern enthalten, mindestens 7
// Ziffern insgesamt. Vermeidet banale Zahlen (Uhrzeiten, Datum).
const LINK_REGEX = new RegExp(
'(https?:\\/\\/[^\\s<>"]+)' + // 1: URL
'|([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,})' + // 2: Email
'|((?:\\+|00)\\d[\\d\\s()\\-\\/]{6,}\\d|0\\d{2,4}[\\s\\/\\-]?[\\d\\s\\-\\/]{5,}\\d)', // 3: Telefon
'g',
);
const LINK_STYLE = { color: '#0096FF', textDecorationLine: 'underline' } as TextStyle;
interface Segment {
text: string;
kind: 'text' | 'url' | 'email' | 'phone';
}
function tokenize(raw: string): Segment[] {
const out: Segment[] = [];
let lastEnd = 0;
LINK_REGEX.lastIndex = 0;
let m: RegExpExecArray | null;
while ((m = LINK_REGEX.exec(raw)) !== null) {
if (m.index > lastEnd) {
out.push({ text: raw.slice(lastEnd, m.index), kind: 'text' });
}
if (m[1]) out.push({ text: m[1], kind: 'url' });
else if (m[2]) out.push({ text: m[2], kind: 'email' });
else if (m[3]) out.push({ text: m[3], kind: 'phone' });
lastEnd = LINK_REGEX.lastIndex;
}
if (lastEnd < raw.length) out.push({ text: raw.slice(lastEnd), kind: 'text' });
return out;
}
function onPress(seg: Segment) {
try {
if (seg.kind === 'url') {
Linking.openURL(seg.text);
} else if (seg.kind === 'email') {
Linking.openURL(`mailto:${seg.text}`);
} else if (seg.kind === 'phone') {
// Android-Dialer erwartet tel:-Schema ohne Leerzeichen/Bindestriche
const clean = seg.text.replace(/[\s\-\/()]/g, '');
Linking.openURL(`tel:${clean}`);
}
} catch {}
}
interface Props {
text: string;
style?: StyleProp<TextStyle>;
}
const MessageText: React.FC<Props> = ({ text, style }) => {
const segments = React.useMemo(() => tokenize(text), [text]);
return (
<Text style={style} selectable>
{segments.map((seg, i) => {
if (seg.kind === 'text') {
return <Text key={i}>{seg.text}</Text>;
}
return (
<Text key={i} style={LINK_STYLE} onPress={() => onPress(seg)}>
{seg.text}
</Text>
);
})}
</Text>
);
};
export default MessageText;