mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-21 22:46:32 +00:00
fix(places): render markdown in the mobile place description
The mobile shell printed place descriptions and notes as plain text while the desktop rows and inspector run the same field through react-markdown, so a description written with the formatting bar read as `# headings` and `**asterisks**` on the phone. Put the renderer in one shared component instead of copying it next to each mobile twin, with a clamped variant for list rows that keeps them one line high the way the desktop sidebar does. A clamped caption sits inside the tappable row, so its links keep their text and lose the anchor. Fixes #2337
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import MarkdownText from './MarkdownText'
|
||||
|
||||
describe('MarkdownText', () => {
|
||||
it('FE-MDTEXT-001: a description written in markdown reads as formatted text, not as syntax (#2337)', () => {
|
||||
render(<MarkdownText>{'# Checkpoint Charlie\n\nThe **most famous** crossing.'}</MarkdownText>)
|
||||
expect(screen.getByRole('heading', { name: 'Checkpoint Charlie' })).toBeInTheDocument()
|
||||
expect(screen.getByText('most famous').tagName).toBe('STRONG')
|
||||
expect(screen.queryByText(/#\s*Checkpoint/)).toBeNull()
|
||||
})
|
||||
|
||||
it('FE-MDTEXT-002: a gfm table is a table', () => {
|
||||
render(<MarkdownText>{'| Day | Stop |\n| --- | --- |\n| 1 | Gate |'}</MarkdownText>)
|
||||
expect(screen.getByRole('table')).toBeInTheDocument()
|
||||
expect(screen.getByRole('columnheader', { name: 'Day' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('FE-MDTEXT-003: a single newline stays a line break, the way the editors show it', () => {
|
||||
const { container } = render(<MarkdownText>{'Opening hours\n9–17'}</MarkdownText>)
|
||||
expect(container.querySelector('br')).not.toBeNull()
|
||||
})
|
||||
|
||||
it('FE-MDTEXT-004: a link in a full view opens in its own tab', () => {
|
||||
render(<MarkdownText>{'[tickets](https://example.com/tickets)'}</MarkdownText>)
|
||||
const link = screen.getByRole('link', { name: 'tickets' })
|
||||
expect(link).toHaveAttribute('target', '_blank')
|
||||
expect(link.getAttribute('rel')).toContain('noopener')
|
||||
})
|
||||
|
||||
it('FE-MDTEXT-005: a clamped row keeps the link text but drops the anchor, so the tap opens the row', () => {
|
||||
render(<MarkdownText clamp>{'[tickets](https://example.com/tickets)'}</MarkdownText>)
|
||||
expect(screen.queryByRole('link')).toBeNull()
|
||||
expect(screen.getByText('tickets')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('FE-MDTEXT-006: a clamped row is held to one line so a long description cannot swallow the list', () => {
|
||||
const { container } = render(<MarkdownText clamp className="text-m-muted">{'# Berlin Wall Memorial\n\nA long second paragraph.'}</MarkdownText>)
|
||||
const wrapper = container.firstElementChild as HTMLElement
|
||||
expect(wrapper.style.maxHeight).toBe('1.2em')
|
||||
expect(wrapper.className).toContain('truncate')
|
||||
expect(wrapper.className).toContain('text-m-muted')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,42 @@
|
||||
import Markdown from 'react-markdown'
|
||||
import type { Components } from 'react-markdown'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import remarkBreaks from 'remark-breaks'
|
||||
import { markdownLinkComponents } from './markdownLink'
|
||||
|
||||
/**
|
||||
* A clamped caption lives inside a tappable row, so an anchor there would eat
|
||||
* the tap that opens the row. The link text stays, the anchor goes.
|
||||
*/
|
||||
const clampedComponents: Components = {
|
||||
a: ({ children }) => <>{children}</>,
|
||||
}
|
||||
|
||||
/**
|
||||
* A description or note, rendered (#2337). The field is Markdown wherever it is
|
||||
* written — the editors ship a formatting bar — so printing it raw anywhere
|
||||
* shows `# headings` and `**asterisks**` to the reader.
|
||||
*
|
||||
* `clamp` is the list-row variant: one line high, matching what the desktop
|
||||
* sidebar rows do with the same field. The height cap is inline because the row
|
||||
* itself sets a font size, and a Tailwind `leading-*` would race with it.
|
||||
*/
|
||||
export default function MarkdownText({ children, className = '', clamp = false }: {
|
||||
children: string
|
||||
className?: string
|
||||
clamp?: boolean
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={`collab-note-md ${clamp ? 'truncate ' : ''}${className}`}
|
||||
style={clamp ? { maxHeight: '1.2em', lineHeight: 1.2 } : undefined}
|
||||
>
|
||||
<Markdown
|
||||
remarkPlugins={[remarkGfm, remarkBreaks]}
|
||||
components={clamp ? clampedComponents : markdownLinkComponents}
|
||||
>
|
||||
{children}
|
||||
</Markdown>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user