;; -*- lexical-binding: t; -*- ;;; latex packages (setq org-latex-packages-alist '(("" "amsfonts" t)) ;; org-format-latex-options (plist-put org-format-latex-options :scale 1.5) ) ;;; org-export (require 'ox) ;;; org capture bookmark (setq org-capture-bookmark nil) ;;; org adapt indentation (setq org-adapt-indentation nil) ;;; org modules (setq org-modules '(ol-gnus ol-info ol-eww)) ;;; Hide unnecessary decorations (setq org-hide-emphasis-markers nil) (setq org-hide-macro-markers nil) (setq org-hide-leading-stars nil) ;;; follow link on return (setq org-return-follows-link t) ;;; org-element (require 'org-element) ;;; org-clock (require 'org-clock) ;;; tempo (require 'org-tempo) (add-to-list 'org-structure-template-alist (cons "g" "src durand-greek")) (add-to-list 'org-structure-template-alist (cons "el" "src emacs-lisp")) (add-to-list 'org-structure-template-alist (cons "pf" "proof")) ;; (add-to-list 'org-modules 'ol-gnus) ;;; Keys ;; Mac OS has a keyboard short cut for H-a, and I don't know how to ;; disable that! (define-key global-map (vector ?\H-a) #'org-agenda) (define-key global-map (vector 3 97) #'org-agenda) (define-key global-map (vector 3 99) #'org-capture) (define-key global-map (vector 3 ?l) #'org-store-link) (define-key org-mode-map (vector ?\C-') nil) (define-key org-mode-map (vector ?\C-,) nil) (define-key org-mode-map (vector 'C-return) nil) (define-key org-mode-map (vector 'C-S-return) nil) (define-key org-mode-map (vector 3 ?\S-l) #'org-toggle-link-display) (define-key org-mode-map (vector 3 ?\C-\S-l) #'org-insert-last-stored-link) (declare-function 'durand-pulse-pulse-line "~/.emacs.d/basic.el") (add-hook 'org-follow-link-hook #'durand-pulse-recenter-top) ;;; Some variables (setq org-todo-keywords '((sequence "TODO(t)" "START(s)" "WORKING(w)" "HARD-WORKING(h)" "ALMOST(a)" "|" "DONE(d)") (sequence "TO-THINK(c)" "PENDING(p)" "HARD(r)" "IMPOSSIBLE(i)" "|" "SOLVED(v)")) org-tags-column -110 org-special-ctrl-a/e nil org-highlight-latex-and-related '(native latex script entities) org-agenda-files (mapcar (lambda (fn) (expand-file-name fn org-directory)) (list "notes.org" "agenda.org" "aujourdhui.org" "math_article_links.org")) org-log-into-drawer t org-highest-priority ?A org-lowest-priority ?E org-default-priority ?B org-pretty-entities t org-link-file-path-type 'noabbrev ) ;;; open link functions (dolist (scheme '("ftp" "http" "https" "mailto" "news")) (org-link-set-parameters scheme :follow (lambda (url &optional arg) (browse-url (concat scheme ":" url) arg)))) (use-package "tablist" 'tablist) (setq pdf-info-epdfinfo-program "/Users/durand/elisp_packages/pdf-tools/epdfinfo" ) (load-config "org-pdftools.el") (set 'org-pdftools-link-prefix "pdfview") (org-pdftools-setup-link) ;;; Archive advice ;;;###autoload (defun durand-archive-save-and-kill (old-fun &optional find-done) "Save and kill the buffer after archiving." (let* ((location (org-archive--compute-location (or (org-entry-get nil "ARCHIVE" 'inherit) org-archive-location))) (archive-buffer-name (file-name-nondirectory (car location))) (opened (get-buffer archive-buffer-name))) (funcall old-fun find-done) (unless opened (when (get-buffer archive-buffer-name) (with-current-buffer archive-buffer-name (ignore-errors (save-buffer 0))) (kill-buffer archive-buffer-name))))) (advice-add 'org-archive-subtree :around 'durand-archive-save-and-kill) ;;; Archiving ;;;###autoload (defun durand-org-archive-file-name (file-name) "Produce an archive file name. The rule is as follows: example.org example.org_archive example(1).org_archive example(2).org_archive etc." (cond ((not (or (string-match "org$" file-name) (string-match "org_archive$" file-name))) (user-error "Not an org file."))) (cond ((string-match "org$" file-name) (replace-match "org_archive" nil nil file-name)) ((string-match "\\((\\([[:digit:]]+\\))\\).org_archive$" file-name) (replace-match (format "(%d)" (1+ (string-to-number (match-string-no-properties 2 file-name)))) nil nil file-name 1)) (t (replace-regexp-in-string ".org_archive$" "(1).org_archive" file-name)))) ;;;###autoload (defun durand-org-goto-archive () "Go to the archive file of the current org file, if any. It will cycle through all archive files of the file. The rule is as follows: example.org example.org_archive example(1).org_archive example(2).org_archive etc." (interactive) (unless (derived-mode-p 'org-mode) (user-error "Not in an org file.")) (let* ((current-name (buffer-file-name (current-buffer))) (base-name (cond ((string-match "org_archive$" current-name) (replace-match "org" nil nil current-name)) (t current-name))) (base-name (cond ((string-match "\\(([[:digit:]])\\).org$" base-name) (replace-match "" nil nil base-name 1)) (t base-name))) (next-name (durand-org-archive-file-name current-name)) (next-name (cond ((file-exists-p next-name) next-name) (t base-name)))) (find-file next-name))) ;;; Set tags ;; NOTE: Modified by Durand to use `completing-read-multiple' instead of ;; `completing-read'. ;;;###autoload (defun durand-org-set-tags-command (&optional arg) "Set the tags for the current visible entry. When called with `\\[universal-argument]' prefix argument ARG, \ realign all tags in the current buffer. When called with `\\[universal-argument] \\[universal-argument]' prefix argument, \ unconditionally do not offer the fast tag selection interface. If a region is active, set tags in the region according to the setting of `org-loop-over-headlines-in-active-region'. This function is for interactive use only; in Lisp code use `org-set-tags' instead." (interactive "P") (let ((org-use-fast-tag-selection (unless (equal '(16) arg) org-use-fast-tag-selection))) (cond ((equal '(4) arg) (org-align-tags t)) ((and (org-region-active-p) org-loop-over-headlines-in-active-region) (let (org-loop-over-headlines-in-active-region) ; hint: infinite recursion. (org-map-entries #'org-set-tags-command nil (if (eq org-loop-over-headlines-in-active-region 'start-level) 'region-start-level 'region) (lambda () (when (org-invisible-p) (org-end-of-subtree nil t)))))) (t (save-excursion (org-back-to-heading) (let* ((all-tags (org-get-tags)) (table (setq org-last-tags-completion-table (org--tag-add-to-alist (and org-complete-tags-always-offer-all-agenda-tags (org-global-tags-completion-table (org-agenda-files))) (or org-current-tag-alist (org-get-buffer-tags))))) (current-tags (cl-remove-if (lambda (tag) (get-text-property 0 'inherited tag)) all-tags)) (inherited-tags (cl-remove-if-not (lambda (tag) (get-text-property 0 'inherited tag)) all-tags)) (tags (replace-regexp-in-string ;; Ignore all forbidden characters in tags. "[^[:alnum:]_@#%]+" ":" (if (or (eq t org-use-fast-tag-selection) (and org-use-fast-tag-selection (delq nil (mapcar #'cdr table)))) (org-fast-tag-selection current-tags inherited-tags table (and org-fast-tag-selection-include-todo org-todo-key-alist)) (let ((org-add-colon-after-tag-completion (< 1 (length table)))) ;; I change this part (org-trim (org-make-tag-string (completing-read-multiple "Tags: " #'org-tags-completion-function nil nil (replace-regexp-in-string ":" "," (org-make-tag-string current-tags)) 'org-tags-history)))))))) (org-set-tags tags))))) ;; `save-excursion' may not replace the point at the right ;; position. (when (and (save-excursion (skip-chars-backward "*") (bolp)) (looking-at-p " ")) (forward-char)))) (advice-add 'org-set-tags-command :override 'durand-org-set-tags-command) ;;; Accounts ;;;###autoload (defvar durand-org-capture-account-which-list '("breakfast" "brunch" "brunverage" "lunch" "dinner" "beverage" "snack" "fruit") "The list of purposes of accounts in the capture-template for accounting.") ;;;###autoload (defun durand-org-capture-account-template () "Org capture template for account." (let* ((shop-and-items (durand-org-complete-capture-account)) (which (completing-read "For what? " durand-org-capture-account-which-list)) (cost (number-to-string (read-number "Cost: " 0))) (from (completing-read "From: " '("Cash" "etique"))) (active-time (format-time-string (cdr org-time-stamp-formats))) (inactive-time (let ((temp active-time)) (while (string-match "<\\|>" temp) (setf temp (replace-match (cond ((string= (match-string-no-properties 0 temp) "<") "[") ((string= (match-string-no-properties 0 temp) ">") "]")) nil nil temp))) temp))) (format "%s\n :PROPERTIES:\n :cost: %s\n :FROM: %s\n :RECORD_TIME: %s\n :END: \n %s%%?" which cost from inactive-time shop-and-items))) (require 'org-protocol) ;;; Capture templates (setq org-capture-templates '(("d" "Record Diaries" entry (file+olp+datetree "~/org/diary.org") "* %?\n :PROPERTIES:\n :RECORD_TIME: %U\n :END:\n\n" :jump-to-captured t) ("l" "Store links" entry (file "/Users/durand/org/math_article_links.org") "* TO-THINK %? %(org-insert-time-stamp (org-read-date nil t \"+0d\") nil t)\n%a\n" :kill-buffer t) ("L" "for storing webpages" entry #'org-determine-link-file "* PENDING %(org-filter-title) %(org-determine-tag)\n :PROPERTIES:\n :RECORD_TIME: %U\n :END:\n\n %(org-filtered-link)\n %i\n %?" :empty-lines 1 :kill-buffer t :immediate-finish t) ("t" "TODO" entry (file "~/org/aujourdhui.org") "* TODO %? %^{Date to do:}t\n :PROPERTIES:\n :RECORD_TIME: %U\n :END:\n\n" :kill-buffer t) ;; ("b" "Blog posts" entry ;; (file+headline "~/org/notes.org" "Blog posts") ;; "* %? %(org-insert-time-stamp (org-read-date nil t \"+0d\"))\n%i\n") ;; ("a" "Abstractions" entry ;; (file+headline "~/org/wiki.org" "Abstractions") ;; "* ABSTRACT %?\n :PROPERTIES:\n :RECORD_TIME: %U\n :END:\n\n") ("A" "Agenda" entry (file+headline "~/org/agenda.org" "Agenda") "* TODO %?\n :PROPERTIES:\n :RECORD_TIME: %U\n :DURATION: %^{Date: }t\n :END:\n\n") ;; ("y" "YiFu" entry ;; (file+headline "~/org/wiki.org" "Yi Fu Tips") ;; "* MEMO %^{word}\n :PROPERTIES:\n :STORY: %\\2\n :MEANING: %\\3\n :END:\n** Yi Fu story\n %^{story}\n** Meaning\n %^{meaning}" ;; :kill-buffer t ;; :immediate-finish t) ("c" "Chansons" entry (file+headline "~/org/wiki.org" "Liste de Chansons") "* MEMO %^{title}\n :PROPERTIES:\n :RECORD_TIME: %U\n :LINK: [[%^{link}][%^{description}]]\n :END:\n %?" :jump-to-captured t) ;; ("f" "français" entry ;; (file+headline "~/org/français/français.org" "Liste de mots français") ;; "* MEMO %^{mot} :drill:\n :PROPERTIES:\n :DRILL_CARD_TYPE: français\n :RECORD_TIME: %U\n :MEANING: %^{ce qu'il veut dire}\n :END:\n\n MEANING: %\\2\n%?" ;; :jump-to-captured t) ("g" "GNUS" entry (file "~/org/notes.org") "* TO-THINK %:subject\n :PROPERTIES:\n :RECORD_TIME: %U\n :END:\n %:from\n %:to\n %a\n %?" :empty-lines 1 :kill-buffer t))) (setq org-capture-templates-contexts '(("g" ((in-mode . "gnus-summary-mode") (in-mode . "gnus-article-mode"))))) ;;; agenda custom commands (setq org-agenda-custom-commands '(("o" "Custom" ((agenda "" (;; (org-super-agenda-groups ;; '((:name "Progress today" ;; :log t ;; :order -1) ;; (:name "Morning Tasks" ;; :log t ;; :tag "morning" ;; :order 1) ;; (:name "Afternoon Taks" ;; :log t ;; :tag "afternoon" ;; :order 2) ;; (:name "Night Taks" ;; :log t ;; :tag "night" ;; :order 3) ;; (:name "Deadlines" :deadline t) ;; (:name "Health" ;; :tag "santé" ;; :log t ;; :order 5) ;; (:name "MATH" ;; :tag "math" ;; :order -1) ;; (:name "Très Important" ;; :priority "A" ;; :order -1) ;; (:name "Scheduled" ;; :and (:scheduled t :not (:priority "A")) ;; :order 5 ;; :log t))) (org-agenda-span 'day) (org-agenda-sorting-strategy '(priority-down time-up)))) ;; NOTE: A reading plan (tags "a_voir+matin|a_voir+après_midi|a_voir+nuit" ((org-agenda-files '("~/org/notes.org" "~/org/math_article_links.org")) (org-agenda-overriding-header "À Lire") ;; (org-super-agenda-groups ;; '((:name "Le Matin" :tag "matin") ;; (:name "L'après-midi" :tag "après_midi") ;; (:name "La Nuit" :tag "nuit"))) )) (todo "TO-THINK" (;; (org-super-agenda-groups ;; '((:name "À Voir" :tag "a_voir") ;; (:name "Mathématiques" :tag "math") ;; (:name "TeX" :tag "tex") ;; (:name "Question" :tag "question"))) (org-agenda-overriding-header "TO-THINK")))) ((org-agenda-block-separator nil))) ;; ("n" "test" ;; ((durand-agenda-command-test))) ;; ("m" "sections with time" ;; ((durand-agenda-command-sections-time))) )) ;;; elisp in link confirmation (setq org-link-elisp-confirm-function 'y-or-n-p) ;;; agenda window setup (setq org-agenda-window-setup 'other-tab) ;; The following will be ignored since the above is 'other-tab. (setq org-agenda-restore-windows-after-quit t) ;;; novel addresses ;;;###autoload (defvar durand-novel-addresses-regexp '("uukanshu" "ptwxz" "piaotian" "101novel" "booktxt") "Regexp for matching a novel website.") ;;;###autoload (defun org-determine-link-file () "Go to the file to capture to based upon the URL" (let* ((link (plist-get org-store-link-plist :link)) (file-name (cond ((string-match-p "https?://www.youtube.com" link) "youtube_links.org") ((or (string-match-p "https?://math.stackexchange.com" link) (string-match-p "https?://mathoverflow.net/" link)) "math_article_links.org") ((let ((temp durand-novel-addresses-regexp) result) (while (and (consp temp) (not result)) (cond ((string-match-p (car temp) link) (setq result t))) (setq temp (cdr temp))) result) "notes.org") ((string-match-p "https?://stacks.math.columbia.edu/" link) "math_article_links.org") (t "notes.org")))) (find-file (expand-file-name file-name org-directory)) (goto-char (point-max)))) ;;; filter title in the link ;;;###autoload (defun org-filtered-link () "Filter out some unnecessary parts in the link description" (save-match-data (let* ((link (plist-get org-store-link-plist :link)) (title (plist-get org-store-link-plist :description)) (filtered (cond ((string-match " - Mathematics Stack Exchange" title) (replace-match "" nil nil title)) ((string-match " - YouTube" title) (replace-match "" nil nil title)) ((string-match "\\(.*?\\)最新章节列表,\\1无弹窗_UU看书" title) (replace-match "\\1" nil nil title)) (t title)))) (org-link-make-string link filtered)))) ;;; Determine tag based upon the URL ;;;###autoload (defun org-determine-tag () "Determine tag based upon the URL" (let ((link (plist-get org-store-link-plist :link))) (cond ((string-match-p "https?://www.youtube.com" link) ":youtube:") ((or (string-match-p "https?://math.stackexchange.com" link) (string-match-p "https?://mathoverflow.net/" link)) ":stack:web_link:") ((let ((temp durand-novel-addresses-regexp) result) (while (and (consp temp) (not result)) (cond ((string-match-p (car temp) link) (setq result t))) (setq temp (cdr temp))) result) ":roman:") ((string-match-p "https?://stacks.math.columbia.edu/" link) ":web_link:stack:") (t ":web_link:")))) ;;;###autoload (defun org-update-novels (&optional desc) "Update the html link to a novel, or to a web_link. If DESC is non-`nil', then it is the description of the new link." (interactive) ;; HACK: Refocus the selected frame. ;; I was doing this in the applescript. But for some reason it is messed up. So ;; I let Emacs gain focus by itself now. (select-frame-set-input-focus (selected-frame)) (let* ((tags (completing-read "tag: " '("roman-ARCHIVE" "web_link-ARCHIVE") nil t)) (roman-p (string-match "roman" tags)) (files '("/Users/durand/org/notes.org" "/Users/durand/org/math_article_links.org")) (prompt (if roman-p "Chois un roman à mettre à jour: " "Chois un web lien à mettre à jour: ")) cands) (setf cands (cl-loop for file in files append (with-current-file file nil (org-map-entries (lambda () (let ((orig (durand-org-link-info t))) (list (car orig) (cdr orig) file))) tags)))) (unless roman-p (setf cands (nreverse cands))) (let* ((choix (completing-read prompt cands nil t)) (item (cl-assoc choix cands :test #'string=)) (lien (read-string "Le lien: " (current-kill 0 t)))) (with-current-file (caddr item) nil (goto-char (cadr item)) (org-update-link lien nil nil desc))))) ;;; filter out the title ;;;###autoload (defun org-filter-title () "Filter out some unnecessary parts of the link title" (let ((title (plist-get org-store-link-plist :description))) (cond ((string-match " - Mathematics Stack Exchange" title) (replace-match "" nil nil title)) ((string-match " - YouTube" title) (replace-match "" nil nil title)) ((string-match "\\(.*?\\)最新章节列表,\\1无弹窗_UU看书" title) (replace-match "\\1" nil nil title)) (t title)))) ;;; publishing settings ;;;; Custom preamble for the sidebar (defvar durand-org-publish-sidebar nil "The sidebar that provides the navigation of the website.") (setq durand-org-publish-sidebar "
\n\ Math \n\ Code \n\ Life \n\ Home \n\ Feeds (Atom)
") ;; REVIEW: This might be unnecessary. (defun durand-org-publish-insert-sidebar (_arg) "Return the sidebar." durand-org-publish-sidebar) ;;;;; Add a custom head (defvar durand-org-publish-css-file nil "A custom css-file for publishing.") (setq durand-org-publish-css-file "") (defvar durand-org-publish-favicon nil "A custom favicon for publishing.") (setq durand-org-publish-favicon "") ;;;; Don't include JavaScript in the HTML (setq org-html-head-include-scripts nil) ;;;; Projects settings (setq org-publish-project-alist (list (list "website" :components (list "math" "code" "life" "main")) (list "code" :base-directory (directory-file-name (expand-file-name "code" (expand-file-name "blog" org-directory))) :base-extension "org" :publishing-directory (directory-file-name (expand-file-name "public" org-directory)) :publishing-function #'org-html-publish-to-html :section-numbers nil :with-toc nil :with-email t :with-creator t :auto-sitemap t :recursive t :sitemap-function #'durand-org-publish-sitemap :sitemap-format-entry #'durand-org-publish-sitemap-format :sitemap-date-format "Published: %F %a %R" :sitemap-filename "code-sitemap.org" :sitemap-title "About coding" :sitemap-sort-files 'anti-chronologically :html-head (concat durand-org-publish-css-file "\n" durand-org-publish-favicon) :html-link-home "" :html-link-up "" :html-home/up-format "" :html-preamble #'durand-org-publish-insert-sidebar) (list "math" :base-directory (directory-file-name (expand-file-name "math" (expand-file-name "blog" org-directory))) :base-extension "org" :publishing-directory (directory-file-name (expand-file-name "public" org-directory)) :publishing-function #'org-html-publish-to-html :section-numbers nil :with-toc nil :with-email t :with-creator t :auto-sitemap t :recursive t :sitemap-function #'durand-org-publish-sitemap :sitemap-format-entry #'durand-org-publish-sitemap-format :sitemap-date-format "Published: %F %a %R" :sitemap-filename "math-sitemap.org" :sitemap-title "Mathematics" :sitemap-sort-files 'anti-chronologically :html-head (concat durand-org-publish-css-file "\n" durand-org-publish-favicon) :html-link-home "" :html-link-up "" :html-home/up-format "" :html-preamble #'durand-org-publish-insert-sidebar) (list "life" :base-directory (directory-file-name (expand-file-name "life" (expand-file-name "blog" org-directory))) :base-extension "org" :publishing-directory (directory-file-name (expand-file-name "public" org-directory)) :publishing-function #'org-html-publish-to-html :section-numbers nil :with-toc nil :with-email t :with-creator t :auto-sitemap t :recursive t :sitemap-function #'durand-org-publish-sitemap :sitemap-format-entry #'durand-org-publish-sitemap-format :sitemap-date-format "Published: %F %a %R" :sitemap-filename "life-sitemap.org" :sitemap-title "My life" :sitemap-sort-files 'anti-chronologically :html-head (concat durand-org-publish-css-file "\n" durand-org-publish-favicon) :html-link-home "" :html-link-up "" :html-home/up-format "" :html-preamble #'durand-org-publish-insert-sidebar) (list "main" :base-directory (directory-file-name (expand-file-name "blog" org-directory)) :base-extension "org" :publishing-directory (directory-file-name (expand-file-name "public" org-directory)) :publishing-function #'org-html-publish-to-html :section-numbers nil :with-toc nil :with-email t :with-creator t :auto-sitemap nil :recursive nil :html-head (concat durand-org-publish-css-file "\n" durand-org-publish-favicon) :html-link-home "" :html-link-up "" :html-home/up-format "" :html-preamble #'durand-org-publish-insert-sidebar))) ;;;; Sitemap function ;;;;; A hack to insert some string in the sitemap file. (defvar durand-sitemap-custom-string-alist nil "An association list that relates the title of the sitemap and the string to insert.") (setq durand-sitemap-custom-string-alist (list (list "About coding" "This is my coding blog. It contains my coding experiments, or \ one might think of them as development diaries." "code-atom.xml") (list "My life" "This is my casual blog. It contains articles about my plain \ life." "life-atom.xml") (list "Mathematics" "My Mathematics-related articles are put here." "math-atom.xml"))) ;;;;; Custom sitemap function (defun durand-org-publish-sitemap (title rep) "Return the sitemap as a string. TITLE is the title of the sitemap. REP is a representation of the files and directories in the project. Use such functions as `org-list-to-org' or `org-list-to-subtree' to transform it." (format "#+TITLE: %s\n#+AUTHOR: JSDurand\n%s\n#+DATE: <%s>\n\n%s\n\ \n#+ATTR_HTML: :border nil :rules nil :frame nil\n\ %s\n\n\ [[https://jsdurand.xyz/%s][Web feed]]" title "#+HTML_LINK_UP: index.html" (format-time-string "%F %a %R") (cadr (assoc title durand-sitemap-custom-string-alist #'string=)) ;; generate a table (org-list-to-generic rep '(:ustart "|---|" :uend "|---|" :isep "|---|")) (caddr (assoc title durand-sitemap-custom-string-alist #'string=)))) ;;;;; Custom sitemap format ;; NOTE: I use a table to style the entries. ;; NOTE: This stores the date in an ugly long format. But worry not: ;; it will be replaced by a clean form in the post-processing phase. ;; The long form is inserted here so that we can sort the entries ;; precisely. (defun durand-org-publish-sitemap-format (entry _style project) "Format the entry for the sitemap as a table with a date. ENTRY is the entry file name to format. STYLE is either 'list or 'tree, which is ignored by us. PROJECT is the current project." (format "| %s | [[file:%s][%s]] |" (format-time-string "%FT%T%z" (org-publish-find-date entry project) (current-time-zone)) entry (org-publish-find-title entry project))) ;;;; Post-processing ;; I post-process the sitemaps in order to generate a section ;; of "latest updates" on the index page. ;;;;; fix: plist-get not working ;; For some reason the built-in `plist-get' does not work... (defun durand-org-publish-plist-get (prop plist) (let (res) (while (consp plist) (cond ((eq (car plist) prop) (setq res (cadr plist)) (setq plist nil)) ((setq plist (cddr plist))))) res)) ;;;;; Helper for converting the format from `parse-time-string' (defun durand-org-publish-convert-time (spec) "Convert SPEC to a valid time value. SPEC should be the result of `parse-time-string'. It is assumed that the year, the month, and the day components are present." (let ((sec (car spec)) (minute (cadr spec)) (hour (caddr spec))) (encode-time (append (list (or sec 0) (or minute 0) (or hour 0)) (cdddr spec))))) ;;;;; Post process to generate the index page (defvar durand-org-index-entries-max-num 10 "The maximal number of entries to show on the index page.") (autoload #'durand-take (locate-user-emacs-file "common.el")) (defun durand-org-post-process (project) "Generate a proper index page and Atom feeds. Also shorten the date strings in the sitemap files, and store the completion information in an attribute. The feeds are generated by the function `durand-org-generate-atom-feed'." (let* ((project-plist (cdr (assoc project org-publish-project-alist #'string=))) (components (durand-org-publish-plist-get :components project-plist)) (sitemap-file (durand-org-publish-plist-get :sitemap-filename project-plist)) ;; I cheat here (publishing-dir (expand-file-name "~/org/public/")) (publish-sitemap-file (cond (sitemap-file (replace-regexp-in-string "org$" "html" (expand-file-name sitemap-file publishing-dir))))) (index-file (expand-file-name "index.html" publishing-dir)) contents) (cond ;; depth-first recursion (components (setq contents (apply #'append (delete nil (mapcar #'durand-org-post-process components)))) ;; We only want some items (setq contents (durand-take durand-org-index-entries-max-num (sort contents (lambda (x y) (time-less-p (car y) (car x)))))) ;; If contents is non-nil, we need to process the info in the ;; index page. (cond (contents (with-temp-buffer (insert-file-contents index-file) (goto-char (point-min)) (search-forward "Latest updates") (goto-char (line-end-position)) (while (search-forward "") (match-end 0)))) (insert "\n") (insert "\n\n\n") (mapc (lambda (entry) (insert "") (insert "\n") (insert "\n\n")) contents) (insert "\n
") (insert (format-time-string "%F" (car entry))) (insert "") (insert (cdr entry)) (insert "
") (write-region nil nil index-file))))) ((and publish-sitemap-file (stringp publish-sitemap-file) (file-exists-p publish-sitemap-file)) (with-temp-buffer (insert-file-contents publish-sitemap-file) (goto-char (point-min)) (search-forward "" nil t) (let ((regexp (rx-to-string '(seq ""))) ">") t)) pos pos-2 temp temp-time) (while (re-search-forward regexp nil t) (setq pos (point)) ;; replace the first org-left by org-right (save-excursion (goto-char (match-beginning 0)) (setq pos-2 (match-end 0)) (save-match-data (cond ((re-search-forward "left" pos-2 t) (replace-match "right") ;; fix pos (setq pos (1+ pos)))))) (search-forward "\n") (write-region nil nil publish-sitemap-file))) (setq temp (cons (cons temp-time (progn (re-search-forward regexp) (setq pos (point)) (search-forward "" orig-string) (setq temp (match-end 0)) (substring orig-string temp (progn (string-match "" orig-string temp) (match-beginning 0))))) (file-name (progn (string-match "href=\"" orig-string) (setq temp (match-end 0)) (substring orig-string temp (progn (string-match "\">" orig-string temp) (match-beginning 0))))) (content (with-temp-buffer (insert-file-contents (expand-file-name file-name publishing-dir)) (goto-char (point-min)) (search-forward "
" nil t) (search-forward "

" nil t) (buffer-substring-no-properties (1+ (point)) (progn (search-forward "

" nil t) (match-beginning 0))))) ;; escape html (content (replace-regexp-in-string ">" ">" (replace-regexp-in-string "<" "<" (replace-regexp-in-string "&" "&" content))))) (list title (durand-org-atom-format-time (file-attribute-modification-time (file-attributes (expand-file-name file-name publishing-dir)))) (concat "https://jsdurand.xyz/" file-name) (concat "https://jsdurand.xyz/" file-name) (durand-org-atom-format-time (car cell)) content))) (reverse temp))) (reverse temp))))))) ;;;;; add date/time info ;; I want to show the date of the article below the title, so that it ;; is clearer when the article is written. The default display at the ;; bottom of the page is too hidden. (defun durand-org-publish-html-advice (html-file-name) "Advice `org-html-publish-html' to add a date/time info below the \ title." (with-temp-buffer (insert-file-contents html-file-name) (goto-char (point-min)) (cond ((search-forward "h1 class=\"title\"" nil t) ;; if it does not have a title, we do not add a time info. (search-forward "" nil) ;; if it already has a time info, don't generate again (cond ((save-excursion (forward-char 1) (looking-at-p "

Date: " nil t) (setq temp (match-end 0)) (buffer-substring-no-properties temp (progn (search-forward "

") (match-beginning 0)))))))) (date-time (and date (encode-time (append (durand-take 8 (parse-time-string date)) (list 28800)))))) (cond (date (insert (format "\n

%s

\n" (format-time-string "%F" date-time))) (write-region nil nil html-file-name)))))))))) (advice-add #'org-html-publish-to-html :filter-return #'durand-org-publish-html-advice) ;;;; Atom feed ;;;;; Fixed pre-amble, template, and post-amble. (defvar durand-org-atom-titles-alist nil "An assocuation list of Atom feed titles with the project name.") (setq durand-org-atom-titles-alist (list (list "code" "JSDurand's codes" "https://jsdurand.xyz/code-atom.xml") (list "math" "Math articles of JSDurand" "https://jsdurand.xyz/math-atom.xml") (list "life" "JSDurand's life" "https://jsdurand.xyz/life-atom.xml"))) (defvar durand-org-atom-preamble nil "The preamble of an Atom feed.") (setq durand-org-atom-preamble " https://jsdurand.xyz/atom.xml %s JSDurand https://jsdurand.xyz mmemmew@gmail.com Copyright (c) 2021, JSDurand %s \ Emacs\n") (defvar durand-org-atom-entry-template nil "The template for an Atom entry. It HAS to be formatted with 6 arguments in the following order: TITLE: the title. Note this does not have a subtitle. UPDATED-TIME: the newest updated time. ID: I think I will use the URL as the ID directly. LINK: Link to this entry. PUBLISHED-TIME: the time this is published. CONTENT: a short content. ") (setq durand-org-atom-entry-template " %s %s %s %s %s ") (defvar durand-org-atom-postamble nil "The post-amble of an Atom feed.") (setq durand-org-atom-postamble "") ;;;;; Format time easily (defun durand-org-atom-format-time (time) "Format TIME in an acceptable way." (concat (format-time-string "%FT%T" time) (format "%s%02d:%02d" (cond ((> (car (current-time-zone)) 0) "+") ("-")) (/ (car (current-time-zone)) 3600) (% (car (current-time-zone)) 3600)))) ;;;;; The Atom feeds are generated in this function. (defun durand-org-generate-atom-feed (project file-name entries) "Generate an Atom feed for ENTRIES and save in FILE-NAME. PROJECT is the name of the subproject. ENTRIES is a list of entries. An entry is a list of the form (TITLE UPTIME ID LINK PUBTIME CONTENT). See the documentation string for `durand-org-atom-entry-template' for more." (with-temp-buffer (insert (apply #'format durand-org-atom-preamble (append (cdr (assoc project durand-org-atom-titles-alist #'string=)) (list (durand-org-atom-format-time nil)))) "\n") (mapc (lambda (entry) (insert (apply #'format durand-org-atom-entry-template entry) "\n")) entries) (insert durand-org-atom-postamble) (write-region nil nil file-name)))