From c5ddf68ea13e0fd743441e0421398c41f4b3f8c4 Mon Sep 17 00:00:00 2001 From: Colin Powell Date: Mon, 16 Feb 2026 17:15:53 -0500 Subject: [PATCH] Add pinned today, webhook support and style changes --- main.py | 405 +++++++++++++++++++++++++++++++++++++++++-- static/style.css | 188 ++++++++++++++++++++ templates/index.html | 54 ++++++ 3 files changed, 634 insertions(+), 13 deletions(-) diff --git a/main.py b/main.py index 4885cd0..bb80c64 100644 --- a/main.py +++ b/main.py @@ -3,6 +3,7 @@ from __future__ import annotations import argparse import html +import json import mimetypes import os import re @@ -10,10 +11,12 @@ import socketserver import threading import webbrowser from dataclasses import dataclass +from datetime import datetime, timezone from http.server import BaseHTTPRequestHandler, HTTPServer from pathlib import Path from typing import Iterable from urllib.parse import parse_qs, urlencode, urlparse +from urllib.request import Request, urlopen @dataclass @@ -23,6 +26,7 @@ class OrgFile: title: str file_id: str | None content: str + mtime: float = 0.0 ROOT_DIR = Path(__file__).resolve().parent @@ -34,6 +38,16 @@ ORG_LINK_RE = re.compile(r"\[\[([^\]]+)\](?:\[([^\]]*)\])?\]") URL_RE = re.compile(r"https?://[^\s<>'\"()\[\]]+") CREATED_LINE_RE = re.compile(r"^\s*(?:#\+)?CREATED:\s*(.+?)\s*$", flags=re.IGNORECASE) ORG_TIMESTAMP_RE = re.compile(r"[\[<](\d{4})-(\d{2})-(\d{2})(?:\s+\w{3})?(?:\s+(\d{2}):(\d{2}))?[\]>]") +PROPERTY_LINE_RE = re.compile(r"^\s*:([A-Za-z_][A-Za-z0-9_-]*):\s+(.+?)\s*$") +INLINE_TIMESTAMP_RE = re.compile( + r"(?P[<\[])" + r"(?P\d{4})-(?P\d{2})-(?P\d{2})" + r"(?:\s+(?P\w{2,3}))?" + r"(?:\s+(?P\d{2}):(?P\d{2})(?:-(?P\d{2}):(?P\d{2}))?)?" + r"[>\]]" +) +TODO_KEYWORDS = {"TODO", "STRT", "DONE", "WAIT"} +TODO_KEYWORD_RE = re.compile(r"^(" + "|".join(TODO_KEYWORDS) + r")\b\s*(.*)", re.DOTALL) DEFAULT_CONFIG_PATH = ROOT_DIR / "config.yaml" @@ -59,10 +73,11 @@ def scan_org_files(base_dir: Path) -> list[OrgFile]: title=extract_org_title(content, file_path.stem), file_id=extract_org_id(content), content=content, + mtime=file_path.stat().st_mtime, ) ) - org_files.sort(key=lambda f: f.relative_path.lower()) + org_files.sort(key=lambda f: f.mtime, reverse=True) return org_files @@ -220,6 +235,75 @@ def truncate_label(text: str, max_chars: int = 32) -> str: return text[: max_chars - 3] + "..." +_MONTH_ABBR = [ + "", "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", +] + + +def _format_timestamp(m: re.Match[str]) -> str: + """Turn an org timestamp match into a styled