diff --git a/DESCRIPTION b/DESCRIPTION
index efc67ba4d..9e8e5cdac 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -1,6 +1,6 @@
Package: sandpaper
Title: Create and Curate Carpentries Lessons
-Version: 0.14.0
+Version: 0.14.1
Authors@R: c(
person(given = "Zhian N.",
family = "Kamvar",
diff --git a/NEWS.md b/NEWS.md
index 1f37a4e59..a568d5941 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,3 +1,10 @@
+# sandpaper 0.14.1 (2023-11-09)
+
+## BUG FIX
+
+* `mailto:` links are no longer prepended with the URL (reported: @apirogov,
+ #538; fixed: @zkamvar, #539)
+
# sandpaper 0.14.0 (2023-10-02)
## NEW FEATURES
diff --git a/R/utils-renv.R b/R/utils-renv.R
index a9c23c3bf..54ba727dd 100644
--- a/R/utils-renv.R
+++ b/R/utils-renv.R
@@ -250,6 +250,12 @@ callr_manage_deps <- function(path, repos, snapshot, lockfile_exists) {
deps <- unique(renv::dependencies(path = path, root = path, dev = TRUE)$Package)
pkgs <- setdiff(deps, installed)
needs_hydration <- length(pkgs) > 0
+ if (packageVersion("renv") >= "1.0.0") {
+ # We only need to hydrate the packages that do not exist in the lockfile
+ # and that are not installed
+ lock <- renv::lockfile_read(renv_lock)
+ pkgs <- setdiff(pkgs, names(lock$Packages))
+ }
} else {
# If there is not a lockfile, we need to run a fully hydration
pkgs <- NULL
diff --git a/R/utils-xml.R b/R/utils-xml.R
index 4073771fe..51270e4ba 100644
--- a/R/utils-xml.R
+++ b/R/utils-xml.R
@@ -144,9 +144,12 @@ use_instructor <- function(nodes = NULL) {
if (length(nodes) == 0) return(nodes)
copy <- xml2::read_html(as.character(nodes))
# find all local links and transform non-html and nested links ---------
- lnk <- xml2::xml_find_all(copy,
- ".//a[@href][not(contains(@href, '://')) and not(starts-with(@href, '#'))]"
- )
+ no_external <- "not(contains(@href, '://'))"
+ no_anchors <- "not(starts-with(@href, '#'))"
+ no_mail <- "not(starts-with(@href, 'mailto:'))"
+ predicate <- paste(c(no_external, no_anchors, no_mail), collapse = " and ")
+ XPath <- sprintf(".//a[@href][%s]", predicate)
+ lnk <- xml2::xml_find_all(copy, XPath)
lnk_hrefs <- xml2::xml_attr(lnk, "href")
lnk_paths <- xml2::url_parse(lnk_hrefs)$path
# links without HTML extension
diff --git a/tests/testthat/_snaps/utils-xml.md b/tests/testthat/_snaps/utils-xml.md
index b6fbe9db0..3e8fb8830 100644
--- a/tests/testthat/_snaps/utils-xml.md
+++ b/tests/testthat/_snaps/utils-xml.md
@@ -3,7 +3,7 @@
Code
xml2::xml_find_all(html_test, ".//a[@href]")
Output
- {xml_nodeset (10)}
+ {xml_nodeset (11)}
[1] a
[2] b
[3] c
@@ -14,13 +14,14 @@
[8] h
[9] i
[10] j
+ [11] k
---
Code
xml2::xml_find_all(res, ".//a[@href]")
Output
- {xml_nodeset (10)}
+ {xml_nodeset (11)}
[1] a
[2] b
[3] c
@@ -31,4 +32,5 @@
[8] h
[9] i
[10] j
+ [11] k
diff --git a/tests/testthat/test-utils-xml.R b/tests/testthat/test-utils-xml.R
index b4d9379ef..f8e2d64fe 100644
--- a/tests/testthat/test-utils-xml.R
+++ b/tests/testthat/test-utils-xml.R
@@ -12,13 +12,14 @@ test_that("paths in instructor view that are nested or not HTML get diverted", {
"[g](files/confirmation.html)", # asset
"[h](#what-the)",
"[i](other-page.html#section)",
- "[j](other-page)"
+ "[j](other-page)",
+ "[k](mailto:workbench@example.com?subject='no')"
)))
res <- xml2::read_html(use_instructor(html_test))
# refs are transformed according to our rules
refs <- xml2::xml_text(xml2::xml_find_all(res, ".//@href"))
expect_equal(startsWith(refs, "../"),
- c(FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE))
+ c(FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE))
expect_snapshot(xml2::xml_find_all(html_test, ".//a[@href]"))
expect_snapshot(xml2::xml_find_all(res, ".//a[@href]"))
})