squidfunk / mkdocs-material

Documentation that simply works
https://squidfunk.github.io/mkdocs-material/
MIT License
20.44k stars 3.49k forks source link

Expandable Code Blocks #4964

Closed azbpa closed 1 year ago

azbpa commented 1 year ago

Context

No response

Description

Add option to make code blocks expandable so that only a few lines are shown at first place. A button below the code block will give the option to expand it entirely.

Related links

Use Cases

Visuals

image Source: https://docs.aws.amazon.com/securityhub/latest/userguide/prepare-upcoming-features.html#appendix-sample-findings

Before submitting

squidfunk commented 1 year ago

Thanks for suggesting. I've investigated possible ways of implementing this, and I think I found a way that works without JavaScript! We would need a little support from the Highlight extension, provided by Python Markdown Extensions. The solution is similar to what we do with content tabs.

HTML prototype ``` html
markdown_extensions:

  # Python Markdown
  - abbr
  - admonition
  - attr_list
  - def_list
  - footnotes
  - md_in_html
  - toc:
      permalink: true

  # Python Markdown Extensions
  - pymdownx.arithmatex:
      generic: true
  - pymdownx.betterem:
      smart_enable: all
  - pymdownx.caret
  - pymdownx.details
  - pymdownx.emoji:
      emoji_index: !!python/name:materialx.emoji.twemoji
      emoji_generator: !!python/name:materialx.emoji.to_svg
  - pymdownx.highlight
  - pymdownx.inlinehilite
  - pymdownx.keys
  - pymdownx.mark
  - pymdownx.smartsymbols
  - pymdownx.superfences
  - pymdownx.tabbed:
      alternate_style: true
  - pymdownx.tasklist:
      custom_checkbox: true
  - pymdownx.tilde

```

The prototype looks like this:

Bildschirmfoto 2023-02-18 um 16 09 20 Bildschirmfoto 2023-02-18 um 16 09 14

The next step would be to on-board @facelessuser, and check if he would be willing to implement something like this, given that it fits the tone and vision of the Highlight extension. When this is done, we could add styling support in Material for MkDocs. In any case, this needs to be taken upstream, and there are no actionables for us until support is added.


Further considerations:

facelessuser commented 1 year ago

I'm not so sure how all this would work, but I'm willing to at least talk about it and see if it maybe could work somehow. I have concerns about this, especially when paired with Pygments. This is taking a very simple case and applying such logic, but what about when syntax highlighting is at play? What if we are applying this to a code block using the table format and line numbers?

Even if this is too convoluted for Highlight, we might be able to provide a new general-purpose block that applies something like this to a given code block. General purpose blocks are getting closer and closer to being ready for the public to start trying. I guess there are always custom fences as well.

Anyways, while I see maybe a possibility when done with a very simple example as shown above, there are definitely some concerns with this playing nice with other features enabled.

squidfunk commented 1 year ago

Couldn't this be done similar to / or with line spans? We could even make this configurable similar to hl_lines, e.g. collapse_lines="4" to collapse everything from the 4th line on, or collapse_lines="4-8" to only collapse lines 4-8. As I understand, only the line spans would need to be wrapped, so this should – in theory – be possible to implement as a post processor, not? Essentially, transform:

