mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2026-01-17 18:31:19 +00:00
Lazy load TextPreview (#281)
* feat: add lazy loading for TextPreview * styling and truncate
This commit is contained in:
parent
28dd16dfeb
commit
29fadc6ef0
@ -0,0 +1,33 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { TextPreview } from './TextPreview'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
export const LazyTextPreview = ({ item }: { item: Item }) => {
|
||||
const [visible, setVisible] = useState(false)
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
if (entries[0].isIntersecting) {
|
||||
setVisible(true)
|
||||
observer.disconnect()
|
||||
}
|
||||
})
|
||||
if (ref.current) observer.observe(ref.current)
|
||||
return () => observer.disconnect()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
{visible ? (
|
||||
<TextPreview item={item} />
|
||||
) : (
|
||||
<div className='tw:flex tw:justify-center '>
|
||||
<div className='tw:loading tw:spinner tw:h-8 tw:opacity-50' />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,75 +1,23 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
import truncate from 'markdown-truncate'
|
||||
import Markdown from 'react-markdown'
|
||||
import rehypeRaw from 'rehype-raw'
|
||||
import remarkBreaks from 'remark-breaks'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
|
||||
import { useAddFilterTag } from '#components/Map/hooks/useFilter'
|
||||
import { useGetItemTags, useTags } from '#components/Map/hooks/useTags'
|
||||
import { decodeTag } from '#utils/FormatTags'
|
||||
import { RichTextEditor } from '#components/Input/RichTextEditor/RichTextEditor'
|
||||
import { fixUrls, mailRegex } from '#utils/ReplaceURLs'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
export const TextPreview = ({ item }: { item: Item }) => {
|
||||
const getItemTags = useGetItemTags()
|
||||
|
||||
if (!item.text) return null
|
||||
// Text auf ~100 Zeichen stutzen (inkl. Ellipse „…“)
|
||||
const previewRaw = truncate(item.text, { limit: 100, ellipsis: true }) as string
|
||||
else {
|
||||
let replacedText = truncate(item.text, { limit: 100, ellipsis: true }) as string
|
||||
|
||||
const withExtraHashes = previewRaw.replace(
|
||||
/^(#{1,6})\s/gm,
|
||||
(_match: string, hashes: string): string => `${hashes}## `,
|
||||
)
|
||||
replacedText = fixUrls(item.text)
|
||||
|
||||
return (
|
||||
<div className='markdown'>
|
||||
<Markdown remarkPlugins={[remarkBreaks, remarkGfm]} rehypePlugins={[rehypeRaw]}>
|
||||
{removeMentionSpans(removeHashtags(withExtraHashes))}
|
||||
</Markdown>
|
||||
{getItemTags(item).map((tag) => (
|
||||
<HashTag tag={tag} key={tag} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
if (replacedText) {
|
||||
replacedText = replacedText.replace(mailRegex, (url) => {
|
||||
return `[${url}](mailto:${url})`
|
||||
})
|
||||
}
|
||||
|
||||
return <RichTextEditor defaultValue={replacedText} readOnly={true} />
|
||||
}
|
||||
}
|
||||
|
||||
export const HashTag = ({ tag }: { tag: Tag }) => {
|
||||
const tags = useTags()
|
||||
const t = tags.find((t) => t.name.toLocaleLowerCase() === tag.name.toLocaleLowerCase())
|
||||
const addFilterTag = useAddFilterTag()
|
||||
if (!t) return null
|
||||
return (
|
||||
<a
|
||||
className='hashtag'
|
||||
style={{ color: t.color }}
|
||||
key={`${t.name}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
addFilterTag(t)
|
||||
}}
|
||||
>
|
||||
{`#${decodeTag(tag.name)} `}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
function removeMentionSpans(html) {
|
||||
return html.replace(
|
||||
/<span\b(?=[^>]*\bdata-type="mention")(?=[^>]*\bclass="mention")[^>]*>[\s\S]*?<\/span>/gi,
|
||||
'',
|
||||
)
|
||||
}
|
||||
|
||||
function removeHashtags(str) {
|
||||
return str
|
||||
// 1. Hashtags entfernen, außer sie stehen am Zeilenanfang als Markdown-Heading
|
||||
.replace(
|
||||
/(^|\s)(?!#{1,6}\s)(#[A-Za-z0-9_]+)\b/g,
|
||||
'$1'
|
||||
)
|
||||
|
||||
// 3. Anfangs/Ende trimmen
|
||||
.trim()
|
||||
}
|
||||
@ -6,3 +6,4 @@ export { TextView } from './TextView'
|
||||
export { StartEndView } from './StartEndView'
|
||||
export { PopupButton } from './PopupButton'
|
||||
export { TextPreview } from './TextPreview'
|
||||
export { LazyTextPreview } from './LazyTextPreview'
|
||||
|
||||
@ -3,7 +3,10 @@ import { useNavigate } from 'react-router-dom'
|
||||
|
||||
import { useSetSelectPosition } from '#components/Map/hooks/useSelectPosition'
|
||||
import useWindowDimensions from '#components/Map/hooks/useWindowDimension'
|
||||
import { StartEndView, TextPreview } from '#components/Map/Subcomponents/ItemPopupComponents'
|
||||
import {
|
||||
StartEndView,
|
||||
LazyTextPreview,
|
||||
} from '#components/Map/Subcomponents/ItemPopupComponents'
|
||||
import { HeaderView } from '#components/Map/Subcomponents/ItemPopupComponents/HeaderView'
|
||||
|
||||
import { DateUserInfo } from './DateUserInfo'
|
||||
@ -51,7 +54,7 @@ export const ItemCard = ({
|
||||
></HeaderView>
|
||||
<div className='tw:overflow-y-auto tw:overflow-x-hidden tw:max-h-64 fade'>
|
||||
{i.layer?.itemType.show_start_end && <StartEndView item={i}></StartEndView>}
|
||||
{i.layer?.itemType.show_text && <TextPreview item={i} />}
|
||||
{i.layer?.itemType.show_text && <LazyTextPreview item={i} />}
|
||||
</div>
|
||||
<DateUserInfo item={i}></DateUserInfo>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user