Files
blog/org/index.org
2025-10-25 18:58:30 +02:00

212 lines
9.3 KiB
Org Mode
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#+TITLE: Index
* Hello There!
Welcome to my website! I'm [[./about.org][akk0]]. I blog about Emacs, programming, meditation, otaku stuff, and other things that interest me. You can view the full list of pages on the [[./sitemap.org][sitemap]].
This site launched recently and is still under construction; please pardon the dust! I maintain a [[./projects/blog.org][somewhat up-to-date TO-DO list]]; don't hestitate to reach out to me through the email address in the footer for feedback or feature suggestions ^^".
Here's this week's [[./journal/w43-2025.org][journal]]!
** /Pro Tips/
- You can fold and unfold sections by clicking the headline. Try it!
- Click on any cell with a dotted outline in the habit tracker to see my journal entry for it. The currently selected cell will be highlighted in pink.
** Habits
#+BEGIN_CENTER
/Today is Saturday, 25th October./
#+END_CENTER
#+BEGIN_SRC emacs-lisp :exports results :results value html :cache no
(defun akk0/org-to-html (org-string)
"Convert ORG-STRING to HTML."
(with-temp-buffer
(insert org-string)
(org-mode)
(org-export-as 'html nil nil t nil)))
(defun akk0/sort-habits (habit-alist)
"Sort habit-alist by a predefined order of custom-ids."
(let ((order '("dailies-blogging" "dailies-meditation" "dailies-french" "dailies-engineering"
"dailies-exercise" "dailies-drawing" "dailies-reading" "dailies-social")))
(sort (copy-sequence habit-alist)
(lambda (a b)
(let ((pos-a (or (cl-position (car a) order :test #'equal) 999))
(pos-b (or (cl-position (car b) order :test #'equal) 999)))
(< pos-a pos-b))))))
(defun akk0/extract-habits (file)
"Extract habits with date context from FILE.
Returns a list of plists with :custom-id, :todo-state, :date, :day-of-year."
(with-temp-buffer
(insert-file-contents file)
(org-mode)
(let (results)
(org-element-map (org-element-parse-buffer) 'headline
(lambda (hl)
(let ((custom-id (org-element-property :CUSTOM_ID hl))
(todo-state (org-element-property :todo-keyword hl)))
(when (and custom-id todo-state)
;; Get parent properties for context
(let* ((parent (org-element-property :parent hl))
(day-of-year (or (org-element-property :DAILIES-DAY parent)
(let ((grandparent (org-element-property :parent parent)))
(when grandparent
(org-element-property :DAILIES-DAY grandparent)))))
(body (org-element-interpret-data
(org-element-contents hl))))
(push (list :custom-id custom-id
:todo-state todo-state
:day-of-year day-of-year
:file file
:body body)
results))))))
(nreverse results))))
(defun akk0/extract-all-habits (files)
(mapcan #'akk0/extract-habits files))
(setq akk0/journal-files
(directory-files "~/Blog/org/journal/" t "^w.*\\.org$"))
(defun akk0/habits-alist (habits)
"Transform HABITS list into nested alists: custom-id → day-of-year → habit-data."
(let (result)
(dolist (habit habits)
(let* ((custom-id (plist-get habit :custom-id))
(day-of-year (plist-get habit :day-of-year))
(todo-state (plist-get habit :todo-state))
(body (plist-get habit :body))
;; Get the alist for this custom-id
(inner-alist (alist-get custom-id result nil nil #'equal))
;; Store full data instead of just todo-state
(habit-data (list :todo-state todo-state :body body :day-of-year day-of-year))
;; Update the inner alist
(updated-inner (cons (cons day-of-year habit-data) inner-alist)))
;; Update result
(setf (alist-get custom-id result nil nil #'equal) updated-inner)))
result))
(setq habit-alist (akk0/habits-alist (akk0/extract-all-habits akk0/journal-files)))
(defun akk0/get-habit-history (habit-alist custom-id day-number days-back window-size)
(let* ((inner-alist (alist-get custom-id habit-alist nil nil #'equal))
(result nil)
(all-states nil)
(score-for-state (lambda (state)
(cond ((equal state "NO") -1)
((equal state "YES") 1)
((equal state "EXCELLENT") 2)
(t 0)))))
(dotimes (i days-back)
(let* ((current-day (+ (- day-number days-back) i 1))
(current-day-str (number-to-string current-day))
(habit-data (alist-get current-day-str inner-alist nil nil #'equal))
(todo-state (if habit-data
(plist-get habit-data :todo-state)
"NODATA"))
(body (if habit-data
(plist-get habit-data :body)
""))
(doy (if habit-data
(plist-get habit-data :day-of-year)
""))
)
(push todo-state all-states)
(let* ((window-states (seq-take all-states window-size))
(rolling-score (apply #'+ (mapcar score-for-state window-states))))
(push (list :todo-state todo-state
:score (max 1 (min 5 (/ (+ rolling-score 5) 2)))
:body body
:doy doy)
result))))
(nreverse result)))
(defun akk0/habits-to-html-table (habit-alist day-number days-back window-size)
"Generate HTML table of habits with rolling scores.
Rows are custom-ids, columns are days."
(let ((color-map '(("NODATA" . "grey")
("YES" . "green")
("NO" . "red")
("FREED" . "purple")
("EXCELLENT" . "blue")))
(symbol-map '(("NODATA" . "")
("YES" . "")
("NO" . "×")
("FREED" . "")
("EXCELLENT" . "")))
(sorted-habits (akk0/sort-habits habit-alist))
(html ""))
;; Start table
(setq html (concat html "<table style='margin-left: auto; margin-right:auto; margin-bottom: 0.8rem;'>\n"))
;; Header row with day numbers
(setq html (concat html " <tr>\n <th></th>\n"))
(dotimes (i days-back)
(let ((day (+ (- day-number days-back) i 1)))
(setq html (concat
html
(cond ((= day day-number) "<th>●</th>")
((= 0 (% (- day day-number) 7)) "<th>○</th>")
(t "<th />"))))))
(setq html (concat html " </tr>\n"))
;; Data rows - one per habit
(dolist (entry sorted-habits)
(let* ((custom-id (car entry))
(history (akk0/get-habit-history habit-alist custom-id day-number days-back window-size)))
(setq html (concat html (format " <tr>\n <td style='padding-right: 20px; padding-top: 5px; padding-bottom: 5px;'><i>%s</i></td>\n" (capitalize (string-remove-prefix "dailies-" custom-id)))))
;; Cell for each day
(dolist (day-data history)
(let* ((todo-state (plist-get day-data :todo-state))
(score (plist-get day-data :score))
(body (plist-get day-data :body))
(doy (plist-get day-data :doy))
(body-html (if (and body (not (string-empty-p body)))
(akk0/org-to-html body)
""))
(color (alist-get todo-state color-map nil nil #'equal))
(symbol (alist-get todo-state symbol-map nil nil #'equal))
(class (format "habit-brightness-%d" score))
(style-var (format "--%s%d" color score))
(escaped-body (replace-regexp-in-string "\"" "&quot;"
(replace-regexp-in-string "\n" "&#10;" body-html)))
)
(setq html (concat html (format " <td class=\"%s habit-cell\" style=\"background-color:var(%s)\" data-body=\"%s\" onclick=\"showHabitPopup(this)\" data-doy=\"%s\" data-activity=\"%s\"
data-status=\"%s\">%s</td>\n"
class style-var
escaped-body
doy
custom-id
todo-state
symbol)))))
(setq html (concat html " </tr>\n"))))
;; End table
(setq html (concat html "</table>\n"))
html))
(akk0/habits-to-html-table habit-alist 298 30 5)
#+END_SRC
#+BEGIN_EXPORT html
<span class="center"><b>Key:</b>
<span style="color: var(--grey3);">Unknown</span> |
<span style="color: var(--red3);">× No</span> |
<span style="color: var(--green3);">● Yes</span> |
<span style="color: var(--blue3);">♦ Excellent</span> |
<span style="color: var(--purple3);">♣ Freed Up</span>
</span>
<hr />
<div class="habit-popup" id="habitPopup">
<div class="habit-popup-content" id="habitPopupContent">
<span class='center'><i>This section intentionally left blank.</i></span>
</div>
</div>
#+END_EXPORT