;; 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 "
" " " "
")) (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/")) "Exocortex requires javascript") " " (a (@ (class "nav-link") (href "/blogs/")) "Blog") " " (a (@ (class "nav-link") (href "https://cosmicflow.space:3030/")) "Git clearweb") " " (a (@ (class "nav-link") (href "http://git.cosmicflow.i2p/")) "Git i2p") " " (a (@ (class "nav-link") (href "http://pohnq5z43ok3tefj6v3qx2zgn7zinpf4s55kztqxifa2poc3oirpe2id.onion/")) "Git tor") " " (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 "/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 "/attributions/")) "Attributions") " · " (a (@ (href "https://cosmicflow.space:3030/DibyashanuPati/cosmicflow")) "Source Code (clearweb)") " · " (a (@ (href "http://git.cosmicflow.i2p/DibyashanuPati/cosmicflow")) "Source Code (i2p)") " · " (a (@ (href "http://pohnq5z43ok3tefj6v3qx2zgn7zinpf4s55kztqxifa2poc3oirpe2id.onion/DibyashanuPati/cosmicflow")) "Source Code (tor)") (p "© 2024-2025 Dibyashanu Pati - CC BY-SA 4.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 "\n" "" (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 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 "%s" ;; (org-element-property :raw-link link) ;; (org-element-property :raw-link link))) ;; ((string-prefix-p "/" (org-element-property :raw-link link)) ;; (format "%s" ;; (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 "%s" (org-element-property :raw-link link) (org-element-property :raw-link link))) ((string-prefix-p "/" (org-element-property :raw-link link)) (format "%s" (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 "\"%s\"" (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 "%s%s" level (or attributes "") anchor-name anchor-name text level (or contents "")) (concat (when (org-export-first-sibling-p headline info) ""))) (when (and container (not (string= "" container))) (format "" (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 "
%s
" (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 "
\n%s\n
" ;; 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 How to Help 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!")