doomemacs/modules/email/mu4e/config.el

638 lines
30 KiB
EmacsLisp
Raw Normal View History

;;; email/mu4e/config.el -*- lexical-binding: t; -*-
2017-04-08 01:31:20 -04:00
(defvar +mu4e-backend 'mbsync
2018-01-24 15:08:43 -05:00
"Which backend to use. Can either be offlineimap, mbsync or nil (manual).")
2017-04-08 01:31:20 -04:00
2020-10-16 10:48:41 +08:00
(defvar +mu4e-personal-addresses 'nil
"Alternative to mu4e-personal-addresses that can be set for each account (mu4e context).")
2017-04-08 01:31:20 -04:00
;;
;;; Packages
2017-04-08 01:31:20 -04:00
(use-package! mu4e
:commands mu4e mu4e-compose-new
2018-01-24 15:08:43 -05:00
:init
(provide 'html2text) ; disable obsolete package
(when (or (not (require 'mu4e-meta nil t))
(version< mu4e-mu-version "1.4"))
(setq mu4e-maildir "~/.mail"
mu4e-user-mail-address-list nil))
(setq mu4e-attachment-dir
(lambda (&rest _)
(expand-file-name ".attachments" (mu4e-root-maildir))))
2017-04-08 01:31:20 -04:00
:config
(pcase +mu4e-backend
2018-01-24 15:08:43 -05:00
(`mbsync
(setq mu4e-get-mail-command "mbsync -a"
mu4e-change-filenames-when-moving t))
(`offlineimap
(setq mu4e-get-mail-command "offlineimap -o -q")))
(setq mu4e-update-interval nil
2017-04-08 01:31:20 -04:00
mu4e-view-show-addresses t
2018-01-24 15:08:43 -05:00
mu4e-sent-messages-behavior 'sent
mu4e-hide-index-messages t
2017-04-08 01:31:20 -04:00
;; try to show images
mu4e-view-show-images t
mu4e-view-image-max-width 800
2020-09-23 00:22:04 +08:00
mu4e-view-use-gnus t ; the way of the future: https://github.com/djcb/mu/pull/1442#issuecomment-591695814
2017-04-08 01:31:20 -04:00
;; configuration for sending mail
message-send-mail-function #'smtpmail-send-it
2017-04-08 01:31:20 -04:00
smtpmail-stream-type 'starttls
2018-01-24 15:08:43 -05:00
message-kill-buffer-on-exit t ; close after sending
2017-04-08 01:31:20 -04:00
;; start with the first (default) context;
mu4e-context-policy 'pick-first
;; compose with the current context, or ask
mu4e-compose-context-policy 'ask-if-none
;; use helm/ivy/vertico
2018-01-24 15:08:43 -05:00
mu4e-completing-read-function
(cond ((featurep! :completion ivy) #'ivy-completing-read)
((featurep! :completion helm) #'completing-read)
((featurep! :completion vertico) #'completing-read)
2018-01-24 15:08:43 -05:00
(t #'ido-completing-read))
mu4e-attachment-dir
(concat
(if (executable-find "xdg-user-dir")
;; remove trailing newline
(substring (shell-command-to-string "xdg-user-dir DOWNLOAD") 0 -1)
(expand-file-name (or (getenv "XDG_DOWNLOAD_DIR")
"Downloads")
"~"))
"/")
2017-04-08 01:31:20 -04:00
;; no need to ask
2018-10-03 09:05:36 +13:00
mu4e-confirm-quit nil
2020-10-01 18:23:28 +08:00
mu4e-headers-thread-single-orphan-prefix '("─>" . "─▶")
mu4e-headers-thread-orphan-prefix '("┬>" . "┬▶ ")
mu4e-headers-thread-last-child-prefix '("└>" . "╰▶")
mu4e-headers-thread-child-prefix '("├>" . "├▶")
mu4e-headers-thread-connection-prefix '("" . "")
;; remove 'lists' column
mu4e-headers-fields
'((:account-stripe . 1)
2020-10-01 18:23:28 +08:00
(:human-date . 8)
(:flags . 6) ; 3 icon flags
(:from-or-to . 25)
2018-01-24 15:08:43 -05:00
(:subject)))
;; set mail user agent
2020-10-01 18:23:28 +08:00
(setq mail-user-agent 'mu4e-user-agent
message-mail-user-agent 'mu4e-user-agent)
2020-09-23 00:23:01 +08:00
;; Set the icons only when a graphical frame has been created
(if (display-graphic-p)
(+mu4e-initialise-icons)
2020-09-23 00:23:01 +08:00
;; When it's the server, wait till the first graphical frame
(add-hook!
2020-10-15 22:30:46 +08:00
'server-after-make-frame-hook
(defun +mu4e-initialise-icons-hook ()
(when (display-graphic-p)
(+mu4e-initialise-icons)
(remove-hook 'server-after-make-frame-hook
#'+mu4e-initialise-icons-hook)))))
2020-09-25 16:37:11 +08:00
(plist-put (cdr (assoc :flags mu4e-header-info)) :shortname " Flags") ; default=Flgs
2020-10-01 18:23:28 +08:00
(add-to-list 'mu4e-bookmarks
'(:name "Flagged messages" :query "flag:flagged" :key ?f) t)
;; TODO avoid assuming that all-the-icons is present
(defvar +mu4e-header-colorized-faces
'(all-the-icons-green
all-the-icons-lblue
all-the-icons-purple-alt
all-the-icons-blue-alt
all-the-icons-purple
all-the-icons-yellow)
"Faces to use when coloring folders and account stripes.")
(defvar +mu4e-min-header-frame-width 120
"Minimum reasonable with for the header view.")
;; Add a column to display what email account the email belongs to,
;; and an account color stripe column
(defvar +mu4e-header--maildir-colors nil)
(setq mu4e-header-info-custom
'((:account .
(:name "Account"
:shortname "Account"
:help "which account/maildir this email belongs to"
:function
(lambda (msg)
(let ((maildir (replace-regexp-in-string
"\\`/?\\([^/]+\\)/.*\\'" "\\1"
2020-11-18 00:20:46 +08:00
(mu4e-message-field msg :maildir))))
(+mu4e-colorize-str
(replace-regexp-in-string
"^gmail"
(propertize "g" 'face 'bold-italic)
maildir)
'+mu4e-header--maildir-colors
maildir)))))
(:account-stripe .
(:name "Account"
:shortname ""
:help "Which account/maildir this email belongs to"
:function
(lambda (msg)
(let ((account
(replace-regexp-in-string
"\\`/?\\([^/]+\\)/.*\\'" "\\1"
(mu4e-message-field msg :maildir))))
(propertize
(+mu4e-colorize-str "" '+mu4e-header--maildir-colors account)
'help-echo account)))))
(:recipnum .
(:name "Number of recipients"
:shortname ""
:help "Number of recipients for this message"
:function
(lambda (msg)
(propertize (format "%2d"
(+ (length (mu4e-message-field msg :to))
(length (mu4e-message-field msg :cc))))
2020-09-25 16:37:11 +08:00
'face 'mu4e-footer-face))))))
;; Marks usually affect the current view
(defadvice! +mu4e--refresh-current-view-a (&rest _)
:after #'mu4e-mark-execute-all (mu4e-headers-rerun-search))
;; Wrap text in messages
2018-10-24 01:09:17 -04:00
(setq-hook! 'mu4e-view-mode-hook truncate-lines nil)
2017-04-08 01:31:20 -04:00
;; Html mails might be better rendered in a browser
(add-to-list 'mu4e-view-actions '("View in browser" . mu4e-action-view-in-browser))
(when (fboundp 'make-xwidget)
(add-to-list 'mu4e-view-actions '("xwidgets view" . mu4e-action-view-with-xwidget)))
;; The header view needs a certain amount of horizontal space to
;; actually show you all the information you want to see
;; so if the header view is entered from a narrow frame,
;; it's probably worth trying to expand it
(defun +mu4e-widen-frame-maybe ()
"Expand the frame with if it's less than `+mu4e-min-header-frame-width'."
(when (< (frame-width) +mu4e-min-header-frame-width)
(set-frame-width (selected-frame) +mu4e-min-header-frame-width)))
(add-hook 'mu4e-headers-mode-hook #'+mu4e-widen-frame-maybe)
2017-04-08 01:31:20 -04:00
(when (fboundp 'imagemagick-register-types)
(imagemagick-register-types))
(map! :map mu4e-main-mode-map
:ne "h" #'+workspace/other)
(map! :map mu4e-headers-mode-map
:vne "l" #'+mu4e/capture-msg-to-agenda)
;; Functionality otherwise obscured in mu4e 1.6
(when (version<= "1.6" mu4e-mu-version)
(defun +mu4e-view-select-attachment ()
"Use completing-read to select a single attachment.
Acts like a singular `mu4e-view-save-attachments', without the saving."
(let ((parts (mu4e~view-gather-mime-parts)) files)
(dolist (part parts)
(let ((fname (cdr (assoc 'filename (assoc "attachment" (cdr part))))))
(when fname
(push (cons (concat (format "%-2s " (car part))
(when (featurep 'all-the-icons) (all-the-icons-icon-for-file fname))
(format " %s " fname)
(format "(%s)" (file-size-human-readable (with-current-buffer (cadr part) (buffer-size)))))
part)
files))))
(if files
(cdr (assoc (completing-read "Select attachment: " (mapcar #'car files))
files))
(user-error (mu4e-format "No attached files found")))))
(defun +mu4e-view-open-attachment ()
"Select an attachment, and open it."
(interactive)
(mu4e~view-open-file
(mu4e~view-mime-part-to-temp-file (cdr (+mu4e-view-select-attachment)))))
(defun +mu4e-view-select-part ()
(let ((parts (mu4e~view-gather-mime-parts)) partinfo labeledparts
maxfnamelen fnamefmt maxsizelen sizefmt)
(dolist (part parts)
(push (list :index (car part)
:mimetype (if (string= "text/plain" (caaddr part))
(format "%s (%s)"
(caaddr part)
(alist-get 'charset (cdaddr part)))
(caaddr part))
:type (car (nth 5 part))
:filename (cdr (assoc 'filename (assoc "attachment" (cdr part))))
:size (file-size-human-readable (with-current-buffer (cadr part) (buffer-size)))
:part part)
partinfo))
(setq maxfnamelen (apply #'max 7 (mapcar (lambda (i) (length (plist-get i :filename))) partinfo))
fnamefmt (format " %%-%ds " maxfnamelen)
maxsizelen (apply #'max (mapcar (lambda (i) (length (plist-get i :size))) partinfo))
sizefmt (format "%%-%ds " maxsizelen))
(dolist (pinfo partinfo)
(push (cons (concat (propertize (format "%-2s " (plist-get pinfo :index)) 'face '(bold font-lock-type-face))
(when (featurep 'all-the-icons)
(all-the-icons-icon-for-file (or (plist-get pinfo :filename) "")))
(format fnamefmt (or (plist-get pinfo :filename)
(propertize (plist-get pinfo :type) 'face '(italic font-lock-doc-face))))
(format sizefmt (propertize (plist-get pinfo :size) 'face 'font-lock-builtin-face))
(propertize (plist-get pinfo :mimetype) 'face 'font-lock-constant-face))
(plist-get pinfo :part))
labeledparts))
(cdr (assoc (completing-read "Select part: " (mapcar #'car labeledparts))
labeledparts))))
(defun +mu4e-view-select-mime-part-action ()
"Select a MIME part, and perform an action on it."
(interactive)
(mu4e-view-mime-part-action (car (+mu4e-view-select-part))))
(map! :map mu4e-view-mode-map
:ne "A" #'+mu4e-view-select-mime-part-action
:ne "p" #'mu4e-view-save-attachments
:ne "o" #'+mu4e-view-open-attachment))
(map! :localleader
:map mu4e-compose-mode-map
:desc "send and exit" "s" #'message-send-and-exit
:desc "kill buffer" "d" #'message-kill-buffer
:desc "save draft" "S" #'message-dont-send
:desc "attach" "a" #'+mu4e/attach-files)
;; Due to evil, none of the marking commands work when making a visual selection in
;; the headers view of mu4e. Without overriding any evil commands we may actually
;; want to use in and evil selection, this can be easily fixed.
(when (featurep! :editor evil)
(map! :map mu4e-headers-mode-map
:v "*" #'mu4e-headers-mark-for-something
:v "!" #'mu4e-headers-mark-for-read
:v "?" #'mu4e-headers-mark-for-unread
:v "u" #'mu4e-headers-mark-for-unmark))
(add-hook 'mu4e-compose-pre-hook '+mu4e-set-from-address-h)
(defadvice! +mu4e-ensure-compose-writeable-a (&rest _)
"Ensure that compose buffers are writable.
This should already be the case yet it does not always seem to be."
:before #'mu4e-compose-new
:before #'mu4e-compose-reply
:before #'mu4e-compose-forward
:before #'mu4e-compose-resend
(read-only-mode -1))
(advice-add #'mu4e~key-val :filter-return #'+mu4e~main-keyval-str-prettier-a)
(advice-add #'mu4e~main-action-str :override #'+mu4e~main-action-str-prettier-a)
(when (featurep! :editor evil)
;; As +mu4e~main-action-str-prettier replaces [k]ey with key q]uit should become quit
2020-10-13 17:21:52 +08:00
(setq evil-collection-mu4e-end-region-misc "quit"))
;; process lock control
(when IS-WINDOWS
(setq
+mu4e-lock-file (expand-file-name "~/AppData/Local/Temp/mu4e_lock")
+mu4e-lock-request-file (expand-file-name "~/AppData/Local/Temp/mu4e_lock_request")))
2020-10-13 17:21:52 +08:00
(add-hook 'kill-emacs-hook #'+mu4e-lock-file-delete-maybe)
(advice-add 'mu4e~start :around #'+mu4e-lock-start)
(advice-add 'mu4e-quit :after #'+mu4e-lock-file-delete-maybe))
2017-04-08 01:31:20 -04:00
(unless (featurep! +org)
(after! mu4e
(defun org-msg-mode (&optional _)
"Dummy function."
(message "Enable the +org mu4e flag to use org-msg-mode."))
(defun +mu4e-compose-org-msg-handle-toggle (&rest _)
"Placeholder to allow for the assumtion that this function is defined.
Ignores all arguments and returns nil."
nil)))
(use-package! org-msg
:after mu4e
2020-10-14 13:01:46 +08:00
:when (featurep! +org)
:config
(setq org-msg-options "html-postamble:nil H:5 num:nil ^:{} toc:nil author:nil email:nil tex:dvipng"
org-msg-startup "hidestars indent inlineimages"
org-msg-greeting-name-limit 3
org-msg-default-alternatives '((new . (utf-8 html))
(reply-to-text . (utf-8))
(reply-to-html . (utf-8 html)))
org-msg-convert-citation t)
2017-04-16 21:24:18 -04:00
(defvar +org-msg-currently-exporting nil
"Helper variable to indicate whether org-msg is currently exporting the org buffer to HTML.
Usefull for affecting HTML export config.")
(defadvice! +org-msg--now-exporting-a (&rest _)
:before #'org-msg-org-to-xml
(setq +org-msg-currently-exporting t))
(defadvice! +org-msg--not-exporting-a (&rest _)
:after #'org-msg-org-to-xml
(setq +org-msg-currently-exporting nil))
(advice-add #'org-html-latex-fragment :override #'+org-html-latex-fragment-scaled-a)
(advice-add #'org-html-latex-environment :override #'+org-html-latex-environment-scaled-a)
(map! :map org-msg-edit-mode-map
:desc "attach" "C-c C-a" #'+mu4e/attach-files
:localleader
:desc "attach" "a" #'+mu4e/attach-files)
(defvar +mu4e-compose-org-msg-toggle-next t ; t to initialise org-msg
"Whether to toggle ")
(defun +mu4e-compose-org-msg-handle-toggle (toggle-p)
(when (xor toggle-p +mu4e-compose-org-msg-toggle-next)
(org-msg-mode (if org-msg-mode -1 1))
(setq +mu4e-compose-org-msg-toggle-next
(not +mu4e-compose-org-msg-toggle-next))))
(defadvice! +mu4e-maybe-toggle-org-msg-a (fn &optional toggle-p)
:around #'mu4e-compose-new
:around #'mu4e-compose-reply
:around #'mu4e-compose-forward
:around #'mu4e-compose-resend
(interactive "p")
(+mu4e-compose-org-msg-handle-toggle (/= 1 (or toggle-p 0)))
(funcall fn))
(defadvice! +mu4e-draft-open-signature-a (fn &rest args)
"Prevent `mu4e-compose-signature' from being used with `org-msg-mode'."
:around #'mu4e-draft-open
(let ((mu4e-compose-signature (unless org-msg-mode mu4e-compose-signature)))
(apply fn args)))
2021-02-25 18:36:08 +08:00
(map! :map org-msg-edit-mode-map
"TAB" #'org-msg-tab) ; only <tab> bound by default
(defvar +org-msg-accent-color "#c01c28"
"Accent color to use in org-msg's generated CSS.
Must be set before org-msg is loaded to take effect.")
(setq org-msg-enforce-css
(let* ((font-family '(font-family . "-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen, Ubuntu, Cantarell,\
\"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";"))
(monospace-font '(font-family . "SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;"))
(font-size '(font-size . "11pt"))
(font `(,font-family ,font-size))
(line-height '(line-height . "1.2"))
(theme-color +org-msg-accent-color)
(bold '(font-weight . "bold"))
(color `(color . ,theme-color))
(table `((margin-top . "6px") (margin-bottom . "6px")
(border-left . "none") (border-right . "none")
(border-top . "2px solid #222222")
(border-bottom . "2px solid #222222")
))
(ftl-number `(,color ,bold (text-align . "left")))
(inline-modes '(asl c c++ conf cpp csv diff ditaa emacs-lisp
fundamental ini json makefile man org plantuml
python sh xml))
(inline-src `((background-color . "rgba(27,31,35,.05)")
(border-radius . "3px")
(padding . ".2em .4em")
(font-size . "90%") ,monospace-font
(margin . 0)))
(code-src
(mapcar (lambda (mode)
`(code ,(intern (concat "src src-" (symbol-name mode)))
,inline-src))
inline-modes))
(base-quote '((padding-left . "5px") (margin-left . "10px")
(margin-top . "20px") (margin-bottom . "0")
(font-style . "italic") (background . "#f9f9f9")))
(quote-palette '("#6A8FBF" "#bf8f6a" "#6abf8a" "#906abf"
"#6aaebf" "#bf736a" "#bfb66a" "#bf6a94"
"#6abf9b" "#bf6a7d" "#acbf6a" "#6a74bf"))
(quotes
(mapcar (lambda (x)
(let ((c (nth x quote-palette)))
`(div ,(intern (format "quote%d" (1+ x)))
(,@base-quote
(color . ,c)
(border-left . ,(concat "3px solid "
(org-msg-lighten c)))))))
(number-sequence 0 (1- (length quote-palette))))))
`((del nil ((color . "grey") (border-left . "none")
(text-decoration . "line-through") (margin-bottom . "0px")
(margin-top . "10px") (line-height . "11pt")))
(a nil (,color))
(a reply-header ((color . "black") (text-decoration . "none")))
(div reply-header ((padding . "3.0pt 0in 0in 0in")
(border-top . "solid #e1e1e1 1.0pt")
(margin-bottom . "20px")))
(span underline ((text-decoration . "underline")))
(li nil (,line-height (margin-bottom . "0px")
(margin-top . "2px")
(max-width . "84ch")))
(nil org-ul ((list-style-type . "disc")))
(nil org-ol (,@font ,line-height (margin-bottom . "0px")
(margin-top . "0px") (margin-left . "30px")
(padding-top . "0px") (padding-left . "5px")))
(nil signature (,@font (margin-bottom . "20px")))
(blockquote nil ((padding . "2px 12px") (margin-left . "10px")
(margin-top . "10px") (margin-bottom . "0")
(border-left . "3px solid #ccc")
(font-style . "italic")
(background . "#f9f9f9")))
(p blockquote ((margin . "0") (padding . "4px 0")))
,@quotes
(code nil (,font-size ,monospace-font (background . "#f9f9f9")))
,@code-src
(nil linenr ((padding-right . "1em")
(color . "black")
(background-color . "#aaaaaa")))
(pre nil ((line-height . "1.2")
(color . ,(face-foreground 'default))
(background-color . ,(face-background 'default))
(margin . "4px 0px 8px 0px")
(padding . "8px 12px")
(width . "max-content")
(min-width . "80ch")
(border-radius . "5px")
(font-weight . "500")
,monospace-font))
(div org-src-container ((margin-top . "10px")))
(nil figure-number ,ftl-number)
(nil table-number)
(caption nil ((text-align . "left")
(background . ,theme-color)
(color . "white")
,bold))
(nil t-above ((caption-side . "top")))
(nil t-bottom ((caption-side . "bottom")))
(nil listing-number ,ftl-number)
(nil figure ,ftl-number)
(nil org-src-name ,ftl-number)
(img nil ((vertical-align . "middle")
(max-width . "100%")))
(img latex-fragment-inline ((margin . "0 0.1em")))
(table nil (,@table ,line-height (border-collapse . "collapse")))
(th nil ((border . "none") (border-bottom . "1px solid #222222")
(background-color . "#EDEDED") (font-weight . "500")
(padding . "3px 10px")))
(td nil (,@table (padding . "1px 10px")
(background-color . "#f9f9f9") (border . "none")))
(td org-left ((text-align . "left")))
(td org-right ((text-align . "right")))
(td org-center ((text-align . "center")))
(kbd nil ((border . "1px solid #d1d5da") (border-radius . "3px")
(box-shadow . "inset 0 -1px 0 #d1d5da")
(background-color . "#fafbfc") (color . "#444d56")
(padding . "3px 5px") (display . "inline-block")))
(div outline-text-4 ((margin-left . "15px")))
(div outline-4 ((margin-left . "10px")))
(h4 nil ((margin-bottom . "0px") (font-size . "11pt")))
(h3 nil ((margin-bottom . "0px")
,color (font-size . "14pt")))
(h2 nil ((margin-top . "20px") (margin-bottom . "20px")
,color (font-size . "18pt")))
(h1 nil ((margin-top . "20px") (margin-bottom . "0px")
,color (font-size . "24pt")))
(p nil ((text-decoration . "none") (line-height . "1.4")
(margin-top . "10px") (margin-bottom . "0px")
,font-size (max-width . "90ch")))
(b nil ((font-weight . "500") (color . ,theme-color)))
(div nil (,@font (line-height . "12pt")))))))
2017-04-08 01:31:20 -04:00
2018-01-24 15:08:43 -05:00
;;
;;; Gmail integration
(when (featurep! +gmail)
(after! mu4e
(defvar +mu4e-gmail-accounts nil
"Gmail accounts that do not contain \"gmail\" in address and maildir.
An alist of Gmail addresses of the format \((\"username@domain.com\" . \"account-maildir\"))
to which Gmail integrations (behind the `+gmail' flag of the `mu4e' module) should be applied.
See `+mu4e-msg-gmail-p' and `mu4e-sent-messages-behavior'.")
;; don't save message to Sent Messages, Gmail/IMAP takes care of this
(setq mu4e-sent-messages-behavior
(lambda () ;; TODO make use +mu4e-msg-gmail-p
(if (or (string-match-p "@gmail.com\\'" (message-sendmail-envelope-from))
(member (message-sendmail-envelope-from)
(mapcar #'car +mu4e-gmail-accounts)))
'delete 'sent)))
(defun +mu4e-msg-gmail-p (msg)
(let ((root-maildir
(replace-regexp-in-string "/.*" ""
(substring (mu4e-message-field msg :maildir) 1))))
(or (string-match-p "gmail" root-maildir)
(member root-maildir (mapcar #'cdr +mu4e-gmail-accounts)))))
;; In my workflow, emails won't be moved at all. Only their flags/labels are
;; changed. Se we redefine the trash and refile marks not to do any moving.
;; However, the real magic happens in `+mu4e-gmail-fix-flags-h'.
;;
;; Gmail will handle the rest.
(defun +mu4e--mark-seen (docid _msg target)
(mu4e~proc-move docid (mu4e~mark-check-target target) "+S-u-N"))
(defvar +mu4e--last-invalid-gmail-action 0)
(delq! 'delete mu4e-marks #'assq)
(setf (alist-get 'delete mu4e-marks)
(list
:char '("D" . "")
:prompt "Delete"
2020-10-31 19:12:24 +08:00
:show-target (lambda (_target) "delete")
:action (lambda (docid msg target)
(if (+mu4e-msg-gmail-p msg)
(progn (message "The delete operation is invalid for Gmail accounts. Trashing instead.")
(+mu4e--mark-seen docid msg target)
(when (< 2 (- (float-time) +mu4e--last-invalid-gmail-action))
(sit-for 1))
(setq +mu4e--last-invalid-gmail-action (float-time)))
(mu4e~proc-remove docid))))
(alist-get 'trash mu4e-marks)
(list :char '("d" . "")
:prompt "dtrash"
:dyn-target (lambda (_target msg) (mu4e-get-trash-folder msg))
:action (lambda (docid msg target)
(if (+mu4e-msg-gmail-p msg)
(+mu4e--mark-seen docid msg target)
(mu4e~proc-move docid (mu4e~mark-check-target target) "+T-N"))))
;; Refile will be my "archive" function.
(alist-get 'refile mu4e-marks)
(list :char '("r" . "")
:prompt "rrefile"
:dyn-target (lambda (_target msg) (mu4e-get-refile-folder msg))
:action (lambda (docid msg target)
(if (+mu4e-msg-gmail-p msg)
(+mu4e--mark-seen docid msg target)
(mu4e~proc-move docid (mu4e~mark-check-target target) "-N")))
#'+mu4e--mark-seen))
;; This hook correctly modifies gmail flags on emails when they are marked.
;; Without it, refiling (archiving), trashing, and flagging (starring) email
;; won't properly result in the corresponding gmail action, since the marks
;; are ineffectual otherwise.
(add-hook! 'mu4e-mark-execute-pre-hook
(defun +mu4e-gmail-fix-flags-h (mark msg)
(when (+mu4e-msg-gmail-p msg)
(pcase mark
(`trash (mu4e-action-retag-message msg "-\\Inbox,+\\Trash,-\\Draft"))
(`delete (mu4e-action-retag-message msg "-\\Inbox,+\\Trash,-\\Draft"))
(`refile (mu4e-action-retag-message msg "-\\Inbox"))
(`flag (mu4e-action-retag-message msg "+\\Starred"))
(`unflag (mu4e-action-retag-message msg "-\\Starred"))))))))
2020-10-10 03:28:58 +08:00
;;
;;; Alerts
(use-package! mu4e-alert
:after mu4e
:config
(setq doom-modeline-mu4e t)
(mu4e-alert-enable-mode-line-display)
(mu4e-alert-enable-notifications)
(when IS-LINUX
(mu4e-alert-set-default-style 'libnotify)
(defvar +mu4e-alert-bell-cmd '("paplay" . "/usr/share/sounds/freedesktop/stereo/message.oga")
"Cons list with command to play a sound, and the sound file to play.
Disabled when set to nil.")
2020-10-10 03:28:58 +08:00
(setq mu4e-alert-email-notification-types '(subjects))
2020-10-31 19:12:24 +08:00
(defun +mu4e-alert-grouped-mail-notification-formatter-with-bell (mail-group _all-mails)
2020-10-10 03:28:58 +08:00
"Default function to format MAIL-GROUP for notification.
ALL-MAILS are the all the unread emails"
(when +mu4e-alert-bell-cmd
(start-process (car +mu4e-alert-bell-cmd) (cdr +mu4e-alert-bell-cmd)))
2020-10-10 03:28:58 +08:00
(if (> (length mail-group) 1)
(let* ((mail-count (length mail-group))
(first-mail (car mail-group))
(title-prefix (format "You have %d unread emails"
mail-count))
(field-value (mu4e-alert--get-group first-mail))
(title-suffix (format (pcase mu4e-alert-group-by
(`:from "from %s:")
(`:to "to %s:")
(`:maildir "in %s:")
(`:priority "with %s priority:")
(`:flags "with %s flags:"))
field-value))
(title (format "%s %s" title-prefix title-suffix)))
(list :title title
:body (s-join "\n"
(mapcar (lambda (mail)
(format "%s<b>%s</b> • %s"
(cond
((plist-get mail :in-reply-to) "")
((string-match-p "\\`Fwd:"
(plist-get mail :subject)) "")
(t "  "))
(truncate-string-to-width (caar (plist-get mail :from))
20 nil nil t)
(truncate-string-to-width
(replace-regexp-in-string "\\`Re: \\|\\`Fwd: " ""
(plist-get mail :subject))
40 nil nil t)))
mail-group))))
(let* ((new-mail (car mail-group))
(subject (plist-get new-mail :subject))
(sender (caar (plist-get new-mail :from))))
(list :title sender :body subject))))
(setq mu4e-alert-grouped-mail-notification-formatter #'+mu4e-alert-grouped-mail-notification-formatter-with-bell)))