support speaker notes and timing comments on the title slide

Pandoc puts content written before the first heading into an otherwise
empty phantom slide right after the title slide. When such a slide
consists of nothing but ::: notes blocks, timing comments, and
whitespace, it is now merged into the title slide: the title gets the
data-timing attribute and the notes, the phantom slide disappears, and
slide_timing() reports the title time as "(title)".

Change-Id: I984e924c467c4e15905b97c128c380887132f09d
diff --git a/R/slide_timing.R b/R/slide_timing.R
index 560716d..7fbc976 100644
--- a/R/slide_timing.R
+++ b/R/slide_timing.R
@@ -138,6 +138,52 @@
   ifelse(is.na(speaker), "(unnamed)", speaker)
 }
 
+# Timing comments and ::: notes blocks written before the first heading do
+# not land on the title slide: pandoc puts them into an otherwise empty
+# phantom slide right after it. Merge such a phantom slide -- consisting of
+# nothing but notes, timing comments, and whitespace -- into the title
+# slide, so that notes and timing can be given for the title as well.
+merge_title_slide_notes <- function(lines) {
+  title_open <- grep('<section class="title-frame"', lines)
+  if (length(title_open) == 0) {
+    return(lines)
+  }
+  title_open <- title_open[1]
+  title_close <- which(lines == "</section>")
+  title_close <- title_close[title_close > title_open][1]
+
+  phantom_open <- grep("^<section", lines)
+  phantom_open <- phantom_open[phantom_open > title_close][1]
+  if (is.na(phantom_open)) {
+    return(lines)
+  }
+  phantom_close <- which(lines == "</section>")
+  phantom_close <- phantom_close[phantom_close > phantom_open][1]
+
+  inner <- lines[(phantom_open + 1):(phantom_close - 1)]
+
+  # the phantom slide is only merged if it has no visible content beyond
+  # notes, timing comments, and whitespace
+  aside <- grep('<aside class="notes">', inner)
+  aside_end <- grep("</aside>", inner)
+  in_aside <- if (length(aside) > 0) {
+    unlist(Map(":", aside, aside_end[aside_end > aside][1]))
+  }
+  content <- seq_along(inner)
+  content <- content[!inner %in% ""]
+  content <- setdiff(content, c(grep("<!--", inner), in_aside))
+  if (length(content) > 0) {
+    return(lines)
+  }
+
+  moved <- inner[nzchar(trimws(inner))]
+  append(
+    lines[-(phantom_open:phantom_close)],
+    moved,
+    after = title_close - 1
+  )
+}
+
 # Convert timing comments into data-timing attributes on the enclosing
 # <section> elements of a rendered reveal.js HTML document. Returns the
 # modified lines invisibly along with the extracted comments as attributes.