Compare commits

..

59 Commits

Author SHA1 Message Date
Eric Huss
6bf7fadc29 Merge pull request #2943 from ehuss/config-changelog
Add some more 0.5 Config API changes
2025-11-17 23:27:24 +00:00
Eric Huss
d193775a3b Add some more 0.5 Config API changes
I forgot these in https://github.com/rust-lang/mdBook/pull/2942
2025-11-17 15:21:42 -08:00
Eric Huss
8e4bc4aecd Merge pull request #2942 from ehuss/fix-env-config
Add error handling to env config handling
2025-11-17 22:48:19 +00:00
Eric Huss
2afad43bdd Add error handling to env config handling
This adds several changes to how environment variables are handled to
more closely align with how configs are handled, and to fix an issue
with replacing entire tables. The changes are:

- Top-level tables like `MDBOOK_BOOK` now *replace* the contents of the
  `book` table instead of merging it. This adds consistency with how all
  the other environment objects work.
- Fixed allowing top-level replacement of `MDBOOK_BOOK` and
  `MDBOOK_OUTPUT`. This was inadvertently recently broken.
- Added ability to replace top-level `MDBOOK_RUST`. I don't recall why
  that wasn't included.
- Reject invalid keys like `MDBOOK_FOO`.
- Reject unknown keys, like `MDBOOK_BOOK='{"xyz": 123}'`
- Reject invalid types, like `MDBOOK_BOOK='{"title": 123}'`
2025-11-17 14:38:58 -08:00
Eric Huss
5445458d1a Add tests for various environment config issues
These currently aren't working as expected.
2025-11-17 12:47:01 -08:00
Eric Huss
ef476a7329 Merge pull request #2940 from ehuss/bump-version
Update to 0.5.0
2025-11-17 19:17:23 +00:00
Eric Huss
262afdc2f8 Update to 0.5.0
This is the stable release of 0.5.0. No changes have been made since
0.5.0-beta.2.
2025-11-17 08:57:52 -08:00
Eric Huss
fff6087f36 Merge pull request #2929 from ehuss/bump-version
Update to 0.5.0-beta.2.
2025-11-08 02:08:05 +00:00
Eric Huss
922f0d8ad4 Update to 0.5.0-beta.2.
I expect this to be the last pre-release before 0.5.
2025-11-07 18:00:52 -08:00
Eric Huss
ab1325b213 Merge pull request #2928 from ehuss/update-dependencies-no-fork
Disable update-dependencies on forks
2025-11-07 18:11:59 +00:00
Eric Huss
63b159741b Disable update-dependencies on forks
This disables the update-dependencies cron job in forks. It's not
uncommon for people to leave GitHub Actions enabled in a fork (which in
my experience seems to be the default?), and this unfortunately means
that this job will run in all those forks which is probably not what
people want.
2025-11-07 10:06:38 -08:00
Eric Huss
4a9a517f27 Merge pull request #2927 from ehuss/fix-unbalanced-html-in-header
Handle unclosed HTML tags inside a markdown element
2025-11-06 15:38:21 +00:00
Eric Huss
700839f77f Handle unclosed HTML tags inside a markdown element
This fixes an issue where it was panicking due to an unbalanced HTML tag
when exiting a markdown element. The problem was that the tag stack was
left non-empty when processing was finished due to `end_tag` being out
of sync with the pulldown-cmark event tags.

There really should be better validation that the stack is in sync and
balanced, but this should address the main culprit of the interplay of
raw HTML tags and pulldown-cmark events.
2025-11-06 07:31:45 -08:00
Eric Huss
152132458e Move end tag handling to a function
This is to reduce the size of the processing function.
2025-11-06 07:25:55 -08:00
Eric Huss
054da77b6a Add test for unbalanced html in a header
This is unexpectedly panicking.
2025-11-06 07:25:53 -08:00
Eric Huss
9a5e8dbb0a Merge pull request #2926 from ehuss/fix-fa-icons
Fix missing font-awesome icons in the guide
2025-11-06 14:53:56 +00:00
Eric Huss
63c45cd879 Fix missing font-awesome icons in the guide
After the switch in https://github.com/rust-lang/mdBook/pull/1330, some
of the icons were renamed.
2025-11-06 06:48:03 -08:00
Eric Huss
bc7ca458b6 Merge pull request #2923 from LikeLakers2/hide-resize-handle-on-noscript
Hide the sidebar resize indicator when JS isn't available
2025-11-06 03:41:26 +00:00
MichiRecRoom
07fb33f5da Hide the sidebar resize handle when scripting isn't available 2025-11-05 19:11:09 -08:00
Eric Huss
7d1566860c Merge pull request #2924 from ehuss/html-end-tags
Add better handling for unbalanced HTML tags
2025-11-06 03:02:40 +00:00
Eric Huss
f0117ec3df Add a comment about synchronizing the event stack 2025-11-05 11:45:46 -08:00
Eric Huss
22065ebc79 Give a warning for unclosed HTML tags
This changes the internal error message to a warning to let the user
know that the HTML tags are unbalanced. In the future this will be a
denyable lint.

This is a very primitive approach of just ignoring the end tag. Ideally
it should recover using the standard HTML parsing algorithm, since there
is a chance that there will be a cascade of errors under certain
unbalanced situations.
2025-11-05 11:42:43 -08:00
Eric Huss
5905bf1d85 Factor out Token::TagToken to combat rightwards drift 2025-11-05 11:33:58 -08:00
Eric Huss
1646e4923a Add a test for HTML tags out of sync 2025-11-05 11:19:42 -08:00
Eric Huss
1e190137c3 Add a check for unclosed elements on the stack
This checks for any unclosed elements when processing is finished. This
is intended to detect invalid HTML in the source, or bugs in the tree
builder. Raw HTML elements generate a warning (which in the future will
be a configurable lint). All other sync errors are internal errors as
they are not expected, and it would be helpful to know if they ever
happen.
2025-11-05 11:14:39 -08:00
Eric Huss
4417f8cb0a Add a test for unclosed HTML tags 2025-11-05 10:45:46 -08:00
Eric Huss
cc7f8be496 Merge pull request #2922 from ehuss/header-id-lowercase
Lowercase heading IDs
2025-11-05 00:14:42 +00:00
Eric Huss
051fc9f01d Add a comment about the intent of HTML id generation 2025-11-04 16:05:36 -08:00
Eric Huss
d0bde467e0 Lowercase heading IDs
This switches from ASCII lowercase to Unicode lowercase when generating
heading IDs. This brings mdbook more in line with other tools and sites
when they generate heading IDs. The generation still isn't 100% the same
as other tools and sites, but it is usually the same in most cases.

