diff --git a/Cargo.lock b/Cargo.lock index f15ca03..b5a0386 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -847,6 +847,7 @@ dependencies = [ "askama_actix", "clap", "git2", + "regex", "serde", "serde_json", ] diff --git a/Cargo.toml b/Cargo.toml index acd9160..c1ee171 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,5 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" git2 = "0.16.0" + +regex = "1" diff --git a/README.md b/README.md index 5b0e27e..f22bbcd 100644 --- a/README.md +++ b/README.md @@ -13,3 +13,17 @@ https://stackoverflow.com/questions/6249095/how-to-set-the-caret-cursor-position |: OR :: Match a colon (?:$|\S): Match end or a non-whitespace + +Javascript Mutex.mutex() + +## traiter le copier/coller +editor.addEventListener("paste", function(e) { + // cancel paste + e.preventDefault(); + + // get text representation of clipboard + var text = (e.originalEvent || e).clipboardData.getData('text/plain'); + + // insert text manually + document.execCommand("insertHTML", false, text); +}); diff --git a/pages/index.md b/pages/index.md new file mode 100644 index 0000000..c7120e2 --- /dev/null +++ b/pages/index.md @@ -0,0 +1,38 @@ +# RustyNotes +L'objectif de RustyNotes est de pouvoir saisir des notes avec un minimum de formatage le plus rapidement possible. +> Cette page est un bac à sable. Les modifications ne seront jamais enregistrées. +> Pour réinitialiser la page, il suffit de la recharger. +## Utilisation +Pour passer en mode édition, il suffit de cliquer sur le texte à modifier. +Un appui sur la touche `ESC` permet de repasser en mode affichage. +L'enregistrement est automatique dès que l'on sort du mode édition ou après 5 secondes sans modifications. +## Syntaxe +### Titres +Il y a 4 niveaux de titres : +- `# titre 1` +- `## titre 2` +- `### titre 3` +- `#### titre 4` +### Liens +Pour une url longue à laquelle on veut associer un libellé plus parlant, on choisira la syntaxe complète : +- [Wikipedia Markdown](https://fr.wikipedia.org/wiki/Markdown) +Si on veut que le libellé corresponde à l'url, on pourra utiliser l'une des syntaxes suivantes : +- [](https://www.google.fr) +- [index]() +> **A noter :** Les url du type `protocole://` s'ouvriront dans un nouvel onglet, tandis que les url pointant sur des pages internes s'ouvriront par défaut dans l'onglet actuel. +### Listes +#### Liste à puces +Les listes à puces peuvent avoir trois niveaux. On peut utiliser indifféremment `-`, `*` ou `+`. +- élément de liste de niveau 1 +- + élément de liste de niveau 2 +- - - élément de liste de niveau 3 +#### Listes numérotées +A l'instar des listes à puces, les listes numérotées peuvent avoir trois niveaux. +La numérotation n'est pas automatique. +1. premier élément +2. deuxième élément +2.1. premier élément de deuxième niveau niveau +2.2. second élément de deuxième niveau +3. troisième niveau +3.1. élément 3.1. +3.1.1. élément 3.1.1. \ No newline at end of file diff --git a/pages/poissons.md b/pages/poissons.md new file mode 100644 index 0000000..bb3e7c9 --- /dev/null +++ b/pages/poissons.md @@ -0,0 +1,8 @@ +# Les poissons osseux +## Qui sont-ils ? +## Les différentes parties du poisson +## De toutes les tailles et de toutes les couleurs +## Où vivent-ils ? +## Comment respirent-ils ? +## Que mangent-ils ? +## Comment se reproduisent-ils ? \ No newline at end of file diff --git a/src/index.rs b/src/index.rs index 5bdd898..3ca19a3 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1,5 +1,5 @@ use actix_web::web; -use actix_web::{HttpResponse, Responder, get}; +use actix_web::{Responder, get}; use askama_actix::Template; use askama_actix::TemplateToResponse; @@ -8,12 +8,12 @@ use crate::commons::AppData; #[derive(Template)] #[template(path = "index.html")] -pub struct IndexTemplate { +pub struct PageTemplate { pub name: String, } #[get("/")] async fn index(data: web::Data) -> impl Responder { let name = data.name.to_owned(); - IndexTemplate { name }.to_response() + PageTemplate { name }.to_response() } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index b73d3ed..21cf3c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ +pub mod page; pub mod index; pub mod commons; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 92ebb75..e628a2f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,8 @@ -use actix_web::{post, web, App, HttpResponse, HttpServer, Responder}; +use actix_web::{web, App, HttpServer}; use actix_files; use mdnotes::commons::{Arguments, AppData}; -use mdnotes::index; +use mdnotes::{ page, index }; #[actix_web::main] async fn main() -> std::io::Result<()> { @@ -20,6 +20,8 @@ async fn main() -> std::io::Result<()> { App::new() .app_data(web::Data::new(appdata.to_owned())) .service(index::index) + .service(page::page) + //.service(page::page_link) .service(actix_files::Files::new("/static", "./static")) .service(actix_files::Files::new("/static/modules", "./static/modules")) }) diff --git a/src/page.rs b/src/page.rs new file mode 100644 index 0000000..2b1dda9 --- /dev/null +++ b/src/page.rs @@ -0,0 +1,58 @@ +use actix_web::web; +use actix_web::{Responder, get}; + +use std::fs; +//use regex::Regex; + +use askama_actix::Template; +use askama_actix::TemplateToResponse; + +use crate::commons::AppData; + +#[derive(Template)] +#[template(path = "page.html")] +pub struct PageTemplate { + pub name: String, + pub md: String, + pub init: String, +} + +#[get("/page/{page}")] +async fn page(path: web::Path<(String,)>, data: web::Data) -> impl Responder { + let pagename = &path.0; + let name = data.name.to_owned() + " " + pagename.as_str(); + let filename = String::from("pages/") + pagename.as_str() + ".md"; + let md = match fs::read_to_string(filename) { + Ok(txt) => txt, + Err(_) => String::from("# Nouvelle page"), + }; + let init = String::from("init();"); + PageTemplate { name, md, init }.to_response() +} + +/*#[get("/page/{oldpage}/{page}")] +async fn page_link(path: web::Path<(String, String,)>, data: web::Data) -> impl Responder { + let pagename = &path.1; + let name = data.name.to_owned() + " " + pagename.as_str(); + let filename = String::from("pages/") + pagename.as_str() + ".md"; + let md = match fs::read_to_string(filename) { + Ok(txt) => txt, + Err(_) => String::from("# Nouvelle page"), + }; + let init = format!("init('{}')", pagename); + PageTemplate { name, md, init }.to_response() +}*/ + +/*fn normalize_pagename(pagename: String) -> Result { + let pagename = pagename.trim(); + + let space_re = Regex::new(r"[-\s]").unwrap(); + let pagename = &*space_re.replace_all(pagename, "_"); + + let re = Regex::new(r"").unwrap(); + if re.is_match(pagename) { + Ok(pagename.to_owned()) + } else { + Err(()) + } +}*/ \ No newline at end of file diff --git a/static/mdnotes.css b/static/mdnotes.css index 42f0ea7..2cc5a5b 100644 --- a/static/mdnotes.css +++ b/static/mdnotes.css @@ -7,6 +7,11 @@ body { margin: 0; } +button { + width: 2.2rem; + height: 2.2rem; +} + textarea#ta { display: none; width: 100%; @@ -22,34 +27,43 @@ div#mdnotes { padding-right: 3rem; padding-bottom: 1rem; background-color: #ffffff; + min-height: 90%; } div#mdnotes:focus { outline: none; } +div#mdnotes div.line { + border-bottom: 1px solid #dddddd; + padding-top: 0rem; + margin-top: 0rem; + font-size: 8pt; +} + div#mdnotes div.h1 { font-size: 3rem; - margin-top: 1rem; + margin-top: 1.5rem; margin-bottom: 1.3rem; padding-bottom: 0.5rem; border-bottom: 1px solid #dddddd; } div#mdnotes div.h2 { - font-size: 2.2rem; - margin-top: 0.5rem; + font-size: 2.3rem; + margin-top: 1rem; margin-bottom: 1rem; } div#mdnotes div.h3 { - font-size: 1.5rem; - margin-top: 0.5rem; - margin-bottom: 1rem; + font-size: 1.8rem; + margin-top: 0.8rem; + margin-bottom: 0.8rem; } div#mdnotes div.h4 { font-style: italic; + font-size: 1.4rem; margin-top: 0.5rem; margin-bottom: 0.5rem; } @@ -78,40 +92,79 @@ div#mdnotes div.bq3 { border-left: .9rem solid #6688bb; } -div#mdnotes div.ul1 { padding-left: 2.2rem; } +div#mdnotes div.ul1 { display:list-item; list-style-position: inside; padding-left: 2.2rem; padding-bottom: .2rem; padding-top: .2rem; } div#mdnotes div.ul1.bq1 { padding-left: 1.9rem; } div#mdnotes div.ul1.bq2 { padding-left: 1.6rem; } div#mdnotes div.ul1.bq3 { padding-left: 1.3rem; } -div#mdnotes div.ul2 { padding-left: 2.7rem; } +div#mdnotes div.ul2 { display:list-item; list-style-position: inside; padding-left: 2.7rem; padding-bottom: .2rem; padding-top: .2rem; } div#mdnotes div.ul2.bq1 { padding-left: 2.4rem; } div#mdnotes div.ul2.bq2 { padding-left: 2.1rem; } div#mdnotes div.ul2.bq3 { padding-left: 1.8rem; } -div#mdnotes div.ul3 { padding-left: 3.2rem; } +div#mdnotes div.ul3 { display:list-item; list-style-position: inside; padding-left: 3.2rem; padding-bottom: .2rem; padding-top: .2rem; } div#mdnotes div.ul3.bq1 { padding-left: 2.9rem; } div#mdnotes div.ul3.bq2 { padding-left: 2.6rem; } div#mdnotes div.ul3.bq3 { padding-left: 2.3rem; } -div#mdnotes div.ol1 { padding-left: 2.2rem; } +div#mdnotes div.ol1 { padding-left: 2.2rem; padding-bottom: .2rem; padding-top: .2rem; } div#mdnotes div.ol1.bq1 { padding-left: 1.9rem; } div#mdnotes div.ol1.bq2 { padding-left: 1.6rem; } div#mdnotes div.ol1.bq3 { padding-left: 1.3rem; } -div#mdnotes div.ol2 { padding-left: 2.7rem; } +div#mdnotes div.ol2 { padding-left: 2.7rem; padding-bottom: .2rem; padding-top: .2rem; } div#mdnotes div.ol2.bq1 { padding-left: 2.4rem; } div#mdnotes div.ol2.bq2 { padding-left: 2.1rem; } div#mdnotes div.ol2.bq3 { padding-left: 1.8rem; } -div#mdnotes div.ol3 { padding-left: 3.2rem; } +div#mdnotes div.ol3 { padding-left: 3.2rem; padding-bottom: .2rem; padding-top: .2rem; } div#mdnotes div.ol3.bq1 { padding-left: 2.9rem; } div#mdnotes div.ol3.bq2 { padding-left: 2.6rem; } div#mdnotes div.ol3.bq3 { padding-left: 2.3rem; } -div#mdnotes div.tablecell { +div#mdnotes span.oltoken { + display: inline-block; + width: 2rem; +} + +div#mdnotes span.lefttablespacer { + display: inline-block; +} + +div#mdnotes span.righttablespacer { display: inline-block; } +div#mdnotes div.tablerow { + margin-top: .5rem; + margin-bottom: .9rem; +} + +div#mdnotes span.tablerow { + border-bottom: 1px solid #dddddd; + padding-bottom: 0.4rem; +} + +div#mdnotes div.firsttablerow { + margin-top: 1.5rem; +} + +div#mdnotes div.firsttablerow span.tablerow { + margin-bottom: 1.5rem; + border-bottom: 1px solid #666666; + font-weight: bold; +} + +div#mdnotes div.lasttablerow { + margin-bottom: 1.5rem; +} + +div#mdnotes div.lasttablerow span.tablerow { + margin-bottom: 1.5rem; + border-bottom: none; +} + + div#mdnotes div.mdnotes_line span.token { font-weight: 1; color: #1353b3; diff --git a/static/modules/md.js b/static/modules/md.js index 196de78..8542a27 100644 --- a/static/modules/md.js +++ b/static/modules/md.js @@ -1,11 +1,19 @@ function load(textarea, div) { div.innerHTML = ''; let lines = textarea.value.split('\n'); + let firsttableline = null; for (let i = 0; i < lines.length; i++) { let line = lines[i]; - line = line.replace(/\t/, emsp()); let elem = formatLine(line); div.append(elem); + if (elem.classList.contains('tablerow')) { + if (firsttableline == null) { + firsttableline = elem; + } + } else if (firsttableline != null) { + formatTable(firsttableline); + firsttableline = null; + } } } @@ -13,7 +21,12 @@ function save(textarea, div) { let lines = div.children; let text = ''; for (let i=0; i\s*>\s*>\s/i)) { - token = /^(\s*>\s*>\s*>\s)/i; + } else if (line.match(/^\s*>\s*>\s*>(\s|$)/i)) { + token = /^(\s*>\s*>\s*>(\s|$))/i; elem.classList.add('bq3'); elem.classList.add('bq'); - } else if (line.match(/^\s*>\s*>\s/i)) { - token = /^(\s*>\s*>\s)/i; + } else if (line.match(/^\s*>\s*>(\s|$)/i)) { + token = /^(\s*>\s*>(\s|$))/i; elem.classList.add('bq2'); elem.classList.add('bq'); - } else if (line.match(/^\s*>\s/i)) { - token = /^(\s*>\s)/i; + } else if (line.match(/^\s*>(\s|$)/i)) { + token = /^(\s*>(\s|$))/i; elem.classList.add('bq1'); elem.classList.add('bq'); } else { elem.classList.add('body'); } if (elem.classList.contains('bq') || elem.classList.contains('body')) { - if (line.match(/^\s*((>\s*){0,3}\s)?([\*\-+]\s*){3}\s/i)) { - token = /^(\s*(>\s*){0,3}([\*\-+]\s*){2})/i; + if (line.match(/^\s*((>\s*){0,3}\s)?([\*\-+]\s*){3}(\s|$)/i)) { + token = /^(\s*(>\s*){0,3}([\*\-+]\s*){3}(\s|$))/i; elem.classList.add('ul3'); elem.classList.remove('body'); - } else if (line.match(/^\s*((>\s*){0,3}\s)?([\*\-+]\s*){2}\s/i)) { - token = /^(\s*(>\s*){0,3}[\*\-+])/i; + } else if (line.match(/^\s*((>\s*){0,3}\s)?([\*\-+]\s*){2}(\s|$)/i)) { + token = /^(\s*(>\s*){0,3}([\*\-+]\s*){2}(\s|$))/i; elem.classList.add('ul2'); elem.classList.remove('body'); - } else if (line.match(/^\s*((>\s*){0,3}\s)?[\*\-+]\s/i)) { - token = /^(\s*(>\s*){0,3}\s*)/; + } else if (line.match(/^\s*((>\s*){0,3}\s)?[\*\-+](\s|$)/i)) { + token = /^(\s*(>\s*){0,3}[\*\-+](\s*|$))/; elem.classList.add('ul1'); elem.classList.remove('body'); - } else if (line.match(/^\s*((>\s*){0,3}\s)?([0-9]+\.){3}\s/i)) { - token = /^(\s*(>\s*){0,3}\s*)/; + } else if (line.match(/^\s*((>\s*){0,3}\s)?([0-9]+\.){3}(\s|$)/i)) { + token = /^(\s*(>\s*){0,3}(\s*|$))/; elem.classList.add('ol3'); elem.classList.remove('body'); - } else if (line.match(/^\s*((>\s*){0,3}\s)?([0-9]+\.){2}\s/i)) { - token = /^(\s*(>\s*){0,3}\s*)/; + } else if (line.match(/^\s*((>\s*){0,3}\s)?([0-9]+\.){2}(\s|$)/i)) { + token = /^(\s*(>\s*){0,3}(\s*|$))/; elem.classList.add('ol2'); elem.classList.remove('body'); - } else if (line.match(/^\s*((>\s*){0,3}\s)?[0-9]+\.\s/i)) { - token = /^(\s*(>\s*){0,3}\s*)/; + } else if (line.match(/^\s*((>\s*){0,3}\s)?[0-9]+\.(\s|$)/i)) { + token = /^(\s*(>\s*){0,3}(\s*|$))/; elem.classList.add('ol1'); elem.classList.remove('body'); } } - elem.classList.add('mdnotes_line'); - line = addLink(line); - line = addBold(line); - line = addItalic(line); - line = addMono(line); if (token != null) { line = line.replace(token, '$1'); } + line = addLink(line); + line = addBold(line); + line = addItalic(line); + line = addTableLine(line); + line = addMono(line, listMono); elem.innerHTML = line; + if (elem.getElementsByClassName('tablerow').length > 0) { + if (elem.childNodes[0].childNodes.length > 0) { + let child = elem.childNodes[0].childNodes[0]; + while(child != null) { + if (child.nodeType == 3) { + let newchild = document.createElement('span'); + newchild.innerText = child.nodeValue; + child.parentNode.replaceChild(newchild, child); + child = newchild; + } + child = child.nextSibling; + } + } + elem.classList.remove('body'); + elem.classList.add('tablerow'); + } let links = elem.getElementsByClassName('link'); for (let i=0; i`$1`'); +function addTableLine(line) { + if (line.match(/^\s*\|(\s*.*?\|)*/)) { + let cpt = 0; + let re = /\|/g; + let match; + let matches = []; + while ((match = re.exec(line)) != null) { + matches[cpt] = match.index; + if (cpt > 1000) { + continue; + } + cpt++; + } + matches = matches.reverse(); + for(let i=0; i|' + line.substring(index+1, line.length); + continue; + } + if (i == 0 && index == line.length - 1) { + // dernier | + line = line.substring(0, index) + '|' + line.substring(index+1, line.length); + continue; + } + line = line.substring(0, index) + '|' + line.substring(index+1, line.length); + } + line = '' + line + ''; + } + return line; +} + +function formatTable(line) { + if (! line.classList.contains('tablerow')) { + return; + } + + let firstline = line; + let lastline = line; + while (firstline.previousSibling !== null && firstline.previousSibling.classList.contains('tablerow')) { + firstline = firstline.previousSibling; + firstline.classList.remove('firsttablerow'); + firstline.classList.remove('lasttablerow'); + } + while (lastline.nextSibling !== null && lastline.nextSibling.classList.contains('tablerow')) { + lastline = lastline.nextSibling; + lastline.classList.remove('firsttablerow'); + lastline.classList.remove('lasttablerow'); + } + firstline.classList.add('firsttablerow'); + lastline.classList.add('lasttablerow'); + resizeTableCols(firstline, lastline) +} + + +function resizeTableCols(firstline, lastline) { + let colsmaxwidth = []; + let currentline = firstline; + while (currentline !== null && currentline.classList.contains('tablerow')) { + let tablerow = currentline.getElementsByClassName('tablerow')[0]; + let childNodes = tablerow.childNodes; + let currentwidth = 0; + let col = 0; + let currentleft; + for(let i=0; i 1000) { + continue; + } + cpt++; + } + matches = matches.reverse(); + for(let i=0; i`' + mono + '`' + line.substring(matches[i]+2, line.length); + } return line; } function addLink(line) { - line = line.replace(/(\[(.*?)\]\((.*?)\))/ig, '[$2]($3)'); + line = line.replace(/(\[([^\]]+?)\]\(([^\)]+?)\))/ig, '[$2]($3)'); + line = line.replace(/(\[([^\]]+?)\]\(\))/ig, '[$2]()'); + line = line.replace(/(\[\]\(([^\)]+?)\))/ig, '[]($2)'); return line; } @@ -153,4 +339,4 @@ function addItalic(line) { return line; } -export { load, save, formatLine }; +export { load, save, formatLine, formatTable }; diff --git a/static/modules/mdnotes.js b/static/modules/mdnotes.js index fa3ac91..f37e50b 100644 --- a/static/modules/mdnotes.js +++ b/static/modules/mdnotes.js @@ -1,5 +1,12 @@ import { getStartPositionInLine, setStartPositionInLine } from './position.js'; -import { formatLine, load, save } from './md.js'; +import { formatLine, load, save, formatTable } from './md.js'; + +function timeoutSave() { + if (window.tos !== null) { + window.clearTimeout(window.tos); + } + window.tos = window.setTimeout(onsave, 5000); +} function ontextarea(e) { let ta = document.getElementById('ta'); @@ -19,17 +26,26 @@ function onload(e) { } function onedit(e) { + timeoutSave(); let ret = getStartPositionInLine(); let line = ret[0]; let position = ret[1]; if (line.innerText == '\n') { line.className = 'mdnotes_line'; + line.classList.add('body'); return; } + let prevline = line.previousSibling; + if (prevline != null && prevline.innerText == '\n') { + prevline.className = 'mdnotes_line'; + prevline.classList.add('body'); + } + let newline = formatLine(line.innerText); line.parentNode.replaceChild(newline, line); + formatTable(newline); setStartPositionInLine(newline, position); } @@ -49,27 +65,56 @@ function onkeydown(e) { } else { line.innerHTML = txt.substring(0, position) + key + txt.substring(position, txt.length); } - /*if (line.innerText.match(RegExp(emsp()))) { - alert('emsp'); - }*/ setStartPositionInLine(line, position + 1); onedit(e); return false; } +function onkeypress(e) { + if (e.key == 'Escape') { + e.preventDefault(); + document.getElementById('mdnotes').blur(); + onsave(); + return false; + } + if (e.key == 'Enter') { + + } +} + function onkeyup(e) { if (e.key == 'Escape') { e.preventDefault(); document.getElementById('mdnotes').blur(); + onsave(); return false; } } -function init() { +function onpaste(e) { + timeoutSave(); + let data = e.clipboardData.getData('text/plain'); + if (data.match(/ \w+:\/\/.*/i)) { + data = '[](' + data + ')'; + e.clipBoard.setData('text/plain', data); + } +} + +function oncopy(e) { + alert(e.clipboardData.getData('text/plain')); +} + +function init(pagename = null) { + /*if (pagename != null) { + window.history.replaceState(null, '', '/page/' + pagename); + }*/ let mdnotesdiv = document.getElementById('mdnotes'); mdnotesdiv.addEventListener('input', onedit); mdnotesdiv.addEventListener('keyup', onkeyup); + mdnotesdiv.addEventListener('keypress', onkeypress); mdnotesdiv.addEventListener('keydown', onkeydown); + mdnotesdiv.addEventListener('paste', onpaste); + mdnotesdiv.addEventListener('copy', oncopy); let saveButton = document.getElementById('saveButton'); saveButton.addEventListener('click', onsave); loadButton.addEventListener('click', onload); @@ -78,4 +123,4 @@ function init() { onload(); } -init(); \ No newline at end of file +export { init }; \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 7754159..2390f95 100644 --- a/templates/index.html +++ b/templates/index.html @@ -8,24 +8,9 @@ {{name}} - + -
- -
- +

diff --git a/templates/page.html b/templates/page.html new file mode 100644 index 0000000..03779e4 --- /dev/null +++ b/templates/page.html @@ -0,0 +1,24 @@ + + + + + + + + + + {{name}} + + + + +
+ +
+ + + +