How to Create Nested Containers

I am creating paragraph indentation using the following code:

 show heading: it => {
      block(inset: (left: content-indent * it.level), it) // 根据标题级别缩进 / Indent based on heading level
    }

    // 显示段落和列表时根据对应的标题层级缩进
    // Indent paragraphs based on corresponding heading level
    show selector.or(par, enum, list, table, raw): it => context { // 查找标题和段落并传递给it变量
      let h = query(selector(heading).before(here())).at(-1, default: none) // 获取前一个标题 / Get previous heading
      if h == none {
        return it               // 如果没有标题,返回原段落 / Return original paragraph if no heading
      }
      block(inset: (left: content-indent * (h.level + 1)), it) // 根据标题级别缩进,额外再缩进一级与标题区分 / Indent based on heading level + 1
    }

    // 显示一级标题时根据设置分页
    // Possibly page break when displaying level 1 headings
    show heading.where(level: 1): it => { 
      if chapter-pagebreak {    // 在新页开始章节 / Start chapters on new page
        pagebreak(weak: true)   // 弱分页 / Weak page break
      }
      it                        // 返回标题内容 / Return heading content
    }
    
    // 显示正文内容
    // Display main body content
    setup-content(
      body,                     // 正文内容 / Main body content
    )

Now I want to modify it to create nested indentation based on the heading levels to achieve more formatting options.

The goal is to achieve the following effect:

My template

The code block you are using in your post assumes the code is in markup mode, but the code you included is in code mode. Can you change it to match, one way or the other? This makes the formatting work correctly.

```
#let variable = 0
```
```typc
let variable
```

The code you posted does not result in any output because it cannot be compiled. Posting a Short, Self Contained, Correct (Compilable), Example (SSCCE) highlights to others what specifically is going wrong. Otherwise they have to troubleshoot things that are not part of your actual question.

As a bonus, preparing a SSCCE sometimes results in the problem becoming so obvious that the solution is also obvious.


I got your code to run and I see that content is already indented depending on what heading it falls under. Can you give an example or more details about what result you are looking for exactly? As it stands I don’t know how to provide information to you that would actually get you closer to the solution you are looking for.


Please review the FAQ and How to post in the Questions category. Following these guidelines can help get people interested in solving your problem, or at least lower the boundary for them to help.

3 Likes

Based on a question I recently asked on how to wrap the content of a section, I think I have been able to do it:

First, the function from my previous question. The only difference is that I have used positional instead of named arguments.

#let is-heading(it) = {
  it.func() == heading
}

/// A show rule that wrap everything between two headings of the same level
///
/// Usage: ```
/// #show: wrapp-section.with(
///   depth: 1, // optional
///   wrapper: (heading: none, section: none) => {
///    rect[
///      heading: #box[#rect[#heading]]
///      
///      section: #box[#rect[#section]]
///    ]
///  }
/// )
/// ```
///
/// Note: `#set` rules (like `#set text(lang: "fr")`) must be declared before calling `#show: wrapp-section`
#let wrapp-section(
  body,
  depth: 1,
  wrapper: none,
) = {
  // The heading of the current section
  let heading = none
  // The content of the current section
  let section = ()

  for it in body.children {
    let x = it.func();
    
    if (is-heading(it) and it.fields().at("depth") < depth) {
      if heading != none {
        // Complete the last section
        wrapper(heading: heading, section: section.join())
        heading = none
        section = ()
      }
      it
    } else if is-heading(it) and it.fields().at("depth") == depth {
      if heading != none {
        // Complete the last section
        wrapper(heading, section.join())
        heading = none
        section = ()
      }

      heading = it
    } else if heading != none {
      // Continue the current section
      section.push(it)
    } else {
      it // if not in any section (before the first heading of the appropriate depth)
    }
  }

  // Complete the last section
  if heading != none {
    wrapper(heading, section.join())
  }
}

Then, a recursive function that create the nesting from a heading and what I call a section (everything before the next heading of the same level)

#let nest-block(body, depth: 1) = {
  wrapp-section(
    depth: depth,
    wrapper: (heading, content) => {
      block(
        stroke: (left: black),
        inset: (left: 1em),
      )[
        #heading
        #nest-block(depth: depth + 1, content)
      ]
    }
  )[#body]
}

And finally, we can use it:

// For some reason, must be done before calling #show
#set text(lang: "en")

#show: wrapp-section.with(
  depth: 1,
  wrapper: (heading, content) => {
    heading
    nest-block(depth: 2, content)
  }
)

= heading 1 - a

== heading 2 - a

some
text _and_ 
more

=== heading 3 - a

stuff

== heading 2 - b
test


= heading 1 - c

== heading 2 - c

some
text _and_ 
more

=== heading 3 - c

stuff

== heading 2 - d
test

If you prefer to have the level 1 heading being nested too, you can replace the #show rule by

#show: nest-block

Hi @CrossDark, I moved the response you got in your newer thread to this and deleted the new one. Please don’t post the same question twice; instead, ask follow-up questions in the original thread (assuming the answer you got was not sufficient) or ask a distinct question in a new thread, possibly linking to the older discussion so that people know what was already mentioned.

@Andrew, thanks for identifying the duplicate.

1 Like

I directly converted my original template’s main function into wrapp-section.However, the following error occurred:

parbreak does not have field "children"

Do nested functions need to be independent of the main function?

My Project

I am no typst expert, but from what I understood of the errors I got when writing wrapp-section, nested functions are no different from regular functions, and the error you got is most probably because you sent individual items instead of a list of items to wrapp-section.

Maybe, you can try to make it work for a single level first, fix all errors, then create the recursive version

// put all your other `#show` rules before

#show: wrapp-section.with(
  depth: 1,  // or whatever heading level you want to apply it for testing
  wrapper: (heading, content) => {
    block(
      stroke: (left: black),
      inset: (left: 1em),
    )[
      // apply all the formatting you want to the heading or the content here
      #heading
      #content
    ]
  }
)