Format the structured lines in park4night waypoint popups
A park4night <desc> packs a free-text narrative and several lines of structured data (address, rating, services, activities, the listing URL) into one field, separated only by newlines. Rendered as a single paragraph it read as a wall of run-together text — no visual hierarchy between the actual description and what's really key-value metadata. formatDescription now recognizes "Label: value" lines (Rating/Services/ Activities) and bare-URL lines, and gives each its own row instead of burying them in the narrative paragraph: a bordered meta block with bold labels, and an isolated link line. Anything that doesn't match — the real narrative text, or a plain GPX file's <desc> with no such structure — stays exactly as it rendered before, so this only adds structure, never removes or reorders text.
This commit is contained in:
1 parent
6f7d332993
commit
3759f7a5c9
3 files changed
+88
-2
No files matched your search
+15
-1
@@ -315,7 +315,7 @@ Like the radar overlay, GPX layers are removed by `map.setStyle()`
|
|||||||
(switching Online/Offline) and get re-added on `style.load` for
|
(switching Online/Offline) and get re-added on `style.load` for
|
||||||
whichever files were checked.
|
whichever files were checked.
|
||||||
|
|
||||||
Waypoint popups:: Clicking a waypoint circle opens a MapLibre popup
|
Waypoint popups:: Clicking a waypoint badge opens a MapLibre popup
|
||||||
with its name, category (if any), description and time. Click/hover handling is
|
with its name, category (if any), description and time. Click/hover handling is
|
||||||
registered once per file (via `map.on(type, layerId, handler)`, a
|
registered once per file (via `map.on(type, layerId, handler)`, a
|
||||||
delegated listener that survives `setStyle()`) rather than on every
|
delegated listener that survives `setStyle()`) rather than on every
|
||||||
@@ -328,6 +328,20 @@ splitting the text on a URL pattern and escaping every piece
|
|||||||
assembling any HTML; the URL pattern excludes quote characters, so a
|
assembling any HTML; the URL pattern excludes quote characters, so a
|
||||||
matched URL can never break out of the `href="..."` attribute. Every
|
matched URL can never break out of the `href="..."` attribute. Every
|
||||||
other value is HTML-escaped before being handed to `Popup#setHTML`.
|
other value is HTML-escaped before being handed to `Popup#setHTML`.
|
||||||
|
+
|
||||||
|
A park4night `<desc>` packs a free-text narrative and several lines of
|
||||||
|
structured data (address, rating, services, activities, the listing
|
||||||
|
URL) into that one field, separated only by newlines — rendered as a
|
||||||
|
single paragraph, it read as a wall of run-together text.
|
||||||
|
`formatDescription` pulls out whichever lines match a recognizable
|
||||||
|
`Label: value` shape (`Rating`/`Services`/`Activities`) or are a bare
|
||||||
|
URL, and renders each kind with its own visual treatment (a bordered
|
||||||
|
"meta" block with bold labels for the former, an isolated link line for
|
||||||
|
the latter); everything else — the actual narrative, or a plain GPX
|
||||||
|
file's `<desc>` with none of this structure at all — renders exactly as
|
||||||
|
before, one paragraph with line breaks. So this only ever adds
|
||||||
|
structure on top of recognized lines; it never loses or hides text from
|
||||||
|
files that don't have any.
|
||||||
|
|
||||||
Track properties:: Every track feature (both formats) also carries
|
Track properties:: Every track feature (both formats) also carries
|
||||||
`distance_meters`, `elevation_gain_meters`, `duration_secs`,
|
`distance_meters`, `elevation_gain_meters`, `duration_secs`,
|
||||||
|
|||||||
@@ -450,6 +450,32 @@ html, body {
|
|||||||
letter-spacing: 0.02em;
|
letter-spacing: 0.02em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gpx-popup__description {
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gpx-popup__meta {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding-top: 6px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gpx-popup__meta-row + .gpx-popup__meta-row {
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gpx-popup__meta-label {
|
||||||
|
font-weight: 600;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gpx-popup__link {
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.gnss-control__status {
|
.gnss-control__status {
|
||||||
color: #888;
|
color: #888;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|||||||
+47
-1
@@ -757,7 +757,7 @@ function popupHtml({ name, description, time, category }) {
|
|||||||
const parts = [];
|
const parts = [];
|
||||||
if (name) parts.push(`<strong>${escapeHtml(name)}</strong>`);
|
if (name) parts.push(`<strong>${escapeHtml(name)}</strong>`);
|
||||||
if (category) parts.push(`<p class="gpx-popup__category">${escapeHtml(category)}</p>`);
|
if (category) parts.push(`<p class="gpx-popup__category">${escapeHtml(category)}</p>`);
|
||||||
if (description) parts.push(`<p>${linkify(description).replace(/\n/g, "<br>")}</p>`);
|
if (description) parts.push(formatDescription(description));
|
||||||
if (time) {
|
if (time) {
|
||||||
const parsed = new Date(time);
|
const parsed = new Date(time);
|
||||||
if (!Number.isNaN(parsed.getTime())) {
|
if (!Number.isNaN(parsed.getTime())) {
|
||||||
@@ -795,6 +795,52 @@ function escapeHtml(text) {
|
|||||||
return div.innerHTML;
|
return div.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// park4night exports pack a free-text description and several lines of
|
||||||
|
// structured data (address, rating, services, activities, the listing URL)
|
||||||
|
// into one <desc> blob, separated only by newlines — rendered as one plain
|
||||||
|
// paragraph, it read as a wall of run-together text. Lines matching a
|
||||||
|
// recognizable "Label: value" or bare-URL shape get pulled out and rendered
|
||||||
|
// as their own visually distinct rows instead; anything else (the actual
|
||||||
|
// narrative, or a plain GPX file's <desc> with no such structure at all)
|
||||||
|
// stays exactly as before, one paragraph with line breaks — so this only
|
||||||
|
// ever adds structure, never loses any text.
|
||||||
|
const DESCRIPTION_META_LINE = /^(Rating|Services|Activities):\s*(.+)$/i;
|
||||||
|
const DESCRIPTION_LINK_LINE = /^https?:\/\/\S+$/;
|
||||||
|
|
||||||
|
function formatDescription(description) {
|
||||||
|
const body = [];
|
||||||
|
const meta = [];
|
||||||
|
const links = [];
|
||||||
|
|
||||||
|
for (const line of description.split("\n")) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
const metaMatch = trimmed.match(DESCRIPTION_META_LINE);
|
||||||
|
if (DESCRIPTION_LINK_LINE.test(trimmed)) {
|
||||||
|
links.push(trimmed);
|
||||||
|
} else if (metaMatch) {
|
||||||
|
meta.push(metaMatch);
|
||||||
|
} else {
|
||||||
|
body.push(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = [];
|
||||||
|
const bodyText = body.join("\n").trim();
|
||||||
|
if (bodyText) parts.push(`<p class="gpx-popup__description">${linkify(bodyText).replace(/\n/g, "<br>")}</p>`);
|
||||||
|
if (meta.length > 0) {
|
||||||
|
const rows = meta
|
||||||
|
.map(
|
||||||
|
([, label, value]) =>
|
||||||
|
`<div class="gpx-popup__meta-row"><span class="gpx-popup__meta-label">${escapeHtml(label)}</span> ${escapeHtml(value)}</div>`,
|
||||||
|
)
|
||||||
|
.join("");
|
||||||
|
parts.push(`<div class="gpx-popup__meta">${rows}</div>`);
|
||||||
|
}
|
||||||
|
links.forEach((url) => parts.push(`<p class="gpx-popup__link">${urlToLink(url)}</p>`));
|
||||||
|
|
||||||
|
return parts.join("");
|
||||||
|
}
|
||||||
|
|
||||||
// Turns bare URLs in untrusted text into safe <a> links: the string is
|
// Turns bare URLs in untrusted text into safe <a> links: the string is
|
||||||
// split on the URL pattern and every piece is escaped — including the URL
|
// split on the URL pattern and every piece is escaped — including the URL
|
||||||
// itself, both as href and as link text — before any HTML is assembled, so
|
// itself, both as href and as link text — before any HTML is assembled, so
|
||||||
|
|||||||
Reference in new issue
Block a user