``` collapse_lines="4"
L1
L2
L3
L4
L5
L6

Into:
L1
L2
L3
<input type="checkbox" id="###">
<span class="__collapse">
  L4
  L5
  L6
</span>
<label for="###">Expand</label> 


I've tested this with highlighted code as well, and it works nicely. I only removed all highlighting for a more terse example.
facelessuser commented 1 year ago

Have you tested this with table formatted code blocks?

squidfunk commented 1 year ago

Hmm, no, not yet. I don't think it will work with tables. But I guess it might work with pymdownx-inline, right?

facelessuser commented 1 year ago

I'm still not sure why we couldn't use generic blocks to create a simple collapsible wrapper.

/// collapse

some code with a lot of lines

///

If I'm being honest, I'm not a huge fan of wrapping lines with an input checkbox. Maybe it will grow on me?

I'm not saying I'm against the suggested approach, but I'm skeptical that there is no other way, and that this is the best and only way. That's not to discount the research you've put into this. If I were going to do something like this, it needs to work generally well and I'd probably want to explore some other avenues even if they lead to dead ends. If I'm to support such an approach, I want to be 100% sure I couldn't approach this in a better way.

Hmm, no, not yet. I don't think it will work with tables. But I guess it might work with pymdownx-inline, right?

I imagine it could work fine with pymdownx-inline.

I know there are some complications right now, but I know things like :has() are on the horizon. Browsers are moving forward with this where they weren't before. Granted, they will be somewhat limited for the sake of performance, but they are coming, granted, I don't know how many years until we can actually use them. So clearly, this isn't available now, but something to keep in mind as we are thinking forward.

squidfunk commented 1 year ago

I'm agnostic to how things are implemented – this was just a first rough sketch. Let's let that sit for a while and maybe come back to it later at some point. I think it would be a great addition to the toolbelt. If it proves unfeasible, no problem. I just didn't want to throw you something without having sketched out a potential solution myself.

facelessuser commented 1 year ago

Yep, that makes sense. I may tinker with the idea myself. It may be that this is the only pure CSS approach right now, but that probably just highlights the weakness in the current, implemented CSS spec. I'm just hesitant a bit right now as it is an invasive approach and only works on some of the supported code block styles, and I'm certain the future will allow us to do this better if there isn't already a way now. I won't deny that many may find the feature useful though.

I have a feeling that if I did implement this as currently proposed, I'd be more willing to post-process it in some generic block as described above than make it an integrated part of Highlight. That would give me the ability to implement an approach native to Highlight at some future point when the landscape is a bit more attractive for a pure CSS, collapsible block. I could just bundle it in Material Extras or something like that.

facelessuser commented 1 year ago

I feel like something like this could work. No JS, just CSS. It's kind of sloppy right now, but the idea is simply that it sets the max height on the block and fades the text at the bottom. When you expand, it shows everything.

https://codepen.io/facelessuser/pen/RwYWgJo

collapse

facelessuser commented 1 year ago

Pulled it off in Material using generic blocks:

Screenshot 2023-02-18 at 8 53 50 PM
facelessuser commented 1 year ago

After tweaking it a bit, I'm thinking this is probably a fairly reasonable approach. If you wanted to not have it collapsable after expansion, the button could just be hidden after expansion. I think a reasonable alternative is just adding a class to a code block and making it shorter, but scrollable, but if expanding blocks is absolutely desired, I think this approach could work and wouldn't require us to deeply integrate it into Highlight. We just create a simple wrapper around the code blocks. Anyways, I think I'm done exploring this for now.

collapse

squidfunk commented 1 year ago

Does this work with code annotations? FWIW, setting max-height implies overflow: hidden, and this might mess with code annotations. Strangely, I thought it was definite, but it seems like it works in Webkit. Not sure about other browsers, though. This is why I resorted to the nested span solution, which I still find to be more flexible, as it would allow for supporting multiple foldable sections.

So, this still works in Chrome (to my surprise), but other browsers would need to be tested:

Bildschirmfoto 2023-02-19 um 09 48 50
facelessuser commented 1 year ago

I had not tested annotations, in your test, do the annotations need to be wrapped in the container or outside?

This is why I resorted to the nested span solution, which I still find to be more flexible, as it would allow for supporting multiple foldable sections.

I guess I missed this part. I've never seen this done anywhere.

I thought this was for posting large code blocks without taking up a ton of space. I didn't realize people wanted multiple foldable sections. This seems like it creates very complex code examples. I guess some your suggestions make more sense in context now.

If multiple, foldable code blocks is really what is desired, then we could still go with your idea, but I think approaching it with a special Material, generic block would be the ideal way to go as least for now. I'm not exactly sure how complicated I want Highlight to be. At the very least, it would allow me to ship the functionally as it's own experimental thing and have it mature in Material first. This may be a good candidate for the Material Extra repo that I already maintain.

So, I imagine we could have a block that you can wrap your code (and annotations?) in, and we could extract the HTML for the code block and then post process it with foldable sections.

facelessuser commented 1 year ago

Just a note, the simple expand option I provided worked fine in Firefox, Chrome, and Safari. As a simple option, it is more than sufficient and very quick and easy to get working as I technically already have a prototype.

But if we absolutely want multiple folding regions, that can be done. It's obviously more work in the post-processing realm, but I imagine doable.

Regardless of which direction we go, I'll probably utilize the generic block feature that should be available soon-ish. Deep integration into Highlight may or may not happen at some future time.

facelessuser commented 1 year ago

My current focus is to get out the generic block plugin out. I'll personally be using the basic fold in my own documents as I'm actually kind of liking the look and functionality. I can't see any negatives except that it isn't as quite as powerful as the per-line folding approach. FWIW I think it works well for what I personally need. If it turns out people are interested, I don't mind making it publically available.

Screenshot 2023-02-19 at 9 21 48 PM

As far as the more complex folding approach, I'll probably look at it sometime after the generic blocks plugin is released. I'm not exactly sure when, but it would definitely be after that gets out. I think it is generally more complex than I personally need, but I can see people maybe wanting to utilize this approach as it does open up more possibilities. I think we can achieve everything in the suggested prototype without having to deeply integrate this into Highlight.

azbpa commented 1 year ago

@squidfunk / @facelessuser thanks for the great discussion of this request.

The last screenshot shared looks like what I was imagining. If I got the discussion right, this is based on pure CSS and thereby could be already added using customization stylesheet file.

I would appreciate if you would share your current code so I could test it.

squidfunk commented 1 year ago

@facelessuser the last screenshot you posted looks pretty good. Could you create a PR with the necessary changes when the generic block plugin is out? And this is language agnostic, right?

Regarding multiple foldable regions – this might be something for the future, so no hurry.

facelessuser commented 1 year ago

And this is language agnostic, right?

It certainly can be. Right now I have the enable/disable labels with optional text. In the screenshot, I just set the option with no text and specify icons in the style. There are tooltips as well where you can change the text, these also can bet set to nothing to disable them.

Regarding multiple foldable regions – this might be something for the future, so no hurry.

Yep, totally understand. I'm hoping to have the general blocks available sometime soon-ish, so once they are out I'll decide whether I mainline the demoed collapsible block, or drop it in Material Extra. Regardless, once available, I'll create a pull request so you can polish up anything you don't like 🙂.

facelessuser commented 1 year ago

@azbpa I'm about to release a beta of the general purpose blocks: https://github.com/facelessuser/pymdown-extensions/pull/1777.

The actual plugin is here: https://github.com/facelessuser/pymdown-extensions/blob/2d4ec563576803ea9d3f121a17f3b0e913ffa62c/tools/collapse_code.py. It will not work yet without the pymdownx beta release as it builds on the general purpose block framework in that release. I might get the beta out today?

As mentioned, it does work with just CSS. The bulk of it is here: https://github.com/facelessuser/pymdown-extensions/blob/2d4ec563576803ea9d3f121a17f3b0e913ffa62c/docs/src/scss/extensions/_highlight.scss#L150

There may be some corner cases for tabs and mobile where some things may need to be adjusted. I think I made some of those changes, but I haven't gathered all the specific changes into one place yet, and this is based on my docs which do have some custom stuff, so I really need to play with this in a pure Material environment to give a complete bare minimum of what is required.

facelessuser commented 1 year ago

Beta is live. Real-world you can play with here: example here: https://facelessuser.github.io/pymdown-extensions/extensions/critic/#css.

squidfunk commented 1 year ago

Nice! If you find some time, could you isolate the necessary CSS changes and send a PR? One problem with the label approach is: it's not accessible. This should be fixable by adding tabIndex=0 to both label elements, so they can be opened with keyboard. Also, proper ARIA attributes are probably a good idea.

facelessuser commented 1 year ago

I can add the tabIndex stuff. Can you make a suggestion for ARIA? Do we just need aria-label?

squidfunk commented 1 year ago

Thinking about ARIA, I think we need to do this in JS. IIRC, we need to set aria-expanded on the code block dynamically, i.e., whenever it is expanded or collapsed. Additionally, we might need to link the label elements with the code block, to denote that the code block expansion is controlled by those. We'll do ARIA attributes in Material for MkDocs. However, setting tabIndex=0 can (and IMHO should) be set in the output HTML.

One more thing: we need to be able to translate the title attributes of the labels. I guess that means we need those as options, similar to permalink_title in the toc extension. This will also integrate nicely with improved tooltips.

facelessuser commented 1 year ago

Titles are already exposed as options .

facelessuser commented 1 year ago

I've also already added tabindex. It's live in the docs.

Vasperous commented 1 year ago

@facelessuser apologies if I missed it but I couldn't find any further references to this. Was this ever brought out of beta? I tried searching for how to enable this feature but couldn't find anything. If the answer is custom CSS that is fine, just wanted to check. Thank you!

facelessuser commented 1 year ago

Ah, yes. Thanks for the reminder. I had not come back with the complete solution for Material. I will try and look into this and get back to you this week. I have a complete, working solution, I just need to extract exactly what is needed for Material.

Vasperous commented 1 year ago

Ah, yes. Thanks for the reminder. I had not come back with the complete solution for Material. I will try and look into this and get back to you this week. I have a complete, working solution, I just need to extract exactly what is needed for Material.

Thank you! Looking forward to seeing it as I have some lengthy code blocks which this feature would be extremely helpful for.

facelessuser commented 1 year ago

Using this custom extension: https://github.com/facelessuser/pymdown-extensions/blob/main/tools/collapse_code.py to wrap a single code block, paired with the following CSS should be all that is needed.

Minimum Required CSS ```css .md-typeset .collapse-code{ position:relative; margin-top:1em; margin-bottom:1em } .md-typeset .collapse-code pre, .md-typeset .collapse-code .highlighttable { margin-top:0; margin-bottom:0 } .md-typeset .collapse-code input{ display:none } .md-typeset .collapse-code input~.code-footer{ width:100%; margin:0; padding:.25em .5em .25em 0 } .md-typeset .collapse-code input~.code-footer label{ position:relative; margin:.05em; padding:.15em .8em; color:var(--md-primary-bg-color); font-size:90%; background-color:var(--md-primary-fg-color); -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat; -webkit-mask-size:contain; mask-size:contain; border-radius:.1rem; cursor:pointer; content:"" } .md-typeset .collapse-code input~.code-footer label:hover{ background-color:var(--md-accent-fg-color) } .md-typeset .collapse-code input~.code-footer label::before{ position:absolute; top:.15em; left:.15em; display:block; box-sizing:border-box; width:1.25em; height:1.25em; background-color:var(--md-primary-bg-color); background-size:1.25em;content:"" } .md-typeset .collapse-code input~.code-footer label.expand{ display:none } .md-typeset .collapse-code input~.code-footer label.expand::before{ -webkit-mask-image:url('data:image/svg+xml;charset=utf-8,'); mask-image:url('data:image/svg+xml;charset=utf-8,') } .md-typeset .collapse-code input~.code-footer label.collapse::before{ -webkit-mask-image:url('data:image/svg+xml;charset=utf-8,'); mask-image:url('data:image/svg+xml;charset=utf-8,') } .md-typeset .collapse-code input:checked~.code-footer label.expand{ display:inline } .md-typeset .collapse-code input:checked~.code-footer label.collapse{ display:none } .md-typeset .collapse-code input:checked+div.highlight code, .md-typeset .collapse-code input:checked+div.highlight .linenodiv{ max-height:9.375em; overflow:hidden } .md-typeset .collapse-code input:checked~.code-footer{ position:absolute; bottom:0; left:0; padding:2em .5em .5em .8rem; background-image:linear-gradient(to bottom,transparent,var(--md-default-bg-color) 80% 100%) } .md-typeset .tabbed-block>.collapse-code:first-child, .md-typeset .tabbed-block>.collapse-code:first-child { margin: 0; } @media screen and (max-width:44.9375em){ .md-typeset>.collapse-code{ margin-right:-.8rem; margin-left:-.8rem } .md-typeset>.collapse-code label.collapse{ left:.8rem } } ```
Screenshot 2023-09-13 at 11 56 52 AM

If you were to wrap anything other than code blocks or you were to wrap more than a singular code block, it is likely you would get odd styling. This is styled to mimic how Material handles singular code blocks with the added interface to expand them.

For Material to integrate this, I would need to officially release the custom plugin. And to do that, I'd like to get feedback from @squidfunk first. Once in the wild, it is unlikely I would make any large changes to it.

Currently, I just place the custom plugin in my projects tools folder and configure it like so:

https://github.com/facelessuser/pymdown-extensions/blob/main/mkdocs.yml#L197

Vasperous commented 11 months ago

Using this custom extension: https://github.com/facelessuser/pymdown-extensions/blob/main/tools/collapse_code.py to wrap a single code block, paired with the following CSS should be all that is needed.

Minimum Required CSS

Screenshot 2023-09-13 at 11 56 52 AM

If you were to wrap anything other than code blocks or you were to wrap more than a singular code block, it is likely you would get odd styling. This is styled to mimic how Material handles singular code blocks with the added interface to expand them.

For Material to integrate this, I would need to officially release the custom plugin. And to do that, I'd like to get feedback from @squidfunk first. Once in the wild, it is unlikely I would make any large changes to it.

Currently, I just place the custom plugin in my projects tools folder and configure it like so:

https://github.com/facelessuser/pymdown-extensions/blob/main/mkdocs.yml#L197

Thank you!!!

Vasperous commented 11 months ago

@facelessuser actually I cannot get the solution to work, I created the tools folder locally and can never get mkdocs to find the extension. I found a recommendation to use python -m mkdocs serve which got rid of the error but the processing never actually occurred. I even put the collapse_code.py in my package folder (i.e., in pip) but still no luck even after getting it to past the error. I feel like I am missing something basic.

Also, I noted in your page you use the forward slashes to create different sections. I don't believe material supports this so the best I could do to create the code block was something like this:

<div class="collapse-code">
...
</div>
Vasperous commented 11 months ago

I did some more research and it looks like I have to use the Blocks feature, is there a way to do this without using Blocks?

facelessuser commented 11 months ago

Is there reason you are adverse to using Blocks?

Vasperous commented 11 months ago

@facelessuser no specific reason other than its not something I am terribly familiar with. In this case its a syntax I am using just for the collapse code. Regardless, thank you for the help, much appreciated :)

facelessuser commented 11 months ago

Well, you are free to write your own plugin instead or use raw HTML that matches what the CSS expects.

hellt commented 10 months ago

I wonder if the extension/plugin proposed by @facelessuser can be further extended to expand horizontally as well?

As per the linked issue I logged https://github.com/squidfunk/mkdocs-material/issues/2171 in the tech docs we often work with data tables, and tables are known to span wide.

It would be amazing to have an option to expand in both ways as shown here in the description.

I am willing to sponsor this work.

NikosAlexandris commented 8 months ago

I think having full-width, expandable or else options to show more is useful for a technical documentation engine. Think of command line output, for example. Currently, and depending on what customisation or plugin (i.e. https://pawamoy.github.io/markdown-exec/), the output isn't always easy to read.

I am experimenting with it at https://nikosalexandris.github.io/rekx/how_to/inspect/. Content gets truncated which is not great.

hellt commented 7 months ago

Here is a good example how fullscreen code blocks are done on dev.to https://dev.to/waylonwalker/installing-system-nerd-fonts-with-ansible-35kh

https://github.com/squidfunk/mkdocs-material/assets/5679861/575d3fc1-82a1-4f23-8ac4-dbc1a38b66af

SPyofgame200 commented 5 months ago

Currently, I just place the custom plugin in my projects tools folder and configure it like so:

https://github.com/facelessuser/pymdown-extensions/blob/main/mkdocs.yml#L197

I think you mean #L227 ?

image

PlasmaHH commented 3 months ago

Since it took me a moment to get tools/collapse_code.py to work let me note here a few things that might make it easier for future visitors:

Due to the way pythons sys.path is populated you either have to use python3 -m mkdocs or add . to your PYTHONPATH to make this work ( which is super inconvenient for my setup, I am still looking for a way to add it from within the mkdocs config )

Not setting expand_text/collapse_text to empty will cause the two arrow icons to overlap the text. I added padding-left: 1.5em to my css for label.expand and label.collapse to make it look better for my case, no idea though if that will be useful for the general public.

It Seems to be unclear how to use it within markdown syntax, so this is how the syntax looks for me and works:

/// collapse-code

all the code in multiple lines

///

All in all I find this thing quite useful and am wondering if we could get that maybe as part of the official extensions package? The setup is quite a bit more involved than for everything else, especially with the sys.path thing being a major invonvenience at least for me.

PS: Maybe it would be possible to add the number of lines or so as a parameter that the collapsed version should be in size? Sometimes it seems quite appropriate to me to be able to chose...

PPS: would making an option to have a scrollable code box fit in here as well or would it make sense to have that as a completely seperate feature?

kolibril13 commented 1 week ago

I’m interested in using this feature as well and noticed that a solution is already available above https://github.com/squidfunk/mkdocs-material/issues/4964#issuecomment-1718085465 .

With that in mind, would it be possible to make this a default feature and include a reference to it in the documentation at Code Blocks Reference?

Many thanks for your consideration!

squidfunk commented 1 week ago

Sure! We're working on much bigger things right now, but we will very likely consider this again at a later point.

kolibril13 commented 1 week ago

That's great to hear! All the best for your next development steps!