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` | |
\ No newline at end of file | ... | ... |
1 | +-- Your SQL goes here | |
2 | +CREATE TABLE "users" ( | |
3 | + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, | |
4 | + name TEXT NOT NULL, | |
5 | + address TEXT NOT NULL, | |
6 | + date_created TEXT NOT NULL | |
7 | +); | |
8 | + | |
9 | +INSERT INTO | |
10 | + "users"(name, address, date_created) | |
11 | +VALUES | |
12 | + ("John", "123 Av Q", "Today"); | ... | ... |
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