Skip to content

Commit

Permalink
Merge pull request #274 from carpentries/add-images-and-instructor-notes
Browse files Browse the repository at this point in the history
add images and instructor notes
  • Loading branch information
zkamvar authored Apr 22, 2022
2 parents 44b1c8b + 30d59b2 commit b96e156
Show file tree
Hide file tree
Showing 16 changed files with 456 additions and 40 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: sandpaper
Title: Create and Curate Carpentries Lessons
Version: 0.4.1
Version: 0.5.0
Authors@R: c(
person(given = "Zhian N.",
family = "Kamvar",
Expand Down
22 changes: 22 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
# sandpaper 0.5.0

NEW FEATURES
------------

* `images.html` is built with internal function `build_images()`, collecting
all images and displaying alt text for non-screen reader users (while
marking those paragraphs as `aria-hidden` so that screen reader users do not
have it read twice).
* `instructor-notes.html` is now built with the internal function
`build_instructor_notes()` and now collects instructor notes from the
episodes in a section called `aggregate-instructor-notes`.

MISC
----

* The internal `build_agg_page()` has a new argument, `append`, which takes an
XPath expression of what node should have children appended. Defaults to
`"self::node()"`. An example of alternate usage is in
`build_instructor_notes()`, which uses
`section[@id='aggregate-instructor-notes']`.

# sandpaper 0.4.1

MISC
Expand Down
64 changes: 64 additions & 0 deletions R/build_images.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#' @rdname build_agg
build_images <- function(pkg, pages = NULL, quiet = FALSE) {
build_agg_page(pkg = pkg,
pages = pages,
title = "All Images",
slug = "images",
aggregate = "/figure",
prefix = FALSE,
quiet = quiet)
}

#' Make a section of aggregated images
#'
#' This will insert xml figure nodes into the images page, printing the alt text
#' descriptions for users who are not using screen readers.
#'
#' @param name the name of the section, (may or may not be prefixed with `images-`)
#' @param contents an `xml_nodeset` of figure elements from [get_content()]
#' @param parent the parent div of the images page
#' @return the section that was added to the parent
#'
#' @keywords internal
#' @seealso [build_images()], [get_content()]
#' @examples
#' if (FALSE) {
#' lsn <- "/path/to/lesson"
#' pkg <- pkgdown::as_pkgdown(fs::path(lsn, "site"))
#'
#' # read in the All in One page and extract its content
#' img <- get_content("images", content = "self::*", pkg = pkg)
#' fig_content <- get_content("01-introduction", content = "/figure", pkg = pkg)
#' make_images_section("01-introduction", contents = fig_content, parent = img)
#' }
make_images_section <- function(name, contents, parent) {
title <- names(name)
uri <- sub("^images-", "", name)
new_section <- "<section id='{name}'>
<h2 class='section-heading'><a href='{uri}.html'>{title}</a></h2>
<hr class='half-width'/>
</section>"
section <- xml2::read_xml(glue::glue(new_section))

for (element in seq_along(contents)) {
content <- contents[[element]]
alt <- xml2::xml_text(xml2::xml_find_all(content, "./img/@alt"))
n <- length(alt)
xml2::xml_add_child(section, "h3", glue::glue("Figure {element}"),
id = glue::glue("{name}-figure-{element}"))
for (i in seq_along(alt)) {
txt <- alt[[i]]
if (length(txt) == 0) {
txt <- "[no alt-text]"
}
if (txt == "") {
txt <- "[decorative]"
}
desc <- glue::glue("Image {i} of {n}: {sQuote(txt)}")
xml2::xml_add_child(section, "p", 'aria-hidden'="true", desc)
}
xml2::xml_add_child(section, contents[[element]])
xml2::xml_add_child(section, "hr")
}
xml2::xml_add_child(parent, section)
}
103 changes: 103 additions & 0 deletions R/build_instructor_notes.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#' @rdname build_agg
#' @param built a vector of markdown documents that have recently been rebuilt
#' (for future use)
build_instructor_notes <- function(pkg, pages = NULL, built = NULL, quiet) {
path <- root_path(pkg$src_path)
this_lesson(path)
outpath <- fs::path(pkg$dst_path, "instructor-notes.html")
already_built <- template_check$valid() &&
fs::file_exists(outpath) &&
!is.null(built) &&
!"instructor-notes" %in% get_slug(built)
if (!already_built) {
page_globals <- setup_page_globals()
inote <- .resources$get()[["instructors"]]
inote <- inote[get_slug(inote) == "instructor-notes"]
html <- render_html(inote)
if (html != '') {
html <- xml2::read_html(html)
fix_nodes(html)
}

this_dat <- list(
this_page = "instructor-notes.html",
body = use_instructor(html),
pagetitle = "Instructor Notes"
)

page_globals$instructor$update(this_dat)

this_dat$body = use_learner(html)
page_globals$learner$update(this_dat)

page_globals$meta$update(this_dat)

build_html(template = "extra", pkg = pkg, nodes = html,
global_data = page_globals, path_md = "instructor-notes.html", quiet = TRUE)
}
build_agg_page(pkg = pkg,
pages = pages,
title = this_dat$pagetitle,
slug = "instructor-notes",
aggregate = "/div[contains(@class, 'instructor-note')]//div[@class='accordion-body']",
append = "section[@id='aggregate-instructor-notes']",
prefix = FALSE,
quiet = quiet)
}

#' Make a section of aggregated instructor notes
#'
#' This will append instructor notes from the inline sections of the lesson to
#' the instructor-notes page, separated by section and `<hr>` elements.
#'
#' @param name the name of the section, (may or may not be prefixed with `images-`)
#' @param contents an `xml_nodeset` of figure elements from [get_content()]
#' @param parent the parent div of the images page
#' @return the section that was added to the parent
#' @note On the learner view, instructor notes will not be present
#'
#' @keywords internal
#' @seealso [build_instructor_notes()], [get_content()]
#' @examples
#' if (FALSE) {
#' lsn <- "/path/to/lesson"
#' pkg <- pkgdown::as_pkgdown(fs::path(lsn, "site"))
#'
#' # read in the All in One page and extract its content
#' notes <- get_content("instructor-notes", content =
#' "section[@id='aggregate-instructor-notes']", pkg = pkg, instructor = TRUE)
#' agg <- "/div[contains(@class, 'instructor-note')]//div[@class='accordion-body']"
#' note_content <- get_content("01-introduction", content = agg, pkg = pkg)
#' make_instructornotes_section("01-introduction", contents = note_content,
#' parent = notes)
#'
#' # NOTE: if the object for "contents" ends with "_learn", no content will be
#' # appended
#' note_learn <- note_content
#' make_instructornotes_section("01-introduction", contents = note_learn,
#' parent = notes)
#'
#' }
make_instructornotes_section <- function(name, contents, parent) {
# Since we have hidden the instructor notes from the learner sections,
# there is no point to iterate here, so we return early.
the_call <- match.call()
is_learner <- endsWith(as.character(the_call[["contents"]]), "learn")
if (is_learner) {
return(invisible(NULL))
}
title <- names(name)
uri <- sub("^instructor-notes-", "", name)
new_section <- "<section id='{name}'>
<h2 class='section-heading'><a href='{uri}.html'>{title}</a></h2>
<hr class='half-width'/>
</section>"
section <- xml2::read_xml(glue::glue(new_section))
for (element in contents) {
for (child in xml2::xml_children(element)) {
xml2::xml_add_child(section, child)
}
xml2::xml_add_child(section, "hr")
}
xml2::xml_add_child(parent, section)
}
4 changes: 4 additions & 0 deletions R/build_site.R
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ build_site <- function(path = ".", quiet = !interactive(), preview = TRUE, overr
build_keypoints(pkg, pages = html_pages, quiet = quiet)
if (!quiet) cli::cli_rule(cli::style_bold("Creating All-in-one page"))
build_aio(pkg, pages = html_pages, quiet = quiet)
if (!quiet) cli::cli_rule(cli::style_bold("Creating Images page"))
build_images(pkg, pages = html_pages, quiet = quiet)
if (!quiet) cli::cli_rule(cli::style_bold("Creating Instructor Notes"))
build_instructor_notes(pkg, pages = html_pages, built = built, quiet = quiet)

build_sitemap(pkg$dst_path, paths = html_pages$paths, quiet = quiet)

Expand Down
79 changes: 54 additions & 25 deletions R/utils-aggregate.R
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#' - `$instructor`: all of the pages in the instructor view
#' - `$paths`: the absolute paths for the pages
#'
#' @keywords internal
#' @examples
#' tmpdir <- tempfile()
#' on.exit(fs::dir_delete(tmpdir))
Expand Down Expand Up @@ -43,8 +44,9 @@ read_all_html <- function(path) {
#' @param pkg an object created via [pkgdown::as_pkgdown()] of a lesson.
#' @param title the new page title
#' @param slug the slug for the page (e.g. "aio" will become "aio.html")
#' @param quiet if `TRUE`, no messages will be emitted. If FALSE,
#' pkgdown will report creation of the temporary file.
#' @param new if `TRUE`, (default), the page will be generated
#' from a new template. If `FALSE`, the page is assumed to have been pre-built and
#' should be appended to.
#' @return
#' - `provision_agg_page()`: a list:
#' - `$learner`: an `xml_document` templated for the learner page
Expand Down Expand Up @@ -92,21 +94,26 @@ read_all_html <- function(path) {
#' provision_agg_page(pkg, title = "All In One", slug = "aio", quiet = FALSE)
#'
#' }
provision_agg_page <- function(pkg, title = "Key Points", slug = "key-points", quiet) {
if (is.null(.html$get()$template$extra)) {
provision_extra_template(pkg)
provision_agg_page <- function(pkg, title = "Key Points", slug = "key-points", new = FALSE) {
if (new) {
if (is.null(.html$get()$template$extra)) {
provision_extra_template(pkg)
}
learner <- .html$get()$template$extra$learner
instructor <- .html$get()$template$extra$instructor
learner <- gsub("--FIXME TITLE", title, learner)
instructor <- gsub("--FIXME TITLE", title, instructor)
learner <- gsub("--FIXME", slug, learner)
instructor <- gsub("--FIXME", slug, instructor)
} else {
uri <- as_html(slug)
learner <- fs::path(pkg$dst_path, uri)
instructor <- fs::path(pkg$dst_path, "instructor", uri)
}

learner <- .html$get()$template$extra$learner
instructor <- .html$get()$template$extra$instructor
learner <- gsub("--FIXME TITLE", title, learner)
instructor <- gsub("--FIXME TITLE", title, instructor)
learner <- gsub("--FIXME", slug, learner)
instructor <- gsub("--FIXME", slug, instructor)

return(list(learner = xml2::read_html(learner),
instructor = xml2::read_html(instructor),
needs_episodes = TRUE)
needs_episodes = new)
)
}

Expand Down Expand Up @@ -156,8 +163,13 @@ section_fun <- function(slug) {
#' @param aggregate a selector for the lesson content you want to aggregate.
#' The default is "section", which will aggregate all sections, but nothing
#' outside of the sections. To grab everything in the page, use "*"
#' @param append a selector for the section of the page where the aggregate data
#' should be placed. This defaults to "self::node()", which indicates that the
#' entire page should be appended.
#' @param prefix flag to add a prefix for the aggregated sections. Defaults to
#' `FALSE`.
#' @param quiet if `TRUE`, no messages will be emitted. If FALSE, pkgdown will
#' report creation of the temporary file.
#' @return NULL, invisibly. This is called for its side-effect
#'
#' @details
Expand Down Expand Up @@ -190,28 +202,42 @@ section_fun <- function(slug) {
#' build_aio(pkg, htmls, quiet = FALSE)
#' build_keypoints(pkg, htmls, quiet = FALSE)
#' }
build_agg_page <- function(pkg, pages, title = NULL, slug = NULL, aggregate = "section", prefix = FALSE, quiet = FALSE) {
build_agg_page <- function(pkg, pages, title = NULL, slug = NULL, aggregate = "section", append = "self::node()", prefix = FALSE, quiet = FALSE) {
path <- root_path(pkg$src_path)
out_path <- pkg$dst_path
this_lesson(path)
agg <- provision_agg_page(pkg, title = title, slug = slug, quiet)

new_content <- append == "self::node()" || append == "self::*"
agg <- provision_agg_page(pkg, title = title, slug = slug, new = new_content)
if (agg$needs_episodes) {
remove_fix_node(agg$learner, slug)
remove_fix_node(agg$instructor, slug)
}

make_section <- section_fun(slug)

learn <- get_content(agg$learner, content = "section", label = TRUE)
learn_parent <- get_content(agg$learner, content = "self::*")
learn_parent <- get_content(agg$learner, content = append)
instruct_parent <- get_content(agg$instructor, content = append)
needs_content <- !new_content && length(instruct_parent) == 0
if (needs_content) {
# When the content requested does not exist, we append a new section with
# the id of aggregate-{slug}
sid <- paste0("aggregate-", slug)
learn_content <- get_content(agg$learner, content = "self::node()")
xml2::xml_add_child(learn_content, "section", id = sid)
learn_parent <- xml2::xml_child(learn_content, xml2::xml_length(learn_content))

instruct <- get_content(agg$instructor, content = "section", label = TRUE)
instruct_parent <- get_content(agg$instructor, content = "self::*")
instruct_content <- get_content(agg$instructor, content = "self::node()")
xml2::xml_add_child(instruct_content, "section", id = sid)
instruct_parent <- xml2::xml_child(instruct_content, xml2::xml_length(instruct_content))
}
# clean up any content that currently exists
xml2::xml_remove(xml2::xml_children(learn_parent))
xml2::xml_remove(xml2::xml_children(instruct_parent))

the_episodes <- .resources$get()[["episodes"]]
the_slugs <- get_slug(the_episodes)
the_slugs <- if (prefix) paste0(slug, "-", the_slugs) else the_slugs
old_names <- names(learn)

for (episode in seq(the_episodes)) {
ep_learn <- ep_instruct <- the_episodes[episode]
Expand All @@ -223,7 +249,7 @@ build_agg_page <- function(pkg, pages, title = NULL, slug = NULL, aggregate = "s
}
ep_title <- as.character(xml2::xml_contents(get_content(ep_learn, ".//h1")))
names(ename) <- paste(ep_title, collapse = "")
ep_learn <- get_content(ep_learn, content = aggregate, pkg = pkg)
ep_learn <- get_content(ep_learn, content = aggregate, pkg = pkg)
ep_instruct <- get_content(ep_instruct, content = aggregate, pkg = pkg, instructor = TRUE)
make_section(ename, ep_learn, learn_parent)
make_section(ename, ep_instruct, instruct_parent)
Expand All @@ -232,12 +258,12 @@ build_agg_page <- function(pkg, pages, title = NULL, slug = NULL, aggregate = "s
learn_out <- fs::path(out_path, as_html(slug))
instruct_out <- fs::path(out_path, as_html(slug, instructor = TRUE))
report <- "Writing '{.file {out}}'"
out <- fs::path_rel(learn_out, pkg$dst_path)
if (!quiet) cli::cli_text(report)
writeLines(as.character(agg$learner), learn_out)
out <- fs::path_rel(instruct_out, pkg$dst_path)
if (!quiet) cli::cli_text(report)
writeLines(as.character(agg$instructor), instruct_out)
out <- fs::path_rel(learn_out, pkg$dst_path)
if (!quiet) cli::cli_text(report)
writeLines(as.character(agg$learner), learn_out)
}

#' Get sections from an episode's HTML page
Expand Down Expand Up @@ -272,11 +298,14 @@ build_agg_page <- function(pkg, pages, title = NULL, slug = NULL, aggregate = "s
#' lsn <- "/path/to/lesson"
#' pkg <- pkgdown::as_pkgdown(fs::path(lsn, "site"))
#'
#' # for AiO pages, this will return only sections:
#' # for AiO pages, this will return only the top-level sections:
#' get_content("aio", content = "section", label = TRUE, pkg = pkg)
#'
#' # for episode pages, this will return everything that's not template
#' get_content("01-introduction", pkg = pkg)
#'
#' # for things that are within lessons but we don't know their exact location,
#' # we can prefix a `/` to double up the slash, which will produce
#'
#' }
get_content <- function(episode, content = "*", label = FALSE, pkg = NULL,
Expand Down
2 changes: 1 addition & 1 deletion R/utils-built-db.R
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ get_built_db <- function(db = "site/built/md5sum.txt", filter = "*R?md") {
#' @seealso [get_built_db()]
reserved_db <- function(db) {
reserved <- c("index", "README", "CONTRIBUTING", "learners/setup",
"profiles[/].*", "links")
"profiles[/].*", "instructors[/]instructor-notes[.]*", "links")
reserved <- paste(reserved, collapse = "|")
reserved <- paste0("^(", reserved, ")[.]R?md")
db[!grepl(reserved, db$file, perl = TRUE), , drop = FALSE]
Expand Down
1 change: 1 addition & 0 deletions _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ reference:
- provision_extra_template
- get_content
- make_aio_section
- make_images_section
- build_agg_page
- title: "[Internal] Resource Discovery/Management"
desc: >
Expand Down
Loading

0 comments on commit b96e156

Please sign in to comment.