* Theming

- Custom CSS for blogs
- Custom themes for instance
- New dark theme
- UI to choose your instance theme
- Option to disable blog themes if you prefer to only have the instance theme
- UI to choose a blog theme
This commit is contained in:
Ana Gelez
2019-08-21 00:42:04 +02:00
committed by GitHub
parent fb60236a54
commit a6c84daa1a
203 changed files with 667 additions and 334 deletions
+1
View File
@@ -23,6 +23,7 @@ serde_derive = "1.0"
serde_json = "1.0"
tantivy = "0.10.1"
url = "2.1"
walkdir = "2.2"
webfinger = "0.4.1"
whatlang = "0.7.1"
shrinkwraprs = "0.2.1"
+4 -1
View File
@@ -44,6 +44,7 @@ pub struct Blog {
pub summary_html: SafeString,
pub icon_id: Option<i32>,
pub banner_id: Option<i32>,
pub theme: Option<String>,
}
#[derive(Default, Insertable)]
@@ -61,6 +62,7 @@ pub struct NewBlog {
pub summary_html: SafeString,
pub icon_id: Option<i32>,
pub banner_id: Option<i32>,
pub theme: Option<String>,
}
const BLOG_PREFIX: &str = "~";
@@ -272,7 +274,7 @@ impl Blog {
pub fn icon_url(&self, conn: &Connection) -> String {
self.icon_id
.and_then(|id| Media::get(conn, id).and_then(|m| m.url()).ok())
.unwrap_or_else(|| "/static/default-avatar.png".to_string())
.unwrap_or_else(|| "/static/images/default-avatar.png".to_string())
}
pub fn banner_url(&self, conn: &Connection) -> Option<String> {
@@ -392,6 +394,7 @@ impl FromId<PlumeRocket> for Blog {
.summary_string()
.unwrap_or_default(),
),
theme: None,
},
)
}
+2
View File
@@ -14,6 +14,7 @@ pub struct Config {
pub search_index: String,
pub rocket: Result<RocketConfig, RocketError>,
pub logo: LogoConfig,
pub default_theme: String,
}
#[derive(Debug, Clone)]
@@ -199,5 +200,6 @@ lazy_static! {
search_index: var("SEARCH_INDEX").unwrap_or_else(|_| "search_index".to_owned()),
rocket: get_rocket_config(),
logo: LogoConfig::default(),
default_theme: var("DEFAULT_THEME").unwrap_or_else(|_| "default-light".to_owned()),
};
}
+54
View File
@@ -186,6 +186,60 @@ impl Instance {
.get_result(conn)
.map_err(Error::from)
}
/// Returns a list of the local instance themes (all files matching `static/css/NAME/theme.css`)
///
/// The list only contains the name of the themes, without their extension or full path.
pub fn list_themes() -> Result<Vec<String>> {
// List all the files in static/css
std::path::Path::new("static")
.join("css")
.read_dir()
.map(|files| {
files
.filter_map(std::result::Result::ok)
// Only keep actual directories (each theme has its own dir)
.filter(|f| f.file_type().map(|t| t.is_dir()).unwrap_or(false))
// Only keep the directory name (= theme name)
.filter_map(|f| {
f.path()
.file_name()
.and_then(std::ffi::OsStr::to_str)
.map(std::borrow::ToOwned::to_owned)
})
// Ignore the one starting with "blog-": these are the blog themes
.filter(|f| !f.starts_with("blog-"))
.collect()
})
.map_err(Error::from)
}
/// Returns a list of the local blog themes (all files matching `static/css/blog-NAME/theme.css`)
///
/// The list only contains the name of the themes, without their extension or full path.
pub fn list_blog_themes() -> Result<Vec<String>> {
// List all the files in static/css
std::path::Path::new("static")
.join("css")
.read_dir()
.map(|files| {
files
.filter_map(std::result::Result::ok)
// Only keep actual directories (each theme has its own dir)
.filter(|f| f.file_type().map(|t| t.is_dir()).unwrap_or(false))
// Only keep the directory name (= theme name)
.filter_map(|f| {
f.path()
.file_name()
.and_then(std::ffi::OsStr::to_str)
.map(std::borrow::ToOwned::to_owned)
})
// Only keep the one starting with "blog-": these are the blog themes
.filter(|f| f.starts_with("blog-"))
.collect()
})
.map_err(Error::from)
}
}
#[cfg(test)]
+1
View File
@@ -33,6 +33,7 @@ extern crate serde_json;
#[macro_use]
extern crate tantivy;
extern crate url;
extern crate walkdir;
extern crate webfinger;
extern crate whatlang;
+3
View File
@@ -47,6 +47,7 @@ table! {
summary_html -> Text,
icon_id -> Nullable<Int4>,
banner_id -> Nullable<Int4>,
theme -> Nullable<Varchar>,
}
}
@@ -216,6 +217,8 @@ table! {
last_fetched_date -> Timestamp,
fqn -> Text,
summary_html -> Text,
preferred_theme -> Nullable<Varchar>,
hide_custom_css -> Bool,
}
}
+3 -46
View File
@@ -73,6 +73,8 @@ pub struct User {
pub last_fetched_date: NaiveDateTime,
pub fqn: String,
pub summary_html: SafeString,
pub preferred_theme: Option<String>,
pub hide_custom_css: bool,
}
#[derive(Default, Insertable)]
@@ -202,30 +204,6 @@ impl User {
.map_err(Error::from)
}
pub fn update(
&self,
conn: &Connection,
name: String,
email: String,
summary: String,
) -> Result<User> {
diesel::update(self)
.set((
users::display_name.eq(name),
users::email.eq(email),
users::summary_html.eq(utils::md_to_html(
&summary,
None,
false,
Some(Media::get_media_processor(conn, vec![self])),
)
.0),
users::summary.eq(summary),
))
.execute(conn)?;
User::get(conn, self.id)
}
pub fn count_local(conn: &Connection) -> Result<i64> {
users::table
.filter(users::instance_id.eq(Instance::get_local()?.id))
@@ -668,7 +646,7 @@ impl User {
pub fn avatar_url(&self, conn: &Connection) -> String {
self.avatar_id
.and_then(|id| Media::get(conn, id).and_then(|m| m.url()).ok())
.unwrap_or_else(|| "/static/default-avatar.png".to_string())
.unwrap_or_else(|| "/static/images/default-avatar.png".to_string())
}
pub fn webfinger(&self, conn: &Connection) -> Result<Webfinger> {
@@ -1076,27 +1054,6 @@ pub(crate) mod tests {
});
}
#[test]
fn update() {
let conn = &db();
conn.test_transaction::<_, (), _>(|| {
let inserted = fill_database(conn);
let updated = inserted[0]
.update(
conn,
"new name".to_owned(),
"em@il".to_owned(),
"<p>summary</p><script></script>".to_owned(),
)
.unwrap();
assert_eq!(updated.display_name, "new name");
assert_eq!(updated.email.unwrap(), "em@il");
assert_eq!(updated.summary_html.get(), "<p>summary</p>");
Ok(())
});
}
#[test]
fn auth() {
let conn = &db();