summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSacha Chua <sacha@sachachua.com>2022-11-10 08:04:31 -0500
committerSacha Chua <sacha@sachachua.com>2022-11-10 08:04:31 -0500
commitf16a68d2c43a38ac64de5a38ddcd87db09e737bf (patch)
tree747937a77e1e57373909ab7f36b3bc5a57f130f8
parentd6b6ce5ec0e73b22fe2da1ac8b9685419e1ad17e (diff)
downloademacsconf-el-f16a68d2c43a38ac64de5a38ddcd87db09e737bf.tar.xz
emacsconf-el-f16a68d2c43a38ac64de5a38ddcd87db09e737bf.zip
hyperlist, reload
-rw-r--r--emacsconf-publish.el106
-rw-r--r--emacsconf-stream.el93
-rw-r--r--emacsconf.el113
3 files changed, 236 insertions, 76 deletions
diff --git a/emacsconf-publish.el b/emacsconf-publish.el
index a3c0323..72683bc 100644
--- a/emacsconf-publish.el
+++ b/emacsconf-publish.el
@@ -151,7 +151,31 @@
talk
"<div class=\"vid\">${video-html}<div>${extra}</div>${resources}${chapter-list}</div>"))))
+(defun emacsconf-publish-format-res-talks (info)
+ (mapconcat
+ (lambda (o)
+ (concat
+ "<tr>"
+ (format
+ "<td><a name=\"%s\"></a>"
+ (plist-get o :slug))
+ (plist-get o :qa-link)
+ "</td>"
+ "<td>" (if (plist-get o :pad-url)
+ (format "<a href=\"%s\" target=\"_blank\" rel=\"noreferrer\">Open pad</a>" (plist-get o :pad-url))
+ "")
+ "</td>"
+ "<td>" (format "<a href=\"%s\" target=\"_blank\" rel=\"noreferrer\">Open chat</a>" (plist-get o :webchat-url))
+ ""
+ "</td>"
+ "<td>" (format-time-string "%-l:%M" (plist-get o :start-time) emacsconf-timezone) "</td>"
+ "<td>" (or (plist-get o :slug) "") "</td>"
+ "<td>" (or (plist-get o :title) "") "</td>"
+ "</tr>"))
+ info
+ "\n"))
(defun emacsconf-publish-res-index ()
+ "Publish BBB room URLs and pad links for volunteer convenience."
(interactive)
(let ((emacsconf-use-absolute-url t)
(emacsconf-base-url "")
@@ -166,43 +190,42 @@
o)
o)))
(emacsconf-prepare-for-display (emacsconf-get-talk-info)))))
- (mapc (lambda (track)
- (let ((track-talks (seq-filter (lambda (o) (string= (plist-get o :track)
- (plist-get track :name)))
- info)))
- (with-temp-file (expand-file-name (format "index-%s.html" (plist-get track :id)) emacsconf-res-dir)
- (insert
- "<html><head><link rel=\"stylesheet\" href=\"style.css\"></head><body>
+ (mapc
+ (lambda (track)
+ (let*
+ ((track-talks (seq-filter (lambda (o) (string= (plist-get o :track)
+ (plist-get track :name)))
+ info))
+ (result
+ (concat
+ "<html><head><link rel=\"stylesheet\" href=\"style.css\"></head><body>
<div>"
- (with-temp-buffer
- (svg-print (emacsconf-schedule-svg 800 300 info))
- (buffer-string))
- "</div><table>"
- (mapconcat
- (lambda (o)
- (concat
- "<tr>"
- (format
- "<td><a name=\"%s\"></a>"
- (plist-get o :slug))
- (plist-get o :qa-link)
- "</td>"
- "<td>" (if (plist-get o :pad-url)
- (format "<a href=\"%s\" target=\"_blank\" rel=\"noreferrer\">Open pad</a>" (plist-get o :pad-url))
- "")
- "</td>"
- "<td>" (format "<a href=\"%s\" target=\"_blank\" rel=\"noreferrer\">Open chat</a>" (plist-get o :webchat-url))
- ""
- "</td>"
- "<td>" (format-time-string "%-l:%M" (plist-get o :start-time) emacsconf-timezone) "</td>"
- "<td>" (or (plist-get o :slug) "") "</td>"
- "<td>" (or (plist-get o :title) "") "</td>"
- "</tr>"))
- (seq-filter (lambda (talk) (string= (plist-get talk :track)
- (plist-get track :name))) info)
- "\n")
- "</table></body></html>"))))
- emacsconf-tracks)))
+ (with-temp-buffer
+ (svg-print (emacsconf-schedule-svg 800 300 info))
+ (buffer-string))
+ "</div><h1>"
+ (plist-get track :name)
+ "</h1><table>"
+ "<tr><th colspan=\"6\" style=\"text-align: left\">Saturday</th></tr>"
+ (emacsconf-publish-format-res-talks
+ (emacsconf-prepare-for-display
+ (emacsconf-filter-talks-by-time
+ (format "2022-12-03T08:00:00%s" emacsconf-timezone-offset)
+ (format "2022-12-03T18:00:00%s" emacsconf-timezone-offset)
+ track-talks)))
+ "<tr><th colspan=\"6\" style=\"text-align: left\">Sunday</th></tr>"
+ (emacsconf-publish-format-res-talks
+ (emacsconf-prepare-for-display
+ (emacsconf-filter-talks-by-time
+ (format "2022-12-04T08:00:00%s" emacsconf-timezone-offset)
+ (format "2022-12-04T18:00:00%s" emacsconf-timezone-offset)
+ track-talks)))
+ "</table></body></html>")))
+ (with-temp-file (expand-file-name (format "index-%s.html" (plist-get track :id)) emacsconf-res-dir)
+ (insert result))
+ (with-temp-file (expand-file-name (format "index-%s.html" (plist-get track :id)) emacsconf-backstage-dir)
+ (insert result))))
+ emacsconf-tracks)))
(defun emacsconf-index-card-video (video-id video-file talk extensions &optional backstage)
(let* ((video-base (and video-file (file-name-base video-file)))
@@ -875,15 +898,6 @@ Entries are sorted chronologically, with different tracks interleaved."
(defun emacsconf-sum (field talks)
(apply '+ (seq-map (lambda (talk) (string-to-number (or (plist-get talk field) "0"))) talks)))
-(defun emacsconf-publish-backstage-add-to-todo-hook ()
- (interactive)
- (with-current-buffer (find-file-noselect emacsconf-org-file)
- (add-hook 'org-after-todo-state-change-hook #'emacsconf-publish-backstage-org-after-todo-state-change nil t)))
-(defun emacsconf-publish-backstage-remove-from-todo-hook ()
- (interactive)
- (with-current-buffer (find-file-noselect emacsconf-org-file)
- (remove-hook 'org-after-todo-state-change-hook #'emacsconf-publish-backstage-org-after-todo-state-change t)))
-
(defun emacsconf-publish-backstage-org-on-state-change (talk)
(save-window-excursion
(emacsconf-with-talk-heading talk
@@ -906,7 +920,7 @@ Entries are sorted chronologically, with different tracks interleaved."
(insert
"<html><head><meta charset=\"UTF-8\"><link rel=\"stylesheet\" href=\"/style.css\" /></head><body>"
"<p>Schedule by status: (gray: waiting, light yellow: processing, yellow: to assign, light green: captioning, green: captioned and ready)<br />Updated by conf.org and the wiki repository</br />"
- "<img src=\"https://emacsconf.org/2022/organizers-notebook/schedule.svg\" /></p>"
+ "<img src=\"schedule.svg\" /></p>"
(format "<p>Waiting for %d talks (~%d minutes) out of %d total</p>"
(length (assoc-default "WAITING_FOR_PREREC" by-status))
(emacsconf-sum :time (assoc-default "WAITING_FOR_PREREC" by-status))
diff --git a/emacsconf-stream.el b/emacsconf-stream.el
index 9e93f20..705d0da 100644
--- a/emacsconf-stream.el
+++ b/emacsconf-stream.el
@@ -67,16 +67,22 @@ Files should be in YEAR/video-slug--main.webm and video-slug--main.vtt.")
(defun emacsconf-stream-svg-set-text (dom id text)
"Update DOM to set the tspan in the element with ID to TEXT.
If the element doesn't have a tspan child, use the element itself."
- (setq text (svg--encode-text text))
- (let ((node (or (dom-child-by-tag
- (car (dom-by-id dom id))
- 'tspan)
- (dom-by-id dom id))))
- (cond
- ((null node)) ; skip
- ((= (length node) 2)
- (nconc node (list text)))
- (t (setf (elt node 2) text)))))
+ (if (or (null text) (string= text ""))
+ (let ((node (dom-by-id dom id)))
+ (when node
+ (dom-set-attribute node 'style "visibility: hidden")
+ (dom-set-attribute (dom-child-by-tag node 'tspan) 'style "fill: none; stroke: none")))
+ (setq text (svg--encode-text text))
+ (let ((node (or (dom-child-by-tag
+ (car (dom-by-id dom id))
+ 'tspan)
+ (dom-by-id dom id))))
+ (cond
+ ((null node)
+ (error "Could not find node %s" id)) ; skip
+ ((= (length node) 2)
+ (nconc node (list text)))
+ (t (setf (elt node 2) text))))))
(defun emacsconf-stream-add-talk-props (talk)
"Create an overlay for TALK.
@@ -248,11 +254,16 @@ This uses the BBB room if available, or the IRC channel if not."
(unless (file-directory-p emacsconf-stream-overlay-dir)
(make-directory emacsconf-stream-overlay-dir))
(mapc (lambda (talk)
- (emacsconf-stream-write-talk-overlay-svgs
- talk
- (expand-file-name (concat (plist-get talk :slug) "-video.svg") emacsconf-stream-overlay-dir)
- (expand-file-name (concat (plist-get talk :slug) "-other.svg") emacsconf-stream-overlay-dir)))
- info))
+ (unless (file-exists-p (expand-file-name (concat (plist-get talk :slug) "-video.png") emacsconf-stream-overlay-dir))
+ (emacsconf-stream-write-talk-overlay-svgs
+ talk
+ (expand-file-name (concat (plist-get talk :slug) "-video.svg") emacsconf-stream-overlay-dir)
+ (expand-file-name (concat (plist-get talk :slug) "-other.svg") emacsconf-stream-overlay-dir))))
+ info)
+ (emacsconf-stream-write-talk-overlay-svgs
+ nil
+ (expand-file-name "blank-video.svg" emacsconf-stream-overlay-dir)
+ (expand-file-name "blank-other.svg" emacsconf-stream-overlay-dir)))
(defun emacsconf-stream-display-talk-info (talk)
(interactive (list (emacsconf-complete-talk-info)))
@@ -314,6 +325,57 @@ This uses the BBB room if available, or the IRC channel if not."
(set-frame-size nil 1280 720 t)
(mapc #'emacsconf-stream-generate-title-page info)))
+(defun emacsconf-stream-generate-in-between-pages (&optional info)
+ (interactive)
+ (setq info (emacsconf-prepare-for-display (emacsconf-filter-talks (or info (emacsconf-get-talk-info)))))
+ (let* ((by-track (seq-group-by (lambda (o) (plist-get o :track)) info))
+ (dir (expand-file-name "in-between" emacsconf-stream-asset-dir))
+ (template (expand-file-name "template.svg" dir)))
+ (mapc (lambda (track)
+ (let (prev)
+ (mapc (lambda (talk)
+ (let ((dom (xml-parse-file template)))
+ (mapc (lambda (entry)
+ (let ((prefix (car entry)))
+ (emacsconf-stream-svg-set-text dom (concat prefix "title")
+ (plist-get (cdr entry) :title))
+ (emacsconf-stream-svg-set-text dom (concat prefix "speakers")
+ (plist-get (cdr entry) :speakers-with-pronouns))
+ (emacsconf-stream-svg-set-text dom (concat prefix "url")
+ (and (cdr entry) (concat emacsconf-base-url (plist-get (cdr entry) :url))))
+ (emacsconf-stream-svg-set-text
+ dom
+ (concat prefix "qa")
+ (pcase (plist-get (cdr entry) :q-and-a)
+ ("live" "Live Q&A after talk")
+ ("IRC" "IRC Q&A after talk")
+ (_ "")))))
+ (list (cons "previous-" prev)
+ (cons "current-" talk)))
+ (with-temp-file (expand-file-name (concat (plist-get talk :slug) ".svg") dir)
+ (dom-print dom))
+ (unless (file-exists-p (expand-file-name (concat (plist-get talk :slug) ".png")
+ dir))
+ (shell-command
+ (concat "inkscape --export-type=png --export-dpi=300 --export-background-opacity=0 "
+ (shell-quote-argument (expand-file-name (concat (plist-get talk :slug) ".svg")
+ dir))))))
+ (setq prev talk))
+ (cdr track)))
+ (let ((default-directory dir))
+ (shell-command
+ (concat
+ "convert "
+ (mapconcat (lambda (talk) (shell-quote-argument
+ (concat (plist-get talk :slug) ".png")))
+ (cdr track)
+ " ")
+ " "
+ (plist-get (emacsconf-get-track (car track)) :id)
+ "-in-between.pdf"
+ ))))
+ by-track)))
+
(defun emacsconf-stream-generate-test-subtitles (&optional info)
(interactive)
(setq info (emacsconf-filter-talks (or info (emacsconf-get-talk-info))))
@@ -364,6 +426,7 @@ This uses the BBB room if available, or the IRC channel if not."
(message "Done %s" talk)
(undo-boundary))))
+
(defun emacsconf-stream-schedule-timers (&optional info)
(interactive)
(setq info (emacsconf-filter-talks (or info (emacsconf-get-talk-info))))
diff --git a/emacsconf.el b/emacsconf.el
index 8e4c4e1..2f5b6e2 100644
--- a/emacsconf.el
+++ b/emacsconf.el
@@ -134,7 +134,11 @@
(mapc (lambda (file)
(copy-file file (expand-file-name (file-name-nondirectory file)
emacsconf-backstage-dir)
- t))
+ t)
+ (when emacsconf-cache-dir
+ (copy-file file (expand-file-name (file-name-nondirectory file)
+ emacsconf-cache-dir)
+ t)))
(or (dired-get-marked-files)
(list (buffer-file-name)))))
@@ -927,10 +931,12 @@
(emacsconf-backstage-password . emacsconf_backstage_password))))))
;; (emacsconf-ansible-load-vars (expand-file-name "prod-vars.yml" emacsconf-ansible-directory))
;;; Tracks
-(defvar emacsconf-tracks '((:name "General" :color "peachpuff" :id "gen" :channel "emacsconf-gen"
- :tramp "/ssh:emacsconf-gen@res.emacsconf.org#46668:")
- (:name "Development" :color "skyblue" :id "dev" :channel "emacsconf-dev"
- :tramp "/ssh:emacsconf-dev@res.emacsconf.org#46668:")))
+(defvar emacsconf-tracks
+ '(
+ (:name "General" :color "peachpuff" :id "gen" :channel "emacsconf-gen"
+ :tramp "/ssh:emacsconf-gen@res.emacsconf.org#46668:")
+ (:name "Development" :color "skyblue" :id "dev" :channel "emacsconf-dev"
+ :tramp "/ssh:emacsconf-dev@res.emacsconf.org#46668:")))
(defun emacsconf-get-track (name)
(when (listp name) (setq name (plist-get name :track)))
@@ -985,8 +991,9 @@
(when (stringp start-time) (setq start-time (date-to-time start-time)))
(when (stringp end-time) (setq end-time (date-to-time end-time)))
(seq-filter (lambda (o)
- (and (not (time-less-p (plist-get o :start-time) end-time))
- (not (time-less-p start-time (plist-get o :end-time)))))
+ (and (plist-get o :start-time)
+ (time-less-p (plist-get o :start-time) end-time)
+ (time-less-p start-time (plist-get o :end-time))))
info))
(defun emacsconf-get-shift (time)
@@ -1070,7 +1077,7 @@ Filter by TRACK if given. Use INFO as the list of talks."
(defun emacsconf-reflow ()
"Help reflow text files."
(interactive)
- (let (input last-input)
+ (let (input last-input (case-fold-search t))
(while (not (string= "" (setq input (read-string "Word: "))))
(when (string= input "!")
(delete-backward-char 1)
@@ -1114,9 +1121,9 @@ Filter by TRACK if given. Use INFO as the list of talks."
#'emacsconf-org-after-todo-state-change t)))
(defvar emacsconf-todo-hooks
- '(emacsconf-stream-play-talk-on-change ;; play the talk
- emacsconf-stream-open-qa-windows-on-change
- emacsconf-erc-announce-on-change ;; announce via ERC
+ '((emacsconf-stream-play-talk-on-change "gen") ;; play the talk - dev will be managed separately
+ (emacsconf-stream-open-qa-windows-on-change "gen")
+ ;; emacsconf-erc-announce-on-change ;; announce via ERC
emacsconf-publish-media-files-on-change
emacsconf-publish-bbb-redirect
emacsconf-publish-backstage-org-on-state-change ;; update the backstage index
@@ -1126,9 +1133,18 @@ Filter by TRACK if given. Use INFO as the list of talks."
They will be called with TALK.")
(defun emacsconf-org-after-todo-state-change ()
- "Run all the hooks in `emacsconf-todo-hooks'."
- (let ((talk (emacsconf-get-talk-info-for-subtree)))
- (run-hook-with-args 'emacsconf-todo-hooks talk)))
+ "Run all the hooks in `emacsconf-todo-hooks'.
+If an `emacsconf-todo-hooks' entry is a list, run it only for the
+tracks with the ID in the cdr of that list."
+ (let* ((talk (emacsconf-get-talk-info-for-subtree))
+ (track (emacsconf-get-track (plist-get talk :track))))
+ (mapc
+ (lambda (hook-entry)
+ (cond
+ ((symbolp hook-entry) (funcall hook-entry talk))
+ ((member (plist-get track :id) (cdr hook-entry))
+ (funcall (car hook-entry) talk))))
+ emacsconf-todo-hooks)))
(defun emacsconf-broadcast (message)
(interactive "MMessage: ")
@@ -1149,10 +1165,77 @@ They will be called with TALK.")
(org-agenda-list nil emacsconf-date 2)))
(defun emacsconf-update-talk-status (slug from-states to-state)
- (interactive (list (emacsconf-complete-talk) (read-string "From: ") (read-string "To: ")))
+ (interactive (list (emacsconf-complete-talk) "." (completing-read "To: " (mapcar 'car emacsconf-status-types))))
(emacsconf-with-talk-heading slug
(when (string-match from-states (org-entry-get (point) "TODO"))
(org-todo to-state))))
+;; copied from org-ascii--indent-string
+(defun emacsconf-indent-string (s width)
+ "Indent S by WIDTH spaces."
+ (replace-regexp-in-string "\\(^\\)[ \t]*\\S-" (make-string width ?\s) s nil nil 1))
+
+(defun emacsconf-shift-hyperlist (params talks &optional do-insert)
+ "Use PARAMS to personalize the shift hyperlist."
+ (mapconcat (lambda (talk)
+ (emacsconf-talk-hyperlist (append (assoc-default (plist-get (emacsconf-get-track talk) :id)
+ params)
+ talk)
+ do-insert))
+ talks))
+
+(defun emacsconf-talk-hyperlist (talk &optional do-insert)
+ (interactive (list (emacsconf-complete-talk-info) t))
+ (let ((result
+ (emacsconf-replace-plist-in-string
+ talk
+ (format "- %s %s %s %s\n%s"
+ (format-time-string "%H:%M" (plist-get talk :start-time) emacsconf-timezone)
+ (plist-get talk :track)
+ (plist-get talk :slug)
+ (plist-get talk :title)
+ (replace-regexp-in-string
+ "\\(^\\)[ \t]*\\S-" " "
+ (pcase (or (plist-get talk :q-and-a) "")
+ ((rx "live")
+ "- [ ] ${checkin}: Check ${speakers-with-pronouns} (${irc}) into BBB room sometime beforehand
+- [ ] ${stream}: Display the in-between slide
+- [ ] ${host}: Connect to the ${track} channel in Mumble and introduce the talk
+- [ ] ${stream}: [[elisp:(emacsconf-update-talk-status \"${slug}\" \".\" \"PLAYING\")][Play the talk]]
+- [ ] ${host}: Join the Q&A room and open the pad; optionally open IRC for ${channel}
+- [ ] [? speaker missing?] ${host}: Let #emacsconf-org know so that we can text or call the speaker
+- [ ] ${stream}: After the talk, [[elisp:(emacsconf-update-talk-status \"${slug}\" \".\" \"CLOSED_Q\")][open the Q&A window and the pad]], give the host the go-ahead via Mumble or #emacsconf-org
+- [ ] ${host}: Start recording and read questions
+- [ ] ${host}: [[elisp:(emacsconf-update-talk-status \"${slug}\" \".\" \"OPEN_Q\")][Decide when to open the Q&A]] and announce that people can join using the URL on the talk page
+- [? Open Q&A is still going on and it's about five minutes before the next talk]
+ - [ ] ${host}: Let the speaker know about the time and that the Q&A can continue off-stream if people want to join
+- [? Open Q&A is still going on and it's about two minutes before the next talk]
+ - [ ] ${host}: Announce that the Q&A will continue if people want to join the BBB room from the talk page, and the stream will now move to the next talk
+- [? Q&A is done]
+ - [ ] ${host}: [[elisp:(emacsconf-update-talk-status \"${slug}\" \".\" \"TO_ARCHIVE\")][Mark talk as to archive]], close Q&A windows
+- [ ] ${stream}: Close the Q&A windows and move on to the next talk
+")
+ ((rx "irc")
+ "- [ ] ${stream}: Display the in-between slide
+- [ ] ${host}: Connect to the ${track} channel in Mumble and introduce the talk
+- [ ] ${stream}: [[elisp:(emacsconf-update-talk-status \"${slug}\" \".\" \"PLAYING\")][Start talk]]
+- [ ] ${stream}: [[elisp:(emacsconf-update-talk-status \"${slug}\" \".\" \"OPEN_Q\")][Open the IRC channel (${channel}) and the pad]]
+- [ ] ${stream}: When it's time for the next talk, close the Q&A windows and move on to the next talk
+")
+ (_
+ "- [ ] ${stream}: Display the in-between slide
+- [ ] ${host}: Connect to the ${track} channel in Mumble and introduce the talk
+- [ ] ${stream}: [[elisp:(emacsconf-update-talk-status \"${slug}\" \".\" \"PLAYING\")][Start talk]]
+- [ ] ${stream}: [[elisp:(emacsconf-update-talk-status \"${slug}\" \".\" \"OPEN_Q\")][Open the IRC channel (${channel}) and the pad]]
+- [ ] ${stream}: When it's time for the next talk, close the Q&A windows and move on to the next talk
+"))
+ nil nil 1)))))
+ (if do-insert (insert result))
+ result))
+
+(defun emacsconf-reload ()
+ "Reload the emacsconf-el modules."
+ (interactive)
+ (mapc #'load-library '("emacsconf" "emacsconf-erc" "emacsconf-publish" "emacsconf-stream" "emacsconf-pad")))
(provide 'emacsconf)
;;; emacsconf.el ends here