mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2025-12-13 07:46:10 +00:00
feat: improve TextPreview truncation
This commit is contained in:
parent
46705b2162
commit
89c5258665
@ -23,6 +23,7 @@ It is the base of [Utopia Map](https://github.com/utopia-os/utopia-map) and [Uto
|
||||
* User authentification API-Interface
|
||||
* Customizable Profiles for users and other items
|
||||
* App shell with navigation bar and sidebar
|
||||
* HTML-aware text previews that preserve mentions when content is shortened
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
import { render } from '@testing-library/react'
|
||||
import { describe, it, expect } from 'vitest'
|
||||
|
||||
import { TextPreview } from './TextPreview'
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
describe('<TextPreview />', () => {
|
||||
it('does not output partial mention tags when truncated', () => {
|
||||
const mention = '<span data-type="mention">#hash</span>'
|
||||
const item: Item = {
|
||||
id: '1',
|
||||
name: 'Test',
|
||||
text: `${'a'.repeat(95)} ${mention}`,
|
||||
}
|
||||
const { container } = render(<TextPreview item={item} />)
|
||||
expect(container.innerHTML).not.toContain('<span data-type="mention"')
|
||||
})
|
||||
})
|
||||
@ -1,9 +1,13 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
import truncate from 'markdown-truncate'
|
||||
import htmlTruncate from 'html-truncate'
|
||||
import Markdown from 'react-markdown'
|
||||
import rehypeRaw from 'rehype-raw'
|
||||
import remarkBreaks from 'remark-breaks'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import remarkParse from 'remark-parse'
|
||||
import remarkRehype from 'remark-rehype'
|
||||
import rehypeStringify from 'rehype-stringify'
|
||||
import { unified } from 'unified'
|
||||
|
||||
import { useAddFilterTag } from '#components/Map/hooks/useFilter'
|
||||
import { useTags } from '#components/Map/hooks/useTags'
|
||||
@ -13,13 +17,28 @@ import type { Item } from '#types/Item'
|
||||
|
||||
export const TextPreview = ({ item }: { item: Item }) => {
|
||||
if (!item.text) return null
|
||||
// Text auf ~100 Zeichen stutzen (inkl. Ellipse „…“)
|
||||
const previewRaw = truncate(item.text, { limit: 100, ellipsis: true }) as string
|
||||
// Convert Markdown to HTML and truncate with awareness of markup
|
||||
const html = unified()
|
||||
.use(remarkParse)
|
||||
.use(remarkGfm)
|
||||
.use(remarkBreaks)
|
||||
.use(remarkRehype, { allowDangerousHtml: true })
|
||||
.use(rehypeRaw)
|
||||
.use(rehypeStringify, { allowDangerousHtml: true })
|
||||
.processSync(item.text)
|
||||
.toString()
|
||||
|
||||
const withExtraHashes = previewRaw.replace(
|
||||
/^(#{1,6})\s/gm,
|
||||
(_match: string, hashes: string): string => `${hashes}## `,
|
||||
)
|
||||
// decrease heading levels similar to previous Markdown manipulation
|
||||
const withSmallHeadings = html.replace(/<(\/?)h([1-6])/g, (_m, slash, level) => {
|
||||
const newLevel = Math.min(6, Number(level) + 2)
|
||||
return `<${slash}h${newLevel}`
|
||||
})
|
||||
|
||||
// Text auf ~100 Zeichen stutzen (inkl. Ellipse „…“)
|
||||
const previewRaw = htmlTruncate(withSmallHeadings, 100, {
|
||||
ellipsis: '…',
|
||||
reserveLastWord: true,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='markdown'>
|
||||
@ -28,7 +47,7 @@ export const TextPreview = ({ item }: { item: Item }) => {
|
||||
rehypePlugins={[rehypeRaw]}
|
||||
components={{ span: Span }}
|
||||
>
|
||||
{withExtraHashes}
|
||||
{previewRaw}
|
||||
</Markdown>
|
||||
</div>
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user