Implement editing
This commit is contained in:
parent
60a87d1898
commit
1a5b39b3a1
7 changed files with 142 additions and 33 deletions
13
Cargo.lock
generated
13
Cargo.lock
generated
|
@ -19,6 +19,7 @@ dependencies = [
|
||||||
"regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde_derive 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_derive 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_json 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde_urlencoded 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_urlencoded 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"tokio-io 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"tokio-io 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"tokio-proto 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"tokio-proto 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -572,6 +573,17 @@ dependencies = [
|
||||||
"synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_urlencoded"
|
name = "serde_urlencoded"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
@ -893,6 +905,7 @@ dependencies = [
|
||||||
"checksum serde 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "f7726f29ddf9731b17ff113c461e362c381d9d69433f79de4f3dd572488823e9"
|
"checksum serde 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "f7726f29ddf9731b17ff113c461e362c381d9d69433f79de4f3dd572488823e9"
|
||||||
"checksum serde_derive 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cf823e706be268e73e7747b147aa31c8f633ab4ba31f115efb57e5047c3a76dd"
|
"checksum serde_derive 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cf823e706be268e73e7747b147aa31c8f633ab4ba31f115efb57e5047c3a76dd"
|
||||||
"checksum serde_derive_internals 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37aee4e0da52d801acfbc0cc219eb1eda7142112339726e427926a6f6ee65d3a"
|
"checksum serde_derive_internals 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37aee4e0da52d801acfbc0cc219eb1eda7142112339726e427926a6f6ee65d3a"
|
||||||
|
"checksum serde_json 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d243424e06f9f9c39e3cd36147470fd340db785825e367625f79298a6ac6b7ac"
|
||||||
"checksum serde_urlencoded 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce0fd303af908732989354c6f02e05e2e6d597152870f2c6990efb0577137480"
|
"checksum serde_urlencoded 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce0fd303af908732989354c6f02e05e2e6d597152870f2c6990efb0577137480"
|
||||||
"checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23"
|
"checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23"
|
||||||
"checksum smallvec 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c8cbcd6df1e117c2210e13ab5109635ad68a929fcbb8964dc965b76cb5ee013"
|
"checksum smallvec 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c8cbcd6df1e117c2210e13ab5109635ad68a929fcbb8964dc965b76cb5ee013"
|
||||||
|
|
|
@ -12,6 +12,7 @@ tokio-service = "0.1"
|
||||||
serde_derive = "1.0.0"
|
serde_derive = "1.0.0"
|
||||||
serde = "1.0.0"
|
serde = "1.0.0"
|
||||||
serde_urlencoded = "0.5.0"
|
serde_urlencoded = "0.5.0"
|
||||||
|
serde_json = "1.0"
|
||||||
r2d2 = "0.7"
|
r2d2 = "0.7"
|
||||||
r2d2-diesel = "0.16"
|
r2d2-diesel = "0.16"
|
||||||
regex = "0.2"
|
regex = "0.2"
|
||||||
|
|
|
@ -11,6 +11,7 @@ extern crate hyper;
|
||||||
extern crate pulldown_cmark;
|
extern crate pulldown_cmark;
|
||||||
extern crate r2d2;
|
extern crate r2d2;
|
||||||
extern crate r2d2_diesel;
|
extern crate r2d2_diesel;
|
||||||
|
extern crate serde_json;
|
||||||
extern crate serde_urlencoded;
|
extern crate serde_urlencoded;
|
||||||
|
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
77
src/site.rs
77
src/site.rs
|
@ -5,6 +5,8 @@ use hyper;
|
||||||
use hyper::header::ContentType;
|
use hyper::header::ContentType;
|
||||||
use hyper::mime;
|
use hyper::mime;
|
||||||
use hyper::server::*;
|
use hyper::server::*;
|
||||||
|
use serde_json;
|
||||||
|
use serde_urlencoded;
|
||||||
|
|
||||||
use models;
|
use models;
|
||||||
use state::State;
|
use state::State;
|
||||||
|
@ -47,6 +49,7 @@ fn render_markdown(src: &str) -> String {
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref TEXT_HTML: mime::Mime = "text/html;charset=utf-8".parse().unwrap();
|
static ref TEXT_HTML: mime::Mime = "text/html;charset=utf-8".parse().unwrap();
|
||||||
|
static ref APPLICATION_JSON: mime::Mime = "application/json".parse().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(BartDisplay)]
|
#[derive(BartDisplay)]
|
||||||
|
@ -70,7 +73,7 @@ struct WikiLookup {
|
||||||
|
|
||||||
impl Lookup for WikiLookup {
|
impl Lookup for WikiLookup {
|
||||||
type Resource = ArticleResource;
|
type Resource = ArticleResource;
|
||||||
type Error = Box<::std::error::Error + Send>;
|
type Error = Box<::std::error::Error + Send + Sync>;
|
||||||
type Future = futures::future::FutureResult<Option<Self::Resource>, Self::Error>;
|
type Future = futures::future::FutureResult<Option<Self::Resource>, Self::Error>;
|
||||||
|
|
||||||
fn lookup(&self, path: &str, _query: Option<&str>, _fragment: Option<&str>) -> Self::Future {
|
fn lookup(&self, path: &str, _query: Option<&str>, _fragment: Option<&str>) -> Self::Future {
|
||||||
|
@ -85,7 +88,7 @@ impl Lookup for WikiLookup {
|
||||||
if let Ok(article_id) = slug.parse() {
|
if let Ok(article_id) = slug.parse() {
|
||||||
match self.state.get_article_revision_by_id(article_id) {
|
match self.state.get_article_revision_by_id(article_id) {
|
||||||
Ok(Some(article)) => {
|
Ok(Some(article)) => {
|
||||||
futures::finished(Some(ArticleResource::new(article)))
|
futures::finished(Some(ArticleResource::new(self.state.clone(), article)))
|
||||||
},
|
},
|
||||||
Ok(None) => futures::finished(None),
|
Ok(None) => futures::finished(None),
|
||||||
Err(err) => futures::failed(err),
|
Err(err) => futures::failed(err),
|
||||||
|
@ -97,14 +100,13 @@ impl Lookup for WikiLookup {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ArticleResource {
|
struct ArticleResource {
|
||||||
|
state: State,
|
||||||
data: models::ArticleRevision,
|
data: models::ArticleRevision,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArticleResource {
|
impl ArticleResource {
|
||||||
fn new(data: models::ArticleRevision) -> Self {
|
fn new(state: State, data: models::ArticleRevision) -> Self {
|
||||||
Self {
|
Self { state, data }
|
||||||
data
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,14 +116,14 @@ impl Resource for ArticleResource {
|
||||||
vec![Options, Head, Get, Put]
|
vec![Options, Head, Get, Put]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn head(&self) -> futures::BoxFuture<Response, Box<::std::error::Error + Send>> {
|
fn head(&self) -> futures::BoxFuture<Response, Box<::std::error::Error + Send + Sync>> {
|
||||||
futures::finished(Response::new()
|
futures::finished(Response::new()
|
||||||
.with_status(hyper::StatusCode::Ok)
|
.with_status(hyper::StatusCode::Ok)
|
||||||
.with_header(ContentType(TEXT_HTML.clone()))
|
.with_header(ContentType(TEXT_HTML.clone()))
|
||||||
).boxed()
|
).boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get(self) -> futures::BoxFuture<Response, Box<::std::error::Error + Send>> {
|
fn get(self) -> futures::BoxFuture<Response, Box<::std::error::Error + Send + Sync>> {
|
||||||
use chrono::{self, TimeZone, Local};
|
use chrono::{self, TimeZone, Local};
|
||||||
|
|
||||||
#[derive(BartDisplay)]
|
#[derive(BartDisplay)]
|
||||||
|
@ -152,8 +154,52 @@ impl Resource for ArticleResource {
|
||||||
).boxed()
|
).boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn put(self, body: &[u8]) -> futures::BoxFuture<Response, Box<::std::error::Error + Send>> {
|
fn put<S: 'static + futures::Stream<Item=hyper::Chunk, Error=hyper::Error> + Send + Sync>(self, body: S) -> futures::BoxFuture<Response, Box<::std::error::Error + Send + Sync>> {
|
||||||
unimplemented!()
|
// TODO Check incoming Content-Type
|
||||||
|
|
||||||
|
use chrono::{TimeZone, Local};
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct UpdateArticle {
|
||||||
|
base_revision: i32,
|
||||||
|
body: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct PutResponse<'a> {
|
||||||
|
revision: i32,
|
||||||
|
rendered: &'a str,
|
||||||
|
created: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
body
|
||||||
|
.concat2()
|
||||||
|
.map_err(|x| Box::new(x) as Box<::std::error::Error + Send + Sync>)
|
||||||
|
.and_then(move |body| {
|
||||||
|
let update: UpdateArticle = match serde_urlencoded::from_bytes(&body) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(err) => return futures::finished(Response::new()
|
||||||
|
.with_status(hyper::StatusCode::BadRequest)
|
||||||
|
.with_body(format!("{:#?}", err))
|
||||||
|
).boxed()
|
||||||
|
};
|
||||||
|
|
||||||
|
let updated = match self.state.update_article(self.data.article_id, update.base_revision, &update.body) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(x) => return futures::failed(x).boxed(),
|
||||||
|
};
|
||||||
|
|
||||||
|
futures::finished(Response::new()
|
||||||
|
.with_status(hyper::StatusCode::Ok)
|
||||||
|
.with_header(ContentType(APPLICATION_JSON.clone()))
|
||||||
|
.with_body(serde_json::to_string(&PutResponse {
|
||||||
|
revision: updated.revision,
|
||||||
|
rendered: &render_markdown(&updated.body),
|
||||||
|
created: &Local.from_utc_datetime(&updated.created).to_string(),
|
||||||
|
}).expect("Should never fail"))
|
||||||
|
).boxed()
|
||||||
|
})
|
||||||
|
.boxed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,7 +225,7 @@ impl Site {
|
||||||
.with_status(hyper::StatusCode::NotFound)
|
.with_status(hyper::StatusCode::NotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn internal_server_error(err: Box<::std::error::Error + Send>) -> Response {
|
fn internal_server_error(err: Box<::std::error::Error + Send + Sync>) -> Response {
|
||||||
eprintln!("Internal Server Error:\n{:#?}", err);
|
eprintln!("Internal Server Error:\n{:#?}", err);
|
||||||
|
|
||||||
Response::new()
|
Response::new()
|
||||||
|
@ -210,14 +256,7 @@ impl Service for Site {
|
||||||
Options => futures::finished(resource.options()).boxed(),
|
Options => futures::finished(resource.options()).boxed(),
|
||||||
Head => resource.head(),
|
Head => resource.head(),
|
||||||
Get => resource.get(),
|
Get => resource.get(),
|
||||||
Put => {
|
Put => resource.put(body),
|
||||||
use futures::Stream;
|
|
||||||
body
|
|
||||||
.concat2()
|
|
||||||
.map_err(|x| Box::new(x) as Box<::std::error::Error + Send>)
|
|
||||||
.and_then(move |body| resource.put(&body))
|
|
||||||
.boxed()
|
|
||||||
},
|
|
||||||
_ => futures::finished(resource.method_not_allowed()).boxed()
|
_ => futures::finished(resource.method_not_allowed()).boxed()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
55
src/state.rs
55
src/state.rs
|
@ -1,6 +1,7 @@
|
||||||
use std;
|
use std;
|
||||||
|
|
||||||
use chrono;
|
use chrono;
|
||||||
|
use diesel;
|
||||||
use diesel::sqlite::SqliteConnection;
|
use diesel::sqlite::SqliteConnection;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use r2d2::Pool;
|
use r2d2::Pool;
|
||||||
|
@ -13,12 +14,6 @@ pub struct State {
|
||||||
connection_pool: Pool<ConnectionManager<SqliteConnection>>
|
connection_pool: Pool<ConnectionManager<SqliteConnection>>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct UpdateArticle {
|
|
||||||
base_revision: i32,
|
|
||||||
body: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Error = Box<std::error::Error + Send + Sync>;
|
pub type Error = Box<std::error::Error + Send + Sync>;
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
|
@ -46,4 +41,52 @@ impl State {
|
||||||
.load::<models::ArticleRevision>(&*self.connection_pool.get()?)?
|
.load::<models::ArticleRevision>(&*self.connection_pool.get()?)?
|
||||||
.pop())
|
.pop())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_article(&self, article_id: i32, base_revision: i32, body: &str) -> Result<models::ArticleRevision, Error> {
|
||||||
|
let conn = self.connection_pool.get()?;
|
||||||
|
conn.transaction(|| {
|
||||||
|
use schema::article_revisions;
|
||||||
|
|
||||||
|
let (latest_revision, title) = article_revisions::table
|
||||||
|
.filter(article_revisions::article_id.eq(article_id))
|
||||||
|
.order(article_revisions::revision.desc())
|
||||||
|
.limit(1)
|
||||||
|
.select((article_revisions::revision, article_revisions::title))
|
||||||
|
.load::<(i32, String)>(&*conn)?
|
||||||
|
.pop()
|
||||||
|
.unwrap_or_else(|| unimplemented!("TODO Missing an error type"));
|
||||||
|
|
||||||
|
if latest_revision != base_revision {
|
||||||
|
// TODO: If it is the same edit repeated, just respond OK
|
||||||
|
// TODO: If there is a conflict, transform the edit to work seamlessly
|
||||||
|
unimplemented!("TODO Missing handling of revision conflicts");
|
||||||
|
}
|
||||||
|
let new_revision = base_revision + 1;
|
||||||
|
|
||||||
|
#[derive(Insertable)]
|
||||||
|
#[table_name="article_revisions"]
|
||||||
|
struct NewRevision<'a> {
|
||||||
|
article_id: i32,
|
||||||
|
revision: i32,
|
||||||
|
title: &'a str,
|
||||||
|
body: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::insert(&NewRevision {
|
||||||
|
article_id,
|
||||||
|
revision: new_revision,
|
||||||
|
title: &title,
|
||||||
|
body
|
||||||
|
})
|
||||||
|
.into(article_revisions::table)
|
||||||
|
.execute(&*conn)?;
|
||||||
|
|
||||||
|
Ok(article_revisions::table
|
||||||
|
.filter(article_revisions::article_id.eq(article_id))
|
||||||
|
.filter(article_revisions::revision.eq(new_revision))
|
||||||
|
.load::<models::ArticleRevision>(&*conn)?
|
||||||
|
.pop()
|
||||||
|
.expect("We just inserted this row!"))
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,13 +7,13 @@ lazy_static! {
|
||||||
static ref TEXT_PLAIN: mime::Mime = "text/plain;charset=utf-8".parse().unwrap();
|
static ref TEXT_PLAIN: mime::Mime = "text/plain;charset=utf-8".parse().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
type Error = Box<std::error::Error + Send>;
|
type Error = Box<std::error::Error + Send + Sync>;
|
||||||
|
|
||||||
pub trait Resource {
|
pub trait Resource {
|
||||||
fn allow(&self) -> Vec<hyper::Method>;
|
fn allow(&self) -> Vec<hyper::Method>;
|
||||||
fn head(&self) -> futures::BoxFuture<server::Response, Error>;
|
fn head(&self) -> futures::BoxFuture<server::Response, Error>;
|
||||||
fn get(self) -> futures::BoxFuture<server::Response, Error>;
|
fn get(self) -> futures::BoxFuture<server::Response, Error>;
|
||||||
fn put(self, body: &[u8]) -> futures::BoxFuture<server::Response, Error>;
|
fn put<S: 'static + futures::Stream<Item=hyper::Chunk, Error=hyper::Error> + Send + Sync>(self, body: S) -> futures::BoxFuture<server::Response, Error>;
|
||||||
|
|
||||||
fn options(&self) -> Response {
|
fn options(&self) -> Response {
|
||||||
Response::new()
|
Response::new()
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="editor">
|
<div class="editor">
|
||||||
<form action="" method="POST">
|
<form action="" method="POST">
|
||||||
<input type=hidden name=baseRevision value="{{revision}}">
|
<input type=hidden name=base_revision value="{{revision}}">
|
||||||
<textarea autocomplete=off name=body>{{raw}}</textarea>
|
<textarea autocomplete=off name=body>{{raw}}</textarea>
|
||||||
<textarea autocomplete=off class="shadow-control"></textarea>
|
<textarea autocomplete=off class="shadow-control"></textarea>
|
||||||
<div class="editor-controls">
|
<div class="editor-controls">
|
||||||
|
@ -26,10 +26,10 @@
|
||||||
<dd>{{article_id}}</dd>
|
<dd>{{article_id}}</dd>
|
||||||
|
|
||||||
<dt>Revision</dt>
|
<dt>Revision</dt>
|
||||||
<dd>{{revision}}</dd>
|
<dd class="revision">{{revision}}</dd>
|
||||||
|
|
||||||
<dt>Last updated</dt>
|
<dt>Last updated</dt>
|
||||||
<dd>{{created}}</dd>
|
<dd class="last-updated">{{created}}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
@ -49,22 +49,29 @@ function queryArgsFromForm(form) {
|
||||||
return items.join('&');
|
return items.join('&');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let hasBeenOpen = false;
|
||||||
function openEditor() {
|
function openEditor() {
|
||||||
const article = document.querySelector("article");
|
const article = document.querySelector("article");
|
||||||
const rendered = document.querySelector(".rendered");
|
const rendered = document.querySelector(".rendered");
|
||||||
const editor = document.querySelector(".editor");
|
const editor = document.querySelector(".editor");
|
||||||
const textarea = editor.querySelector('textarea[name="body"]');
|
const textarea = editor.querySelector('textarea[name="body"]');
|
||||||
const shadow = editor.querySelector('textarea.shadow-control');
|
const shadow = editor.querySelector('textarea.shadow-control');
|
||||||
|
const form = editor.querySelector("form");
|
||||||
|
|
||||||
textarea.style.height = rendered.clientHeight + "px";
|
textarea.style.height = rendered.clientHeight + "px";
|
||||||
|
|
||||||
article.classList.add('edit');
|
article.classList.add('edit');
|
||||||
|
|
||||||
autosizeTextarea(textarea, shadow);
|
autosizeTextarea(textarea, shadow);
|
||||||
|
|
||||||
|
textarea.focus();
|
||||||
|
|
||||||
|
if (hasBeenOpen) return;
|
||||||
|
hasBeenOpen = true;
|
||||||
|
|
||||||
textarea.addEventListener('input', () => autosizeTextarea(textarea, shadow));
|
textarea.addEventListener('input', () => autosizeTextarea(textarea, shadow));
|
||||||
window.addEventListener('resize', () => autosizeTextarea(textarea, shadow));
|
window.addEventListener('resize', () => autosizeTextarea(textarea, shadow));
|
||||||
|
|
||||||
const form = editor.querySelector("form");
|
|
||||||
form.addEventListener("submit", function (ev) {
|
form.addEventListener("submit", function (ev) {
|
||||||
(async function () {
|
(async function () {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
@ -86,6 +93,13 @@ function openEditor() {
|
||||||
|
|
||||||
if (!response.ok) throw new Error("Unexpected status code (" + response.status + ")");
|
if (!response.ok) throw new Error("Unexpected status code (" + response.status + ")");
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
form.elements.base_revision.value = result.revision;
|
||||||
|
document.querySelector("footer .revision").textContent = result.revision;
|
||||||
|
document.querySelector("footer .last-updated").textContent = result.created;
|
||||||
|
rendered.innerHTML = result.rendered;
|
||||||
|
article.classList.remove('edit');
|
||||||
|
|
||||||
textarea.disabled = false;
|
textarea.disabled = false;
|
||||||
}()
|
}()
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
@ -94,8 +108,6 @@ function openEditor() {
|
||||||
alert(err);
|
alert(err);
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
textarea.focus();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document
|
document
|
||||||
|
|
Loading…
Reference in a new issue