dotfiles/dotfiles/emacs.d/org-config.org

37 KiB

include-file-as-forms

We're going to use this to write separate parts of our config to different sections that will then be provided to our use package statment

(defun imalison:include-file-as-forms (filename)
  "Include the contents of FILENAME into the source as s-expressions."
  (with-temp-buffer
    (insert-file-contents-literally filename)
    (goto-char (point-min))
    (let (forms form)
      (condition-case nil
          (while t
            (setq form (read (current-buffer)))
            (push form forms))
        (end-of-file nil))
      `,@(nreverse forms))))

use-package

(defmacro imalison:org-config ()
  `(use-package org
     :commands (org-mode org org-mobile-push org-mobile-pull org-agenda)
     :mode ("\\.org\\'" . org-mode)
     :preface (progn ,@(imalison:include-file-as-forms (imalison:join-paths user-emacs-directory "org-config-preface.el")))
     :custom ,(imalison:include-file-as-forms (imalison:join-paths user-emacs-directory "org-config-custom.el"))
     :config (progn ,@(imalison:include-file-as-forms (imalison:join-paths user-emacs-directory "org-config-config.el")))
     :bind (,(imalison:include-file-as-forms (imalison:join-paths user-emacs-directory "org-config-bind.el")))))

(imalison:org-config)

My Customization Variables

(defvar imalison:org-dir "~/org")
(defvar imalison:shared-org-dir "~/katnivan")
(defvar imalison:org-whoami "Ivan Malison")
(defvar imalison:org-people (list nil "Ivan Malison" "Kat Huang"))
(defvar imalison:org-people-associated-states
  '(("Ivan Malison" . ("IVANDONE"))
    ("Kat Huang" . ("KATDONE"))))
(defvar imalison:org-default-initial-state "TODO")

(defvar imalison:org-gtd-file
  (imalison:join-paths imalison:org-dir "gtd.org"))
(defvar imalison:org-habits-file
  (imalison:join-paths imalison:org-dir "habits.org"))
(defvar imalison:org-calendar-file
  (imalison:join-paths imalison:org-dir "calendar.org"))
(defvar imalison:org-inbox-file
  (imalison:join-paths imalison:org-dir "inbox.org"))
(defvar imalison:shared-org-gtd-file
  (imalison:join-paths imalison:shared-org-dir "shared_gtd.org"))
(defvar imalison:shared-habits-file
  (imalison:join-paths imalison:shared-org-dir "shared_habits.org"))
(defvar imalison:shared-calendar-file
  (imalison:join-paths imalison:shared-org-dir "shared_calendar.org"))
(defvar imalison:shared-shopping-file
  (imalison:join-paths imalison:shared-org-dir "shared_shopping.org"))
(defvar imalison:shared-repeating-file
  (imalison:join-paths imalison:shared-org-dir "repeating.org"))

(defvar imalison:orgzly-files
  (list (imalison:join-paths imalison:shared-org-dir "kat_orgzly.org")
        (imalison:join-paths imalison:shared-org-dir "ivan_orgzly.org")
        (imalison:join-paths imalison:org-dir "orgzly.org")))

(defun imalison:add-to-org-agenda-files (incoming-files)
  (setq org-agenda-files
        (delete-dups
         (cl-loop for filepath in (append org-agenda-files incoming-files)
                  when (and filepath (file-exists-p (file-truename filepath)))
                  collect (file-truename filepath)))))

(defvar imalison:created-property-string "
:PROPERTIES:
:CREATED: %U
:END:")

Bindings

("C-c c" . org-capture)
:map org-mode-map
(("C-e" . end-of-visual-line)
 ("C-c n t" . org-insert-todo-heading)
 ("C-c n s" . org-insert-todo-subheading)
 ("C-c n h" . org-insert-habit)
 ("C-c n m" . org-make-habit)
 ("C-c n l" . org-store-link)
 ("C-c n i" . org-insert-link)
 ("C-c C-t" . org-todo)
 ("C-c C-S-t" . org-todo-force-notes)
 ("M-." . elisp-slime-nav-find-elisp-thing-at-point))

Org Variable Customization

(org-agenda-log-mode-items '(closed))
(org-agenda-skip-deadline-if-done t)
(org-agenda-skip-scheduled-if-done t)
(org-agenda-span 10)
(org-agenda-start-day "-0d")
(org-agenda-timegrid-use-ampm 1)
(org-agenda-window-setup 'other-window)
(org-columns-default-format "%80ITEM(Task) %10Effort(Effort){:} %10CLOCKSUM")
(org-deadline-warning-days 0)
(org-default-priority ?C)
(org-edit-src-content-indentation 0)
(org-enforce-todo-dependencies t)
(org-export-headline-levels 3)
(org-fold-catch-invisible-edits 'show)
(org-goto-interface 'outline-path-completion)
(org-goto-max-level 10)
(org-habit-graph-column 60)
(org-habit-show-all-today nil)
(org-habit-show-habits-only-for-today t)
(org-imenu-depth 10)
(org-log-into-drawer t)
(org-log-redeadline nil)
(org-log-reschedule nil)
(org-lowest-priority 69) ;; The character E
(org-outline-path-complete-in-steps nil)
(org-refile-allow-creating-parent-nodes t)
(org-refile-use-outline-path 'file)
(org-src-fontify-natively t)
(org-src-preserve-indentation t)
(org-startup-folded t)
(org-startup-indented nil)
(org-todo-repeat-to-state "TODO")
(org-treat-insert-todo-heading-as-state-change t)
(org-refile-targets '((org-agenda-files . (:maxlevel . 2))
                      (org-agenda-files . (:level . 0))))
(org-global-properties
 '(quote (("Effort_ALL" . "0:15 0:30 0:45 1:00 2:00 3:00 4:00 5:00 6:00 0:00")
          ("STYLE_ALL" . "habit"))))
(org-todo-keywords
 '((sequence "INBOX(i!)" "TODO(t!)" "NEXT(n!)" "STARTED(s!)" "WAIT(w!)"
             "BACKLOG(b!)" "|" "DONE(d!)" "HANDLED(h!)" "EXPIRED(e!)"
             "CANCELED(c!)")))

Skip showing deadline when we are scheduled

(org-agenda-skip-deadline-prewarning-if-scheduled t)

Config

Miscellaneous Functions

(require 'cl-lib)
(require 'org-habit)

(defun imalison:maybe-symbol-name (arg)
  (if (symbolp arg)
      (symbol-name arg)
    arg))

(defun imalison:set-display-custom-times ()
  (setq org-display-custom-times nil))

(when (not (fboundp 'org-is-habit-p))
  (defun org-is-habit-p ()
    (string-equal (org-entry-get nil "STYLE") "habit")))

(defun org-todo-force-notes ()
  (interactive)
  (let ((org-todo-log-states
         (mapcar (lambda (state)
                   (list state 'note 'time))
                 (apply 'append org-todo-sets))))
    (cond ((eq major-mode 'org-mode)  (org-todo))
          ((eq major-mode 'org-agenda-mode) (org-agenda-todo)))))

(defun org-make-habit ()
  (interactive)
  (org-set-property "STYLE" "habit"))

(defun org-insert-habit ()
  (interactive)
  (org-insert-todo-heading nil)
  (org-make-habit))

(defun org-todo-at-date (date)
  (interactive (list (org-time-string-to-time (org-read-date))))
  (flet ((org-current-effective-time (&rest r) date)
            (org-today (&rest r) (time-to-days date)))
    (cond ((eq major-mode 'org-mode) (org-todo))
          ((eq major-mode 'org-agenda-mode) (org-agenda-todo)))))

(defun imalison:lower-todo-priorities ()
  "Lower the priority of each TODO heading in all org-agenda files by 1."
  (interactive)
  (org-map-entries
   (lambda ()
     (when (org-entry-is-todo-p)
       (org-priority-down)))
   nil
   'agenda))

(cl-defun imalison:org-at-time (&key time (fn 'org-wild-notifier-check))
  (interactive)
  (setq time (or time (time-convert (encode-time (parse-time-string (org-read-date))) 'list)))
  (message "%s" time)
  (flet
      ((current-time (&rest args) time))
    (funcall fn)))

Miscellaneous

(add-to-list 'org-show-context-detail '(org-goto . lineage))
(add-to-list 'org-src-lang-modes '("plantuml" . plantuml))

(define-key mode-specific-map [?a] 'org-agenda)
(unbind-key "C-j" org-mode-map)

Hooks

;; TODO why is this set
(add-hook 'org-mode-hook (lambda () (setq org-todo-key-trigger t)))

Disables

(add-hook 'org-agenda-mode-hook 'imalison:set-display-custom-times)
(add-hook 'org-agenda-mode-hook (lambda () (eldoc-mode -1)))

auto-revert-mode

(add-hook 'org-mode-hook (lambda () (auto-revert-mode +1)))
(add-to-list 'revert-without-query "\\.org\\'")

Modules

(add-to-list 'org-modules 'org-habit)

Assignee/Completer

(defmacro imalison:def-agenda-pred (&rest forms)
  `(lambda ()
     (unless ,@forms
       (or (outline-next-heading)
           (point-max)))))

(defun imalison:org-known-assignees ()
  imalison:org-people)

(defun imalison:set-person-for-prop (&rest args)
  (interactive)
  (if (eq major-mode 'org-agenda-mode)
      (org-agenda-with-point-at-orig-entry nil
        (apply 'imalison--set-person-for-prop args))
    (apply 'imalison--set-person-for-prop args)))

(cl-defun imalison--set-person-for-prop (&key assignee (override t) (property "ASSIGNEE"))
  (interactive)
  (let ((chosen-person
         (if (null assignee)
             (completing-read "Choose person: "
                              (imalison:org-known-assignees)
                              nil t)
           (or assignee imalison:org-whoami))))
    (when (or override (not (org-entry-get nil property)))
      (org-set-property property chosen-person))))

(defalias 'imalison:set-assignee 'imalison:set-person-for-prop)

(defun imalison:set-completer (&rest args)
  (interactive)
  (apply 'imalison:set-person-for-prop :property "COMPLETER" args))

(defun imalison:assign-to-self-if-unassigned ()
  (interactive)
  (imalison:set-assignee :assignee imalison:org-whoami :override nil))

(defun imalison:shared-org-file-p ()
  (string-prefix-p (file-truename imalison:shared-org-dir)
                   (file-truename default-directory)))

(defun imalison:habit-or-repeating-heading ()
  (org-is-habit-p))

(defun imalison:shared-non-habit-p ()
  (and (not (imalison:habit-or-repeating-heading))
       (imalison:shared-org-file-p)))

(defvar imalison:auto-assign-to-self-predicates
  (list 'imalison:shared-non-habit-p))

(defun imalison:auto-assign-to-self-when ()
  (cl-loop for pred in imalison:auto-assign-to-self-predicates
           when (funcall pred)
           return t
           finally return nil))

(defun imalison:maybe-auto-assign-to-self (&rest args)
  (when (imalison:auto-assign-to-self-when)
    (imalison:assign-to-self-if-unassigned)))

(advice-add 'org-schedule :after 'imalison:maybe-auto-assign-to-self)

(defun imalison:relevant-to (person)
  (let ((assignee (org-entry-get nil "ASSIGNEE"))
        (completer (org-entry-get nil "COMPLETER")))
    (or
     (null person)
     (string-equal assignee person)
     (string-equal completer person)
     (and
      (null (or assignee completer))
      (or
       (imalison:shared-org-file-p)
       (equal person imalison:org-whoami))))))

(defmacro imalison:assigned-to-me ()
  `(let ((assignee (org-entry-get nil "ASSIGNEE")))
     (or (string-equal assignee imalison:org-whoami)
         (null assignee))))

(defalias 'imalison:assigned-to-me-agenda-pred
  (imalison:def-agenda-pred
   (imalison:assigned-to-me)))

Add a hook to automatically set completer

(defun imalison:maybe-add-completer (state-change)
  (when
      (and (eq (plist-get state-change :type) 'todo-state-change)
           (member (plist-get state-change :to) org-done-keywords)
           (imalison:shared-non-habit-p)
           (null (org-entry-get nil "COMPLETER")))
    (org-set-property "COMPLETER" imalison:org-whoami)))
(add-hook 'org-trigger-hook 'imalison:maybe-add-completer)

Tools for controlling the log book

(defvar imalison:org-agenda-filter-to nil)

(defun imalison:org-agenda-filter-log-to-completions-for (person)
  (interactive (list (completing-read "Choose a person" imalison:org-people)))
  (when (string-equal person "nil")
    (setq person nil))
  (setq imalison:org-agenda-filter-to person)
  (when (eq major-mode 'org-agenda-mode)
    (org-agenda-redo)))

(defun imalison:state-belongs-to (state person)
  (member
   state (cdr (assoc person imalison:org-people-associated-states))))

(defun imalison:state-relevant-to (state person)
  (not
   (member
    state
    (--mapcat it (--filter (not (equal (car it) person))
                           imalison:org-people-associated-states)))))

(defun org-agenda-states-filter (state &optional marker)
  (and (member state org-done-keywords-for-agenda)
       (or (null imalison:org-agenda-filter-to)
           (save-excursion
             (set-buffer (marker-buffer marker))
             (goto-char (marker-position marker))
             (or
              (imalison:state-belongs-to state imalison:org-agenda-filter-to)
              (and
               (imalison:relevant-to imalison:org-agenda-filter-to)
               (imalison:state-relevant-to state imalison:org-agenda-filter-to)))))))

Agenda

(require 'org-agenda)

Agenda Files

(imalison:add-to-org-agenda-files
 (nconc (list imalison:org-gtd-file imalison:org-habits-file
              imalison:org-calendar-file imalison:org-inbox-file
              imalison:shared-org-gtd-file imalison:shared-habits-file
              imalison:shared-calendar-file imalison:shared-shopping-file
              imalison:shared-repeating-file)
        imalison:orgzly-files))

Predicates

(defun org-get-priority-at-point ()
  (save-excursion
    (beginning-of-line)
    (org-back-to-heading t)
    (when (looking-at org-priority-regexp)
	  (let ((ms (match-string 2)))
	    (org-priority-to-value ms)))))

(defmacro imalison:def-agenda-priority-pred (priority)
  `(imalison:def-agenda-pred
    (>= (org-get-priority-at-point) ,priority)))

(cl-defun imalison:org-time-condition-met-p (&key (property "CREATED") (days 30) (future nil))
  (let* ((property-value (org-entry-get (point) property))
         (comparison-time
          (if future
              (time-add (current-time) (days-to-time days))
            (time-subtract (current-time) (days-to-time days))))
         (formatted-time-string (format-time-string "<%Y-%m-%d %H:%M>" comparison-time))
         (compare-time (org-time-string-to-time formatted-time-string))
         (node-time (when property-value (org-time-string-to-time property-value))))
    (when node-time
      (if future
          (time-less-p node-time compare-time)
        (time-less-p compare-time node-time)))))

(defun org-cmp-creation-times (a b)
  (let ((a-created (get-date-created-from-agenda-entry a))
        (b-created (get-date-created-from-agenda-entry b)))
    (imalison:compare-int-list a-created b-created)))

Transient support

(when (fboundp 'org-agenda-transient)
  (bind-key "C-c a" 'org-agenda-transient))

Agenda Commands (Views)

(let ((high-priority
       ;; The < in the following line has behavior that is opposite
       ;; to what one might expect.
       `(tags-todo
         "+PRIORITY<\"C\""
         ((org-agenda-overriding-header "High priority tasks:")
          (org-agenda-skip-function
           ,(imalison:def-agenda-pred
             (not (org-is-habit-p))))
          )))
      (due-today
       `(alltodo
         ""
         ((org-agenda-overriding-header "Due today:")
          (org-agenda-skip-function
           ,(imalison:def-agenda-pred
             (or
              (imalison:org-time-condition-met-p
               :property "DEADLINE" :days 0 :future t)
              (imalison:org-time-condition-met-p
               :property "SCHEDULED" :days 0 :future t)))))))
      (all-habits
       `(agenda
         ""
         ((org-agenda-overriding-header "Habits:")
          (org-agenda-ndays 1)
          (org-agenda-span 1)
          (org-agenda-use-time-grid nil)
          (org-agenda-skip-function
           ,(imalison:def-agenda-pred
             (org-is-habit-p)))
          (org-habit-show-all-today t))))
      (recently-created
       `(alltodo
         ""
         ((org-agenda-overriding-header "Recently Created:")
          (org-agenda-skip-function
           ,(imalison:def-agenda-pred
             (imalison:org-time-condition-met-p :days 10)))
          (org-agenda-cmp-user-defined 'org-cmp-creation-times)
          (org-agenda-sorting-strategy '(user-defined-down)))))
      (next '(todo "NEXT"))
      (started '(todo "STARTED"))
      (inbox '(todo "INBOX"))
      (missing-deadline
       '(tags-todo "-DEADLINE={.}/!"
                   ((org-agenda-overriding-header
                     "These don't have deadlines:"))))
      (missing-priority
       '(tags-todo "-PRIORITY={.}/!"
                   ((org-agenda-overriding-header
                     "These don't have priorities:")))))

  (setq org-agenda-custom-commands
        `(("M" "Main agenda view"
           ((agenda ""
                    ((org-agenda-overriding-header "Agenda:")
                     (org-agenda-ndays 5)
                     (org-deadline-warning-days 0)
                     (org-agenda-skip-function 'imalison:assigned-to-me-agenda-pred)))
            ,high-priority
            ,all-habits
            ,next
            ,recently-created
            ,inbox)
           nil nil)
          ,(cons "A" (cons "High priority" high-priority))
          ,(cons "d" (cons "Overdue tasks and due today" due-today))
          ,(cons "r" (cons "Recently created" recently-created))
          ("h" "A, B priority:" tags-todo "+PRIORITY<\"C\""
           ((org-agenda-overriding-header
             "High Priority:")))
          ("c" "At least priority C:" tags-todo "+PRIORITY<\"D\""
           ((org-agenda-overriding-header
             "At least priority C:")))
          ("l" "Completions"
           . (agenda ""
                     ((org-agenda-overriding-header "Completion History:")
                      (org-agenda-ndays 5)
                      (org-agenda-entry-types '(:none))
                      (org-agenda-start-with-log-mode '(state))))))))

Filters

Regexp Presets
(defvar imalison:org-agenda-regexp-presets
  `(("incomplete" . (lambda ()
                      (concat "-"
                              (rx--to-expr (cons 'or org-done-keywords-for-agenda)))))))

(defun imalison:org-agenda-filter-by-regexp-preset ()
  (interactive)
  (let* ((regex-fn
          (cdr (assoc (completing-read "Select a preset:"
                                       imalison:org-agenda-regexp-presets)
                      imalison:org-agenda-regexp-presets)))
         (new-regex (funcall regex-fn)))
    (push new-regex org-agenda-regexp-filter)
    (org-agenda-filter-apply org-agenda-regexp-filter 'regexp)))
Binding
:map org-agenda-mode-map
(("p" . imalison:org-agenda-filter-by-regexp-preset))

Sorting

I don't want habits to be sorted separately. If they are scheduled for a specific time, they should appear in the agenda at that time!

(let ((entry (assoc 'agenda org-agenda-sorting-strategy)))
  (setf (cdr entry) (remove 'habit-down (cdr entry))))

(let ((entry (assoc 'agenda org-agenda-sorting-strategy)))
  (setf (cdr entry) (remove 'habit-up (cdr entry))))

Archive

(defun org-archive-if (condition-function)
  (if (funcall condition-function)
      (let ((next-point-marker
             (save-excursion (org-forward-heading-same-level 1) (point-marker))))
        (org-archive-subtree)
        (setq org-map-continue-from (marker-position next-point-marker)))))

(defun org-archive-if-completed ()
  (interactive)
  (org-archive-if 'org-entry-is-done-p))

(defun org-archive-completed-in-buffer ()
  (interactive)
  (org-map-entries 'org-archive-if-completed))

(defun org-archive-all-in-buffer ()
  (interactive)
  (org-map-entries 'org-archive-subtree))

Capture

Helper Functions

(cl-defun imalison:make-org-template (&key (content "%?"))
  (with-temp-buffer
    (org-mode)
    (insert content)
    (org-set-property "CREATED"
                      (with-temp-buffer
                        (org-insert-time-stamp
                         (org-current-effective-time) t t)))
    (buffer-substring-no-properties (point-min) (point-max))))

(defun imalison:make-org-template-from-file (filename)
  (imalison:make-org-template (imalison:get-string-from-file filename)))

(cl-defun imalison:make-org-todo-template
    (&key (content "%?") (creation-state imalison:org-default-initial-state))
  (with-temp-buffer
    (org-mode)
    (org-insert-heading)
    (insert content)
    (org-todo creation-state)
    (org-set-property "CREATED"
                      (with-temp-buffer
                        (org-insert-time-stamp
                         (org-current-effective-time) t t)))
    (remove-hook 'post-command-hook 'org-add-log-note)
    (let ((org-log-note-purpose 'state)
          (org-log-note-return-to (point-marker))
          (org-log-note-marker (progn (goto-char (org-log-beginning t))
                                      (point-marker)))
          (org-log-note-state creation-state))
      (org-add-log-note))
    (buffer-substring-no-properties (point-min) (point-max))))

(defun imalison:make-org-linked-todo-template ()
  (imalison:make-org-todo-template "[#C] %? %A"))

Templates

(use-package org-capture
  :straight nil
  :config
  (add-to-list 'org-capture-templates
               `("t" "GTD Todo (Linked)" entry (file ,imalison:org-gtd-file)
                 (function imalison:make-org-linked-todo-template)))

  (add-to-list 'org-capture-templates
               `("g" "GTD Todo" entry (file ,imalison:org-gtd-file)
                 (function imalison:make-org-todo-template)))

  (add-to-list 'org-capture-templates
               `("s" "Shared GTD Todo" entry (file ,imalison:shared-org-gtd-file)
                 (function imalison:make-org-todo-template)))

  (add-to-list 'org-capture-templates
               `("y" "Calendar entry (Linked)" entry
                 (file ,imalison:org-calendar-file)
                 ,(format "%s%s\n%s" "* %? %A" imalison:created-property-string "%^T")))

  (add-to-list 'org-capture-templates
               `("c" "Calendar entry" entry
                 (file ,imalison:org-calendar-file)
                 ,(format "%s\n%s\n%s" "* %?" imalison:created-property-string "%^T")))

  (add-to-list 'org-capture-templates
               `("z" "Shopping Todo" entry (file ,imalison:shared-shopping-file)
                 (function (lambda (&rest args) (imalison:make-org-todo-template :creation-state "TODO")))))

  (add-to-list 'org-capture-templates
               `("h" "Habit" entry (file ,imalison:org-habits-file)
                 "* TODO
SCHEDULED: %^t
:PROPERTIES:
:CREATED: %U
:STYLE: habit
:END:")))

Babel

(add-hook 'org-mode-hook 'imalison:load-babel-languages)
(defun imalison:load-babel-languages ()
  (let* ((loaded-ob (or (require 'ob-sh nil t) (require 'ob-shell nil t)))
         (ob-shell-name
          (when loaded-ob
            (intern (substring-no-properties (imalison:maybe-symbol-name loaded-ob) 3))))
         (added-modes (when ob-shell-name `((,ob-shell-name . t)))))
    (org-babel-do-load-languages
     'org-babel-load-languages
     `((python . t)
       (ruby . t)
       (octave . t)
       (plantuml . t)
       (js . t)
       ,@added-modes))))

(use-package ob-typescript
  :config
  (progn
    (org-babel-do-load-languages
     'org-babel-load-languages '((typescript . t)))))

(use-package ob-mermaid
  :config
  (org-babel-do-load-languages
   'org-babel-load-languages '((mermaid . t))))

frame-mode handling

Note that this does not go into org-config-config.el. This is on purpose

(use-package org
  :after frame-mode
  :config
  (progn
    (defun imalison:org-frame-mode-hook ()
      (setq org-src-window-setup 'current-window)
      (when frame-mode
        (progn
          (setcdr (assoc 'file org-link-frame-setup) 'find-file-other-frame))))
    (add-hook 'frame-mode-hook 'imalison:org-frame-mode-hook)))

Disable yasnippet in org-mode

(use-package org
  :straight nil
  :config
  (progn
    (defun imalison:disable-yas ()
      (yas-minor-mode -1))
    (add-hook 'org-mode-hook 'imalison:disable-yas)))

Set Background Color of Source Blocks for Export

This was taken from here.

(use-package org
  :config
  (progn
    (defun imalison:org-inline-css-hook (exporter)
      "Insert custom inline css to automatically set the
background of code to whatever theme I'm using's background"
      (when (eq exporter 'html)
        (let* ((my-pre-bg (face-background 'default))
               (my-pre-fg (face-foreground 'default)))
          (setq
           org-html-head-extra
           (concat
            org-html-head-extra
            (format "<style type=\"text/css\">\n pre.src {background-color: %s; color: %s;}</style>\n"
                    my-pre-bg my-pre-fg))))))

    (add-hook 'org-export-before-processing-hook 'imalison:org-inline-css-hook)))

Use my own default naming scheme for org-headings

First we define a function that will generate a sanitized version of the heading as its link target.

(defun imalison:org-get-raw-value (item)
  (when (listp item)
    (let* ((property-list (cadr item)))
      (when property-list (plist-get property-list :raw-value)))))

(defun imalison:sanitize-name (name)
  (replace-regexp-in-string "[^[:alpha:]]" "" (s-downcase name)))

(defun imalison:generate-name (datum cache)
  (let ((raw-value (imalison:org-get-raw-value datum)))
    (if raw-value
        (imalison:sanitize-name raw-value)
      ;; This is the default implementation from org
      (let ((type (org-element-type datum)))
        (format "org%s%d"
                (if type
                    (replace-regexp-in-string "-" "" (symbol-name type))
                    "secondarystring")
                (incf (gethash type cache 0)))))))

This function replaces the default naming scheme with a call to imalison:generate-name, and uses a slightly different uniquify approach.

(use-package ox
  :defer t
  :straight nil
  :config
  (defun org-export-get-reference (datum info)
    "Return a unique reference for DATUM, as a string.
DATUM is either an element or an object.  INFO is the current
export state, as a plist.  Returned reference consists of
alphanumeric characters only."
    (let ((type (org-element-type datum))
          (cache (or (plist-get info :internal-references)
                     (let ((h (make-hash-table :test #'eq)))
                       (plist-put info :internal-references h)
                       h)))
          (reverse-cache (or (plist-get info :taken-internal-references)
                             (let ((h (make-hash-table :test 'equal)))
                               (plist-put info :taken-internal-references h)
                               h))))
      (or (gethash datum cache)
          (let* ((name (imalison:generate-name datum cache))
                 (number (+ 1 (gethash name reverse-cache -1)))
                 (new-name (format "%s%s" name (if (< 0 number) number ""))))
            (puthash name number reverse-cache)
            (puthash datum new-name cache)
            new-name)))))

Add link icons in headings that lead to themselves

(use-package ox-html
  :commands (org-html-export-as-html org-html-export-as-html)
  :straight nil
  :preface
  (progn
    (defvar imalison:link-svg-html
      "<svg aria-hidden=\"true\" class=\"octicon octicon-link\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg>")
    (defvar imalison:current-html-headline)
    (defun imalison:set-current-html-headline (headline &rest args)
      (setq imalison:current-html-headline headline))
    (defun imalison:clear-current-html-headline (&rest args)
      (setq imalison:current-html-headline nil))
    (defun imalison:org-html-format-heading-function (todo todo-type priority text tags info)
      (let* ((reference (when imalison:current-html-headline
                          (org-export-get-reference imalison:current-html-headline info)))
             ;; Don't do anything special if the current headline is not set
             (new-text (if reference
                           (format "%s <a href=\"#%s\">%s</a>" text reference imalison:link-svg-html)
                         text)))
        (org-html-format-headline-default-function
         todo todo-type priority new-text tags info))))
  :config
  (progn
    ;; This is set before and cleared afterwards, so that we know when we are
    ;; generating the text for the headline itself and when we are not.
    (advice-add 'org-html-headline :before 'imalison:set-current-html-headline)
    (advice-add 'org-html-headline :after 'imalison:clear-current-html-headline)
    (setq org-html-format-headline-function
          'imalison:org-html-format-heading-function)))

Allow with query params in image extentions

(use-package ox-html
  :defer t
  :straight nil
  :config
  (setq org-html-inline-image-rules
        '(("file" . "\\.\\(jpeg\\|jpg\\|png\\|gif\\|svg\\)\\(\\?.*?\\)?\\'")

          ("http" . "\\.\\(jpeg\\|jpg\\|png\\|gif\\|svg\\)\\(\\?.*?\\)?\\'")
          ("https" . "\\.\\(jpeg\\|jpg\\|png\\|gif\\|svg\\)\\(\\?.*?\\)?\\'"))))

Use org-tempo to allow inserting templates using e.g. <s

(use-package org-tempo
  :straight nil
  :after org)

Packages

org-present

(use-package org-present
  :after org
  :commands org-present)

org-bullets

(use-package org-bullets
  :commands org-bullets-mode
  :after org
  :preface
  (add-hook 'org-mode-hook (lambda () (org-bullets-mode 1))))

org-ehtml

(use-package org-ehtml
  :after web-server
  :disabled t
  :config
  (progn
    (setq org-ehtml-allow-agenda t)
    (setq org-ehtml-editable-headlines t)
    (setq org-ehtml-everything-editable t)
    (ws-start org-ehtml-handler 8888)))

org-modern

(use-package org-modern
  :disabled t
  :after org
  :hook (org-mode . org-modern-mode))

org-project-capture

(use-package org-project-capture
  :after org
  :bind ("C-c o p" . org-project-capture-project-todo-completing-read)
  ;; We want this to load somewhat quickly because we need to update the list of agenda files
  :defer 2
  :config
  (progn
    (use-package org-projectile
      :demand t
      :config
      (setq org-project-capture-default-backend
            (make-instance 'org-project-capture-projectile-backend)))
    (setq org-project-capture-strategy
          (make-instance 'org-project-capture-combine-strategies
                         :strategies (list (make-instance 'org-project-capture-single-file-strategy)
                                           (make-instance 'org-project-capture-per-project-strategy))))
    (setq org-project-capture-projects-file
          (imalison:join-paths imalison:org-dir "projects.org")
          org-project-capture-capture-template
          (format "%s%s" "* TODO %?" imalison:created-property-string)
          occ-auto-insert-category-heading t)
    (add-to-list 'org-capture-templates
                 (org-project-capture-project-todo-entry
                  :capture-character "l"
                  :capture-heading "Linked Project TODO"))
    (add-to-list 'org-capture-templates
                 (org-project-capture-project-todo-entry
                  :capture-character "p"))
    (setq org-confirm-elisp-link-function nil)
    (imalison:add-to-org-agenda-files (org-project-capture-todo-files))))

org-pomodoro

(use-package org-pomodoro
  :after org)

org-roam

(use-package org-roam
  :after org
  :defer 1
  :preface
  (defun imalison:org-roam-browse-backlink ()
      (interactive)
      (let* ((node-alist
              (cl-loop for backlink in (org-roam-backlinks-get (org-roam-node-at-point))
                       for node = (org-roam-backlink-source-node backlink)
                       collect `(,(org-roam-node-title node) . ,node)))
             (selected-name (completing-read "Select a backlink to visit: " node-alist))
             (selected-node (alist-get selected-name node-alist nil nil 'string-equal)))
        (org-roam-node-visit selected-node)))
  :bind
  (("C-c r f" . org-roam-node-find)
   ("C-c r i" . org-roam-node-insert)
   ("C-c r b" . imalison:org-roam-browse-backlink)
   ("C-c r t" . org-roam-buffer-toggle))
  :config
  (progn
    (when (version<= "29.0" emacs-version)
      (use-package emacsql-sqlite-builtin
        :demand t)
      (setq org-roam-database-connector 'sqlite-builtin))
    (org-roam-db-autosync-mode +1)

    (defun imalison:frames-displaying-buffer (buf)
      "Return a list of frames in which BUF is displayed."
      (let ((target-buffer (if (bufferp buf) buf (get-buffer buf))))
        (if target-buffer
            (delq nil
                  (mapcar (lambda (frame)
                            (if (get-buffer-window target-buffer frame)
                                frame))
                          (frame-list)))
          nil)))

    (defun imalison:org-roam-frame-based-buffer-visibility-fn ()
      (cond
       ((--any (funcall frame-mode-is-frame-viewable-fn it)
               (imalison:frames-displaying-buffer org-roam-buffer)) 'visible)
       ((get-buffer org-roam-buffer) 'exists)
       (t 'none)))

    (use-package frame-mode
      :if imalison:use-frame-mode
      :demand t
      :config
      (progn
        (emit-variable-set-mode
         imalison:org-roam-set-frame-visibility-mode
         org-roam-buffer-visibility-fn 'imalison:org-roam-frame-based-buffer-visibility-fn)
        (emit-make-mode-dependent imalison:org-roam-set-frame-visibility-mode frame-mode))))
  :custom
  (org-roam-directory (file-truename "~/org/roam/")))
ui
(use-package emacs-http-server
  :demand t)

(use-package org-roam-ui
  :after org-roam
  :custom
  (org-roam-ui-sync-theme t)
  (org-roam-ui-follow t)
  (org-roam-ui-update-on-save t)
  (org-roam-ui-open-on-start nil))

org-wild-notifier

(use-package org-wild-notifier
  :after org
  :custom
  ((org-wild-notifier-keyword-whitelist nil)
   (org-wild-notifier-tags-blacklist '("nonotify"))
   (org-wild-notifier-alert-time '(30 5 0))
   (org-wild-notifier-extra-alert-plist '(:persistent t))
   (org-wild-notifier-additional-environment-regexes
    (list (rx string-start "imalison:org-whoami")))
   (org-wild-notifier-predicate-whitelist
    `((lambda (marker)
        (save-excursion
          (set-buffer (marker-buffer marker))
          (goto-char (marker-position marker))
          ,(macroexpand '(imalison:assigned-to-me))))))
   (org-wild-notifier-predicate-blacklist
    '(org-wild-notifier-done-keywords-predicate))
   (org-wild-notifier-show-any-overdue-with-day-wide-alerts t)
   (org-wild-notifier-day-wide-alert-times '("10pm")))
  :config
  (org-wild-notifier-mode +1)
  (defun org-wild-notify-check-at-time ()
    (interactive)
    (imalison:org-at-time
     :fn (lambda ()
           (org-wild-notifier--check-events (funcall (org-wild-notifier--retrieve-events))))))
  (defun org-wild-notify-list-at-time ()
    (interactive)
    (imalison:org-at-time
     :fn (lambda ()
           (message "%s"
                    (->> (funcall (org-wild-notifier--retrieve-events))
                         (-map 'org-wild-notifier--check-event)
                         (-flatten)
                         (-uniq)))))))

org-reveal

(use-package ox-reveal
  :defer t
  :commands org-reveal
  :config
  (setq org-reveal-root
      (imalison:join-paths "file://" imalison:projects-directory "reveal.js")))

org-fc

(use-package org-fc
  :after org
  :bind ("C-c 9" . org-fc-hydra/body)
  :config
  (progn
    (require 'org-fc-hydra))
  :straight (org-fc :type git :host github :repo "l3kn/org-fc"
                    :files ("*.el" "awk" "demo.org")))

org-ql

(use-package org-ql
  :bind ("C-c o s" . org-ql-find-in-agenda)
  :commands org-ql-find-in-agenda)

org-window-habit

(use-package org-window-habit
  :demand t
  :straight
  (org-window-habit
   :repo "colonelpanic8/org-window-habit"
   :host github
   :files ("org-window-habit.el"))
  :custom
  ((org-window-habit-property-prefix nil)
   (org-window-habit-repeat-to-scheduled t)
   (org-window-habit-preceding-intervals 21)
   (org-window-habit-following-days 2))
  :config
  (progn
    (org-window-habit-mode +1)))