diff --git a/cry/static/event.js b/cry/static/event.js index 74d01e8..eb38637 100644 --- a/cry/static/event.js +++ b/cry/static/event.js @@ -21,6 +21,8 @@ events.addEventListener("redirect", (e) => { window.location = e.data; }); +// NOTE: This was designed for a refresh redesign but I haven't the heart to do +// it yet. events.addEventListener("progress", (e) => { // Grab the progress element being referred to. const parameters = JSON.parse(e.data); diff --git a/cry/static/index.js b/cry/static/index.js new file mode 100644 index 0000000..fae136b --- /dev/null +++ b/cry/static/index.js @@ -0,0 +1,60 @@ +console.log("Hello world!"); + +function time_ago(time) { + // Much like the python in feed.py + const now = Date.now(); + const seconds = Math.round((now - time) / 1000); + + // If you *must* have live output (for debugging or something) just + // uncomment here. + // return `${seconds}s`; + + if (seconds < 90) { + return `${seconds}s`; + } + const minutes = Math.round(seconds / 60); + if (minutes < 90) { + return `${minutes}m`; + } + const hours = Math.round(minutes / 60); + if (hours < 24) { + return `${hours}h`; + } + const days = Math.round(hours / 24); + if (days <= 7) { + return `${days}d`; + } + const weeks = Math.round(days / 7); + if (weeks < 52) { + return `${weeks}w`; + } + + const years = Math.round(weeks / 52); + return `{years}y` +} + +function timeAgoTick(selector, format) { + const tags = document.querySelectorAll(selector); + for (const tag of tags) { + const then = Date.parse(tag.getAttribute("datetime")); + const when = time_ago(then); + const formatted = format(when); + + // NOTE: I'm being a real dork about this because I am super nervous + // about leaking memory/making the page heaver than it needs to be, + // with a callback every second and all. + const text = tag.firstChild; + if (text.data != formatted) { + text.data = formatted; + } + } +} + +function registerTimeCallback() { + setInterval(() => { + timeAgoTick(".time-ago-bare", (a) => a); + timeAgoTick(".time-ago-paren", (a) => `(${a})`); + }, 1000); +} + +addEventListener("DOMContentLoaded", () => registerTimeCallback()); diff --git a/cry/views.py b/cry/views.py index 0ae16b6..9ec9e1c 100644 --- a/cry/views.py +++ b/cry/views.py @@ -41,7 +41,10 @@ def _feed_summary(f: feed.Feed) -> tags.li: {"class": "feed-summary-timestamp"}, tags.i( f"Posted ", - time_(entry.time_ago(), datetime=entry.posted_time_iso()), + time_( + {"class": "time-ago-bare", "datetime": entry.posted_time_iso()}, + entry.time_ago(), + ), " ago", ), ), @@ -116,8 +119,11 @@ def feed_view(feeds: list[feed.Feed]) -> tags.html: ), " ", time_( + { + "class": "time-ago-paren", + "datetime": entry.posted_time_iso(), + }, f"({entry.time_ago()})", - datetime=entry.posted_time_iso(), ), ) for entry in f.entries @@ -131,6 +137,7 @@ def feed_view(feeds: list[feed.Feed]) -> tags.html: ), ), ), + tags.script({"src": "/index.js"}), ) assert isinstance(document, tags.html) return document @@ -188,6 +195,7 @@ def status_view() -> tags.html: return document +# NOTE: Not in use yet. def refresh_view(feeds: list[feed.Feed]) -> tags.html: document = tags.html( _standard_header("Refreshing Feeds..."), diff --git a/cry/web.py b/cry/web.py index 038bec8..c177e13 100644 --- a/cry/web.py +++ b/cry/web.py @@ -1,10 +1,8 @@ import asyncio import contextlib import dataclasses -import dominate.tags as tags import functools import http.server -import io import pathlib import time import traceback @@ -17,6 +15,7 @@ from . import database from . import feed from . import views + class DeadlineCondition: """A condition variable that allows you to wait with a timeout.""" @@ -296,6 +295,7 @@ def refresh_feeds(sink: EventChannel): REFRESH_TASK: BackgroundTask | None = None + @background_task def subscribe(sink: EventChannel, url: str): """Subscribe to a feed.""" @@ -338,9 +338,11 @@ class Handler(http.server.BaseHTTPRequestHandler): if self.path == "/": return self.serve_feeds() elif self.path == "/style.css": - return self.serve_style() + return self.serve_static("style.css", "text/css") elif self.path == "/event.js": - return self.serve_event_js() + return self.serve_static("event.js", "text/javascript") + elif self.path == "/index.js": + return self.serve_static("index.js", "text/javascript") elif self.path == "/refresh-status": return self.serve_status() elif self.path == "/subscribe-status": @@ -478,16 +480,10 @@ class Handler(http.server.BaseHTTPRequestHandler): self.end_headers() self.wfile.write(response) - def serve_event_js(self): + def serve_static(self, name: str, content_type: str): self.write_file( - pathlib.Path(__file__).parent / "static" / "event.js", - content_type="text/javascript", - ) - - def serve_style(self): - self.write_file( - pathlib.Path(__file__).parent / "static" / "style.css", - content_type="text/css", + pathlib.Path(__file__).parent / "static" / name, + content_type=content_type, ) def write_file(self, path: pathlib.Path, content_type: str):