Compare commits
No commits in common. "f6665b6b6efb70c585783b1dac862d3dc051e0ec" and "4099bbe732f958bc89795754578cf44834dcebfa" have entirely different histories.
f6665b6b6e
...
4099bbe732
@ -3,6 +3,7 @@
|
|||||||
rustflags = [ "--cfg=web_sys_unstable_apis" ]
|
rustflags = [ "--cfg=web_sys_unstable_apis" ]
|
||||||
|
|
||||||
[registry]
|
[registry]
|
||||||
|
default = "xinu"
|
||||||
global-credential-providers = ["cargo:token"]
|
global-credential-providers = ["cargo:token"]
|
||||||
|
|
||||||
[registries.xinu]
|
[registries.xinu]
|
||||||
|
|||||||
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -2910,7 +2910,7 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "letterbox"
|
name = "letterbox"
|
||||||
version = "0.0.116"
|
version = "0.0.115"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"build-info",
|
"build-info",
|
||||||
"build-info-build",
|
"build-info-build",
|
||||||
@ -2936,7 +2936,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "letterbox-server"
|
name = "letterbox-server"
|
||||||
version = "0.0.116"
|
version = "0.0.115"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ammonia",
|
"ammonia",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@ -3455,7 +3455,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "notmuch"
|
name = "notmuch"
|
||||||
version = "0.0.116"
|
version = "0.0.115"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itertools 0.10.5",
|
"itertools 0.10.5",
|
||||||
"log",
|
"log",
|
||||||
@ -4250,7 +4250,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "procmail2notmuch"
|
name = "procmail2notmuch"
|
||||||
version = "0.0.116"
|
version = "0.0.115"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
]
|
]
|
||||||
@ -5329,7 +5329,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shared"
|
name = "shared"
|
||||||
version = "0.0.116"
|
version = "0.0.115"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"build-info",
|
"build-info",
|
||||||
"notmuch",
|
"notmuch",
|
||||||
|
|||||||
15
Cargo.toml
15
Cargo.toml
@ -1,10 +1,15 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
default-members = ["server"]
|
default-members = [
|
||||||
members = ["web", "server", "notmuch", "procmail2notmuch", "shared"]
|
"server"
|
||||||
|
]
|
||||||
[profile.dev]
|
members = [
|
||||||
opt-level = 1
|
"web",
|
||||||
|
"server",
|
||||||
|
"notmuch",
|
||||||
|
"procmail2notmuch",
|
||||||
|
"shared"
|
||||||
|
]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "notmuch"
|
name = "notmuch"
|
||||||
version = "0.0.116"
|
version = "0.0.115"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "procmail2notmuch"
|
name = "procmail2notmuch"
|
||||||
version = "0.0.116"
|
version = "0.0.115"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "letterbox-server"
|
name = "letterbox-server"
|
||||||
version = "0.0.116"
|
version = "0.0.115"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
default-run = "letterbox-server"
|
default-run = "letterbox-server"
|
||||||
|
|
||||||
|
|||||||
@ -11,4 +11,4 @@ port = 9345
|
|||||||
#log_level = "critical"
|
#log_level = "critical"
|
||||||
newsreader_database_url = "postgres://newsreader@nixos-07.h.xinu.tv/newsreader"
|
newsreader_database_url = "postgres://newsreader@nixos-07.h.xinu.tv/newsreader"
|
||||||
newsreader_tantivy_db_path = "../target/database/newsreader"
|
newsreader_tantivy_db_path = "../target/database/newsreader"
|
||||||
slurp_cache_path = "/tmp/letterbox/slurp"
|
slurp_cache_path = "/net/nasx/x/letterbox/slurp"
|
||||||
|
|||||||
@ -1,2 +0,0 @@
|
|||||||
-- Add down migration script here
|
|
||||||
DROP INDEX nzb_posts_created_at_idx;
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
-- Add up migration script here
|
|
||||||
CREATE INDEX nzb_posts_created_at_idx ON nzb_posts USING btree (created_at);
|
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
use chrono::NaiveDateTime;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use letterbox_server::mail::read_mail_to_db;
|
use letterbox_server::mail::read_mail_to_db;
|
||||||
use sqlx::postgres::PgPool;
|
use sqlx::postgres::PgPool;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
8
server/src/custom.css
Normal file
8
server/src/custom.css
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
pre {
|
||||||
|
background-color: var(--color-bg);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background-color: var(--color-bg-secondary);
|
||||||
|
}
|
||||||
@ -110,29 +110,6 @@ impl Transformer for StripHtml {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InlineRemoteStyle<'a> {
|
|
||||||
base_url: &'a Option<Url>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl<'a> Transformer for InlineRemoteStyle<'a> {
|
|
||||||
async fn transform(&self, _: &Option<Url>, html: &str) -> Result<String, TransformError> {
|
|
||||||
//info!("HTML:\n{html}");
|
|
||||||
Ok(
|
|
||||||
match CSSInliner::options()
|
|
||||||
.base_url(self.base_url.clone())
|
|
||||||
.build()
|
|
||||||
.inline(&html)
|
|
||||||
{
|
|
||||||
Ok(inlined_html) => inlined_html,
|
|
||||||
Err(err) => {
|
|
||||||
error!("failed to inline remote CSS: {err}");
|
|
||||||
html.to_string()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
struct InlineStyle;
|
struct InlineStyle;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@ -141,10 +118,10 @@ impl Transformer for InlineStyle {
|
|||||||
let css = concat!(
|
let css = concat!(
|
||||||
"/* chrome-default.css */\n",
|
"/* chrome-default.css */\n",
|
||||||
include_str!("chrome-default.css"),
|
include_str!("chrome-default.css"),
|
||||||
//"\n/* mvp.css */\n",
|
"\n/* mvp.css */\n",
|
||||||
//include_str!("mvp.css"),
|
include_str!("mvp.css"),
|
||||||
//"\n/* Xinu Specific overrides */\n",
|
"\n/* Xinu Specific overrides */\n",
|
||||||
//include_str!("custom.css"),
|
include_str!("custom.css"),
|
||||||
);
|
);
|
||||||
let inline_opts = InlineOptions {
|
let inline_opts = InlineOptions {
|
||||||
inline_style_tags: true,
|
inline_style_tags: true,
|
||||||
@ -252,7 +229,6 @@ impl Transformer for AddOutlink {
|
|||||||
|
|
||||||
struct SlurpContents {
|
struct SlurpContents {
|
||||||
cacher: Arc<Mutex<FilesystemCacher>>,
|
cacher: Arc<Mutex<FilesystemCacher>>,
|
||||||
inline_css: bool,
|
|
||||||
site_selectors: HashMap<String, Vec<Selector>>,
|
site_selectors: HashMap<String, Vec<Selector>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,25 +245,13 @@ impl SlurpContents {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Transformer for SlurpContents {
|
impl Transformer for SlurpContents {
|
||||||
fn should_run(&self, link: &Option<Url>, html: &str) -> bool {
|
fn should_run(&self, link: &Option<Url>, _: &str) -> bool {
|
||||||
let mut will_slurp = false;
|
|
||||||
if let Some(link) = link {
|
if let Some(link) = link {
|
||||||
will_slurp = self.get_selectors(link).is_some();
|
return self.get_selectors(link).is_some();
|
||||||
}
|
}
|
||||||
if !will_slurp && self.inline_css {
|
false
|
||||||
return InlineStyle {}.should_run(link, html);
|
|
||||||
}
|
|
||||||
will_slurp
|
|
||||||
}
|
}
|
||||||
async fn transform(&self, link: &Option<Url>, html: &str) -> Result<String, TransformError> {
|
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 {
|
let Some(link) = link else {
|
||||||
return Ok(html.to_string());
|
return Ok(html.to_string());
|
||||||
};
|
};
|
||||||
@ -296,51 +260,13 @@ impl Transformer for SlurpContents {
|
|||||||
};
|
};
|
||||||
let cacher = self.cacher.lock().await;
|
let cacher = self.cacher.lock().await;
|
||||||
let body = if let Some(body) = cacher.get(link.as_str()) {
|
let body = if let Some(body) = cacher.get(link.as_str()) {
|
||||||
|
info!("cache hit for {link}");
|
||||||
String::from_utf8_lossy(&body).to_string()
|
String::from_utf8_lossy(&body).to_string()
|
||||||
} else {
|
} else {
|
||||||
let body = reqwest::get(link.as_str()).await?.text().await?;
|
let body = reqwest::get(link.as_str()).await?.text().await?;
|
||||||
cacher.set(link.as_str(), body.as_bytes());
|
cacher.set(link.as_str(), body.as_bytes());
|
||||||
body
|
body
|
||||||
};
|
};
|
||||||
let body = Arc::new(body);
|
|
||||||
let base_url = Some(link.clone());
|
|
||||||
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);
|
|
||||||
|
|
||||||
match res {
|
|
||||||
Ok(inlined_html) => inlined_html,
|
|
||||||
Err(err) => {
|
|
||||||
error!("failed to inline remote CSS: {err}");
|
|
||||||
Arc::into_inner(inner_body).expect("failed to take body out of Arc")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
match res {
|
|
||||||
Ok(inlined_html) => inlined_html,
|
|
||||||
Err(err) => {
|
|
||||||
error!("failed to spawn inline remote CSS: {err}");
|
|
||||||
Arc::into_inner(body).expect("failed to take body out of Arc")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Arc::into_inner(body).expect("failed to take body out of Arc")
|
|
||||||
};
|
|
||||||
|
|
||||||
let doc = Html::parse_document(&body);
|
let doc = Html::parse_document(&body);
|
||||||
|
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
@ -351,7 +277,7 @@ impl Transformer for SlurpContents {
|
|||||||
//warn!("couldn't find '{:?}' in {}", selector, link);
|
//warn!("couldn't find '{:?}' in {}", selector, link);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(results.join("<br>"))
|
Ok(results.join("<hr>"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use std::{fs::File, io::Read};
|
use std::{fs::File, io, io::Read};
|
||||||
|
|
||||||
use mailparse::{
|
use mailparse::{
|
||||||
addrparse_header, dateparse, parse_mail, MailHeaderMap, MailParseError, ParsedMail,
|
addrparse_header, dateparse, parse_mail, MailHeaderMap, MailParseError, ParsedMail,
|
||||||
@ -63,7 +63,8 @@ pub async fn read_mail_to_db(pool: &PgPool, path: &str) -> Result<(), MailError>
|
|||||||
|
|
||||||
println!("Feed: {feed_id} Subject: {}", subject);
|
println!("Feed: {feed_id} Subject: {}", subject);
|
||||||
|
|
||||||
if let Some(_m) = first_html(&m) {
|
if let Some(m) = first_html(&m) {
|
||||||
|
let body = m.get_body()?;
|
||||||
info!("add email {slug} {subject} {message_id} {date} {uid} {url}");
|
info!("add email {slug} {subject} {message_id} {date} {uid} {url}");
|
||||||
} else {
|
} else {
|
||||||
return Err(MailError::MissingHtmlPart.into());
|
return Err(MailError::MissingHtmlPart.into());
|
||||||
|
|||||||
@ -15,8 +15,9 @@ use crate::{
|
|||||||
config::Config,
|
config::Config,
|
||||||
error::ServerError,
|
error::ServerError,
|
||||||
graphql::{Corpus, NewsPost, Tag, Thread, ThreadSummary},
|
graphql::{Corpus, NewsPost, Tag, Thread, ThreadSummary},
|
||||||
thread_summary_from_row, AddOutlink, FrameImages, Query, SanitizeHtml, SlurpContents,
|
thread_summary_from_row, AddOutlink, EscapeHtml, FrameImages, InlineStyle, Query, SanitizeHtml,
|
||||||
ThreadSummaryRecord, Transformer, NEWSREADER_TAG_PREFIX, NEWSREADER_THREAD_PREFIX,
|
SlurpContents, ThreadSummaryRecord, Transformer, NEWSREADER_TAG_PREFIX,
|
||||||
|
NEWSREADER_THREAD_PREFIX,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn is_newsreader_query(query: &Query) -> bool {
|
pub fn is_newsreader_query(query: &Query) -> bool {
|
||||||
@ -188,6 +189,7 @@ pub async fn thread(
|
|||||||
|
|
||||||
let slug = r.site.unwrap_or("no-slug".to_string());
|
let slug = r.site.unwrap_or("no-slug".to_string());
|
||||||
let site = r.name.unwrap_or("NO SITE".to_string());
|
let site = r.name.unwrap_or("NO SITE".to_string());
|
||||||
|
let default_homepage = "http://no-homepage";
|
||||||
// TODO: remove the various places that have this as an Option
|
// TODO: remove the various places that have this as an Option
|
||||||
let link = Some(Url::parse(&r.link)?);
|
let link = Some(Url::parse(&r.link)?);
|
||||||
let mut body = r.summary.unwrap_or("NO SUMMARY".to_string());
|
let mut body = r.summary.unwrap_or("NO SUMMARY".to_string());
|
||||||
@ -195,7 +197,6 @@ pub async fn thread(
|
|||||||
let body_tranformers: Vec<Box<dyn Transformer>> = vec![
|
let body_tranformers: Vec<Box<dyn Transformer>> = vec![
|
||||||
Box::new(SlurpContents {
|
Box::new(SlurpContents {
|
||||||
cacher,
|
cacher,
|
||||||
inline_css: true,
|
|
||||||
site_selectors: hashmap![
|
site_selectors: hashmap![
|
||||||
"atmeta.com".to_string() => vec![
|
"atmeta.com".to_string() => vec![
|
||||||
Selector::parse("div.entry-content").unwrap(),
|
Selector::parse("div.entry-content").unwrap(),
|
||||||
@ -223,9 +224,6 @@ pub async fn thread(
|
|||||||
"ingowald.blog".to_string() => vec![
|
"ingowald.blog".to_string() => vec![
|
||||||
Selector::parse("article").unwrap(),
|
Selector::parse("article").unwrap(),
|
||||||
],
|
],
|
||||||
"jvns.ca".to_string() => vec![
|
|
||||||
Selector::parse("article").unwrap(),
|
|
||||||
],
|
|
||||||
"mitchellh.com".to_string() => vec![Selector::parse("div.w-full").unwrap()],
|
"mitchellh.com".to_string() => vec![Selector::parse("div.w-full").unwrap()],
|
||||||
"natwelch.com".to_string() => vec![
|
"natwelch.com".to_string() => vec![
|
||||||
Selector::parse("article div.prose").unwrap(),
|
Selector::parse("article div.prose").unwrap(),
|
||||||
@ -237,17 +235,6 @@ pub async fn thread(
|
|||||||
Selector::parse("span.story-byline").unwrap(),
|
Selector::parse("span.story-byline").unwrap(),
|
||||||
Selector::parse("div.p").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(),
|
|
||||||
],
|
|
||||||
"www.redox-os.org".to_string() => vec![
|
"www.redox-os.org".to_string() => vec![
|
||||||
Selector::parse("div.content").unwrap(),
|
Selector::parse("div.content").unwrap(),
|
||||||
],
|
],
|
||||||
@ -259,12 +246,12 @@ pub async fn thread(
|
|||||||
}),
|
}),
|
||||||
Box::new(FrameImages),
|
Box::new(FrameImages),
|
||||||
Box::new(AddOutlink),
|
Box::new(AddOutlink),
|
||||||
// TODO: causes doubling of images in cloudflare blogs
|
Box::new(EscapeHtml),
|
||||||
//Box::new(EscapeHtml),
|
|
||||||
Box::new(SanitizeHtml {
|
Box::new(SanitizeHtml {
|
||||||
cid_prefix: "",
|
cid_prefix: "",
|
||||||
base_url: &link,
|
base_url: &link,
|
||||||
}),
|
}),
|
||||||
|
Box::new(InlineStyle),
|
||||||
];
|
];
|
||||||
for t in body_tranformers.iter() {
|
for t in body_tranformers.iter() {
|
||||||
if t.should_run(&link, &body) {
|
if t.should_run(&link, &body) {
|
||||||
|
|||||||
@ -9,6 +9,7 @@ use log::{error, info, warn};
|
|||||||
use mailparse::{parse_content_type, parse_mail, MailHeader, MailHeaderMap, ParsedMail};
|
use mailparse::{parse_content_type, parse_mail, MailHeader, MailHeaderMap, ParsedMail};
|
||||||
use memmap::MmapOptions;
|
use memmap::MmapOptions;
|
||||||
use notmuch::Notmuch;
|
use notmuch::Notmuch;
|
||||||
|
use rocket::http::uri::error::PathError;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
@ -223,7 +224,7 @@ pub async fn thread(
|
|||||||
}
|
}
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
r#"<p class="view-part-text-plain font-mono whitespace-pre">{}</p>"#,
|
r#"<p class="view-part-text-plain">{}</p>"#,
|
||||||
// Trim newlines to prevent excessive white space at the beginning/end of
|
// Trim newlines to prevent excessive white space at the beginning/end of
|
||||||
// presenation. Leave tabs and spaces incase plain text attempts to center a
|
// presenation. Leave tabs and spaces incase plain text attempts to center a
|
||||||
// header on the first line.
|
// header on the first line.
|
||||||
@ -578,7 +579,7 @@ fn flatten_body_parts(parts: &[Body]) -> Body {
|
|||||||
.map(|p| match p {
|
.map(|p| match p {
|
||||||
Body::PlainText(PlainText { text, .. }) => {
|
Body::PlainText(PlainText { text, .. }) => {
|
||||||
format!(
|
format!(
|
||||||
r#"<p class="view-part-text-plain font-mono whitespace-pre">{}</p>"#,
|
r#"<p class="view-part-text-plain">{}</p>"#,
|
||||||
// Trim newlines to prevent excessive white space at the beginning/end of
|
// Trim newlines to prevent excessive white space at the beginning/end of
|
||||||
// presenation. Leave tabs and spaces incase plain text attempts to center a
|
// presenation. Leave tabs and spaces incase plain text attempts to center a
|
||||||
// header on the first line.
|
// header on the first line.
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "shared"
|
name = "shared"
|
||||||
version = "0.0.116"
|
version = "0.0.115"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
version = "0.0.116"
|
version = "0.0.115"
|
||||||
name = "letterbox"
|
name = "letterbox"
|
||||||
repository = "https://github.com/seed-rs/seed-quickstart"
|
repository = "https://github.com/seed-rs/seed-quickstart"
|
||||||
authors = ["Bill Thiede <git@xinu.tv>"]
|
authors = ["Bill Thiede <git@xinu.tv>"]
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
[build]
|
[build]
|
||||||
release = false
|
release = true
|
||||||
|
|
||||||
[serve]
|
[serve]
|
||||||
# The address to serve on.
|
# The address to serve on.
|
||||||
|
|||||||
@ -4,18 +4,22 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<!--
|
||||||
|
-->
|
||||||
|
<link rel="stylesheet" href="https://jenil.github.io/bulmaswatch/cyborg/bulmaswatch.min.css">
|
||||||
|
<!-- Pretty checkboxes from https://justboil.github.io/bulma-checkbox/ -->
|
||||||
|
<link data-trunk rel="css" href="static/main.css" />
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.3.0/css/all.min.css"
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.3.0/css/all.min.css"
|
||||||
integrity="sha512-SzlrxWUlpfuzQ+pcUCosxcglQRNAq/DZjVsC0lE40xsADsfeQoEypE+enwcOiGjk/bSuGGKHEyjSoQ1zVisanQ=="
|
integrity="sha512-SzlrxWUlpfuzQ+pcUCosxcglQRNAq/DZjVsC0lE40xsADsfeQoEypE+enwcOiGjk/bSuGGKHEyjSoQ1zVisanQ=="
|
||||||
crossorigin="anonymous" referrerpolicy="no-referrer" />
|
crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||||
<link rel="icon" href="https://static.xinu.tv/favicon/letterbox.svg" />
|
<link rel="icon" href="https://static.xinu.tv/favicon/letterbox.svg" />
|
||||||
|
<link data-trunk rel="css" href="static/style.css" />
|
||||||
<!-- tall thin font for user icon -->
|
<!-- tall thin font for user icon -->
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<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 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/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="tailwind-css" href="./src/tailwind.css" />
|
||||||
<link data-trunk rel="css" href="static/overrides.css" />
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@ -507,7 +507,6 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
Msg::WindowScrolled => {
|
Msg::WindowScrolled => {
|
||||||
info!("WindowScrolled");
|
|
||||||
if let Some(el) = model.content_el.get() {
|
if let Some(el) = model.content_el.get() {
|
||||||
let ih = window()
|
let ih = window()
|
||||||
.inner_height()
|
.inner_height()
|
||||||
@ -516,7 +515,6 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
.value_of();
|
.value_of();
|
||||||
|
|
||||||
let r = el.get_bounding_client_rect();
|
let r = el.get_bounding_client_rect();
|
||||||
info!("r {r:?} ih {ih}");
|
|
||||||
if r.height() < ih {
|
if r.height() < ih {
|
||||||
// The whole content fits in the window, no scrollbar
|
// The whole content fits in the window, no scrollbar
|
||||||
orders.send_msg(Msg::SetProgress(0.));
|
orders.send_msg(Msg::SetProgress(0.));
|
||||||
|
|||||||
49
web/src/view/desktop.rs
Normal file
49
web/src/view/desktop.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
use seed::{prelude::*, *};
|
||||||
|
use seed_hooks::topo;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
graphql::show_thread_query::*,
|
||||||
|
state::{Context, Model, Msg},
|
||||||
|
view::{self, reading_progress, view_header, view_search_results},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[topo::nested]
|
||||||
|
pub(super) fn view(model: &Model) -> Node<Msg> {
|
||||||
|
let show_icon_text = true;
|
||||||
|
// Do two queries, one without `unread` so it loads fast, then a second with unread.
|
||||||
|
let content = match &model.context {
|
||||||
|
Context::None => div![h1!["Loading"]],
|
||||||
|
Context::ThreadResult {
|
||||||
|
thread: ShowThreadQueryThread::EmailThread(thread),
|
||||||
|
open_messages,
|
||||||
|
} => view::thread(thread, open_messages, show_icon_text, &model.content_el),
|
||||||
|
Context::ThreadResult {
|
||||||
|
thread: ShowThreadQueryThread::NewsPost(post),
|
||||||
|
..
|
||||||
|
} => view::news_post(post, show_icon_text, &model.content_el),
|
||||||
|
Context::SearchResult {
|
||||||
|
query,
|
||||||
|
results,
|
||||||
|
count,
|
||||||
|
pager,
|
||||||
|
selected_threads,
|
||||||
|
} => view_search_results(
|
||||||
|
&query,
|
||||||
|
results.as_slice(),
|
||||||
|
*count,
|
||||||
|
pager,
|
||||||
|
selected_threads,
|
||||||
|
show_icon_text,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
div![
|
||||||
|
C!["main-content"],
|
||||||
|
reading_progress(model.read_completion_ratio),
|
||||||
|
div![view::tags(model), view::versions(&model.versions)],
|
||||||
|
div![
|
||||||
|
view_header(&model.query, &model.refreshing_state),
|
||||||
|
content,
|
||||||
|
view_header(&model.query, &model.refreshing_state),
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
121
web/src/view/mobile.rs
Normal file
121
web/src/view/mobile.rs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use seed::{prelude::*, *};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
api::urls,
|
||||||
|
graphql::{front_page_query::*, show_thread_query::*},
|
||||||
|
state::{Context, Model, Msg},
|
||||||
|
view::{
|
||||||
|
self, human_age, pretty_authors, reading_progress, search_toolbar, set_title, tags_chiclet,
|
||||||
|
view_header,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(super) fn view(model: &Model) -> Node<Msg> {
|
||||||
|
let show_icon_text = false;
|
||||||
|
let content = match &model.context {
|
||||||
|
Context::None => div![h1!["Loading"]],
|
||||||
|
Context::ThreadResult {
|
||||||
|
thread: ShowThreadQueryThread::EmailThread(thread),
|
||||||
|
open_messages,
|
||||||
|
} => view::thread(thread, open_messages, show_icon_text, &model.content_el),
|
||||||
|
Context::ThreadResult {
|
||||||
|
thread: ShowThreadQueryThread::NewsPost(post),
|
||||||
|
..
|
||||||
|
} => view::news_post(post, show_icon_text, &model.content_el),
|
||||||
|
Context::SearchResult {
|
||||||
|
query,
|
||||||
|
results,
|
||||||
|
count,
|
||||||
|
pager,
|
||||||
|
selected_threads,
|
||||||
|
} => search_results(
|
||||||
|
&query,
|
||||||
|
results.as_slice(),
|
||||||
|
*count,
|
||||||
|
pager,
|
||||||
|
selected_threads,
|
||||||
|
show_icon_text,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
div![
|
||||||
|
reading_progress(model.read_completion_ratio),
|
||||||
|
view_header(&model.query, &model.refreshing_state),
|
||||||
|
content,
|
||||||
|
view_header(&model.query, &model.refreshing_state),
|
||||||
|
div![view::tags(model), view::versions(&model.versions)]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_results(
|
||||||
|
query: &str,
|
||||||
|
results: &[FrontPageQuerySearchNodes],
|
||||||
|
count: usize,
|
||||||
|
pager: &FrontPageQuerySearchPageInfo,
|
||||||
|
selected_threads: &HashSet<String>,
|
||||||
|
show_icon_text: bool,
|
||||||
|
) -> Node<Msg> {
|
||||||
|
if query.is_empty() {
|
||||||
|
set_title("all mail");
|
||||||
|
} else {
|
||||||
|
set_title(query);
|
||||||
|
}
|
||||||
|
let rows = results.iter().map(|r| {
|
||||||
|
let tid = r.thread.clone();
|
||||||
|
let check_tid = r.thread.clone();
|
||||||
|
let datetime = human_age(r.timestamp as i64);
|
||||||
|
let unread_idx = r.tags.iter().position(|e| e == &"unread");
|
||||||
|
let mut tags = r.tags.clone();
|
||||||
|
if let Some(idx) = unread_idx {
|
||||||
|
tags.remove(idx);
|
||||||
|
};
|
||||||
|
div![
|
||||||
|
C!["row"],
|
||||||
|
label![
|
||||||
|
C!["b-checkbox", "checkbox", "is-large"],
|
||||||
|
input![attrs! {
|
||||||
|
At::Type=>"checkbox",
|
||||||
|
At::Checked=>selected_threads.contains(&tid).as_at_value(),
|
||||||
|
}],
|
||||||
|
span![C!["check"]],
|
||||||
|
ev(Ev::Input, move |e| {
|
||||||
|
if let Some(input) = e
|
||||||
|
.target()
|
||||||
|
.as_ref()
|
||||||
|
.expect("failed to get reference to target")
|
||||||
|
.dyn_ref::<web_sys::HtmlInputElement>()
|
||||||
|
{
|
||||||
|
if input.checked() {
|
||||||
|
Msg::SelectionAddThread(check_tid)
|
||||||
|
} else {
|
||||||
|
Msg::SelectionRemoveThread(check_tid)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Msg::Noop
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
a![
|
||||||
|
C!["has-text-light", "summary"],
|
||||||
|
IF!(unread_idx.is_some() => C!["unread"]),
|
||||||
|
attrs! {
|
||||||
|
At::Href => urls::thread(&tid)
|
||||||
|
},
|
||||||
|
div![C!["subject"], &r.subject],
|
||||||
|
span![C!["from", "is-size-7"], pretty_authors(&r.authors)],
|
||||||
|
div![
|
||||||
|
span![C!["is-size-7"], tags_chiclet(&tags, true)],
|
||||||
|
span![C!["is-size-7", "float-right", "date"], datetime]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
});
|
||||||
|
let show_bulk_edit = !selected_threads.is_empty();
|
||||||
|
div![
|
||||||
|
C!["search-results"],
|
||||||
|
search_toolbar(count, pager, show_bulk_edit, show_icon_text),
|
||||||
|
div![C!["index"], rows],
|
||||||
|
search_toolbar(count, pager, show_bulk_edit, show_icon_text),
|
||||||
|
]
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
48
web/src/view/tablet.rs
Normal file
48
web/src/view/tablet.rs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
use seed::{prelude::*, *};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
graphql::show_thread_query::*,
|
||||||
|
state::{Context, Model, Msg},
|
||||||
|
view::{self, reading_progress, view_header, view_search_results},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(super) fn view(model: &Model) -> Node<Msg> {
|
||||||
|
let show_icon_text = false;
|
||||||
|
// Do two queries, one without `unread` so it loads fast, then a second with unread.
|
||||||
|
let content = match &model.context {
|
||||||
|
Context::None => div![h1!["Loading"]],
|
||||||
|
Context::ThreadResult {
|
||||||
|
thread: ShowThreadQueryThread::EmailThread(thread),
|
||||||
|
open_messages,
|
||||||
|
} => view::thread(thread, open_messages, show_icon_text, &model.content_el),
|
||||||
|
Context::ThreadResult {
|
||||||
|
thread: ShowThreadQueryThread::NewsPost(post),
|
||||||
|
..
|
||||||
|
} => view::news_post(post, show_icon_text, &model.content_el),
|
||||||
|
Context::SearchResult {
|
||||||
|
query,
|
||||||
|
results,
|
||||||
|
count,
|
||||||
|
pager,
|
||||||
|
selected_threads,
|
||||||
|
} => view_search_results(
|
||||||
|
&query,
|
||||||
|
results.as_slice(),
|
||||||
|
*count,
|
||||||
|
pager,
|
||||||
|
selected_threads,
|
||||||
|
show_icon_text,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
div![
|
||||||
|
C!["main-content"],
|
||||||
|
div![
|
||||||
|
reading_progress(model.read_completion_ratio),
|
||||||
|
view_header(&model.query, &model.refreshing_state),
|
||||||
|
content,
|
||||||
|
view_header(&model.query, &model.refreshing_state),
|
||||||
|
view::tags(model),
|
||||||
|
view::versions(&model.versions)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
268
web/static/main.css
Normal file
268
web/static/main.css
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
/* Bulma Utilities */
|
||||||
|
.b-checkbox.checkbox {
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Box-shadow on hover */
|
||||||
|
.b-checkbox.checkbox {
|
||||||
|
outline: none;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox:not(.button) {
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox:not(.button) + .checkbox:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox] {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
opacity: 0;
|
||||||
|
outline: none;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox] + .check {
|
||||||
|
width: 1.25em;
|
||||||
|
height: 1.25em;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 2px solid #7a7a7a;
|
||||||
|
transition: background 150ms ease-out;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:checked + .check {
|
||||||
|
background: #00d1b2 url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Cpath style='fill:%23fff' d='M 0.04038059,0.6267767 0.14644661,0.52071068 0.42928932,0.80355339 0.3232233,0.90961941 z M 0.21715729,0.80355339 0.85355339,0.16715729 0.95961941,0.2732233 0.3232233,0.90961941 z'%3E%3C/path%3E%3C/svg%3E") no-repeat center center;
|
||||||
|
border-color: #00d1b2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:checked + .check.is-white {
|
||||||
|
background: white url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Cpath style='fill:%230a0a0a' d='M 0.04038059,0.6267767 0.14644661,0.52071068 0.42928932,0.80355339 0.3232233,0.90961941 z M 0.21715729,0.80355339 0.85355339,0.16715729 0.95961941,0.2732233 0.3232233,0.90961941 z'%3E%3C/path%3E%3C/svg%3E") no-repeat center center;
|
||||||
|
border-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:checked + .check.is-black {
|
||||||
|
background: #0a0a0a url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Cpath style='fill:white' d='M 0.04038059,0.6267767 0.14644661,0.52071068 0.42928932,0.80355339 0.3232233,0.90961941 z M 0.21715729,0.80355339 0.85355339,0.16715729 0.95961941,0.2732233 0.3232233,0.90961941 z'%3E%3C/path%3E%3C/svg%3E") no-repeat center center;
|
||||||
|
border-color: #0a0a0a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:checked + .check.is-light {
|
||||||
|
background: whitesmoke url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Cpath style='fill:rgba(0, 0, 0, 0.7)' d='M 0.04038059,0.6267767 0.14644661,0.52071068 0.42928932,0.80355339 0.3232233,0.90961941 z M 0.21715729,0.80355339 0.85355339,0.16715729 0.95961941,0.2732233 0.3232233,0.90961941 z'%3E%3C/path%3E%3C/svg%3E") no-repeat center center;
|
||||||
|
border-color: whitesmoke;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:checked + .check.is-dark {
|
||||||
|
background: #363636 url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Cpath style='fill:%23fff' d='M 0.04038059,0.6267767 0.14644661,0.52071068 0.42928932,0.80355339 0.3232233,0.90961941 z M 0.21715729,0.80355339 0.85355339,0.16715729 0.95961941,0.2732233 0.3232233,0.90961941 z'%3E%3C/path%3E%3C/svg%3E") no-repeat center center;
|
||||||
|
border-color: #363636;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:checked + .check.is-primary {
|
||||||
|
background: #00d1b2 url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Cpath style='fill:%23fff' d='M 0.04038059,0.6267767 0.14644661,0.52071068 0.42928932,0.80355339 0.3232233,0.90961941 z M 0.21715729,0.80355339 0.85355339,0.16715729 0.95961941,0.2732233 0.3232233,0.90961941 z'%3E%3C/path%3E%3C/svg%3E") no-repeat center center;
|
||||||
|
border-color: #00d1b2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:checked + .check.is-link {
|
||||||
|
background: #485fc7 url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Cpath style='fill:%23fff' d='M 0.04038059,0.6267767 0.14644661,0.52071068 0.42928932,0.80355339 0.3232233,0.90961941 z M 0.21715729,0.80355339 0.85355339,0.16715729 0.95961941,0.2732233 0.3232233,0.90961941 z'%3E%3C/path%3E%3C/svg%3E") no-repeat center center;
|
||||||
|
border-color: #485fc7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:checked + .check.is-info {
|
||||||
|
background: #3e8ed0 url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Cpath style='fill:%23fff' d='M 0.04038059,0.6267767 0.14644661,0.52071068 0.42928932,0.80355339 0.3232233,0.90961941 z M 0.21715729,0.80355339 0.85355339,0.16715729 0.95961941,0.2732233 0.3232233,0.90961941 z'%3E%3C/path%3E%3C/svg%3E") no-repeat center center;
|
||||||
|
border-color: #3e8ed0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:checked + .check.is-success {
|
||||||
|
background: #48c78e url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Cpath style='fill:%23fff' d='M 0.04038059,0.6267767 0.14644661,0.52071068 0.42928932,0.80355339 0.3232233,0.90961941 z M 0.21715729,0.80355339 0.85355339,0.16715729 0.95961941,0.2732233 0.3232233,0.90961941 z'%3E%3C/path%3E%3C/svg%3E") no-repeat center center;
|
||||||
|
border-color: #48c78e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:checked + .check.is-warning {
|
||||||
|
background: #ffe08a url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Cpath style='fill:rgba(0, 0, 0, 0.7)' d='M 0.04038059,0.6267767 0.14644661,0.52071068 0.42928932,0.80355339 0.3232233,0.90961941 z M 0.21715729,0.80355339 0.85355339,0.16715729 0.95961941,0.2732233 0.3232233,0.90961941 z'%3E%3C/path%3E%3C/svg%3E") no-repeat center center;
|
||||||
|
border-color: #ffe08a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:checked + .check.is-danger {
|
||||||
|
background: #f14668 url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Cpath style='fill:%23fff' d='M 0.04038059,0.6267767 0.14644661,0.52071068 0.42928932,0.80355339 0.3232233,0.90961941 z M 0.21715729,0.80355339 0.85355339,0.16715729 0.95961941,0.2732233 0.3232233,0.90961941 z'%3E%3C/path%3E%3C/svg%3E") no-repeat center center;
|
||||||
|
border-color: #f14668;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:indeterminate + .check, .b-checkbox.checkbox input[type=checkbox].is-indeterminate + .check {
|
||||||
|
background: #00d1b2 url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Crect style='fill:%23fff' width='0.7' height='0.2' x='.15' y='.4'%3E%3C/rect%3E%3C/svg%3E") no-repeat center center;
|
||||||
|
border-color: #00d1b2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:indeterminate + .check.is-white, .b-checkbox.checkbox input[type=checkbox].is-indeterminate + .check.is-white {
|
||||||
|
background: white url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Crect style='fill:%230a0a0a' width='0.7' height='0.2' x='.15' y='.4'%3E%3C/rect%3E%3C/svg%3E") no-repeat center center;
|
||||||
|
border-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:indeterminate + .check.is-black, .b-checkbox.checkbox input[type=checkbox].is-indeterminate + .check.is-black {
|
||||||
|
background: #0a0a0a url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Crect style='fill:white' width='0.7' height='0.2' x='.15' y='.4'%3E%3C/rect%3E%3C/svg%3E") no-repeat center center;
|
||||||
|
border-color: #0a0a0a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:indeterminate + .check.is-light, .b-checkbox.checkbox input[type=checkbox].is-indeterminate + .check.is-light {
|
||||||
|
background: whitesmoke url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Crect style='fill:rgba(0, 0, 0, 0.7)' width='0.7' height='0.2' x='.15' y='.4'%3E%3C/rect%3E%3C/svg%3E") no-repeat center center;
|
||||||
|
border-color: whitesmoke;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:indeterminate + .check.is-dark, .b-checkbox.checkbox input[type=checkbox].is-indeterminate + .check.is-dark {
|
||||||
|
background: #363636 url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Crect style='fill:%23fff' width='0.7' height='0.2' x='.15' y='.4'%3E%3C/rect%3E%3C/svg%3E") no-repeat center center;
|
||||||
|
border-color: #363636;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:indeterminate + .check.is-primary, .b-checkbox.checkbox input[type=checkbox].is-indeterminate + .check.is-primary {
|
||||||
|
background: #00d1b2 url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Crect style='fill:%23fff' width='0.7' height='0.2' x='.15' y='.4'%3E%3C/rect%3E%3C/svg%3E") no-repeat center center;
|
||||||
|
border-color: #00d1b2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:indeterminate + .check.is-link, .b-checkbox.checkbox input[type=checkbox].is-indeterminate + .check.is-link {
|
||||||
|
background: #485fc7 url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Crect style='fill:%23fff' width='0.7' height='0.2' x='.15' y='.4'%3E%3C/rect%3E%3C/svg%3E") no-repeat center center;
|
||||||
|
border-color: #485fc7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:indeterminate + .check.is-info, .b-checkbox.checkbox input[type=checkbox].is-indeterminate + .check.is-info {
|
||||||
|
background: #3e8ed0 url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Crect style='fill:%23fff' width='0.7' height='0.2' x='.15' y='.4'%3E%3C/rect%3E%3C/svg%3E") no-repeat center center;
|
||||||
|
border-color: #3e8ed0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:indeterminate + .check.is-success, .b-checkbox.checkbox input[type=checkbox].is-indeterminate + .check.is-success {
|
||||||
|
background: #48c78e url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Crect style='fill:%23fff' width='0.7' height='0.2' x='.15' y='.4'%3E%3C/rect%3E%3C/svg%3E") no-repeat center center;
|
||||||
|
border-color: #48c78e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:indeterminate + .check.is-warning, .b-checkbox.checkbox input[type=checkbox].is-indeterminate + .check.is-warning {
|
||||||
|
background: #ffe08a url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Crect style='fill:rgba(0, 0, 0, 0.7)' width='0.7' height='0.2' x='.15' y='.4'%3E%3C/rect%3E%3C/svg%3E") no-repeat center center;
|
||||||
|
border-color: #ffe08a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:indeterminate + .check.is-danger, .b-checkbox.checkbox input[type=checkbox].is-indeterminate + .check.is-danger {
|
||||||
|
background: #f14668 url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Crect style='fill:%23fff' width='0.7' height='0.2' x='.15' y='.4'%3E%3C/rect%3E%3C/svg%3E") no-repeat center center;
|
||||||
|
border-color: #f14668;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:focus + .check {
|
||||||
|
box-shadow: 0 0 0.5em rgba(122, 122, 122, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:focus:checked + .check {
|
||||||
|
box-shadow: 0 0 0.5em rgba(0, 209, 178, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:focus:checked + .check.is-white {
|
||||||
|
box-shadow: 0 0 0.5em rgba(255, 255, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:focus:checked + .check.is-black {
|
||||||
|
box-shadow: 0 0 0.5em rgba(10, 10, 10, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:focus:checked + .check.is-light {
|
||||||
|
box-shadow: 0 0 0.5em rgba(245, 245, 245, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:focus:checked + .check.is-dark {
|
||||||
|
box-shadow: 0 0 0.5em rgba(54, 54, 54, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:focus:checked + .check.is-primary {
|
||||||
|
box-shadow: 0 0 0.5em rgba(0, 209, 178, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:focus:checked + .check.is-link {
|
||||||
|
box-shadow: 0 0 0.5em rgba(72, 95, 199, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:focus:checked + .check.is-info {
|
||||||
|
box-shadow: 0 0 0.5em rgba(62, 142, 208, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:focus:checked + .check.is-success {
|
||||||
|
box-shadow: 0 0 0.5em rgba(72, 199, 142, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:focus:checked + .check.is-warning {
|
||||||
|
box-shadow: 0 0 0.5em rgba(255, 224, 138, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox]:focus:checked + .check.is-danger {
|
||||||
|
box-shadow: 0 0 0.5em rgba(241, 70, 104, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox .control-label {
|
||||||
|
padding-left: calc(0.75em - 1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox.button {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox[disabled] {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox:hover input[type=checkbox]:not(:disabled) + .check {
|
||||||
|
border-color: #00d1b2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox:hover input[type=checkbox]:not(:disabled) + .check.is-white {
|
||||||
|
border-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox:hover input[type=checkbox]:not(:disabled) + .check.is-black {
|
||||||
|
border-color: #0a0a0a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox:hover input[type=checkbox]:not(:disabled) + .check.is-light {
|
||||||
|
border-color: whitesmoke;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox:hover input[type=checkbox]:not(:disabled) + .check.is-dark {
|
||||||
|
border-color: #363636;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox:hover input[type=checkbox]:not(:disabled) + .check.is-primary {
|
||||||
|
border-color: #00d1b2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox:hover input[type=checkbox]:not(:disabled) + .check.is-link {
|
||||||
|
border-color: #485fc7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox:hover input[type=checkbox]:not(:disabled) + .check.is-info {
|
||||||
|
border-color: #3e8ed0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox:hover input[type=checkbox]:not(:disabled) + .check.is-success {
|
||||||
|
border-color: #48c78e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox:hover input[type=checkbox]:not(:disabled) + .check.is-warning {
|
||||||
|
border-color: #ffe08a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox:hover input[type=checkbox]:not(:disabled) + .check.is-danger {
|
||||||
|
border-color: #f14668;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox.is-small {
|
||||||
|
border-radius: 2px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox.is-medium {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox.is-large {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
@ -1,33 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
: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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user