Milestone 2.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import uuid
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional, Tuple
|
||||
@@ -15,7 +15,6 @@ from app.utils.slugs import slugify
|
||||
from app.utils.time import now_iso_utc, is_iso_utc
|
||||
|
||||
|
||||
REQUIRED_FIELDS = ("id", "title", "created", "updated")
|
||||
OPTIONAL_LIST_FIELDS = ("aliases", "tags")
|
||||
|
||||
|
||||
@@ -42,7 +41,6 @@ def _normalize_metadata(meta: dict) -> dict:
|
||||
if val is None:
|
||||
val = []
|
||||
if isinstance(val, str):
|
||||
# Allow comma-separated string as input
|
||||
val = [x.strip() for x in val.split(",") if x.strip()]
|
||||
if not isinstance(val, list):
|
||||
val = [str(val)]
|
||||
@@ -112,15 +110,17 @@ def note_path_for_slug(vault: Vault, slug: str) -> Tuple[str, str]:
|
||||
return rel, abs_path
|
||||
|
||||
|
||||
def load_note_from_file(vault: Vault, abs_path: str) -> Note:
|
||||
with open(abs_path, "rb") as f:
|
||||
post = frontmatter.load(f)
|
||||
def _load_frontmatter_text(abs_path: str):
|
||||
# Always read as text for python-frontmatter
|
||||
with open(abs_path, "r", encoding="utf-8", errors="replace") as f:
|
||||
return frontmatter.loads(f.read())
|
||||
|
||||
|
||||
def load_note_from_file(vault: Vault, abs_path: str) -> Note:
|
||||
post = _load_frontmatter_text(abs_path)
|
||||
meta = _normalize_metadata(post.metadata)
|
||||
title = meta["title"]
|
||||
slug = slugify(title)
|
||||
rel, _ = note_path_for_slug(vault, slug)
|
||||
# Use actual rel path of the file (could differ from current slug if renamed later)
|
||||
rel = vault.relpath(abs_path)
|
||||
|
||||
note = Note(
|
||||
@@ -161,8 +161,6 @@ def create_note(
|
||||
slug = slugify(meta["title"])
|
||||
rel, abs_path = note_path_for_slug(vault, slug)
|
||||
abs_path = ensure_unique_path(abs_path)
|
||||
|
||||
# If collision caused a suffix, recompute rel path accordingly
|
||||
rel = vault.relpath(abs_path)
|
||||
|
||||
note = Note(
|
||||
@@ -193,29 +191,46 @@ def save_note(vault: Vault, note: Note) -> None:
|
||||
def list_notes() -> list[Note]:
|
||||
vault = get_vault()
|
||||
notes: list[Note] = []
|
||||
for path in vault.iter_markdown_files():
|
||||
for p in vault.iter_markdown_files():
|
||||
try:
|
||||
notes.append(load_note_from_file(vault, path))
|
||||
abs_path = p if os.path.isabs(p) else vault.abspath(p)
|
||||
notes.append(load_note_from_file(vault, abs_path))
|
||||
except Exception:
|
||||
# Ignore malformed files for now (could log)
|
||||
continue
|
||||
# Sort by updated desc
|
||||
notes.sort(key=lambda n: n.updated, reverse=True)
|
||||
return notes
|
||||
|
||||
|
||||
_hex_re = re.compile(r"[0-9a-fA-F]")
|
||||
def _uuid_key(value: str) -> Optional[str]:
|
||||
if value is None:
|
||||
return None
|
||||
s = "".join(_hex_re.findall(str(value)))
|
||||
if len(s) == 32:
|
||||
return s.lower()
|
||||
try:
|
||||
return uuid.UUID(str(value)).hex
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def load_note_by_id(note_id: str) -> Optional[Note]:
|
||||
vault = get_vault()
|
||||
for path in vault.iter_markdown_files():
|
||||
target_key = _uuid_key(note_id)
|
||||
if not target_key:
|
||||
return None
|
||||
|
||||
for p in vault.iter_markdown_files():
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
post = frontmatter.load(f)
|
||||
meta = _normalize_metadata(post.metadata)
|
||||
if str(meta.get("id")) == str(note_id):
|
||||
# Load fully to construct Note with body and rel path
|
||||
return load_note_from_file(vault, path)
|
||||
abs_path = p if os.path.isabs(p) else vault.abspath(p)
|
||||
post = _load_frontmatter_text(abs_path)
|
||||
raw_id = (post.metadata or {}).get("id")
|
||||
cand_key = _uuid_key(raw_id)
|
||||
if cand_key and cand_key == target_key:
|
||||
return load_note_from_file(vault, abs_path)
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@@ -224,7 +239,6 @@ def update_note_body(note_id: str, new_body: str) -> Optional[Note]:
|
||||
if not note:
|
||||
return None
|
||||
note.body = new_body
|
||||
# Update timestamp
|
||||
note.updated = now_iso_utc()
|
||||
vault = get_vault()
|
||||
save_note(vault, note)
|
||||
|
Reference in New Issue
Block a user