This commit is contained in:
Akko
2025-10-25 18:58:30 +02:00
parent fa280d0c8a
commit e3c7037dab
55 changed files with 3459 additions and 804 deletions

View File

@@ -1,6 +1,211 @@
#+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 ^^".
Find me on [[https://bsky.app/profile/webbieweb.org][bsky]]!
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