91 lines
2.9 KiB
TypeScript
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;
|