;;; init.el --- Emacs config Org-roam-based daily notes
;; ---------------------
;; 📦 Package Management
;; ---------------------
(require 'package)
(setq package-enable-at-startup nil)
;; Add multiple reputable package archives
(setq package-archives
'(("melpa" . "https://melpa.org/packages/")
("melpa-stable" . "https://stable.melpa.org/packages/")
("gnu" . "https://elpa.gnu.org/packages/")
("nongnu" . "https://elpa.nongnu.org/nongnu/")
("org" . "https://orgmode.org/elpa/")))
(package-initialize)
(unless (package-installed-p 'use-package)
(package-refresh-contents)
(package-install 'use-package))
(require 'use-package)
(setq use-package-always-ensure t)
;; ---------------------------------------
;; 📚 Org-mode Setup (with macOS bindings)
;; ---------------------------------------
(use-package org
:ensure t
:hook ((org-mode . visual-line-mode)
(org-mode . variable-pitch-mode))
:bind (("C-c c" . org-capture) ;; Org capture shortcut
:map org-mode-map
("s-d" . org-deadline) ;; ⌘ + d → set deadline
("s-s" . org-schedule) ;; ⌘ + s → schedule
("s-a" . org-agenda)) ;; ⌘ + a → agenda
:config
(setq org-directory "~/org/")
(setq org-agenda-files '("~/org/"))
(setq org-log-done 'time)
(setq org-startup-indented t)
(setq org-hide-emphasis-markers t)
(setq org-startup-with-inline-images t)
(setq org-deadline-warning-days 7)
(setq org-agenda-span 'week)
(setq org-read-date-popup-calendar t)
(setq calendar-week-start-day 1))
;; ---------------------------------------
;; 🧠 Org-roam Setup
;; ---------------------------------------
(use-package org-roam
:ensure t
:demand t ;; Ensure org-roam is loaded by default
:init
(setq org-roam-v2-ack t)
:custom
(org-roam-directory "~/MEGA/org/roam")
(org-roam-completion-everywhere t)
:bind (("C-c n l" . org-roam-buffer-toggle)
("C-c n f" . org-roam-node-find)
("C-c n i" . org-roam-node-insert)
("C-c n I" . org-roam-node-insert-immediate)
("C-c n p" . my/org-roam-find-project)
("C-c n t" . my/org-roam-capture-task)
("C-c n b" . my/org-roam-capture-inbox)
:map org-mode-map
("C-M-i" . completion-at-point)
:map org-roam-dailies-map
("Y" . org-roam-dailies-capture-yesterday)
("T" . org-roam-dailies-capture-tomorrow))
:bind-keymap
("C-c n d" . org-roam-dailies-map)
:config
(require 'org-roam-dailies) ;; Ensure the keymap is available
(org-roam-db-autosync-mode))
(defun org-roam-node-insert-immediate (arg &rest args)
(interactive "P")
(let ((args (push arg args))
(org-roam-capture-templates (list (append (car org-roam-capture-templates)
'(:immediate-finish t)))))
(apply #'org-roam-node-insert args)))
(defun my/org-roam-filter-by-tag (tag-name)
(lambda (node)
(member tag-name (org-roam-node-tags node))))
(defun my/org-roam-list-notes-by-tag (tag-name)
(mapcar #'org-roam-node-file
(seq-filter
(my/org-roam-filter-by-tag tag-name)
(org-roam-node-list))))
(defun my/org-roam-refresh-agenda-list ()
(interactive)
(setq org-agenda-files (my/org-roam-list-notes-by-tag "Project")))
;; Build the agenda list the first time for the session
(my/org-roam-refresh-agenda-list)
(defun my/org-roam-project-finalize-hook ()
"Adds the captured project file to `org-agenda-files' if the
capture was not aborted."
;; Remove the hook since it was added temporarily
(remove-hook 'org-capture-after-finalize-hook #'my/org-roam-project-finalize-hook)
;; Add project file to the agenda list if the capture was confirmed
(unless org-note-abort
(with-current-buffer (org-capture-get :buffer)
(add-to-list 'org-agenda-files (buffer-file-name)))))
(defun my/org-roam-find-project ()
(interactive)
;; Add the project file to the agenda after capture is finished
(add-hook 'org-capture-after-finalize-hook #'my/org-roam-project-finalize-hook)
;; Select a project file to open, creating it if necessary
(org-roam-node-find
nil
nil
(my/org-roam-filter-by-tag "Project")
:templates
'(("p" "project" plain "* Goals\n\n%?\n\n* Tasks\n\n** TODO Add initial tasks\n\n* Dates\n\n"
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+category: ${title}\n#+filetags: Project")
:unnarrowed t))))
(defun my/org-roam-capture-inbox ()
(interactive)
(org-roam-capture- :node (org-roam-node-create)
:templates '(("i" "inbox" plain "* %?"
:if-new (file+head "Inbox.org" "#+title: Inbox\n")))))
(defun my/org-roam-capture-task ()
(interactive)
;; Add the project file to the agenda after capture is finished
(add-hook 'org-capture-after-finalize-hook #'my/org-roam-project-finalize-hook)
;; Capture the new task, creating the project file if necessary
(org-roam-capture- :node (org-roam-node-read
nil
(my/org-roam-filter-by-tag "Project"))
:templates '(("p" "project" plain "** TODO %?"
:if-new (file+head+olp "%<%Y%m%d%H%M%S>-${slug}.org"
"#+title: ${title}\n#+category: ${title}\n#+filetags: Project"
("Tasks"))))))
(defun my/org-roam-copy-todo-to-today ()
(interactive)
(let ((org-refile-keep t) ;; Set this to nil to delete the original!
(org-roam-dailies-capture-templates
'(("t" "tasks" entry "%?"
:if-new (file+head+olp "%<%Y-%m-%d>.org" "#+title: %<%Y-%m-%d>\n" ("Tasks")))))
(org-after-refile-insert-hook #'save-buffer)
today-file
pos)
(save-window-excursion
(org-roam-dailies--capture (current-time) t)
(setq today-file (buffer-file-name))
(setq pos (point)))
;; Only refile if the target file is different than the current file
(unless (equal (file-truename today-file)
(file-truename (buffer-file-name)))
(org-refile nil nil (list "Tasks" today-file nil pos)))))
(add-to-list 'org-after-todo-state-change-hook
(lambda ()
(when (equal org-state "DONE")
(my/org-roam-copy-todo-to-today))))
;; -------------------------------
;; 💡 Visual Enhancements
;; -------------------------------
(use-package org-bullets
:hook (org-mode . org-bullets-mode))
(use-package visual-fill-column
:hook (org-mode . visual-fill-column-mode)
:config
(setq visual-fill-column-width 100
visual-fill-column-center-text t))
(use-package solarized-theme
:config
(load-theme 'solarized-dark t))
(global-display-line-numbers-mode 1)
(add-hook 'org-mode-hook (lambda () (display-line-numbers-mode 0)))
;; Optional: Nicer Org UI
(use-package org-modern
:config (global-org-modern-mode))
(use-package org-appear
:hook (org-mode . org-appear-mode))
;; -------------------------------
;; 🖥️ macOS Enhancements and Font
;; -------------------------------
(when (eq system-type 'darwin)
;; Set font size to 18pt and background to black
(add-to-list 'default-frame-alist '(font . "Menlo-18"))
(add-to-list 'default-frame-alist '(background-color . "black"))
(add-to-list 'default-frame-alist '(foreground-color . "white"))
(setq mac-option-modifier 'meta)
(setq mac-command-modifier 'super)
(setq mac-right-option-modifier nil))
;; -------------------------------
;; 🔤 Spell Checking & Dictionary
;; -------------------------------
(setq ispell-program-name "aspell")
(setq ispell-dictionary "en") ;; default dictionary
;; Enable Flyspell in text and org modes
(dolist (hook '(text-mode-hook org-mode-hook))
(add-hook hook 'flyspell-mode))
;; Optional: enable in comments for code
(add-hook 'prog-mode-hook 'flyspell-prog-mode)
;; Set correction keybinding (use M-$ to check word)
(global-set-key (kbd "C-;") 'flyspell-correct-wrapper)
(use-package flyspell
:ensure t
:hook ((text-mode . flyspell-mode)
(org-mode . flyspell-mode)
(prog-mode . flyspell-prog-mode))
:config
(setq ispell-program-name "aspell"
ispell-dictionary "en"))
(provide 'init)
;;; init.el ends here
(custom-set-variables
;; custom-set-variables was added by Custom.
;; If you edit it by hand, you could mess it up, so be careful.
;; Your init file should contain only one such instance.
;; If there is more than one, they won't work right.
'(package-selected-packages nil))
(custom-set-faces
;; custom-set-faces was added by Custom.
;; If you edit it by hand, you could mess it up, so be careful.
;; Your init file should contain only one such instance.
;; If there is more than one, they won't work right.
)