Commit c179a4f809d04c0edab19d002f1fd13eb15f22b2
1 parent
b9da2ed6
Add patch creation an apply on load logic
Showing
7 changed files
with
235 additions
and
81 deletions
No preview for this file type
@@ -6,71 +6,20 @@ mod models; | @@ -6,71 +6,20 @@ mod models; | ||
6 | mod routes; | 6 | mod routes; |
7 | mod schema; | 7 | mod schema; |
8 | 8 | ||
9 | -use std::io::Write; | ||
10 | -use crate::routes::markdown::get_markdowns; | 9 | +use crate::routes::markdown::{get_markdowns, update_markdown}; |
11 | use crate::routes::other::*; | 10 | use crate::routes::other::*; |
12 | use crate::routes::user::*; | 11 | use crate::routes::user::*; |
13 | 12 | ||
14 | use actix_web::{guard, web, App, HttpResponse, HttpServer}; | 13 | use actix_web::{guard, web, App, HttpResponse, HttpServer}; |
15 | use diesel::r2d2::{self, ConnectionManager}; | 14 | use diesel::r2d2::{self, ConnectionManager}; |
16 | use diesel::SqliteConnection; | 15 | use diesel::SqliteConnection; |
17 | -use diffy::create_patch; | ||
18 | -use flate2::Compression; | ||
19 | -use flate2::write::{DeflateEncoder, DeflateDecoder}; | ||
20 | use listenfd::ListenFd; | 16 | use listenfd::ListenFd; |
17 | +use routes::markdown::get_markdown; | ||
21 | 18 | ||
22 | pub(crate) type Pool = r2d2::Pool<ConnectionManager<SqliteConnection>>; | 19 | pub(crate) type Pool = r2d2::Pool<ConnectionManager<SqliteConnection>>; |
23 | 20 | ||
24 | -const ORIGINAL :&str = r"This is a long Multiline text to | ||
25 | -see if we can create a small patch set from | ||
26 | -a longer text. | ||
27 | - | ||
28 | -This paragraph will be kept without modification. The reason I | ||
29 | -try this is to see if the diff becomes smaller than storing a | ||
30 | -compressed new version of this text... | ||
31 | - | ||
32 | -So this whole paragraph is obsolete. It will be removed in the | ||
33 | -modified text. | ||
34 | - | ||
35 | -There is probably a threshold up to what amount of text is needed | ||
36 | -to make the diff smaller. | ||
37 | -"; | ||
38 | - | ||
39 | -const MODIFIED :&str = r"This is a long multiline text to | ||
40 | -see if we can create a small patch set from | ||
41 | -a longer text. | ||
42 | - | ||
43 | -This paragraph will be kept without modification. The reason I | ||
44 | -try this is to see if the diff becomes smaller than storing a | ||
45 | -compressed new version of this text... | ||
46 | - | ||
47 | -This is the replacement for the previous paragraph. | ||
48 | - | ||
49 | -There is probably a threshold up to what amount of text is needed | ||
50 | -to make the diff smaller. | ||
51 | -"; | ||
52 | - | ||
53 | #[actix_rt::main] | 21 | #[actix_rt::main] |
54 | async fn main() -> std::io::Result<()> { | 22 | async fn main() -> std::io::Result<()> { |
55 | - /* just some playing with diffy */ | ||
56 | - let patch = format!("{}", create_patch(ORIGINAL, MODIFIED)); | ||
57 | - println!("{} - {}", patch.len(), patch); | ||
58 | - | ||
59 | - let mut encoder = DeflateEncoder::new(Vec::new(), Compression::best()); | ||
60 | - encoder.write_all(patch.as_bytes()).unwrap(); | ||
61 | - let compressed = encoder.finish().unwrap(); | ||
62 | - println!("{} - {:?}", compressed.len(), compressed); | ||
63 | - | ||
64 | - let mut decoder = DeflateDecoder::new(Vec::new()); | ||
65 | - decoder.write_all(compressed.as_ref()).unwrap(); | ||
66 | - let decompressed = decoder.finish().unwrap(); | ||
67 | - let decompressed = match std::str::from_utf8(decompressed.as_ref()) { | ||
68 | - Ok(v) => v, | ||
69 | - Err(e) => panic!("Invalid UTF-8 sequence: {}", e), | ||
70 | - }; | ||
71 | - println!("{} - {}", decompressed.len(), decompressed); | ||
72 | - /* ======= */ | ||
73 | - | ||
74 | let mut listenfd = ListenFd::from_env(); | 23 | let mut listenfd = ListenFd::from_env(); |
75 | 24 | ||
76 | dotenv::dotenv().ok(); | 25 | dotenv::dotenv().ok(); |
@@ -87,6 +36,10 @@ async fn main() -> std::io::Result<()> { | @@ -87,6 +36,10 @@ async fn main() -> std::io::Result<()> { | ||
87 | . service( web::resource("/markdowns") | 36 | . service( web::resource("/markdowns") |
88 | . route(web::get().to(get_markdowns)) | 37 | . route(web::get().to(get_markdowns)) |
89 | ) | 38 | ) |
39 | + . service( web::resource("/markdowns/{id}") | ||
40 | + . route(web::get().to(get_markdown)) | ||
41 | + . route(web::put().to(update_markdown)) | ||
42 | + ) | ||
90 | . service( web::resource("/users") | 43 | . service( web::resource("/users") |
91 | . route(web::get().to(get_users)) | 44 | . route(web::get().to(get_users)) |
92 | . route(web::put().to(create_user)) | 45 | . route(web::put().to(create_user)) |
@@ -8,6 +8,10 @@ use diesel::{ | @@ -8,6 +8,10 @@ use diesel::{ | ||
8 | RunQueryDsl | 8 | RunQueryDsl |
9 | }; | 9 | }; |
10 | use serde::{Deserialize, Serialize}; | 10 | use serde::{Deserialize, Serialize}; |
11 | +use std::io::Write; | ||
12 | +use diffy::{apply, create_patch, Patch}; | ||
13 | +use flate2::Compression; | ||
14 | +use flate2::write::{DeflateEncoder, DeflateDecoder}; | ||
11 | 15 | ||
12 | 16 | ||
13 | #[derive(Debug, Serialize, Deserialize, Queryable, Identifiable)] | 17 | #[derive(Debug, Serialize, Deserialize, Queryable, Identifiable)] |
@@ -20,6 +24,16 @@ pub struct Markdown { | @@ -20,6 +24,16 @@ pub struct Markdown { | ||
20 | pub date_updated: String, | 24 | pub date_updated: String, |
21 | } | 25 | } |
22 | 26 | ||
27 | +#[derive(Debug, Serialize, Deserialize, Queryable, Identifiable)] | ||
28 | +#[table_name = "markdown_diffs"] | ||
29 | +#[primary_key(markdown_id, diff_id)] | ||
30 | +pub struct MarkdownDiff { | ||
31 | + pub markdown_id: i32, | ||
32 | + pub diff_id: i32, | ||
33 | + pub diff: Vec<u8>, | ||
34 | + pub date_created: String, | ||
35 | +} | ||
36 | + | ||
23 | #[derive(Debug, Insertable)] | 37 | #[derive(Debug, Insertable)] |
24 | #[table_name = "markdowns"] | 38 | #[table_name = "markdowns"] |
25 | pub struct MarkdownNew<'a> { | 39 | pub struct MarkdownNew<'a> { |
@@ -30,12 +44,23 @@ pub struct MarkdownNew<'a> { | @@ -30,12 +44,23 @@ pub struct MarkdownNew<'a> { | ||
30 | pub date_updated: &'a str, | 44 | pub date_updated: &'a str, |
31 | } | 45 | } |
32 | 46 | ||
47 | +#[derive(Debug, Insertable)] | ||
48 | +#[table_name = "markdown_diffs"] | ||
49 | +pub struct MarkdownDiffNew<'a> { | ||
50 | + pub markdown_id: i32, | ||
51 | + pub diff_id: i32, | ||
52 | + pub diff: &'a [u8], | ||
53 | + pub date_created: &'a str, | ||
54 | +} | ||
55 | + | ||
33 | #[derive(Debug, Serialize, Deserialize, AsChangeset)] | 56 | #[derive(Debug, Serialize, Deserialize, AsChangeset)] |
34 | #[table_name="markdowns"] | 57 | #[table_name="markdowns"] |
35 | pub struct MarkdownJson { | 58 | pub struct MarkdownJson { |
36 | pub name: String, | 59 | pub name: String, |
37 | pub content: String, | 60 | pub content: String, |
38 | pub number_of_versions: i32, | 61 | pub number_of_versions: i32, |
62 | + pub date_created: String, | ||
63 | + pub date_updated: String, | ||
39 | } | 64 | } |
40 | 65 | ||
41 | pub(crate) enum Action { | 66 | pub(crate) enum Action { |
@@ -82,11 +107,34 @@ pub(crate) fn get_markdowns(pool: Arc<Pool>) -> Result<Vec<Markdown>, Error> | @@ -82,11 +107,34 @@ pub(crate) fn get_markdowns(pool: Arc<Pool>) -> Result<Vec<Markdown>, Error> | ||
82 | } | 107 | } |
83 | 108 | ||
84 | pub(crate) fn get_markdown( pool: Arc<Pool> | 109 | pub(crate) fn get_markdown( pool: Arc<Pool> |
85 | - , ident: i32 ) -> Result<Markdown, Error> | 110 | + , ident: &str |
111 | + , patch: Option<i32> ) -> Result<Markdown, Error> | ||
86 | { | 112 | { |
87 | use crate::schema::markdowns::dsl::*; | 113 | use crate::schema::markdowns::dsl::*; |
114 | + use crate::schema::markdown_diffs::dsl::*; | ||
88 | let db_connection = pool.get()?; | 115 | let db_connection = pool.get()?; |
89 | - Ok(markdowns.find(ident).first::<Markdown>(&db_connection)?) | 116 | + |
117 | + let mut markdown = markdowns | ||
118 | + . filter(name.eq(ident)) | ||
119 | + . first::<Markdown>(&db_connection)?; | ||
120 | + | ||
121 | + if let Some(patch) = patch { | ||
122 | + let result = markdown_diffs . filter(markdown_id.eq(markdown.id)) | ||
123 | + . filter(diff_id.ge(patch)) | ||
124 | + . order(diff_id.desc()) | ||
125 | + . load::<MarkdownDiff>(&db_connection)?; | ||
126 | + | ||
127 | + let mut decoder = DeflateDecoder::new(Vec::new()); | ||
128 | + for patch in result { | ||
129 | + decoder.write_all(patch.diff.as_ref()).unwrap(); | ||
130 | + let decomp = decoder.reset(Vec::new()).unwrap(); | ||
131 | + let decomp = Patch::from_str( | ||
132 | + std::str::from_utf8(decomp.as_ref()).unwrap()).unwrap(); | ||
133 | + markdown.content = apply(&mut markdown.content, &decomp).unwrap(); | ||
134 | + } | ||
135 | + }; | ||
136 | + | ||
137 | + Ok(markdown) | ||
90 | } | 138 | } |
91 | 139 | ||
92 | pub(crate) fn delete_markdown( pool: Arc<Pool> | 140 | pub(crate) fn delete_markdown( pool: Arc<Pool> |
@@ -98,19 +146,54 @@ pub(crate) fn delete_markdown( pool: Arc<Pool> | @@ -98,19 +146,54 @@ pub(crate) fn delete_markdown( pool: Arc<Pool> | ||
98 | } | 146 | } |
99 | 147 | ||
100 | pub(crate) fn update_markdown( pool: Arc<Pool> | 148 | pub(crate) fn update_markdown( pool: Arc<Pool> |
101 | - , ident: i32 | ||
102 | - , item: MarkdownJson ) -> Result<Markdown, Error> | 149 | + , ident: String |
150 | + , mut item: MarkdownJson ) -> Result<Markdown, Error> | ||
103 | { | 151 | { |
104 | use crate::schema::markdowns::dsl::*; | 152 | use crate::schema::markdowns::dsl::*; |
153 | + use crate::schema::markdown_diffs::dsl::*; | ||
105 | let db_connection = pool.get()?; | 154 | let db_connection = pool.get()?; |
106 | - let mut markdown = markdowns.find(ident).first::<Markdown>(&db_connection)?; | 155 | + let mut markdown = markdowns |
156 | + . filter(name.eq(ident)) | ||
157 | + . first::<Markdown>(&db_connection)?; | ||
158 | + | ||
159 | + let patch = format!( "{}", create_patch( item.content.as_str() | ||
160 | + , markdown.content.as_str() )); | ||
161 | + let mut encoder = DeflateEncoder::new(Vec::new(), Compression::best()); | ||
162 | + encoder.write_all(patch.as_bytes()).unwrap(); | ||
163 | + let compressed = encoder.finish().unwrap(); | ||
164 | + | ||
165 | + let last_diff = match markdown_diffs . filter(markdown_id.eq(markdown.id)) | ||
166 | + . order(diff_id.desc()) | ||
167 | + . first::<MarkdownDiff>(&db_connection) | ||
168 | + { | ||
169 | + Ok(result) => (result.diff_id, Some(result)), | ||
170 | + Err(_) => (0, None), | ||
171 | + }; | ||
172 | + | ||
107 | let now = chrono::Local::now().naive_local(); | 173 | let now = chrono::Local::now().naive_local(); |
174 | + let new_markdown_diff = MarkdownDiffNew { | ||
175 | + markdown_id: markdown.id, | ||
176 | + diff_id: last_diff.0 + 1, | ||
177 | + diff: compressed.as_ref(), | ||
178 | + date_created: markdown.date_updated.as_str(), | ||
179 | + }; | ||
180 | + | ||
181 | + item.date_updated = format!("{}", now); | ||
182 | + item.number_of_versions = item.number_of_versions + 1; | ||
183 | + | ||
184 | + db_connection.transaction::<_, Error, _>(|| { | ||
185 | + insert_into(markdown_diffs) . values(&new_markdown_diff) | ||
186 | + . execute(&db_connection)?; | ||
187 | + | ||
188 | + update(&markdown).set(&item).execute(&db_connection)?; | ||
189 | + | ||
190 | + Ok(()) | ||
191 | + }).unwrap(); | ||
108 | 192 | ||
109 | - update(markdowns.find(ident)).set(&item).execute(&db_connection)?; | ||
110 | markdown.name = item.name; | 193 | markdown.name = item.name; |
111 | markdown.content = item.content; | 194 | markdown.content = item.content; |
112 | markdown.number_of_versions = item.number_of_versions; | 195 | markdown.number_of_versions = item.number_of_versions; |
113 | - markdown.date_updated = format!("{}", now); | 196 | + markdown.date_updated = item.date_updated; |
114 | 197 | ||
115 | Ok(markdown) | 198 | Ok(markdown) |
116 | } | 199 | } |
@@ -3,12 +3,50 @@ use crate::Pool; | @@ -3,12 +3,50 @@ use crate::Pool; | ||
3 | 3 | ||
4 | use actix_web::{Error, HttpResponse, web}; | 4 | use actix_web::{Error, HttpResponse, web}; |
5 | use anyhow::Result; | 5 | use anyhow::Result; |
6 | +use serde::Deserialize; | ||
7 | + | ||
8 | +#[derive(Debug, Deserialize)] | ||
9 | +pub struct Patchset { | ||
10 | + patch: Option<i32>, | ||
11 | +} | ||
6 | 12 | ||
7 | pub async fn get_markdowns(pool: web::Data<Pool>) | 13 | pub async fn get_markdowns(pool: web::Data<Pool>) |
8 | -> Result<HttpResponse, Error> | 14 | -> Result<HttpResponse, Error> |
9 | { | 15 | { |
10 | - Ok(web::block(move || markdown::get_markdowns(pool.into_inner())) | 16 | + Ok( web::block(move || markdown::get_markdowns(pool.into_inner())) |
17 | + . await | ||
18 | + . map(|markdowns| HttpResponse::Ok().json(markdowns)) | ||
19 | + . map_err(|_| HttpResponse::InternalServerError())? | ||
20 | + ) | ||
21 | +} | ||
22 | + | ||
23 | +pub async fn get_markdown( pool: web::Data<Pool> | ||
24 | + , name: web::Path<String> | ||
25 | + , patch: web::Query<Patchset> | ||
26 | + ) -> Result<HttpResponse, Error> | ||
27 | +{ | ||
28 | + let pool = pool.into_inner(); | ||
29 | + let name = name.into_inner(); | ||
30 | + let patch = patch.into_inner(); | ||
31 | + | ||
32 | + Ok( web::block(move || markdown::get_markdown(pool, name.as_str(), patch.patch)) | ||
33 | + . await | ||
34 | + . map(|markdowns| HttpResponse::Ok().json(markdowns)) | ||
35 | + . map_err(|_| HttpResponse::InternalServerError())? | ||
36 | + ) | ||
37 | +} | ||
38 | + | ||
39 | +pub async fn update_markdown( pool: web::Data<Pool> | ||
40 | + , name: web::Path<String> | ||
41 | + , item: web::Json<markdown::MarkdownJson> ) | ||
42 | + -> Result<HttpResponse, Error> | ||
43 | +{ | ||
44 | + let pool = pool.into_inner(); | ||
45 | + let name = name.into_inner(); | ||
46 | + let item = item.into_inner(); | ||
47 | + | ||
48 | + Ok(web::block(move || markdown::update_markdown(pool, name, item)) | ||
11 | . await | 49 | . await |
12 | - . map(|markdowns| HttpResponse::Ok().json(markdowns)) | 50 | + . map(|markdown| HttpResponse::Ok().json(markdown)) |
13 | . map_err(|_| HttpResponse::InternalServerError())?) | 51 | . map_err(|_| HttpResponse::InternalServerError())?) |
14 | } | 52 | } |
@@ -12,6 +12,7 @@ crate-type = ["cdylib", "rlib"] | @@ -12,6 +12,7 @@ crate-type = ["cdylib", "rlib"] | ||
12 | default = ["console_error_panic_hook"] | 12 | default = ["console_error_panic_hook"] |
13 | 13 | ||
14 | [dependencies] | 14 | [dependencies] |
15 | +katex = { version = "0.4", default-features = false, features = ["wasm-js"] } | ||
15 | pulldown-cmark = "0.9" | 16 | pulldown-cmark = "0.9" |
16 | console_log = "^0.1" | 17 | console_log = "^0.1" |
17 | log = "^0.4" | 18 | log = "^0.4" |
@@ -39,6 +40,7 @@ features = [ | @@ -39,6 +40,7 @@ features = [ | ||
39 | "Headers", | 40 | "Headers", |
40 | "HtmlElement", | 41 | "HtmlElement", |
41 | "HtmlInputElement", | 42 | "HtmlInputElement", |
43 | + "MouseEvent", | ||
42 | "Node", | 44 | "Node", |
43 | "Request", | 45 | "Request", |
44 | "RequestInit", | 46 | "RequestInit", |
@@ -3,7 +3,7 @@ mod data; | @@ -3,7 +3,7 @@ mod data; | ||
3 | use js_sys::JsString; | 3 | use js_sys::JsString; |
4 | use log::Level; | 4 | use log::Level; |
5 | use mogwai::prelude::*; | 5 | use mogwai::prelude::*; |
6 | -use web_sys::{RequestInit, RequestMode, Request, Response}; | 6 | +use web_sys::{RequestInit, RequestMode, Request, Response, MouseEvent}; |
7 | use serde::{Deserialize, Serialize}; | 7 | use serde::{Deserialize, Serialize}; |
8 | use std::panic; | 8 | use std::panic; |
9 | use wasm_bindgen::prelude::*; | 9 | use wasm_bindgen::prelude::*; |
@@ -12,6 +12,7 @@ use wasm_bindgen::prelude::*; | @@ -12,6 +12,7 @@ use wasm_bindgen::prelude::*; | ||
12 | enum AppLogic { | 12 | enum AppLogic { |
13 | Update, | 13 | Update, |
14 | Toggle, | 14 | Toggle, |
15 | + Store, | ||
15 | } | 16 | } |
16 | 17 | ||
17 | fn md_to_html(source: &str) -> String { | 18 | fn md_to_html(source: &str) -> String { |
@@ -24,21 +25,23 @@ fn md_to_html(source: &str) -> String { | @@ -24,21 +25,23 @@ fn md_to_html(source: &str) -> String { | ||
24 | html_output | 25 | html_output |
25 | } | 26 | } |
26 | 27 | ||
27 | -#[derive(Debug, Serialize, Deserialize)] | 28 | +#[derive(Clone, Debug, Serialize, Deserialize)] |
28 | pub struct MarkdownJson { | 29 | pub struct MarkdownJson { |
29 | pub name: String, | 30 | pub name: String, |
30 | pub content: String, | 31 | pub content: String, |
31 | pub number_of_versions: i32, | 32 | pub number_of_versions: i32, |
33 | + pub date_created: String, | ||
34 | + pub date_updated: String, | ||
32 | } | 35 | } |
33 | 36 | ||
34 | -pub type MarkdownsJson = Vec<MarkdownJson>; | ||
35 | - | ||
36 | -async fn md_from_db() -> String { | 37 | +async fn md_from_db() -> MarkdownJson { |
37 | let window = web_sys::window().unwrap(); | 38 | let window = web_sys::window().unwrap(); |
38 | let mut opts = RequestInit::new(); | 39 | let mut opts = RequestInit::new(); |
39 | opts.method("GET").mode(RequestMode::Cors); | 40 | opts.method("GET").mode(RequestMode::Cors); |
40 | 41 | ||
41 | - let request = Request::new_with_str_and_init("/api/v0/markdowns", &opts).unwrap(); | 42 | + let request = Request::new_with_str_and_init( |
43 | + "/api/v0/markdowns/md-example?patch=3" | ||
44 | + , &opts ).unwrap(); | ||
42 | request.headers().set("Accept", "application/json").unwrap(); | 45 | request.headers().set("Accept", "application/json").unwrap(); |
43 | 46 | ||
44 | let response = JsFuture::from(window.fetch_with_request(&request)) | 47 | let response = JsFuture::from(window.fetch_with_request(&request)) |
@@ -46,15 +49,38 @@ async fn md_from_db() -> String { | @@ -46,15 +49,38 @@ async fn md_from_db() -> String { | ||
46 | . dyn_into::<Response>().unwrap(); | 49 | . dyn_into::<Response>().unwrap(); |
47 | let data = String::from( JsFuture::from(response.text().unwrap()) | 50 | let data = String::from( JsFuture::from(response.text().unwrap()) |
48 | . await.unwrap() | 51 | . await.unwrap() |
49 | - . dyn_into::<JsString>().unwrap() ); | ||
50 | - let data :MarkdownsJson = serde_json::from_str(data.as_str()).unwrap(); | 52 | + . dyn_into::<JsString>().unwrap() |
53 | + ); | ||
54 | + let data :MarkdownJson = serde_json::from_str(data.as_str()).unwrap(); | ||
55 | + | ||
56 | + data.clone() | ||
57 | +} | ||
58 | + | ||
59 | +async fn md_to_db(md :MarkdownJson) { | ||
60 | + let encoded = serde_json::to_string(&md).unwrap(); | ||
61 | + | ||
62 | + let window = web_sys::window().unwrap(); | ||
63 | + let mut opts = RequestInit::new(); | ||
64 | + opts . method("PUT") | ||
65 | + . mode(RequestMode::Cors) | ||
66 | + . body(Some(&encoded.into())); | ||
51 | 67 | ||
52 | - String::from(&data[0].content) | 68 | + let url_str = format!("/api/v0/markdowns/{}", md.name); |
69 | + let request = Request::new_with_str_and_init(url_str.as_str(), &opts) | ||
70 | + . unwrap(); | ||
71 | + request.headers().set("Content-Type", "application/json").unwrap(); | ||
72 | + | ||
73 | + let _response = JsFuture::from(window.fetch_with_request(&request)) | ||
74 | + . await.unwrap() | ||
75 | + . dyn_into::<Response>().unwrap(); | ||
76 | + | ||
77 | + /* do something with the response here.... */ | ||
53 | } | 78 | } |
54 | 79 | ||
55 | async fn editor_logic( mut rx_logic: broadcast::Receiver<AppLogic> | 80 | async fn editor_logic( mut rx_logic: broadcast::Receiver<AppLogic> |
56 | , tx_view: broadcast::Sender<String> | 81 | , tx_view: broadcast::Sender<String> |
57 | - , mut rx_dom: broadcast::Receiver<Dom> ) { | 82 | + , mut rx_dom: broadcast::Receiver<Dom> |
83 | + , md :MarkdownJson ) { | ||
58 | let dom = rx_dom.next().await.unwrap(); | 84 | let dom = rx_dom.next().await.unwrap(); |
59 | let mut show_edit = false; | 85 | let mut show_edit = false; |
60 | 86 | ||
@@ -82,8 +108,39 @@ async fn editor_logic( mut rx_logic: broadcast::Receiver<AppLogic> | @@ -82,8 +108,39 @@ async fn editor_logic( mut rx_logic: broadcast::Receiver<AppLogic> | ||
82 | 108 | ||
83 | update(&dom); | 109 | update(&dom); |
84 | 110 | ||
111 | + /* play with katex ==== */ | ||
112 | + let opts = katex::Opts::builder() | ||
113 | + . output_type(katex::opts::OutputType::Mathml) | ||
114 | + . build().unwrap(); | ||
115 | + let formula1 = katex::render_with_opts("E = mc^2", &opts).unwrap(); | ||
116 | + let formula2 = katex::render_with_opts("e^{i*\\pi} +1 = 0", &opts).unwrap(); | ||
117 | + | ||
118 | + if let Either::Left(dom_js) = dom.inner_read() { | ||
119 | + dom_js . to_owned() | ||
120 | + . dyn_into::<Node>().unwrap() | ||
121 | + . child_nodes().get(1).unwrap() | ||
122 | + . child_nodes().get(2).unwrap() | ||
123 | + . dyn_into::<HtmlElement>().unwrap() | ||
124 | + . set_inner_html(formula1.as_str()) | ||
125 | + }; | ||
126 | + | ||
127 | + if let Either::Left(dom_js) = dom.inner_read() { | ||
128 | + dom_js . to_owned() | ||
129 | + . dyn_into::<Node>().unwrap() | ||
130 | + . child_nodes().get(1).unwrap() | ||
131 | + . child_nodes().get(3).unwrap() | ||
132 | + . dyn_into::<HtmlElement>().unwrap() | ||
133 | + . set_inner_html(formula2.as_str()) | ||
134 | + }; | ||
135 | + /* =========== */ | ||
136 | + | ||
85 | while let Some(msg) = rx_logic.next().await { | 137 | while let Some(msg) = rx_logic.next().await { |
86 | match msg { | 138 | match msg { |
139 | + AppLogic::Store => { | ||
140 | + let mut new_md = md.clone(); | ||
141 | + new_md.content = get_md(&dom); | ||
142 | + md_to_db(new_md).await; | ||
143 | + }, | ||
87 | AppLogic::Update => update(&dom), | 144 | AppLogic::Update => update(&dom), |
88 | AppLogic::Toggle => { | 145 | AppLogic::Toggle => { |
89 | show_edit = ! show_edit; | 146 | show_edit = ! show_edit; |
@@ -101,23 +158,42 @@ async fn editor_logic( mut rx_logic: broadcast::Receiver<AppLogic> | @@ -101,23 +158,42 @@ async fn editor_logic( mut rx_logic: broadcast::Receiver<AppLogic> | ||
101 | fn editor_view( tx_logic: broadcast::Sender<AppLogic> | 158 | fn editor_view( tx_logic: broadcast::Sender<AppLogic> |
102 | , rx_view: broadcast::Receiver<String> | 159 | , rx_view: broadcast::Receiver<String> |
103 | , tx_dom: broadcast::Sender<Dom> | 160 | , tx_dom: broadcast::Sender<Dom> |
104 | - , init_data: &str | 161 | + , md: &MarkdownJson |
105 | ) -> ViewBuilder<Dom> { | 162 | ) -> ViewBuilder<Dom> { |
106 | let ns = "http://www.w3.org/2000/svg"; | 163 | let ns = "http://www.w3.org/2000/svg"; |
107 | 164 | ||
165 | + let input_filter = tx_logic | ||
166 | + . sink() | ||
167 | + . contra_map(|_| AppLogic::Update); | ||
168 | + let store_filter = tx_logic | ||
169 | + . sink() | ||
170 | + . contra_filter_map(|e :DomEvent| { | ||
171 | + if let Either::Left(e) = e.clone_inner() { | ||
172 | + let e = e.dyn_into::<MouseEvent>().unwrap(); | ||
173 | + match e.alt_key() { | ||
174 | + true => Some(AppLogic::Store), | ||
175 | + false => None | ||
176 | + } | ||
177 | + } else { | ||
178 | + None | ||
179 | + } | ||
180 | + }); | ||
181 | + let toggle_filter = tx_logic | ||
182 | + . sink() | ||
183 | + . contra_map(|_| AppLogic::Toggle); | ||
184 | + | ||
108 | builder! { | 185 | builder! { |
109 | <div class="input" | 186 | <div class="input" |
110 | style:width="33%" | 187 | style:width="33%" |
111 | - on:input=tx_logic.sink().contra_map(|_| AppLogic::Update) | 188 | + on:input=input_filter |
112 | capture:view=tx_dom.sink()> | 189 | capture:view=tx_dom.sink()> |
113 | <div contenteditable="true" | 190 | <div contenteditable="true" |
114 | style:cursor="text" | 191 | style:cursor="text" |
115 | style:display=("none", rx_view)> | 192 | style:display=("none", rx_view)> |
116 | - <pre>{init_data}</pre> | 193 | + <pre on:click=store_filter>{md.content.clone()}</pre> |
117 | </div> | 194 | </div> |
118 | <div> | 195 | <div> |
119 | - <button on:click=tx_logic . sink() | ||
120 | - . contra_map(|_| AppLogic::Toggle)> | 196 | + <button on:click=toggle_filter> |
121 | <svg version="1.1" id="Capa_1" xmlns=ns | 197 | <svg version="1.1" id="Capa_1" xmlns=ns |
122 | x="0px" y="0px" viewBox="0 0 220.001 220.001" | 198 | x="0px" y="0px" viewBox="0 0 220.001 220.001" |
123 | style:width="1.5em" style:height="1.5em"> | 199 | style:width="1.5em" style:height="1.5em"> |
@@ -131,6 +207,8 @@ fn editor_view( tx_logic: broadcast::Sender<AppLogic> | @@ -131,6 +207,8 @@ fn editor_view( tx_logic: broadcast::Sender<AppLogic> | ||
131 | </svg> | 207 | </svg> |
132 | </button> | 208 | </button> |
133 | <div></div> | 209 | <div></div> |
210 | + <div></div> | ||
211 | + <div></div> | ||
134 | </div> | 212 | </div> |
135 | </div> | 213 | </div> |
136 | } | 214 | } |
@@ -141,13 +219,13 @@ pub async fn main() -> Result<(), JsValue> { | @@ -141,13 +219,13 @@ pub async fn main() -> Result<(), JsValue> { | ||
141 | panic::set_hook(Box::new(console_error_panic_hook::hook)); | 219 | panic::set_hook(Box::new(console_error_panic_hook::hook)); |
142 | console_log::init_with_level(Level::Trace).unwrap(); | 220 | console_log::init_with_level(Level::Trace).unwrap(); |
143 | 221 | ||
144 | - let data = md_from_db().await; | 222 | + let md = md_from_db().await; |
145 | 223 | ||
146 | let (tx_dom, rx_dom) = broadcast::bounded(1); | 224 | let (tx_dom, rx_dom) = broadcast::bounded(1); |
147 | let (tx_logic, rx_logic) = broadcast::bounded(1); | 225 | let (tx_logic, rx_logic) = broadcast::bounded(1); |
148 | let (tx_view, rx_view) = broadcast::bounded(1); | 226 | let (tx_view, rx_view) = broadcast::bounded(1); |
149 | - let comp = Component::from( editor_view(tx_logic, rx_view, tx_dom, data.as_str()) ) | ||
150 | - . with_logic( editor_logic(rx_logic, tx_view, rx_dom) ); | 227 | + let comp = Component::from( editor_view(tx_logic, rx_view, tx_dom, &md) ) |
228 | + . with_logic( editor_logic(rx_logic, tx_view, rx_dom, md) ); | ||
151 | 229 | ||
152 | let page = Component::from(builder! {{comp}}); | 230 | let page = Component::from(builder! {{comp}}); |
153 | page.build()?.run() | 231 | page.build()?.run() |
Please
register
or
login
to post a comment