Showing
31 changed files
with
1203 additions
and
0 deletions
Cargo.toml
0 → 100644
Makefile
0 → 100644
1 | +.PHONY: start wasm build run clean | ||
2 | + | ||
3 | +PROFILE ?= dev | ||
4 | +ifeq "$(PROFILE)" "release" | ||
5 | +PROFILE = --release | ||
6 | +else | ||
7 | +PROFILE = | ||
8 | +endif | ||
9 | + | ||
10 | +start: | ||
11 | + systemfd --no-pid -s http::3000 -- \ | ||
12 | + cargo watch -i static/ -s "make run" | ||
13 | + | ||
14 | +wasm: | ||
15 | + wasm-pack build $(PROFILE) -d ../static/ui -t web ./ui | ||
16 | + | ||
17 | +build: | ||
18 | + cargo build $(PROFILE) --bin artshop-server | ||
19 | + | ||
20 | +run: build wasm | ||
21 | + cargo run --bin artshop-server | ||
22 | + | ||
23 | +release: | ||
24 | + docker build -t artshop -f build/Dockerfile . | ||
25 | + | ||
26 | +clean: | ||
27 | + cargo clean | ||
28 | + rm -Rf ./static/ui |
crud.db
0 → 100644
No preview for this file type
diesel.toml
0 → 100644
docs/dead-code.rs
0 → 100644
1 | +async fn logic( mut rx_logic: broadcast::Receiver<AppLogic> | ||
2 | + , tx_view: broadcast::Sender<AppView> | ||
3 | + , mut rx_dom: broadcast::Receiver<Dom> ) { | ||
4 | + let mut clicks = 0u32; | ||
5 | + let dom = rx_dom.next().await.unwrap(); | ||
6 | + | ||
7 | + while let Some(msg) = rx_logic.next().await { | ||
8 | + if let Either::Left(dom_js) = dom.inner_read() { | ||
9 | + /* | ||
10 | + * Here we can get the whole dom element with all sub elements and | ||
11 | + * text data. | ||
12 | + * Needed to get the contenteditable dom element content. | ||
13 | + */ | ||
14 | + let text = dom_js | ||
15 | + . to_owned() | ||
16 | + . dyn_into::<Node>().unwrap() | ||
17 | + . first_child().unwrap() | ||
18 | + . first_child().unwrap(); | ||
19 | + | ||
20 | + console::log_1(text.as_ref()); | ||
21 | + } | ||
22 | + | ||
23 | + match msg { | ||
24 | + AppLogic::Click => { | ||
25 | + clicks += 1; | ||
26 | + tx_view.broadcast(AppView::Clicked(clicks)).await.unwrap(); | ||
27 | + }, | ||
28 | + _ => () | ||
29 | + } | ||
30 | + } | ||
31 | +} | ||
32 | + | ||
33 | +fn view( tx_logic: broadcast::Sender<AppLogic> | ||
34 | + , rx_view: broadcast::Receiver<AppView> | ||
35 | + , tx_dom: broadcast::Sender<Dom> | ||
36 | +) -> ViewBuilder<Dom> { | ||
37 | + builder! { | ||
38 | + <div style:float="left" | ||
39 | + style:padding="1em" | ||
40 | + style:border_radius=".5em" | ||
41 | + style:border="1px solid #ddd" | ||
42 | + style:background="#f7f7f7" | ||
43 | + style:cursor="pointer" | ||
44 | + on:click=tx_logic.sink().contra_map(|_| AppLogic::Click) | ||
45 | + capture:view=tx_dom.sink()> | ||
46 | + <p> | ||
47 | + {( | ||
48 | + "Hello from mogwai!", | ||
49 | + rx_view.to_owned().map(|msg| { | ||
50 | + match msg { | ||
51 | + AppView::Clicked(1) => | ||
52 | + format!("Caught 1 click, click again 😀"), | ||
53 | + AppView::Clicked(n) => | ||
54 | + format!("Caught {} clicks", n), | ||
55 | + } | ||
56 | + }) | ||
57 | + )} | ||
58 | + </p> | ||
59 | + </div> | ||
60 | + } | ||
61 | +} | ||
62 | + |
docs/editor.html
0 → 100644
1 | +<!doctype html> | ||
2 | +<html> | ||
3 | +<head> | ||
4 | +<title>Rich Text Editor</title> | ||
5 | +<script type="text/javascript"> | ||
6 | + var oDoc, sDefTxt; | ||
7 | + | ||
8 | + function initDoc() { | ||
9 | + oDoc = document.getElementById("textBox"); | ||
10 | + sDefTxt = oDoc.innerHTML; | ||
11 | + if (document.compForm.switchMode.checked) { setDocMode(true); } | ||
12 | + } | ||
13 | + | ||
14 | + function formatDoc(sCmd, sValue) { | ||
15 | + if (validateMode()) { | ||
16 | + document.execCommand(sCmd, false, sValue); oDoc.focus(); | ||
17 | + } | ||
18 | + } | ||
19 | + | ||
20 | + function validateMode() { | ||
21 | + if (!document.compForm.switchMode.checked) { return true ; } | ||
22 | + alert("Uncheck \"Show HTML\"."); | ||
23 | + oDoc.focus(); | ||
24 | + return false; | ||
25 | + } | ||
26 | + | ||
27 | +function setDocMode(bToSource) { | ||
28 | + var oContent; | ||
29 | + if (bToSource) { | ||
30 | + oContent = document.createTextNode(oDoc.innerHTML); | ||
31 | + oDoc.innerHTML = ""; | ||
32 | + var oPre = document.createElement("pre"); | ||
33 | + oDoc.contentEditable = false; | ||
34 | + oPre.id = "sourceText"; | ||
35 | + oPre.contentEditable = true; | ||
36 | + oPre.appendChild(oContent); | ||
37 | + oDoc.appendChild(oPre); | ||
38 | + document.execCommand("defaultParagraphSeparator", false, "div"); | ||
39 | + } else { | ||
40 | + if (document.all) { | ||
41 | + oDoc.innerHTML = oDoc.innerText; | ||
42 | + } else { | ||
43 | + oContent = document.createRange(); | ||
44 | + oContent.selectNodeContents(oDoc.firstChild); | ||
45 | + oDoc.innerHTML = oContent.toString(); | ||
46 | + } | ||
47 | + oDoc.contentEditable = true; | ||
48 | + } | ||
49 | + oDoc.focus(); | ||
50 | +} | ||
51 | + | ||
52 | +function printDoc() { | ||
53 | + if (!validateMode()) { return; } | ||
54 | + var oPrntWin = window.open( "" | ||
55 | + , "_blank" | ||
56 | + , "width=450,height=470,left=400,top=100,menubar=yes,toolbar=no,location=no,scrollbars=yes" ); | ||
57 | + oPrntWin.document.open(); | ||
58 | + oPrntWin.document.write("<!doctype html><html><head><title>Print<\/title><\/head><body onload=\"print();\">" + oDoc.innerHTML + "<\/body><\/html>"); | ||
59 | + oPrntWin.document.close(); | ||
60 | +} | ||
61 | +</script> | ||
62 | +<style type="text/css"> | ||
63 | +.intLink { cursor: pointer; } | ||
64 | +img.intLink { border: 0; } | ||
65 | +#toolBar1 select { font-size:10px; } | ||
66 | +#textBox { | ||
67 | + width: 540px; | ||
68 | + height: 200px; | ||
69 | + border: 1px #000000 solid; | ||
70 | + padding: 12px; | ||
71 | + overflow: scroll; | ||
72 | +} | ||
73 | +#textBox #sourceText { | ||
74 | + padding: 0; | ||
75 | + margin: 0; | ||
76 | + min-width: 498px; | ||
77 | + min-height: 200px; | ||
78 | +} | ||
79 | +#editMode label { cursor: pointer; } | ||
80 | +</style> | ||
81 | +</head> | ||
82 | +<body onload="initDoc();"> | ||
83 | +<form name="compForm" | ||
84 | + method="post" | ||
85 | + action="sample.php" | ||
86 | + onsubmit=" if(validateMode()) { | ||
87 | + this.myDoc.value=oDoc.innerHTML; | ||
88 | + return true; | ||
89 | + } | ||
90 | + return false; "> | ||
91 | +<input type="hidden" name="myDoc"> | ||
92 | +<div id="toolBar1"> | ||
93 | +<select onchange="formatDoc('formatblock',this[this.selectedIndex].value);this.selectedIndex=0;"> | ||
94 | +<option selected>- formatting -</option> | ||
95 | +<option value="h1">Title 1 <h1></option> | ||
96 | +<option value="h2">Title 2 <h2></option> | ||
97 | +<option value="h3">Title 3 <h3></option> | ||
98 | +<option value="h4">Title 4 <h4></option> | ||
99 | +<option value="h5">Title 5 <h5></option> | ||
100 | +<option value="h6">Subtitle <h6></option> | ||
101 | +<option value="p">Paragraph <p></option> | ||
102 | +<option value="pre">Preformatted <pre></option> | ||
103 | +</select> | ||
104 | +<select onchange="formatDoc('fontname',this[this.selectedIndex].value);this.selectedIndex=0;"> | ||
105 | +<option class="heading" selected>- font -</option> | ||
106 | +<option>Arial</option> | ||
107 | +<option>Arial Black</option> | ||
108 | +<option>Courier New</option> | ||
109 | +<option>Times New Roman</option> | ||
110 | +</select> | ||
111 | +<select onchange="formatDoc('fontsize',this[this.selectedIndex].value);this.selectedIndex=0;"> | ||
112 | +<option class="heading" selected>- size -</option> | ||
113 | +<option value="1">Very small</option> | ||
114 | +<option value="2">A bit small</option> | ||
115 | +<option value="3">Normal</option> | ||
116 | +<option value="4">Medium-large</option> | ||
117 | +<option value="5">Big</option> | ||
118 | +<option value="6">Very big</option> | ||
119 | +<option value="7">Maximum</option> | ||
120 | +</select> | ||
121 | +<select onchange="formatDoc('forecolor',this[this.selectedIndex].value);this.selectedIndex=0;"> | ||
122 | +<option class="heading" selected>- color -</option> | ||
123 | +<option value="red">Red</option> | ||
124 | +<option value="blue">Blue</option> | ||
125 | +<option value="green">Green</option> | ||
126 | +<option value="black">Black</option> | ||
127 | +</select> | ||
128 | +<select onchange="formatDoc('backcolor',this[this.selectedIndex].value);this.selectedIndex=0;"> | ||
129 | +<option class="heading" selected>- background -</option> | ||
130 | +<option value="red">Red</option> | ||
131 | +<option value="green">Green</option> | ||
132 | +<option value="black">Black</option> | ||
133 | +</select> | ||
134 | +</div> | ||
135 | +<div id="toolBar2"> | ||
136 | +<img class="intLink" title="Clean" onclick="if(validateMode()&&confirm('Are you sure?')){oDoc.innerHTML=sDefTxt};" src="data:image/gif;base64,R0lGODlhFgAWAIQbAD04KTRLYzFRjlldZl9vj1dusY14WYODhpWIbbSVFY6O7IOXw5qbms+wUbCztca0ccS4kdDQjdTLtMrL1O3YitHa7OPcsd/f4PfvrvDv8Pv5xv///////////////////yH5BAEKAB8ALAAAAAAWABYAAAV84CeOZGmeaKqubMteyzK547QoBcFWTm/jgsHq4rhMLoxFIehQQSAWR+Z4IAyaJ0kEgtFoLIzLwRE4oCQWrxoTOTAIhMCZ0tVgMBQKZHAYyFEWEV14eQ8IflhnEHmFDQkAiSkQCI2PDC4QBg+OAJc0ewadNCOgo6anqKkoIQA7" /> | ||
137 | +<img class="intLink" title="Print" onclick="printDoc();" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAABGdBTUEAALGPC/xhBQAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9oEBxcZFmGboiwAAAAIdEVYdENvbW1lbnQA9syWvwAAAuFJREFUOMvtlUtsjFEUx//n3nn0YdpBh1abRpt4LFqtqkc3jRKkNEIsiIRIBBEhJJpKlIVo4m1RRMKKjQiRMJRUqUdKPT71qpIpiRKPaqdF55tv5vvusZjQTjOlseUkd3Xu/3dPzusC/22wtu2wRn+jG5So/OCDh8ycMJDflehMlkJkVK7KUYN+ufzA/RttH76zaVocDptRxzQtNi3mRWuPc+6cKtlXZ/sddP2uu9uXlmYXZ6Qm8v4Tz8lhF1H+zDQXt7S8oLMXtbF4e8QaFHjj3kbP2MzkktHpiTjp9VH6iHiA+whtAsX5brpwueMGdONdf/2A4M7ukDs1JW662+XkqTkeUoqjKtOjm2h53YFL15pSJ04Zc94wdtibr26fXlC2mzRvBccEbz2kiRFD414tKMlEZbVGT33+qCoHgha81SWYsew0r1uzfNylmtpx80pngQQ91LwVk2JGvGnfvZG6YcYRAT16GFtW5kKKfo1EQLtfh5Q2etT0BIWF+aitq4fDbk+ImYo1OxvGF03waFJQvBCkvDffRyEtxQiFFYgAZTHS0zwAGD7fG5TNnYNTp8/FzvGwJOfmgG7GOx0SAKKgQgDMgKBI0NJGMEImpGDk5+WACEwEd0ywblhGUZ4Hw5OdUekRBLT7DTgdEgxACsIznx8zpmWh7k4rkpJcuHDxCul6MDsmmBXDlWCH2+XozSgBnzsNCEE4euYV4pwCpsWYPW0UHDYBKSWu1NYjENDReqtKjwn2+zvtTc1vMSTB/mvev/WEYSlASsLimcOhOBJxw+N3aP/SjefNL5GePZmpu4kG7OPr1+tOfPyUu3BecWYKcwQcDFmwFKAUo90fhKDInBCAmvqnyMgqUEagQwCoHBDc1rjv9pIlD8IbVkz6qYViIBQGTJPx4k0XpIgEZoRN1Da0cij4VfR0ta3WvBXH/rjdCufv6R2zPgPH/e4pxSBCpeatqPrjNiso203/5s/zA171Mv8+w1LOAAAAAElFTkSuQmCC"> | ||
138 | +<img class="intLink" title="Undo" onclick="formatDoc('undo');" src="data:image/gif;base64,R0lGODlhFgAWAOMKADljwliE33mOrpGjuYKl8aezxqPD+7/I19DV3NHa7P///////////////////////yH5BAEKAA8ALAAAAAAWABYAAARR8MlJq7046807TkaYeJJBnES4EeUJvIGapWYAC0CsocQ7SDlWJkAkCA6ToMYWIARGQF3mRQVIEjkkSVLIbSfEwhdRIH4fh/DZMICe3/C4nBQBADs=" /> | ||
139 | +<img class="intLink" title="Redo" onclick="formatDoc('redo');" src="data:image/gif;base64,R0lGODlhFgAWAMIHAB1ChDljwl9vj1iE34Kl8aPD+7/I1////yH5BAEKAAcALAAAAAAWABYAAANKeLrc/jDKSesyphi7SiEgsVXZEATDICqBVJjpqWZt9NaEDNbQK1wCQsxlYnxMAImhyDoFAElJasRRvAZVRqqQXUy7Cgx4TC6bswkAOw==" /> | ||
140 | +<img class="intLink" title="Remove formatting" onclick="formatDoc('removeFormat')" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAABGdBTUEAALGPC/xhBQAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9oECQMCKPI8CIIAAAAIdEVYdENvbW1lbnQA9syWvwAAAuhJREFUOMtjYBgFxAB501ZWBvVaL2nHnlmk6mXCJbF69zU+Hz/9fB5O1lx+bg45qhl8/fYr5it3XrP/YWTUvvvk3VeqGXz70TvbJy8+Wv39+2/Hz19/mGwjZzuTYjALuoBv9jImaXHeyD3H7kU8fPj2ICML8z92dlbtMzdeiG3fco7J08foH1kurkm3E9iw54YvKwuTuom+LPt/BgbWf3//sf37/1/c02cCG1lB8f//f95DZx74MTMzshhoSm6szrQ/a6Ir/Z2RkfEjBxuLYFpDiDi6Af///2ckaHBp7+7wmavP5n76+P2ClrLIYl8H9W36auJCbCxM4szMTJac7Kza////R3H1w2cfWAgafPbqs5g7D95++/P1B4+ECK8tAwMDw/1H7159+/7r7ZcvPz4fOHbzEwMDwx8GBgaGnNatfHZx8zqrJ+4VJBh5CQEGOySEua/v3n7hXmqI8WUGBgYGL3vVG7fuPK3i5GD9/fja7ZsMDAzMG/Ze52mZeSj4yu1XEq/ff7W5dvfVAS1lsXc4Db7z8C3r8p7Qjf///2dnZGxlqJuyr3rPqQd/Hhyu7oSpYWScylDQsd3kzvnH738wMDzj5GBN1VIWW4c3KDon7VOvm7S3paB9u5qsU5/x5KUnlY+eexQbkLNsErK61+++VnAJcfkyMTIwffj0QwZbJDKjcETs1Y8evyd48toz8y/ffzv//vPP4veffxpX77z6l5JewHPu8MqTDAwMDLzyrjb/mZm0JcT5Lj+89+Ybm6zz95oMh7s4XbygN3Sluq4Mj5K8iKMgP4f0////fv77//8nLy+7MCcXmyYDAwODS9jM9tcvPypd35pne3ljdjvj26+H2dhYpuENikgfvQeXNmSl3tqepxXsqhXPyc666s+fv1fMdKR3TK72zpix8nTc7bdfhfkEeVbC9KhbK/9iYWHiErbu6MWbY/7//8/4//9/pgOnH6jGVazvFDRtq2VgiBIZrUTIBgCk+ivHvuEKwAAAAABJRU5ErkJggg=="> | ||
141 | +<img class="intLink" title="Bold" onclick="formatDoc('bold');" src="data:image/gif;base64,R0lGODlhFgAWAID/AMDAwAAAACH5BAEAAAAALAAAAAAWABYAQAInhI+pa+H9mJy0LhdgtrxzDG5WGFVk6aXqyk6Y9kXvKKNuLbb6zgMFADs=" /> | ||
142 | +<img class="intLink" title="Italic" onclick="formatDoc('italic');" src="data:image/gif;base64,R0lGODlhFgAWAKEDAAAAAF9vj5WIbf///yH5BAEAAAMALAAAAAAWABYAAAIjnI+py+0Po5x0gXvruEKHrF2BB1YiCWgbMFIYpsbyTNd2UwAAOw==" /> | ||
143 | +<img class="intLink" title="Underline" onclick="formatDoc('underline');" src="data:image/gif;base64,R0lGODlhFgAWAKECAAAAAF9vj////////yH5BAEAAAIALAAAAAAWABYAAAIrlI+py+0Po5zUgAsEzvEeL4Ea15EiJJ5PSqJmuwKBEKgxVuXWtun+DwxCCgA7" /> | ||
144 | +<img class="intLink" title="Left align" onclick="formatDoc('justifyleft');" src="data:image/gif;base64,R0lGODlhFgAWAID/AMDAwAAAACH5BAEAAAAALAAAAAAWABYAQAIghI+py+0Po5y02ouz3jL4D4JMGELkGYxo+qzl4nKyXAAAOw==" /> | ||
145 | +<img class="intLink" title="Center align" onclick="formatDoc('justifycenter');" src="data:image/gif;base64,R0lGODlhFgAWAID/AMDAwAAAACH5BAEAAAAALAAAAAAWABYAQAIfhI+py+0Po5y02ouz3jL4D4JOGI7kaZ5Bqn4sycVbAQA7" /> | ||
146 | +<img class="intLink" title="Right align" onclick="formatDoc('justifyright');" src="data:image/gif;base64,R0lGODlhFgAWAID/AMDAwAAAACH5BAEAAAAALAAAAAAWABYAQAIghI+py+0Po5y02ouz3jL4D4JQGDLkGYxouqzl43JyVgAAOw==" /> | ||
147 | +<img class="intLink" title="Numbered list" onclick="formatDoc('insertorderedlist');" src="data:image/gif;base64,R0lGODlhFgAWAMIGAAAAADljwliE35GjuaezxtHa7P///////yH5BAEAAAcALAAAAAAWABYAAAM2eLrc/jDKSespwjoRFvggCBUBoTFBeq6QIAysQnRHaEOzyaZ07Lu9lUBnC0UGQU1K52s6n5oEADs=" /> | ||
148 | +<img class="intLink" title="Dotted list" onclick="formatDoc('insertunorderedlist');" src="data:image/gif;base64,R0lGODlhFgAWAMIGAAAAAB1ChF9vj1iE33mOrqezxv///////yH5BAEAAAcALAAAAAAWABYAAAMyeLrc/jDKSesppNhGRlBAKIZRERBbqm6YtnbfMY7lud64UwiuKnigGQliQuWOyKQykgAAOw==" /> | ||
149 | +<img class="intLink" title="Quote" onclick="formatDoc('formatblock','blockquote');" src="data:image/gif;base64,R0lGODlhFgAWAIQXAC1NqjFRjkBgmT9nqUJnsk9xrFJ7u2R9qmKBt1iGzHmOrm6Sz4OXw3Odz4Cl2ZSnw6KxyqO306K63bG70bTB0rDI3bvI4P///////////////////////////////////yH5BAEKAB8ALAAAAAAWABYAAAVP4CeOZGmeaKqubEs2CekkErvEI1zZuOgYFlakECEZFi0GgTGKEBATFmJAVXweVOoKEQgABB9IQDCmrLpjETrQQlhHjINrTq/b7/i8fp8PAQA7" /> | ||
150 | +<img class="intLink" title="Delete indentation" onclick="formatDoc('outdent');" src="data:image/gif;base64,R0lGODlhFgAWAMIHAAAAADljwliE35GjuaezxtDV3NHa7P///yH5BAEAAAcALAAAAAAWABYAAAM2eLrc/jDKCQG9F2i7u8agQgyK1z2EIBil+TWqEMxhMczsYVJ3e4ahk+sFnAgtxSQDqWw6n5cEADs=" /> | ||
151 | +<img class="intLink" title="Add indentation" onclick="formatDoc('indent');" src="data:image/gif;base64,R0lGODlhFgAWAOMIAAAAADljwl9vj1iE35GjuaezxtDV3NHa7P///////////////////////////////yH5BAEAAAgALAAAAAAWABYAAAQ7EMlJq704650B/x8gemMpgugwHJNZXodKsO5oqUOgo5KhBwWESyMQsCRDHu9VOyk5TM9zSpFSr9gsJwIAOw==" /> | ||
152 | +<img class="intLink" title="Hyperlink" onclick="var sLnk=prompt('Write the URL here','http:\/\/');if(sLnk&&sLnk!=''&&sLnk!='http://'){formatDoc('createlink',sLnk)}" src="data:image/gif;base64,R0lGODlhFgAWAOMKAB1ChDRLY19vj3mOrpGjuaezxrCztb/I19Ha7Pv8/f///////////////////////yH5BAEKAA8ALAAAAAAWABYAAARY8MlJq7046827/2BYIQVhHg9pEgVGIklyDEUBy/RlE4FQF4dCj2AQXAiJQDCWQCAEBwIioEMQBgSAFhDAGghGi9XgHAhMNoSZgJkJei33UESv2+/4vD4TAQA7" /> | ||
153 | +<img class="intLink" title="Cut" onclick="formatDoc('cut');" src="data:image/gif;base64,R0lGODlhFgAWAIQSAB1ChBFNsRJTySJYwjljwkxwl19vj1dusYODhl6MnHmOrpqbmpGjuaezxrCztcDCxL/I18rL1P///////////////////////////////////////////////////////yH5BAEAAB8ALAAAAAAWABYAAAVu4CeOZGmeaKqubDs6TNnEbGNApNG0kbGMi5trwcA9GArXh+FAfBAw5UexUDAQESkRsfhJPwaH4YsEGAAJGisRGAQY7UCC9ZAXBB+74LGCRxIEHwAHdWooDgGJcwpxDisQBQRjIgkDCVlfmZqbmiEAOw==" /> | ||
154 | +<img class="intLink" title="Copy" onclick="formatDoc('copy');" src="data:image/gif;base64,R0lGODlhFgAWAIQcAB1ChBFNsTRLYyJYwjljwl9vj1iE31iGzF6MnHWX9HOdz5GjuYCl2YKl8ZOt4qezxqK63aK/9KPD+7DI3b/I17LM/MrL1MLY9NHa7OPs++bx/Pv8/f///////////////yH5BAEAAB8ALAAAAAAWABYAAAWG4CeOZGmeaKqubOum1SQ/kPVOW749BeVSus2CgrCxHptLBbOQxCSNCCaF1GUqwQbBd0JGJAyGJJiobE+LnCaDcXAaEoxhQACgNw0FQx9kP+wmaRgYFBQNeAoGihCAJQsCkJAKOhgXEw8BLQYciooHf5o7EA+kC40qBKkAAAGrpy+wsbKzIiEAOw==" /> | ||
155 | +<img class="intLink" title="Paste" onclick="formatDoc('paste');" src="data:image/gif;base64,R0lGODlhFgAWAIQUAD04KTRLY2tXQF9vj414WZWIbXmOrpqbmpGjudClFaezxsa0cb/I1+3YitHa7PrkIPHvbuPs+/fvrvv8/f///////////////////////////////////////////////yH5BAEAAB8ALAAAAAAWABYAAAWN4CeOZGmeaKqubGsusPvBSyFJjVDs6nJLB0khR4AkBCmfsCGBQAoCwjF5gwquVykSFbwZE+AwIBV0GhFog2EwIDchjwRiQo9E2Fx4XD5R+B0DDAEnBXBhBhN2DgwDAQFjJYVhCQYRfgoIDGiQJAWTCQMRiwwMfgicnVcAAAMOaK+bLAOrtLUyt7i5uiUhADs=" /> | ||
156 | +</div> | ||
157 | +<div id="textBox" contenteditable="true"><p>Lorem ipsum</p></div> | ||
158 | +<p id="editMode"><input type="checkbox" name="switchMode" id="switchBox" onchange="setDocMode(this.checked);" /> <label for="switchBox">Show HTML</label></p> | ||
159 | +<p><input type="submit" value="Send" /></p> | ||
160 | +</form> | ||
161 | +</body> | ||
162 | +</html> |
docs/examples.md
0 → 100644
1 | +# Ein sehr schöner Titel | ||
2 | + | ||
3 | +## Ein sinnloser Text | ||
4 | + | ||
5 | +Hier kommt ganz viel Text der irgendwie auch was machen soll, aber Zeilen | ||
6 | +sollen auch im <pre> Eingabefeld automatisch umbrechen. | ||
7 | + | ||
8 | +Ein neuner Paragraph beginnt nach einer Leerzeile. | ||
9 | +Ein Umbruch entsteht wie gewohnt durch 2 spaces am Ende einer | ||
10 | +Zeile. | ||
11 | + | ||
12 | +## Fußnoten | ||
13 | + | ||
14 | +Vllt. kann man sogar so was wie Fussnoten[^1] in den Markdown Text | ||
15 | +einbinden... diese kann man dann irgendwo einbauen... | ||
16 | + | ||
17 | +--- | ||
18 | + | ||
19 | +[^1]: Zum Beispiel so... | ||
20 | + | ||
21 | +[^2]: Oder so... | ||
22 | + | ||
23 | +## inline html ist im Moment auch ok. | ||
24 | + | ||
25 | +<pre>Lustigerweise geht auch inline html</pre> | ||
26 | + | ||
27 | +## Listen for fun | ||
28 | + | ||
29 | +- ein Liste | ||
30 | + - mehr Liste | ||
31 | + - diesmal als Subliste. | ||
32 | +- und was auch immer... | ||
33 | + 1. und nun Verschachtelt. | ||
34 | + 1. Numeriert. | ||
35 | + 2. huhuhu | ||
36 | + 3. wie bitte. | ||
37 | + 2. juhu | ||
38 | +- noch mehr Liste | ||
39 | + | ||
40 | +## Preformated Text | ||
41 | + | ||
42 | +```Hier kommt der code``` | ||
43 | + | ||
44 | +Und hier der Paragraph mit `inline code` der auch sehr schön aussehen kann. | ||
45 | + | ||
46 | +## Hervorhebungen | ||
47 | + | ||
48 | +Man kann Text auch sehr schön formatieren. So ist es z.B. möglich | ||
49 | +*Worte kursiv zu stellen* oder man kann **sie auch fett schreiben**. | ||
50 | +Als spezielles feature kann der von mir verwendete Parser auch | ||
51 | +~~Texte durchstreichen~~. | ||
52 | + | ||
53 | +Nur wenn man Text <u>unterstreichen</u> will muss man auf inline html | ||
54 | +zurückgreifen. | ||
55 | + | ||
56 | +## Blockquotes und horizontale Linie | ||
57 | + | ||
58 | +> Dies sollte jetzt als quote erkennbar sein. | ||
59 | +> | ||
60 | +>> Auch diese sind schachtelbar | ||
61 | +> | ||
62 | +> Und weiter gehts. | ||
63 | + | ||
64 | +--- | ||
65 | + | ||
66 | +> Aber dies ist ein neuer quote. | ||
67 | + | ||
68 | +## Links | ||
69 | + | ||
70 | +Ein link kann inline geschrieben werden, so wie diese zu | ||
71 | +[Heise.de](https://heise.de/ 'Heise.de') oder als Referenz am Ende des Textes | ||
72 | +wie diese nach [Telepolis][lnk1]. | ||
73 | + | ||
74 | +## Bilder koennte man auch einbinden. | ||
75 | + | ||
76 | +Wie Links lassen sich auch Bilder wie mein | ||
77 | +![Gravatar](https://www.gravatar.com/avatar/fd016c954ec4ed3a4315eeed6c8b97b8) | ||
78 | +in den Text ein. | ||
79 | + | ||
80 | +Im Fließtext sieht das allerdings ein bisschen dumm aus es sei denn man hat | ||
81 | +entsprechend angepasste styles. Besser scheint mir daher Bilder nur zwischen | ||
82 | +Paragraphen zu plazieren. | ||
83 | + | ||
84 | +![Gravatar](https://www.gravatar.com/avatar/fd016c954ec4ed3a4315eeed6c8b97b8) | ||
85 | + | ||
86 | +Etwas so wie hier. | ||
87 | + | ||
88 | +## Tabellen sollten auch gehen... | ||
89 | + | ||
90 | +Die folgenden Beispiele kommen von [markdown.land][lnk2]: | ||
91 | + | ||
92 | +| Item | Price | # In stock | | ||
93 | +|--------------|-----------|------------| | ||
94 | +| Juicy Apples | 1.99 | *8* | | ||
95 | +| Bananas | **1.89** | 5234 | | ||
96 | + | ||
97 | +Man braucht sie nicht schön zu formatieren. | ||
98 | + | ||
99 | +Item | Price | # In stock | ||
100 | +---|---|--- | ||
101 | +Juicy Apples | 1.99 | 739 | ||
102 | +Bananas | 1.89 | 6 | ||
103 | + | ||
104 | +und die Spaltenausrichtung kann man auch einstellen: | ||
105 | + | ||
106 | + | ||
107 | +| Item | Price | # In stock | | ||
108 | +|--------------|:-----:|-----------:| | ||
109 | +| Juicy Apples | 1.99 | 739 | | ||
110 | +| Bananas | 1.8900 | 6 | | ||
111 | + | ||
112 | + | ||
113 | +[lnk1]: https://heise.de/tp/ 'Telepolis' | ||
114 | +[lnk2]: https://markdown.land/markdown-table 'markdown.land' |
docs/urls.md
0 → 100644
1 | +## compile markdown | ||
2 | +- [DIY markdown compiler](https://jesselawson.org/rust/getting-started-with-rust-by-building-a-tiny-markdown-compiler/ 'DIY markdown compiler) | ||
3 | +- [CRATE markdown](https://crates.io/crates/markdown 'CRATE markdown') | ||
4 | +- [CRATE pulldown_cmark](https://crates.io/crates/pulldown-cmark 'CRATE pulldown_cmark') | ||
5 | + - [RDOC pulldown_cmark](https://docs.rs/pulldown-cmark/latest/pulldown_cmark/ 'RDOC pulldown_cmark') | ||
6 | + | ||
7 | +## get DOM from string (in browser) | ||
8 | +- [MDN DOMParser](https://developer.mozilla.org/en-US/docs/Web/API/DOMParser 'MDN DOMParser') | ||
9 | +- [WEB_SYS DomParser](https://docs.rs/web-sys/latest/web_sys/struct.DomParser.html 'WEB_SYS DomParser') | ||
10 | + | ||
11 | +## Markdown syntax help | ||
12 | +- [TutorialsAndYou markdown](https://www.tutorialsandyou.com/markdown/ 'TutorialsAndYou markdown') | ||
13 | +- [Commonmark tutorial](https://commonmark.org/help/tutorial/ 'Commonmark tutorial') | ||
14 | + | ||
15 | +## Free Artwork | ||
16 | +- [Designlooter](https://designlooter.com/ 'Designlooter') | ||
17 | +- [SVGRepo](https://www.svgrepo.com/ 'SVGRepo') |
scripts/common.sh
0 → 100755
1 | +#!/bin/sh -eu | ||
2 | + | ||
3 | +export PATH=$HOME/.cargo/bin:$PATH: | ||
4 | + | ||
5 | +echo "Rust Setup" | ||
6 | + | ||
7 | +if hash rustup 2>/dev/null; then | ||
8 | + echo "Have rustup, skipping installation..." | ||
9 | +else | ||
10 | + echo "Installing rustup..." | ||
11 | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y | ||
12 | +fi | ||
13 | + | ||
14 | +rustup update | ||
15 | + | ||
16 | +if hash wasm-pack 2>/dev/null; then | ||
17 | + echo "Have wasm-pack, skipping installation..." | ||
18 | +else | ||
19 | + echo "Installing wasm-pack..." | ||
20 | + curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh | ||
21 | +fi | ||
22 | + | ||
23 | +if hash cargo-generate 2>/dev/null; then | ||
24 | + echo "Have cargo-generate, skipping installation..." | ||
25 | +else | ||
26 | + echo "Installing cargo-generate..." | ||
27 | + cargo install cargo-generate | ||
28 | + which cargo-generate | ||
29 | +fi |
scripts/test.sh
0 → 100755
1 | +#!/bin/sh -eu | ||
2 | + | ||
3 | +ROOT="$(git rev-parse --show-toplevel)" | ||
4 | +. $ROOT/scripts/common.sh | ||
5 | + | ||
6 | +if [ -z ${GITHUB_REF+blah} ]; then | ||
7 | + GITHUB_REF="$(git rev-parse --abbrev-ref HEAD)" | ||
8 | +fi | ||
9 | + | ||
10 | +BRANCH=$(basename $GITHUB_REF) | ||
11 | + | ||
12 | +echo "Testing project generation from the '$BRANCH' branch..." | ||
13 | +cd .. | ||
14 | +ls -lah mogwai-template | ||
15 | +cargo generate --git ./mogwai-template --name gen-test | ||
16 | +cd gen-test | ||
17 | +wasm-pack build --target web |
server/Cargo.toml
0 → 100644
1 | +# This is from the webpage: | ||
2 | +# https://www.developer.com/languages/creating-an-api-with-rust-and-sqlite/ | ||
3 | +# I have fixed some things manually as the code on that page did not compile | ||
4 | +# without. | ||
5 | + | ||
6 | +# Additional informations at https://actix.rs/docs/databases/ assume one | ||
7 | +# should use web::block to access the database... | ||
8 | +# TODO check what is the difference to this approach. | ||
9 | +# - well, we use web::block already for write actions. | ||
10 | + | ||
11 | +# Introduction to rust async: | ||
12 | +# https://gruberbastian.com/posts/rust_async/ | ||
13 | +# https://blog.logrocket.com/a-practical-guide-to-async-in-rust/ | ||
14 | +# https://os.phil-opp.com/async-await/ | ||
15 | + | ||
16 | +# Simple explanation on technical terms synchronous, asynchronous, concurrent | ||
17 | +# and parallel. | ||
18 | +# https://medium.com/plain-and-simple/synchronous-vs-asynchronous-vs-concurrent-vs-parallel-4342bfb8b9f2 | ||
19 | + | ||
20 | +[package] | ||
21 | +name = "artshop-server" | ||
22 | +version = "0.1.0" | ||
23 | +workspace = ".." | ||
24 | +edition = "2018" | ||
25 | + | ||
26 | +[dependencies] | ||
27 | +actix-files = "0.2" | ||
28 | +actix-web = "2.0" | ||
29 | +actix-rt = "1.1.1" | ||
30 | +diesel = { version = "1.4.7", features = ["sqlite", "r2d2"]} | ||
31 | +r2d2 = "0.8.9" | ||
32 | +dotenv = "0.15.0" | ||
33 | +serde = "1.0" | ||
34 | +serde_derive = "1.0" | ||
35 | +serde_json = "1.0" | ||
36 | +anyhow = "1.0" | ||
37 | +chrono = "0.4.15" | ||
38 | +listenfd = "0.3" |
server/curl/adduser.sh
0 → 100755
1 | +#!/bin/env sh | ||
2 | + | ||
3 | +[[ $# -lt 2 ]] && exit | ||
4 | + | ||
5 | +USER=$1 | ||
6 | +ADDRESS=$2 | ||
7 | + | ||
8 | +echo "Add user: ${USER} with address: ${ADDRESS}" | ||
9 | + | ||
10 | +curl -i --header "Content-Type: application/json" \ | ||
11 | + --request PUT \ | ||
12 | + --data '{"name":"'"${USER}"'","address":"'"${ADDRESS}"'"}' \ | ||
13 | + http://localhost:8080/users |
server/curl/deluser.sh
0 → 100755
server/curl/updateuser.sh
0 → 100755
1 | +#!/bin/env sh | ||
2 | + | ||
3 | +[[ $# -lt 3 ]] && exit | ||
4 | + | ||
5 | +ID=$1 | ||
6 | +USER=$2 | ||
7 | +ADDRESS=$3 | ||
8 | + | ||
9 | +echo "Update user ${ID}: ${USER} with address: ${ADDRESS}" | ||
10 | + | ||
11 | +curl -i --header "Content-Type: application/json" \ | ||
12 | + --request PUT \ | ||
13 | + --data '{"name":"'"${USER}"'","address":"'"${ADDRESS}"'"}' \ | ||
14 | + http://localhost:8080/users/${ID} |
server/migrations/.gitkeep
0 → 100644
1 | +-- This file should undo anything in `up.sql` |
server/src/error.rs
0 → 100644
1 | +use std::error::Error as StdError; | ||
2 | +use std::fmt; | ||
3 | + | ||
4 | +use diesel::result; | ||
5 | +use r2d2; | ||
6 | + | ||
7 | +#[derive(Debug)] | ||
8 | +pub(crate) enum Error { | ||
9 | + DieselResult(result::Error), | ||
10 | + DieselR2d2(r2d2::Error), | ||
11 | +} | ||
12 | + | ||
13 | +impl fmt::Display for Error { | ||
14 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
15 | + match self { | ||
16 | + Error::DieselR2d2(r) => write!(f, "{}", r), | ||
17 | + Error::DieselResult(r) => write!(f, "{}", r), | ||
18 | + } | ||
19 | + } | ||
20 | +} | ||
21 | + | ||
22 | +impl StdError for Error { | ||
23 | + fn source(&self) -> Option<&(dyn StdError + 'static)> { | ||
24 | + match self { | ||
25 | + Error::DieselR2d2(r) => Some(r), | ||
26 | + Error::DieselResult(r) => Some(r), | ||
27 | + } | ||
28 | + } | ||
29 | +} | ||
30 | + | ||
31 | +impl From<result::Error> for Error { | ||
32 | + fn from(error: result::Error) -> Self { | ||
33 | + Error::DieselResult(error) | ||
34 | + } | ||
35 | +} | ||
36 | + | ||
37 | +impl From<r2d2::Error> for Error { | ||
38 | + fn from(error: r2d2::Error) -> Self { | ||
39 | + Error::DieselR2d2(error) | ||
40 | + } | ||
41 | +} |
server/src/main.rs
0 → 100644
1 | +#[macro_use] | ||
2 | +extern crate diesel; | ||
3 | + | ||
4 | +mod error; | ||
5 | +mod models; | ||
6 | +mod routes; | ||
7 | +mod schema; | ||
8 | + | ||
9 | +use actix_web::{guard, web, App, HttpResponse, HttpServer}; | ||
10 | +use diesel::r2d2::{self, ConnectionManager}; | ||
11 | +use diesel::SqliteConnection; | ||
12 | +use listenfd::ListenFd; | ||
13 | + | ||
14 | +pub(crate) type Pool = r2d2::Pool<ConnectionManager<SqliteConnection>>; | ||
15 | + | ||
16 | +#[actix_rt::main] | ||
17 | +async fn main() -> std::io::Result<()> { | ||
18 | + let mut listenfd = ListenFd::from_env(); | ||
19 | + | ||
20 | + dotenv::dotenv().ok(); | ||
21 | + | ||
22 | + let database_url = std::env::var("DATABASE_URL").expect("NOT FOUND"); | ||
23 | + let database_pool = Pool::builder() | ||
24 | + .build(ConnectionManager::new(database_url)) | ||
25 | + .unwrap(); | ||
26 | + | ||
27 | + let server = HttpServer::new(move || { | ||
28 | + App::new() . data(database_pool.clone()) | ||
29 | + . route("/", web::get().to(routes::root)) | ||
30 | + . service(actix_files::Files::new("/static", "./static")) | ||
31 | + . service( web::resource("/users") | ||
32 | + . route(web::get().to(routes::get_users)) | ||
33 | + . route(web::put().to(routes::create_user))) | ||
34 | + . service( web::resource("/users/{id}") | ||
35 | + . route(web::delete().to(routes::delete_user)) | ||
36 | + . route(web::get().to(routes::get_user)) | ||
37 | + . route(web::put().to(routes::update_user))) | ||
38 | + . service( web::scope("") | ||
39 | + . route("/", web::get().to(routes::root)) | ||
40 | + . route("/index", web::get().to(routes::root)) | ||
41 | + . route("/index.html", web::get().to(routes::root)) | ||
42 | + . route("/favicon", web::get().to(routes::favicon)) | ||
43 | + . route("/favicon.ico", web::get().to(routes::favicon))) | ||
44 | + . default_service( web::resource("") | ||
45 | + . route(web::get().to(routes::p404)) | ||
46 | + . route( web::route() | ||
47 | + . guard(guard::Not(guard::Get())) | ||
48 | + . to(HttpResponse::MethodNotAllowed) )) | ||
49 | + }); | ||
50 | + | ||
51 | + let server = match listenfd.take_tcp_listener(0).unwrap() { | ||
52 | + Some(l) => server.listen(l)?, | ||
53 | + None => server.bind("localhost:8080")?, | ||
54 | + }; | ||
55 | + | ||
56 | + server.run().await | ||
57 | +} |
server/src/models.rs
0 → 100644
1 | +use std::sync::Arc; | ||
2 | +use crate::schema::*; | ||
3 | +use crate::error::Error; | ||
4 | +use crate::Pool; | ||
5 | +use diesel::prelude::*; | ||
6 | +use diesel::{ | ||
7 | + dsl::{delete, insert_into, update}, | ||
8 | + RunQueryDsl | ||
9 | +}; | ||
10 | +use serde::{Deserialize, Serialize}; | ||
11 | + | ||
12 | + | ||
13 | +#[derive(Debug, Serialize, Deserialize, Queryable, Identifiable)] | ||
14 | +pub struct User { | ||
15 | + pub id: i32, | ||
16 | + pub name: String, | ||
17 | + pub address: String, | ||
18 | + pub date_created: String, | ||
19 | +} | ||
20 | + | ||
21 | +#[derive(Debug, Insertable)] | ||
22 | +#[table_name = "users"] | ||
23 | +pub struct UserNew<'a> { | ||
24 | + pub name: &'a str, | ||
25 | + pub address: &'a str, | ||
26 | + pub date_created: &'a str, | ||
27 | +} | ||
28 | + | ||
29 | +#[derive(Debug, Serialize, Deserialize, AsChangeset)] | ||
30 | +#[table_name="users"] | ||
31 | +pub struct UserJson { | ||
32 | + pub name: String, | ||
33 | + pub address: String, | ||
34 | +} | ||
35 | + | ||
36 | +pub(crate) enum Action { | ||
37 | + Created(User), | ||
38 | + Found(User), | ||
39 | +} | ||
40 | + | ||
41 | + | ||
42 | +pub(crate) fn create_user( pool: Arc<Pool> | ||
43 | + , item: UserJson ) -> Result<Action, Error> { | ||
44 | + use crate::schema::users::dsl::*; | ||
45 | + let db_connection = pool.get()?; | ||
46 | + | ||
47 | + match users . filter(name.eq(&item.name)) | ||
48 | + . first::<User>(&db_connection) | ||
49 | + { | ||
50 | + Ok(result) => Ok(Action::Found(result)), | ||
51 | + Err(_) => { | ||
52 | + let now = chrono::Local::now().naive_local(); | ||
53 | + let new_user = UserNew { | ||
54 | + name: &item.name, | ||
55 | + address: &item.address, | ||
56 | + date_created: &format!("{}", now), | ||
57 | + }; | ||
58 | + | ||
59 | + Ok(Action::Created(db_connection.transaction(|| { | ||
60 | + insert_into(users) . values(&new_user) | ||
61 | + . execute(&db_connection)?; | ||
62 | + | ||
63 | + users . order(id.desc()) | ||
64 | + . first::<User>(&db_connection) | ||
65 | + })?)) | ||
66 | + } | ||
67 | + } | ||
68 | +} | ||
69 | + | ||
70 | +pub(crate) fn get_users(pool: Arc<Pool>) -> Result<Vec<User>, Error> | ||
71 | +{ | ||
72 | + use crate::schema::users::dsl::*; | ||
73 | + let db_connection = pool.get()?; | ||
74 | + Ok(users.load::<User>(&db_connection)?) | ||
75 | +} | ||
76 | + | ||
77 | +pub(crate) fn get_user( pool: Arc<Pool> | ||
78 | + , ident: i32 ) -> Result<User, Error> | ||
79 | +{ | ||
80 | + use crate::schema::users::dsl::*; | ||
81 | + let db_connection = pool.get()?; | ||
82 | + Ok(users.find(ident).first::<User>(&db_connection)?) | ||
83 | +} | ||
84 | + | ||
85 | +pub(crate) fn delete_user( pool: Arc<Pool> | ||
86 | + , ident: i32 ) -> Result<usize, Error> | ||
87 | +{ | ||
88 | + use crate::schema::users::dsl::*; | ||
89 | + let db_connection = pool.get()?; | ||
90 | + Ok(delete(users.find(ident)).execute(&db_connection)?) | ||
91 | +} | ||
92 | + | ||
93 | +pub(crate) fn update_user( pool: Arc<Pool> | ||
94 | + , ident: i32 | ||
95 | + , item: UserJson ) -> Result<User, Error> | ||
96 | +{ | ||
97 | + use crate::schema::users::dsl::*; | ||
98 | + let db_connection = pool.get()?; | ||
99 | + let mut user = users.find(ident).first::<User>(&db_connection)?; | ||
100 | + | ||
101 | + update(users.find(ident)).set(&item).execute(&db_connection)?; | ||
102 | + user.name = item.name; | ||
103 | + user.address = item.address; | ||
104 | + | ||
105 | + Ok(user) | ||
106 | +} |
server/src/routes.rs
0 → 100644
1 | +use crate::models::{self, Action}; | ||
2 | +use crate::Pool; | ||
3 | + | ||
4 | +use actix_web::{Error, HttpResponse, web}; | ||
5 | +use anyhow::Result; | ||
6 | + | ||
7 | +pub async fn root() -> Result<actix_files::NamedFile, Error> { | ||
8 | + Ok(actix_files::NamedFile::open("static/index.html")?) | ||
9 | +} | ||
10 | + | ||
11 | +pub async fn p404() -> Result<actix_files::NamedFile, Error> { | ||
12 | + Ok(actix_files::NamedFile::open("static/404.html")?) | ||
13 | +} | ||
14 | + | ||
15 | +pub async fn favicon() -> Result<actix_files::NamedFile, Error> { | ||
16 | + Ok(actix_files::NamedFile::open("static/favicon.ico")?) | ||
17 | +} | ||
18 | + | ||
19 | +pub async fn create_user( pool: web::Data<Pool> | ||
20 | + , item: web::Json<models::UserJson> ) | ||
21 | + -> Result<HttpResponse, Error> | ||
22 | +{ | ||
23 | + let pool = pool.into_inner(); | ||
24 | + let item = item.into_inner(); | ||
25 | + | ||
26 | + Ok(web::block(move || models::create_user(pool, item)) | ||
27 | + . await | ||
28 | + . map(|action| { | ||
29 | + match action { | ||
30 | + Action::Created(user) => HttpResponse::Created().json(user), | ||
31 | + Action::Found(user) => HttpResponse::Ok().json(user), | ||
32 | + }}) | ||
33 | + . map_err(|_| HttpResponse::InternalServerError())?) | ||
34 | +} | ||
35 | + | ||
36 | +pub async fn get_users(pool: web::Data<Pool>) | ||
37 | + -> Result<HttpResponse, Error> | ||
38 | +{ | ||
39 | + Ok(web::block(move || models::get_users(pool.into_inner())) | ||
40 | + . await | ||
41 | + . map(|users| HttpResponse::Ok().json(users)) | ||
42 | + . map_err(|_| HttpResponse::InternalServerError())?) | ||
43 | +} | ||
44 | + | ||
45 | +pub async fn get_user(pool: web::Data<Pool>, id: web::Path<i32>) | ||
46 | + -> Result<HttpResponse, Error> | ||
47 | +{ | ||
48 | + let pool = pool.into_inner(); | ||
49 | + let id = id.into_inner(); | ||
50 | + | ||
51 | + Ok(web::block(move || models::get_user(pool, id)) | ||
52 | + . await | ||
53 | + . map(|user| HttpResponse::Ok().json(user)) | ||
54 | + . map_err(|_| HttpResponse::InternalServerError())?) | ||
55 | +} | ||
56 | + | ||
57 | +pub async fn delete_user(pool: web::Data<Pool>, id: web::Path<i32>) | ||
58 | + -> Result<HttpResponse, Error> | ||
59 | +{ | ||
60 | + let pool = pool.into_inner(); | ||
61 | + let id = id.into_inner(); | ||
62 | + | ||
63 | + Ok(web::block(move || models::delete_user(pool, id)) | ||
64 | + . await | ||
65 | + . map(|_| HttpResponse::NoContent().finish()) | ||
66 | + . map_err(|_| HttpResponse::InternalServerError())?) | ||
67 | +} | ||
68 | + | ||
69 | +pub async fn update_user( pool: web::Data<Pool> | ||
70 | + , id: web::Path<i32> | ||
71 | + , item: web::Json<models::UserJson> ) | ||
72 | + -> Result<HttpResponse, Error> | ||
73 | +{ | ||
74 | + let pool = pool.into_inner(); | ||
75 | + let id = id.into_inner(); | ||
76 | + let item = item.into_inner(); | ||
77 | + | ||
78 | + Ok(web::block(move || models::update_user(pool, id, item)) | ||
79 | + . await | ||
80 | + . map(|user| HttpResponse::Ok().json(user)) | ||
81 | + . map_err(|_| HttpResponse::InternalServerError())?) | ||
82 | +} |
server/src/schema.rs
0 → 100644
static/404.html
0 → 100644
static/favicon.ico
0 → 100644
No preview for this file type
static/index.html
0 → 100644
1 | +<!DOCTYPE html> | ||
2 | +<html lang="en"> | ||
3 | + <head> | ||
4 | + <meta charset="utf-8"> | ||
5 | + <title>artshop-frontend</title> | ||
6 | + <link rel="stylesheet" href="/static/style.css"> | ||
7 | + </head> | ||
8 | + <body> | ||
9 | + <script type="module"> | ||
10 | + import init from '/static/ui/artshop_frontend.js'; | ||
11 | + window.addEventListener('load', async () => { | ||
12 | + await init(); | ||
13 | + }); | ||
14 | + </script> | ||
15 | + </body> | ||
16 | +</html> |
static/style.css
0 → 100644
1 | +.input { | ||
2 | + float: left; | ||
3 | + padding: 1em; | ||
4 | + border-radius: .5em; | ||
5 | + border: 1px solid #ddd; | ||
6 | + background: #f7f7f7; | ||
7 | +} | ||
8 | + | ||
9 | +.input p { | ||
10 | + text-align: justify; | ||
11 | + text-indent: .5em; | ||
12 | + margin-block: .5em; | ||
13 | +} | ||
14 | + | ||
15 | +.input > div:first-child { | ||
16 | + height: 10em; | ||
17 | +} | ||
18 | + | ||
19 | +.input > div:first-child pre { | ||
20 | + white-space: pre-wrap; | ||
21 | + height: 100%; | ||
22 | + width: 100%; | ||
23 | + overflow-y: scroll; | ||
24 | + overflow-x: hidden; | ||
25 | +} | ||
26 | + | ||
27 | +.input > div:last-child { | ||
28 | + position: relative; | ||
29 | +} | ||
30 | + | ||
31 | +.input > div:last-child > button { | ||
32 | + position: absolute; | ||
33 | + right: .1em; | ||
34 | +} | ||
35 | + | ||
36 | +.input blockquote { | ||
37 | + border-left: 5px solid #ccc; | ||
38 | + margin: .5em 10px; | ||
39 | + padding: .5em 10px; | ||
40 | +} | ||
41 | + | ||
42 | +.input blockquote p { | ||
43 | + text-align: left; | ||
44 | + text-indent: 0; | ||
45 | + margin-block: 0; | ||
46 | +} | ||
47 | + | ||
48 | +.footnote-definition > * { | ||
49 | + display: inline; | ||
50 | +} |
ui/Cargo.toml
0 → 100644
1 | +[package] | ||
2 | +name = "artshop-frontend" | ||
3 | +version = "0.0.0" | ||
4 | +authors = ["Georg Hopp <georg@steffers.org>"] | ||
5 | +workspace = ".." | ||
6 | +edition = "2018" | ||
7 | + | ||
8 | +[lib] | ||
9 | +crate-type = ["cdylib", "rlib"] | ||
10 | + | ||
11 | +[features] | ||
12 | +default = ["console_error_panic_hook"] | ||
13 | + | ||
14 | +[dependencies] | ||
15 | +pulldown-cmark = "0.9" | ||
16 | +console_log = "^0.1" | ||
17 | +log = "^0.4" | ||
18 | +serde = { version = "^1.0", features = ["derive"] } | ||
19 | +serde_json = "^1.0" | ||
20 | +wasm-bindgen = "^0.2" | ||
21 | + | ||
22 | +# The `console_error_panic_hook` crate provides better debugging of panics by | ||
23 | +# logging them with `console.error`. This is great for development, but requires | ||
24 | +# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for | ||
25 | +# code size when deploying. | ||
26 | +console_error_panic_hook = { version = "0.1.6", optional = true } | ||
27 | +wee_alloc = { version = "0.4.2", optional = true } | ||
28 | + | ||
29 | +[dependencies.mogwai] | ||
30 | +version = "^0.5" | ||
31 | + | ||
32 | +[dependencies.web-sys] | ||
33 | +version = "^0.3" | ||
34 | +features = [ | ||
35 | + "Document", | ||
36 | + "DomParser", | ||
37 | + "HtmlElement", | ||
38 | + "HtmlInputElement", | ||
39 | + "Node", | ||
40 | + "SupportedType" | ||
41 | +] | ||
42 | + | ||
43 | +[dev-dependencies] | ||
44 | +wasm-bindgen-test = "0.2" |
ui/src/data.rs
0 → 100644
1 | +pub(crate) const MD_EXAMPLE :&str = r"# Ein sehr schöner Titel | ||
2 | + | ||
3 | +## Ein sinnloser Text | ||
4 | + | ||
5 | +Hier kommt ganz viel Text der irgendwie auch was machen soll, aber Zeilen | ||
6 | +sollen auch im <pre> Eingabefeld automatisch umbrechen. | ||
7 | + | ||
8 | +Ein neuner Paragraph beginnt nach einer Leerzeile. | ||
9 | +Ein Umbruch entsteht wie gewohnt durch 2 spaces am Ende einer | ||
10 | +Zeile. | ||
11 | + | ||
12 | +## Fußnoten | ||
13 | + | ||
14 | +Vllt. kann man sogar so was wie Fussnoten[^1] in den Markdown Text | ||
15 | +einbinden... diese kann man dann irgendwo einbauen... | ||
16 | + | ||
17 | +--- | ||
18 | + | ||
19 | +[^1]: Zum Beispiel so... | ||
20 | + | ||
21 | +[^2]: Oder so... | ||
22 | + | ||
23 | +## inline html ist im Moment auch ok. | ||
24 | + | ||
25 | +<pre>Lustigerweise geht auch inline html</pre> | ||
26 | + | ||
27 | +## Listen for fun | ||
28 | + | ||
29 | +- ein Liste | ||
30 | + - mehr Liste | ||
31 | + - diesmal als Subliste. | ||
32 | +- und was auch immer... | ||
33 | + 1. und nun Verschachtelt. | ||
34 | + 1. Numeriert. | ||
35 | + 2. huhuhu | ||
36 | + 3. wie bitte. | ||
37 | + 2. juhu | ||
38 | +- noch mehr Liste | ||
39 | + | ||
40 | +## Preformated Text | ||
41 | + | ||
42 | +```Hier kommt der code``` | ||
43 | + | ||
44 | +Und hier der Paragraph mit `inline code` der auch sehr schön aussehen kann. | ||
45 | + | ||
46 | +## Hervorhebungen | ||
47 | + | ||
48 | +Man kann Text auch sehr schön formatieren. So ist es z.B. möglich | ||
49 | +*Worte kursiv zu stellen* oder man kann **sie auch fett schreiben**. | ||
50 | +Als spezielles feature kann der von mir verwendete Parser auch | ||
51 | +~~Texte durchstreichen~~. | ||
52 | + | ||
53 | +Nur wenn man Text <u>unterstreichen</u> will muss man auf inline html | ||
54 | +zurückgreifen. | ||
55 | + | ||
56 | +## Blockquotes und horizontale Linie | ||
57 | + | ||
58 | +> Dies sollte jetzt als quote erkennbar sein. | ||
59 | +> | ||
60 | +>> Auch diese sind schachtelbar | ||
61 | +> | ||
62 | +> Und weiter gehts. | ||
63 | + | ||
64 | +--- | ||
65 | + | ||
66 | +> Aber dies ist ein neuer quote. | ||
67 | + | ||
68 | +## Links | ||
69 | + | ||
70 | +Ein link kann inline geschrieben werden, so wie diese zu | ||
71 | +[Heise.de](https://heise.de/ 'Heise.de') oder als Referenz am Ende des Textes | ||
72 | +wie diese nach [Telepolis][lnk1]. | ||
73 | + | ||
74 | +## Bilder koennte man auch einbinden. | ||
75 | + | ||
76 | +Wie Links lassen sich auch Bilder wie mein | ||
77 | +![Gravatar](https://www.gravatar.com/avatar/fd016c954ec4ed3a4315eeed6c8b97b8) | ||
78 | +in den Text ein. | ||
79 | + | ||
80 | +Im Fließtext sieht das allerdings ein bisschen dumm aus es sei denn man hat | ||
81 | +entsprechend angepasste styles. Besser scheint mir daher Bilder nur zwischen | ||
82 | +Paragraphen zu plazieren. | ||
83 | + | ||
84 | +![Gravatar](https://www.gravatar.com/avatar/fd016c954ec4ed3a4315eeed6c8b97b8) | ||
85 | + | ||
86 | +Etwas so wie hier. | ||
87 | + | ||
88 | +## Tabellen sollten auch gehen... | ||
89 | + | ||
90 | +Die folgenden Beispiele kommen von [markdown.land][lnk2]: | ||
91 | + | ||
92 | +| Item | Price | # In stock | | ||
93 | +|--------------|-----------|------------| | ||
94 | +| Juicy Apples | 1.99 | *8* | | ||
95 | +| Bananas | **1.89** | 5234 | | ||
96 | + | ||
97 | +Man braucht sie nicht schön zu formatieren. | ||
98 | + | ||
99 | +Item | Price | # In stock | ||
100 | +---|---|--- | ||
101 | +Juicy Apples | 1.99 | 739 | ||
102 | +Bananas | 1.89 | 6 | ||
103 | + | ||
104 | +und die Spaltenausrichtung kann man auch einstellen: | ||
105 | + | ||
106 | + | ||
107 | +| Item | Price | # In stock | | ||
108 | +|--------------|:-----:|-----------:| | ||
109 | +| Juicy Apples | 1.99 | 739 | | ||
110 | +| Bananas | 1.8900 | 6 | | ||
111 | + | ||
112 | + | ||
113 | +[lnk1]: https://heise.de/tp/ 'Telepolis' | ||
114 | +[lnk2]: https://markdown.land/markdown-table 'markdown.land'"; | ||
115 | + | ||
116 | +/* | ||
117 | +pub(crate) const PEN_ICON :&str = r#"<?xml version="1.0" encoding="iso-8859-1"?> | ||
118 | +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" | ||
119 | + "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> | ||
120 | +<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" | ||
121 | + xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | ||
122 | + viewBox="0 0 220.001 220.001" | ||
123 | + style="enable-background:new 0 0 220.001 220.001;" xml:space="preserve"> | ||
124 | + <g> | ||
125 | + <polygon points="0,220 59.34,213.86 6.143,160.661"/> | ||
126 | + <path d="M132.018,34.787l53.197,53.197L69.568,203.631L16.37, | ||
127 | + 150.434L132.018,34.787z M212.696,60.502c9.738-9.738,9.742-25.527, | ||
128 | + 0-35.268l-17.93-17.93c-9.738-9.74-25.529-9.738-35.268,0l-17.346, | ||
129 | + 17.347l53.199,53.196L212.696,60.502z"/> | ||
130 | + </g> | ||
131 | +</svg>"#; | ||
132 | +*/ |
ui/src/lib.rs
0 → 100644
1 | +mod data; | ||
2 | + | ||
3 | +use log::Level; | ||
4 | +use mogwai::prelude::*; | ||
5 | +use std::panic; | ||
6 | +use wasm_bindgen::prelude::*; | ||
7 | + | ||
8 | + | ||
9 | +#[derive(Clone)] | ||
10 | +enum AppLogic { | ||
11 | + Update, | ||
12 | + Toggle, | ||
13 | +} | ||
14 | + | ||
15 | +fn md_to_html(source: &str) -> String { | ||
16 | + use pulldown_cmark::{Parser, Options, html}; | ||
17 | + | ||
18 | + let parser = Parser::new_ext(source, Options::all()); | ||
19 | + let mut html_output = String::new(); | ||
20 | + html::push_html(&mut html_output, parser); | ||
21 | + | ||
22 | + html_output | ||
23 | +} | ||
24 | + | ||
25 | +async fn editor_logic( mut rx_logic: broadcast::Receiver<AppLogic> | ||
26 | + , tx_view: broadcast::Sender<String> | ||
27 | + , mut rx_dom: broadcast::Receiver<Dom> ) { | ||
28 | + let dom = rx_dom.next().await.unwrap(); | ||
29 | + let mut show_edit = false; | ||
30 | + | ||
31 | + fn get_md(dom: &Dom) -> String { | ||
32 | + match dom.inner_read() { | ||
33 | + Either::Left(dom_js) => dom_js . to_owned() | ||
34 | + . dyn_into::<Node>().unwrap() | ||
35 | + . first_child().unwrap() | ||
36 | + . dyn_into::<HtmlElement>().unwrap() | ||
37 | + . inner_text(), | ||
38 | + _ => String::from(""), | ||
39 | + } | ||
40 | + } | ||
41 | + | ||
42 | + fn update(dom: &Dom) { | ||
43 | + if let Either::Left(dom_js) = dom.inner_read() { | ||
44 | + dom_js . to_owned() | ||
45 | + . dyn_into::<Node>().unwrap() | ||
46 | + . child_nodes().get(1).unwrap() | ||
47 | + . child_nodes().get(1).unwrap() | ||
48 | + . dyn_into::<HtmlElement>().unwrap() | ||
49 | + . set_inner_html(md_to_html(get_md(dom).as_str()).as_str()) | ||
50 | + }; | ||
51 | + } | ||
52 | + | ||
53 | + update(&dom); | ||
54 | + | ||
55 | + while let Some(msg) = rx_logic.next().await { | ||
56 | + match msg { | ||
57 | + AppLogic::Update => update(&dom), | ||
58 | + AppLogic::Toggle => { | ||
59 | + show_edit = ! show_edit; | ||
60 | + match show_edit { | ||
61 | + true => tx_view . broadcast(String::from("block")) | ||
62 | + . await.unwrap(), | ||
63 | + false => tx_view . broadcast(String::from("none")) | ||
64 | + . await.unwrap(), | ||
65 | + }; | ||
66 | + }, | ||
67 | + } | ||
68 | + } | ||
69 | +} | ||
70 | + | ||
71 | +fn editor_view( tx_logic: broadcast::Sender<AppLogic> | ||
72 | + , rx_view: broadcast::Receiver<String> | ||
73 | + , tx_dom: broadcast::Sender<Dom> | ||
74 | +) -> ViewBuilder<Dom> { | ||
75 | + let ns = "http://www.w3.org/2000/svg"; | ||
76 | + builder! { | ||
77 | + <div class="input" | ||
78 | + style:width="33%" | ||
79 | + on:input=tx_logic.sink().contra_map(|_| AppLogic::Update) | ||
80 | + capture:view=tx_dom.sink()> | ||
81 | + <div contenteditable="true" | ||
82 | + style:cursor="text" | ||
83 | + style:display=("none", rx_view)> | ||
84 | + <pre>{data::MD_EXAMPLE}</pre> | ||
85 | + </div> | ||
86 | + <div> | ||
87 | + <button on:click=tx_logic . sink() | ||
88 | + . contra_map(|_| AppLogic::Toggle)> | ||
89 | + <svg version="1.1" id="Capa_1" xmlns=ns | ||
90 | + x="0px" y="0px" viewBox="0 0 220.001 220.001" | ||
91 | + style:width="1.5em" style:height="1.5em"> | ||
92 | + <g xmlns=ns> | ||
93 | + <polygon xmlns=ns points="0,220 59.34,213.86 6.143,160.661"></polygon> | ||
94 | + <path xmlns=ns d="M132.018,34.787l53.197,53.197L69.568,203.631L16.37, | ||
95 | + 150.434L132.018,34.787z M212.696,60.502c9.738-9.738,9.742-25.527, | ||
96 | + 0-35.268l-17.93-17.93c-9.738-9.74-25.529-9.738-35.268,0l-17.346, | ||
97 | + 17.347l53.199,53.196L212.696,60.502z"></path> | ||
98 | + </g> | ||
99 | + </svg> | ||
100 | + </button> | ||
101 | + <div></div> | ||
102 | + </div> | ||
103 | + </div> | ||
104 | + } | ||
105 | +} | ||
106 | + | ||
107 | +#[wasm_bindgen(start)] | ||
108 | +pub fn main() -> Result<(), JsValue> { | ||
109 | + panic::set_hook(Box::new(console_error_panic_hook::hook)); | ||
110 | + console_log::init_with_level(Level::Trace).unwrap(); | ||
111 | + | ||
112 | + let (tx_dom, rx_dom) = broadcast::bounded(1); | ||
113 | + let (tx_logic, rx_logic) = broadcast::bounded(1); | ||
114 | + let (tx_view, rx_view) = broadcast::bounded(1); | ||
115 | + let comp = Component::from( editor_view(tx_logic, rx_view, tx_dom) ) | ||
116 | + . with_logic( editor_logic(rx_logic, tx_view, rx_dom) ); | ||
117 | + | ||
118 | + let page = Component::from(builder! {{comp}}); | ||
119 | + page.build()?.run() | ||
120 | +} |
Please
register
or
login
to post a comment