Slide: leading tabs in a new slide's body set the outline level (#370)

This commit is contained in:
MarekSpodBiedry
2026-10-13 17:52:00 +02:00
parent 457406a7a5
commit 007741718e
2 changed files with 43 additions and 7 deletions
+27 -1
View File
@@ -108,7 +108,12 @@ fn new(s: &mut Session, p: &Value) -> Result<Value> {
if let Some(b) = str_param(p, "body")
&& let Some(sh) = slide.shapes.iter_mut().find(|x| x.ph_type().is_some_and(|k| !k.is_title()))
{
sh.text = Some(TextBody::from_text(b));
let mut body = TextBody::from_text(b);
// Leading tabs set the outline level, as in text.set.
for para in &mut body.paragraphs {
super::text::apply_leading_tabs(para);
}
sh.text = Some(body);
}
let at = usize_param(p, "at").unwrap_or(if doc.slides.is_empty() { 0 } else { sel.slide + 1 }).min(doc.slides.len());
let id = slide.id;
@@ -584,3 +589,24 @@ fn from_outline(s: &mut Session, p: &Value) -> Result<Value> {
Ok(json!({"slides": n}))
})
}
#[cfg(test)]
mod tests {
use serde_json::json;
use crate::Session;
#[test]
fn new_slide_body_leading_tabs_become_levels() {
let mut s = Session::with_new();
let text = "Level one\n\tLevel two\n\t\tLevel three\nLevel one again";
s.execute("slide.new", &json!({"layout": "titleAndContent", "title": "Indent", "body": text})).expect("new slide");
let st = s.doc().expect("doc");
let slide = st.doc.slides.get(st.selection.slide).expect("slide");
let body = slide.shapes.iter().find(|x| x.ph_type().is_some_and(|k| !k.is_title())).expect("body");
let paras = &body.text.as_ref().expect("text").paragraphs;
let got: Vec<(u8, String)> = paras.iter().map(|p| (p.level, p.text())).collect();
let want = [(0, "Level one"), (1, "Level two"), (2, "Level three"), (0, "Level one again")];
assert_eq!(got, want.iter().map(|(l, t)| (*l, t.to_string())).collect::<Vec<_>>());
}
}
+16 -6
View File
@@ -687,6 +687,20 @@ fn select_para(s: &mut Session, p: &Value) -> Result<Value> {
ok()
}
/// Turn leading tabs of a paragraph into its outline level (capped at 8) and strip them.
/// Returns whether any tab was found.
pub(crate) fn apply_leading_tabs(para: &mut deckcraft_model::text::Paragraph) -> bool {
let tabs = para.runs.first().map(|r| r.text.chars().take_while(|c| *c == '\t').count()).unwrap_or(0);
if tabs == 0 {
return false;
}
if let Some(r) = para.runs.first_mut() {
r.text = r.text.trim_start_matches('\t').to_string();
}
para.level = tabs.min(8) as u8;
true
}
fn set(s: &mut Session, p: &Value) -> Result<Value> {
let text = str_param(p, "text").ok_or_else(|| bad("text.set", "missing `text`"))?.to_string();
let id = match id_param(p, "id") {
@@ -706,13 +720,9 @@ fn set(s: &mut Session, p: &Value) -> Result<Value> {
let mut nb = TextBody::from_text(&text);
for (i, para) in nb.paragraphs.iter_mut().enumerate() {
// Leading tabs set the outline level.
let tabs = para.runs.first().map(|r| r.text.chars().take_while(|c| *c == '\t').count()).unwrap_or(0);
if tabs > 0
&& let Some(r) = para.runs.first_mut()
if !apply_leading_tabs(para)
&& let Some((lv, pp)) = levels.get(i)
{
r.text = r.text.trim_start_matches('\t').to_string();
para.level = tabs.min(8) as u8;
} else if let Some((lv, pp)) = levels.get(i) {
para.level = *lv;
para.props = pp.clone();
}