diff --git a/Cargo.lock b/Cargo.lock index 0b9c13f..b08b65c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,32 +1,11 @@ [root] -name = "sausagewiki" +name = "static_resource_derive" version = "0.1.0" dependencies = [ - "bart 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "bart_derive 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "clap 2.26.0 (registry+https://github.com/rust-lang/crates.io-index)", - "diesel 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", - "diesel_codegen 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-cpupool 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "libsqlite3-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "num 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", - "pulldown-cmark 0.0.11 (registry+https://github.com/rust-lang/crates.io-index)", + "base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", - "r2d2 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "r2d2-diesel 0.16.0 (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_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)", - "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-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -100,6 +79,20 @@ name = "bitflags" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "block-buffer" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "byte-tools" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "byteorder" version = "1.1.0" @@ -188,6 +181,14 @@ dependencies = [ "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "digest" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "dtoa" version = "0.4.1" @@ -198,6 +199,11 @@ name = "either" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "futures" version = "0.1.14" @@ -217,6 +223,15 @@ name = "gcc" version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "generic-array" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nodrop 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "typenum 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "httparse" version = "1.2.3" @@ -399,6 +414,14 @@ dependencies = [ "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "nodrop" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "odds 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "nom" version = "2.2.1" @@ -444,6 +467,11 @@ dependencies = [ "libc 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "odds" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "percent-encoding" version = "1.0.0" @@ -539,6 +567,38 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "sausagewiki" +version = "0.1.0" +dependencies = [ + "bart 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bart_derive 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.26.0 (registry+https://github.com/rust-lang/crates.io-index)", + "diesel 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", + "diesel_codegen 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-cpupool 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libsqlite3-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "pulldown-cmark 0.0.11 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "r2d2 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "r2d2-diesel 0.16.0 (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_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)", + "static_resource_derive 0.1.0", + "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-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "scheduled-thread-pool" version = "0.1.0" @@ -603,6 +663,18 @@ dependencies = [ "url 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "sha2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "slab" version = "0.3.0" @@ -739,6 +811,11 @@ dependencies = [ "futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "typenum" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unicase" version = "2.0.0" @@ -853,6 +930,8 @@ dependencies = [ "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" "checksum bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1370e9fc2a6ae53aea8b7a5110edbd08836ed87c88736dfabccade1c2b44bff4" "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" +"checksum block-buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1339a1042f5d9f295737ad4d9a6ab6bf81c84a933dba110b9200cd6d1448b814" +"checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" "checksum byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff81738b726f5d099632ceaffe7fb65b90212e8dce59d518729e7e8634032d3d" "checksum bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d828f97b58cc5de3e40c421d0cf2132d6b2da4ee0e11b8632fa838f0f9333ad6" "checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" @@ -863,11 +942,14 @@ dependencies = [ "checksum diesel 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)" = "67f5b7408ddb7a834c0f191fb49b4398ccae19b226afafeeff5cb1eaac768b89" "checksum diesel_codegen 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "044abc50a0ee67e195b0ae95c9ffe6903748434294636dd67808301b7df1902f" "checksum diesel_infer_schema 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cf0e5b3d40abfc2eadba06df2c1b93b61be579390f4dba620f08e8172cce30c4" +"checksum digest 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e5b29bf156f3f4b3c4f610a25ff69370616ae6e0657d416de22645483e72af0a" "checksum dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80c8b71fd71146990a9742fc06dcbbde19161a267e0ad4e572c35162f4578c90" "checksum either 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18785c1ba806c258137c937e44ada9ee7e69a37e3c72077542cd2f069d78562a" +"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" "checksum futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "4b63a4792d4f8f686defe3b39b92127fea6344de5d38202b2ee5a11bbbf29d6a" "checksum futures-cpupool 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a283c84501e92cade5ea673a2a7ca44f71f209ccdd302a3e0896f50083d2c5ff" "checksum gcc 0.3.53 (registry+https://github.com/rust-lang/crates.io-index)" = "e8310f7e9c890398b0e80e301c4f474e9918d2b27fca8f48486ca775fa9ffc5a" +"checksum generic-array 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fceb69994e330afed50c93524be68c42fa898c2d9fd4ee8da03bd7363acd26f2" "checksum httparse 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "af2f2dd97457e8fb1ae7c5a420db346af389926e36f43768b96f101546b04a07" "checksum hyper 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "641abc3e3fcf0de41165595f801376e01106bca1fd876dda937730e477ca004c" "checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d" @@ -889,12 +971,14 @@ dependencies = [ "checksum mio 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "dbd91d3bfbceb13897065e97b2ef177a09a438cb33612b2d371bf568819a9313" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" "checksum net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)" = "3a80f842784ef6c9a958b68b7516bc7e35883c614004dd94959a4dca1b716c09" +"checksum nodrop 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "52cd74cd09beba596430cc6e3091b74007169a56246e1262f0ba451ea95117b2" "checksum nom 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf51a729ecf40266a2368ad335a5fdde43471f545a967109cd62146ecf8b66ff" "checksum num 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "a311b77ebdc5dd4cf6449d81e4135d9f0e3b153839ac90e648a8ef538f923525" "checksum num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "d1452e8b06e448a07f0e6ebb0bb1d92b8890eea63288c0b627331d53514d0fba" "checksum num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "7485fcc84f85b4ecd0ea527b14189281cf27d60e583ae65ebc9c088b13dffe01" "checksum num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "99843c856d68d8b4313b03a17e33c4bb42ae8f6610ea81b28abe076ac721b9b0" "checksum num_cpus 1.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aec53c34f2d0247c5ca5d32cca1478762f301740468ee9ee6dcb7a0dd7a0c584" +"checksum odds 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "c3df9b730298cea3a1c3faa90b7e2f9df3a9c400d0936d6015e6165734eefcba" "checksum percent-encoding 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de154f638187706bde41d9b4738748933d64e6b37bdbffc0b47a97d16a6ae356" "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" "checksum pulldown-cmark 0.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "4c7c04a8bb38f80717527edea39c82378c2ef13ecdbc914cbd90653a2e24afdf" @@ -916,6 +1000,7 @@ dependencies = [ "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 sha2 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d963c78ce367df26d7ea8b8cc655c651b42e8a1e584e869c1e17dae3ccb116a" "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 strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694" @@ -931,6 +1016,7 @@ dependencies = [ "checksum tokio-io 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b4ab83e7adb5677e42e405fa4ceff75659d93c4d7d7dd22f52fcec59ee9f02af" "checksum tokio-proto 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fbb47ae81353c63c487030659494b295f6cb6576242f907f203473b191b0389" "checksum tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "24da22d077e0f15f55162bdbdc661228c1581892f52074fb242678d015b45162" +"checksum typenum 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "13a99dc6780ef33c78780b826cf9d2a78840b72cae9474de4bcaf9051e60ebbd" "checksum unicase 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2e01da42520092d0cd2d6ac3ae69eb21a22ad43ff195676b86f8c37f487d6b80" "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" "checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f" diff --git a/Cargo.toml b/Cargo.toml index 94b3e1b..31868a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,9 @@ version = "0.1" version = "0.0.11" default-features = false +[dependencies.static_resource_derive] +path = "libs/static_resource_derive" + [build-dependencies] quote = "0.3.10" walkdir = "1" @@ -53,3 +56,5 @@ walkdir = "1" default-features = false features = ["sqlite", "chrono"] version = "0.15.0" + +[workspace] diff --git a/assets/script.js b/assets/script.js new file mode 100644 index 0000000..964a319 --- /dev/null +++ b/assets/script.js @@ -0,0 +1,97 @@ +function autosizeTextarea(textarea, shadow) { + shadow.style.width = textarea.clientWidth + "px"; + shadow.value = textarea.value; + textarea.style.height = shadow.scrollHeight + "px"; +} + +function queryArgsFromForm(form) { + const items = []; + for (const {name, value} of form.elements) { + if (!name) continue; + items.push(encodeURIComponent(name) + '=' + encodeURIComponent(value)); + } + return items.join('&'); +} + +let hasBeenOpen = false; +function openEditor() { + const article = document.querySelector("article"); + const rendered = article.querySelector(".rendered"); + const editor = article.querySelector(".editor"); + const textarea = editor.querySelector('textarea[name="body"]'); + const shadow = editor.querySelector('textarea.shadow-control'); + const form = editor.querySelector("form"); + const cancel = editor.querySelector('.cancel'); + + const footer = document.querySelector("footer"); + const revision = footer.querySelector(".revision"); + const lastUpdated = footer.querySelector(".last-updated"); + + textarea.style.height = rendered.clientHeight + "px"; + + article.classList.add('edit'); + + autosizeTextarea(textarea, shadow); + + textarea.focus(); + + if (hasBeenOpen) return; + hasBeenOpen = true; + + textarea.addEventListener('input', () => autosizeTextarea(textarea, shadow)); + window.addEventListener('resize', () => autosizeTextarea(textarea, shadow)); + + form.addEventListener("submit", function (ev) { + ev.preventDefault(); + ev.stopPropagation(); + + (async function () { + const body = queryArgsFromForm(form); + textarea.disabled = true; + + const response = await fetch( + form.getAttribute("action"), + { + method: 'PUT', + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + body: body, + } + ); + + if (!response.ok) throw new Error("Unexpected status code (" + response.status + ")"); + + const result = await response.json(); + form.elements.base_revision.value = result.revision; + revision.textContent = result.revision; + lastUpdated.textContent = result.created; + rendered.innerHTML = result.rendered; + article.classList.remove('edit'); + + textarea.disabled = false; + }() + .catch(err => { + textarea.disabled = false; + console.error(err); + alert(err); + })); + }); + + cancel.addEventListener('click', function (ev) { + ev.preventDefault(); + ev.stopPropagation(); + + article.classList.remove('edit'); + form.reset(); + }); +} + +document + .getElementById("openEditor") + .addEventListener("click", function (ev) { + ev.preventDefault(); + ev.stopPropagation(); + + openEditor(); + }) diff --git a/assets/style.css b/assets/style.css new file mode 100644 index 0000000..115bf21 --- /dev/null +++ b/assets/style.css @@ -0,0 +1,183 @@ +html { + font-family: "Apple Garamond", "Baskerville", + "Times New Roman", "Droid Serif", "Times", + "Source Serif Pro", serif; +} + +h1 { + font-family: 'Amatic SC', cursive; + + font-weight: normal; + font-style: normal; + + font-size: 40px; + line-height: 54px; +} + +h2 { + font-family: inherit; + font-weight: bold; + font-style: normal; + font-size: 18px; + line-height: 32px; + margin-top: 32px; + margin-bottom: 0; +} + +header { + max-width: 600px; + width: 100%; + margin: 40px auto 0 auto; +} + +article { + font-size: 18px; + line-height: 32px; + max-width: 600px; + width: 100%; + margin: 0 auto 120px auto; +} + +blockquote { + margin-left: 0; + padding-left: 12px; + border-left: 4px solid #eee; +} + +p { + margin: 0 0 28px 0; +} + +code, pre { + background: #f8f8f8; + font-family: "SF Mono", "Monaco", + "Inconsolata", "Fira Mono", + "Droid Sans Mono", "Source Code Pro", + monospace; +} + +pre { + overflow: auto; +} + +a { + color: #5e90af; +} + +a:visited { + color: inherit; +} + +a:hover { + color: #79b9e1; +} + + +/* Sticky footer */ +html, body { + height: 100%; + margin: 0; + padding: 0; +} +body { + display: flex; + flex-direction: column; +} +article { + flex: 1; +} + + +footer { + padding: 16px 0; + background: #f8f8f8; + color: #444; + text-align: center; + font-family: -apple-system, BlinkMacSystemFont, + "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", + "Droid Sans", "Helvetica Neue", sans-serif; +} + +footer dl { + margin: 0; +} + +footer dt, footer dd { + display: inline; + margin: 0; +} + +footer dt::after { + content: ": "; + display: inline; +} + +footer dd::after { + content: "|"; + margin: 0 12px; +} + +footer dd:last-child::after { + content: ""; + margin: 0; +} + +textarea { + border: none; + background: none; + margin: 0; + padding: 0; + font-family: "SF Mono", "Monaco", + "Inconsolata", "Fira Mono", + "Droid Sans Mono", "Source Code Pro", + monospace; + + width: 100%; + resize: none; + overflow: hidden; +} + +.shadow-control { + visibility: hidden; + position: fixed; + height: auto; + min-height: 100px; +} + +.editor { + display: none; +} + +.editor textarea[name="body"] { + height: 600px; +} + +.edit .editor { + display: block; +} + +.edit .rendered { + display: none; +} + +.editor-controls { + position: fixed; + right: 0; + bottom: 0; + left: 0; + + background: #91A238; + padding: 10px 20px; +} + +@media (min-width: 600px) { + .editor-controls { + position: fixed; + left: auto; + right: 20px; + bottom: 20px; + + box-shadow: 2px 2px 8px rgba(0,0,0, 0.25); + } +} diff --git a/libs/static_resource_derive/Cargo.toml b/libs/static_resource_derive/Cargo.toml new file mode 100644 index 0000000..21d0f72 --- /dev/null +++ b/libs/static_resource_derive/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "static_resource_derive" +version = "0.1.0" +authors = ["Magnus Hoff "] + +[dependencies] +quote = "0.3.10" +syn = "0.10.5" +sha2 = "0.6" +base64 = "0.6" + +[lib] +proc-macro = true diff --git a/libs/static_resource_derive/src/lib.rs b/libs/static_resource_derive/src/lib.rs new file mode 100644 index 0000000..7cdb939 --- /dev/null +++ b/libs/static_resource_derive/src/lib.rs @@ -0,0 +1,115 @@ +#![recursion_limit="128"] + +#[macro_use] extern crate quote; + +extern crate base64; +extern crate proc_macro; +extern crate sha2; +extern crate syn; + +use proc_macro::TokenStream; +use std::fs::File; +use std::io::prelude::*; +use std::path::{Path, PathBuf}; + +fn user_crate_root() -> PathBuf { + std::env::current_dir().expect("Unable to get current directory") +} + +fn find_attr<'a>(attrs: &'a Vec, name: &str) -> Option<&'a str> { + attrs.iter() + .find(|&x| x.name() == name) + .and_then(|ref attr| match &attr.value { + &syn::MetaItem::NameValue(_, syn::Lit::Str(ref template, _)) => Some(template), + _ => None + }) + .map(|x| x.as_ref()) +} + +fn buf_file>(filename: P) -> Vec { + let mut f = File::open(filename) + .expect("Unable to open file for reading"); + + let mut buf = Vec::new(); + f.read_to_end(&mut buf) + .expect("Unable to read file"); + + buf +} + +fn calculate_checksum>(filename: P) -> String { + use base64::*; + use sha2::{Sha256, Digest}; + + encode_config(&Sha256::digest(&buf_file(filename)), URL_SAFE) +} + +#[proc_macro_derive(StaticResource, attributes(filename, mime))] +pub fn static_resource(input: TokenStream) -> TokenStream { + let s = input.to_string(); + let ast = syn::parse_macro_input(&s).unwrap(); + + let filename = find_attr(&ast.attrs, "filename") + .expect("The `filename` attribute must be specified"); + let abs_filename = user_crate_root().join(filename); + let abs_filename = abs_filename.to_str().expect("Absolute file path must be valid Unicode"); + + let checksum = calculate_checksum(&abs_filename); + + let mime = find_attr(&ast.attrs, "mime") + .expect("The `mime` attribute must be specified"); + + let name = &ast.ident; + let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); + + let gen = quote! { + #[allow(unused_attributes, unused_qualifications, unknown_lints, clippy)] + #[automatically_derived] + impl #impl_generics Resource for #name #ty_generics #where_clause { + fn allow(&self) -> Vec<::hyper::Method> { + use ::hyper::Method::*; + vec![Options, Head, Get] + } + + fn head(&self) -> futures::BoxFuture> { + futures::finished(Response::new() + .with_status(::hyper::StatusCode::Ok) + .with_header(::hyper::header::ContentType( + #mime.parse().expect("Statically supplied mime type must be parseable"))) + .with_header(::hyper::header::CacheControl(vec![ + ::hyper::header::CacheDirective::Extension("immutable".to_owned(), None), + ::hyper::header::CacheDirective::MaxAge(31556926), + ::hyper::header::CacheDirective::Public, + ])) + .with_header(::hyper::header::ETag(Self::etag())) + ).boxed() + } + + fn get(self: Box) -> futures::BoxFuture> { + let body = include_bytes!(#abs_filename); + + self.head().map(move |head| + head + .with_header(::hyper::header::ContentLength(body.len() as u64)) + .with_body(body as &'static [u8]) + ).boxed() + } + + fn put(self: Box, _body: hyper::Body) -> futures::BoxFuture> { + futures::finished(self.method_not_allowed()).boxed() + } + } + + impl #impl_generics #name #ty_generics #where_clause { + fn checksum() -> &'static str { + #checksum + } + + fn etag() -> ::hyper::header::EntityTag { + ::hyper::header::EntityTag::new(false, Self::checksum().to_owned()) + } + } + }; + + gen.parse().unwrap() +} diff --git a/src/main.rs b/src/main.rs index f957224..7c660e2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ #[macro_use] extern crate diesel_codegen; #[macro_use] extern crate lazy_static; #[macro_use] extern crate serde_derive; +#[macro_use] extern crate static_resource_derive; extern crate chrono; extern crate clap; diff --git a/src/site.rs b/src/site.rs index eda6939..af551f9 100644 --- a/src/site.rs +++ b/src/site.rs @@ -1,6 +1,7 @@ // #[derive(BartDisplay)] can cause unused extern crates warning: #![allow(unused_extern_crates)] +use std::collections::HashMap; use std::fmt; use futures::{self, Future}; @@ -60,6 +61,7 @@ lazy_static! { struct Layout<'a, T: 'a + fmt::Display> { pub title: &'a str, pub body: &'a T, + pub style_css_checksum: &'a str, } #[derive(BartDisplay)] @@ -70,12 +72,43 @@ struct NotFound; #[template = "templates/500.html"] struct InternalServerError; +#[derive(StaticResource)] +#[filename = "assets/style.css"] +#[mime = "text/css"] +struct StyleCss; + +#[derive(StaticResource)] +#[filename = "assets/script.js"] +#[mime = "application/javascript"] +struct ScriptJs; + struct WikiLookup { state: State, + lookup_map: HashMap Box>>, +} + +impl WikiLookup { + fn new(state: State) -> WikiLookup { + let mut lookup_map = HashMap::new(); + + lookup_map.insert( + format!("/_assets/style-{}.css", StyleCss::checksum()), + Box::new(|| Box::new(StyleCss) as Box) + as Box Box> + ); + + lookup_map.insert( + format!("/_assets/script-{}.js", ScriptJs::checksum()), + Box::new(|| Box::new(ScriptJs) as Box) + as Box Box> + ); + + WikiLookup { state, lookup_map } + } } impl Lookup for WikiLookup { - type Resource = ArticleResource; + type Resource = Box; type Error = Box<::std::error::Error + Send + Sync>; type Future = futures::BoxFuture, Self::Error>; @@ -84,14 +117,17 @@ impl Lookup for WikiLookup { if path.starts_with("/_") { // Reserved namespace - return futures::finished(None).boxed(); + + return futures::finished( + self.lookup_map.get(path).map(|x| x()) + ).boxed(); } let slug = &path[1..]; if let Ok(article_id) = slug.parse() { let state = self.state.clone(); self.state.get_article_revision_by_id(article_id) - .and_then(|x| Ok(x.map(move |article| ArticleResource::new(state, article)))) + .and_then(|x| Ok(x.map(move |article| Box::new(ArticleResource::new(state, article)) as Box))) .boxed() } else { futures::finished(None).boxed() @@ -123,7 +159,7 @@ impl Resource for ArticleResource { ).boxed() } - fn get(self) -> futures::BoxFuture> { + fn get(self: Box) -> futures::BoxFuture> { use chrono::{self, TimeZone, Local}; #[derive(BartDisplay)] @@ -136,6 +172,8 @@ impl Resource for ArticleResource { title: &'a str, raw: &'a str, rendered: String, + + script_js_checksum: &'a str, } self.head().map(move |head| @@ -149,12 +187,14 @@ impl Resource for ArticleResource { title: &self.data.title, raw: &self.data.body, rendered: render_markdown(&self.data.body), - } + script_js_checksum: ScriptJs::checksum(), + }, + style_css_checksum: StyleCss::checksum(), }.to_string()) ).boxed() } - fn put(self, body: hyper::Body) -> futures::BoxFuture> { + fn put(self: Box, body: hyper::Body) -> futures::BoxFuture> { // TODO Check incoming Content-Type use chrono::{TimeZone, Local}; @@ -206,7 +246,7 @@ pub struct Site { impl Site { pub fn new(state: State) -> Site { Site { - root: WikiLookup { state } + root: WikiLookup::new(state) } } @@ -216,6 +256,7 @@ impl Site { .with_body(Layout { title: "Not found", body: &NotFound, + style_css_checksum: StyleCss::checksum(), }.to_string()) .with_status(hyper::StatusCode::NotFound) } @@ -228,6 +269,7 @@ impl Site { .with_body(Layout { title: "Internal server error", body: &InternalServerError, + style_css_checksum: StyleCss::checksum(), }.to_string()) .with_status(hyper::StatusCode::InternalServerError) } diff --git a/src/web/lookup.rs b/src/web/lookup.rs index 6132f9c..4920ba5 100644 --- a/src/web/lookup.rs +++ b/src/web/lookup.rs @@ -1,9 +1,7 @@ -use super::resource; - use futures; pub trait Lookup { - type Resource: resource::Resource; + type Resource; type Error; type Future: futures::Future, Error=Self::Error>; diff --git a/src/web/resource.rs b/src/web/resource.rs index 93e1a12..cca5675 100644 --- a/src/web/resource.rs +++ b/src/web/resource.rs @@ -12,8 +12,8 @@ type Error = Box; pub trait Resource { fn allow(&self) -> Vec; fn head(&self) -> futures::BoxFuture; - fn get(self) -> futures::BoxFuture; - fn put(self, body: hyper::Body) -> futures::BoxFuture; + fn get(self: Box) -> futures::BoxFuture; + fn put(self: Box, body: hyper::Body) -> futures::BoxFuture; fn options(&self) -> Response { Response::new() diff --git a/templates/article_revision.html b/templates/article_revision.html index 4a2f4a5..237163b 100644 --- a/templates/article_revision.html +++ b/templates/article_revision.html @@ -1,3 +1,5 @@ + +

{{title}}

@@ -32,103 +34,3 @@
{{created}}
- - diff --git a/templates/layout.html b/templates/layout.html index 796f39b..229f638 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -4,191 +4,7 @@ {{title}} - + {{{body}}}