web & server: finish initial tailwind rewrite

This commit is contained in:
Bill Thiede 2025-01-27 14:00:46 -08:00
parent 70fb635eda
commit ee93d725ba
7 changed files with 158 additions and 108 deletions

View File

@ -1,8 +0,0 @@
pre {
background-color: var(--color-bg);
color: var(--color-text);
}
code {
background-color: var(--color-bg-secondary);
}

View File

@ -117,26 +117,7 @@ struct InlineRemoteStyle<'a> {
#[async_trait]
impl<'a> Transformer for InlineRemoteStyle<'a> {
async fn transform(&self, _: &Option<Url>, html: &str) -> Result<String, TransformError> {
let css = concat!(
"/* chrome-default.css */\n",
include_str!("chrome-default.css"),
"\n/* mvp.css */\n",
include_str!("mvp.css"),
"\n/* Xinu Specific overrides */\n",
include_str!("custom.css"),
);
let inline_opts = InlineOptions {
//inline_style_tags: true,
//keep_style_tags: false,
//keep_link_tags: true,
base_url: self.base_url.clone(),
//load_remote_stylesheets: true,
//preallocate_node_capacity: 32,
..InlineOptions::default()
};
//info!("HTML:\n{html}");
info!("base_url: {:#?}", self.base_url);
Ok(
match CSSInliner::options()
.base_url(self.base_url.clone())
@ -160,10 +141,10 @@ impl Transformer for InlineStyle {
let css = concat!(
"/* chrome-default.css */\n",
include_str!("chrome-default.css"),
"\n/* mvp.css */\n",
include_str!("mvp.css"),
"\n/* Xinu Specific overrides */\n",
include_str!("custom.css"),
//"\n/* mvp.css */\n",
//include_str!("mvp.css"),
//"\n/* Xinu Specific overrides */\n",
//include_str!("custom.css"),
);
let inline_opts = InlineOptions {
inline_style_tags: true,
@ -288,13 +269,25 @@ impl SlurpContents {
#[async_trait]
impl Transformer for SlurpContents {
fn should_run(&self, link: &Option<Url>, _: &str) -> bool {
fn should_run(&self, link: &Option<Url>, html: &str) -> bool {
let mut will_slurp = false;
if let Some(link) = link {
return self.get_selectors(link).is_some();
will_slurp = self.get_selectors(link).is_some();
}
false
if !will_slurp && self.inline_css {
return InlineStyle {}.should_run(link, html);
}
will_slurp
}
async fn transform(&self, link: &Option<Url>, html: &str) -> Result<String, TransformError> {
if let Some(test_link) = link {
// If SlurpContents is configured for inline CSS, but no
// configuration found for this site, use the local InlineStyle
// transform.
if self.inline_css && self.get_selectors(test_link).is_none() {
return InlineStyle {}.transform(link, html).await;
}
}
let Some(link) = link else {
return Ok(html.to_string());
};
@ -303,7 +296,6 @@ impl Transformer for SlurpContents {
};
let cacher = self.cacher.lock().await;
let body = if let Some(body) = cacher.get(link.as_str()) {
info!("cache hit for {link}");
String::from_utf8_lossy(&body).to_string()
} else {
let body = reqwest::get(link.as_str()).await?.text().await?;
@ -315,8 +307,17 @@ impl Transformer for SlurpContents {
let body = if self.inline_css {
let inner_body = Arc::clone(&body);
let res = tokio::task::spawn_blocking(move || {
let css = concat!(
"/* chrome-default.css */\n",
include_str!("chrome-default.css"),
"\n/* vars.css */\n",
include_str!("../../web/static/vars.css"),
//"\n/* Xinu Specific overrides */\n",
//include_str!("custom.css"),
);
let res = CSSInliner::options()
.base_url(base_url)
.extra_css(Some(std::borrow::Cow::Borrowed(css)))
.build()
.inline(&inner_body);

View File

@ -15,9 +15,8 @@ use crate::{
config::Config,
error::ServerError,
graphql::{Corpus, NewsPost, Tag, Thread, ThreadSummary},
thread_summary_from_row, AddOutlink, EscapeHtml, FrameImages, InlineRemoteStyle, Query,
SanitizeHtml, SlurpContents, ThreadSummaryRecord, Transformer, NEWSREADER_TAG_PREFIX,
NEWSREADER_THREAD_PREFIX,
thread_summary_from_row, AddOutlink, FrameImages, Query, SanitizeHtml, SlurpContents,
ThreadSummaryRecord, Transformer, NEWSREADER_TAG_PREFIX, NEWSREADER_THREAD_PREFIX,
};
pub fn is_newsreader_query(query: &Query) -> bool {
@ -196,8 +195,7 @@ pub async fn thread(
let body_tranformers: Vec<Box<dyn Transformer>> = vec![
Box::new(SlurpContents {
cacher,
// TODO: make this true when bulma is finally removed
inline_css: false,
inline_css: true,
site_selectors: hashmap![
"atmeta.com".to_string() => vec![
Selector::parse("div.entry-content").unwrap(),
@ -239,6 +237,14 @@ pub async fn thread(
Selector::parse("span.story-byline").unwrap(),
Selector::parse("div.p").unwrap(),
],
"theonion.com".to_string() => vec![
// Single cartoon
Selector::parse("article > div > div > figure").unwrap(),
// Image at top of article
Selector::parse("article > header > div > div > figure").unwrap(),
// Article body
Selector::parse("article .entry-content > *").unwrap(),
],
"trofi.github.io".to_string() => vec![
Selector::parse("#content").unwrap(),
],

View File

@ -13,7 +13,9 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@700&display=swap" rel="stylesheet">
<!-- <link data-trunk rel="css" href="static/site-specific.css" /> -->
<link data-trunk rel="css" href="static/vars.css" />
<link data-trunk rel="tailwind-css" href="./src/tailwind.css" />
<link data-trunk rel="css" href="static/overrides.css" />
</head>
<body>

View File

@ -86,7 +86,13 @@ pub fn view(model: &Model) -> Node<Msg> {
} => search_results(&query, results.as_slice(), *count, pager, selected_threads),
};
div![
C!["flex", "flex-wrap-reverse", "bg-black", "text-white"],
C![
"flex",
"flex-wrap-reverse",
"bg-black",
"text-white",
"lg:flex-nowrap"
],
div![
C!["w-full", "lg:w-48", "flex-none", "flex", "flex-col"],
tags(model),
@ -190,6 +196,7 @@ fn set_title(title: &str) {
seed::document().set_title(&format!("lb: {}", title));
}
// TODO: unifiy tags_chiclet, removable_tags_chiclet, and tags inside news_post()
fn tags_chiclet(tags: &[String], is_mobile: bool) -> impl Iterator<Item = Node<Msg>> + '_ {
tags.iter().map(move |tag| {
let hex = compute_color(tag);
@ -1147,8 +1154,6 @@ pub fn tags(model: &Model) -> Node<Msg> {
]
}
fn news_post(post: &ShowThreadQueryThreadOnNewsPost, content_el: &ElRef<HtmlElement>) -> Node<Msg> {
// TODO remove and replace with CSS styling
let show_icon_text = true;
let subject = &post.title;
set_title(subject);
let read_thread_id = post.thread_id.clone();
@ -1156,94 +1161,68 @@ fn news_post(post: &ShowThreadQueryThreadOnNewsPost, content_el: &ElRef<HtmlElem
fn tag(tag: String, is_mobile: bool) -> Node<Msg> {
let hex = compute_color(&tag);
let style = style! {St::BackgroundColor=>hex};
let classes = C!["NOTPORTED", "tag", IF!(is_mobile => "is-small")];
let attrs = attrs! {
At::Href => urls::search(&format!("tag:{tag}"), 0)
};
let tag = tag.clone();
div![
C![
"NOTPORTED",
"message-tags",
"field",
"is-grouped",
"is-grouped-multiline"
],
div![
C!["NOTPORTED", "control"],
div![
C!["NOTPORTED", "tags", "has-addons"],
a![
classes,
attrs,
style,
match tag.as_str() {
"attachment" => span!["📎"],
"replied" => span![i![C!["NOTPORTED", "fa-solid", "fa-reply"]]],
_ => span![&tag],
},
ev(Ev::Click, move |_| Msg::FrontPageRequest {
query: format!("tag:{tag}"),
after: None,
before: None,
first: None,
last: None,
})
]
]
]
a![
attrs,
span![C![&tw_classes::TAG], style, &tag],
ev(Ev::Click, move |_| Msg::FrontPageRequest {
query: format!("tag:{tag}"),
after: None,
before: None,
first: None,
last: None,
})
]
}
div![
C!["NOTPORTED", "thread"],
h3![C!["NOTPORTED", "is-size-5"], subject],
tag(format!("News/{}", post.slug), false),
C!["lg:p-4", "max-w-4xl"],
div![
C!["NOTPORTED", "level", "is-mobile"],
C!["p-4", "lg:p-0"],
h3![C!["text-xl"], subject],
span![tag(format!("News/{}", post.slug), false)],
div![
C!["NOTPORTED", "level-item"],
C!["pt-4", "gap-2", "flex", "justify-around"],
div![
C!["NOTPORTED", "buttons", "has-addons"],
button![
C!["NOTPORTED", "button", "mark-read"],
C![&tw_classes::BUTTON, "rounded-r-none"],
attrs! {At::Title => "Mark as read"},
span![
C!["NOTPORTED", "icon", "is-small"],
i![C!["NOTPORTED", "far", "fa-envelope-open"]]
],
IF!(show_icon_text=>span!["Read"]),
span![i![C!["far", "fa-envelope-open"]]],
span![C!["pl-2", "hidden", "md:inline"], "Read"],
ev(Ev::Click, move |_| Msg::MultiMsg(vec![
Msg::SetUnread(read_thread_id, false),
Msg::GoToSearchResults
])),
],
button![
C!["NOTPORTED", "button", "mark-unread"],
C![&tw_classes::BUTTON, "rounded-l-none"],
attrs! {At::Title => "Mark as unread"},
span![
C!["NOTPORTED", "icon", "is-small"],
i![C!["NOTPORTED", "far", "fa-envelope"]]
],
IF!(show_icon_text=>span!["Unread"]),
span![i![C!["far", "fa-envelope"]]],
span![C!["pl-2", "hidden", "md:inline"], "Unread"],
ev(Ev::Click, move |_| Msg::MultiMsg(vec![
Msg::SetUnread(unread_thread_id, true),
Msg::GoToSearchResults
])),
],
],
// Placeholder for symmetry with email view that has Spam button
div![],
],
// This would be the holder for spam buttons on emails, needed to keep layout
// consistent
div![C!["NOTPORTED", "level-item"], div![]]
],
div![
C!["NOTPORTED", "message"],
div![C!["NOTPORTED", "header"], render_news_post_header(&post)],
C!["lg:mt-4"],
div![render_news_post_header(&post)],
div![
C![
"NOTPORTED",
"body",
"bg-white",
"text-black",
"p-4",
"lg:mb-4",
"min-w-full",
"overflow-x-auto",
"news-post",
format!("site-{}", post.slug)
],
@ -1251,12 +1230,6 @@ fn news_post(post: &ShowThreadQueryThreadOnNewsPost, content_el: &ElRef<HtmlElem
raw![&post.body]
]
],
/* TODO(wathiede): plumb in orignal id
a![
attrs! {At::Href=>api::original(&thread_node.0.as_ref().expect("message missing").id)},
"Original"
],
*/
click_to_top(),
]
}
@ -1289,7 +1262,7 @@ fn render_news_post_header(post: &ShowThreadQueryThreadOnNewsPost) -> Node<Msg>
],
];
div![
C!["flex", "p-4"],
C!["flex", "p-4", "bg-neutral-800"],
div![favicon],
div![
C!["px-4", "mr-auto"],
@ -1329,6 +1302,7 @@ fn render_news_post_header(post: &ShowThreadQueryThreadOnNewsPost) -> Node<Msg>
]
}
fn reading_progress(ratio: f64) -> Node<Msg> {
return div![];
let percent = ratio * 100.;
info!("percent {percent}");
div![
@ -1336,7 +1310,7 @@ fn reading_progress(ratio: f64) -> Node<Msg> {
"fixed",
"top-0",
"left-0",
"w-screen",
"w-full",
"h-1",
"bg-gray-200",
IF!(percent<1. => "hidden")

33
web/static/overrides.css Normal file
View File

@ -0,0 +1,33 @@
html {
background-color: black;
}
.news-post a {
color: var(--color-link) !important;
text-decoration: underline;
}
.news-post br {
display: block;
margin-top: 1em;
content: " ";
}
.news-post h1,
.news-post h2,
.news-post h3,
.news-post h4 {
margin-top: 1em !important;
margin-bottom: 1em !important;
}
.news-post p {
margin-bottom: 1em;
}
.news-post pre,
.news-post code {
font-family: monospace;
background-color: #eee !important;
padding: 0.5em !important;
}

42
web/static/vars.css Normal file
View File

@ -0,0 +1,42 @@
:root {
--active-brightness: 0.85;
--border-radius: 5px;
--box-shadow: 2px 2px 10px;
--color-accent: #118bee15;
--color-bg: #fff;
--color-bg-secondary: #e9e9e9;
--color-link: #118bee;
--color-secondary: #920de9;
--color-secondary-accent: #920de90b;
--color-shadow: #f4f4f4;
--color-table: #118bee;
--color-text: #000;
--color-text-secondary: #999;
--color-scrollbar: #cacae8;
--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
--hover-brightness: 1.2;
--justify-important: center;
--justify-normal: left;
--line-height: 1.5;
/*
--width-card: 285px;
--width-card-medium: 460px;
--width-card-wide: 800px;
*/
--width-content: 1080px;
}
@media (prefers-color-scheme: dark) {
:root[color-mode="user"] {
--color-accent: #0097fc4f;
--color-bg: #333;
--color-bg-secondary: #555;
--color-link: #0097fc;
--color-secondary: #e20de9;
--color-secondary-accent: #e20de94f;
--color-shadow: #bbbbbb20;
--color-table: #0097fc;
--color-text: #f7f7f7;
--color-text-secondary: #aaa;
}
}