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 | 6 | mod routes; |
| 7 | 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 | 10 | use crate::routes::other::*; |
| 12 | 11 | use crate::routes::user::*; |
| 13 | 12 | |
| 14 | 13 | use actix_web::{guard, web, App, HttpResponse, HttpServer}; |
| 15 | 14 | use diesel::r2d2::{self, ConnectionManager}; |
| 16 | 15 | use diesel::SqliteConnection; |
| 17 | -use diffy::create_patch; | |
| 18 | -use flate2::Compression; | |
| 19 | -use flate2::write::{DeflateEncoder, DeflateDecoder}; | |
| 20 | 16 | use listenfd::ListenFd; |
| 17 | +use routes::markdown::get_markdown; | |
| 21 | 18 | |
| 22 | 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 | 21 | #[actix_rt::main] |
| 54 | 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 | 23 | let mut listenfd = ListenFd::from_env(); |
| 75 | 24 | |
| 76 | 25 | dotenv::dotenv().ok(); |
| ... | ... | @@ -87,6 +36,10 @@ async fn main() -> std::io::Result<()> { |
| 87 | 36 | . service( web::resource("/markdowns") |
| 88 | 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 | 43 | . service( web::resource("/users") |
| 91 | 44 | . route(web::get().to(get_users)) |
| 92 | 45 | . route(web::put().to(create_user)) | ... | ... |
| ... | ... | @@ -8,6 +8,10 @@ use diesel::{ |
| 8 | 8 | RunQueryDsl |
| 9 | 9 | }; |
| 10 | 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 | 17 | #[derive(Debug, Serialize, Deserialize, Queryable, Identifiable)] |
| ... | ... | @@ -20,6 +24,16 @@ pub struct Markdown { |
| 20 | 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 | 37 | #[derive(Debug, Insertable)] |
| 24 | 38 | #[table_name = "markdowns"] |
| 25 | 39 | pub struct MarkdownNew<'a> { |
| ... | ... | @@ -30,12 +44,23 @@ pub struct MarkdownNew<'a> { |
| 30 | 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 | 56 | #[derive(Debug, Serialize, Deserialize, AsChangeset)] |
| 34 | 57 | #[table_name="markdowns"] |
| 35 | 58 | pub struct MarkdownJson { |
| 36 | 59 | pub name: String, |
| 37 | 60 | pub content: String, |
| 38 | 61 | pub number_of_versions: i32, |
| 62 | + pub date_created: String, | |
| 63 | + pub date_updated: String, | |
| 39 | 64 | } |
| 40 | 65 | |
| 41 | 66 | pub(crate) enum Action { |
| ... | ... | @@ -82,11 +107,34 @@ pub(crate) fn get_markdowns(pool: Arc<Pool>) -> Result<Vec<Markdown>, Error> |
| 82 | 107 | } |
| 83 | 108 | |
| 84 | 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 | 113 | use crate::schema::markdowns::dsl::*; |
| 114 | + use crate::schema::markdown_diffs::dsl::*; | |
| 88 | 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 | 140 | 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 | 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 | 152 | use crate::schema::markdowns::dsl::*; |
| 153 | + use crate::schema::markdown_diffs::dsl::*; | |
| 105 | 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 | 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 | 193 | markdown.name = item.name; |
| 111 | 194 | markdown.content = item.content; |
| 112 | 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 | 198 | Ok(markdown) |
| 116 | 199 | } | ... | ... |
| ... | ... | @@ -3,12 +3,50 @@ use crate::Pool; |
| 3 | 3 | |
| 4 | 4 | use actix_web::{Error, HttpResponse, web}; |
| 5 | 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 | 13 | pub async fn get_markdowns(pool: web::Data<Pool>) |
| 8 | 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 | 49 | . await |
| 12 | - . map(|markdowns| HttpResponse::Ok().json(markdowns)) | |
| 50 | + . map(|markdown| HttpResponse::Ok().json(markdown)) | |
| 13 | 51 | . map_err(|_| HttpResponse::InternalServerError())?) |
| 14 | 52 | } | ... | ... |
| ... | ... | @@ -12,6 +12,7 @@ crate-type = ["cdylib", "rlib"] |
| 12 | 12 | default = ["console_error_panic_hook"] |
| 13 | 13 | |
| 14 | 14 | [dependencies] |
| 15 | +katex = { version = "0.4", default-features = false, features = ["wasm-js"] } | |
| 15 | 16 | pulldown-cmark = "0.9" |
| 16 | 17 | console_log = "^0.1" |
| 17 | 18 | log = "^0.4" |
| ... | ... | @@ -39,6 +40,7 @@ features = [ |
| 39 | 40 | "Headers", |
| 40 | 41 | "HtmlElement", |
| 41 | 42 | "HtmlInputElement", |
| 43 | + "MouseEvent", | |
| 42 | 44 | "Node", |
| 43 | 45 | "Request", |
| 44 | 46 | "RequestInit", | ... | ... |
| ... | ... | @@ -3,7 +3,7 @@ mod data; |
| 3 | 3 | use js_sys::JsString; |
| 4 | 4 | use log::Level; |
| 5 | 5 | use mogwai::prelude::*; |
| 6 | -use web_sys::{RequestInit, RequestMode, Request, Response}; | |
| 6 | +use web_sys::{RequestInit, RequestMode, Request, Response, MouseEvent}; | |
| 7 | 7 | use serde::{Deserialize, Serialize}; |
| 8 | 8 | use std::panic; |
| 9 | 9 | use wasm_bindgen::prelude::*; |
| ... | ... | @@ -12,6 +12,7 @@ use wasm_bindgen::prelude::*; |
| 12 | 12 | enum AppLogic { |
| 13 | 13 | Update, |
| 14 | 14 | Toggle, |
| 15 | + Store, | |
| 15 | 16 | } |
| 16 | 17 | |
| 17 | 18 | fn md_to_html(source: &str) -> String { |
| ... | ... | @@ -24,21 +25,23 @@ fn md_to_html(source: &str) -> String { |
| 24 | 25 | html_output |
| 25 | 26 | } |
| 26 | 27 | |
| 27 | -#[derive(Debug, Serialize, Deserialize)] | |
| 28 | +#[derive(Clone, Debug, Serialize, Deserialize)] | |
| 28 | 29 | pub struct MarkdownJson { |
| 29 | 30 | pub name: String, |
| 30 | 31 | pub content: String, |
| 31 | 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 | 38 | let window = web_sys::window().unwrap(); |
| 38 | 39 | let mut opts = RequestInit::new(); |
| 39 | 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 | 45 | request.headers().set("Accept", "application/json").unwrap(); |
| 43 | 46 | |
| 44 | 47 | let response = JsFuture::from(window.fetch_with_request(&request)) |
| ... | ... | @@ -46,15 +49,38 @@ async fn md_from_db() -> String { |
| 46 | 49 | . dyn_into::<Response>().unwrap(); |
| 47 | 50 | let data = String::from( JsFuture::from(response.text().unwrap()) |
| 48 | 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 | 80 | async fn editor_logic( mut rx_logic: broadcast::Receiver<AppLogic> |
| 56 | 81 | , tx_view: broadcast::Sender<String> |
| 57 | - , mut rx_dom: broadcast::Receiver<Dom> ) { | |
| 82 | + , mut rx_dom: broadcast::Receiver<Dom> | |
| 83 | + , md :MarkdownJson ) { | |
| 58 | 84 | let dom = rx_dom.next().await.unwrap(); |
| 59 | 85 | let mut show_edit = false; |
| 60 | 86 | |
| ... | ... | @@ -82,8 +108,39 @@ async fn editor_logic( mut rx_logic: broadcast::Receiver<AppLogic> |
| 82 | 108 | |
| 83 | 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 | 137 | while let Some(msg) = rx_logic.next().await { |
| 86 | 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 | 144 | AppLogic::Update => update(&dom), |
| 88 | 145 | AppLogic::Toggle => { |
| 89 | 146 | show_edit = ! show_edit; |
| ... | ... | @@ -101,23 +158,42 @@ async fn editor_logic( mut rx_logic: broadcast::Receiver<AppLogic> |
| 101 | 158 | fn editor_view( tx_logic: broadcast::Sender<AppLogic> |
| 102 | 159 | , rx_view: broadcast::Receiver<String> |
| 103 | 160 | , tx_dom: broadcast::Sender<Dom> |
| 104 | - , init_data: &str | |
| 161 | + , md: &MarkdownJson | |
| 105 | 162 | ) -> ViewBuilder<Dom> { |
| 106 | 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 | 185 | builder! { |
| 109 | 186 | <div class="input" |
| 110 | 187 | style:width="33%" |
| 111 | - on:input=tx_logic.sink().contra_map(|_| AppLogic::Update) | |
| 188 | + on:input=input_filter | |
| 112 | 189 | capture:view=tx_dom.sink()> |
| 113 | 190 | <div contenteditable="true" |
| 114 | 191 | style:cursor="text" |
| 115 | 192 | style:display=("none", rx_view)> |
| 116 | - <pre>{init_data}</pre> | |
| 193 | + <pre on:click=store_filter>{md.content.clone()}</pre> | |
| 117 | 194 | </div> |
| 118 | 195 | <div> |
| 119 | - <button on:click=tx_logic . sink() | |
| 120 | - . contra_map(|_| AppLogic::Toggle)> | |
| 196 | + <button on:click=toggle_filter> | |
| 121 | 197 | <svg version="1.1" id="Capa_1" xmlns=ns |
| 122 | 198 | x="0px" y="0px" viewBox="0 0 220.001 220.001" |
| 123 | 199 | style:width="1.5em" style:height="1.5em"> |
| ... | ... | @@ -131,6 +207,8 @@ fn editor_view( tx_logic: broadcast::Sender<AppLogic> |
| 131 | 207 | </svg> |
| 132 | 208 | </button> |
| 133 | 209 | <div></div> |
| 210 | + <div></div> | |
| 211 | + <div></div> | |
| 134 | 212 | </div> |
| 135 | 213 | </div> |
| 136 | 214 | } |
| ... | ... | @@ -141,13 +219,13 @@ pub async fn main() -> Result<(), JsValue> { |
| 141 | 219 | panic::set_hook(Box::new(console_error_panic_hook::hook)); |
| 142 | 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 | 224 | let (tx_dom, rx_dom) = broadcast::bounded(1); |
| 147 | 225 | let (tx_logic, rx_logic) = broadcast::bounded(1); |
| 148 | 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 | 230 | let page = Component::from(builder! {{comp}}); |
| 153 | 231 | page.build()?.run() | ... | ... |
Please
register
or
login
to post a comment