114 lines
4.8 KiB
EmacsLisp
114 lines
4.8 KiB
EmacsLisp
;;; noise-switcher.el --- Minibuffer conversation switcher for Noise -*- lexical-binding: t; -*-
|
|
|
|
;;; Commentary:
|
|
|
|
;; Provides minibuffer completion for switching conversations.
|
|
;; Collects conversations and contacts from the database, presents them
|
|
;; with fuzzy matching in a vertico-style minibuffer completion.
|
|
|
|
;;; Code:
|
|
|
|
(require 'noise-db)
|
|
(require 'noise-new-chat)
|
|
|
|
(defun noise-switcher ()
|
|
"Switch to a Signal conversation via minibuffer completion.
|
|
Shows existing conversations and contacts without conversations."
|
|
(interactive)
|
|
(noise-db-init)
|
|
(let* ((collection (noise-switcher--collection))
|
|
(choice (completing-read
|
|
"Switch to chat: "
|
|
(lambda (string pred action)
|
|
(if (eq action 'metadata)
|
|
'(metadata
|
|
(display-sort-function . identity)
|
|
(annotation-function . noise-switcher--annotate))
|
|
(complete-with-action action collection string pred)))
|
|
nil t)))
|
|
(when choice
|
|
(let ((item (cdr (assoc choice collection))))
|
|
(if item
|
|
(if (gethash "id" item)
|
|
;; It's a conversation (has an id)
|
|
(message "Open conversation: %s (not yet implemented)" (gethash "name" item))
|
|
;; It's a contact without a conversation — start one
|
|
(noise-new-chat-with-contact (gethash "contact_id" item)
|
|
(gethash "name" item)))
|
|
(message "Unknown selection: %s" choice))))))
|
|
|
|
(defun noise-switcher--collection ()
|
|
"Build the completion collection.
|
|
Returns an alist of (display-string . hash-table)."
|
|
(let ((conversations (noise-db-get-all-conversations))
|
|
(contacts (noise-db-get-all-contacts))
|
|
(collection nil))
|
|
;; Add conversations
|
|
(dolist (conv conversations)
|
|
(let ((name (gethash "name" conv)))
|
|
(push (cons name conv) collection)))
|
|
;; Add contacts without conversations
|
|
(let ((conv-contacts (make-hash-table :test 'equal)))
|
|
(dolist (conv conversations)
|
|
(let ((members (noise-db-get-conversation-members (gethash "id" conv))))
|
|
(dolist (m members)
|
|
(puthash (gethash "contact_id" m) t conv-contacts))))
|
|
(dolist (contact contacts)
|
|
(let ((contact-id (gethash "id" contact))
|
|
(name (gethash "name" contact)))
|
|
(unless (gethash contact-id conv-contacts)
|
|
;; Contact without conversation - add a stub
|
|
(let ((ht (make-hash-table :test 'equal)))
|
|
(puthash "contact_id" contact-id ht)
|
|
(puthash "name" name ht)
|
|
(puthash "type" "direct" ht)
|
|
(puthash "unread_count" 0 ht)
|
|
(puthash "last_message_time" 0 ht)
|
|
(push (cons name ht) collection))))))
|
|
collection))
|
|
|
|
(defun noise-switcher--annotate (candidate)
|
|
"Annotate CANDIDATE with type, unread, and time info for completing-read."
|
|
(let* ((collection (noise-switcher--collection))
|
|
(item (cdr (assoc candidate collection))))
|
|
(when item
|
|
(let* ((type (gethash "type" item "direct"))
|
|
(unread (gethash "unread_count" item 0))
|
|
(time (gethash "last_message_time" item 0))
|
|
(time-str (noise-switcher--format-time-annot time))
|
|
(annotation (format "%s" type)))
|
|
(when (> unread 0)
|
|
(setq annotation (concat annotation (format " · %d unread" unread))))
|
|
(when (> time 0)
|
|
(setq annotation (concat annotation (format " · %s" time-str))))
|
|
(format " %s" annotation)))))
|
|
|
|
(defun noise-switcher--format-time-annot (timestamp)
|
|
"Format TIMESTAMP for annotation display."
|
|
(if (<= timestamp 0)
|
|
""
|
|
(let* ((msg-time (seconds-to-time timestamp))
|
|
(diff-days (/ (float-time (time-subtract (current-time) msg-time)) 86400)))
|
|
(cond
|
|
((< diff-days 1) (format-time-string "%H:%M" msg-time))
|
|
((< diff-days 7) (format-time-string "%a" msg-time))
|
|
(t (format-time-string "%b %e" msg-time))))))
|
|
|
|
(defun noise-switcher--format-candidate (name info)
|
|
"Format a switcher candidate for display."
|
|
(let* ((type (or (cdr (assoc "type" info)) "direct"))
|
|
(unread (or (cdr (assoc "unread_count" info)) 0))
|
|
(time (or (cdr (assoc "last_message_time" info)) 0))
|
|
(time-str (if (> time 0) (noise-switcher--format-time-annot time) ""))
|
|
(annotation (concat type (if (> unread 0) (format " · %d unread" unread) "")
|
|
(if (> time 0) (format " · %s" time-str) ""))))
|
|
(format "%-25s %s" name annotation)))
|
|
|
|
(defun noise-switcher--filter (query candidates)
|
|
"Filter CANDIDATES by QUERY using substring matching."
|
|
(let ((q (downcase query)))
|
|
(seq-filter (lambda (c) (string-match-p (regexp-quote q) (downcase c))) candidates)))
|
|
|
|
(provide 'noise-switcher)
|
|
;;; noise-switcher.el ends here
|