Three small things that together make a board work: a link on every
listed record to a page about it (ResourceSpec.link, {id} and {field}
in the address); a state mail whose `to` is a field of the record this
one belongs to ({notice.email}), with that record's fields in the
text, and a lint that the form carries the record field it reaches
through; and value: "{user.email}" filled from the session and hidden,
so someone the site knows is not asked who they are.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GT37Z1Xtfd9pUuQtMTg6Yt
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
470338b969
commit
fc998c8592
+19
-3
@@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::answers::{Answer, SelfTransitionAnswer, TransitionAnswers, TransitionItem};
|
||||
use crate::auth::{current_user, User};
|
||||
use crate::content::{is_qualified, render_inline_markdown, Alternative, Question, Responsible, SiteConfig, Transition};
|
||||
use crate::content::{is_qualified, render_inline_markdown, Alternative, Question, Responsible, RowLink, SiteConfig, Transition};
|
||||
use crate::i18n::t;
|
||||
use crate::resource::{get_requirement_binding, get_requirement_options, get_resource};
|
||||
|
||||
@@ -1301,6 +1301,7 @@ fn AlternativeCard(
|
||||
alternative=alt_name.clone()
|
||||
feature_name=feature_name.clone()
|
||||
transitions=spec.transitions.clone()
|
||||
link=spec.link.clone()
|
||||
empty=spec.empty.clone()
|
||||
pending_transitions=pending_transitions
|
||||
transition_batch=transition_batch
|
||||
@@ -1753,6 +1754,7 @@ fn ResourceFeature(
|
||||
alternative: String,
|
||||
feature_name: String,
|
||||
transitions: Vec<Transition>,
|
||||
link: Option<RowLink>,
|
||||
empty: Option<String>,
|
||||
pending_transitions: RwSignal<std::collections::HashMap<(String, String), String>>,
|
||||
transition_batch: ServerAction<TransitionAnswers>,
|
||||
@@ -1787,6 +1789,7 @@ fn ResourceFeature(
|
||||
{move || {
|
||||
let feature_name = feature_name.clone();
|
||||
let transitions = transitions.clone();
|
||||
let link = link.clone();
|
||||
let empty = empty.clone().unwrap_or_else(|| t(&lang().get(), "nothing_here_yet").to_string());
|
||||
data.get()
|
||||
.map(|res| match res {
|
||||
@@ -1795,6 +1798,7 @@ fn ResourceFeature(
|
||||
<ResourceValue
|
||||
feature_name=feature_name
|
||||
transitions=transitions
|
||||
link=link
|
||||
empty=empty
|
||||
value=value
|
||||
pending_transitions=pending_transitions
|
||||
@@ -1822,6 +1826,7 @@ fn ResourceFeature(
|
||||
fn ResourceValue(
|
||||
feature_name: String,
|
||||
transitions: Vec<Transition>,
|
||||
link: Option<RowLink>,
|
||||
empty: String,
|
||||
value: serde_json::Value,
|
||||
pending_transitions: RwSignal<std::collections::HashMap<(String, String), String>>,
|
||||
@@ -1853,11 +1858,13 @@ fn ResourceValue(
|
||||
children={
|
||||
let feature_name = feature_name.clone();
|
||||
let transitions = transitions.clone();
|
||||
let link = link.clone();
|
||||
move |answer: Answer| {
|
||||
view! {
|
||||
<AnswerRow
|
||||
feature_name=feature_name.clone()
|
||||
transitions=transitions.clone()
|
||||
link=link.clone()
|
||||
answer=answer
|
||||
pending_transitions=pending_transitions
|
||||
transition_batch=transition_batch
|
||||
@@ -2116,6 +2123,7 @@ fn SelectField(
|
||||
fn AnswerRow(
|
||||
feature_name: String,
|
||||
transitions: Vec<Transition>,
|
||||
link: Option<RowLink>,
|
||||
answer: Answer,
|
||||
pending_transitions: RwSignal<std::collections::HashMap<(String, String), String>>,
|
||||
transition_batch: ServerAction<TransitionAnswers>,
|
||||
@@ -2148,6 +2156,7 @@ fn AnswerRow(
|
||||
let state = answer.state.clone();
|
||||
let item_id = answer.id.clone();
|
||||
let key = (feature_name.clone(), item_id.clone());
|
||||
let link = link.map(|l| (l.href(&answer.id, &answer.responses), l.label));
|
||||
|
||||
view! {
|
||||
<article class="answer-row" class:decided=move || decided>
|
||||
@@ -2195,6 +2204,9 @@ fn AnswerRow(
|
||||
<span class="answer-state">{state}</span>
|
||||
<span class="answer-time">{format_ms(submitted_ms)}</span>
|
||||
</div>
|
||||
// The page about this record, when the list offers one:
|
||||
// where a reader answers a notice.
|
||||
{link.map(|(href, label)| view! { <a class="answer-link" href=href>{label}</a> })}
|
||||
// What the person has written back since, oldest first: the
|
||||
// conversation beside the case, which a desk could not see
|
||||
// before. A note moves nothing, so there is no button here.
|
||||
@@ -2338,13 +2350,17 @@ pub async fn get_question(
|
||||
path: String,
|
||||
chain: Option<String>,
|
||||
) -> Result<Option<Page>, ServerFnError> {
|
||||
use crate::content::{path_matches, resolve_question};
|
||||
use crate::auth::{User, SESSION_USER_KEY};
|
||||
use crate::content::{fill_from_user, path_matches, resolve_question};
|
||||
use crate::server::AppState;
|
||||
let state = expect_context::<AppState>();
|
||||
let questions = state.questions.load();
|
||||
let Some(question) = resolve_question(&questions, &path) else {
|
||||
let Some(mut question) = resolve_question(&questions, &path) else {
|
||||
return Ok(None);
|
||||
};
|
||||
let session: tower_sessions::Session = leptos_axum::extract().await?;
|
||||
let user = session.get::<User>(SESSION_USER_KEY).await.map_err(|e| ServerFnError::new(e.to_string()))?;
|
||||
fill_from_user(&mut question, user.as_ref());
|
||||
let mut chain_gate = None;
|
||||
if let Some(target) = &question.requires_chain {
|
||||
// The claimed lineage must verifiably end at the required
|
||||
|
||||
Reference in New Issue
Block a user