Add sidenotes
This commit is contained in:
parent
1171675ee3
commit
b84e09ed28
@ -22,3 +22,6 @@ pub fn is_checked<'s>((id, slice): &(i32, &[String])) -> ::askama::Result<bool>
|
||||
.filter_map(|s| s.parse::<i32>().ok())
|
||||
.any(|s| s == *id))
|
||||
}
|
||||
pub fn some_or_default<'s>(s: &Option<String>) -> ::askama::Result<String> {
|
||||
Ok(s.as_ref().cloned().unwrap_or_default())
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
#![feature(iterator_try_collect, duration_constructors)]
|
||||
#![feature(iterator_try_collect, duration_constructors, string_remove_matches)]
|
||||
|
||||
use actix::SyncArbiter;
|
||||
use actix_files::Files;
|
||||
@ -7,7 +7,7 @@ use actix_session::{storage::RedisSessionStore, SessionMiddleware};
|
||||
use actix_web::cookie::Key;
|
||||
use actix_web::{error, HttpResponse};
|
||||
use actix_web::{web::Data, App, HttpServer};
|
||||
use actix_web_prom::{PrometheusMetrics, PrometheusMetricsBuilder};
|
||||
use actix_web_prom::PrometheusMetricsBuilder;
|
||||
use sea_orm::Database;
|
||||
use serde_qs as qs;
|
||||
use serde_qs::actix::QsQueryConfig;
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::actors::search::{Find, Search};
|
||||
use crate::types::*;
|
||||
use crate::utils::ParsedIngredient;
|
||||
use crate::{entities, entities::prelude::*, filters};
|
||||
use actix_files::Files;
|
||||
use actix_identity::Identity;
|
||||
@ -300,7 +301,7 @@ struct ImageUpload {
|
||||
#[template(path = "recipies/create_form.jinja", ext = "html")]
|
||||
struct RecipeForm {
|
||||
title: String,
|
||||
summary: String,
|
||||
summary: Option<String>,
|
||||
ingredients: String,
|
||||
steps: String,
|
||||
tags: String,
|
||||
@ -324,7 +325,7 @@ struct RecipeForm {
|
||||
struct EditRecipeForm {
|
||||
id: i32,
|
||||
title: String,
|
||||
summary: String,
|
||||
summary: Option<String>,
|
||||
ingredients: String,
|
||||
steps: String,
|
||||
tags: String,
|
||||
@ -648,6 +649,7 @@ async fn show(
|
||||
name: ingredients.get(&ri.ingredient_id)?.to_owned(),
|
||||
qty: ri.qty,
|
||||
unit: ri.unit,
|
||||
sidenote: ri.sidenote,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@ -674,7 +676,7 @@ async fn recipe_form(admin: Identity, db: Data<DatabaseConnection>) -> RecipeFor
|
||||
let ingredients = Ingredients::find().all(&**db).await.unwrap_or_default();
|
||||
RecipeForm {
|
||||
title: "".into(),
|
||||
summary: "".into(),
|
||||
summary: None,
|
||||
ingredients: "".into(),
|
||||
steps: "".into(),
|
||||
tags: "".into(),
|
||||
@ -741,7 +743,8 @@ async fn save_recipe(form: RecipeForm, t: &mut DatabaseTransaction) -> Result<i3
|
||||
use sea_orm::ActiveValue::*;
|
||||
|
||||
let mut summary = String::new();
|
||||
let parser = pulldown_cmark::Parser::new(&form.summary);
|
||||
let raw_summary = form.summary.unwrap_or_default();
|
||||
let parser = pulldown_cmark::Parser::new(&raw_summary);
|
||||
pulldown_cmark::html::push_html(&mut summary, parser);
|
||||
|
||||
let time = match form.time {
|
||||
@ -793,7 +796,7 @@ async fn create_ingredients(
|
||||
entities::ingredients::Column::Name.is_in(
|
||||
ingredients
|
||||
.iter()
|
||||
.map(|(name, ..)| name.to_string())
|
||||
.map(|ParsedIngredient { name, .. }| name.to_string())
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
)
|
||||
@ -808,11 +811,13 @@ async fn create_ingredients(
|
||||
.collect::<BTreeSet<_>>();
|
||||
let missing = ingredients
|
||||
.iter()
|
||||
.filter(|(name, ..)| !known.contains(name))
|
||||
.map(|(name, ..)| entities::ingredients::ActiveModel {
|
||||
.filter(|ParsedIngredient { name, .. }| !known.contains(name))
|
||||
.map(
|
||||
|ParsedIngredient { name, .. }| entities::ingredients::ActiveModel {
|
||||
name: Set(name.to_owned()),
|
||||
..Default::default()
|
||||
})
|
||||
},
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
let mut v = Vec::with_capacity(missing.len());
|
||||
for missing in missing {
|
||||
@ -835,15 +840,23 @@ async fn create_ingredients(
|
||||
RecipeIngredients::insert_many(
|
||||
ingredients
|
||||
.into_iter()
|
||||
.filter_map(|(name, unit, qty)| {
|
||||
.filter_map(
|
||||
|ParsedIngredient {
|
||||
name,
|
||||
unit,
|
||||
qty,
|
||||
sidenote,
|
||||
}| {
|
||||
Some(entities::recipe_ingredients::ActiveModel {
|
||||
ingredient_id: Set(*map.get(&name)?),
|
||||
qty: Set(qty),
|
||||
unit: Set(unit.unwrap_or_default()),
|
||||
recipe_id: Set(recipe_id),
|
||||
sidenote: Set(sidenote),
|
||||
..Default::default()
|
||||
})
|
||||
})
|
||||
},
|
||||
)
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.exec(&mut *t)
|
||||
@ -1082,7 +1095,7 @@ async fn edit_recipe(
|
||||
Ok(EditRecipeForm {
|
||||
id,
|
||||
title: recipe.title,
|
||||
summary: recipe.summary.unwrap_or_default(),
|
||||
summary: recipe.summary,
|
||||
ingredients: recipe_ingredients
|
||||
.into_iter()
|
||||
.filter_map(|ingredient| {
|
||||
@ -1168,7 +1181,8 @@ async fn save_recipe_changes(
|
||||
use sea_orm::ActiveValue::*;
|
||||
|
||||
let mut summary = String::new();
|
||||
let parser = pulldown_cmark::Parser::new(&form.summary);
|
||||
let raw_summary = form.summary.unwrap_or_default();
|
||||
let parser = pulldown_cmark::Parser::new(&raw_summary);
|
||||
pulldown_cmark::html::push_html(&mut summary, parser);
|
||||
|
||||
let time = match form.time {
|
||||
|
@ -11,6 +11,7 @@ pub struct Ingredient {
|
||||
pub qty: String,
|
||||
pub name: String,
|
||||
pub unit: String,
|
||||
pub sidenote: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
|
@ -32,12 +32,32 @@ pub fn parse_steps(s: &str) -> Result<Vec<(String, Option<String>)>, Error> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_ingredients(s: &str) -> Result<Vec<(String, Option<String>, String)>, Error> {
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct ParsedIngredient {
|
||||
pub qty: String,
|
||||
pub name: String,
|
||||
pub unit: Option<String>,
|
||||
pub sidenote: Option<String>,
|
||||
}
|
||||
|
||||
pub fn parse_ingredients(s: &str) -> Result<Vec<ParsedIngredient>, Error> {
|
||||
s.lines()
|
||||
.map(|s| s.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| {
|
||||
let mut pieces = s
|
||||
let sidenote = s
|
||||
.chars()
|
||||
.skip_while(|c| *c != '(')
|
||||
.skip_while(|c| *c == '(')
|
||||
.take_while(|c| *c != ')')
|
||||
.collect::<String>()
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
let d = s.chars().take_while(|c| *c != '(');
|
||||
let pieces = d.collect::<String>();
|
||||
let mut pieces = pieces
|
||||
.trim()
|
||||
.split_whitespace()
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect::<Vec<_>>();
|
||||
@ -46,12 +66,22 @@ pub fn parse_ingredients(s: &str) -> Result<Vec<(String, Option<String>, String)
|
||||
// no unit
|
||||
2 => {
|
||||
let qty = pieces.remove(0);
|
||||
Ok((pieces.join(" "), None, qty.to_string()))
|
||||
Ok(ParsedIngredient {
|
||||
name: pieces.join(" "),
|
||||
unit: None,
|
||||
qty: qty.to_string(),
|
||||
sidenote: Some(sidenote).filter(|s| !s.is_empty()),
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
let qty = pieces.remove(0);
|
||||
let unit = pieces.remove(0);
|
||||
Ok((pieces.join(" "), Some(unit.to_string()), qty.to_string()))
|
||||
Ok(ParsedIngredient {
|
||||
name: pieces.join(" "),
|
||||
unit: Some(unit.to_string()),
|
||||
qty: qty.to_string(),
|
||||
sidenote: Some(sidenote).filter(|s| !s.is_empty()),
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -75,3 +105,27 @@ pub async fn refresh_records(db: Data<DatabaseConnection>, search: Data<Search>)
|
||||
.collect::<Vec<_>>();
|
||||
let _ = search.send(Refresh { records }).await;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn ingredient_without_unit() {}
|
||||
|
||||
#[test]
|
||||
fn ingredient_with_unit() {}
|
||||
|
||||
#[test]
|
||||
fn ingredient_with_summary() {
|
||||
let s = "1 szt pomarańcza (może być czerwona)";
|
||||
let ingredient = parse_ingredients(s).unwrap_or_default();
|
||||
let expected = vec![ParsedIngredient {
|
||||
qty: "1".into(),
|
||||
unit: Some("szt".into()),
|
||||
name: "pomarańcza".into(),
|
||||
sidenote: Some("może być czerwona".into()),
|
||||
}];
|
||||
assert_eq!(ingredient, expected);
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@
|
||||
<label for="summary" class="flex gap-4">
|
||||
<span class="w-24 shrink-0">Summary:</span>
|
||||
<div class="flex gap-2 w-full">
|
||||
<textarea id="summary" name="summary" class="textarea-with-view" required>{{summary}}</textarea>
|
||||
<textarea id="summary" name="summary" class="textarea-with-view" required>{{summary|some_or_default}}</textarea>
|
||||
<div class="content w-1/2"></div>
|
||||
</div>
|
||||
</label>
|
||||
|
@ -44,6 +44,13 @@
|
||||
<span class="font-bold">
|
||||
{{ ingredient.name }}
|
||||
</span>
|
||||
{% match ingredient.sidenote %}
|
||||
{% when Some with (sidenote) %}
|
||||
<span class="mr-1">
|
||||
({{ sidenote }})
|
||||
</span>
|
||||
{% when None %}
|
||||
{% endmatch %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
@ -34,7 +34,7 @@
|
||||
<label for="summary" class="flex gap-4">
|
||||
<span class="w-24 shrink-0">Summary:</span>
|
||||
<div class="flex gap-2 w-full">
|
||||
<textarea id="summary" name="summary" class="textarea-with-view" required>{{summary}}</textarea>
|
||||
<textarea id="summary" name="summary" class="textarea-with-view" required>{{summary|some_or_default}}</textarea>
|
||||
<div class="content w-1/2"></div>
|
||||
</div>
|
||||
</label>
|
||||
|
@ -1,4 +1,4 @@
|
||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.11
|
||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -1,4 +1,4 @@
|
||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.11
|
||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0
|
||||
|
||||
pub mod prelude;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.11
|
||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0
|
||||
|
||||
pub use super::ingredients::Entity as Ingredients;
|
||||
pub use super::recipe_ingredients::Entity as RecipeIngredients;
|
||||
|
@ -1,4 +1,4 @@
|
||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.11
|
||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -12,6 +12,7 @@ pub struct Model {
|
||||
pub qty: String,
|
||||
pub unit: String,
|
||||
pub recipe_id: i32,
|
||||
pub sidenote: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
|
@ -1,4 +1,4 @@
|
||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.11
|
||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -1,4 +1,4 @@
|
||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.11
|
||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -1,4 +1,4 @@
|
||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.11
|
||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -1,4 +1,4 @@
|
||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.11
|
||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -2,6 +2,7 @@ pub use sea_orm_migration::prelude::*;
|
||||
|
||||
mod m20220101_000001_create_table;
|
||||
mod m20220101_000002_change_quantity_type;
|
||||
mod m20220101_000003_add_sidenote_to_recipe_ingredient;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
@ -11,6 +12,7 @@ impl MigratorTrait for Migrator {
|
||||
vec![
|
||||
Box::new(m20220101_000001_create_table::Migration),
|
||||
Box::new(m20220101_000002_change_quantity_type::Migration),
|
||||
Box::new(m20220101_000003_add_sidenote_to_recipe_ingredient::Migration),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -33,9 +33,4 @@ impl MigrationTrait for Migration {
|
||||
#[derive(DeriveIden)]
|
||||
enum RecipeIngredient {
|
||||
RecipeIngredients,
|
||||
Id,
|
||||
IngredientId,
|
||||
Qty,
|
||||
Unit,
|
||||
RecipeId,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user