cosmicflow/create-site.el
2024-12-06 16:36:42 +05:30

435 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 "/blogs/")) "Blog") " "
(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 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 "Except where otherwise noted, this work is licensed under http://creativecommons.org/licenses/by/3.0/")))
)
)
)
)))))
(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!")