mirror of
https://github.com/tommilligan/mdbook-admonish.git
synced 2025-12-27 13:30:47 -05:00
Compare commits
113 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea6c0a687a | ||
|
|
50b9187ee5 | ||
|
|
fbd07ecaf0 | ||
|
|
37d03b872e | ||
|
|
fa91fe3778 | ||
|
|
d59dce3533 | ||
|
|
21d86546ff | ||
|
|
79ee592f56 | ||
|
|
0d3eb11a2c | ||
|
|
43579f902b | ||
|
|
7ee3643dc3 | ||
|
|
c4bf2608e7 | ||
|
|
d4667a5f55 | ||
|
|
8751b3cd24 | ||
|
|
516bbc6e7e | ||
|
|
fc5c5eae39 | ||
|
|
147a9bdb98 | ||
|
|
540ea8e5e8 | ||
|
|
ae4bac21ba | ||
|
|
f0c4d3764e | ||
|
|
a942a8094e | ||
|
|
d10a427b0f | ||
|
|
41a3b05094 | ||
|
|
d0f73baa3f | ||
|
|
4c40418090 | ||
|
|
30c9ad2269 | ||
|
|
f7482415a7 | ||
|
|
08397fbffa | ||
|
|
b78fc39031 | ||
|
|
fe7b475753 | ||
|
|
a56976d085 | ||
|
|
08967d550d | ||
|
|
8fa1411095 | ||
|
|
a1e5cfa48d | ||
|
|
33fd522d68 | ||
|
|
4805398359 | ||
|
|
c681ff922d | ||
|
|
cd0726aaf2 | ||
|
|
a08967e073 | ||
|
|
f7e6970fa3 | ||
|
|
5d2124b319 | ||
|
|
d79ebb4fad | ||
|
|
9df896cd77 | ||
|
|
ffb819c315 | ||
|
|
f278374c88 | ||
|
|
2c18292401 | ||
|
|
294af2478c | ||
|
|
278d17792b | ||
|
|
9f6c73091a | ||
|
|
9f221abc12 | ||
|
|
82c7bd4fd9 | ||
|
|
9bca2a66df | ||
|
|
80cce8480c | ||
|
|
5d5b73ded6 | ||
|
|
c17a66440c | ||
|
|
068a375647 | ||
|
|
f04016d017 | ||
|
|
3f8bf86ac3 | ||
|
|
8045e217c9 | ||
|
|
85fde44c09 | ||
|
|
a2c3e49ef9 | ||
|
|
8a0ecc5dd1 | ||
|
|
467f5f8f13 | ||
|
|
c4a05dae9d | ||
|
|
1bb14684c4 | ||
|
|
b3798b4d9f | ||
|
|
26c344a1e6 | ||
|
|
b1e6a5ee1e | ||
|
|
0d7b64d1b8 | ||
|
|
9730cd0aaa | ||
|
|
dc219f755d | ||
|
|
a2524f890b | ||
|
|
c3207e4d16 | ||
|
|
ab63c90231 | ||
|
|
d5bdde1f5c | ||
|
|
52ca8fc831 | ||
|
|
e1ea411e9a | ||
|
|
31d5a27a6d | ||
|
|
0f0e02702c | ||
|
|
7235d5f349 | ||
|
|
0304995dbb | ||
|
|
197d9cd059 | ||
|
|
04ff932f1f | ||
|
|
1526a5d814 | ||
|
|
eb21495797 | ||
|
|
ebe6f7815c | ||
|
|
c0c953c865 | ||
|
|
0fa34a66a0 | ||
|
|
dfc12c3652 | ||
|
|
108edfffc5 | ||
|
|
38f1933e8f | ||
|
|
a839038263 | ||
|
|
8501c812d9 | ||
|
|
933432afb2 | ||
|
|
496e8f7c6d | ||
|
|
1877fd1731 | ||
|
|
20286b3fae | ||
|
|
8e68cf919f | ||
|
|
02640dab1f | ||
|
|
771e9c9fd8 | ||
|
|
cce9343c47 | ||
|
|
20b158966b | ||
|
|
491f9cf341 | ||
|
|
6deaf1ea2b | ||
|
|
041e5a566f | ||
|
|
99b5a235cf | ||
|
|
39edc4d92a | ||
|
|
7773213093 | ||
|
|
e888fcd021 | ||
|
|
95dc7582ad | ||
|
|
b658eb6049 | ||
|
|
623291625a | ||
|
|
4dad5a86c8 |
62
.github/workflows/check.yml
vendored
62
.github/workflows/check.yml
vendored
@@ -6,12 +6,12 @@ jobs:
|
||||
# Fast test before we kick off all the other jobs
|
||||
fast-test:
|
||||
name: Fast test
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Cache build files
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
@@ -19,10 +19,9 @@ jobs:
|
||||
target
|
||||
key: fast-test-${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}
|
||||
- name: Install toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
- name: Install more toolchain
|
||||
run: rustup component add rustfmt clippy
|
||||
- name: Run tests
|
||||
@@ -32,28 +31,36 @@ jobs:
|
||||
detailed-test:
|
||||
needs: fast-test
|
||||
name: Test main target
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Cache build files
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
~/.cargo/bin
|
||||
cargo_target
|
||||
/tmp/cargo-install-target-dir
|
||||
key: detailed-test-${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}
|
||||
- name: Install toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
- name: Install node toolchain
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "yarn"
|
||||
cache-dependency-path: compile_assets/yarn.lock
|
||||
- name: Install additional test dependencies
|
||||
env:
|
||||
CARGO_TARGET_DIR: cargo_target
|
||||
CARGO_TARGET_DIR: "/tmp/cargo-install-target-dir"
|
||||
run: ./scripts/install
|
||||
- name: Run check script
|
||||
run: ./scripts/check
|
||||
@@ -65,31 +72,41 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-20.04
|
||||
# - windows-2019
|
||||
- ubuntu-24.04
|
||||
rust:
|
||||
- stable
|
||||
- beta
|
||||
- 1.66.0
|
||||
- 1.82.0
|
||||
experimental:
|
||||
- false
|
||||
# Run a canary test on nightly that's allowed to fail
|
||||
include:
|
||||
- os: ubuntu-20.04
|
||||
# Run a canary test on nightly that's allowed to fail
|
||||
- os: ubuntu-24.04
|
||||
rust: nightly
|
||||
experimental: true
|
||||
# Don't bother retesting stable linux, we did it in the comprehensive test
|
||||
# Test only stable on Windows, presume we'd get same result on other
|
||||
# versions as Linux
|
||||
- os: windows-2022
|
||||
rust: stable
|
||||
experimental: false
|
||||
exclude:
|
||||
- os: ubuntu-20.04
|
||||
# Don't bother retesting stable linux, we did it in the comprehensive test
|
||||
- os: ubuntu-24.04
|
||||
rust: stable
|
||||
experimental: false
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: ${{ matrix.experimental }}
|
||||
steps:
|
||||
# This is required, otherwise we get files with CRLF on Windows
|
||||
# Which causes tests relying on data loaded from files to fail
|
||||
- name: Set git to use LF everywhere
|
||||
run: |
|
||||
git config --global core.autocrlf false
|
||||
git config --global core.eol lf
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Cache build files
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
@@ -97,9 +114,8 @@ jobs:
|
||||
target
|
||||
key: test-${{ matrix.os }}-${{ matrix.rust }}-cargo-${{ hashFiles('**/Cargo.toml') }}
|
||||
- name: Install toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
- name: Run tests
|
||||
run: cargo test
|
||||
|
||||
64
.github/workflows/deploy.yml
vendored
64
.github/workflows/deploy.yml
vendored
@@ -16,14 +16,16 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- x86_64-unknown-linux-gnu
|
||||
- x86_64-unknown-linux-musl
|
||||
- x86_64-apple-darwin
|
||||
- x86_64-pc-windows-msvc
|
||||
include:
|
||||
- target: x86_64-unknown-linux-gnu
|
||||
- target: aarch64-unknown-linux-musl
|
||||
os: ubuntu-latest
|
||||
name: aarch64-unknown-linux-musl.tar.gz
|
||||
- target: x86_64-unknown-linux-gnu
|
||||
# Deliberately pinned to the same version `mdbook` uses to build
|
||||
# binaries, so we use the same glibc version
|
||||
#
|
||||
# ref: https://github.com/rust-lang/mdBook/pull/1955
|
||||
os: ubuntu-24.04
|
||||
name: x86_64-unknown-linux-gnu.tar.gz
|
||||
- target: x86_64-unknown-linux-musl
|
||||
os: ubuntu-latest
|
||||
@@ -37,40 +39,42 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Cache files between builds
|
||||
- name: Setup | Cache Cargo
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
# Note that we don't cache the `target` directory here
|
||||
# so we do a completely clean rebuild for artefacts
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- name: Setup | Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
profile: minimal
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Setup | musl tools
|
||||
if: matrix.target == 'x86_64-unknown-linux-musl'
|
||||
run: sudo apt install -y musl-tools
|
||||
- name: Setup | cross
|
||||
if: endsWith(matrix.target, '-unknown-linux-musl')
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cross
|
||||
|
||||
- name: Build | Build
|
||||
if: matrix.target != 'x86_64-unknown-linux-musl'
|
||||
if: ${{ !endsWith(matrix.target, '-unknown-linux-musl') }}
|
||||
run: cargo build --release --target ${{ matrix.target }}
|
||||
|
||||
- name: Build | Build (musl)
|
||||
if: matrix.target == 'x86_64-unknown-linux-musl'
|
||||
run: cargo build --release --target ${{ matrix.target }}
|
||||
if: endsWith(matrix.target, '-unknown-linux-musl')
|
||||
run: cross build --release --target ${{ matrix.target }}
|
||||
|
||||
- name: Post Setup | Extract tag name
|
||||
shell: bash
|
||||
run: echo "##[set-output name=tag;]$(echo ${GITHUB_REF#refs/tags/})"
|
||||
run: echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||||
id: extract_tag
|
||||
|
||||
- name: Post Setup | Prepare artifacts [Windows]
|
||||
@@ -78,7 +82,6 @@ jobs:
|
||||
run: |
|
||||
mkdir target/stage
|
||||
cd target/${{ matrix.target }}/release
|
||||
strip ${{ env.CRATE_NAME }}.exe
|
||||
7z a ../../stage/${{ env.CRATE_NAME }}-${{ steps.extract_tag.outputs.tag }}-${{ matrix.name }} ${{ env.CRATE_NAME }}.exe
|
||||
cd -
|
||||
- name: Post Setup | Prepare artifacts [-nix]
|
||||
@@ -86,14 +89,15 @@ jobs:
|
||||
run: |
|
||||
mkdir target/stage
|
||||
cd target/${{ matrix.target }}/release
|
||||
strip ${{ env.CRATE_NAME }}
|
||||
tar czvf ../../stage/${{ env.CRATE_NAME }}-${{ steps.extract_tag.outputs.tag }}-${{ matrix.name }} ${{ env.CRATE_NAME }}
|
||||
cd -
|
||||
- name: Post Setup | Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.CRATE_NAME }}-${{ steps.extract_tag.outputs.tag }}-${{ matrix.name }}
|
||||
path: target/stage/*
|
||||
# Idempotency: overwrite artefact by name if we're rerunning the deployment
|
||||
overwrite: true
|
||||
|
||||
# Create GitHub release with Rust build targets and release notes
|
||||
github_release:
|
||||
@@ -102,23 +106,23 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup | Artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- name: Setup | Extract version
|
||||
shell: bash
|
||||
run: echo "##[set-output name=version;]$(echo ${GITHUB_REF#refs/tags/v})"
|
||||
run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
|
||||
id: extract_version
|
||||
|
||||
- name: Setup | Release notes
|
||||
run: |
|
||||
cat CHANGELOG.md | sed -n '/^## ${{ steps.extract_version.outputs.version }}$/,/^## /p' | sed '$d' > RELEASE.md
|
||||
- name: Build | Publish
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: ${{ env.CRATE_NAME }}-*/${{ env.CRATE_NAME }}-*
|
||||
body_path: RELEASE.md
|
||||
@@ -129,24 +133,22 @@ jobs:
|
||||
publish:
|
||||
name: Publish to crates.io
|
||||
needs: github_release
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
~/.cargo/bin
|
||||
cargo_target
|
||||
# We reuse the cache from our detailed test environment, if available
|
||||
key: detailed-test-${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}
|
||||
- name: Install toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
- name: Publish crate
|
||||
env:
|
||||
CARGO_LOGIN_TOKEN: ${{ secrets.CARGO_LOGIN_TOKEN }}
|
||||
|
||||
17
.github/workflows/docs.yml
vendored
17
.github/workflows/docs.yml
vendored
@@ -11,32 +11,35 @@ permissions:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
~/.cargo/bin
|
||||
cargo_target
|
||||
/tmp/cargo-install-target-dir
|
||||
# We reuse the cache from our detailed test environment, if available
|
||||
key: detailed-test-${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}
|
||||
- name: Install toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
- name: Install mdbook
|
||||
env:
|
||||
CARGO_TARGET_DIR: "/tmp/cargo-install-target-dir"
|
||||
run: ./scripts/install-mdbook
|
||||
- name: Install mdbook extras
|
||||
env:
|
||||
CARGO_TARGET_DIR: "/tmp/cargo-install-target-dir"
|
||||
run: ./book/scripts/install-mdbook-extras
|
||||
- name: Build book
|
||||
run: ./scripts/build-book
|
||||
- name: Push docs
|
||||
uses: JamesIves/github-pages-deploy-action@v4.4.1
|
||||
uses: JamesIves/github-pages-deploy-action@v4
|
||||
with:
|
||||
branch: gh-pages
|
||||
folder: book/book
|
||||
|
||||
146
CHANGELOG.md
146
CHANGELOG.md
@@ -1,6 +1,142 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
## v1.20.0
|
||||
|
||||
### Changed
|
||||
|
||||
- MSRV (minimum supported rust version) is now 1.82.0 ([#227](https://github.com/tommilligan/mdbook-admonish/pull/227))
|
||||
- Collapsible blocks now show content in print view ([#228](https://github.com/tommilligan/mdbook-admonish/pull/228)). Thanks to [@igor-petruk](https://github.com/igor-petruk) for raising this issue.
|
||||
|
||||
## v1.19.0
|
||||
|
||||
### Changed
|
||||
|
||||
- MSRV (minimum supported rust version) is now 1.76.0 ([#208](https://github.com/tommilligan/mdbook-admonish/pull/208))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed blocks not rendering correctly in indented list items. Thanks to [@JorelAli](https://github.com/JorelAli) for the bug report! ([#224](https://github.com/tommilligan/mdbook-admonish/pull/224))
|
||||
|
||||
## v1.18.0
|
||||
|
||||
### Changed
|
||||
|
||||
- Add ARIA attributes to generated blocks. Thanks to [@toastal](https://github.com/toastal) for suggesting this feature! ([#195](https://github.com/tommilligan/mdbook-admonish/pull/195))
|
||||
- Note: This subtly alters the emitted HTML, and could cause additional styles applied to blocks to break. Native `mdbook-admonish` styles are not affected.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed some valid configurations producing TOML serialization errors. Thanks to [@DianaNites](https://github.com/DianaNites) for reporting this! ([#197](https://github.com/tommilligan/mdbook-admonish/pull/197))
|
||||
|
||||
## v1.17.1
|
||||
|
||||
### Fixed
|
||||
|
||||
- Removed a stray debug statement ([#186](https://github.com/tommilligan/mdbook-admonish/pull/186))
|
||||
|
||||
## v1.17.0
|
||||
|
||||
### Changed
|
||||
|
||||
- Blocks should have key-value options separated by commas. Existing syntax remains is supported for back-compatibility. See [the documentation on Additional Options](https://tommilligan.github.io/mdbook-admonish/#additional-options) for more details ([#181](https://github.com/tommilligan/mdbook-admonish/pull/181))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Titles contining `=` will now render correctly. Thanks to [@s00500](https://github.com/s00500) for the bug report! ([#181](https://github.com/tommilligan/mdbook-admonish/pull/181))
|
||||
|
||||
## v1.16.0
|
||||
|
||||
### Changed
|
||||
|
||||
- MSRV (minimum supported rust version) is now 1.74.0 ([#175](https://github.com/tommilligan/mdbook-admonish/pull/175))
|
||||
- `custom` directives should now be configured under the `directive.custom` option. Existing `custom` configurations are supported for back compatibility ([#179](https://github.com/tommilligan/mdbook-admonish/pull/174))
|
||||
|
||||
### Added
|
||||
|
||||
- Make blocks `collapsible` on a per-directive basis. Thanks to [@yannickseurin](https://github.com/yannickseurin) for contributing this feature! ([#174](https://github.com/tommilligan/mdbook-admonish/pull/174))
|
||||
|
||||
### Fixed
|
||||
|
||||
- The `css_id_prefix` option now uses snake case for consistency (kebab case remains supported for back compatibility). Thanks to [@yannickseurin](https://github.com/yannickseurin) for fixing this! ([#173](https://github.com/tommilligan/mdbook-admonish/pull/173))
|
||||
|
||||
## 1.15.0
|
||||
|
||||
### Added
|
||||
|
||||
- Support [custom directives](https://tommilligan.github.io/mdbook-admonish/overview.html#custom-blocks) with the new `mdbook-admonish generate-custom` helper. See the [mdbook-admonish book](https://tommilligan.github.io/mdbook-admonish/overview.html#custom-blocks) for guidance. Thanks to [@Sky9x](https://github.com/Sky9x) for helping design this feature! ([#165](https://github.com/tommilligan/mdbook-admonish/pull/165))
|
||||
|
||||
### Fixed
|
||||
|
||||
- `additional-css` unix style path normalization. Thanks to [@carlocorradini](https://github.com/carlocorradini) for reporting and fixing! ([#163](https://github.com/tommilligan/mdbook-admonish/pull/163))
|
||||
|
||||
## 1.14.0
|
||||
|
||||
### Changed
|
||||
|
||||
- Styles version updated to `3.0.1`. Run `mdbook-admonish install` to update.
|
||||
|
||||
### Added
|
||||
|
||||
- You can now set custom CSS ids for admonition blocks with the `id` field. Thanks to [@Sky9x](https://github.com/Sky9x) for contributing this feature! ([#144](https://github.com/tommilligan/mdbook-admonish/pull/144))
|
||||
- You can also now customize the CSS id prefix with the config option `default.css_id_prefix`
|
||||
|
||||
### Fixed
|
||||
|
||||
- Improve rendering of blocks in print/PDF view. Thanks to [@csk111165](https://github.com/csk111165) for the report ([#152](https://github.com/tommilligan/mdbook-admonish/issues/152))
|
||||
- Fix the default titles for `tldr` and `faq` directives looking bad. They now render as `TL;DR` and `FAQ` by default. Thanks [@joshka](https://github.com/joshka) for fixing this! ([#154](https://github.com/tommilligan/mdbook-admonish/pull/154))
|
||||
|
||||
## 1.13.1
|
||||
|
||||
### Changed
|
||||
|
||||
- Bumped internal `mdbook` version to `0.4.35` ([#142](https://github.com/tommilligan/mdbook-admonish/pull/142))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Relaxed `clap` dependency to fix compilation error when using other `mdbook-*` plugins. Thanks to [@joshka](https://github.com/joshka) for the [report](https://github.com/tommilligan/mdbook-admonish/pull/141)! ([#142](https://github.com/tommilligan/mdbook-admonish/pull/142))
|
||||
|
||||
## 1.13.0
|
||||
|
||||
### Changed
|
||||
|
||||
- Required styles version is now `^3.0.0` (release `1.13.0`). Run `mdbook-admonish install` to update.
|
||||
- Internal CSS classnames for directives are now prefixed with `admonish-`, so `warning` is now `admonish-warning`. This avoids a conflict with upstream classnames introduced in `mdbook 0.4.35`. Thanks to [@phoenixr-codes](https://github.com/phoenixr-codes) for the report and fix! ([#139](https://github.com/tommilligan/mdbook-admonish/pull/139))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Some minor inconsistencies in SCSS (and downstream CSS) styles were fixed by adopting Prettier linting ([#138](https://github.com/tommilligan/mdbook-admonish/pull/138))
|
||||
|
||||
## 1.12.1
|
||||
|
||||
### Fixed
|
||||
|
||||
- Panic when searching for an indent in non-ASCII content. Thanks to [@CoralPink](https://github.com/CoralPink) for the report! ([#128](https://github.com/tommilligan/mdbook-admonish/pull/128))
|
||||
|
||||
## 1.12.0
|
||||
|
||||
### Added
|
||||
|
||||
- Admonitions are now supported when indented inside other elements, such as a list. Thanks to [@mattburgess](https://github.com/mattburgess) for the report! ([#124](https://github.com/tommilligan/mdbook-admonish/pull/124))
|
||||
|
||||
## 1.11.1
|
||||
|
||||
### Fixed
|
||||
|
||||
- Reverted internal dependency upgrades that unintentionally increased MSRV from 1.66.0 in 1.11.0
|
||||
|
||||
## 1.11.0 (yanked)
|
||||
|
||||
**Note:** This release has been yanked.
|
||||
|
||||
It unintentionally increased the MSRV from 1.66.0
|
||||
|
||||
### Changed
|
||||
|
||||
- `gnu` prebuilt binaries are now built on `ubuntu-20.04` to match `mdbook` binaries. Thanks to [@eitsupi](https://github.com/eitsupi) for the fix! ([#118](https://github.com/tommilligan/mdbook-admonish/pull/118))
|
||||
|
||||
### Added
|
||||
|
||||
- `aarch64-unknown-linux-musl` prebuilt binary now available ([#119](https://github.com/tommilligan/mdbook-admonish/pull/119))
|
||||
|
||||
## 1.10.2
|
||||
|
||||
@@ -33,7 +169,7 @@
|
||||
|
||||
### Changed
|
||||
|
||||
- Styles updated to `^2.0.1`. Run `mdbook-admonish install` to update.
|
||||
- Styles version updated to `2.0.1`. Run `mdbook-admonish install` to update.
|
||||
- MSRV (minimum supported rust version) is now 1.64.0 for clap v4 ([#79](https://github.com/tommilligan/mdbook-admonish/pull/79))
|
||||
- More verbose error messages for invalid TOML configurations ([#79](https://github.com/tommilligan/mdbook-admonish/pull/79))
|
||||
|
||||
@@ -136,7 +272,11 @@ This behaviour is [documented in the readme here](https://github.com/tommilligan
|
||||
- Flattened indentation of generated HTML, otherwise it's styled as a markdown code block
|
||||
- Fixed edge cases where the info string changes length when parsed, causing title/body to be incorrectly split
|
||||
|
||||
## 1.3.0
|
||||
## 1.3.0 (yanked)
|
||||
|
||||
**Note:** This release has been yanked.
|
||||
|
||||
It unintentionally introduced a serious parsing bug.
|
||||
|
||||
### Added
|
||||
|
||||
|
||||
31
CONTRIBUTING.md
Normal file
31
CONTRIBUTING.md
Normal 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.
|
||||
1854
Cargo.lock
generated
1854
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
40
Cargo.toml
40
Cargo.toml
@@ -1,7 +1,8 @@
|
||||
[package]
|
||||
name = "mdbook-admonish"
|
||||
version = "1.10.2"
|
||||
version = "1.20.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.76.0"
|
||||
|
||||
authors = ["Tom Milligan <code@tommilligan.net>"]
|
||||
description = "A preprocessor for mdbook to add Material Design admonishments."
|
||||
@@ -17,27 +18,32 @@ name = "mdbook-admonish"
|
||||
path = "src/bin/mdbook-admonish.rs"
|
||||
required-features = ["cli"]
|
||||
|
||||
[profile.release]
|
||||
strip = true
|
||||
|
||||
[lib]
|
||||
name = "mdbook_admonish"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.72"
|
||||
clap = { version = "4", default_features = false, features = ["std", "derive"], optional = true }
|
||||
env_logger = { version = "0.10", default_features = false, optional = true }
|
||||
log = "0.4.19"
|
||||
mdbook = "0.4.34"
|
||||
once_cell = "1.18.0"
|
||||
pulldown-cmark = "0.9.3"
|
||||
regex = "1.9.3"
|
||||
semver = "1.0.18"
|
||||
serde = { version = "1.0.183", features = ["derive"] }
|
||||
serde_json = "1.0.104"
|
||||
# The version of toml that mdbook uses internally (and uses in it's public api)
|
||||
# Only used for compatilibilty with the mdbook public api
|
||||
toml_mdbook = { package = "toml", version = "0.5.11" }
|
||||
toml = "0.7.6"
|
||||
toml_edit = { version = "0.19.14", optional = true }
|
||||
anyhow = "1.0.86"
|
||||
# Note: clap 4.4 increases MSRV to 1.70.0 (2023-06-01)
|
||||
# To use MSRV supported dependencies, install using the lockfile with
|
||||
# `cargo install mdbook-admonish --locked`
|
||||
clap = { version = "4.5", default-features = false, features = ["std", "derive"], optional = true }
|
||||
env_logger = { version = "0.11", default-features = false, optional = true }
|
||||
log = "0.4.21"
|
||||
mdbook = "0.4.40"
|
||||
once_cell = "1.19.0"
|
||||
path-slash = "0.2.1"
|
||||
pulldown-cmark = "0.13"
|
||||
regex = "1.10.5"
|
||||
semver = "1.0.23"
|
||||
serde = { version = "1.0.203", features = ["derive"] }
|
||||
serde_json = "1.0.117"
|
||||
toml = "0.8.14"
|
||||
toml_edit = { version = "0.22.14", optional = true }
|
||||
hex_color = { version = "3.0.0", features = ["serde"] }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.4.0"
|
||||
|
||||
55
README.md
55
README.md
@@ -21,9 +21,11 @@ into this:
|
||||
|
||||
Read the documentation [here](https://tommilligan.github.io/mdbook-admonish/), to see the actual examples in action. You can see the source in the [`./book`](./book) subdirectory.
|
||||
|
||||
Other projects using mdbook-admonish:
|
||||
Projects using mdbook-admonish include:
|
||||
|
||||
- [The Rhai Book](https://rhai.rs/book/)
|
||||
- [The Rhai Book](https://rhai.rs/book/) ([source](https://github.com/rhaiscript/book))
|
||||
- [The Trunk Guide](https://trunkrs.dev/guide/) ([source](https://github.com/trunk-rs/trunk))
|
||||
- [PRQL language book](https://prql-lang.org/book/) ([source](https://github.com/PRQL/prql))
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -70,6 +72,9 @@ Install the tool:
|
||||
|
||||
```bash
|
||||
cargo install mdbook-admonish
|
||||
|
||||
# If you get compilation/installation errors, try a locked installation
|
||||
cargo install mdbook-admonish --locked
|
||||
```
|
||||
|
||||
Then let `mdbook-admonish` add the required files and configuration:
|
||||
@@ -101,6 +106,21 @@ Then, build your book as usual:
|
||||
mdbook path/to/book
|
||||
```
|
||||
|
||||
### Reproducible builds
|
||||
|
||||
For a reproducible build suitable for use in CI or scripts, please:
|
||||
|
||||
- Pin to a specific version
|
||||
- Install with lockfile dependencies
|
||||
- Always install the latest CSS assets
|
||||
|
||||
```bash
|
||||
cargo install mdbook-admonish --vers "1.5.0" --locked
|
||||
mdbook-admonish install path/to/your/book
|
||||
```
|
||||
|
||||
The Minimum Supported Rust Version (MSRV) is documented in `Cargo.toml`, and noted in the `CHANGELOG.md`. We aims to support around six months of stable Rust.
|
||||
|
||||
### Updates
|
||||
|
||||
**Please note**, when updating your version of `mdbook-admonish`, updated styles will not be applied unless you rerun `mdbook-admonish install` to update the additional CSS files in your book.
|
||||
@@ -117,12 +137,6 @@ ERROR:
|
||||
|
||||
If you want to update across minor versions without breakage, you should always run `mdbook-admonish install`.
|
||||
|
||||
Alternatively, pin to a specific version for a reproducible installation:
|
||||
|
||||
```bash
|
||||
cargo install mdbook-admonish --vers "1.5.0" --locked
|
||||
```
|
||||
|
||||
### Process included files
|
||||
|
||||
You can ensure that content inlined with `{{#include}}` is also processed by [setting the `after` option](https://rust-lang.github.io/mdBook/format/configuration/preprocessors.html#require-a-certain-order):
|
||||
@@ -149,30 +163,7 @@ Guarantees provided are as follows:
|
||||
|
||||
## Development
|
||||
|
||||
Project design
|
||||
|
||||
- Compiled CSS styles are built and committed from SCSS sources. See the `compile_assets` folder for details.
|
||||
- `mdbook-admonish install` is responsible for delivering additional assets and configuration to a client book.
|
||||
- `mdbook-admonish` is responsible for preprocessing book data, adding HTML that references compiled classnames.
|
||||
|
||||
### Scripts to get started
|
||||
|
||||
- `./scripts/install` installs other toolchains required for development
|
||||
- `./scripts/check` runs a full CI check
|
||||
- `./scripts/rebuild-book` rebuilds the reference book under `./book`. This is useful for integration testing locally.
|
||||
|
||||
### Making breaking changes in CSS
|
||||
|
||||
To make a breaking change in CSS, you should:
|
||||
|
||||
- Update the assets version in `./src/bin/assets/VERSION`
|
||||
- Update the required assets version specifier in `./src/REQUIRED_ASSETS_VERSION`
|
||||
|
||||
You must make the next `mdbook-admonish` crate version at least a **minor** version bump.
|
||||
|
||||
### Releasing
|
||||
|
||||
Github workflows are setup such that pushing a `vX.Y.Z` tag will trigger a release to be cut.
|
||||
See [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines on developing.
|
||||
|
||||
## Thanks
|
||||
|
||||
|
||||
@@ -4,12 +4,16 @@ language = "en"
|
||||
multilingual = false
|
||||
src = "src"
|
||||
title = "The mdbook-admonish book"
|
||||
git-repository-url = "https://github.com/tommilligan/mdbook-admonish"
|
||||
|
||||
[preprocessor]
|
||||
|
||||
[preprocessor.admonish]
|
||||
command = "mdbook-admonish"
|
||||
assets_version = "2.0.2" # do not edit: managed by `mdbook-admonish install`
|
||||
assets_version = "3.1.0" # do not edit: managed by `mdbook-admonish install`
|
||||
|
||||
[preprocessor.admonish.directive.custom]
|
||||
expensive = { icon = "./money-bag.svg", color = "#24ab38" }
|
||||
|
||||
[preprocessor.toc]
|
||||
command = "mdbook-toc"
|
||||
@@ -18,4 +22,4 @@ renderer = ["html"]
|
||||
[output]
|
||||
|
||||
[output.html]
|
||||
additional-css = ["./mdbook-admonish.css"]
|
||||
additional-css = ["./mdbook-admonish.css", "./mdbook-admonish-custom.css"]
|
||||
|
||||
20
book/mdbook-admonish-custom.css
Normal file
20
book/mdbook-admonish-custom.css
Normal 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
6
book/money-bag.svg
Normal 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 |
@@ -5,5 +5,5 @@ set -exuo pipefail
|
||||
cd "$(dirname "$0")"/../..
|
||||
|
||||
if ! mdbook-toc --version; then
|
||||
cargo install mdbook-toc --version 0.13.0 --force
|
||||
cargo install mdbook-toc --version 0.14.1 --force
|
||||
fi
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
|
||||
- [Overview](./overview.md)
|
||||
- [Reference](./reference.md)
|
||||
- [Examples](./examples.md)
|
||||
|
||||
15
book/src/examples.md
Normal file
15
book/src/examples.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Examples
|
||||
|
||||
## Combining multiple custom properties
|
||||
|
||||
Note that the comma `,` is used to seperate custom options.
|
||||
|
||||
````
|
||||
```admonish quote collapsible=true, title='A title that really <span style="color: #e70073">pops</span>'
|
||||
To really <b><span style="color: #e70073">grab</span></b> your reader's attention.
|
||||
```
|
||||
````
|
||||
|
||||
```admonish quote collapsible=true, title='A title that really <span style="color: #e70073">pops</span>'
|
||||
To really <b><span style="color: #e70073">grab</span></b> your reader's attention.
|
||||
```
|
||||
@@ -78,14 +78,19 @@ You can also configure the build to fail loudly, by setting `on_failure = "bail"
|
||||
|
||||
### Additional Options
|
||||
|
||||
You can pass additional options to each block. The options are structured as TOML key-value pairs.
|
||||
You can pass additional options to each block. Options are given like a [TOML Inline Table](https://toml.io/en/v1.0.0#inline-table), as key-value pairs separated by commas.
|
||||
|
||||
`mdbook-admonish` parses options by wrapping your options in an inline table before parsing them, so please consult [The TOML Reference](https://toml.io) if you run into any syntax errors. Be aware that:
|
||||
|
||||
- Key-value pairs must be separated with a comma `,`
|
||||
- TOML escapes must be escaped again - for instance, write `\"` as `\\"`.
|
||||
- For complex strings such as HTML, you may want to use a [literal string](https://toml.io/en/v1.0.0#string) to avoid complex escape sequences
|
||||
|
||||
Note that some options can be passed globally, through the `default` section in `book.toml`. See the [configuration reference](./reference.md#booktoml-configuration) for more details.
|
||||
|
||||
#### Custom title
|
||||
|
||||
A custom title can be provided, contained in a double quoted TOML string.
|
||||
Note that TOML escapes must be escaped again - for instance, write `\"` as `\\"`.
|
||||
A custom title can be provided:
|
||||
|
||||
````
|
||||
```admonish warning title="Data loss"
|
||||
@@ -114,13 +119,13 @@ This will take a while, go and grab a drink of water.
|
||||
Markdown and HTML can be used in the inner content, as you'd expect:
|
||||
|
||||
````
|
||||
```admonish tip title="_Referencing_ and <i>dereferencing</i>"
|
||||
```admonish tip title='_Referencing_ and <i>dereferencing</i>'
|
||||
The opposite of *referencing* by using `&` is *dereferencing*, which is
|
||||
accomplished with the <span style="color: hotpink">dereference operator</span>, `*`.
|
||||
```
|
||||
````
|
||||
|
||||
```admonish tip title="_Referencing_ and <i>dereferencing</i>"
|
||||
```admonish tip title='_Referencing_ and <i>dereferencing</i>'
|
||||
The opposite of *referencing* by using `&` is *dereferencing*, which is
|
||||
accomplished with the <span style="color: hotpink">dereference operator</span>, `*`.
|
||||
```
|
||||
@@ -148,7 +153,7 @@ print "Hello, world!"
|
||||
If you want to provide custom styling to a specific admonition, you can attach one or more custom classnames:
|
||||
|
||||
````
|
||||
```admonish note class="custom-0 custom-1"
|
||||
```admonish note title="Stylish", class="custom-0 custom-1"
|
||||
Styled with my custom CSS class.
|
||||
```
|
||||
````
|
||||
@@ -161,18 +166,79 @@ Will yield something like the following HTML, which you can then apply styles to
|
||||
</div>
|
||||
```
|
||||
|
||||
#### Custom CSS ID
|
||||
|
||||
If you want to customize the CSS `id` field, set `id="custom-id"`.
|
||||
This will ignore [`default.css_id_prefix`](reference.md#default).
|
||||
|
||||
The default id is a normalized version of the admonishment's title,
|
||||
prefixed with the `default.css_id_prefix`,
|
||||
with an appended number if multiple blocks would have the same id.
|
||||
|
||||
Setting the `id` field will _ignore_ all other ids and the duplicate counter.
|
||||
|
||||
````
|
||||
```admonish info title="My Info", id="my-special-info"
|
||||
Link to this block with `#my-special-info` instead of the default `#admonition-my-info`.
|
||||
```
|
||||
````
|
||||
|
||||
#### Collapsible
|
||||
|
||||
For a block to be initially collapsible, and then be openable, set `collapsible=true`:
|
||||
|
||||
````
|
||||
```admonish collapsible=true
|
||||
```admonish title="Sneaky", collapsible=true
|
||||
Content will be hidden initially.
|
||||
```
|
||||
````
|
||||
|
||||
Will yield something like the following HTML, which you can then apply styles to:
|
||||
|
||||
```admonish collapsible=true
|
||||
```admonish title="Sneaky", collapsible=true
|
||||
Content will be hidden initially.
|
||||
```
|
||||
|
||||
### Custom blocks
|
||||
|
||||
You can add new block types via the `book.toml` config:
|
||||
|
||||
```toml
|
||||
# book.toml
|
||||
|
||||
[[preprocessor.admonish.custom]]
|
||||
directive = "expensive"
|
||||
icon = "./money-bag.svg"
|
||||
color = "#24ab38"
|
||||
aliases = ["money", "cash", "budget"]
|
||||
```
|
||||
|
||||
You must then generate the relevant CSS file, and reference it in the `output.html` section.
|
||||
`mdbook-admonish` has a helper to quickly do this for you:
|
||||
|
||||
```bash
|
||||
# Generates a file at ./mdbook-admonish-custom.css with your styles in
|
||||
$ mdbook-admonish generate-custom ./mdbook-admonish-custom.css
|
||||
```
|
||||
|
||||
```toml
|
||||
# book.toml
|
||||
|
||||
[output.html]
|
||||
# Reference the new file, so it's bundled in with book styles
|
||||
additional-css = ["./mdbook-admonish.css", "./mdbook-admonish-custom.css"]
|
||||
```
|
||||
|
||||
You can then reference the new directive (or alias) like usual in your blocks.
|
||||
|
||||
````
|
||||
```admonish expensive
|
||||
Remember, this operation costs money!
|
||||
```
|
||||
````
|
||||
|
||||
```admonish expensive
|
||||
Remember, this operation costs money!
|
||||
```
|
||||
|
||||
You can also set a default `title`. See the [Reference](./reference.md) page for more details.
|
||||
|
||||
@@ -38,6 +38,7 @@ Subfields:
|
||||
|
||||
- `default.title` (optional): Title to use for blocks. Defaults to the directive used in titlecase.
|
||||
- `default.collapsible` (optional, default: `false`): Make blocks collapsible by default when set to `true`.
|
||||
- `default.css_id_prefix` (optional, default: `"admonition-"`): The default css id prefix to add to the id of all blocks. Ignored on blocks with an `id` field.
|
||||
|
||||
### `renderer`
|
||||
|
||||
@@ -74,6 +75,53 @@ Subfields:
|
||||
- For the `html` renderer, the default value is `html`.
|
||||
- For all other renderers, the default value is `preserve`.
|
||||
|
||||
### `directive`
|
||||
|
||||
Optional.
|
||||
|
||||
Settings relating to each type of block.
|
||||
|
||||
#### `builtin`
|
||||
|
||||
Optional.
|
||||
|
||||
Override the settings of a builtin directive.
|
||||
|
||||
The subkey of `builtin` is the directive to override. This must be the first directive listed in the [Directives](#directives) section below, e.g. `warning` (not `caution` or other aliases).
|
||||
|
||||
```toml
|
||||
[preprocessor.admonish.directive.builtin.warning]
|
||||
collapsible = true
|
||||
```
|
||||
|
||||
Subfields:
|
||||
|
||||
- `collapsible` (optional): The default boolean value of the collapsible property for this type of block.
|
||||
|
||||
#### `custom`
|
||||
|
||||
Optional.
|
||||
|
||||
Additional types of block to support. The subkey of `custom` is the new directive to support.
|
||||
|
||||
You must run `mdbook-admonish generate-custom` after updating these values, to generate the correct styles.
|
||||
|
||||
```toml
|
||||
[preprocessor.admonish.directive.custom.expensive]
|
||||
icon = "./money-bag.svg"
|
||||
color = "#24ab38"
|
||||
collapsible = true
|
||||
aliases = ["money", "cash", "budget"]
|
||||
```
|
||||
|
||||
Subfields:
|
||||
|
||||
- `icon`: A filepath relative to the book root to load an SVG icon from.
|
||||
- `color`: An RGB hex encoded color to use for the icon.
|
||||
- `collapsible` (optional): The default boolean value of the collapsible property for this type of block.
|
||||
- `aliases` (optional): One or more alternative directives to use this block.
|
||||
- `title` (optional): The default title for this type of block. If not specified, defaults to the directive in title case. To give each alias a custom title, add multiple custom blocks.
|
||||
|
||||
### `command`
|
||||
|
||||
Required.
|
||||
@@ -92,6 +140,8 @@ This is automatically updated by `mdbook-admonish install` and should not be edi
|
||||
|
||||
All supported directives are listed below.
|
||||
|
||||
Custom directives can be added via the `custom` config option above.
|
||||
|
||||
`note`
|
||||
|
||||
```admonish note
|
||||
|
||||
2
compile_assets/.gitignore
vendored
2
compile_assets/.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
/node_modules
|
||||
*.css
|
||||
*.css.map
|
||||
.yarn/
|
||||
.pnp*
|
||||
|
||||
@@ -4,9 +4,17 @@
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "sass --no-source-map scss/mdbook-admonish.scss ../src/bin/assets/mdbook-admonish.css"
|
||||
"build": "yarn run build-prod && yarn run build-custom-expected",
|
||||
"build-prod": "sass --no-source-map scss/mdbook-admonish.scss ../src/bin/assets/mdbook-admonish.css",
|
||||
"build-custom-expected": "sass --no-source-map scss/mdbook-admonish-custom-expected.scss ../src/test_data/mdbook-admonish-custom-expected.css",
|
||||
"lint": "prettier --check .",
|
||||
"fix": "prettier --write ."
|
||||
},
|
||||
"dependencies": {
|
||||
"sass": "^1.49.7"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3.0.3"
|
||||
},
|
||||
"packageManager": "yarn@4.8.1+sha512.bc946f2a022d7a1a38adfc15b36a66a3807a67629789496c3714dd1703d2e6c6b1c69ff9ec3b43141ac7a1dd853b7685638eb0074300386a59c18df351ef8ff6"
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
@use "sass:color";
|
||||
@use "sass:list";
|
||||
@use "./lib";
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Variables
|
||||
@@ -30,47 +31,48 @@
|
||||
/// Admonition flavours
|
||||
$admonitions: (
|
||||
// pencil
|
||||
note: $clr-blue-a200 "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/></svg>",
|
||||
admonish-note: $clr-blue-a200
|
||||
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/></svg>",
|
||||
// clipboard-text
|
||||
abstract summary tldr: $clr-light-blue-a400 "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17 9H7V7h10m0 6H7v-2h10m-3 6H7v-2h7M12 3a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z'/></svg>",
|
||||
admonish-abstract admonish-summary admonish-tldr: $clr-light-blue-a400
|
||||
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17 9H7V7h10m0 6H7v-2h10m-3 6H7v-2h7M12 3a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z'/></svg>",
|
||||
// information
|
||||
info todo: $clr-cyan-a700 "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z'/></svg>",
|
||||
admonish-info admonish-todo: $clr-cyan-a700
|
||||
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z'/></svg>",
|
||||
// fire
|
||||
tip hint important: $clr-teal-a700 "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17.66 11.2c-.23-.3-.51-.56-.77-.82-.67-.6-1.43-1.03-2.07-1.66C13.33 7.26 13 4.85 13.95 3c-.95.23-1.78.75-2.49 1.32-2.59 2.08-3.61 5.75-2.39 8.9.04.1.08.2.08.33 0 .22-.15.42-.35.5-.23.1-.47.04-.66-.12a.58.58 0 0 1-.14-.17c-1.13-1.43-1.31-3.48-.55-5.12C5.78 10 4.87 12.3 5 14.47c.06.5.12 1 .29 1.5.14.6.41 1.2.71 1.73 1.08 1.73 2.95 2.97 4.96 3.22 2.14.27 4.43-.12 6.07-1.6 1.83-1.66 2.47-4.32 1.53-6.6l-.13-.26c-.21-.46-.77-1.26-.77-1.26m-3.16 6.3c-.28.24-.74.5-1.1.6-1.12.4-2.24-.16-2.9-.82 1.19-.28 1.9-1.16 2.11-2.05.17-.8-.15-1.46-.28-2.23-.12-.74-.1-1.37.17-2.06.19.38.39.76.63 1.06.77 1 1.98 1.44 2.24 2.8.04.14.06.28.06.43.03.82-.33 1.72-.93 2.27z'/></svg>",
|
||||
admonish-tip admonish-hint admonish-important: $clr-teal-a700
|
||||
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17.66 11.2c-.23-.3-.51-.56-.77-.82-.67-.6-1.43-1.03-2.07-1.66C13.33 7.26 13 4.85 13.95 3c-.95.23-1.78.75-2.49 1.32-2.59 2.08-3.61 5.75-2.39 8.9.04.1.08.2.08.33 0 .22-.15.42-.35.5-.23.1-.47.04-.66-.12a.58.58 0 0 1-.14-.17c-1.13-1.43-1.31-3.48-.55-5.12C5.78 10 4.87 12.3 5 14.47c.06.5.12 1 .29 1.5.14.6.41 1.2.71 1.73 1.08 1.73 2.95 2.97 4.96 3.22 2.14.27 4.43-.12 6.07-1.6 1.83-1.66 2.47-4.32 1.53-6.6l-.13-.26c-.21-.46-.77-1.26-.77-1.26m-3.16 6.3c-.28.24-.74.5-1.1.6-1.12.4-2.24-.16-2.9-.82 1.19-.28 1.9-1.16 2.11-2.05.17-.8-.15-1.46-.28-2.23-.12-.74-.1-1.37.17-2.06.19.38.39.76.63 1.06.77 1 1.98 1.44 2.24 2.8.04.14.06.28.06.43.03.82-.33 1.72-.93 2.27z'/></svg>",
|
||||
// check-bold
|
||||
success check done: $clr-green-a700 "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m9 20.42-6.21-6.21 2.83-2.83L9 14.77l9.88-9.89 2.83 2.83L9 20.42z'/></svg>",
|
||||
admonish-success admonish-check admonish-done: $clr-green-a700
|
||||
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m9 20.42-6.21-6.21 2.83-2.83L9 14.77l9.88-9.89 2.83 2.83L9 20.42z'/></svg>",
|
||||
// help-circle
|
||||
question help faq: $clr-light-green-a700 "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m15.07 11.25-.9.92C13.45 12.89 13 13.5 13 15h-2v-.5c0-1.11.45-2.11 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41a2 2 0 0 0-2-2 2 2 0 0 0-2 2H8a4 4 0 0 1 4-4 4 4 0 0 1 4 4 3.2 3.2 0 0 1-.93 2.25M13 19h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10c0-5.53-4.5-10-10-10z'/></svg>",
|
||||
admonish-question admonish-help admonish-faq: $clr-light-green-a700
|
||||
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m15.07 11.25-.9.92C13.45 12.89 13 13.5 13 15h-2v-.5c0-1.11.45-2.11 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41a2 2 0 0 0-2-2 2 2 0 0 0-2 2H8a4 4 0 0 1 4-4 4 4 0 0 1 4 4 3.2 3.2 0 0 1-.93 2.25M13 19h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10c0-5.53-4.5-10-10-10z'/></svg>",
|
||||
// alert
|
||||
warning caution attention: $clr-orange-a400 "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 14h-2V9h2m0 9h-2v-2h2M1 21h22L12 2 1 21z'/></svg>",
|
||||
admonish-warning admonish-caution admonish-attention: $clr-orange-a400
|
||||
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 14h-2V9h2m0 9h-2v-2h2M1 21h22L12 2 1 21z'/></svg>",
|
||||
// close-thick
|
||||
failure fail missing: $clr-red-a200 "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 6.91 17.09 4 12 9.09 6.91 4 4 6.91 9.09 12 4 17.09 6.91 20 12 14.91 17.09 20 20 17.09 14.91 12 20 6.91z'/></svg>",
|
||||
admonish-failure admonish-fail admonish-missing: $clr-red-a200
|
||||
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 6.91 17.09 4 12 9.09 6.91 4 4 6.91 9.09 12 4 17.09 6.91 20 12 14.91 17.09 20 20 17.09 14.91 12 20 6.91z'/></svg>",
|
||||
// lighting-bold
|
||||
danger error: $clr-red-a400 "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11 15H6l7-14v8h5l-7 14v-8z'/></svg>",
|
||||
admonish-danger admonish-error: $clr-red-a400
|
||||
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11 15H6l7-14v8h5l-7 14v-8z'/></svg>",
|
||||
// bug
|
||||
bug: $clr-pink-a400 "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 12h-4v-2h4m0 6h-4v-2h4m6-6h-2.81a5.985 5.985 0 0 0-1.82-1.96L17 4.41 15.59 3l-2.17 2.17a6.002 6.002 0 0 0-2.83 0L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z'/></svg>",
|
||||
admonish-bug: $clr-pink-a400
|
||||
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 12h-4v-2h4m0 6h-4v-2h4m6-6h-2.81a5.985 5.985 0 0 0-1.82-1.96L17 4.41 15.59 3l-2.17 2.17a6.002 6.002 0 0 0-2.83 0L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z'/></svg>",
|
||||
// format-list-numbered
|
||||
example: $clr-deep-purple-a200 "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M7 13v-2h14v2H7m0 6v-2h14v2H7M7 7V5h14v2H7M3 8V5H2V4h2v4H3m-1 9v-1h3v4H2v-1h2v-.5H3v-1h1V17H2m2.25-7a.75.75 0 0 1 .75.75c0 .2-.08.39-.21.52L3.12 13H5v1H2v-.92L4 11H2v-1h2.25z'/></svg>",
|
||||
admonish-example: $clr-deep-purple-a200
|
||||
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M7 13v-2h14v2H7m0 6v-2h14v2H7M7 7V5h14v2H7M3 8V5H2V4h2v4H3m-1 9v-1h3v4H2v-1h2v-.5H3v-1h1V17H2m2.25-7a.75.75 0 0 1 .75.75c0 .2-.08.39-.21.52L3.12 13H5v1H2v-.92L4 11H2v-1h2.25z'/></svg>",
|
||||
// format-quote-close
|
||||
quote cite: $clr-grey "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z'/></svg>",
|
||||
admonish-quote admonish-cite: $clr-grey
|
||||
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z'/></svg>"
|
||||
) !default;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Rules: layout
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Admonition variables
|
||||
:root {
|
||||
@each $names, $props in $admonitions {
|
||||
--md-admonition-icon--#{nth($names, 1)}:
|
||||
url("data:image/svg+xml;charset=utf-8,#{nth($props, 2)}");
|
||||
}
|
||||
--md-details-icon:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8.59 16.58 13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42Z'/></svg>");
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Static content - base for all admonitions
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Admonition
|
||||
:is(.admonition) {
|
||||
@@ -86,10 +88,13 @@ $admonitions: (
|
||||
border: 0 solid black;
|
||||
border-inline-start-width: 0.4rem;
|
||||
border-radius: 0.2rem;
|
||||
box-shadow: 0 0.2rem 1rem rgba(0, 0, 0, 0.05), 0 0 0.1rem rgba(0, 0, 0, 0.1);;
|
||||
box-shadow:
|
||||
0 0.2rem 1rem rgba(0, 0, 0, 0.05),
|
||||
0 0 0.1rem rgba(0, 0, 0, 0.1);
|
||||
|
||||
// [print]: Omit shadow as it may lead to rendering errors
|
||||
// During print mode in browser, pdf renderer
|
||||
@media print {
|
||||
// Omit shadow as it may lead to rendering errors
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
@@ -125,20 +130,22 @@ a.admonition-anchor-link {
|
||||
position: absolute;
|
||||
left: -1.2rem;
|
||||
// Ensure we have enough padding, so that we can move the mouse to click on it
|
||||
padding-right: 1.0rem;
|
||||
padding-right: 1rem;
|
||||
|
||||
&:link, &:visited {
|
||||
&:link,
|
||||
&:visited {
|
||||
// Don't make links colored (override to standard text color)
|
||||
// variable provided downstream by mdbook
|
||||
color: var(--fg);
|
||||
}
|
||||
&:link:hover, &:visited:hover {
|
||||
&:link:hover,
|
||||
&:visited:hover {
|
||||
// No underline on hover
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '§';
|
||||
content: "§";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +159,9 @@ a.admonition-anchor-link {
|
||||
padding-inline: 4.4rem 1.2rem;
|
||||
font-weight: 700;
|
||||
background-color: color.adjust($clr-blue-a200, $alpha: -0.9);
|
||||
// Always print title bar tint
|
||||
print-color-adjust: exact;
|
||||
-webkit-print-color-adjust: exact;
|
||||
// Compatilility with rendering markdown inside the content
|
||||
display: flex;
|
||||
|
||||
@@ -173,6 +183,9 @@ a.admonition-anchor-link {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
background-color: $clr-blue-a200;
|
||||
// Always print icon
|
||||
print-color-adjust: exact;
|
||||
-webkit-print-color-adjust: exact;
|
||||
mask-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"></svg>');
|
||||
-webkit-mask-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"></svg>');
|
||||
mask-repeat: no-repeat;
|
||||
@@ -182,17 +195,24 @@ a.admonition-anchor-link {
|
||||
content: "";
|
||||
}
|
||||
|
||||
|
||||
// Show anchor link on hover over title
|
||||
&:hover a.admonition-anchor-link {
|
||||
display: initial
|
||||
display: initial;
|
||||
}
|
||||
}
|
||||
|
||||
// During print mode in browser, pdf renderer
|
||||
@media print {
|
||||
// Expand collapsed sections to show details
|
||||
details.admonition::details-content {
|
||||
display: contents;
|
||||
}
|
||||
}
|
||||
|
||||
summary.admonition-title {
|
||||
details.admonition > &::after {
|
||||
position: absolute;
|
||||
top: .625em;
|
||||
top: 0.625em;
|
||||
inset-inline-end: 1.6rem;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
@@ -205,55 +225,34 @@ summary.admonition-title {
|
||||
-webkit-mask-size: contain;
|
||||
content: "";
|
||||
transform: rotate(0deg);
|
||||
transition: transform .25s;
|
||||
transition: transform 0.25s;
|
||||
}
|
||||
|
||||
details[open].admonition > &::after {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Rules: flavours
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
@each $names, $props in $admonitions {
|
||||
$name: list.nth($names, 1);
|
||||
$tint: list.nth($props, 1);
|
||||
|
||||
// Admonition flavour selectors
|
||||
$flavours: ();
|
||||
@each $name in $names {
|
||||
$flavours: list.join($flavours, ".#{$name}", $separator: comma);
|
||||
}
|
||||
|
||||
// Admonition flavour
|
||||
:is(.admonition):is(#{$flavours}) {
|
||||
border-color: $tint;
|
||||
}
|
||||
|
||||
// Admonition flavour title
|
||||
:is(#{$flavours}) > :is(.admonition-title, summary.admonition-title) {
|
||||
background-color: color.adjust($tint, $alpha: -0.9);
|
||||
|
||||
// Admonition icon
|
||||
&::before {
|
||||
background-color: $tint;
|
||||
mask-image: var(--md-admonition-icon--#{$name});
|
||||
-webkit-mask-image: var(--md-admonition-icon--#{$name});
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
// Hide details marker for Safari that loves to show it anyway
|
||||
// ref: https://github.com/tommilligan/mdbook-admonish/pull/185
|
||||
&::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
--md-details-icon: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8.59 16.58 13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42Z'/></svg>");
|
||||
}
|
||||
|
||||
// Generate rules for each specified admonition variant
|
||||
@include lib.from-admonitions($admonitions);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Rules: themes
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// One rule per builtin theme in mdbook, overriding the default fg/bg if matched
|
||||
//
|
||||
// This must be after the variant CSS, so it can override they styles set there
|
||||
|
||||
.navy {
|
||||
& :is(.admonition) {
|
||||
@@ -261,7 +260,8 @@ summary.admonition-title {
|
||||
}
|
||||
}
|
||||
|
||||
.ayu, .coal {
|
||||
.ayu,
|
||||
.coal {
|
||||
& :is(.admonition) {
|
||||
background-color: var(--theme-hover);
|
||||
}
|
||||
@@ -272,8 +272,10 @@ summary.admonition-title {
|
||||
background-color: var(--sidebar-bg);
|
||||
color: var(--sidebar-fg);
|
||||
}
|
||||
|
||||
& .admonition-anchor-link {
|
||||
&:link, &:visited {
|
||||
&:link,
|
||||
&:visited {
|
||||
color: var(--sidebar-fg);
|
||||
}
|
||||
}
|
||||
|
||||
52
compile_assets/scss/lib.scss
Normal file
52
compile_assets/scss/lib.scss
Normal file
@@ -0,0 +1,52 @@
|
||||
@use "sass:color";
|
||||
@use "sass:list";
|
||||
|
||||
@mixin from-admonitions($admonitions) {
|
||||
// ----------------------------------------------------------------------------
|
||||
// Rules: layout
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Admonition variables
|
||||
:root {
|
||||
@each $names, $props in $admonitions {
|
||||
--md-admonition-icon--#{nth($names, 1)}: url("data:image/svg+xml;charset=utf-8,#{nth($props, 2)}");
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Rules: flavours
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
@each $names, $props in $admonitions {
|
||||
$name: list.nth($names, 1);
|
||||
$tint: list.nth($props, 1);
|
||||
|
||||
// Admonition flavour selectors
|
||||
$flavours: ();
|
||||
|
||||
@each $name in $names {
|
||||
$flavours: list.join($flavours, ".#{$name}", $separator: comma);
|
||||
}
|
||||
|
||||
// Admonition flavour
|
||||
:is(.admonition):is(#{$flavours}) {
|
||||
border-color: $tint;
|
||||
}
|
||||
|
||||
// Admonition flavour title
|
||||
:is(#{$flavours}) > :is(.admonition-title, summary.admonition-title) {
|
||||
background-color: color.adjust($tint, $alpha: -0.9);
|
||||
|
||||
// Admonition icon
|
||||
&::before {
|
||||
background-color: $tint;
|
||||
mask-image: var(--md-admonition-icon--#{$name});
|
||||
-webkit-mask-image: var(--md-admonition-icon--#{$name});
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
16
compile_assets/scss/mdbook-admonish-custom-expected.scss
Normal file
16
compile_assets/scss/mdbook-admonish-custom-expected.scss
Normal file
@@ -0,0 +1,16 @@
|
||||
// This file aims to generate the subset of CSS specific to a single admonition directive.
|
||||
//
|
||||
// This is used for unit test data in the rust css generation module.
|
||||
|
||||
@use "sass:color";
|
||||
@use "sass:list";
|
||||
@use "./lib";
|
||||
@import "./material-color";
|
||||
|
||||
$admonitions: (
|
||||
admonish-note: $clr-blue-a200
|
||||
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/></svg>",
|
||||
) !default;
|
||||
|
||||
// Generate rules for each specified admonition variant
|
||||
@include lib.from-admonitions($admonitions);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,13 +9,18 @@ title = "mdbook-admonish-integration"
|
||||
|
||||
[preprocessor.admonish]
|
||||
command = "mdbook-admonish"
|
||||
assets_version = "2.0.2" # do not edit: managed by `mdbook-admonish install`
|
||||
assets_version = "3.1.0" # do not edit: managed by `mdbook-admonish install`
|
||||
after = ["links"]
|
||||
|
||||
[[preprocessor.admonish.custom]]
|
||||
directive = "frog"
|
||||
icon = "./frog.svg"
|
||||
color = "#9004CC"
|
||||
|
||||
[preprocessor.admonish.renderer.test]
|
||||
render_mode = "strip"
|
||||
|
||||
[output]
|
||||
|
||||
[output.html]
|
||||
additional-css = ["./mdbook-admonish.css"]
|
||||
additional-css = ["./mdbook-admonish.css", "./mdbook-admonish-custom.css"]
|
||||
|
||||
@@ -9,13 +9,18 @@ title = "mdbook-admonish-integration"
|
||||
|
||||
[preprocessor.admonish]
|
||||
command = "mdbook-admonish"
|
||||
assets_version = "2.0.2" # do not edit: managed by `mdbook-admonish install`
|
||||
assets_version = "3.1.0" # do not edit: managed by `mdbook-admonish install`
|
||||
after = ["links"]
|
||||
|
||||
[[preprocessor.admonish.custom]]
|
||||
directive = "frog"
|
||||
icon = "./frog.svg"
|
||||
color = "#9004CC"
|
||||
|
||||
[preprocessor.admonish.renderer.test]
|
||||
render_mode = "strip"
|
||||
|
||||
[output]
|
||||
|
||||
[output.html]
|
||||
additional-css = ["./mdbook-admonish.css"]
|
||||
additional-css = ["./mdbook-admonish.css", "./mdbook-admonish-custom.css"]
|
||||
|
||||
@@ -1,81 +1,108 @@
|
||||
<h1 id="chapter-1"><a class="header" href="#chapter-1">Chapter 1</a></h1>
|
||||
<div id="admonition-what-is-this" class="admonition abstract">
|
||||
<div id="admonition-what-is-this" class="admonition admonish-abstract" role="note" aria-labelledby="admonition-what-is-this-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-what-is-this-title">
|
||||
<p>What <i>is</i> this?</p>
|
||||
<p><a class="admonition-anchor-link" href="#admonition-what-is-this"></a></p>
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-what-is-this"></a>
|
||||
</div>
|
||||
<div>
|
||||
<p>This book acts as an integration test for <code>mdbook-admonish</code>.</p>
|
||||
<p>It verifies that <code>mdbook</code> post-processes our generated HTML in the way we expect.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="admonition-note" class="admonition note">
|
||||
<div id="admonition-note" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-note-title">
|
||||
<p>Note</p>
|
||||
<p><a class="admonition-anchor-link" href="#admonition-note"></a></p>
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-note"></a>
|
||||
</div>
|
||||
<div>
|
||||
<p>Simples</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="admonition-default" class="admonition warning">
|
||||
<div id="admonition-frog" class="admonition admonish-frog" role="note" aria-labelledby="admonition-frog-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-frog-title">
|
||||
<p>Frog</p>
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-frog"></a>
|
||||
</div>
|
||||
<div>
|
||||
<p>Custom frog directive</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="admonition-default" class="admonition admonish-warning" role="note">
|
||||
<div>
|
||||
<p>No title, only body</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="admonition-error-rendering-admonishment" class="admonition bug">
|
||||
<div id="admonition-error-rendering-admonishment" class="admonition admonish-bug" role="note" aria-labelledby="admonition-error-rendering-admonishment-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-error-rendering-admonishment-title">
|
||||
<p>Error rendering admonishment</p>
|
||||
<p><a class="admonition-anchor-link" href="#admonition-error-rendering-admonishment"></a></p>
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-error-rendering-admonishment"></a>
|
||||
</div>
|
||||
<div>
|
||||
<p>Failed with:</p>
|
||||
<pre><code class="language-log">TOML parsing error: TOML parse error at line 1, column 8
|
||||
<pre><code class="language-log">'title="' is not a valid directive or TOML key-value pair.
|
||||
|
||||
TOML parsing error: TOML parse error at line 1, column 21
|
||||
|
|
||||
1 | title="
|
||||
| ^
|
||||
1 | config = { title=" }
|
||||
| ^
|
||||
invalid basic string
|
||||
|
||||
</code></pre>
|
||||
<p>Original markdown input:</p>
|
||||
<pre><code class="language-markdown">```admonish title="
|
||||
<pre><code class="language-markdown">```admonish title="
|
||||
No title, only body
|
||||
```
|
||||
</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<details id="admonition-note-1" class="admonition note">
|
||||
<details id="admonition-note-1" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-1-title">
|
||||
<summary class="admonition-title">
|
||||
<div id="admonition-note-1-title">
|
||||
<p>Note</p>
|
||||
<p><a class="admonition-anchor-link" href="#admonition-note-1"></a></p>
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-note-1"></a>
|
||||
</summary>
|
||||
<div>
|
||||
<p>Hidden on load</p>
|
||||
</div>
|
||||
</details>
|
||||
<div id="admonition-warning" class="admonition warning">
|
||||
<div id="admonition-warning" class="admonition admonish-warning" role="note" aria-labelledby="admonition-warning-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-warning-title">
|
||||
<p>Warning</p>
|
||||
<p><a class="admonition-anchor-link" href="#admonition-warning"></a></p>
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-warning"></a>
|
||||
</div>
|
||||
<div>
|
||||
<p>This is a commonly shared warning!</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="admonition-note-2" class="admonition note">
|
||||
<div id="admonition-note-2" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-2-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-note-2-title">
|
||||
<p>Note</p>
|
||||
<p><a class="admonition-anchor-link" href="#admonition-note-2"></a></p>
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-note-2"></a>
|
||||
</div>
|
||||
<div>
|
||||
<pre><code class="language-bash">Nested code block
|
||||
</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div id="admonition-note-3" class="admonition note">
|
||||
<div id="admonition-note-3" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-3-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-note-3-title">
|
||||
<p>Note</p>
|
||||
<p><a class="admonition-anchor-link" href="#admonition-note-3"></a></p>
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-note-3"></a>
|
||||
</div>
|
||||
<div>
|
||||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||||
@@ -90,4 +117,48 @@ let x = 20;
|
||||
<span class="boring">}</span></code></pre></pre>
|
||||
</div>
|
||||
</div>
|
||||
<p>In a list:</p>
|
||||
<ol>
|
||||
<li>
|
||||
<p>Thing one</p>
|
||||
<pre><code class="language-sh">Thing one
|
||||
</code></pre>
|
||||
</li>
|
||||
<li>
|
||||
<p>Thing two</p>
|
||||
<div id="admonition-note-4" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-4-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-note-4-title">
|
||||
<p>Note</p>
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-note-4"></a>
|
||||
</div>
|
||||
<div>
|
||||
<p>Thing two</p>
|
||||
</div>
|
||||
</div>
|
||||
<ol>
|
||||
<li>
|
||||
<p>Thing two nested</p>
|
||||
<div id="admonition-note-5" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-5-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-note-5-title">
|
||||
<p>Note</p>
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-note-5"></a>
|
||||
</div>
|
||||
<div>
|
||||
<p>Thing two nested (should not be a code block)
|
||||
Regression tests for https://github.com/tommilligan/mdbook-admonish/issues/223</p>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>
|
||||
<p>Thing three</p>
|
||||
<pre><code class="language-sh">Thing three
|
||||
</code></pre>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
|
||||
20
integration/expected/mdbook-admonish-custom.css
Normal file
20
integration/expected/mdbook-admonish-custom.css
Normal 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
29
integration/frog.svg
Normal 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 |
20
integration/mdbook-admonish-custom.css
Normal file
20
integration/mdbook-admonish-custom.css
Normal 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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@ It verifies that `mdbook` post-processes our generated HTML in the way we expect
|
||||
Simples
|
||||
```
|
||||
|
||||
```admonish frog
|
||||
Custom frog directive
|
||||
```
|
||||
|
||||
```admonish warning ""
|
||||
No title, only body
|
||||
```
|
||||
@@ -41,3 +45,30 @@ let x = 10;
|
||||
let x = 20;
|
||||
```
|
||||
````
|
||||
|
||||
In a list:
|
||||
|
||||
1. Thing one
|
||||
|
||||
```sh
|
||||
Thing one
|
||||
```
|
||||
|
||||
1. Thing two
|
||||
|
||||
```admonish
|
||||
Thing two
|
||||
```
|
||||
|
||||
1. Thing two nested
|
||||
|
||||
```admonish
|
||||
Thing two nested (should not be a code block)
|
||||
Regression tests for https://github.com/tommilligan/mdbook-admonish/issues/223
|
||||
```
|
||||
|
||||
1. Thing three
|
||||
|
||||
```sh
|
||||
Thing three
|
||||
```
|
||||
|
||||
@@ -8,6 +8,34 @@ function eprintln() {
|
||||
>&2 echo "$1"
|
||||
}
|
||||
|
||||
# Node things
|
||||
pushd compile_assets > /dev/null
|
||||
|
||||
eprintln "Linting style sources"
|
||||
yarn run lint
|
||||
|
||||
eprintln "Checking compiled styles up to date"
|
||||
COMITTED_ASSETS="$(cat ../src/bin/assets/mdbook-admonish.css)"
|
||||
yarn run build
|
||||
RECOMPILED_ASSETS="$(cat ../src/bin/assets/mdbook-admonish.css)"
|
||||
set +e
|
||||
diff -u <(printf "%s" "$COMITTED_ASSETS") <(printf "%s" "$RECOMPILED_ASSETS")
|
||||
DIFF_RESULT=$?
|
||||
set -e
|
||||
|
||||
if [ "$DIFF_RESULT" != 0 ]; then
|
||||
eprintln ""
|
||||
eprintln "error: committed assets are not up to date"
|
||||
eprintln ""
|
||||
eprintln "error: To update committed assets, please run"
|
||||
eprintln "cd compile_assets && yarn run build"
|
||||
eprintln "and commit the result"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
popd > /dev/null
|
||||
|
||||
# Rust things
|
||||
eprintln "Formatting sources"
|
||||
cargo fmt -- --check
|
||||
|
||||
@@ -24,5 +52,6 @@ cargo test --no-default-features --features cli
|
||||
eprintln "Building documentation"
|
||||
cargo doc --no-deps --lib
|
||||
|
||||
# Integration test
|
||||
eprintln "Running mdbook integration test"
|
||||
./integration/scripts/check
|
||||
|
||||
@@ -4,10 +4,21 @@
|
||||
#
|
||||
# Does not include offline node development stack (i.e. yarn)
|
||||
|
||||
set -exuo pipefail
|
||||
set -euo pipefail
|
||||
|
||||
function eprintln() {
|
||||
>&2 echo "$1"
|
||||
}
|
||||
|
||||
cd "$(dirname "$0")"/..
|
||||
|
||||
eprintln "Installing additional Rust components"
|
||||
rustup component add rustfmt clippy
|
||||
|
||||
eprintln "Installing mdbook"
|
||||
./scripts/install-mdbook
|
||||
|
||||
eprintln "Installing node dependencies"
|
||||
pushd compile_assets > /dev/null
|
||||
yarn install --frozen-lockfile
|
||||
popd > /dev/null
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -exuo pipefail
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")"/..
|
||||
|
||||
if ! mdbook --version; then
|
||||
cargo install mdbook --version 0.4.32 --force
|
||||
function eprintln() {
|
||||
>&2 echo "$1"
|
||||
}
|
||||
|
||||
VERSION="0.4.51"
|
||||
|
||||
eprintln "Checking if mdbook $VERSION is installed"
|
||||
if [[ "$(mdbook --version)" != "mdbook v$VERSION" ]]; then
|
||||
eprintln "Installing mdbook $VERSION"
|
||||
cargo install mdbook --version "$VERSION" --force
|
||||
fi
|
||||
eprintln "mdbook $VERSION is installed"
|
||||
|
||||
@@ -1 +1 @@
|
||||
^2.0.0
|
||||
^3.0.0
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.0.2
|
||||
3.1.0
|
||||
|
||||
@@ -1,33 +1,4 @@
|
||||
@charset "UTF-8";
|
||||
:root {
|
||||
--md-admonition-icon--note:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/></svg>");
|
||||
--md-admonition-icon--abstract:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17 9H7V7h10m0 6H7v-2h10m-3 6H7v-2h7M12 3a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z'/></svg>");
|
||||
--md-admonition-icon--info:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z'/></svg>");
|
||||
--md-admonition-icon--tip:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17.66 11.2c-.23-.3-.51-.56-.77-.82-.67-.6-1.43-1.03-2.07-1.66C13.33 7.26 13 4.85 13.95 3c-.95.23-1.78.75-2.49 1.32-2.59 2.08-3.61 5.75-2.39 8.9.04.1.08.2.08.33 0 .22-.15.42-.35.5-.23.1-.47.04-.66-.12a.58.58 0 0 1-.14-.17c-1.13-1.43-1.31-3.48-.55-5.12C5.78 10 4.87 12.3 5 14.47c.06.5.12 1 .29 1.5.14.6.41 1.2.71 1.73 1.08 1.73 2.95 2.97 4.96 3.22 2.14.27 4.43-.12 6.07-1.6 1.83-1.66 2.47-4.32 1.53-6.6l-.13-.26c-.21-.46-.77-1.26-.77-1.26m-3.16 6.3c-.28.24-.74.5-1.1.6-1.12.4-2.24-.16-2.9-.82 1.19-.28 1.9-1.16 2.11-2.05.17-.8-.15-1.46-.28-2.23-.12-.74-.1-1.37.17-2.06.19.38.39.76.63 1.06.77 1 1.98 1.44 2.24 2.8.04.14.06.28.06.43.03.82-.33 1.72-.93 2.27z'/></svg>");
|
||||
--md-admonition-icon--success:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m9 20.42-6.21-6.21 2.83-2.83L9 14.77l9.88-9.89 2.83 2.83L9 20.42z'/></svg>");
|
||||
--md-admonition-icon--question:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m15.07 11.25-.9.92C13.45 12.89 13 13.5 13 15h-2v-.5c0-1.11.45-2.11 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41a2 2 0 0 0-2-2 2 2 0 0 0-2 2H8a4 4 0 0 1 4-4 4 4 0 0 1 4 4 3.2 3.2 0 0 1-.93 2.25M13 19h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10c0-5.53-4.5-10-10-10z'/></svg>");
|
||||
--md-admonition-icon--warning:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 14h-2V9h2m0 9h-2v-2h2M1 21h22L12 2 1 21z'/></svg>");
|
||||
--md-admonition-icon--failure:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 6.91 17.09 4 12 9.09 6.91 4 4 6.91 9.09 12 4 17.09 6.91 20 12 14.91 17.09 20 20 17.09 14.91 12 20 6.91z'/></svg>");
|
||||
--md-admonition-icon--danger:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11 15H6l7-14v8h5l-7 14v-8z'/></svg>");
|
||||
--md-admonition-icon--bug:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 12h-4v-2h4m0 6h-4v-2h4m6-6h-2.81a5.985 5.985 0 0 0-1.82-1.96L17 4.41 15.59 3l-2.17 2.17a6.002 6.002 0 0 0-2.83 0L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z'/></svg>");
|
||||
--md-admonition-icon--example:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M7 13v-2h14v2H7m0 6v-2h14v2H7M7 7V5h14v2H7M3 8V5H2V4h2v4H3m-1 9v-1h3v4H2v-1h2v-.5H3v-1h1V17H2m2.25-7a.75.75 0 0 1 .75.75c0 .2-.08.39-.21.52L3.12 13H5v1H2v-.92L4 11H2v-1h2.25z'/></svg>");
|
||||
--md-admonition-icon--quote:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z'/></svg>");
|
||||
--md-details-icon:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8.59 16.58 13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42Z'/></svg>");
|
||||
}
|
||||
|
||||
:is(.admonition) {
|
||||
display: flow-root;
|
||||
margin: 1.5625em 0;
|
||||
@@ -84,6 +55,8 @@ a.admonition-anchor-link::before {
|
||||
padding-inline: 4.4rem 1.2rem;
|
||||
font-weight: 700;
|
||||
background-color: rgba(68, 138, 255, 0.1);
|
||||
print-color-adjust: exact;
|
||||
-webkit-print-color-adjust: exact;
|
||||
display: flex;
|
||||
}
|
||||
:is(.admonition-title, summary.admonition-title) p {
|
||||
@@ -99,6 +72,8 @@ html :is(.admonition-title, summary.admonition-title):last-child {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
background-color: #448aff;
|
||||
print-color-adjust: exact;
|
||||
-webkit-print-color-adjust: exact;
|
||||
mask-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"></svg>');
|
||||
-webkit-mask-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"></svg>');
|
||||
mask-repeat: no-repeat;
|
||||
@@ -111,6 +86,11 @@ html :is(.admonition-title, summary.admonition-title):last-child {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
@media print {
|
||||
details.admonition::details-content {
|
||||
display: contents;
|
||||
}
|
||||
}
|
||||
details.admonition > summary.admonition-title::after {
|
||||
position: absolute;
|
||||
top: 0.625em;
|
||||
@@ -131,205 +111,227 @@ details.admonition > summary.admonition-title::after {
|
||||
details[open].admonition > summary.admonition-title::after {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
summary.admonition-title::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.note) {
|
||||
:root {
|
||||
--md-details-icon: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8.59 16.58 13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42Z'/></svg>");
|
||||
}
|
||||
|
||||
:root {
|
||||
--md-admonition-icon--admonish-note: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/></svg>");
|
||||
--md-admonition-icon--admonish-abstract: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17 9H7V7h10m0 6H7v-2h10m-3 6H7v-2h7M12 3a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z'/></svg>");
|
||||
--md-admonition-icon--admonish-info: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z'/></svg>");
|
||||
--md-admonition-icon--admonish-tip: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17.66 11.2c-.23-.3-.51-.56-.77-.82-.67-.6-1.43-1.03-2.07-1.66C13.33 7.26 13 4.85 13.95 3c-.95.23-1.78.75-2.49 1.32-2.59 2.08-3.61 5.75-2.39 8.9.04.1.08.2.08.33 0 .22-.15.42-.35.5-.23.1-.47.04-.66-.12a.58.58 0 0 1-.14-.17c-1.13-1.43-1.31-3.48-.55-5.12C5.78 10 4.87 12.3 5 14.47c.06.5.12 1 .29 1.5.14.6.41 1.2.71 1.73 1.08 1.73 2.95 2.97 4.96 3.22 2.14.27 4.43-.12 6.07-1.6 1.83-1.66 2.47-4.32 1.53-6.6l-.13-.26c-.21-.46-.77-1.26-.77-1.26m-3.16 6.3c-.28.24-.74.5-1.1.6-1.12.4-2.24-.16-2.9-.82 1.19-.28 1.9-1.16 2.11-2.05.17-.8-.15-1.46-.28-2.23-.12-.74-.1-1.37.17-2.06.19.38.39.76.63 1.06.77 1 1.98 1.44 2.24 2.8.04.14.06.28.06.43.03.82-.33 1.72-.93 2.27z'/></svg>");
|
||||
--md-admonition-icon--admonish-success: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m9 20.42-6.21-6.21 2.83-2.83L9 14.77l9.88-9.89 2.83 2.83L9 20.42z'/></svg>");
|
||||
--md-admonition-icon--admonish-question: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m15.07 11.25-.9.92C13.45 12.89 13 13.5 13 15h-2v-.5c0-1.11.45-2.11 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41a2 2 0 0 0-2-2 2 2 0 0 0-2 2H8a4 4 0 0 1 4-4 4 4 0 0 1 4 4 3.2 3.2 0 0 1-.93 2.25M13 19h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10c0-5.53-4.5-10-10-10z'/></svg>");
|
||||
--md-admonition-icon--admonish-warning: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 14h-2V9h2m0 9h-2v-2h2M1 21h22L12 2 1 21z'/></svg>");
|
||||
--md-admonition-icon--admonish-failure: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 6.91 17.09 4 12 9.09 6.91 4 4 6.91 9.09 12 4 17.09 6.91 20 12 14.91 17.09 20 20 17.09 14.91 12 20 6.91z'/></svg>");
|
||||
--md-admonition-icon--admonish-danger: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11 15H6l7-14v8h5l-7 14v-8z'/></svg>");
|
||||
--md-admonition-icon--admonish-bug: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 12h-4v-2h4m0 6h-4v-2h4m6-6h-2.81a5.985 5.985 0 0 0-1.82-1.96L17 4.41 15.59 3l-2.17 2.17a6.002 6.002 0 0 0-2.83 0L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z'/></svg>");
|
||||
--md-admonition-icon--admonish-example: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M7 13v-2h14v2H7m0 6v-2h14v2H7M7 7V5h14v2H7M3 8V5H2V4h2v4H3m-1 9v-1h3v4H2v-1h2v-.5H3v-1h1V17H2m2.25-7a.75.75 0 0 1 .75.75c0 .2-.08.39-.21.52L3.12 13H5v1H2v-.92L4 11H2v-1h2.25z'/></svg>");
|
||||
--md-admonition-icon--admonish-quote: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z'/></svg>");
|
||||
}
|
||||
|
||||
:is(.admonition):is(.admonish-note) {
|
||||
border-color: #448aff;
|
||||
}
|
||||
|
||||
:is(.note) > :is(.admonition-title, summary.admonition-title) {
|
||||
:is(.admonish-note) > :is(.admonition-title, summary.admonition-title) {
|
||||
background-color: rgba(68, 138, 255, 0.1);
|
||||
}
|
||||
:is(.note) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
:is(.admonish-note) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
background-color: #448aff;
|
||||
mask-image: var(--md-admonition-icon--note);
|
||||
-webkit-mask-image: var(--md-admonition-icon--note);
|
||||
mask-image: var(--md-admonition-icon--admonish-note);
|
||||
-webkit-mask-image: var(--md-admonition-icon--admonish-note);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.abstract, .summary, .tldr) {
|
||||
:is(.admonition):is(.admonish-abstract, .admonish-summary, .admonish-tldr) {
|
||||
border-color: #00b0ff;
|
||||
}
|
||||
|
||||
:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary.admonition-title) {
|
||||
:is(.admonish-abstract, .admonish-summary, .admonish-tldr) > :is(.admonition-title, summary.admonition-title) {
|
||||
background-color: rgba(0, 176, 255, 0.1);
|
||||
}
|
||||
:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
:is(.admonish-abstract, .admonish-summary, .admonish-tldr) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
background-color: #00b0ff;
|
||||
mask-image: var(--md-admonition-icon--abstract);
|
||||
-webkit-mask-image: var(--md-admonition-icon--abstract);
|
||||
mask-image: var(--md-admonition-icon--admonish-abstract);
|
||||
-webkit-mask-image: var(--md-admonition-icon--admonish-abstract);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.info, .todo) {
|
||||
:is(.admonition):is(.admonish-info, .admonish-todo) {
|
||||
border-color: #00b8d4;
|
||||
}
|
||||
|
||||
:is(.info, .todo) > :is(.admonition-title, summary.admonition-title) {
|
||||
:is(.admonish-info, .admonish-todo) > :is(.admonition-title, summary.admonition-title) {
|
||||
background-color: rgba(0, 184, 212, 0.1);
|
||||
}
|
||||
:is(.info, .todo) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
:is(.admonish-info, .admonish-todo) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
background-color: #00b8d4;
|
||||
mask-image: var(--md-admonition-icon--info);
|
||||
-webkit-mask-image: var(--md-admonition-icon--info);
|
||||
mask-image: var(--md-admonition-icon--admonish-info);
|
||||
-webkit-mask-image: var(--md-admonition-icon--admonish-info);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.tip, .hint, .important) {
|
||||
:is(.admonition):is(.admonish-tip, .admonish-hint, .admonish-important) {
|
||||
border-color: #00bfa5;
|
||||
}
|
||||
|
||||
:is(.tip, .hint, .important) > :is(.admonition-title, summary.admonition-title) {
|
||||
:is(.admonish-tip, .admonish-hint, .admonish-important) > :is(.admonition-title, summary.admonition-title) {
|
||||
background-color: rgba(0, 191, 165, 0.1);
|
||||
}
|
||||
:is(.tip, .hint, .important) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
:is(.admonish-tip, .admonish-hint, .admonish-important) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
background-color: #00bfa5;
|
||||
mask-image: var(--md-admonition-icon--tip);
|
||||
-webkit-mask-image: var(--md-admonition-icon--tip);
|
||||
mask-image: var(--md-admonition-icon--admonish-tip);
|
||||
-webkit-mask-image: var(--md-admonition-icon--admonish-tip);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.success, .check, .done) {
|
||||
:is(.admonition):is(.admonish-success, .admonish-check, .admonish-done) {
|
||||
border-color: #00c853;
|
||||
}
|
||||
|
||||
:is(.success, .check, .done) > :is(.admonition-title, summary.admonition-title) {
|
||||
:is(.admonish-success, .admonish-check, .admonish-done) > :is(.admonition-title, summary.admonition-title) {
|
||||
background-color: rgba(0, 200, 83, 0.1);
|
||||
}
|
||||
:is(.success, .check, .done) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
:is(.admonish-success, .admonish-check, .admonish-done) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
background-color: #00c853;
|
||||
mask-image: var(--md-admonition-icon--success);
|
||||
-webkit-mask-image: var(--md-admonition-icon--success);
|
||||
mask-image: var(--md-admonition-icon--admonish-success);
|
||||
-webkit-mask-image: var(--md-admonition-icon--admonish-success);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.question, .help, .faq) {
|
||||
:is(.admonition):is(.admonish-question, .admonish-help, .admonish-faq) {
|
||||
border-color: #64dd17;
|
||||
}
|
||||
|
||||
:is(.question, .help, .faq) > :is(.admonition-title, summary.admonition-title) {
|
||||
:is(.admonish-question, .admonish-help, .admonish-faq) > :is(.admonition-title, summary.admonition-title) {
|
||||
background-color: rgba(100, 221, 23, 0.1);
|
||||
}
|
||||
:is(.question, .help, .faq) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
:is(.admonish-question, .admonish-help, .admonish-faq) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
background-color: #64dd17;
|
||||
mask-image: var(--md-admonition-icon--question);
|
||||
-webkit-mask-image: var(--md-admonition-icon--question);
|
||||
mask-image: var(--md-admonition-icon--admonish-question);
|
||||
-webkit-mask-image: var(--md-admonition-icon--admonish-question);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.warning, .caution, .attention) {
|
||||
:is(.admonition):is(.admonish-warning, .admonish-caution, .admonish-attention) {
|
||||
border-color: #ff9100;
|
||||
}
|
||||
|
||||
:is(.warning, .caution, .attention) > :is(.admonition-title, summary.admonition-title) {
|
||||
:is(.admonish-warning, .admonish-caution, .admonish-attention) > :is(.admonition-title, summary.admonition-title) {
|
||||
background-color: rgba(255, 145, 0, 0.1);
|
||||
}
|
||||
:is(.warning, .caution, .attention) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
:is(.admonish-warning, .admonish-caution, .admonish-attention) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
background-color: #ff9100;
|
||||
mask-image: var(--md-admonition-icon--warning);
|
||||
-webkit-mask-image: var(--md-admonition-icon--warning);
|
||||
mask-image: var(--md-admonition-icon--admonish-warning);
|
||||
-webkit-mask-image: var(--md-admonition-icon--admonish-warning);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.failure, .fail, .missing) {
|
||||
:is(.admonition):is(.admonish-failure, .admonish-fail, .admonish-missing) {
|
||||
border-color: #ff5252;
|
||||
}
|
||||
|
||||
:is(.failure, .fail, .missing) > :is(.admonition-title, summary.admonition-title) {
|
||||
:is(.admonish-failure, .admonish-fail, .admonish-missing) > :is(.admonition-title, summary.admonition-title) {
|
||||
background-color: rgba(255, 82, 82, 0.1);
|
||||
}
|
||||
:is(.failure, .fail, .missing) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
:is(.admonish-failure, .admonish-fail, .admonish-missing) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
background-color: #ff5252;
|
||||
mask-image: var(--md-admonition-icon--failure);
|
||||
-webkit-mask-image: var(--md-admonition-icon--failure);
|
||||
mask-image: var(--md-admonition-icon--admonish-failure);
|
||||
-webkit-mask-image: var(--md-admonition-icon--admonish-failure);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.danger, .error) {
|
||||
:is(.admonition):is(.admonish-danger, .admonish-error) {
|
||||
border-color: #ff1744;
|
||||
}
|
||||
|
||||
:is(.danger, .error) > :is(.admonition-title, summary.admonition-title) {
|
||||
:is(.admonish-danger, .admonish-error) > :is(.admonition-title, summary.admonition-title) {
|
||||
background-color: rgba(255, 23, 68, 0.1);
|
||||
}
|
||||
:is(.danger, .error) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
:is(.admonish-danger, .admonish-error) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
background-color: #ff1744;
|
||||
mask-image: var(--md-admonition-icon--danger);
|
||||
-webkit-mask-image: var(--md-admonition-icon--danger);
|
||||
mask-image: var(--md-admonition-icon--admonish-danger);
|
||||
-webkit-mask-image: var(--md-admonition-icon--admonish-danger);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.bug) {
|
||||
:is(.admonition):is(.admonish-bug) {
|
||||
border-color: #f50057;
|
||||
}
|
||||
|
||||
:is(.bug) > :is(.admonition-title, summary.admonition-title) {
|
||||
:is(.admonish-bug) > :is(.admonition-title, summary.admonition-title) {
|
||||
background-color: rgba(245, 0, 87, 0.1);
|
||||
}
|
||||
:is(.bug) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
:is(.admonish-bug) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
background-color: #f50057;
|
||||
mask-image: var(--md-admonition-icon--bug);
|
||||
-webkit-mask-image: var(--md-admonition-icon--bug);
|
||||
mask-image: var(--md-admonition-icon--admonish-bug);
|
||||
-webkit-mask-image: var(--md-admonition-icon--admonish-bug);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.example) {
|
||||
:is(.admonition):is(.admonish-example) {
|
||||
border-color: #7c4dff;
|
||||
}
|
||||
|
||||
:is(.example) > :is(.admonition-title, summary.admonition-title) {
|
||||
:is(.admonish-example) > :is(.admonition-title, summary.admonition-title) {
|
||||
background-color: rgba(124, 77, 255, 0.1);
|
||||
}
|
||||
:is(.example) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
:is(.admonish-example) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
background-color: #7c4dff;
|
||||
mask-image: var(--md-admonition-icon--example);
|
||||
-webkit-mask-image: var(--md-admonition-icon--example);
|
||||
mask-image: var(--md-admonition-icon--admonish-example);
|
||||
-webkit-mask-image: var(--md-admonition-icon--admonish-example);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.quote, .cite) {
|
||||
:is(.admonition):is(.admonish-quote, .admonish-cite) {
|
||||
border-color: #9e9e9e;
|
||||
}
|
||||
|
||||
:is(.quote, .cite) > :is(.admonition-title, summary.admonition-title) {
|
||||
:is(.admonish-quote, .admonish-cite) > :is(.admonition-title, summary.admonition-title) {
|
||||
background-color: rgba(158, 158, 158, 0.1);
|
||||
}
|
||||
:is(.quote, .cite) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
:is(.admonish-quote, .admonish-cite) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
background-color: #9e9e9e;
|
||||
mask-image: var(--md-admonition-icon--quote);
|
||||
-webkit-mask-image: var(--md-admonition-icon--quote);
|
||||
mask-image: var(--md-admonition-icon--admonish-quote);
|
||||
-webkit-mask-image: var(--md-admonition-icon--admonish-quote);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
@@ -340,7 +342,8 @@ details[open].admonition > summary.admonition-title::after {
|
||||
background-color: var(--sidebar-bg);
|
||||
}
|
||||
|
||||
.ayu :is(.admonition), .coal :is(.admonition) {
|
||||
.ayu :is(.admonition),
|
||||
.coal :is(.admonition) {
|
||||
background-color: var(--theme-hover);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
use anyhow::Result;
|
||||
use anyhow::{Context, Result};
|
||||
use clap::{Parser, Subcommand};
|
||||
use mdbook::{
|
||||
errors::Error,
|
||||
preprocess::{CmdPreprocessor, Preprocessor},
|
||||
};
|
||||
use mdbook::preprocess::{CmdPreprocessor, Preprocessor};
|
||||
use mdbook_admonish::Admonish;
|
||||
#[cfg(feature = "cli-install")]
|
||||
use serde::Deserialize;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use std::{io, process};
|
||||
use std::process;
|
||||
|
||||
/// mdbook preprocessor to add support for admonitions
|
||||
#[derive(Parser)]
|
||||
@@ -36,6 +35,18 @@ enum Commands {
|
||||
#[arg(long)]
|
||||
css_dir: Option<PathBuf>,
|
||||
},
|
||||
|
||||
/// Generate CSS file for custom directives.
|
||||
GenerateCustom {
|
||||
/// Root directory for the book, should contain the configuration file (`book.toml`)
|
||||
///
|
||||
/// If not set, defaults to the current directory.
|
||||
#[arg(long)]
|
||||
dir: Option<PathBuf>,
|
||||
|
||||
/// File to write generated css to.
|
||||
output: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@@ -62,10 +73,13 @@ fn run(cli: Cli) -> Result<()> {
|
||||
dir.unwrap_or_else(|| PathBuf::from(".")),
|
||||
css_dir.unwrap_or_else(|| PathBuf::from(".")),
|
||||
),
|
||||
Some(Commands::GenerateCustom { dir, output }) => {
|
||||
handle_generate_custom(dir.unwrap_or_else(|| PathBuf::from(".")), output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_preprocessing() -> Result<(), Error> {
|
||||
fn handle_preprocessing() -> std::result::Result<(), mdbook::errors::Error> {
|
||||
let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?;
|
||||
|
||||
if ctx.mdbook_version != mdbook::MDBOOK_VERSION {
|
||||
@@ -94,15 +108,59 @@ fn handle_supports(renderer: String) -> ! {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Config {
|
||||
#[serde(default)]
|
||||
preprocessor: Preprocessors,
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize)]
|
||||
struct Preprocessors {
|
||||
#[serde(default)]
|
||||
admonish: Option<toml::Table>,
|
||||
|
||||
#[serde(flatten)]
|
||||
_other: toml::Table,
|
||||
}
|
||||
|
||||
/// Load the plugin specific config as a toml string, for private deserialization.
|
||||
fn admonish_config_string(config: &Config) -> Result<String> {
|
||||
Ok(toml::to_string(
|
||||
&config
|
||||
.preprocessor
|
||||
.admonish
|
||||
.as_ref()
|
||||
.context("No configuration for mdbook-admonish in book.toml")?,
|
||||
)?)
|
||||
}
|
||||
|
||||
fn handle_generate_custom(proj_dir: PathBuf, output: PathBuf) -> Result<()> {
|
||||
let config = proj_dir.join("book.toml");
|
||||
log::info!("Reading configuration file '{}'", config.display());
|
||||
let data = fs::read_to_string(&config)
|
||||
.with_context(|| format!("can't read configuration file '{}'", config.display()))?;
|
||||
let config: Config = toml::from_str(&data).context("Invalid configuration file")?;
|
||||
|
||||
let css =
|
||||
mdbook_admonish::custom::css_from_config(&proj_dir, &admonish_config_string(&config)?)?;
|
||||
|
||||
log::info!("Writing custom CSS file '{}'", output.display());
|
||||
fs::write(output, css)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "cli-install")]
|
||||
mod install {
|
||||
use anyhow::{Context, Result};
|
||||
use path_slash::PathExt;
|
||||
use std::borrow::Cow;
|
||||
use std::path::Path;
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
io::Write,
|
||||
path::PathBuf,
|
||||
};
|
||||
use toml_edit::{self, Array, Document, Item, Table, Value};
|
||||
use toml_edit::{self, Array, DocumentMut, Item, Table, Value};
|
||||
|
||||
const ADMONISH_CSS_FILES: &[(&str, &[u8])] = &[(
|
||||
"mdbook-admonish.css",
|
||||
@@ -122,13 +180,23 @@ mod install {
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize path to UNIX style.
|
||||
// This avoids conflicts/rewriting files when projects are used under different
|
||||
// operating systems (e.g. on Windows, after being used on Linux)
|
||||
//
|
||||
// https://github.com/tommilligan/mdbook-admonish/issues/161
|
||||
fn normalize_config_file_path(path: &Path) -> Result<Cow<'_, str>> {
|
||||
path.to_slash()
|
||||
.context("UNIX style path normalization error")
|
||||
}
|
||||
|
||||
pub fn handle_install(proj_dir: PathBuf, css_dir: PathBuf) -> Result<()> {
|
||||
let config = proj_dir.join("book.toml");
|
||||
log::info!("Reading configuration file '{}'", config.display());
|
||||
let toml = fs::read_to_string(&config)
|
||||
.with_context(|| format!("can't read configuration file '{}'", config.display()))?;
|
||||
let mut doc = toml
|
||||
.parse::<Document>()
|
||||
.parse::<DocumentMut>()
|
||||
.context("configuration is not valid TOML")?;
|
||||
|
||||
if let Ok(preprocessor) = preprocessor(&mut doc) {
|
||||
@@ -139,7 +207,7 @@ mod install {
|
||||
);
|
||||
preprocessor["assets_version"] = value;
|
||||
} else {
|
||||
log::info!("Unexpected configuration, not updating prereprocessor configuration");
|
||||
log::info!("Unexpected configuration, not updating preprocessor configuration");
|
||||
};
|
||||
|
||||
let mut additional_css = additional_css(&mut doc);
|
||||
@@ -148,12 +216,13 @@ mod install {
|
||||
// Normalize path to remove no-op components
|
||||
// https://github.com/tommilligan/mdbook-admonish/issues/47
|
||||
let filepath: PathBuf = filepath.components().collect();
|
||||
let filepath_str = filepath.to_str().context("non-utf8 filepath")?;
|
||||
|
||||
if let Ok(ref mut additional_css) = additional_css {
|
||||
if !additional_css.contains_str(filepath_str) {
|
||||
let filepath_str = normalize_config_file_path(&filepath)?;
|
||||
|
||||
if !additional_css.contains_str(&filepath_str) {
|
||||
log::info!("Adding '{filepath_str}' to 'additional-css'");
|
||||
additional_css.push(filepath_str);
|
||||
additional_css.push(filepath_str.as_ref());
|
||||
}
|
||||
} else {
|
||||
log::warn!("Unexpected configuration, not updating 'additional-css'");
|
||||
@@ -190,7 +259,7 @@ A beautifully styled message.
|
||||
/// Return the `additional-css` field, initializing if required.
|
||||
///
|
||||
/// Return `Err` if the existing configuration is unknown.
|
||||
fn additional_css(doc: &mut Document) -> Result<&mut Array, ()> {
|
||||
fn additional_css(doc: &mut DocumentMut) -> Result<&mut Array, ()> {
|
||||
let doc = doc.as_table_mut();
|
||||
|
||||
let empty_table = Item::Table(Table::default());
|
||||
@@ -214,7 +283,7 @@ A beautifully styled message.
|
||||
/// Return the preprocessor table for admonish, initializing if required.
|
||||
///
|
||||
/// Return `Err` if the existing configuration is unknown.
|
||||
fn preprocessor(doc: &mut Document) -> Result<&mut Item, ()> {
|
||||
fn preprocessor(doc: &mut DocumentMut) -> Result<&mut Item, ()> {
|
||||
let doc = doc.as_table_mut();
|
||||
|
||||
let empty_table = Item::Table(Table::default());
|
||||
@@ -227,4 +296,24 @@ A beautifully styled message.
|
||||
item["command"] = toml_edit::value("mdbook-admonish");
|
||||
Ok(item)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
/// This test seems redundant, but would fail on Windows.
|
||||
///
|
||||
/// We want to always convert to a fixed output string style, independant
|
||||
/// of runtime platform, and forward slashes in relative paths are fine on
|
||||
/// Windows.
|
||||
#[test]
|
||||
fn test_normalize_config_file_path() {
|
||||
let input = PathBuf::from(".")
|
||||
.join("css-dir")
|
||||
.join("mdbook-admonish.css");
|
||||
let expected = "./css-dir/mdbook-admonish.css";
|
||||
let actual = normalize_config_file_path(&input).unwrap();
|
||||
assert_eq!(actual.as_ref(), expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,23 +2,62 @@ use anyhow::{Context, Result};
|
||||
use mdbook::preprocess::PreprocessorContext;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::types::AdmonitionDefaults;
|
||||
use crate::types::{AdmonitionDefaults, BuiltinDirective, BuiltinDirectiveConfig};
|
||||
|
||||
/// Loads the plugin configuration from mdbook internals.
|
||||
///
|
||||
/// Roundtrips config to string, to avoid linking the plugin's internal version of toml
|
||||
/// to the one publically exposed by the mdbook library.
|
||||
pub(crate) fn admonish_config_from_context(ctx: &PreprocessorContext) -> Result<Config> {
|
||||
let table: String = toml_mdbook::to_string(
|
||||
let table: String = toml::to_string(
|
||||
ctx.config
|
||||
.get_preprocessor("admonish")
|
||||
.context("No configuration for mdbook-admonish in book.toml")?,
|
||||
)?;
|
||||
toml::from_str(&table).context("Invalid mdbook-admonish configuration in book.toml")
|
||||
)
|
||||
.context("Could not serialize mdbook-admonish config. This is a bug in the toml library.")?;
|
||||
admonish_config_from_str(&table)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub(crate) fn admonish_config_from_str(data: &str) -> Result<Config> {
|
||||
let readonly: ConfigReadonly =
|
||||
toml::from_str(data).context("Invalid mdbook-admonish configuration in book.toml")?;
|
||||
let config = readonly.into();
|
||||
log::debug!("Loaded admonish config: {:?}", config);
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// All valid input states including back-compatibility fields.
|
||||
///
|
||||
/// This struct deliberately does not implement Serialize as it never meant to
|
||||
/// be written, only converted to Config.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Default)]
|
||||
struct ConfigReadonly {
|
||||
#[serde(default)]
|
||||
pub on_failure: OnFailure,
|
||||
|
||||
#[serde(default)]
|
||||
pub default: AdmonitionDefaults,
|
||||
|
||||
#[serde(default)]
|
||||
pub renderer: HashMap<String, RendererConfig>,
|
||||
|
||||
#[serde(default)]
|
||||
pub assets_version: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub custom: Vec<CustomDirectiveReadonly>,
|
||||
|
||||
#[serde(default)]
|
||||
pub builtin: HashMap<BuiltinDirective, BuiltinDirectiveConfig>,
|
||||
|
||||
#[serde(default)]
|
||||
pub directive: DirectiveConfig,
|
||||
}
|
||||
|
||||
/// The canonical config format, without back-compatibility
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Default)]
|
||||
pub(crate) struct Config {
|
||||
#[serde(default)]
|
||||
pub on_failure: OnFailure,
|
||||
@@ -31,6 +70,79 @@ pub(crate) struct Config {
|
||||
|
||||
#[serde(default)]
|
||||
pub assets_version: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub directive: DirectiveConfig,
|
||||
}
|
||||
|
||||
impl From<ConfigReadonly> for Config {
|
||||
fn from(other: ConfigReadonly) -> Self {
|
||||
let ConfigReadonly {
|
||||
on_failure,
|
||||
default,
|
||||
renderer,
|
||||
assets_version,
|
||||
custom,
|
||||
builtin,
|
||||
mut directive,
|
||||
} = other;
|
||||
|
||||
// Merge deprecated config fields into main config object
|
||||
directive.custom.extend(
|
||||
custom
|
||||
.into_iter()
|
||||
.map(|CustomDirectiveReadonly { directive, config }| (directive, config)),
|
||||
);
|
||||
directive.builtin.extend(builtin);
|
||||
|
||||
Self {
|
||||
on_failure,
|
||||
default,
|
||||
renderer,
|
||||
assets_version,
|
||||
directive,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Default)]
|
||||
pub(crate) struct DirectiveConfig {
|
||||
#[serde(default)]
|
||||
pub custom: HashMap<String, CustomDirective>,
|
||||
|
||||
#[serde(default)]
|
||||
pub builtin: HashMap<BuiltinDirective, BuiltinDirectiveConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub(crate) struct CustomDirective {
|
||||
/// Path to an SVG file, relative to the book root.
|
||||
pub icon: PathBuf,
|
||||
|
||||
/// Primary color for this directive.
|
||||
pub color: hex_color::HexColor,
|
||||
|
||||
/// Alternative directives the user can specify
|
||||
#[serde(default)]
|
||||
pub aliases: Vec<String>,
|
||||
|
||||
/// Title to use, human readable.
|
||||
#[serde(default)]
|
||||
pub title: Option<String>,
|
||||
|
||||
/// Default collapsible value.
|
||||
#[serde(default)]
|
||||
pub collapsible: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub(crate) struct CustomDirectiveReadonly {
|
||||
/// The primary directive. Used for CSS classnames
|
||||
pub directive: String,
|
||||
|
||||
/// Path to an SVG file, relative to the book root.
|
||||
#[serde(flatten)]
|
||||
config: CustomDirective,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
@@ -58,3 +170,156 @@ impl Default for OnFailure {
|
||||
Self::Continue
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::types::BuiltinDirective;
|
||||
|
||||
#[test]
|
||||
fn empty_config_okay() -> Result<()> {
|
||||
let actual = admonish_config_from_str("")?;
|
||||
let expected = Config::default();
|
||||
assert_eq!(actual, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn css_id_prefix_kebab_case_allowed() -> Result<()> {
|
||||
let expected = Config {
|
||||
default: AdmonitionDefaults {
|
||||
css_id_prefix: Some("flam-".to_owned()),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Snake case okay
|
||||
let actual = admonish_config_from_str(r#"default = { css_id_prefix = "flam-" }"#)?;
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
// Kebab case back-compat okay
|
||||
let actual = admonish_config_from_str(r#"default = { css-id-prefix = "flam-" }"#)?;
|
||||
assert_eq!(actual, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_old_and_new_custom_directives() -> Result<()> {
|
||||
let serialized = r##"
|
||||
[directive.custom.purple]
|
||||
icon = "/tmp/test-directive.svg"
|
||||
color = "#9B4F96"
|
||||
aliases = ["test-directive-alias-0"]
|
||||
title = "Purple"
|
||||
collapsible = true
|
||||
|
||||
[[custom]]
|
||||
directive = "blue"
|
||||
icon = "/tmp/test-directive.svg"
|
||||
color = "#0038A8"
|
||||
aliases = []
|
||||
title = "Blue"
|
||||
"##;
|
||||
let expected = Config {
|
||||
directive: DirectiveConfig {
|
||||
custom: HashMap::from([
|
||||
(
|
||||
"purple".to_owned(),
|
||||
CustomDirective {
|
||||
icon: PathBuf::from("/tmp/test-directive.svg"),
|
||||
color: hex_color::HexColor::from((155, 79, 150)),
|
||||
aliases: vec!["test-directive-alias-0".to_owned()],
|
||||
title: Some("Purple".to_owned()),
|
||||
collapsible: Some(true),
|
||||
},
|
||||
),
|
||||
(
|
||||
"blue".to_owned(),
|
||||
CustomDirective {
|
||||
icon: PathBuf::from("/tmp/test-directive.svg"),
|
||||
color: hex_color::HexColor::from((0, 56, 168)),
|
||||
aliases: vec![],
|
||||
title: Some("Blue".to_owned()),
|
||||
collapsible: None,
|
||||
},
|
||||
),
|
||||
]),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let actual = admonish_config_from_str(serialized)?;
|
||||
assert_eq!(actual, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_config_roundtrip() -> Result<()> {
|
||||
let input = Config {
|
||||
default: AdmonitionDefaults {
|
||||
css_id_prefix: Some("flam-".to_owned()),
|
||||
collapsible: true,
|
||||
title: Some("".to_owned()),
|
||||
},
|
||||
assets_version: Some("1.1.1".to_owned()),
|
||||
directive: DirectiveConfig {
|
||||
custom: HashMap::from([(
|
||||
"test-directive".to_owned(),
|
||||
CustomDirective {
|
||||
icon: PathBuf::from("/tmp/test-directive.svg"),
|
||||
color: hex_color::HexColor::from((155, 79, 150)),
|
||||
aliases: vec!["test-directive-alias-0".to_owned()],
|
||||
title: Some("test-directive-title".to_owned()),
|
||||
collapsible: Some(true),
|
||||
},
|
||||
)]),
|
||||
builtin: HashMap::from([(
|
||||
BuiltinDirective::Warning,
|
||||
BuiltinDirectiveConfig {
|
||||
collapsible: Some(true),
|
||||
},
|
||||
)]),
|
||||
},
|
||||
on_failure: OnFailure::Bail,
|
||||
renderer: HashMap::from([(
|
||||
"test-mode".to_owned(),
|
||||
RendererConfig {
|
||||
render_mode: Some(RenderMode::Strip),
|
||||
},
|
||||
)]),
|
||||
};
|
||||
|
||||
let expected = r##"on_failure = "bail"
|
||||
assets_version = "1.1.1"
|
||||
|
||||
[default]
|
||||
title = ""
|
||||
collapsible = true
|
||||
css_id_prefix = "flam-"
|
||||
|
||||
[renderer.test-mode]
|
||||
render_mode = "strip"
|
||||
|
||||
[directive.custom.test-directive]
|
||||
icon = "/tmp/test-directive.svg"
|
||||
color = "#9B4F96"
|
||||
aliases = ["test-directive-alias-0"]
|
||||
title = "test-directive-title"
|
||||
collapsible = true
|
||||
|
||||
[directive.builtin.warning]
|
||||
collapsible = true
|
||||
"##;
|
||||
|
||||
let serialized = toml::to_string(&input)?;
|
||||
assert_eq!(serialized, expected);
|
||||
|
||||
let actual = admonish_config_from_str(&serialized)?;
|
||||
assert_eq!(actual, input);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
mod toml_wrangling;
|
||||
mod v1;
|
||||
mod v2;
|
||||
mod v3;
|
||||
|
||||
/// Configuration as described by the instance of an admonition in markdown.
|
||||
///
|
||||
/// This structure represents the configuration the user must provide in each
|
||||
/// instance.
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Default)]
|
||||
pub(crate) struct InstanceConfig {
|
||||
pub(crate) directive: String,
|
||||
pub(crate) title: Option<String>,
|
||||
pub(crate) id: Option<String>,
|
||||
pub(crate) additional_classnames: Vec<String>,
|
||||
pub(crate) collapsible: Option<bool>,
|
||||
}
|
||||
@@ -34,20 +37,29 @@ impl InstanceConfig {
|
||||
/// - `Some(InstanceConfig)` if this is an `admonish` block
|
||||
pub fn from_info_string(info_string: &str) -> Option<Result<Self, String>> {
|
||||
let config_string = admonition_config_string(info_string)?;
|
||||
Some(Self::from_admonish_config_string(config_string))
|
||||
}
|
||||
|
||||
// If we succeed at parsing v2, return that. Otherwise hold onto the error
|
||||
let config_v2_error = match v2::from_config_string(config_string) {
|
||||
Ok(config) => return Some(Ok(config)),
|
||||
Err(config) => config,
|
||||
/// Parse an info string that is known to be for `admonish`.
|
||||
fn from_admonish_config_string(config_string: &str) -> Result<Self, String> {
|
||||
// If we succeed at parsing v3, return that. Otherwise hold onto the error
|
||||
let config_v3_error = match v3::from_config_string(config_string) {
|
||||
Ok(config) => return Ok(config),
|
||||
Err(error) => error,
|
||||
};
|
||||
|
||||
Some(if let Ok(config) = v1::from_config_string(config_string) {
|
||||
// If we succeed at parsing v1, return that.
|
||||
Ok(config)
|
||||
} else {
|
||||
// Otherwise return our v2 error.
|
||||
Err(config_v2_error)
|
||||
})
|
||||
// If we succeed at parsing v2, return that
|
||||
if let Ok(config) = v2::from_config_string(config_string) {
|
||||
return Ok(config);
|
||||
};
|
||||
|
||||
// If we succeed at parsing v1, return that.
|
||||
if let Ok(config) = v1::from_config_string(config_string) {
|
||||
return Ok(config);
|
||||
}
|
||||
|
||||
// Otherwise return our v3 error.
|
||||
Err(config_v3_error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,18 +81,37 @@ mod test {
|
||||
InstanceConfig {
|
||||
directive: "note".to_owned(),
|
||||
title: None,
|
||||
id: None,
|
||||
additional_classnames: vec!["additional-classname".to_owned()],
|
||||
collapsible: None,
|
||||
}
|
||||
);
|
||||
// v2 syntax is supported
|
||||
assert_eq!(
|
||||
InstanceConfig::from_info_string(r#"admonish title="Custom Title" type="question""#)
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
InstanceConfig::from_info_string(
|
||||
r#"admonish title="Custom Title" type="question" id="my-id""#
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
InstanceConfig {
|
||||
directive: "question".to_owned(),
|
||||
title: Some("Custom Title".to_owned()),
|
||||
id: Some("my-id".to_owned()),
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: None,
|
||||
}
|
||||
);
|
||||
// v3 syntax is supported
|
||||
assert_eq!(
|
||||
InstanceConfig::from_info_string(
|
||||
r#"admonish title="Custom Title", type="question", id="my-id""#
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
InstanceConfig {
|
||||
directive: "question".to_owned(),
|
||||
title: Some("Custom Title".to_owned()),
|
||||
id: Some("my-id".to_owned()),
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: None,
|
||||
}
|
||||
|
||||
44
src/config/toml_wrangling.rs
Normal file
44
src/config/toml_wrangling.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||
pub(crate) struct UserInput {
|
||||
#[serde(default)]
|
||||
pub r#type: Option<String>,
|
||||
#[serde(default)]
|
||||
pub title: Option<String>,
|
||||
#[serde(default)]
|
||||
pub id: Option<String>,
|
||||
#[serde(default)]
|
||||
pub class: Option<String>,
|
||||
#[serde(default)]
|
||||
pub collapsible: Option<bool>,
|
||||
}
|
||||
|
||||
impl UserInput {
|
||||
pub fn classnames(&self) -> Vec<String> {
|
||||
self.class
|
||||
.as_ref()
|
||||
.map(|class| {
|
||||
class
|
||||
.split(' ')
|
||||
.filter(|classname| !classname.is_empty())
|
||||
.map(|classname| classname.to_owned())
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) static RX_DIRECTIVE: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r#"^[A-Za-z0-9_-]+$"#).expect("directive regex"));
|
||||
|
||||
pub(crate) fn format_toml_parsing_error(error: impl Display) -> String {
|
||||
format!("TOML parsing error: {error}")
|
||||
}
|
||||
|
||||
pub(crate) fn format_invalid_directive(directive: &str, original_error: impl Display) -> String {
|
||||
format!("'{directive}' is not a valid directive or TOML key-value pair.\n\n{original_error}")
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
147
src/config/v2.rs
147
src/config/v2.rs
@@ -1,28 +1,16 @@
|
||||
use super::toml_wrangling::{
|
||||
format_invalid_directive, format_toml_parsing_error, UserInput, RX_DIRECTIVE,
|
||||
};
|
||||
use super::InstanceConfig;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||
struct UserInput {
|
||||
#[serde(default)]
|
||||
r#type: Option<String>,
|
||||
#[serde(default)]
|
||||
title: Option<String>,
|
||||
#[serde(default)]
|
||||
class: Option<String>,
|
||||
#[serde(default)]
|
||||
collapsible: Option<bool>,
|
||||
}
|
||||
|
||||
/// Transform our config string into valid toml
|
||||
fn bare_key_value_pairs_to_toml(pairs: &str) -> String {
|
||||
use regex::Captures;
|
||||
|
||||
static RX_BARE_KEY_ASSIGNMENT: Lazy<Regex> = Lazy::new(|| {
|
||||
let bare_key = r#"[A-Za-z0-9_-]+"#;
|
||||
Regex::new(&format!("(?:{bare_key}) *=")).expect("bare key assignment regex")
|
||||
});
|
||||
static RX_BARE_KEY_ASSIGNMENT: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r#"(?:[A-Za-z0-9_-]+) *="#).expect("bare key assignment regex"));
|
||||
|
||||
fn prefix_with_newline(captures: &Captures) -> String {
|
||||
format!(
|
||||
@@ -39,10 +27,18 @@ fn bare_key_value_pairs_to_toml(pairs: &str) -> String {
|
||||
.into_owned()
|
||||
}
|
||||
|
||||
fn user_input_from_config_toml(config_toml: &str) -> Result<UserInput, String> {
|
||||
toml::from_str(config_toml).map_err(format_toml_parsing_error)
|
||||
}
|
||||
|
||||
/// Parse and return the config assuming v2 format.
|
||||
///
|
||||
/// Note that if an error occurs, a parsed struct that can be returned to
|
||||
/// show the error message will be returned.
|
||||
///
|
||||
/// The basic idea here is to accept space separated key-value pairs, break them
|
||||
/// onto separate lines, and then parse them as a TOML document.
|
||||
/// This breaks when values contain a literal '=' sign, for which v3 syntax should be used.
|
||||
pub(crate) fn from_config_string(config_string: &str) -> Result<InstanceConfig, String> {
|
||||
let config_toml = bare_key_value_pairs_to_toml(config_string);
|
||||
let config_toml = config_toml.trim();
|
||||
@@ -50,7 +46,7 @@ pub(crate) fn from_config_string(config_string: &str) -> Result<InstanceConfig,
|
||||
let config: UserInput = match toml::from_str(config_toml) {
|
||||
Ok(config) => config,
|
||||
Err(error) => {
|
||||
let original_error = Err(format!("TOML parsing error: {error}"));
|
||||
let original_error = format_toml_parsing_error(error);
|
||||
|
||||
// For ergonomic reasons, we allow users to specify the directive without
|
||||
// a key. So if parsing fails initially, take the first line,
|
||||
@@ -60,17 +56,11 @@ pub(crate) fn from_config_string(config_string: &str) -> Result<InstanceConfig,
|
||||
None => (config_toml, ""),
|
||||
};
|
||||
|
||||
static RX_DIRECTIVE: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r#"^[A-Za-z0-9_-]+$"#).expect("directive regex"));
|
||||
|
||||
if !RX_DIRECTIVE.is_match(directive) {
|
||||
return original_error;
|
||||
return Err(format_invalid_directive(directive, original_error));
|
||||
}
|
||||
|
||||
let mut config: UserInput = match toml::from_str(config_toml) {
|
||||
Ok(config) => config,
|
||||
Err(_) => return original_error,
|
||||
};
|
||||
let mut config = user_input_from_config_toml(config_toml)?;
|
||||
config.r#type = Some(directive.to_owned());
|
||||
config
|
||||
}
|
||||
@@ -88,6 +78,7 @@ pub(crate) fn from_config_string(config_string: &str) -> Result<InstanceConfig,
|
||||
Ok(InstanceConfig {
|
||||
directive: config.r#type.unwrap_or_default(),
|
||||
title: config.title,
|
||||
id: config.id,
|
||||
additional_classnames,
|
||||
collapsible: config.collapsible,
|
||||
})
|
||||
@@ -99,80 +90,120 @@ mod test {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_from_config_string_v2() {
|
||||
assert_eq!(
|
||||
from_config_string("").unwrap(),
|
||||
fn test_from_config_string_v2() -> Result<(), ()> {
|
||||
fn check(config_string: &str, expected: InstanceConfig) -> Result<(), ()> {
|
||||
let actual = match from_config_string(config_string) {
|
||||
Ok(config) => config,
|
||||
Err(error) => panic!("Expected config to be valid, got error:\n\n{}", error),
|
||||
};
|
||||
assert_eq!(actual, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
check(
|
||||
"",
|
||||
InstanceConfig {
|
||||
directive: "".to_owned(),
|
||||
title: None,
|
||||
id: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: None,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
from_config_string(" ").unwrap(),
|
||||
},
|
||||
)?;
|
||||
check(
|
||||
" ",
|
||||
InstanceConfig {
|
||||
directive: "".to_owned(),
|
||||
title: None,
|
||||
id: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: None,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
from_config_string(
|
||||
r#"type="note" class="additional classname" title="Никита" collapsible=true"#
|
||||
)
|
||||
.unwrap(),
|
||||
},
|
||||
)?;
|
||||
check(
|
||||
r#"type="note" class="additional classname" title="Никита" collapsible=true"#,
|
||||
InstanceConfig {
|
||||
directive: "note".to_owned(),
|
||||
title: Some("Никита".to_owned()),
|
||||
id: None,
|
||||
additional_classnames: vec!["additional".to_owned(), "classname".to_owned()],
|
||||
collapsible: Some(true),
|
||||
}
|
||||
);
|
||||
},
|
||||
)?;
|
||||
// Specifying unknown keys is okay, as long as they're valid
|
||||
assert_eq!(
|
||||
from_config_string(r#"unkonwn="but valid toml""#).unwrap(),
|
||||
check(
|
||||
r#"unkonwn="but valid toml""#,
|
||||
InstanceConfig {
|
||||
directive: "".to_owned(),
|
||||
title: None,
|
||||
id: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: None,
|
||||
}
|
||||
);
|
||||
},
|
||||
)?;
|
||||
// Just directive is fine
|
||||
assert_eq!(
|
||||
from_config_string(r#"info"#).unwrap(),
|
||||
check(
|
||||
r#"info"#,
|
||||
InstanceConfig {
|
||||
directive: "info".to_owned(),
|
||||
title: None,
|
||||
id: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: None,
|
||||
}
|
||||
);
|
||||
},
|
||||
)?;
|
||||
// Directive plus toml config
|
||||
assert_eq!(
|
||||
from_config_string(r#"info title="Information" collapsible=false"#).unwrap(),
|
||||
check(
|
||||
r#"info title="Information" collapsible=false"#,
|
||||
InstanceConfig {
|
||||
directive: "info".to_owned(),
|
||||
title: Some("Information".to_owned()),
|
||||
id: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: Some(false),
|
||||
}
|
||||
);
|
||||
},
|
||||
)?;
|
||||
// Test custom id
|
||||
check(
|
||||
r#"info title="My Info" id="my-info-custom-id""#,
|
||||
InstanceConfig {
|
||||
directive: "info".to_owned(),
|
||||
title: Some("My Info".to_owned()),
|
||||
id: Some("my-info-custom-id".to_owned()),
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: None,
|
||||
},
|
||||
)?;
|
||||
// Directive after toml config is an error
|
||||
assert!(from_config_string(r#"title="Information" info"#).is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_config_string_invalid_directive() {
|
||||
assert_eq!(
|
||||
from_config_string(r#"oh!wow titlel=""#).unwrap_err(),
|
||||
r#"'oh!wow' is not a valid directive or TOML key-value pair.
|
||||
|
||||
TOML parsing error: TOML parse error at line 1, column 3
|
||||
|
|
||||
1 | oh!wow
|
||||
| ^
|
||||
expected `.`, `=`
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_config_string_invalid_toml_value() {
|
||||
assert_eq!(
|
||||
from_config_string(r#"note titlel=""#).unwrap_err(),
|
||||
r#"TOML parsing error: TOML parse error at line 1, column 6
|
||||
r#"TOML parsing error: TOML parse error at line 1, column 9
|
||||
|
|
||||
1 | note
|
||||
| ^
|
||||
expected `.`, `=`
|
||||
1 | titlel="
|
||||
| ^
|
||||
invalid basic string
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
202
src/config/v3.rs
Normal file
202
src/config/v3.rs
Normal file
@@ -0,0 +1,202 @@
|
||||
use super::toml_wrangling::{
|
||||
format_invalid_directive, format_toml_parsing_error, UserInput, RX_DIRECTIVE,
|
||||
};
|
||||
use super::InstanceConfig;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||
struct Wrapper<T> {
|
||||
config: T,
|
||||
}
|
||||
|
||||
/// Transform our config string into valid toml
|
||||
fn bare_inline_table_to_toml(pairs: &str) -> String {
|
||||
format!("config = {{ {pairs} }}")
|
||||
}
|
||||
|
||||
fn user_input_from_config_string(config_string: &str) -> Result<UserInput, String> {
|
||||
match toml::from_str::<Wrapper<_>>(&bare_inline_table_to_toml(config_string)) {
|
||||
Ok(wrapper) => Ok(wrapper.config),
|
||||
Err(error) => Err(format_toml_parsing_error(error)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse and return the config assuming v3 format.
|
||||
///
|
||||
/// Note that if an error occurs, a parsed struct that can be returned to
|
||||
/// show the error message will be returned.
|
||||
///
|
||||
/// The basic idea here is to accept the inside of an inline table, wrap it,
|
||||
/// parse it, and then use the toml values.
|
||||
pub(crate) fn from_config_string(config_string: &str) -> Result<InstanceConfig, String> {
|
||||
let config_string = config_string.trim();
|
||||
|
||||
let config = match user_input_from_config_string(config_string) {
|
||||
Ok(config) => config,
|
||||
Err(error) => {
|
||||
// For ergonomic reasons, we allow users to specify the directive without
|
||||
// a key. So if parsing fails initially, take the first word,
|
||||
// use that as the directive, and reparse.
|
||||
let (directive, config_string) = match config_string.split_once(' ') {
|
||||
Some((directive, config_string)) => (directive.trim(), config_string.trim()),
|
||||
None => (config_string, ""),
|
||||
};
|
||||
|
||||
if !RX_DIRECTIVE.is_match(directive) {
|
||||
return Err(format_invalid_directive(directive, error));
|
||||
}
|
||||
|
||||
let mut config = user_input_from_config_string(config_string)?;
|
||||
config.r#type = Some(directive.to_owned());
|
||||
config
|
||||
}
|
||||
};
|
||||
|
||||
let additional_classnames = config.classnames();
|
||||
Ok(InstanceConfig {
|
||||
directive: config.r#type.unwrap_or_default(),
|
||||
title: config.title,
|
||||
id: config.id,
|
||||
additional_classnames,
|
||||
collapsible: config.collapsible,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_from_config_string_v3() -> Result<(), ()> {
|
||||
fn check(config_string: &str, expected: InstanceConfig) -> Result<(), ()> {
|
||||
let actual = match from_config_string(config_string) {
|
||||
Ok(config) => config,
|
||||
Err(error) => {
|
||||
panic!("Expected config '{config_string}' to be valid, got error:\n\n{error}")
|
||||
}
|
||||
};
|
||||
assert_eq!(actual, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
check(
|
||||
"",
|
||||
InstanceConfig {
|
||||
directive: "".to_owned(),
|
||||
title: None,
|
||||
id: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: None,
|
||||
},
|
||||
)?;
|
||||
check(
|
||||
" ",
|
||||
InstanceConfig {
|
||||
directive: "".to_owned(),
|
||||
title: None,
|
||||
id: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: None,
|
||||
},
|
||||
)?;
|
||||
check(
|
||||
r#"type="note", class="additional classname", title="Никита", collapsible=true"#,
|
||||
InstanceConfig {
|
||||
directive: "note".to_owned(),
|
||||
title: Some("Никита".to_owned()),
|
||||
id: None,
|
||||
additional_classnames: vec!["additional".to_owned(), "classname".to_owned()],
|
||||
collapsible: Some(true),
|
||||
},
|
||||
)?;
|
||||
// Specifying unknown keys is okay, as long as they're valid
|
||||
check(
|
||||
r#"unkonwn="but valid toml""#,
|
||||
InstanceConfig {
|
||||
directive: "".to_owned(),
|
||||
title: None,
|
||||
id: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: None,
|
||||
},
|
||||
)?;
|
||||
// Just directive is fine
|
||||
check(
|
||||
r#"info"#,
|
||||
InstanceConfig {
|
||||
directive: "info".to_owned(),
|
||||
title: None,
|
||||
id: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: None,
|
||||
},
|
||||
)?;
|
||||
// Directive plus toml config
|
||||
check(
|
||||
r#"info title="Information", collapsible=false"#,
|
||||
InstanceConfig {
|
||||
directive: "info".to_owned(),
|
||||
title: Some("Information".to_owned()),
|
||||
id: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: Some(false),
|
||||
},
|
||||
)?;
|
||||
// Test custom id
|
||||
check(
|
||||
r#"info title="My Info", id="my-info-custom-id""#,
|
||||
InstanceConfig {
|
||||
directive: "info".to_owned(),
|
||||
title: Some("My Info".to_owned()),
|
||||
id: Some("my-info-custom-id".to_owned()),
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: None,
|
||||
},
|
||||
)?;
|
||||
// Directive after toml config is an error
|
||||
assert!(from_config_string(r#"title="Information" info"#).is_err());
|
||||
// HTML with quotes inside content
|
||||
// Note that we use toml literal (single quoted) strings here
|
||||
check(
|
||||
r#"info title='My <span class="emphasis">Title</span>'"#,
|
||||
InstanceConfig {
|
||||
directive: "info".to_owned(),
|
||||
title: Some(r#"My <span class="emphasis">Title</span>"#.to_owned()),
|
||||
id: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: None,
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_config_string_invalid_directive() {
|
||||
assert_eq!(
|
||||
from_config_string(r#"oh!wow titlel=""#).unwrap_err(),
|
||||
r#"'oh!wow' is not a valid directive or TOML key-value pair.
|
||||
|
||||
TOML parsing error: TOML parse error at line 1, column 14
|
||||
|
|
||||
1 | config = { oh!wow titlel=" }
|
||||
| ^
|
||||
expected `.`, `=`
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_config_string_invalid_toml_value() {
|
||||
assert_eq!(
|
||||
from_config_string(r#"note titlel=""#).unwrap_err(),
|
||||
r#"TOML parsing error: TOML parse error at line 1, column 22
|
||||
|
|
||||
1 | config = { titlel=" }
|
||||
| ^
|
||||
invalid basic string
|
||||
"#
|
||||
);
|
||||
}
|
||||
}
|
||||
111
src/custom.rs
Normal file
111
src/custom.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
//! This module is responsible for generating custom CSS for new admonition variants.
|
||||
//!
|
||||
//! It has unit tests to ensure the output matches that of the compile_assets CSS.
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use hex_color::{Case, HexColor};
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
static RX_COLLAPSE_NEWLINES: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"[\r\n]+\s*").expect("invalid whitespace regex"));
|
||||
|
||||
// Do some simple things to make the svg input probably a valid data url
|
||||
// Based on this gist: https://gist.github.com/jennyknuth/222825e315d45a738ed9d6e04c7a88d0
|
||||
fn svg_to_data_url(svg: &str) -> String {
|
||||
const XMLNS: &str = r#"http://www.w3.org/2000/svg"#;
|
||||
//
|
||||
let mut svg = RX_COLLAPSE_NEWLINES.replace_all(svg, "").to_string();
|
||||
if !svg.contains(XMLNS) {
|
||||
log::warn!("Your SVG file does not contain '<svg xmlns=\"{XMLNS}\"', it will likely fail to render.");
|
||||
}
|
||||
|
||||
svg = svg
|
||||
.replace('"', "'")
|
||||
.replace('%', "%25")
|
||||
.replace('#', "%23")
|
||||
.replace('{', "%7B")
|
||||
.replace('}', "%7D");
|
||||
format!("url(\"data:image/svg+xml;charset=utf-8,{}\")", svg)
|
||||
}
|
||||
|
||||
/// Given a valid set of inputs, generate the relevant CSS.
|
||||
///
|
||||
/// It is up to the caller to validate inputs.
|
||||
fn directive_css(name: &str, svg_data: &str, tint: HexColor) -> String {
|
||||
let data_url = svg_to_data_url(svg_data);
|
||||
let tint_faint = format!("rgba({}, {}, {}, {})", tint.r, tint.g, tint.b, 0.1);
|
||||
let tint = tint.display_rgb().with_case(Case::Lower);
|
||||
format!(
|
||||
":root {{
|
||||
--md-admonition-icon--admonish-{name}: {data_url};
|
||||
}}
|
||||
|
||||
:is(.admonition):is(.admonish-{name}) {{
|
||||
border-color: {tint};
|
||||
}}
|
||||
|
||||
:is(.admonish-{name}) > :is(.admonition-title, summary.admonition-title) {{
|
||||
background-color: {tint_faint};
|
||||
}}
|
||||
:is(.admonish-{name}) > :is(.admonition-title, summary.admonition-title)::before {{
|
||||
background-color: {tint};
|
||||
mask-image: var(--md-admonition-icon--admonish-{name});
|
||||
-webkit-mask-image: var(--md-admonition-icon--admonish-{name});
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}}
|
||||
",
|
||||
name = name,
|
||||
data_url = data_url,
|
||||
tint = tint,
|
||||
tint_faint = tint_faint
|
||||
)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn css_from_config(book_dir: &Path, config: &str) -> Result<String> {
|
||||
let config = crate::book_config::admonish_config_from_str(config)?;
|
||||
let custom_directives = config.directive.custom;
|
||||
|
||||
if custom_directives.is_empty() {
|
||||
return Err(anyhow!("No custom directives provided"));
|
||||
}
|
||||
|
||||
log::info!("Loaded {} custom directives", custom_directives.len());
|
||||
let mut css = String::new();
|
||||
for (directive_name, directive) in custom_directives.iter() {
|
||||
let svg = fs::read_to_string(book_dir.join(&directive.icon))
|
||||
.with_context(|| format!("can't read icon file '{}'", directive.icon.display()))?;
|
||||
css.push_str(&directive_css(directive_name, &svg, directive.color));
|
||||
}
|
||||
Ok(css)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
const GENERATED_CSS: &str = include_str!("./test_data/mdbook-admonish-custom-expected.css");
|
||||
const NOTE_SVG: &str = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox='0 0 24 24'>
|
||||
<path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/>
|
||||
</svg>
|
||||
"#;
|
||||
|
||||
// Verify the generated CSS here against a sample from the compile_assets output.
|
||||
//
|
||||
// The ensures that any new custom CSS will be in line with official styles.
|
||||
#[test]
|
||||
fn verify_against_generated_css() {
|
||||
let actual = directive_css("note", NOTE_SVG, HexColor::parse("#448aff").unwrap());
|
||||
assert_eq!(
|
||||
GENERATED_CSS, actual,
|
||||
"Rust generated CSS is out of step with SCSS generated CSS"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
609
src/markdown.rs
609
src/markdown.rs
@@ -1,17 +1,16 @@
|
||||
use mdbook::errors::Result as MdbookResult;
|
||||
use pulldown_cmark::{CodeBlockKind::*, Event, Options, Parser, Tag};
|
||||
|
||||
pub use crate::preprocessor::Admonish;
|
||||
use crate::{
|
||||
book_config::OnFailure,
|
||||
parse::parse_admonition,
|
||||
types::{AdmonitionDefaults, RenderTextMode},
|
||||
types::{Overrides, RenderTextMode},
|
||||
};
|
||||
|
||||
pub(crate) fn preprocess(
|
||||
content: &str,
|
||||
on_failure: OnFailure,
|
||||
admonition_defaults: &AdmonitionDefaults,
|
||||
overrides: &Overrides,
|
||||
render_text_mode: RenderTextMode,
|
||||
) -> MdbookResult<String> {
|
||||
let mut id_counter = Default::default();
|
||||
@@ -28,12 +27,15 @@ pub(crate) fn preprocess(
|
||||
for (event, span) in events.into_offset_iter() {
|
||||
if let Event::Start(Tag::CodeBlock(Fenced(info_string))) = event.clone() {
|
||||
let span_content = &content[span.start..span.end];
|
||||
const INDENT_SCAN_MAX: usize = 1024;
|
||||
let indent = indent_of(content, span.start, INDENT_SCAN_MAX);
|
||||
|
||||
let admonition = match parse_admonition(
|
||||
info_string.as_ref(),
|
||||
admonition_defaults,
|
||||
overrides,
|
||||
span_content,
|
||||
on_failure,
|
||||
indent,
|
||||
) {
|
||||
Some(admonition) => admonition,
|
||||
None => continue,
|
||||
@@ -44,7 +46,7 @@ pub(crate) fn preprocess(
|
||||
// Once we've identitified admonition blocks, handle them differently
|
||||
// depending on our render mode
|
||||
let new_content = match render_text_mode {
|
||||
RenderTextMode::Html => admonition.html_with_unique_ids(&mut id_counter),
|
||||
RenderTextMode::Html => admonition.html(&mut id_counter),
|
||||
RenderTextMode::Strip => admonition.strip(),
|
||||
};
|
||||
|
||||
@@ -62,16 +64,81 @@ pub(crate) fn preprocess(
|
||||
Ok(content)
|
||||
}
|
||||
|
||||
/// Returns the indent of the given position.
|
||||
///
|
||||
/// Defined as the number of characters between the given `position` (where
|
||||
/// position is a valid char boundary byte-index in `content`),
|
||||
/// and the previous newline character `\n`.
|
||||
///
|
||||
/// `max` is the maximum number of characters to scan before assuming there is
|
||||
/// no indent (will return zero if exceeded).
|
||||
///
|
||||
/// ## Panics
|
||||
///
|
||||
/// Will panic if `position` is not a valid utf-8 char boundary index of `content`.
|
||||
fn indent_of(content: &str, position: usize, max: usize) -> usize {
|
||||
// Scan for a line start before this span.
|
||||
content[..position]
|
||||
.chars()
|
||||
.rev()
|
||||
// For safety, only scan up to a fixed limit of the text
|
||||
.take(max)
|
||||
.position(|c| c == '\n')
|
||||
// If we can't find a newline, assume no indent
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::types::AdmonitionDefaults;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn indent_of_samples() {
|
||||
for (content, position, max, expected) in [
|
||||
// Empty case
|
||||
("", 0, 10, 0),
|
||||
("no newline", 4, 10, 0),
|
||||
// Newline at position 5, difference from 8 is 3
|
||||
("with\nnewline", 8, 10, 3),
|
||||
// If no newline in safety limit, return 0
|
||||
("with\nnewline", 8, 2, 0),
|
||||
// Safety limit is characters, not bytes
|
||||
// Regression test for FIXME LINK
|
||||
(
|
||||
"例えばこれは",
|
||||
// Position is generated by mdbook internals, should be valid char limit
|
||||
// This mimics the second character starting the span
|
||||
"例".len(),
|
||||
// Any arbitrary safetly limit should be valid
|
||||
1,
|
||||
// Should not panic
|
||||
0,
|
||||
),
|
||||
(
|
||||
"例え\n れは",
|
||||
// Position is generated by mdbook internals, should be valid char limit
|
||||
// This mimics the second character starting the span
|
||||
"例え\n ".len(),
|
||||
// Any arbitrary safetly limit should be valid
|
||||
4,
|
||||
// Should not panic
|
||||
2,
|
||||
),
|
||||
] {
|
||||
let actual = indent_of(content, position, max);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
||||
|
||||
fn prep(content: &str) -> String {
|
||||
preprocess(
|
||||
content,
|
||||
OnFailure::Continue,
|
||||
&AdmonitionDefaults::default(),
|
||||
&Overrides::default(),
|
||||
RenderTextMode::Html,
|
||||
)
|
||||
.unwrap()
|
||||
@@ -88,11 +155,13 @@ Text
|
||||
|
||||
let expected = r##"# Chapter
|
||||
|
||||
<div id="admonition-note" class="admonition note">
|
||||
<div id="admonition-note" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-note-title">
|
||||
|
||||
Note
|
||||
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-note"></a>
|
||||
</div>
|
||||
<div>
|
||||
@@ -120,11 +189,13 @@ Text
|
||||
|
||||
let expected = r##"# Chapter
|
||||
|
||||
<div id="admonition-note" class="admonition note">
|
||||
<div id="admonition-note" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-note-title">
|
||||
|
||||
Note
|
||||
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-note"></a>
|
||||
</div>
|
||||
<div>
|
||||
@@ -152,11 +223,13 @@ Text
|
||||
|
||||
let expected = r##"# Chapter
|
||||
|
||||
<div id="admonition-warning" class="admonition warning">
|
||||
<div id="admonition-warning" class="admonition admonish-warning" role="note" aria-labelledby="admonition-warning-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-warning-title">
|
||||
|
||||
Warning
|
||||
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-warning"></a>
|
||||
</div>
|
||||
<div>
|
||||
@@ -182,11 +255,13 @@ Text
|
||||
|
||||
let expected = r##"# Chapter
|
||||
|
||||
<div id="admonition-caution" class="admonition warning">
|
||||
<div id="admonition-caution" class="admonition admonish-warning" role="note" aria-labelledby="admonition-caution-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-caution-title">
|
||||
|
||||
Caution
|
||||
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-caution"></a>
|
||||
</div>
|
||||
<div>
|
||||
@@ -212,11 +287,13 @@ Text
|
||||
|
||||
let expected = r##"# Chapter
|
||||
|
||||
<div id="admonition-read-this" class="admonition warning">
|
||||
<div id="admonition-read-this" class="admonition admonish-warning" role="note" aria-labelledby="admonition-read-this-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-read-this-title">
|
||||
|
||||
Read **this**!
|
||||
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-read-this"></a>
|
||||
</div>
|
||||
<div>
|
||||
@@ -306,11 +383,13 @@ hello
|
||||
|
||||
let expected = r##"
|
||||
|
||||
<div id="admonition-and-in-the-title" class="admonition note">
|
||||
<div id="admonition-and-in-the-title" class="admonition admonish-note" role="note" aria-labelledby="admonition-and-in-the-title-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-and-in-the-title-title">
|
||||
|
||||
And "<i>in</i>" the title
|
||||
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-and-in-the-title"></a>
|
||||
</div>
|
||||
<div>
|
||||
@@ -336,11 +415,13 @@ hello
|
||||
|
||||
let expected = r##"
|
||||
|
||||
<div id="admonition-trademark" class="admonition warning">
|
||||
<div id="admonition-trademark" class="admonition admonish-warning" role="note" aria-labelledby="admonition-trademark-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-trademark-title">
|
||||
|
||||
Trademark™
|
||||
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-trademark"></a>
|
||||
</div>
|
||||
<div>
|
||||
@@ -365,11 +446,13 @@ Will have bonus classnames
|
||||
|
||||
let expected = r##"
|
||||
|
||||
<div id="admonition-tip" class="admonition tip my-style other-style">
|
||||
<div id="admonition-tip" class="admonition admonish-tip my-style other-style" role="note" aria-labelledby="admonition-tip-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-tip-title">
|
||||
|
||||
Tip
|
||||
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-tip"></a>
|
||||
</div>
|
||||
<div>
|
||||
@@ -393,11 +476,13 @@ Will have bonus classnames
|
||||
|
||||
let expected = r##"
|
||||
|
||||
<div id="admonition-developers-dont-want-you-to-know-this-one-weird-tip" class="admonition tip my-style other-style">
|
||||
<div id="admonition-developers-dont-want-you-to-know-this-one-weird-tip" class="admonition admonish-tip my-style other-style" role="note" aria-labelledby="admonition-developers-dont-want-you-to-know-this-one-weird-tip-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-developers-dont-want-you-to-know-this-one-weird-tip-title">
|
||||
|
||||
Developers don't want you to know this one weird tip!
|
||||
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-developers-dont-want-you-to-know-this-one-weird-tip"></a>
|
||||
</div>
|
||||
<div>
|
||||
@@ -418,16 +503,16 @@ Will have bonus classnames
|
||||
```
|
||||
"#;
|
||||
|
||||
let expected = r##"
|
||||
let expected = r#"
|
||||
|
||||
<div id="admonition-default" class="admonition note">
|
||||
<div id="admonition-default" class="admonition admonish-note" role="note">
|
||||
<div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
"##;
|
||||
"#;
|
||||
|
||||
assert_eq!(expected, prep(content));
|
||||
}
|
||||
@@ -446,11 +531,13 @@ Content one.
|
||||
|
||||
let expected = r##"
|
||||
|
||||
<div id="admonition-my-note" class="admonition note">
|
||||
<div id="admonition-my-note" class="admonition admonish-note" role="note" aria-labelledby="admonition-my-note-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-my-note-title">
|
||||
|
||||
My Note
|
||||
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-my-note"></a>
|
||||
</div>
|
||||
<div>
|
||||
@@ -461,11 +548,13 @@ Content zero.
|
||||
</div>
|
||||
|
||||
|
||||
<div id="admonition-my-note-1" class="admonition note">
|
||||
<div id="admonition-my-note-1" class="admonition admonish-note" role="note" aria-labelledby="admonition-my-note-1-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-my-note-1-title">
|
||||
|
||||
My Note
|
||||
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-my-note-1"></a>
|
||||
</div>
|
||||
<div>
|
||||
@@ -489,11 +578,13 @@ Bonus content!
|
||||
|
||||
let expected = r##"
|
||||
|
||||
<div id="admonition-article-heading" class="admonition tip my other-style">
|
||||
<div id="admonition-article-heading" class="admonition admonish-tip my other-style" role="note" aria-labelledby="admonition-article-heading-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-article-heading-title">
|
||||
|
||||
Article Heading
|
||||
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-article-heading"></a>
|
||||
</div>
|
||||
<div>
|
||||
@@ -517,11 +608,13 @@ Bonus content!
|
||||
|
||||
let expected = r##"
|
||||
|
||||
<div id="admonition-error-rendering-admonishment" class="admonition bug">
|
||||
<div id="admonition-error-rendering-admonishment" class="admonition admonish-bug" role="note" aria-labelledby="admonition-error-rendering-admonishment-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-error-rendering-admonishment-title">
|
||||
|
||||
Error rendering admonishment
|
||||
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-error-rendering-admonishment"></a>
|
||||
</div>
|
||||
<div>
|
||||
@@ -529,10 +622,12 @@ Error rendering admonishment
|
||||
Failed with:
|
||||
|
||||
```log
|
||||
TOML parsing error: TOML parse error at line 1, column 8
|
||||
'title="' is not a valid directive or TOML key-value pair.
|
||||
|
||||
TOML parsing error: TOML parse error at line 1, column 21
|
||||
|
|
||||
1 | title="
|
||||
| ^
|
||||
1 | config = { title=" }
|
||||
| ^
|
||||
invalid basic string
|
||||
|
||||
```
|
||||
@@ -564,7 +659,7 @@ Bonus content!
|
||||
preprocess(
|
||||
content,
|
||||
OnFailure::Bail,
|
||||
&AdmonitionDefaults::default(),
|
||||
&Overrides::default(),
|
||||
RenderTextMode::Html
|
||||
)
|
||||
.unwrap_err()
|
||||
@@ -591,7 +686,7 @@ x = 20;
|
||||
preprocess(
|
||||
content,
|
||||
OnFailure::Bail,
|
||||
&AdmonitionDefaults::default(),
|
||||
&Overrides::default(),
|
||||
RenderTextMode::Strip
|
||||
)
|
||||
.unwrap(),
|
||||
@@ -617,11 +712,13 @@ Hidden
|
||||
|
||||
let expected = r##"
|
||||
|
||||
<details id="admonition-note" class="admonition note">
|
||||
<details id="admonition-note" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-title">
|
||||
<summary class="admonition-title">
|
||||
<div id="admonition-note-title">
|
||||
|
||||
Note
|
||||
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-note"></a>
|
||||
</summary>
|
||||
<div>
|
||||
@@ -646,11 +743,13 @@ Text
|
||||
|
||||
let expected = r##"# Chapter
|
||||
|
||||
<div id="admonition-admonish" class="admonition note">
|
||||
<div id="admonition-admonish" class="admonition admonish-note" role="note" aria-labelledby="admonition-admonish-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-admonish-title">
|
||||
|
||||
Admonish
|
||||
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-admonish"></a>
|
||||
</div>
|
||||
<div>
|
||||
@@ -665,9 +764,13 @@ Text
|
||||
let preprocess_result = preprocess(
|
||||
content,
|
||||
OnFailure::Continue,
|
||||
&AdmonitionDefaults {
|
||||
title: Some("Admonish".to_owned()),
|
||||
collapsible: false,
|
||||
&Overrides {
|
||||
book: AdmonitionDefaults {
|
||||
title: Some("Admonish".to_owned()),
|
||||
css_id_prefix: None,
|
||||
collapsible: false,
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
RenderTextMode::Html,
|
||||
)
|
||||
@@ -684,9 +787,9 @@ A simple admonition.
|
||||
Text
|
||||
"#;
|
||||
|
||||
let expected = r##"# Chapter
|
||||
let expected = r#"# Chapter
|
||||
|
||||
<div id="admonition-default" class="admonition note">
|
||||
<div id="admonition-default" class="admonition admonish-note" role="note">
|
||||
<div>
|
||||
|
||||
A simple admonition.
|
||||
@@ -694,14 +797,18 @@ A simple admonition.
|
||||
</div>
|
||||
</div>
|
||||
Text
|
||||
"##;
|
||||
"#;
|
||||
|
||||
let preprocess_result = preprocess(
|
||||
content,
|
||||
OnFailure::Continue,
|
||||
&AdmonitionDefaults {
|
||||
title: Some("Admonish".to_owned()),
|
||||
collapsible: false,
|
||||
&Overrides {
|
||||
book: AdmonitionDefaults {
|
||||
title: Some("Admonish".to_owned()),
|
||||
css_id_prefix: None,
|
||||
collapsible: false,
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
RenderTextMode::Html,
|
||||
)
|
||||
@@ -716,11 +823,43 @@ Text
|
||||
A simple admonition.
|
||||
```
|
||||
Text
|
||||
"#;
|
||||
|
||||
let expected = r#"# Chapter
|
||||
|
||||
<div id="admonition-default" class="admonition admonish-note" role="note">
|
||||
<div>
|
||||
|
||||
A simple admonition.
|
||||
|
||||
</div>
|
||||
</div>
|
||||
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="admonition-default" class="admonition note">
|
||||
<div id="yay-custom-id" class="admonition admonish-success" role="note" aria-labelledby="yay-custom-id-title">
|
||||
<div class="admonition-title">
|
||||
<div id="yay-custom-id-title">
|
||||
|
||||
Check
|
||||
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#yay-custom-id"></a>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
A simple admonition.
|
||||
@@ -732,4 +871,394 @@ Text
|
||||
|
||||
assert_eq!(expected, prep(content));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_custom_id_default_prefix() {
|
||||
let content = r#"# Chapter
|
||||
```admonish check
|
||||
A simple admonition.
|
||||
```
|
||||
Text
|
||||
"#;
|
||||
|
||||
let expected = r##"# Chapter
|
||||
|
||||
<div id="admonition-check" class="admonition admonish-success" role="note" aria-labelledby="admonition-check-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-check-title">
|
||||
|
||||
Check
|
||||
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-check"></a>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
A simple admonition.
|
||||
|
||||
</div>
|
||||
</div>
|
||||
Text
|
||||
"##;
|
||||
|
||||
assert_eq!(expected, prep(content));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_custom_id_default_prefix_custom_title() {
|
||||
let content = r#"# Chapter
|
||||
```admonish check title="Check Mark"
|
||||
A simple admonition.
|
||||
```
|
||||
Text
|
||||
"#;
|
||||
|
||||
let expected = r##"# Chapter
|
||||
|
||||
<div id="admonition-check-mark" class="admonition admonish-success" role="note" aria-labelledby="admonition-check-mark-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-check-mark-title">
|
||||
|
||||
Check Mark
|
||||
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-check-mark"></a>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
A simple admonition.
|
||||
|
||||
</div>
|
||||
</div>
|
||||
Text
|
||||
"##;
|
||||
|
||||
assert_eq!(expected, prep(content));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn title_and_content_with_html() {
|
||||
// Note that we use toml literal (single quoted) strings here
|
||||
// and the fact we have an equals sign in the value does not cause
|
||||
// us to break (because we're using v3 syntax, not v2)
|
||||
let content = r#"# Chapter
|
||||
```admonish success title='Check <span class="emphasis">Mark</span>'
|
||||
A <span class="emphasis">simple</span> admonition.
|
||||
```
|
||||
Text
|
||||
"#;
|
||||
|
||||
let expected = r##"# Chapter
|
||||
|
||||
<div id="admonition-check-mark" class="admonition admonish-success" role="note" aria-labelledby="admonition-check-mark-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-check-mark-title">
|
||||
|
||||
Check <span class="emphasis">Mark</span>
|
||||
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-check-mark"></a>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
A <span class="emphasis">simple</span> admonition.
|
||||
|
||||
</div>
|
||||
</div>
|
||||
Text
|
||||
"##;
|
||||
|
||||
assert_eq!(expected, prep(content));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_default_id_prefix() {
|
||||
let content = r#"# Chapter
|
||||
```admonish info
|
||||
A simple admonition.
|
||||
```
|
||||
Text
|
||||
"#;
|
||||
|
||||
let expected = r##"# Chapter
|
||||
|
||||
<div id="info" class="admonition admonish-info" role="note" aria-labelledby="info-title">
|
||||
<div class="admonition-title">
|
||||
<div id="info-title">
|
||||
|
||||
Info
|
||||
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#info"></a>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
A simple admonition.
|
||||
|
||||
</div>
|
||||
</div>
|
||||
Text
|
||||
"##;
|
||||
|
||||
let preprocess_result = preprocess(
|
||||
content,
|
||||
OnFailure::Continue,
|
||||
&Overrides {
|
||||
book: AdmonitionDefaults {
|
||||
title: Some("Info".to_owned()),
|
||||
css_id_prefix: Some("".to_owned()),
|
||||
collapsible: false,
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
RenderTextMode::Html,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(expected, preprocess_result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_id_prefix_custom_title() {
|
||||
let content = r#"# Chapter
|
||||
```admonish info title="My Title"
|
||||
A simple admonition.
|
||||
```
|
||||
Text
|
||||
"#;
|
||||
|
||||
let expected = r##"# Chapter
|
||||
|
||||
<div id="prefix-my-title" class="admonition admonish-info" role="note" aria-labelledby="prefix-my-title-title">
|
||||
<div class="admonition-title">
|
||||
<div id="prefix-my-title-title">
|
||||
|
||||
My Title
|
||||
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#prefix-my-title"></a>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
A simple admonition.
|
||||
|
||||
</div>
|
||||
</div>
|
||||
Text
|
||||
"##;
|
||||
|
||||
let preprocess_result = preprocess(
|
||||
content,
|
||||
OnFailure::Continue,
|
||||
&Overrides {
|
||||
book: AdmonitionDefaults {
|
||||
title: Some("Info".to_owned()),
|
||||
css_id_prefix: Some("prefix-".to_owned()),
|
||||
collapsible: false,
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
RenderTextMode::Html,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(expected, preprocess_result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_id_custom_title() {
|
||||
let content = r#"# Chapter
|
||||
```admonish info title="My Title" id="my-section-id"
|
||||
A simple admonition.
|
||||
```
|
||||
Text
|
||||
"#;
|
||||
|
||||
let expected = r##"# Chapter
|
||||
|
||||
<div id="my-section-id" class="admonition admonish-info" role="note" aria-labelledby="my-section-id-title">
|
||||
<div class="admonition-title">
|
||||
<div id="my-section-id-title">
|
||||
|
||||
My Title
|
||||
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#my-section-id"></a>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
A simple admonition.
|
||||
|
||||
</div>
|
||||
</div>
|
||||
Text
|
||||
"##;
|
||||
|
||||
let preprocess_result = preprocess(
|
||||
content,
|
||||
OnFailure::Continue,
|
||||
&Overrides {
|
||||
book: AdmonitionDefaults {
|
||||
title: Some("Info".to_owned()),
|
||||
css_id_prefix: Some("ignored-prefix-".to_owned()),
|
||||
collapsible: false,
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
RenderTextMode::Html,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(expected, preprocess_result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_embed() {
|
||||
let content = r#"# Chapter
|
||||
|
||||
1. Thing one
|
||||
|
||||
```sh
|
||||
Thing one
|
||||
```
|
||||
|
||||
1. Thing two
|
||||
|
||||
```admonish
|
||||
Thing two
|
||||
```
|
||||
|
||||
1. Thing three
|
||||
|
||||
```sh
|
||||
Thing three
|
||||
```
|
||||
"#;
|
||||
|
||||
let expected = r##"# Chapter
|
||||
|
||||
1. Thing one
|
||||
|
||||
```sh
|
||||
Thing one
|
||||
```
|
||||
|
||||
1. Thing two
|
||||
|
||||
|
||||
<div id="admonition-note" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-note-title">
|
||||
|
||||
Note
|
||||
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-note"></a>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
Thing two
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
1. Thing three
|
||||
|
||||
```sh
|
||||
Thing three
|
||||
```
|
||||
"##;
|
||||
|
||||
assert_eq!(expected, prep(content));
|
||||
}
|
||||
|
||||
// Regression test for https://github.com/tommilligan/mdbook-admonish/issues/223
|
||||
#[test]
|
||||
fn nested_list_should_not_render_indented_code_block() {
|
||||
let content = r#"# Chapter
|
||||
|
||||
- Level one
|
||||
|
||||
```admonish
|
||||
Thing one
|
||||
line two
|
||||
```
|
||||
|
||||
- Level two
|
||||
|
||||
```admonish
|
||||
Thing two
|
||||
line two
|
||||
```
|
||||
|
||||
- Level three
|
||||
|
||||
```admonish
|
||||
Thing three
|
||||
line two
|
||||
```
|
||||
"#;
|
||||
|
||||
let expected = r##"# Chapter
|
||||
|
||||
- Level one
|
||||
|
||||
|
||||
<div id="admonition-note" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-note-title">
|
||||
|
||||
Note
|
||||
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-note"></a>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
Thing one
|
||||
line two
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
- Level two
|
||||
|
||||
|
||||
<div id="admonition-note-1" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-1-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-note-1-title">
|
||||
|
||||
Note
|
||||
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-note-1"></a>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
Thing two
|
||||
line two
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
- Level three
|
||||
|
||||
|
||||
<div id="admonition-note-2" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-2-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-note-2-title">
|
||||
|
||||
Note
|
||||
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-note-2"></a>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
Thing three
|
||||
line two
|
||||
|
||||
</div>
|
||||
</div>
|
||||
"##;
|
||||
|
||||
assert_eq!(expected, prep(content));
|
||||
}
|
||||
}
|
||||
|
||||
33
src/parse.rs
33
src/parse.rs
@@ -1,12 +1,11 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub use crate::preprocessor::Admonish;
|
||||
use crate::{
|
||||
book_config::OnFailure,
|
||||
render::Admonition,
|
||||
resolve::AdmonitionMeta,
|
||||
types::{AdmonitionDefaults, Directive},
|
||||
types::{BuiltinDirective, CssId, Overrides},
|
||||
};
|
||||
|
||||
/// Given the content in the span of the code block, and the info string,
|
||||
@@ -20,18 +19,17 @@ use crate::{
|
||||
/// If the code block is not an admonition, return `None`.
|
||||
pub(crate) fn parse_admonition<'a>(
|
||||
info_string: &'a str,
|
||||
admonition_defaults: &'a AdmonitionDefaults,
|
||||
overrides: &'a Overrides,
|
||||
content: &'a str,
|
||||
on_failure: OnFailure,
|
||||
indent: usize,
|
||||
) -> Option<Result<Admonition<'a>>> {
|
||||
// We need to know fence details anyway for error messages
|
||||
let extracted = extract_admonish_body(content);
|
||||
|
||||
let info = AdmonitionMeta::from_info_string(info_string, admonition_defaults)?;
|
||||
let info = AdmonitionMeta::from_info_string(info_string, overrides)?;
|
||||
let info = match info {
|
||||
Ok(info) => info,
|
||||
// FIXME return error messages to break build if configured
|
||||
// Err(message) => return Some(Err(content)),
|
||||
Err(message) => {
|
||||
// Construct a fence capable of enclosing whatever we wrote for the
|
||||
// actual input block
|
||||
@@ -45,8 +43,9 @@ pub(crate) fn parse_admonition<'a>(
|
||||
r#"Error processing admonition. To fail the build instead of continuing, set 'on_failure = "bail"'"#
|
||||
);
|
||||
Ok(Admonition {
|
||||
directive: Directive::Bug,
|
||||
directive: BuiltinDirective::Bug.to_string(),
|
||||
title: "Error rendering admonishment".to_owned(),
|
||||
css_id: CssId::Prefix("admonition-".to_owned()),
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: false,
|
||||
content: Cow::Owned(format!(
|
||||
@@ -63,6 +62,7 @@ Original markdown input:
|
||||
{enclosing_fence}
|
||||
"#
|
||||
)),
|
||||
indent,
|
||||
})
|
||||
}
|
||||
OnFailure::Bail => Err(anyhow!("Error processing admonition, bailing:\n{content}")),
|
||||
@@ -70,7 +70,24 @@ Original markdown input:
|
||||
}
|
||||
};
|
||||
|
||||
Some(Ok(Admonition::new(info, extracted.body)))
|
||||
Some(Ok(Admonition::new(
|
||||
info,
|
||||
extracted.body,
|
||||
// Note that this is a bit hacky - the fence information comes from the start
|
||||
// of the block, and includes the whole line.
|
||||
//
|
||||
// This is more likely to be what we want, as ending indentation is unrelated
|
||||
// according to the commonmark spec (ref https://spec.commonmark.org/0.12/#example-85)
|
||||
//
|
||||
// The main case we're worried about here is indenting enough to be inside list items,
|
||||
// and in this case the starting code fence must be indented enough to be considered
|
||||
// part of the list item.
|
||||
//
|
||||
// The hacky thing is that we're considering line indent in the document as a whole,
|
||||
// not relative to the context of some containing item. But I think that's what we
|
||||
// want for now, anyway.
|
||||
indent,
|
||||
)))
|
||||
}
|
||||
|
||||
/// We can't trust the info string length to find the start of the body
|
||||
|
||||
@@ -8,7 +8,7 @@ use mdbook::{
|
||||
use crate::{
|
||||
book_config::{admonish_config_from_context, Config, RenderMode},
|
||||
markdown::preprocess,
|
||||
types::RenderTextMode,
|
||||
types::{Overrides, RenderTextMode},
|
||||
};
|
||||
|
||||
pub struct Admonish;
|
||||
@@ -22,9 +22,21 @@ impl Preprocessor for Admonish {
|
||||
let config = admonish_config_from_context(ctx)?;
|
||||
ensure_compatible_assets_version(&config)?;
|
||||
|
||||
let custom_directives = config
|
||||
.directive
|
||||
.custom
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect();
|
||||
let on_failure = config.on_failure;
|
||||
let admonition_defaults = config.default;
|
||||
|
||||
let overrides = Overrides {
|
||||
book: admonition_defaults,
|
||||
custom: custom_directives,
|
||||
builtin: config.directive.builtin,
|
||||
};
|
||||
|
||||
// Load what rendering we should do from config, falling back to a default
|
||||
let render_mode = config
|
||||
.renderer
|
||||
@@ -53,15 +65,11 @@ impl Preprocessor for Admonish {
|
||||
|
||||
if let BookItem::Chapter(ref mut chapter) = *item {
|
||||
res = Some(
|
||||
preprocess(
|
||||
&chapter.content,
|
||||
on_failure,
|
||||
&admonition_defaults,
|
||||
render_text_mode,
|
||||
)
|
||||
.map(|md| {
|
||||
chapter.content = md;
|
||||
}),
|
||||
preprocess(&chapter.content, on_failure, &overrides, render_text_mode).map(
|
||||
|md| {
|
||||
chapter.content = md;
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -170,11 +178,13 @@ x = 20;
|
||||
"#;
|
||||
let expected_content = r##"
|
||||
|
||||
<div id="admonition-title" class="admonition note">
|
||||
<div id="admonition-title" class="admonition admonish-note" role="note" aria-labelledby="admonition-title-title">
|
||||
<div class="admonition-title">
|
||||
<div id="admonition-title-title">
|
||||
|
||||
Title
|
||||
|
||||
</div>
|
||||
<a class="admonition-anchor-link" href="#admonition-title"></a>
|
||||
</div>
|
||||
<div>
|
||||
@@ -190,7 +200,7 @@ x = 20;
|
||||
|
||||
let ctx = mock_context(
|
||||
&json!({
|
||||
"assets_version": "2.0.0"
|
||||
"assets_version": "3.0.0"
|
||||
}),
|
||||
"html",
|
||||
);
|
||||
@@ -212,7 +222,7 @@ x = 20;
|
||||
"#;
|
||||
let ctx = mock_context(
|
||||
&json!({
|
||||
"assets_version": "2.0.0"
|
||||
"assets_version": "3.0.0"
|
||||
}),
|
||||
"test",
|
||||
);
|
||||
@@ -242,7 +252,7 @@ x = 20;
|
||||
"#;
|
||||
let ctx = mock_context(
|
||||
&json!({
|
||||
"assets_version": "2.0.0",
|
||||
"assets_version": "3.0.0",
|
||||
"renderer": {
|
||||
"test": {
|
||||
"render_mode": "strip",
|
||||
|
||||
146
src/render.rs
146
src/render.rs
@@ -2,42 +2,25 @@ use mdbook::utils::unique_id_from_content;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub use crate::preprocessor::Admonish;
|
||||
use crate::{resolve::AdmonitionMeta, types::Directive};
|
||||
|
||||
impl Directive {
|
||||
fn classname(&self) -> &'static str {
|
||||
match self {
|
||||
Directive::Note => "note",
|
||||
Directive::Abstract => "abstract",
|
||||
Directive::Info => "info",
|
||||
Directive::Tip => "tip",
|
||||
Directive::Success => "success",
|
||||
Directive::Question => "question",
|
||||
Directive::Warning => "warning",
|
||||
Directive::Failure => "failure",
|
||||
Directive::Danger => "danger",
|
||||
Directive::Bug => "bug",
|
||||
Directive::Example => "example",
|
||||
Directive::Quote => "quote",
|
||||
}
|
||||
}
|
||||
}
|
||||
use crate::{resolve::AdmonitionMeta, types::CssId};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) struct Admonition<'a> {
|
||||
pub(crate) directive: Directive,
|
||||
pub(crate) directive: String,
|
||||
pub(crate) title: String,
|
||||
pub(crate) content: Cow<'a, str>,
|
||||
pub(crate) css_id: CssId,
|
||||
pub(crate) additional_classnames: Vec<String>,
|
||||
pub(crate) collapsible: bool,
|
||||
pub(crate) indent: usize,
|
||||
}
|
||||
|
||||
impl<'a> Admonition<'a> {
|
||||
pub(crate) fn new(info: AdmonitionMeta, content: &'a str) -> Self {
|
||||
pub(crate) fn new(info: AdmonitionMeta, content: &'a str, indent: usize) -> Self {
|
||||
let AdmonitionMeta {
|
||||
directive,
|
||||
title,
|
||||
css_id,
|
||||
additional_classnames,
|
||||
collapsible,
|
||||
} = info;
|
||||
@@ -45,78 +28,107 @@ impl<'a> Admonition<'a> {
|
||||
directive,
|
||||
title,
|
||||
content: Cow::Borrowed(content),
|
||||
css_id,
|
||||
additional_classnames,
|
||||
collapsible,
|
||||
indent,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn html_with_unique_ids(&self, id_counter: &mut HashMap<String, usize>) -> String {
|
||||
let anchor_id = unique_id_from_content(
|
||||
if !self.title.is_empty() {
|
||||
&self.title
|
||||
} else {
|
||||
ANCHOR_ID_DEFAULT
|
||||
},
|
||||
id_counter,
|
||||
);
|
||||
self.html(&anchor_id)
|
||||
}
|
||||
pub(crate) fn html(self, id_counter: &mut HashMap<String, usize>) -> String {
|
||||
let anchor_id = match &self.css_id {
|
||||
CssId::Verbatim(id) => Cow::Borrowed(id.as_str()),
|
||||
CssId::Prefix(prefix) => {
|
||||
let id = unique_id_from_content(
|
||||
if !self.title.is_empty() {
|
||||
&self.title
|
||||
} else {
|
||||
ANCHOR_ID_DEFAULT
|
||||
},
|
||||
id_counter,
|
||||
);
|
||||
|
||||
fn html(&self, anchor_id: &str) -> String {
|
||||
let mut additional_class = Cow::Borrowed(self.directive.classname());
|
||||
let title = &self.title;
|
||||
let content = &self.content;
|
||||
|
||||
let title_block = if self.collapsible { "summary" } else { "div" };
|
||||
|
||||
let title_html = if !title.is_empty() {
|
||||
Cow::Owned(format!(
|
||||
r##"<{title_block} class="admonition-title">
|
||||
|
||||
{title}
|
||||
|
||||
<a class="admonition-anchor-link" href="#{ANCHOR_ID_PREFIX}-{anchor_id}"></a>
|
||||
</{title_block}>
|
||||
"##
|
||||
))
|
||||
} else {
|
||||
Cow::Borrowed("")
|
||||
Cow::Owned(format!("{}{}", prefix, id))
|
||||
}
|
||||
};
|
||||
|
||||
if !self.additional_classnames.is_empty() {
|
||||
let mut buffer = additional_class.into_owned();
|
||||
for additional_classname in &self.additional_classnames {
|
||||
buffer.push(' ');
|
||||
buffer.push_str(additional_classname);
|
||||
}
|
||||
let title = &self.title;
|
||||
let content = &self.content;
|
||||
let indent = " ".repeat(self.indent);
|
||||
|
||||
additional_class = Cow::Owned(buffer);
|
||||
let (titlebar_html, title_id) = if !title.is_empty() {
|
||||
let titlebar_element = if self.collapsible { "summary" } else { "div" };
|
||||
let title_id = format!("{anchor_id}-title");
|
||||
let titlebar_html = Cow::Owned(format!(
|
||||
r##"{indent}<{titlebar_element} class="admonition-title">
|
||||
{indent}<div id="{title_id}">
|
||||
{indent}
|
||||
{indent}{title}
|
||||
{indent}
|
||||
{indent}</div>
|
||||
{indent}<a class="admonition-anchor-link" href="#{anchor_id}"></a>
|
||||
{indent}</{titlebar_element}>
|
||||
"##
|
||||
));
|
||||
(titlebar_html, Some(title_id))
|
||||
} else {
|
||||
(Cow::Borrowed(""), None)
|
||||
};
|
||||
|
||||
let mut classes = vec![
|
||||
"admonition".to_owned(),
|
||||
format!("admonish-{}", self.directive),
|
||||
];
|
||||
classes.extend(self.additional_classnames);
|
||||
let classes = classes.join(" ");
|
||||
|
||||
let mut attributes = vec![
|
||||
("id", anchor_id),
|
||||
("class", Cow::Owned(classes)),
|
||||
("role", Cow::Borrowed("note")),
|
||||
];
|
||||
if let Some(title_id) = title_id {
|
||||
attributes.push(("aria-labelledby", Cow::Owned(title_id)));
|
||||
}
|
||||
let attributes = join_attributes(&attributes);
|
||||
|
||||
let admonition_block = if self.collapsible { "details" } else { "div" };
|
||||
let admonition_element = if self.collapsible { "details" } else { "div" };
|
||||
// Notes on the HTML template:
|
||||
// - the additional whitespace around the content are deliberate
|
||||
// In line with the commonmark spec, this allows the inner content to be
|
||||
// rendered as markdown paragraphs.
|
||||
// - We should not indent the inner content, as it retains the indent
|
||||
// it is written with.
|
||||
format!(
|
||||
r#"
|
||||
<{admonition_block} id="{ANCHOR_ID_PREFIX}-{anchor_id}" class="admonition {additional_class}">
|
||||
{title_html}<div>
|
||||
{indent}<{admonition_element} {attributes}>
|
||||
{titlebar_html}{indent}<div>
|
||||
|
||||
{content}
|
||||
|
||||
</div>
|
||||
</{admonition_block}>"#,
|
||||
{indent}</div>
|
||||
{indent}</{admonition_element}>"#,
|
||||
)
|
||||
}
|
||||
|
||||
/// Strips all admonish syntax, leaving the plain content of the block.
|
||||
pub(crate) fn strip(&self) -> String {
|
||||
pub(crate) fn strip(self) -> String {
|
||||
// Add in newlines to preserve line numbering for test output
|
||||
// These replace the code fences we stripped out
|
||||
format!("\n{}\n", self.content)
|
||||
}
|
||||
}
|
||||
|
||||
const ANCHOR_ID_PREFIX: &str = "admonition";
|
||||
fn join_attributes(attributes: &[(impl AsRef<str>, impl AsRef<str>)]) -> String {
|
||||
let mut buffer = String::new();
|
||||
for (key, value) in attributes {
|
||||
buffer.push_str(key.as_ref());
|
||||
buffer.push_str(r#"=""#);
|
||||
buffer.push_str(value.as_ref());
|
||||
buffer.push_str(r#"" "#);
|
||||
}
|
||||
buffer.pop();
|
||||
buffer
|
||||
}
|
||||
|
||||
const ANCHOR_ID_DEFAULT: &str = "default";
|
||||
|
||||
386
src/resolve.rs
386
src/resolve.rs
@@ -1,5 +1,6 @@
|
||||
use crate::config::InstanceConfig;
|
||||
use crate::types::{AdmonitionDefaults, Directive};
|
||||
use crate::types::{BuiltinDirective, CssId, CustomDirective, CustomDirectiveMap, Overrides};
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// All information required to render an admonition.
|
||||
@@ -7,56 +8,141 @@ use std::str::FromStr;
|
||||
/// i.e. all configured options have been resolved at this point.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) struct AdmonitionMeta {
|
||||
pub directive: Directive,
|
||||
pub directive: String,
|
||||
pub title: String,
|
||||
pub css_id: CssId,
|
||||
pub additional_classnames: Vec<String>,
|
||||
pub collapsible: bool,
|
||||
}
|
||||
|
||||
/// Wrapper type to hold any value directive configuration.
|
||||
enum Directive {
|
||||
Builtin(BuiltinDirective),
|
||||
Custom(CustomDirective),
|
||||
}
|
||||
|
||||
impl fmt::Display for Directive {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Builtin(builtin) => builtin.fmt(f),
|
||||
Self::Custom(custom) => f.write_str(&custom.directive),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Directive {
|
||||
fn from_str(custom_directive_map: &CustomDirectiveMap, string: &str) -> Result<Self, ()> {
|
||||
if let Ok(builtin) = BuiltinDirective::from_str(string) {
|
||||
return Ok(Self::Builtin(builtin));
|
||||
}
|
||||
|
||||
if let Some(config) = custom_directive_map.get(string) {
|
||||
return Ok(Self::Custom(config.clone()));
|
||||
}
|
||||
|
||||
Err(())
|
||||
}
|
||||
|
||||
fn title(self, raw_directive: &str) -> String {
|
||||
match self {
|
||||
Directive::Builtin(_) => format_builtin_directive_title(raw_directive),
|
||||
Directive::Custom(custom) => custom
|
||||
.title
|
||||
.clone()
|
||||
.unwrap_or_else(|| uppercase_first(raw_directive)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AdmonitionMeta {
|
||||
pub fn from_info_string(
|
||||
info_string: &str,
|
||||
defaults: &AdmonitionDefaults,
|
||||
overrides: &Overrides,
|
||||
) -> Option<Result<Self, String>> {
|
||||
InstanceConfig::from_info_string(info_string)
|
||||
.map(|raw| raw.map(|raw| Self::resolve(raw, defaults)))
|
||||
.map(|raw| raw.map(|raw| Self::resolve(raw, overrides)))
|
||||
}
|
||||
|
||||
/// Combine the per-admonition configuration with global defaults (and
|
||||
/// other logic) to resolve the values needed for rendering.
|
||||
fn resolve(raw: InstanceConfig, defaults: &AdmonitionDefaults) -> Self {
|
||||
fn resolve(raw: InstanceConfig, overrides: &Overrides) -> Self {
|
||||
let InstanceConfig {
|
||||
directive: raw_directive,
|
||||
title,
|
||||
id,
|
||||
additional_classnames,
|
||||
collapsible,
|
||||
} = raw;
|
||||
|
||||
// Use values from block, else load default value
|
||||
let title = title.or_else(|| defaults.title.clone());
|
||||
let collapsible = collapsible.unwrap_or(defaults.collapsible);
|
||||
let title = title.or_else(|| overrides.book.title.clone());
|
||||
|
||||
let directive = Directive::from_str(&overrides.custom, &raw_directive);
|
||||
|
||||
let collapsible = match directive {
|
||||
// If the directive is a builin one, use collapsible from block, else use default
|
||||
// value of the builtin directive, else use global default value
|
||||
Ok(Directive::Builtin(directive)) => collapsible.unwrap_or(
|
||||
overrides
|
||||
.builtin
|
||||
.get(&directive)
|
||||
.and_then(|config| config.collapsible)
|
||||
.unwrap_or(overrides.book.collapsible),
|
||||
),
|
||||
// If the directive is a custom one, use collapsible from block, else use default
|
||||
// value of the custom directive, else use global default value
|
||||
Ok(Directive::Custom(ref custom_dir)) => {
|
||||
collapsible.unwrap_or(custom_dir.collapsible.unwrap_or(overrides.book.collapsible))
|
||||
}
|
||||
Err(_) => collapsible.unwrap_or(overrides.book.collapsible),
|
||||
};
|
||||
|
||||
// Load the directive (and title, if one still not given)
|
||||
let (directive, title) = match (Directive::from_str(&raw_directive), title) {
|
||||
(Ok(directive), None) => (directive, ucfirst(&raw_directive)),
|
||||
(Err(_), None) => (Directive::Note, "Note".to_owned()),
|
||||
(Ok(directive), Some(title)) => (directive, title),
|
||||
(Err(_), Some(title)) => (Directive::Note, title),
|
||||
let (directive, title) = match (directive, title) {
|
||||
(Ok(directive), None) => (directive.to_string(), directive.title(&raw_directive)),
|
||||
(Err(_), None) => (BuiltinDirective::Note.to_string(), "Note".to_owned()),
|
||||
(Ok(directive), Some(title)) => (directive.to_string(), title),
|
||||
(Err(_), Some(title)) => (BuiltinDirective::Note.to_string(), title),
|
||||
};
|
||||
|
||||
let css_id = if let Some(verbatim) = id {
|
||||
CssId::Verbatim(verbatim)
|
||||
} else {
|
||||
const DEFAULT_CSS_ID_PREFIX: &str = "admonition-";
|
||||
CssId::Prefix(
|
||||
overrides
|
||||
.book
|
||||
.css_id_prefix
|
||||
.clone()
|
||||
.unwrap_or_else(|| DEFAULT_CSS_ID_PREFIX.to_owned()),
|
||||
)
|
||||
};
|
||||
|
||||
Self {
|
||||
directive,
|
||||
title,
|
||||
css_id,
|
||||
additional_classnames,
|
||||
collapsible,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Make the first letter of `input` upppercase.
|
||||
/// Format the title of an admonition directive
|
||||
///
|
||||
/// We special case a few words to make them look nicer (e.g. "tldr" -> "TL;DR" and "faq" -> "FAQ").
|
||||
fn format_builtin_directive_title(input: &str) -> String {
|
||||
match input {
|
||||
"tldr" => "TL;DR".to_owned(),
|
||||
"faq" => "FAQ".to_owned(),
|
||||
_ => uppercase_first(input),
|
||||
}
|
||||
}
|
||||
|
||||
/// Make the first letter of `input` uppercase.
|
||||
///
|
||||
/// source: https://stackoverflow.com/a/38406885
|
||||
fn ucfirst(input: &str) -> String {
|
||||
fn uppercase_first(input: &str) -> String {
|
||||
let mut chars = input.chars();
|
||||
match chars.next() {
|
||||
None => String::new(),
|
||||
@@ -66,9 +152,25 @@ fn ucfirst(input: &str) -> String {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::types::{AdmonitionDefaults, BuiltinDirectiveConfig};
|
||||
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_format_builtin_directive_title() {
|
||||
assert_eq!(format_builtin_directive_title(""), "");
|
||||
assert_eq!(format_builtin_directive_title("a"), "A");
|
||||
assert_eq!(format_builtin_directive_title("tldr"), "TL;DR");
|
||||
assert_eq!(format_builtin_directive_title("faq"), "FAQ");
|
||||
assert_eq!(format_builtin_directive_title("note"), "Note");
|
||||
assert_eq!(format_builtin_directive_title("abstract"), "Abstract");
|
||||
// Unicode
|
||||
assert_eq!(format_builtin_directive_title("🦀"), "🦀");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_admonition_info_from_raw() {
|
||||
assert_eq!(
|
||||
@@ -76,14 +178,16 @@ mod test {
|
||||
InstanceConfig {
|
||||
directive: " ".to_owned(),
|
||||
title: None,
|
||||
id: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: None,
|
||||
},
|
||||
&Default::default()
|
||||
&Overrides::default(),
|
||||
),
|
||||
AdmonitionMeta {
|
||||
directive: Directive::Note,
|
||||
directive: "note".to_owned(),
|
||||
title: "Note".to_owned(),
|
||||
css_id: CssId::Prefix("admonition-".to_owned()),
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: false,
|
||||
}
|
||||
@@ -97,20 +201,260 @@ mod test {
|
||||
InstanceConfig {
|
||||
directive: " ".to_owned(),
|
||||
title: None,
|
||||
id: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: None,
|
||||
},
|
||||
&AdmonitionDefaults {
|
||||
title: Some("Important!!!".to_owned()),
|
||||
collapsible: true,
|
||||
},
|
||||
&Overrides {
|
||||
book: AdmonitionDefaults {
|
||||
title: Some("Important!!!".to_owned()),
|
||||
css_id_prefix: Some("custom-prefix-".to_owned()),
|
||||
collapsible: true,
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
),
|
||||
AdmonitionMeta {
|
||||
directive: Directive::Note,
|
||||
directive: "note".to_owned(),
|
||||
title: "Important!!!".to_owned(),
|
||||
css_id: CssId::Prefix("custom-prefix-".to_owned()),
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_admonition_info_from_raw_with_defaults_and_custom_id() {
|
||||
assert_eq!(
|
||||
AdmonitionMeta::resolve(
|
||||
InstanceConfig {
|
||||
directive: " ".to_owned(),
|
||||
title: None,
|
||||
id: Some("my-custom-id".to_owned()),
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: None,
|
||||
},
|
||||
&Overrides {
|
||||
book: AdmonitionDefaults {
|
||||
title: Some("Important!!!".to_owned()),
|
||||
css_id_prefix: Some("ignored-custom-prefix-".to_owned()),
|
||||
collapsible: true,
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
),
|
||||
AdmonitionMeta {
|
||||
directive: "note".to_owned(),
|
||||
title: "Important!!!".to_owned(),
|
||||
css_id: CssId::Verbatim("my-custom-id".to_owned()),
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_admonition_info_from_raw_with_custom_directive() {
|
||||
assert_eq!(
|
||||
AdmonitionMeta::resolve(
|
||||
InstanceConfig {
|
||||
directive: "frog".to_owned(),
|
||||
title: None,
|
||||
id: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: None,
|
||||
},
|
||||
&Overrides {
|
||||
custom: [CustomDirective {
|
||||
directive: "frog".to_owned(),
|
||||
aliases: Vec::new(),
|
||||
title: None,
|
||||
collapsible: None,
|
||||
}]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}
|
||||
),
|
||||
AdmonitionMeta {
|
||||
directive: "frog".to_owned(),
|
||||
title: "Frog".to_owned(),
|
||||
css_id: CssId::Prefix("admonition-".to_owned()),
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_admonition_info_from_raw_with_custom_directive_and_title() {
|
||||
assert_eq!(
|
||||
AdmonitionMeta::resolve(
|
||||
InstanceConfig {
|
||||
directive: "frog".to_owned(),
|
||||
title: None,
|
||||
id: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: None,
|
||||
},
|
||||
&Overrides {
|
||||
custom: [CustomDirective {
|
||||
directive: "frog".to_owned(),
|
||||
aliases: Vec::new(),
|
||||
title: Some("🏳️🌈".to_owned()),
|
||||
collapsible: None,
|
||||
}]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}
|
||||
),
|
||||
AdmonitionMeta {
|
||||
directive: "frog".to_owned(),
|
||||
title: "🏳️🌈".to_owned(),
|
||||
css_id: CssId::Prefix("admonition-".to_owned()),
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_admonition_info_from_raw_with_custom_directive_alias() {
|
||||
assert_eq!(
|
||||
AdmonitionMeta::resolve(
|
||||
InstanceConfig {
|
||||
directive: "toad".to_owned(),
|
||||
title: Some("Still a frog".to_owned()),
|
||||
id: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: None,
|
||||
},
|
||||
&Overrides {
|
||||
custom: [CustomDirective {
|
||||
directive: "frog".to_owned(),
|
||||
aliases: vec!["newt".to_owned(), "toad".to_owned()],
|
||||
title: Some("🏳️🌈".to_owned()),
|
||||
collapsible: None,
|
||||
}]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}
|
||||
),
|
||||
AdmonitionMeta {
|
||||
directive: "frog".to_owned(),
|
||||
title: "Still a frog".to_owned(),
|
||||
css_id: CssId::Prefix("admonition-".to_owned()),
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_admonition_info_from_raw_with_collapsible_custom_directive() {
|
||||
assert_eq!(
|
||||
AdmonitionMeta::resolve(
|
||||
InstanceConfig {
|
||||
directive: "frog".to_owned(),
|
||||
title: None,
|
||||
id: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: None,
|
||||
},
|
||||
&Overrides {
|
||||
custom: [CustomDirective {
|
||||
directive: "frog".to_owned(),
|
||||
aliases: Vec::new(),
|
||||
title: None,
|
||||
collapsible: Some(true),
|
||||
}]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}
|
||||
),
|
||||
AdmonitionMeta {
|
||||
directive: "frog".to_owned(),
|
||||
title: "Frog".to_owned(),
|
||||
css_id: CssId::Prefix("admonition-".to_owned()),
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_admonition_info_from_raw_with_collapsible_builtin_directive() {
|
||||
assert_eq!(
|
||||
AdmonitionMeta::resolve(
|
||||
InstanceConfig {
|
||||
directive: "abstract".to_owned(),
|
||||
title: None,
|
||||
id: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: None,
|
||||
},
|
||||
&Overrides {
|
||||
book: AdmonitionDefaults {
|
||||
title: None,
|
||||
css_id_prefix: None,
|
||||
collapsible: false,
|
||||
},
|
||||
builtin: HashMap::from([(
|
||||
BuiltinDirective::Abstract,
|
||||
BuiltinDirectiveConfig {
|
||||
collapsible: Some(true),
|
||||
}
|
||||
)]),
|
||||
..Default::default()
|
||||
}
|
||||
),
|
||||
AdmonitionMeta {
|
||||
directive: "abstract".to_owned(),
|
||||
title: "Abstract".to_owned(),
|
||||
css_id: CssId::Prefix("admonition-".to_owned()),
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_admonition_info_from_raw_with_non_collapsible_builtin_directive() {
|
||||
assert_eq!(
|
||||
AdmonitionMeta::resolve(
|
||||
InstanceConfig {
|
||||
directive: "abstract".to_owned(),
|
||||
title: None,
|
||||
id: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: None,
|
||||
},
|
||||
&Overrides {
|
||||
book: AdmonitionDefaults {
|
||||
title: None,
|
||||
css_id_prefix: None,
|
||||
collapsible: true,
|
||||
},
|
||||
builtin: HashMap::from([(
|
||||
BuiltinDirective::Abstract,
|
||||
BuiltinDirectiveConfig {
|
||||
collapsible: Some(false),
|
||||
}
|
||||
)]),
|
||||
..Default::default()
|
||||
}
|
||||
),
|
||||
AdmonitionMeta {
|
||||
directive: "abstract".to_owned(),
|
||||
title: "Abstract".to_owned(),
|
||||
css_id: CssId::Prefix("admonition-".to_owned()),
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
20
src/test_data/mdbook-admonish-custom-expected.css
Normal file
20
src/test_data/mdbook-admonish-custom-expected.css
Normal 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;
|
||||
}
|
||||
128
src/types.rs
128
src/types.rs
@@ -1,4 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Book wide defaults that may be provided by the user.
|
||||
@@ -9,10 +11,22 @@ pub(crate) struct AdmonitionDefaults {
|
||||
|
||||
#[serde(default)]
|
||||
pub(crate) collapsible: bool,
|
||||
|
||||
#[serde(default)]
|
||||
// For backwards compatibility, we support this field with kebab-case style
|
||||
// naming, even though this was introduced in error.
|
||||
#[serde(alias = "css-id-prefix")]
|
||||
pub(crate) css_id_prefix: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) enum Directive {
|
||||
/// First class supported directives by the crate.
|
||||
///
|
||||
/// These are guaranteed to have valid CSS/icons available.
|
||||
///
|
||||
/// Custom directives can also be added via the book.toml config.
|
||||
#[derive(Debug, PartialEq, Clone, Copy, Eq, Deserialize, Serialize, Hash)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub(crate) enum BuiltinDirective {
|
||||
Note,
|
||||
Abstract,
|
||||
Info,
|
||||
@@ -27,7 +41,7 @@ pub(crate) enum Directive {
|
||||
Quote,
|
||||
}
|
||||
|
||||
impl FromStr for Directive {
|
||||
impl FromStr for BuiltinDirective {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(string: &str) -> Result<Self, ()> {
|
||||
@@ -49,8 +63,116 @@ impl FromStr for Directive {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for BuiltinDirective {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let value = match self {
|
||||
Self::Note => "note",
|
||||
Self::Abstract => "abstract",
|
||||
Self::Info => "info",
|
||||
Self::Tip => "tip",
|
||||
Self::Success => "success",
|
||||
Self::Question => "question",
|
||||
Self::Warning => "warning",
|
||||
Self::Failure => "failure",
|
||||
Self::Danger => "danger",
|
||||
Self::Bug => "bug",
|
||||
Self::Example => "example",
|
||||
Self::Quote => "quote",
|
||||
};
|
||||
f.write_str(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// The subset of information we care about during plugin runtime for custom directives.
|
||||
///
|
||||
/// This drops information only needed during CSS generation.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct CustomDirective {
|
||||
pub directive: String,
|
||||
pub aliases: Vec<String>,
|
||||
pub title: Option<String>,
|
||||
pub collapsible: Option<bool>,
|
||||
}
|
||||
|
||||
impl From<(String, crate::book_config::CustomDirective)> for CustomDirective {
|
||||
fn from((directive, config): (String, crate::book_config::CustomDirective)) -> Self {
|
||||
let crate::book_config::CustomDirective {
|
||||
aliases,
|
||||
title,
|
||||
collapsible,
|
||||
..
|
||||
} = config;
|
||||
Self {
|
||||
directive,
|
||||
aliases,
|
||||
title,
|
||||
collapsible,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A map from the user given directive to underlying config.
|
||||
///
|
||||
/// The terminology is a bit mixed here - this map allows any input-directive,
|
||||
/// and returns the output-directive config.
|
||||
///
|
||||
/// i.e. this is the step alias mapping happens at
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(crate) struct CustomDirectiveMap {
|
||||
inner: HashMap<String, CustomDirective>,
|
||||
}
|
||||
|
||||
impl CustomDirectiveMap {
|
||||
pub fn get(&self, key: &str) -> Option<&CustomDirective> {
|
||||
self.inner.get(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<CustomDirective> for CustomDirectiveMap {
|
||||
fn from_iter<I: IntoIterator<Item = CustomDirective>>(iter: I) -> Self {
|
||||
let mut inner = HashMap::default();
|
||||
for config in iter.into_iter() {
|
||||
inner
|
||||
.entry(config.directive.clone())
|
||||
.or_insert(config.clone());
|
||||
|
||||
for alias in config.aliases.iter() {
|
||||
inner.entry(alias.clone()).or_insert(config.clone());
|
||||
}
|
||||
}
|
||||
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub(crate) struct BuiltinDirectiveConfig {
|
||||
/// Default collapsible value.
|
||||
#[serde(default)]
|
||||
pub collapsible: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum RenderTextMode {
|
||||
Strip,
|
||||
Html,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) enum CssId {
|
||||
/// id="my-id" in the admonishment
|
||||
///
|
||||
/// used directly for the id field
|
||||
Verbatim(String),
|
||||
/// the prefix from default.css_id_prefix (or "admonish-" if not specified)
|
||||
///
|
||||
/// will generate the rest of the id based on the title
|
||||
Prefix(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(crate) struct Overrides {
|
||||
pub book: AdmonitionDefaults,
|
||||
pub builtin: HashMap<BuiltinDirective, BuiltinDirectiveConfig>,
|
||||
pub custom: CustomDirectiveMap,
|
||||
}
|
||||
|
||||
13
v2.md
Normal file
13
v2.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# v2 Notes
|
||||
|
||||
## Compatibility to drop
|
||||
|
||||
- v1 config loading system from block info strings
|
||||
- v2 config loading system from block info strings
|
||||
- Support for `custom` configuration, moved to `directive.custom`
|
||||
- `css-id-prefix` kebab case
|
||||
|
||||
## Wishlist
|
||||
|
||||
- `mdbook` not to use a broken version of `toml` in the public api
|
||||
- `mdbook` to support loading css files from plugins
|
||||
Reference in New Issue
Block a user