diff --git a/assets/style.css b/assets/style.css index 613a3d8..b93f87c 100644 --- a/assets/style.css +++ b/assets/style.css @@ -61,6 +61,17 @@ article>hr { margin: 20px auto; } +.notice { + background: lightyellow; + padding: 32px 48px; + + box-sizing: border-box; + max-width: 616px; + width: 100%; + margin-left: auto; + margin-right: auto; +} + header, article>* { box-sizing: border-box; max-width: 616px; diff --git a/src/resources/article_resource.rs b/src/resources/article_resource.rs index da3fb83..d4203e1 100644 --- a/src/resources/article_resource.rs +++ b/src/resources/article_resource.rs @@ -67,7 +67,7 @@ impl Resource for ArticleResource { fn get(self: Box) -> ResponseFuture { #[derive(BartDisplay)] - #[template="templates/article_revision.html"] + #[template="templates/article.html"] struct Template<'a> { revision: i32, last_updated: Option<&'a str>, @@ -123,7 +123,7 @@ impl Resource for ArticleResource { } #[derive(BartDisplay)] - #[template="templates/article_revision_contents.html"] + #[template="templates/article_contents.html"] struct Template<'a> { title: &'a str, rendered: String, diff --git a/src/resources/article_revision_resource.rs b/src/resources/article_revision_resource.rs new file mode 100644 index 0000000..bc12f48 --- /dev/null +++ b/src/resources/article_revision_resource.rs @@ -0,0 +1,111 @@ +use chrono::{TimeZone, DateTime, Local}; +use futures::{self, Future}; +use hyper; +use hyper::header::ContentType; +use hyper::server::*; + +use assets::StyleCss; +use mimes::*; +use models; +use rendering::render_markdown; +use site::Layout; +use web::{Resource, ResponseFuture}; + +use super::changes_resource::QueryParameters; +use super::pagination::Pagination; + +pub struct ArticleRevisionResource { + data: models::ArticleRevision, +} + +impl ArticleRevisionResource { + pub fn new(data: models::ArticleRevision) -> Self { + Self { data } + } +} + +pub fn timestamp_and_author(sequence_number: i32, article_id: i32, created: &DateTime, author: Option<&str>) -> String { + struct Author<'a> { + author: &'a str, + history: String, + } + + #[derive(BartDisplay)] + #[template_string = "{{created}}{{#author}} by {{.author}}{{/author}}"] + struct Template<'a> { + created: &'a str, + article_history: &'a str, + author: Option>, + } + + let pagination = Pagination::Before(sequence_number + 1); + + Template { + created: &created.to_rfc2822(), + article_history: &format!("_changes{}", + QueryParameters::default() + .pagination(pagination) + .article_id(Some(article_id)) + .into_link() + ), + author: author.map(|author| Author { + author: &author, + history: format!("_changes{}", + QueryParameters::default() + .pagination(pagination) + .author(Some(author.to_owned())) + .into_link() + ), + }), + }.to_string() +} + +impl Resource for ArticleRevisionResource { + fn allow(&self) -> Vec { + use hyper::Method::*; + vec![Options, Head, Get, Put] + } + + fn head(&self) -> ResponseFuture { + Box::new(futures::finished(Response::new() + .with_status(hyper::StatusCode::Ok) + .with_header(ContentType(TEXT_HTML.clone())) + )) + } + + fn get(self: Box) -> ResponseFuture { + #[derive(BartDisplay)] + #[template="templates/article_revision.html"] + struct Template<'a> { + link_current: &'a str, + timestamp_and_author: &'a str, + + title: &'a str, + rendered: String, + } + + let head = self.head(); + let data = self.data; + + Box::new(head + .and_then(move |head| + Ok(head + .with_body(Layout { + base: Some("../../"), // Hmm, should perhaps accept `base` as argument + title: &data.title, + body: &Template { + link_current: &format!("_by_id/{}", data.article_id), + timestamp_and_author: ×tamp_and_author( + data.sequence_number, + data.article_id, + &Local.from_utc_datetime(&data.created), + data.author.as_ref().map(|x| &**x) + ), + title: &data.title, + rendered: render_markdown(&data.body), + }, + style_css_checksum: StyleCss::checksum(), + }.to_string())) + )) + } +} diff --git a/src/resources/mod.rs b/src/resources/mod.rs index 8fdf8bd..a033249 100644 --- a/src/resources/mod.rs +++ b/src/resources/mod.rs @@ -1,6 +1,7 @@ pub mod pagination; mod article_redirect_resource; +mod article_revision_resource; mod article_resource; mod changes_resource; mod new_article_resource; @@ -8,6 +9,7 @@ mod sitemap_resource; mod temporary_redirect_resource; pub use self::article_redirect_resource::ArticleRedirectResource; +pub use self::article_revision_resource::ArticleRevisionResource; pub use self::article_resource::ArticleResource; pub use self::changes_resource::{ChangesLookup, ChangesResource}; pub use self::new_article_resource::NewArticleResource; diff --git a/src/resources/new_article_resource.rs b/src/resources/new_article_resource.rs index 41a7935..5acfc34 100644 --- a/src/resources/new_article_resource.rs +++ b/src/resources/new_article_resource.rs @@ -50,7 +50,7 @@ impl Resource for NewArticleResource { fn get(self: Box) -> ResponseFuture { #[derive(BartDisplay)] - #[template="templates/article_revision.html"] + #[template="templates/article.html"] struct Template<'a> { revision: &'a str, last_updated: Option<&'a str>, @@ -107,7 +107,7 @@ impl Resource for NewArticleResource { } #[derive(BartDisplay)] - #[template="templates/article_revision_contents.html"] + #[template="templates/article_contents.html"] struct Template<'a> { title: &'a str, rendered: String, diff --git a/src/resources/pagination.rs b/src/resources/pagination.rs index b407518..9b7b4a6 100644 --- a/src/resources/pagination.rs +++ b/src/resources/pagination.rs @@ -24,7 +24,7 @@ struct PaginationStruct { before: Option, } -#[derive(Clone)] +#[derive(Copy, Clone)] pub enum Pagination { After(T), Before(T), diff --git a/src/state.rs b/src/state.rs index 0c7b7c1..0fc1e4f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -87,6 +87,21 @@ impl State { } } + pub fn get_article_slug(&self, article_id: i32) -> CpuFuture, Error> { + let connection_pool = self.connection_pool.clone(); + + self.cpu_pool.spawn_fn(move || { + use schema::article_revisions; + + Ok(article_revisions::table + .filter(article_revisions::article_id.eq(article_id)) + .filter(article_revisions::latest.eq(true)) + .select((article_revisions::slug)) + .first::(&*connection_pool.get()?) + .optional()?) + }) + } + pub fn get_article_revision(&self, article_id: i32, revision: i32) -> CpuFuture, Error> { let connection_pool = self.connection_pool.clone(); diff --git a/src/wiki_lookup.rs b/src/wiki_lookup.rs index 336dcf9..8cb4035 100644 --- a/src/wiki_lookup.rs +++ b/src/wiki_lookup.rs @@ -76,6 +76,53 @@ impl WikiLookup { WikiLookup { state, changes_lookup } } + fn revisions_lookup(&self, path: &str, _query: Option<&str>) -> ::Future { + let (article_id, revision): (i32, i32) = match (|| -> Result<_, ::Error> { + let (article_id, tail) = split_one(path)?; + let (revision, tail) = split_one(tail.ok_or("Not found")?)?; + if tail.is_some() { + return Err("Not found".into()); + } + + Ok((article_id.parse()?, revision.parse()?)) + })() { + Ok(x) => x, + Err(_) => return Box::new(finished(None)), + }; + + Box::new( + self.state.get_article_revision(article_id, revision) + .and_then(|article_revision| + Ok(article_revision.map(move |x| Box::new( + ArticleRevisionResource::new(x) + ) as BoxResource)) + ) + ) + } + + fn by_id_lookup(&self, path: &str, _query: Option<&str>) -> ::Future { + let article_id: i32 = match (|| -> Result<_, ::Error> { + let (article_id, tail) = split_one(path)?; + if tail.is_some() { + return Err("Not found".into()); + } + + Ok(article_id.parse()?) + })() { + Ok(x) => x, + Err(_) => return Box::new(finished(None)), + }; + + Box::new( + self.state.get_article_slug(article_id) + .and_then(|slug| + Ok(slug.map(|slug| Box::new( + TemporaryRedirectResource::new(format!("../{}", slug)) + ) as BoxResource)) + ) + ) + } + fn reserved_lookup(&self, path: &str, query: Option<&str>) -> ::Future { let (head, tail) = match split_one(path) { Ok(x) => x, @@ -85,10 +132,14 @@ impl WikiLookup { match (head.as_ref(), tail) { ("_assets", Some(asset)) => Box::new(asset_lookup(asset)), + ("_by_id", Some(tail)) => + self.by_id_lookup(tail, query), ("_changes", None) => Box::new(self.changes_lookup.lookup(query)), ("_new", None) => Box::new(finished(Some(Box::new(NewArticleResource::new(self.state.clone(), None)) as BoxResource))), + ("_revisions", Some(tail)) => + self.revisions_lookup(tail, query), ("_sitemap", None) => Box::new(finished(Some(Box::new(SitemapResource::new(self.state.clone())) as BoxResource))), _ => Box::new(finished(None)), diff --git a/templates/article.html b/templates/article.html new file mode 100644 index 0000000..f99bb10 --- /dev/null +++ b/templates/article.html @@ -0,0 +1,40 @@ + + +
+
+{{>article_contents.html}} +
+ +
+
+ +
+

+
+ +
+

+ + + +

+
+ +
+{{#cancel_url}} + Cancel +{{/cancel_url}} + +
+ +
+
+
+ +
+
  • {{#last_updated}}{{{.}}}{{/last_updated}}
  • Edit
+{{>footer/items.html}} +
diff --git a/templates/article_revision_contents.html b/templates/article_contents.html similarity index 100% rename from templates/article_revision_contents.html rename to templates/article_contents.html diff --git a/templates/article_revision.html b/templates/article_revision.html index 3c4d00b..3b5340e 100644 --- a/templates/article_revision.html +++ b/templates/article_revision.html @@ -1,40 +1,17 @@ - +
+ +
+

+ You are viewing an historical version of this article, + authored at {{{timestamp_and_author}}}. +

+
-
-{{>article_revision_contents.html}} -
- -
-
- -
-

-
- -
-

- - - -

-
- -
-{{#cancel_url}} - Cancel -{{/cancel_url}} - -
- -
+{{>article_contents.html}}