Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Selective Include #338

Open
jwalzer opened this issue Jan 24, 2023 · 12 comments
Open

Selective Include #338

jwalzer opened this issue Jan 24, 2023 · 12 comments

Comments

@jwalzer
Copy link

jwalzer commented Jan 24, 2023

Issue

Allow for include to only include specific parts of a page

description

Make it possible to only include specific parts of an page.

This could be made possible, by markup in the included page:

Example:

included_page.md

# This is includepage

## Description
This is a description of the include


## Usage
Include `included_page.md` for FOO or BAR
It consists of two ranges, you can include:

* FOO :  This is the standard-Text for FOO
* BAR :  This is the standard-Text for BAR

<!-- #include_range section="FOO" -->
FOO foo foo Foo
<!-- /include_range -->

<!-- #include_range section="BAR" -->
BAR bar bar Baz
<!-- /include_range -->

including_page.md

# Including only BAR
<!-- #include [[included_page]] section BAR-->
BAR bar bar Baz
<!-- /include -->

--------

# Including only FOO
<!-- #include [[included_page]] section FOO-->
FOO foo foo Foo
<!-- /include -->

notice

  • The section title is optional
  • When the includer specifies a section that is not available in the to-be-included page, then the result is empty
  • When the includer does not specify a section, then the result depends on the to-be-included page:
    • If the included page does not define an include_range then the result is the whole page (backward compatibility)
    • If the included page defines an include_range without a section name, then the result is this section
    • If the included page defines one or multiple include_ranges with section names, then the result will be all the sections with that name in the order they appear in the included page
@zefhemel
Copy link
Collaborator

I can see this working, I would challenge how “niche” this use case is, though. What would you be using this for in practice?

@jwalzer
Copy link
Author

jwalzer commented Jan 27, 2023

This useage would be nice, to document in some kind, how templates can/should be used for.
Some examples:
Sometimes a template is not well described by its name alone, but I see rather a lot query-render-templates in the /template/ folder. Giving these templates a description, where it is used, or at least what type of query they could/should be used with can be helpful (ok, maybe this could be done with frontmatter, but I haven't yet looked into, how frontmatter is dealt with templating)

Another example is, to keep some cross linked documentation. I imagine autogenerated system documentation for some servers. Every server has distinct aspects hat should be described. But maybe I want some of the aspects of all the hosts summarized together in an overview page. Having the aspects marked as sections, I can pull them to different overview pages via a query.

(and I see a next FeatureRequest incoming, with "parameters for templates" ;) )

@v411e
Copy link
Contributor

v411e commented Jan 2, 2024

I came across the same use-case but didn't know about this issue. My thoughts on that (see also Discord):

I thought about modularizing the page with the section and then include the section page in both pages, but this is not really beautiful and spreads the content over multiple pages which is not that great.

My use case is like this:

  • I am doing some research on a topic and for each book/paper that I read, I create a page in silverbullet where I note the most important things and also include links to the original source stored in Zotero
  • To keep track on all relevant notes of a certain topic, I created a Meta page for that topic, where I would collect all relevant findings, separated for each book/paper.
  • This lead me to copy the findings from the book/paper-page to the meta-page, but it would have been nicer to just have a kind of "magic window" from the meta-page to the specific section on the book/paper-page.
  • Including everything from a certain header until the next header of the same level would be a great solution.
  • Alternatively, one could maybe include everything from a certain anchor to another anchor

@henrikx
Copy link

henrikx commented Feb 13, 2024

Basic implementation with Space Script:

