mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-30 21:47:00 -06:00
[ZZ1] insert.dateTime in the Handout/Notes Master view fills the master's date placeholder
PowerPoint: Header and Footer > Notes and Handouts > Date and time puts a date field in the date placeholder of the notes and handout masters, and every handout and notes page shows it. Before, with no text caret the command wrote the current slide's date placeholder (design .headerFooter) even in those views, so the handout PDF showed nothing. Now it writes the selected date placeholder, else the master's own, as a field (update) or plain text, answers where: master, and refuses (changing nothing) when the master has no date placeholder. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
55e5ee56b4
commit
6a1b8f1e39
@@ -98,7 +98,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Date & Time",
|
||||
["Insert", "Text"],
|
||||
None,
|
||||
"{format?: datetime1..datetime13, update?: bool, language?: en-US|en-GB|fr-FR|ja-JP|sv-SE} — no keys opens the dialog",
|
||||
"{format?: datetime1..datetime13, update?: bool, language?: en-US|en-GB|fr-FR|ja-JP|sv-SE} — no keys opens the dialog; in the Handout or Notes Master view it fills the selected date placeholder (else the master's own) and answers where: \"master\"",
|
||||
has_slide,
|
||||
date_time
|
||||
),
|
||||
@@ -473,6 +473,70 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// ZZ1: in the handout and notes master views the date goes into the master's date placeholder
|
||||
/// (not the current slide), so the exported handout and notes pages show it.
|
||||
#[test]
|
||||
fn on_a_page_master_the_date_fills_its_date_placeholder() {
|
||||
for view in ["view.handoutMaster", "view.notesMaster"] {
|
||||
let mut s = session();
|
||||
s.execute(view, &json!({})).unwrap();
|
||||
let (id, master) = {
|
||||
let st = s.doc().unwrap();
|
||||
let m = crate::page_master(&st.doc, st.selection.target).unwrap().clone();
|
||||
(m.shapes.iter().find(|x| x.ph_type() == Some(deckcraft_model::PhType::Date)).unwrap().id.0, m)
|
||||
};
|
||||
assert!(master.shapes.iter().find(|x| x.id.0 == id).and_then(|x| x.text.as_ref()).is_none_or(|t| t.text().is_empty()));
|
||||
s.execute("edit.select", &json!({"ids": [id]})).unwrap();
|
||||
let v = s.execute("insert.dateTime", &json!({"format": "datetime10", "update": true})).unwrap();
|
||||
assert_eq!(v["where"], "master", "{view}: {v}");
|
||||
assert!(date_run(&s).is_none(), "the slide keeps no date");
|
||||
let st = s.doc().unwrap();
|
||||
let m = crate::page_master(&st.doc, st.selection.target).unwrap();
|
||||
let run = m
|
||||
.shapes
|
||||
.iter()
|
||||
.find(|x| x.id.0 == id)
|
||||
.and_then(|x| x.text.as_ref())
|
||||
.and_then(|t| t.paragraphs.first())
|
||||
.and_then(|p| p.runs.first())
|
||||
.cloned()
|
||||
.unwrap();
|
||||
assert!(matches!(run.kind, RunKind::Field { ref field } if field == "datetime10"), "{view}: {run:?}");
|
||||
let placed = deckcraft_render::place_master_texts(&st.doc, m, 1);
|
||||
assert!(placed.iter().any(|p| p.body.text() == run.text && run.text.contains(':')), "{view}: the page shows the date");
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
let st = s.doc().unwrap();
|
||||
let m = crate::page_master(&st.doc, st.selection.target).unwrap();
|
||||
assert!(m.shapes.iter().find(|x| x.id.0 == id).and_then(|x| x.text.as_ref()).is_none_or(|t| t.text().is_empty()), "one undo");
|
||||
// A fixed date in a placeholder whose run is en-GB reads day first, and the answer
|
||||
// reports the text that was written, not the en-US sample.
|
||||
s.execute("insert.dateTime", &json!({"format": "datetime1", "update": false, "language": "en-GB"})).unwrap();
|
||||
let v = s.execute("insert.dateTime", &json!({"format": "datetime1", "update": false})).unwrap();
|
||||
let st = s.doc().unwrap();
|
||||
let m = crate::page_master(&st.doc, st.selection.target).unwrap();
|
||||
let run = m
|
||||
.shapes
|
||||
.iter()
|
||||
.find(|x| x.id.0 == id)
|
||||
.and_then(|x| x.text.as_ref())
|
||||
.and_then(|t| t.paragraphs.first())
|
||||
.and_then(|p| p.runs.first())
|
||||
.cloned()
|
||||
.unwrap();
|
||||
assert_eq!(run.props.lang.as_deref(), Some("en-GB"), "{view}: {run:?}");
|
||||
assert_eq!(v["text"].as_str(), Some(run.text.as_str()), "{view}: the answer reports the written text: {v}");
|
||||
let now = deckcraft_text::datetime::DateTime::now_local().unwrap();
|
||||
let gb = deckcraft_text::datetime::field_text("datetime1", Some("en-GB"), now);
|
||||
assert_eq!(Some(run.text.as_str()), gb.as_deref(), "{view}: the date in en-GB order (dd/mm/yyyy)");
|
||||
// No date placeholder: refused, nothing changed.
|
||||
let key = if view == "view.handoutMaster" { "handout.placeholders" } else { "notes.placeholders" };
|
||||
s.execute(key, &json!({"date": false})).unwrap();
|
||||
let rev = s.doc().unwrap().revision;
|
||||
assert!(s.execute("insert.dateTime", &json!({"format": "datetime1"})).is_err());
|
||||
assert_eq!(s.doc().unwrap().revision, rev);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn without_a_caret_the_date_lands_on_this_slide_only() {
|
||||
let mut s = session();
|
||||
|
||||
@@ -149,6 +149,11 @@ fn apply_date(s: &mut Session, o: &DateOpts) -> Result<Value> {
|
||||
}
|
||||
return Ok(json!({"where": "caret", "format": o.format, "update": o.update, "text": o.sample}));
|
||||
}
|
||||
// Handout or Notes Master view: the master's date placeholder (the selected one, else the
|
||||
// master's own), which every handout or notes page then shows.
|
||||
if s.doc()?.selection.target.is_page_master() {
|
||||
return write_master_date(s, o);
|
||||
}
|
||||
// Not editing: the slide's date placeholder (this slide only). A layout with no date
|
||||
// placeholder gets a text box instead, so the chosen format is still inserted.
|
||||
s.execute("design.headerFooter", &json!({"date": true, "all": false}))?;
|
||||
@@ -158,6 +163,49 @@ fn apply_date(s: &mut Session, o: &DateOpts) -> Result<Value> {
|
||||
Ok(json!({"where": "footer", "format": o.format, "update": o.update, "text": o.sample}))
|
||||
}
|
||||
|
||||
/// Put the date in the date placeholder of the handout or notes master being edited.
|
||||
fn write_master_date(s: &mut Session, o: &DateOpts) -> Result<Value> {
|
||||
let (format, sample, lang, update) = (o.format.clone(), o.sample.clone(), o.lang.clone(), o.update);
|
||||
let has_date = {
|
||||
let st = s.doc()?;
|
||||
crate::page_master(&st.doc, st.selection.target).is_some_and(|m| m.shapes.iter().any(|x| x.ph_type() == Some(deckcraft_model::PhType::Date)))
|
||||
};
|
||||
if !has_date {
|
||||
return Err(bad(
|
||||
"insert.dateTime",
|
||||
"this master has no date placeholder; show it with handout.placeholders or notes.placeholders {date: true}",
|
||||
));
|
||||
}
|
||||
// The text the master holds afterwards, which the answer reports (it can differ from the
|
||||
// caller's sample when the placeholder's own language formats a fixed date).
|
||||
let mut written = o.sample.clone();
|
||||
s.edit(|doc, sel| {
|
||||
let Some(m) = crate::page_master_mut(doc, sel.target) else { return Ok(false) };
|
||||
let m = std::sync::Arc::make_mut(m);
|
||||
let date = |x: &deckcraft_model::Shape| x.ph_type() == Some(deckcraft_model::PhType::Date);
|
||||
let chosen = sel.shapes.iter().find_map(|id| m.shapes.iter().position(|x| x.id == *id && date(x)));
|
||||
let Some(i) = chosen.or_else(|| m.shapes.iter().position(date)) else { return Ok(false) };
|
||||
let Some(sh) = m.shapes.get_mut(i) else { return Ok(false) };
|
||||
let mut props = sh.text.as_ref().and_then(|t| t.paragraphs.first()).and_then(|p| p.runs.first()).map(|r| r.props.clone()).unwrap_or_default();
|
||||
// A fixed date reads in the placeholder's own language when the caller named none.
|
||||
let text = match (&lang, &props.lang) {
|
||||
(None, Some(l)) if !update => date_sample(&format, Some(l)).unwrap_or_else(|_| sample.clone()),
|
||||
_ => sample.clone(),
|
||||
};
|
||||
if let Some(lang) = lang {
|
||||
props.lang = Some(lang);
|
||||
}
|
||||
let kind = if update { RunKind::Field { field: format } } else { RunKind::Text };
|
||||
let body = sh.text.clone().unwrap_or_default();
|
||||
let mut para = body.paragraphs.first().cloned().unwrap_or_default();
|
||||
written.clone_from(&text);
|
||||
para.runs = vec![deckcraft_model::Run { text, props, kind }];
|
||||
sh.text = Some(TextBody { paragraphs: vec![para], ..body });
|
||||
Ok(true)
|
||||
})?;
|
||||
Ok(json!({"where": "master", "format": o.format, "update": o.update, "text": written}))
|
||||
}
|
||||
|
||||
/// Point the current slide's date placeholder at this format (a field, or the sample as plain text).
|
||||
fn write_date_placeholder(s: &mut Session, o: &DateOpts) -> Result<bool> {
|
||||
let format = o.format.clone();
|
||||
|
||||
Reference in New Issue
Block a user