import { CometChat } from "@cometchat/chat-sdk-react-native"; import React, { JSX } from "react"; import { ColorValue, Linking, Text, TextStyle } from "react-native"; import { emailPattern, phoneNumPattern, urlPattern } from "../../constants/UIKitConstants"; import { CometChatTextFormatter } from "../CometChatTextFormatter"; export class CometChatUrlsFormatter extends CometChatTextFormatter { protected style: { linkTextColor?: ColorValue; linkTextFont?: TextStyle; } = { linkTextColor: "blue", linkTextFont:{ fontSize: 17, fontWeight: "400" }, }; constructor(loggedInUser?: CometChat.User) { super(); this.loggedInUser = loggedInUser; } private Link = ({ text, url, style }: any) => { const handlePress = async () => { try { let finalUrl = url; if (!url.match(/^(https?|mailto|tel):/i)) { finalUrl = `http://${url}`; } const canOpen = await Linking.canOpenURL(finalUrl); if (canOpen) { Linking.openURL(finalUrl); } else { // Try opening anyway as fallback Linking.openURL(finalUrl).catch((err) => { console.log("Can not open link", finalUrl, err); }); } } catch (err) { console.log("Error opening URL:", err); } }; return ( {text} ); }; setStyle = (style: { linkTextFont?: TextStyle; linkTextColor?: ColorValue }) => { this.style = style; }; private getPatternGroup = (str: string): { phone?: string; email?: string; url?: string } => { let result: any = {}; if (str.match(phoneNumPattern)) result["phone"] = str; if (str.match(emailPattern)) result["email"] = str; if (str.match(urlPattern)) result["url"] = str; return result; }; /** * Formats the input text if provided, otherwise edits the text at the cursor position. * * @param {string|null} inputText - The input text to be formatted. * @returns {string|void} - The formatted input text, or void if inputText is not provided. */ getFormattedText(inputText: string | null | JSX.Element) { if (!inputText) { return null; } try { let formattedText = this.getFormatTextForLinks({ str: inputText, style: this.style }); return formattedText; } catch { // Gracefully handle regex stack overflow on Android Hermes for very long messages. // Return the input as-is rather than crashing the app. return inputText; } } getFormatTextForLinks = ({ str, style }: any): any => { if (typeof str === "string") { let res = str.matchAll( (phoneNumPattern + "|" + emailPattern + "|" + urlPattern) as unknown as RegExp ); for (let resPart of res) { let { email, phone } = this.getPatternGroup(resPart[0]); let pre: string, post: string; pre = str.substring(0, resPart?.index); post = str.substring(resPart.index! + resPart[0].length); let urlLink = ""; if (email) urlLink = "mailto:"; if (phone) urlLink = "tel:"; return ( {pre} {this.Link({ text: resPart[0].trim(), url: urlLink.trim() + resPart[0].trim(), style: { ...this.style }, })} {this.getFormatTextForLinks({ str: post, style: style, })} ); } return {str}; } else if (React.isValidElement(str)) { // str is a React element if ((str as React.ReactElement).props.children) { // If the React element have children, we map over these children // and call addMentionsSpan recursively for each child. return React.cloneElement(str as React.ReactElement, { children: React.Children.map((str as React.ReactElement).props.children, (child) => { return this.getFormatTextForLinks({ str: child, style: style }); }), }); } else { // If the React element does not have children, return it as is return str; } } else { throw new Error(`Unsupported str type: ${typeof str}`); } }; /** * Retrieves the message object. * * @returns {CometChat.BaseMessage} - The current message object. */ getMessage() { return this.messageObject; } /** * Sets the message object. * * @param {CometChat.BaseMessage} messageObject - The message object to be set. */ setMessage(messageObject: CometChat.BaseMessage) { this.messageObject = messageObject; } }