silverbullet.registerFunction("templateSection", async (link) => {
  const linkregex = /\[\[(.*?)#(#*)(.*?)\]\]/
  const name = link.match(linkregex)[1]
  const heading = link.match(linkregex)[3]
  const pageContent = await syscall("space.readPage", name);
  const md = await syscall("markdown.parseMarkdown", pageContent);
  const yamlAsList = await getMdAsList(md.children);
  let positionOfMatchedHeader;
  let positionOfEndSection;
  for (let i = 0; i < yamlAsList.length; i++) {
    let item = yamlAsList[i];
    if (item.type) {
      if (item.type.startsWith('ATXHeading')) {
        if (positionOfMatchedHeader | positionOfMatchedHeader === 0) {
          positionOfEndSection = item.from;
          break;
        }
        let yamlofheadingaslist = await getMdAsList(item.children);
        let textOfItem = await parseMdHeadingContent(yamlofheadingaslist);
        if (textOfItem && textOfItem === heading) {
          positionOfMatchedHeader = item.from;
        }
      }
    }
  }
  
  return pageContent.substring(positionOfMatchedHeader, positionOfEndSection);

  async function parseMdHeadingContent(children) {
    for (let i = 0; i < children.length; i++) {
      let item = children[i];
      if (item.type) {
        if (item.type === 'WikiLink') {
          return parseMdWikiLinkContent(item.children);
        }
      }
    }
    return children[1].text.substring(1);
  }

  function parseMdWikiLinkContent(children) {
    let indexOfContent;
    for (let i = 0; i < children.length; i++) {
      let item = children[i];
      if (item.type) {
        if (item.type === 'WikiLinkAlias') {
          indexOfContent = i;
          break;
        }
      }
    }
    if (!indexOfContent) {
      for (let i = 0; i < children.length; i++) {
        let item = children[i];
        if (item.type) {
          if (item.type === 'WikiLinkPage') {
            indexOfContent = i;
            break;
          }
        }
      }
    }
    return children[indexOfContent].children[0].text;
  }

  async function getMdAsList(childrenYaml) {
    const yaml = await syscall("yaml.stringify", childrenYaml);
    const yamlAsList = await syscall("yaml.parse", yaml);
    return yamlAsList;
  }
})

Fetches page content after a header until the next header. (therefore also stops at subheaders). One limitation is that it will always choose the first match it finds if you are matching a header and there are two headers with the same text. Using a link for the argument, makes Silverbullet register it as a link, so that if it breaks you can see it on the maintenance page.

template:

{{templateSection("[[Page#Header]]")}}

command template:

---
tags: template
hooks.snippet:
  slashCommand: include-note
---
```
```template
{{escapeDirective('templateSection("[[|^|]]")')}}
```

I haven't tested it much. Let me know if you find any issues.

@Nibodhika
Copy link

I have another use case for this, I'm moving my recipes to silverbullet, some recipes need another one, e.g. cannelloni would need one recipe for the pasta/pancakes and one for the filling. I could include the whole file (which is what I'm doing now) but including just parts of it would let me be more organized to for example have the page show:

# Ingredients
## Pancake
\```template
{{template([[internal-template/today]], "ingredients")}}
\```
...
## Filling
* Minced Beef

# Preparation
## Pancake
\```template
{{template([[internal-template/today]], "preparation")}}
\```
## Filling
Fry the beef...

I'm not entirely sure how to do the definition of the "ingredients" and "preparation", I like the idea of anchoring the header as a simple solution, but the proposed comment for start and end of a section are lot more generic so I think they're better. That being said I'm not a fan of the HTML comment syntax, and I would personally prefer a simpler syntax, e.g.

# Ingredients
{% ingredients %} 
* 1Kg flour
...
{% /ingredients %}

# Preparation
{% preparation %} 
Mix the ingredients...
{% /preparation %}

Other ideas could include {{ ingredients }} (this one is particularly interesting because outside of a template it means nothing but silverbullet already colors it as if it did) or // ingredients

@henrikx
Copy link

henrikx commented Feb 13, 2024

You should be able to adapt my example to be more like what was suggested in the OP or your own suggestion. Personally, I just wanted to be able to refer to a subheading.

@Nibodhika
Copy link

Sorry, yeah, I started writing that comment before you posted that, I'll try that if it works for a header it should be easy to adapt to work with any of the syntaxes I proposed. Thanks.

@Nibodhika
Copy link

For anyone else reading, I've adapted @henrikx script to work with the {{ section }} syntax so you can include multiple headings:

silverbullet.registerFunction("pageSection", async (page, section) => {
  const start_section = "{{ " + section + " }}";
  const end_section = "{{ /" + section + " }}";
  const start_pos = page.indexOf(start_section)+ start_section.length;
  const end_pos = page.indexOf(end_section);
  if (start_pos == -1 || end_pos == -1){
    return `**Error**: Could not find section ${section}`;
  }
  return page.substring(start_pos, end_pos);
})

It's not perfect because imported blocks lose their highlight for code and the templates get reprocessed (although that might be desirable).

@zefhemel
Copy link
Collaborator

zefhemel commented Feb 13, 2024

Clever stuff. One syntax suggestion to consider instead of {{ section }} and {{ /section }} would be the HTML comment syntax which is technically also markdown compatible and has the nice feature of being hidden when rendered to HTML. So e.g. <!-- section --> and <!-- /section -->

@henrikx
Copy link

henrikx commented Feb 14, 2024

On not being able to render code-block:
Is that even possible in a live template at all? @zefhemel

Edit: Possibly caused by #634

@Nibodhika
Copy link

I still see them rendered in the page though @zefhemel but in any case I've updated mine locally to use the HTML comment syntax because the one I used there had problems when I created a template that tried to use them since they are valid syntax inside of a template.

Is there any way that I can make the comments be collapsed or somewhat hidden by default on pages until I get to them with a cursor? I think that would make this even sweeter since then they're not visible by default on the page where they're defined.

@erwinlauener
Copy link

@Nibodhika: What's about this? It's toggled

<details>
  <summary>Lorem</summary>
  - ipsum
</details>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants