Files
noise/noise-chat-list.el
T

143 lines
5.1 KiB
EmacsLisp

;;; noise-chat-list.el --- Chat list buffer for Noise -*- lexical-binding: t; -*-
;;; Commentary:
;; Provides the *signal* buffer — an ibuffer-style list of all conversations
;; with unread counts, last message previews, and timestamps.
;;; Code:
(require 'noise-db)
(require 'cl-lib)
(defvar noise-chat-list-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "n") #'next-line)
(define-key map (kbd "p") #'previous-line)
(define-key map (kbd "RET") #'noise-chat-list-open)
(define-key map (kbd "m") #'noise-chat-list-mark-read)
(define-key map (kbd "g") #'noise-chat-list-refresh)
(define-key map (kbd "/") #'noise-chat-list-filter)
(define-key map (kbd "q") #'quit-window)
map)
"Keymap for `noise-chat-list-mode'.")
(define-derived-mode noise-chat-list-mode special-mode "Signal Chats"
"Major mode for the Noise Signal chat list.
\\{noise-chat-list-mode-map}"
:group 'noise
(setq buffer-read-only t)
(setq-local header-line-format
(concat " U "
(format "%-20s" "Name")
(format "%-40s" "Last message")
"When")))
;;;###autoload
(defun noise-chat-list ()
"Display the Noise Signal chat list."
(interactive)
(noise-db-init)
(switch-to-buffer "*signal*")
(noise-chat-list-mode)
(noise-chat-list--render))
(defun noise-chat-list--render ()
"Render the conversation list in the *signal* buffer."
(let ((inhibit-read-only t)
(conversations (noise-db-get-all-conversations)))
(erase-buffer)
(if (null conversations)
(insert "No conversations. Sync from signal-cli with `g`.\n")
(dolist (conv conversations)
(insert (noise-chat-list--format-row conv) "\n")))
(noise-chat-list--update-modeline conversations)
(goto-char (point-min))
(set-buffer-modified-p nil)))
(defun noise-chat-list--update-modeline (conversations)
"Update the mode line with chat statistics."
(let* ((total (length conversations))
(unread-total (cl-reduce #'+
(mapcar (lambda (c) (or (gethash "unread_count" c) 0))
conversations)
:initial-value 0)))
(setq mode-line-format
(format " ⌁ %s %d chats %s %s (%s) ─ U:%%- ─ Top ───"
(propertize "*signal*" 'face 'bold)
total
(if (> unread-total 0)
(format "· %d unread" unread-total)
"")
""
"Signal Chats"))))
(defun noise-chat-list--format-time (timestamp)
"Format TIMESTAMP (unix seconds) as a human-readable relative time string.
Today: HH:MM, this week: Ddd, older: Mon DD."
(let* ((now (current-time))
(msg-time (seconds-to-time timestamp))
(diff (float-time (time-subtract now msg-time)))
(one-day 86400)
(one-week (* 7 one-day)))
(cond
((< diff one-day)
(format-time-string "%H:%M" msg-time))
((< diff one-week)
(format-time-string "%a" msg-time))
(t
(format-time-string "%b %e" msg-time)))))
(defun noise-chat-list--format-row (conv)
"Format a single conversation row for display.
CONV is a hash table with string keys from noise-db."
(let* ((unread (or (gethash "unread_count" conv) 0))
(name (or (gethash "name" conv) "Unknown"))
(preview (or (gethash "last_message_preview" conv) ""))
(sender (or (gethash "last_message_sender" conv) ""))
(timestamp (or (gethash "last_message_time" conv) 0))
(fringe (if (> unread 0) "●" " "))
(unread-str (if (> unread 0) (format "%3d" unread) " "))
(name-str (truncate-string-to-width name 18 nil nil "…"))
(preview-prefix
(cond
((string= sender "") preview)
((string= sender "me") (concat "me: " preview))
(t (concat sender ": " preview))))
(preview-str (truncate-string-to-width preview-prefix 36 nil nil "…"))
(time-str (noise-chat-list--format-time timestamp)))
(concat " " fringe " " unread-str " "
(propertize (format "%-18s" name-str) 'face 'bold) " "
preview-str " "
(propertize time-str 'face 'shadow))))
(defun noise-chat-list-open ()
"Open the conversation at point."
(interactive)
(let* ((conversations (noise-db-get-all-conversations))
(idx (1- (line-number-at-pos)))
(conv (nth idx conversations)))
(if conv
(message "Open conversation: %s (not yet implemented)"
(gethash "name" conv))
(message "No conversation on this line."))))
(defun noise-chat-list-mark-read ()
"Mark the conversation at point as read."
(interactive)
(message "Mark read: not yet connected to signal-cli"))
(defun noise-chat-list-refresh ()
"Refresh the chat list (re-render from database)."
(interactive)
(noise-chat-list--render)
(message "Chat list refreshed."))
(defun noise-chat-list-filter ()
"Filter the chat list with a search query."
(interactive)
(message "Filter: not yet implemented"))
(provide 'noise-chat-list)
;;; noise-chat-list.el ends here