diff --git a/TODO.adoc b/TODO.adoc index 5824c1d..61e6ae1 100644 --- a/TODO.adoc +++ b/TODO.adoc @@ -45,6 +45,7 @@ independence from connectivity. ** [x] Edit/delete individual notes from their popup (logged-in only) — targets a feature by a client-generated `properties.id` (`GpxStore#update_feature`/`#delete_feature`), not array position ** [x] Measure's vertices are draggable (mousedown + move, `event.preventDefault()` to stop the map's own drag-pan) and deletable (click to select — shown in yellow — then Delete/Backspace) ** [x] Fixed: a note saved before `properties.id` existed (the earliest test notes) had no Edit/Delete-able target — the popup no longer offers them without an id, and `rake gpx:backfill_note_ids` assigns one to any pre-existing Note feature missing it +** [x] Title + body split (name in the black header, body in a separate field) with light self-authored markdown (`**bold**`, `*italic*`, auto-linked URLs) in the body — a *new*, separate `formatNoteBody` (not `formatDescription`, which stays plain/untrusted-safe for uploaded GPX/park4night content per docs/nps-resources.adoc's earlier markdown-rendering call) ** [x] Gated behind login: NotesControl isn't added to the map at all when logged out, Measure's "Save as note" button is hidden (Measure itself stays usable either way) ** [ ] The Unigrid Matrix category filter only re-slices the *points* source (`_applyCategoryFilter`) — toggling the "Note" category off hides point notes but not polygon/line ones sharing that category * [ ] Offline satellite tile caching (e.g. ESRI) for a selected route area — key for judging surface quality and obstacles when wild camping diff --git a/public/css/style.css b/public/css/style.css index 0f385bd..f5a4154 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -869,7 +869,8 @@ html, body { display: none; } -.notes-panel__text { +.notes-panel__title, +.notes-panel__body { width: 100%; box-sizing: border-box; font: inherit; @@ -877,6 +878,13 @@ html, body { padding: 4px 6px; border: 1px solid #ddd; border-radius: 4px; +} + +.notes-panel__title { + font-weight: 600; +} + +.notes-panel__body { resize: vertical; } @@ -1053,7 +1061,8 @@ html, body { color: #fff; } -.gpx-popup__edit-text { +.gpx-popup__edit-title, +.gpx-popup__edit-body { width: 100%; box-sizing: border-box; font: inherit; @@ -1061,6 +1070,14 @@ html, body { padding: 4px 6px; border: 1px solid #ddd; border-radius: 4px; + margin-bottom: 6px; +} + +.gpx-popup__edit-title { + font-weight: 600; +} + +.gpx-popup__edit-body { resize: vertical; } diff --git a/public/js/gpx.js b/public/js/gpx.js index 2b19e8d..9c288b1 100644 --- a/public/js/gpx.js +++ b/public/js/gpx.js @@ -742,23 +742,29 @@ export class GpxPanelControl { const content = popup.getElement().querySelector(".maplibregl-popup-content"); content.innerHTML = `
${escapeHtml(category)}
`); if (photos?.length > 0) parts.push(formatPhotos(photos)); - if (description) parts.push(formatDescription(description)); + // A Note's description is self-authored by the (logged-in) viewer, not + // uploaded/untrusted like every other description here — formatDescription + // is deliberately left alone for that untrusted case (see + // docs/nps-resources.adoc's note on why markdown rendering wasn't adopted + // for it); formatNoteBody is the separate, narrower path that's actually + // safe to add light markdown to. + if (description) parts.push(category === "Note" ? formatNoteBody(description) : formatDescription(description)); if (time) { const parsed = new Date(time); if (!Number.isNaN(parsed.getTime())) { @@ -1233,3 +1245,17 @@ function urlToLink(url) { const href = escapeHtml(core); return `${href}${escapeHtml(trailing)}`; } + +// A Note's body only — never an uploaded file's description (see the +// comment where this is called). linkify() runs first and does its own +// escaping, so what it returns is already safe HTML; **bold**/*italic* are +// then matched against that (already-escaped) text and wrapped in real +// tags — order matters: running it before linkify would have that same +// escaping re-encode the tags this just added. +function formatNoteBody(text) { + let html = linkify(text) + .replace(/\*\*(.+?)\*\*/g, "$1") + .replace(/\*(.+?)\*/g, "$1") + .replace(/\n/g, "${html}
`; +} diff --git a/public/js/notes.js b/public/js/notes.js index 93eb141..a4ddc1a 100644 --- a/public/js/notes.js +++ b/public/js/notes.js @@ -28,7 +28,8 @@ export class NoteSaveForm { this._el.className = "notes-panel__form"; this._el.hidden = true; this._el.innerHTML = ` - + +