diff --git a/assets/script.js b/assets/script.js index 9bc7e88..0b235a6 100644 --- a/assets/script.js +++ b/assets/script.js @@ -8,16 +8,17 @@ function autosizeTextarea(textarea, shadow) { function queryArgsFromForm(form) { const items = []; - for (const {name, value} of form.elements) { + for (const {name, value, type, checked} of form.elements) { if (!name) continue; + if (type === "radio" && !checked) continue; items.push(encodeURIComponent(name) + '=' + encodeURIComponent(value)); } return items.join('&'); } function isEdited(form) { - for (const {name, value, defaultValue} of form.elements) { - if (name && (value !== defaultValue)) return true; + for (const {name, value, defaultValue, checked, defaultChecked} of form.elements) { + if (name && ((value !== defaultValue) || (checked !== defaultChecked))) return true; } return false; } @@ -60,6 +61,7 @@ function confirmDiscard() { let hasBeenOpen = false; function openEditor() { + const bodyElement = document.querySelector("body"); const container = document.querySelector(".container"); const rendered = container.querySelector(".rendered"); const editor = container.querySelector(".editor"); @@ -119,6 +121,7 @@ function openEditor() { .then(result => { // Update url-bar, page title and footer window.history.replaceState(null, result.title, result.slug == "" ? "." : result.slug); + // TODO Cancel-link URL should be updated to new slug document.querySelector("title").textContent = result.title; lastUpdated.innerHTML = result.last_updated; lastUpdated.classList.remove("missing"); @@ -129,10 +132,14 @@ function openEditor() { form.elements.title.value = result.title; shadow.value = textarea.value = result.body; + form.querySelector(`.theme-picker--option[value=${JSON.stringify(result.theme)}]`).checked = true; + bodyElement.className = `theme-${result.theme}`; + // Update form: form.elements.base_revision.value = result.revision; for (const element of form.elements) { element.defaultValue = element.value; + element.defaultChecked = element.checked; } if (!result.conflict) { @@ -196,6 +203,13 @@ function openEditor() { doSave(); } }); + + const themeOptions = form.querySelectorAll(".theme-picker--option"); + for (let themeOption of themeOptions) { + themeOption.addEventListener("click", function (ev) { + bodyElement.className = `theme-${ev.target.value}`; + }); + } } document diff --git a/assets/style.css b/assets/style.css index 6a896fa..8c11cb0 100644 --- a/assets/style.css +++ b/assets/style.css @@ -329,6 +329,49 @@ h1>input { transition-timing-function: cubic-bezier(.17,.84,.44,1); } +.theme-picker { + position: absolute; + top: 0; + left: 0; + right: 0; + + display: flex; +} + +.theme-picker--option { + /* reset */ + -webkit-appearance: none; + -moz-appearance: none; + -o-appearance: none; + -ms-appearance: none; + appearance: none; + border: none; + border-radius: 0; + margin: 0; + padding: 0; + + height: 20px; + background: var(--theme-main); + color: var(--theme-text); + + flex-grow: 1; + + position: relative; +} + +.theme-picker--option:checked::after { + content: " "; + display: block; + background: white; + border-radius: 5px; + width: 10px; + height: 10px; + + position: absolute; + top: calc(50% - 5px); + left: calc(50% - 5px); +} + .button { border-radius: 2px; diff --git a/src/resources/article_resource.rs b/src/resources/article_resource.rs index 19be95f..96a8df4 100644 --- a/src/resources/article_resource.rs +++ b/src/resources/article_resource.rs @@ -11,11 +11,16 @@ use mimes::*; use rendering::render_markdown; use site::Layout; use state::{State, UpdateResult, RebaseConflict}; -use theme::Theme; +use theme::{self, Theme}; use web::{Resource, ResponseFuture}; use super::changes_resource::QueryParameters; +struct SelectableTheme { + theme: Theme, + selected: bool, +} + #[derive(BartDisplay)] #[template="templates/article.html"] struct Template<'a> { @@ -27,7 +32,7 @@ struct Template<'a> { title: &'a str, raw: &'a str, rendered: String, - theme: Theme, + themes: &'a [SelectableTheme], } impl<'a> Template<'a> { @@ -118,7 +123,10 @@ impl Resource for ArticleResource { title: &data.title, raw: &data.body, rendered: render_markdown(&data.body), - theme: data.theme, + themes: &theme::THEMES.iter().map(|&x| SelectableTheme { + theme: x, + selected: x == data.theme, + }).collect::>(), }, }.to_string())) })) @@ -259,7 +267,10 @@ impl Resource for ArticleResource { title: &title, raw: &body, rendered: render_markdown(&body), - theme, + themes: &theme::THEMES.iter().map(|&x| SelectableTheme { + theme: x, + selected: x == theme, + }).collect::>(), }, }.to_string()) ) diff --git a/src/resources/new_article_resource.rs b/src/resources/new_article_resource.rs index 9d356fa..a348a99 100644 --- a/src/resources/new_article_resource.rs +++ b/src/resources/new_article_resource.rs @@ -58,6 +58,12 @@ impl Resource for NewArticleResource { } fn get(self: Box) -> ResponseFuture { + // TODO Remove duplication with article_resource.rs: + struct SelectableTheme { + theme: Theme, + selected: bool, + } + #[derive(BartDisplay)] #[template="templates/article.html"] struct Template<'a> { @@ -69,7 +75,7 @@ impl Resource for NewArticleResource { title: &'a str, raw: &'a str, rendered: &'a str, - theme: Theme, + themes: &'a [SelectableTheme], } impl<'a> Template<'a> { fn script_js(&self) -> &'static str { @@ -95,13 +101,17 @@ impl Resource for NewArticleResource { // Implicitly start in edit-mode when no slug is given. This // currently directly corresponds to the /_new endpoint + // TODO: Also start in edit mode when the ?edit query arg is given edit: self.slug.is_none(), cancel_url: self.slug.as_ref().map(|x| &**x), title: &title, raw: "", rendered: EMPTY_ARTICLE_MESSAGE, - theme, + themes: &theme::THEMES.iter().map(|&x| SelectableTheme { + theme: x, + selected: x == theme, + }).collect::>(), }, }.to_string())) })) diff --git a/templates/article.html b/templates/article.html index 2753017..5ae5914 100644 --- a/templates/article.html +++ b/templates/article.html @@ -5,7 +5,7 @@ {{>article_contents.html}} -
+
@@ -14,9 +14,14 @@
+
+{{#themes}} + +{{/themes}} +
+

-