diff --git a/main.py b/main.py
index 3d10310a..17494121 100644
--- a/main.py
+++ b/main.py
@@ -1369,6 +1369,7 @@ async def add_issue(issue: dict):
"author": issue.get("author", "Anonymous").strip(),
"created": datetime.utcnow().isoformat(),
"resolved": False,
+ "comments": [],
}
if not new_issue["title"]:
raise HTTPException(status_code=400, detail="Title is required")
@@ -1379,13 +1380,22 @@ async def add_issue(issue: dict):
@app.patch("/issues/{issue_id}")
async def update_issue(issue_id: str, update: dict):
- """Update an issue (e.g. toggle resolved state)."""
+ """Update an issue (toggle resolved, edit title/description/category)."""
issues = _load_issues()
found = None
for i in issues:
if i.get("id") == issue_id:
if "resolved" in update:
i["resolved"] = bool(update["resolved"])
+ if "title" in update:
+ title = update["title"].strip()
+ if not title:
+ raise HTTPException(status_code=400, detail="Title cannot be empty")
+ i["title"] = title
+ if "description" in update:
+ i["description"] = update["description"].strip()
+ if "category" in update:
+ i["category"] = update["category"]
found = i
break
if not found:
@@ -1394,6 +1404,33 @@ async def update_issue(issue_id: str, update: dict):
return found
+@app.post("/issues/{issue_id}/comments")
+async def add_comment(issue_id: str, comment: dict):
+ """Add a comment to an issue."""
+ issues = _load_issues()
+ found = None
+ for i in issues:
+ if i.get("id") == issue_id:
+ found = i
+ break
+ if not found:
+ raise HTTPException(status_code=404, detail="Issue not found")
+ text = comment.get("text", "").strip()
+ if not text:
+ raise HTTPException(status_code=400, detail="Comment text is required")
+ new_comment = {
+ "id": uuid.uuid4().hex[:8],
+ "author": comment.get("author", "Anonymous").strip(),
+ "text": text,
+ "created": datetime.utcnow().isoformat(),
+ }
+ if "comments" not in found:
+ found["comments"] = []
+ found["comments"].append(new_comment)
+ _save_issues(issues)
+ return new_comment
+
+
@app.delete("/issues/{issue_id}")
async def delete_issue(issue_id: str):
"""Permanently delete an issue."""
@@ -2768,7 +2805,9 @@ async def ws_receive_snapshots(
landblock = data.get("landblock")
if landblock:
dungeon_map_cache[landblock] = data
- logger.info(f"Cached dungeon map for {landblock} ({len(data.get('z_levels', []))} z-levels)")
+ logger.info(
+ f"Cached dungeon map for {landblock} ({len(data.get('z_levels', []))} z-levels)"
+ )
await _broadcast_to_browser_clients(data)
continue
# Unknown message types are ignored
@@ -3328,7 +3367,9 @@ class NoCacheStaticFiles(StaticFiles):
# Check content-type header since root path "" resolves to index.html
# via html=True and we need to catch it too.
ct = response.headers.get("content-type", "").lower()
- if any(t in ct for t in ("text/html", "javascript", "text/css", "application/json")):
+ if any(
+ t in ct for t in ("text/html", "javascript", "text/css", "application/json")
+ ):
response.headers["Cache-Control"] = "no-cache, must-revalidate"
return response
diff --git a/static/script.js b/static/script.js
index 4b6f8882..81641cd4 100644
--- a/static/script.js
+++ b/static/script.js
@@ -4135,6 +4135,12 @@ const ISSUE_CATEGORIES = {
other: { label: 'Other', color: '#888888' },
};
+function escapeHtml(str) {
+ const div = document.createElement('div');
+ div.textContent = str;
+ return div.innerHTML;
+}
+
function showIssuesWindow() {
const { win, content, isNew } = createWindow(
'issuesWindow', 'Issues Board', 'issues-window'
@@ -4145,8 +4151,8 @@ function showIssuesWindow() {
return;
}
- win.style.width = '500px';
- win.style.height = '450px';
+ win.style.width = '540px';
+ win.style.height = '520px';
// Issues list container
const listDiv = document.createElement('div');
@@ -4159,6 +4165,7 @@ function showIssuesWindow() {
form.className = 'issues-form';
form.innerHTML = `
+