Skip to content

Feature: download button for code blocks#32

Open
joapuiib wants to merge 2 commits intojaywhj:mainfrom
joapuiib:feat/download-code-blocks
Open

Feature: download button for code blocks#32
joapuiib wants to merge 2 commits intojaywhj:mainfrom
joapuiib:feat/download-code-blocks

Conversation

@joapuiib
Copy link
Copy Markdown
Contributor

@joapuiib joapuiib commented Apr 6, 2026

This PR adds a download button to the code blocks following this syntax:

```lang {data-download="1" title="custom-title.txt"}
Content
```
  • Blob downloads data-download="1": Download the code block content. The filename suggested to the browser is resolved in the following order of priority:
    • data-download-filename attribute on the code block
    • title option on the code block
    • Fallback: download.txt
```lang {data-download="https://github.com/user/project/blob/main/project/file.ext"}
Content
```
  • URL downloads data-download=url: Create a link button with a download attribute. The filename suggested to the browser is resolved in the following order of priority:
    • data-download-filename attribute on the code block
    • title option on the code block
    • Last path segment of the URL
    • Fallback: download.txt


Code block buttons now handle translations properly (code.select translation was missing).
English and Catalan translations are updated with this changes.

@joapuiib
Copy link
Copy Markdown
Contributor Author

joapuiib commented Apr 6, 2026

I've set version 10.1.4 in the docs since I don't think this will merged soon.
Nevertherless, I can change it to any version.

@joapuiib joapuiib marked this pull request as ready for review April 6, 2026 12:10
@jaywhj
Copy link
Copy Markdown
Owner

jaywhj commented Apr 8, 2026

Here are a few suggestions:

  1. The value 1 in data-download="1" seems meaningless. If 1 works, then 2, 3, 4, etc., would all work as well. Therefore, I suggest removing the 1 for simplicity:

    {data-download}
    {data-download  title="custom-title.txt"}
    

    It will also be easy to parse—an attribute without a value indicates Blob download.

  2. Does the download link for data-download support both HTTP URLs and local file paths? For example:

    {data-download="guides/file.ext"}
    {data-download="https://xxx/file.ext"}
    
  3. Regarding the filename rules, would the following design be more user-friendly?

    • When no title is provided, use undefined or unnamed as the fallback filename. If a file type marker exists after the fenced code block, it is recommended to auto-detect that type:

      without title filename desc
      ``` undefined or unnamed
      ```javascript undefined.js recognize javascript as .js
      ```java undefined.java recognize java as .java
      ```python undefined.py recognize python as .py
    • When title is present, use it directly as the filename and ignore the file type marker:

      with title filename desc
      ``` title="Content tabs" content-tabs title (lowercase and joined by a hyphen)
      ``` title="中文" 中文 title
      ``` title=".gitignore" .gitignore title
      ``` py title="projects/autoplay.py" autoplay.py title (last path segment)
      ``` yaml title=".authors.yml" .authors.yml title

@joapuiib
Copy link
Copy Markdown
Contributor Author

joapuiib commented Apr 8, 2026

  1. data-download for blob and data-download=url for URL files seems reasonable.

  2. I'd keep data-download-filename in case a writter wants to specify a different title for download.

  3. Regarding the filename rules:

    When no title is provided, use undefined or unnamed as the fallback filename.

    We can choose whichever fallback we want to use. I'm not against anything in particular, but when the user looks at his disk and sees undefined might be confused. download.txt is more self-describing.

    (...) If a file type marker exists after the fenced code block, it is recommended to auto-detect that type.

    That involves some kind of logic to map language types to extensions. To make the site user-friendly is also the writter's responsability and ideally, setting a title or data-download-filename=<...>.

    One thing that its feasible is to append the block language type as extension:

    ```py -> download.py
    ```python -> download.python
    ```java -> download.java

    When title is present, use it directly as the filename and ignore the file type marker...

    I agree on the rules proposed.


@jaywhj
Let me know on which direction should we move.

@jaywhj
Copy link
Copy Markdown
Owner

jaywhj commented Apr 9, 2026

(1)
Using download as the fallback name is fine.
We can also add a configuration option to allow users to customize the fallback name.

(2)
The name data-download-filename is a bit long; how about just filename?

(3)
If we adopt the new filename field, title will no longer be the preferred option. I suggest adjusting the rules as follows:

  • If filename exists, use it directly without any additional processing.
  • If filename does not exist, check whether title exists. If it does, use title directly; if not, use {download. + shorthand of the block language} as the filename.

(4)
Using the block language directly as the file extension is not a perfect solution, as the downloaded file may not be recognized as the corresponding language type. For example:

.shell
.javascript
.python
.csharp

Most parsers should implement a mapping between language types and file extensions; we need to look up how to use that mapping.

@joapuiib
Copy link
Copy Markdown
Contributor Author

joapuiib commented Apr 9, 2026

I've made some changes based on your feedback.

(1) It is not possible to set download attribute to a <pre>, that's why we used data-download. Also, if no value is provided, it will be the same as the attribute name.

```py { data-download } -> <pre class="language-py" data-download="data-download">

I've thought of data-download="blob" or data-download=url, but we could leave data-download and check for data-download="data-download" for blob downloads.

(2) Fallback filename and language extension

The language extension detection is already implemented: the language-* class Pygments adds to the container
is read directly, so no mapping table is needed:

  • ```py → download.py
  • ```python → download.python
  • ```java → download.java

A config option for the fallback name can be added later if needed.

If we want to map languages to extensions, we would need a dictionary for storing the transformations. The only question is how exhaustive you
want the table to be, but for most practical cases the shortcode already equals the extension. Is it worth to mantain such dictionary?

(3) Attribute name

We went with data-filename — shorter than data-download-filename and consistent with the data-* convention already used by data-download.

(4) Filename priority and processing

Agreed — filename should be used as-is with no processing. Title similarly. The priority chain is now:

  1. data-filename
  2. Normalized title (last path segment, lowercase and hyphens)
  3. (URL mode only) last path segment of the URL
  4. download. or download.txt

@jaywhj
Copy link
Copy Markdown
Owner

jaywhj commented Apr 10, 2026

@joapuiib

I just ran some tests, and there are quite a few issues with this download feature. All of the following cases failed to parse:

```python data-download
```python data-download="blob"
```python title="bubble_sort" data-download="blob"
```python title="bubble_sort" hl_lines="1-3" data-download="blob"

Actual supported usage:

```python {data-download="blob"}

Furthermore, even this format fails to parse, meaning no other attributes can be added after the language type:

```python title="bubble_sort" {data-download="blob"}

Under these circumstances, users will definitely not accept this, because the original default behavior already supports parsing multiple attributes ```python title="bubble_sort" hl_lines="1-3", however, adding {data-download="blob"} causes the page renders incorrectly.

Can we remove the curly brace syntax? Let’s just add the new attributes directly to the existing format, for example:

```python title="bubble_sort" hl_lines="1-3" data-download

Additionally, this configuration option currently has no effect:

  • content.code.download

@jaywhj
Copy link
Copy Markdown
Owner

jaywhj commented Apr 10, 2026

@joapuiib

Suggestions for Download Type Detection

The recommended priority order is as follows:

(1) Check if data-download is a blob.
The following two expressions are equivalent and all represent the blob type:

```data-download
```python data-download="blob" title="xxx"

(2) Check if data-download is a URL:

```python data-download="https://xxx"
```python data-download="http://xxx"
```python data-download="ftp://xxx"

(3) Check if data-download is a local file (relative or absolute path):

```python data-download="acme-auto.yml"
```python data-download="blog/acme-auto.yml"
```python data-download="/authors.yml"

(4) If none of the above, treat it as an invalid download.
When the user clicks to download, we can use the project’s built-in content.tooltips to indicate that the URL is invalid.

Suggestions for Download Filenames

I think the data-filename attribute may not be necessary, not even the title attribute, only the data-download attribute is required.
For URL and local file download types, both already have appropriate default filenames that the system will automatically display.
We only need to assign a matching filename specifically for the blob type:

```data-download   					→    download.txt
```python data-download   			→    download.python
```java data-download="blob"   		→    download.java

With this adjustment, the overall design and usage will become much simpler.

@jaywhj
Copy link
Copy Markdown
Owner

jaywhj commented May 2, 2026

@joapuiib

Hi, I’ve redesigned the download feature over the past few days.

data-download now supports values wrapped in curly braces, raw plain values, and mixed format of both.

Feel free to test it:

pip install git+https://github.com/jaywhj/mkdocs-materialx.git@4b1e2f3

Enable toggle:

markdown_extensions:
  - material.extensions.code_download

The following formats are all supported:

``` data-download
``` data-download="blob"
``` python data-download
``` python title="bubble_sort" data-download
``` python title="bubble_sort" data-download="assets/xxx.py"
``` python title="bubble_sort" {data-download="blob"}
``` yaml title="title2" { data-download hl_lines="2" } linenums="20"
``` yaml title="title3" { data-download } { hl_lines="2" } linenums="30"

While this implements the desired functionality and offers a more convenient writing experience for users, I feel the added complexity is hardly worthwhile. In fact, direct HTTP links written natively in Markdown already support downloads out of the box, this enhancement only improves the user experience specifically for blob content.

To support the raw syntax for data-download without curly braces, I created a standalone extension code_download.py .
Theoretically, this feature would be more appropriately implemented by the upstream pymdown project.
I’ve opened a discussion upstream, but it remains unclear if the maintainers will accept this feature request.

@jaywhj
Copy link
Copy Markdown
Owner

jaywhj commented May 4, 2026

I've further optimized the code and structure. Feel free to test the latest commit:

pip install git+https://github.com/jaywhj/mkdocs-materialx.git@4b1e2f3

Alternatively, you can directly checkout the feat/code-download branch for testing.

Two usage methods are now supported:

  • All attributes wrapped in curly braces (no need to enable the code_download extension)
  • Raw syntax without curly braces (requires configuration of the code_download extension)

The upstream discussion is here: facelessuser/pymdown-extensions#2885

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

Successfully merging this pull request may close these issues.

2 participants