Split notes into a title + body, with light self-authored markdown
Both NoteSaveForm (create) and the popup's inline Edit form now have a title input (-> properties.name, shown in the black header, same as every other waypoint popup) and a separate body textarea (-> properties.description) for more detail than a single line. New formatNoteBody renders that body with a small self-rolled markdown subset — **bold**, *italic*, auto-linked URLs (reusing the existing linkify), newlines to <br> — deliberately a new function, not a change to formatDescription: that one renders uploaded GPX/park4night content, which is untrusted, and docs/nps-resources.adoc already has a specific call not to add markdown rendering to that path. A note's body is written by the logged-in viewer themselves, not uploaded, so that concern doesn't apply here — the escape-then-selectively-markup order (linkify first, since it does its own escaping, then wrap already-escaped text in real <strong>/<em> tags) keeps it safe either way. Verified live: title renders in the popup header, **bold**/*italic*/ a URL in the body all render correctly. Full test suite: 115 runs, 0 failures (unaffected — description is just another property, not something the backend routes inspect).
This commit is contained in:
1 parent
2a22906a68
commit
465a644365
4 files changed
+74
-20
No files matched your search
+21
-11
@@ -28,7 +28,8 @@ export class NoteSaveForm {
|
||||
this._el.className = "notes-panel__form";
|
||||
this._el.hidden = true;
|
||||
this._el.innerHTML = `
|
||||
<textarea class="notes-panel__text" rows="2" placeholder="Note…"></textarea>
|
||||
<input type="text" class="notes-panel__title" placeholder="Title">
|
||||
<textarea class="notes-panel__body" rows="3" placeholder="Details (optional) — **bold**, *italic*, links"></textarea>
|
||||
<select class="notes-panel__target"></select>
|
||||
<input type="text" class="notes-panel__new-name" placeholder="Layer name" hidden>
|
||||
<div class="notes-panel__actions">
|
||||
@@ -38,7 +39,8 @@ export class NoteSaveForm {
|
||||
`;
|
||||
container.appendChild(this._el);
|
||||
|
||||
this._text = this._el.querySelector(".notes-panel__text");
|
||||
this._title = this._el.querySelector(".notes-panel__title");
|
||||
this._body = this._el.querySelector(".notes-panel__body");
|
||||
this._target = this._el.querySelector(".notes-panel__target");
|
||||
this._newName = this._el.querySelector(".notes-panel__new-name");
|
||||
this._saveButton = this._el.querySelector(".notes-panel__save");
|
||||
@@ -58,8 +60,9 @@ export class NoteSaveForm {
|
||||
async open(geometry) {
|
||||
this._geometry = geometry;
|
||||
this._el.hidden = false;
|
||||
this._text.value = "";
|
||||
this._text.focus();
|
||||
this._title.value = "";
|
||||
this._body.value = "";
|
||||
this._title.focus();
|
||||
await this._populateTargets();
|
||||
}
|
||||
|
||||
@@ -90,8 +93,9 @@ export class NoteSaveForm {
|
||||
}
|
||||
|
||||
async _save() {
|
||||
const text = this._text.value.trim();
|
||||
if (!text || !this._geometry) return;
|
||||
const title = this._title.value.trim();
|
||||
const details = this._body.value.trim();
|
||||
if (!title || !this._geometry) return;
|
||||
|
||||
const isNewLayer = this._target.value === NEW_LAYER_VALUE;
|
||||
const name = isNewLayer ? this._newName.value.trim() : "";
|
||||
@@ -102,17 +106,23 @@ export class NoteSaveForm {
|
||||
|
||||
const feature = {
|
||||
type: "Feature",
|
||||
properties: { id: generateNoteId(), name: text, category: "Note", time: new Date().toISOString() },
|
||||
properties: {
|
||||
id: generateNoteId(),
|
||||
name: title,
|
||||
category: "Note",
|
||||
time: new Date().toISOString(),
|
||||
...(details ? { description: details } : {}),
|
||||
},
|
||||
geometry: this._geometry,
|
||||
};
|
||||
|
||||
const body = new URLSearchParams({ feature: JSON.stringify(feature) });
|
||||
if (isNewLayer) body.set("name", name);
|
||||
else body.set("path", this._target.value);
|
||||
const requestBody = new URLSearchParams({ feature: JSON.stringify(feature) });
|
||||
if (isNewLayer) requestBody.set("name", name);
|
||||
else requestBody.set("path", this._target.value);
|
||||
|
||||
this._saveButton.disabled = true;
|
||||
try {
|
||||
const response = await fetch("/api/gpx/notes", { method: "POST", body });
|
||||
const response = await fetch("/api/gpx/notes", { method: "POST", body: requestBody });
|
||||
if (!response.ok) throw new Error(`save failed: ${response.status}`);
|
||||
notifyGpxStoreChanged();
|
||||
this.close();
|
||||
|
||||
Reference in new issue
Block a user