mirror of
https://github.com/rust-lang/mdBook.git
synced 2025-12-28 15:01:45 -05:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9ff0e8a77 | ||
|
|
0bda57175d | ||
|
|
6287e6a44f | ||
|
|
953d3821b6 | ||
|
|
488ace15ff | ||
|
|
289028850f | ||
|
|
2a55ff62f3 | ||
|
|
6bf86806e4 | ||
|
|
90bd7207ec | ||
|
|
27b29fdaf2 | ||
|
|
154e0fb308 | ||
|
|
0de177a344 | ||
|
|
f154b2fb65 | ||
|
|
d7759fbf4d | ||
|
|
f84e670edd | ||
|
|
9a9c625319 | ||
|
|
b9ca108fca | ||
|
|
e99dc51fb3 | ||
|
|
7ee5b6643b | ||
|
|
42781bcd6b | ||
|
|
41d372de26 | ||
|
|
69599646e7 | ||
|
|
69fef40e57 | ||
|
|
a323620e02 | ||
|
|
ea0b835b38 | ||
|
|
58f0f3b0f2 | ||
|
|
e7a61efb39 | ||
|
|
d48bc29373 | ||
|
|
72f154bee4 | ||
|
|
1c71eaa964 | ||
|
|
c195aa990d | ||
|
|
34bdcaf8b3 | ||
|
|
41399fc29c | ||
|
|
7f82a197b9 | ||
|
|
71d44933f0 | ||
|
|
f01bf88e69 | ||
|
|
b5ea84c60d | ||
|
|
148c806e34 | ||
|
|
38279deed7 | ||
|
|
55f7ed1c37 | ||
|
|
eb0f7179ab | ||
|
|
5fb3675151 | ||
|
|
77b4f6a940 | ||
|
|
6308da699a | ||
|
|
62a727c041 | ||
|
|
3bc5d907f4 | ||
|
|
3cd12e7092 |
75
.travis.yml
75
.travis.yml
@@ -1,87 +1,46 @@
|
||||
# Based on the "trust" template v0.1.1
|
||||
# https://github.com/japaric/trust/tree/v0.1.1
|
||||
|
||||
dist: trusty
|
||||
language: rust
|
||||
services: docker
|
||||
sudo: required
|
||||
|
||||
cache: cargo
|
||||
cache:
|
||||
- cargo
|
||||
|
||||
before_cache:
|
||||
- chmod -R a+r $HOME/.cargo
|
||||
- chmod -R a+r $HOME/.cargo
|
||||
|
||||
env:
|
||||
global:
|
||||
- CRATE_NAME=mdbook
|
||||
- secure: DPzSRXyfRIVTibv1wOKFeGekXlL8sumGEZxpeq911MpLlrndOKmOo5Ibi3JD8fbUOsE9A/5spj4B2KQNjhbplH+Cp26oEikjuNAA6cA/b2+/TMoC3i0klAYpVopBBV3FFna0gLP+q6t6fzG2v9TJrvmmVav6KVX6ylPNvD/LoReCjrkpgLIQuAQ6dSQNor9uV+EVt4plKhhkiS28DlYdgmTvNb5g4dzOhs8hoWty72J765VYWEDDC8qXn6N9GyrhsC3dhjASGn+1QDSCADYdbG9nrRlb4CZhrfcgOnHhAFva363kshg9HtCphigMgQy2oZXk4nLWK90/HuaPPkVj+N/lpIYjtiHOunToZJfIb0MWzyVI+7+I7WR6n6XbhLCPMe/sPXHHQ3HhQhZZ9xv7CDx9IkYJQBcF3LC+9kzJRi4QT0UTqrxcO3ncgXwvholP8Vg2KKPqFcbuyLPzbvr/o8zIilvLUFAEoDPfTEwSAC4BCzaGkFQVWzhWkgw8Pe1ckOEYFkZ0VLBuCpEiz+x45sbBL1SnnO5xhpjmdc572ZyW7ZmAABw1VfiWhhBWg4WGSf8lLnDHhNA36Qon34pnME/xpJQtWoo7ZZkkzvzYP/oW88/0UIMWDSOYKz7MijXlbNUggwAwUhrLzXDuB71HUKfPreFubfUxbOpu+OtTcOQ=
|
||||
|
||||
matrix:
|
||||
include:
|
||||
# Android
|
||||
- env: TARGET=arm-linux-androideabi DISABLE_TESTS=1
|
||||
|
||||
# Linux
|
||||
- env: TARGET=aarch64-unknown-linux-gnu
|
||||
- env: TARGET=arm-unknown-linux-gnueabi
|
||||
- env: TARGET=i686-unknown-linux-gnu
|
||||
- env: TARGET=x86_64-unknown-linux-gnu
|
||||
- env: TARGET=x86_64-unknown-linux-musl
|
||||
|
||||
# Mac
|
||||
- env: TARGET=i686-apple-darwin
|
||||
os: osx
|
||||
- env: TARGET=x86_64-apple-darwin
|
||||
os: osx
|
||||
|
||||
# BSD
|
||||
- env: TARGET=i686-unknown-freebsd DISABLE_TESTS=1
|
||||
- env: TARGET=x86_64-unknown-freebsd DISABLE_TESTS=1
|
||||
- env: TARGET=x86_64-unknown-netbsd DISABLE_TESTS=1
|
||||
|
||||
# Other channels
|
||||
- env: TARGET=x86_64-unknown-linux-gnu
|
||||
rust: beta
|
||||
- env: TARGET=x86_64-apple-darwin
|
||||
os: osx
|
||||
rust: beta
|
||||
- env: TARGET=x86_64-unknown-linux-gnu
|
||||
rust: nightly
|
||||
- env: TARGET=x86_64-apple-darwin
|
||||
os: osx
|
||||
rust: nightly
|
||||
|
||||
before_install:
|
||||
- set -e
|
||||
- rustup self update
|
||||
- CRATE_NAME=mdbook
|
||||
- TARGET=x86_64-unknown-linux-gnu
|
||||
|
||||
install:
|
||||
- sh ci/install.sh
|
||||
- source ~/.cargo/env || true
|
||||
- sh ci/install.sh
|
||||
- export PATH=$PATH:$HOME/.cargo/bin
|
||||
|
||||
script:
|
||||
- bash ci/script.sh
|
||||
- cargo build --all --no-default-features
|
||||
- cargo build --verbose
|
||||
- cargo test --verbose
|
||||
|
||||
after_success:
|
||||
- bash ci/github_pages.sh
|
||||
- bash ci/github_pages.sh
|
||||
|
||||
before_deploy:
|
||||
- sh ci/before_deploy.sh
|
||||
- sh ci/before_deploy.sh
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
- secure: cURRWBr034iqBz/ifD7uOunBfNR30YxIXfgLX0osWz+iafkVbhDGYYz9sBmRraqO2P7L2koEXMADVb/md1kI2+ykiq/ml+l9zuEAZPVmvSGUN7ZD+7s+lu3l5OBPG5z175T+b2q2q2m8XVR7TW20ra4QbE0bq06KAoOyjSgQVBTSCYsL9uTsGwiVRMEqqJT/BmKhKJNkpGsTKyBSKkOXvfeAAbE260vXUDEN9TYdJ3fvteRrpwLX56ee64gIZUq0RjDc4SKIEqilM6iUtNMvurqaewYNGkiXKRruV6BPCHxEHo6NNT46kOJLBJTf7gZw//dWhSoWpg9P0gdAnPWm407kSa3F7aJ1eRShAFQ4BLyfz9efTqm+jP3fOp7Mm7igSh9w6caSRuOnSsUf5+raRQ8E5Y9HsWGzzpZQk24Fx9EGZ04EeDSdpZAFz+jcbMpHf8t2p4CEx0CCNwYvKx6EydMKbMF5QteQ8SQkXNLhv7Rz2OgtXWYZPRVCMfQfOplsi2InsLCrQxTgwh+6u654SqVSgaHG+IncEAxBrdWy4rHcg7qereUcKfcY3k96vaDxdn/T2c00Ig0aNFR91YnixGMd6J6tQgDcRK9jh6fUm1CCBE9hT+pNUmtgYKuWBoLZexUZFFnfuBed0WciBot1bGDDamndqKq0jJiAzg+GMHk=
|
||||
- secure: cURRWBr034iqBz/ifD7uOunBfNR30YxIXfgLX0osWz+iafkVbhDGYYz9sBmRraqO2P7L2koEXMADVb/md1kI2+ykiq/ml+l9zuEAZPVmvSGUN7ZD+7s+lu3l5OBPG5z175T+b2q2q2m8XVR7TW20ra4QbE0bq06KAoOyjSgQVBTSCYsL9uTsGwiVRMEqqJT/BmKhKJNkpGsTKyBSKkOXvfeAAbE260vXUDEN9TYdJ3fvteRrpwLX56ee64gIZUq0RjDc4SKIEqilM6iUtNMvurqaewYNGkiXKRruV6BPCHxEHo6NNT46kOJLBJTf7gZw//dWhSoWpg9P0gdAnPWm407kSa3F7aJ1eRShAFQ4BLyfz9efTqm+jP3fOp7Mm7igSh9w6caSRuOnSsUf5+raRQ8E5Y9HsWGzzpZQk24Fx9EGZ04EeDSdpZAFz+jcbMpHf8t2p4CEx0CCNwYvKx6EydMKbMF5QteQ8SQkXNLhv7Rz2OgtXWYZPRVCMfQfOplsi2InsLCrQxTgwh+6u654SqVSgaHG+IncEAxBrdWy4rHcg7qereUcKfcY3k96vaDxdn/T2c00Ig0aNFR91YnixGMd6J6tQgDcRK9jh6fUm1CCBE9hT+pNUmtgYKuWBoLZexUZFFnfuBed0WciBot1bGDDamndqKq0jJiAzg+GMHk=
|
||||
file_glob: true
|
||||
file: "$CRATE_NAME-$TRAVIS_TAG-$TARGET.*"
|
||||
on:
|
||||
condition: "$TRAVIS_RUST_VERSION = stable"
|
||||
tags: true
|
||||
provider: releases
|
||||
skip_cleanup: true
|
||||
|
||||
branches:
|
||||
only:
|
||||
- "/^v\\d+\\.\\d+\\.\\d+.*$/"
|
||||
- master
|
||||
- "/^v\\d+\\.\\d+\\.\\d+.*$/"
|
||||
- master
|
||||
|
||||
notifications:
|
||||
email:
|
||||
|
||||
439
Cargo.lock
generated
439
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
17
Cargo.toml
17
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mdbook"
|
||||
version = "0.1.4"
|
||||
version = "0.1.8"
|
||||
authors = ["Mathieu David <mathieudavid@mathieudavid.org>", "Michael-F-Bryan <michaelfbryan@gmail.com>"]
|
||||
description = "Create books from markdown files"
|
||||
documentation = "http://rust-lang-nursery.github.io/mdBook/index.html"
|
||||
@@ -14,11 +14,6 @@ exclude = [
|
||||
"src/theme/stylus/**",
|
||||
]
|
||||
|
||||
[package.metadata.release]
|
||||
sign-commit = true
|
||||
push-remote = "origin"
|
||||
tag-prefix = "v"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.24"
|
||||
chrono = "0.4"
|
||||
@@ -34,8 +29,8 @@ env_logger = "0.5.0-rc.1"
|
||||
toml = "0.4"
|
||||
memchr = "2.0"
|
||||
open = "1.1"
|
||||
regex = "0.2.1"
|
||||
tempdir = "0.3.4"
|
||||
regex = "1.0.0"
|
||||
tempfile = "3.0"
|
||||
itertools = "0.7"
|
||||
shlex = "0.1"
|
||||
toml-query = "0.6"
|
||||
@@ -46,12 +41,12 @@ time = { version = "0.1.34", optional = true }
|
||||
crossbeam = { version = "0.3", optional = true }
|
||||
|
||||
# Serve feature
|
||||
iron = { version = "0.5", optional = true }
|
||||
staticfile = { version = "0.4", optional = true }
|
||||
iron = { version = "0.6", optional = true }
|
||||
staticfile = { version = "0.5", optional = true }
|
||||
ws = { version = "0.7", optional = true}
|
||||
|
||||
# Search feature
|
||||
elasticlunr-rs = { version = "1.0", optional = true }
|
||||
elasticlunr-rs = { version = "2.2", optional = true, default-features = false }
|
||||
ammonia = { version = "1.1", optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
|
||||
@@ -41,7 +41,7 @@ There are multiple ways to install mdBook.
|
||||
|
||||
2. **From Crates.io**
|
||||
|
||||
This requires [Rust] and Cargo to be installed. Once you have installed
|
||||
This requires at least [Rust] 1.20 and Cargo to be installed. Once you have installed
|
||||
Rust, type the following in the terminal:
|
||||
|
||||
```
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
# Summary
|
||||
|
||||
- [mdBook](README.md)
|
||||
- [Command Line Tool](cli/cli-tool.md)
|
||||
- [Command Line Tool](cli/README.md)
|
||||
- [init](cli/init.md)
|
||||
- [build](cli/build.md)
|
||||
- [watch](cli/watch.md)
|
||||
- [serve](cli/serve.md)
|
||||
- [test](cli/test.md)
|
||||
- [clean](cli/clean.md)
|
||||
- [Format](format/format.md)
|
||||
- [Format](format/README.md)
|
||||
- [SUMMARY.md](format/summary.md)
|
||||
- [Configuration](format/config.md)
|
||||
- [Theme](format/theme/theme.md)
|
||||
- [Theme](format/theme/README.md)
|
||||
- [index.hbs](format/theme/index-hbs.md)
|
||||
- [Syntax highlighting](format/theme/syntax-highlighting.md)
|
||||
- [Editor](format/theme/editor.md)
|
||||
- [MathJax Support](format/mathjax.md)
|
||||
- [mdBook specific features](format/mdbook.md)
|
||||
- [For Developers](for_developers/index.md)
|
||||
- [Continuous Integration](continuous-integration.md)
|
||||
- [For Developers](for_developers/README.md)
|
||||
- [Preprocessors](for_developers/preprocessors.md)
|
||||
- [Alternate Backends](for_developers/backends.md)
|
||||
|
||||
-----------
|
||||
|
||||
[Contributors](misc/contributors.md)
|
||||
|
||||
55
book-example/src/continuous-integration.md
Normal file
55
book-example/src/continuous-integration.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Running `mdbook` in Continuous Integration
|
||||
|
||||
While the following examples use Travis CI, their principles should
|
||||
straightforwardly transfer to other continuous integration providers as well.
|
||||
|
||||
## Ensuring Your Book Builds and Tests Pass
|
||||
|
||||
Here is a sample Travis CI `.travis.yml` configuration that ensures `mdbook
|
||||
build` and `mdbook test` run successfully. The key to fast CI turnaround times
|
||||
is caching `mdbook` installs, so that you aren't compiling `mdbook` on every CI
|
||||
run.
|
||||
|
||||
```yaml
|
||||
language: rust
|
||||
sudo: false
|
||||
|
||||
cache:
|
||||
- cargo
|
||||
|
||||
rust:
|
||||
- stable
|
||||
|
||||
before_script:
|
||||
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
|
||||
- (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.1" mdbook)
|
||||
- cargo install-update -a
|
||||
|
||||
script:
|
||||
- cd path/to/mybook && mdbook build && mdbook test
|
||||
```
|
||||
|
||||
## Deploying Your Book to GitHub Pages
|
||||
|
||||
Following these instructions will result in your book being published to GitHub
|
||||
pages after a successful CI run on your repository's `master` branch.
|
||||
|
||||
First, create a new GitHub oauth token with the "public_repo" permissions (or
|
||||
"repo" for private repositories). Go to your repository's Travis CI settings
|
||||
page and add an environment variable named `GITHUB_TOKEN` that is marked secure
|
||||
and *not* shown in the logs.
|
||||
|
||||
Then, add this snippet to your `.travis.yml`:
|
||||
|
||||
```yaml
|
||||
deploy:
|
||||
provider: pages
|
||||
skip-cleanup: true
|
||||
github-token: $GITHUB_TOKEN
|
||||
local-dir: path/to/mybook/book
|
||||
keep-history: false
|
||||
on:
|
||||
branch: master
|
||||
```
|
||||
|
||||
That's it!
|
||||
@@ -58,12 +58,21 @@ This controls the build process of your book.
|
||||
will be created when the book is built (i.e. `create-missing = true`). If this
|
||||
is `false` then the build process will instead exit with an error if any files
|
||||
do not exist.
|
||||
- **preprocess:** Specify which preprocessors to be applied. Default is `["links", "index"]`. To disable default preprocessors, pass an empty array `[]` in.
|
||||
|
||||
|
||||
The following preprocessors are available and included by default:
|
||||
|
||||
- `links`: Expand the `{{# playpen}}` and `{{# include}}` handlebars helpers in a chapter.
|
||||
- `index`: Convert all chapter files named `README.md` into `index.md`. That is to say, all `README.md` would be rendered to an index file `index.html` in the rendered book.
|
||||
|
||||
|
||||
**book.toml**
|
||||
```toml
|
||||
[build]
|
||||
build-dir = "build"
|
||||
create-missing = false
|
||||
preprocess = ["links", "index"]
|
||||
```
|
||||
|
||||
### HTML renderer options
|
||||
@@ -132,6 +141,11 @@ title = "Example book"
|
||||
authors = ["John Doe", "Jane Doe"]
|
||||
description = "The example book covers examples."
|
||||
|
||||
[build]
|
||||
build-dir = "book"
|
||||
create-missing = true
|
||||
preprocess = ["links", "index"]
|
||||
|
||||
[output.html]
|
||||
theme = "my-theme"
|
||||
curly-quotes = true
|
||||
|
||||
@@ -9,9 +9,13 @@ To enable MathJax, you need to add the `mathjax-support` key to your `book.toml`
|
||||
mathjax-support = true
|
||||
```
|
||||
|
||||
>**Note:**
|
||||
>**Note:**
|
||||
The usual delimiters MathJax uses are not yet supported. You can't currently use `$$ ... $$` as delimiters and the `\[ ... \]` delimiters need an extra backslash to work. Hopefully this limitation will be lifted soon.
|
||||
|
||||
>**Note:**
|
||||
> When you use double backslashes in MathJax blocks (for example in commands such as `\begin{cases} \frac 1 2 \\ \frac 3 4 \end{cases}`) you need to add _two extra_ backslashes (e.g., `\begin{cases} \frac 1 2 \\\\ \frac 3 4 \end{cases}`).
|
||||
|
||||
|
||||
### Inline equations
|
||||
Inline equations are delimited by `\\(` and `\\)`. So for example, to render the following inline equation \\( \int x dx = \frac{x^2}{2} + C \\) you would write the following:
|
||||
```
|
||||
|
||||
@@ -16,3 +16,4 @@ If you have contributed to mdBook and I forgot to add you, don't hesitate to add
|
||||
- [projektir](https://github.com/projektir)
|
||||
- [Phaiax](https://github.com/Phaiax)
|
||||
- [Matt Ickstadt](https://github.com/mattico)
|
||||
- Weihang Lo ([@weihanglo](https://github.com/weihanglo))
|
||||
|
||||
@@ -15,11 +15,9 @@ main() {
|
||||
;;
|
||||
esac
|
||||
|
||||
test -f Cargo.lock || cargo generate-lockfile
|
||||
cargo rustc --bin mdbook --target $TARGET --release -- -C lto
|
||||
|
||||
cross rustc --bin mdbook --target $TARGET --release -- -C lto
|
||||
|
||||
cp target/$TARGET/release/mdbook $stage/
|
||||
cp target/release/mdbook $stage/
|
||||
|
||||
cd $stage
|
||||
tar czf $src/$CRATE_NAME-$TRAVIS_TAG-$TARGET.tar.gz *
|
||||
@@ -28,4 +26,4 @@ main() {
|
||||
rm -rf $stage
|
||||
}
|
||||
|
||||
main
|
||||
main
|
||||
|
||||
22
ci/script.sh
22
ci/script.sh
@@ -1,22 +0,0 @@
|
||||
# This script takes care of testing your crate
|
||||
|
||||
set -ex
|
||||
|
||||
main() {
|
||||
cross build --target $TARGET --all --no-default-features
|
||||
cross build --target $TARGET --all
|
||||
cross build --target $TARGET --all --release
|
||||
|
||||
if [ ! -z $DISABLE_TESTS ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
cross test --target $TARGET --no-default-features
|
||||
cross test --target $TARGET
|
||||
cross test --target $TARGET --release
|
||||
}
|
||||
|
||||
# we don't run the "test phase" when doing deploys
|
||||
if [ -z $TRAVIS_TAG ]; then
|
||||
main
|
||||
fi
|
||||
3
release.toml
Normal file
3
release.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
sign-commit = true
|
||||
push-remote = "origin"
|
||||
tag-prefix = "v"
|
||||
@@ -1,7 +1 @@
|
||||
array_layout = "Visual"
|
||||
chain_indent = "Visual"
|
||||
fn_args_layout = "Visual"
|
||||
fn_call_style = "Visual"
|
||||
format_strings = true
|
||||
generics_indent = "Visual"
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::env;
|
||||
use std::process::Command;
|
||||
use clap::{App, ArgMatches, SubCommand};
|
||||
use mdbook::MDBook;
|
||||
use mdbook::errors::Result;
|
||||
use mdbook::utils;
|
||||
use mdbook::config;
|
||||
use get_book_dir;
|
||||
|
||||
@@ -68,20 +67,15 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Obtains author name from git config file if it can be located.
|
||||
/// Obtains author name from git config file by running the `git config` command.
|
||||
fn get_author_name() -> Option<String> {
|
||||
if let Some(home) = env::home_dir() {
|
||||
let git_config_path = home.join(".gitconfig");
|
||||
let content = utils::fs::file_to_string(git_config_path).unwrap();
|
||||
let user_name = content
|
||||
.lines()
|
||||
.filter(|x| !x.starts_with("#"))
|
||||
.map(|x| x.trim_left())
|
||||
.filter(|x| x.starts_with("name"))
|
||||
.next();
|
||||
user_name
|
||||
.and_then(|x| x.rsplit("=").next())
|
||||
.map(|x| x.trim().to_owned())
|
||||
let output = Command::new("git")
|
||||
.args(&["config", "--get", "user.name"])
|
||||
.output()
|
||||
.ok()?;
|
||||
|
||||
if output.status.success() {
|
||||
Some(String::from_utf8_lossy(&output.stdout).trim().to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
@@ -297,7 +297,7 @@ impl Display for Chapter {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempdir::TempDir;
|
||||
use tempfile::{TempDir, Builder as TempFileBuilder};
|
||||
use std::io::Write;
|
||||
|
||||
const DUMMY_SRC: &'static str = "
|
||||
@@ -311,7 +311,7 @@ And here is some \
|
||||
|
||||
/// Create a dummy `Link` in a temporary directory.
|
||||
fn dummy_link() -> (Link, TempDir) {
|
||||
let temp = TempDir::new("book").unwrap();
|
||||
let temp = TempFileBuilder::new().prefix("book").tempdir().unwrap();
|
||||
|
||||
let chapter_path = temp.path().join("chapter_1.md");
|
||||
File::create(&chapter_path)
|
||||
|
||||
@@ -16,12 +16,17 @@ pub use self::init::BookBuilder;
|
||||
use std::path::PathBuf;
|
||||
use std::io::Write;
|
||||
use std::process::Command;
|
||||
use tempdir::TempDir;
|
||||
use tempfile::Builder as TempFileBuilder;
|
||||
use toml::Value;
|
||||
|
||||
use utils;
|
||||
use renderer::{CmdRenderer, HtmlHandlebars, RenderContext, Renderer};
|
||||
use preprocess::{LinkPreprocessor, Preprocessor, PreprocessorContext};
|
||||
use preprocess::{
|
||||
LinkPreprocessor,
|
||||
IndexPreprocessor,
|
||||
Preprocessor,
|
||||
PreprocessorContext
|
||||
};
|
||||
use errors::*;
|
||||
|
||||
use config::Config;
|
||||
@@ -213,11 +218,12 @@ impl MDBook {
|
||||
.flat_map(|x| vec![x.0, x.1])
|
||||
.collect();
|
||||
|
||||
let temp_dir = TempDir::new("mdbook")?;
|
||||
let temp_dir = TempFileBuilder::new().prefix("mdbook").tempdir()?;
|
||||
|
||||
let preprocess_context = PreprocessorContext::new(self.root.clone(), self.config.clone());
|
||||
|
||||
LinkPreprocessor::new().run(&preprocess_context, &mut self.book)?;
|
||||
IndexPreprocessor::new().run(&preprocess_context, &mut self.book)?;
|
||||
|
||||
for item in self.iter() {
|
||||
if let BookItem::Chapter(ref ch) = *item {
|
||||
@@ -322,15 +328,19 @@ fn determine_renderers(config: &Config) -> Vec<Box<Renderer>> {
|
||||
}
|
||||
|
||||
fn default_preprocessors() -> Vec<Box<Preprocessor>> {
|
||||
vec![Box::new(LinkPreprocessor::new())]
|
||||
vec![
|
||||
Box::new(LinkPreprocessor::new()),
|
||||
Box::new(IndexPreprocessor::new()),
|
||||
]
|
||||
}
|
||||
|
||||
/// Look at the `MDBook` and try to figure out what preprocessors to run.
|
||||
fn determine_preprocessors(config: &Config) -> Result<Vec<Box<Preprocessor>>> {
|
||||
let preprocess_list = match config.build.preprocess {
|
||||
Some(ref p) => p,
|
||||
// If no preprocessor field is set, default to the LinkPreprocessor. This allows you
|
||||
// to disable the LinkPreprocessor by setting "preprocess" to an empty list.
|
||||
// If no preprocessor field is set, default to the LinkPreprocessor and
|
||||
// IndexPreprocessor. This allows you to disable default preprocessors
|
||||
// by setting "preprocess" to an empty list.
|
||||
None => return Ok(default_preprocessors()),
|
||||
};
|
||||
|
||||
@@ -339,6 +349,7 @@ fn determine_preprocessors(config: &Config) -> Result<Vec<Box<Preprocessor>>> {
|
||||
for key in preprocess_list {
|
||||
match key.as_ref() {
|
||||
"links" => preprocessors.push(Box::new(LinkPreprocessor::new())),
|
||||
"index" => preprocessors.push(Box::new(IndexPreprocessor::new())),
|
||||
_ => bail!("{:?} is not a recognised preprocessor", key),
|
||||
}
|
||||
}
|
||||
@@ -403,7 +414,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_defaults_to_link_preprocessor_if_not_set() {
|
||||
fn config_defaults_to_link_and_index_preprocessor_if_not_set() {
|
||||
let cfg = Config::default();
|
||||
|
||||
// make sure we haven't got anything in the `output` table
|
||||
@@ -412,8 +423,9 @@ mod tests {
|
||||
let got = determine_preprocessors(&cfg);
|
||||
|
||||
assert!(got.is_ok());
|
||||
assert_eq!(got.as_ref().unwrap().len(), 1);
|
||||
assert_eq!(got.as_ref().unwrap().len(), 2);
|
||||
assert_eq!(got.as_ref().unwrap()[0].name(), "links");
|
||||
assert_eq!(got.as_ref().unwrap()[1].name(), "index");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -99,7 +99,7 @@ extern crate serde_derive;
|
||||
#[macro_use]
|
||||
extern crate serde_json;
|
||||
extern crate shlex;
|
||||
extern crate tempdir;
|
||||
extern crate tempfile;
|
||||
extern crate toml;
|
||||
extern crate toml_query;
|
||||
|
||||
|
||||
91
src/preprocess/index.rs
Normal file
91
src/preprocess/index.rs
Normal file
@@ -0,0 +1,91 @@
|
||||
use std::path::Path;
|
||||
use regex::Regex;
|
||||
|
||||
use errors::*;
|
||||
|
||||
use super::{Preprocessor, PreprocessorContext};
|
||||
use book::{Book, BookItem};
|
||||
|
||||
/// A preprocessor for converting file name `README.md` to `index.md` since
|
||||
/// `README.md` is the de facto index file in a markdown-based documentation.
|
||||
pub struct IndexPreprocessor;
|
||||
|
||||
impl IndexPreprocessor {
|
||||
/// Create a new `IndexPreprocessor`.
|
||||
pub fn new() -> Self {
|
||||
IndexPreprocessor
|
||||
}
|
||||
}
|
||||
|
||||
impl Preprocessor for IndexPreprocessor {
|
||||
fn name(&self) -> &str {
|
||||
"index"
|
||||
}
|
||||
|
||||
fn run(&self, ctx: &PreprocessorContext, book: &mut Book) -> Result<()> {
|
||||
let source_dir = ctx.root.join(&ctx.config.book.src);
|
||||
book.for_each_mut(|section: &mut BookItem| {
|
||||
if let BookItem::Chapter(ref mut ch) = *section {
|
||||
if is_readme_file(&ch.path) {
|
||||
let index_md = source_dir
|
||||
.join(ch.path.with_file_name("index.md"));
|
||||
if index_md.exists() {
|
||||
warn_readme_name_conflict(&ch.path, &index_md);
|
||||
}
|
||||
|
||||
ch.path.set_file_name("index.md");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn warn_readme_name_conflict<P: AsRef<Path>>(readme_path: P, index_path: P) {
|
||||
let file_name = readme_path.as_ref().file_name().unwrap_or_default();
|
||||
let parent_dir = index_path.as_ref().parent().unwrap_or(index_path.as_ref());
|
||||
warn!("It seems that there are both {:?} and index.md under \"{}\".", file_name, parent_dir.display());
|
||||
warn!("mdbook converts {:?} into index.html by default. It may cause", file_name);
|
||||
warn!("unexpected behavior if putting both files under the same directory.");
|
||||
warn!("To solve the warning, try to rearrange the book structure or disable");
|
||||
warn!("\"index\" preprocessor to stop the conversion.");
|
||||
}
|
||||
|
||||
fn is_readme_file<P: AsRef<Path>>(path: P) -> bool {
|
||||
lazy_static! {
|
||||
static ref RE: Regex = Regex::new(r"(?i)^readme$").unwrap();
|
||||
}
|
||||
RE.is_match(
|
||||
path.as_ref()
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or_default()
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn file_stem_exactly_matches_readme_case_insensitively() {
|
||||
let path = "path/to/Readme.md";
|
||||
assert!(is_readme_file(path));
|
||||
|
||||
let path = "path/to/README.md";
|
||||
assert!(is_readme_file(path));
|
||||
|
||||
let path = "path/to/rEaDmE.md";
|
||||
assert!(is_readme_file(path));
|
||||
|
||||
let path = "path/to/README.markdown";
|
||||
assert!(is_readme_file(path));
|
||||
|
||||
let path = "path/to/README";
|
||||
assert!(is_readme_file(path));
|
||||
|
||||
let path = "path/to/README-README.md";
|
||||
assert!(!is_readme_file(path));
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ use super::{Preprocessor, PreprocessorContext};
|
||||
use book::{Book, BookItem};
|
||||
|
||||
const ESCAPE_CHAR: char = '\\';
|
||||
const MAX_LINK_NESTED_DEPTH: usize = 10;
|
||||
|
||||
/// A preprocessor for expanding the `{{# playpen}}` and `{{# include}}`
|
||||
/// helpers in a chapter.
|
||||
@@ -36,7 +37,7 @@ impl Preprocessor for LinkPreprocessor {
|
||||
.map(|dir| src_dir.join(dir))
|
||||
.expect("All book items have a parent");
|
||||
|
||||
let content = replace_all(&ch.content, base);
|
||||
let content = replace_all(&ch.content, base, &ch.path, 0);
|
||||
ch.content = content;
|
||||
}
|
||||
});
|
||||
@@ -45,11 +46,12 @@ impl Preprocessor for LinkPreprocessor {
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_all<P: AsRef<Path>>(s: &str, path: P) -> String {
|
||||
fn replace_all<P: AsRef<Path>>(s: &str, path: P, source: &P, depth: usize) -> String {
|
||||
// When replacing one thing in a string by something with a different length,
|
||||
// the indices after that will not correspond,
|
||||
// we therefore have to store the difference to correct this
|
||||
let path = path.as_ref();
|
||||
let source = source.as_ref();
|
||||
let mut previous_end_index = 0;
|
||||
let mut replaced = String::new();
|
||||
|
||||
@@ -58,7 +60,15 @@ fn replace_all<P: AsRef<Path>>(s: &str, path: P) -> String {
|
||||
|
||||
match playpen.render_with_path(&path) {
|
||||
Ok(new_content) => {
|
||||
replaced.push_str(&new_content);
|
||||
if depth < MAX_LINK_NESTED_DEPTH {
|
||||
if let Some(rel_path) = playpen.link.relative_path(path) {
|
||||
replaced.push_str(&replace_all(&new_content, rel_path, &source.to_path_buf(), depth + 1));
|
||||
}
|
||||
}
|
||||
else {
|
||||
error!("Stack depth exceeded in {}. Check for cyclic includes",
|
||||
source.display());
|
||||
}
|
||||
previous_end_index = playpen.end_index;
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -84,6 +94,27 @@ enum LinkType<'a> {
|
||||
Playpen(PathBuf, Vec<&'a str>),
|
||||
}
|
||||
|
||||
impl<'a> LinkType<'a> {
|
||||
fn relative_path<P: AsRef<Path>>(self, base: P) -> Option<PathBuf> {
|
||||
let base = base.as_ref();
|
||||
match self {
|
||||
LinkType::Escaped => None,
|
||||
LinkType::IncludeRange(p, _) => Some(return_relative_path(base, &p)),
|
||||
LinkType::IncludeRangeFrom(p, _) => Some(return_relative_path(base, &p)),
|
||||
LinkType::IncludeRangeTo(p, _) => Some(return_relative_path(base, &p)),
|
||||
LinkType::IncludeRangeFull(p, _) => Some(return_relative_path(base, &p)),
|
||||
LinkType::Playpen(p,_) => Some(return_relative_path(base, &p))
|
||||
}
|
||||
}
|
||||
}
|
||||
fn return_relative_path<P: AsRef<Path>>(base: P, relative: P) -> PathBuf {
|
||||
base.as_ref()
|
||||
.join(relative)
|
||||
.parent()
|
||||
.expect("Included file should not be /")
|
||||
.to_path_buf()
|
||||
}
|
||||
|
||||
fn parse_include_path(path: &str) -> LinkType<'static> {
|
||||
let mut parts = path.split(':');
|
||||
let path = parts.next().unwrap().into();
|
||||
@@ -91,7 +122,7 @@ fn parse_include_path(path: &str) -> LinkType<'static> {
|
||||
let start = parts
|
||||
.next()
|
||||
.and_then(|s| s.parse::<usize>().ok())
|
||||
.map(|val| val.checked_sub(1).unwrap_or(0));
|
||||
.map(|val| val.saturating_sub(1));
|
||||
let end = parts.next();
|
||||
let has_end = end.is_some();
|
||||
let end = end.and_then(|s| s.parse::<usize>().ok());
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
//! Book preprocessing.
|
||||
|
||||
pub use self::links::LinkPreprocessor;
|
||||
pub use self::index::IndexPreprocessor;
|
||||
|
||||
mod links;
|
||||
mod index;
|
||||
|
||||
use book::Book;
|
||||
use config::Config;
|
||||
@@ -10,7 +12,7 @@ use errors::*;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Extra information for a `Preprocessor` to give them more context when
|
||||
/// Extra information for a `Preprocessor` to give them more context when
|
||||
/// processing a book.
|
||||
pub struct PreprocessorContext {
|
||||
/// The location of the book directory on disk.
|
||||
@@ -26,7 +28,7 @@ impl PreprocessorContext {
|
||||
}
|
||||
}
|
||||
|
||||
/// An operation which is run immediately after loading a book into memory and
|
||||
/// An operation which is run immediately after loading a book into memory and
|
||||
/// before it gets rendered.
|
||||
pub trait Preprocessor {
|
||||
/// Get the `Preprocessor`'s name.
|
||||
@@ -35,4 +37,4 @@ pub trait Preprocessor {
|
||||
/// Run this `Preprocessor`, allowing it to update the book before it is
|
||||
/// given to a renderer.
|
||||
fn run(&self, ctx: &PreprocessorContext, book: &mut Book) -> Result<()>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
use book::{Book, BookItem, Chapter};
|
||||
use config::{Config, HtmlConfig, Playpen};
|
||||
use errors::*;
|
||||
use renderer::{RenderContext, Renderer};
|
||||
use renderer::html_handlebars::helpers;
|
||||
use theme::{self, Theme, playpen_editor};
|
||||
use renderer::{RenderContext, Renderer};
|
||||
use theme::{self, playpen_editor, Theme};
|
||||
use utils;
|
||||
|
||||
#[allow(unused_imports)] use std::ascii::AsciiExt;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::{self, File};
|
||||
@@ -42,9 +41,9 @@ impl HtmlHandlebars {
|
||||
let path = ch.path
|
||||
.to_str()
|
||||
.chain_err(|| "Could not convert path to str")?;
|
||||
let filepath = Path::new(&ch.path)
|
||||
.with_extension("html");
|
||||
let filepathstr = filepath.to_str()
|
||||
let filepath = Path::new(&ch.path).with_extension("html");
|
||||
let filepathstr = filepath
|
||||
.to_str()
|
||||
.chain_err(|| "Could not convert HTML path to str")?;
|
||||
let filepathstr = utils::fs::normalize_path(filepathstr);
|
||||
|
||||
@@ -57,9 +56,9 @@ impl HtmlHandlebars {
|
||||
let title: String;
|
||||
{
|
||||
let book_title = ctx.data
|
||||
.get("book_title")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.unwrap_or("");
|
||||
.get("book_title")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.unwrap_or("");
|
||||
title = ch.name.clone() + " - " + book_title;
|
||||
}
|
||||
|
||||
@@ -67,18 +66,16 @@ impl HtmlHandlebars {
|
||||
ctx.data.insert("content".to_owned(), json!(content));
|
||||
ctx.data.insert("chapter_title".to_owned(), json!(ch.name));
|
||||
ctx.data.insert("title".to_owned(), json!(title));
|
||||
ctx.data.insert("path_to_root".to_owned(),
|
||||
json!(utils::fs::path_to_root(&ch.path)));
|
||||
ctx.data.insert(
|
||||
"path_to_root".to_owned(),
|
||||
json!(utils::fs::path_to_root(&ch.path)),
|
||||
);
|
||||
|
||||
// Render the handlebars template with the data
|
||||
debug!("Render template");
|
||||
let rendered = ctx.handlebars.render("index", &ctx.data)?;
|
||||
|
||||
let rendered = self.post_process(
|
||||
rendered,
|
||||
&filepathstr,
|
||||
&ctx.html_config.playpen,
|
||||
);
|
||||
let rendered = self.post_process(rendered, &filepathstr, &ctx.html_config.playpen);
|
||||
|
||||
// Write to file
|
||||
debug!("Creating {} ✓", filepathstr);
|
||||
@@ -106,10 +103,11 @@ impl HtmlHandlebars {
|
||||
// This could cause a problem when someone displays
|
||||
// code containing <base href=...>
|
||||
// on the front page, however this case should be very very rare...
|
||||
content = content.lines()
|
||||
.filter(|line| !line.contains("<base href="))
|
||||
.collect::<Vec<&str>>()
|
||||
.join("\n");
|
||||
content = content
|
||||
.lines()
|
||||
.filter(|line| !line.contains("<base href="))
|
||||
.collect::<Vec<&str>>()
|
||||
.join("\n");
|
||||
|
||||
utils::fs::write_file(destination, "index.html", content.as_bytes())?;
|
||||
|
||||
@@ -122,11 +120,7 @@ impl HtmlHandlebars {
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(let_and_return))]
|
||||
fn post_process(&self,
|
||||
rendered: String,
|
||||
filepath: &str,
|
||||
playpen_config: &Playpen)
|
||||
-> String {
|
||||
fn post_process(&self, rendered: String, filepath: &str, playpen_config: &Playpen) -> String {
|
||||
let rendered = build_header_links(&rendered, filepath);
|
||||
let rendered = fix_anchor_links(&rendered, filepath);
|
||||
let rendered = fix_code_blocks(&rendered);
|
||||
@@ -143,6 +137,9 @@ impl HtmlHandlebars {
|
||||
) -> Result<()> {
|
||||
use utils::fs::write_file;
|
||||
|
||||
write_file(destination, ".nojekyll",
|
||||
b"This file makes sure that Github Pages doesn't process mdBook's output.")?;
|
||||
|
||||
write_file(destination, "book.js", &theme.js)?;
|
||||
write_file(destination, "book.css", &theme.css)?;
|
||||
write_file(destination, "favicon.png", &theme.favicon)?;
|
||||
@@ -153,37 +150,37 @@ impl HtmlHandlebars {
|
||||
write_file(destination, "clipboard.min.js", &theme.clipboard_js)?;
|
||||
write_file(
|
||||
destination,
|
||||
"_FontAwesome/css/font-awesome.css",
|
||||
"FontAwesome/css/font-awesome.css",
|
||||
theme::FONT_AWESOME,
|
||||
)?;
|
||||
write_file(
|
||||
destination,
|
||||
"_FontAwesome/fonts/fontawesome-webfont.eot",
|
||||
"FontAwesome/fonts/fontawesome-webfont.eot",
|
||||
theme::FONT_AWESOME_EOT,
|
||||
)?;
|
||||
write_file(
|
||||
destination,
|
||||
"_FontAwesome/fonts/fontawesome-webfont.svg",
|
||||
"FontAwesome/fonts/fontawesome-webfont.svg",
|
||||
theme::FONT_AWESOME_SVG,
|
||||
)?;
|
||||
write_file(
|
||||
destination,
|
||||
"_FontAwesome/fonts/fontawesome-webfont.ttf",
|
||||
"FontAwesome/fonts/fontawesome-webfont.ttf",
|
||||
theme::FONT_AWESOME_TTF,
|
||||
)?;
|
||||
write_file(
|
||||
destination,
|
||||
"_FontAwesome/fonts/fontawesome-webfont.woff",
|
||||
"FontAwesome/fonts/fontawesome-webfont.woff",
|
||||
theme::FONT_AWESOME_WOFF,
|
||||
)?;
|
||||
write_file(
|
||||
destination,
|
||||
"_FontAwesome/fonts/fontawesome-webfont.woff2",
|
||||
"FontAwesome/fonts/fontawesome-webfont.woff2",
|
||||
theme::FONT_AWESOME_WOFF2,
|
||||
)?;
|
||||
write_file(
|
||||
destination,
|
||||
"_FontAwesome/fonts/FontAwesome.ttf",
|
||||
"FontAwesome/fonts/FontAwesome.ttf",
|
||||
theme::FONT_AWESOME_TTF,
|
||||
)?;
|
||||
|
||||
@@ -196,7 +193,8 @@ impl HtmlHandlebars {
|
||||
write_file(destination, "ace.js", playpen_editor::ACE_JS)?;
|
||||
write_file(destination, "mode-rust.js", playpen_editor::MODE_RUST_JS)?;
|
||||
write_file(destination, "theme-dawn.js", playpen_editor::THEME_DAWN_JS)?;
|
||||
write_file(destination,
|
||||
write_file(
|
||||
destination,
|
||||
"theme-tomorrow_night.js",
|
||||
playpen_editor::THEME_TOMORROW_NIGHT_JS,
|
||||
)?;
|
||||
@@ -206,28 +204,42 @@ impl HtmlHandlebars {
|
||||
}
|
||||
|
||||
/// Update the context with data for this file
|
||||
fn configure_print_version(&self,
|
||||
data: &mut serde_json::Map<String, serde_json::Value>,
|
||||
print_content: &str) {
|
||||
fn configure_print_version(
|
||||
&self,
|
||||
data: &mut serde_json::Map<String, serde_json::Value>,
|
||||
print_content: &str,
|
||||
) {
|
||||
// Make sure that the Print chapter does not display the title from
|
||||
// the last rendered chapter by removing it from its context
|
||||
data.remove("title");
|
||||
data.insert("is_print".to_owned(), json!(true));
|
||||
data.insert("path".to_owned(), json!("print.md"));
|
||||
data.insert("content".to_owned(), json!(print_content));
|
||||
data.insert("path_to_root".to_owned(),
|
||||
json!(utils::fs::path_to_root(Path::new("print.md"))));
|
||||
data.insert(
|
||||
"path_to_root".to_owned(),
|
||||
json!(utils::fs::path_to_root(Path::new("print.md"))),
|
||||
);
|
||||
}
|
||||
|
||||
fn register_hbs_helpers(&self, handlebars: &mut Handlebars, html_config: &HtmlConfig) {
|
||||
handlebars.register_helper("toc", Box::new(helpers::toc::RenderToc {no_section_label: html_config.no_section_label}));
|
||||
handlebars.register_helper(
|
||||
"toc",
|
||||
Box::new(helpers::toc::RenderToc {
|
||||
no_section_label: html_config.no_section_label,
|
||||
}),
|
||||
);
|
||||
handlebars.register_helper("previous", Box::new(helpers::navigation::previous));
|
||||
handlebars.register_helper("next", Box::new(helpers::navigation::next));
|
||||
}
|
||||
|
||||
/// Copy across any additional CSS and JavaScript files which the book
|
||||
/// has been configured to use.
|
||||
fn copy_additional_css_and_js(&self, html: &HtmlConfig, root: &Path, destination: &Path) -> Result<()> {
|
||||
fn copy_additional_css_and_js(
|
||||
&self,
|
||||
html: &HtmlConfig,
|
||||
root: &Path,
|
||||
destination: &Path,
|
||||
) -> Result<()> {
|
||||
let custom_files = html.additional_css.iter().chain(html.additional_js.iter());
|
||||
|
||||
debug!("Copying additional CSS and JS");
|
||||
@@ -258,6 +270,25 @@ impl HtmlHandlebars {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(mattico): Remove some time after the 0.1.8 release
|
||||
fn maybe_wrong_theme_dir(dir: &Path) -> Result<bool> {
|
||||
fn entry_is_maybe_book_file(entry: fs::DirEntry) -> Result<bool> {
|
||||
Ok(entry.file_type()?.is_file()
|
||||
&& entry.path().extension().map_or(false, |ext| ext == "md"))
|
||||
}
|
||||
|
||||
if dir.is_dir() {
|
||||
for entry in fs::read_dir(dir)? {
|
||||
if entry_is_maybe_book_file(entry?).unwrap_or(false) {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderer for HtmlHandlebars {
|
||||
fn name(&self) -> &str {
|
||||
"html"
|
||||
@@ -274,9 +305,19 @@ impl Renderer for HtmlHandlebars {
|
||||
|
||||
let theme_dir = match html_config.theme {
|
||||
Some(ref theme) => theme.to_path_buf(),
|
||||
None => src_dir.join("theme"),
|
||||
None => ctx.root.join("theme"),
|
||||
};
|
||||
|
||||
if html_config.theme.is_none()
|
||||
&& maybe_wrong_theme_dir(&src_dir.join("theme")).unwrap_or(false)
|
||||
{
|
||||
warn!(
|
||||
"Previous versions of mdBook erroneously accepted `./src/theme` as an automatic \
|
||||
theme directory"
|
||||
);
|
||||
warn!("Please move your theme files to `./theme` for them to continue being used");
|
||||
}
|
||||
|
||||
let theme = theme::Theme::new(theme_dir);
|
||||
|
||||
debug!("Register the index handlebars template");
|
||||
@@ -305,9 +346,7 @@ impl Renderer for HtmlHandlebars {
|
||||
is_index: is_index,
|
||||
html_config: html_config.clone(),
|
||||
};
|
||||
self.render_item(item,
|
||||
ctx,
|
||||
&mut print_content)?;
|
||||
self.render_item(item, ctx, &mut print_content)?;
|
||||
is_index = false;
|
||||
}
|
||||
|
||||
@@ -321,9 +360,7 @@ impl Renderer for HtmlHandlebars {
|
||||
debug!("Render template");
|
||||
let rendered = handlebars.render("index", &data)?;
|
||||
|
||||
let rendered = self.post_process(rendered,
|
||||
"print.html",
|
||||
&html_config.playpen);
|
||||
let rendered = self.post_process(rendered, "print.html", &html_config.playpen);
|
||||
|
||||
utils::fs::write_file(&destination, "print.html", &rendered.into_bytes())?;
|
||||
debug!("Creating print.html ✓");
|
||||
@@ -345,14 +382,25 @@ impl Renderer for HtmlHandlebars {
|
||||
}
|
||||
}
|
||||
|
||||
fn make_data(root: &Path, book: &Book, config: &Config, html_config: &HtmlConfig) -> Result<serde_json::Map<String, serde_json::Value>> {
|
||||
fn make_data(
|
||||
root: &Path,
|
||||
book: &Book,
|
||||
config: &Config,
|
||||
html_config: &HtmlConfig,
|
||||
) -> Result<serde_json::Map<String, serde_json::Value>> {
|
||||
trace!("make_data");
|
||||
let html = config.html_config().unwrap_or_default();
|
||||
|
||||
let mut data = serde_json::Map::new();
|
||||
data.insert("language".to_owned(), json!("en"));
|
||||
data.insert("book_title".to_owned(), json!(config.book.title.clone().unwrap_or_default()));
|
||||
data.insert("description".to_owned(), json!(config.book.description.clone().unwrap_or_default()));
|
||||
data.insert(
|
||||
"book_title".to_owned(),
|
||||
json!(config.book.title.clone().unwrap_or_default()),
|
||||
);
|
||||
data.insert(
|
||||
"description".to_owned(),
|
||||
json!(config.book.description.clone().unwrap_or_default()),
|
||||
);
|
||||
data.insert("favicon".to_owned(), json!("favicon.png"));
|
||||
if let Some(ref livereload) = html_config.livereload_url {
|
||||
data.insert("livereload".to_owned(), json!(livereload));
|
||||
@@ -372,13 +420,8 @@ fn make_data(root: &Path, book: &Book, config: &Config, html_config: &HtmlConfig
|
||||
let mut css = Vec::new();
|
||||
for style in &html.additional_css {
|
||||
match style.strip_prefix(root) {
|
||||
Ok(p) => {
|
||||
css.push(p.to_str().expect("Could not convert to str"))
|
||||
},
|
||||
Err(_) => {
|
||||
css.push(style.to_str()
|
||||
.expect("Could not convert to str"))
|
||||
}
|
||||
Ok(p) => css.push(p.to_str().expect("Could not convert to str")),
|
||||
Err(_) => css.push(style.to_str().expect("Could not convert to str")),
|
||||
}
|
||||
}
|
||||
data.insert("additional_css".to_owned(), json!(css));
|
||||
@@ -390,12 +433,13 @@ fn make_data(root: &Path, book: &Book, config: &Config, html_config: &HtmlConfig
|
||||
for script in &html.additional_js {
|
||||
match script.strip_prefix(root) {
|
||||
Ok(p) => js.push(p.to_str().expect("Could not convert to str")),
|
||||
Err(_) => {
|
||||
js.push(script.file_name()
|
||||
.expect("File has a file name")
|
||||
.to_str()
|
||||
.expect("Could not convert to str"))
|
||||
}
|
||||
Err(_) => js.push(
|
||||
script
|
||||
.file_name()
|
||||
.expect("File has a file name")
|
||||
.to_str()
|
||||
.expect("Could not convert to str"),
|
||||
),
|
||||
}
|
||||
}
|
||||
data.insert("additional_js".to_owned(), json!(js));
|
||||
@@ -413,10 +457,12 @@ fn make_data(root: &Path, book: &Book, config: &Config, html_config: &HtmlConfig
|
||||
}
|
||||
} else if search.is_some() {
|
||||
warn!("mdBook compiled without search support, ignoring `output.html.search` table");
|
||||
warn!("please reinstall with `cargo install mdbook --force --features search`\
|
||||
to use the search feature")
|
||||
warn!(
|
||||
"please reinstall with `cargo install mdbook --force --features search`to use the \
|
||||
search feature"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
let mut chapters = vec![];
|
||||
|
||||
for item in book.iter() {
|
||||
@@ -455,22 +501,25 @@ fn build_header_links(html: &str, filepath: &str) -> String {
|
||||
let regex = Regex::new(r"<h(\d)>(.*?)</h\d>").unwrap();
|
||||
let mut id_counter = HashMap::new();
|
||||
|
||||
regex.replace_all(html, |caps: &Captures| {
|
||||
let level = caps[1].parse()
|
||||
.expect("Regex should ensure we only ever get numbers here");
|
||||
regex
|
||||
.replace_all(html, |caps: &Captures| {
|
||||
let level = caps[1]
|
||||
.parse()
|
||||
.expect("Regex should ensure we only ever get numbers here");
|
||||
|
||||
wrap_header_with_link(level, &caps[2], &mut id_counter, filepath)
|
||||
})
|
||||
.into_owned()
|
||||
wrap_header_with_link(level, &caps[2], &mut id_counter, filepath)
|
||||
})
|
||||
.into_owned()
|
||||
}
|
||||
|
||||
/// Wraps a single header tag with a link, making sure each tag gets its own
|
||||
/// unique ID by appending an auto-incremented number (if necessary).
|
||||
fn wrap_header_with_link(level: usize,
|
||||
content: &str,
|
||||
id_counter: &mut HashMap<String, usize>,
|
||||
filepath: &str)
|
||||
-> String {
|
||||
fn wrap_header_with_link(
|
||||
level: usize,
|
||||
content: &str,
|
||||
id_counter: &mut HashMap<String, usize>,
|
||||
filepath: &str,
|
||||
) -> String {
|
||||
let raw_id = utils::id_from_content(content);
|
||||
|
||||
let id_count = id_counter.entry(raw_id.clone()).or_insert(0);
|
||||
@@ -496,21 +545,23 @@ fn wrap_header_with_link(level: usize,
|
||||
// that in a very inelegant way
|
||||
fn fix_anchor_links(html: &str, filepath: &str) -> String {
|
||||
let regex = Regex::new(r##"<a([^>]+)href="#([^"]+)"([^>]*)>"##).unwrap();
|
||||
regex.replace_all(html, |caps: &Captures| {
|
||||
let before = &caps[1];
|
||||
let anchor = &caps[2];
|
||||
let after = &caps[3];
|
||||
regex
|
||||
.replace_all(html, |caps: &Captures| {
|
||||
let before = &caps[1];
|
||||
let anchor = &caps[2];
|
||||
let after = &caps[3];
|
||||
|
||||
format!("<a{before}href=\"{filepath}#{anchor}\"{after}>",
|
||||
format!(
|
||||
"<a{before}href=\"{filepath}#{anchor}\"{after}>",
|
||||
before = before,
|
||||
filepath = filepath,
|
||||
anchor = anchor,
|
||||
after = after)
|
||||
})
|
||||
.into_owned()
|
||||
after = after
|
||||
)
|
||||
})
|
||||
.into_owned()
|
||||
}
|
||||
|
||||
|
||||
// The rust book uses annotations for rustdoc to test code snippets,
|
||||
// like the following:
|
||||
// ```rust,should_panic
|
||||
@@ -521,51 +572,54 @@ fn fix_anchor_links(html: &str, filepath: &str) -> String {
|
||||
// This function replaces all commas by spaces in the code block classes
|
||||
fn fix_code_blocks(html: &str) -> String {
|
||||
let regex = Regex::new(r##"<code([^>]+)class="([^"]+)"([^>]*)>"##).unwrap();
|
||||
regex.replace_all(html, |caps: &Captures| {
|
||||
let before = &caps[1];
|
||||
let classes = &caps[2].replace(",", " ");
|
||||
let after = &caps[3];
|
||||
regex
|
||||
.replace_all(html, |caps: &Captures| {
|
||||
let before = &caps[1];
|
||||
let classes = &caps[2].replace(",", " ");
|
||||
let after = &caps[3];
|
||||
|
||||
format!(r#"<code{before}class="{classes}"{after}>"#,
|
||||
format!(
|
||||
r#"<code{before}class="{classes}"{after}>"#,
|
||||
before = before,
|
||||
classes = classes,
|
||||
after = after)
|
||||
}).into_owned()
|
||||
after = after
|
||||
)
|
||||
})
|
||||
.into_owned()
|
||||
}
|
||||
|
||||
fn add_playpen_pre(html: &str, playpen_config: &Playpen) -> String {
|
||||
let regex = Regex::new(r##"((?s)<code[^>]?class="([^"]+)".*?>(.*?)</code>)"##).unwrap();
|
||||
regex.replace_all(html, |caps: &Captures| {
|
||||
let text = &caps[1];
|
||||
let classes = &caps[2];
|
||||
let code = &caps[3];
|
||||
regex
|
||||
.replace_all(html, |caps: &Captures| {
|
||||
let text = &caps[1];
|
||||
let classes = &caps[2];
|
||||
let code = &caps[3];
|
||||
|
||||
if (classes.contains("language-rust") && !classes.contains("ignore")) ||
|
||||
classes.contains("mdbook-runnable")
|
||||
{
|
||||
// wrap the contents in an external pre block
|
||||
if playpen_config.editable && classes.contains("editable") ||
|
||||
text.contains("fn main") || text.contains("quick_main!")
|
||||
if (classes.contains("language-rust") && !classes.contains("ignore"))
|
||||
|| classes.contains("mdbook-runnable")
|
||||
{
|
||||
format!("<pre class=\"playpen\">{}</pre>", text)
|
||||
} else {
|
||||
// we need to inject our own main
|
||||
let (attrs, code) = partition_source(code);
|
||||
// wrap the contents in an external pre block
|
||||
if playpen_config.editable && classes.contains("editable")
|
||||
|| text.contains("fn main") || text.contains("quick_main!")
|
||||
{
|
||||
format!("<pre class=\"playpen\">{}</pre>", text)
|
||||
} else {
|
||||
// we need to inject our own main
|
||||
let (attrs, code) = partition_source(code);
|
||||
|
||||
format!("<pre class=\"playpen\"><code class=\"{}\">\n# \
|
||||
#![allow(unused_variables)]\n\
|
||||
{}#fn main() {{\n\
|
||||
{}\
|
||||
#}}</code></pre>",
|
||||
classes,
|
||||
attrs,
|
||||
code)
|
||||
format!(
|
||||
"<pre class=\"playpen\"><code class=\"{}\">\n# \
|
||||
#![allow(unused_variables)]\n{}#fn main() {{\n{}#}}</code></pre>",
|
||||
classes, attrs, code
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// not language-rust, so no-op
|
||||
text.to_owned()
|
||||
}
|
||||
} else {
|
||||
// not language-rust, so no-op
|
||||
text.to_owned()
|
||||
}
|
||||
}).into_owned()
|
||||
})
|
||||
.into_owned()
|
||||
}
|
||||
|
||||
fn partition_source(s: &str) -> (String, String) {
|
||||
|
||||
@@ -157,64 +157,58 @@ fn render_item(
|
||||
/// Using a JS script is a workaround for CORS in `file://` URIs. It also removes the need for
|
||||
/// downloading/parsing JSON in JS.
|
||||
fn write_to_js(index: Index, search_config: &Search) -> Result<String> {
|
||||
// These structs mirror the configuration javascript object accepted by
|
||||
// http://elasticlunr.com/docs/configuration.js.html
|
||||
use std::collections::BTreeMap;
|
||||
use self::elasticlunr::config::{SearchBool, SearchOptions, SearchOptionsField};
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct SearchOptionsField {
|
||||
boost: u8,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct SearchOptionsFields {
|
||||
title: SearchOptionsField,
|
||||
body: SearchOptionsField,
|
||||
breadcrumbs: SearchOptionsField,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct SearchOptions {
|
||||
bool: String,
|
||||
expand: bool,
|
||||
struct ResultsOptions {
|
||||
limit_results: u32,
|
||||
teaser_word_count: u32,
|
||||
fields: SearchOptionsFields,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct SearchindexJson {
|
||||
/// The options used for displaying search results
|
||||
resultsoptions: ResultsOptions,
|
||||
/// The searchoptions for elasticlunr.js
|
||||
searchoptions: SearchOptions,
|
||||
/// The index for elasticlunr.js
|
||||
index: elasticlunr::Index,
|
||||
}
|
||||
|
||||
let mut fields = BTreeMap::new();
|
||||
let mut opt = SearchOptionsField::default();
|
||||
opt.boost = Some(search_config.boost_title);
|
||||
fields.insert("title".into(), opt);
|
||||
opt.boost = Some(search_config.boost_paragraph);
|
||||
fields.insert("body".into(), opt);
|
||||
opt.boost = Some(search_config.boost_hierarchy);
|
||||
fields.insert("breadcrumbs".into(), opt);
|
||||
|
||||
let searchoptions = SearchOptions {
|
||||
bool: if search_config.use_boolean_and {
|
||||
"AND".into()
|
||||
SearchBool::And
|
||||
} else {
|
||||
"OR".into()
|
||||
SearchBool::Or
|
||||
},
|
||||
expand: search_config.expand,
|
||||
fields,
|
||||
};
|
||||
|
||||
let resultsoptions = ResultsOptions {
|
||||
limit_results: search_config.limit_results,
|
||||
teaser_word_count: search_config.teaser_word_count,
|
||||
fields: SearchOptionsFields {
|
||||
title: SearchOptionsField {
|
||||
boost: search_config.boost_title,
|
||||
},
|
||||
body: SearchOptionsField {
|
||||
boost: search_config.boost_paragraph,
|
||||
},
|
||||
breadcrumbs: SearchOptionsField {
|
||||
boost: search_config.boost_hierarchy,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let json_contents = SearchindexJson {
|
||||
searchoptions: searchoptions,
|
||||
index: index,
|
||||
resultsoptions,
|
||||
searchoptions,
|
||||
index,
|
||||
};
|
||||
|
||||
// By converting to serde_json::Value as an intermediary, we use a
|
||||
// BTreeMap internally and can force a stable ordering of map keys.
|
||||
let json_contents = serde_json::to_value(&json_contents)?;
|
||||
let json_contents = serde_json::to_string(&json_contents)?;
|
||||
|
||||
Ok(format!("window.search = {};", json_contents))
|
||||
@@ -222,7 +216,7 @@ fn write_to_js(index: Index, search_config: &Search) -> Result<String> {
|
||||
|
||||
fn clean_html(html: &str) -> String {
|
||||
lazy_static! {
|
||||
static ref AMMONIA: ammonia::Builder<'static> = {
|
||||
static ref AMMONIA: ammonia::Builder<'static> = {
|
||||
let mut clean_content = HashSet::new();
|
||||
clean_content.insert("script");
|
||||
clean_content.insert("style");
|
||||
|
||||
|
Before Width: | Height: | Size: 348 KiB After Width: | Height: | Size: 348 KiB |
@@ -81,11 +81,13 @@ table thead td {
|
||||
box-sizing: border-box;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overscroll-behavior-y: contain;
|
||||
-webkit-transition: -webkit-transform 0.5s;
|
||||
-moz-transition: -moz-transform 0.5s;
|
||||
-o-transition: -o-transform 0.5s;
|
||||
-ms-transition: -ms-transform 0.5s;
|
||||
transition: transform 0.5s;
|
||||
}
|
||||
.js .sidebar {
|
||||
-webkit-transition: -webkit-transform 0.3s;
|
||||
-moz-transition: -moz-transform 0.3s;
|
||||
-o-transition: -o-transform 0.3s;
|
||||
-ms-transition: -ms-transform 0.3s;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
.sidebar code {
|
||||
line-height: 2em;
|
||||
@@ -137,42 +139,44 @@ table thead td {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.page-wrapper {
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-webkit-transition: padding-left 0.5s, margin-left 0.5s, left 0.5s;
|
||||
-moz-transition: padding-left 0.5s, margin-left 0.5s, left 0.5s;
|
||||
-o-transition: padding-left 0.5s, margin-left 0.5s, left 0.5s;
|
||||
-ms-transition: padding-left 0.5s, margin-left 0.5s, left 0.5s;
|
||||
transition: padding-left 0.5s, margin-left 0.5s, left 0.5s;
|
||||
}
|
||||
.js .page-wrapper {
|
||||
-webkit-transition: margin-left 0.3s ease, -webkit-transform 0.3s ease;
|
||||
-moz-transition: margin-left 0.3s ease, -moz-transform 0.3s ease;
|
||||
-o-transition: margin-left 0.3s ease, -o-transform 0.3s ease;
|
||||
-ms-transition: margin-left 0.3s ease, -ms-transform 0.3s ease;
|
||||
transition: margin-left 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
.sidebar-visible .page-wrapper {
|
||||
left: 300px;
|
||||
-webkit-transform: translateX(300px);
|
||||
-moz-transform: translateX(300px);
|
||||
-o-transform: translateX(300px);
|
||||
-ms-transform: translateX(300px);
|
||||
transform: translateX(300px);
|
||||
}
|
||||
@media only screen and (min-width: 620px) {
|
||||
.sidebar-visible .page-wrapper {
|
||||
-webkit-transform: none;
|
||||
-moz-transform: none;
|
||||
-o-transform: none;
|
||||
-ms-transform: none;
|
||||
transform: none;
|
||||
margin-left: 300px;
|
||||
}
|
||||
}
|
||||
.page {
|
||||
outline: 0;
|
||||
padding: 0 15px;
|
||||
}
|
||||
.content {
|
||||
position: relative;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
overflow-y: auto;
|
||||
right: 0;
|
||||
left: 0;
|
||||
padding: 0 15px;
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
.sidebar-visible .content {
|
||||
position: absolute;
|
||||
top: 52px;
|
||||
}
|
||||
.content > main {
|
||||
.content main {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 750px;
|
||||
@@ -205,11 +209,13 @@ table thead td {
|
||||
-webkit-flex-wrap: wrap;
|
||||
-ms-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
-webkit-transition: -webkit-transform 0.5s, border-bottom-color 0.5s;
|
||||
-moz-transition: -moz-transform 0.5s, border-bottom-color 0.5s;
|
||||
-o-transition: -o-transform 0.5s, border-bottom-color 0.5s;
|
||||
-ms-transition: -ms-transform 0.5s, border-bottom-color 0.5s;
|
||||
transition: transform 0.5s, border-bottom-color 0.5s;
|
||||
}
|
||||
.js #menu-bar > #menu-bar-sticky-container {
|
||||
-webkit-transition: -webkit-transform 0.3s;
|
||||
-moz-transition: -moz-transform 0.3s;
|
||||
-o-transition: -o-transform 0.3s;
|
||||
-ms-transition: -ms-transform 0.3s;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
#menu-bar i,
|
||||
#menu-bar .icon-button {
|
||||
@@ -217,15 +223,15 @@ table thead td {
|
||||
margin: 0 10px;
|
||||
z-index: 10;
|
||||
line-height: 50px;
|
||||
cursor: pointer;
|
||||
-webkit-transition: color 0.5s;
|
||||
-moz-transition: color 0.5s;
|
||||
-o-transition: color 0.5s;
|
||||
-ms-transition: color 0.5s;
|
||||
transition: color 0.5s;
|
||||
}
|
||||
#menu-bar i:hover,
|
||||
#menu-bar .icon-button:hover {
|
||||
cursor: pointer;
|
||||
#menu-bar #print-button {
|
||||
margin: 0 15px;
|
||||
}
|
||||
html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-container {
|
||||
-webkit-transform: translateY(-60px);
|
||||
@@ -234,6 +240,9 @@ html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-conta
|
||||
-ms-transform: translateY(-60px);
|
||||
transform: translateY(-60px);
|
||||
}
|
||||
.no-js .left-buttons {
|
||||
display: none;
|
||||
}
|
||||
.menu-title {
|
||||
display: inline-block;
|
||||
font-weight: 200;
|
||||
@@ -252,6 +261,8 @@ html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-conta
|
||||
overflow: hidden;
|
||||
-o-text-overflow: ellipsis;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.js .menu-title {
|
||||
cursor: pointer;
|
||||
}
|
||||
.nav-chapters {
|
||||
@@ -363,7 +374,6 @@ html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-conta
|
||||
.light .content .header:link,
|
||||
.light .content .header:visited {
|
||||
color: #333;
|
||||
pointer: cursor;
|
||||
}
|
||||
.light .content .header:link:hover,
|
||||
.light .content .header:visited:hover {
|
||||
@@ -554,7 +564,6 @@ html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-conta
|
||||
.coal .content .header:link,
|
||||
.coal .content .header:visited {
|
||||
color: #98a3ad;
|
||||
pointer: cursor;
|
||||
}
|
||||
.coal .content .header:link:hover,
|
||||
.coal .content .header:visited:hover {
|
||||
@@ -745,7 +754,6 @@ html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-conta
|
||||
.navy .content .header:link,
|
||||
.navy .content .header:visited {
|
||||
color: #bcbdd0;
|
||||
pointer: cursor;
|
||||
}
|
||||
.navy .content .header:link:hover,
|
||||
.navy .content .header:visited:hover {
|
||||
@@ -936,7 +944,6 @@ html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-conta
|
||||
.rust .content .header:link,
|
||||
.rust .content .header:visited {
|
||||
color: #262625;
|
||||
pointer: cursor;
|
||||
}
|
||||
.rust .content .header:link:hover,
|
||||
.rust .content .header:visited:hover {
|
||||
@@ -1127,7 +1134,6 @@ html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-conta
|
||||
.ayu .content .header:link,
|
||||
.ayu .content .header:visited {
|
||||
color: #c5c5c5;
|
||||
pointer: cursor;
|
||||
}
|
||||
.ayu .content .header:link:hover,
|
||||
.ayu .content .header:visited:hover {
|
||||
@@ -1316,12 +1322,14 @@ html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-conta
|
||||
.mobile-nav-chapters {
|
||||
display: none;
|
||||
}
|
||||
#page-wrapper {
|
||||
left: 0;
|
||||
overflow-y: initial;
|
||||
}
|
||||
#page-wrapper.page-wrapper {
|
||||
padding-left: 0px;
|
||||
-webkit-transform: none;
|
||||
-moz-transform: none;
|
||||
-o-transform: none;
|
||||
-ms-transform: none;
|
||||
transform: none;
|
||||
margin-left: 0px;
|
||||
overflow-y: initial;
|
||||
}
|
||||
#content {
|
||||
max-width: none;
|
||||
@@ -1355,16 +1363,14 @@ html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-conta
|
||||
h6 {
|
||||
page-break-inside: avoid;
|
||||
page-break-after: avoid;
|
||||
/*break-after: avoid*/
|
||||
}
|
||||
pre,
|
||||
code {
|
||||
page-break-inside: avoid;
|
||||
white-space: pre-wrap /* CSS 3 */;
|
||||
white-space: -moz-pre-wrap /* Mozilla, since 1999 */;
|
||||
white-space: -pre-wrap /* Opera 4-6 */;
|
||||
white-space: -o-pre-wrap /* Opera 7 */;
|
||||
word-wrap: break-word /* Internet Explorer 5.5+ */;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.fa {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
.tooltiptext {
|
||||
@@ -1401,18 +1407,18 @@ mark {
|
||||
-o-transition: background-color 300ms linear;
|
||||
-ms-transition: background-color 300ms linear;
|
||||
transition: background-color 300ms linear;
|
||||
cursor: pointer;
|
||||
}
|
||||
.fade-out {
|
||||
mark.fade-out {
|
||||
background-color: rgba(0,0,0,0) !important;
|
||||
cursor: auto;
|
||||
}
|
||||
.searchbar-outer {
|
||||
display: none;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 750px;
|
||||
}
|
||||
#searchbar {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: 5px auto 0px auto;
|
||||
padding: 10px 16px;
|
||||
@@ -1428,7 +1434,6 @@ mark {
|
||||
padding: 18px 0 0 5px;
|
||||
}
|
||||
.searchresults-outer {
|
||||
display: none;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 750px;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
// Fix back button cache problem
|
||||
window.onunload = function () { };
|
||||
|
||||
@@ -55,6 +57,7 @@ function playpen_text(playpen) {
|
||||
var txt = playpen_text(pre_block);
|
||||
var re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g;
|
||||
var snippet_crates = [];
|
||||
var item;
|
||||
while (item = re.exec(txt)) {
|
||||
snippet_crates.push(item[1]);
|
||||
}
|
||||
@@ -175,7 +178,7 @@ function playpen_text(playpen) {
|
||||
buttons.innerHTML = "<button class=\"fa fa-expand\" title=\"Show hidden lines\" aria-label=\"Show hidden lines\"></button>";
|
||||
|
||||
// add expand button
|
||||
pre_block.prepend(buttons);
|
||||
pre_block.insertBefore(buttons, pre_block.firstChild);
|
||||
|
||||
pre_block.querySelector('.buttons').addEventListener('click', function (e) {
|
||||
if (e.target.classList.contains('fa-expand')) {
|
||||
@@ -213,7 +216,7 @@ function playpen_text(playpen) {
|
||||
if (!buttons) {
|
||||
buttons = document.createElement('div');
|
||||
buttons.className = 'buttons';
|
||||
pre_block.prepend(buttons);
|
||||
pre_block.insertBefore(buttons, pre_block.firstChild);
|
||||
}
|
||||
|
||||
var clipButton = document.createElement('button');
|
||||
@@ -222,7 +225,7 @@ function playpen_text(playpen) {
|
||||
clipButton.setAttribute('aria-label', clipButton.title);
|
||||
clipButton.innerHTML = '<i class=\"tooltiptext\"></i>';
|
||||
|
||||
buttons.prepend(clipButton);
|
||||
buttons.insertBefore(clipButton, buttons.firstChild);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -233,7 +236,7 @@ function playpen_text(playpen) {
|
||||
if (!buttons) {
|
||||
buttons = document.createElement('div');
|
||||
buttons.className = 'buttons';
|
||||
pre_block.prepend(buttons);
|
||||
pre_block.insertBefore(buttons, pre_block.firstChild);
|
||||
}
|
||||
|
||||
var runCodeButton = document.createElement('button');
|
||||
@@ -248,8 +251,8 @@ function playpen_text(playpen) {
|
||||
copyCodeClipboardButton.title = 'Copy to clipboard';
|
||||
copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title);
|
||||
|
||||
buttons.prepend(runCodeButton);
|
||||
buttons.prepend(copyCodeClipboardButton);
|
||||
buttons.insertBefore(runCodeButton, buttons.firstChild);
|
||||
buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild);
|
||||
|
||||
runCodeButton.addEventListener('click', function (e) {
|
||||
run_rust_code(pre_block);
|
||||
@@ -262,7 +265,7 @@ function playpen_text(playpen) {
|
||||
undoChangesButton.title = 'Undo changes';
|
||||
undoChangesButton.setAttribute('aria-label', undoChangesButton.title);
|
||||
|
||||
buttons.prepend(undoChangesButton);
|
||||
buttons.insertBefore(undoChangesButton, buttons.firstChild);
|
||||
|
||||
undoChangesButton.addEventListener('click', function () {
|
||||
let editor = window.ace.edit(code_block);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="{{ language }}" class="sidebar-visible">
|
||||
<html lang="{{ language }}" class="sidebar-visible no-js">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
@@ -18,38 +18,22 @@
|
||||
<link rel="shortcut icon" href="{{ favicon }}">
|
||||
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
|
||||
|
||||
<link rel="stylesheet" href="highlight.css">
|
||||
<link rel="stylesheet" href="tomorrow-night.css">
|
||||
<link rel="stylesheet" href="ayu-highlight.css">
|
||||
|
||||
<!-- Custom theme -->
|
||||
<!-- Custom theme stylesheets -->
|
||||
{{#each additional_css}}
|
||||
<link rel="stylesheet" href="{{this}}">
|
||||
{{/each}}
|
||||
|
||||
{{#if mathjax_support}}
|
||||
<!-- MathJax -->
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
|
||||
<script async type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
|
||||
{{/if}}
|
||||
|
||||
<!-- Fetch Clipboard.js from CDN but have a local fallback -->
|
||||
<script src="https://cdn.jsdelivr.net/clipboard.js/1.6.1/clipboard.min.js"></script>
|
||||
<script>
|
||||
if (typeof Clipboard == 'undefined') {
|
||||
document.write(unescape("%3Cscript src='clipboard.min.js'%3E%3C/script%3E"));
|
||||
}
|
||||
</script>
|
||||
|
||||
<noscript>
|
||||
<style type="text/css">
|
||||
.javascript-only {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</noscript>
|
||||
|
||||
</head>
|
||||
<body class="light">
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
@@ -74,7 +58,7 @@
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = 'light'; }
|
||||
document.body.className = theme;
|
||||
document.querySelector('html').className = theme;
|
||||
document.querySelector('html').className = theme + ' js';
|
||||
</script>
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
@@ -99,7 +83,7 @@
|
||||
{{> header}}
|
||||
<div id="menu-bar" class="menu-bar">
|
||||
<div id="menu-bar-sticky-container">
|
||||
<div class="left-buttons javascript-only">
|
||||
<div class="left-buttons">
|
||||
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</button>
|
||||
@@ -131,13 +115,15 @@
|
||||
</div>
|
||||
|
||||
{{#if search_enabled}}
|
||||
<div id="searchbar-outer" class="searchbar-outer">
|
||||
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||||
</div>
|
||||
<div id="searchresults-outer" class="searchresults-outer">
|
||||
<div class="searchresults-header" id="searchresults-header"></div>
|
||||
<ul id="searchresults">
|
||||
</ul>
|
||||
<div id="search-wrapper" class="hidden">
|
||||
<form id="searchbar-outer" class="searchbar-outer">
|
||||
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||||
</form>
|
||||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||||
<div id="searchresults-header" class="searchresults-header"></div>
|
||||
<ul id="searchresults">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
@@ -190,18 +176,6 @@
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Local fallback for Font Awesome -->
|
||||
<script>
|
||||
if (getComputedStyle(document.querySelector(".fa")).fontFamily !== "FontAwesome") {
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.type = 'text/css';
|
||||
link.href = '_FontAwesome/css/font-awesome.css';
|
||||
document.head.insertBefore(link, document.head.firstChild)
|
||||
}
|
||||
</script>
|
||||
|
||||
{{#if livereload}}
|
||||
<!-- Livereload script (if served using the cli tool) -->
|
||||
<script type="text/javascript">
|
||||
@@ -221,7 +195,7 @@
|
||||
|
||||
{{#if google_analytics}}
|
||||
<!-- Google Analytics Tag -->
|
||||
<script>
|
||||
<script type="text/javascript">
|
||||
var localAddrs = ["localhost", "127.0.0.1", ""];
|
||||
|
||||
// make sure we don't activate google analytics if the developer is
|
||||
@@ -255,21 +229,32 @@
|
||||
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
|
||||
{{/if}}
|
||||
|
||||
{{#if is_print}}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
window.print();
|
||||
})
|
||||
</script>
|
||||
{{/if}}
|
||||
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="book.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
<script src="highlight.js"></script>
|
||||
<script src="book.js"></script>
|
||||
|
||||
<!-- Custom JS script -->
|
||||
<!-- Custom JS scripts -->
|
||||
{{#each additional_js}}
|
||||
<script type="text/javascript" src="{{this}}"></script>
|
||||
{{/each}}
|
||||
|
||||
{{#if is_print}}
|
||||
{{#if mathjax_support}}
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('load', function() {
|
||||
MathJax.Hub.Register.StartupHook('End', function() {
|
||||
window.setTimeout(window.print, 100);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{{else}}
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('load', function() {
|
||||
window.setTimeout(window.print, 100);
|
||||
});
|
||||
</script>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -21,22 +21,22 @@ pub static TOMORROW_NIGHT_CSS: &'static [u8] = include_bytes!("tomorrow-night.cs
|
||||
pub static HIGHLIGHT_CSS: &'static [u8] = include_bytes!("highlight.css");
|
||||
pub static AYU_HIGHLIGHT_CSS: &'static [u8] = include_bytes!("ayu-highlight.css");
|
||||
pub static CLIPBOARD_JS: &'static [u8] = include_bytes!("clipboard.min.js");
|
||||
pub static FONT_AWESOME: &'static [u8] = include_bytes!("_FontAwesome/css/font-awesome.min.css");
|
||||
pub static FONT_AWESOME: &'static [u8] = include_bytes!("FontAwesome/css/font-awesome.min.css");
|
||||
pub static FONT_AWESOME_EOT: &'static [u8] =
|
||||
include_bytes!("_FontAwesome/fonts/fontawesome-webfont.eot");
|
||||
include_bytes!("FontAwesome/fonts/fontawesome-webfont.eot");
|
||||
pub static FONT_AWESOME_SVG: &'static [u8] =
|
||||
include_bytes!("_FontAwesome/fonts/fontawesome-webfont.svg");
|
||||
include_bytes!("FontAwesome/fonts/fontawesome-webfont.svg");
|
||||
pub static FONT_AWESOME_TTF: &'static [u8] =
|
||||
include_bytes!("_FontAwesome/fonts/fontawesome-webfont.ttf");
|
||||
include_bytes!("FontAwesome/fonts/fontawesome-webfont.ttf");
|
||||
pub static FONT_AWESOME_WOFF: &'static [u8] =
|
||||
include_bytes!("_FontAwesome/fonts/fontawesome-webfont.woff");
|
||||
include_bytes!("FontAwesome/fonts/fontawesome-webfont.woff");
|
||||
pub static FONT_AWESOME_WOFF2: &'static [u8] =
|
||||
include_bytes!("_FontAwesome/fonts/fontawesome-webfont.woff2");
|
||||
pub static FONT_AWESOME_OTF: &'static [u8] = include_bytes!("_FontAwesome/fonts/FontAwesome.otf");
|
||||
include_bytes!("FontAwesome/fonts/fontawesome-webfont.woff2");
|
||||
pub static FONT_AWESOME_OTF: &'static [u8] = include_bytes!("FontAwesome/fonts/FontAwesome.otf");
|
||||
|
||||
|
||||
/// The `Theme` struct should be used instead of the static variables because
|
||||
/// the `new()` method will look if the user has a theme directory in his
|
||||
/// the `new()` method will look if the user has a theme directory in their
|
||||
/// source folder and use the users theme instead of the default.
|
||||
///
|
||||
/// You should only ever use the static variables directly if you want to
|
||||
@@ -134,7 +134,7 @@ fn load_file_contents<P: AsRef<Path>>(filename: P, dest: &mut Vec<u8>) -> Result
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempdir::TempDir;
|
||||
use tempfile::Builder as TempFileBuilder;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
@@ -159,7 +159,7 @@ mod tests {
|
||||
.map(|f| f.path())
|
||||
.filter(|p| p.is_file() && !p.ends_with(".rs"));
|
||||
|
||||
let temp = TempDir::new("mdbook").unwrap();
|
||||
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
|
||||
|
||||
// "touch" all of the special files so we have empty copies
|
||||
for special_file in special_files {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
"use strict";
|
||||
window.editors = [];
|
||||
(function(editors) {
|
||||
if (typeof(ace) === 'undefined' || !ace) {
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
"use strict";
|
||||
window.search = window.search || {};
|
||||
(function search(search) {
|
||||
// Search functionality
|
||||
//
|
||||
// You can use !hasFocus() to prevent keyhandling in your key
|
||||
// event handlers while the user is typing his search.
|
||||
// event handlers while the user is typing their search.
|
||||
|
||||
if (!Mark || !elasticlunr) {
|
||||
return;
|
||||
}
|
||||
|
||||
var searchbar = document.getElementById('searchbar'),
|
||||
var search_wrap = document.getElementById('search-wrapper'),
|
||||
searchbar = document.getElementById('searchbar'),
|
||||
searchbar_outer = document.getElementById('searchbar-outer'),
|
||||
searchresults = document.getElementById('searchresults'),
|
||||
searchresults_outer = document.getElementById('searchresults-outer'),
|
||||
@@ -18,11 +20,13 @@ window.search = window.search || {};
|
||||
content = document.getElementById('content'),
|
||||
|
||||
searchindex = null,
|
||||
resultsoptions = {
|
||||
teaser_word_count: 30,
|
||||
limit_results: 30,
|
||||
},
|
||||
searchoptions = {
|
||||
bool: "AND",
|
||||
expand: true,
|
||||
teaser_word_count: 30,
|
||||
limit_results: 30,
|
||||
fields: {
|
||||
title: {boost: 1},
|
||||
body: {boost: 1},
|
||||
@@ -185,7 +189,7 @@ window.search = window.search || {};
|
||||
}
|
||||
|
||||
var window_weight = [];
|
||||
var window_size = Math.min(weighted.length, searchoptions.teaser_word_count);
|
||||
var window_size = Math.min(weighted.length, resultsoptions.teaser_word_count);
|
||||
|
||||
var cur_sum = 0;
|
||||
for (var wordindex = 0; wordindex < window_size; wordindex++) {
|
||||
@@ -236,15 +240,19 @@ window.search = window.search || {};
|
||||
}
|
||||
|
||||
function init() {
|
||||
resultsoptions = window.search.resultsoptions;
|
||||
searchoptions = window.search.searchoptions;
|
||||
searchbar_outer = window.search.searchbar_outer;
|
||||
searchindex = elasticlunr.Index.load(window.search.index);
|
||||
|
||||
// Set up events
|
||||
searchicon.addEventListener('click', function(e) { searchIconClickHandler(); }, false);
|
||||
searchbar.addEventListener('keyup', function(e) { searchbarKeyUpHandler(); }, false);
|
||||
document.addEventListener('keydown', function (e) { globalKeyHandler(e); }, false);
|
||||
document.addEventListener('keydown', function(e) { globalKeyHandler(e); }, false);
|
||||
// If the user uses the browser buttons, do the same as if a reload happened
|
||||
window.onpopstate = function(e) { doSearchOrMarkFromUrl(); };
|
||||
// Suppress "submit" events so the page doesn't reload when the user presses Enter
|
||||
document.addEventListener('submit', function(e) { e.preventDefault(); }, false);
|
||||
|
||||
// If reloaded, do the search or mark again, depending on the current url parameters
|
||||
doSearchOrMarkFromUrl();
|
||||
@@ -294,90 +302,84 @@ window.search = window.search || {};
|
||||
|
||||
// Eventhandler for keyevents on `document`
|
||||
function globalKeyHandler(e) {
|
||||
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
|
||||
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.target.type === 'textarea') { return; }
|
||||
|
||||
if (e.keyCode == ESCAPE_KEYCODE) {
|
||||
if (e.keyCode === ESCAPE_KEYCODE) {
|
||||
e.preventDefault();
|
||||
searchbar.classList.remove("active");
|
||||
setSearchUrlParameters("",
|
||||
(searchbar.value.trim() != "") ? "push" : "replace");
|
||||
(searchbar.value.trim() !== "") ? "push" : "replace");
|
||||
if (hasFocus()) {
|
||||
unfocusSearchbar();
|
||||
}
|
||||
showSearch(false);
|
||||
marker.unmark();
|
||||
return;
|
||||
}
|
||||
if (!hasFocus() && e.keyCode == SEARCH_HOTKEY_KEYCODE) {
|
||||
} else if (!hasFocus() && e.keyCode === SEARCH_HOTKEY_KEYCODE) {
|
||||
e.preventDefault();
|
||||
showSearch(true);
|
||||
window.scrollTo(0, 0);
|
||||
searchbar.focus();
|
||||
return;
|
||||
}
|
||||
if (hasFocus() && e.keyCode == DOWN_KEYCODE) {
|
||||
searchbar.select();
|
||||
} else if (hasFocus() && e.keyCode === DOWN_KEYCODE) {
|
||||
e.preventDefault();
|
||||
unfocusSearchbar();
|
||||
searchresults.children('li').first().classList.add("focus");
|
||||
return;
|
||||
}
|
||||
if (!hasFocus() && (e.keyCode == DOWN_KEYCODE
|
||||
|| e.keyCode == UP_KEYCODE
|
||||
|| e.keyCode == SELECT_KEYCODE)) {
|
||||
searchresults.firstElementChild.classList.add("focus");
|
||||
} else if (!hasFocus() && (e.keyCode === DOWN_KEYCODE
|
||||
|| e.keyCode === UP_KEYCODE
|
||||
|| e.keyCode === SELECT_KEYCODE)) {
|
||||
// not `:focus` because browser does annoying scrolling
|
||||
var current_focus = search.searchresults.find("li.focus");
|
||||
if (current_focus.length == 0) return;
|
||||
var focused = searchresults.querySelector("li.focus");
|
||||
if (!focused) return;
|
||||
e.preventDefault();
|
||||
if (e.keyCode == DOWN_KEYCODE) {
|
||||
var next = current_focus.next()
|
||||
if (next.length > 0) {
|
||||
current_focus.classList.remove("focus");
|
||||
if (e.keyCode === DOWN_KEYCODE) {
|
||||
var next = focused.nextElementSibling;
|
||||
if (next) {
|
||||
focused.classList.remove("focus");
|
||||
next.classList.add("focus");
|
||||
}
|
||||
} else if (e.keyCode == UP_KEYCODE) {
|
||||
current_focus.classList.remove("focus");
|
||||
var prev = current_focus.prev();
|
||||
if (prev.length == 0) {
|
||||
searchbar.focus();
|
||||
} else {
|
||||
} else if (e.keyCode === UP_KEYCODE) {
|
||||
focused.classList.remove("focus");
|
||||
var prev = focused.previousElementSibling;
|
||||
if (prev) {
|
||||
prev.classList.add("focus");
|
||||
} else {
|
||||
searchbar.select();
|
||||
}
|
||||
} else {
|
||||
window.location = current_focus.children('a').attr('href');
|
||||
} else { // SELECT_KEYCODE
|
||||
window.location.assign(focused.querySelector('a'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showSearch(yes) {
|
||||
if (yes) {
|
||||
searchbar_outer.style.display = 'block';
|
||||
search_wrap.classList.remove('hidden');
|
||||
searchicon.setAttribute('aria-expanded', 'true');
|
||||
} else {
|
||||
searchbar_outer.style.display = 'none';
|
||||
searchresults_outer.style.display = 'none';
|
||||
searchbar.value = '';
|
||||
removeChildren(searchresults);
|
||||
search_wrap.classList.add('hidden');
|
||||
searchicon.setAttribute('aria-expanded', 'false');
|
||||
var results = searchresults.children;
|
||||
for (var i = 0; i < results.length; i++) {
|
||||
results[i].classList.remove("focus");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showResults(yes) {
|
||||
if (yes) {
|
||||
searchbar_outer.style.display = 'block';
|
||||
searchresults_outer.style.display = 'block';
|
||||
searchresults_outer.classList.remove('hidden');
|
||||
} else {
|
||||
searchresults_outer.style.display = 'none';
|
||||
searchresults_outer.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// Eventhandler for search icon
|
||||
function searchIconClickHandler() {
|
||||
if (searchbar_outer.style.display === 'block') {
|
||||
showSearch(false);
|
||||
} else {
|
||||
if (search_wrap.classList.contains('hidden')) {
|
||||
showSearch(true);
|
||||
window.scrollTo(0, 0);
|
||||
searchbar.focus();
|
||||
searchbar.select();
|
||||
} else {
|
||||
showSearch(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,7 +435,7 @@ window.search = window.search || {};
|
||||
|
||||
// Do the actual search
|
||||
var results = searchindex.search(searchterm, searchoptions);
|
||||
var resultcount = Math.min(results.length, searchoptions.limit_results);
|
||||
var resultcount = Math.min(results.length, resultsoptions.limit_results);
|
||||
|
||||
// Display search metrics
|
||||
searchresults_header.innerText = formatSearchMetric(resultcount, searchterm);
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
& > #menu-bar-sticky-container {
|
||||
display: flex
|
||||
flex-wrap: wrap
|
||||
transition: transform 0.5s, border-bottom-color 0.5s
|
||||
.js & {
|
||||
transition: transform 0.3s
|
||||
}
|
||||
}
|
||||
|
||||
i, .icon-button {
|
||||
@@ -15,18 +17,23 @@
|
||||
margin: 0 10px
|
||||
z-index: 10
|
||||
line-height: 50px
|
||||
|
||||
cursor: pointer
|
||||
transition: color 0.5s
|
||||
}
|
||||
|
||||
&:hover { cursor: pointer }
|
||||
#print-button {
|
||||
margin: 0 15px
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-container {
|
||||
html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-container {
|
||||
transform: translateY(-60px);
|
||||
}
|
||||
|
||||
.no-js .left-buttons {
|
||||
display: none
|
||||
}
|
||||
|
||||
.menu-title {
|
||||
display: inline-block
|
||||
font-weight: 200
|
||||
@@ -38,5 +45,7 @@ html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-conta
|
||||
white-space: nowrap
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
cursor: pointer;
|
||||
.js & {
|
||||
cursor: pointer
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,18 +2,20 @@
|
||||
|
||||
.page-wrapper {
|
||||
box-sizing: border-box
|
||||
left: 0
|
||||
position: absolute
|
||||
right: 0
|
||||
top: 0
|
||||
bottom: 0
|
||||
|
||||
// Animation: slide away
|
||||
transition: padding-left 0.5s, margin-left 0.5s, left 0.5s
|
||||
.js & {
|
||||
transition: margin-left 0.3s ease, transform 0.3s ease
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-visible .page-wrapper {
|
||||
left: $sidebar-width
|
||||
transform: translateX($sidebar-width)
|
||||
|
||||
@media only screen and (min-width: $sidebar-reflow-width) {
|
||||
transform: none
|
||||
margin-left: $sidebar-width
|
||||
}
|
||||
}
|
||||
|
||||
.page {
|
||||
@@ -22,12 +24,7 @@
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative
|
||||
top: 0
|
||||
bottom: 0
|
||||
overflow-y: auto
|
||||
right: 0
|
||||
left: 0
|
||||
padding: 0 15px
|
||||
padding-bottom: 50px
|
||||
|
||||
@@ -44,8 +41,3 @@
|
||||
|
||||
img { max-width: 100%; }
|
||||
}
|
||||
|
||||
.sidebar-visible .content {
|
||||
position: absolute
|
||||
top: 52px
|
||||
}
|
||||
|
||||
@@ -7,13 +7,10 @@
|
||||
display: none
|
||||
}
|
||||
|
||||
#page-wrapper {
|
||||
left: 0;
|
||||
overflow-y: initial;
|
||||
}
|
||||
|
||||
#page-wrapper.page-wrapper {
|
||||
padding-left: 0px;
|
||||
transform: none;
|
||||
margin-left: 0px;
|
||||
overflow-y: initial;
|
||||
}
|
||||
|
||||
#content {
|
||||
@@ -46,15 +43,14 @@
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
page-break-inside: avoid
|
||||
page-break-after: avoid
|
||||
/*break-after: avoid*/
|
||||
}
|
||||
|
||||
pre, code {
|
||||
page-break-inside: avoid
|
||||
white-space: pre-wrap /* CSS 3 */
|
||||
white-space: -moz-pre-wrap /* Mozilla, since 1999 */
|
||||
white-space: -pre-wrap /* Opera 4-6 */
|
||||
white-space: -o-pre-wrap /* Opera 7 */
|
||||
word-wrap: break-word /* Internet Explorer 5.5+ */
|
||||
white-space: pre-wrap
|
||||
}
|
||||
|
||||
.fa {
|
||||
display: none !important
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,21 +9,21 @@ mark {
|
||||
padding: 0 3px 1px 3px;
|
||||
margin: 0 -3px -1px -3px;
|
||||
transition: background-color 300ms linear;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.fade-out {
|
||||
background-color: rgba(0,0,0,0) !important
|
||||
mark.fade-out {
|
||||
background-color: rgba(0,0,0,0) !important;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.searchbar-outer {
|
||||
display: none;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: $content-max-width;
|
||||
}
|
||||
|
||||
#searchbar {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: 5px auto 0px auto;
|
||||
padding: 10px 16px;
|
||||
@@ -37,7 +37,6 @@ mark {
|
||||
}
|
||||
|
||||
.searchresults-outer {
|
||||
display: none;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: $content-max-width;
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
overscroll-behavior-y: contain;
|
||||
|
||||
// Animation: slide away
|
||||
transition: transform 0.5s
|
||||
.js & {
|
||||
transition: transform 0.3s
|
||||
}
|
||||
|
||||
code {
|
||||
line-height: 2em;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
.content .header:link, .content .header:visited {
|
||||
color: $fg;
|
||||
pointer: cursor;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
$sidebar-width = 300px
|
||||
$page-padding = 15px
|
||||
$content-max-width = 750px
|
||||
$content-min-width = 320px
|
||||
$page-plus-sidebar-width = $content-max-width + $sidebar-width + $page-padding * 2
|
||||
$sidebar-reflow-width = $sidebar-width + $content-min-width
|
||||
|
||||
@@ -193,14 +193,14 @@ pub fn copy_files_except_ext(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
extern crate tempdir;
|
||||
extern crate tempfile;
|
||||
|
||||
use super::copy_files_except_ext;
|
||||
use std::fs;
|
||||
|
||||
#[test]
|
||||
fn copy_files_except_ext_test() {
|
||||
let tmp = match tempdir::TempDir::new("") {
|
||||
let tmp = match tempfile::TempDir::new() {
|
||||
Ok(t) => t,
|
||||
Err(_) => panic!("Could not create a temp dir"),
|
||||
};
|
||||
|
||||
@@ -50,7 +50,7 @@ pub fn take_lines<R: RangeArgument<usize>>(s: &str, range: R) -> String {
|
||||
let start = *range.start().unwrap_or(&0);
|
||||
let mut lines = s.lines().skip(start);
|
||||
match range.end() {
|
||||
Some(&end) => lines.take(end.checked_sub(start).unwrap_or(0)).join("\n"),
|
||||
Some(&end) => lines.take(end.saturating_sub(start)).join("\n"),
|
||||
None => lines.join("\n"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
//! Integration tests to make sure alternate backends work.
|
||||
|
||||
extern crate mdbook;
|
||||
extern crate tempdir;
|
||||
extern crate tempfile;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
use std::path::Path;
|
||||
use tempdir::TempDir;
|
||||
use tempfile::{TempDir, Builder as TempFileBuilder};
|
||||
use mdbook::config::Config;
|
||||
use mdbook::MDBook;
|
||||
|
||||
@@ -55,7 +55,7 @@ fn backends_receive_render_context_via_stdin() {
|
||||
use std::fs::File;
|
||||
use mdbook::renderer::RenderContext;
|
||||
|
||||
let temp = TempDir::new("output").unwrap();
|
||||
let temp = TempFileBuilder::new().prefix("output").tempdir().unwrap();
|
||||
let out_file = temp.path().join("out.txt");
|
||||
let cmd = tee_command(&out_file);
|
||||
|
||||
@@ -70,7 +70,7 @@ fn backends_receive_render_context_via_stdin() {
|
||||
}
|
||||
|
||||
fn dummy_book_with_backend(name: &str, command: &str) -> (MDBook, TempDir) {
|
||||
let temp = TempDir::new("mdbook").unwrap();
|
||||
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
|
||||
|
||||
let mut config = Config::default();
|
||||
config
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Not all features are used in all test crates, so...
|
||||
#![allow(dead_code, unused_variables, unused_imports, unused_extern_crates)]
|
||||
extern crate mdbook;
|
||||
extern crate tempdir;
|
||||
extern crate tempfile;
|
||||
extern crate walkdir;
|
||||
|
||||
use std::path::Path;
|
||||
@@ -15,7 +15,7 @@ use mdbook::utils::fs::file_to_string;
|
||||
|
||||
// The funny `self::` here is because we've got an `extern crate ...` and are
|
||||
// in a submodule
|
||||
use self::tempdir::TempDir;
|
||||
use self::tempfile::{TempDir, Builder as TempFileBuilder};
|
||||
use self::mdbook::MDBook;
|
||||
use self::walkdir::WalkDir;
|
||||
|
||||
@@ -47,7 +47,7 @@ impl DummyBook {
|
||||
|
||||
/// Write a book to a temporary directory using the provided settings.
|
||||
pub fn build(&self) -> Result<TempDir> {
|
||||
let temp = TempDir::new("dummy_book").chain_err(|| "Unable to create temp directory")?;
|
||||
let temp = TempFileBuilder::new().prefix("dummy_book").tempdir().chain_err(|| "Unable to create temp directory")?;
|
||||
|
||||
let dummy_book_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/dummy_book");
|
||||
recursive_copy(&dummy_book_root, temp.path()).chain_err(|| {
|
||||
@@ -128,11 +128,11 @@ fn recursive_copy<A: AsRef<Path>, B: AsRef<Path>>(from: A, to: B) -> Result<()>
|
||||
}
|
||||
|
||||
pub fn new_copy_of_example_book() -> Result<TempDir> {
|
||||
let temp = TempDir::new("book-example")?;
|
||||
let temp = TempFileBuilder::new().prefix("book-example").tempdir()?;
|
||||
|
||||
let book_example = Path::new(env!("CARGO_MANIFEST_DIR")).join("book-example");
|
||||
|
||||
recursive_copy(book_example, temp.path())?;
|
||||
|
||||
Ok(temp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
- [First Chapter](first/index.md)
|
||||
- [Nested Chapter](first/nested.md)
|
||||
- [Includes](first/includes.md)
|
||||
- [Recursive](first/recursive.md)
|
||||
- [Second Chapter](second.md)
|
||||
|
||||
---
|
||||
|
||||
2
tests/dummy_book/src/first/recursive.md
Normal file
2
tests/dummy_book/src/first/recursive.md
Normal file
@@ -0,0 +1,2 @@
|
||||
Around the world, around the world
|
||||
{{#include recursive.md}}
|
||||
1
tests/dummy_book/src2/README.md
Normal file
1
tests/dummy_book/src2/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# Root README
|
||||
7
tests/dummy_book/src2/SUMMARY.md
Normal file
7
tests/dummy_book/src2/SUMMARY.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# This dummy book is for testing the conversion of README.md to index.html by IndexPreprocessor
|
||||
|
||||
[Root README](README.md)
|
||||
|
||||
- [1st README](first/README.md)
|
||||
- [2nd README](second/README.md)
|
||||
- [2nd index](second/index.md)
|
||||
1
tests/dummy_book/src2/first/README.md
Normal file
1
tests/dummy_book/src2/first/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# First README
|
||||
1
tests/dummy_book/src2/second/README.md
Normal file
1
tests/dummy_book/src2/second/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# Second README
|
||||
1
tests/dummy_book/src2/second/index.md
Normal file
1
tests/dummy_book/src2/second/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# Second index
|
||||
@@ -1,11 +1,11 @@
|
||||
extern crate mdbook;
|
||||
extern crate tempdir;
|
||||
extern crate tempfile;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::fs;
|
||||
use mdbook::MDBook;
|
||||
use mdbook::config::Config;
|
||||
use tempdir::TempDir;
|
||||
use tempfile::Builder as TempFileBuilder;
|
||||
|
||||
|
||||
/// Run `mdbook init` in an empty directory and make sure the default files
|
||||
@@ -14,7 +14,7 @@ use tempdir::TempDir;
|
||||
fn base_mdbook_init_should_create_default_content() {
|
||||
let created_files = vec!["book", "src", "src/SUMMARY.md", "src/chapter_1.md"];
|
||||
|
||||
let temp = TempDir::new("mdbook").unwrap();
|
||||
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
|
||||
for file in &created_files {
|
||||
assert!(!temp.path().join(file).exists());
|
||||
}
|
||||
@@ -34,7 +34,7 @@ fn base_mdbook_init_should_create_default_content() {
|
||||
fn run_mdbook_init_with_custom_book_and_src_locations() {
|
||||
let created_files = vec!["out", "in", "in/SUMMARY.md", "in/chapter_1.md"];
|
||||
|
||||
let temp = TempDir::new("mdbook").unwrap();
|
||||
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
|
||||
for file in &created_files {
|
||||
assert!(
|
||||
!temp.path().join(file).exists(),
|
||||
@@ -61,7 +61,7 @@ fn run_mdbook_init_with_custom_book_and_src_locations() {
|
||||
|
||||
#[test]
|
||||
fn book_toml_isnt_required() {
|
||||
let temp = TempDir::new("mdbook").unwrap();
|
||||
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
|
||||
let md = MDBook::init(temp.path()).build().unwrap();
|
||||
|
||||
let _ = fs::remove_file(temp.path().join("book.toml"));
|
||||
|
||||
@@ -2,7 +2,7 @@ extern crate mdbook;
|
||||
#[macro_use]
|
||||
extern crate pretty_assertions;
|
||||
extern crate select;
|
||||
extern crate tempdir;
|
||||
extern crate tempfile;
|
||||
extern crate walkdir;
|
||||
|
||||
mod dummy_book;
|
||||
@@ -16,9 +16,9 @@ use std::ffi::OsStr;
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
use select::document::Document;
|
||||
use select::predicate::{Class, Name, Predicate};
|
||||
use tempdir::TempDir;
|
||||
use tempfile::Builder as TempFileBuilder;
|
||||
use mdbook::errors::*;
|
||||
use mdbook::utils::fs::file_to_string;
|
||||
use mdbook::utils::fs::{file_to_string, write_file};
|
||||
use mdbook::config::Config;
|
||||
use mdbook::MDBook;
|
||||
|
||||
@@ -29,7 +29,7 @@ const TOC_TOP_LEVEL: &[&'static str] = &[
|
||||
"Conclusion",
|
||||
"Introduction",
|
||||
];
|
||||
const TOC_SECOND_LEVEL: &[&'static str] = &["1.1. Nested Chapter", "1.2. Includes"];
|
||||
const TOC_SECOND_LEVEL: &[&'static str] = &["1.1. Nested Chapter", "1.2. Includes", "1.3. Recursive"];
|
||||
|
||||
/// Make sure you can load the dummy book and build it without panicking.
|
||||
#[test]
|
||||
@@ -313,6 +313,20 @@ fn able_to_include_files_in_chapters() {
|
||||
assert_doesnt_contain_strings(&includes, &["{{#include ../SUMMARY.md::}}"]);
|
||||
}
|
||||
|
||||
/// Ensure cyclic includes are capped so that no exceptions occur
|
||||
#[test]
|
||||
fn recursive_includes_are_capped() {
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
let md = MDBook::load(temp.path()).unwrap();
|
||||
md.build().unwrap();
|
||||
|
||||
let recursive = temp.path().join("book/first/recursive.html");
|
||||
let content = &["Around the world, around the world
|
||||
Around the world, around the world
|
||||
Around the world, around the world"];
|
||||
assert_contains_strings(&recursive, content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn example_book_can_build() {
|
||||
let example_book_dir = dummy_book::new_copy_of_example_book().unwrap();
|
||||
@@ -324,7 +338,7 @@ fn example_book_can_build() {
|
||||
|
||||
#[test]
|
||||
fn book_with_a_reserved_filename_does_not_build() {
|
||||
let tmp_dir = TempDir::new("mdBook").unwrap();
|
||||
let tmp_dir = TempFileBuilder::new().prefix("mdBook").tempdir().unwrap();
|
||||
let src_path = tmp_dir.path().join("src");
|
||||
fs::create_dir(&src_path).unwrap();
|
||||
|
||||
@@ -340,6 +354,54 @@ fn book_with_a_reserved_filename_does_not_build() {
|
||||
assert!(got.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn by_default_mdbook_use_index_preprocessor_to_convert_readme_to_index() {
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
let mut cfg = Config::default();
|
||||
cfg.set("book.src", "src2").expect("Couldn't set config.book.src to \"src2\".");
|
||||
let md = MDBook::load_with_config(temp.path(), cfg).unwrap();
|
||||
md.build().unwrap();
|
||||
|
||||
let first_index = temp.path()
|
||||
.join("book")
|
||||
.join("first")
|
||||
.join("index.html");
|
||||
let expected_strings = vec![
|
||||
r#"href="first/index.html""#,
|
||||
r#"href="second/index.html""#,
|
||||
"First README",
|
||||
];
|
||||
assert_contains_strings(&first_index, &expected_strings);
|
||||
assert_doesnt_contain_strings(&first_index, &vec!["README.html"]);
|
||||
|
||||
let second_index = temp.path()
|
||||
.join("book")
|
||||
.join("second")
|
||||
.join("index.html");
|
||||
let unexpected_strings = vec![
|
||||
"Second README",
|
||||
];
|
||||
assert_doesnt_contain_strings(&second_index, &unexpected_strings);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn theme_dir_overrides_work_correctly() {
|
||||
let book_dir = dummy_book::new_copy_of_example_book().unwrap();
|
||||
let book_dir = book_dir.path();
|
||||
let theme_dir = book_dir.join("theme");
|
||||
|
||||
let mut index = ::mdbook::theme::INDEX.to_vec();
|
||||
index.extend_from_slice(b"\n<!-- This is a modified index.hbs! -->");
|
||||
|
||||
write_file(&theme_dir, "index.hbs", &index).unwrap();
|
||||
|
||||
let md = MDBook::load(book_dir).unwrap();
|
||||
md.build().unwrap();
|
||||
|
||||
let built_index = book_dir.join("book").join("index.html");
|
||||
dummy_book::assert_contains_strings(built_index, &["This is a modified index.hbs!"]);
|
||||
}
|
||||
|
||||
#[cfg(feature = "search")]
|
||||
mod search {
|
||||
extern crate serde_json;
|
||||
@@ -376,7 +438,7 @@ mod search {
|
||||
assert_eq!(docs["first/index.html#some-section"]["body"], "");
|
||||
assert_eq!(
|
||||
docs["first/includes.html#summary"]["body"],
|
||||
"Introduction First Chapter Nested Chapter Includes Second Chapter Conclusion"
|
||||
"Introduction First Chapter Nested Chapter Includes Recursive Second Chapter Conclusion"
|
||||
);
|
||||
assert_eq!(
|
||||
docs["first/includes.html#summary"]["breadcrumbs"],
|
||||
@@ -391,7 +453,7 @@ mod search {
|
||||
// Setting this to `true` may cause issues with `cargo watch`,
|
||||
// since it may not finish writing the fixture before the tests
|
||||
// are run again.
|
||||
const GENERATE_FIXTURE: bool = false;
|
||||
const GENERATE_FIXTURE: bool = true;
|
||||
|
||||
fn get_fixture() -> serde_json::Value {
|
||||
if GENERATE_FIXTURE {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"title": 1
|
||||
},
|
||||
"first/includes.html#summary": {
|
||||
"body": 9,
|
||||
"body": 10,
|
||||
"breadcrumbs": 3,
|
||||
"title": 1
|
||||
},
|
||||
@@ -62,7 +62,7 @@
|
||||
"title": "Includes"
|
||||
},
|
||||
"first/includes.html#summary": {
|
||||
"body": "Introduction First Chapter Nested Chapter Includes Second Chapter Conclusion",
|
||||
"body": "Introduction First Chapter Nested Chapter Includes Recursive Second Chapter Conclusion",
|
||||
"breadcrumbs": "First Chapter » Summary",
|
||||
"id": "first/includes.html#summary",
|
||||
"title": "Summary"
|
||||
@@ -742,6 +742,30 @@
|
||||
"r": {
|
||||
"df": 0,
|
||||
"docs": {},
|
||||
"e": {
|
||||
"c": {
|
||||
"df": 0,
|
||||
"docs": {},
|
||||
"u": {
|
||||
"df": 0,
|
||||
"docs": {},
|
||||
"r": {
|
||||
"df": 0,
|
||||
"docs": {},
|
||||
"s": {
|
||||
"df": 1,
|
||||
"docs": {
|
||||
"first/includes.html#summary": {
|
||||
"tf": 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"df": 0,
|
||||
"docs": {}
|
||||
},
|
||||
"u": {
|
||||
"df": 0,
|
||||
"docs": {},
|
||||
@@ -1630,6 +1654,30 @@
|
||||
"r": {
|
||||
"df": 0,
|
||||
"docs": {},
|
||||
"e": {
|
||||
"c": {
|
||||
"df": 0,
|
||||
"docs": {},
|
||||
"u": {
|
||||
"df": 0,
|
||||
"docs": {},
|
||||
"r": {
|
||||
"df": 0,
|
||||
"docs": {},
|
||||
"s": {
|
||||
"df": 1,
|
||||
"docs": {
|
||||
"first/includes.html#summary": {
|
||||
"tf": 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"df": 0,
|
||||
"docs": {}
|
||||
},
|
||||
"u": {
|
||||
"df": 0,
|
||||
"docs": {},
|
||||
@@ -2138,6 +2186,10 @@
|
||||
"ref": "id",
|
||||
"version": "0.9.5"
|
||||
},
|
||||
"resultsoptions": {
|
||||
"limit_results": 30,
|
||||
"teaser_word_count": 30
|
||||
},
|
||||
"searchoptions": {
|
||||
"bool": "OR",
|
||||
"expand": true,
|
||||
@@ -2151,8 +2203,6 @@
|
||||
"title": {
|
||||
"boost": 2
|
||||
}
|
||||
},
|
||||
"limit_results": 30,
|
||||
"teaser_word_count": 30
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user