commit
7f8cba9ba0
9 changed files with 1089 additions and 115 deletions
|
@ -84,7 +84,7 @@ Modules that reconfigure or augment packages or features built into Emacs.
|
|||
+ [[file:../modules/emacs/vc/README.org][vc]] - TODO
|
||||
|
||||
* :email
|
||||
+ [[file:../modules/email/mu4e/README.org][mu4e]] =+gmail= - TODO
|
||||
+ [[file:../modules/email/mu4e/README.org][mu4e]] =+org +gmail= - TODO
|
||||
+ [[file:../modules/email/notmuch/README.org][notmuch]] - TODO
|
||||
+ wanderlust =+gmail= - TODO
|
||||
|
||||
|
|
|
@ -171,7 +171,7 @@
|
|||
;;zig ; C, but simpler
|
||||
|
||||
:email
|
||||
;;(mu4e +gmail)
|
||||
;;(mu4e +org +gmail)
|
||||
;;notmuch
|
||||
;;(wanderlust +gmail)
|
||||
|
||||
|
|
|
@ -3,21 +3,20 @@
|
|||
#+SINCE: v2.0
|
||||
#+STARTUP: inlineimages
|
||||
|
||||
* Table of Contents :TOC:
|
||||
* Table of Contents :TOC:noexport:
|
||||
- [[#description][Description]]
|
||||
- [[#maintainers][Maintainers]]
|
||||
- [[#module-flags][Module Flags]]
|
||||
- [[#plugins][Plugins]]
|
||||
- [[#prerequisites][Prerequisites]]
|
||||
- [[#macos][MacOS]]
|
||||
- [[#arch-linux][Arch Linux]]
|
||||
- [[#nixos][NixOS]]
|
||||
- [[#opensuse][openSUSE]]
|
||||
- [[#debianubuntu][Debian/Ubuntu]]
|
||||
- [[#features][Features]]
|
||||
- [[#configuration][Configuration]]
|
||||
- [[#offlineimap][offlineimap]]
|
||||
- [[#mbsync][mbsync]]
|
||||
- [[#mu-and-mu4e][mu and mu4e]]
|
||||
- [[#orgmsg][OrgMsg]]
|
||||
- [[#mu4e-alert][mu4e-alert]]
|
||||
- [[#troubleshooting][Troubleshooting]]
|
||||
- [[#no-such-file-or-directory-mu4e][=No such file or directory, mu4e=]]
|
||||
- [[#void-function-org-time-add-error-on-gentoo][~(void-function org-time-add)~ error on Gentoo]]
|
||||
|
@ -34,41 +33,50 @@ It uses ~mu4e~ to read my email, but depends on ~offlineimap~ (to sync my email
|
|||
via IMAP) and ~mu~ (to index my mail into a format ~mu4e~ can understand).
|
||||
#+end_quote
|
||||
|
||||
** Maintainers
|
||||
+ [[https://github.com/tecosaur][@tecosaur]]
|
||||
|
||||
** Module Flags
|
||||
+ ~+gmail~ Enables gmail-specific configuration.
|
||||
+ =+gmail= Enables gmail-specific configuration for mail ~To~ or ~From~ a gmail
|
||||
address, or a maildir with ~gmail~ in the name.
|
||||
+ =+org= Use =org-msg= for composing email in Org, then sending a multipart text
|
||||
(ASCII export) and HTML message.
|
||||
|
||||
** Plugins
|
||||
+ [[https://github.com/jeremy-compostella/org-msg][org-msg]]
|
||||
+ [[https://github.com/iqbalansari/mu4e-alert][mu4e-alert]]
|
||||
+ =+org=
|
||||
+ [[https://github.com/jeremy-compostella/org-msg][org-msg]]
|
||||
|
||||
* Prerequisites
|
||||
This module requires:
|
||||
|
||||
+ Either ~mbsync~ (default) or ~offlineimap~ (to sync mail with)
|
||||
+ Either ~mbsync~ (recommended, default) or ~offlineimap~ (to sync mail with)
|
||||
+ ~mu~, to index your downloaded messages and to provide the ~mu4e~ package.
|
||||
|
||||
** MacOS
|
||||
#+BEGIN_SRC sh
|
||||
brew install mu
|
||||
# And one of the following
|
||||
brew install isync # mbsync
|
||||
brew install offlineimap
|
||||
#+END_SRC
|
||||
#+name: Install Matrix
|
||||
| Platform | Install command | Base packages |
|
||||
|---------------+------------------------+---------------------|
|
||||
| MacOS | ~brew install <pkgs>~ | =mu= |
|
||||
| Arch | ~pacman -S <pkgs>~ | (AUR, ~yay -S~) =mu= |
|
||||
| openSUSE | ~zypper install <pkgs>~ | =maildir-utils=, =mu4e= |
|
||||
| Fedora | ~dnf install <pkgs>~ | =maildir-utils= |
|
||||
| Debian/Ubuntu | ~apt-get install <pkgs>~ | =maildir-utils=, =mu4e= |
|
||||
|
||||
** Arch Linux
|
||||
Run one of the following commands.
|
||||
The install either the =isync= (=mbsync=) or =offlineimap= package.
|
||||
|
||||
#+BEGIN_SRC sh
|
||||
sudo pacman -S isync # mbsync
|
||||
# OR
|
||||
sudo pacman -S offlineimap
|
||||
#+END_SRC
|
||||
To send mail, mu4e uses [[https://www.gnu.org/software/emacs/manual/html_mono/smtpmail.html][smtpmail]] (an Emacs library) by default.
|
||||
You can also run a local SMTP server like =sendmail= or =postfix=, or use an SMTP
|
||||
forwarder such as =msmtp= (recommended).
|
||||
|
||||
Install ~mu~, which is not available in the main repositories but in the AUR, by
|
||||
using for example the AUR helper ~yay~.
|
||||
|
||||
#+BEGIN_SRC sh
|
||||
yay -S mu
|
||||
#+END_SRC
|
||||
If you use =msmtp=, you'll likely want to add the following to your
|
||||
=config.el=:
|
||||
#+begin_src emacs-lisp
|
||||
(setq sendmail-program "/usr/bin/msmtp"
|
||||
send-mail-function #'smtpmail-send-it
|
||||
message-sendmail-f-is-evil t
|
||||
message-sendmail-extra-arguments '("--read-envelope-from")
|
||||
message-send-mail-function #'message-send-mail-with-sendmail)
|
||||
#+end_src
|
||||
|
||||
** NixOS
|
||||
#+BEGIN_SRC nix
|
||||
|
@ -82,27 +90,16 @@ environment.systemPackages = with pkgs; [
|
|||
|
||||
[[https://github.com/Emiller88/dotfiles/blob/5eaabedf1b141c80a8d32e1b496055231476f65e/modules/shell/mail.nix][An example of setting up mbsync and mu with home-manager]]
|
||||
|
||||
** openSUSE
|
||||
Remove ~#~ in ~#sync_program=offlineimap~ to choose ~offlineimap~ instead of
|
||||
~mbsync~.
|
||||
|
||||
#+BEGIN_SRC sh :dir /sudo::
|
||||
sync_program=isync # mbsync
|
||||
#sync_program=offlineimap
|
||||
|
||||
sudo zypper install maildir-utils $sync_program
|
||||
#+END_SRC
|
||||
|
||||
** Debian/Ubuntu
|
||||
Run the command corresponding to the desired backend and the last one.
|
||||
#+BEGIN_SRC sh
|
||||
sudo apt-get install isync # mbsync
|
||||
# or
|
||||
sudo apt-get install offlineimap
|
||||
# then
|
||||
sudo apt-get install maildir-utils mu4e # mu and mu4e respectivly
|
||||
#+END_SRC
|
||||
* TODO Features
|
||||
* Features
|
||||
+ Tidied mu4e headers view, with flags from =all-the-icons=
|
||||
+ Consistent coloring of reply depths (across compose and gnus modes)
|
||||
+ Prettified =mu4e:main= view
|
||||
+ Cooperative locking of the =mu= process. Another Emacs instance may request
|
||||
access, or grab the lock when it's available.
|
||||
+ =org-msg= integration with =+org=, which can be toggled per-message, with revamped style and
|
||||
an accent color
|
||||
+ Gmail integrations with the =+gmail= flag
|
||||
+ Email notifications with =mu4e-alert=, and (on Linux) a customised notification style
|
||||
|
||||
* Configuration
|
||||
** offlineimap
|
||||
|
@ -126,8 +123,8 @@ You can now proceed with the [[#mu-and-mu4e][mu and mu4e]] section.
|
|||
The steps needed to set up =mu4e= with =mbsync= are very similar to the ones for
|
||||
[[#offlineimap][offlineimap]].
|
||||
|
||||
Start with writing a ~\~/.mbsyncrc~. An example for GMAIL can be found on
|
||||
[[http://pragmaticemacs.com/emacs/migrating-from-offlineimap-to-mbsync-for-mu4e/][pragmaticemacs.com]]. A non-GMAIL example is available as a gist [[https://gist.github.com/agraul/60977cc497c3aec44e10591f94f49ef0][here]]. The [[http://isync.sourceforge.net/mbsync.html][manual
|
||||
Start with writing a ~~/.mbsyncrc~. An example for Gmail can be found on
|
||||
[[http://pragmaticemacs.com/emacs/migrating-from-offlineimap-to-mbsync-for-mu4e/][pragmaticemacs.com]]. A non-Gmail example is available as a gist [[https://gist.github.com/agraul/60977cc497c3aec44e10591f94f49ef0][here]]. The [[http://isync.sourceforge.net/mbsync.html][manual
|
||||
page]] contains all needed information to set up your own.
|
||||
|
||||
Next you can download your email with ~mbsync --all~. This may take a while, but
|
||||
|
@ -135,11 +132,18 @@ should be quicker than =offlineimap= ;).
|
|||
|
||||
You can now proceed with the [[#mu-and-mu4e][mu and mu4e]] section.
|
||||
|
||||
*** Faster syncing
|
||||
It's possible to use IMAP IDLE to be quickly notified of updates, then use a
|
||||
tailored =mbsync= command to just fetch the new changes.
|
||||
|
||||
If this is of interest, this approach can be seen [[https://tecosaur.github.io/emacs-config/config.html#fetching][in @tecosaur's config]] where
|
||||
[[https://gitlab.com/shackra/goimapnotify][goimapnotify]] is used for this.
|
||||
|
||||
** mu and mu4e
|
||||
You should have your email downloaded already. If you have not, you need to set
|
||||
=offlineimap= or =mbsync= up before you proceed.
|
||||
|
||||
Before you can use =mu4e= or the cli program =mu=, you need to index your email
|
||||
Before you can use =mu4e= or the CLI program =mu=, you need to index your email
|
||||
initially. How to do that differs a little depending on the version of =mu= you
|
||||
use. You can check your version with ~mu --version~.
|
||||
|
||||
|
@ -169,6 +173,73 @@ Then configure Emacs to use your email address:
|
|||
t)
|
||||
#+END_SRC
|
||||
|
||||
If you use multiple email accounts, defining them with ~set-email-account!~ will
|
||||
automatically set the appropriate account context when replying to emails in
|
||||
that account's maildir. ~mu4e-context-policy~ and ~mu4e-compose-context-policy~
|
||||
can be modified to change context behavior when opening mu4e and composing
|
||||
email:
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(setq mu4e-context-policy 'ask-if-none
|
||||
mu4e-compose-context-policy 'always-ask)
|
||||
#+end_src
|
||||
|
||||
If you send mail from various email aliases for different services,
|
||||
~+mu4e-personal-addresses~ can be set per-context with ~set-email-account!~. If
|
||||
you are not replying to an email to or from one of the specified aliases, you
|
||||
will be prompted for an alias to send from.
|
||||
|
||||
*** Gmail
|
||||
With the =+gmail= flag, integrations are applied which account for the different
|
||||
behaviour of Gmail.
|
||||
|
||||
The integrations are applied to addresses with /both/ "@gmail.com" in the
|
||||
account address and "gmail" in the account maildir, as well as accounts listed
|
||||
in ~+mu4e-gmail-accounts~. Any domain can be specified, so G Suite accounts can
|
||||
benefit from the integrations:
|
||||
#+begin_src emacs-lisp
|
||||
;; if "gmail" is missing from the address or maildir, the account must be listed here
|
||||
(setq +mu4e-gmail-accounts '(("hlissner@gmail.com" . "/hlissner")
|
||||
("example@example.com" . "/example")))
|
||||
#+end_src
|
||||
|
||||
If you only use Gmail, you can improve performance due to the way Gmail
|
||||
presents messages over IMAP:
|
||||
#+begin_src emacs-lisp
|
||||
;; don't need to run cleanup after indexing for gmail
|
||||
(setq mu4e-index-cleanup nil
|
||||
;; because gmail uses labels as folders we can use lazy check since
|
||||
;; messages don't really "move"
|
||||
mu4e-index-lazy-check t)
|
||||
#+end_src
|
||||
|
||||
Also, note that Gmail's IMAP settings must have "When I mark a message in IMAP
|
||||
as deleted: Auto-Expunge off - Wait for the client to update the server." and
|
||||
"When a message is marked as deleted and expunged from the last visible IMAP
|
||||
folder: Move the message to the trash" for the integrations to work as expected.
|
||||
|
||||
** OrgMsg
|
||||
With the =+org= flag, =org-msg= is installed, and ~org-msg-mode~ is enabled before
|
||||
composing the first message. To disable ~org-msg-mode~ by default, simply
|
||||
#+BEGIN_SRC emacs-lisp :tangle no
|
||||
(setq mu4e-compose--org-msg-toggle-next nil)
|
||||
#+END_SRC
|
||||
|
||||
To toggle org-msg for a single message, just apply the universal argument to the
|
||||
compose or reply command (=SPC u= with ~evil~, =C-u= otherwise).
|
||||
|
||||
The accent color that Doom uses can be customised by setting
|
||||
~+org-msg-accent-color~ to a CSS color string.
|
||||
|
||||
** mu4e-alert
|
||||
This provides notifications through the [[https://github.com/jwiegley/alert][alert]] library.
|
||||
|
||||
If you don't like this, simply add
|
||||
#+begin_src emacs-lisp
|
||||
(package! mu4e-alert :disable t)
|
||||
#+end_src
|
||||
to your [[elisp:(find-file (expand-file-name "packages.el" doom-private-dir))][packages.el]] and it will not be used.
|
||||
|
||||
* Troubleshooting
|
||||
** =No such file or directory, mu4e=
|
||||
You will get =No such file or directory, mu4e= errors if you don't run ~doom
|
||||
|
|
132
modules/email/mu4e/autoload/advice.el
Normal file
132
modules/email/mu4e/autoload/advice.el
Normal file
|
@ -0,0 +1,132 @@
|
|||
;;; email/mu4e/autoload/advice.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;;###autoload
|
||||
(defun +mu4e~main-action-str-prettier-a (str &optional func-or-shortcut)
|
||||
"Highlight the first occurrence of [.] in STR.
|
||||
If FUNC-OR-SHORTCUT is non-nil and if it is a function, call it
|
||||
when STR is clicked (using RET or mouse-2); if FUNC-OR-SHORTCUT is
|
||||
a string, execute the corresponding keyboard action when it is
|
||||
clicked."
|
||||
(let ((newstr
|
||||
(replace-regexp-in-string
|
||||
"\\[\\(..?\\)\\]"
|
||||
(lambda(m)
|
||||
(format "%s"
|
||||
(propertize (match-string 1 m) 'face '(mode-line-emphasis bold))))
|
||||
(replace-regexp-in-string "\t\\*" "\t⚫" str)))
|
||||
(map (make-sparse-keymap))
|
||||
(func (if (functionp func-or-shortcut)
|
||||
func-or-shortcut
|
||||
(if (stringp func-or-shortcut)
|
||||
(lambda()(interactive)
|
||||
(execute-kbd-macro func-or-shortcut))))))
|
||||
(define-key map [mouse-2] func)
|
||||
(define-key map (kbd "RET") func)
|
||||
(put-text-property 0 (length newstr) 'keymap map newstr)
|
||||
(put-text-property (string-match "[A-Za-z].+$" newstr)
|
||||
(- (length newstr) 1) 'mouse-face 'highlight newstr)
|
||||
newstr))
|
||||
|
||||
;;;###autoload
|
||||
(defun +mu4e~main-keyval-str-prettier-a (str)
|
||||
"Replace '*' with '⚫' in STR."
|
||||
(replace-regexp-in-string "\t\\*" "\t⚫" str))
|
||||
|
||||
;; Org msg LaTeX image scaling
|
||||
|
||||
;;;###autoload
|
||||
(defun +org-msg-img-scale-css (img-uri)
|
||||
"For a given IMG-URI, use imagemagick to find its width."
|
||||
(if +org-msg-currently-exporting
|
||||
(when (and (not IS-WINDOWS)) ; relies on posix path
|
||||
(let ((with-call (and (executable-find "identify")
|
||||
(doom-call-process "identify" "-format" "%w"
|
||||
(substring img-uri 7))))) ; 7=(length "file://")
|
||||
(unless width-call
|
||||
(setq width-call (doom-call-process "file" (substring img-uri 7)))
|
||||
(setcdr width-call (replace-regexp-in-string "^.*image data, \\([0-9]+\\).*$" "\\1" (cdr width-call)))
|
||||
(setcar width-call (if (< 0 (string-to-number (cdr width-call))) 0 1)))
|
||||
(when (= (car width-call) 0)
|
||||
(list :width
|
||||
(format "%.1fpx"
|
||||
(/ (string-to-number (cdr width-call))
|
||||
(plist-get org-format-latex-options :scale)))))))
|
||||
(list :style (format "transform: scale(%.3f)"
|
||||
(/ 1.0 (plist-get org-format-latex-options :scale))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun +org-html-latex-fragment-scaled-a (latex-fragment _contents info)
|
||||
"Transcode a LATEX-FRAGMENT object from Org to HTML.
|
||||
CONTENTS is nil. INFO is a plist holding contextual information.
|
||||
|
||||
This differs from `org-html-latex-fragment' in that it uses the LaTeX fragment
|
||||
as a meaningful alt value, applies a class to indicate what sort of fragment it is
|
||||
(latex-fragment-inline or latex-fragment-block), and (on Linux) scales the image to
|
||||
account for the value of :scale in `org-format-latex-options'."
|
||||
(let ((latex-frag (org-element-property :value latex-fragment))
|
||||
(processing-type (plist-get info :with-latex)))
|
||||
(cond
|
||||
((memq processing-type '(t mathjax))
|
||||
(org-html-format-latex latex-frag 'mathjax info))
|
||||
((eq processing-type 'html)
|
||||
(org-html-format-latex latex-frag 'html info))
|
||||
((assq processing-type org-preview-latex-process-alist)
|
||||
(let ((formula-link
|
||||
(org-html-format-latex latex-frag processing-type info)))
|
||||
(when (and formula-link (string-match "file:\\([^]]*\\)" formula-link))
|
||||
(let ((source (org-export-file-uri (match-string 1 formula-link)))
|
||||
(attributes (append (list :alt latex-frag
|
||||
:class
|
||||
(concat "latex-fragment-"
|
||||
(if (equal "\\(" (substring latex-frag 0 2))
|
||||
"inline" "block")))
|
||||
(when (memq processing-type '(dvipng convert))
|
||||
(+org-msg-img-scale-css source)))))
|
||||
(org-html--format-image source attributes info)))))
|
||||
(t latex-frag))))
|
||||
|
||||
;;;###autoload
|
||||
(defun +org-html-latex-environment-scaled-a (latex-environment _contents info)
|
||||
"Transcode a LATEX-ENVIRONMENT element from Org to HTML.
|
||||
CONTENTS is nil. INFO is a plist holding contextual information.
|
||||
|
||||
This differs from `org-html-latex-environment' in that (on Linux) it
|
||||
scales the image to account for the value of :scale in `org-format-latex-options'."
|
||||
(let ((processing-type (plist-get info :with-latex))
|
||||
(latex-frag (org-remove-indentation
|
||||
(org-element-property :value latex-environment)))
|
||||
(attributes (org-export-read-attribute :attr_html latex-environment))
|
||||
(label (and (org-element-property :name latex-environment)
|
||||
(org-export-get-reference latex-environment info)))
|
||||
(caption (and (org-html--latex-environment-numbered-p latex-environment)
|
||||
(number-to-string
|
||||
(org-export-get-ordinal
|
||||
latex-environment info nil
|
||||
(lambda (l _)
|
||||
(and (org-html--math-environment-p l)
|
||||
(org-html--latex-environment-numbered-p l))))))))
|
||||
(plist-put attributes :class "latex-environment")
|
||||
(cond
|
||||
((memq processing-type '(t mathjax))
|
||||
(org-html-format-latex
|
||||
(if (org-string-nw-p label)
|
||||
(replace-regexp-in-string "\\`.*"
|
||||
(format "\\&\n\\\\label{%s}" label)
|
||||
latex-frag)
|
||||
latex-frag)
|
||||
'mathjax info))
|
||||
((assq processing-type org-preview-latex-process-alist)
|
||||
(let ((formula-link
|
||||
(org-html-format-latex
|
||||
(org-html--unlabel-latex-environment latex-frag)
|
||||
processing-type info)))
|
||||
(when (and formula-link (string-match "file:\\([^]]*\\)" formula-link))
|
||||
(let ((source (org-export-file-uri (match-string 1 formula-link))))
|
||||
(org-html--wrap-latex-environment
|
||||
(org-html--format-image source
|
||||
(append attributes
|
||||
(when (memq processing-type '(dvipng convert))
|
||||
(+org-msg-img-scale-css source)))
|
||||
info)
|
||||
info caption label)))))
|
||||
(t (org-html--wrap-latex-environment latex-frag info caption label)))))
|
|
@ -15,6 +15,7 @@ OPTIONAL:
|
|||
+ `mu4e-trash-folder'
|
||||
+ `mu4e-refile-folder'
|
||||
+ `mu4e-compose-signature'
|
||||
+ `+mu4e-personal-addresses'
|
||||
|
||||
DEFAULT-P is a boolean. If non-nil, it marks that email account as the
|
||||
default/fallback account."
|
||||
|
@ -22,29 +23,30 @@ default/fallback account."
|
|||
(when (version< mu4e-mu-version "1.4")
|
||||
(when-let (address (cdr (assq 'user-mail-address letvars)))
|
||||
(add-to-list 'mu4e-user-mail-address-list address)))
|
||||
;; remove existing context with same label
|
||||
(setq mu4e-contexts
|
||||
(cl-loop for context in mu4e-contexts
|
||||
unless (string= (mu4e-context-name context) label)
|
||||
collect context))
|
||||
(let ((context (make-mu4e-context
|
||||
:name label
|
||||
:enter-func (lambda () (mu4e-message "Switched to %s" label))
|
||||
:leave-func #'mu4e-clear-caches
|
||||
:enter-func
|
||||
(lambda () (mu4e-message "Switched to %s" label))
|
||||
:leave-func
|
||||
(lambda () (progn (setq +mu4e-personal-addresses nil)
|
||||
(mu4e-clear-caches)))
|
||||
:match-func
|
||||
(lambda (msg)
|
||||
(when msg
|
||||
(string-prefix-p (format "/%s" label)
|
||||
(mu4e-message-field msg :maildir))))
|
||||
(mu4e-message-field msg :maildir) t)))
|
||||
:vars letvars)))
|
||||
(push context mu4e-contexts)
|
||||
(when default-p
|
||||
(setq-default mu4e-context-current context))
|
||||
(add-to-list 'mu4e-contexts context (not default-p))
|
||||
context)))
|
||||
|
||||
|
||||
|
||||
(defvar +mu4e-workspace-name "*mu4e*"
|
||||
"TODO")
|
||||
"Name of the workspace created by `=mu4e', dedicated to mu4e.")
|
||||
(defvar +mu4e--old-wconf nil)
|
||||
|
||||
(add-hook 'mu4e-main-mode-hook #'+mu4e-init-h)
|
||||
|
@ -55,7 +57,13 @@ default/fallback account."
|
|||
(interactive)
|
||||
(require 'mu4e)
|
||||
(if (featurep! :ui workspaces)
|
||||
(+workspace-switch +mu4e-workspace-name t)
|
||||
;; delete current workspace if empty
|
||||
;; this is useful when mu4e is in the daemon
|
||||
;; as otherwise you can accumulate empty workspaces
|
||||
(progn
|
||||
(unless (+workspace-buffer-list)
|
||||
(+workspace-delete (+workspace-current-name)))
|
||||
(+workspace-switch +mu4e-workspace-name t))
|
||||
(setq +mu4e--old-wconf (current-window-configuration))
|
||||
(delete-other-windows)
|
||||
(switch-to-buffer (doom-fallback-buffer)))
|
||||
|
@ -71,9 +79,241 @@ default/fallback account."
|
|||
;; TODO Interactively select email account
|
||||
(call-interactively #'mu4e-compose-new))
|
||||
|
||||
(defun +mu4e--get-string-width (str)
|
||||
"Return the width in pixels of a string in the current
|
||||
window's default font. If the font is mono-spaced, this
|
||||
will also be the width of all other printable characters."
|
||||
(let ((window (selected-window))
|
||||
(remapping face-remapping-alist))
|
||||
(with-temp-buffer
|
||||
(make-local-variable 'face-remapping-alist)
|
||||
(setq face-remapping-alist remapping)
|
||||
(set-window-buffer window (current-buffer))
|
||||
(insert str)
|
||||
(car (window-text-pixel-size)))))
|
||||
|
||||
;;
|
||||
;; Hooks
|
||||
(cl-defun +mu4e-normalised-icon (name &key set color height v-adjust)
|
||||
"Convert :icon declaration to icon"
|
||||
(let* ((icon-set (intern (concat "all-the-icons-" (or set "faicon"))))
|
||||
(v-adjust (or v-adjust 0.02))
|
||||
(height (or height 0.8))
|
||||
(icon (if color
|
||||
(apply icon-set `(,name :face ,(intern (concat "all-the-icons-" color)) :height ,height :v-adjust ,v-adjust))
|
||||
(apply icon-set `(,name :height ,height :v-adjust ,v-adjust))))
|
||||
(icon-width (+mu4e--get-string-width icon))
|
||||
(space-width (+mu4e--get-string-width " "))
|
||||
(space-factor (- 2 (/ (float icon-width) space-width))))
|
||||
(concat (propertize " " 'display `(space . (:width ,space-factor))) icon)))
|
||||
|
||||
;; Set up all the fancy icons
|
||||
;;;###autoload
|
||||
(defun +mu4e-initialise-icons ()
|
||||
(setq mu4e-use-fancy-chars t
|
||||
mu4e-headers-draft-mark (cons "D" (+mu4e-normalised-icon "pencil"))
|
||||
mu4e-headers-flagged-mark (cons "F" (+mu4e-normalised-icon "flag"))
|
||||
mu4e-headers-new-mark (cons "N" (+mu4e-normalised-icon "sync" :set "material" :height 0.8 :v-adjust -0.10))
|
||||
mu4e-headers-passed-mark (cons "P" (+mu4e-normalised-icon "arrow-right"))
|
||||
mu4e-headers-replied-mark (cons "R" (+mu4e-normalised-icon "arrow-right"))
|
||||
mu4e-headers-seen-mark (cons "S" "") ;(+mu4e-normalised-icon "eye" :height 0.6 :v-adjust 0.07 :color "dsilver"))
|
||||
mu4e-headers-trashed-mark (cons "T" (+mu4e-normalised-icon "trash"))
|
||||
mu4e-headers-attach-mark (cons "a" (+mu4e-normalised-icon "file-text-o" :color "silver"))
|
||||
mu4e-headers-encrypted-mark (cons "x" (+mu4e-normalised-icon "lock"))
|
||||
mu4e-headers-signed-mark (cons "s" (+mu4e-normalised-icon "certificate" :height 0.7 :color "dpurple"))
|
||||
mu4e-headers-unread-mark (cons "u" (+mu4e-normalised-icon "eye-slash" :v-adjust 0.05))))
|
||||
|
||||
(defun +mu4e-colorize-str (str &optional unique herring)
|
||||
"Apply a face from `+mu4e-header-colorized-faces' to STR.
|
||||
If HERRING is set, it will be used to determine the face instead of STR.
|
||||
Will try to make unique when non-nil UNIQUE,
|
||||
a quoted symbol for a alist of current strings and faces provided."
|
||||
(unless herring
|
||||
(setq herring str))
|
||||
(put-text-property
|
||||
0 (length str)
|
||||
'face
|
||||
(if (not unique)
|
||||
(+mu4e--str-color-face herring str)
|
||||
(let ((unique-alist (eval unique)))
|
||||
(unless (assoc herring unique-alist)
|
||||
(if (> (length unique-alist) (length +mu4e-header-colorized-faces))
|
||||
(push (cons herring (+mu4e--str-color-face herring)) unique-alist)
|
||||
(let ((offset 0) color color?)
|
||||
(while (not color)
|
||||
(setq color? (+mu4e--str-color-face herring offset))
|
||||
(if (not (rassoc color? unique-alist))
|
||||
(setq color color?)
|
||||
(setq offset (1+ offset))
|
||||
(when (> offset (length +mu4e-header-colorized-faces))
|
||||
(message "Warning: +mu4e-colorize-str was called with non-unique-alist UNIQUE-alist alist.")
|
||||
(setq color (+mu4e--str-color-face herring)))))
|
||||
(push (cons herring color) unique-alist)))
|
||||
(set unique unique-alist))
|
||||
(cdr (assoc herring unique-alist))))
|
||||
str)
|
||||
str)
|
||||
|
||||
(defun +mu4e--str-color-face (str &optional offset)
|
||||
"Select a face from `+mu4e-header-colorized-faces' based on
|
||||
STR and any integer OFFSET."
|
||||
(let* ((str-sum (apply #'+ (mapcar (lambda (c) (% c 3)) str)))
|
||||
(color (nth (% (+ str-sum (if offset offset 0))
|
||||
(length +mu4e-header-colorized-faces))
|
||||
+mu4e-header-colorized-faces)))
|
||||
color))
|
||||
|
||||
(defvar +org-capture-emails-file "todo.org"
|
||||
"Default target for storing mu4e emails captured from within mu4e.
|
||||
Requires a \"* Email\" heading be present in the file.")
|
||||
|
||||
;; Adding emails to the agenda
|
||||
;; Perfect for when you see an email you want to reply to
|
||||
;; later, but don't want to forget about
|
||||
;;;###autoload
|
||||
(defun +mu4e/capture-msg-to-agenda (arg)
|
||||
"Refile a message and add a entry in `+org-capture-emails-file' with a
|
||||
deadline. Default deadline is today. With one prefix, deadline
|
||||
is tomorrow. With two prefixes, select the deadline."
|
||||
(interactive "p")
|
||||
(let ((sec "^* Email")
|
||||
(msg (mu4e-message-at-point)))
|
||||
(when msg
|
||||
;; put the message in the agenda
|
||||
(with-current-buffer (find-file-noselect
|
||||
(expand-file-name +org-capture-emails-file org-directory))
|
||||
(save-excursion
|
||||
;; find header section
|
||||
(goto-char (point-min))
|
||||
(when (re-search-forward sec nil t)
|
||||
(let (org-M-RET-may-split-line
|
||||
(lev (org-outline-level))
|
||||
(folded-p (invisible-p (point-at-eol)))
|
||||
(from (plist-get msg :from)))
|
||||
;; place the subheader
|
||||
(when folded-p (show-branches)) ; unfold if necessary
|
||||
(org-end-of-meta-data) ; skip property drawer
|
||||
(org-insert-todo-heading 1) ; insert a todo heading
|
||||
(when (= (org-outline-level) lev) ; demote if necessary
|
||||
(org-do-demote))
|
||||
;; insert message and add deadline
|
||||
(insert (concat " Respond to "
|
||||
"[[mu4e:msgid:"
|
||||
(plist-get msg :message-id) "]["
|
||||
(truncate-string-to-width
|
||||
(or (caar from) (cadr from)) 25 nil nil t)
|
||||
" - "
|
||||
(truncate-string-to-width
|
||||
(plist-get msg :subject) 40 nil nil t)
|
||||
"]] "))
|
||||
(org-deadline nil
|
||||
(cond ((= arg 1) (format-time-string "%Y-%m-%d"))
|
||||
((= arg 4) "+1d")))
|
||||
|
||||
(org-update-parent-todo-statistics)
|
||||
|
||||
;; refold as necessary
|
||||
(if folded-p
|
||||
(progn
|
||||
(org-up-heading-safe)
|
||||
(hide-subtree))
|
||||
(hide-entry))))))
|
||||
;; refile the message and update
|
||||
;; (cond ((eq major-mode 'mu4e-view-mode)
|
||||
;; (mu4e-view-mark-for-refile))
|
||||
;; ((eq major-mode 'mu4e-headers-mode)
|
||||
;; (mu4e-headers-mark-for-refile)))
|
||||
(message "Refiled \"%s\" and added to the agenda for %s"
|
||||
(truncate-string-to-width
|
||||
(plist-get msg :subject) 40 nil nil t)
|
||||
(cond ((= arg 1) "today")
|
||||
((= arg 4) "tomorrow")
|
||||
(t "later"))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun +mu4e/attach-files (&optional files-to-attach)
|
||||
"When called in a dired buffer, ask for a message to attach the marked files to.
|
||||
When called in a mu4e:compose or org-msg buffer, `read-file-name'to either
|
||||
attach a file, or select a folder to open dired in and select file attachments
|
||||
(using `dired-mu4e-attach-ctrl-c-ctrl-c').
|
||||
|
||||
When otherwise called, open a dired buffer and enable `dired-mu4e-attach-ctrl-c-ctrl-c'."
|
||||
;; TODO add ability to attach files (+dirs) as a single (named) archive
|
||||
(interactive "p")
|
||||
(+mu4e-compose-org-msg-handle-toggle (/= 1 files-to-attach))
|
||||
(pcase major-mode
|
||||
((or 'mu4e-compose-mode 'org-msg-edit-mode)
|
||||
(let ((mail-buffer (current-buffer))
|
||||
(location (read-file-name "Attach: ")))
|
||||
(if (not (file-directory-p location))
|
||||
(pcase major-mode
|
||||
('mu4e-compose-mode
|
||||
(save-excursion
|
||||
(goto-char (point-max))
|
||||
(unless (eq (current-column) 0)
|
||||
(insert "\n\n")
|
||||
(forward-line 2))
|
||||
(mail-add-attachment location)))
|
||||
('org-msg-edit-mode (org-msg-attach-attach location)))
|
||||
(split-window-sensibly)
|
||||
(with-current-buffer (dired location)
|
||||
(setq-local dired-mail-buffer mail-buffer)
|
||||
(dired-mu4e-attach-ctrl-c-ctrl-c 1)))))
|
||||
('dired-mode
|
||||
(unless (and files-to-attach (/= 1 files-to-attach))
|
||||
(setq files-to-attach
|
||||
(delq nil
|
||||
(mapcar
|
||||
;; don't attach directories
|
||||
(lambda (f) (if (file-directory-p f) nil f))
|
||||
(nreverse (dired-map-over-marks (dired-get-filename) nil))))))
|
||||
(if (not files-to-attach)
|
||||
(progn
|
||||
(message "No files marked, aborting.")
|
||||
(kill-buffer-and-window))
|
||||
(if-let ((mail-target-buffer (bound-and-true-p dired-mail-buffer)))
|
||||
(progn (kill-buffer-and-window)
|
||||
(switch-to-buffer mail-target-buffer))
|
||||
(if (and (+mu4e-current-buffers)
|
||||
(y-or-n-p "Attach files to existing mail composition buffer? "))
|
||||
(progn (setf mail-target-buffer
|
||||
(completing-read "Message: " (+mu4e-current-buffers)))
|
||||
(kill-buffer-and-window)
|
||||
(switch-to-buffer mail-target-buffer))
|
||||
(kill-buffer-and-window)
|
||||
(mu4e-compose 'new)))
|
||||
(mapcar
|
||||
(pcase major-mode
|
||||
('mu4e-compose-mode #'mail-add-attachment)
|
||||
('org-msg-edit-mode #'org-msg-attach-attach))
|
||||
files-to-attach)))
|
||||
(_
|
||||
(split-window-sensibly)
|
||||
(with-current-buffer (call-interactively #'find-file)
|
||||
(dired-mu4e-attach-ctrl-c-ctrl-c 1)))))
|
||||
|
||||
(define-minor-mode dired-mu4e-attach-ctrl-c-ctrl-c
|
||||
"Adds C-c C-c as a keybinding to attach files to a message."
|
||||
:lighter "attach"
|
||||
:keymap (let ((map (make-sparse-keymap)))
|
||||
(define-key map (kbd "C-c C-c") '+mu4e/attach-files)
|
||||
map)
|
||||
(setq header-line-format
|
||||
(when dired-mu4e-attach-ctrl-c-ctrl-c
|
||||
(substitute-command-keys
|
||||
"Mu4e attach active. `\\[+mu4e/attach-files]' to attach the marked files."))))
|
||||
|
||||
(defun +mu4e-current-buffers ()
|
||||
"Return a list of active message buffers."
|
||||
(let (buffers)
|
||||
(save-current-buffer
|
||||
(dolist (buffer (buffer-list t))
|
||||
(set-buffer buffer)
|
||||
(when (or (and (derived-mode-p 'message-mode)
|
||||
(null message-sent-message-via))
|
||||
(eq major-mode 'org-msg-edit-mode))
|
||||
(push (buffer-name buffer) buffers))))
|
||||
(nreverse buffers)))
|
||||
|
||||
;;; Hooks
|
||||
|
||||
(defun +mu4e-init-h ()
|
||||
(add-hook 'kill-buffer-hook #'+mu4e-kill-mu4e-h nil t))
|
||||
|
@ -87,3 +327,22 @@ default/fallback account."
|
|||
(+mu4e--old-wconf
|
||||
(set-window-configuration +mu4e--old-wconf)
|
||||
(setq +mu4e--old-wconf nil))))
|
||||
|
||||
;;;###autoload
|
||||
(defun +mu4e-set-from-address-h ()
|
||||
"If the user defines multiple `+mu4e-personal-addresses' for email aliases
|
||||
within a context, set `user-mail-address' to an alias found in the 'To' or
|
||||
'From' headers of the parent message if present, or prompt the user for a
|
||||
preferred alias"
|
||||
(when-let ((addresses (if (or mu4e-contexts +mu4e-personal-addresses)
|
||||
(and (> (length +mu4e-personal-addresses) 1)
|
||||
+mu4e-personal-addresses)
|
||||
(mu4e-personal-addresses))))
|
||||
(setq user-mail-address
|
||||
(if mu4e-compose-parent-message
|
||||
(let ((to (mapcar #'cdr (mu4e-message-field mu4e-compose-parent-message :to)))
|
||||
(from (mapcar #'cdr (mu4e-message-field mu4e-compose-parent-message :from))))
|
||||
(or (car (seq-intersection to addresses))
|
||||
(car (seq-intersection from addresses))
|
||||
(completing-read "From: " addresses)))
|
||||
(completing-read "From: " addresses)))))
|
||||
|
|
91
modules/email/mu4e/autoload/mu-lock.el
Normal file
91
modules/email/mu4e/autoload/mu-lock.el
Normal file
|
@ -0,0 +1,91 @@
|
|||
;;; email/mu4e/autoload/mu-lock.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defvar +mu4e-lock-file (expand-file-name "mu4e_lock" temporary-file-directory)
|
||||
"Location of the lock file which stores the PID of the process currenty running mu4e")
|
||||
(defvar +mu4e-lock-request-file (expand-file-name "mu4e_lock_request" temporary-file-directory)
|
||||
"Location of the lock file for which creating indicated that another process wants the lock to be released")
|
||||
|
||||
(defvar +mu4e-lock-greedy nil
|
||||
"Whether to 'grab' the `+mu4e-lock-file' if nobody else has it, i.e. start Mu4e")
|
||||
(defvar +mu4e-lock-relaxed t
|
||||
"Whether if someone else wants the lock (signaled via `+mu4e-lock-request-file'), we should stop Mu4e and let go of it")
|
||||
|
||||
(defun +mu4e-lock-pid-info ()
|
||||
"Get info on the PID refered to in `+mu4e-lock-file' in the form (pid . process-attributes)
|
||||
If the file or process do not exist, the lock file is deleted an nil returned."
|
||||
(when (file-exists-p +mu4e-lock-file)
|
||||
(let* ((pid (string-to-number
|
||||
(with-temp-buffer
|
||||
(setq coding-system-for-read 'utf-8)
|
||||
(insert-file-contents +mu4e-lock-file)
|
||||
(buffer-string))))
|
||||
(process (process-attributes pid)))
|
||||
(if (and process (string-match-p "emacs" (alist-get 'args process)))
|
||||
(cons pid process)
|
||||
(delete-file +mu4e-lock-file) nil))))
|
||||
|
||||
(defun +mu4e-lock-available (&optional strict)
|
||||
"If the `+mu4e-lock-file' is available (unset or owned by this emacs) return t.
|
||||
If STRICT only accept an unset lock file."
|
||||
(not (when-let* ((lock-info (+mu4e-lock-pid-info))
|
||||
(pid (car lock-info)))
|
||||
(when (or strict (/= (emacs-pid) pid)) t))))
|
||||
|
||||
;;;###autoload
|
||||
(defun +mu4e-lock-file-delete-maybe ()
|
||||
"Check `+mu4e-lock-file', and delete it if this process is responsible for it."
|
||||
(when (+mu4e-lock-available)
|
||||
(delete-file +mu4e-lock-file)
|
||||
(file-notify-rm-watch +mu4e-lock--request-watcher)))
|
||||
|
||||
;;;###autoload
|
||||
(defun +mu4e-lock-start (orig-fun &optional callback)
|
||||
"Check `+mu4e-lock-file', and if another process is responsible for it, abort starting.
|
||||
Else, write to this process' PID to the lock file"
|
||||
(unless (+mu4e-lock-available)
|
||||
(call-process "touch" nil nil nil +mu4e-lock-request-file)
|
||||
(message "Lock file exists, requesting that it be given up")
|
||||
(sleep-for 0.1)
|
||||
(delete-file +mu4e-lock-request-file))
|
||||
(if (not (+mu4e-lock-available))
|
||||
(user-error "Unfortunately another Emacs is already doing stuff with Mu4e, and you can only have one at a time")
|
||||
(write-region (number-to-string (emacs-pid)) nil +mu4e-lock-file)
|
||||
(delete-file +mu4e-lock-request-file)
|
||||
(call-process "touch" nil nil nil +mu4e-lock-request-file)
|
||||
(funcall orig-fun callback)
|
||||
(setq +mu4e-lock--request-watcher
|
||||
(file-notify-add-watch +mu4e-lock-request-file
|
||||
'(change)
|
||||
#'+mu4e-lock-request))))
|
||||
|
||||
(defvar +mu4e-lock--file-watcher nil)
|
||||
(defvar +mu4e-lock--file-just-deleted nil)
|
||||
(defvar +mu4e-lock--request-watcher nil)
|
||||
|
||||
(defun +mu4e-lock-add-watcher ()
|
||||
(setq +mu4e-lock--file-just-deleted nil)
|
||||
(file-notify-rm-watch +mu4e-lock--file-watcher)
|
||||
(setq +mu4e-lock--file-watcher
|
||||
(file-notify-add-watch +mu4e-lock-file
|
||||
'(change)
|
||||
#'+mu4e-lock-file-updated)))
|
||||
|
||||
(defun +mu4e-lock-request (event)
|
||||
"Handle another process requesting the Mu4e lock."
|
||||
(when (equal (nth 1 event) 'created)
|
||||
(when +mu4e-lock-relaxed
|
||||
(mu4e~stop)
|
||||
(file-notify-rm-watch +mu4e-lock--file-watcher)
|
||||
(message "Someone else wants to use Mu4e, releasing lock")
|
||||
(delete-file +mu4e-lock-file)
|
||||
(run-at-time 0.2 nil #'+mu4e-lock-add-watcher))
|
||||
(delete-file +mu4e-lock-request-file)))
|
||||
|
||||
(defun +mu4e-lock-file-updated (event)
|
||||
(if +mu4e-lock--file-just-deleted
|
||||
(+mu4e-lock-add-watcher)
|
||||
(when (equal (nth 1 event) 'deleted)
|
||||
(setq +mu4e-lock--file-just-deleted t)
|
||||
(when (and +mu4e-lock-greedy (+mu4e-lock-available t))
|
||||
(message "Noticed Mu4e lock was available, grabbed it")
|
||||
(run-at-time 0.2 nil #'mu4e~start)))))
|
|
@ -3,6 +3,9 @@
|
|||
(defvar +mu4e-backend 'mbsync
|
||||
"Which backend to use. Can either be offlineimap, mbsync or nil (manual).")
|
||||
|
||||
(defvar +mu4e-personal-addresses 'nil
|
||||
"Alternative to mu4e-personal-addresses that can be set for each account (mu4e context).")
|
||||
|
||||
|
||||
;;
|
||||
;;; Packages
|
||||
|
@ -27,13 +30,13 @@
|
|||
(setq mu4e-get-mail-command "offlineimap -o -q")))
|
||||
|
||||
(setq mu4e-update-interval nil
|
||||
mu4e-compose-format-flowed t ; visual-line-mode + auto-fill upon sending
|
||||
mu4e-view-show-addresses t
|
||||
mu4e-sent-messages-behavior 'sent
|
||||
mu4e-hide-index-messages t
|
||||
;; try to show images
|
||||
mu4e-view-show-images t
|
||||
mu4e-view-image-max-width 800
|
||||
mu4e-view-use-gnus t ; the way of the future: https://github.com/djcb/mu/pull/1442#issuecomment-591695814
|
||||
;; configuration for sending mail
|
||||
message-send-mail-function #'smtpmail-send-it
|
||||
smtpmail-stream-type 'starttls
|
||||
|
@ -48,44 +51,106 @@
|
|||
((featurep! :completion helm) #'completing-read)
|
||||
((featurep! :completion vertico) #'completing-read)
|
||||
(t #'ido-completing-read))
|
||||
mu4e-attachment-dir
|
||||
(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")
|
||||
"~"))
|
||||
;; no need to ask
|
||||
mu4e-confirm-quit nil
|
||||
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 . 12)
|
||||
(:human-date . 12)
|
||||
(:flags . 4)
|
||||
(:from . 25)
|
||||
'((:account-stripe . 1)
|
||||
(:human-date . 8)
|
||||
(:flags . 6) ; 3 icon flags
|
||||
(:from-or-to . 25)
|
||||
(:subject)))
|
||||
|
||||
;; set mail user agent
|
||||
(setq mail-user-agent 'mu4e-user-agent)
|
||||
(setq mail-user-agent 'mu4e-user-agent
|
||||
message-mail-user-agent 'mu4e-user-agent)
|
||||
|
||||
;; Use fancy icons
|
||||
(setq mu4e-use-fancy-chars t
|
||||
mu4e-headers-draft-mark '("D" . "")
|
||||
mu4e-headers-flagged-mark '("F" . "")
|
||||
mu4e-headers-new-mark '("N" . "")
|
||||
mu4e-headers-passed-mark '("P" . "")
|
||||
mu4e-headers-replied-mark '("R" . "")
|
||||
mu4e-headers-seen-mark '("S" . "")
|
||||
mu4e-headers-trashed-mark '("T" . "")
|
||||
mu4e-headers-attach-mark '("a" . "")
|
||||
mu4e-headers-encrypted-mark '("x" . "")
|
||||
mu4e-headers-signed-mark '("s" . "")
|
||||
mu4e-headers-unread-mark '("u" . ""))
|
||||
;; Set the icons only when a graphical frame has been created
|
||||
(if (display-graphic-p)
|
||||
(+mu4e-initialise-icons)
|
||||
;; When it's the server, wait till the first graphical frame
|
||||
(add-hook!
|
||||
'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)))))
|
||||
|
||||
;; Add a column to display what email account the email belongs to.
|
||||
(add-to-list 'mu4e-header-info-custom
|
||||
'(:account
|
||||
:name "Account"
|
||||
:shortname "Account"
|
||||
:help "Which account this email belongs to"
|
||||
:function
|
||||
(lambda (msg)
|
||||
(let ((maildir (mu4e-message-field msg :maildir)))
|
||||
(format "%s" (substring maildir 1 (string-match-p "/" maildir 1)))))))
|
||||
(plist-put (cdr (assoc :flags mu4e-header-info)) :shortname " Flags") ; default=Flgs
|
||||
(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"
|
||||
(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))))
|
||||
'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))
|
||||
|
||||
|
@ -94,25 +159,266 @@
|
|||
|
||||
;; 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)
|
||||
|
||||
(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)
|
||||
|
||||
(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" #'mail-add-attachment))
|
||||
: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
|
||||
(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")))
|
||||
|
||||
(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))
|
||||
|
||||
(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
|
||||
:hook (mu4e-compose-pre . org-msg-mode)
|
||||
:after mu4e
|
||||
:when (featurep! +org)
|
||||
:config
|
||||
(setq org-msg-startup "inlineimages"
|
||||
(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 '(html text)))
|
||||
org-msg-default-alternatives '((new . (utf-8 html))
|
||||
(reply-to-text . (utf-8))
|
||||
(reply-to-html . (utf-8 html)))
|
||||
org-msg-convert-citation t)
|
||||
|
||||
(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 (orig-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 orig-fn))
|
||||
|
||||
(defadvice! +mu4e-draft-open-signature-a (orig-fn compose-type &optional msg)
|
||||
"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)))
|
||||
(funcall orig-fn compose-type msg)))
|
||||
|
||||
(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 . ,(doom-color 'fg))
|
||||
(background-color . ,(doom-color 'bg))
|
||||
(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")))))))
|
||||
|
||||
|
||||
;;
|
||||
|
@ -120,36 +426,71 @@
|
|||
|
||||
(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 'delete
|
||||
(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)))
|
||||
|
||||
;; don't need to run cleanup after indexing for gmail
|
||||
mu4e-index-cleanup nil
|
||||
|
||||
;; because gmail uses labels as folders we can use lazy check since
|
||||
;; messages don't really "move"
|
||||
mu4e-index-lazy-check t)
|
||||
(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'.
|
||||
;; 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 'trash mu4e-marks)
|
||||
(setf (alist-get 'delete mu4e-marks)
|
||||
(list
|
||||
:char '("D" . "✘")
|
||||
:prompt "Delete"
|
||||
: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 #'+mu4e--mark-seen)
|
||||
: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 #'+mu4e--mark-seen))
|
||||
: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
|
||||
|
@ -157,8 +498,70 @@
|
|||
;; are ineffectual otherwise.
|
||||
(add-hook! 'mu4e-mark-execute-pre-hook
|
||||
(defun +mu4e-gmail-fix-flags-h (mark msg)
|
||||
(pcase mark
|
||||
(`trash (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")))))))
|
||||
(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"))))))))
|
||||
|
||||
;;
|
||||
;;; 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.")
|
||||
|
||||
(setq mu4e-alert-email-notification-types '(subjects))
|
||||
(defun +mu4e-alert-grouped-mail-notification-formatter-with-bell (mail-group _all-mails)
|
||||
"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)))
|
||||
(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)))
|
||||
|
|
15
modules/email/mu4e/doctor.el
Normal file
15
modules/email/mu4e/doctor.el
Normal file
|
@ -0,0 +1,15 @@
|
|||
;;; email/mu4e/doctor.el -*- lexical-binding: t; -*-
|
||||
|
||||
(unless (executable-find "mu")
|
||||
(warn! "Couldn't find mu command. Mu4e requires this to work."))
|
||||
|
||||
(unless (or (executable-find "mbsync")
|
||||
(executable-find "offlineimap"))
|
||||
(warn! "Couldn't find mbsync or offlineimap command. \
|
||||
You may not have a way of fetching mail."))
|
||||
|
||||
(when (and (featurep! +org)
|
||||
(not IS-WINDOWS))
|
||||
(unless (executable-find "identify")
|
||||
(warn! "Couldn't find the identify command from imagemagick. \
|
||||
LaTeX fragment re-scaling with org-msg will not work.")))
|
|
@ -1,4 +1,7 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; email/mu4e/packages.el
|
||||
|
||||
(package! org-msg :pin "b0765b2d0bc06cdd1fd78836ef958eb81e603dd1")
|
||||
(when (featurep! +org)
|
||||
(package! org-msg :pin "4c92c627b6cfb234fd257b714a5dbfc72d7af8d2"))
|
||||
|
||||
(package! mu4e-alert :pin "91f0657c5b245a9de57aa38391221fb5d141d9bd")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue