Compare commits

...

104 Commits

Author SHA1 Message Date
Tom Milligan
ea6c0a687a Merge pull request #229 from tommilligan/prep-1.20.0
chore: prep v1.20.0 release
2025-06-06 14:53:02 +01:00
Tom Milligan
50b9187ee5 chore: prep v1.20.0 release 2025-06-06 14:35:51 +01:00
Tom Milligan
fbd07ecaf0 Merge pull request #228 from tommilligan/print-collapsed-details
feat: show collapsed content in print view
2025-06-06 14:21:49 +01:00
Tom Milligan
37d03b872e feat: show collapsed content in print view 2025-06-06 12:58:11 +01:00
Tom Milligan
fa91fe3778 Merge pull request #227 from tommilligan/update-deps-2025-06
chore: update dependencies
2025-06-06 12:37:47 +01:00
Tom Milligan
d59dce3533 chore: update dependencies 2025-06-06 12:34:38 +01:00
Tom Milligan
21d86546ff Merge pull request #224 from tommilligan/fix-nested-list-content-indent
fix: do not double indent content in lists
2025-02-17 15:55:07 +00:00
Tom Milligan
79ee592f56 chore: prep v1.19.0 release 2025-02-17 15:42:27 +00:00
Tom Milligan
0d3eb11a2c fix: do not double indent content in lists 2025-02-17 15:38:33 +00:00
CoralPink
43579f902b Hide extra markers displayed in Safari. (#185)
* Hide extra markers displayed in `Safari`.

* [review] move webkit fix

---------

Co-authored-by: Tom Milligan <tom.milligan@uipath.com>
2024-08-17 06:19:01 +01:00
Tom Milligan
7ee3643dc3 Merge pull request #209 from tommilligan/deps-upgrade-2
chore: relax action constraints
2024-08-16 08:54:37 +01:00
Tom Milligan
c4bf2608e7 chore: relax action constraints 2024-08-16 08:50:25 +01:00
Tom Milligan
d4667a5f55 Merge pull request #208 from tommilligan/deps-upgrade
chore: upgrade rust, deps and actions
2024-08-16 08:39:23 +01:00
Tom Milligan
8751b3cd24 chore: upgrade github actions 2024-08-16 07:56:14 +01:00
Tom Milligan
516bbc6e7e chore: update rust lockfile 2024-08-16 07:48:11 +01:00
Tom Milligan
fc5c5eae39 chore: bump msrvc to 1.76.0 2024-08-16 07:46:50 +01:00
Tom Milligan
147a9bdb98 Merge pull request #199 from tommilligan/prep-1.18.0
chore: prep 1.18.0 release
2024-06-20 15:26:16 +01:00
Tom Milligan
540ea8e5e8 chore: prep 1.18.0 release 2024-06-20 14:43:12 +01:00
Tom Milligan
ae4bac21ba Merge pull request #198 from tommilligan/upgrade-deps
chore: upgrade deps
2024-06-20 10:53:11 +01:00
Tom Milligan
f0c4d3764e chore: upgrade deps 2024-06-20 10:42:01 +01:00
Tom Milligan
a942a8094e Merge pull request #197 from tommilligan/toml-serialize-error
fix: use up to date toml for serialization
2024-06-20 10:40:44 +01:00
Tom Milligan
d10a427b0f fix: use up to date toml for serialization 2024-06-20 10:05:48 +01:00
Tom Milligan
41a3b05094 Merge pull request #195 from tommilligan/aria-labelledby
feat: add aria attributes to block
2024-06-15 19:51:45 +01:00
Tom Milligan
d0f73baa3f Merge pull request #194 from tommilligan/readme-examples
readme: add more example projects
2024-06-15 13:46:16 +01:00
Tom Milligan
4c40418090 feat: add aria attributes to block 2024-06-15 13:43:58 +01:00
toastal
30c9ad2269 internal: refactor html rendering 2024-06-15 13:42:38 +01:00
Tom Milligan
f7482415a7 readme: add more example projects 2024-06-15 10:20:37 +01:00
Tom Milligan
08397fbffa Merge pull request #192 from tommilligan/prep-v1.17.1
chore: prep v1.17.1 release
2024-06-11 14:51:18 +01:00
Tom Milligan
b78fc39031 chore: prep v1.17.1 release 2024-06-11 14:14:48 +01:00
Tom Milligan
fe7b475753 Merge pull request #186 from tommilligan/tommilligan-patch-1
fix: remove stray debug statement
2024-05-29 19:54:24 +01:00
Tom Milligan
a56976d085 fix: remove stray debug statement 2024-05-29 19:48:54 +01:00
Tom Milligan
08967d550d ci: fix deploy.yml 2024-05-24 15:06:58 +01:00
Tom Milligan
8fa1411095 Merge pull request #184 from tommilligan/fix-cargo-target-dir-release
ci: be more limited with CARGO_TARGET_DIR
2024-05-24 15:05:19 +01:00
Tom Milligan
a1e5cfa48d ci: be more limited with CARGO_TARGET_DIR 2024-05-24 14:59:46 +01:00
Tom Milligan
33fd522d68 Revert "ci: fix deploy artefacts"
This reverts commit c681ff922d.
2024-05-24 14:50:55 +01:00
Tom Milligan
4805398359 Merge pull request #183 from tommilligan/fix-deploy-artefacts
ci: fix deploy artefacts
2024-05-24 14:47:06 +01:00
Tom Milligan
c681ff922d ci: fix deploy artefacts 2024-05-24 14:46:52 +01:00
Tom Milligan
cd0726aaf2 Merge pull request #182 from tommilligan/prep-1.17.0
chore: bump version to 1.17.0, update ci
2024-05-24 13:37:20 +01:00
Tom Milligan
a08967e073 chore: remove deprecated set-output command 2024-05-24 13:27:29 +01:00
Tom Milligan
f7e6970fa3 chore: upgrade github actions 2024-05-24 13:22:51 +01:00
Tom Milligan
5d2124b319 chore: bump version to 1.17.0 2024-05-24 13:17:20 +01:00
Tom Milligan
d79ebb4fad chore: update v2 wishlist 2024-05-24 13:16:12 +01:00
Tom Milligan
9df896cd77 Merge pull request #181 from tommilligan/fix-breaking-on-equals-in-value
fix: support toml values with equal sign
2024-05-24 13:04:16 +01:00
Tom Milligan
ffb819c315 fix: support toml values with equal sign 2024-05-24 12:59:59 +01:00
Tom Milligan
f278374c88 Merge pull request #180 from tommilligan/better-errors-for-html
fix: better errors on toml parsing failures
2024-05-24 11:01:48 +01:00
Tom Milligan
2c18292401 fix: better errors on toml parsing failures 2024-05-24 10:38:39 +01:00
Tom Milligan
294af2478c Merge pull request #177 from tommilligan/cache-ci-more
ci: cache more cargo target dir
2024-05-19 17:38:27 +01:00
Tom Milligan
278d17792b Merge pull request #178 from tommilligan/fix-changelog
chore: fix changelog
2024-05-19 17:33:39 +01:00
Tom Milligan
9f6c73091a chore: fix changelog 2024-05-19 17:33:24 +01:00
Tom Milligan
9f221abc12 ci: cache more cargo target dir 2024-05-19 17:30:47 +01:00
Tom Milligan
82c7bd4fd9 Merge pull request #176 from tommilligan/prep-1.16.0
chore: prep 1.16.0 release
2024-05-19 17:20:49 +01:00
Tom Milligan
9bca2a66df chore: prep 1.16.0 release 2024-05-19 17:13:16 +01:00
Tom Milligan
80cce8480c Merge pull request #174 from yannickseurin/custom-collapsible
Allow to set the collapsible property for each directive
2024-05-19 17:03:37 +01:00
Yannick Seurin
5d5b73ded6 allow to set collapsible default value for each directive 2024-05-19 16:57:21 +01:00
Tom Milligan
c17a66440c Merge pull request #175 from tommilligan/update-toml
chore: dependency and msrv upgrades
2024-05-19 11:21:54 +01:00
Tom Milligan
068a375647 chore: dependency and msrv upgrades 2024-05-19 11:05:09 +01:00
Tom Milligan
f04016d017 Merge pull request #173 from yannickseurin/snake-case
remove serde kebab-case renaming for AdmonitionDefaults struct
2024-05-19 09:49:38 +01:00
Tom Milligan
3f8bf86ac3 fix: allow css_id_prefix kebab cases for backcompat 2024-05-19 09:43:48 +01:00
Yannick Seurin
8045e217c9 remove serde kebab-case renaming 2024-04-29 22:53:11 +02:00
Tom Milligan
85fde44c09 Merge pull request #170 from meator/git-repository-url
book: Add GitHub link
2024-03-20 11:41:40 +00:00
meator
a2c3e49ef9 book: Add GitHub link 2024-03-19 21:41:02 +01:00
Tom Milligan
8a0ecc5dd1 Merge pull request #167 from tommilligan/prep-1.15.0
chore: prep v1.15.0 release
2023-12-30 21:41:13 +01:00
Tom Milligan
467f5f8f13 chore: prep v1.15.0 release 2023-12-30 20:37:34 +00:00
Tom Milligan
c4a05dae9d Merge pull request #165 from tommilligan/99-custom-admonitions
feat: custom directives
2023-12-30 21:20:35 +01:00
Tom Milligan
1bb14684c4 chore: use LF everywhere in ci 2023-12-30 20:14:53 +00:00
Tom Milligan
b3798b4d9f feat: custom directives 2023-12-30 20:14:53 +00:00
Tom Milligan
26c344a1e6 Merge pull request #163 from tommilligan/fix-161
fix: additional-css UNIX style path normalization
2023-12-18 18:00:43 +01:00
Tom Milligan
b1e6a5ee1e Merge pull request #164 from tommilligan/contributing
chore: split contributing guide
2023-12-18 13:25:57 +01:00
Tom Milligan
0d7b64d1b8 chore: split contributing guide 2023-12-18 12:25:34 +00:00
carlocorradini
9730cd0aaa fix: additional-css UNIX style path normalization 2023-12-18 12:20:54 +00:00
Tom Milligan
dc219f755d Merge pull request #155 from tommilligan/prep-1.14.0
chore: prep v0.14.0 release
2023-11-16 01:04:35 +01:00
Tom Milligan
a2524f890b chore: prep v0.14.0 release 2023-11-16 00:04:19 +00:00
Sky
c3207e4d16 Custom ids (#144)
* Custom ids

- You can now set custom CSS ids for admonishment blocks with the `id` field.
- You can now customize the default CSS id prefix (default is `"admonition-"`).

Co-authored-by: Tom Milligan <tom.milligan@uipath.com>
2023-11-15 22:34:21 +00:00
Tom Milligan
ab63c90231 Merge pull request #154 from joshka/main
fix: use correct case for TL;DR and FAQ
2023-11-15 22:43:20 +01:00
Josh McKinney
d5bdde1f5c fix: use correct case for TL;DR and FAQ
Fixes: #153
2023-11-15 13:24:56 -08:00
Tom Milligan
52ca8fc831 Merge pull request #152 from tommilligan/print-pdf-render
fix: better rendering for pdf/print view
2023-11-13 17:09:38 +01:00
Tom Milligan
e1ea411e9a fix: better rendering for pdf/print view 2023-11-13 15:46:58 +00:00
Tom Milligan
31d5a27a6d Merge pull request #143 from tommilligan/prep-1.13.1
chore: prep 1.13.1 release
2023-10-17 09:59:48 +01:00
Tom Milligan
0f0e02702c chore: prep 1.13.1 release 2023-10-17 09:37:32 +01:00
Tom Milligan
7235d5f349 Merge pull request #142 from tommilligan/rust-version
ci: add msrv to cargo toml, update mdbook version
2023-10-15 13:26:11 +01:00
Tom Milligan
0304995dbb ci: add msrv to cargo toml, update mdbook version 2023-10-15 13:11:01 +01:00
Tom Milligan
197d9cd059 Merge pull request #140 from tommilligan/prep-1.13.0
chore: prep v0.13.0 release
2023-10-06 15:21:27 +01:00
Tom Milligan
04ff932f1f chore: prep v0.13.0 release 2023-10-06 14:52:13 +01:00
Tom Milligan
1526a5d814 Merge pull request #139 from tommilligan/pr-137
prefix directive class names with 'admonish-'
2023-10-06 14:16:45 +01:00
phoenixr-codes
eb21495797 prefix directive class names with 'admonish-'
This change exists to prevent conflicts with the newly added 'warning' class by mdBook. See also: https://github.com/rust-lang/mdBook/blob/master/CHANGELOG.md#added
2023-10-06 13:43:54 +01:00
Tom Milligan
ebe6f7815c Merge pull request #138 from tommilligan/ci-prettier
ci: lint styles with prettier
2023-10-06 13:32:48 +01:00
Tom Milligan
c0c953c865 ci: lint styles with prettier 2023-10-06 13:15:45 +01:00
Tom Milligan
0fa34a66a0 Merge pull request #136 from tommilligan/deps-20231001
chore: deps 20231001
2023-10-02 08:50:53 +01:00
dependabot[bot]
dfc12c3652 chore(deps): bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-02 08:37:26 +01:00
dependabot[bot]
108edfffc5 chore(deps): bump JamesIves/github-pages-deploy-action
Bumps [JamesIves/github-pages-deploy-action](https://github.com/jamesives/github-pages-deploy-action) from 4.4.1 to 4.4.3.
- [Release notes](https://github.com/jamesives/github-pages-deploy-action/releases)
- [Commits](https://github.com/jamesives/github-pages-deploy-action/compare/v4.4.1...v4.4.3)

---
updated-dependencies:
- dependency-name: JamesIves/github-pages-deploy-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-02 08:37:26 +01:00
Tom Milligan
38f1933e8f chore: bump rust deps 2023-10-02 08:37:26 +01:00
Tom Milligan
a839038263 Merge pull request #128 from tommilligan/bug-127
markdown: fix panic when searching for indent
2023-09-19 13:02:43 +01:00
Tom Milligan
8501c812d9 chore: prep v1.12.1 2023-09-19 12:21:42 +01:00
Tom Milligan
933432afb2 markdown: fix panic when searching for indent 2023-09-19 12:21:42 +01:00
Tom Milligan
496e8f7c6d Merge pull request #126 from tommilligan/prep-1.12.0
chore: prep v1.12.0 release
2023-09-16 11:22:08 +01:00
Tom Milligan
1877fd1731 chore: upgrade internal dependencies 2023-09-16 10:58:17 +01:00
Tom Milligan
20286b3fae chore: prep v1.12.0 release 2023-09-16 10:55:53 +01:00
Tom Milligan
8e68cf919f Merge pull request #124 from tommilligan/bug-123
feat: support admonitions inside list items
2023-09-10 00:02:58 +01:00
Tom Milligan
02640dab1f feat: support admonitions inside list items 2023-09-09 23:44:58 +01:00
Tom Milligan
771e9c9fd8 Merge pull request #125 from tommilligan/prep-v1.11.1
chore: prepare v1.11.1 release
2023-09-09 23:43:48 +01:00
Tom Milligan
cce9343c47 chore: prepare v1.11.1 release 2023-09-09 23:32:10 +01:00
Tom Milligan
20b158966b Revert "chore: bump lockfile"
This reverts commit 39edc4d92a.
2023-09-09 23:24:26 +01:00
Tom Milligan
491f9cf341 Merge pull request #122 from tommilligan/fix-docs
docs: fix mdbook-toc build failure
2023-09-09 09:12:27 +01:00
Tom Milligan
6deaf1ea2b docs: fix mdbook-toc build failure 2023-09-09 09:12:07 +01:00
54 changed files with 5610 additions and 1704 deletions

View File

@@ -6,12 +6,12 @@ jobs:
# Fast test before we kick off all the other jobs
fast-test:
name: Fast test
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
steps:
- name: Checkout sources
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Cache build files
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
@@ -19,10 +19,9 @@ jobs:
target
key: fast-test-${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}
- name: Install toolchain
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
override: true
- name: Install more toolchain
run: rustup component add rustfmt clippy
- name: Run tests
@@ -32,28 +31,36 @@ jobs:
detailed-test:
needs: fast-test
name: Test main target
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
steps:
- name: Checkout sources
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Cache build files
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
~/.cargo/bin
cargo_target
/tmp/cargo-install-target-dir
key: detailed-test-${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}
- name: Install toolchain
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
override: true
components: rustfmt, clippy
- name: Enable Corepack
run: corepack enable
- name: Install node toolchain
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "yarn"
cache-dependency-path: compile_assets/yarn.lock
- name: Install additional test dependencies
env:
CARGO_TARGET_DIR: cargo_target
CARGO_TARGET_DIR: "/tmp/cargo-install-target-dir"
run: ./scripts/install
- name: Run check script
run: ./scripts/check
@@ -65,31 +72,41 @@ jobs:
strategy:
matrix:
os:
- ubuntu-20.04
# - windows-2019
- ubuntu-24.04
rust:
- stable
- beta
- 1.66.0
- 1.82.0
experimental:
- false
# Run a canary test on nightly that's allowed to fail
include:
- os: ubuntu-20.04
# Run a canary test on nightly that's allowed to fail
- os: ubuntu-24.04
rust: nightly
experimental: true
# Don't bother retesting stable linux, we did it in the comprehensive test
# Test only stable on Windows, presume we'd get same result on other
# versions as Linux
- os: windows-2022
rust: stable
experimental: false
exclude:
- os: ubuntu-20.04
# Don't bother retesting stable linux, we did it in the comprehensive test
- os: ubuntu-24.04
rust: stable
experimental: false
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.experimental }}
steps:
# This is required, otherwise we get files with CRLF on Windows
# Which causes tests relying on data loaded from files to fail
- name: Set git to use LF everywhere
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- name: Checkout sources
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Cache build files
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
@@ -97,9 +114,8 @@ jobs:
target
key: test-${{ matrix.os }}-${{ matrix.rust }}-cargo-${{ hashFiles('**/Cargo.toml') }}
- name: Install toolchain
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
override: true
- name: Run tests
run: cargo test

View File

@@ -25,7 +25,7 @@ jobs:
# binaries, so we use the same glibc version
#
# ref: https://github.com/rust-lang/mdBook/pull/1955
os: ubuntu-20.04
os: ubuntu-24.04
name: x86_64-unknown-linux-gnu.tar.gz
- target: x86_64-unknown-linux-musl
os: ubuntu-latest
@@ -39,23 +39,23 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Setup | Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
# Cache files between builds
- name: Setup | Cache Cargo
uses: actions/cache@v3
uses: actions/cache@v4
with:
# Note that we don't cache the `target` directory here
# so we do a completely clean rebuild for artefacts
path: |
~/.cargo/registry
~/.cargo/git
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Setup | Rust
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
override: true
profile: minimal
target: ${{ matrix.target }}
- name: Setup | cross
@@ -74,7 +74,7 @@ jobs:
- name: Post Setup | Extract tag name
shell: bash
run: echo "##[set-output name=tag;]$(echo ${GITHUB_REF#refs/tags/})"
run: echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
id: extract_tag
- name: Post Setup | Prepare artifacts [Windows]
@@ -92,10 +92,12 @@ jobs:
tar czvf ../../stage/${{ env.CRATE_NAME }}-${{ steps.extract_tag.outputs.tag }}-${{ matrix.name }} ${{ env.CRATE_NAME }}
cd -
- name: Post Setup | Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ env.CRATE_NAME }}-${{ steps.extract_tag.outputs.tag }}-${{ matrix.name }}
path: target/stage/*
# Idempotency: overwrite artefact by name if we're rerunning the deployment
overwrite: true
# Create GitHub release with Rust build targets and release notes
github_release:
@@ -104,23 +106,23 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Setup | Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup | Artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
- name: Setup | Extract version
shell: bash
run: echo "##[set-output name=version;]$(echo ${GITHUB_REF#refs/tags/v})"
run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
id: extract_version
- name: Setup | Release notes
run: |
cat CHANGELOG.md | sed -n '/^## ${{ steps.extract_version.outputs.version }}$/,/^## /p' | sed '$d' > RELEASE.md
- name: Build | Publish
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
files: ${{ env.CRATE_NAME }}-*/${{ env.CRATE_NAME }}-*
body_path: RELEASE.md
@@ -131,24 +133,22 @@ jobs:
publish:
name: Publish to crates.io
needs: github_release
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v3
- uses: actions/cache@v3
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
~/.cargo/bin
cargo_target
# We reuse the cache from our detailed test environment, if available
key: detailed-test-${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}
- name: Install toolchain
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
override: true
- name: Publish crate
env:
CARGO_LOGIN_TOKEN: ${{ secrets.CARGO_LOGIN_TOKEN }}

View File

@@ -11,32 +11,35 @@ permissions:
jobs:
publish:
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v3
- uses: actions/cache@v3
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
~/.cargo/bin
cargo_target
/tmp/cargo-install-target-dir
# We reuse the cache from our detailed test environment, if available
key: detailed-test-${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}
- name: Install toolchain
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
override: true
- name: Install mdbook
env:
CARGO_TARGET_DIR: "/tmp/cargo-install-target-dir"
run: ./scripts/install-mdbook
- name: Install mdbook extras
env:
CARGO_TARGET_DIR: "/tmp/cargo-install-target-dir"
run: ./book/scripts/install-mdbook-extras
- name: Build book
run: ./scripts/build-book
- name: Push docs
uses: JamesIves/github-pages-deploy-action@v4.4.1
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: gh-pages
folder: book/book

View File

@@ -1,8 +1,134 @@
# Changelog
## Unreleased
## v1.20.0
## 1.11.0
### Changed
- MSRV (minimum supported rust version) is now 1.82.0 ([#227](https://github.com/tommilligan/mdbook-admonish/pull/227))
- Collapsible blocks now show content in print view ([#228](https://github.com/tommilligan/mdbook-admonish/pull/228)). Thanks to [@igor-petruk](https://github.com/igor-petruk) for raising this issue.
## v1.19.0
### Changed
- MSRV (minimum supported rust version) is now 1.76.0 ([#208](https://github.com/tommilligan/mdbook-admonish/pull/208))
### Fixed
- Fixed blocks not rendering correctly in indented list items. Thanks to [@JorelAli](https://github.com/JorelAli) for the bug report! ([#224](https://github.com/tommilligan/mdbook-admonish/pull/224))
## v1.18.0
### Changed
- Add ARIA attributes to generated blocks. Thanks to [@toastal](https://github.com/toastal) for suggesting this feature! ([#195](https://github.com/tommilligan/mdbook-admonish/pull/195))
- Note: This subtly alters the emitted HTML, and could cause additional styles applied to blocks to break. Native `mdbook-admonish` styles are not affected.
### Fixed
- Fixed some valid configurations producing TOML serialization errors. Thanks to [@DianaNites](https://github.com/DianaNites) for reporting this! ([#197](https://github.com/tommilligan/mdbook-admonish/pull/197))
## v1.17.1
### Fixed
- Removed a stray debug statement ([#186](https://github.com/tommilligan/mdbook-admonish/pull/186))
## v1.17.0
### Changed
- Blocks should have key-value options separated by commas. Existing syntax remains is supported for back-compatibility. See [the documentation on Additional Options](https://tommilligan.github.io/mdbook-admonish/#additional-options) for more details ([#181](https://github.com/tommilligan/mdbook-admonish/pull/181))
### Fixed
- Titles contining `=` will now render correctly. Thanks to [@s00500](https://github.com/s00500) for the bug report! ([#181](https://github.com/tommilligan/mdbook-admonish/pull/181))
## v1.16.0
### Changed
- MSRV (minimum supported rust version) is now 1.74.0 ([#175](https://github.com/tommilligan/mdbook-admonish/pull/175))
- `custom` directives should now be configured under the `directive.custom` option. Existing `custom` configurations are supported for back compatibility ([#179](https://github.com/tommilligan/mdbook-admonish/pull/174))
### Added
- Make blocks `collapsible` on a per-directive basis. Thanks to [@yannickseurin](https://github.com/yannickseurin) for contributing this feature! ([#174](https://github.com/tommilligan/mdbook-admonish/pull/174))
### Fixed
- The `css_id_prefix` option now uses snake case for consistency (kebab case remains supported for back compatibility). Thanks to [@yannickseurin](https://github.com/yannickseurin) for fixing this! ([#173](https://github.com/tommilligan/mdbook-admonish/pull/173))
## 1.15.0
### Added
- Support [custom directives](https://tommilligan.github.io/mdbook-admonish/overview.html#custom-blocks) with the new `mdbook-admonish generate-custom` helper. See the [mdbook-admonish book](https://tommilligan.github.io/mdbook-admonish/overview.html#custom-blocks) for guidance. Thanks to [@Sky9x](https://github.com/Sky9x) for helping design this feature! ([#165](https://github.com/tommilligan/mdbook-admonish/pull/165))
### Fixed
- `additional-css` unix style path normalization. Thanks to [@carlocorradini](https://github.com/carlocorradini) for reporting and fixing! ([#163](https://github.com/tommilligan/mdbook-admonish/pull/163))
## 1.14.0
### Changed
- Styles version updated to `3.0.1`. Run `mdbook-admonish install` to update.
### Added
- You can now set custom CSS ids for admonition blocks with the `id` field. Thanks to [@Sky9x](https://github.com/Sky9x) for contributing this feature! ([#144](https://github.com/tommilligan/mdbook-admonish/pull/144))
- You can also now customize the CSS id prefix with the config option `default.css_id_prefix`
### Fixed
- Improve rendering of blocks in print/PDF view. Thanks to [@csk111165](https://github.com/csk111165) for the report ([#152](https://github.com/tommilligan/mdbook-admonish/issues/152))
- Fix the default titles for `tldr` and `faq` directives looking bad. They now render as `TL;DR` and `FAQ` by default. Thanks [@joshka](https://github.com/joshka) for fixing this! ([#154](https://github.com/tommilligan/mdbook-admonish/pull/154))
## 1.13.1
### Changed
- Bumped internal `mdbook` version to `0.4.35` ([#142](https://github.com/tommilligan/mdbook-admonish/pull/142))
### Fixed
- Relaxed `clap` dependency to fix compilation error when using other `mdbook-*` plugins. Thanks to [@joshka](https://github.com/joshka) for the [report](https://github.com/tommilligan/mdbook-admonish/pull/141)! ([#142](https://github.com/tommilligan/mdbook-admonish/pull/142))
## 1.13.0
### Changed
- Required styles version is now `^3.0.0` (release `1.13.0`). Run `mdbook-admonish install` to update.
- Internal CSS classnames for directives are now prefixed with `admonish-`, so `warning` is now `admonish-warning`. This avoids a conflict with upstream classnames introduced in `mdbook 0.4.35`. Thanks to [@phoenixr-codes](https://github.com/phoenixr-codes) for the report and fix! ([#139](https://github.com/tommilligan/mdbook-admonish/pull/139))
### Fixed
- Some minor inconsistencies in SCSS (and downstream CSS) styles were fixed by adopting Prettier linting ([#138](https://github.com/tommilligan/mdbook-admonish/pull/138))
## 1.12.1
### Fixed
- Panic when searching for an indent in non-ASCII content. Thanks to [@CoralPink](https://github.com/CoralPink) for the report! ([#128](https://github.com/tommilligan/mdbook-admonish/pull/128))
## 1.12.0
### Added
- Admonitions are now supported when indented inside other elements, such as a list. Thanks to [@mattburgess](https://github.com/mattburgess) for the report! ([#124](https://github.com/tommilligan/mdbook-admonish/pull/124))
## 1.11.1
### Fixed
- Reverted internal dependency upgrades that unintentionally increased MSRV from 1.66.0 in 1.11.0
## 1.11.0 (yanked)
**Note:** This release has been yanked.
It unintentionally increased the MSRV from 1.66.0
### Changed
@@ -43,7 +169,7 @@
### Changed
- Styles updated to `^2.0.1`. Run `mdbook-admonish install` to update.
- Styles version updated to `2.0.1`. Run `mdbook-admonish install` to update.
- MSRV (minimum supported rust version) is now 1.64.0 for clap v4 ([#79](https://github.com/tommilligan/mdbook-admonish/pull/79))
- More verbose error messages for invalid TOML configurations ([#79](https://github.com/tommilligan/mdbook-admonish/pull/79))
@@ -146,7 +272,11 @@ This behaviour is [documented in the readme here](https://github.com/tommilligan
- Flattened indentation of generated HTML, otherwise it's styled as a markdown code block
- Fixed edge cases where the info string changes length when parsed, causing title/body to be incorrectly split
## 1.3.0
## 1.3.0 (yanked)
**Note:** This release has been yanked.
It unintentionally introduced a serious parsing bug.
### Added

31
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,31 @@
# Contributing
## Workflow
Please submit a PR from a **new branch** in your fork.
Please do not submit a PR from your fork's `main` branch, as it makes collaborating on/editing the branch a pain.
## Project design
- Compiled CSS styles are built and committed from SCSS sources. See the `compile_assets` folder for details.
- `mdbook-admonish install` is responsible for delivering additional assets and configuration to a client book.
- `mdbook-admonish` is responsible for preprocessing book data, adding HTML that references compiled classnames.
## Scripts to get started
- `./scripts/install` installs other toolchains required for development
- `./scripts/check` runs a full CI check
- `./scripts/rebuild-book` rebuilds the reference book under `./book`. This is useful for integration testing locally.
## Making breaking changes in CSS
To make a breaking change in CSS, you should:
- Update the assets version in `./src/bin/assets/VERSION`
- Update the required assets version specifier in `./src/REQUIRED_ASSETS_VERSION`
You must make the next `mdbook-admonish` crate version at least a **minor** version bump.
## Releasing
Github workflows are setup such that pushing a `vX.Y.Z` tag will trigger a release to be cut.

1744
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,8 @@
[package]
name = "mdbook-admonish"
version = "1.11.0"
version = "1.20.0"
edition = "2021"
rust-version = "1.76.0"
authors = ["Tom Milligan <code@tommilligan.net>"]
description = "A preprocessor for mdbook to add Material Design admonishments."
@@ -25,22 +26,24 @@ name = "mdbook_admonish"
path = "src/lib.rs"
[dependencies]
anyhow = "1.0.72"
clap = { version = "4", default_features = false, features = ["std", "derive"], optional = true }
env_logger = { version = "0.10", default_features = false, optional = true }
log = "0.4.19"
mdbook = "0.4.34"
once_cell = "1.18.0"
pulldown-cmark = "0.9.3"
regex = "1.9.3"
semver = "1.0.18"
serde = { version = "1.0.183", features = ["derive"] }
serde_json = "1.0.104"
# The version of toml that mdbook uses internally (and uses in it's public api)
# Only used for compatilibilty with the mdbook public api
toml_mdbook = { package = "toml", version = "0.5.11" }
toml = "0.7.6"
toml_edit = { version = "0.19.14", optional = true }
anyhow = "1.0.86"
# Note: clap 4.4 increases MSRV to 1.70.0 (2023-06-01)
# To use MSRV supported dependencies, install using the lockfile with
# `cargo install mdbook-admonish --locked`
clap = { version = "4.5", default-features = false, features = ["std", "derive"], optional = true }
env_logger = { version = "0.11", default-features = false, optional = true }
log = "0.4.21"
mdbook = "0.4.40"
once_cell = "1.19.0"
path-slash = "0.2.1"
pulldown-cmark = "0.13"
regex = "1.10.5"
semver = "1.0.23"
serde = { version = "1.0.203", features = ["derive"] }
serde_json = "1.0.117"
toml = "0.8.14"
toml_edit = { version = "0.22.14", optional = true }
hex_color = { version = "3.0.0", features = ["serde"] }
[dev-dependencies]
pretty_assertions = "1.4.0"

View File

@@ -21,9 +21,11 @@ into this:
Read the documentation [here](https://tommilligan.github.io/mdbook-admonish/), to see the actual examples in action. You can see the source in the [`./book`](./book) subdirectory.
Other projects using mdbook-admonish:
Projects using mdbook-admonish include:
- [The Rhai Book](https://rhai.rs/book/)
- [The Rhai Book](https://rhai.rs/book/) ([source](https://github.com/rhaiscript/book))
- [The Trunk Guide](https://trunkrs.dev/guide/) ([source](https://github.com/trunk-rs/trunk))
- [PRQL language book](https://prql-lang.org/book/) ([source](https://github.com/PRQL/prql))
## Usage
@@ -70,6 +72,9 @@ Install the tool:
```bash
cargo install mdbook-admonish
# If you get compilation/installation errors, try a locked installation
cargo install mdbook-admonish --locked
```
Then let `mdbook-admonish` add the required files and configuration:
@@ -101,6 +106,21 @@ Then, build your book as usual:
mdbook path/to/book
```
### Reproducible builds
For a reproducible build suitable for use in CI or scripts, please:
- Pin to a specific version
- Install with lockfile dependencies
- Always install the latest CSS assets
```bash
cargo install mdbook-admonish --vers "1.5.0" --locked
mdbook-admonish install path/to/your/book
```
The Minimum Supported Rust Version (MSRV) is documented in `Cargo.toml`, and noted in the `CHANGELOG.md`. We aims to support around six months of stable Rust.
### Updates
**Please note**, when updating your version of `mdbook-admonish`, updated styles will not be applied unless you rerun `mdbook-admonish install` to update the additional CSS files in your book.
@@ -117,12 +137,6 @@ ERROR:
If you want to update across minor versions without breakage, you should always run `mdbook-admonish install`.
Alternatively, pin to a specific version for a reproducible installation:
```bash
cargo install mdbook-admonish --vers "1.5.0" --locked
```
### Process included files
You can ensure that content inlined with `{{#include}}` is also processed by [setting the `after` option](https://rust-lang.github.io/mdBook/format/configuration/preprocessors.html#require-a-certain-order):
@@ -149,30 +163,7 @@ Guarantees provided are as follows:
## Development
Project design
- Compiled CSS styles are built and committed from SCSS sources. See the `compile_assets` folder for details.
- `mdbook-admonish install` is responsible for delivering additional assets and configuration to a client book.
- `mdbook-admonish` is responsible for preprocessing book data, adding HTML that references compiled classnames.
### Scripts to get started
- `./scripts/install` installs other toolchains required for development
- `./scripts/check` runs a full CI check
- `./scripts/rebuild-book` rebuilds the reference book under `./book`. This is useful for integration testing locally.
### Making breaking changes in CSS
To make a breaking change in CSS, you should:
- Update the assets version in `./src/bin/assets/VERSION`
- Update the required assets version specifier in `./src/REQUIRED_ASSETS_VERSION`
You must make the next `mdbook-admonish` crate version at least a **minor** version bump.
### Releasing
Github workflows are setup such that pushing a `vX.Y.Z` tag will trigger a release to be cut.
See [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines on developing.
## Thanks

View File

@@ -4,12 +4,16 @@ language = "en"
multilingual = false
src = "src"
title = "The mdbook-admonish book"
git-repository-url = "https://github.com/tommilligan/mdbook-admonish"
[preprocessor]
[preprocessor.admonish]
command = "mdbook-admonish"
assets_version = "2.0.2" # do not edit: managed by `mdbook-admonish install`
assets_version = "3.1.0" # do not edit: managed by `mdbook-admonish install`
[preprocessor.admonish.directive.custom]
expensive = { icon = "./money-bag.svg", color = "#24ab38" }
[preprocessor.toc]
command = "mdbook-toc"
@@ -18,4 +22,4 @@ renderer = ["html"]
[output]
[output.html]
additional-css = ["./mdbook-admonish.css"]
additional-css = ["./mdbook-admonish.css", "./mdbook-admonish-custom.css"]

View File

@@ -0,0 +1,20 @@
:root {
--md-admonition-icon--admonish-expensive: url("data:image/svg+xml;charset=utf-8,<svg width='800px' height='800px' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --><!-- ref: https://www.svgrepo.com/svg/526038/money-bag --><!-- Used under CC Attribution License; Author: Solar Icons --><path fill-rule='evenodd' clip-rule='evenodd' d='M12.052 1.25H11.948C11.0495 1.24997 10.3003 1.24995 9.70552 1.32991C9.07773 1.41432 8.51093 1.59999 8.05546 2.05546C7.59999 2.51093 7.41432 3.07773 7.32991 3.70552C7.27259 4.13189 7.25637 5.15147 7.25179 6.02566C5.22954 6.09171 4.01536 6.32778 3.17157 7.17157C2 8.34315 2 10.2288 2 14C2 17.7712 2 19.6569 3.17157 20.8284C4.34314 22 6.22876 22 9.99998 22H14C17.7712 22 19.6569 22 20.8284 20.8284C22 19.6569 22 17.7712 22 14C22 10.2288 22 8.34315 20.8284 7.17157C19.9846 6.32778 18.7705 6.09171 16.7482 6.02566C16.7436 5.15147 16.7274 4.13189 16.6701 3.70552C16.5857 3.07773 16.4 2.51093 15.9445 2.05546C15.4891 1.59999 14.9223 1.41432 14.2945 1.32991C13.6997 1.24995 12.9505 1.24997 12.052 1.25ZM15.2479 6.00188C15.2434 5.15523 15.229 4.24407 15.1835 3.9054C15.1214 3.44393 15.0142 3.24644 14.8839 3.11612C14.7536 2.9858 14.5561 2.87858 14.0946 2.81654C13.6116 2.7516 12.964 2.75 12 2.75C11.036 2.75 10.3884 2.7516 9.90539 2.81654C9.44393 2.87858 9.24644 2.9858 9.11612 3.11612C8.9858 3.24644 8.87858 3.44393 8.81654 3.9054C8.771 4.24407 8.75661 5.15523 8.75208 6.00188C9.1435 6 9.55885 6 10 6H14C14.4412 6 14.8565 6 15.2479 6.00188ZM12 9.25C12.4142 9.25 12.75 9.58579 12.75 10V10.0102C13.8388 10.2845 14.75 11.143 14.75 12.3333C14.75 12.7475 14.4142 13.0833 14 13.0833C13.5858 13.0833 13.25 12.7475 13.25 12.3333C13.25 11.9493 12.8242 11.4167 12 11.4167C11.1758 11.4167 10.75 11.9493 10.75 12.3333C10.75 12.7174 11.1758 13.25 12 13.25C13.3849 13.25 14.75 14.2098 14.75 15.6667C14.75 16.857 13.8388 17.7155 12.75 17.9898V18C12.75 18.4142 12.4142 18.75 12 18.75C11.5858 18.75 11.25 18.4142 11.25 18V17.9898C10.1612 17.7155 9.25 16.857 9.25 15.6667C9.25 15.2525 9.58579 14.9167 10 14.9167C10.4142 14.9167 10.75 15.2525 10.75 15.6667C10.75 16.0507 11.1758 16.5833 12 16.5833C12.8242 16.5833 13.25 16.0507 13.25 15.6667C13.25 15.2826 12.8242 14.75 12 14.75C10.6151 14.75 9.25 13.7903 9.25 12.3333C9.25 11.143 10.1612 10.2845 11.25 10.0102V10C11.25 9.58579 11.5858 9.25 12 9.25Z' fill='%231C274C'/></svg>");
}
:is(.admonition):is(.admonish-expensive) {
border-color: #24ab38;
}
:is(.admonish-expensive) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(36, 171, 56, 0.1);
}
:is(.admonish-expensive) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #24ab38;
mask-image: var(--md-admonition-icon--admonish-expensive);
-webkit-mask-image: var(--md-admonition-icon--admonish-expensive);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}

6
book/money-bag.svg Normal file
View File

@@ -0,0 +1,6 @@
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<!-- ref: https://www.svgrepo.com/svg/526038/money-bag -->
<!-- Used under CC Attribution License; Author: Solar Icons -->
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.052 1.25H11.948C11.0495 1.24997 10.3003 1.24995 9.70552 1.32991C9.07773 1.41432 8.51093 1.59999 8.05546 2.05546C7.59999 2.51093 7.41432 3.07773 7.32991 3.70552C7.27259 4.13189 7.25637 5.15147 7.25179 6.02566C5.22954 6.09171 4.01536 6.32778 3.17157 7.17157C2 8.34315 2 10.2288 2 14C2 17.7712 2 19.6569 3.17157 20.8284C4.34314 22 6.22876 22 9.99998 22H14C17.7712 22 19.6569 22 20.8284 20.8284C22 19.6569 22 17.7712 22 14C22 10.2288 22 8.34315 20.8284 7.17157C19.9846 6.32778 18.7705 6.09171 16.7482 6.02566C16.7436 5.15147 16.7274 4.13189 16.6701 3.70552C16.5857 3.07773 16.4 2.51093 15.9445 2.05546C15.4891 1.59999 14.9223 1.41432 14.2945 1.32991C13.6997 1.24995 12.9505 1.24997 12.052 1.25ZM15.2479 6.00188C15.2434 5.15523 15.229 4.24407 15.1835 3.9054C15.1214 3.44393 15.0142 3.24644 14.8839 3.11612C14.7536 2.9858 14.5561 2.87858 14.0946 2.81654C13.6116 2.7516 12.964 2.75 12 2.75C11.036 2.75 10.3884 2.7516 9.90539 2.81654C9.44393 2.87858 9.24644 2.9858 9.11612 3.11612C8.9858 3.24644 8.87858 3.44393 8.81654 3.9054C8.771 4.24407 8.75661 5.15523 8.75208 6.00188C9.1435 6 9.55885 6 10 6H14C14.4412 6 14.8565 6 15.2479 6.00188ZM12 9.25C12.4142 9.25 12.75 9.58579 12.75 10V10.0102C13.8388 10.2845 14.75 11.143 14.75 12.3333C14.75 12.7475 14.4142 13.0833 14 13.0833C13.5858 13.0833 13.25 12.7475 13.25 12.3333C13.25 11.9493 12.8242 11.4167 12 11.4167C11.1758 11.4167 10.75 11.9493 10.75 12.3333C10.75 12.7174 11.1758 13.25 12 13.25C13.3849 13.25 14.75 14.2098 14.75 15.6667C14.75 16.857 13.8388 17.7155 12.75 17.9898V18C12.75 18.4142 12.4142 18.75 12 18.75C11.5858 18.75 11.25 18.4142 11.25 18V17.9898C10.1612 17.7155 9.25 16.857 9.25 15.6667C9.25 15.2525 9.58579 14.9167 10 14.9167C10.4142 14.9167 10.75 15.2525 10.75 15.6667C10.75 16.0507 11.1758 16.5833 12 16.5833C12.8242 16.5833 13.25 16.0507 13.25 15.6667C13.25 15.2826 12.8242 14.75 12 14.75C10.6151 14.75 9.25 13.7903 9.25 12.3333C9.25 11.143 10.1612 10.2845 11.25 10.0102V10C11.25 9.58579 11.5858 9.25 12 9.25Z" fill="#1C274C"/>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -5,5 +5,5 @@ set -exuo pipefail
cd "$(dirname "$0")"/../..
if ! mdbook-toc --version; then
cargo install mdbook-toc --version 0.13.0 --force
cargo install mdbook-toc --version 0.14.1 --force
fi

View File

@@ -2,3 +2,4 @@
- [Overview](./overview.md)
- [Reference](./reference.md)
- [Examples](./examples.md)

15
book/src/examples.md Normal file
View File

@@ -0,0 +1,15 @@
# Examples
## Combining multiple custom properties
Note that the comma `,` is used to seperate custom options.
````
```admonish quote collapsible=true, title='A title that really <span style="color: #e70073">pops</span>'
To really <b><span style="color: #e70073">grab</span></b> your reader's attention.
```
````
```admonish quote collapsible=true, title='A title that really <span style="color: #e70073">pops</span>'
To really <b><span style="color: #e70073">grab</span></b> your reader's attention.
```

View File

@@ -78,14 +78,19 @@ You can also configure the build to fail loudly, by setting `on_failure = "bail"
### Additional Options
You can pass additional options to each block. The options are structured as TOML key-value pairs.
You can pass additional options to each block. Options are given like a [TOML Inline Table](https://toml.io/en/v1.0.0#inline-table), as key-value pairs separated by commas.
`mdbook-admonish` parses options by wrapping your options in an inline table before parsing them, so please consult [The TOML Reference](https://toml.io) if you run into any syntax errors. Be aware that:
- Key-value pairs must be separated with a comma `,`
- TOML escapes must be escaped again - for instance, write `\"` as `\\"`.
- For complex strings such as HTML, you may want to use a [literal string](https://toml.io/en/v1.0.0#string) to avoid complex escape sequences
Note that some options can be passed globally, through the `default` section in `book.toml`. See the [configuration reference](./reference.md#booktoml-configuration) for more details.
#### Custom title
A custom title can be provided, contained in a double quoted TOML string.
Note that TOML escapes must be escaped again - for instance, write `\"` as `\\"`.
A custom title can be provided:
````
```admonish warning title="Data loss"
@@ -114,13 +119,13 @@ This will take a while, go and grab a drink of water.
Markdown and HTML can be used in the inner content, as you'd expect:
````
```admonish tip title="_Referencing_ and <i>dereferencing</i>"
```admonish tip title='_Referencing_ and <i>dereferencing</i>'
The opposite of *referencing* by using `&` is *dereferencing*, which is
accomplished with the <span style="color: hotpink">dereference operator</span>, `*`.
```
````
```admonish tip title="_Referencing_ and <i>dereferencing</i>"
```admonish tip title='_Referencing_ and <i>dereferencing</i>'
The opposite of *referencing* by using `&` is *dereferencing*, which is
accomplished with the <span style="color: hotpink">dereference operator</span>, `*`.
```
@@ -148,7 +153,7 @@ print "Hello, world!"
If you want to provide custom styling to a specific admonition, you can attach one or more custom classnames:
````
```admonish note class="custom-0 custom-1"
```admonish note title="Stylish", class="custom-0 custom-1"
Styled with my custom CSS class.
```
````
@@ -161,18 +166,79 @@ Will yield something like the following HTML, which you can then apply styles to
</div>
```
#### Custom CSS ID
If you want to customize the CSS `id` field, set `id="custom-id"`.
This will ignore [`default.css_id_prefix`](reference.md#default).
The default id is a normalized version of the admonishment's title,
prefixed with the `default.css_id_prefix`,
with an appended number if multiple blocks would have the same id.
Setting the `id` field will _ignore_ all other ids and the duplicate counter.
````
```admonish info title="My Info", id="my-special-info"
Link to this block with `#my-special-info` instead of the default `#admonition-my-info`.
```
````
#### Collapsible
For a block to be initially collapsible, and then be openable, set `collapsible=true`:
````
```admonish collapsible=true
```admonish title="Sneaky", collapsible=true
Content will be hidden initially.
```
````
Will yield something like the following HTML, which you can then apply styles to:
```admonish collapsible=true
```admonish title="Sneaky", collapsible=true
Content will be hidden initially.
```
### Custom blocks
You can add new block types via the `book.toml` config:
```toml
# book.toml
[[preprocessor.admonish.custom]]
directive = "expensive"
icon = "./money-bag.svg"
color = "#24ab38"
aliases = ["money", "cash", "budget"]
```
You must then generate the relevant CSS file, and reference it in the `output.html` section.
`mdbook-admonish` has a helper to quickly do this for you:
```bash
# Generates a file at ./mdbook-admonish-custom.css with your styles in
$ mdbook-admonish generate-custom ./mdbook-admonish-custom.css
```
```toml
# book.toml
[output.html]
# Reference the new file, so it's bundled in with book styles
additional-css = ["./mdbook-admonish.css", "./mdbook-admonish-custom.css"]
```
You can then reference the new directive (or alias) like usual in your blocks.
````
```admonish expensive
Remember, this operation costs money!
```
````
```admonish expensive
Remember, this operation costs money!
```
You can also set a default `title`. See the [Reference](./reference.md) page for more details.

View File

@@ -38,6 +38,7 @@ Subfields:
- `default.title` (optional): Title to use for blocks. Defaults to the directive used in titlecase.
- `default.collapsible` (optional, default: `false`): Make blocks collapsible by default when set to `true`.
- `default.css_id_prefix` (optional, default: `"admonition-"`): The default css id prefix to add to the id of all blocks. Ignored on blocks with an `id` field.
### `renderer`
@@ -74,6 +75,53 @@ Subfields:
- For the `html` renderer, the default value is `html`.
- For all other renderers, the default value is `preserve`.
### `directive`
Optional.
Settings relating to each type of block.
#### `builtin`
Optional.
Override the settings of a builtin directive.
The subkey of `builtin` is the directive to override. This must be the first directive listed in the [Directives](#directives) section below, e.g. `warning` (not `caution` or other aliases).
```toml
[preprocessor.admonish.directive.builtin.warning]
collapsible = true
```
Subfields:
- `collapsible` (optional): The default boolean value of the collapsible property for this type of block.
#### `custom`
Optional.
Additional types of block to support. The subkey of `custom` is the new directive to support.
You must run `mdbook-admonish generate-custom` after updating these values, to generate the correct styles.
```toml
[preprocessor.admonish.directive.custom.expensive]
icon = "./money-bag.svg"
color = "#24ab38"
collapsible = true
aliases = ["money", "cash", "budget"]
```
Subfields:
- `icon`: A filepath relative to the book root to load an SVG icon from.
- `color`: An RGB hex encoded color to use for the icon.
- `collapsible` (optional): The default boolean value of the collapsible property for this type of block.
- `aliases` (optional): One or more alternative directives to use this block.
- `title` (optional): The default title for this type of block. If not specified, defaults to the directive in title case. To give each alias a custom title, add multiple custom blocks.
### `command`
Required.
@@ -92,6 +140,8 @@ This is automatically updated by `mdbook-admonish install` and should not be edi
All supported directives are listed below.
Custom directives can be added via the `custom` config option above.
`note`
```admonish note

View File

@@ -1,3 +1,5 @@
/node_modules
*.css
*.css.map
.yarn/
.pnp*

View File

@@ -4,9 +4,17 @@
"main": "index.js",
"license": "MIT",
"scripts": {
"build": "sass --no-source-map scss/mdbook-admonish.scss ../src/bin/assets/mdbook-admonish.css"
"build": "yarn run build-prod && yarn run build-custom-expected",
"build-prod": "sass --no-source-map scss/mdbook-admonish.scss ../src/bin/assets/mdbook-admonish.css",
"build-custom-expected": "sass --no-source-map scss/mdbook-admonish-custom-expected.scss ../src/test_data/mdbook-admonish-custom-expected.css",
"lint": "prettier --check .",
"fix": "prettier --write ."
},
"dependencies": {
"sass": "^1.49.7"
}
},
"devDependencies": {
"prettier": "^3.0.3"
},
"packageManager": "yarn@4.8.1+sha512.bc946f2a022d7a1a38adfc15b36a66a3807a67629789496c3714dd1703d2e6c6b1c69ff9ec3b43141ac7a1dd853b7685638eb0074300386a59c18df351ef8ff6"
}

View File

@@ -22,6 +22,7 @@
@use "sass:color";
@use "sass:list";
@use "./lib";
// ----------------------------------------------------------------------------
// Variables
@@ -30,47 +31,48 @@
/// Admonition flavours
$admonitions: (
// pencil
note: $clr-blue-a200 "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/></svg>",
admonish-note: $clr-blue-a200
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/></svg>",
// clipboard-text
abstract summary tldr: $clr-light-blue-a400 "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17 9H7V7h10m0 6H7v-2h10m-3 6H7v-2h7M12 3a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z'/></svg>",
admonish-abstract admonish-summary admonish-tldr: $clr-light-blue-a400
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17 9H7V7h10m0 6H7v-2h10m-3 6H7v-2h7M12 3a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z'/></svg>",
// information
info todo: $clr-cyan-a700 "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z'/></svg>",
admonish-info admonish-todo: $clr-cyan-a700
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z'/></svg>",
// fire
tip hint important: $clr-teal-a700 "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17.66 11.2c-.23-.3-.51-.56-.77-.82-.67-.6-1.43-1.03-2.07-1.66C13.33 7.26 13 4.85 13.95 3c-.95.23-1.78.75-2.49 1.32-2.59 2.08-3.61 5.75-2.39 8.9.04.1.08.2.08.33 0 .22-.15.42-.35.5-.23.1-.47.04-.66-.12a.58.58 0 0 1-.14-.17c-1.13-1.43-1.31-3.48-.55-5.12C5.78 10 4.87 12.3 5 14.47c.06.5.12 1 .29 1.5.14.6.41 1.2.71 1.73 1.08 1.73 2.95 2.97 4.96 3.22 2.14.27 4.43-.12 6.07-1.6 1.83-1.66 2.47-4.32 1.53-6.6l-.13-.26c-.21-.46-.77-1.26-.77-1.26m-3.16 6.3c-.28.24-.74.5-1.1.6-1.12.4-2.24-.16-2.9-.82 1.19-.28 1.9-1.16 2.11-2.05.17-.8-.15-1.46-.28-2.23-.12-.74-.1-1.37.17-2.06.19.38.39.76.63 1.06.77 1 1.98 1.44 2.24 2.8.04.14.06.28.06.43.03.82-.33 1.72-.93 2.27z'/></svg>",
admonish-tip admonish-hint admonish-important: $clr-teal-a700
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17.66 11.2c-.23-.3-.51-.56-.77-.82-.67-.6-1.43-1.03-2.07-1.66C13.33 7.26 13 4.85 13.95 3c-.95.23-1.78.75-2.49 1.32-2.59 2.08-3.61 5.75-2.39 8.9.04.1.08.2.08.33 0 .22-.15.42-.35.5-.23.1-.47.04-.66-.12a.58.58 0 0 1-.14-.17c-1.13-1.43-1.31-3.48-.55-5.12C5.78 10 4.87 12.3 5 14.47c.06.5.12 1 .29 1.5.14.6.41 1.2.71 1.73 1.08 1.73 2.95 2.97 4.96 3.22 2.14.27 4.43-.12 6.07-1.6 1.83-1.66 2.47-4.32 1.53-6.6l-.13-.26c-.21-.46-.77-1.26-.77-1.26m-3.16 6.3c-.28.24-.74.5-1.1.6-1.12.4-2.24-.16-2.9-.82 1.19-.28 1.9-1.16 2.11-2.05.17-.8-.15-1.46-.28-2.23-.12-.74-.1-1.37.17-2.06.19.38.39.76.63 1.06.77 1 1.98 1.44 2.24 2.8.04.14.06.28.06.43.03.82-.33 1.72-.93 2.27z'/></svg>",
// check-bold
success check done: $clr-green-a700 "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m9 20.42-6.21-6.21 2.83-2.83L9 14.77l9.88-9.89 2.83 2.83L9 20.42z'/></svg>",
admonish-success admonish-check admonish-done: $clr-green-a700
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m9 20.42-6.21-6.21 2.83-2.83L9 14.77l9.88-9.89 2.83 2.83L9 20.42z'/></svg>",
// help-circle
question help faq: $clr-light-green-a700 "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m15.07 11.25-.9.92C13.45 12.89 13 13.5 13 15h-2v-.5c0-1.11.45-2.11 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41a2 2 0 0 0-2-2 2 2 0 0 0-2 2H8a4 4 0 0 1 4-4 4 4 0 0 1 4 4 3.2 3.2 0 0 1-.93 2.25M13 19h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10c0-5.53-4.5-10-10-10z'/></svg>",
admonish-question admonish-help admonish-faq: $clr-light-green-a700
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m15.07 11.25-.9.92C13.45 12.89 13 13.5 13 15h-2v-.5c0-1.11.45-2.11 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41a2 2 0 0 0-2-2 2 2 0 0 0-2 2H8a4 4 0 0 1 4-4 4 4 0 0 1 4 4 3.2 3.2 0 0 1-.93 2.25M13 19h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10c0-5.53-4.5-10-10-10z'/></svg>",
// alert
warning caution attention: $clr-orange-a400 "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 14h-2V9h2m0 9h-2v-2h2M1 21h22L12 2 1 21z'/></svg>",
admonish-warning admonish-caution admonish-attention: $clr-orange-a400
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 14h-2V9h2m0 9h-2v-2h2M1 21h22L12 2 1 21z'/></svg>",
// close-thick
failure fail missing: $clr-red-a200 "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 6.91 17.09 4 12 9.09 6.91 4 4 6.91 9.09 12 4 17.09 6.91 20 12 14.91 17.09 20 20 17.09 14.91 12 20 6.91z'/></svg>",
admonish-failure admonish-fail admonish-missing: $clr-red-a200
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 6.91 17.09 4 12 9.09 6.91 4 4 6.91 9.09 12 4 17.09 6.91 20 12 14.91 17.09 20 20 17.09 14.91 12 20 6.91z'/></svg>",
// lighting-bold
danger error: $clr-red-a400 "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11 15H6l7-14v8h5l-7 14v-8z'/></svg>",
admonish-danger admonish-error: $clr-red-a400
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11 15H6l7-14v8h5l-7 14v-8z'/></svg>",
// bug
bug: $clr-pink-a400 "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 12h-4v-2h4m0 6h-4v-2h4m6-6h-2.81a5.985 5.985 0 0 0-1.82-1.96L17 4.41 15.59 3l-2.17 2.17a6.002 6.002 0 0 0-2.83 0L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z'/></svg>",
admonish-bug: $clr-pink-a400
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 12h-4v-2h4m0 6h-4v-2h4m6-6h-2.81a5.985 5.985 0 0 0-1.82-1.96L17 4.41 15.59 3l-2.17 2.17a6.002 6.002 0 0 0-2.83 0L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z'/></svg>",
// format-list-numbered
example: $clr-deep-purple-a200 "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M7 13v-2h14v2H7m0 6v-2h14v2H7M7 7V5h14v2H7M3 8V5H2V4h2v4H3m-1 9v-1h3v4H2v-1h2v-.5H3v-1h1V17H2m2.25-7a.75.75 0 0 1 .75.75c0 .2-.08.39-.21.52L3.12 13H5v1H2v-.92L4 11H2v-1h2.25z'/></svg>",
admonish-example: $clr-deep-purple-a200
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M7 13v-2h14v2H7m0 6v-2h14v2H7M7 7V5h14v2H7M3 8V5H2V4h2v4H3m-1 9v-1h3v4H2v-1h2v-.5H3v-1h1V17H2m2.25-7a.75.75 0 0 1 .75.75c0 .2-.08.39-.21.52L3.12 13H5v1H2v-.92L4 11H2v-1h2.25z'/></svg>",
// format-quote-close
quote cite: $clr-grey "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z'/></svg>",
admonish-quote admonish-cite: $clr-grey
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z'/></svg>"
) !default;
// ----------------------------------------------------------------------------
// Rules: layout
// ----------------------------------------------------------------------------
// Admonition variables
:root {
@each $names, $props in $admonitions {
--md-admonition-icon--#{nth($names, 1)}:
url("data:image/svg+xml;charset=utf-8,#{nth($props, 2)}");
}
--md-details-icon:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8.59 16.58 13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42Z'/></svg>");
}
// ----------------------------------------------------------------------------
// Static content - base for all admonitions
// ----------------------------------------------------------------------------
// Admonition
:is(.admonition) {
@@ -86,10 +88,13 @@ $admonitions: (
border: 0 solid black;
border-inline-start-width: 0.4rem;
border-radius: 0.2rem;
box-shadow: 0 0.2rem 1rem rgba(0, 0, 0, 0.05), 0 0 0.1rem rgba(0, 0, 0, 0.1);;
box-shadow:
0 0.2rem 1rem rgba(0, 0, 0, 0.05),
0 0 0.1rem rgba(0, 0, 0, 0.1);
// [print]: Omit shadow as it may lead to rendering errors
// During print mode in browser, pdf renderer
@media print {
// Omit shadow as it may lead to rendering errors
box-shadow: none;
}
@@ -125,20 +130,22 @@ a.admonition-anchor-link {
position: absolute;
left: -1.2rem;
// Ensure we have enough padding, so that we can move the mouse to click on it
padding-right: 1.0rem;
padding-right: 1rem;
&:link, &:visited {
&:link,
&:visited {
// Don't make links colored (override to standard text color)
// variable provided downstream by mdbook
color: var(--fg);
}
&:link:hover, &:visited:hover {
&:link:hover,
&:visited:hover {
// No underline on hover
text-decoration: none;
}
&::before {
content: '§';
content: "§";
}
}
@@ -152,6 +159,9 @@ a.admonition-anchor-link {
padding-inline: 4.4rem 1.2rem;
font-weight: 700;
background-color: color.adjust($clr-blue-a200, $alpha: -0.9);
// Always print title bar tint
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
// Compatilility with rendering markdown inside the content
display: flex;
@@ -173,6 +183,9 @@ a.admonition-anchor-link {
width: 2rem;
height: 2rem;
background-color: $clr-blue-a200;
// Always print icon
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
mask-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"></svg>');
-webkit-mask-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"></svg>');
mask-repeat: no-repeat;
@@ -182,17 +195,24 @@ a.admonition-anchor-link {
content: "";
}
// Show anchor link on hover over title
&:hover a.admonition-anchor-link {
display: initial
display: initial;
}
}
// During print mode in browser, pdf renderer
@media print {
// Expand collapsed sections to show details
details.admonition::details-content {
display: contents;
}
}
summary.admonition-title {
details.admonition > &::after {
position: absolute;
top: .625em;
top: 0.625em;
inset-inline-end: 1.6rem;
height: 2rem;
width: 2rem;
@@ -205,55 +225,34 @@ summary.admonition-title {
-webkit-mask-size: contain;
content: "";
transform: rotate(0deg);
transition: transform .25s;
transition: transform 0.25s;
}
details[open].admonition > &::after {
transform: rotate(90deg);
}
}
// ----------------------------------------------------------------------------
// Rules: flavours
// ----------------------------------------------------------------------------
@each $names, $props in $admonitions {
$name: list.nth($names, 1);
$tint: list.nth($props, 1);
// Admonition flavour selectors
$flavours: ();
@each $name in $names {
$flavours: list.join($flavours, ".#{$name}", $separator: comma);
}
// Admonition flavour
:is(.admonition):is(#{$flavours}) {
border-color: $tint;
}
// Admonition flavour title
:is(#{$flavours}) > :is(.admonition-title, summary.admonition-title) {
background-color: color.adjust($tint, $alpha: -0.9);
// Admonition icon
&::before {
background-color: $tint;
mask-image: var(--md-admonition-icon--#{$name});
-webkit-mask-image: var(--md-admonition-icon--#{$name});
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
// Hide details marker for Safari that loves to show it anyway
// ref: https://github.com/tommilligan/mdbook-admonish/pull/185
&::-webkit-details-marker {
display: none;
}
}
:root {
--md-details-icon: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8.59 16.58 13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42Z'/></svg>");
}
// Generate rules for each specified admonition variant
@include lib.from-admonitions($admonitions);
// ----------------------------------------------------------------------------
// Rules: themes
// ----------------------------------------------------------------------------
//
// One rule per builtin theme in mdbook, overriding the default fg/bg if matched
//
// This must be after the variant CSS, so it can override they styles set there
.navy {
& :is(.admonition) {
@@ -261,7 +260,8 @@ summary.admonition-title {
}
}
.ayu, .coal {
.ayu,
.coal {
& :is(.admonition) {
background-color: var(--theme-hover);
}
@@ -272,8 +272,10 @@ summary.admonition-title {
background-color: var(--sidebar-bg);
color: var(--sidebar-fg);
}
& .admonition-anchor-link {
&:link, &:visited {
&:link,
&:visited {
color: var(--sidebar-fg);
}
}

View File

@@ -0,0 +1,52 @@
@use "sass:color";
@use "sass:list";
@mixin from-admonitions($admonitions) {
// ----------------------------------------------------------------------------
// Rules: layout
// ----------------------------------------------------------------------------
// Admonition variables
:root {
@each $names, $props in $admonitions {
--md-admonition-icon--#{nth($names, 1)}: url("data:image/svg+xml;charset=utf-8,#{nth($props, 2)}");
}
}
// ----------------------------------------------------------------------------
// Rules: flavours
// ----------------------------------------------------------------------------
@each $names, $props in $admonitions {
$name: list.nth($names, 1);
$tint: list.nth($props, 1);
// Admonition flavour selectors
$flavours: ();
@each $name in $names {
$flavours: list.join($flavours, ".#{$name}", $separator: comma);
}
// Admonition flavour
:is(.admonition):is(#{$flavours}) {
border-color: $tint;
}
// Admonition flavour title
:is(#{$flavours}) > :is(.admonition-title, summary.admonition-title) {
background-color: color.adjust($tint, $alpha: -0.9);
// Admonition icon
&::before {
background-color: $tint;
mask-image: var(--md-admonition-icon--#{$name});
-webkit-mask-image: var(--md-admonition-icon--#{$name});
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,16 @@
// This file aims to generate the subset of CSS specific to a single admonition directive.
//
// This is used for unit test data in the rust css generation module.
@use "sass:color";
@use "sass:list";
@use "./lib";
@import "./material-color";
$admonitions: (
admonish-note: $clr-blue-a200
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/></svg>",
) !default;
// Generate rules for each specified admonition variant
@include lib.from-admonitions($admonitions);

File diff suppressed because it is too large Load Diff

View File

@@ -9,13 +9,18 @@ title = "mdbook-admonish-integration"
[preprocessor.admonish]
command = "mdbook-admonish"
assets_version = "2.0.2" # do not edit: managed by `mdbook-admonish install`
assets_version = "3.1.0" # do not edit: managed by `mdbook-admonish install`
after = ["links"]
[[preprocessor.admonish.custom]]
directive = "frog"
icon = "./frog.svg"
color = "#9004CC"
[preprocessor.admonish.renderer.test]
render_mode = "strip"
[output]
[output.html]
additional-css = ["./mdbook-admonish.css"]
additional-css = ["./mdbook-admonish.css", "./mdbook-admonish-custom.css"]

View File

@@ -9,13 +9,18 @@ title = "mdbook-admonish-integration"
[preprocessor.admonish]
command = "mdbook-admonish"
assets_version = "2.0.2" # do not edit: managed by `mdbook-admonish install`
assets_version = "3.1.0" # do not edit: managed by `mdbook-admonish install`
after = ["links"]
[[preprocessor.admonish.custom]]
directive = "frog"
icon = "./frog.svg"
color = "#9004CC"
[preprocessor.admonish.renderer.test]
render_mode = "strip"
[output]
[output.html]
additional-css = ["./mdbook-admonish.css"]
additional-css = ["./mdbook-admonish.css", "./mdbook-admonish-custom.css"]

View File

@@ -1,81 +1,108 @@
<h1 id="chapter-1"><a class="header" href="#chapter-1">Chapter 1</a></h1>
<div id="admonition-what-is-this" class="admonition abstract">
<div id="admonition-what-is-this" class="admonition admonish-abstract" role="note" aria-labelledby="admonition-what-is-this-title">
<div class="admonition-title">
<div id="admonition-what-is-this-title">
<p>What <i>is</i> this?</p>
<p><a class="admonition-anchor-link" href="#admonition-what-is-this"></a></p>
</div>
<a class="admonition-anchor-link" href="#admonition-what-is-this"></a>
</div>
<div>
<p>This book acts as an integration test for <code>mdbook-admonish</code>.</p>
<p>It verifies that <code>mdbook</code> post-processes our generated HTML in the way we expect.</p>
</div>
</div>
<div id="admonition-note" class="admonition note">
<div id="admonition-note" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-title">
<div class="admonition-title">
<div id="admonition-note-title">
<p>Note</p>
<p><a class="admonition-anchor-link" href="#admonition-note"></a></p>
</div>
<a class="admonition-anchor-link" href="#admonition-note"></a>
</div>
<div>
<p>Simples</p>
</div>
</div>
<div id="admonition-default" class="admonition warning">
<div id="admonition-frog" class="admonition admonish-frog" role="note" aria-labelledby="admonition-frog-title">
<div class="admonition-title">
<div id="admonition-frog-title">
<p>Frog</p>
</div>
<a class="admonition-anchor-link" href="#admonition-frog"></a>
</div>
<div>
<p>Custom frog directive</p>
</div>
</div>
<div id="admonition-default" class="admonition admonish-warning" role="note">
<div>
<p>No title, only body</p>
</div>
</div>
<div id="admonition-error-rendering-admonishment" class="admonition bug">
<div id="admonition-error-rendering-admonishment" class="admonition admonish-bug" role="note" aria-labelledby="admonition-error-rendering-admonishment-title">
<div class="admonition-title">
<div id="admonition-error-rendering-admonishment-title">
<p>Error rendering admonishment</p>
<p><a class="admonition-anchor-link" href="#admonition-error-rendering-admonishment"></a></p>
</div>
<a class="admonition-anchor-link" href="#admonition-error-rendering-admonishment"></a>
</div>
<div>
<p>Failed with:</p>
<pre><code class="language-log">TOML parsing error: TOML parse error at line 1, column 8
<pre><code class="language-log">'title="' is not a valid directive or TOML key-value pair.
TOML parsing error: TOML parse error at line 1, column 21
|
1 | title=&quot;
| ^
1 | config = { title=" }
| ^
invalid basic string
</code></pre>
<p>Original markdown input:</p>
<pre><code class="language-markdown">```admonish title=&quot;
<pre><code class="language-markdown">```admonish title="
No title, only body
```
</code></pre>
</div>
</div>
<details id="admonition-note-1" class="admonition note">
<details id="admonition-note-1" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-1-title">
<summary class="admonition-title">
<div id="admonition-note-1-title">
<p>Note</p>
<p><a class="admonition-anchor-link" href="#admonition-note-1"></a></p>
</div>
<a class="admonition-anchor-link" href="#admonition-note-1"></a>
</summary>
<div>
<p>Hidden on load</p>
</div>
</details>
<div id="admonition-warning" class="admonition warning">
<div id="admonition-warning" class="admonition admonish-warning" role="note" aria-labelledby="admonition-warning-title">
<div class="admonition-title">
<div id="admonition-warning-title">
<p>Warning</p>
<p><a class="admonition-anchor-link" href="#admonition-warning"></a></p>
</div>
<a class="admonition-anchor-link" href="#admonition-warning"></a>
</div>
<div>
<p>This is a commonly shared warning!</p>
</div>
</div>
<div id="admonition-note-2" class="admonition note">
<div id="admonition-note-2" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-2-title">
<div class="admonition-title">
<div id="admonition-note-2-title">
<p>Note</p>
<p><a class="admonition-anchor-link" href="#admonition-note-2"></a></p>
</div>
<a class="admonition-anchor-link" href="#admonition-note-2"></a>
</div>
<div>
<pre><code class="language-bash">Nested code block
</code></pre>
</div>
</div>
<div id="admonition-note-3" class="admonition note">
<div id="admonition-note-3" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-3-title">
<div class="admonition-title">
<div id="admonition-note-3-title">
<p>Note</p>
<p><a class="admonition-anchor-link" href="#admonition-note-3"></a></p>
</div>
<a class="admonition-anchor-link" href="#admonition-note-3"></a>
</div>
<div>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
@@ -90,4 +117,48 @@ let x = 20;
<span class="boring">}</span></code></pre></pre>
</div>
</div>
<p>In a list:</p>
<ol>
<li>
<p>Thing one</p>
<pre><code class="language-sh">Thing one
</code></pre>
</li>
<li>
<p>Thing two</p>
<div id="admonition-note-4" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-4-title">
<div class="admonition-title">
<div id="admonition-note-4-title">
<p>Note</p>
</div>
<a class="admonition-anchor-link" href="#admonition-note-4"></a>
</div>
<div>
<p>Thing two</p>
</div>
</div>
<ol>
<li>
<p>Thing two nested</p>
<div id="admonition-note-5" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-5-title">
<div class="admonition-title">
<div id="admonition-note-5-title">
<p>Note</p>
</div>
<a class="admonition-anchor-link" href="#admonition-note-5"></a>
</div>
<div>
<p>Thing two nested (should not be a code block)
Regression tests for https://github.com/tommilligan/mdbook-admonish/issues/223</p>
</div>
</div>
</li>
</ol>
</li>
<li>
<p>Thing three</p>
<pre><code class="language-sh">Thing three
</code></pre>
</li>
</ol>

View File

@@ -0,0 +1,20 @@
:root {
--md-admonition-icon--admonish-frog: url("data:image/svg+xml;charset=utf-8,<svg height='800px' width='800px' version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 512 512' xml:space='preserve'><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --><!-- ref: https://www.svgrepo.com/svg/232692/frog --><!-- Used under CC0 Licence --><path d='M472,268.109H40c-4.418,0-8,3.582-8,8s3.582,8,8,8h432c4.418,0,8-3.582,8-8S476.418,268.109,472,268.109z'/><path d='M320,300.109H192c-4.418,0-8,3.582-8,8s3.582,8,8,8h128c4.418,0,8-3.582,8-8S324.418,300.109,320,300.109z'/><path d='M483.105,224.949c-9.717-8.094-13.741-11.93-11.921-21.398c5.588-10.328,8.816-22.464,8.816-35.441c0-21.712-9.036-41.065-23.058-53.523c-8.67-14.834-24.283-25.463-43.124-29.246c-20.05-4.027-40.644,0.458-55.09,11.994C321.682,126.917,290,140.109,256,140.109s-65.682-13.192-102.728-42.776c-14.446-11.535-35.04-16.02-55.09-11.994c-18.841,3.783-34.454,14.412-43.124,29.246C41.036,127.044,32,146.397,32,168.109c0,12.977,3.228,25.113,8.816,35.441c1.82,9.468-2.204,13.305-11.921,21.398C16.701,235.106,0,249.018,0,284.109c0,30.939,44.4,68.017,85.951,92.4c55.883,32.792,117.864,51.6,170.049,51.6s114.166-18.808,170.049-51.6C467.6,352.126,512,315.049,512,284.109C512,249.018,495.299,235.106,483.105,224.949z M464,168.109c0,10.585-2.696,20.437-7.312,28.661c-0.078,0.131-0.148,0.266-0.219,0.402c-7.916,13.829-21.31,22.937-36.47,22.937c-24.262,0-44-23.327-44-52s19.738-52,44-52c9.282,0,17.896,3.423,25.002,9.245c0.351,0.356,0.731,0.674,1.135,0.955C456.965,135.79,464,150.995,464,168.109z M65.857,126.314c0.407-0.282,0.791-0.603,1.144-0.962c7.105-5.82,15.718-9.242,24.999-9.242c24.262,0,44,23.327,44,52s-19.738,52-44,52c-15.159,0-28.552-9.107-36.468-22.935c-0.072-0.138-0.143-0.275-0.222-0.407C50.696,188.545,48,178.694,48,168.11C48,150.999,55.032,135.795,65.857,126.314z M417.951,362.709c-52.713,30.933-113.256,49.4-161.951,49.4s-109.238-18.467-161.951-49.4C48.095,335.744,16,303.422,16,284.109c0-27.596,11.761-37.393,23.135-46.867c5.547-4.621,11.494-9.583,15.006-16.426c10.334,9.552,23.514,15.293,37.858,15.293c33.084,0,60-30.505,60-68c0-33.803-21.879-61.914-50.429-67.125c15.396-3.025,30.975,0.273,41.717,8.852c40.075,32.002,74.837,46.273,112.712,46.273s72.637-14.271,112.712-46.273c10.743-8.579,26.321-11.878,41.717-8.852C381.879,106.195,360,134.307,360,168.109c0,37.495,26.916,68,60,68c14.344,0,27.524-5.741,37.858-15.293c3.512,6.842,9.459,11.805,15.006,16.426C484.239,246.717,496,256.513,496,284.109C496,303.422,463.905,335.744,417.951,362.709z'/><path d='M92,180.109c8.445,0,36-1.154,36-16s-27.555-16-36-16s-36,1.154-36,16S83.555,180.109,92,180.109z'/><path d='M384,164.109c0,14.846,27.555,16,36,16s36-1.154,36-16s-27.555-16-36-16S384,149.263,384,164.109z'/><path d='M232,228.109c4.418,0,8-3.582,8-8v-8c0-4.418-3.582-8-8-8s-8,3.582-8,8v8C224,224.528,227.582,228.109,232,228.109z'/><path d='M280,228.109c4.418,0,8-3.582,8-8v-8c0-4.418-3.582-8-8-8s-8,3.582-8,8v8C272,224.528,275.582,228.109,280,228.109z'/></svg>");
}
:is(.admonition):is(.admonish-frog) {
border-color: #9004cc;
}
:is(.admonish-frog) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(144, 4, 204, 0.1);
}
:is(.admonish-frog) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #9004cc;
mask-image: var(--md-admonition-icon--admonish-frog);
-webkit-mask-image: var(--md-admonition-icon--admonish-frog);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}

29
integration/frog.svg Normal file
View File

@@ -0,0 +1,29 @@
<svg height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512" xml:space="preserve">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<!-- ref: https://www.svgrepo.com/svg/232692/frog -->
<!-- Used under CC0 Licence -->
<path d="M472,268.109H40c-4.418,0-8,3.582-8,8s3.582,8,8,8h432c4.418,0,8-3.582,8-8S476.418,268.109,472,268.109z"/>
<path d="M320,300.109H192c-4.418,0-8,3.582-8,8s3.582,8,8,8h128c4.418,0,8-3.582,8-8S324.418,300.109,320,300.109z"/>
<path d="M483.105,224.949c-9.717-8.094-13.741-11.93-11.921-21.398c5.588-10.328,8.816-22.464,8.816-35.441
c0-21.712-9.036-41.065-23.058-53.523c-8.67-14.834-24.283-25.463-43.124-29.246c-20.05-4.027-40.644,0.458-55.09,11.994
C321.682,126.917,290,140.109,256,140.109s-65.682-13.192-102.728-42.776c-14.446-11.535-35.04-16.02-55.09-11.994
c-18.841,3.783-34.454,14.412-43.124,29.246C41.036,127.044,32,146.397,32,168.109c0,12.977,3.228,25.113,8.816,35.441
c1.82,9.468-2.204,13.305-11.921,21.398C16.701,235.106,0,249.018,0,284.109c0,30.939,44.4,68.017,85.951,92.4
c55.883,32.792,117.864,51.6,170.049,51.6s114.166-18.808,170.049-51.6C467.6,352.126,512,315.049,512,284.109
C512,249.018,495.299,235.106,483.105,224.949z M464,168.109c0,10.585-2.696,20.437-7.312,28.661
c-0.078,0.131-0.148,0.266-0.219,0.402c-7.916,13.829-21.31,22.937-36.47,22.937c-24.262,0-44-23.327-44-52s19.738-52,44-52
c9.282,0,17.896,3.423,25.002,9.245c0.351,0.356,0.731,0.674,1.135,0.955C456.965,135.79,464,150.995,464,168.109z M65.857,126.314
c0.407-0.282,0.791-0.603,1.144-0.962c7.105-5.82,15.718-9.242,24.999-9.242c24.262,0,44,23.327,44,52s-19.738,52-44,52
c-15.159,0-28.552-9.107-36.468-22.935c-0.072-0.138-0.143-0.275-0.222-0.407C50.696,188.545,48,178.694,48,168.11
C48,150.999,55.032,135.795,65.857,126.314z M417.951,362.709c-52.713,30.933-113.256,49.4-161.951,49.4
s-109.238-18.467-161.951-49.4C48.095,335.744,16,303.422,16,284.109c0-27.596,11.761-37.393,23.135-46.867
c5.547-4.621,11.494-9.583,15.006-16.426c10.334,9.552,23.514,15.293,37.858,15.293c33.084,0,60-30.505,60-68
c0-33.803-21.879-61.914-50.429-67.125c15.396-3.025,30.975,0.273,41.717,8.852c40.075,32.002,74.837,46.273,112.712,46.273
s72.637-14.271,112.712-46.273c10.743-8.579,26.321-11.878,41.717-8.852C381.879,106.195,360,134.307,360,168.109
c0,37.495,26.916,68,60,68c14.344,0,27.524-5.741,37.858-15.293c3.512,6.842,9.459,11.805,15.006,16.426
C484.239,246.717,496,256.513,496,284.109C496,303.422,463.905,335.744,417.951,362.709z"/>
<path d="M92,180.109c8.445,0,36-1.154,36-16s-27.555-16-36-16s-36,1.154-36,16S83.555,180.109,92,180.109z"/>
<path d="M384,164.109c0,14.846,27.555,16,36,16s36-1.154,36-16s-27.555-16-36-16S384,149.263,384,164.109z"/>
<path d="M232,228.109c4.418,0,8-3.582,8-8v-8c0-4.418-3.582-8-8-8s-8,3.582-8,8v8C224,224.528,227.582,228.109,232,228.109z"/>
<path d="M280,228.109c4.418,0,8-3.582,8-8v-8c0-4.418-3.582-8-8-8s-8,3.582-8,8v8C272,224.528,275.582,228.109,280,228.109z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,20 @@
:root {
--md-admonition-icon--admonish-frog: url("data:image/svg+xml;charset=utf-8,<svg height='800px' width='800px' version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 512 512' xml:space='preserve'><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --><!-- ref: https://www.svgrepo.com/svg/232692/frog --><!-- Used under CC0 Licence --><path d='M472,268.109H40c-4.418,0-8,3.582-8,8s3.582,8,8,8h432c4.418,0,8-3.582,8-8S476.418,268.109,472,268.109z'/><path d='M320,300.109H192c-4.418,0-8,3.582-8,8s3.582,8,8,8h128c4.418,0,8-3.582,8-8S324.418,300.109,320,300.109z'/><path d='M483.105,224.949c-9.717-8.094-13.741-11.93-11.921-21.398c5.588-10.328,8.816-22.464,8.816-35.441c0-21.712-9.036-41.065-23.058-53.523c-8.67-14.834-24.283-25.463-43.124-29.246c-20.05-4.027-40.644,0.458-55.09,11.994C321.682,126.917,290,140.109,256,140.109s-65.682-13.192-102.728-42.776c-14.446-11.535-35.04-16.02-55.09-11.994c-18.841,3.783-34.454,14.412-43.124,29.246C41.036,127.044,32,146.397,32,168.109c0,12.977,3.228,25.113,8.816,35.441c1.82,9.468-2.204,13.305-11.921,21.398C16.701,235.106,0,249.018,0,284.109c0,30.939,44.4,68.017,85.951,92.4c55.883,32.792,117.864,51.6,170.049,51.6s114.166-18.808,170.049-51.6C467.6,352.126,512,315.049,512,284.109C512,249.018,495.299,235.106,483.105,224.949z M464,168.109c0,10.585-2.696,20.437-7.312,28.661c-0.078,0.131-0.148,0.266-0.219,0.402c-7.916,13.829-21.31,22.937-36.47,22.937c-24.262,0-44-23.327-44-52s19.738-52,44-52c9.282,0,17.896,3.423,25.002,9.245c0.351,0.356,0.731,0.674,1.135,0.955C456.965,135.79,464,150.995,464,168.109z M65.857,126.314c0.407-0.282,0.791-0.603,1.144-0.962c7.105-5.82,15.718-9.242,24.999-9.242c24.262,0,44,23.327,44,52s-19.738,52-44,52c-15.159,0-28.552-9.107-36.468-22.935c-0.072-0.138-0.143-0.275-0.222-0.407C50.696,188.545,48,178.694,48,168.11C48,150.999,55.032,135.795,65.857,126.314z M417.951,362.709c-52.713,30.933-113.256,49.4-161.951,49.4s-109.238-18.467-161.951-49.4C48.095,335.744,16,303.422,16,284.109c0-27.596,11.761-37.393,23.135-46.867c5.547-4.621,11.494-9.583,15.006-16.426c10.334,9.552,23.514,15.293,37.858,15.293c33.084,0,60-30.505,60-68c0-33.803-21.879-61.914-50.429-67.125c15.396-3.025,30.975,0.273,41.717,8.852c40.075,32.002,74.837,46.273,112.712,46.273s72.637-14.271,112.712-46.273c10.743-8.579,26.321-11.878,41.717-8.852C381.879,106.195,360,134.307,360,168.109c0,37.495,26.916,68,60,68c14.344,0,27.524-5.741,37.858-15.293c3.512,6.842,9.459,11.805,15.006,16.426C484.239,246.717,496,256.513,496,284.109C496,303.422,463.905,335.744,417.951,362.709z'/><path d='M92,180.109c8.445,0,36-1.154,36-16s-27.555-16-36-16s-36,1.154-36,16S83.555,180.109,92,180.109z'/><path d='M384,164.109c0,14.846,27.555,16,36,16s36-1.154,36-16s-27.555-16-36-16S384,149.263,384,164.109z'/><path d='M232,228.109c4.418,0,8-3.582,8-8v-8c0-4.418-3.582-8-8-8s-8,3.582-8,8v8C224,224.528,227.582,228.109,232,228.109z'/><path d='M280,228.109c4.418,0,8-3.582,8-8v-8c0-4.418-3.582-8-8-8s-8,3.582-8,8v8C272,224.528,275.582,228.109,280,228.109z'/></svg>");
}
:is(.admonition):is(.admonish-frog) {
border-color: #9004cc;
}
:is(.admonish-frog) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(144, 4, 204, 0.1);
}
:is(.admonish-frog) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #9004cc;
mask-image: var(--md-admonition-icon--admonish-frog);
-webkit-mask-image: var(--md-admonition-icon--admonish-frog);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}

View File

@@ -34,6 +34,27 @@ if [ "$DIFF_RESULT" != 0 ]; then
exit 1
fi
eprintln "Generating custom CSS (book)"
mdbook-admonish generate-custom mdbook-admonish-custom.css
eprintln "Verifying generated book custom css"
set +e
diff -u \
"expected/mdbook-admonish-custom.css" \
"./mdbook-admonish-custom.css"
DIFF_RESULT=$?
set -e
if [ "$DIFF_RESULT" != 0 ]; then
eprintln ""
eprintln "error: generated custom css was different than expected"
eprintln ""
eprintln "error: If you expected the output to change, run:"
eprintln "cp ./integration/mdbook-admonish-custom.css ./integration/expected/mdbook-admonish-custom.css"
eprintln "and commit the result"
exit 1
fi
eprintln "Building mdbook"
mdbook build

View File

@@ -10,6 +10,10 @@ It verifies that `mdbook` post-processes our generated HTML in the way we expect
Simples
```
```admonish frog
Custom frog directive
```
```admonish warning ""
No title, only body
```
@@ -41,3 +45,30 @@ let x = 10;
let x = 20;
```
````
In a list:
1. Thing one
```sh
Thing one
```
1. Thing two
```admonish
Thing two
```
1. Thing two nested
```admonish
Thing two nested (should not be a code block)
Regression tests for https://github.com/tommilligan/mdbook-admonish/issues/223
```
1. Thing three
```sh
Thing three
```

View File

@@ -8,6 +8,34 @@ function eprintln() {
>&2 echo "$1"
}
# Node things
pushd compile_assets > /dev/null
eprintln "Linting style sources"
yarn run lint
eprintln "Checking compiled styles up to date"
COMITTED_ASSETS="$(cat ../src/bin/assets/mdbook-admonish.css)"
yarn run build
RECOMPILED_ASSETS="$(cat ../src/bin/assets/mdbook-admonish.css)"
set +e
diff -u <(printf "%s" "$COMITTED_ASSETS") <(printf "%s" "$RECOMPILED_ASSETS")
DIFF_RESULT=$?
set -e
if [ "$DIFF_RESULT" != 0 ]; then
eprintln ""
eprintln "error: committed assets are not up to date"
eprintln ""
eprintln "error: To update committed assets, please run"
eprintln "cd compile_assets && yarn run build"
eprintln "and commit the result"
exit 1
fi
popd > /dev/null
# Rust things
eprintln "Formatting sources"
cargo fmt -- --check
@@ -24,5 +52,6 @@ cargo test --no-default-features --features cli
eprintln "Building documentation"
cargo doc --no-deps --lib
# Integration test
eprintln "Running mdbook integration test"
./integration/scripts/check

View File

@@ -4,10 +4,21 @@
#
# Does not include offline node development stack (i.e. yarn)
set -exuo pipefail
set -euo pipefail
function eprintln() {
>&2 echo "$1"
}
cd "$(dirname "$0")"/..
eprintln "Installing additional Rust components"
rustup component add rustfmt clippy
eprintln "Installing mdbook"
./scripts/install-mdbook
eprintln "Installing node dependencies"
pushd compile_assets > /dev/null
yarn install --frozen-lockfile
popd > /dev/null

View File

@@ -1,9 +1,18 @@
#!/bin/bash
set -exuo pipefail
set -euo pipefail
cd "$(dirname "$0")"/..
if ! mdbook --version; then
cargo install mdbook --version 0.4.32 --force
function eprintln() {
>&2 echo "$1"
}
VERSION="0.4.51"
eprintln "Checking if mdbook $VERSION is installed"
if [[ "$(mdbook --version)" != "mdbook v$VERSION" ]]; then
eprintln "Installing mdbook $VERSION"
cargo install mdbook --version "$VERSION" --force
fi
eprintln "mdbook $VERSION is installed"

View File

@@ -1 +1 @@
^2.0.0
^3.0.0

View File

@@ -1 +1 @@
2.0.2
3.1.0

View File

@@ -1,33 +1,4 @@
@charset "UTF-8";
:root {
--md-admonition-icon--note:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/></svg>");
--md-admonition-icon--abstract:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17 9H7V7h10m0 6H7v-2h10m-3 6H7v-2h7M12 3a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z'/></svg>");
--md-admonition-icon--info:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z'/></svg>");
--md-admonition-icon--tip:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17.66 11.2c-.23-.3-.51-.56-.77-.82-.67-.6-1.43-1.03-2.07-1.66C13.33 7.26 13 4.85 13.95 3c-.95.23-1.78.75-2.49 1.32-2.59 2.08-3.61 5.75-2.39 8.9.04.1.08.2.08.33 0 .22-.15.42-.35.5-.23.1-.47.04-.66-.12a.58.58 0 0 1-.14-.17c-1.13-1.43-1.31-3.48-.55-5.12C5.78 10 4.87 12.3 5 14.47c.06.5.12 1 .29 1.5.14.6.41 1.2.71 1.73 1.08 1.73 2.95 2.97 4.96 3.22 2.14.27 4.43-.12 6.07-1.6 1.83-1.66 2.47-4.32 1.53-6.6l-.13-.26c-.21-.46-.77-1.26-.77-1.26m-3.16 6.3c-.28.24-.74.5-1.1.6-1.12.4-2.24-.16-2.9-.82 1.19-.28 1.9-1.16 2.11-2.05.17-.8-.15-1.46-.28-2.23-.12-.74-.1-1.37.17-2.06.19.38.39.76.63 1.06.77 1 1.98 1.44 2.24 2.8.04.14.06.28.06.43.03.82-.33 1.72-.93 2.27z'/></svg>");
--md-admonition-icon--success:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m9 20.42-6.21-6.21 2.83-2.83L9 14.77l9.88-9.89 2.83 2.83L9 20.42z'/></svg>");
--md-admonition-icon--question:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m15.07 11.25-.9.92C13.45 12.89 13 13.5 13 15h-2v-.5c0-1.11.45-2.11 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41a2 2 0 0 0-2-2 2 2 0 0 0-2 2H8a4 4 0 0 1 4-4 4 4 0 0 1 4 4 3.2 3.2 0 0 1-.93 2.25M13 19h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10c0-5.53-4.5-10-10-10z'/></svg>");
--md-admonition-icon--warning:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 14h-2V9h2m0 9h-2v-2h2M1 21h22L12 2 1 21z'/></svg>");
--md-admonition-icon--failure:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 6.91 17.09 4 12 9.09 6.91 4 4 6.91 9.09 12 4 17.09 6.91 20 12 14.91 17.09 20 20 17.09 14.91 12 20 6.91z'/></svg>");
--md-admonition-icon--danger:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11 15H6l7-14v8h5l-7 14v-8z'/></svg>");
--md-admonition-icon--bug:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 12h-4v-2h4m0 6h-4v-2h4m6-6h-2.81a5.985 5.985 0 0 0-1.82-1.96L17 4.41 15.59 3l-2.17 2.17a6.002 6.002 0 0 0-2.83 0L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z'/></svg>");
--md-admonition-icon--example:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M7 13v-2h14v2H7m0 6v-2h14v2H7M7 7V5h14v2H7M3 8V5H2V4h2v4H3m-1 9v-1h3v4H2v-1h2v-.5H3v-1h1V17H2m2.25-7a.75.75 0 0 1 .75.75c0 .2-.08.39-.21.52L3.12 13H5v1H2v-.92L4 11H2v-1h2.25z'/></svg>");
--md-admonition-icon--quote:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z'/></svg>");
--md-details-icon:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8.59 16.58 13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42Z'/></svg>");
}
:is(.admonition) {
display: flow-root;
margin: 1.5625em 0;
@@ -84,6 +55,8 @@ a.admonition-anchor-link::before {
padding-inline: 4.4rem 1.2rem;
font-weight: 700;
background-color: rgba(68, 138, 255, 0.1);
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
display: flex;
}
:is(.admonition-title, summary.admonition-title) p {
@@ -99,6 +72,8 @@ html :is(.admonition-title, summary.admonition-title):last-child {
width: 2rem;
height: 2rem;
background-color: #448aff;
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
mask-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"></svg>');
-webkit-mask-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"></svg>');
mask-repeat: no-repeat;
@@ -111,6 +86,11 @@ html :is(.admonition-title, summary.admonition-title):last-child {
display: initial;
}
@media print {
details.admonition::details-content {
display: contents;
}
}
details.admonition > summary.admonition-title::after {
position: absolute;
top: 0.625em;
@@ -131,205 +111,227 @@ details.admonition > summary.admonition-title::after {
details[open].admonition > summary.admonition-title::after {
transform: rotate(90deg);
}
summary.admonition-title::-webkit-details-marker {
display: none;
}
:is(.admonition):is(.note) {
:root {
--md-details-icon: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8.59 16.58 13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42Z'/></svg>");
}
:root {
--md-admonition-icon--admonish-note: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/></svg>");
--md-admonition-icon--admonish-abstract: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17 9H7V7h10m0 6H7v-2h10m-3 6H7v-2h7M12 3a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z'/></svg>");
--md-admonition-icon--admonish-info: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z'/></svg>");
--md-admonition-icon--admonish-tip: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17.66 11.2c-.23-.3-.51-.56-.77-.82-.67-.6-1.43-1.03-2.07-1.66C13.33 7.26 13 4.85 13.95 3c-.95.23-1.78.75-2.49 1.32-2.59 2.08-3.61 5.75-2.39 8.9.04.1.08.2.08.33 0 .22-.15.42-.35.5-.23.1-.47.04-.66-.12a.58.58 0 0 1-.14-.17c-1.13-1.43-1.31-3.48-.55-5.12C5.78 10 4.87 12.3 5 14.47c.06.5.12 1 .29 1.5.14.6.41 1.2.71 1.73 1.08 1.73 2.95 2.97 4.96 3.22 2.14.27 4.43-.12 6.07-1.6 1.83-1.66 2.47-4.32 1.53-6.6l-.13-.26c-.21-.46-.77-1.26-.77-1.26m-3.16 6.3c-.28.24-.74.5-1.1.6-1.12.4-2.24-.16-2.9-.82 1.19-.28 1.9-1.16 2.11-2.05.17-.8-.15-1.46-.28-2.23-.12-.74-.1-1.37.17-2.06.19.38.39.76.63 1.06.77 1 1.98 1.44 2.24 2.8.04.14.06.28.06.43.03.82-.33 1.72-.93 2.27z'/></svg>");
--md-admonition-icon--admonish-success: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m9 20.42-6.21-6.21 2.83-2.83L9 14.77l9.88-9.89 2.83 2.83L9 20.42z'/></svg>");
--md-admonition-icon--admonish-question: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m15.07 11.25-.9.92C13.45 12.89 13 13.5 13 15h-2v-.5c0-1.11.45-2.11 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41a2 2 0 0 0-2-2 2 2 0 0 0-2 2H8a4 4 0 0 1 4-4 4 4 0 0 1 4 4 3.2 3.2 0 0 1-.93 2.25M13 19h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10c0-5.53-4.5-10-10-10z'/></svg>");
--md-admonition-icon--admonish-warning: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 14h-2V9h2m0 9h-2v-2h2M1 21h22L12 2 1 21z'/></svg>");
--md-admonition-icon--admonish-failure: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 6.91 17.09 4 12 9.09 6.91 4 4 6.91 9.09 12 4 17.09 6.91 20 12 14.91 17.09 20 20 17.09 14.91 12 20 6.91z'/></svg>");
--md-admonition-icon--admonish-danger: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11 15H6l7-14v8h5l-7 14v-8z'/></svg>");
--md-admonition-icon--admonish-bug: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 12h-4v-2h4m0 6h-4v-2h4m6-6h-2.81a5.985 5.985 0 0 0-1.82-1.96L17 4.41 15.59 3l-2.17 2.17a6.002 6.002 0 0 0-2.83 0L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z'/></svg>");
--md-admonition-icon--admonish-example: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M7 13v-2h14v2H7m0 6v-2h14v2H7M7 7V5h14v2H7M3 8V5H2V4h2v4H3m-1 9v-1h3v4H2v-1h2v-.5H3v-1h1V17H2m2.25-7a.75.75 0 0 1 .75.75c0 .2-.08.39-.21.52L3.12 13H5v1H2v-.92L4 11H2v-1h2.25z'/></svg>");
--md-admonition-icon--admonish-quote: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z'/></svg>");
}
:is(.admonition):is(.admonish-note) {
border-color: #448aff;
}
:is(.note) > :is(.admonition-title, summary.admonition-title) {
:is(.admonish-note) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(68, 138, 255, 0.1);
}
:is(.note) > :is(.admonition-title, summary.admonition-title)::before {
:is(.admonish-note) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #448aff;
mask-image: var(--md-admonition-icon--note);
-webkit-mask-image: var(--md-admonition-icon--note);
mask-image: var(--md-admonition-icon--admonish-note);
-webkit-mask-image: var(--md-admonition-icon--admonish-note);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.abstract, .summary, .tldr) {
:is(.admonition):is(.admonish-abstract, .admonish-summary, .admonish-tldr) {
border-color: #00b0ff;
}
:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary.admonition-title) {
:is(.admonish-abstract, .admonish-summary, .admonish-tldr) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(0, 176, 255, 0.1);
}
:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary.admonition-title)::before {
:is(.admonish-abstract, .admonish-summary, .admonish-tldr) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #00b0ff;
mask-image: var(--md-admonition-icon--abstract);
-webkit-mask-image: var(--md-admonition-icon--abstract);
mask-image: var(--md-admonition-icon--admonish-abstract);
-webkit-mask-image: var(--md-admonition-icon--admonish-abstract);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.info, .todo) {
:is(.admonition):is(.admonish-info, .admonish-todo) {
border-color: #00b8d4;
}
:is(.info, .todo) > :is(.admonition-title, summary.admonition-title) {
:is(.admonish-info, .admonish-todo) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(0, 184, 212, 0.1);
}
:is(.info, .todo) > :is(.admonition-title, summary.admonition-title)::before {
:is(.admonish-info, .admonish-todo) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #00b8d4;
mask-image: var(--md-admonition-icon--info);
-webkit-mask-image: var(--md-admonition-icon--info);
mask-image: var(--md-admonition-icon--admonish-info);
-webkit-mask-image: var(--md-admonition-icon--admonish-info);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.tip, .hint, .important) {
:is(.admonition):is(.admonish-tip, .admonish-hint, .admonish-important) {
border-color: #00bfa5;
}
:is(.tip, .hint, .important) > :is(.admonition-title, summary.admonition-title) {
:is(.admonish-tip, .admonish-hint, .admonish-important) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(0, 191, 165, 0.1);
}
:is(.tip, .hint, .important) > :is(.admonition-title, summary.admonition-title)::before {
:is(.admonish-tip, .admonish-hint, .admonish-important) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #00bfa5;
mask-image: var(--md-admonition-icon--tip);
-webkit-mask-image: var(--md-admonition-icon--tip);
mask-image: var(--md-admonition-icon--admonish-tip);
-webkit-mask-image: var(--md-admonition-icon--admonish-tip);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.success, .check, .done) {
:is(.admonition):is(.admonish-success, .admonish-check, .admonish-done) {
border-color: #00c853;
}
:is(.success, .check, .done) > :is(.admonition-title, summary.admonition-title) {
:is(.admonish-success, .admonish-check, .admonish-done) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(0, 200, 83, 0.1);
}
:is(.success, .check, .done) > :is(.admonition-title, summary.admonition-title)::before {
:is(.admonish-success, .admonish-check, .admonish-done) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #00c853;
mask-image: var(--md-admonition-icon--success);
-webkit-mask-image: var(--md-admonition-icon--success);
mask-image: var(--md-admonition-icon--admonish-success);
-webkit-mask-image: var(--md-admonition-icon--admonish-success);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.question, .help, .faq) {
:is(.admonition):is(.admonish-question, .admonish-help, .admonish-faq) {
border-color: #64dd17;
}
:is(.question, .help, .faq) > :is(.admonition-title, summary.admonition-title) {
:is(.admonish-question, .admonish-help, .admonish-faq) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(100, 221, 23, 0.1);
}
:is(.question, .help, .faq) > :is(.admonition-title, summary.admonition-title)::before {
:is(.admonish-question, .admonish-help, .admonish-faq) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #64dd17;
mask-image: var(--md-admonition-icon--question);
-webkit-mask-image: var(--md-admonition-icon--question);
mask-image: var(--md-admonition-icon--admonish-question);
-webkit-mask-image: var(--md-admonition-icon--admonish-question);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.warning, .caution, .attention) {
:is(.admonition):is(.admonish-warning, .admonish-caution, .admonish-attention) {
border-color: #ff9100;
}
:is(.warning, .caution, .attention) > :is(.admonition-title, summary.admonition-title) {
:is(.admonish-warning, .admonish-caution, .admonish-attention) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(255, 145, 0, 0.1);
}
:is(.warning, .caution, .attention) > :is(.admonition-title, summary.admonition-title)::before {
:is(.admonish-warning, .admonish-caution, .admonish-attention) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #ff9100;
mask-image: var(--md-admonition-icon--warning);
-webkit-mask-image: var(--md-admonition-icon--warning);
mask-image: var(--md-admonition-icon--admonish-warning);
-webkit-mask-image: var(--md-admonition-icon--admonish-warning);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.failure, .fail, .missing) {
:is(.admonition):is(.admonish-failure, .admonish-fail, .admonish-missing) {
border-color: #ff5252;
}
:is(.failure, .fail, .missing) > :is(.admonition-title, summary.admonition-title) {
:is(.admonish-failure, .admonish-fail, .admonish-missing) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(255, 82, 82, 0.1);
}
:is(.failure, .fail, .missing) > :is(.admonition-title, summary.admonition-title)::before {
:is(.admonish-failure, .admonish-fail, .admonish-missing) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #ff5252;
mask-image: var(--md-admonition-icon--failure);
-webkit-mask-image: var(--md-admonition-icon--failure);
mask-image: var(--md-admonition-icon--admonish-failure);
-webkit-mask-image: var(--md-admonition-icon--admonish-failure);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.danger, .error) {
:is(.admonition):is(.admonish-danger, .admonish-error) {
border-color: #ff1744;
}
:is(.danger, .error) > :is(.admonition-title, summary.admonition-title) {
:is(.admonish-danger, .admonish-error) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(255, 23, 68, 0.1);
}
:is(.danger, .error) > :is(.admonition-title, summary.admonition-title)::before {
:is(.admonish-danger, .admonish-error) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #ff1744;
mask-image: var(--md-admonition-icon--danger);
-webkit-mask-image: var(--md-admonition-icon--danger);
mask-image: var(--md-admonition-icon--admonish-danger);
-webkit-mask-image: var(--md-admonition-icon--admonish-danger);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.bug) {
:is(.admonition):is(.admonish-bug) {
border-color: #f50057;
}
:is(.bug) > :is(.admonition-title, summary.admonition-title) {
:is(.admonish-bug) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(245, 0, 87, 0.1);
}
:is(.bug) > :is(.admonition-title, summary.admonition-title)::before {
:is(.admonish-bug) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #f50057;
mask-image: var(--md-admonition-icon--bug);
-webkit-mask-image: var(--md-admonition-icon--bug);
mask-image: var(--md-admonition-icon--admonish-bug);
-webkit-mask-image: var(--md-admonition-icon--admonish-bug);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.example) {
:is(.admonition):is(.admonish-example) {
border-color: #7c4dff;
}
:is(.example) > :is(.admonition-title, summary.admonition-title) {
:is(.admonish-example) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(124, 77, 255, 0.1);
}
:is(.example) > :is(.admonition-title, summary.admonition-title)::before {
:is(.admonish-example) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #7c4dff;
mask-image: var(--md-admonition-icon--example);
-webkit-mask-image: var(--md-admonition-icon--example);
mask-image: var(--md-admonition-icon--admonish-example);
-webkit-mask-image: var(--md-admonition-icon--admonish-example);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.quote, .cite) {
:is(.admonition):is(.admonish-quote, .admonish-cite) {
border-color: #9e9e9e;
}
:is(.quote, .cite) > :is(.admonition-title, summary.admonition-title) {
:is(.admonish-quote, .admonish-cite) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(158, 158, 158, 0.1);
}
:is(.quote, .cite) > :is(.admonition-title, summary.admonition-title)::before {
:is(.admonish-quote, .admonish-cite) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #9e9e9e;
mask-image: var(--md-admonition-icon--quote);
-webkit-mask-image: var(--md-admonition-icon--quote);
mask-image: var(--md-admonition-icon--admonish-quote);
-webkit-mask-image: var(--md-admonition-icon--admonish-quote);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
@@ -340,7 +342,8 @@ details[open].admonition > summary.admonition-title::after {
background-color: var(--sidebar-bg);
}
.ayu :is(.admonition), .coal :is(.admonition) {
.ayu :is(.admonition),
.coal :is(.admonition) {
background-color: var(--theme-hover);
}

View File

@@ -1,13 +1,12 @@
use anyhow::Result;
use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use mdbook::{
errors::Error,
preprocess::{CmdPreprocessor, Preprocessor},
};
use mdbook::preprocess::{CmdPreprocessor, Preprocessor};
use mdbook_admonish::Admonish;
#[cfg(feature = "cli-install")]
use serde::Deserialize;
use std::fs;
use std::io;
use std::path::PathBuf;
use std::{io, process};
use std::process;
/// mdbook preprocessor to add support for admonitions
#[derive(Parser)]
@@ -36,6 +35,18 @@ enum Commands {
#[arg(long)]
css_dir: Option<PathBuf>,
},
/// Generate CSS file for custom directives.
GenerateCustom {
/// Root directory for the book, should contain the configuration file (`book.toml`)
///
/// If not set, defaults to the current directory.
#[arg(long)]
dir: Option<PathBuf>,
/// File to write generated css to.
output: PathBuf,
},
}
fn main() {
@@ -62,10 +73,13 @@ fn run(cli: Cli) -> Result<()> {
dir.unwrap_or_else(|| PathBuf::from(".")),
css_dir.unwrap_or_else(|| PathBuf::from(".")),
),
Some(Commands::GenerateCustom { dir, output }) => {
handle_generate_custom(dir.unwrap_or_else(|| PathBuf::from(".")), output)
}
}
}
fn handle_preprocessing() -> Result<(), Error> {
fn handle_preprocessing() -> std::result::Result<(), mdbook::errors::Error> {
let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?;
if ctx.mdbook_version != mdbook::MDBOOK_VERSION {
@@ -94,15 +108,59 @@ fn handle_supports(renderer: String) -> ! {
}
}
#[derive(Deserialize)]
struct Config {
#[serde(default)]
preprocessor: Preprocessors,
}
#[derive(Default, Deserialize)]
struct Preprocessors {
#[serde(default)]
admonish: Option<toml::Table>,
#[serde(flatten)]
_other: toml::Table,
}
/// Load the plugin specific config as a toml string, for private deserialization.
fn admonish_config_string(config: &Config) -> Result<String> {
Ok(toml::to_string(
&config
.preprocessor
.admonish
.as_ref()
.context("No configuration for mdbook-admonish in book.toml")?,
)?)
}
fn handle_generate_custom(proj_dir: PathBuf, output: PathBuf) -> Result<()> {
let config = proj_dir.join("book.toml");
log::info!("Reading configuration file '{}'", config.display());
let data = fs::read_to_string(&config)
.with_context(|| format!("can't read configuration file '{}'", config.display()))?;
let config: Config = toml::from_str(&data).context("Invalid configuration file")?;
let css =
mdbook_admonish::custom::css_from_config(&proj_dir, &admonish_config_string(&config)?)?;
log::info!("Writing custom CSS file '{}'", output.display());
fs::write(output, css)?;
Ok(())
}
#[cfg(feature = "cli-install")]
mod install {
use anyhow::{Context, Result};
use path_slash::PathExt;
use std::borrow::Cow;
use std::path::Path;
use std::{
fs::{self, File},
io::Write,
path::PathBuf,
};
use toml_edit::{self, Array, Document, Item, Table, Value};
use toml_edit::{self, Array, DocumentMut, Item, Table, Value};
const ADMONISH_CSS_FILES: &[(&str, &[u8])] = &[(
"mdbook-admonish.css",
@@ -122,13 +180,23 @@ mod install {
}
}
// Normalize path to UNIX style.
// This avoids conflicts/rewriting files when projects are used under different
// operating systems (e.g. on Windows, after being used on Linux)
//
// https://github.com/tommilligan/mdbook-admonish/issues/161
fn normalize_config_file_path(path: &Path) -> Result<Cow<'_, str>> {
path.to_slash()
.context("UNIX style path normalization error")
}
pub fn handle_install(proj_dir: PathBuf, css_dir: PathBuf) -> Result<()> {
let config = proj_dir.join("book.toml");
log::info!("Reading configuration file '{}'", config.display());
let toml = fs::read_to_string(&config)
.with_context(|| format!("can't read configuration file '{}'", config.display()))?;
let mut doc = toml
.parse::<Document>()
.parse::<DocumentMut>()
.context("configuration is not valid TOML")?;
if let Ok(preprocessor) = preprocessor(&mut doc) {
@@ -139,7 +207,7 @@ mod install {
);
preprocessor["assets_version"] = value;
} else {
log::info!("Unexpected configuration, not updating prereprocessor configuration");
log::info!("Unexpected configuration, not updating preprocessor configuration");
};
let mut additional_css = additional_css(&mut doc);
@@ -148,12 +216,13 @@ mod install {
// Normalize path to remove no-op components
// https://github.com/tommilligan/mdbook-admonish/issues/47
let filepath: PathBuf = filepath.components().collect();
let filepath_str = filepath.to_str().context("non-utf8 filepath")?;
if let Ok(ref mut additional_css) = additional_css {
if !additional_css.contains_str(filepath_str) {
let filepath_str = normalize_config_file_path(&filepath)?;
if !additional_css.contains_str(&filepath_str) {
log::info!("Adding '{filepath_str}' to 'additional-css'");
additional_css.push(filepath_str);
additional_css.push(filepath_str.as_ref());
}
} else {
log::warn!("Unexpected configuration, not updating 'additional-css'");
@@ -190,7 +259,7 @@ A beautifully styled message.
/// Return the `additional-css` field, initializing if required.
///
/// Return `Err` if the existing configuration is unknown.
fn additional_css(doc: &mut Document) -> Result<&mut Array, ()> {
fn additional_css(doc: &mut DocumentMut) -> Result<&mut Array, ()> {
let doc = doc.as_table_mut();
let empty_table = Item::Table(Table::default());
@@ -214,7 +283,7 @@ A beautifully styled message.
/// Return the preprocessor table for admonish, initializing if required.
///
/// Return `Err` if the existing configuration is unknown.
fn preprocessor(doc: &mut Document) -> Result<&mut Item, ()> {
fn preprocessor(doc: &mut DocumentMut) -> Result<&mut Item, ()> {
let doc = doc.as_table_mut();
let empty_table = Item::Table(Table::default());
@@ -227,4 +296,24 @@ A beautifully styled message.
item["command"] = toml_edit::value("mdbook-admonish");
Ok(item)
}
#[cfg(test)]
mod test {
use super::*;
/// This test seems redundant, but would fail on Windows.
///
/// We want to always convert to a fixed output string style, independant
/// of runtime platform, and forward slashes in relative paths are fine on
/// Windows.
#[test]
fn test_normalize_config_file_path() {
let input = PathBuf::from(".")
.join("css-dir")
.join("mdbook-admonish.css");
let expected = "./css-dir/mdbook-admonish.css";
let actual = normalize_config_file_path(&input).unwrap();
assert_eq!(actual.as_ref(), expected);
}
}
}

View File

@@ -2,23 +2,62 @@ use anyhow::{Context, Result};
use mdbook::preprocess::PreprocessorContext;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use crate::types::AdmonitionDefaults;
use crate::types::{AdmonitionDefaults, BuiltinDirective, BuiltinDirectiveConfig};
/// Loads the plugin configuration from mdbook internals.
///
/// Roundtrips config to string, to avoid linking the plugin's internal version of toml
/// to the one publically exposed by the mdbook library.
pub(crate) fn admonish_config_from_context(ctx: &PreprocessorContext) -> Result<Config> {
let table: String = toml_mdbook::to_string(
let table: String = toml::to_string(
ctx.config
.get_preprocessor("admonish")
.context("No configuration for mdbook-admonish in book.toml")?,
)?;
toml::from_str(&table).context("Invalid mdbook-admonish configuration in book.toml")
)
.context("Could not serialize mdbook-admonish config. This is a bug in the toml library.")?;
admonish_config_from_str(&table)
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub(crate) fn admonish_config_from_str(data: &str) -> Result<Config> {
let readonly: ConfigReadonly =
toml::from_str(data).context("Invalid mdbook-admonish configuration in book.toml")?;
let config = readonly.into();
log::debug!("Loaded admonish config: {:?}", config);
Ok(config)
}
/// All valid input states including back-compatibility fields.
///
/// This struct deliberately does not implement Serialize as it never meant to
/// be written, only converted to Config.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Default)]
struct ConfigReadonly {
#[serde(default)]
pub on_failure: OnFailure,
#[serde(default)]
pub default: AdmonitionDefaults,
#[serde(default)]
pub renderer: HashMap<String, RendererConfig>,
#[serde(default)]
pub assets_version: Option<String>,
#[serde(default)]
pub custom: Vec<CustomDirectiveReadonly>,
#[serde(default)]
pub builtin: HashMap<BuiltinDirective, BuiltinDirectiveConfig>,
#[serde(default)]
pub directive: DirectiveConfig,
}
/// The canonical config format, without back-compatibility
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Default)]
pub(crate) struct Config {
#[serde(default)]
pub on_failure: OnFailure,
@@ -31,6 +70,79 @@ pub(crate) struct Config {
#[serde(default)]
pub assets_version: Option<String>,
#[serde(default)]
pub directive: DirectiveConfig,
}
impl From<ConfigReadonly> for Config {
fn from(other: ConfigReadonly) -> Self {
let ConfigReadonly {
on_failure,
default,
renderer,
assets_version,
custom,
builtin,
mut directive,
} = other;
// Merge deprecated config fields into main config object
directive.custom.extend(
custom
.into_iter()
.map(|CustomDirectiveReadonly { directive, config }| (directive, config)),
);
directive.builtin.extend(builtin);
Self {
on_failure,
default,
renderer,
assets_version,
directive,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Default)]
pub(crate) struct DirectiveConfig {
#[serde(default)]
pub custom: HashMap<String, CustomDirective>,
#[serde(default)]
pub builtin: HashMap<BuiltinDirective, BuiltinDirectiveConfig>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub(crate) struct CustomDirective {
/// Path to an SVG file, relative to the book root.
pub icon: PathBuf,
/// Primary color for this directive.
pub color: hex_color::HexColor,
/// Alternative directives the user can specify
#[serde(default)]
pub aliases: Vec<String>,
/// Title to use, human readable.
#[serde(default)]
pub title: Option<String>,
/// Default collapsible value.
#[serde(default)]
pub collapsible: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub(crate) struct CustomDirectiveReadonly {
/// The primary directive. Used for CSS classnames
pub directive: String,
/// Path to an SVG file, relative to the book root.
#[serde(flatten)]
config: CustomDirective,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
@@ -58,3 +170,156 @@ impl Default for OnFailure {
Self::Continue
}
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
use crate::types::BuiltinDirective;
#[test]
fn empty_config_okay() -> Result<()> {
let actual = admonish_config_from_str("")?;
let expected = Config::default();
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn css_id_prefix_kebab_case_allowed() -> Result<()> {
let expected = Config {
default: AdmonitionDefaults {
css_id_prefix: Some("flam-".to_owned()),
..Default::default()
},
..Default::default()
};
// Snake case okay
let actual = admonish_config_from_str(r#"default = { css_id_prefix = "flam-" }"#)?;
assert_eq!(actual, expected);
// Kebab case back-compat okay
let actual = admonish_config_from_str(r#"default = { css-id-prefix = "flam-" }"#)?;
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn merge_old_and_new_custom_directives() -> Result<()> {
let serialized = r##"
[directive.custom.purple]
icon = "/tmp/test-directive.svg"
color = "#9B4F96"
aliases = ["test-directive-alias-0"]
title = "Purple"
collapsible = true
[[custom]]
directive = "blue"
icon = "/tmp/test-directive.svg"
color = "#0038A8"
aliases = []
title = "Blue"
"##;
let expected = Config {
directive: DirectiveConfig {
custom: HashMap::from([
(
"purple".to_owned(),
CustomDirective {
icon: PathBuf::from("/tmp/test-directive.svg"),
color: hex_color::HexColor::from((155, 79, 150)),
aliases: vec!["test-directive-alias-0".to_owned()],
title: Some("Purple".to_owned()),
collapsible: Some(true),
},
),
(
"blue".to_owned(),
CustomDirective {
icon: PathBuf::from("/tmp/test-directive.svg"),
color: hex_color::HexColor::from((0, 56, 168)),
aliases: vec![],
title: Some("Blue".to_owned()),
collapsible: None,
},
),
]),
..Default::default()
},
..Default::default()
};
let actual = admonish_config_from_str(serialized)?;
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn full_config_roundtrip() -> Result<()> {
let input = Config {
default: AdmonitionDefaults {
css_id_prefix: Some("flam-".to_owned()),
collapsible: true,
title: Some("".to_owned()),
},
assets_version: Some("1.1.1".to_owned()),
directive: DirectiveConfig {
custom: HashMap::from([(
"test-directive".to_owned(),
CustomDirective {
icon: PathBuf::from("/tmp/test-directive.svg"),
color: hex_color::HexColor::from((155, 79, 150)),
aliases: vec!["test-directive-alias-0".to_owned()],
title: Some("test-directive-title".to_owned()),
collapsible: Some(true),
},
)]),
builtin: HashMap::from([(
BuiltinDirective::Warning,
BuiltinDirectiveConfig {
collapsible: Some(true),
},
)]),
},
on_failure: OnFailure::Bail,
renderer: HashMap::from([(
"test-mode".to_owned(),
RendererConfig {
render_mode: Some(RenderMode::Strip),
},
)]),
};
let expected = r##"on_failure = "bail"
assets_version = "1.1.1"
[default]
title = ""
collapsible = true
css_id_prefix = "flam-"
[renderer.test-mode]
render_mode = "strip"
[directive.custom.test-directive]
icon = "/tmp/test-directive.svg"
color = "#9B4F96"
aliases = ["test-directive-alias-0"]
title = "test-directive-title"
collapsible = true
[directive.builtin.warning]
collapsible = true
"##;
let serialized = toml::to_string(&input)?;
assert_eq!(serialized, expected);
let actual = admonish_config_from_str(&serialized)?;
assert_eq!(actual, input);
Ok(())
}
}

View File

@@ -1,14 +1,17 @@
mod toml_wrangling;
mod v1;
mod v2;
mod v3;
/// Configuration as described by the instance of an admonition in markdown.
///
/// This structure represents the configuration the user must provide in each
/// instance.
#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Default)]
pub(crate) struct InstanceConfig {
pub(crate) directive: String,
pub(crate) title: Option<String>,
pub(crate) id: Option<String>,
pub(crate) additional_classnames: Vec<String>,
pub(crate) collapsible: Option<bool>,
}
@@ -34,20 +37,29 @@ impl InstanceConfig {
/// - `Some(InstanceConfig)` if this is an `admonish` block
pub fn from_info_string(info_string: &str) -> Option<Result<Self, String>> {
let config_string = admonition_config_string(info_string)?;
Some(Self::from_admonish_config_string(config_string))
}
// If we succeed at parsing v2, return that. Otherwise hold onto the error
let config_v2_error = match v2::from_config_string(config_string) {
Ok(config) => return Some(Ok(config)),
Err(config) => config,
/// Parse an info string that is known to be for `admonish`.
fn from_admonish_config_string(config_string: &str) -> Result<Self, String> {
// If we succeed at parsing v3, return that. Otherwise hold onto the error
let config_v3_error = match v3::from_config_string(config_string) {
Ok(config) => return Ok(config),
Err(error) => error,
};
Some(if let Ok(config) = v1::from_config_string(config_string) {
// If we succeed at parsing v1, return that.
Ok(config)
} else {
// Otherwise return our v2 error.
Err(config_v2_error)
})
// If we succeed at parsing v2, return that
if let Ok(config) = v2::from_config_string(config_string) {
return Ok(config);
};
// If we succeed at parsing v1, return that.
if let Ok(config) = v1::from_config_string(config_string) {
return Ok(config);
}
// Otherwise return our v3 error.
Err(config_v3_error)
}
}
@@ -69,18 +81,37 @@ mod test {
InstanceConfig {
directive: "note".to_owned(),
title: None,
id: None,
additional_classnames: vec!["additional-classname".to_owned()],
collapsible: None,
}
);
// v2 syntax is supported
assert_eq!(
InstanceConfig::from_info_string(r#"admonish title="Custom Title" type="question""#)
.unwrap()
.unwrap(),
InstanceConfig::from_info_string(
r#"admonish title="Custom Title" type="question" id="my-id""#
)
.unwrap()
.unwrap(),
InstanceConfig {
directive: "question".to_owned(),
title: Some("Custom Title".to_owned()),
id: Some("my-id".to_owned()),
additional_classnames: Vec::new(),
collapsible: None,
}
);
// v3 syntax is supported
assert_eq!(
InstanceConfig::from_info_string(
r#"admonish title="Custom Title", type="question", id="my-id""#
)
.unwrap()
.unwrap(),
InstanceConfig {
directive: "question".to_owned(),
title: Some("Custom Title".to_owned()),
id: Some("my-id".to_owned()),
additional_classnames: Vec::new(),
collapsible: None,
}

View File

@@ -0,0 +1,44 @@
use once_cell::sync::Lazy;
use regex::Regex;
use serde::Deserialize;
use std::fmt::Display;
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub(crate) struct UserInput {
#[serde(default)]
pub r#type: Option<String>,
#[serde(default)]
pub title: Option<String>,
#[serde(default)]
pub id: Option<String>,
#[serde(default)]
pub class: Option<String>,
#[serde(default)]
pub collapsible: Option<bool>,
}
impl UserInput {
pub fn classnames(&self) -> Vec<String> {
self.class
.as_ref()
.map(|class| {
class
.split(' ')
.filter(|classname| !classname.is_empty())
.map(|classname| classname.to_owned())
.collect()
})
.unwrap_or_default()
}
}
pub(crate) static RX_DIRECTIVE: Lazy<Regex> =
Lazy::new(|| Regex::new(r#"^[A-Za-z0-9_-]+$"#).expect("directive regex"));
pub(crate) fn format_toml_parsing_error(error: impl Display) -> String {
format!("TOML parsing error: {error}")
}
pub(crate) fn format_invalid_directive(directive: &str, original_error: impl Display) -> String {
format!("'{directive}' is not a valid directive or TOML key-value pair.\n\n{original_error}")
}

View File

@@ -52,6 +52,7 @@ pub(crate) fn from_config_string(config_string: &str) -> Result<InstanceConfig,
Ok(InstanceConfig {
directive: directive.to_owned(),
title,
id: None,
additional_classnames,
collapsible: None,
})
@@ -69,6 +70,7 @@ mod test {
InstanceConfig {
directive: "".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
}
@@ -78,6 +80,7 @@ mod test {
InstanceConfig {
directive: "".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
}
@@ -87,6 +90,7 @@ mod test {
InstanceConfig {
directive: "unknown".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
}
@@ -96,6 +100,7 @@ mod test {
InstanceConfig {
directive: "note".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
}
@@ -105,6 +110,7 @@ mod test {
InstanceConfig {
directive: "note".to_owned(),
title: None,
id: None,
additional_classnames: vec!["additional-classname".to_owned()],
collapsible: None,
}

View File

@@ -1,28 +1,16 @@
use super::toml_wrangling::{
format_invalid_directive, format_toml_parsing_error, UserInput, RX_DIRECTIVE,
};
use super::InstanceConfig;
use once_cell::sync::Lazy;
use regex::Regex;
use serde::Deserialize;
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
struct UserInput {
#[serde(default)]
r#type: Option<String>,
#[serde(default)]
title: Option<String>,
#[serde(default)]
class: Option<String>,
#[serde(default)]
collapsible: Option<bool>,
}
/// Transform our config string into valid toml
fn bare_key_value_pairs_to_toml(pairs: &str) -> String {
use regex::Captures;
static RX_BARE_KEY_ASSIGNMENT: Lazy<Regex> = Lazy::new(|| {
let bare_key = r#"[A-Za-z0-9_-]+"#;
Regex::new(&format!("(?:{bare_key}) *=")).expect("bare key assignment regex")
});
static RX_BARE_KEY_ASSIGNMENT: Lazy<Regex> =
Lazy::new(|| Regex::new(r#"(?:[A-Za-z0-9_-]+) *="#).expect("bare key assignment regex"));
fn prefix_with_newline(captures: &Captures) -> String {
format!(
@@ -39,10 +27,18 @@ fn bare_key_value_pairs_to_toml(pairs: &str) -> String {
.into_owned()
}
fn user_input_from_config_toml(config_toml: &str) -> Result<UserInput, String> {
toml::from_str(config_toml).map_err(format_toml_parsing_error)
}
/// Parse and return the config assuming v2 format.
///
/// Note that if an error occurs, a parsed struct that can be returned to
/// show the error message will be returned.
///
/// The basic idea here is to accept space separated key-value pairs, break them
/// onto separate lines, and then parse them as a TOML document.
/// This breaks when values contain a literal '=' sign, for which v3 syntax should be used.
pub(crate) fn from_config_string(config_string: &str) -> Result<InstanceConfig, String> {
let config_toml = bare_key_value_pairs_to_toml(config_string);
let config_toml = config_toml.trim();
@@ -50,7 +46,7 @@ pub(crate) fn from_config_string(config_string: &str) -> Result<InstanceConfig,
let config: UserInput = match toml::from_str(config_toml) {
Ok(config) => config,
Err(error) => {
let original_error = Err(format!("TOML parsing error: {error}"));
let original_error = format_toml_parsing_error(error);
// For ergonomic reasons, we allow users to specify the directive without
// a key. So if parsing fails initially, take the first line,
@@ -60,17 +56,11 @@ pub(crate) fn from_config_string(config_string: &str) -> Result<InstanceConfig,
None => (config_toml, ""),
};
static RX_DIRECTIVE: Lazy<Regex> =
Lazy::new(|| Regex::new(r#"^[A-Za-z0-9_-]+$"#).expect("directive regex"));
if !RX_DIRECTIVE.is_match(directive) {
return original_error;
return Err(format_invalid_directive(directive, original_error));
}
let mut config: UserInput = match toml::from_str(config_toml) {
Ok(config) => config,
Err(_) => return original_error,
};
let mut config = user_input_from_config_toml(config_toml)?;
config.r#type = Some(directive.to_owned());
config
}
@@ -88,6 +78,7 @@ pub(crate) fn from_config_string(config_string: &str) -> Result<InstanceConfig,
Ok(InstanceConfig {
directive: config.r#type.unwrap_or_default(),
title: config.title,
id: config.id,
additional_classnames,
collapsible: config.collapsible,
})
@@ -99,80 +90,120 @@ mod test {
use pretty_assertions::assert_eq;
#[test]
fn test_from_config_string_v2() {
assert_eq!(
from_config_string("").unwrap(),
fn test_from_config_string_v2() -> Result<(), ()> {
fn check(config_string: &str, expected: InstanceConfig) -> Result<(), ()> {
let actual = match from_config_string(config_string) {
Ok(config) => config,
Err(error) => panic!("Expected config to be valid, got error:\n\n{}", error),
};
assert_eq!(actual, expected);
Ok(())
}
check(
"",
InstanceConfig {
directive: "".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
}
);
assert_eq!(
from_config_string(" ").unwrap(),
},
)?;
check(
" ",
InstanceConfig {
directive: "".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
}
);
assert_eq!(
from_config_string(
r#"type="note" class="additional classname" title="Никита" collapsible=true"#
)
.unwrap(),
},
)?;
check(
r#"type="note" class="additional classname" title="Никита" collapsible=true"#,
InstanceConfig {
directive: "note".to_owned(),
title: Some("Никита".to_owned()),
id: None,
additional_classnames: vec!["additional".to_owned(), "classname".to_owned()],
collapsible: Some(true),
}
);
},
)?;
// Specifying unknown keys is okay, as long as they're valid
assert_eq!(
from_config_string(r#"unkonwn="but valid toml""#).unwrap(),
check(
r#"unkonwn="but valid toml""#,
InstanceConfig {
directive: "".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
}
);
},
)?;
// Just directive is fine
assert_eq!(
from_config_string(r#"info"#).unwrap(),
check(
r#"info"#,
InstanceConfig {
directive: "info".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
}
);
},
)?;
// Directive plus toml config
assert_eq!(
from_config_string(r#"info title="Information" collapsible=false"#).unwrap(),
check(
r#"info title="Information" collapsible=false"#,
InstanceConfig {
directive: "info".to_owned(),
title: Some("Information".to_owned()),
id: None,
additional_classnames: Vec::new(),
collapsible: Some(false),
}
);
},
)?;
// Test custom id
check(
r#"info title="My Info" id="my-info-custom-id""#,
InstanceConfig {
directive: "info".to_owned(),
title: Some("My Info".to_owned()),
id: Some("my-info-custom-id".to_owned()),
additional_classnames: Vec::new(),
collapsible: None,
},
)?;
// Directive after toml config is an error
assert!(from_config_string(r#"title="Information" info"#).is_err());
Ok(())
}
#[test]
fn test_from_config_string_invalid_directive() {
assert_eq!(
from_config_string(r#"oh!wow titlel=""#).unwrap_err(),
r#"'oh!wow' is not a valid directive or TOML key-value pair.
TOML parsing error: TOML parse error at line 1, column 3
|
1 | oh!wow
| ^
expected `.`, `=`
"#
);
}
#[test]
fn test_from_config_string_invalid_toml_value() {
assert_eq!(
from_config_string(r#"note titlel=""#).unwrap_err(),
r#"TOML parsing error: TOML parse error at line 1, column 6
r#"TOML parsing error: TOML parse error at line 1, column 9
|
1 | note
| ^
expected `.`, `=`
1 | titlel="
| ^
invalid basic string
"#
);
}

202
src/config/v3.rs Normal file
View File

@@ -0,0 +1,202 @@
use super::toml_wrangling::{
format_invalid_directive, format_toml_parsing_error, UserInput, RX_DIRECTIVE,
};
use super::InstanceConfig;
use serde::Deserialize;
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
struct Wrapper<T> {
config: T,
}
/// Transform our config string into valid toml
fn bare_inline_table_to_toml(pairs: &str) -> String {
format!("config = {{ {pairs} }}")
}
fn user_input_from_config_string(config_string: &str) -> Result<UserInput, String> {
match toml::from_str::<Wrapper<_>>(&bare_inline_table_to_toml(config_string)) {
Ok(wrapper) => Ok(wrapper.config),
Err(error) => Err(format_toml_parsing_error(error)),
}
}
/// Parse and return the config assuming v3 format.
///
/// Note that if an error occurs, a parsed struct that can be returned to
/// show the error message will be returned.
///
/// The basic idea here is to accept the inside of an inline table, wrap it,
/// parse it, and then use the toml values.
pub(crate) fn from_config_string(config_string: &str) -> Result<InstanceConfig, String> {
let config_string = config_string.trim();
let config = match user_input_from_config_string(config_string) {
Ok(config) => config,
Err(error) => {
// For ergonomic reasons, we allow users to specify the directive without
// a key. So if parsing fails initially, take the first word,
// use that as the directive, and reparse.
let (directive, config_string) = match config_string.split_once(' ') {
Some((directive, config_string)) => (directive.trim(), config_string.trim()),
None => (config_string, ""),
};
if !RX_DIRECTIVE.is_match(directive) {
return Err(format_invalid_directive(directive, error));
}
let mut config = user_input_from_config_string(config_string)?;
config.r#type = Some(directive.to_owned());
config
}
};
let additional_classnames = config.classnames();
Ok(InstanceConfig {
directive: config.r#type.unwrap_or_default(),
title: config.title,
id: config.id,
additional_classnames,
collapsible: config.collapsible,
})
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_from_config_string_v3() -> Result<(), ()> {
fn check(config_string: &str, expected: InstanceConfig) -> Result<(), ()> {
let actual = match from_config_string(config_string) {
Ok(config) => config,
Err(error) => {
panic!("Expected config '{config_string}' to be valid, got error:\n\n{error}")
}
};
assert_eq!(actual, expected);
Ok(())
}
check(
"",
InstanceConfig {
directive: "".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
)?;
check(
" ",
InstanceConfig {
directive: "".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
)?;
check(
r#"type="note", class="additional classname", title="Никита", collapsible=true"#,
InstanceConfig {
directive: "note".to_owned(),
title: Some("Никита".to_owned()),
id: None,
additional_classnames: vec!["additional".to_owned(), "classname".to_owned()],
collapsible: Some(true),
},
)?;
// Specifying unknown keys is okay, as long as they're valid
check(
r#"unkonwn="but valid toml""#,
InstanceConfig {
directive: "".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
)?;
// Just directive is fine
check(
r#"info"#,
InstanceConfig {
directive: "info".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
)?;
// Directive plus toml config
check(
r#"info title="Information", collapsible=false"#,
InstanceConfig {
directive: "info".to_owned(),
title: Some("Information".to_owned()),
id: None,
additional_classnames: Vec::new(),
collapsible: Some(false),
},
)?;
// Test custom id
check(
r#"info title="My Info", id="my-info-custom-id""#,
InstanceConfig {
directive: "info".to_owned(),
title: Some("My Info".to_owned()),
id: Some("my-info-custom-id".to_owned()),
additional_classnames: Vec::new(),
collapsible: None,
},
)?;
// Directive after toml config is an error
assert!(from_config_string(r#"title="Information" info"#).is_err());
// HTML with quotes inside content
// Note that we use toml literal (single quoted) strings here
check(
r#"info title='My <span class="emphasis">Title</span>'"#,
InstanceConfig {
directive: "info".to_owned(),
title: Some(r#"My <span class="emphasis">Title</span>"#.to_owned()),
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
)?;
Ok(())
}
#[test]
fn test_from_config_string_invalid_directive() {
assert_eq!(
from_config_string(r#"oh!wow titlel=""#).unwrap_err(),
r#"'oh!wow' is not a valid directive or TOML key-value pair.
TOML parsing error: TOML parse error at line 1, column 14
|
1 | config = { oh!wow titlel=" }
| ^
expected `.`, `=`
"#
);
}
#[test]
fn test_from_config_string_invalid_toml_value() {
assert_eq!(
from_config_string(r#"note titlel=""#).unwrap_err(),
r#"TOML parsing error: TOML parse error at line 1, column 22
|
1 | config = { titlel=" }
| ^
invalid basic string
"#
);
}
}

111
src/custom.rs Normal file
View File

@@ -0,0 +1,111 @@
//! This module is responsible for generating custom CSS for new admonition variants.
//!
//! It has unit tests to ensure the output matches that of the compile_assets CSS.
use anyhow::{anyhow, Context, Result};
use hex_color::{Case, HexColor};
use once_cell::sync::Lazy;
use regex::Regex;
use std::fs;
use std::path::Path;
static RX_COLLAPSE_NEWLINES: Lazy<Regex> =
Lazy::new(|| Regex::new(r"[\r\n]+\s*").expect("invalid whitespace regex"));
// Do some simple things to make the svg input probably a valid data url
// Based on this gist: https://gist.github.com/jennyknuth/222825e315d45a738ed9d6e04c7a88d0
fn svg_to_data_url(svg: &str) -> String {
const XMLNS: &str = r#"http://www.w3.org/2000/svg"#;
//
let mut svg = RX_COLLAPSE_NEWLINES.replace_all(svg, "").to_string();
if !svg.contains(XMLNS) {
log::warn!("Your SVG file does not contain '<svg xmlns=\"{XMLNS}\"', it will likely fail to render.");
}
svg = svg
.replace('"', "'")
.replace('%', "%25")
.replace('#', "%23")
.replace('{', "%7B")
.replace('}', "%7D");
format!("url(\"data:image/svg+xml;charset=utf-8,{}\")", svg)
}
/// Given a valid set of inputs, generate the relevant CSS.
///
/// It is up to the caller to validate inputs.
fn directive_css(name: &str, svg_data: &str, tint: HexColor) -> String {
let data_url = svg_to_data_url(svg_data);
let tint_faint = format!("rgba({}, {}, {}, {})", tint.r, tint.g, tint.b, 0.1);
let tint = tint.display_rgb().with_case(Case::Lower);
format!(
":root {{
--md-admonition-icon--admonish-{name}: {data_url};
}}
:is(.admonition):is(.admonish-{name}) {{
border-color: {tint};
}}
:is(.admonish-{name}) > :is(.admonition-title, summary.admonition-title) {{
background-color: {tint_faint};
}}
:is(.admonish-{name}) > :is(.admonition-title, summary.admonition-title)::before {{
background-color: {tint};
mask-image: var(--md-admonition-icon--admonish-{name});
-webkit-mask-image: var(--md-admonition-icon--admonish-{name});
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}}
",
name = name,
data_url = data_url,
tint = tint,
tint_faint = tint_faint
)
}
#[doc(hidden)]
pub fn css_from_config(book_dir: &Path, config: &str) -> Result<String> {
let config = crate::book_config::admonish_config_from_str(config)?;
let custom_directives = config.directive.custom;
if custom_directives.is_empty() {
return Err(anyhow!("No custom directives provided"));
}
log::info!("Loaded {} custom directives", custom_directives.len());
let mut css = String::new();
for (directive_name, directive) in custom_directives.iter() {
let svg = fs::read_to_string(book_dir.join(&directive.icon))
.with_context(|| format!("can't read icon file '{}'", directive.icon.display()))?;
css.push_str(&directive_css(directive_name, &svg, directive.color));
}
Ok(css)
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
const GENERATED_CSS: &str = include_str!("./test_data/mdbook-admonish-custom-expected.css");
const NOTE_SVG: &str = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox='0 0 24 24'>
<path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/>
</svg>
"#;
// Verify the generated CSS here against a sample from the compile_assets output.
//
// The ensures that any new custom CSS will be in line with official styles.
#[test]
fn verify_against_generated_css() {
let actual = directive_css("note", NOTE_SVG, HexColor::parse("#448aff").unwrap());
assert_eq!(
GENERATED_CSS, actual,
"Rust generated CSS is out of step with SCSS generated CSS"
)
}
}

View File

@@ -1,5 +1,11 @@
//! For usage and reference, see the [mdbook-admonish book](https://tommilligan.github.io/mdbook-admonish/)
//!
//! Documentation is hosted externally, as docs.rs does not currently support plugins.
mod book_config;
mod config;
#[doc(hidden)]
pub mod custom;
mod markdown;
mod parse;
mod preprocessor;

View File

@@ -1,17 +1,16 @@
use mdbook::errors::Result as MdbookResult;
use pulldown_cmark::{CodeBlockKind::*, Event, Options, Parser, Tag};
pub use crate::preprocessor::Admonish;
use crate::{
book_config::OnFailure,
parse::parse_admonition,
types::{AdmonitionDefaults, RenderTextMode},
types::{Overrides, RenderTextMode},
};
pub(crate) fn preprocess(
content: &str,
on_failure: OnFailure,
admonition_defaults: &AdmonitionDefaults,
overrides: &Overrides,
render_text_mode: RenderTextMode,
) -> MdbookResult<String> {
let mut id_counter = Default::default();
@@ -28,12 +27,15 @@ pub(crate) fn preprocess(
for (event, span) in events.into_offset_iter() {
if let Event::Start(Tag::CodeBlock(Fenced(info_string))) = event.clone() {
let span_content = &content[span.start..span.end];
const INDENT_SCAN_MAX: usize = 1024;
let indent = indent_of(content, span.start, INDENT_SCAN_MAX);
let admonition = match parse_admonition(
info_string.as_ref(),
admonition_defaults,
overrides,
span_content,
on_failure,
indent,
) {
Some(admonition) => admonition,
None => continue,
@@ -44,7 +46,7 @@ pub(crate) fn preprocess(
// Once we've identitified admonition blocks, handle them differently
// depending on our render mode
let new_content = match render_text_mode {
RenderTextMode::Html => admonition.html_with_unique_ids(&mut id_counter),
RenderTextMode::Html => admonition.html(&mut id_counter),
RenderTextMode::Strip => admonition.strip(),
};
@@ -62,16 +64,81 @@ pub(crate) fn preprocess(
Ok(content)
}
/// Returns the indent of the given position.
///
/// Defined as the number of characters between the given `position` (where
/// position is a valid char boundary byte-index in `content`),
/// and the previous newline character `\n`.
///
/// `max` is the maximum number of characters to scan before assuming there is
/// no indent (will return zero if exceeded).
///
/// ## Panics
///
/// Will panic if `position` is not a valid utf-8 char boundary index of `content`.
fn indent_of(content: &str, position: usize, max: usize) -> usize {
// Scan for a line start before this span.
content[..position]
.chars()
.rev()
// For safety, only scan up to a fixed limit of the text
.take(max)
.position(|c| c == '\n')
// If we can't find a newline, assume no indent
.unwrap_or_default()
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
use crate::types::AdmonitionDefaults;
use super::*;
#[test]
fn indent_of_samples() {
for (content, position, max, expected) in [
// Empty case
("", 0, 10, 0),
("no newline", 4, 10, 0),
// Newline at position 5, difference from 8 is 3
("with\nnewline", 8, 10, 3),
// If no newline in safety limit, return 0
("with\nnewline", 8, 2, 0),
// Safety limit is characters, not bytes
// Regression test for FIXME LINK
(
"例えばこれは",
// Position is generated by mdbook internals, should be valid char limit
// This mimics the second character starting the span
"".len(),
// Any arbitrary safetly limit should be valid
1,
// Should not panic
0,
),
(
"例え\n れは",
// Position is generated by mdbook internals, should be valid char limit
// This mimics the second character starting the span
"例え\n ".len(),
// Any arbitrary safetly limit should be valid
4,
// Should not panic
2,
),
] {
let actual = indent_of(content, position, max);
assert_eq!(actual, expected);
}
}
fn prep(content: &str) -> String {
preprocess(
content,
OnFailure::Continue,
&AdmonitionDefaults::default(),
&Overrides::default(),
RenderTextMode::Html,
)
.unwrap()
@@ -88,11 +155,13 @@ Text
let expected = r##"# Chapter
<div id="admonition-note" class="admonition note">
<div id="admonition-note" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-title">
<div class="admonition-title">
<div id="admonition-note-title">
Note
</div>
<a class="admonition-anchor-link" href="#admonition-note"></a>
</div>
<div>
@@ -120,11 +189,13 @@ Text
let expected = r##"# Chapter
<div id="admonition-note" class="admonition note">
<div id="admonition-note" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-title">
<div class="admonition-title">
<div id="admonition-note-title">
Note
</div>
<a class="admonition-anchor-link" href="#admonition-note"></a>
</div>
<div>
@@ -152,11 +223,13 @@ Text
let expected = r##"# Chapter
<div id="admonition-warning" class="admonition warning">
<div id="admonition-warning" class="admonition admonish-warning" role="note" aria-labelledby="admonition-warning-title">
<div class="admonition-title">
<div id="admonition-warning-title">
Warning
</div>
<a class="admonition-anchor-link" href="#admonition-warning"></a>
</div>
<div>
@@ -182,11 +255,13 @@ Text
let expected = r##"# Chapter
<div id="admonition-caution" class="admonition warning">
<div id="admonition-caution" class="admonition admonish-warning" role="note" aria-labelledby="admonition-caution-title">
<div class="admonition-title">
<div id="admonition-caution-title">
Caution
</div>
<a class="admonition-anchor-link" href="#admonition-caution"></a>
</div>
<div>
@@ -212,11 +287,13 @@ Text
let expected = r##"# Chapter
<div id="admonition-read-this" class="admonition warning">
<div id="admonition-read-this" class="admonition admonish-warning" role="note" aria-labelledby="admonition-read-this-title">
<div class="admonition-title">
<div id="admonition-read-this-title">
Read **this**!
</div>
<a class="admonition-anchor-link" href="#admonition-read-this"></a>
</div>
<div>
@@ -306,11 +383,13 @@ hello
let expected = r##"
<div id="admonition-and-in-the-title" class="admonition note">
<div id="admonition-and-in-the-title" class="admonition admonish-note" role="note" aria-labelledby="admonition-and-in-the-title-title">
<div class="admonition-title">
<div id="admonition-and-in-the-title-title">
And "<i>in</i>" the title
</div>
<a class="admonition-anchor-link" href="#admonition-and-in-the-title"></a>
</div>
<div>
@@ -336,11 +415,13 @@ hello
let expected = r##"
<div id="admonition-trademark" class="admonition warning">
<div id="admonition-trademark" class="admonition admonish-warning" role="note" aria-labelledby="admonition-trademark-title">
<div class="admonition-title">
<div id="admonition-trademark-title">
Trademark™
</div>
<a class="admonition-anchor-link" href="#admonition-trademark"></a>
</div>
<div>
@@ -365,11 +446,13 @@ Will have bonus classnames
let expected = r##"
<div id="admonition-tip" class="admonition tip my-style other-style">
<div id="admonition-tip" class="admonition admonish-tip my-style other-style" role="note" aria-labelledby="admonition-tip-title">
<div class="admonition-title">
<div id="admonition-tip-title">
Tip
</div>
<a class="admonition-anchor-link" href="#admonition-tip"></a>
</div>
<div>
@@ -393,11 +476,13 @@ Will have bonus classnames
let expected = r##"
<div id="admonition-developers-dont-want-you-to-know-this-one-weird-tip" class="admonition tip my-style other-style">
<div id="admonition-developers-dont-want-you-to-know-this-one-weird-tip" class="admonition admonish-tip my-style other-style" role="note" aria-labelledby="admonition-developers-dont-want-you-to-know-this-one-weird-tip-title">
<div class="admonition-title">
<div id="admonition-developers-dont-want-you-to-know-this-one-weird-tip-title">
Developers don't want you to know this one weird tip!
</div>
<a class="admonition-anchor-link" href="#admonition-developers-dont-want-you-to-know-this-one-weird-tip"></a>
</div>
<div>
@@ -420,7 +505,7 @@ Will have bonus classnames
let expected = r#"
<div id="admonition-default" class="admonition note">
<div id="admonition-default" class="admonition admonish-note" role="note">
<div>
@@ -446,11 +531,13 @@ Content one.
let expected = r##"
<div id="admonition-my-note" class="admonition note">
<div id="admonition-my-note" class="admonition admonish-note" role="note" aria-labelledby="admonition-my-note-title">
<div class="admonition-title">
<div id="admonition-my-note-title">
My Note
</div>
<a class="admonition-anchor-link" href="#admonition-my-note"></a>
</div>
<div>
@@ -461,11 +548,13 @@ Content zero.
</div>
<div id="admonition-my-note-1" class="admonition note">
<div id="admonition-my-note-1" class="admonition admonish-note" role="note" aria-labelledby="admonition-my-note-1-title">
<div class="admonition-title">
<div id="admonition-my-note-1-title">
My Note
</div>
<a class="admonition-anchor-link" href="#admonition-my-note-1"></a>
</div>
<div>
@@ -489,11 +578,13 @@ Bonus content!
let expected = r##"
<div id="admonition-article-heading" class="admonition tip my other-style">
<div id="admonition-article-heading" class="admonition admonish-tip my other-style" role="note" aria-labelledby="admonition-article-heading-title">
<div class="admonition-title">
<div id="admonition-article-heading-title">
Article Heading
</div>
<a class="admonition-anchor-link" href="#admonition-article-heading"></a>
</div>
<div>
@@ -517,11 +608,13 @@ Bonus content!
let expected = r##"
<div id="admonition-error-rendering-admonishment" class="admonition bug">
<div id="admonition-error-rendering-admonishment" class="admonition admonish-bug" role="note" aria-labelledby="admonition-error-rendering-admonishment-title">
<div class="admonition-title">
<div id="admonition-error-rendering-admonishment-title">
Error rendering admonishment
</div>
<a class="admonition-anchor-link" href="#admonition-error-rendering-admonishment"></a>
</div>
<div>
@@ -529,10 +622,12 @@ Error rendering admonishment
Failed with:
```log
TOML parsing error: TOML parse error at line 1, column 8
'title="' is not a valid directive or TOML key-value pair.
TOML parsing error: TOML parse error at line 1, column 21
|
1 | title="
| ^
1 | config = { title=" }
| ^
invalid basic string
```
@@ -564,7 +659,7 @@ Bonus content!
preprocess(
content,
OnFailure::Bail,
&AdmonitionDefaults::default(),
&Overrides::default(),
RenderTextMode::Html
)
.unwrap_err()
@@ -591,7 +686,7 @@ x = 20;
preprocess(
content,
OnFailure::Bail,
&AdmonitionDefaults::default(),
&Overrides::default(),
RenderTextMode::Strip
)
.unwrap(),
@@ -617,11 +712,13 @@ Hidden
let expected = r##"
<details id="admonition-note" class="admonition note">
<details id="admonition-note" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-title">
<summary class="admonition-title">
<div id="admonition-note-title">
Note
</div>
<a class="admonition-anchor-link" href="#admonition-note"></a>
</summary>
<div>
@@ -646,11 +743,13 @@ Text
let expected = r##"# Chapter
<div id="admonition-admonish" class="admonition note">
<div id="admonition-admonish" class="admonition admonish-note" role="note" aria-labelledby="admonition-admonish-title">
<div class="admonition-title">
<div id="admonition-admonish-title">
Admonish
</div>
<a class="admonition-anchor-link" href="#admonition-admonish"></a>
</div>
<div>
@@ -665,9 +764,13 @@ Text
let preprocess_result = preprocess(
content,
OnFailure::Continue,
&AdmonitionDefaults {
title: Some("Admonish".to_owned()),
collapsible: false,
&Overrides {
book: AdmonitionDefaults {
title: Some("Admonish".to_owned()),
css_id_prefix: None,
collapsible: false,
},
..Default::default()
},
RenderTextMode::Html,
)
@@ -686,7 +789,7 @@ Text
let expected = r#"# Chapter
<div id="admonition-default" class="admonition note">
<div id="admonition-default" class="admonition admonish-note" role="note">
<div>
A simple admonition.
@@ -699,9 +802,13 @@ Text
let preprocess_result = preprocess(
content,
OnFailure::Continue,
&AdmonitionDefaults {
title: Some("Admonish".to_owned()),
collapsible: false,
&Overrides {
book: AdmonitionDefaults {
title: Some("Admonish".to_owned()),
css_id_prefix: None,
collapsible: false,
},
..Default::default()
},
RenderTextMode::Html,
)
@@ -720,7 +827,7 @@ Text
let expected = r#"# Chapter
<div id="admonition-default" class="admonition note">
<div id="admonition-default" class="admonition admonish-note" role="note">
<div>
A simple admonition.
@@ -732,4 +839,426 @@ Text
assert_eq!(expected, prep(content));
}
#[test]
fn standard_custom_id() {
let content = r#"# Chapter
```admonish check id="yay-custom-id"
A simple admonition.
```
Text
"#;
let expected = r##"# Chapter
<div id="yay-custom-id" class="admonition admonish-success" role="note" aria-labelledby="yay-custom-id-title">
<div class="admonition-title">
<div id="yay-custom-id-title">
Check
</div>
<a class="admonition-anchor-link" href="#yay-custom-id"></a>
</div>
<div>
A simple admonition.
</div>
</div>
Text
"##;
assert_eq!(expected, prep(content));
}
#[test]
fn no_custom_id_default_prefix() {
let content = r#"# Chapter
```admonish check
A simple admonition.
```
Text
"#;
let expected = r##"# Chapter
<div id="admonition-check" class="admonition admonish-success" role="note" aria-labelledby="admonition-check-title">
<div class="admonition-title">
<div id="admonition-check-title">
Check
</div>
<a class="admonition-anchor-link" href="#admonition-check"></a>
</div>
<div>
A simple admonition.
</div>
</div>
Text
"##;
assert_eq!(expected, prep(content));
}
#[test]
fn no_custom_id_default_prefix_custom_title() {
let content = r#"# Chapter
```admonish check title="Check Mark"
A simple admonition.
```
Text
"#;
let expected = r##"# Chapter
<div id="admonition-check-mark" class="admonition admonish-success" role="note" aria-labelledby="admonition-check-mark-title">
<div class="admonition-title">
<div id="admonition-check-mark-title">
Check Mark
</div>
<a class="admonition-anchor-link" href="#admonition-check-mark"></a>
</div>
<div>
A simple admonition.
</div>
</div>
Text
"##;
assert_eq!(expected, prep(content));
}
#[test]
fn title_and_content_with_html() {
// Note that we use toml literal (single quoted) strings here
// and the fact we have an equals sign in the value does not cause
// us to break (because we're using v3 syntax, not v2)
let content = r#"# Chapter
```admonish success title='Check <span class="emphasis">Mark</span>'
A <span class="emphasis">simple</span> admonition.
```
Text
"#;
let expected = r##"# Chapter
<div id="admonition-check-mark" class="admonition admonish-success" role="note" aria-labelledby="admonition-check-mark-title">
<div class="admonition-title">
<div id="admonition-check-mark-title">
Check <span class="emphasis">Mark</span>
</div>
<a class="admonition-anchor-link" href="#admonition-check-mark"></a>
</div>
<div>
A <span class="emphasis">simple</span> admonition.
</div>
</div>
Text
"##;
assert_eq!(expected, prep(content));
}
#[test]
fn empty_default_id_prefix() {
let content = r#"# Chapter
```admonish info
A simple admonition.
```
Text
"#;
let expected = r##"# Chapter
<div id="info" class="admonition admonish-info" role="note" aria-labelledby="info-title">
<div class="admonition-title">
<div id="info-title">
Info
</div>
<a class="admonition-anchor-link" href="#info"></a>
</div>
<div>
A simple admonition.
</div>
</div>
Text
"##;
let preprocess_result = preprocess(
content,
OnFailure::Continue,
&Overrides {
book: AdmonitionDefaults {
title: Some("Info".to_owned()),
css_id_prefix: Some("".to_owned()),
collapsible: false,
},
..Default::default()
},
RenderTextMode::Html,
)
.unwrap();
assert_eq!(expected, preprocess_result);
}
#[test]
fn custom_id_prefix_custom_title() {
let content = r#"# Chapter
```admonish info title="My Title"
A simple admonition.
```
Text
"#;
let expected = r##"# Chapter
<div id="prefix-my-title" class="admonition admonish-info" role="note" aria-labelledby="prefix-my-title-title">
<div class="admonition-title">
<div id="prefix-my-title-title">
My Title
</div>
<a class="admonition-anchor-link" href="#prefix-my-title"></a>
</div>
<div>
A simple admonition.
</div>
</div>
Text
"##;
let preprocess_result = preprocess(
content,
OnFailure::Continue,
&Overrides {
book: AdmonitionDefaults {
title: Some("Info".to_owned()),
css_id_prefix: Some("prefix-".to_owned()),
collapsible: false,
},
..Default::default()
},
RenderTextMode::Html,
)
.unwrap();
assert_eq!(expected, preprocess_result);
}
#[test]
fn custom_id_custom_title() {
let content = r#"# Chapter
```admonish info title="My Title" id="my-section-id"
A simple admonition.
```
Text
"#;
let expected = r##"# Chapter
<div id="my-section-id" class="admonition admonish-info" role="note" aria-labelledby="my-section-id-title">
<div class="admonition-title">
<div id="my-section-id-title">
My Title
</div>
<a class="admonition-anchor-link" href="#my-section-id"></a>
</div>
<div>
A simple admonition.
</div>
</div>
Text
"##;
let preprocess_result = preprocess(
content,
OnFailure::Continue,
&Overrides {
book: AdmonitionDefaults {
title: Some("Info".to_owned()),
css_id_prefix: Some("ignored-prefix-".to_owned()),
collapsible: false,
},
..Default::default()
},
RenderTextMode::Html,
)
.unwrap();
assert_eq!(expected, preprocess_result);
}
#[test]
fn list_embed() {
let content = r#"# Chapter
1. Thing one
```sh
Thing one
```
1. Thing two
```admonish
Thing two
```
1. Thing three
```sh
Thing three
```
"#;
let expected = r##"# Chapter
1. Thing one
```sh
Thing one
```
1. Thing two
<div id="admonition-note" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-title">
<div class="admonition-title">
<div id="admonition-note-title">
Note
</div>
<a class="admonition-anchor-link" href="#admonition-note"></a>
</div>
<div>
Thing two
</div>
</div>
1. Thing three
```sh
Thing three
```
"##;
assert_eq!(expected, prep(content));
}
// Regression test for https://github.com/tommilligan/mdbook-admonish/issues/223
#[test]
fn nested_list_should_not_render_indented_code_block() {
let content = r#"# Chapter
- Level one
```admonish
Thing one
line two
```
- Level two
```admonish
Thing two
line two
```
- Level three
```admonish
Thing three
line two
```
"#;
let expected = r##"# Chapter
- Level one
<div id="admonition-note" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-title">
<div class="admonition-title">
<div id="admonition-note-title">
Note
</div>
<a class="admonition-anchor-link" href="#admonition-note"></a>
</div>
<div>
Thing one
line two
</div>
</div>
- Level two
<div id="admonition-note-1" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-1-title">
<div class="admonition-title">
<div id="admonition-note-1-title">
Note
</div>
<a class="admonition-anchor-link" href="#admonition-note-1"></a>
</div>
<div>
Thing two
line two
</div>
</div>
- Level three
<div id="admonition-note-2" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-2-title">
<div class="admonition-title">
<div id="admonition-note-2-title">
Note
</div>
<a class="admonition-anchor-link" href="#admonition-note-2"></a>
</div>
<div>
Thing three
line two
</div>
</div>
"##;
assert_eq!(expected, prep(content));
}
}

View File

@@ -1,12 +1,11 @@
use anyhow::{anyhow, Result};
use std::borrow::Cow;
pub use crate::preprocessor::Admonish;
use crate::{
book_config::OnFailure,
render::Admonition,
resolve::AdmonitionMeta,
types::{AdmonitionDefaults, Directive},
types::{BuiltinDirective, CssId, Overrides},
};
/// Given the content in the span of the code block, and the info string,
@@ -20,18 +19,17 @@ use crate::{
/// If the code block is not an admonition, return `None`.
pub(crate) fn parse_admonition<'a>(
info_string: &'a str,
admonition_defaults: &'a AdmonitionDefaults,
overrides: &'a Overrides,
content: &'a str,
on_failure: OnFailure,
indent: usize,
) -> Option<Result<Admonition<'a>>> {
// We need to know fence details anyway for error messages
let extracted = extract_admonish_body(content);
let info = AdmonitionMeta::from_info_string(info_string, admonition_defaults)?;
let info = AdmonitionMeta::from_info_string(info_string, overrides)?;
let info = match info {
Ok(info) => info,
// FIXME return error messages to break build if configured
// Err(message) => return Some(Err(content)),
Err(message) => {
// Construct a fence capable of enclosing whatever we wrote for the
// actual input block
@@ -45,8 +43,9 @@ pub(crate) fn parse_admonition<'a>(
r#"Error processing admonition. To fail the build instead of continuing, set 'on_failure = "bail"'"#
);
Ok(Admonition {
directive: Directive::Bug,
directive: BuiltinDirective::Bug.to_string(),
title: "Error rendering admonishment".to_owned(),
css_id: CssId::Prefix("admonition-".to_owned()),
additional_classnames: Vec::new(),
collapsible: false,
content: Cow::Owned(format!(
@@ -63,6 +62,7 @@ Original markdown input:
{enclosing_fence}
"#
)),
indent,
})
}
OnFailure::Bail => Err(anyhow!("Error processing admonition, bailing:\n{content}")),
@@ -70,7 +70,24 @@ Original markdown input:
}
};
Some(Ok(Admonition::new(info, extracted.body)))
Some(Ok(Admonition::new(
info,
extracted.body,
// Note that this is a bit hacky - the fence information comes from the start
// of the block, and includes the whole line.
//
// This is more likely to be what we want, as ending indentation is unrelated
// according to the commonmark spec (ref https://spec.commonmark.org/0.12/#example-85)
//
// The main case we're worried about here is indenting enough to be inside list items,
// and in this case the starting code fence must be indented enough to be considered
// part of the list item.
//
// The hacky thing is that we're considering line indent in the document as a whole,
// not relative to the context of some containing item. But I think that's what we
// want for now, anyway.
indent,
)))
}
/// We can't trust the info string length to find the start of the body

View File

@@ -8,7 +8,7 @@ use mdbook::{
use crate::{
book_config::{admonish_config_from_context, Config, RenderMode},
markdown::preprocess,
types::RenderTextMode,
types::{Overrides, RenderTextMode},
};
pub struct Admonish;
@@ -22,9 +22,21 @@ impl Preprocessor for Admonish {
let config = admonish_config_from_context(ctx)?;
ensure_compatible_assets_version(&config)?;
let custom_directives = config
.directive
.custom
.into_iter()
.map(Into::into)
.collect();
let on_failure = config.on_failure;
let admonition_defaults = config.default;
let overrides = Overrides {
book: admonition_defaults,
custom: custom_directives,
builtin: config.directive.builtin,
};
// Load what rendering we should do from config, falling back to a default
let render_mode = config
.renderer
@@ -53,15 +65,11 @@ impl Preprocessor for Admonish {
if let BookItem::Chapter(ref mut chapter) = *item {
res = Some(
preprocess(
&chapter.content,
on_failure,
&admonition_defaults,
render_text_mode,
)
.map(|md| {
chapter.content = md;
}),
preprocess(&chapter.content, on_failure, &overrides, render_text_mode).map(
|md| {
chapter.content = md;
},
),
);
}
});
@@ -170,11 +178,13 @@ x = 20;
"#;
let expected_content = r##"
<div id="admonition-title" class="admonition note">
<div id="admonition-title" class="admonition admonish-note" role="note" aria-labelledby="admonition-title-title">
<div class="admonition-title">
<div id="admonition-title-title">
Title
</div>
<a class="admonition-anchor-link" href="#admonition-title"></a>
</div>
<div>
@@ -190,7 +200,7 @@ x = 20;
let ctx = mock_context(
&json!({
"assets_version": "2.0.0"
"assets_version": "3.0.0"
}),
"html",
);
@@ -212,7 +222,7 @@ x = 20;
"#;
let ctx = mock_context(
&json!({
"assets_version": "2.0.0"
"assets_version": "3.0.0"
}),
"test",
);
@@ -242,7 +252,7 @@ x = 20;
"#;
let ctx = mock_context(
&json!({
"assets_version": "2.0.0",
"assets_version": "3.0.0",
"renderer": {
"test": {
"render_mode": "strip",

View File

@@ -2,42 +2,25 @@ use mdbook::utils::unique_id_from_content;
use std::borrow::Cow;
use std::collections::HashMap;
pub use crate::preprocessor::Admonish;
use crate::{resolve::AdmonitionMeta, types::Directive};
impl Directive {
fn classname(&self) -> &'static str {
match self {
Directive::Note => "note",
Directive::Abstract => "abstract",
Directive::Info => "info",
Directive::Tip => "tip",
Directive::Success => "success",
Directive::Question => "question",
Directive::Warning => "warning",
Directive::Failure => "failure",
Directive::Danger => "danger",
Directive::Bug => "bug",
Directive::Example => "example",
Directive::Quote => "quote",
}
}
}
use crate::{resolve::AdmonitionMeta, types::CssId};
#[derive(Debug, PartialEq)]
pub(crate) struct Admonition<'a> {
pub(crate) directive: Directive,
pub(crate) directive: String,
pub(crate) title: String,
pub(crate) content: Cow<'a, str>,
pub(crate) css_id: CssId,
pub(crate) additional_classnames: Vec<String>,
pub(crate) collapsible: bool,
pub(crate) indent: usize,
}
impl<'a> Admonition<'a> {
pub(crate) fn new(info: AdmonitionMeta, content: &'a str) -> Self {
pub(crate) fn new(info: AdmonitionMeta, content: &'a str, indent: usize) -> Self {
let AdmonitionMeta {
directive,
title,
css_id,
additional_classnames,
collapsible,
} = info;
@@ -45,78 +28,107 @@ impl<'a> Admonition<'a> {
directive,
title,
content: Cow::Borrowed(content),
css_id,
additional_classnames,
collapsible,
indent,
}
}
pub(crate) fn html_with_unique_ids(&self, id_counter: &mut HashMap<String, usize>) -> String {
let anchor_id = unique_id_from_content(
if !self.title.is_empty() {
&self.title
} else {
ANCHOR_ID_DEFAULT
},
id_counter,
);
self.html(&anchor_id)
}
pub(crate) fn html(self, id_counter: &mut HashMap<String, usize>) -> String {
let anchor_id = match &self.css_id {
CssId::Verbatim(id) => Cow::Borrowed(id.as_str()),
CssId::Prefix(prefix) => {
let id = unique_id_from_content(
if !self.title.is_empty() {
&self.title
} else {
ANCHOR_ID_DEFAULT
},
id_counter,
);
fn html(&self, anchor_id: &str) -> String {
let mut additional_class = Cow::Borrowed(self.directive.classname());
let title = &self.title;
let content = &self.content;
let title_block = if self.collapsible { "summary" } else { "div" };
let title_html = if !title.is_empty() {
Cow::Owned(format!(
r##"<{title_block} class="admonition-title">
{title}
<a class="admonition-anchor-link" href="#{ANCHOR_ID_PREFIX}-{anchor_id}"></a>
</{title_block}>
"##
))
} else {
Cow::Borrowed("")
Cow::Owned(format!("{}{}", prefix, id))
}
};
if !self.additional_classnames.is_empty() {
let mut buffer = additional_class.into_owned();
for additional_classname in &self.additional_classnames {
buffer.push(' ');
buffer.push_str(additional_classname);
}
let title = &self.title;
let content = &self.content;
let indent = " ".repeat(self.indent);
additional_class = Cow::Owned(buffer);
let (titlebar_html, title_id) = if !title.is_empty() {
let titlebar_element = if self.collapsible { "summary" } else { "div" };
let title_id = format!("{anchor_id}-title");
let titlebar_html = Cow::Owned(format!(
r##"{indent}<{titlebar_element} class="admonition-title">
{indent}<div id="{title_id}">
{indent}
{indent}{title}
{indent}
{indent}</div>
{indent}<a class="admonition-anchor-link" href="#{anchor_id}"></a>
{indent}</{titlebar_element}>
"##
));
(titlebar_html, Some(title_id))
} else {
(Cow::Borrowed(""), None)
};
let mut classes = vec![
"admonition".to_owned(),
format!("admonish-{}", self.directive),
];
classes.extend(self.additional_classnames);
let classes = classes.join(" ");
let mut attributes = vec![
("id", anchor_id),
("class", Cow::Owned(classes)),
("role", Cow::Borrowed("note")),
];
if let Some(title_id) = title_id {
attributes.push(("aria-labelledby", Cow::Owned(title_id)));
}
let attributes = join_attributes(&attributes);
let admonition_block = if self.collapsible { "details" } else { "div" };
let admonition_element = if self.collapsible { "details" } else { "div" };
// Notes on the HTML template:
// - the additional whitespace around the content are deliberate
// In line with the commonmark spec, this allows the inner content to be
// rendered as markdown paragraphs.
// - We should not indent the inner content, as it retains the indent
// it is written with.
format!(
r#"
<{admonition_block} id="{ANCHOR_ID_PREFIX}-{anchor_id}" class="admonition {additional_class}">
{title_html}<div>
{indent}<{admonition_element} {attributes}>
{titlebar_html}{indent}<div>
{content}
</div>
</{admonition_block}>"#,
{indent}</div>
{indent}</{admonition_element}>"#,
)
}
/// Strips all admonish syntax, leaving the plain content of the block.
pub(crate) fn strip(&self) -> String {
pub(crate) fn strip(self) -> String {
// Add in newlines to preserve line numbering for test output
// These replace the code fences we stripped out
format!("\n{}\n", self.content)
}
}
const ANCHOR_ID_PREFIX: &str = "admonition";
fn join_attributes(attributes: &[(impl AsRef<str>, impl AsRef<str>)]) -> String {
let mut buffer = String::new();
for (key, value) in attributes {
buffer.push_str(key.as_ref());
buffer.push_str(r#"=""#);
buffer.push_str(value.as_ref());
buffer.push_str(r#"" "#);
}
buffer.pop();
buffer
}
const ANCHOR_ID_DEFAULT: &str = "default";

View File

@@ -1,5 +1,6 @@
use crate::config::InstanceConfig;
use crate::types::{AdmonitionDefaults, Directive};
use crate::types::{BuiltinDirective, CssId, CustomDirective, CustomDirectiveMap, Overrides};
use std::fmt;
use std::str::FromStr;
/// All information required to render an admonition.
@@ -7,56 +8,141 @@ use std::str::FromStr;
/// i.e. all configured options have been resolved at this point.
#[derive(Debug, PartialEq)]
pub(crate) struct AdmonitionMeta {
pub directive: Directive,
pub directive: String,
pub title: String,
pub css_id: CssId,
pub additional_classnames: Vec<String>,
pub collapsible: bool,
}
/// Wrapper type to hold any value directive configuration.
enum Directive {
Builtin(BuiltinDirective),
Custom(CustomDirective),
}
impl fmt::Display for Directive {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Builtin(builtin) => builtin.fmt(f),
Self::Custom(custom) => f.write_str(&custom.directive),
}
}
}
impl Directive {
fn from_str(custom_directive_map: &CustomDirectiveMap, string: &str) -> Result<Self, ()> {
if let Ok(builtin) = BuiltinDirective::from_str(string) {
return Ok(Self::Builtin(builtin));
}
if let Some(config) = custom_directive_map.get(string) {
return Ok(Self::Custom(config.clone()));
}
Err(())
}
fn title(self, raw_directive: &str) -> String {
match self {
Directive::Builtin(_) => format_builtin_directive_title(raw_directive),
Directive::Custom(custom) => custom
.title
.clone()
.unwrap_or_else(|| uppercase_first(raw_directive)),
}
}
}
impl AdmonitionMeta {
pub fn from_info_string(
info_string: &str,
defaults: &AdmonitionDefaults,
overrides: &Overrides,
) -> Option<Result<Self, String>> {
InstanceConfig::from_info_string(info_string)
.map(|raw| raw.map(|raw| Self::resolve(raw, defaults)))
.map(|raw| raw.map(|raw| Self::resolve(raw, overrides)))
}
/// Combine the per-admonition configuration with global defaults (and
/// other logic) to resolve the values needed for rendering.
fn resolve(raw: InstanceConfig, defaults: &AdmonitionDefaults) -> Self {
fn resolve(raw: InstanceConfig, overrides: &Overrides) -> Self {
let InstanceConfig {
directive: raw_directive,
title,
id,
additional_classnames,
collapsible,
} = raw;
// Use values from block, else load default value
let title = title.or_else(|| defaults.title.clone());
let collapsible = collapsible.unwrap_or(defaults.collapsible);
let title = title.or_else(|| overrides.book.title.clone());
let directive = Directive::from_str(&overrides.custom, &raw_directive);
let collapsible = match directive {
// If the directive is a builin one, use collapsible from block, else use default
// value of the builtin directive, else use global default value
Ok(Directive::Builtin(directive)) => collapsible.unwrap_or(
overrides
.builtin
.get(&directive)
.and_then(|config| config.collapsible)
.unwrap_or(overrides.book.collapsible),
),
// If the directive is a custom one, use collapsible from block, else use default
// value of the custom directive, else use global default value
Ok(Directive::Custom(ref custom_dir)) => {
collapsible.unwrap_or(custom_dir.collapsible.unwrap_or(overrides.book.collapsible))
}
Err(_) => collapsible.unwrap_or(overrides.book.collapsible),
};
// Load the directive (and title, if one still not given)
let (directive, title) = match (Directive::from_str(&raw_directive), title) {
(Ok(directive), None) => (directive, ucfirst(&raw_directive)),
(Err(_), None) => (Directive::Note, "Note".to_owned()),
(Ok(directive), Some(title)) => (directive, title),
(Err(_), Some(title)) => (Directive::Note, title),
let (directive, title) = match (directive, title) {
(Ok(directive), None) => (directive.to_string(), directive.title(&raw_directive)),
(Err(_), None) => (BuiltinDirective::Note.to_string(), "Note".to_owned()),
(Ok(directive), Some(title)) => (directive.to_string(), title),
(Err(_), Some(title)) => (BuiltinDirective::Note.to_string(), title),
};
let css_id = if let Some(verbatim) = id {
CssId::Verbatim(verbatim)
} else {
const DEFAULT_CSS_ID_PREFIX: &str = "admonition-";
CssId::Prefix(
overrides
.book
.css_id_prefix
.clone()
.unwrap_or_else(|| DEFAULT_CSS_ID_PREFIX.to_owned()),
)
};
Self {
directive,
title,
css_id,
additional_classnames,
collapsible,
}
}
}
/// Make the first letter of `input` upppercase.
/// Format the title of an admonition directive
///
/// We special case a few words to make them look nicer (e.g. "tldr" -> "TL;DR" and "faq" -> "FAQ").
fn format_builtin_directive_title(input: &str) -> String {
match input {
"tldr" => "TL;DR".to_owned(),
"faq" => "FAQ".to_owned(),
_ => uppercase_first(input),
}
}
/// Make the first letter of `input` uppercase.
///
/// source: https://stackoverflow.com/a/38406885
fn ucfirst(input: &str) -> String {
fn uppercase_first(input: &str) -> String {
let mut chars = input.chars();
match chars.next() {
None => String::new(),
@@ -66,9 +152,25 @@ fn ucfirst(input: &str) -> String {
#[cfg(test)]
mod test {
use std::collections::HashMap;
use crate::types::{AdmonitionDefaults, BuiltinDirectiveConfig};
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_format_builtin_directive_title() {
assert_eq!(format_builtin_directive_title(""), "");
assert_eq!(format_builtin_directive_title("a"), "A");
assert_eq!(format_builtin_directive_title("tldr"), "TL;DR");
assert_eq!(format_builtin_directive_title("faq"), "FAQ");
assert_eq!(format_builtin_directive_title("note"), "Note");
assert_eq!(format_builtin_directive_title("abstract"), "Abstract");
// Unicode
assert_eq!(format_builtin_directive_title("🦀"), "🦀");
}
#[test]
fn test_admonition_info_from_raw() {
assert_eq!(
@@ -76,14 +178,16 @@ mod test {
InstanceConfig {
directive: " ".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
&Default::default()
&Overrides::default(),
),
AdmonitionMeta {
directive: Directive::Note,
directive: "note".to_owned(),
title: "Note".to_owned(),
css_id: CssId::Prefix("admonition-".to_owned()),
additional_classnames: Vec::new(),
collapsible: false,
}
@@ -97,20 +201,260 @@ mod test {
InstanceConfig {
directive: " ".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
&AdmonitionDefaults {
title: Some("Important!!!".to_owned()),
collapsible: true,
},
&Overrides {
book: AdmonitionDefaults {
title: Some("Important!!!".to_owned()),
css_id_prefix: Some("custom-prefix-".to_owned()),
collapsible: true,
},
..Default::default()
}
),
AdmonitionMeta {
directive: Directive::Note,
directive: "note".to_owned(),
title: "Important!!!".to_owned(),
css_id: CssId::Prefix("custom-prefix-".to_owned()),
additional_classnames: Vec::new(),
collapsible: true,
}
);
}
#[test]
fn test_admonition_info_from_raw_with_defaults_and_custom_id() {
assert_eq!(
AdmonitionMeta::resolve(
InstanceConfig {
directive: " ".to_owned(),
title: None,
id: Some("my-custom-id".to_owned()),
additional_classnames: Vec::new(),
collapsible: None,
},
&Overrides {
book: AdmonitionDefaults {
title: Some("Important!!!".to_owned()),
css_id_prefix: Some("ignored-custom-prefix-".to_owned()),
collapsible: true,
},
..Default::default()
}
),
AdmonitionMeta {
directive: "note".to_owned(),
title: "Important!!!".to_owned(),
css_id: CssId::Verbatim("my-custom-id".to_owned()),
additional_classnames: Vec::new(),
collapsible: true,
}
);
}
#[test]
fn test_admonition_info_from_raw_with_custom_directive() {
assert_eq!(
AdmonitionMeta::resolve(
InstanceConfig {
directive: "frog".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
&Overrides {
custom: [CustomDirective {
directive: "frog".to_owned(),
aliases: Vec::new(),
title: None,
collapsible: None,
}]
.into_iter()
.collect(),
..Default::default()
}
),
AdmonitionMeta {
directive: "frog".to_owned(),
title: "Frog".to_owned(),
css_id: CssId::Prefix("admonition-".to_owned()),
additional_classnames: Vec::new(),
collapsible: false,
}
);
}
#[test]
fn test_admonition_info_from_raw_with_custom_directive_and_title() {
assert_eq!(
AdmonitionMeta::resolve(
InstanceConfig {
directive: "frog".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
&Overrides {
custom: [CustomDirective {
directive: "frog".to_owned(),
aliases: Vec::new(),
title: Some("🏳️‍🌈".to_owned()),
collapsible: None,
}]
.into_iter()
.collect(),
..Default::default()
}
),
AdmonitionMeta {
directive: "frog".to_owned(),
title: "🏳️‍🌈".to_owned(),
css_id: CssId::Prefix("admonition-".to_owned()),
additional_classnames: Vec::new(),
collapsible: false,
}
);
}
#[test]
fn test_admonition_info_from_raw_with_custom_directive_alias() {
assert_eq!(
AdmonitionMeta::resolve(
InstanceConfig {
directive: "toad".to_owned(),
title: Some("Still a frog".to_owned()),
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
&Overrides {
custom: [CustomDirective {
directive: "frog".to_owned(),
aliases: vec!["newt".to_owned(), "toad".to_owned()],
title: Some("🏳️‍🌈".to_owned()),
collapsible: None,
}]
.into_iter()
.collect(),
..Default::default()
}
),
AdmonitionMeta {
directive: "frog".to_owned(),
title: "Still a frog".to_owned(),
css_id: CssId::Prefix("admonition-".to_owned()),
additional_classnames: Vec::new(),
collapsible: false,
}
);
}
#[test]
fn test_admonition_info_from_raw_with_collapsible_custom_directive() {
assert_eq!(
AdmonitionMeta::resolve(
InstanceConfig {
directive: "frog".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
&Overrides {
custom: [CustomDirective {
directive: "frog".to_owned(),
aliases: Vec::new(),
title: None,
collapsible: Some(true),
}]
.into_iter()
.collect(),
..Default::default()
}
),
AdmonitionMeta {
directive: "frog".to_owned(),
title: "Frog".to_owned(),
css_id: CssId::Prefix("admonition-".to_owned()),
additional_classnames: Vec::new(),
collapsible: true,
}
);
}
#[test]
fn test_admonition_info_from_raw_with_collapsible_builtin_directive() {
assert_eq!(
AdmonitionMeta::resolve(
InstanceConfig {
directive: "abstract".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
&Overrides {
book: AdmonitionDefaults {
title: None,
css_id_prefix: None,
collapsible: false,
},
builtin: HashMap::from([(
BuiltinDirective::Abstract,
BuiltinDirectiveConfig {
collapsible: Some(true),
}
)]),
..Default::default()
}
),
AdmonitionMeta {
directive: "abstract".to_owned(),
title: "Abstract".to_owned(),
css_id: CssId::Prefix("admonition-".to_owned()),
additional_classnames: Vec::new(),
collapsible: true,
}
);
}
#[test]
fn test_admonition_info_from_raw_with_non_collapsible_builtin_directive() {
assert_eq!(
AdmonitionMeta::resolve(
InstanceConfig {
directive: "abstract".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
&Overrides {
book: AdmonitionDefaults {
title: None,
css_id_prefix: None,
collapsible: true,
},
builtin: HashMap::from([(
BuiltinDirective::Abstract,
BuiltinDirectiveConfig {
collapsible: Some(false),
}
)]),
..Default::default()
}
),
AdmonitionMeta {
directive: "abstract".to_owned(),
title: "Abstract".to_owned(),
css_id: CssId::Prefix("admonition-".to_owned()),
additional_classnames: Vec::new(),
collapsible: false,
}
);
}
}

View File

@@ -0,0 +1,20 @@
:root {
--md-admonition-icon--admonish-note: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/></svg>");
}
:is(.admonition):is(.admonish-note) {
border-color: #448aff;
}
:is(.admonish-note) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(68, 138, 255, 0.1);
}
:is(.admonish-note) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #448aff;
mask-image: var(--md-admonition-icon--admonish-note);
-webkit-mask-image: var(--md-admonition-icon--admonish-note);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}

View File

@@ -1,4 +1,6 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
use std::str::FromStr;
/// Book wide defaults that may be provided by the user.
@@ -9,10 +11,22 @@ pub(crate) struct AdmonitionDefaults {
#[serde(default)]
pub(crate) collapsible: bool,
#[serde(default)]
// For backwards compatibility, we support this field with kebab-case style
// naming, even though this was introduced in error.
#[serde(alias = "css-id-prefix")]
pub(crate) css_id_prefix: Option<String>,
}
#[derive(Debug, PartialEq)]
pub(crate) enum Directive {
/// First class supported directives by the crate.
///
/// These are guaranteed to have valid CSS/icons available.
///
/// Custom directives can also be added via the book.toml config.
#[derive(Debug, PartialEq, Clone, Copy, Eq, Deserialize, Serialize, Hash)]
#[serde(rename_all = "lowercase")]
pub(crate) enum BuiltinDirective {
Note,
Abstract,
Info,
@@ -27,7 +41,7 @@ pub(crate) enum Directive {
Quote,
}
impl FromStr for Directive {
impl FromStr for BuiltinDirective {
type Err = ();
fn from_str(string: &str) -> Result<Self, ()> {
@@ -49,8 +63,116 @@ impl FromStr for Directive {
}
}
impl fmt::Display for BuiltinDirective {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let value = match self {
Self::Note => "note",
Self::Abstract => "abstract",
Self::Info => "info",
Self::Tip => "tip",
Self::Success => "success",
Self::Question => "question",
Self::Warning => "warning",
Self::Failure => "failure",
Self::Danger => "danger",
Self::Bug => "bug",
Self::Example => "example",
Self::Quote => "quote",
};
f.write_str(value)
}
}
/// The subset of information we care about during plugin runtime for custom directives.
///
/// This drops information only needed during CSS generation.
#[derive(Debug, Clone)]
pub(crate) struct CustomDirective {
pub directive: String,
pub aliases: Vec<String>,
pub title: Option<String>,
pub collapsible: Option<bool>,
}
impl From<(String, crate::book_config::CustomDirective)> for CustomDirective {
fn from((directive, config): (String, crate::book_config::CustomDirective)) -> Self {
let crate::book_config::CustomDirective {
aliases,
title,
collapsible,
..
} = config;
Self {
directive,
aliases,
title,
collapsible,
}
}
}
/// A map from the user given directive to underlying config.
///
/// The terminology is a bit mixed here - this map allows any input-directive,
/// and returns the output-directive config.
///
/// i.e. this is the step alias mapping happens at
#[derive(Debug, Clone, Default)]
pub(crate) struct CustomDirectiveMap {
inner: HashMap<String, CustomDirective>,
}
impl CustomDirectiveMap {
pub fn get(&self, key: &str) -> Option<&CustomDirective> {
self.inner.get(key)
}
}
impl FromIterator<CustomDirective> for CustomDirectiveMap {
fn from_iter<I: IntoIterator<Item = CustomDirective>>(iter: I) -> Self {
let mut inner = HashMap::default();
for config in iter.into_iter() {
inner
.entry(config.directive.clone())
.or_insert(config.clone());
for alias in config.aliases.iter() {
inner.entry(alias.clone()).or_insert(config.clone());
}
}
Self { inner }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub(crate) struct BuiltinDirectiveConfig {
/// Default collapsible value.
#[serde(default)]
pub collapsible: Option<bool>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum RenderTextMode {
Strip,
Html,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum CssId {
/// id="my-id" in the admonishment
///
/// used directly for the id field
Verbatim(String),
/// the prefix from default.css_id_prefix (or "admonish-" if not specified)
///
/// will generate the rest of the id based on the title
Prefix(String),
}
#[derive(Debug, Clone, Default)]
pub(crate) struct Overrides {
pub book: AdmonitionDefaults,
pub builtin: HashMap<BuiltinDirective, BuiltinDirectiveConfig>,
pub custom: CustomDirectiveMap,
}

13
v2.md Normal file
View File

@@ -0,0 +1,13 @@
# v2 Notes
## Compatibility to drop
- v1 config loading system from block info strings
- v2 config loading system from block info strings
- Support for `custom` configuration, moved to `directive.custom`
- `css-id-prefix` kebab case
## Wishlist
- `mdbook` not to use a broken version of `toml` in the public api
- `mdbook` to support loading css files from plugins