diff --git a/Cargo.lock b/Cargo.lock index 97d66c0..92db0d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,7 +88,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6" dependencies = [ "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -220,7 +220,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -316,35 +316,41 @@ checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" [[package]] name = "askama" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb98f10f371286b177db5eeb9a6e5396609555686a35e1d4f7b9a9c6d8af0139" +checksum = "47cbc3cf73fa8d9833727bbee4835ba5c421a0d65b72daf9a7b5d0e0f9cfb57e" dependencies = [ "askama_derive", "askama_escape", - "askama_shared", + "humansize", + "num-traits", + "percent-encoding", ] [[package]] name = "askama_actix" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c52f74f8382a142ecfc052100b21abc33f2c069e20fe345808e7ed914b179449" +checksum = "e4b0dd17cfe203b00ba3853a89fba459ecf24c759b738b244133330607c78e55" dependencies = [ "actix-web", "askama", - "askama_shared", ] [[package]] name = "askama_derive" -version = "0.11.2" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87bf87e6e8b47264efa9bde63d6225c6276a52e05e91bf37eaa8afd0032d6b71" +checksum = "c22fbe0413545c098358e56966ff22cdd039e10215ae213cfbd65032b119fc94" dependencies = [ - "askama_shared", + "basic-toml", + "mime", + "mime_guess", + "nom", "proc-macro2", - "syn", + "quote", + "serde", + "syn 2.0.10", ] [[package]] @@ -353,26 +359,6 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" -[[package]] -name = "askama_shared" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf722b94118a07fcbc6640190f247334027685d4e218b794dbfe17c32bf38ed0" -dependencies = [ - "askama_escape", - "humansize", - "mime", - "mime_guess", - "nom", - "num-traits", - "percent-encoding", - "proc-macro2", - "quote", - "serde", - "syn", - "toml", -] - [[package]] name = "async-trait" version = "0.1.64" @@ -381,7 +367,7 @@ checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -408,6 +394,15 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +[[package]] +name = "basic-toml" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c0de75129aa8d0cceaf750b89013f0e08804d6ec61416da787b35ad0d7cddf1" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -508,6 +503,7 @@ dependencies = [ "actix-web", "askama", "askama_actix", + "chrono", "clap", "git2", "lazy_static", @@ -523,9 +519,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.23" +version = "0.4.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" dependencies = [ "iana-time-zone", "js-sys", @@ -571,7 +567,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -685,7 +681,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn", + "syn 1.0.107", ] [[package]] @@ -702,7 +698,7 @@ checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -726,7 +722,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 1.0.107", ] [[package]] @@ -737,7 +733,7 @@ checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ "darling_core", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -754,7 +750,7 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -767,7 +763,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.0", - "syn", + "syn 1.0.107", ] [[package]] @@ -799,7 +795,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -900,7 +896,7 @@ checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -1095,9 +1091,12 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "humansize" -version = "1.1.1" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] [[package]] name = "iana-time-zone" @@ -1265,6 +1264,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libm" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" + [[package]] name = "libsqlite3-sys" version = "0.25.2" @@ -1660,7 +1665,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.107", "version_check", ] @@ -1677,9 +1682,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.50" +version = "1.0.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +checksum = "e472a104799c74b514a57226160104aa483546de37e839ec50e3c2e41dd87534" dependencies = [ "unicode-ident", ] @@ -1692,9 +1697,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.23" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] @@ -1808,6 +1813,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a" dependencies = [ "bitflags", + "chrono", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -1962,7 +1968,7 @@ checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -2008,7 +2014,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -2117,6 +2123,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aad1363ed6d37b84299588d62d3a7d95b5a5c2d9aad5c85609fda12afaa1f40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "take_mut" version = "0.2.2" @@ -2149,7 +2166,7 @@ checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -2233,7 +2250,7 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -2261,15 +2278,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - [[package]] name = "tracing" version = "0.1.37" @@ -2344,7 +2352,7 @@ checksum = "89851716b67b937e393b3daa8423e67ddfc4bbbf1654bcf05488e95e0828db0c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -2481,7 +2489,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.107", "wasm-bindgen-shared", ] @@ -2503,7 +2511,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/Cargo.toml b/Cargo.toml index 5ab8a72..87bc987 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,8 +14,8 @@ uuid = {version = "1.3.0", features=["v4", "fast-rng"]} actix-web = "4" actix-files = "0.6.2" actix-session = { version = "0.7.2", features = ["cookie-session"] } -askama = { version = "0.11.1", features = ["with-actix-web"] } -askama_actix = "0.13.0" +askama = { version = "0.12.0", features = ["with-actix-web"] } +askama_actix = "0.14.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" @@ -26,7 +26,8 @@ regex = "1" r2d2 = "0.8" r2d2_sqlite = "0.21.0" -rusqlite = {version = "0.28.0", features=["bundled"]} +chrono = "0.4.24" +rusqlite = {version = "0.28.0", features=["bundled", "chrono"]} lazy_static = "1.4.0" mongodb = "2.3.1" diff --git a/db/db.sql b/db/db.sql index b5a5f3e..5c8ddea 100644 --- a/db/db.sql +++ b/db/db.sql @@ -3,6 +3,10 @@ create table pages ( page_name text, page_text text, active boolean default true not null, + creation_date date, + modification_date date, + parent_domain text, + parent_page text, primary key (domain, page_name) ); diff --git a/db/db.sqlite3 b/db/db.sqlite3 index 4e800b2..cb3fed2 100644 Binary files a/db/db.sqlite3 and b/db/db.sqlite3 differ diff --git a/src/db.rs b/src/db.rs index e1c6a2c..d3c6387 100644 --- a/src/db.rs +++ b/src/db.rs @@ -14,6 +14,7 @@ fn uuid4() -> String { #[derive(Debug, Serialize, Deserialize)] pub struct Page { pub domain: String, + pub domain_name: String, pub page_name: String, pub page_text: String, } @@ -45,6 +46,34 @@ pub async fn get_domains(pool: &Pool) -> Result, Error> { .map_err(error::ErrorInternalServerError) } +pub async fn get_pages_by_domain( + pool: &Pool, + domain: String, +) -> Result, Error> { + let pool = pool.clone(); + + let conn = web::block(move || pool.get()) + .await? + .map_err(error::ErrorInternalServerError)?; + + web::block(move || { + let mut stmt = conn + .prepare("SELECT domain, REPLACE(domain, '_', ' '), page_name from pages WHERE active=true and domain=?")?; + + stmt.query_map([domain], |row| { + Ok(Page { + domain: row.get(0)?, + domain_name: row.get(1)?, + page_name: row.get(2)?, + page_text: String::from(""), + }) + }) + .and_then(Iterator::collect) + }) + .await? + .map_err(error::ErrorInternalServerError) +} + pub async fn get_page_by_name( pool: &Pool, domain: String, @@ -58,13 +87,14 @@ pub async fn get_page_by_name( web::block(move || { let mut stmt = conn - .prepare("SELECT domain, page_name, page_text from pages WHERE active=true and domain=? and page_name=?")?; + .prepare("SELECT domain, REPLACE(domain, '_', ' '), page_name, page_text from pages WHERE active=true and domain=? and page_name=?")?; stmt.query_map([domain, pagename], |row| { Ok(Page { domain: row.get(0)?, - page_name: row.get(1)?, - page_text: row.get(2)?, + domain_name: row.get(1)?, + page_name: row.get(2)?, + page_text: row.get(3)?, }) }) .and_then(Iterator::collect) @@ -78,6 +108,8 @@ pub async fn update_page( domain: String, page_name: String, page_text: String, + parent_domain: Option<&str>, + parent_page: Option<&str> ) -> Result { let pool = pool.clone(); @@ -85,10 +117,20 @@ pub async fn update_page( .await? .map_err(error::ErrorInternalServerError)?; + let parent_domain = match parent_domain { + Some(pd) => pd, + None => "" + }.to_owned(); + + let parent_page = match parent_page { + Some(pp) => pp, + None => "" + }.to_owned(); + web::block(move || { let mut stmt = conn - .prepare("insert or replace into pages (domain, page_name, page_text, active) values (?, ?, ?, true)")?; - stmt.execute([domain, page_name, page_text, ]) + .prepare("insert or replace into pages (domain, page_name, page_text, active, parent_domain, parent_page) values (?, ?, ?, true, ?, ?)")?; + stmt.execute([domain.to_owned(), page_name.to_owned(), page_text.to_owned(), parent_domain, parent_page ]) }) .await? .map_err(error::ErrorInternalServerError) diff --git a/src/domain.rs b/src/domain.rs new file mode 100644 index 0000000..867214f --- /dev/null +++ b/src/domain.rs @@ -0,0 +1,29 @@ +use actix_web::web; +use actix_web::{Responder, get}; + +use askama_actix::Template; +use askama_actix::TemplateToResponse; + +use crate::commons::AppData; +use crate::db; +use crate::db::Page; + +#[derive(Template)] +#[template(path = "domain.html")] +pub struct DomainTemplate { + pub app_name: String, + pub base_url: String, + pub domain: String, + pub pages: Vec, +} + +#[get("/{domaine}")] +async fn domain(path: web::Path, data: web::Data) -> impl Responder { + let app_name = data.app_name.to_owned(); + let base_url = data.base_url.to_owned(); + let domain = path.to_string(); + + let pages = db::get_pages_by_domain(&data.db_pool, domain.to_owned()).await.unwrap(); + + DomainTemplate { app_name, base_url, domain: domain.to_owned(), pages }.to_response() +} diff --git a/src/lib.rs b/src/lib.rs index da8829f..96d10e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ pub mod page; pub mod index; +pub mod domain; pub mod commons; pub mod db; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 9a14f11..131f3b3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ use r2d2::Pool; use r2d2_sqlite::{self, SqliteConnectionManager}; use cheezenotes::commons::{Arguments, AppData}; -use cheezenotes::{ page, index }; +use cheezenotes::{ page, index, domain }; #[actix_web::main] async fn main() -> std::io::Result<()> { @@ -34,6 +34,7 @@ async fn main() -> std::io::Result<()> { .service(actix_files::Files::new("/static/modules", "./static/modules")) .service(page::page) .service(page::save_page) + .service(domain::domain) }) .bind((ip, port))? .run() diff --git a/src/page.rs b/src/page.rs index 24b4107..8b208ac 100644 --- a/src/page.rs +++ b/src/page.rs @@ -1,8 +1,9 @@ +use actix_web::body::BoxBody; use actix_web::http::header::ContentType; use actix_web::web::Query; -use actix_web::{get, put, Responder}; +use actix_web::{get, put, Responder, HttpRequest}; use actix_web::{web, HttpResponse}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use askama_actix::Template; use askama_actix::TemplateToResponse; @@ -27,6 +28,27 @@ struct QueryParams { pub fromPage: Option, } +#[derive(Serialize, Deserialize)] +pub struct Page { + pub domain: String, + pub page: String, + pub page_text: String, + pub parent_domain: String, + pub parent_page: String, +} +impl Responder for Page { + type Body = BoxBody; + + fn respond_to(self, _req: &HttpRequest) -> HttpResponse { + let res_body = serde_json::to_string(&self).unwrap(); + + // Create HttpResponse and set Content Type + HttpResponse::Ok() + .content_type(ContentType::json()) + .body(res_body) + } +} + fn new_page_text( page_name: String, domain_from: &Option, @@ -98,7 +120,7 @@ async fn save_page( if pagename == "index" { return HttpResponse::Ok(); } - db::update_page(&data.db_pool, domain.to_owned(), pagename.to_owned(), body) + db::update_page(&data.db_pool, domain.to_owned(), pagename.to_owned(), body.to_owned(), None, None) .await .unwrap(); HttpResponse::Ok() diff --git a/static/cheezenotes.css b/static/cheezenotes.css index 1ec4f7f..9f00586 100644 --- a/static/cheezenotes.css +++ b/static/cheezenotes.css @@ -27,6 +27,9 @@ html { font-family: system-ui; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + /*font-family: "Freestyle script",Ubuntu;*/ font-size: 12pt; } @@ -184,6 +187,7 @@ div#cheezenotes:focus { div#cheezenotes input.checkbox { vertical-align: bottom; + margin-right: 1em; } div#cheezenotes div.line { diff --git a/static/modules/cheezenotes.js b/static/modules/cheezenotes.js index b81f16f..973c626 100644 --- a/static/modules/cheezenotes.js +++ b/static/modules/cheezenotes.js @@ -356,6 +356,10 @@ function onpopstate(e) { }).catch((error) => { alert(error); }); } +function onparentbutton(e) { + +} + function init(domain, page) { let cheezenotes = document.getElementById('cheezenotes'); window.domain = domain; @@ -378,6 +382,16 @@ function init(domain, page) { let saveButton = addButton('saveButton', ':save', onsave); saveButton.disabled = true; + addOnOffButton('editModeButton', ':edit_note', ':visibility', + function (e) { + if (editModeButton.classList.contains('buttonon')) { + cheezenotes.contentEditable = false; + } else { + cheezenotes.contentEditable = true; + } + }, true); + addOnOffButton('taButton', ':notes', ':notes', ontextarea); + addButton('parentButton', ':arrow_upward', onparentbutton); addSeparator(); addButton('boldButton', ':format_bold', onboldbutton); addButton('italicButton', ':format_italic', onitalicbutton); @@ -387,27 +401,10 @@ function init(domain, page) { addButton('h1Button', 'H1', onh1button); addButton('h2Button', 'H2', onh2button); addButton('h3Button', 'H3', onh3button); - addButton('bqButton', ':format_quote', onbqbutton); - addSeparator(); - addOnOffButton('editModeButton', ':edit_note', ':visibility', - function (e) { - if (editModeButton.classList.contains('buttonon')) { - cheezenotes.contentEditable = false; - } else { - cheezenotes.contentEditable = true; - } - }, true); - lastButton(addOnOffButton('taButton', ':notes', ':notes', ontextarea)); + lastButton(addButton('bqButton', ':format_quote', onbqbutton)); disableFormatButtons(); - /*if (pagename != null) { - fetch(pagename + "?data=").then((response) => { - let ta = document.getElementById('ta'); - response.text().then((data) => { ta.value = data; onload(); }); - }).catch((error) => { alert(error); }); - }*/ - onload(); } diff --git a/static/modules/md.js b/static/modules/md.js index f7b5529..ea27dec 100644 --- a/static/modules/md.js +++ b/static/modules/md.js @@ -15,6 +15,7 @@ function load(textarea, div) { let lines = textarea.value.split('\n'); for (let i = 0; i < lines.length; i++) { let line = lines[i]; + line.replace('<', '<'); let elem = formatLine(line); div.append(elem); } @@ -125,11 +126,6 @@ function onlink(e) { window.page = page; load(document.getElementById('ta'), cheezenotes); - /*let editModeButton = document.getElementById('editModeButton'); - if (editModeButton.classList.contains('buttonoff')) { - let cheezenotes = document.getElementById('cheezenotes'); - cheezenotes.contentEditable = false; - }*/ content.scrollTop = 0; content.scrollLeft = 0; }); @@ -148,6 +144,19 @@ function onlinkout(e) { } } +function oncheckbox(e) { + let cb = e.currentTarget; + let line = cb.parentNode; + let text = line.innerText; + if (cb.checked) { + text = text.replace(/^(\s*)(\[\])/, "$1[v]"); + } else { + text = text.replace(/^(\s*)(\[[vVxX]\])/, "$1[]"); + } + let newline = formatLine(text); + line.parentNode.replaceChild(newline, line); +} + function formatLine(line) { let token = null; let elem = document.createElement('div'); @@ -255,6 +264,15 @@ function formatLine(line) { line = addLink(line, listLink); line = addMono(line, listMono); elem.innerHTML = line; + + let checkboxes = elem.getElementsByClassName('checkbox'); + for (let i = 0; i < checkboxes.length; i++) { + let checkbox = checkboxes[i]; + checkbox.addEventListener('mouseover', onlinkin); + checkbox.addEventListener('change', oncheckbox); + checkbox.addEventListener('mouseout', onlinkout); + } + if (elem.getElementsByClassName('tablerow').length > 0) { if (elem.childNodes[0].childNodes.length > 0) { let child = elem.childNodes[0].childNodes[0]; @@ -551,14 +569,6 @@ function addLink(line, listLink) { return line; } -/*function formatLink(link) { - link = link.replace(/(\[!([^\]]*?)\]\(([^\)]+?)\))/i, '[!$2]($3)'); - link = link.replace(/(\[([^\]]+?)\]\(([^\)]+?)\))/i, '[$2]($3)'); - link = link.replace(/(\[([^\]]+?)\]\(\))/i, '[$2]()'); - link = link.replace(/(\[\]\(([^\)]+?)\))/i, '[]($2)'); - return link; -}*/ - function formatLink(link) { let matches = link.match(/\[(.*)\]\((.*)\)/); let libelle = matches[1]; diff --git a/templates/domain.html b/templates/domain.html new file mode 100644 index 0000000..f1c889f --- /dev/null +++ b/templates/domain.html @@ -0,0 +1,19 @@ + + + + + + + + + + {{app_name}} {{ domain }} + + + +

{{app_name}} {{ domain }}

+ {% for page in pages %} +

{{ page.page_name }}

+ {% endfor %} + +