Closes https://github.com/rust-lang/mdBook/issues/1059
2025-11-04 16:05:31 -08:00
Eric Huss
475951c9ee Merge pull request #2921 from ehuss/header-id-changelog
Add note to changelog about a change in heading ID generation
2025-11-04 23:06:17 +00:00
Eric Huss
5b2cc1735b Add note to changelog about a change in heading ID generation
I didn't fully appreciate that this changed in
https://github.com/rust-lang/mdBook/pull/2844 which changed how text
content was collected from heading tags.
2025-11-04 14:56:08 -08:00
Eric Huss
1c00395230 Merge pull request #2920 from rust-lang/renovate/cargo-semver-checks-0.x
Update cargo-semver-checks to v0.45.0
2025-11-02 04:10:45 +00:00
renovate[bot]
9799326590 Update cargo-semver-checks to v0.45.0 2025-11-01 20:40:49 +00:00
Eric Huss
2546c8cc60 Merge pull request #2916 from rust-lang/update-dependencies
Update cargo dependencies
2025-11-01 04:14:37 +00:00
Eric Huss
33c9b4063e Merge pull request #2919 from rust-lang/renovate/node-24.x
Update dependency node to v24
2025-11-01 04:14:10 +00:00
Eric Huss
f66fd92f32 Merge pull request #2918 from rust-lang/renovate/actions-setup-node-6.x
Update actions/setup-node action to v6
2025-11-01 04:13:51 +00:00
Eric Huss
541e16335b Merge pull request #2917 from rust-lang/renovate/browser-ui-test-0.x
Update dependency browser-ui-test to v0.22.3
2025-11-01 04:13:28 +00:00
renovate[bot]
6cc40cb5f7 Update dependency node to v24 2025-11-01 00:33:10 +00:00
renovate[bot]
37f8a79d4d Update actions/setup-node action to v6 2025-11-01 00:33:06 +00:00
renovate[bot]
385246a9ef Update dependency browser-ui-test to v0.22.3 2025-11-01 00:33:02 +00:00
github-actions[bot]
7619f9a91c Update cargo dependencies
```
name          old req compatible latest new req
====          ======= ========== ====== =======
clap          4.5.50  4.5.51     4.5.51 4.5.51
clap_complete 4.5.59  4.5.60     4.5.60 4.5.60
ignore        0.4.24  0.4.25     0.4.25 0.4.25
snapbox       0.6.22  0.6.23     0.6.23 0.6.23
```
2025-11-01 00:32:23 +00:00
Eric Huss
f27c3aea4c Merge pull request #2915 from ehuss/fontawesome-warning
Add a warning when a Font Awesome font is missing
2025-10-31 02:38:29 +00:00
Eric Huss
b3bd103742 Add a warning when a Font Awesome font is missing
With the migration to Font Awesome 6, I'm running into books where the
icon names are missing or have changed. This adds a warning to help
identify those situations.
2025-10-30 19:30:01 -07:00
Eric Huss
7e5fa3565b Add a fontawesome test for a missing icon
This is a test for when an icon is missing.
2025-10-30 19:27:40 -07:00
Eric Huss
6d9f49cbc5 Update fontawesome test to make it clear the intent
This example was intended to demonstrate that it only translates if
there are no child elements.
2025-10-30 19:24:49 -07:00
Eric Huss
8670bcc540 Merge pull request #2914 from ehuss/fix-print-relative
Fix print page links for internal links to non-chapters
2025-10-31 01:44:04 +00:00
Eric Huss
005f4d648a Fix print page links for internal links to non-chapters
This fixes links on the print page that go to an internal destination
that is not a chapter. The path would have the wrong relative
destination, and would be broken. The logic for detecting this was
incorrectly only checking if a link went outside the book, or didn't
have an html extension. This doesn't work for links to HTML files that
are inside the book, but not one of the chapters.
2025-10-30 18:37:12 -07:00
Eric Huss
59343b525d Add test for print relative for page that doesn't exist
An example where this can happen is a link to a page that was renamed
and redirected, or just a normal HTML file.
2025-10-30 18:04:51 -07:00
Eric Huss
e8d7dd6f57 Merge pull request #2913 from ehuss/html-definition-lists
Don't modify headers or dt if the tag is manually written HTML
2025-10-30 19:10:36 +00:00
Eric Huss
54175698d5 Don't modify headers or dt if the tag is manually written HTML
This changes it so that header and `<dt>` tags manually written as HTML
are not modified (no anchor, no id, etc.). This is to avoid mangling any
HTML that the user explicitly crafted.

I'm not sure what the fallout from the headers might be, since I'm not
100% sure there aren't uses where the user wanted mdbook to modify
manual HTML. However, I don't see any in rust-lang's use.
2025-10-30 11:47:43 -07:00
Eric Huss
07ed00e8f7 Add a test for manually written header tags 2025-10-30 11:40:46 -07:00
Eric Huss
ab8a4dfa5a Add a test for definition lists with manual HTML tags 2025-10-30 10:02:27 -07:00
Eric Huss
e2c954f693 Merge pull request #2911 from ehuss/trace-events
Add some trace logging for event processing
2025-10-30 03:42:12 +00:00
Eric Huss
b2111a3f91 Merge pull request #2910 from ehuss/contains-key
Add Config::contains_key
2025-10-30 03:35:48 +00:00
Eric Huss
8ba833feb2 Add some trace logging for event processing
This adds some trace logging to help debug markdown parsing and HTML
parsing.
2025-10-29 20:34:11 -07:00
Eric Huss
7124f4c7de Merge pull request #2909 from rust-lang/ehuss-patch-2
Fix typo in changelog
2025-10-30 03:30:04 +00:00
Eric Huss
1cc4cbb202 Add Config::contains_key
This adds the method `contains_key` to assist with detecting if a key is
set in the config. There have been a few scenarios where I have needed
this when upgrading to 0.5. For now this only supports the `output` and
`preprocessor`. Checking the presence in the other tables isn't easy,
but could potentially be added if needed.
2025-10-29 20:29:46 -07:00
Eric Huss
405f407260 Fix typo in changelog 2025-10-29 20:23:31 -07:00
Eric Huss
eaa778bebd Merge pull request #2908 from rust-lang/ehuss-patch-1
mdbook-compare: fix duplicate "diff" print
2025-10-30 02:51:01 +00:00
36 changed files with 708 additions and 211 deletions

View File

@@ -78,9 +78,9 @@ jobs:
- name: Install Rust
run: bash ci/install-rust.sh stable x86_64-unknown-linux-gnu
- name: Install npm
uses: actions/setup-node@v5
uses: actions/setup-node@v6
with:
node-version: 22
node-version: 24
- name: Install browser-ui-test
run: npm install
- name: Run eslint
@@ -119,7 +119,7 @@ jobs:
- name: Install cargo-semver-checks
run: |
mkdir installed-bins
curl -Lf https://github.com/obi1kenobi/cargo-semver-checks/releases/download/v0.44.0/cargo-semver-checks-x86_64-unknown-linux-gnu.tar.gz \
curl -Lf https://github.com/obi1kenobi/cargo-semver-checks/releases/download/v0.45.0/cargo-semver-checks-x86_64-unknown-linux-gnu.tar.gz \
| tar -xz --directory=./installed-bins
echo `pwd`/installed-bins >> $GITHUB_PATH
- run: cargo semver-checks --workspace

View File

@@ -8,6 +8,7 @@ jobs:
update:
name: Update dependencies
runs-on: ubuntu-latest
if: github.repository == 'rust-lang/mdBook'
steps:
- uses: actions/checkout@v5
- name: Install Rust

View File

