mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 07:53:09 +00:00
refactor(pdf): the markdown rows and styles in a module of their own; the shared stop's markdown test beside its component
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
"src/components/PDF/TripPDF.tsx": 1082,
|
||||
"src/components/Planner/DayDetailPanel.test.tsx": 2333,
|
||||
"src/components/Planner/DayPlanSidebar.test.tsx": 5349,
|
||||
"src/components/Planner/DayPlanSidebar.tsx": 2953,
|
||||
"src/components/Planner/DayPlanSidebar.tsx": 2936,
|
||||
"src/components/Planner/PlaceFormModal.test.tsx": 2836,
|
||||
"src/components/Planner/PlaceFormModal.tsx": 1157,
|
||||
"src/components/Planner/PlaceInspector.tsx": 1290,
|
||||
|
||||
@@ -24,7 +24,7 @@ import { fetchExchangeRates } from '../../hooks/useExchangeRates'
|
||||
import { getFlightLegs, getTrainLegs, usesStationRoute } from '../../utils/flightLegs'
|
||||
import { isServiceStopType } from '../Roadtrip/roadtripModel'
|
||||
import { onlyMyPlan } from './pdfScope'
|
||||
import { createMarkdownHtml } from '../shared/markdownHtml'
|
||||
import { createPdfMarkdown, PDF_MARKDOWN_CSS } from './pdfMarkdown'
|
||||
|
||||
/**
|
||||
* Every day starts a new page by default. On a trip of short days that prints
|
||||
@@ -232,10 +232,8 @@ export async function downloadTripPDF({ trip, days, places, assignments: allStor
|
||||
let hasTransportNotes = false
|
||||
const loc = _locale || undefined
|
||||
const tr = _t || (k => k)
|
||||
// A place's description and notes are markdown in the app, so the export renders
|
||||
// them the same way instead of printing the raw syntax (#2692).
|
||||
const markdown = createMarkdownHtml()
|
||||
const mdBlock = (text: string) => `<div class="info-row"><span class="info-spacer"></span><div class="info-text muted md">${markdown.html(text)}</div></div>`
|
||||
const markdown = createPdfMarkdown()
|
||||
const mdBlock = markdown.row
|
||||
// The store read is the fallback, not the source: a caller that forgets the
|
||||
// prop still prints the reader's own format instead of silently reverting.
|
||||
const is12h = (_timeFormat || useSettingsStore.getState().settings.time_format || '24h') === '12h'
|
||||
@@ -841,17 +839,7 @@ export async function downloadTripPDF({ trip, days, places, assignments: allStor
|
||||
.info-spacer { width: 13px; flex-shrink: 0; }
|
||||
.info-text { font-size: 9px; color: #64748b; line-height: 1.5; }
|
||||
.info-text.muted { color: #94a3b8; }
|
||||
.info-text.md { min-width: 0; overflow-wrap: anywhere; }
|
||||
.md > :first-child { margin-top: 0; }
|
||||
.md > :last-child { margin-bottom: 0; }
|
||||
.md p, .md ul, .md ol, .md table, .md blockquote, .md pre { margin: 0 0 4px; }
|
||||
.md ul, .md ol { padding-inline-start: 14px; }
|
||||
.md h1, .md h2, .md h3, .md h4, .md h5, .md h6 { font-size: 1.1em; font-weight: 700; margin: 4px 0 2px; }
|
||||
.md table { border-collapse: collapse; }
|
||||
.md th, .md td { border: 1px solid #e2e8f0; padding: 2px 5px; text-align: start; }
|
||||
.md code { font-family: ui-monospace, monospace; font-size: 8.5px; }
|
||||
.md a { color: inherit; text-decoration: underline; }
|
||||
.md blockquote { border-inline-start: 2px solid #e2e8f0; padding-inline-start: 6px; }
|
||||
${PDF_MARKDOWN_CSS}
|
||||
|
||||
.chips { display: flex; flex-wrap: wrap; gap: 4px; padding-left: 21px; margin-top: 4px; }
|
||||
.chip { display: inline-flex; align-items: center; gap: 3px; font-size: 8px; font-weight: 600; background: #f1f5f9; color: #374151; border-radius: 99px; padding: 2px 7px; white-space: nowrap; }
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { createMarkdownHtml } from '../shared/markdownHtml';
|
||||
|
||||
/**
|
||||
* A place's description and notes are markdown in the app, so the export
|
||||
* renders them the same way instead of printing the raw syntax (#2692): one
|
||||
* info row per text, the HTML from the app's own sanitized pipeline.
|
||||
*/
|
||||
export function createPdfMarkdown(): { row: (text: string) => string; dispose: () => void } {
|
||||
const markdown = createMarkdownHtml();
|
||||
return {
|
||||
row: (text) =>
|
||||
`<div class="info-row"><span class="info-spacer"></span><div class="info-text muted md">${markdown.html(text)}</div></div>`,
|
||||
dispose: markdown.dispose,
|
||||
};
|
||||
}
|
||||
|
||||
/** The markdown's own spacing, lists, tables and links at the export's small size. */
|
||||
export const PDF_MARKDOWN_CSS = `
|
||||
.info-text.md { min-width: 0; overflow-wrap: anywhere; }
|
||||
.md > :first-child { margin-top: 0; }
|
||||
.md > :last-child { margin-bottom: 0; }
|
||||
.md p, .md ul, .md ol, .md table, .md blockquote, .md pre { margin: 0 0 4px; }
|
||||
.md ul, .md ol { padding-inline-start: 14px; }
|
||||
.md h1, .md h2, .md h3, .md h4, .md h5, .md h6 { font-size: 1.1em; font-weight: 700; margin: 4px 0 2px; }
|
||||
.md table { border-collapse: collapse; }
|
||||
.md th, .md td { border: 1px solid #e2e8f0; padding: 2px 5px; text-align: start; }
|
||||
.md code { font-family: ui-monospace, monospace; font-size: 8.5px; }
|
||||
.md a { color: inherit; text-decoration: underline; }
|
||||
.md blockquote { border-inline-start: 2px solid #e2e8f0; padding-inline-start: 6px; }
|
||||
`;
|
||||
@@ -1657,31 +1657,6 @@ describe('SharedTripPage', () => {
|
||||
await waitFor(() => expect(screen.getByText('Somewhere 1')).toBeInTheDocument());
|
||||
expect(screen.queryByText('1 h')).toBeNull();
|
||||
});
|
||||
|
||||
// #2692: the description and notes are markdown in the planner, so the shared page renders them too.
|
||||
it('renders markdown in the description and notes, and keeps raw HTML as text', async () => {
|
||||
const marked = {
|
||||
21: [{
|
||||
id: 304, day_id: 21, order_index: 0, notes: '**Bring** cash',
|
||||
place: {
|
||||
id: 504, name: 'Market',
|
||||
description: ['## Opening', '', '| Day | Hours |', '| --- | --- |', '| Sat | 8 to 14 |', '', '<img src=x onerror=alert(1)>'].join('\n'),
|
||||
address: 'Market Square 1', lat: null, lng: null, duration_minutes: 0, notes: ['- fruit', '- bread'].join('\n'),
|
||||
website: null, phone: null, category_id: null, price: null, place_time: null, end_time: null,
|
||||
image_url: null, transport_mode: 'walking', category: null, tags: [],
|
||||
},
|
||||
}],
|
||||
};
|
||||
await open('markdown-token', payload({ days, assignments: marked }));
|
||||
await waitFor(() => expect(screen.getByText('Market Square 1')).toBeInTheDocument());
|
||||
|
||||
expect(screen.getByRole('heading', { name: 'Opening' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('cell', { name: '8 to 14' })).toBeInTheDocument();
|
||||
expect(screen.getByText('Bring').tagName).toBe('STRONG');
|
||||
expect(screen.getByText('fruit').tagName).toBe('LI');
|
||||
expect(document.querySelector('img[src="x"]')).toBeNull();
|
||||
expect(screen.queryByText('## Opening')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('FE-PAGE-SHARED-039: a booking shows its note and its link, and nothing that is not http(s) (#2320)', () => {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { render, screen } from '../../../tests/helpers/render';
|
||||
import { SharedPlaceDetails } from './SharedPlaceDetails';
|
||||
|
||||
/** FE-PAGE-SHARED-PLACE-001: a stop's description and notes on a shared trip, read as the planner shows them (#2692). */
|
||||
|
||||
describe('SharedPlaceDetails', () => {
|
||||
it('FE-PAGE-SHARED-PLACE-001: renders markdown in the description and notes, and keeps raw HTML as text', () => {
|
||||
render(
|
||||
<SharedPlaceDetails
|
||||
place={{
|
||||
name: 'Market',
|
||||
address: 'Market Square 1',
|
||||
description: [
|
||||
'## Opening',
|
||||
'',
|
||||
'| Day | Hours |',
|
||||
'| --- | --- |',
|
||||
'| Sat | 8 to 14 |',
|
||||
'',
|
||||
'<img src=x onerror=alert(1)>',
|
||||
].join('\n'),
|
||||
notes: ['- fruit', '- bread'].join('\n'),
|
||||
}}
|
||||
assignmentNotes="**Bring** cash"
|
||||
/>
|
||||
);
|
||||
expect(screen.getByRole('heading', { name: 'Opening' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('cell', { name: '8 to 14' })).toBeInTheDocument();
|
||||
expect(screen.getByText('Bring').tagName).toBe('STRONG');
|
||||
expect(screen.getByText('fruit').tagName).toBe('LI');
|
||||
expect(document.querySelector('img[src="x"]')).toBeNull();
|
||||
expect(screen.queryByText('## Opening')).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user