439 lines
18 KiB
EmacsLisp
Executable file
439 lines
18 KiB
EmacsLisp
Executable file
;; Set the package installation directory so that packages aren't stored in the
|
|
;; ~/.emacs.d/elpa path.
|
|
(require 'package)
|
|
(setq package-user-dir (expand-file-name "./.packages"))
|
|
(setq package-archives '(("melpa" . "https://melpa.org/packages/")
|
|
("melpa-stable" . "https://stable.melpa.org/packages/")
|
|
("elpa" . "https://elpa.gnu.org/packages/")))
|
|
|
|
;; Initialize the package system
|
|
(package-initialize)
|
|
(unless package-archive-contents
|
|
(package-refresh-contents))
|
|
|
|
;; Install use-package
|
|
(unless (package-installed-p 'use-package)
|
|
(package-install 'use-package))
|
|
(require 'use-package)
|
|
|
|
;;; see these later
|
|
;; Require built-in dependencies
|
|
(require 'vc-git)
|
|
(require 'ox-publish)
|
|
(require 'subr-x)
|
|
(require 'cl-lib)
|
|
|
|
|
|
;; Install dependencies
|
|
(package-install 'htmlize)
|
|
(package-install 'ox-gemini)
|
|
|
|
;; Load the publishing system
|
|
(require 'ox-publish)
|
|
(use-package ox-gemini)
|
|
|
|
;; Install other dependencies
|
|
(use-package esxml
|
|
:pin "melpa-stable"
|
|
:ensure t)
|
|
|
|
(use-package htmlize
|
|
:ensure t)
|
|
|
|
(use-package webfeeder
|
|
:ensure t)
|
|
|
|
|
|
|
|
(defvar yt-iframe-format
|
|
(concat "<div class=\"video\">"
|
|
" <iframe src=\"https://www.youtube.com/embed/%s\" allowfullscreen></iframe>"
|
|
"</div>"))
|
|
|
|
(defun my/embed-video (video-id)
|
|
(format yt-iframe-format video-id))
|
|
|
|
|
|
|
|
(setq user-full-name "Dibyashanu Pati")
|
|
(setq user-mail-address "dibyashanu@protonmail.com")
|
|
|
|
(defvar my/site-url (if (string-equal (getenv "CI") "true")
|
|
"https://cosmicflow.space"
|
|
"http://localhost:8080")
|
|
"The URL for the site being generated.")
|
|
|
|
(defun my/site-header ()
|
|
(list `(header (@ (class "site-header"))
|
|
(div (@ (class "container"))
|
|
(div (@ (class "site-title"))
|
|
(img (@ (class "logo")
|
|
(src "/pics/sc_logo.png")
|
|
(alt "Cosmicflow")))))
|
|
(div (@ (class "site-masthead"))
|
|
(div (@ (class "container"))
|
|
(nav (@ (class "nav"))
|
|
(a (@ (class "nav-link") (href "/")) "Home") " "
|
|
(a (@ (class "nav-link") (href "/exocortex/")) "Thought Garden") " "
|
|
(a (@ (class "nav-link") (href "/blogs/")) "Blog") " "
|
|
(a (@ (class "nav-link") (href "https://cosmicflow.space:3030/")) "Git") " "
|
|
(a (@ (class "nav-link") (href "/about/")) "About") " "
|
|
(a (@ (class "nav-link") (href "/contact/")) "PGP") " "
|
|
(a (@ (class "nav-link") (href "/links/")) "Links") " "
|
|
(a (@ (class "nav-link") (href "/gallery/")) "Gallery") " "
|
|
;; (a (@ (class "nav-link") (href "https://store.systemcrafters.net?utm_source=sc-site-nav")) "Store") " "
|
|
(a (@ (class "nav-link") (href "/news/")) "News")))))))
|
|
|
|
(defun my/site-footer ()
|
|
(list `(footer (@ (class "site-footer"))
|
|
(div (@ (class "container"))
|
|
(div (@ (class "row"))
|
|
(div (@ (class "column"))
|
|
(p (a (@ (href "/privacy_policy/")) "Privacy Policy")
|
|
" · "
|
|
(a (@ (href "/credits/")) "Credits")
|
|
" · "
|
|
(a (@ (href "/source_code/")) "Source Code")
|
|
;; " · "
|
|
;; (a (@ (rel "me") (href "https://fosstodon.org/@daviwil")) "Fediverse"))
|
|
(p "© 2024-2025 Dibyashanu Pati - Except where otherwise noted, this work is licensed under http://creativecommons.org/licenses/by/3.0/"
|
|
(img (@ (class "column align-left")
|
|
(src "/pics/CCLogoColorPop1.gif")
|
|
(style "width: 40px")
|
|
(alt "Creative Commons")))
|
|
(img (@ (class "column align-left")
|
|
(src "/pics/brainmade-88x31-dark.png")
|
|
(style "width: 60px")
|
|
(alt "brainmade.org")))
|
|
)
|
|
|
|
)
|
|
)
|
|
)))))
|
|
|
|
|
|
(defun get-article-output-path (org-file pub-dir)
|
|
(let ((article-dir (concat pub-dir
|
|
(downcase
|
|
(file-name-as-directory
|
|
(file-name-sans-extension
|
|
(file-name-nondirectory org-file)))))))
|
|
|
|
(if (string-match "\\/index.org\\|\\/404.org$" org-file)
|
|
pub-dir
|
|
(progn
|
|
(unless (file-directory-p article-dir)
|
|
(make-directory article-dir t))
|
|
article-dir))))
|
|
|
|
|
|
(defun my/get-commit-hash ()
|
|
"Get the short hash of the latest commit in the current repository."
|
|
(string-trim-right
|
|
(with-output-to-string
|
|
(with-current-buffer standard-output
|
|
(vc-git-command t nil nil "rev-parse" "--short" "HEAD")))))
|
|
|
|
|
|
(cl-defun my/generate-page (title
|
|
content
|
|
info
|
|
&key
|
|
(publish-date)
|
|
(head-extra)
|
|
(pre-content)
|
|
(exclude-header)
|
|
(exclude-footer))
|
|
(concat
|
|
"<!-- Generated from " (my/get-commit-hash) " on " (format-time-string "%Y-%m-%d @ %H:%M") " with " org-export-creator-string " -->\n"
|
|
"<!DOCTYPE html>"
|
|
(sxml-to-xml
|
|
`(html (@ (lang "en"))
|
|
(head
|
|
(meta (@ (charset "utf-8")))
|
|
(meta (@ (author "Cosmicflow - Dibyashanu Pati")))
|
|
(meta (@ (name "viewport")
|
|
(content "width=device-width, initial-scale=1, shrink-to-fit=no")))
|
|
(link (@ (rel "icon") (type "image/png") (href "/img/favicon.png")))
|
|
(link (@ (rel "alternative")
|
|
(type "application/rss+xml")
|
|
(title "System Crafters News")
|
|
(href "/rss/news.xml")))
|
|
(link (@ (rel "stylesheet") (href "/fonts/iosevka-aile/iosevka-aile.css")))
|
|
(link (@ (rel "stylesheet") (href "/fonts/jetbrains-mono/jetbrains-mono.css")))
|
|
(link (@ (rel "stylesheet") (href "/css/code.css")))
|
|
(link (@ (rel "stylesheet") (href "/css/site.css")))
|
|
;; (script (@ (defer "defer")
|
|
;; (data-domain "systemcrafters.net")
|
|
;; (src "https://plausible.io/js/plausible.js"))
|
|
;; ;; Empty string to cause a closing </script> tag
|
|
;; "")
|
|
;; ,(when head-extra head-extra)
|
|
(title ,(concat title " - Cosmicflow")))
|
|
(body ,@(unless exclude-header
|
|
(my/site-header))
|
|
(div (@ (class "container"))
|
|
(div (@ (class "site-post"))
|
|
(h1 (@ (class "site-post-title"))
|
|
,title)
|
|
,(when publish-date
|
|
`(p (@ (class "site-post-meta")) ,publish-date))
|
|
,(if-let ((video-id (plist-get info :video)))
|
|
(my/embed-video video-id))
|
|
,(when pre-content pre-content)
|
|
(div (@ (id "content"))
|
|
,content))
|
|
)
|
|
,@(unless exclude-footer
|
|
(my/site-footer)))))))
|
|
|
|
(defun my/org-html-template (contents info)
|
|
(my/generate-page (org-export-data (plist-get info :title) info)
|
|
contents
|
|
info
|
|
:publish-date (org-export-data (org-export-get-date info "%B %e, %Y") info)))
|
|
|
|
(defun my/org-html-link (link contents info)
|
|
"Removes file extension and changes the path into lowercase file:// links."
|
|
(when (and (string= 'file (org-element-property :type link))
|
|
(string= "org" (file-name-extension (org-element-property :path link))))
|
|
(org-element-put-property link :path
|
|
(downcase
|
|
(file-name-sans-extension
|
|
(org-element-property :path link)))))
|
|
|
|
;; (let ((exported-link (org-export-custom-protocol-maybe link contents 'html info)))
|
|
;; (cond
|
|
;; (exported-link exported-link)
|
|
;; ((equal contents nil)
|
|
;; (format "<a href=\"%s\">%s</a>"
|
|
;; (org-element-property :raw-link link)
|
|
;; (org-element-property :raw-link link)))
|
|
;; ((string-prefix-p "/" (org-element-property :raw-link link))
|
|
;; (format "<a href=\"%s\">%s</a>"
|
|
;; (org-element-property :raw-link link)
|
|
;; contents))
|
|
;; (t (org-export-with-backend 'html link contents info))))
|
|
(let ((exported-link (org-export-custom-protocol-maybe link contents 'html info)))
|
|
(cond
|
|
(exported-link exported-link)
|
|
((equal contents nil)
|
|
(format "<a href=\"%s\">%s</a>"
|
|
(org-element-property :raw-link link)
|
|
(org-element-property :raw-link link)))
|
|
((string-prefix-p "/" (org-element-property :raw-link link))
|
|
(format "<a href=\"%s\">%s</a>"
|
|
(org-element-property :raw-link link)
|
|
contents))
|
|
((and (string-match-p (concat "\\." (regexp-opt '("jpg" "jpeg" "png" "gif")))
|
|
(org-element-property :raw-link link))
|
|
(not (equal contents nil)))
|
|
(format "<img src=\"%s\" alt=\"%s\" style=\"max-width: 100%%; height: auto;\">"
|
|
(org-element-property :raw-link link)
|
|
contents))
|
|
(t (org-export-with-backend 'html link contents info))))
|
|
)
|
|
|
|
|
|
(defun my/make-heading-anchor-name (headline-text)
|
|
(thread-last headline-text
|
|
(downcase)
|
|
(replace-regexp-in-string " " "-")
|
|
(replace-regexp-in-string "[^[:alnum:]_-]" "")))
|
|
|
|
|
|
(defun my/org-html-headline (headline contents info)
|
|
(let* ((text (org-export-data (org-element-property :title headline) info))
|
|
(level (org-export-get-relative-level headline info))
|
|
(level (min 7 (when level (1+ level))))
|
|
(anchor-name (my/make-heading-anchor-name text))
|
|
(attributes (org-element-property :ATTR_HTML headline))
|
|
(container (org-element-property :HTML_CONTAINER headline))
|
|
(container-class (and container (org-element-property :HTML_CONTAINER_CLASS headline))))
|
|
(when attributes
|
|
(setq attributes
|
|
(format " %s" (org-html--make-attribute-string
|
|
(org-export-read-attribute 'attr_html `(nil
|
|
(attr_html ,(split-string attributes))))))))
|
|
(concat
|
|
(when (and container (not (string= "" container)))
|
|
(format "<%s%s>" container (if container-class (format " class=\"%s\"" container-class) "")))
|
|
(if (not (org-export-low-level-p headline info))
|
|
(format "<h%d%s><a id=\"%s\" class=\"anchor\" href=\"#%s\">¶</a>%s</h%d>%s"
|
|
level
|
|
(or attributes "")
|
|
anchor-name
|
|
anchor-name
|
|
text
|
|
level
|
|
(or contents ""))
|
|
(concat
|
|
(when (org-export-first-sibling-p headline info) "<ul>")
|
|
(format "<li>%s%s</li>" text (or contents ""))
|
|
(when (org-export-last-sibling-p headline info) "</ul>")))
|
|
(when (and container (not (string= "" container)))
|
|
(format "</%s>" (cl-subseq container 0 (cl-search " " container)))))))
|
|
|
|
(defun my/org-html-src-block (src-block _contents info)
|
|
(let* ((lang (org-element-property :language src-block))
|
|
(code (org-html-format-code src-block info)))
|
|
(format "<pre>%s</pre>" (string-trim code))))
|
|
|
|
;; (defun my/org-html-special-block (special-block contents info)
|
|
;; "Transcode a SPECIAL-BLOCK element from Org to HTML.
|
|
;; CONTENTS holds the contents of the block. INFO is a plist
|
|
;; holding contextual information."
|
|
;; (let* ((block-type (org-element-property :type special-block))
|
|
;; (attributes (org-export-read-attribute :attr_html special-block)))
|
|
;; (format "<div class=\"%s center\">\n%s\n</div>"
|
|
;; block-type
|
|
;; (or contents
|
|
;; (if (string= block-type "cta")
|
|
;; "If you find this guide helpful, please consider supporting System Crafters via the links on the <a href=\"/how-to-help/#support-my-work\">How to Help</a> page!"
|
|
;; "")))))
|
|
|
|
(org-export-define-derived-backend 'site-html 'html
|
|
:translate-alist
|
|
'((template . my/org-html-template)
|
|
(link . my/org-html-link)
|
|
(src-block . my/org-html-src-block)
|
|
;; (special-block . my/org-html-special-block)
|
|
(headline . my/org-html-headline))
|
|
:options-alist
|
|
'((:video "VIDEO" nil nil)))
|
|
|
|
(defun org-html-publish-to-html (plist filename pub-dir)
|
|
"Publish an org file to HTML, using the FILENAME as the output directory."
|
|
(let ((article-path (get-article-output-path filename pub-dir)))
|
|
(cl-letf (((symbol-function 'org-export-output-file-name)
|
|
(lambda (extension &optional subtreep pub-dir)
|
|
;; The 404 page is a special case, it must be named "404.html"
|
|
(concat article-path
|
|
(if (string= (file-name-nondirectory filename) "404.org") "404" "index")
|
|
extension))))
|
|
(org-publish-org-to 'site-html
|
|
filename
|
|
(concat "." (or (plist-get plist :html-extension)
|
|
"html"))
|
|
plist
|
|
article-path))))
|
|
|
|
|
|
(defun my/rss-extract-title (html-file)
|
|
"Extract the title from an HTML file."
|
|
(with-temp-buffer
|
|
(insert-file-contents html-file)
|
|
(let ((dom (libxml-parse-html-region (point-min) (point-max))))
|
|
(dom-text (car (dom-by-class dom "site-post-title"))))))
|
|
|
|
(defun my/rss-extract-date (html-file)
|
|
"Extract the post date from an HTML file."
|
|
(with-temp-buffer
|
|
(insert-file-contents html-file)
|
|
(let* ((dom (libxml-parse-html-region (point-min) (point-max)))
|
|
(date-string (dom-text (car (dom-by-class dom "site-post-meta"))))
|
|
(parsed-date (parse-time-string date-string))
|
|
(day (nth 3 parsed-date))
|
|
(month (nth 4 parsed-date))
|
|
(year (nth 5 parsed-date)))
|
|
;; NOTE: Hardcoding this at 8am for now
|
|
(encode-time 0 0 8 day month year))))
|
|
|
|
(setq webfeeder-title-function #'my/rss-extract-title
|
|
webfeeder-date-function #'my/rss-extract-date)
|
|
|
|
;; (setq org-publish-use-timestamps-flag nil)
|
|
;; (setq org-html-validation-link nil
|
|
;; org-html-head-include-scripts nil
|
|
;; org-html-head-include-default-style nil
|
|
;; org-html-preamble nil
|
|
;; org-html-postamble nil
|
|
;; org-html-use-infojs nil
|
|
;; )
|
|
(setq org-publish-use-timestamps-flag t
|
|
org-publish-timestamp-directory "./.org-cache/"
|
|
org-export-with-section-numbers nil
|
|
org-export-use-babel nil
|
|
org-export-with-smart-quotes t
|
|
org-export-with-sub-superscripts nil
|
|
org-export-with-tags 'not-in-toc
|
|
org-html-htmlize-output-type 'css
|
|
org-html-prefer-user-labels t
|
|
;; org-html-link-home my/site-url
|
|
org-html-link-use-abs-url t
|
|
org-html-link-org-files-as-html t
|
|
org-html-html5-fancy t
|
|
org-html-self-link-headlines t
|
|
org-export-with-toc nil
|
|
make-backup-files nil)
|
|
|
|
|
|
|
|
(setq org-publish-project-alist
|
|
(list '("cosmicflow:main"
|
|
:base-directory "./content"
|
|
:base-extension "org"
|
|
:publishing-directory "./public/cosmicflow-html"
|
|
:publishing-function org-html-publish-to-html
|
|
:with-title nil
|
|
;;:with-author nil
|
|
;;:with-creator nil
|
|
;;:email nil
|
|
:with-timestamps nil)
|
|
'("cosmicflow:assets"
|
|
:base-directory "./assets"
|
|
:base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|woff2\\|ttf"
|
|
:publishing-directory "./public/cosmicflow-html"
|
|
:recursive t
|
|
:publishing-function org-publish-attachment)
|
|
|
|
'("cosmicpirates-gmi"
|
|
:base-directory "./content"
|
|
:base-extension "org"
|
|
:publishing-directory "./public/cosmicflow-gmi"
|
|
:recursive t
|
|
:publishing-function org-gemini-publish-to-gemini
|
|
:with-author nil
|
|
:with-creator nil
|
|
:email nil
|
|
:with-timestamps nil)
|
|
;; ("cosmicpirates-gmi-static"
|
|
;; :base-directory "./content/static"
|
|
;; :base-extension "css\\|ttf\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|xml\\|html"
|
|
;; :publishing-directory "./public/cosmicpirates.space-gmi/static"
|
|
;; :recursive t
|
|
;; :publishing-function org-publish-attachment
|
|
;; export txt file as txt file in org html website publish
|
|
;; )
|
|
)
|
|
|
|
)
|
|
|
|
;; (defun my/publish ()
|
|
;; "Publish the entire site."
|
|
;; (interactive)
|
|
;; (org-publish-all (string-equal (or (getenv "FORCE")
|
|
;; (getenv "CI"))
|
|
;; "true"))
|
|
|
|
;; (webfeeder-build "rss/news.xml"
|
|
;; "./public"
|
|
;; my/site-url
|
|
;; (let ((default-directory (expand-file-name "./public/")))
|
|
;; (remove "news/index.html"
|
|
;; (directory-files-recursively "news"
|
|
;; ".*\\.html$")))
|
|
;; :builder 'webfeeder-make-rss
|
|
;; :title "cosmiflow news"
|
|
;; :description "News and Insights from Cosmicflow!"
|
|
;; :author "Dibyashanu Pati")
|
|
|
|
;; )
|
|
|
|
|
|
|
|
|
|
;; publish the websites
|
|
(org-publish-all t)
|
|
|
|
(message "Build complete!")
|