Implement /_new, for creating new articles without making up a URL first
This commit is contained in:
parent
ce1bbee22c
commit
1d485798eb
6 changed files with 51 additions and 33 deletions
|
@ -107,3 +107,7 @@ document
|
||||||
|
|
||||||
openEditor();
|
openEditor();
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (document.querySelector(".container").classList.contains("edit")) {
|
||||||
|
openEditor();
|
||||||
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ impl Resource for ArticleResource {
|
||||||
revision: i32,
|
revision: i32,
|
||||||
created: &'a chrono::DateTime<Local>,
|
created: &'a chrono::DateTime<Local>,
|
||||||
|
|
||||||
slug: &'a str,
|
cancel_url: Option<&'a str>,
|
||||||
title: &'a str,
|
title: &'a str,
|
||||||
raw: &'a str,
|
raw: &'a str,
|
||||||
rendered: String,
|
rendered: String,
|
||||||
|
@ -68,7 +68,7 @@ impl Resource for ArticleResource {
|
||||||
article_id: data.article_id,
|
article_id: data.article_id,
|
||||||
revision: data.revision,
|
revision: data.revision,
|
||||||
created: &Local.from_utc_datetime(&data.created),
|
created: &Local.from_utc_datetime(&data.created),
|
||||||
slug: &data.slug,
|
cancel_url: Some(&data.slug),
|
||||||
title: &data.title,
|
title: &data.title,
|
||||||
raw: &data.body,
|
raw: &data.body,
|
||||||
rendered: render_markdown(&data.body),
|
rendered: render_markdown(&data.body),
|
||||||
|
|
|
@ -26,11 +26,11 @@ fn title_from_slug(slug: &str) -> String {
|
||||||
|
|
||||||
pub struct NewArticleResource {
|
pub struct NewArticleResource {
|
||||||
state: State,
|
state: State,
|
||||||
slug: String,
|
slug: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NewArticleResource {
|
impl NewArticleResource {
|
||||||
pub fn new(state: State, slug: String) -> Self {
|
pub fn new(state: State, slug: Option<String>) -> Self {
|
||||||
Self { state, slug }
|
Self { state, slug }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ impl Resource for NewArticleResource {
|
||||||
revision: &'a str,
|
revision: &'a str,
|
||||||
created: &'a str,
|
created: &'a str,
|
||||||
|
|
||||||
slug: &'a str,
|
cancel_url: Option<&'a str>,
|
||||||
title: &'a str,
|
title: &'a str,
|
||||||
raw: &'a str,
|
raw: &'a str,
|
||||||
rendered: &'a str,
|
rendered: &'a str,
|
||||||
|
@ -64,7 +64,8 @@ impl Resource for NewArticleResource {
|
||||||
script_js_checksum: &'a str,
|
script_js_checksum: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
let title = title_from_slug(&self.slug);
|
let title = self.slug.as_ref()
|
||||||
|
.map_or("".to_owned(), |x| title_from_slug(x));
|
||||||
|
|
||||||
Box::new(self.head()
|
Box::new(self.head()
|
||||||
.and_then(move |head| {
|
.and_then(move |head| {
|
||||||
|
@ -75,7 +76,7 @@ impl Resource for NewArticleResource {
|
||||||
article_id: NDASH,
|
article_id: NDASH,
|
||||||
revision: NDASH,
|
revision: NDASH,
|
||||||
created: NDASH,
|
created: NDASH,
|
||||||
slug: &self.slug,
|
cancel_url: self.slug.as_ref().map(|x| &**x),
|
||||||
title: &title,
|
title: &title,
|
||||||
raw: "",
|
raw: "",
|
||||||
rendered: EMPTY_ARTICLE_MESSAGE,
|
rendered: EMPTY_ARTICLE_MESSAGE,
|
||||||
|
|
14
src/state.rs
14
src/state.rs
|
@ -38,7 +38,10 @@ struct NewRevision<'a> {
|
||||||
latest: bool,
|
latest: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decide_slug(conn: &SqliteConnection, article_id: i32, prev_title: &str, title: &str, prev_slug: &str) -> Result<String, Error> {
|
fn decide_slug(conn: &SqliteConnection, article_id: i32, prev_title: &str, title: &str, prev_slug: Option<&str>) -> Result<String, Error> {
|
||||||
|
let base_slug = ::slug::slugify(title);
|
||||||
|
|
||||||
|
if let Some(prev_slug) = prev_slug {
|
||||||
if prev_slug == "" {
|
if prev_slug == "" {
|
||||||
// Never give a non-empty slug to the front page
|
// Never give a non-empty slug to the front page
|
||||||
return Ok(String::new());
|
return Ok(String::new());
|
||||||
|
@ -48,11 +51,10 @@ fn decide_slug(conn: &SqliteConnection, article_id: i32, prev_title: &str, title
|
||||||
return Ok(prev_slug.to_owned());
|
return Ok(prev_slug.to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
let base_slug = ::slug::slugify(title);
|
|
||||||
|
|
||||||
if base_slug == prev_slug {
|
if base_slug == prev_slug {
|
||||||
return Ok(base_slug);
|
return Ok(base_slug);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
use schema::article_revisions;
|
use schema::article_revisions;
|
||||||
|
|
||||||
|
@ -170,7 +172,7 @@ impl State {
|
||||||
}
|
}
|
||||||
let new_revision = base_revision + 1;
|
let new_revision = base_revision + 1;
|
||||||
|
|
||||||
let slug = decide_slug(&*conn, article_id, &prev_title, &title, &prev_slug)?;
|
let slug = decide_slug(&*conn, article_id, &prev_title, &title, Some(&prev_slug))?;
|
||||||
|
|
||||||
diesel::update(
|
diesel::update(
|
||||||
article_revisions::table
|
article_revisions::table
|
||||||
|
@ -200,7 +202,7 @@ impl State {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_article(&self, target_slug: String, title: String, body: String)
|
pub fn create_article(&self, target_slug: Option<String>, title: String, body: String)
|
||||||
-> CpuFuture<models::ArticleRevision, Error>
|
-> CpuFuture<models::ArticleRevision, Error>
|
||||||
{
|
{
|
||||||
let connection_pool = self.connection_pool.clone();
|
let connection_pool = self.connection_pool.clone();
|
||||||
|
@ -225,7 +227,7 @@ impl State {
|
||||||
.pop().expect("Statement must evaluate to an integer")
|
.pop().expect("Statement must evaluate to an integer")
|
||||||
};
|
};
|
||||||
|
|
||||||
let slug = decide_slug(&*conn, article_id, "", &title, &target_slug)?;
|
let slug = decide_slug(&*conn, article_id, "", &title, target_slug.as_ref().map(|x| &**x))?;
|
||||||
|
|
||||||
let new_revision = 1;
|
let new_revision = 1;
|
||||||
|
|
||||||
|
|
|
@ -13,25 +13,34 @@ use web::{Lookup, Resource};
|
||||||
type BoxResource = Box<Resource + Sync + Send>;
|
type BoxResource = Box<Resource + Sync + Send>;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref LOOKUP_MAP: HashMap<String, Box<Fn() -> BoxResource + Sync + Send>> = {
|
static ref LOOKUP_MAP: HashMap<String, Box<Fn(State) -> BoxResource + Sync + Send>> = {
|
||||||
let mut lookup_map = HashMap::new();
|
let mut lookup_map = HashMap::new();
|
||||||
|
|
||||||
|
lookup_map.insert(
|
||||||
|
"/_new".to_string(),
|
||||||
|
Box::new(|state|
|
||||||
|
Box::new(
|
||||||
|
NewArticleResource::new(state, None)
|
||||||
|
) as BoxResource
|
||||||
|
) as Box<Fn(State) -> BoxResource + Sync + Send>
|
||||||
|
);
|
||||||
|
|
||||||
lookup_map.insert(
|
lookup_map.insert(
|
||||||
format!("/_assets/style-{}.css", StyleCss::checksum()),
|
format!("/_assets/style-{}.css", StyleCss::checksum()),
|
||||||
Box::new(|| Box::new(StyleCss) as BoxResource)
|
Box::new(|_| Box::new(StyleCss) as BoxResource)
|
||||||
as Box<Fn() -> Box<Resource + Sync + Send> + Sync + Send>
|
as Box<Fn(State) -> BoxResource + Sync + Send>
|
||||||
);
|
);
|
||||||
|
|
||||||
lookup_map.insert(
|
lookup_map.insert(
|
||||||
format!("/_assets/script-{}.js", ScriptJs::checksum()),
|
format!("/_assets/script-{}.js", ScriptJs::checksum()),
|
||||||
Box::new(|| Box::new(ScriptJs) as BoxResource)
|
Box::new(|_| Box::new(ScriptJs) as BoxResource)
|
||||||
as Box<Fn() -> Box<Resource + Sync + Send> + Sync + Send>
|
as Box<Fn(State) -> BoxResource + Sync + Send>
|
||||||
);
|
);
|
||||||
|
|
||||||
lookup_map.insert(
|
lookup_map.insert(
|
||||||
format!("/_assets/amatic-sc-v9-latin-regular.woff"),
|
format!("/_assets/amatic-sc-v9-latin-regular.woff"),
|
||||||
Box::new(|| Box::new(AmaticFont) as BoxResource)
|
Box::new(|_| Box::new(AmaticFont) as BoxResource)
|
||||||
as Box<Fn() -> Box<Resource + Sync + Send> + Sync + Send>
|
as Box<Fn(State) -> BoxResource + Sync + Send>
|
||||||
);
|
);
|
||||||
|
|
||||||
lookup_map
|
lookup_map
|
||||||
|
@ -61,7 +70,7 @@ impl Lookup for WikiLookup {
|
||||||
// Reserved namespace
|
// Reserved namespace
|
||||||
|
|
||||||
return Box::new(finished(
|
return Box::new(finished(
|
||||||
LOOKUP_MAP.get(path).map(|x| x())
|
LOOKUP_MAP.get(path).map(|x| x(self.state.clone()))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +101,7 @@ impl Lookup for WikiLookup {
|
||||||
Box::new(self.state.lookup_slug(slug.clone())
|
Box::new(self.state.lookup_slug(slug.clone())
|
||||||
.and_then(|x| Ok(Some(match x {
|
.and_then(|x| Ok(Some(match x {
|
||||||
SlugLookup::Miss =>
|
SlugLookup::Miss =>
|
||||||
Box::new(NewArticleResource::new(state, slug)) as BoxResource,
|
Box::new(NewArticleResource::new(state, Some(slug))) as BoxResource,
|
||||||
SlugLookup::Hit { article_id, revision } =>
|
SlugLookup::Hit { article_id, revision } =>
|
||||||
Box::new(ArticleResource::new(state, article_id, revision)) as BoxResource,
|
Box::new(ArticleResource::new(state, article_id, revision)) as BoxResource,
|
||||||
SlugLookup::Redirect(slug) =>
|
SlugLookup::Redirect(slug) =>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script src="_assets/script-{{script_js_checksum}}.js" defer></script>
|
<script src="_assets/script-{{script_js_checksum}}.js" defer></script>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container {{^cancel_url}}edit{{/cancel_url}}">
|
||||||
<div class="rendered">
|
<div class="rendered">
|
||||||
{{>article_revision_contents.html}}
|
{{>article_revision_contents.html}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,7 +19,9 @@
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<div class="editor-controls">
|
<div class="editor-controls">
|
||||||
<a class="cancel" href="{{slug}}">Cancel</a>
|
{{#cancel_url}}
|
||||||
|
<a class="cancel" href="{{.}}">Cancel</a>
|
||||||
|
{{/cancel_url}}
|
||||||
<button type=submit>Save</button>
|
<button type=submit>Save</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue