141 lines
3.6 KiB
Python
141 lines
3.6 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
|
|
import frontmatter
|
|
from flask import (
|
|
Blueprint,
|
|
abort,
|
|
current_app,
|
|
jsonify,
|
|
redirect,
|
|
render_template,
|
|
request,
|
|
url_for,
|
|
)
|
|
|
|
from app.services.notes_fs import (
|
|
create_note,
|
|
list_notes,
|
|
load_note_by_id,
|
|
update_note_body,
|
|
)
|
|
from app.services.renderer import render_markdown
|
|
from app.services.vault import Vault
|
|
|
|
bp = Blueprint("notes", __name__, url_prefix="/notes")
|
|
|
|
|
|
@bp.get("/")
|
|
def notes_index():
|
|
# Notes list page
|
|
notes = list_notes()
|
|
return render_template("notes/list.html", notes=notes)
|
|
|
|
|
|
@bp.get("/new")
|
|
def notes_new():
|
|
return render_template("notes/new.html")
|
|
|
|
|
|
@bp.post("/")
|
|
def notes_create():
|
|
title = (request.form.get("title") or "").strip()
|
|
body = (request.form.get("body") or "").strip()
|
|
tags_raw = (request.form.get("tags") or "").strip()
|
|
tags = [t.strip() for t in tags_raw.split(",") if t.strip()] if tags_raw else []
|
|
|
|
if not title:
|
|
return (
|
|
render_template(
|
|
"notes/new.html",
|
|
error="Title is required",
|
|
title=title,
|
|
body=body,
|
|
tags_raw=tags_raw,
|
|
),
|
|
400,
|
|
)
|
|
|
|
note = create_note(title=title, body=body, tags=tags)
|
|
return redirect(url_for("notes.notes_view", note_id=note.id))
|
|
|
|
|
|
@bp.get("/<note_id>")
|
|
def notes_view(note_id: str):
|
|
note = load_note_by_id(note_id)
|
|
if not note:
|
|
abort(404)
|
|
|
|
all_notes = list_notes()
|
|
rendered = render_markdown(note.body or "", all_notes=all_notes)
|
|
|
|
return render_template(
|
|
"notes/view.html",
|
|
note=note,
|
|
rendered_html=rendered["html"],
|
|
unresolved_wikilinks=rendered["unresolved_wikilinks"],
|
|
outbound_note_ids=rendered["outbound_note_ids"],
|
|
)
|
|
|
|
|
|
@bp.post("/<note_id>/body")
|
|
def notes_update_body(note_id: str):
|
|
new_body = request.form.get("body") or ""
|
|
note = update_note_body(note_id, new_body)
|
|
if not note:
|
|
abort(404)
|
|
return redirect(url_for("notes.notes_view", note_id=note.id))
|
|
|
|
|
|
# Debug endpoints: available when Flask debug is on, or KB_ENABLE_DEBUG_ROUTES=True
|
|
def _debug_enabled() -> bool:
|
|
return bool(current_app.debug or current_app.config.get("KB_ENABLE_DEBUG_ROUTES", False))
|
|
|
|
|
|
@bp.get("/_debug/ids")
|
|
def notes_debug_ids():
|
|
if not _debug_enabled():
|
|
abort(404)
|
|
notes = list_notes()
|
|
return jsonify(notes=[{"id": n.id, "title": n.title, "path": n.rel_path} for n in notes])
|
|
|
|
|
|
@bp.get("/_debug/scan")
|
|
def notes_debug_scan():
|
|
if not _debug_enabled():
|
|
abort(404)
|
|
|
|
vault_path = current_app.config.get("KB_VAULT_PATH")
|
|
v = Vault(vault_path)
|
|
v.ensure_structure()
|
|
notes_dir = v.paths.notes
|
|
|
|
exists = os.path.isdir(notes_dir)
|
|
try:
|
|
ls_notes = sorted(os.listdir(notes_dir)) if exists else []
|
|
except Exception as e:
|
|
ls_notes = [f"<error listing dir: {e}>"]
|
|
|
|
discovered = list(v.iter_markdown_files())
|
|
|
|
probe = []
|
|
for p in discovered:
|
|
try:
|
|
with open(p, "r", encoding="utf-8", errors="replace") as f:
|
|
post = frontmatter.loads(f.read())
|
|
meta = post.metadata or {}
|
|
probe.append({"path": p, "ok": True, "id": meta.get("id"), "title": meta.get("title")})
|
|
except Exception as e:
|
|
probe.append({"path": p, "ok": False, "error": str(e)})
|
|
|
|
return jsonify(
|
|
{
|
|
"vault": vault_path,
|
|
"notes_dir": notes_dir,
|
|
"notes_dir_exists": exists,
|
|
"notes_dir_list": ls_notes,
|
|
"discovered_md_files": discovered,
|
|
"probe": probe,
|
|
}
|
|
) |