mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-11 00:37:05 -04:00
131 lines
3.4 KiB
TypeScript
131 lines
3.4 KiB
TypeScript
import { useTranslation } from 'react-i18next';
|
|
import { View, Text, StyleSheet, Linking } from 'react-native';
|
|
|
|
import type { Item } from '@/utils/dist/core/models/vault';
|
|
import { getFieldValue, FieldKey } from '@/utils/dist/core/models/vault';
|
|
|
|
import { useColors } from '@/hooks/useColorScheme';
|
|
|
|
import { ThemedText } from '@/components/themed/ThemedText';
|
|
import { ThemedView } from '@/components/themed/ThemedView';
|
|
import { RobustPressable } from '@/components/ui/RobustPressable';
|
|
|
|
type NotesSectionProps = {
|
|
item: Item;
|
|
};
|
|
|
|
/**
|
|
* Split text into parts, separating URLs from regular text to make them clickable.
|
|
*/
|
|
const splitTextAndUrls = (text: string): { type: 'text' | 'url', content: string, url?: string }[] => {
|
|
const urlPattern = /(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/g;
|
|
|
|
const parts: { type: 'text' | 'url', content: string, url?: string }[] = [];
|
|
let lastIndex = 0;
|
|
let match;
|
|
|
|
while ((match = urlPattern.exec(text)) !== null) {
|
|
// Add text before the URL if it's not empty
|
|
if (match.index > lastIndex) {
|
|
const textBefore = text.slice(lastIndex, match.index);
|
|
if (textBefore.trim()) {
|
|
parts.push({
|
|
type: 'text',
|
|
content: textBefore
|
|
});
|
|
}
|
|
}
|
|
|
|
// Add the URL
|
|
const url = match[0];
|
|
const href = url.startsWith('http') ? url : `http://${url}`;
|
|
parts.push({
|
|
type: 'url',
|
|
content: url,
|
|
url: href
|
|
});
|
|
|
|
lastIndex = match.index + url.length;
|
|
}
|
|
|
|
// Add remaining text if it's not empty
|
|
if (lastIndex < text.length) {
|
|
const remainingText = text.slice(lastIndex);
|
|
if (remainingText.trim()) {
|
|
parts.push({
|
|
type: 'text',
|
|
content: remainingText
|
|
});
|
|
}
|
|
}
|
|
|
|
return parts;
|
|
};
|
|
|
|
/**
|
|
* Notes section component.
|
|
*/
|
|
export const NotesSection: React.FC<NotesSectionProps> = ({ item }) : React.ReactNode => {
|
|
const { t } = useTranslation();
|
|
const colors = useColors();
|
|
|
|
const notes = getFieldValue(item, FieldKey.NotesContent);
|
|
|
|
if (!notes) {
|
|
return null;
|
|
}
|
|
|
|
const parts = splitTextAndUrls(notes);
|
|
|
|
/**
|
|
* Handle the link press.
|
|
*/
|
|
const handleLinkPress = (url: string) : void => {
|
|
Linking.openURL(url);
|
|
};
|
|
|
|
const styles = StyleSheet.create({
|
|
link: {
|
|
color: colors.primary,
|
|
fontSize: 14,
|
|
textDecorationLine: 'underline',
|
|
},
|
|
notes: {
|
|
color: colors.text,
|
|
fontSize: 14,
|
|
},
|
|
notesContainer: {
|
|
backgroundColor: colors.accentBackground,
|
|
borderRadius: 8,
|
|
padding: 12,
|
|
},
|
|
section: {
|
|
gap: 8,
|
|
paddingTop: 16,
|
|
},
|
|
});
|
|
|
|
return (
|
|
<ThemedView style={styles.section}>
|
|
<ThemedText type="subtitle">{t('items.notes')}</ThemedText>
|
|
<View style={styles.notesContainer}>
|
|
{parts.map((part, index) => {
|
|
if (part.type === 'url') {
|
|
return (
|
|
<RobustPressable key={index} onPress={() => handleLinkPress(part.url!)}>
|
|
<Text style={styles.link} selectable={true}>
|
|
{part.content}
|
|
</Text>
|
|
</RobustPressable>
|
|
);
|
|
}
|
|
return (
|
|
<Text key={index} style={styles.notes} selectable={true}>
|
|
{part.content}
|
|
</Text>
|
|
);
|
|
})}
|
|
</View>
|
|
</ThemedView>
|
|
);
|
|
}; |