@@ -1,15 +1,40 @@
# Changelog
## 0.5 Migration Guide
## mdBook 0.5.0
[v0.4.52...v0.5.0](https://github.com/rust-lang/mdBook/compare/v0.4.52...v0.5.0)
During the pre-release phase of the 0.5 release, the documentation may be found at <https://rust-lang.github.io/mdBook/pre-release/>.
The 0.5.0 release is the next major release of mdBook, containing over 130 PRs since 0.4.52! The primary focus for this release has been an evolution of the Rust APIs to make it easier to maintain, to evolve in a backwards-compatible fashion, to clean up some things that have accumulated over time, and to significantly improve the performance and compile-times.
This release also includes many new features described below.
We have prepared a [0.5 Migration Guide](#05-migration-guide) to help existing authors switch from 0.4.
The final 0.5.0 release only contains the following changes since [0.5.0-beta.2](#mdbook-050-beta2):
- Added error handling to environment config handling. This checks that environment variables starting with `MDBOOK_` are correctly specified instead of silently ignoring. This also fixed being able to replace entire top-level tables like `MDBOOK_OUTPUT`.
[#2942](https://github.com/rust-lang/mdBook/pull/2942)
## 0.5 Migration Guide
The 0.5 release contains several breaking changes from the 0.4 release. Preprocessors and renderers will need to be migrated to continue to work with this release. After updating your configuration, it is recommended to carefully compare and review how your book renders to ensure everything is working correctly.
If you have overridden any of the theme files, you will likely need to update them to match the current version.
See the entries below for [mdBook 0.5.0-alpha.1](#mdbook-050-alpha1), [mdBook 0.5.0-beta.1](#mdbook-050-beta1), and [mdBook 0.5.0-beta.2](#mdbook-050-beta2) for a more complete list of changes and fixes.
The following is a summary of the changes that may require your attention when updating to 0.5:
### Major additions
- Added sidebar heading navigation. This includes the `output.html.sidebar-header-nav` option to disable it.
[#2822](https://github.com/rust-lang/mdBook/pull/2822)
- Added support for definition lists. These are enabled by default, with the option `output.html.definition-lists` to disable it. See [docs](https://rust-lang.github.io/mdBook/format/markdown.html#definition-lists) for more.
[#2847](https://github.com/rust-lang/mdBook/pull/2847)
- Added support for admonitions. These are enabled by default, with the option `output.html.admonitions` to disable it. See [docs](https://rust-lang.github.io/mdBook/format/markdown.html#admonitions) for more.
[#2851](https://github.com/rust-lang/mdBook/pull/2851)
- Links on the print page now link to elements on the print page instead of linking out to the individual chapters.
[#2844](https://github.com/rust-lang/mdBook/pull/2844)
### Config changes
- Unknown fields in config are now an error.
@@ -34,6 +59,10 @@ The following is a summary of the changes that may require your attention when u
[#2775](https://github.com/rust-lang/mdBook/pull/2775)
- Removed the very old legacy config support. Warnings have been displayed in previous versions on how to migrate.
[#2783](https://github.com/rust-lang/mdBook/pull/2783)
- Top-level config values set from the environment like `MDBOOK_BOOK` now *replace* the contents of the top-level table instead of merging into it.
[#2942](https://github.com/rust-lang/mdBook/pull/2942)
- Invalid environment variables are now rejected. Previously unknown keys like `MDBOOK_FOO` would be ignored, or keys or invalid values inside objects like the `[book]` table would be ignored.
[#2942](https://github.com/rust-lang/mdBook/pull/2942)
### Theme changes
@@ -58,6 +87,13 @@ The following is a summary of the changes that may require your attention when u
[#2847](https://github.com/rust-lang/mdBook/pull/2847)
- Added support for admonitions. These are enabled by default, with the option `output.html.admonitions` to disable it.
[#2851](https://github.com/rust-lang/mdBook/pull/2851)
- Header ID generation has some minor changes to bring the ID generation closer to other tools and sites:
- IDs now use Unicode lowercase instead of ASCII lowercase.
[#2922](https://github.com/rust-lang/mdBook/pull/2922)
- Headers that start or end with HTML characters like `<`, `&`, or `>` now replace those characters in the link ID with `-` instead of being stripped.
[#2844](https://github.com/rust-lang/mdBook/pull/2844)
- Headers are no longer modified if the tag is manually written HTML.
[#2913](https://github.com/rust-lang/mdBook/pull/2913)
### CLI changes
@@ -76,9 +112,13 @@ The following is a summary of the changes that may require your attention when u
- [`mdbook-markdown`](https://docs.rs/mdbook-markdown/latest/mdbook_markdown/) — The Markdown renderer. If you are processing markdown, this is the crate you should depend on. This is essentially a thin wrapper around `pulldown-cmark`, and re-exports that crate so that you can ensure the version stays in sync with mdBook.
- [`mdbook-summary`](https://docs.rs/mdbook-summary/latest/mdbook_summary/) — The `SUMMARY.md` parser.
- [`mdbook-html`](https://docs.rs/mdbook-html/latest/mdbook_html/) — The HTML renderer.
- [`mdbook-core`](https://docs.rs/mdbook-core/latest/mdbook_core/) — An internal library that is used by the other crates for shared types. You should not depend on this crate directly since types from this crate are rexported from the other crates as appropriate.
- [`mdbook-core`](https://docs.rs/mdbook-core/latest/mdbook_core/) — An internal library that is used by the other crates for shared types. You should not depend on this crate directly since types from this crate are re-exported from the other crates as appropriate.
- Changes to `Config`:
- [`Config::get`](https://docs.rs/mdbook-core/latest/mdbook_core/config/struct.Config.html#method.get) is now generic over the return value, using `serde` to deserialize the value. It also returns a `Result` to handle deserialization errors. [#2773](https://github.com/rust-lang/mdBook/pull/2773)
- [`Config::set`](https://docs.rs/mdbook-core/latest/mdbook_core/config/struct.Config.html#method.set) now validates that the config keys and values are valid.
[#2942](https://github.com/rust-lang/mdBook/pull/2942)
- [`Config::update_from_env`](https://docs.rs/mdbook-core/latest/mdbook_core/config/struct.Config.html#method.update_from_env) now returns a `Result` to indicate any errors.
[#2942](https://github.com/rust-lang/mdBook/pull/2942)
- Removed `Config::get_deserialized`. Use `Config::get` instead.
- Removed `Config::get_deserialized_opt`. Use `Config::get` instead.
- Removed `Config::get_mut`. Use `Config::set` instead.
@@ -102,6 +142,46 @@ The following is a summary of the changes that may require your attention when u
- Various functions in the `utils::fs` module have been removed, renamed, or reworked.
- Most of the functions in the `utils` module have been moved, removed, or made private.
## mdBook 0.5.0-beta.2
[v0.5.0-beta.1...v0.5.0-beta.2](https://github.com/rust-lang/mdBook/compare/v0.5.0-beta.1...v0.5.0-beta.2)
### Added
- Added a warning when a Font Awesome icon is missing.
[#2915](https://github.com/rust-lang/mdBook/pull/2915)
- Added some trace logging for event processing.
[#2911](https://github.com/rust-lang/mdBook/pull/2911)
- Added `Config::contains_key`.
[#2910](https://github.com/rust-lang/mdBook/pull/2910)
### Changed
- Heading IDs are now lowercase.
[#2922](https://github.com/rust-lang/mdBook/pull/2922)
- Updated cargo dependencies.
[#2916](https://github.com/rust-lang/mdBook/pull/2916)
- Removed italics for in quotes/comments in code blocks with the `ayu` theme.
[#2904](https://github.com/rust-lang/mdBook/pull/2904)
- Exposed "search" feature from mdbook-driver.
[#2907](https://github.com/rust-lang/mdBook/pull/2907)
### Fixed
- Fixed rust fenced code blocks with an indent.
[#2905](https://github.com/rust-lang/mdBook/pull/2905)
- Headers and `dt` tags are no longer modified if the tag is manually written HTML.
[#2913](https://github.com/rust-lang/mdBook/pull/2913)
- Fixed print page links for internal links to non-chapters.
[#2914](https://github.com/rust-lang/mdBook/pull/2914)
- Better handling for unbalanced HTML tags.
[#2924](https://github.com/rust-lang/mdBook/pull/2924)
- Handle unclosed HTML tags inside a markdown element.
[#2927](https://github.com/rust-lang/mdBook/pull/2927)
- Fixed missing font-awesome icons in the guide.
[#2926](https://github.com/rust-lang/mdBook/pull/2926)
- Hide the sidebar resize indicator when JS isn't available.
[#2923](https://github.com/rust-lang/mdBook/pull/2923)
## mdBook 0.5.0-beta.1
[v0.5.0-alpha.1...v0.5.0-beta.1](https://github.com/rust-lang/mdBook/compare/v0.5.0-alpha.1...v0.5.0-beta.1)

96
Cargo.lock generated
View File

@@ -4,9 +4,9 @@ version = 4
[[package]]
name = "aho-corasick"
version = "1.1.3"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
@@ -194,9 +194,9 @@ dependencies = [
[[package]]
name = "bstr"
version = "1.12.0"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
dependencies = [
"memchr",
"regex-automata",
@@ -217,18 +217,18 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "clap"
version = "4.5.50"
version = "4.5.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623"
checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.50"
version = "4.5.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0"
checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a"
dependencies = [
"anstream",
"anstyle",
@@ -239,9 +239,9 @@ dependencies = [
[[package]]
name = "clap_complete"
version = "4.5.59"
version = "4.5.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2348487adcd4631696ced64ccdb40d38ac4d31cae7f2eec8817fcea1b9d1c43c"
checksum = "8e602857739c5a4291dfa33b5a298aeac9006185229a700e5810a3ef7272d971"
dependencies = [
"clap",
]
@@ -332,7 +332,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.107",
"syn 2.0.108",
]
[[package]]
@@ -343,7 +343,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
dependencies = [
"darling_core",
"quote",
"syn 2.0.107",
"syn 2.0.108",
]
[[package]]
@@ -370,7 +370,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.107",
"syn 2.0.108",
]
[[package]]
@@ -380,7 +380,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
dependencies = [
"derive_builder_core",
"syn 2.0.107",
"syn 2.0.108",
]
[[package]]
@@ -514,7 +514,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.107",
"syn 2.0.108",
]
[[package]]
@@ -585,9 +585,9 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "globset"
version = "0.4.17"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eab69130804d941f8075cfd713bf8848a2c3b3f201a9457a11e6f87e1ab62305"
checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3"
dependencies = [
"aho-corasick",
"bstr",
@@ -765,9 +765,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "ignore"
version = "0.4.24"
version = "0.4.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81776e6f9464432afcc28d03e52eb101c93b6f0566f52aef2427663e700f0403"
checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a"
dependencies = [
"crossbeam-deque",
"globset",
@@ -811,9 +811,9 @@ dependencies = [
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "itoa"
@@ -936,7 +936,7 @@ checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.107",
"syn 2.0.108",
]
[[package]]
@@ -956,7 +956,7 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]]
name = "mdbook"
version = "0.5.0-beta.1"
version = "0.5.0"
dependencies = [
"anyhow",
"axum",
@@ -996,7 +996,7 @@ version = "0.0.0"
[[package]]
name = "mdbook-core"
version = "0.5.0-beta.1"
version = "0.5.0"
dependencies = [
"anyhow",
"regex",
@@ -1009,7 +1009,7 @@ dependencies = [
[[package]]
name = "mdbook-driver"
version = "0.5.0-beta.1"
version = "0.5.0"
dependencies = [
"anyhow",
"indexmap",
@@ -1031,7 +1031,7 @@ dependencies = [
[[package]]
name = "mdbook-html"
version = "0.5.0-beta.1"
version = "0.5.0"
dependencies = [
"anyhow",
"ego-tree",
@@ -1056,7 +1056,7 @@ dependencies = [
[[package]]
name = "mdbook-markdown"
version = "0.5.0-beta.1"
version = "0.5.0"
dependencies = [
"pulldown-cmark",
"regex",
@@ -1065,7 +1065,7 @@ dependencies = [
[[package]]
name = "mdbook-preprocessor"
version = "0.5.0-beta.1"
version = "0.5.0"
dependencies = [
"anyhow",
"mdbook-core",
@@ -1085,7 +1085,7 @@ dependencies = [
[[package]]
name = "mdbook-renderer"
version = "0.5.0-beta.1"
version = "0.5.0"
dependencies = [
"anyhow",
"mdbook-core",
@@ -1095,7 +1095,7 @@ dependencies = [
[[package]]
name = "mdbook-summary"
version = "0.5.0-beta.1"
version = "0.5.0"
dependencies = [
"anyhow",
"mdbook-core",
@@ -1228,9 +1228,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "once_cell_polyfill"
version = "1.70.1"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "opener"
@@ -1308,7 +1308,7 @@ dependencies = [
"pest_meta",
"proc-macro2",
"quote",
"syn 2.0.107",
"syn 2.0.108",
]
[[package]]
@@ -1426,9 +1426,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "proc-macro2"
version = "1.0.101"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
dependencies = [
"unicode-ident",
]
@@ -1650,7 +1650,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.107",
"syn 2.0.108",
]
[[package]]
@@ -1767,9 +1767,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "snapbox"
version = "0.6.22"
version = "0.6.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "805d09a74586d9b17061e5be6ee5f8cc37e5982c349948114ffc5f68093fe5ec"
checksum = "96fa1ce81be900d083b30ec2d481e6658c2acfaa2cfc7be45ccc2cc1b820edb3"
dependencies = [
"anstream",
"anstyle",
@@ -1789,9 +1789,9 @@ dependencies = [
[[package]]
name = "snapbox-macros"
version = "0.3.10"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16569f53ca23a41bb6f62e0a5084aa1661f4814a67fa33696a79073e03a664af"
checksum = "3b750c344002d7cc69afb9da00ebd9b5c0f8ac2eb7d115d9d45d5b5f47718d74"
dependencies = [
"anstream",
]
@@ -1850,9 +1850,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.107"
version = "2.0.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b"
checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917"
dependencies = [
"proc-macro2",
"quote",
@@ -1916,7 +1916,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.107",
"syn 2.0.108",
]
[[package]]
@@ -1951,7 +1951,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.107",
"syn 2.0.108",
]
[[package]]
@@ -2098,7 +2098,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.107",
"syn 2.0.108",
]
[[package]]
@@ -2177,9 +2177,9 @@ checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
[[package]]
name = "unicode-ident"
version = "1.0.19"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "unicode-width"
@@ -2396,5 +2396,5 @@ checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.107",
"syn 2.0.108",
]

View File

@@ -27,8 +27,8 @@ rust-version = "1.88.0" # Keep in sync with installation.md and .github/workflow
[workspace.dependencies]
anyhow = "1.0.100"
axum = "0.8.6"
clap = { version = "4.5.50", features = ["cargo", "wrap_help"] }
clap_complete = "4.5.59"
clap = { version = "4.5.51", features = ["cargo", "wrap_help"] }
clap_complete = "4.5.60"
ego-tree = "0.10.0"
elasticlunr-rs = "3.0.2"
font-awesome-as-a-crate = "0.3.0"
@@ -38,14 +38,14 @@ handlebars = "6.3.2"
hex = "0.4.3"
html5ever = "0.35.0"
indexmap = "2.12.0"
ignore = "0.4.24"
mdbook-core = { path = "crates/mdbook-core", version = "0.5.0-beta.1" }
mdbook-driver = { path = "crates/mdbook-driver", version = "0.5.0-beta.1" }
mdbook-html = { path = "crates/mdbook-html", version = "0.5.0-beta.1" }
mdbook-markdown = { path = "crates/mdbook-markdown", version = "0.5.0-beta.1" }
mdbook-preprocessor = { path = "crates/mdbook-preprocessor", version = "0.5.0-beta.1" }
mdbook-renderer = { path = "crates/mdbook-renderer", version = "0.5.0-beta.1" }
mdbook-summary = { path = "crates/mdbook-summary", version = "0.5.0-beta.1" }
ignore = "0.4.25"
mdbook-core = { path = "crates/mdbook-core", version = "0.5.0" }
mdbook-driver = { path = "crates/mdbook-driver", version = "0.5.0" }
mdbook-html = { path = "crates/mdbook-html", version = "0.5.0" }
mdbook-markdown = { path = "crates/mdbook-markdown", version = "0.5.0" }
mdbook-preprocessor = { path = "crates/mdbook-preprocessor", version = "0.5.0" }
mdbook-renderer = { path = "crates/mdbook-renderer", version = "0.5.0" }
mdbook-summary = { path = "crates/mdbook-summary", version = "0.5.0" }
memchr = "2.7.6"
notify = "8.2.0"
notify-debouncer-mini = "0.7.0"
@@ -59,7 +59,7 @@ serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"
sha2 = "0.10.9"
shlex = "1.3.0"
snapbox = "0.6.22"
snapbox = "0.6.23"
tempfile = "3.23.0"
tokio = "1.48.0"
toml = "0.9.8"
@@ -71,7 +71,7 @@ walkdir = "2.5.0"
[package]
name = "mdbook"
version = "0.5.0-beta.1"
version = "0.5.0"
authors = [
"Mathieu David <mathieudavid@mathieudavid.org>",
"Michael-F-Bryan <michaelfbryan@gmail.com>",

View File

@@ -1,6 +1,6 @@
[package]
name = "mdbook-core"
version = "0.5.0-beta.1"
version = "0.5.0"
description = "The base support library for mdbook, intended for internal use only"
edition.workspace = true
license.workspace = true

View File

@@ -123,11 +123,10 @@ impl Config {
///
/// For example:
///
/// - `MDBOOK_foo` -> `foo`
/// - `MDBOOK_FOO` -> `foo`
/// - `MDBOOK_FOO__BAR` -> `foo.bar`
/// - `MDBOOK_FOO_BAR` -> `foo-bar`
/// - `MDBOOK_FOO_bar__baz` -> `foo-bar.baz`
/// - `MDBOOK_book` -> `book`
/// - `MDBOOK_BOOK` -> `book`
/// - `MDBOOK_BOOK__TITLE` -> `book.title`
/// - `MDBOOK_BOOK__TEXT_DIRECTION` -> `book.text-direction`
///
/// So by setting the `MDBOOK_BOOK__TITLE` environment variable you can
/// override the book's title without needing to touch your `book.toml`.
@@ -147,7 +146,7 @@ impl Config {
/// The latter case may be useful in situations where `mdbook` is invoked
/// from a script or CI, where it sometimes isn't possible to update the
/// `book.toml` before building.
pub fn update_from_env(&mut self) {
pub fn update_from_env(&mut self) -> Result<()> {
debug!("Updating the config from environment variables");
let overrides =
@@ -162,19 +161,9 @@ impl Config {
let parsed_value = serde_json::from_str(&value)
.unwrap_or_else(|_| serde_json::Value::String(value.to_string()));
if key == "book" || key == "build" {
if let serde_json::Value::Object(ref map) = parsed_value {
// To `set` each `key`, we wrap them as `prefix.key`
for (k, v) in map {
let full_key = format!("{key}.{k}");
self.set(&full_key, v).expect("unreachable");
}
return;
}
}
self.set(key, parsed_value).expect("unreachable");
self.set(key, parsed_value)?;
}
Ok(())
}
/// Get a value from the configuration.
@@ -209,6 +198,23 @@ impl Config {
.transpose()
}
/// Returns whether the config contains the given dotted key name.
///
/// The key can have dotted indices to access nested items (e.g.
/// `preprocessor.foo.bar` will check if that key is set in the config).
///
/// This can only access the `output` and `preprocessor` tables.
pub fn contains_key(&self, name: &str) -> bool {
if let Some(key) = name.strip_prefix("output.") {
self.output.read(key)
} else if let Some(key) = name.strip_prefix("preprocessor.") {
self.preprocessor.read(key)
} else {
panic!("invalid key `{name}`");
}
.is_some()
}
/// Returns the configuration for all preprocessors.
pub fn preprocessors<'de, T: Deserialize<'de>>(&self) -> Result<BTreeMap<String, T>> {
self.preprocessor
@@ -249,24 +255,39 @@ impl Config {
/// `output.html.playground` will set the "playground" in the html output
/// table).
///
/// The only way this can fail is if we can't serialize `value` into a
/// `toml::Value`.
/// # Errors
///
/// This will fail if:
///
/// - The value cannot be represented as TOML.
/// - The value is not a correct type.
/// - The key is an unknown configuration option.
pub fn set<S: Serialize, I: AsRef<str>>(&mut self, index: I, value: S) -> Result<()> {
let index = index.as_ref();
let value = Value::try_from(value)
.with_context(|| "Unable to represent the item as a JSON Value")?;
if let Some(key) = index.strip_prefix("book.") {
self.book.update_value(key, value);
if index == "book" {
self.book = value.try_into()?;
} else if index == "build" {
self.build = value.try_into()?;
} else if index == "rust" {
self.rust = value.try_into()?;
} else if index == "output" {
self.output = value;
} else if index == "preprocessor" {
self.preprocessor = value;
} else if let Some(key) = index.strip_prefix("book.") {
self.book.update_value(key, value)?;
} else if let Some(key) = index.strip_prefix("build.") {
self.build.update_value(key, value);
self.build.update_value(key, value)?;
} else if let Some(key) = index.strip_prefix("rust.") {
self.rust.update_value(key, value);
self.rust.update_value(key, value)?;
} else if let Some(key) = index.strip_prefix("output.") {
self.output.update_value(key, value);
self.output.update_value(key, value)?;
} else if let Some(key) = index.strip_prefix("preprocessor.") {
self.preprocessor.update_value(key, value);
self.preprocessor.update_value(key, value)?;
} else {
bail!("invalid key `{index}`");
}
@@ -686,18 +707,13 @@ pub struct SearchChapterSettings {
/// This is definitely not the most performant way to do things, which means you
/// should probably keep it away from tight loops...
trait Updateable<'de>: Serialize + Deserialize<'de> {
fn update_value<S: Serialize>(&mut self, key: &str, value: S) {
fn update_value<S: Serialize>(&mut self, key: &str, value: S) -> Result<()> {
let mut raw = Value::try_from(&self).expect("unreachable");
if let Ok(value) = Value::try_from(value) {
raw.insert(key, value);
} else {
return;
}
if let Ok(updated) = raw.try_into() {
*self = updated;
}
let value = Value::try_from(value)?;
raw.insert(key, value);
let updated = raw.try_into()?;
*self = updated;
Ok(())
}
}
@@ -1161,4 +1177,24 @@ mod tests {
"Failed to deserialize `preprocessor.foo.x`"
);
}
#[test]
fn contains_key() {
let src = r#"
[preprocessor.foo]
x = 123
[output.foo.sub]
y = 'x'
"#;
let cfg = Config::from_str(src).unwrap();
assert!(cfg.contains_key("preprocessor.foo"));
assert!(cfg.contains_key("preprocessor.foo.x"));
assert!(!cfg.contains_key("preprocessor.bar"));
assert!(!cfg.contains_key("preprocessor.foo.y"));
assert!(cfg.contains_key("output.foo"));
assert!(cfg.contains_key("output.foo.sub"));
assert!(cfg.contains_key("output.foo.sub.y"));
assert!(!cfg.contains_key("output.bar"));
assert!(!cfg.contains_key("output.foo.sub.z"));
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "mdbook-driver"
version = "0.5.0-beta.1"
version = "0.5.0"
description = "High-level library for running mdBook"
edition.workspace = true
license.workspace = true

View File

@@ -56,7 +56,7 @@ impl MDBook {
Config::default()
};
config.update_from_env();
config.update_from_env()?;
if tracing::enabled!(tracing::Level::TRACE) {
for line in format!("Config: {config:#?}").lines() {

View File

@@ -1,6 +1,6 @@
[package]
name = "mdbook-html"
version = "0.5.0-beta.1"
version = "0.5.0"
description = "mdBook HTML renderer"
edition.workspace = true
license.workspace = true

View File

@@ -534,6 +534,11 @@ html:not(.sidebar-resizing) .sidebar {
cursor: col-resize;
width: calc(var(--sidebar-resize-indicator-width) - var(--sidebar-resize-indicator-space));
}
html:not(.js) .sidebar-resize-handle {
display: none;
}
/* sidebar-hidden */
#mdbook-sidebar-toggle-anchor:not(:checked) ~ .sidebar {
transform: translateX(calc(0px - var(--sidebar-width) - var(--sidebar-resize-indicator-width)));

View File

@@ -9,7 +9,7 @@ use crate::html::{ChapterTree, Element, serialize};
use crate::utils::{ToUrlPath, id_from_content, normalize_path, unique_id};
use mdbook_core::static_regex;
use std::collections::{HashMap, HashSet};
use std::path::{Component, PathBuf};
use std::path::PathBuf;
/// Takes all the chapter trees, modifies them to be suitable to render for
/// the print page, and returns an string of all the chapters rendered to a
@@ -166,13 +166,9 @@ fn rewrite_links(
{
lookup_key.pop();
lookup_key.push(href_path);
let normalized = normalize_path(&lookup_key);
// If this points outside of the book, don't modify it.
let is_outside = matches!(
normalized.components().next(),
Some(Component::ParentDir | Component::RootDir)
);
if is_outside || !href_path.ends_with(".html") {
lookup_key = normalize_path(&lookup_key);
let is_a_chapter = path_to_root_id.contains_key(&lookup_key);
if !is_a_chapter {
// Make the link relative to the print page location.
let mut rel_path = normalize_path(&base.join(href_path)).to_url_path();
if let Some(anchor) = caps.name("anchor") {
@@ -184,10 +180,7 @@ fn rewrite_links(
}
}
let lookup_key = normalize_path(&lookup_key);
let anchor = caps.name("anchor");
let id = match anchor {
let id = match caps.name("anchor") {
Some(anchor_id) => {
let anchor_id = anchor_id.as_str().to_string();
match id_remap.get(&lookup_key) {
@@ -204,7 +197,15 @@ fn rewrite_links(
}
None => match path_to_root_id.get(&lookup_key) {
Some(id) => id.to_string(),
None => continue,
None => {
// This should be guaranteed that either the
// chapter itself is in the map (for anchor-only
// links), or the is_a_chapter check above.
panic!(
"internal error: expected `{lookup_key:?}` to be in \
root map (chapter path is `{html_path:?}`)"
);
}
},
};
el.insert_attr(attr, format!("#{id}").into());

View File

@@ -19,7 +19,7 @@ use pulldown_cmark::{Alignment, CodeBlockKind, CowStr, Event, LinkType, Tag, Tag
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use std::ops::Deref;
use tracing::{error, warn};
use tracing::{trace, warn};
/// Helper to create a [`QualName`].
macro_rules! attr_qual_name {
@@ -77,6 +77,8 @@ pub(crate) struct Element {
pub(crate) attrs: Attributes,
/// True if this tag ends with `/>`.
pub(crate) self_closing: bool,
/// True if this was raw HTML written in the markdown.
pub(crate) was_raw: bool,
}
impl Element {
@@ -87,6 +89,7 @@ impl Element {
name,
attrs: Attributes::new(),
self_closing: false,
was_raw: false,
}
}
@@ -300,25 +303,10 @@ where
/// The main processing loop. Processes all events until the end.
fn process_events(&mut self) {
while let Some(event) = self.events.next() {
trace!("event={event:?}");
match event {
Event::Start(tag) => self.start_tag(tag),
Event::End(tag) => {
self.pop();
match tag {
TagEnd::TableHead => {
self.table_state = TableState::Body;
self.push(Node::Element(Element::new("tbody")));
}
TagEnd::TableCell => {
self.table_cell_index += 1;
}
TagEnd::Table => {
// Pop tbody or thead
self.pop();
}
_ => {}
}
}
Event::End(tag) => self.end_tag(tag),
Event::Text(text) => {
self.append_text(text.into_tendril());
}
@@ -374,6 +362,7 @@ where
}
}
}
self.finish_stack();
self.collect_footnote_defs();
}
@@ -593,47 +582,59 @@ where
self.push(Node::Element(element));
}
fn end_tag(&mut self, tag: TagEnd) {
// TODO: This should validate that the event stack is properly
// synchronized with the tag stack. That, would likely require keeping
// a parallel "expected end tag" with the tag stack, since mapping a
// pulldown-cmark event tag to an HTML tag isn't always clear.
//
// Check for unclosed HTML tags when exiting a markdown event.
while let Some(node_id) = self.tag_stack.last() {
let node = self.tree.get(*node_id).unwrap().value();
let Node::Element(el) = node else {
break;
};
if !el.was_raw {
break;
}
warn!(
"unclosed HTML tag `<{}>` found in `{}` while exiting {tag:?}\n\
HTML tags must be closed before exiting a markdown element.",
el.name.local,
self.options.path.display(),
);
self.pop();
}
self.pop();
match tag {
TagEnd::TableHead => {
self.table_state = TableState::Body;
self.push(Node::Element(Element::new("tbody")));
}
TagEnd::TableCell => {
self.table_cell_index += 1;
}
TagEnd::Table => {
// Pop tbody or thead
self.pop();
}
_ => {}
}
}
/// Given some HTML, parse it into [`Node`] elements and append them to
/// the current node.
fn append_html(&mut self, html: &str) {
let tokens = parse_html(&html);
let mut is_raw = false;
for token in tokens {
trace!("html token={token:?}");
match token {
Token::DoctypeToken(_) => {}
Token::TagToken(tag) => {
match tag.kind {
TagKind::StartTag => {
let is_closed = is_void_element(&tag.name) || tag.self_closing;
is_raw = matches!(&*tag.name, "script" | "style");
let name = QualName::new(None, html5ever::ns!(html), tag.name);
let attrs = tag
.attrs
.into_iter()
.map(|attr| (attr.name, attr.value))
.collect();
let mut el = Element {
name,
attrs,
self_closing: tag.self_closing,
};
fix_html_link(&mut el);
self.push(Node::Element(el));
if is_closed {
// No end element.
self.pop();
}
}
TagKind::EndTag => {
is_raw = false;
if self.is_html_tag_matching(&tag.name) {
self.pop();
}
// else the stack is corrupt. I'm not really sure
// what to do here...
}
}
}
Token::TagToken(tag) => match tag.kind {
TagKind::StartTag => self.start_html_tag(tag, &mut is_raw),
TagKind::EndTag => self.end_html_tag(tag, &mut is_raw),
},
Token::CommentToken(comment) => {
self.append(Node::Comment(comment));
}
@@ -658,22 +659,59 @@ where
}
}
/// Adds an open HTML tag.
fn start_html_tag(&mut self, tag: html5ever::tokenizer::Tag, is_raw: &mut bool) {
let is_closed = is_void_element(&tag.name) || tag.self_closing;
*is_raw = matches!(&*tag.name, "script" | "style");
let name = QualName::new(None, html5ever::ns!(html), tag.name);
let attrs = tag
.attrs
.into_iter()
.map(|attr| (attr.name, attr.value))
.collect();
let mut el = Element {
name,
attrs,
self_closing: tag.self_closing,
was_raw: true,
};
fix_html_link(&mut el);
self.push(Node::Element(el));
if is_closed {
// No end element.
self.pop();
}
}
/// Closes the given HTML tag.
fn end_html_tag(&mut self, tag: html5ever::tokenizer::Tag, is_raw: &mut bool) {
*is_raw = false;
if self.is_html_tag_matching(&tag.name) {
self.pop();
} else {
// The proper thing to do here is to recover. However, the HTML
// parsing algorithm for that is quite complex. See
// https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inbody
// and the adoption agency algorithm.
warn!(
"unexpected HTML end tag `</{}>` found in `{}`\n\
Check that the HTML tags are properly balanced.",
tag.name,
self.options.path.display()
);
}
}
/// This is used to verify HTML parsing keeps the stack of tags in sync.
fn is_html_tag_matching(&self, name: &str) -> bool {
let current = self.tree.get(self.current_node).unwrap().value();
if let Node::Element(el) = current
&& el.name() == name
{
return true;
true
} else {
false
}
error!(
"internal error: HTML tag stack out of sync.\n
path: `{}`\n\
current={current:?}\n\
pop name: {name}",
self.options.path.display()
);
false
}
/// Eats all pulldown-cmark events until the next `End` matching the
@@ -730,6 +768,40 @@ where
output
}
/// Deals with any unclosed elements on the stack.
fn finish_stack(&mut self) {
while let Some(node_id) = self.tag_stack.pop() {
let node = self.tree.get(node_id).unwrap().value();
match node {
Node::Fragment => {}
Node::Element(el) => {
if el.was_raw {
warn!(
"unclosed HTML tag `<{}>` found in `{}`",
el.name.local,
self.options.path.display()
);
} else {
panic!(
"internal error: expected empty tag stack.\n
path: `{}`\n\
element={el:?}",
self.options.path.display()
);
}
}
node => {
panic!(
"internal error: expected empty tag stack.\n
path: `{}`\n\
node={node:?}",
self.options.path.display()
);
}
}
}
}
/// Appends a new footnote reference.
fn footnote_reference(&mut self, name: CowStr<'event>) {
let len = self.footnote_numbers.len() + 1;
@@ -840,6 +912,11 @@ where
for heading in headings {
let node = self.tree.get(heading).unwrap();
let el = node.value().as_element().unwrap();
// Don't modify tags if they were manually written HTML. The
// user probably had some intent, and we don't want to mess it up.
if el.was_raw {
continue;
}
let href = if let Some(id) = el.attr("id") {
format!("#{id}")
} else {
@@ -966,19 +1043,29 @@ where
new_classes += class;
}
}
if icon.is_empty() {
continue;
}
if !icon.is_empty()
&& let Ok(svg) = fa::svg(type_, &icon)
{
let mut span = Element::new("span");
span.insert_attr("class", new_classes.into());
for (name, value) in &i_el.attrs {
if *name != attr_qual_name!("class") {
span.attrs.insert(name.clone(), value.clone());
match fa::svg(type_, &icon) {
Ok(svg) => {
let mut span = Element::new("span");
span.insert_attr("class", new_classes.into());
for (name, value) in &i_el.attrs {
if *name != attr_qual_name!("class") {
span.attrs.insert(name.clone(), value.clone());
}
}
*node.value() = Node::Element(span);
node.append(Node::RawData(svg.into()));
}
Err(e) => {
warn!(
"failed to find Font Awesome icon for icon `{icon}` \
with type `{type_}` in `{}`: {e}",
self.options.path.display()
);
}
*node.value() = Node::Element(span);
node.append(Node::RawData(svg.into()));
}
}
}

View File

@@ -74,12 +74,22 @@ pub(crate) fn unique_id(id: &str, used: &mut HashSet<String>) -> String {
/// Generates an HTML id from the given text.
pub(crate) fn id_from_content(content: &str) -> String {
// This is intended to be close to how header ID generation is done in
// other sites and tools, but is not 100% the same. Not all sites and
// tools use the same algorithm. See these for more information:
//
// - https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#section-links
// - https://docs.gitlab.com/user/markdown/#heading-ids-and-links
// - https://pandoc.org/MANUAL.html#extension-auto_identifiers
// - https://kramdown.gettalong.org/converter/html#auto-ids
// - https://docs.rs/comrak/latest/comrak/options/struct.Extension.html#structfield.header_ids
content
.trim()
.to_lowercase()
.chars()
.filter_map(|ch| {
if ch.is_alphanumeric() || ch == '_' || ch == '-' {
Some(ch.to_ascii_lowercase())
Some(ch)
} else if ch.is_whitespace() {
Some('-')
} else {
@@ -120,6 +130,6 @@ mod tests {
assert_eq!(id_from_content("한국어"), "한국어");
assert_eq!(id_from_content(""), "");
assert_eq!(id_from_content("中文標題 CJK title"), "中文標題-cjk-title");
assert_eq!(id_from_content("Über"), "Über");
assert_eq!(id_from_content("Über"), "über");
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "mdbook-markdown"
version = "0.5.0-beta.1"
version = "0.5.0"
description = "Markdown processing used in mdBook"
edition.workspace = true
license.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "mdbook-preprocessor"
version = "0.5.0-beta.1"
version = "0.5.0"
description = "Library to assist implementing an mdBook preprocessor"
edition.workspace = true
license.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "mdbook-renderer"
version = "0.5.0-beta.1"
version = "0.5.0"
description = "Library to assist implementing an mdBook renderer"
edition.workspace = true
license.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "mdbook-summary"
version = "0.5.0-beta.1"
version = "0.5.0"
description = "Summary parser for mdBook"
edition.workspace = true
license.workspace = true

View File

@@ -12,11 +12,10 @@ underscore (`_`) is replaced with a dash (`-`).
For example:
- `MDBOOK_foo` -> `foo`
- `MDBOOK_FOO` -> `foo`
- `MDBOOK_FOO__BAR` -> `foo.bar`
- `MDBOOK_FOO_BAR` -> `foo-bar`
- `MDBOOK_FOO_bar__baz` -> `foo-bar.baz`
- `MDBOOK_book` -> `book`
- `MDBOOK_BOOK` -> `book`
- `MDBOOK_BOOK__TITLE` -> `book.title`
- `MDBOOK_BOOK__TEXT_DIRECTION` -> `book.text-direction`
So by setting the `MDBOOK_BOOK__TITLE` environment variable you can override the
book's title without needing to touch your `book.toml`.

View File

@@ -145,9 +145,9 @@ The following configuration options are available:
an icon link will be output in the menu bar of the book.
- **git-repository-icon:** The FontAwesome icon class to use for the git
repository link. Defaults to `fab-github` which looks like <i class="fa fab-github"></i>.
If you are not using GitHub, another option to consider is `fa-code-fork` which looks like <i class="fa fa-code-fork"></i>.
If you are not using GitHub, another option to consider is `fa-code-fork` which looks like <i class="fas fa-code-fork"></i>.
- **edit-url-template:** Edit url template, when provided shows a
"Suggest an edit" button (which looks like <i class="fa fa-edit"></i>) for directly jumping to editing the currently
"Suggest an edit" button (which looks like <i class="fas fa-pencil"></i>) for directly jumping to editing the currently
viewed page. For e.g. GitHub projects set this to
`https://github.com/<owner>/<repo>/edit/<branch>/{path}` or for
Bitbucket projects set it to
@@ -178,7 +178,7 @@ The following configuration options are available:
### `[output.html.print]`
The `[output.html.print]` table provides options for controlling the printable output.
By default, mdBook will include an icon on the top right of the book (which looks like <i class="fa fa-print"></i>) that will print the book as a single page.
By default, mdBook will include an icon on the top right of the book (which looks like <i class="fas fa-print"></i>) that will print the book as a single page.
```toml
[output.html.print]

View File

@@ -73,7 +73,7 @@ nothidden():
## Rust playground
Rust language code blocks will automatically get a play button (<i class="fa fa-play"></i>) which will execute the code and display the output just below the code block.
Rust language code blocks will automatically get a play button (<i class="fas fa-play"></i>) which will execute the code and display the output just below the code block.
This works by sending the code to the [Rust Playground].
```rust

View File

@@ -42,7 +42,7 @@ Tapping the menu bar will scroll the page to the top.
## Search
Each book has a built-in search system.
Pressing the search icon (<i class="fa fa-search"></i>) in the menu bar, or pressing the <kbd>/</kbd> or <kbd>S</kbd> key on the keyboard will open an input box for entering search terms.
Pressing the search icon <i class="fas fa-magnifying-glass"></i> in the menu bar, or pressing the <kbd>/</kbd> or <kbd>S</kbd> key on the keyboard will open an input box for entering search terms.
Typing some terms will show matching chapters and sections in real time.
Clicking any of the results will jump to that section.

View File

@@ -1,6 +1,6 @@
{
"dependencies": {
"browser-ui-test": "0.22.2",
"browser-ui-test": "0.22.3",
"eslint": "^9.34.0"
},
"scripts": {

View File

@@ -207,3 +207,137 @@ unknown field `title`, expected `edition`
"#]]);
});
}
// An invalid top-level key in the environment.
#[test]
fn env_invalid_config_key() {
BookTest::from_dir("config/empty").run("build", |cmd| {
cmd.env("MDBOOK_FOO", "testing")
.expect_failure()
.expect_stdout(str![[""]])
.expect_stderr(str![[r#"
ERROR invalid key `foo`
"#]]);
});
}
// An invalid value in the environment.
#[test]
fn env_invalid_value() {
BookTest::from_dir("config/empty")
.run("build", |cmd| {
cmd.env("MDBOOK_BOOK", r#"{"titlez": "typo"}"#)
.expect_failure()
.expect_stdout(str![[""]])
.expect_stderr(str![[r#"
ERROR unknown field `titlez`, expected one of `title`, `authors`, `description`, `src`, `language`, `text-direction`
"#]]);
})
.run("build", |cmd| {
cmd.env("MDBOOK_BOOK__TITLE", r#"{"looks like obj": "abc"}"#)
.expect_failure()
.expect_stdout(str![[""]])
.expect_stderr(str![[r#"
ERROR invalid type: map, expected a string
in `title`
"#]]);
})
// This is not valid JSON, so falls back to be interpreted as a string.
.run("build", |cmd| {
cmd.env("MDBOOK_BOOK__TITLE", r#"{braces}"#)
.expect_stdout(str![[""]])
.expect_stderr(str![[r#"
INFO Book building has started
INFO Running the html backend
INFO HTML book written to `[ROOT]/book`
"#]]);
})
.check_file_contains("book/index.html", "<title>Chapter 1 - {braces}</title>");
}
// Replacing the entire book table from the environment.
#[test]
fn env_entire_book_table() {
BookTest::init(|_| {})
.change_file(
"book.toml",
"[book]\n\
title = \"config title\"\n\
",
)
.run("build", |cmd| {
cmd.env("MDBOOK_BOOK", r#"{"description": "custom description"}"#);
})
// The book.toml title is removed.
.check_file_contains("book/index.html", "<title>Chapter 1</title>")
.check_file_contains(
"book/index.html",
r#"<meta name="description" content="custom description">"#,
);
}
// Replacing the entire output or preprocessor table from the environment.
#[test]
fn env_entire_output_preprocessor_table() {
BookTest::from_dir("config/empty")
.rust_program(
"mdbook-my-preprocessor",
r#"
fn main() {
let mut args = std::env::args().skip(1);
if args.next().as_deref() == Some("supports") {
return;
}
use std::io::Read;
let mut s = String::new();
std::io::stdin().read_to_string(&mut s).unwrap();
assert!(s.contains("custom preprocessor config"));
println!("{{\"items\": []}}");
}
"#,
)
.rust_program(
"mdbook-my-output",
r#"
fn main() {
use std::io::Read;
let mut s = String::new();
std::io::stdin().read_to_string(&mut s).unwrap();
assert!(s.contains("custom output config"));
eprintln!("preprocessor saw custom config");
}
"#,
)
.run("build", |cmd| {
let mut paths: Vec<_> =
std::env::split_paths(&std::env::var_os("PATH").unwrap_or_default()).collect();
paths.push(cmd.dir.clone());
let path = std::env::join_paths(paths).unwrap().into_string().unwrap();
cmd.env(
"MDBOOK_OUTPUT",
r#"{"my-output": {"foo": "custom output config"}}"#,
)
.env(
"MDBOOK_PREPROCESSOR",
r#"{"my-preprocessor": {"foo": "custom preprocessor config"}}"#,
)
.env("PATH", path)
.expect_stdout(str![[""]])
.expect_stderr(str![[r#"
INFO Book building has started
INFO Running the my-output backend
INFO Invoking the "my-output" renderer
preprocessor saw custom config
"#]]);
})
// No HTML output
.check_file_list("book", str![[""]]);
}

View File

@@ -157,6 +157,10 @@ fn definition_lists() {
.check_main_file(
"book/definition_lists.html",
file!["markdown/definition_lists/expected_disabled/definition_lists.html"],
)
.check_main_file(
"book/html_definition_lists.html",
file!["markdown/definition_lists/expected_disabled/html_definition_lists.html"],
);
}

View File

@@ -62,4 +62,8 @@ const x = 'some *text* inside';
</style>
<style media="(width &lt; 500px)">
.bar { background-color: green }
</style>
</style>
<h2 id="manual-headers"><a class="header" href="#manual-headers">Manual headers</a></h2>
<h2 id="my header"><a href="#foo">My Header</a></h2>
<h3>Another header</h3>

View File

@@ -92,3 +92,9 @@ const x = 'some *text* inside';
<style media="(width < 500px)">
.bar { background-color: green }
</style>
## Manual headers
<h2 id="my header"><a href="#foo">My Header</a></h2>
<h3>Another header</h3>

View File

@@ -0,0 +1,16 @@
<h1 id="html-definition-lists"><a class="header" href="#html-definition-lists">HTML definition lists</a></h1>
<p>Test for definition lists manually written in HTML.</p>
<dl>
<dt>Some tag</dt>
<dd>Some defintion</dd>
<dt class="myclass" id="myid"><a class="option-anchor" href="#foo">Another definition</a></dt>
<dd class="def-class">A definition.</dd>
</dl>

View File

@@ -0,0 +1,16 @@
<h1 id="html-definition-lists"><a class="header" href="#html-definition-lists">HTML definition lists</a></h1>
<p>Test for definition lists manually written in HTML.</p>
<dl>
<dt>Some tag</dt>
<dd>Some defintion</dd>
<dt class="myclass" id="myid"><a class="option-anchor" href="#foo">Another definition</a></dt>
<dd class="def-class">A definition.</dd>
</dl>

View File

@@ -1,3 +1,4 @@
# Summary
- [Definition lists](./definition_lists.md)
- [HTML definition lists](./html_definition_lists.md)

View File

@@ -0,0 +1,10 @@
# HTML definition lists
Test for definition lists manually written in HTML.
<dl>
<dt>Some tag</dt>
<dd>Some defintion</dd>
<dt class="myclass" id="myid"><a class="option-anchor" href="#foo">Another definition</a></dt>
<dd class="def-class">A definition.</dd>
</dl>

View File

@@ -9,6 +9,10 @@ both the print page and the non-print page.</p>
<p>A <a href="#some-section">fragment link</a> should work.</p>
<p>Link <a href="../std/foo/bar.html">outside</a>.</p>
<p>Link <a href="../std/foo/bar.html#panic">outside with anchor</a>.</p>
<p>Link <a href="first/alpha/beta.html">inside but doesnt exist</a>.
Link <a href="first/alpha/beta.html#anchor">inside but doesnt exist with anchor</a>.
Link <a href="first/alpha/gamma.html">inside to html</a>.
Link <a href="first/alpha/gamma.html#anchor">inside to html with anchor</a>.</p>
<p><img src="images/picture.png" alt="Some image"></p>
<p><a href="#first-nested">HTML Link</a></p>
<img src="images/picture.png" alt="raw html">

View File

@@ -11,6 +11,11 @@ Link [outside](../../std/foo/bar.html).
Link [outside with anchor](../../std/foo/bar.html#panic).
Link [inside but doesn't exist](../first/alpha/beta.md).
Link [inside but doesn't exist with anchor](../first/alpha/beta.md#anchor).
Link [inside to html](../first/alpha/gamma.html).
Link [inside to html with anchor](../first/alpha/gamma.html#anchor).
![Some image](../images/picture.png)
<a href="../first/nested.md">HTML Link</a>

View File

@@ -47,7 +47,17 @@ fn first_chapter_is_copied_as_index_even_if_not_first_elem() {
// Fontawesome `<i>` tag support.
#[test]
fn fontawesome() {
BookTest::from_dir("rendering/fontawesome").check_all_main_files();
BookTest::from_dir("rendering/fontawesome")
.run("build", |cmd| {
cmd.expect_stderr(str![[r#"
INFO Book building has started
INFO Running the html backend
WARN failed to find Font Awesome icon for icon `does-not-exist` with type `regular` in `fa.md`: Invalid Font Awesome icon name: visit https://fontawesome.com/icons?d=gallery&m=free to see valid names
INFO HTML book written to `[ROOT]/book`
"#]]);
})
.check_all_main_files();
}
// Tests the rendering when setting the default rust edition.
@@ -229,3 +239,68 @@ fn html_blocks() {
fn code_block_fenced_with_indent() {
BookTest::from_dir("rendering/code_blocks_fenced_with_indent").check_all_main_files();
}
// Unclosed HTML tags.
//
// Note that the HTML parsing algorithm is much more complicated than what
// this is checking.
#[test]
fn unclosed_html_tags() {
BookTest::init(|_| {})
.change_file("src/chapter_1.md", "<div>x<span>foo<i>xyz")
.run("build", |cmd| {
cmd.expect_stderr(str![[r#"
INFO Book building has started
INFO Running the html backend
WARN unclosed HTML tag `<i>` found in `chapter_1.md`
WARN unclosed HTML tag `<span>` found in `chapter_1.md`
WARN unclosed HTML tag `<div>` found in `chapter_1.md`
INFO HTML book written to `[ROOT]/book`
"#]]);
})
.check_main_file(
"book/chapter_1.html",
str!["<div>x<span>foo<i>xyz</i></span></div>"],
);
}
// Test for HTML tags out of sync.
#[test]
fn unbalanced_html_tags() {
BookTest::init(|_| {})
.change_file("src/chapter_1.md", "<div>x<span>foo</div></span>")
.run("build", |cmd| {
cmd.expect_stderr(str![[r#"
INFO Book building has started
INFO Running the html backend
WARN unexpected HTML end tag `</div>` found in `chapter_1.md`
Check that the HTML tags are properly balanced.
WARN unclosed HTML tag `<div>` found in `chapter_1.md`
INFO HTML book written to `[ROOT]/book`
"#]]);
})
.check_main_file("book/chapter_1.html", str!["<div>x<span>foo</span></div>"]);
}
// Test for bug with unbalanced HTML handling in the heading.
#[test]
fn heading_with_unbalanced_html() {
BookTest::init(|_| {})
.change_file("src/chapter_1.md", "### Option<T>")
.run("build", |cmd| {
cmd.expect_stderr(str![[r#"
INFO Book building has started
INFO Running the html backend
WARN unclosed HTML tag `<t>` found in `chapter_1.md` while exiting Heading(H3)
HTML tags must be closed before exiting a markdown element.
INFO HTML book written to `[ROOT]/book`
"#]]);
})
.check_main_file(
"book/chapter_1.html",
str![[r##"<h3 id="option"><a class="header" href="#option">Option<t></t></a></h3>"##]],
);
}

View File

@@ -2,4 +2,5 @@
<p><span class="fa-svg extra-class" id="example1"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M47.6 300.4L228.3 469.1c7.5 7 17.4 10.9 27.7 10.9s20.2-3.9 27.7-10.9L464.4 300.4c30.4-28.3 47.6-68 47.6-109.5v-5.8c0-69.9-50.5-129.5-119.4-141C347 36.5 300.6 51.4 268 84L256 96 244 84c-32.6-32.6-79-47.5-124.6-39.9C50.5 55.6 0 115.2 0 185.1v5.8c0 41.5 17.2 81.2 47.6 109.5z"/></svg></span></p>
<p><span class="fa-svg"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M272 304h-96C78.8 304 0 382.8 0 480c0 17.67 14.33 32 32 32h384c17.67 0 32-14.33 32-32C448 382.8 369.2 304 272 304zM48.99 464C56.89 400.9 110.8 352 176 352h96c65.16 0 119.1 48.95 127 112H48.99zM224 256c70.69 0 128-57.31 128-128c0-70.69-57.31-128-128-128S96 57.31 96 128C96 198.7 153.3 256 224 256zM224 48c44.11 0 80 35.89 80 80c0 44.11-35.89 80-80 80S144 172.1 144 128C144 83.89 179.9 48 224 48z"/></svg></span></p>
<p><span class="fa-svg"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M448 48V384C385 407 366 416 329 416C266 416 242 384 179 384C159 384 143 388 128 392V328C143 324 159 320 179 320C242 320 266 352 329 352C349 352 364 349 384 343V135C364 141 349 144 329 144C266 144 242 112 179 112C128 112 104 133 64 141V448C64 466 50 480 32 480S0 466 0 448V64C0 46 14 32 32 32S64 46 64 64V77C104 69 128 48 179 48C242 48 266 80 329 80C366 80 385 71 448 48Z"/></svg></span></p>
<p><i class="fas fa-heart">Some text here.</i></p>
<p><i class="fas fa-heart">Text prevents translation.</i></p>
<p><i class="fa fa-does-not-exist"></i></p>

View File

@@ -6,4 +6,6 @@
<i class="fab fa-font-awesome"></i>
<i class="fas fa-heart">Some text here.</i>
<i class="fas fa-heart">Text prevents translation.</i>
<i class="fa fa-does-not-exist"></i>