From c16fdde19a5fc3b76f7b6903a95aae922f9a3ba3 Mon Sep 17 00:00:00 2001 From: John Doty Date: Sat, 23 Nov 2024 16:55:16 -0800 Subject: [PATCH 1/2] Two very bad columns --- cry/static/style.css | 62 ++++++++++++++++++++++++++++++++ cry/views.py | 86 ++++++++++++++++++++++++++++++++------------ cry/web.py | 4 +-- 3 files changed, 127 insertions(+), 25 deletions(-) diff --git a/cry/static/style.css b/cry/static/style.css index a6d1d8c..3c4a809 100644 --- a/cry/static/style.css +++ b/cry/static/style.css @@ -41,3 +41,65 @@ li.entry:before { content: '\2022'; padding-right: 0.5rem; } + +/* Feed summaries */ +ul.feed-summary-list { + list-style-type: none; + margin: 0; + padding: 0 1rem 0 0; + + flex-basis: 10%; + flex: 1; + width: 100%; + + height: auto; + overflow-y: auto; +} + +li.feed-summary { + margin-bottom: 0.5rem; + flex: 1; + + overflow: hidden; +} +li.feed-summary h2 { + margin: 0; + font-size: 100%; + flex: 1; +} +li.feed-summary p { + margin: 0; +} +p.feed-title-summary { + height: 1.5rem; + + overflow: hidden; + text-overflow: ellipsis; +} + +/* The feed container */ +div.feed-container { + flex: 1; + flex-basis: 40%; + + height: 100%; + overflow-y: auto; +} + +/* Group it all */ +div.main-container { + margin-top: 1rem; + flex: 1; + + display: flex; + flex-direction: row; + margin-left: auto; + margin-right: auto; + overflow-y: scroll; /*...but we don't scroll because our columns do first!*/ +} + +/* Uh */ +body.feed-view { + display: flex; + flex-direction: column; +} diff --git a/cry/views.py b/cry/views.py index c53833d..c0ac176 100644 --- a/cry/views.py +++ b/cry/views.py @@ -1,9 +1,11 @@ import typing +import dominate import dominate.tags as tags from . import feed + def _standard_header(title: str) -> tags.head: head = tags.head( tags.meta(charset="utf8"), @@ -13,11 +15,33 @@ def _standard_header(title: str) -> tags.head: assert isinstance(head, tags.head) return head + +def _feed_summary(f: feed.Feed) -> tags.li: + if len(f.entries) == 0: + last_entry = tags.p(tags.i("Never posted")) + else: + entry = f.entries[0] + last_entry = [ + tags.p(tags.i(f"Posted {entry.time_ago()} ago")), + tags.p({"class": "feed-title-summary"}, entry.title), + ] + + result = tags.li( + {"class": "feed-summary"}, + # TODO: Image! + tags.h2(f.title), + last_entry, + ) + assert isinstance(result, tags.li) + return result + + def feed_view(feeds: list[feed.Feed]) -> tags.html: document = tags.html( _standard_header("Subscribed Feeds"), - tags.h1("Feeds"), - tags.div( + tags.body( + {"class": "feed-view"}, + tags.h1("Feeds"), tags.form( {"method": "post", "action": "/refresh"}, tags.input_(type="submit", value="Refresh"), @@ -28,33 +52,45 @@ def feed_view(feeds: list[feed.Feed]) -> tags.html: tags.input_(type="url", name="url"), tags.input_(type="submit", value="Subscribe"), ), - ( + tags.div( + {"class": "main-container"}, + tags.ul( + {"class": "feed-summary-list"}, (_feed_summary(f) for f in feeds) + ), tags.div( - {"class": "feed"}, - tags.h2(tags.a(f.title, href=f.link, target="_blank")), + {"class": "feed-container"}, ( - tags.ul( - tags.li( - {"class": "entry"}, - tags.a( - entry.title, href=entry.link, target="_blank" - ), - f" ({entry.time_ago()})", - ) - for entry in f.entries + tags.div( + {"class": "feed"}, + tags.h2(tags.a(f.title, href=f.link, target="_blank")), + ( + tags.ul( + tags.li( + {"class": "entry"}, + tags.a( + entry.title, + href=entry.link, + target="_blank", + ), + f" ({entry.time_ago()})", + ) + for entry in f.entries + ) + if len(f.entries) > 0 + else tags.i("No entries...") + ), ) - if len(f.entries) > 0 - else tags.i("No entries...") + for f in feeds ), - ) - for f in feeds + ), ), ), ) assert isinstance(document, tags.html) return document -def subscribe_choose_view(candidates: typing.Iterable[tuple[str,str]]) -> tags.html: + +def subscribe_choose_view(candidates: typing.Iterable[tuple[str, str]]) -> tags.html: document = tags.html( _standard_header("Choose Feed"), tags.h1("Choose Feed"), @@ -64,11 +100,15 @@ def subscribe_choose_view(candidates: typing.Iterable[tuple[str,str]]) -> tags.h tags.thead(tags.tr(tags.th("Title"), tags.th("URL"), tags.th())), tags.tbody( tags.form( - {"action": "/subscribe", "method":"post"}, + {"action": "/subscribe", "method": "post"}, tags.tr( - tags.td(title), tags.td(url), tags.td( - tags.input_({"type":"hidden", "name":"url", "value":url}), - tags.input_({"type":"submit", "value":"Subscribe"}), + tags.td(title), + tags.td(url), + tags.td( + tags.input_( + {"type": "hidden", "name": "url", "value": url} + ), + tags.input_({"type": "submit", "value": "Subscribe"}), ), ), ) diff --git a/cry/web.py b/cry/web.py index b8fd033..b1c4b7c 100644 --- a/cry/web.py +++ b/cry/web.py @@ -497,7 +497,7 @@ class Handler(http.server.BaseHTTPRequestHandler): feeds.sort(key=feed.sort_key, reverse=True) document = views.feed_view(feeds) - self.write_html(document.render()) + self.write_html("\n" + document.render()) def serve_subscribe_choose(self): try: @@ -512,7 +512,7 @@ class Handler(http.server.BaseHTTPRequestHandler): return document = views.subscribe_choose_view(candidates) - self.write_html(document.render()) + self.write_html("\n" + document.render()) def write_html(self, html: str): response = html.encode("utf-8") From 56d6c8e408ed93e36394dc2ff72da9365a68608e Mon Sep 17 00:00:00 2001 From: John Doty Date: Sun, 24 Nov 2024 08:16:25 -0800 Subject: [PATCH 2/2] So much cleaner! Give up on flexbox man, grids are how my brain works mostly. --- cry/feed.py | 4 ++ cry/static/style.css | 124 ++++++++++++++++++++++++++----------------- cry/views.py | 75 +++++++++++++++++++------- 3 files changed, 135 insertions(+), 68 deletions(-) diff --git a/cry/feed.py b/cry/feed.py index ca4b73f..67b0b76 100644 --- a/cry/feed.py +++ b/cry/feed.py @@ -2,6 +2,7 @@ import asyncio import calendar import dataclasses +import datetime import functools import hashlib import html.parser @@ -132,6 +133,9 @@ class Entry: link=link, ) + def posted_time_iso(self) -> str: + return datetime.datetime.fromtimestamp(self.posted_at / 1000).isoformat() + def time_ago(self) -> str: posted = int(self.posted_at / 1000) seconds = int(time.time()) - posted diff --git a/cry/static/style.css b/cry/static/style.css index 3c4a809..34b4633 100644 --- a/cry/static/style.css +++ b/cry/static/style.css @@ -1,10 +1,9 @@ body { + box-sizing: border-box; margin-right: 4rem; margin-left: 4rem; margin-top: 0; margin-bottom: 0; - display: flex; - flex-direction: column; height: 100vh; } @@ -42,64 +41,91 @@ li.entry:before { padding-right: 0.5rem; } -/* Feed summaries */ -ul.feed-summary-list { - list-style-type: none; - margin: 0; - padding: 0 1rem 0 0; - - flex-basis: 10%; - flex: 1; - width: 100%; - - height: auto; - overflow-y: auto; +/* + * FEED VIEW + */ +.fv-body { + display: grid; + grid-template-columns: 1fr; + grid-template-rows: auto auto 1fr; } -li.feed-summary { - margin-bottom: 0.5rem; - flex: 1; +.fv-title { + grid-row: 1; +} +.fv-subscribe { + grid-row: 2; +} + +.fv-main-panel { + grid-row: 3; + margin-top: 1rem; + + display: grid; + grid-template-rows: 1fr; + grid-template-columns: 1fr 3fr; + overflow-y: scroll; +} + +.fv-summary-panel { + grid-column: 1; + + display: grid; + grid-template-columns: 1fr; + grid-template-rows: auto 1fr; + + margin-right: 1rem; + overflow-y: scroll; +} + +.fv-summary-refresh { + grid-row: 1; + + width: 100%; + display: block; +} + +.fv-summary-refresh input { + width: 100%; + padding: 0.5rem; +} + +.fv-summary-list { + grid-row: 2; + + list-style-type: none; + padding: 0; + overflow-y: scroll; +} + +.fv-feed-panel { + grid-column: 2; + overflow-y: scroll; +} + +/* + * FEED SUMMARY ITEM + */ +.feed-summary { + margin-bottom: 0.5rem; overflow: hidden; } -li.feed-summary h2 { + +.feed-summary-title { margin: 0; font-size: 100%; - flex: 1; + font-weight: bold; } -li.feed-summary p { + +.feed-summary-timestamp { margin: 0; } -p.feed-title-summary { - height: 1.5rem; + +.feed-summary-entry { + margin: 0; overflow: hidden; text-overflow: ellipsis; -} - -/* The feed container */ -div.feed-container { - flex: 1; - flex-basis: 40%; - - height: 100%; - overflow-y: auto; -} - -/* Group it all */ -div.main-container { - margin-top: 1rem; - flex: 1; - - display: flex; - flex-direction: row; - margin-left: auto; - margin-right: auto; - overflow-y: scroll; /*...but we don't scroll because our columns do first!*/ -} - -/* Uh */ -body.feed-view { - display: flex; - flex-direction: column; + white-space: nowrap; } diff --git a/cry/views.py b/cry/views.py index c0ac176..45f1ef9 100644 --- a/cry/views.py +++ b/cry/views.py @@ -1,11 +1,26 @@ import typing -import dominate import dominate.tags as tags from . import feed +class time(tags.html_tag): + """The