From 89c5258665f300ac5212f4afc237bb190dc262bd Mon Sep 17 00:00:00 2001 From: Anton Tranelis <31516529+antontranelis@users.noreply.github.com> Date: Fri, 4 Jul 2025 13:56:46 +0200 Subject: [PATCH] feat: improve TextPreview truncation --- lib/README.md | 1 + .../ItemPopupComponents/TextPreview.spec.tsx | 18 ++++++++++ .../ItemPopupComponents/TextPreview.tsx | 35 ++++++++++++++----- 3 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 lib/src/Components/Map/Subcomponents/ItemPopupComponents/TextPreview.spec.tsx diff --git a/lib/README.md b/lib/README.md index 0d88014b..2940cf49 100644 --- a/lib/README.md +++ b/lib/README.md @@ -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 diff --git a/lib/src/Components/Map/Subcomponents/ItemPopupComponents/TextPreview.spec.tsx b/lib/src/Components/Map/Subcomponents/ItemPopupComponents/TextPreview.spec.tsx new file mode 100644 index 00000000..e36eaeff --- /dev/null +++ b/lib/src/Components/Map/Subcomponents/ItemPopupComponents/TextPreview.spec.tsx @@ -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('', () => { + it('does not output partial mention tags when truncated', () => { + const mention = '#hash' + const item: Item = { + id: '1', + name: 'Test', + text: `${'a'.repeat(95)} ${mention}`, + } + const { container } = render() + expect(container.innerHTML).not.toContain(' { 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 (
@@ -28,7 +47,7 @@ export const TextPreview = ({ item }: { item: Item }) => { rehypePlugins={[rehypeRaw]} components={{ span: Span }} > - {withExtraHashes} + {previewRaw}
)