Compare commits

...

24 Commits

Author SHA1 Message Date
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
42 changed files with 1518 additions and 299 deletions

View File

@@ -72,26 +72,36 @@ jobs:
matrix:
os:
- ubuntu-20.04
# - windows-2019
rust:
- stable
- beta
- 1.66.0
experimental:
- false
# Run a canary test on nightly that's allowed to fail
include:
# Run a canary test on nightly that's allowed to fail
- os: ubuntu-20.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:
# Don't bother retesting stable linux, we did it in the comprehensive test
- os: ubuntu-20.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@v4
- name: Cache build files

View File

@@ -2,17 +2,64 @@
## Unreleased
## 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)
- 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)
- 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
@@ -65,7 +112,7 @@ It unintentionally increased the MSRV from 1.66.0
### 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))

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.

27
Cargo.lock generated
View File

@@ -109,6 +109,12 @@ version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]]
name = "arrayvec"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]]
name = "autocfg"
version = "1.1.0"
@@ -625,6 +631,17 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
[[package]]
name = "hex_color"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d37f101bf4c633f7ca2e4b5e136050314503dd198e78e325ea602c327c484ef0"
dependencies = [
"arrayvec",
"rand",
"serde",
]
[[package]]
name = "html5ever"
version = "0.26.0"
@@ -952,14 +969,16 @@ dependencies = [
[[package]]
name = "mdbook-admonish"
version = "1.12.1"
version = "1.15.0"
dependencies = [
"anyhow",
"clap",
"env_logger",
"hex_color",
"log",
"mdbook",
"once_cell",
"path-slash",
"pretty_assertions",
"pulldown-cmark",
"regex",
@@ -1126,6 +1145,12 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "path-slash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42"
[[package]]
name = "percent-encoding"
version = "2.3.0"

View File

@@ -1,7 +1,8 @@
[package]
name = "mdbook-admonish"
version = "1.12.1"
version = "1.15.0"
edition = "2021"
rust-version = "1.66.0"
authors = ["Tom Milligan <code@tommilligan.net>"]
description = "A preprocessor for mdbook to add Material Design admonishments."
@@ -27,11 +28,14 @@ path = "src/lib.rs"
[dependencies]
anyhow = "1.0.75"
# Note: clap 4.4 increases MSRV to 1.70.0 (2023-06-01)
clap = { version = "~4.3", default_features = false, features = ["std", "derive"], optional = true }
env_logger = { version = "0.10", default_features = false, optional = true }
# To use MSRV supported dependencies, install using the lockfile with
# `cargo install mdbook-admonish --locked`
clap = { version = "4.3", default-features = false, features = ["std", "derive"], optional = true }
env_logger = { version = "0.10", default-features = false, optional = true }
log = "0.4.20"
mdbook = "0.4.35"
once_cell = "1.18.0"
path-slash = "0.2.1"
pulldown-cmark = "0.9.3"
regex = "1.9.6"
semver = "1.0.19"
@@ -42,6 +46,7 @@ serde_json = "1.0.107"
toml_mdbook = { package = "toml", version = "0.5.11" }
toml = "0.8.1"
toml_edit = { version = "0.20.1", optional = true }
hex_color = { version = "3.0.0", features = ["serde"] }
[dev-dependencies]
pretty_assertions = "1.4.0"

View File

@@ -70,6 +70,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 +104,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 +135,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 +161,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

@@ -9,7 +9,12 @@ title = "The mdbook-admonish book"
[preprocessor.admonish]
command = "mdbook-admonish"
assets_version = "2.0.2" # do not edit: managed by `mdbook-admonish install`
assets_version = "3.0.1" # do not edit: managed by `mdbook-admonish install`
[[preprocessor.admonish.custom]]
directive = "expensive"
icon = "./money-bag.svg"
color = "#24ab38"
[preprocessor.toc]
command = "mdbook-toc"
@@ -18,4 +23,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

@@ -161,6 +161,23 @@ 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`:
@@ -176,3 +193,47 @@ Will yield something like the following HTML, which you can then apply styles to
```admonish 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,31 @@ Subfields:
- For the `html` renderer, the default value is `html`.
- For all other renderers, the default value is `preserve`.
### `custom`
Optional.
Additional type of block to support.
You must run `mdbook-admonish generate-custom` after updating these values, to generate the correct styles.
Add blocks using TOML's [Array of Tables](https://toml.io/en/v1.0.0#array-of-tables) notation:
```toml
[[preprocessor.admonish.custom]]
directive = "expensive"
icon = "./money-bag.svg"
color = "#24ab38"
aliases = ["money", "cash", "budget"]
```
Subfields:
- `directive`: The keyword to use this type of block.
- `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.
- `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 +118,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

@@ -4,7 +4,9 @@
"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 ."
},

View File

@@ -22,6 +22,7 @@
@use "sass:color";
@use "sass:list";
@use "./lib";
// ----------------------------------------------------------------------------
// Variables
@@ -30,55 +31,47 @@
/// Admonition flavours
$admonitions: (
// pencil
note: $clr-blue-a200
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
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
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
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
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
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
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
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
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
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
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
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
@@ -165,6 +158,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;
@@ -186,6 +182,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;
@@ -225,47 +224,20 @@ summary.admonition-title {
}
}
// ----------------------------------------------------------------------------
// 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;
}
}
: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) {
@@ -285,6 +257,7 @@ summary.admonition-title {
background-color: var(--sidebar-bg);
color: var(--sidebar-fg);
}
& .admonition-anchor-link {
&:link,
&:visited {

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;
}
}
}
}

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);

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.0.2" # 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.0.2" # 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,5 +1,5 @@
<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">
<div class="admonition-title">
<p>What <i>is</i> this?</p>
<p><a class="admonition-anchor-link" href="#admonition-what-is-this"></a></p>
@@ -9,7 +9,7 @@
<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">
<div class="admonition-title">
<p>Note</p>
<p><a class="admonition-anchor-link" href="#admonition-note"></a></p>
@@ -18,12 +18,21 @@
<p>Simples</p>
</div>
</div>
<div id="admonition-default" class="admonition warning">
<div id="admonition-frog" class="admonition admonish-frog">
<div class="admonition-title">
<p>Frog</p>
<p><a class="admonition-anchor-link" href="#admonition-frog"></a></p>
</div>
<div>
<p>Custom frog directive</p>
</div>
</div>
<div id="admonition-default" class="admonition admonish-warning">
<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">
<div class="admonition-title">
<p>Error rendering admonishment</p>
<p><a class="admonition-anchor-link" href="#admonition-error-rendering-admonishment"></a></p>
@@ -44,7 +53,7 @@ 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">
<summary class="admonition-title">
<p>Note</p>
<p><a class="admonition-anchor-link" href="#admonition-note-1"></a></p>
@@ -53,7 +62,7 @@ No title, only body
<p>Hidden on load</p>
</div>
</details>
<div id="admonition-warning" class="admonition warning">
<div id="admonition-warning" class="admonition admonish-warning">
<div class="admonition-title">
<p>Warning</p>
<p><a class="admonition-anchor-link" href="#admonition-warning"></a></p>
@@ -62,7 +71,7 @@ No title, only body
<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">
<div class="admonition-title">
<p>Note</p>
<p><a class="admonition-anchor-link" href="#admonition-note-2"></a></p>
@@ -72,7 +81,7 @@ No title, only body
</code></pre>
</div>
</div>
<div id="admonition-note-3" class="admonition note">
<div id="admonition-note-3" class="admonition admonish-note">
<div class="admonition-title">
<p>Note</p>
<p><a class="admonition-anchor-link" href="#admonition-note-3"></a></p>
@@ -99,7 +108,7 @@ let x = 20;
</li>
<li>
<p>Thing two</p>
<div id="admonition-note-4" class="admonition note">
<div id="admonition-note-4" class="admonition admonish-note">
<div class="admonition-title">
<p>Note</p>
<p><a class="admonition-anchor-link" href="#admonition-note-4"></a></p>

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
```

View File

@@ -18,7 +18,20 @@ 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

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.35"
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.0.2

View File

@@ -1,20 +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;
@@ -71,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 {
@@ -86,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;
@@ -119,204 +107,223 @@ details[open].admonition > summary.admonition-title::after {
transform: rotate(90deg);
}
: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;

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,9 +108,53 @@ 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_mdbook::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,
@@ -122,6 +180,16 @@ 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());
@@ -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'");
@@ -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,6 +2,7 @@ use anyhow::{Context, Result};
use mdbook::preprocess::PreprocessorContext;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use crate::types::AdmonitionDefaults;
@@ -15,7 +16,11 @@ pub(crate) fn admonish_config_from_context(ctx: &PreprocessorContext) -> Result<
.get_preprocessor("admonish")
.context("No configuration for mdbook-admonish in book.toml")?,
)?;
toml::from_str(&table).context("Invalid mdbook-admonish configuration in book.toml")
admonish_config_from_str(&table)
}
pub(crate) fn admonish_config_from_str(data: &str) -> Result<Config> {
toml::from_str(data).context("Invalid mdbook-admonish configuration in book.toml")
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
@@ -31,6 +36,29 @@ pub(crate) struct Config {
#[serde(default)]
pub assets_version: Option<String>,
#[serde(default)]
pub custom: Vec<CustomDirective>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub(crate) struct CustomDirective {
/// The primary directive. Used for CSS classnames
pub directive: String,
/// 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>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]

View File

@@ -9,6 +9,7 @@ mod v2;
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>,
}
@@ -69,18 +70,22 @@ 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,
}

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

@@ -10,6 +10,8 @@ struct UserInput {
#[serde(default)]
title: Option<String>,
#[serde(default)]
id: Option<String>,
#[serde(default)]
class: Option<String>,
#[serde(default)]
collapsible: Option<bool>,
@@ -88,6 +90,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,
})
@@ -105,6 +108,7 @@ mod test {
InstanceConfig {
directive: "".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
}
@@ -114,6 +118,7 @@ mod test {
InstanceConfig {
directive: "".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
}
@@ -126,6 +131,7 @@ mod test {
InstanceConfig {
directive: "note".to_owned(),
title: Some("Никита".to_owned()),
id: None,
additional_classnames: vec!["additional".to_owned(), "classname".to_owned()],
collapsible: Some(true),
}
@@ -136,6 +142,7 @@ mod test {
InstanceConfig {
directive: "".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
}
@@ -146,6 +153,7 @@ mod test {
InstanceConfig {
directive: "info".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
}
@@ -156,10 +164,22 @@ mod test {
InstanceConfig {
directive: "info".to_owned(),
title: Some("Information".to_owned()),
id: None,
additional_classnames: Vec::new(),
collapsible: Some(false),
}
);
// Test custom id
assert_eq!(
from_config_string(r#"info title="My Info" id="my-info-custom-id""#).unwrap(),
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());
}

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.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 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.directive, &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,17 @@
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::{AdmonitionDefaults, CustomDirectiveMap, RenderTextMode},
};
pub(crate) fn preprocess(
content: &str,
on_failure: OnFailure,
admonition_defaults: &AdmonitionDefaults,
custom_directives: &CustomDirectiveMap,
render_text_mode: RenderTextMode,
) -> MdbookResult<String> {
let mut id_counter = Default::default();
@@ -34,6 +34,7 @@ pub(crate) fn preprocess(
let admonition = match parse_admonition(
info_string.as_ref(),
admonition_defaults,
custom_directives,
span_content,
on_failure,
indent,
@@ -47,7 +48,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(),
};
@@ -137,6 +138,7 @@ mod test {
content,
OnFailure::Continue,
&AdmonitionDefaults::default(),
&CustomDirectiveMap::default(),
RenderTextMode::Html,
)
.unwrap()
@@ -153,7 +155,7 @@ Text
let expected = r##"# Chapter
<div id="admonition-note" class="admonition note">
<div id="admonition-note" class="admonition admonish-note">
<div class="admonition-title">
Note
@@ -185,7 +187,7 @@ Text
let expected = r##"# Chapter
<div id="admonition-note" class="admonition note">
<div id="admonition-note" class="admonition admonish-note">
<div class="admonition-title">
Note
@@ -217,7 +219,7 @@ Text
let expected = r##"# Chapter
<div id="admonition-warning" class="admonition warning">
<div id="admonition-warning" class="admonition admonish-warning">
<div class="admonition-title">
Warning
@@ -247,7 +249,7 @@ Text
let expected = r##"# Chapter
<div id="admonition-caution" class="admonition warning">
<div id="admonition-caution" class="admonition admonish-warning">
<div class="admonition-title">
Caution
@@ -277,7 +279,7 @@ Text
let expected = r##"# Chapter
<div id="admonition-read-this" class="admonition warning">
<div id="admonition-read-this" class="admonition admonish-warning">
<div class="admonition-title">
Read **this**!
@@ -371,7 +373,7 @@ 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">
<div class="admonition-title">
And "<i>in</i>" the title
@@ -401,7 +403,7 @@ hello
let expected = r##"
<div id="admonition-trademark" class="admonition warning">
<div id="admonition-trademark" class="admonition admonish-warning">
<div class="admonition-title">
Trademark™
@@ -430,7 +432,7 @@ 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">
<div class="admonition-title">
Tip
@@ -458,7 +460,7 @@ 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">
<div class="admonition-title">
Developers don't want you to know this one weird tip!
@@ -485,7 +487,7 @@ Will have bonus classnames
let expected = r#"
<div id="admonition-default" class="admonition note">
<div id="admonition-default" class="admonition admonish-note">
<div>
@@ -511,7 +513,7 @@ Content one.
let expected = r##"
<div id="admonition-my-note" class="admonition note">
<div id="admonition-my-note" class="admonition admonish-note">
<div class="admonition-title">
My Note
@@ -526,7 +528,7 @@ Content zero.
</div>
<div id="admonition-my-note-1" class="admonition note">
<div id="admonition-my-note-1" class="admonition admonish-note">
<div class="admonition-title">
My Note
@@ -554,7 +556,7 @@ 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">
<div class="admonition-title">
Article Heading
@@ -582,7 +584,7 @@ Bonus content!
let expected = r##"
<div id="admonition-error-rendering-admonishment" class="admonition bug">
<div id="admonition-error-rendering-admonishment" class="admonition admonish-bug">
<div class="admonition-title">
Error rendering admonishment
@@ -630,6 +632,7 @@ Bonus content!
content,
OnFailure::Bail,
&AdmonitionDefaults::default(),
&CustomDirectiveMap::default(),
RenderTextMode::Html
)
.unwrap_err()
@@ -657,6 +660,7 @@ x = 20;
content,
OnFailure::Bail,
&AdmonitionDefaults::default(),
&CustomDirectiveMap::default(),
RenderTextMode::Strip
)
.unwrap(),
@@ -682,7 +686,7 @@ Hidden
let expected = r##"
<details id="admonition-note" class="admonition note">
<details id="admonition-note" class="admonition admonish-note">
<summary class="admonition-title">
Note
@@ -711,7 +715,7 @@ Text
let expected = r##"# Chapter
<div id="admonition-admonish" class="admonition note">
<div id="admonition-admonish" class="admonition admonish-note">
<div class="admonition-title">
Admonish
@@ -732,8 +736,10 @@ Text
OnFailure::Continue,
&AdmonitionDefaults {
title: Some("Admonish".to_owned()),
css_id_prefix: None,
collapsible: false,
},
&CustomDirectiveMap::default(),
RenderTextMode::Html,
)
.unwrap();
@@ -751,7 +757,7 @@ Text
let expected = r#"# Chapter
<div id="admonition-default" class="admonition note">
<div id="admonition-default" class="admonition admonish-note">
<div>
A simple admonition.
@@ -766,8 +772,10 @@ Text
OnFailure::Continue,
&AdmonitionDefaults {
title: Some("Admonish".to_owned()),
css_id_prefix: None,
collapsible: false,
},
&CustomDirectiveMap::default(),
RenderTextMode::Html,
)
.unwrap();
@@ -785,7 +793,7 @@ Text
let expected = r#"# Chapter
<div id="admonition-default" class="admonition note">
<div id="admonition-default" class="admonition admonish-note">
<div>
A simple admonition.
@@ -798,6 +806,222 @@ 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">
<div class="admonition-title">
Check
<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">
<div class="admonition-title">
Check
<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">
<div class="admonition-title">
Check Mark
<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 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">
<div class="admonition-title">
Info
<a class="admonition-anchor-link" href="#info"></a>
</div>
<div>
A simple admonition.
</div>
</div>
Text
"##;
let preprocess_result = preprocess(
content,
OnFailure::Continue,
&AdmonitionDefaults {
title: Some("Info".to_owned()),
css_id_prefix: Some("".to_owned()),
collapsible: false,
},
&CustomDirectiveMap::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">
<div class="admonition-title">
My Title
<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,
&AdmonitionDefaults {
title: Some("Info".to_owned()),
css_id_prefix: Some("prefix-".to_owned()),
collapsible: false,
},
&CustomDirectiveMap::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">
<div class="admonition-title">
My Title
<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,
&AdmonitionDefaults {
title: Some("Info".to_owned()),
css_id_prefix: Some("ignored-prefix-".to_owned()),
collapsible: false,
},
&CustomDirectiveMap::default(),
RenderTextMode::Html,
)
.unwrap();
assert_eq!(expected, preprocess_result);
}
#[test]
fn list_embed() {
let content = r#"# Chapter
@@ -832,7 +1056,7 @@ Text
1. Thing two
<div id="admonition-note" class="admonition note">
<div id="admonition-note" class="admonition admonish-note">
<div class="admonition-title">
Note

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::{AdmonitionDefaults, BuiltinDirective, CssId, CustomDirectiveMap},
};
/// Given the content in the span of the code block, and the info string,
@@ -21,6 +20,7 @@ use crate::{
pub(crate) fn parse_admonition<'a>(
info_string: &'a str,
admonition_defaults: &'a AdmonitionDefaults,
custom_directives: &'a CustomDirectiveMap,
content: &'a str,
on_failure: OnFailure,
indent: usize,
@@ -28,7 +28,8 @@ pub(crate) fn parse_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, admonition_defaults, custom_directives)?;
let info = match info {
Ok(info) => info,
Err(message) => {
@@ -44,8 +45,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!(

View File

@@ -8,7 +8,7 @@ use mdbook::{
use crate::{
book_config::{admonish_config_from_context, Config, RenderMode},
markdown::preprocess,
types::RenderTextMode,
types::{CustomDirectiveMap, RenderTextMode},
};
pub struct Admonish;
@@ -22,6 +22,8 @@ impl Preprocessor for Admonish {
let config = admonish_config_from_context(ctx)?;
ensure_compatible_assets_version(&config)?;
let custom_directives =
CustomDirectiveMap::from_configs(config.custom.into_iter().map(Into::into));
let on_failure = config.on_failure;
let admonition_defaults = config.default;
@@ -57,6 +59,7 @@ impl Preprocessor for Admonish {
&chapter.content,
on_failure,
&admonition_defaults,
&custom_directives,
render_text_mode,
)
.map(|md| {
@@ -170,7 +173,7 @@ x = 20;
"#;
let expected_content = r##"
<div id="admonition-title" class="admonition note">
<div id="admonition-title" class="admonition admonish-note">
<div class="admonition-title">
Title
@@ -190,7 +193,7 @@ x = 20;
let ctx = mock_context(
&json!({
"assets_version": "2.0.0"
"assets_version": "3.0.0"
}),
"html",
);
@@ -212,7 +215,7 @@ x = 20;
"#;
let ctx = mock_context(
&json!({
"assets_version": "2.0.0"
"assets_version": "3.0.0"
}),
"test",
);
@@ -242,7 +245,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,33 +2,14 @@ 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,
@@ -39,6 +20,7 @@ impl<'a> Admonition<'a> {
let AdmonitionMeta {
directive,
title,
css_id,
additional_classnames,
collapsible,
} = info;
@@ -46,26 +28,30 @@ 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),
CssId::Prefix(prefix) => {
let id = unique_id_from_content(
if !self.title.is_empty() {
&self.title
} else {
ANCHOR_ID_DEFAULT
},
id_counter,
);
Cow::Owned(format!("{}{}", prefix, id))
}
};
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 indent = " ".repeat(self.indent);
@@ -78,7 +64,7 @@ impl<'a> Admonition<'a> {
{indent}
{indent}{title}
{indent}
{indent}<a class="admonition-anchor-link" href="#{ANCHOR_ID_PREFIX}-{anchor_id}"></a>
{indent}<a class="admonition-anchor-link" href="#{anchor_id}"></a>
{indent}</{title_block}>
"##
))
@@ -86,14 +72,12 @@ impl<'a> Admonition<'a> {
Cow::Borrowed("")
};
let mut additional_class = format!("admonish-{}", self.directive);
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);
additional_class.push(' ');
additional_class.push_str(additional_classname);
}
additional_class = Cow::Owned(buffer);
}
let admonition_block = if self.collapsible { "details" } else { "div" };
@@ -103,7 +87,7 @@ impl<'a> Admonition<'a> {
// rendered as markdown paragraphs.
format!(
r#"
{indent}<{admonition_block} id="{ANCHOR_ID_PREFIX}-{anchor_id}" class="admonition {additional_class}">
{indent}<{admonition_block} id="{anchor_id}" class="admonition {additional_class}">
{title_html}{indent}<div>
{indent}
{indent}{content}
@@ -121,5 +105,4 @@ impl<'a> Admonition<'a> {
}
}
const ANCHOR_ID_PREFIX: &str = "admonition";
const ANCHOR_ID_DEFAULT: &str = "default";

View File

@@ -1,5 +1,8 @@
use crate::config::InstanceConfig;
use crate::types::{AdmonitionDefaults, Directive};
use crate::types::{
AdmonitionDefaults, BuiltinDirective, CssId, CustomDirective, CustomDirectiveMap,
};
use std::fmt;
use std::str::FromStr;
/// All information required to render an admonition.
@@ -7,27 +10,73 @@ 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,
custom_directives: &CustomDirectiveMap,
) -> 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, defaults, custom_directives)))
}
/// 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,
defaults: &AdmonitionDefaults,
custom_directives: &CustomDirectiveMap,
) -> Self {
let InstanceConfig {
directive: raw_directive,
title,
id,
additional_classnames,
collapsible,
} = raw;
@@ -36,27 +85,53 @@ impl AdmonitionMeta {
let title = title.or_else(|| defaults.title.clone());
let collapsible = collapsible.unwrap_or(defaults.collapsible);
let directive = Directive::from_str(custom_directives, &raw_directive);
// 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(
defaults
.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(),
@@ -69,6 +144,18 @@ mod test {
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 +163,17 @@ mod test {
InstanceConfig {
directive: " ".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
&Default::default()
&Default::default(),
&CustomDirectiveMap::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 +187,136 @@ mod test {
InstanceConfig {
directive: " ".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
&AdmonitionDefaults {
title: Some("Important!!!".to_owned()),
css_id_prefix: Some("custom-prefix-".to_owned()),
collapsible: true,
},
&CustomDirectiveMap::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,
},
&AdmonitionDefaults {
title: Some("Important!!!".to_owned()),
css_id_prefix: Some("ignored-custom-prefix-".to_owned()),
collapsible: true,
},
&CustomDirectiveMap::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,
},
&AdmonitionDefaults::default(),
&CustomDirectiveMap::from_configs(vec![CustomDirective {
directive: "frog".to_owned(),
aliases: Vec::new(),
title: None,
}]),
),
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,
},
&AdmonitionDefaults::default(),
&CustomDirectiveMap::from_configs(vec![CustomDirective {
directive: "frog".to_owned(),
aliases: Vec::new(),
title: Some("🏳️‍🌈".to_owned()),
}]),
),
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,
},
&AdmonitionDefaults::default(),
&CustomDirectiveMap::from_configs(vec![CustomDirective {
directive: "frog".to_owned(),
aliases: vec!["newt".to_owned(), "toad".to_owned()],
title: Some("🏳️‍🌈".to_owned()),
}]),
),
AdmonitionMeta {
directive: "frog".to_owned(),
title: "Still a frog".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,18 +1,29 @@
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.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Default)]
#[serde(rename_all = "kebab-case")]
pub(crate) struct AdmonitionDefaults {
#[serde(default)]
pub(crate) title: Option<String>,
#[serde(default)]
pub(crate) collapsible: bool,
#[serde(default)]
pub(crate) css_id_prefix: Option<String>,
}
/// 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)]
pub(crate) enum Directive {
pub(crate) enum BuiltinDirective {
Note,
Abstract,
Info,
@@ -27,7 +38,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 +60,101 @@ 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(Clone)]
pub(crate) struct CustomDirective {
pub directive: String,
pub aliases: Vec<String>,
pub title: Option<String>,
}
impl From<crate::book_config::CustomDirective> for CustomDirective {
fn from(other: crate::book_config::CustomDirective) -> Self {
let crate::book_config::CustomDirective {
directive,
aliases,
title,
..
} = other;
Self {
directive,
aliases,
title,
}
}
}
/// 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(Default)]
pub(crate) struct CustomDirectiveMap {
inner: HashMap<String, CustomDirective>,
}
impl CustomDirectiveMap {
pub fn get(&self, key: &str) -> Option<&CustomDirective> {
self.inner.get(key)
}
pub fn from_configs<T>(configs: T) -> Self
where
T: IntoIterator<Item = CustomDirective>,
{
let mut inner = HashMap::default();
for directive in configs.into_iter() {
inner
.entry(directive.directive.clone())
.or_insert(directive.clone());
for alias in directive.aliases.iter() {
inner.entry(alias.clone()).or_insert(directive.clone());
}
}
Self { inner }
}
}
#[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),
}