Commit 850da5fe76de5be6d61c1809ac811ab75b1ad833

Authored by Georg Hopp
0 parents

Initial checkin

  1 +DATABASE_URL=crud.db
  1 +Cargo.lock
  2 +**/*.rs.bk
  3 +
  4 +/target
  5 +/pkg
  6 +/static/ui/
  1 +[workspace]
  2 +members = [ "ui", "server" ]
  3 +
  4 +[profile.release]
  5 +lto = true
  6 +opt-level = "s"
  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
No preview for this file type
  1 +# For documentation on how to configure this file,
  2 +# see diesel.rs/guides/configuring-diesel-cli
  3 +
  4 +[print_schema]
  5 +file = "src/schema.rs"
  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 +
  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 &lt;h1&gt;</option>
  96 +<option value="h2">Title 2 &lt;h2&gt;</option>
  97 +<option value="h3">Title 3 &lt;h3&gt;</option>
  98 +<option value="h4">Title 4 &lt;h4&gt;</option>
  99 +<option value="h5">Title 5 &lt;h5&gt;</option>
  100 +<option value="h6">Subtitle &lt;h6&gt;</option>
  101 +<option value="p">Paragraph &lt;p&gt;</option>
  102 +<option value="pre">Preformatted &lt;pre&gt;</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>
  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 &lt;pre&gt; 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'
  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')
  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
  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
  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"
  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
  1 +#!/bin/env sh
  2 +
  3 +[[ $# -ne 1 ]] && exit
  4 +
  5 +ID=$1
  6 +
  7 +echo "Delete user ${ID}"
  8 +
  9 +curl -i --request DELETE http://localhost:8080/users/${ID}
  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}
  1 +-- This file should undo anything in `up.sql`
  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");
  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 +}
  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 +}
  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 +}
  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 +}
  1 +table! {
  2 + users (id) {
  3 + id -> Integer,
  4 + name -> Text,
  5 + address -> Text,
  6 + date_created -> Text,
  7 + }
  8 +}
  1 +<!DOCTYPE html>
  2 +<html>
  3 + <head>
  4 + <meta charset="UTF-8">
  5 + <title>Vue playground</title>
  6 + <link rel="shortcut icon" type="image/x-icon" href="/favicon" />
  7 + </head>
  8 +
  9 + <body>
  10 + <h1>404</h1>
  11 + Resource not found
  12 + </body>
  13 +</html>
No preview for this file type
  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>
  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 +}
  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"
  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 &lt;pre&gt; 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 +*/
  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