mirror of
https://github.com/storytold/deckcraft.git
synced 2026-11-07 10:55:52 +00:00
Outlines leave footer, date and slide-number placeholders out: export, RTF and the outline view
An agent run exporting a deck as an outline got the footer text and the slide-number field (‹#›) as body lines under each title. PowerPoint's outline view shows title and body placeholders only, so the text outline, the RTF outline (Save As ▸ Outline/RTF) and the outline view now skip the footer-row placeholders as they already skipped the title. Split out of the frame export PR at review, which asked for all three together. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
52f87aca13
commit
ede154d6be
5 files changed
+61
-4
No files matched your search
@@ -6509,3 +6509,35 @@ fn save_to_image_path_is_an_error_not_zip_bytes() {
|
||||
}
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// The outline exports (PowerPoint's outline view) holds titles and body text only: footer,
|
||||
/// date and slide-number placeholders are not body lines.
|
||||
#[test]
|
||||
fn outline_export_skips_footer_date_and_slide_number() {
|
||||
let mut s = session();
|
||||
s.execute("slide.new", &json!({"layout": "titleAndContent", "title": "Agenda", "body": "First point\nSecond point"})).unwrap();
|
||||
s.execute(
|
||||
"design.headerFooter",
|
||||
&json!({"footer": true, "footerText": "Company Confidential", "slideNumber": true, "date": true, "dateText": "1 May 2026"}),
|
||||
)
|
||||
.unwrap();
|
||||
let dir = std::env::temp_dir().join(format!("deckcraft-outline-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let path = dir.join("deck.md");
|
||||
s.execute("file.export", &json!({"path": path.to_string_lossy(), "format": "outline"})).unwrap();
|
||||
let text = std::fs::read_to_string(&path).unwrap();
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
assert!(text.contains("Agenda") && text.contains("First point") && text.contains("Second point"), "{text:?}");
|
||||
assert!(!text.contains("Company Confidential"), "footer text is not a body line: {text:?}");
|
||||
assert!(!text.contains("‹#›"), "the slide-number field is not a body line: {text:?}");
|
||||
assert!(!text.contains("1 May 2026"), "the date is not a body line: {text:?}");
|
||||
// The RTF outline (Save As ▸ Outline/RTF) too.
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let path = dir.join("deck.rtf");
|
||||
s.execute("file.export", &json!({"path": path.to_string_lossy(), "format": "rtf"})).unwrap();
|
||||
let rtf = std::fs::read_to_string(&path).unwrap();
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
assert!(rtf.contains("Agenda") && rtf.contains("First point"), "{rtf:?}");
|
||||
assert!(!rtf.contains("Company Confidential") && !rtf.contains("1 May 2026"), "{rtf:?}");
|
||||
}
|
||||
@@ -694,7 +694,9 @@ pub fn slides_to_outline(p: &Presentation) -> String {
|
||||
out.push_str(&s.title().replace('\n', " "));
|
||||
out.push('\n');
|
||||
for sh in &s.shapes {
|
||||
if sh.ph_type().is_some_and(|k| k.is_title()) {
|
||||
// The title is the slide's line; footer, date and slide-number placeholders are not
|
||||
// body text (PowerPoint's outline view shows title and body placeholders only).
|
||||
if sh.ph_type().is_some_and(|k| k.is_title() || k.is_footer_kind()) {
|
||||
continue;
|
||||
}
|
||||
if let Some(t) = &sh.text {
|
||||
|
||||
@@ -71,7 +71,8 @@ pub fn slides_to_rtf(p: &Presentation) -> String {
|
||||
}
|
||||
out.push_str("\\b0\\par\n");
|
||||
for sh in &s.shapes {
|
||||
if sh.ph_type().is_some_and(|k| k.is_title()) {
|
||||
// As in the text outline: footer, date and slide-number placeholders are not body text.
|
||||
if sh.ph_type().is_some_and(|k| k.is_title() || k.is_footer_kind()) {
|
||||
continue;
|
||||
}
|
||||
let Some(t) = &sh.text else { continue };
|
||||
|
||||
@@ -476,7 +476,7 @@ fn outline_estimate(s: &deckcraft_model::Slide) -> f32 {
|
||||
let lines: usize = s
|
||||
.shapes
|
||||
.iter()
|
||||
.filter(|x| !x.ph_type().is_some_and(|k| k.is_title()))
|
||||
.filter(|x| !x.ph_type().is_some_and(|k| k.is_title() || k.is_footer_kind()))
|
||||
.filter_map(|x| x.text.as_ref())
|
||||
.filter(|tb| !tb.is_empty())
|
||||
.map(|tb| tb.paragraphs.len().max(1))
|
||||
@@ -512,7 +512,8 @@ fn outline_slide(app: &mut SlideApp, ui: &mut Ui, t: &Tokens, i: usize, s: &deck
|
||||
ui.data_mut(|d| d.insert_temp(id, v));
|
||||
}
|
||||
});
|
||||
for sh in s.shapes.iter().filter(|x| !x.ph_type().is_some_and(|k| k.is_title())) {
|
||||
// Footer, date and slide-number placeholders are not outline text, as in PowerPoint.
|
||||
for sh in s.shapes.iter().filter(|x| !x.ph_type().is_some_and(|k| k.is_title() || k.is_footer_kind())) {
|
||||
let Some(tb) = &sh.text else { continue };
|
||||
if tb.is_empty() {
|
||||
continue;
|
||||
|
||||
@@ -259,3 +259,24 @@ fn shift_and_ctrl_shift_clicks_select_ranges() {
|
||||
click(&mut h, 4, Modifiers::COMMAND | Modifiers::SHIFT);
|
||||
assert_eq!(selected(&h), vec![0, 2, 3]);
|
||||
}
|
||||
|
||||
/// The outline view lists a slide's title and body text, not its footer, date and slide-number
|
||||
/// placeholders, as PowerPoint's does: turning them on leaves the slide's outline block as it was.
|
||||
#[test]
|
||||
fn the_outline_view_leaves_footer_date_and_slide_number_out() {
|
||||
let mut s = Session::with_new();
|
||||
s.execute("slide.new", &json!({"layout": "titleAndContent", "title": "Agenda", "body": "First point\nSecond point"})).unwrap();
|
||||
let slide = |s: &Session| {
|
||||
let st = s.doc().unwrap();
|
||||
st.doc.slides[st.selection.slide].clone()
|
||||
};
|
||||
let plain = super::outline_estimate(&slide(&s));
|
||||
s.execute(
|
||||
"design.headerFooter",
|
||||
&json!({"footer": true, "footerText": "Company Confidential", "slideNumber": true, "date": true, "dateText": "1 May 2026"}),
|
||||
)
|
||||
.unwrap();
|
||||
let with_footer = slide(&s);
|
||||
assert!(with_footer.shapes.iter().any(|x| x.ph_type().is_some_and(|k| k.is_footer_kind())), "the footers are on the slide");
|
||||
assert_eq!(super::outline_estimate(&with_footer), plain);
|
||||
}
|
||||
Reference in new issue
Block a user