Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom table of contents #964

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ Imports:
knitr (>= 1.22),
rmarkdown (>= 2.3.5),
xfun (>= 0.13),
tinytex (>= 0.12)
tinytex (>= 0.12),
stringr (>= 1.2.0)
Suggests:
htmlwidgets,
rstudioapi,
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export(tufte_html_book)
export(word_document2)
import(stats)
import(utils)
import(stringr)
importFrom(xfun,in_dir)
importFrom(xfun,read_utf8)
importFrom(xfun,same_path)
Expand Down
44 changes: 42 additions & 2 deletions R/gitbook.R
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ gitbook = function(
) {
html_document2 = function(..., extra_dependencies = list()) {
rmarkdown::html_document(
..., extra_dependencies = c(extra_dependencies, gitbook_dependency(table_css))
..., extra_dependencies = c(gitbook_dependency(table_css), extra_dependencies)
)
}
gb_config = config
Expand Down Expand Up @@ -174,7 +174,46 @@ gitbook_toc = function(x, cur, config) {
i2 = find_token(x, '<!--bookdown:toc2:end-->')
x[i1] = ''; x[i2] = ''
if (i2 - i1 < 2) return(x)

#Changed to allow for toc length to change over the course of the function
prior = x[1:i1]
toc = x[(i1 + 1):(i2 - 1)]
post = x[i2:length(x)]

if(is.character(rmarkdown:::yaml_load_file('_bookdown.yml')[['custom_toc']])){
toc_frame <- readRDS("toc.Rds")
#First add in divisions and headers
for(i in (1:nrow(toc_frame))){
if(toc_frame[i,6] == "HEADING"){
toc <- append(toc, paste('<li class="header">', toc_frame[i,4], '</li>', sep=""), i)
} else if(toc_frame[i,6] == "BREAK"){
toc <- append(toc, '<li class="divider"></li>', i)
}
}

#Check that the in length difference is due to the leading and trailing <ul> tags in toc
if(length(toc) - nrow(toc_frame) != 2){
warning("The toc file listed in _bookdown.yml has more elements than expected. \n
Potential causes include links to non-existent files or lines that are not one of: dividers, section headings, or pages. \n
Consider checking your toc file, or setting clean_book to FALSE and inspecting toc.Rds.")
}

#Now sort out custom page titles and indents
for(i in 1:(nrow(toc_frame))){
#Finds pandoc-generated title (from first H1) and replaces it with title specified in the toc_frame
if(!is.na(toc_frame[i,2])){
toc[i+1] <- str_replace(toc[i+1], gsub(".*>(.+)</a>.*", "\\1", toc[i+1]), paste(" ", toc_frame[i,2]))
}
#Adds in <ul> tags so subchapters work as per the tabs in the toc_frame file
diff <- toc_frame[i,5] - toc_frame[i-1,5]
if(i == 1) diff <- 0
if(diff == 1){
toc[i] <- gsub('</li>$', '<ul>', toc[i])
} else if(diff < 0){
toc[i] <- paste(toc[i], strrep("</li></ul>", times = diff/-1), sep = "")
}
}
}

# numbered sections
r = '^<li><a href="([^#]*)(#[^"]+)"><span class="toc-section-number">([.A-Z0-9]+)</span>(.+)(</a>.*)$'
Expand Down Expand Up @@ -222,7 +261,8 @@ gitbook_toc = function(x, cur, config) {
toc[n] = paste(c('<li class="divider"></li>', extra, toc[n]), collapse = '\n')
}
}
x[(i1 + 1):(i2 - 1)] = toc

x = c(prior, toc, post)
x
}

Expand Down
93 changes: 72 additions & 21 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -68,37 +68,88 @@ book_filename = function(config = load_config(), fallback = TRUE) {
}

source_files = function(format = NULL, config = load_config(), all = FALSE) {
subdir = config[['rmd_subdir']]; subdir_yes = isTRUE(subdir) || is.character(subdir)
# a list of Rmd chapters
files = list.files('.', '[.]Rmd$', ignore.case = TRUE)
# content in subdir if asked
subdir_files = unlist(mapply(
list.files,
if (is.character(subdir)) subdir else '.', '[.]Rmd$', ignore.case = TRUE,
recursive = subdir_yes, full.names = is.character(subdir), USE.NAMES = FALSE
))
subdir_files = setdiff(subdir_files, files)
files = c(files, subdir_files)
# if rmd_files is provided, use those files in addition to those under rmd_subdir
if (length(files2 <- config[['rmd_files']]) > 0) {
# users should specify 'docx' as the output format name for Word, but let's
# make 'word' an alias of 'docx' to avoid further confusion:
# https://stackoverflow.com/q/63678601/559676
if ('word' %in% names(files2) && identical(format, 'docx')) format = 'word'
if (is.list(files2)) files2 = if (all) unlist(files2) else files2[[format]]
# add those files to subdir content if any
files = if (subdir_yes) c(files2, subdir_files) else files2
toc_loc = config[['custom_toc']]
if(is.character(toc_loc)) {
files = toc_build(toc_loc)
} else {
subdir = config[['rmd_subdir']]; subdir_yes = isTRUE(subdir) || is.character(subdir)
# a list of Rmd chapters
files = list.files('.', '[.]Rmd$', ignore.case = TRUE)
# content in subdir if asked
subdir_files = unlist(mapply(
list.files,
if (is.character(subdir)) subdir else '.', '[.]Rmd$', ignore.case = TRUE,
recursive = subdir_yes, full.names = is.character(subdir), USE.NAMES = FALSE
))
subdir_files = setdiff(subdir_files, files)
files = c(files, subdir_files)
# if rmd_files is provided, use those files in addition to those under rmd_subdir
if (length(files2 <- config[['rmd_files']]) > 0) {
# users should specify 'docx' as the output format name for Word, but let's
# make 'word' an alias of 'docx' to avoid further confusion:
# https://stackoverflow.com/q/63678601/559676
if ('word' %in% names(files2) && identical(format, 'docx')) format = 'word'
if (is.list(files2)) files2 = if (all) unlist(files2) else files2[[format]]
# add those files to subdir content if any
files = if (subdir_yes) c(files2, subdir_files) else files2
}
}
# exclude files that start with _, and the merged file
files = files[grep('^[^_]', basename(files))]
files = setdiff(files, with_ext(book_filename(config), c('.md', '.Rmd')))
files = unique(gsub('^[.]/', '', files))
index = 'index' == with_ext(files, '')
# if there is a index.Rmd, put it in the beginning
#if there is an index.Rmd, put it in the beginning
if (any(index)) files = c(files[index], files[!index])
check_special_chars(files)
}


toc_build <- function(toc_loc){
toc <- readLines(toc_loc)
toc <- as.data.frame(toc[nchar(toc) > 0])
names(toc)[1] <- "base"
toc$title <- str_sub(str_extract(toc$base, "(\\[.*\\])"), 2, -2) #Couldn't work out how to regex match between-but-not-including square brackets, so cheated
toc$relref <- str_sub(str_extract(toc$base, "\\(.*\\)"), 2, -2)
toc$relref <- str_replace(toc$relref, "(^/)", "")
toc$heading <- str_extract(toc$base, "(?:^#+(\ |)).+") ##Matches headings including #s; can't do variable length look-behinds?
toc$heading <- sapply(str_split(toc$heading, "^#+(\ |)"), `[`, 2) #Drops the #s
toc$indent <- str_count(str_extract(toc$base, ".+?(?=\\*)"), pattern = "\\t")
toc$indent[is.na(toc$indent)] <- 0
toc$type <- apply(toc, 1, function(r){
if(str_detect(r[1], "(---)")){
"BREAK"
} else if(!is.na(r[4])){
"HEADING"
} else if(is.character(r[3])){
"PAGE"
}
})
toc$exists <- sapply(toc$relref, function(r){ #Check that files listed in toc actually exist
file.exists(r)
})

#Check for files in toc.md without associated files in directory, and for files in toc_loc without an entry in toc.md
#Flags but allows site to be built anyway
missing <- toc[with(toc, is.na(relref) == FALSE & exists == FALSE),]$relref
if(length(missing) > 0){
warning('These files are listed in the table of contents but cannot be found: \n',
lapply(missing, function(r) paste(r, "\n")))
}
missed <- list.files('.' , full.names = TRUE, pattern = "(?i)([.]Rmd$)|([.]md$)", recursive = TRUE)
missed <- str_replace(missed, "(\\.\\/)", "")
missed <- setdiff(missed, toc$relref)
missed <- missed[!str_detect(missed, pattern = "(stubs/)|(toc.md)")]
if(length(missed) > 0){
warning('These files found in the "', getwd(), '" directory or subdirectories, but are not listed in toc.md: \n',
lapply(missed, function(r) paste(r, "\n")))
}
saveRDS(toc, file = "toc.Rds")
#Returns to source_files only the files that are listed in the toc and exist
return(toc[toc$exists == TRUE,]$relref)

}

output_dirname = function(dir, config = load_config(), create = TRUE) {
if (is.null(dir)) {
dir2 = config[['output_dir']]
Expand Down
2 changes: 1 addition & 1 deletion inst/resources/gitbook/css/style.css

Large diffs are not rendered by default.