560 lines
22 KiB
EmacsLisp
560 lines
22 KiB
EmacsLisp
;; -----------------------------
|
||
;; Package bootstrap (BATCH SAFE)
|
||
;; -----------------------------
|
||
(require 'package)
|
||
|
||
(setq package-user-dir (expand-file-name "./.packages"))
|
||
(setq package-archives
|
||
'(("melpa" . "https://melpa.org/packages/")
|
||
("gnu" . "https://elpa.gnu.org/packages/")))
|
||
|
||
(package-initialize)
|
||
|
||
(defvar site-root
|
||
(file-name-directory
|
||
(or load-file-name buffer-file-name)))
|
||
|
||
(defun site-path (path)
|
||
(expand-file-name path site-root))
|
||
|
||
|
||
(unless package-archive-contents
|
||
(package-refresh-contents))
|
||
|
||
(unless (package-installed-p 'org-roam)
|
||
(package-install 'org-roam))
|
||
|
||
(require 'ox-publish)
|
||
(require 'org)
|
||
(require 'ox-html)
|
||
(require 'json)
|
||
(require 'org-roam)
|
||
(require 'ob-latex)
|
||
(require 'seq)
|
||
(require 'cl-lib)
|
||
|
||
(use-package htmlize
|
||
:ensure t)
|
||
|
||
;(setq org-export-with-broken-links t)
|
||
|
||
(setq org-id-track-globally t)
|
||
(setq org-id-locations-file
|
||
"~/master-folder/org_files/org_roam/.org-id-locations")
|
||
|
||
(org-id-update-id-locations)
|
||
|
||
(defun z/org-html-wrap-content-for-stack (orig-fun contents info)
|
||
"Wrap Org HTML #content in stack container, leaving postamble outside."
|
||
(let* ((html (funcall orig-fun contents info))
|
||
(page (file-name-base (plist-get info :output-file)))
|
||
(input-file (plist-get info :input-file))
|
||
|
||
;; Extract planted date from filename (format: YYYYMMDDHHMMSS-title)
|
||
(planted-date
|
||
(if (string-match "^\\([0-9]\\{14\\}\\)" page)
|
||
(let ((date-str (match-string 1 page)))
|
||
(condition-case nil
|
||
(format-time-string "%Y-%m-%d"
|
||
(encode-time
|
||
(string-to-number (substring date-str 12 14)) ; second
|
||
(string-to-number (substring date-str 10 12)) ; minute
|
||
(string-to-number (substring date-str 8 10)) ; hour
|
||
(string-to-number (substring date-str 6 8)) ; day
|
||
(string-to-number (substring date-str 4 6)) ; month
|
||
(string-to-number (substring date-str 0 4)))) ; year
|
||
(error "Unknown")))
|
||
;; Fallback: try to get creation date from input file
|
||
(let ((file-path (if input-file
|
||
(expand-file-name input-file (plist-get info :base-directory))
|
||
nil)))
|
||
(if (and file-path (file-exists-p file-path))
|
||
(format-time-string "%Y-%m-%d" (nth 4 (file-attributes file-path)))
|
||
"Unknown"))))
|
||
|
||
;; Get last modified date (last tended to)
|
||
(last-tended
|
||
(let ((file-path (if input-file
|
||
(expand-file-name input-file (plist-get info :base-directory))
|
||
nil)))
|
||
(if (and file-path (file-exists-p file-path))
|
||
(format-time-string "%Y-%m-%d" (nth 5 (file-attributes file-path)))
|
||
"Unknown")))
|
||
|
||
(stack-close
|
||
"</div></article></div></div>"))
|
||
|
||
(setq html
|
||
(replace-regexp-in-string
|
||
"<div id=\"content\" class=\"content\">"
|
||
(format
|
||
"<div id=\"stack-root\">
|
||
<div class=\"stack-track\">
|
||
<article class=\"stack-pane pane-root\" data-url=\"/%s.html\">
|
||
<div id=\"content\" class=\"content\">"
|
||
page)
|
||
html
|
||
nil t))
|
||
|
||
;; Wrap title with metadata section — edit button removed
|
||
(setq html
|
||
(replace-regexp-in-string
|
||
"<h1 class=\"title\">\\(.*\\)</h1>"
|
||
(lambda (match)
|
||
(let ((title-text (match-string 1 match)))
|
||
(format
|
||
"<div class=\"title-section\">
|
||
<div class=\"title-controls\">
|
||
<button class=\"pane-fullscreen\" aria-label=\"Fullscreen\">⤢</button>
|
||
<button class=\"pane-close\" aria-label=\"Close pane\">×</button>
|
||
</div>
|
||
<h1 class=\"title\">%s</h1>
|
||
<div class=\"title-metadata\">
|
||
<span class=\"metadata-item\">
|
||
<span class=\"metadata-label\">planted</span>
|
||
<span class=\"metadata-value\">%s</span>
|
||
</span>
|
||
<span class=\"metadata-item\">
|
||
<span class=\"metadata-label\">tended</span>
|
||
<span class=\"metadata-value\">%s</span>
|
||
</span>
|
||
</div>
|
||
</div>"
|
||
title-text planted-date last-tended)))
|
||
html
|
||
nil t))
|
||
|
||
(setq html
|
||
(replace-regexp-in-string
|
||
"<div id=\"postamble\" class=\"status\">"
|
||
(concat stack-close "\n<div id=\"postamble\" class=\"status\">")
|
||
html
|
||
nil t))
|
||
|
||
html))
|
||
|
||
(advice-add 'org-html-template :around #'z/org-html-wrap-content-for-stack)
|
||
|
||
(add-to-list 'load-path default-directory)
|
||
|
||
(defvar z-shared-head
|
||
"
|
||
<link rel=\"icon\" type=\"image/png\" sizes=\"48x48\" href=\"/assets/icons/icons8-z-key-64.png\" />
|
||
|
||
<link rel=\"stylesheet\" href=\"/assets/styles/style.css\" />
|
||
<link rel=\"stylesheet\" href=\"/assets/styles/bigger-picture.min.css\" />
|
||
<link rel=\"stylesheet\" href=\"/assets/styles/media.css\" />
|
||
<link rel=\"stylesheet\" href=\"/assets/styles/cookbook.css\" />
|
||
|
||
<script src=\"/assets/scripts/script.js\" defer></script>
|
||
<script src=\"/assets/scripts/search.js\" defer></script>
|
||
<script src=\"/assets/scripts/bigger-picture.min.js\" defer></script>
|
||
<script src=\"/assets/scripts/svg-pan-zoom.min.js\" defer></script>
|
||
<script src=\"/assets/scripts/gallery-init.js\" defer></script>
|
||
<script src=\"/assets/scripts/cookbook.js\" defer></script>
|
||
|
||
<script src=\"/assets/scripts/mermaid.min.js\"></script>
|
||
|
||
<script>
|
||
MathJax = {
|
||
tex: {
|
||
inlineMath: [['\\\\(', '\\\\)']],
|
||
displayMath: [['\\\\[', '\\\\]']],
|
||
processEscapes: true
|
||
},
|
||
options: {
|
||
skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre']
|
||
}
|
||
};
|
||
</script>
|
||
<script src=\"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js\" async></script>
|
||
|
||
<style>
|
||
#mv-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:9999;align-items:center;justify-content:center;}
|
||
#mv-overlay.open{display:flex;}
|
||
#mv-modal{background:#fff;border:1px solid #ddd;border-radius:8px;width:90vw;max-width:1100px;height:85vh;display:flex;flex-direction:column;overflow:hidden;}
|
||
#mv-toolbar{display:flex;align-items:center;gap:8px;padding:10px 14px;border-bottom:1px solid #eee;flex-shrink:0;}
|
||
#mv-toolbar button{border:1px solid #ccc;border-radius:5px;padding:4px 12px;font-size:13px;cursor:pointer;background:none;}
|
||
#mv-toolbar button:hover{background:#f5f5f5;}
|
||
#mv-canvas{flex:1;overflow:hidden;position:relative;cursor:grab;user-select:none;}
|
||
#mv-canvas.dragging{cursor:grabbing;}
|
||
#mv-inner{position:absolute;top:0;left:0;transform-origin:0 0;padding:20px;}
|
||
.mermaid-view-btn{display:inline-block;margin-top:6px;padding:3px 10px;font-size:12px;cursor:pointer;border:1px solid #ccc;border-radius:4px;background:none;color:inherit;}
|
||
</style>
|
||
|
||
<div id=\"mv-overlay\">
|
||
<div id=\"mv-modal\">
|
||
<div id=\"mv-toolbar\">
|
||
<button onclick=\"mvZoom(-0.2)\">−</button>
|
||
<span id=\"mv-lbl\" style=\"min-width:42px;text-align:center;font-size:13px;\">100%</span>
|
||
<button onclick=\"mvZoom(0.2)\">+</button>
|
||
<button onclick=\"mvReset()\">Reset</button>
|
||
<button onclick=\"mvFit()\">Fit</button>
|
||
<button onclick=\"mvClose()\" style=\"margin-left:auto;\">✕ Close</button>
|
||
</div>
|
||
<div id=\"mv-canvas\"><div id=\"mv-inner\"></div></div>
|
||
<div style=\"font-size:12px;color:#888;padding:6px 14px;border-top:1px solid #eee;\">Scroll to zoom · Drag to pan · Esc to close</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
(function(){
|
||
var sc=1,tx=0,ty=0,drag=false,lx=0,ly=0;
|
||
function C(){return document.getElementById('mv-canvas');}
|
||
function N(){return document.getElementById('mv-inner');}
|
||
function L(){return document.getElementById('mv-lbl');}
|
||
function apply(){N().style.transform='translate('+tx+'px,'+ty+'px) scale('+sc+')';L().textContent=Math.round(sc*100)+'%';}
|
||
window.mvZoom=function(d,cx,cy){var r=C().getBoundingClientRect(),p=sc;cx=cx!=null?cx:r.width/2;cy=cy!=null?cy:r.height/2;sc=Math.max(0.1,Math.min(10,sc+d));tx+=(cx-tx)*(1-sc/p);ty+=(cy-ty)*(1-sc/p);apply();};
|
||
window.mvReset=function(){sc=1;tx=0;ty=0;apply();};
|
||
window.mvFit=function(){var cr=C().getBoundingClientRect(),ir=N().getBoundingClientRect();var cw=ir.width/sc,ch=ir.height/sc;sc=Math.min(cr.width/cw,cr.height/ch)*0.9;tx=(cr.width-cw*sc)/2;ty=(cr.height-ch*sc)/2;apply();};
|
||
window.mvOpen=function(el){
|
||
var svg = el.querySelector('svg');
|
||
if (!svg) return;
|
||
var clone = svg.cloneNode(true);
|
||
var vb = clone.getAttribute('viewBox');
|
||
if (vb) {
|
||
var parts = vb.split(' ');
|
||
clone.setAttribute('width', parts[2]);
|
||
clone.setAttribute('height', parts[3]);
|
||
}
|
||
clone.style.maxWidth = 'none';
|
||
clone.style.display = 'block';
|
||
N().innerHTML = '';
|
||
N().appendChild(clone);
|
||
sc=1;tx=0;ty=0;apply();
|
||
document.getElementById('mv-overlay').classList.add('open');
|
||
setTimeout(mvFit, 100);
|
||
};
|
||
window.mvClose=function(){document.getElementById('mv-overlay').classList.remove('open');};
|
||
window.addEventListener('load',function(){
|
||
C().addEventListener('wheel',function(e){e.preventDefault();var r=C().getBoundingClientRect();mvZoom(e.deltaY<0?.15:-.15,e.clientX-r.left,e.clientY-r.top);},{passive:false});
|
||
C().addEventListener('mousedown',function(e){drag=true;lx=e.clientX;ly=e.clientY;C().classList.add('dragging');});
|
||
window.addEventListener('mousemove',function(e){if(!drag)return;tx+=e.clientX-lx;ty+=e.clientY-ly;lx=e.clientX;ly=e.clientY;apply();});
|
||
window.addEventListener('mouseup',function(){drag=false;C().classList.remove('dragging');});
|
||
document.addEventListener('keydown',function(e){if(e.key==='Escape')mvClose();});
|
||
document.getElementById('mv-overlay').addEventListener('click',function(e){if(e.target.id==='mv-overlay')mvClose();});
|
||
});
|
||
})();
|
||
</script>
|
||
|
||
<script>
|
||
window.addEventListener('load', function() {
|
||
mermaid.initialize({ startOnLoad: false, theme: 'neutral' });
|
||
document.querySelectorAll('.mermaid').forEach(el => {
|
||
el.innerHTML = el.innerHTML
|
||
.replace(/>/g, '>')
|
||
.replace(/</g, '<')
|
||
.replace(/&/g, '&');
|
||
});
|
||
mermaid.run({ querySelector: '.mermaid' }).then(function() {
|
||
document.querySelectorAll('.mermaid').forEach(function(el) {
|
||
if (el.querySelector('svg')) {
|
||
var btn = document.createElement('button');
|
||
btn.textContent = 'View diagram ↗';
|
||
btn.className = 'mermaid-view-btn';
|
||
btn.onclick = function(){ mvOpen(el); };
|
||
el.parentNode.insertBefore(btn, el.nextSibling);
|
||
}
|
||
});
|
||
});
|
||
});
|
||
</script>
|
||
"
|
||
)
|
||
|
||
(defvar z-preamble
|
||
"
|
||
<div class=\"banner-header\">
|
||
<div class=\"banner-left\">
|
||
</div>
|
||
|
||
<div class=\"banner-search\">
|
||
<input id=\"search-box\"
|
||
type=\"search\"
|
||
placeholder=\"Search notes…\"
|
||
aria-label=\"Search notes\"
|
||
autocomplete=\"off\" />
|
||
<div id=\"search-results\"></div>
|
||
</div>
|
||
|
||
<div class=\"banner-actions\">
|
||
<button id=\"theme-toggle\" aria-label=\"Toggle theme\" title=\"Toggle light/dark\"></button>
|
||
<button id=\"close-all\">close all</button>
|
||
</div>
|
||
</div>
|
||
"
|
||
)
|
||
|
||
(defvar z-postamble
|
||
"<footer>
|
||
<div class=\"copyright-container\">
|
||
<div class=\"copyright\">
|
||
Copyright © 2022-2026 Zaine Qayyum. All rights reserved unless otherwise noted.</div></div>
|
||
<div class=\"generated\">
|
||
Created with %c on <a href=\"https://www.archlinux.org/\">Arch</a> <a href=\"https://www.gnu.org\">GNU</a>/<a href=\"https://www.kernel.org/\">Linux</a>
|
||
</div>
|
||
</footer>")
|
||
|
||
(defvar z/build-full-rebuild t
|
||
"If non-nil, generate expensive artifacts like search-index.json.")
|
||
|
||
;; Babel language support
|
||
(org-babel-do-load-languages
|
||
'org-babel-load-languages
|
||
'((latex . t)
|
||
(org . t)))
|
||
|
||
(setq org-confirm-babel-evaluate nil ;; Disable confirmation for code block evaluation
|
||
org-export-with-smart-quotes t ;; turns " into curly quotes
|
||
;;org-html-self-link-headlines t ;; headlines link to self
|
||
org-html-validation-link nil ;; dont validate html (in order to check it is up to web standards)
|
||
org-html-inline-images t ;; self-expl
|
||
org-export-with-sub-superscripts t ;; render h_x as large h with subscript x
|
||
)
|
||
(load-file (expand-file-name "lisp/macros.el" default-directory))
|
||
|
||
;; Configure LaTeX image generation
|
||
(setq org-latex-create-formula-image-program 'imagemagick)
|
||
|
||
(defun z/org-html-mermaid-block (code _lang)
|
||
"Export a mermaid src block as a mermaid div."
|
||
(format "<div class=\"mermaid\">\n%s\n</div>" code))
|
||
|
||
;; Hook into ox-html's src block export
|
||
(defun z/org-html-src-block-mermaid (orig-fun src-block contents info)
|
||
(let ((lang (org-element-property :language src-block)))
|
||
(if (string= lang "mermaid")
|
||
(let ((code (org-remove-indentation
|
||
(org-element-property :value src-block))))
|
||
(format "<div class=\"mermaid\">\n%s\n</div>" code))
|
||
(funcall orig-fun src-block contents info))))
|
||
|
||
(advice-add 'org-html-src-block :around #'z/org-html-src-block-mermaid)
|
||
|
||
(defun my/get-org-roam-backlinks (filename)
|
||
"Return backlinks for the given org-roam file. FILENAME should be the full path to the org-roam file."
|
||
(save-excursion
|
||
(with-current-buffer (find-file-noselect filename)
|
||
(goto-char (point-min))
|
||
(if (and (featurep 'org-roam) (org-roam-node-at-point))
|
||
(let* ((node (org-roam-node-at-point))
|
||
(backlinks (org-roam-backlinks-get node)))
|
||
(mapcar (lambda (backlink)
|
||
(let ((source-node (org-roam-backlink-source-node backlink)))
|
||
(list
|
||
:file (org-roam-node-file source-node)
|
||
:title (org-roam-node-title source-node)
|
||
:content (with-current-buffer
|
||
(find-file-noselect (org-roam-node-file source-node))
|
||
(goto-char (org-roam-backlink-point backlink))
|
||
(thing-at-point 'sentence t))
|
||
:link (format "[[id:%s][%s]]"
|
||
(org-roam-node-id source-node)
|
||
(org-roam-node-title source-node)))))
|
||
backlinks)
|
||
)
|
||
nil))))
|
||
|
||
|
||
(defun my/org-export-insert-backlinks (_backend)
|
||
"Insert Org-roam backlinks before export."
|
||
(when (and (buffer-file-name)
|
||
(not (string-match-p "all-files\\.org$" (buffer-file-name)))
|
||
(featurep 'org-roam)
|
||
(org-roam-node-at-point))
|
||
(let ((backlinks (my/get-org-roam-backlinks (buffer-file-name))))
|
||
(when backlinks
|
||
(goto-char (point-max))
|
||
(insert "\n#+ATTR_HTML: :class backlinks-section :id backlinks\n")
|
||
(insert "* Backlinks\n")
|
||
(insert "#+ATTR_HTML: :class backlinks-list\n")
|
||
(dolist (bl backlinks)
|
||
(insert (format "- %s\n" (plist-get bl :link))))))))
|
||
|
||
(setq org-id-locations-file
|
||
(expand-file-name ".org-id-locations"
|
||
(getenv "HOME")))
|
||
(setq org-roam-directory site-root)
|
||
(org-id-update-id-locations)
|
||
|
||
(setq org-html-link-home "")
|
||
(setq org-html-link-up "")
|
||
|
||
(defun z/generate-all-files-org ()
|
||
"Generate all-files.org from all-files.json."
|
||
(let* ((base-dir site-root)
|
||
(json-file (expand-file-name "all-files.json" base-dir))
|
||
(org-file (expand-file-name "all-files.org" base-dir)))
|
||
(when (file-exists-p json-file)
|
||
(message "🧩 Generating all-files.org from JSON…")
|
||
(let* ((json-object-type 'alist)
|
||
(json-array-type 'list)
|
||
(json-key-type 'symbol)
|
||
(data (json-read-file json-file))
|
||
(groups (alist-get 'groups data)))
|
||
(with-temp-file org-file
|
||
(insert "#+title: All Files\n")
|
||
(insert "#+options: toc:nil num:nil\n\n")
|
||
(insert "#+begin_comment\n")
|
||
(insert "This file is auto-generated. Do not edit manually.\n")
|
||
(insert "#+end_comment\n\n")
|
||
(dolist (group groups)
|
||
(let ((letter (symbol-name (car group)))
|
||
(entries (cdr group)))
|
||
(insert (format "* %s\n" letter))
|
||
(dolist (entry entries)
|
||
(let ((id (alist-get 'id entry))
|
||
(title (alist-get 'title entry)))
|
||
(insert (format "- [[id:%s][%s]]\n" id title))))
|
||
(insert "\n")))))
|
||
(message "✅ all-files.org generated"))))
|
||
|
||
(setq org-html-mathjax-options
|
||
'((path "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js")
|
||
(scale 1.0)
|
||
(align "center")
|
||
(font "mathjax-modern")
|
||
(overflow "overflow")
|
||
(autonumber "AMS")
|
||
(multlinewidth "85%")
|
||
(tagside "right")
|
||
(tagindent ".8em")))
|
||
|
||
(setq org-html-mathjax-template
|
||
"<script>
|
||
MathJax = {
|
||
tex: {
|
||
inlineMath: [['\\\\(', '\\\\)']],
|
||
displayMath: [['\\\\[', '\\\\]']]
|
||
}
|
||
};
|
||
</script>
|
||
<script src=\"%PATH\" async></script>")
|
||
|
||
;; -------------------------------------------------------
|
||
;; MOV → MP4 conversion
|
||
;; Converts all .mov files found anywhere under assets/ to
|
||
;; .mp4 siblings IN THE SOURCE directory before publishing.
|
||
;; org-publish then copies both the .mov and the .mp4.
|
||
;; Skips conversion if an up-to-date .mp4 already exists.
|
||
;; -------------------------------------------------------
|
||
|
||
(defun z/convert-mov-to-mp4 (assets-dir)
|
||
"Recursively convert every .mov under ASSETS-DIR to an .mp4 sibling.
|
||
Skips files where the .mp4 already exists and is newer than the .mov."
|
||
(message "🎬 Scanning %s for .mov files…" assets-dir)
|
||
(let ((mov-files
|
||
(directory-files-recursively assets-dir "\\.mov$" nil)))
|
||
(if (null mov-files)
|
||
(message " (no .mov files found)")
|
||
(dolist (mov mov-files)
|
||
(let* ((mp4 (concat (file-name-sans-extension mov) ".mp4"))
|
||
(mov-mtime (float-time (nth 5 (file-attributes mov))))
|
||
(mp4-mtime (if (file-exists-p mp4)
|
||
(float-time (nth 5 (file-attributes mp4)))
|
||
-1)))
|
||
(if (> mp4-mtime mov-mtime)
|
||
(message " [skip] %s (mp4 is up to date)" (file-name-nondirectory mov))
|
||
(message " [convert] %s → %s"
|
||
(file-relative-name mov assets-dir)
|
||
(file-name-nondirectory mp4))
|
||
(let ((exit-code
|
||
(call-process "ffmpeg" nil nil nil
|
||
"-i" mov
|
||
"-c:v" "libx264"
|
||
"-preset" "fast"
|
||
"-crf" "23"
|
||
"-c:a" "aac"
|
||
"-b:a" "128k"
|
||
"-movflags" "+faststart"
|
||
"-y" mp4)))
|
||
(if (= exit-code 0)
|
||
(message " [done] %s" (file-name-nondirectory mp4))
|
||
(message " [ERROR] ffmpeg exited %d for %s"
|
||
exit-code (file-name-nondirectory mov))))))))))
|
||
|
||
(setq org-publish-project-alist
|
||
`(
|
||
("org-roam"
|
||
:base-directory "/home/zaine/master-folder/org_files/org_roam/"
|
||
:base-extension "org"
|
||
:publishing-directory "/home/zaine/master-folder/org_files/org_roam/output/"
|
||
:recursive t
|
||
:publishing-function org-html-publish-to-html
|
||
:with-author nil
|
||
:with-creator nil
|
||
:with-date t
|
||
:with-toc nil
|
||
:section-numbers nil
|
||
|
||
:html-preamble ,z-preamble
|
||
:html-postamble ,z-postamble
|
||
:html-head ,z-shared-head)
|
||
|
||
("org-static"
|
||
:base-directory "/home/zaine/master-folder/org_files/org_roam/assets/"
|
||
:base-extension "css\\|js\\|png\\|jpg\\|gif\\|svg\\|mp4\\|mp3\\|mov"
|
||
:publishing-directory "/home/zaine/master-folder/org_files/org_roam/output/assets/"
|
||
:recursive t
|
||
:publishing-function org-publish-attachment)))
|
||
|
||
(defun z/org-publish-quiet (project force)
|
||
"Publish PROJECT quietly. If FORCE is non-nil, force publishing."
|
||
(let ((inhibit-message t)
|
||
(message-log-max nil))
|
||
(org-publish project force)))
|
||
|
||
(defun z/refresh-org-roam ()
|
||
"Force Org-roam and Org ID caches to refresh (needed for synced notes)."
|
||
(message "starting to refresh...")
|
||
;; Update ID locations
|
||
(org-id-update-id-locations)
|
||
|
||
;; Rebuild roam database
|
||
(when (featurep 'org-roam)
|
||
(org-roam-db-clear-all)
|
||
(org-roam-db-sync))
|
||
(message "✅ Org-roam DB refreshed"))
|
||
|
||
(defun z/build-full ()
|
||
"Full rebuild: Org, assets, and search index."
|
||
(interactive)
|
||
(let* ((start-time (current-time)))
|
||
(message "Starting full rebuild at %s"
|
||
(format-time-string "%Y-%m-%d %H:%M:%S" start-time))
|
||
|
||
(org-id-update-id-locations)
|
||
|
||
;; Convert any .mov files before publishing so org-publish
|
||
;; picks up the .mp4 siblings automatically.
|
||
(z/convert-mov-to-mp4
|
||
"/home/zaine/master-folder/org_files/org_roam/assets/")
|
||
|
||
(setq z/build-full-rebuild t)
|
||
(z/generate-all-files-org)
|
||
(z/org-publish-quiet "org-roam" t)
|
||
(z/org-publish-quiet "org-static" t)
|
||
|
||
(let ((elapsed (float-time (time-subtract (current-time) start-time))))
|
||
(message "✅ Full rebuild complete in %.2f seconds" elapsed))))
|
||
|
||
(defun z/build-fast ()
|
||
"Fast rebuild: Org + assets only (no JSON)."
|
||
(interactive)
|
||
|
||
;; Convert MOVs in fast builds too — cheap (skips up-to-date files).
|
||
(z/convert-mov-to-mp4
|
||
"/home/zaine/master-folder/org_files/org_roam/assets/")
|
||
|
||
(setq z/build-full-rebuild nil)
|
||
(org-publish "org-roam" t)
|
||
(org-publish "org-static" t)
|
||
(message "⚡ Fast rebuild complete (no search index)"))
|
||
|
||
|
||
(cond
|
||
((member "--fast" command-line-args-left)
|
||
(z/build-fast))
|
||
(t
|
||
(z/build-full)))
|