")
list_items = []
for line in markdown_text.splitlines():
if line.strip().startswith("```"):
flush_paragraph(); flush_list()
if not in_code:
in_code = True
body.append("
")
else:
in_code = False
body.append("
")
continue
if in_code:
body.append(html_lib.escape(line) + "\n")
continue
match = re.match(r"^(#{1,6})\s+(.+?)\s*$", line)
if match:
flush_paragraph(); flush_list()
level = len(match.group(1))
heading_text = match.group(2).strip()
slug = slugify_heading(heading_text, used_slugs)
toc.append({"level": level, "text": heading_text, "slug": slug})
body.append(f'{inline_markdown(heading_text)}')
continue
item = re.match(r"^\s*[-*+]\s+(.+)$", line)
if item:
flush_paragraph()
list_items.append(item.group(1).strip())
continue
if not line.strip():
flush_paragraph(); flush_list()
continue
paragraph.append(line.strip())
flush_paragraph(); flush_list()
if in_code:
body.append("")
toc_html = "".join(
f'{html_lib.escape(item["text"])}'
for item in toc
) or '
"""
class KanbanHandler(http.server.BaseHTTPRequestHandler):
def log_message(self, format, *args): return # Silent log
def do_GET(self):
try:
parsed = urllib.parse.urlparse(self.path)
if parsed.path.startswith("/files/"):
file_path = safe_file_path(parsed.path[len("/files/"):])
if file_path is None:
self.send_response(404)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.end_headers()
self.wfile.write(b"File not found or not allowed")
return
if file_path.suffix.lower() == ".md":
markdown_text = file_path.read_text(encoding="utf-8")
rendered = render_markdown_with_toc(markdown_text, file_path.name)
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.end_headers()
self.wfile.write(rendered.encode("utf-8"))
return
content = file_path.read_bytes()
self.send_response(200)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.end_headers()
self.wfile.write(content)
elif parsed.path == "/api/tasks":
self.send_response(200)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Access-Control-Allow-Origin", "*")
self.end_headers()
conn = sqlite3.connect(KANBAN_DB)
rows = conn.execute("SELECT id, title, body, status, assignee, priority, created_at, completed_at FROM tasks").fetchall()
conn.close()
data = [dict(zip(["id","title","body","status","assignee","priority","created_at","completed_at"], r)) for r in rows]
self.wfile.write(json.dumps(data, ensure_ascii=False).encode())
elif parsed.path == "/":
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.end_headers()
self.wfile.write(b'Redirecting to /kanban-os/')
return
else:
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.end_headers()
html = """
NV-Office · Kanban OS
📋 NV-Office Kanban OS
"""
self.wfile.write(html.encode())
except ConnectionResetError: pass
except Exception as e: print(f"Error: {e}", file=sys.stderr)
if __name__ == "__main__":
while True:
try:
socketserver.TCPServer.allow_reuse_address = True
with socketserver.TCPServer(("0.0.0.0", PORT), KanbanHandler) as httpd:
print(f"Durable Kanban server online on port {PORT}")
httpd.serve_forever()
except Exception as e:
print(f"Server crashed: {e}. Restarting...")
time.sleep(1)